You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Calendar.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Gary Kim <gary@garykim.dev>
  7. * @author Georg Ehrke <oc.list@georgehrke.com>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Lukas Reschke <lukas@statuscode.ch>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. * @author Thomas Citharel <nextcloud@tcit.fr>
  12. * @author Thomas Müller <thomas.mueller@tmit.eu>
  13. *
  14. * @license AGPL-3.0
  15. *
  16. * This code is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU Affero General Public License, version 3,
  18. * as published by the Free Software Foundation.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU Affero General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU Affero General Public License, version 3,
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>
  27. *
  28. */
  29. namespace OCA\DAV\CalDAV;
  30. use OCA\DAV\DAV\Sharing\IShareable;
  31. use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
  32. use OCP\IConfig;
  33. use OCP\IL10N;
  34. use Sabre\CalDAV\Backend\BackendInterface;
  35. use Sabre\DAV\Exception\Forbidden;
  36. use Sabre\DAV\Exception\NotFound;
  37. use Sabre\DAV\PropPatch;
  38. /**
  39. * Class Calendar
  40. *
  41. * @package OCA\DAV\CalDAV
  42. * @property BackendInterface|CalDavBackend $caldavBackend
  43. */
  44. class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
  45. /** @var IConfig */
  46. private $config;
  47. /** @var IL10N */
  48. protected $l10n;
  49. /**
  50. * Calendar constructor.
  51. *
  52. * @param BackendInterface $caldavBackend
  53. * @param $calendarInfo
  54. * @param IL10N $l10n
  55. * @param IConfig $config
  56. */
  57. public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n, IConfig $config) {
  58. parent::__construct($caldavBackend, $calendarInfo);
  59. if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) {
  60. $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Contact birthdays');
  61. }
  62. if ($this->getName() === CalDavBackend::PERSONAL_CALENDAR_URI &&
  63. $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) {
  64. $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Personal');
  65. }
  66. $this->config = $config;
  67. $this->l10n = $l10n;
  68. }
  69. /**
  70. * Updates the list of shares.
  71. *
  72. * The first array is a list of people that are to be added to the
  73. * resource.
  74. *
  75. * Every element in the add array has the following properties:
  76. * * href - A url. Usually a mailto: address
  77. * * commonName - Usually a first and last name, or false
  78. * * summary - A description of the share, can also be false
  79. * * readOnly - A boolean value
  80. *
  81. * Every element in the remove array is just the address string.
  82. *
  83. * @param array $add
  84. * @param array $remove
  85. * @return void
  86. * @throws Forbidden
  87. */
  88. public function updateShares(array $add, array $remove) {
  89. if ($this->isShared()) {
  90. throw new Forbidden();
  91. }
  92. $this->caldavBackend->updateShares($this, $add, $remove);
  93. }
  94. /**
  95. * Returns the list of people whom this resource is shared with.
  96. *
  97. * Every element in this array should have the following properties:
  98. * * href - Often a mailto: address
  99. * * commonName - Optional, for example a first + last name
  100. * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
  101. * * readOnly - boolean
  102. * * summary - Optional, a description for the share
  103. *
  104. * @return array
  105. */
  106. public function getShares() {
  107. if ($this->isShared()) {
  108. return [];
  109. }
  110. return $this->caldavBackend->getShares($this->getResourceId());
  111. }
  112. /**
  113. * @return int
  114. */
  115. public function getResourceId() {
  116. return $this->calendarInfo['id'];
  117. }
  118. /**
  119. * @return string
  120. */
  121. public function getPrincipalURI() {
  122. return $this->calendarInfo['principaluri'];
  123. }
  124. /**
  125. * @return array
  126. */
  127. public function getACL() {
  128. $acl = [
  129. [
  130. 'privilege' => '{DAV:}read',
  131. 'principal' => $this->getOwner(),
  132. 'protected' => true,
  133. ],
  134. [
  135. 'privilege' => '{DAV:}read',
  136. 'principal' => $this->getOwner() . '/calendar-proxy-write',
  137. 'protected' => true,
  138. ],
  139. [
  140. 'privilege' => '{DAV:}read',
  141. 'principal' => $this->getOwner() . '/calendar-proxy-read',
  142. 'protected' => true,
  143. ],
  144. ];
  145. if ($this->getName() !== BirthdayService::BIRTHDAY_CALENDAR_URI) {
  146. $acl[] = [
  147. 'privilege' => '{DAV:}write',
  148. 'principal' => $this->getOwner(),
  149. 'protected' => true,
  150. ];
  151. $acl[] = [
  152. 'privilege' => '{DAV:}write',
  153. 'principal' => $this->getOwner() . '/calendar-proxy-write',
  154. 'protected' => true,
  155. ];
  156. } else {
  157. $acl[] = [
  158. 'privilege' => '{DAV:}write-properties',
  159. 'principal' => $this->getOwner(),
  160. 'protected' => true,
  161. ];
  162. $acl[] = [
  163. 'privilege' => '{DAV:}write-properties',
  164. 'principal' => $this->getOwner() . '/calendar-proxy-write',
  165. 'protected' => true,
  166. ];
  167. }
  168. $acl[] = [
  169. 'privilege' => '{DAV:}write-properties',
  170. 'principal' => $this->getOwner() . '/calendar-proxy-read',
  171. 'protected' => true,
  172. ];
  173. if (!$this->isShared()) {
  174. return $acl;
  175. }
  176. if ($this->getOwner() !== parent::getOwner()) {
  177. $acl[] = [
  178. 'privilege' => '{DAV:}read',
  179. 'principal' => parent::getOwner(),
  180. 'protected' => true,
  181. ];
  182. if ($this->canWrite()) {
  183. $acl[] = [
  184. 'privilege' => '{DAV:}write',
  185. 'principal' => parent::getOwner(),
  186. 'protected' => true,
  187. ];
  188. } else {
  189. $acl[] = [
  190. 'privilege' => '{DAV:}write-properties',
  191. 'principal' => parent::getOwner(),
  192. 'protected' => true,
  193. ];
  194. }
  195. }
  196. if ($this->isPublic()) {
  197. $acl[] = [
  198. 'privilege' => '{DAV:}read',
  199. 'principal' => 'principals/system/public',
  200. 'protected' => true,
  201. ];
  202. }
  203. $acl = $this->caldavBackend->applyShareAcl($this->getResourceId(), $acl);
  204. $allowedPrincipals = [
  205. $this->getOwner(),
  206. $this->getOwner(). '/calendar-proxy-read',
  207. $this->getOwner(). '/calendar-proxy-write',
  208. parent::getOwner(),
  209. 'principals/system/public'
  210. ];
  211. return array_filter($acl, function ($rule) use ($allowedPrincipals) {
  212. return \in_array($rule['principal'], $allowedPrincipals, true);
  213. });
  214. }
  215. public function getChildACL() {
  216. return $this->getACL();
  217. }
  218. public function getOwner() {
  219. if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
  220. return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'];
  221. }
  222. return parent::getOwner();
  223. }
  224. public function delete() {
  225. if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']) &&
  226. $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']) {
  227. $principal = 'principal:' . parent::getOwner();
  228. $shares = $this->caldavBackend->getShares($this->getResourceId());
  229. $shares = array_filter($shares, function ($share) use ($principal) {
  230. return $share['href'] === $principal;
  231. });
  232. if (empty($shares)) {
  233. throw new Forbidden();
  234. }
  235. $this->caldavBackend->updateShares($this, [], [
  236. $principal
  237. ]);
  238. return;
  239. }
  240. // Remember when a user deleted their birthday calendar
  241. // in order to not regenerate it on the next contacts change
  242. if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) {
  243. $principalURI = $this->getPrincipalURI();
  244. $userId = substr($principalURI, 17);
  245. $this->config->setUserValue($userId, 'dav', 'generateBirthdayCalendar', 'no');
  246. }
  247. parent::delete();
  248. }
  249. public function propPatch(PropPatch $propPatch) {
  250. // parent::propPatch will only update calendars table
  251. // if calendar is shared, changes have to be made to the properties table
  252. if (!$this->isShared()) {
  253. parent::propPatch($propPatch);
  254. }
  255. }
  256. public function getChild($name) {
  257. $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
  258. if (!$obj) {
  259. throw new NotFound('Calendar object not found');
  260. }
  261. if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) {
  262. throw new NotFound('Calendar object not found');
  263. }
  264. $obj['acl'] = $this->getChildACL();
  265. return new CalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj);
  266. }
  267. public function getChildren() {
  268. $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
  269. $children = [];
  270. foreach ($objs as $obj) {
  271. if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) {
  272. continue;
  273. }
  274. $obj['acl'] = $this->getChildACL();
  275. $children[] = new CalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj);
  276. }
  277. return $children;
  278. }
  279. public function getMultipleChildren(array $paths) {
  280. $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
  281. $children = [];
  282. foreach ($objs as $obj) {
  283. if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) {
  284. continue;
  285. }
  286. $obj['acl'] = $this->getChildACL();
  287. $children[] = new CalendarObject($this->caldavBackend, $this->l10n, $this->calendarInfo, $obj);
  288. }
  289. return $children;
  290. }
  291. public function childExists($name) {
  292. $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
  293. if (!$obj) {
  294. return false;
  295. }
  296. if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) {
  297. return false;
  298. }
  299. return true;
  300. }
  301. public function calendarQuery(array $filters) {
  302. $uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
  303. if ($this->isShared()) {
  304. return array_filter($uris, function ($uri) {
  305. return $this->childExists($uri);
  306. });
  307. }
  308. return $uris;
  309. }
  310. /**
  311. * @param boolean $value
  312. * @return string|null
  313. */
  314. public function setPublishStatus($value) {
  315. $publicUri = $this->caldavBackend->setPublishStatus($value, $this);
  316. $this->calendarInfo['publicuri'] = $publicUri;
  317. return $publicUri;
  318. }
  319. /**
  320. * @return mixed $value
  321. */
  322. public function getPublishStatus() {
  323. return $this->caldavBackend->getPublishStatus($this);
  324. }
  325. public function canWrite() {
  326. if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) {
  327. return false;
  328. }
  329. if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) {
  330. return !$this->calendarInfo['{http://owncloud.org/ns}read-only'];
  331. }
  332. return true;
  333. }
  334. private function isPublic() {
  335. return isset($this->calendarInfo['{http://owncloud.org/ns}public']);
  336. }
  337. protected function isShared() {
  338. if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
  339. return false;
  340. }
  341. return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri'];
  342. }
  343. public function isSubscription() {
  344. return isset($this->calendarInfo['{http://calendarserver.org/ns/}source']);
  345. }
  346. /**
  347. * @inheritDoc
  348. */
  349. public function getChanges($syncToken, $syncLevel, $limit = null) {
  350. if (!$syncToken && $limit) {
  351. throw new UnsupportedLimitOnInitialSyncException();
  352. }
  353. return parent::getChanges($syncToken, $syncLevel, $limit);
  354. }
  355. }