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.

CalDavBackend.php 96KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. * @copyright Copyright (c) 2018 Georg Ehrke
  5. *
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author dartcafe <github@dartcafe.de>
  8. * @author Georg Ehrke <oc.list@georgehrke.com>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Lukas Reschke <lukas@statuscode.ch>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author nhirokinet <nhirokinet@nhiroki.net>
  13. * @author Robin Appelman <robin@icewind.nl>
  14. * @author Roeland Jago Douma <roeland@famdouma.nl>
  15. * @author Stefan Weil <sw@weilnetz.de>
  16. * @author Thomas Citharel <nextcloud@tcit.fr>
  17. * @author Thomas Müller <thomas.mueller@tmit.eu>
  18. * @author Vinicius Cubas Brand <vinicius@eita.org.br>
  19. *
  20. * @license AGPL-3.0
  21. *
  22. * This code is free software: you can redistribute it and/or modify
  23. * it under the terms of the GNU Affero General Public License, version 3,
  24. * as published by the Free Software Foundation.
  25. *
  26. * This program is distributed in the hope that it will be useful,
  27. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. * GNU Affero General Public License for more details.
  30. *
  31. * You should have received a copy of the GNU Affero General Public License, version 3,
  32. * along with this program. If not, see <http://www.gnu.org/licenses/>
  33. *
  34. */
  35. namespace OCA\DAV\CalDAV;
  36. use DateTime;
  37. use OCA\DAV\Connector\Sabre\Principal;
  38. use OCA\DAV\DAV\Sharing\Backend;
  39. use OCA\DAV\DAV\Sharing\IShareable;
  40. use OCA\DAV\Events\CachedCalendarObjectCreatedEvent;
  41. use OCA\DAV\Events\CachedCalendarObjectDeletedEvent;
  42. use OCA\DAV\Events\CachedCalendarObjectUpdatedEvent;
  43. use OCA\DAV\Events\CalendarCreatedEvent;
  44. use OCA\DAV\Events\CalendarDeletedEvent;
  45. use OCA\DAV\Events\CalendarObjectCreatedEvent;
  46. use OCA\DAV\Events\CalendarObjectDeletedEvent;
  47. use OCA\DAV\Events\CalendarObjectUpdatedEvent;
  48. use OCA\DAV\Events\CalendarPublishedEvent;
  49. use OCA\DAV\Events\CalendarShareUpdatedEvent;
  50. use OCA\DAV\Events\CalendarUnpublishedEvent;
  51. use OCA\DAV\Events\CalendarUpdatedEvent;
  52. use OCA\DAV\Events\SubscriptionCreatedEvent;
  53. use OCA\DAV\Events\SubscriptionDeletedEvent;
  54. use OCA\DAV\Events\SubscriptionUpdatedEvent;
  55. use OCP\DB\QueryBuilder\IQueryBuilder;
  56. use OCP\EventDispatcher\IEventDispatcher;
  57. use OCP\IDBConnection;
  58. use OCP\IGroupManager;
  59. use OCP\ILogger;
  60. use OCP\IUser;
  61. use OCP\IUserManager;
  62. use OCP\Security\ISecureRandom;
  63. use Sabre\CalDAV\Backend\AbstractBackend;
  64. use Sabre\CalDAV\Backend\SchedulingSupport;
  65. use Sabre\CalDAV\Backend\SubscriptionSupport;
  66. use Sabre\CalDAV\Backend\SyncSupport;
  67. use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
  68. use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
  69. use Sabre\DAV;
  70. use Sabre\DAV\Exception\Forbidden;
  71. use Sabre\DAV\Exception\NotFound;
  72. use Sabre\DAV\PropPatch;
  73. use Sabre\Uri;
  74. use Sabre\VObject\Component;
  75. use Sabre\VObject\Component\VCalendar;
  76. use Sabre\VObject\Component\VTimeZone;
  77. use Sabre\VObject\DateTimeParser;
  78. use Sabre\VObject\InvalidDataException;
  79. use Sabre\VObject\ParseException;
  80. use Sabre\VObject\Property;
  81. use Sabre\VObject\Reader;
  82. use Sabre\VObject\Recur\EventIterator;
  83. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  84. use Symfony\Component\EventDispatcher\GenericEvent;
  85. /**
  86. * Class CalDavBackend
  87. *
  88. * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php
  89. *
  90. * @package OCA\DAV\CalDAV
  91. */
  92. class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
  93. public const CALENDAR_TYPE_CALENDAR = 0;
  94. public const CALENDAR_TYPE_SUBSCRIPTION = 1;
  95. public const PERSONAL_CALENDAR_URI = 'personal';
  96. public const PERSONAL_CALENDAR_NAME = 'Personal';
  97. public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
  98. public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
  99. /**
  100. * We need to specify a max date, because we need to stop *somewhere*
  101. *
  102. * On 32 bit system the maximum for a signed integer is 2147483647, so
  103. * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
  104. * in 2038-01-19 to avoid problems when the date is converted
  105. * to a unix timestamp.
  106. */
  107. public const MAX_DATE = '2038-01-01';
  108. public const ACCESS_PUBLIC = 4;
  109. public const CLASSIFICATION_PUBLIC = 0;
  110. public const CLASSIFICATION_PRIVATE = 1;
  111. public const CLASSIFICATION_CONFIDENTIAL = 2;
  112. /**
  113. * List of CalDAV properties, and how they map to database field names
  114. * Add your own properties by simply adding on to this array.
  115. *
  116. * Note that only string-based properties are supported here.
  117. *
  118. * @var array
  119. */
  120. public $propertyMap = [
  121. '{DAV:}displayname' => 'displayname',
  122. '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
  123. '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
  124. '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
  125. '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
  126. ];
  127. /**
  128. * List of subscription properties, and how they map to database field names.
  129. *
  130. * @var array
  131. */
  132. public $subscriptionPropertyMap = [
  133. '{DAV:}displayname' => 'displayname',
  134. '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
  135. '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
  136. '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
  137. '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
  138. '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
  139. '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
  140. ];
  141. /** @var array properties to index */
  142. public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
  143. 'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
  144. 'ORGANIZER'];
  145. /** @var array parameters to index */
  146. public static $indexParameters = [
  147. 'ATTENDEE' => ['CN'],
  148. 'ORGANIZER' => ['CN'],
  149. ];
  150. /**
  151. * @var string[] Map of uid => display name
  152. */
  153. protected $userDisplayNames;
  154. /** @var IDBConnection */
  155. private $db;
  156. /** @var Backend */
  157. private $calendarSharingBackend;
  158. /** @var Principal */
  159. private $principalBackend;
  160. /** @var IUserManager */
  161. private $userManager;
  162. /** @var ISecureRandom */
  163. private $random;
  164. /** @var ILogger */
  165. private $logger;
  166. /** @var IEventDispatcher */
  167. private $dispatcher;
  168. /** @var EventDispatcherInterface */
  169. private $legacyDispatcher;
  170. /** @var bool */
  171. private $legacyEndpoint;
  172. /** @var string */
  173. private $dbObjectPropertiesTable = 'calendarobjects_props';
  174. /**
  175. * CalDavBackend constructor.
  176. *
  177. * @param IDBConnection $db
  178. * @param Principal $principalBackend
  179. * @param IUserManager $userManager
  180. * @param IGroupManager $groupManager
  181. * @param ISecureRandom $random
  182. * @param ILogger $logger
  183. * @param IEventDispatcher $dispatcher
  184. * @param EventDispatcherInterface $legacyDispatcher
  185. * @param bool $legacyEndpoint
  186. */
  187. public function __construct(IDBConnection $db,
  188. Principal $principalBackend,
  189. IUserManager $userManager,
  190. IGroupManager $groupManager,
  191. ISecureRandom $random,
  192. ILogger $logger,
  193. IEventDispatcher $dispatcher,
  194. EventDispatcherInterface $legacyDispatcher,
  195. bool $legacyEndpoint = false) {
  196. $this->db = $db;
  197. $this->principalBackend = $principalBackend;
  198. $this->userManager = $userManager;
  199. $this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
  200. $this->random = $random;
  201. $this->logger = $logger;
  202. $this->dispatcher = $dispatcher;
  203. $this->legacyDispatcher = $legacyDispatcher;
  204. $this->legacyEndpoint = $legacyEndpoint;
  205. }
  206. /**
  207. * Return the number of calendars for a principal
  208. *
  209. * By default this excludes the automatically generated birthday calendar
  210. *
  211. * @param $principalUri
  212. * @param bool $excludeBirthday
  213. * @return int
  214. */
  215. public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
  216. $principalUri = $this->convertPrincipal($principalUri, true);
  217. $query = $this->db->getQueryBuilder();
  218. $query->select($query->func()->count('*'))
  219. ->from('calendars')
  220. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
  221. if ($excludeBirthday) {
  222. $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
  223. }
  224. return (int)$query->execute()->fetchColumn();
  225. }
  226. /**
  227. * Returns a list of calendars for a principal.
  228. *
  229. * Every project is an array with the following keys:
  230. * * id, a unique id that will be used by other functions to modify the
  231. * calendar. This can be the same as the uri or a database key.
  232. * * uri, which the basename of the uri with which the calendar is
  233. * accessed.
  234. * * principaluri. The owner of the calendar. Almost always the same as
  235. * principalUri passed to this method.
  236. *
  237. * Furthermore it can contain webdav properties in clark notation. A very
  238. * common one is '{DAV:}displayname'.
  239. *
  240. * Many clients also require:
  241. * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
  242. * For this property, you can just return an instance of
  243. * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
  244. *
  245. * If you return {http://sabredav.org/ns}read-only and set the value to 1,
  246. * ACL will automatically be put in read-only mode.
  247. *
  248. * @param string $principalUri
  249. * @return array
  250. */
  251. public function getCalendarsForUser($principalUri) {
  252. $principalUriOriginal = $principalUri;
  253. $principalUri = $this->convertPrincipal($principalUri, true);
  254. $fields = array_values($this->propertyMap);
  255. $fields[] = 'id';
  256. $fields[] = 'uri';
  257. $fields[] = 'synctoken';
  258. $fields[] = 'components';
  259. $fields[] = 'principaluri';
  260. $fields[] = 'transparent';
  261. // Making fields a comma-delimited list
  262. $query = $this->db->getQueryBuilder();
  263. $query->select($fields)->from('calendars')
  264. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
  265. ->orderBy('calendarorder', 'ASC');
  266. $stmt = $query->execute();
  267. $calendars = [];
  268. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  269. $components = [];
  270. if ($row['components']) {
  271. $components = explode(',',$row['components']);
  272. }
  273. $calendar = [
  274. 'id' => $row['id'],
  275. 'uri' => $row['uri'],
  276. 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  277. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  278. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  279. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  280. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
  281. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
  282. ];
  283. foreach ($this->propertyMap as $xmlName => $dbName) {
  284. $calendar[$xmlName] = $row[$dbName];
  285. }
  286. $this->addOwnerPrincipal($calendar);
  287. if (!isset($calendars[$calendar['id']])) {
  288. $calendars[$calendar['id']] = $calendar;
  289. }
  290. }
  291. $stmt->closeCursor();
  292. // query for shared calendars
  293. $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
  294. $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
  295. $principals = array_map(function ($principal) {
  296. return urldecode($principal);
  297. }, $principals);
  298. $principals[] = $principalUri;
  299. $fields = array_values($this->propertyMap);
  300. $fields[] = 'a.id';
  301. $fields[] = 'a.uri';
  302. $fields[] = 'a.synctoken';
  303. $fields[] = 'a.components';
  304. $fields[] = 'a.principaluri';
  305. $fields[] = 'a.transparent';
  306. $fields[] = 's.access';
  307. $query = $this->db->getQueryBuilder();
  308. $result = $query->select($fields)
  309. ->from('dav_shares', 's')
  310. ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
  311. ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
  312. ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
  313. ->setParameter('type', 'calendar')
  314. ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
  315. ->execute();
  316. $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
  317. while ($row = $result->fetch()) {
  318. if ($row['principaluri'] === $principalUri) {
  319. continue;
  320. }
  321. $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
  322. if (isset($calendars[$row['id']])) {
  323. if ($readOnly) {
  324. // New share can not have more permissions then the old one.
  325. continue;
  326. }
  327. if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
  328. $calendars[$row['id']][$readOnlyPropertyName] === 0) {
  329. // Old share is already read-write, no more permissions can be gained
  330. continue;
  331. }
  332. }
  333. list(, $name) = Uri\split($row['principaluri']);
  334. $uri = $row['uri'] . '_shared_by_' . $name;
  335. $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
  336. $components = [];
  337. if ($row['components']) {
  338. $components = explode(',',$row['components']);
  339. }
  340. $calendar = [
  341. 'id' => $row['id'],
  342. 'uri' => $uri,
  343. 'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
  344. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  345. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  346. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  347. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
  348. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  349. $readOnlyPropertyName => $readOnly,
  350. ];
  351. foreach ($this->propertyMap as $xmlName => $dbName) {
  352. $calendar[$xmlName] = $row[$dbName];
  353. }
  354. $this->addOwnerPrincipal($calendar);
  355. $calendars[$calendar['id']] = $calendar;
  356. }
  357. $result->closeCursor();
  358. return array_values($calendars);
  359. }
  360. /**
  361. * @param $principalUri
  362. * @return array
  363. */
  364. public function getUsersOwnCalendars($principalUri) {
  365. $principalUri = $this->convertPrincipal($principalUri, true);
  366. $fields = array_values($this->propertyMap);
  367. $fields[] = 'id';
  368. $fields[] = 'uri';
  369. $fields[] = 'synctoken';
  370. $fields[] = 'components';
  371. $fields[] = 'principaluri';
  372. $fields[] = 'transparent';
  373. // Making fields a comma-delimited list
  374. $query = $this->db->getQueryBuilder();
  375. $query->select($fields)->from('calendars')
  376. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
  377. ->orderBy('calendarorder', 'ASC');
  378. $stmt = $query->execute();
  379. $calendars = [];
  380. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  381. $components = [];
  382. if ($row['components']) {
  383. $components = explode(',',$row['components']);
  384. }
  385. $calendar = [
  386. 'id' => $row['id'],
  387. 'uri' => $row['uri'],
  388. 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  389. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  390. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  391. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  392. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
  393. ];
  394. foreach ($this->propertyMap as $xmlName => $dbName) {
  395. $calendar[$xmlName] = $row[$dbName];
  396. }
  397. $this->addOwnerPrincipal($calendar);
  398. if (!isset($calendars[$calendar['id']])) {
  399. $calendars[$calendar['id']] = $calendar;
  400. }
  401. }
  402. $stmt->closeCursor();
  403. return array_values($calendars);
  404. }
  405. /**
  406. * @param $uid
  407. * @return string
  408. */
  409. private function getUserDisplayName($uid) {
  410. if (!isset($this->userDisplayNames[$uid])) {
  411. $user = $this->userManager->get($uid);
  412. if ($user instanceof IUser) {
  413. $this->userDisplayNames[$uid] = $user->getDisplayName();
  414. } else {
  415. $this->userDisplayNames[$uid] = $uid;
  416. }
  417. }
  418. return $this->userDisplayNames[$uid];
  419. }
  420. /**
  421. * @return array
  422. */
  423. public function getPublicCalendars() {
  424. $fields = array_values($this->propertyMap);
  425. $fields[] = 'a.id';
  426. $fields[] = 'a.uri';
  427. $fields[] = 'a.synctoken';
  428. $fields[] = 'a.components';
  429. $fields[] = 'a.principaluri';
  430. $fields[] = 'a.transparent';
  431. $fields[] = 's.access';
  432. $fields[] = 's.publicuri';
  433. $calendars = [];
  434. $query = $this->db->getQueryBuilder();
  435. $result = $query->select($fields)
  436. ->from('dav_shares', 's')
  437. ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
  438. ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
  439. ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
  440. ->execute();
  441. while ($row = $result->fetch()) {
  442. list(, $name) = Uri\split($row['principaluri']);
  443. $row['displayname'] = $row['displayname'] . "($name)";
  444. $components = [];
  445. if ($row['components']) {
  446. $components = explode(',',$row['components']);
  447. }
  448. $calendar = [
  449. 'id' => $row['id'],
  450. 'uri' => $row['publicuri'],
  451. 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  452. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  453. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  454. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  455. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
  456. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
  457. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
  458. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
  459. ];
  460. foreach ($this->propertyMap as $xmlName => $dbName) {
  461. $calendar[$xmlName] = $row[$dbName];
  462. }
  463. $this->addOwnerPrincipal($calendar);
  464. if (!isset($calendars[$calendar['id']])) {
  465. $calendars[$calendar['id']] = $calendar;
  466. }
  467. }
  468. $result->closeCursor();
  469. return array_values($calendars);
  470. }
  471. /**
  472. * @param string $uri
  473. * @return array
  474. * @throws NotFound
  475. */
  476. public function getPublicCalendar($uri) {
  477. $fields = array_values($this->propertyMap);
  478. $fields[] = 'a.id';
  479. $fields[] = 'a.uri';
  480. $fields[] = 'a.synctoken';
  481. $fields[] = 'a.components';
  482. $fields[] = 'a.principaluri';
  483. $fields[] = 'a.transparent';
  484. $fields[] = 's.access';
  485. $fields[] = 's.publicuri';
  486. $query = $this->db->getQueryBuilder();
  487. $result = $query->select($fields)
  488. ->from('dav_shares', 's')
  489. ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
  490. ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
  491. ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
  492. ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
  493. ->execute();
  494. $row = $result->fetch(\PDO::FETCH_ASSOC);
  495. $result->closeCursor();
  496. if ($row === false) {
  497. throw new NotFound('Node with name \'' . $uri . '\' could not be found');
  498. }
  499. list(, $name) = Uri\split($row['principaluri']);
  500. $row['displayname'] = $row['displayname'] . ' ' . "($name)";
  501. $components = [];
  502. if ($row['components']) {
  503. $components = explode(',',$row['components']);
  504. }
  505. $calendar = [
  506. 'id' => $row['id'],
  507. 'uri' => $row['publicuri'],
  508. 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  509. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  510. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  511. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  512. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
  513. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  514. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
  515. '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
  516. ];
  517. foreach ($this->propertyMap as $xmlName => $dbName) {
  518. $calendar[$xmlName] = $row[$dbName];
  519. }
  520. $this->addOwnerPrincipal($calendar);
  521. return $calendar;
  522. }
  523. /**
  524. * @param string $principal
  525. * @param string $uri
  526. * @return array|null
  527. */
  528. public function getCalendarByUri($principal, $uri) {
  529. $fields = array_values($this->propertyMap);
  530. $fields[] = 'id';
  531. $fields[] = 'uri';
  532. $fields[] = 'synctoken';
  533. $fields[] = 'components';
  534. $fields[] = 'principaluri';
  535. $fields[] = 'transparent';
  536. // Making fields a comma-delimited list
  537. $query = $this->db->getQueryBuilder();
  538. $query->select($fields)->from('calendars')
  539. ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
  540. ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
  541. ->setMaxResults(1);
  542. $stmt = $query->execute();
  543. $row = $stmt->fetch(\PDO::FETCH_ASSOC);
  544. $stmt->closeCursor();
  545. if ($row === false) {
  546. return null;
  547. }
  548. $components = [];
  549. if ($row['components']) {
  550. $components = explode(',',$row['components']);
  551. }
  552. $calendar = [
  553. 'id' => $row['id'],
  554. 'uri' => $row['uri'],
  555. 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  556. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  557. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  558. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  559. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
  560. ];
  561. foreach ($this->propertyMap as $xmlName => $dbName) {
  562. $calendar[$xmlName] = $row[$dbName];
  563. }
  564. $this->addOwnerPrincipal($calendar);
  565. return $calendar;
  566. }
  567. /**
  568. * @param $calendarId
  569. * @return array|null
  570. */
  571. public function getCalendarById($calendarId) {
  572. $fields = array_values($this->propertyMap);
  573. $fields[] = 'id';
  574. $fields[] = 'uri';
  575. $fields[] = 'synctoken';
  576. $fields[] = 'components';
  577. $fields[] = 'principaluri';
  578. $fields[] = 'transparent';
  579. // Making fields a comma-delimited list
  580. $query = $this->db->getQueryBuilder();
  581. $query->select($fields)->from('calendars')
  582. ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
  583. ->setMaxResults(1);
  584. $stmt = $query->execute();
  585. $row = $stmt->fetch(\PDO::FETCH_ASSOC);
  586. $stmt->closeCursor();
  587. if ($row === false) {
  588. return null;
  589. }
  590. $components = [];
  591. if ($row['components']) {
  592. $components = explode(',',$row['components']);
  593. }
  594. $calendar = [
  595. 'id' => $row['id'],
  596. 'uri' => $row['uri'],
  597. 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
  598. '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
  599. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  600. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
  601. '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
  602. ];
  603. foreach ($this->propertyMap as $xmlName => $dbName) {
  604. $calendar[$xmlName] = $row[$dbName];
  605. }
  606. $this->addOwnerPrincipal($calendar);
  607. return $calendar;
  608. }
  609. /**
  610. * @param $subscriptionId
  611. */
  612. public function getSubscriptionById($subscriptionId) {
  613. $fields = array_values($this->subscriptionPropertyMap);
  614. $fields[] = 'id';
  615. $fields[] = 'uri';
  616. $fields[] = 'source';
  617. $fields[] = 'synctoken';
  618. $fields[] = 'principaluri';
  619. $fields[] = 'lastmodified';
  620. $query = $this->db->getQueryBuilder();
  621. $query->select($fields)
  622. ->from('calendarsubscriptions')
  623. ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
  624. ->orderBy('calendarorder', 'asc');
  625. $stmt = $query->execute();
  626. $row = $stmt->fetch(\PDO::FETCH_ASSOC);
  627. $stmt->closeCursor();
  628. if ($row === false) {
  629. return null;
  630. }
  631. $subscription = [
  632. 'id' => $row['id'],
  633. 'uri' => $row['uri'],
  634. 'principaluri' => $row['principaluri'],
  635. 'source' => $row['source'],
  636. 'lastmodified' => $row['lastmodified'],
  637. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
  638. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  639. ];
  640. foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
  641. if (!is_null($row[$dbName])) {
  642. $subscription[$xmlName] = $row[$dbName];
  643. }
  644. }
  645. return $subscription;
  646. }
  647. /**
  648. * Creates a new calendar for a principal.
  649. *
  650. * If the creation was a success, an id must be returned that can be used to reference
  651. * this calendar in other methods, such as updateCalendar.
  652. *
  653. * @param string $principalUri
  654. * @param string $calendarUri
  655. * @param array $properties
  656. * @return int
  657. */
  658. public function createCalendar($principalUri, $calendarUri, array $properties) {
  659. $values = [
  660. 'principaluri' => $this->convertPrincipal($principalUri, true),
  661. 'uri' => $calendarUri,
  662. 'synctoken' => 1,
  663. 'transparent' => 0,
  664. 'components' => 'VEVENT,VTODO',
  665. 'displayname' => $calendarUri
  666. ];
  667. // Default value
  668. $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
  669. if (isset($properties[$sccs])) {
  670. if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
  671. throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
  672. }
  673. $values['components'] = implode(',',$properties[$sccs]->getValue());
  674. } elseif (isset($properties['components'])) {
  675. // Allow to provide components internally without having
  676. // to create a SupportedCalendarComponentSet object
  677. $values['components'] = $properties['components'];
  678. }
  679. $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
  680. if (isset($properties[$transp])) {
  681. $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
  682. }
  683. foreach ($this->propertyMap as $xmlName => $dbName) {
  684. if (isset($properties[$xmlName])) {
  685. $values[$dbName] = $properties[$xmlName];
  686. }
  687. }
  688. $query = $this->db->getQueryBuilder();
  689. $query->insert('calendars');
  690. foreach ($values as $column => $value) {
  691. $query->setValue($column, $query->createNamedParameter($value));
  692. }
  693. $query->execute();
  694. $calendarId = $query->getLastInsertId();
  695. $calendarData = $this->getCalendarById($calendarId);
  696. $this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
  697. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
  698. '\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
  699. [
  700. 'calendarId' => $calendarId,
  701. 'calendarData' => $calendarData,
  702. ]));
  703. return $calendarId;
  704. }
  705. /**
  706. * Updates properties for a calendar.
  707. *
  708. * The list of mutations is stored in a Sabre\DAV\PropPatch object.
  709. * To do the actual updates, you must tell this object which properties
  710. * you're going to process with the handle() method.
  711. *
  712. * Calling the handle method is like telling the PropPatch object "I
  713. * promise I can handle updating this property".
  714. *
  715. * Read the PropPatch documentation for more info and examples.
  716. *
  717. * @param mixed $calendarId
  718. * @param PropPatch $propPatch
  719. * @return void
  720. */
  721. public function updateCalendar($calendarId, PropPatch $propPatch) {
  722. $supportedProperties = array_keys($this->propertyMap);
  723. $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
  724. $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
  725. $newValues = [];
  726. foreach ($mutations as $propertyName => $propertyValue) {
  727. switch ($propertyName) {
  728. case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
  729. $fieldName = 'transparent';
  730. $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
  731. break;
  732. default:
  733. $fieldName = $this->propertyMap[$propertyName];
  734. $newValues[$fieldName] = $propertyValue;
  735. break;
  736. }
  737. }
  738. $query = $this->db->getQueryBuilder();
  739. $query->update('calendars');
  740. foreach ($newValues as $fieldName => $value) {
  741. $query->set($fieldName, $query->createNamedParameter($value));
  742. }
  743. $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
  744. $query->execute();
  745. $this->addChange($calendarId, "", 2);
  746. $calendarData = $this->getCalendarById($calendarId);
  747. $shares = $this->getShares($calendarId);
  748. $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
  749. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
  750. '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
  751. [
  752. 'calendarId' => $calendarId,
  753. 'calendarData' => $calendarData,
  754. 'shares' => $shares,
  755. 'propertyMutations' => $mutations,
  756. ]));
  757. return true;
  758. });
  759. }
  760. /**
  761. * Delete a calendar and all it's objects
  762. *
  763. * @param mixed $calendarId
  764. * @return void
  765. */
  766. public function deleteCalendar($calendarId) {
  767. $calendarData = $this->getCalendarById($calendarId);
  768. $shares = $this->getShares($calendarId);
  769. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
  770. '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
  771. [
  772. 'calendarId' => $calendarId,
  773. 'calendarData' => $calendarData,
  774. 'shares' => $shares,
  775. ]));
  776. $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
  777. $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
  778. $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
  779. $stmt->execute([$calendarId]);
  780. $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
  781. $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
  782. $this->calendarSharingBackend->deleteAllShares($calendarId);
  783. $query = $this->db->getQueryBuilder();
  784. $query->delete($this->dbObjectPropertiesTable)
  785. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  786. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
  787. ->execute();
  788. if ($calendarData) {
  789. $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
  790. }
  791. }
  792. /**
  793. * Delete all of an user's shares
  794. *
  795. * @param string $principaluri
  796. * @return void
  797. */
  798. public function deleteAllSharesByUser($principaluri) {
  799. $this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
  800. }
  801. /**
  802. * Returns all calendar objects within a calendar.
  803. *
  804. * Every item contains an array with the following keys:
  805. * * calendardata - The iCalendar-compatible calendar data
  806. * * uri - a unique key which will be used to construct the uri. This can
  807. * be any arbitrary string, but making sure it ends with '.ics' is a
  808. * good idea. This is only the basename, or filename, not the full
  809. * path.
  810. * * lastmodified - a timestamp of the last modification time
  811. * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
  812. * '"abcdef"')
  813. * * size - The size of the calendar objects, in bytes.
  814. * * component - optional, a string containing the type of object, such
  815. * as 'vevent' or 'vtodo'. If specified, this will be used to populate
  816. * the Content-Type header.
  817. *
  818. * Note that the etag is optional, but it's highly encouraged to return for
  819. * speed reasons.
  820. *
  821. * The calendardata is also optional. If it's not returned
  822. * 'getCalendarObject' will be called later, which *is* expected to return
  823. * calendardata.
  824. *
  825. * If neither etag or size are specified, the calendardata will be
  826. * used/fetched to determine these numbers. If both are specified the
  827. * amount of times this is needed is reduced by a great degree.
  828. *
  829. * @param mixed $calendarId
  830. * @param int $calendarType
  831. * @return array
  832. */
  833. public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
  834. $query = $this->db->getQueryBuilder();
  835. $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
  836. ->from('calendarobjects')
  837. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  838. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
  839. $stmt = $query->execute();
  840. $result = [];
  841. foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
  842. $result[] = [
  843. 'id' => $row['id'],
  844. 'uri' => $row['uri'],
  845. 'lastmodified' => $row['lastmodified'],
  846. 'etag' => '"' . $row['etag'] . '"',
  847. 'calendarid' => $row['calendarid'],
  848. 'size' => (int)$row['size'],
  849. 'component' => strtolower($row['componenttype']),
  850. 'classification' => (int)$row['classification']
  851. ];
  852. }
  853. return $result;
  854. }
  855. /**
  856. * Returns information from a single calendar object, based on it's object
  857. * uri.
  858. *
  859. * The object uri is only the basename, or filename and not a full path.
  860. *
  861. * The returned array must have the same keys as getCalendarObjects. The
  862. * 'calendardata' object is required here though, while it's not required
  863. * for getCalendarObjects.
  864. *
  865. * This method must return null if the object did not exist.
  866. *
  867. * @param mixed $calendarId
  868. * @param string $objectUri
  869. * @param int $calendarType
  870. * @return array|null
  871. */
  872. public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  873. $query = $this->db->getQueryBuilder();
  874. $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
  875. ->from('calendarobjects')
  876. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  877. ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
  878. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
  879. $stmt = $query->execute();
  880. $row = $stmt->fetch(\PDO::FETCH_ASSOC);
  881. if (!$row) {
  882. return null;
  883. }
  884. return [
  885. 'id' => $row['id'],
  886. 'uri' => $row['uri'],
  887. 'lastmodified' => $row['lastmodified'],
  888. 'etag' => '"' . $row['etag'] . '"',
  889. 'calendarid' => $row['calendarid'],
  890. 'size' => (int)$row['size'],
  891. 'calendardata' => $this->readBlob($row['calendardata']),
  892. 'component' => strtolower($row['componenttype']),
  893. 'classification' => (int)$row['classification']
  894. ];
  895. }
  896. /**
  897. * Returns a list of calendar objects.
  898. *
  899. * This method should work identical to getCalendarObject, but instead
  900. * return all the calendar objects in the list as an array.
  901. *
  902. * If the backend supports this, it may allow for some speed-ups.
  903. *
  904. * @param mixed $calendarId
  905. * @param string[] $uris
  906. * @param int $calendarType
  907. * @return array
  908. */
  909. public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
  910. if (empty($uris)) {
  911. return [];
  912. }
  913. $chunks = array_chunk($uris, 100);
  914. $objects = [];
  915. $query = $this->db->getQueryBuilder();
  916. $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
  917. ->from('calendarobjects')
  918. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  919. ->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
  920. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
  921. foreach ($chunks as $uris) {
  922. $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
  923. $result = $query->execute();
  924. while ($row = $result->fetch()) {
  925. $objects[] = [
  926. 'id' => $row['id'],
  927. 'uri' => $row['uri'],
  928. 'lastmodified' => $row['lastmodified'],
  929. 'etag' => '"' . $row['etag'] . '"',
  930. 'calendarid' => $row['calendarid'],
  931. 'size' => (int)$row['size'],
  932. 'calendardata' => $this->readBlob($row['calendardata']),
  933. 'component' => strtolower($row['componenttype']),
  934. 'classification' => (int)$row['classification']
  935. ];
  936. }
  937. $result->closeCursor();
  938. }
  939. return $objects;
  940. }
  941. /**
  942. * Creates a new calendar object.
  943. *
  944. * The object uri is only the basename, or filename and not a full path.
  945. *
  946. * It is possible return an etag from this function, which will be used in
  947. * the response to this PUT request. Note that the ETag must be surrounded
  948. * by double-quotes.
  949. *
  950. * However, you should only really return this ETag if you don't mangle the
  951. * calendar-data. If the result of a subsequent GET to this object is not
  952. * the exact same as this request body, you should omit the ETag.
  953. *
  954. * @param mixed $calendarId
  955. * @param string $objectUri
  956. * @param string $calendarData
  957. * @param int $calendarType
  958. * @return string
  959. */
  960. public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  961. $extraData = $this->getDenormalizedData($calendarData);
  962. $q = $this->db->getQueryBuilder();
  963. $q->select($q->func()->count('*'))
  964. ->from('calendarobjects')
  965. ->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
  966. ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
  967. ->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
  968. $result = $q->execute();
  969. $count = (int) $result->fetchColumn();
  970. $result->closeCursor();
  971. if ($count !== 0) {
  972. throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
  973. }
  974. $query = $this->db->getQueryBuilder();
  975. $query->insert('calendarobjects')
  976. ->values([
  977. 'calendarid' => $query->createNamedParameter($calendarId),
  978. 'uri' => $query->createNamedParameter($objectUri),
  979. 'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
  980. 'lastmodified' => $query->createNamedParameter(time()),
  981. 'etag' => $query->createNamedParameter($extraData['etag']),
  982. 'size' => $query->createNamedParameter($extraData['size']),
  983. 'componenttype' => $query->createNamedParameter($extraData['componentType']),
  984. 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
  985. 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
  986. 'classification' => $query->createNamedParameter($extraData['classification']),
  987. 'uid' => $query->createNamedParameter($extraData['uid']),
  988. 'calendartype' => $query->createNamedParameter($calendarType),
  989. ])
  990. ->execute();
  991. $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
  992. $this->addChange($calendarId, $objectUri, 1, $calendarType);
  993. $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
  994. if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
  995. $calendarRow = $this->getCalendarById($calendarId);
  996. $shares = $this->getShares($calendarId);
  997. $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
  998. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
  999. '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
  1000. [
  1001. 'calendarId' => $calendarId,
  1002. 'calendarData' => $calendarRow,
  1003. 'shares' => $shares,
  1004. 'objectData' => $objectRow,
  1005. ]
  1006. ));
  1007. } else {
  1008. $subscriptionRow = $this->getSubscriptionById($calendarId);
  1009. $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
  1010. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
  1011. '\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
  1012. [
  1013. 'subscriptionId' => $calendarId,
  1014. 'calendarData' => $subscriptionRow,
  1015. 'shares' => [],
  1016. 'objectData' => $objectRow,
  1017. ]
  1018. ));
  1019. }
  1020. return '"' . $extraData['etag'] . '"';
  1021. }
  1022. /**
  1023. * Updates an existing calendarobject, based on it's uri.
  1024. *
  1025. * The object uri is only the basename, or filename and not a full path.
  1026. *
  1027. * It is possible return an etag from this function, which will be used in
  1028. * the response to this PUT request. Note that the ETag must be surrounded
  1029. * by double-quotes.
  1030. *
  1031. * However, you should only really return this ETag if you don't mangle the
  1032. * calendar-data. If the result of a subsequent GET to this object is not
  1033. * the exact same as this request body, you should omit the ETag.
  1034. *
  1035. * @param mixed $calendarId
  1036. * @param string $objectUri
  1037. * @param string $calendarData
  1038. * @param int $calendarType
  1039. * @return string
  1040. */
  1041. public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  1042. $extraData = $this->getDenormalizedData($calendarData);
  1043. $query = $this->db->getQueryBuilder();
  1044. $query->update('calendarobjects')
  1045. ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
  1046. ->set('lastmodified', $query->createNamedParameter(time()))
  1047. ->set('etag', $query->createNamedParameter($extraData['etag']))
  1048. ->set('size', $query->createNamedParameter($extraData['size']))
  1049. ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
  1050. ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
  1051. ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
  1052. ->set('classification', $query->createNamedParameter($extraData['classification']))
  1053. ->set('uid', $query->createNamedParameter($extraData['uid']))
  1054. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  1055. ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
  1056. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
  1057. ->execute();
  1058. $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
  1059. $this->addChange($calendarId, $objectUri, 2, $calendarType);
  1060. $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
  1061. if (is_array($objectRow)) {
  1062. if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
  1063. $calendarRow = $this->getCalendarById($calendarId);
  1064. $shares = $this->getShares($calendarId);
  1065. $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
  1066. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
  1067. '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
  1068. [
  1069. 'calendarId' => $calendarId,
  1070. 'calendarData' => $calendarRow,
  1071. 'shares' => $shares,
  1072. 'objectData' => $objectRow,
  1073. ]
  1074. ));
  1075. } else {
  1076. $subscriptionRow = $this->getSubscriptionById($calendarId);
  1077. $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
  1078. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
  1079. '\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
  1080. [
  1081. 'subscriptionId' => $calendarId,
  1082. 'calendarData' => $subscriptionRow,
  1083. 'shares' => [],
  1084. 'objectData' => $objectRow,
  1085. ]
  1086. ));
  1087. }
  1088. }
  1089. return '"' . $extraData['etag'] . '"';
  1090. }
  1091. /**
  1092. * @param int $calendarObjectId
  1093. * @param int $classification
  1094. */
  1095. public function setClassification($calendarObjectId, $classification) {
  1096. if (!in_array($classification, [
  1097. self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
  1098. ])) {
  1099. throw new \InvalidArgumentException();
  1100. }
  1101. $query = $this->db->getQueryBuilder();
  1102. $query->update('calendarobjects')
  1103. ->set('classification', $query->createNamedParameter($classification))
  1104. ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
  1105. ->execute();
  1106. }
  1107. /**
  1108. * Deletes an existing calendar object.
  1109. *
  1110. * The object uri is only the basename, or filename and not a full path.
  1111. *
  1112. * @param mixed $calendarId
  1113. * @param string $objectUri
  1114. * @param int $calendarType
  1115. * @return void
  1116. */
  1117. public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  1118. $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
  1119. if (is_array($data)) {
  1120. if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
  1121. $calendarRow = $this->getCalendarById($calendarId);
  1122. $shares = $this->getShares($calendarId);
  1123. $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
  1124. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
  1125. '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
  1126. [
  1127. 'calendarId' => $calendarId,
  1128. 'calendarData' => $calendarRow,
  1129. 'shares' => $shares,
  1130. 'objectData' => $data,
  1131. ]
  1132. ));
  1133. } else {
  1134. $subscriptionRow = $this->getSubscriptionById($calendarId);
  1135. $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
  1136. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
  1137. '\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
  1138. [
  1139. 'subscriptionId' => $calendarId,
  1140. 'calendarData' => $subscriptionRow,
  1141. 'shares' => [],
  1142. 'objectData' => $data,
  1143. ]
  1144. ));
  1145. }
  1146. }
  1147. $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
  1148. $stmt->execute([$calendarId, $objectUri, $calendarType]);
  1149. if (is_array($data)) {
  1150. $this->purgeProperties($calendarId, $data['id'], $calendarType);
  1151. }
  1152. $this->addChange($calendarId, $objectUri, 3, $calendarType);
  1153. }
  1154. /**
  1155. * Performs a calendar-query on the contents of this calendar.
  1156. *
  1157. * The calendar-query is defined in RFC4791 : CalDAV. Using the
  1158. * calendar-query it is possible for a client to request a specific set of
  1159. * object, based on contents of iCalendar properties, date-ranges and
  1160. * iCalendar component types (VTODO, VEVENT).
  1161. *
  1162. * This method should just return a list of (relative) urls that match this
  1163. * query.
  1164. *
  1165. * The list of filters are specified as an array. The exact array is
  1166. * documented by Sabre\CalDAV\CalendarQueryParser.
  1167. *
  1168. * Note that it is extremely likely that getCalendarObject for every path
  1169. * returned from this method will be called almost immediately after. You
  1170. * may want to anticipate this to speed up these requests.
  1171. *
  1172. * This method provides a default implementation, which parses *all* the
  1173. * iCalendar objects in the specified calendar.
  1174. *
  1175. * This default may well be good enough for personal use, and calendars
  1176. * that aren't very large. But if you anticipate high usage, big calendars
  1177. * or high loads, you are strongly advised to optimize certain paths.
  1178. *
  1179. * The best way to do so is override this method and to optimize
  1180. * specifically for 'common filters'.
  1181. *
  1182. * Requests that are extremely common are:
  1183. * * requests for just VEVENTS
  1184. * * requests for just VTODO
  1185. * * requests with a time-range-filter on either VEVENT or VTODO.
  1186. *
  1187. * ..and combinations of these requests. It may not be worth it to try to
  1188. * handle every possible situation and just rely on the (relatively
  1189. * easy to use) CalendarQueryValidator to handle the rest.
  1190. *
  1191. * Note that especially time-range-filters may be difficult to parse. A
  1192. * time-range filter specified on a VEVENT must for instance also handle
  1193. * recurrence rules correctly.
  1194. * A good example of how to interprete all these filters can also simply
  1195. * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
  1196. * as possible, so it gives you a good idea on what type of stuff you need
  1197. * to think of.
  1198. *
  1199. * @param mixed $calendarId
  1200. * @param array $filters
  1201. * @param int $calendarType
  1202. * @return array
  1203. */
  1204. public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
  1205. $componentType = null;
  1206. $requirePostFilter = true;
  1207. $timeRange = null;
  1208. // if no filters were specified, we don't need to filter after a query
  1209. if (!$filters['prop-filters'] && !$filters['comp-filters']) {
  1210. $requirePostFilter = false;
  1211. }
  1212. // Figuring out if there's a component filter
  1213. if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
  1214. $componentType = $filters['comp-filters'][0]['name'];
  1215. // Checking if we need post-filters
  1216. if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
  1217. $requirePostFilter = false;
  1218. }
  1219. // There was a time-range filter
  1220. if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
  1221. $timeRange = $filters['comp-filters'][0]['time-range'];
  1222. // If start time OR the end time is not specified, we can do a
  1223. // 100% accurate mysql query.
  1224. if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
  1225. $requirePostFilter = false;
  1226. }
  1227. }
  1228. }
  1229. $columns = ['uri'];
  1230. if ($requirePostFilter) {
  1231. $columns = ['uri', 'calendardata'];
  1232. }
  1233. $query = $this->db->getQueryBuilder();
  1234. $query->select($columns)
  1235. ->from('calendarobjects')
  1236. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  1237. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
  1238. if ($componentType) {
  1239. $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
  1240. }
  1241. if ($timeRange && $timeRange['start']) {
  1242. $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
  1243. }
  1244. if ($timeRange && $timeRange['end']) {
  1245. $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
  1246. }
  1247. $stmt = $query->execute();
  1248. $result = [];
  1249. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  1250. if ($requirePostFilter) {
  1251. // validateFilterForObject will parse the calendar data
  1252. // catch parsing errors
  1253. try {
  1254. $matches = $this->validateFilterForObject($row, $filters);
  1255. } catch (ParseException $ex) {
  1256. $this->logger->logException($ex, [
  1257. 'app' => 'dav',
  1258. 'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
  1259. ]);
  1260. continue;
  1261. } catch (InvalidDataException $ex) {
  1262. $this->logger->logException($ex, [
  1263. 'app' => 'dav',
  1264. 'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
  1265. ]);
  1266. continue;
  1267. }
  1268. if (!$matches) {
  1269. continue;
  1270. }
  1271. }
  1272. $result[] = $row['uri'];
  1273. }
  1274. return $result;
  1275. }
  1276. /**
  1277. * custom Nextcloud search extension for CalDAV
  1278. *
  1279. * TODO - this should optionally cover cached calendar objects as well
  1280. *
  1281. * @param string $principalUri
  1282. * @param array $filters
  1283. * @param integer|null $limit
  1284. * @param integer|null $offset
  1285. * @return array
  1286. */
  1287. public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
  1288. $calendars = $this->getCalendarsForUser($principalUri);
  1289. $ownCalendars = [];
  1290. $sharedCalendars = [];
  1291. $uriMapper = [];
  1292. foreach ($calendars as $calendar) {
  1293. if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
  1294. $ownCalendars[] = $calendar['id'];
  1295. } else {
  1296. $sharedCalendars[] = $calendar['id'];
  1297. }
  1298. $uriMapper[$calendar['id']] = $calendar['uri'];
  1299. }
  1300. if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
  1301. return [];
  1302. }
  1303. $query = $this->db->getQueryBuilder();
  1304. // Calendar id expressions
  1305. $calendarExpressions = [];
  1306. foreach ($ownCalendars as $id) {
  1307. $calendarExpressions[] = $query->expr()->andX(
  1308. $query->expr()->eq('c.calendarid',
  1309. $query->createNamedParameter($id)),
  1310. $query->expr()->eq('c.calendartype',
  1311. $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
  1312. }
  1313. foreach ($sharedCalendars as $id) {
  1314. $calendarExpressions[] = $query->expr()->andX(
  1315. $query->expr()->eq('c.calendarid',
  1316. $query->createNamedParameter($id)),
  1317. $query->expr()->eq('c.classification',
  1318. $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
  1319. $query->expr()->eq('c.calendartype',
  1320. $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
  1321. }
  1322. if (count($calendarExpressions) === 1) {
  1323. $calExpr = $calendarExpressions[0];
  1324. } else {
  1325. $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
  1326. }
  1327. // Component expressions
  1328. $compExpressions = [];
  1329. foreach ($filters['comps'] as $comp) {
  1330. $compExpressions[] = $query->expr()
  1331. ->eq('c.componenttype', $query->createNamedParameter($comp));
  1332. }
  1333. if (count($compExpressions) === 1) {
  1334. $compExpr = $compExpressions[0];
  1335. } else {
  1336. $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
  1337. }
  1338. if (!isset($filters['props'])) {
  1339. $filters['props'] = [];
  1340. }
  1341. if (!isset($filters['params'])) {
  1342. $filters['params'] = [];
  1343. }
  1344. $propParamExpressions = [];
  1345. foreach ($filters['props'] as $prop) {
  1346. $propParamExpressions[] = $query->expr()->andX(
  1347. $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
  1348. $query->expr()->isNull('i.parameter')
  1349. );
  1350. }
  1351. foreach ($filters['params'] as $param) {
  1352. $propParamExpressions[] = $query->expr()->andX(
  1353. $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
  1354. $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
  1355. );
  1356. }
  1357. if (count($propParamExpressions) === 1) {
  1358. $propParamExpr = $propParamExpressions[0];
  1359. } else {
  1360. $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
  1361. }
  1362. $query->select(['c.calendarid', 'c.uri'])
  1363. ->from($this->dbObjectPropertiesTable, 'i')
  1364. ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
  1365. ->where($calExpr)
  1366. ->andWhere($compExpr)
  1367. ->andWhere($propParamExpr)
  1368. ->andWhere($query->expr()->iLike('i.value',
  1369. $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
  1370. if ($offset) {
  1371. $query->setFirstResult($offset);
  1372. }
  1373. if ($limit) {
  1374. $query->setMaxResults($limit);
  1375. }
  1376. $stmt = $query->execute();
  1377. $result = [];
  1378. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  1379. $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
  1380. if (!in_array($path, $result)) {
  1381. $result[] = $path;
  1382. }
  1383. }
  1384. return $result;
  1385. }
  1386. /**
  1387. * used for Nextcloud's calendar API
  1388. *
  1389. * @param array $calendarInfo
  1390. * @param string $pattern
  1391. * @param array $searchProperties
  1392. * @param array $options
  1393. * @param integer|null $limit
  1394. * @param integer|null $offset
  1395. *
  1396. * @return array
  1397. */
  1398. public function search(array $calendarInfo, $pattern, array $searchProperties,
  1399. array $options, $limit, $offset) {
  1400. $outerQuery = $this->db->getQueryBuilder();
  1401. $innerQuery = $this->db->getQueryBuilder();
  1402. $innerQuery->selectDistinct('op.objectid')
  1403. ->from($this->dbObjectPropertiesTable, 'op')
  1404. ->andWhere($innerQuery->expr()->eq('op.calendarid',
  1405. $outerQuery->createNamedParameter($calendarInfo['id'])))
  1406. ->andWhere($innerQuery->expr()->eq('op.calendartype',
  1407. $outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
  1408. // only return public items for shared calendars for now
  1409. if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
  1410. $innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
  1411. $outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
  1412. }
  1413. $or = $innerQuery->expr()->orX();
  1414. foreach ($searchProperties as $searchProperty) {
  1415. $or->add($innerQuery->expr()->eq('op.name',
  1416. $outerQuery->createNamedParameter($searchProperty)));
  1417. }
  1418. $innerQuery->andWhere($or);
  1419. if ($pattern !== '') {
  1420. $innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
  1421. $outerQuery->createNamedParameter('%' .
  1422. $this->db->escapeLikeParameter($pattern) . '%')));
  1423. }
  1424. $outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
  1425. ->from('calendarobjects', 'c');
  1426. if (isset($options['timerange'])) {
  1427. if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
  1428. $outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
  1429. $outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
  1430. }
  1431. if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
  1432. $outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
  1433. $outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
  1434. }
  1435. }
  1436. if (isset($options['types'])) {
  1437. $or = $outerQuery->expr()->orX();
  1438. foreach ($options['types'] as $type) {
  1439. $or->add($outerQuery->expr()->eq('componenttype',
  1440. $outerQuery->createNamedParameter($type)));
  1441. }
  1442. $outerQuery->andWhere($or);
  1443. }
  1444. $outerQuery->andWhere($outerQuery->expr()->in('c.id',
  1445. $outerQuery->createFunction($innerQuery->getSQL())));
  1446. if ($offset) {
  1447. $outerQuery->setFirstResult($offset);
  1448. }
  1449. if ($limit) {
  1450. $outerQuery->setMaxResults($limit);
  1451. }
  1452. $result = $outerQuery->execute();
  1453. $calendarObjects = $result->fetchAll();
  1454. return array_map(function ($o) {
  1455. $calendarData = Reader::read($o['calendardata']);
  1456. $comps = $calendarData->getComponents();
  1457. $objects = [];
  1458. $timezones = [];
  1459. foreach ($comps as $comp) {
  1460. if ($comp instanceof VTimeZone) {
  1461. $timezones[] = $comp;
  1462. } else {
  1463. $objects[] = $comp;
  1464. }
  1465. }
  1466. return [
  1467. 'id' => $o['id'],
  1468. 'type' => $o['componenttype'],
  1469. 'uid' => $o['uid'],
  1470. 'uri' => $o['uri'],
  1471. 'objects' => array_map(function ($c) {
  1472. return $this->transformSearchData($c);
  1473. }, $objects),
  1474. 'timezones' => array_map(function ($c) {
  1475. return $this->transformSearchData($c);
  1476. }, $timezones),
  1477. ];
  1478. }, $calendarObjects);
  1479. }
  1480. /**
  1481. * @param Component $comp
  1482. * @return array
  1483. */
  1484. private function transformSearchData(Component $comp) {
  1485. $data = [];
  1486. /** @var Component[] $subComponents */
  1487. $subComponents = $comp->getComponents();
  1488. /** @var Property[] $properties */
  1489. $properties = array_filter($comp->children(), function ($c) {
  1490. return $c instanceof Property;
  1491. });
  1492. $validationRules = $comp->getValidationRules();
  1493. foreach ($subComponents as $subComponent) {
  1494. $name = $subComponent->name;
  1495. if (!isset($data[$name])) {
  1496. $data[$name] = [];
  1497. }
  1498. $data[$name][] = $this->transformSearchData($subComponent);
  1499. }
  1500. foreach ($properties as $property) {
  1501. $name = $property->name;
  1502. if (!isset($validationRules[$name])) {
  1503. $validationRules[$name] = '*';
  1504. }
  1505. $rule = $validationRules[$property->name];
  1506. if ($rule === '+' || $rule === '*') { // multiple
  1507. if (!isset($data[$name])) {
  1508. $data[$name] = [];
  1509. }
  1510. $data[$name][] = $this->transformSearchProperty($property);
  1511. } else { // once
  1512. $data[$name] = $this->transformSearchProperty($property);
  1513. }
  1514. }
  1515. return $data;
  1516. }
  1517. /**
  1518. * @param Property $prop
  1519. * @return array
  1520. */
  1521. private function transformSearchProperty(Property $prop) {
  1522. // No need to check Date, as it extends DateTime
  1523. if ($prop instanceof Property\ICalendar\DateTime) {
  1524. $value = $prop->getDateTime();
  1525. } else {
  1526. $value = $prop->getValue();
  1527. }
  1528. return [
  1529. $value,
  1530. $prop->parameters()
  1531. ];
  1532. }
  1533. /**
  1534. * @param string $principalUri
  1535. * @param string $pattern
  1536. * @param array $componentTypes
  1537. * @param array $searchProperties
  1538. * @param array $searchParameters
  1539. * @param array $options
  1540. * @return array
  1541. */
  1542. public function searchPrincipalUri(string $principalUri,
  1543. string $pattern,
  1544. array $componentTypes,
  1545. array $searchProperties,
  1546. array $searchParameters,
  1547. array $options = []): array {
  1548. $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
  1549. $calendarObjectIdQuery = $this->db->getQueryBuilder();
  1550. $calendarOr = $calendarObjectIdQuery->expr()->orX();
  1551. $searchOr = $calendarObjectIdQuery->expr()->orX();
  1552. // Fetch calendars and subscription
  1553. $calendars = $this->getCalendarsForUser($principalUri);
  1554. $subscriptions = $this->getSubscriptionsForUser($principalUri);
  1555. foreach ($calendars as $calendar) {
  1556. $calendarAnd = $calendarObjectIdQuery->expr()->andX();
  1557. $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
  1558. $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
  1559. // If it's shared, limit search to public events
  1560. if ($calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
  1561. $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
  1562. }
  1563. $calendarOr->add($calendarAnd);
  1564. }
  1565. foreach ($subscriptions as $subscription) {
  1566. $subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
  1567. $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
  1568. $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
  1569. // If it's shared, limit search to public events
  1570. if ($subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
  1571. $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
  1572. }
  1573. $calendarOr->add($subscriptionAnd);
  1574. }
  1575. foreach ($searchProperties as $property) {
  1576. $propertyAnd = $calendarObjectIdQuery->expr()->andX();
  1577. $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
  1578. $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
  1579. $searchOr->add($propertyAnd);
  1580. }
  1581. foreach ($searchParameters as $property => $parameter) {
  1582. $parameterAnd = $calendarObjectIdQuery->expr()->andX();
  1583. $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
  1584. $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR)));
  1585. $searchOr->add($parameterAnd);
  1586. }
  1587. if ($calendarOr->count() === 0) {
  1588. return [];
  1589. }
  1590. if ($searchOr->count() === 0) {
  1591. return [];
  1592. }
  1593. $calendarObjectIdQuery->selectDistinct('cob.objectid')
  1594. ->from($this->dbObjectPropertiesTable, 'cob')
  1595. ->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
  1596. ->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
  1597. ->andWhere($calendarOr)
  1598. ->andWhere($searchOr);
  1599. if ('' !== $pattern) {
  1600. if (!$escapePattern) {
  1601. $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
  1602. } else {
  1603. $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
  1604. }
  1605. }
  1606. if (isset($options['limit'])) {
  1607. $calendarObjectIdQuery->setMaxResults($options['limit']);
  1608. }
  1609. if (isset($options['offset'])) {
  1610. $calendarObjectIdQuery->setFirstResult($options['offset']);
  1611. }
  1612. $result = $calendarObjectIdQuery->execute();
  1613. $matches = $result->fetchAll();
  1614. $result->closeCursor();
  1615. $matches = array_map(static function (array $match):int {
  1616. return (int) $match['objectid'];
  1617. }, $matches);
  1618. $query = $this->db->getQueryBuilder();
  1619. $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
  1620. ->from('calendarobjects')
  1621. ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
  1622. $result = $query->execute();
  1623. $calendarObjects = $result->fetchAll();
  1624. $result->closeCursor();
  1625. return array_map(function (array $array): array {
  1626. $array['calendarid'] = (int)$array['calendarid'];
  1627. $array['calendartype'] = (int)$array['calendartype'];
  1628. $array['calendardata'] = $this->readBlob($array['calendardata']);
  1629. return $array;
  1630. }, $calendarObjects);
  1631. }
  1632. /**
  1633. * Searches through all of a users calendars and calendar objects to find
  1634. * an object with a specific UID.
  1635. *
  1636. * This method should return the path to this object, relative to the
  1637. * calendar home, so this path usually only contains two parts:
  1638. *
  1639. * calendarpath/objectpath.ics
  1640. *
  1641. * If the uid is not found, return null.
  1642. *
  1643. * This method should only consider * objects that the principal owns, so
  1644. * any calendars owned by other principals that also appear in this
  1645. * collection should be ignored.
  1646. *
  1647. * @param string $principalUri
  1648. * @param string $uid
  1649. * @return string|null
  1650. */
  1651. public function getCalendarObjectByUID($principalUri, $uid) {
  1652. $query = $this->db->getQueryBuilder();
  1653. $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
  1654. ->from('calendarobjects', 'co')
  1655. ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
  1656. ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
  1657. ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
  1658. $stmt = $query->execute();
  1659. if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  1660. return $row['calendaruri'] . '/' . $row['objecturi'];
  1661. }
  1662. return null;
  1663. }
  1664. /**
  1665. * The getChanges method returns all the changes that have happened, since
  1666. * the specified syncToken in the specified calendar.
  1667. *
  1668. * This function should return an array, such as the following:
  1669. *
  1670. * [
  1671. * 'syncToken' => 'The current synctoken',
  1672. * 'added' => [
  1673. * 'new.txt',
  1674. * ],
  1675. * 'modified' => [
  1676. * 'modified.txt',
  1677. * ],
  1678. * 'deleted' => [
  1679. * 'foo.php.bak',
  1680. * 'old.txt'
  1681. * ]
  1682. * );
  1683. *
  1684. * The returned syncToken property should reflect the *current* syncToken
  1685. * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
  1686. * property This is * needed here too, to ensure the operation is atomic.
  1687. *
  1688. * If the $syncToken argument is specified as null, this is an initial
  1689. * sync, and all members should be reported.
  1690. *
  1691. * The modified property is an array of nodenames that have changed since
  1692. * the last token.
  1693. *
  1694. * The deleted property is an array with nodenames, that have been deleted
  1695. * from collection.
  1696. *
  1697. * The $syncLevel argument is basically the 'depth' of the report. If it's
  1698. * 1, you only have to report changes that happened only directly in
  1699. * immediate descendants. If it's 2, it should also include changes from
  1700. * the nodes below the child collections. (grandchildren)
  1701. *
  1702. * The $limit argument allows a client to specify how many results should
  1703. * be returned at most. If the limit is not specified, it should be treated
  1704. * as infinite.
  1705. *
  1706. * If the limit (infinite or not) is higher than you're willing to return,
  1707. * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
  1708. *
  1709. * If the syncToken is expired (due to data cleanup) or unknown, you must
  1710. * return null.
  1711. *
  1712. * The limit is 'suggestive'. You are free to ignore it.
  1713. *
  1714. * @param string $calendarId
  1715. * @param string $syncToken
  1716. * @param int $syncLevel
  1717. * @param int $limit
  1718. * @param int $calendarType
  1719. * @return array
  1720. */
  1721. public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  1722. // Current synctoken
  1723. $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
  1724. $stmt->execute([ $calendarId ]);
  1725. $currentToken = $stmt->fetchColumn(0);
  1726. if (is_null($currentToken)) {
  1727. return null;
  1728. }
  1729. $result = [
  1730. 'syncToken' => $currentToken,
  1731. 'added' => [],
  1732. 'modified' => [],
  1733. 'deleted' => [],
  1734. ];
  1735. if ($syncToken) {
  1736. $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? AND `calendartype` = ? ORDER BY `synctoken`";
  1737. if ($limit > 0) {
  1738. $query .= " LIMIT " . (int)$limit;
  1739. }
  1740. // Fetching all changes
  1741. $stmt = $this->db->prepare($query);
  1742. $stmt->execute([$syncToken, $currentToken, $calendarId, $calendarType]);
  1743. $changes = [];
  1744. // This loop ensures that any duplicates are overwritten, only the
  1745. // last change on a node is relevant.
  1746. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  1747. $changes[$row['uri']] = $row['operation'];
  1748. }
  1749. foreach ($changes as $uri => $operation) {
  1750. switch ($operation) {
  1751. case 1:
  1752. $result['added'][] = $uri;
  1753. break;
  1754. case 2:
  1755. $result['modified'][] = $uri;
  1756. break;
  1757. case 3:
  1758. $result['deleted'][] = $uri;
  1759. break;
  1760. }
  1761. }
  1762. } else {
  1763. // No synctoken supplied, this is the initial sync.
  1764. $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?";
  1765. $stmt = $this->db->prepare($query);
  1766. $stmt->execute([$calendarId, $calendarType]);
  1767. $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
  1768. }
  1769. return $result;
  1770. }
  1771. /**
  1772. * Returns a list of subscriptions for a principal.
  1773. *
  1774. * Every subscription is an array with the following keys:
  1775. * * id, a unique id that will be used by other functions to modify the
  1776. * subscription. This can be the same as the uri or a database key.
  1777. * * uri. This is just the 'base uri' or 'filename' of the subscription.
  1778. * * principaluri. The owner of the subscription. Almost always the same as
  1779. * principalUri passed to this method.
  1780. *
  1781. * Furthermore, all the subscription info must be returned too:
  1782. *
  1783. * 1. {DAV:}displayname
  1784. * 2. {http://apple.com/ns/ical/}refreshrate
  1785. * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
  1786. * should not be stripped).
  1787. * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
  1788. * should not be stripped).
  1789. * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
  1790. * attachments should not be stripped).
  1791. * 6. {http://calendarserver.org/ns/}source (Must be a
  1792. * Sabre\DAV\Property\Href).
  1793. * 7. {http://apple.com/ns/ical/}calendar-color
  1794. * 8. {http://apple.com/ns/ical/}calendar-order
  1795. * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
  1796. * (should just be an instance of
  1797. * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
  1798. * default components).
  1799. *
  1800. * @param string $principalUri
  1801. * @return array
  1802. */
  1803. public function getSubscriptionsForUser($principalUri) {
  1804. $fields = array_values($this->subscriptionPropertyMap);
  1805. $fields[] = 'id';
  1806. $fields[] = 'uri';
  1807. $fields[] = 'source';
  1808. $fields[] = 'principaluri';
  1809. $fields[] = 'lastmodified';
  1810. $fields[] = 'synctoken';
  1811. $query = $this->db->getQueryBuilder();
  1812. $query->select($fields)
  1813. ->from('calendarsubscriptions')
  1814. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
  1815. ->orderBy('calendarorder', 'asc');
  1816. $stmt = $query->execute();
  1817. $subscriptions = [];
  1818. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  1819. $subscription = [
  1820. 'id' => $row['id'],
  1821. 'uri' => $row['uri'],
  1822. 'principaluri' => $row['principaluri'],
  1823. 'source' => $row['source'],
  1824. 'lastmodified' => $row['lastmodified'],
  1825. '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
  1826. '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
  1827. ];
  1828. foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
  1829. if (!is_null($row[$dbName])) {
  1830. $subscription[$xmlName] = $row[$dbName];
  1831. }
  1832. }
  1833. $subscriptions[] = $subscription;
  1834. }
  1835. return $subscriptions;
  1836. }
  1837. /**
  1838. * Creates a new subscription for a principal.
  1839. *
  1840. * If the creation was a success, an id must be returned that can be used to reference
  1841. * this subscription in other methods, such as updateSubscription.
  1842. *
  1843. * @param string $principalUri
  1844. * @param string $uri
  1845. * @param array $properties
  1846. * @return mixed
  1847. */
  1848. public function createSubscription($principalUri, $uri, array $properties) {
  1849. if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
  1850. throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
  1851. }
  1852. $values = [
  1853. 'principaluri' => $principalUri,
  1854. 'uri' => $uri,
  1855. 'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
  1856. 'lastmodified' => time(),
  1857. ];
  1858. $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
  1859. foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
  1860. if (array_key_exists($xmlName, $properties)) {
  1861. $values[$dbName] = $properties[$xmlName];
  1862. if (in_array($dbName, $propertiesBoolean)) {
  1863. $values[$dbName] = true;
  1864. }
  1865. }
  1866. }
  1867. $valuesToInsert = [];
  1868. $query = $this->db->getQueryBuilder();
  1869. foreach (array_keys($values) as $name) {
  1870. $valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
  1871. }
  1872. $query->insert('calendarsubscriptions')
  1873. ->values($valuesToInsert)
  1874. ->execute();
  1875. $subscriptionId = $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
  1876. $subscriptionRow = $this->getSubscriptionById($subscriptionId);
  1877. $this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent((int)$subscriptionId, $subscriptionRow));
  1878. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
  1879. '\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
  1880. [
  1881. 'subscriptionId' => $subscriptionId,
  1882. 'subscriptionData' => $subscriptionRow,
  1883. ]));
  1884. return $subscriptionId;
  1885. }
  1886. /**
  1887. * Updates a subscription
  1888. *
  1889. * The list of mutations is stored in a Sabre\DAV\PropPatch object.
  1890. * To do the actual updates, you must tell this object which properties
  1891. * you're going to process with the handle() method.
  1892. *
  1893. * Calling the handle method is like telling the PropPatch object "I
  1894. * promise I can handle updating this property".
  1895. *
  1896. * Read the PropPatch documentation for more info and examples.
  1897. *
  1898. * @param mixed $subscriptionId
  1899. * @param PropPatch $propPatch
  1900. * @return void
  1901. */
  1902. public function updateSubscription($subscriptionId, PropPatch $propPatch) {
  1903. $supportedProperties = array_keys($this->subscriptionPropertyMap);
  1904. $supportedProperties[] = '{http://calendarserver.org/ns/}source';
  1905. $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
  1906. $newValues = [];
  1907. foreach ($mutations as $propertyName => $propertyValue) {
  1908. if ($propertyName === '{http://calendarserver.org/ns/}source') {
  1909. $newValues['source'] = $propertyValue->getHref();
  1910. } else {
  1911. $fieldName = $this->subscriptionPropertyMap[$propertyName];
  1912. $newValues[$fieldName] = $propertyValue;
  1913. }
  1914. }
  1915. $query = $this->db->getQueryBuilder();
  1916. $query->update('calendarsubscriptions')
  1917. ->set('lastmodified', $query->createNamedParameter(time()));
  1918. foreach ($newValues as $fieldName => $value) {
  1919. $query->set($fieldName, $query->createNamedParameter($value));
  1920. }
  1921. $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
  1922. ->execute();
  1923. $subscriptionRow = $this->getSubscriptionById($subscriptionId);
  1924. $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
  1925. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
  1926. '\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
  1927. [
  1928. 'subscriptionId' => $subscriptionId,
  1929. 'subscriptionData' => $subscriptionRow,
  1930. 'propertyMutations' => $mutations,
  1931. ]));
  1932. return true;
  1933. });
  1934. }
  1935. /**
  1936. * Deletes a subscription.
  1937. *
  1938. * @param mixed $subscriptionId
  1939. * @return void
  1940. */
  1941. public function deleteSubscription($subscriptionId) {
  1942. $subscriptionRow = $this->getSubscriptionById($subscriptionId);
  1943. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
  1944. '\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
  1945. [
  1946. 'subscriptionId' => $subscriptionId,
  1947. 'subscriptionData' => $this->getSubscriptionById($subscriptionId),
  1948. ]));
  1949. $query = $this->db->getQueryBuilder();
  1950. $query->delete('calendarsubscriptions')
  1951. ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
  1952. ->execute();
  1953. $query = $this->db->getQueryBuilder();
  1954. $query->delete('calendarobjects')
  1955. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  1956. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
  1957. ->execute();
  1958. $query->delete('calendarchanges')
  1959. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  1960. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
  1961. ->execute();
  1962. $query->delete($this->dbObjectPropertiesTable)
  1963. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  1964. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
  1965. ->execute();
  1966. if ($subscriptionRow) {
  1967. $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
  1968. }
  1969. }
  1970. /**
  1971. * Returns a single scheduling object for the inbox collection.
  1972. *
  1973. * The returned array should contain the following elements:
  1974. * * uri - A unique basename for the object. This will be used to
  1975. * construct a full uri.
  1976. * * calendardata - The iCalendar object
  1977. * * lastmodified - The last modification date. Can be an int for a unix
  1978. * timestamp, or a PHP DateTime object.
  1979. * * etag - A unique token that must change if the object changed.
  1980. * * size - The size of the object, in bytes.
  1981. *
  1982. * @param string $principalUri
  1983. * @param string $objectUri
  1984. * @return array
  1985. */
  1986. public function getSchedulingObject($principalUri, $objectUri) {
  1987. $query = $this->db->getQueryBuilder();
  1988. $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
  1989. ->from('schedulingobjects')
  1990. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
  1991. ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
  1992. ->execute();
  1993. $row = $stmt->fetch(\PDO::FETCH_ASSOC);
  1994. if (!$row) {
  1995. return null;
  1996. }
  1997. return [
  1998. 'uri' => $row['uri'],
  1999. 'calendardata' => $row['calendardata'],
  2000. 'lastmodified' => $row['lastmodified'],
  2001. 'etag' => '"' . $row['etag'] . '"',
  2002. 'size' => (int)$row['size'],
  2003. ];
  2004. }
  2005. /**
  2006. * Returns all scheduling objects for the inbox collection.
  2007. *
  2008. * These objects should be returned as an array. Every item in the array
  2009. * should follow the same structure as returned from getSchedulingObject.
  2010. *
  2011. * The main difference is that 'calendardata' is optional.
  2012. *
  2013. * @param string $principalUri
  2014. * @return array
  2015. */
  2016. public function getSchedulingObjects($principalUri) {
  2017. $query = $this->db->getQueryBuilder();
  2018. $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
  2019. ->from('schedulingobjects')
  2020. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
  2021. ->execute();
  2022. $result = [];
  2023. foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
  2024. $result[] = [
  2025. 'calendardata' => $row['calendardata'],
  2026. 'uri' => $row['uri'],
  2027. 'lastmodified' => $row['lastmodified'],
  2028. 'etag' => '"' . $row['etag'] . '"',
  2029. 'size' => (int)$row['size'],
  2030. ];
  2031. }
  2032. return $result;
  2033. }
  2034. /**
  2035. * Deletes a scheduling object from the inbox collection.
  2036. *
  2037. * @param string $principalUri
  2038. * @param string $objectUri
  2039. * @return void
  2040. */
  2041. public function deleteSchedulingObject($principalUri, $objectUri) {
  2042. $query = $this->db->getQueryBuilder();
  2043. $query->delete('schedulingobjects')
  2044. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
  2045. ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
  2046. ->execute();
  2047. }
  2048. /**
  2049. * Creates a new scheduling object. This should land in a users' inbox.
  2050. *
  2051. * @param string $principalUri
  2052. * @param string $objectUri
  2053. * @param string $objectData
  2054. * @return void
  2055. */
  2056. public function createSchedulingObject($principalUri, $objectUri, $objectData) {
  2057. $query = $this->db->getQueryBuilder();
  2058. $query->insert('schedulingobjects')
  2059. ->values([
  2060. 'principaluri' => $query->createNamedParameter($principalUri),
  2061. 'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
  2062. 'uri' => $query->createNamedParameter($objectUri),
  2063. 'lastmodified' => $query->createNamedParameter(time()),
  2064. 'etag' => $query->createNamedParameter(md5($objectData)),
  2065. 'size' => $query->createNamedParameter(strlen($objectData))
  2066. ])
  2067. ->execute();
  2068. }
  2069. /**
  2070. * Adds a change record to the calendarchanges table.
  2071. *
  2072. * @param mixed $calendarId
  2073. * @param string $objectUri
  2074. * @param int $operation 1 = add, 2 = modify, 3 = delete.
  2075. * @param int $calendarType
  2076. * @return void
  2077. */
  2078. protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  2079. $table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
  2080. $query = $this->db->getQueryBuilder();
  2081. $query->select('synctoken')
  2082. ->from($table)
  2083. ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
  2084. $syncToken = (int)$query->execute()->fetchColumn();
  2085. $query = $this->db->getQueryBuilder();
  2086. $query->insert('calendarchanges')
  2087. ->values([
  2088. 'uri' => $query->createNamedParameter($objectUri),
  2089. 'synctoken' => $query->createNamedParameter($syncToken),
  2090. 'calendarid' => $query->createNamedParameter($calendarId),
  2091. 'operation' => $query->createNamedParameter($operation),
  2092. 'calendartype' => $query->createNamedParameter($calendarType),
  2093. ])
  2094. ->execute();
  2095. $stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
  2096. $stmt->execute([
  2097. $calendarId
  2098. ]);
  2099. }
  2100. /**
  2101. * Parses some information from calendar objects, used for optimized
  2102. * calendar-queries.
  2103. *
  2104. * Returns an array with the following keys:
  2105. * * etag - An md5 checksum of the object without the quotes.
  2106. * * size - Size of the object in bytes
  2107. * * componentType - VEVENT, VTODO or VJOURNAL
  2108. * * firstOccurence
  2109. * * lastOccurence
  2110. * * uid - value of the UID property
  2111. *
  2112. * @param string $calendarData
  2113. * @return array
  2114. */
  2115. public function getDenormalizedData($calendarData) {
  2116. $vObject = Reader::read($calendarData);
  2117. $componentType = null;
  2118. $component = null;
  2119. $firstOccurrence = null;
  2120. $lastOccurrence = null;
  2121. $uid = null;
  2122. $classification = self::CLASSIFICATION_PUBLIC;
  2123. foreach ($vObject->getComponents() as $component) {
  2124. if ($component->name !== 'VTIMEZONE') {
  2125. $componentType = $component->name;
  2126. $uid = (string)$component->UID;
  2127. break;
  2128. }
  2129. }
  2130. if (!$componentType) {
  2131. throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
  2132. }
  2133. if ($componentType === 'VEVENT' && $component->DTSTART) {
  2134. $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
  2135. // Finding the last occurrence is a bit harder
  2136. if (!isset($component->RRULE)) {
  2137. if (isset($component->DTEND)) {
  2138. $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
  2139. } elseif (isset($component->DURATION)) {
  2140. $endDate = clone $component->DTSTART->getDateTime();
  2141. $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
  2142. $lastOccurrence = $endDate->getTimeStamp();
  2143. } elseif (!$component->DTSTART->hasTime()) {
  2144. $endDate = clone $component->DTSTART->getDateTime();
  2145. $endDate->modify('+1 day');
  2146. $lastOccurrence = $endDate->getTimeStamp();
  2147. } else {
  2148. $lastOccurrence = $firstOccurrence;
  2149. }
  2150. } else {
  2151. $it = new EventIterator($vObject, (string)$component->UID);
  2152. $maxDate = new DateTime(self::MAX_DATE);
  2153. if ($it->isInfinite()) {
  2154. $lastOccurrence = $maxDate->getTimestamp();
  2155. } else {
  2156. $end = $it->getDtEnd();
  2157. while ($it->valid() && $end < $maxDate) {
  2158. $end = $it->getDtEnd();
  2159. $it->next();
  2160. }
  2161. $lastOccurrence = $end->getTimestamp();
  2162. }
  2163. }
  2164. }
  2165. if ($component->CLASS) {
  2166. $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
  2167. switch ($component->CLASS->getValue()) {
  2168. case 'PUBLIC':
  2169. $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
  2170. break;
  2171. case 'CONFIDENTIAL':
  2172. $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
  2173. break;
  2174. }
  2175. }
  2176. return [
  2177. 'etag' => md5($calendarData),
  2178. 'size' => strlen($calendarData),
  2179. 'componentType' => $componentType,
  2180. 'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
  2181. 'lastOccurence' => $lastOccurrence,
  2182. 'uid' => $uid,
  2183. 'classification' => $classification
  2184. ];
  2185. }
  2186. /**
  2187. * @param $cardData
  2188. * @return bool|string
  2189. */
  2190. private function readBlob($cardData) {
  2191. if (is_resource($cardData)) {
  2192. return stream_get_contents($cardData);
  2193. }
  2194. return $cardData;
  2195. }
  2196. /**
  2197. * @param IShareable $shareable
  2198. * @param array $add
  2199. * @param array $remove
  2200. */
  2201. public function updateShares($shareable, $add, $remove) {
  2202. $calendarId = $shareable->getResourceId();
  2203. $calendarRow = $this->getCalendarById($calendarId);
  2204. $oldShares = $this->getShares($calendarId);
  2205. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
  2206. '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
  2207. [
  2208. 'calendarId' => $calendarId,
  2209. 'calendarData' => $calendarRow,
  2210. 'shares' => $oldShares,
  2211. 'add' => $add,
  2212. 'remove' => $remove,
  2213. ]));
  2214. $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
  2215. $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
  2216. }
  2217. /**
  2218. * @param int $resourceId
  2219. * @param int $calendarType
  2220. * @return array
  2221. */
  2222. public function getShares($resourceId, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  2223. return $this->calendarSharingBackend->getShares($resourceId);
  2224. }
  2225. /**
  2226. * @param boolean $value
  2227. * @param \OCA\DAV\CalDAV\Calendar $calendar
  2228. * @return string|null
  2229. */
  2230. public function setPublishStatus($value, $calendar) {
  2231. $calendarId = $calendar->getResourceId();
  2232. $calendarData = $this->getCalendarById($calendarId);
  2233. $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
  2234. '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
  2235. [
  2236. 'calendarId' => $calendarId,
  2237. 'calendarData' => $calendarData,
  2238. 'public' => $value,
  2239. ]));
  2240. $query = $this->db->getQueryBuilder();
  2241. if ($value) {
  2242. $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
  2243. $query->insert('dav_shares')
  2244. ->values([
  2245. 'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
  2246. 'type' => $query->createNamedParameter('calendar'),
  2247. 'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
  2248. 'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
  2249. 'publicuri' => $query->createNamedParameter($publicUri)
  2250. ]);
  2251. $query->execute();
  2252. $this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
  2253. return $publicUri;
  2254. }
  2255. $query->delete('dav_shares')
  2256. ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
  2257. ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
  2258. $query->execute();
  2259. $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
  2260. return null;
  2261. }
  2262. /**
  2263. * @param \OCA\DAV\CalDAV\Calendar $calendar
  2264. * @return mixed
  2265. */
  2266. public function getPublishStatus($calendar) {
  2267. $query = $this->db->getQueryBuilder();
  2268. $result = $query->select('publicuri')
  2269. ->from('dav_shares')
  2270. ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
  2271. ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
  2272. ->execute();
  2273. $row = $result->fetch();
  2274. $result->closeCursor();
  2275. return $row ? reset($row) : false;
  2276. }
  2277. /**
  2278. * @param int $resourceId
  2279. * @param array $acl
  2280. * @return array
  2281. */
  2282. public function applyShareAcl($resourceId, $acl) {
  2283. return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
  2284. }
  2285. /**
  2286. * update properties table
  2287. *
  2288. * @param int $calendarId
  2289. * @param string $objectUri
  2290. * @param string $calendarData
  2291. * @param int $calendarType
  2292. */
  2293. public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
  2294. $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
  2295. try {
  2296. $vCalendar = $this->readCalendarData($calendarData);
  2297. } catch (\Exception $ex) {
  2298. return;
  2299. }
  2300. $this->purgeProperties($calendarId, $objectId);
  2301. $query = $this->db->getQueryBuilder();
  2302. $query->insert($this->dbObjectPropertiesTable)
  2303. ->values(
  2304. [
  2305. 'calendarid' => $query->createNamedParameter($calendarId),
  2306. 'calendartype' => $query->createNamedParameter($calendarType),
  2307. 'objectid' => $query->createNamedParameter($objectId),
  2308. 'name' => $query->createParameter('name'),
  2309. 'parameter' => $query->createParameter('parameter'),
  2310. 'value' => $query->createParameter('value'),
  2311. ]
  2312. );
  2313. $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
  2314. foreach ($vCalendar->getComponents() as $component) {
  2315. if (!in_array($component->name, $indexComponents)) {
  2316. continue;
  2317. }
  2318. foreach ($component->children() as $property) {
  2319. if (in_array($property->name, self::$indexProperties)) {
  2320. $value = $property->getValue();
  2321. // is this a shitty db?
  2322. if (!$this->db->supports4ByteText()) {
  2323. $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
  2324. }
  2325. $value = mb_substr($value, 0, 254);
  2326. $query->setParameter('name', $property->name);
  2327. $query->setParameter('parameter', null);
  2328. $query->setParameter('value', $value);
  2329. $query->execute();
  2330. }
  2331. if (array_key_exists($property->name, self::$indexParameters)) {
  2332. $parameters = $property->parameters();
  2333. $indexedParametersForProperty = self::$indexParameters[$property->name];
  2334. foreach ($parameters as $key => $value) {
  2335. if (in_array($key, $indexedParametersForProperty)) {
  2336. // is this a shitty db?
  2337. if ($this->db->supports4ByteText()) {
  2338. $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
  2339. }
  2340. $query->setParameter('name', $property->name);
  2341. $query->setParameter('parameter', mb_substr($key, 0, 254));
  2342. $query->setParameter('value', mb_substr($value, 0, 254));
  2343. $query->execute();
  2344. }
  2345. }
  2346. }
  2347. }
  2348. }
  2349. }
  2350. /**
  2351. * deletes all birthday calendars
  2352. */
  2353. public function deleteAllBirthdayCalendars() {
  2354. $query = $this->db->getQueryBuilder();
  2355. $result = $query->select(['id'])->from('calendars')
  2356. ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
  2357. ->execute();
  2358. $ids = $result->fetchAll();
  2359. foreach ($ids as $id) {
  2360. $this->deleteCalendar($id['id']);
  2361. }
  2362. }
  2363. /**
  2364. * @param $subscriptionId
  2365. */
  2366. public function purgeAllCachedEventsForSubscription($subscriptionId) {
  2367. $query = $this->db->getQueryBuilder();
  2368. $query->select('uri')
  2369. ->from('calendarobjects')
  2370. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  2371. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
  2372. $stmt = $query->execute();
  2373. $uris = [];
  2374. foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
  2375. $uris[] = $row['uri'];
  2376. }
  2377. $stmt->closeCursor();
  2378. $query = $this->db->getQueryBuilder();
  2379. $query->delete('calendarobjects')
  2380. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  2381. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
  2382. ->execute();
  2383. $query->delete('calendarchanges')
  2384. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  2385. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
  2386. ->execute();
  2387. $query->delete($this->dbObjectPropertiesTable)
  2388. ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
  2389. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
  2390. ->execute();
  2391. foreach ($uris as $uri) {
  2392. $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
  2393. }
  2394. }
  2395. /**
  2396. * Move a calendar from one user to another
  2397. *
  2398. * @param string $uriName
  2399. * @param string $uriOrigin
  2400. * @param string $uriDestination
  2401. */
  2402. public function moveCalendar($uriName, $uriOrigin, $uriDestination) {
  2403. $query = $this->db->getQueryBuilder();
  2404. $query->update('calendars')
  2405. ->set('principaluri', $query->createNamedParameter($uriDestination))
  2406. ->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
  2407. ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
  2408. ->execute();
  2409. }
  2410. /**
  2411. * read VCalendar data into a VCalendar object
  2412. *
  2413. * @param string $objectData
  2414. * @return VCalendar
  2415. */
  2416. protected function readCalendarData($objectData) {
  2417. return Reader::read($objectData);
  2418. }
  2419. /**
  2420. * delete all properties from a given calendar object
  2421. *
  2422. * @param int $calendarId
  2423. * @param int $objectId
  2424. */
  2425. protected function purgeProperties($calendarId, $objectId) {
  2426. $query = $this->db->getQueryBuilder();
  2427. $query->delete($this->dbObjectPropertiesTable)
  2428. ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
  2429. ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
  2430. $query->execute();
  2431. }
  2432. /**
  2433. * get ID from a given calendar object
  2434. *
  2435. * @param int $calendarId
  2436. * @param string $uri
  2437. * @param int $calendarType
  2438. * @return int
  2439. */
  2440. protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
  2441. $query = $this->db->getQueryBuilder();
  2442. $query->select('id')
  2443. ->from('calendarobjects')
  2444. ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
  2445. ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
  2446. ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
  2447. $result = $query->execute();
  2448. $objectIds = $result->fetch();
  2449. $result->closeCursor();
  2450. if (!isset($objectIds['id'])) {
  2451. throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
  2452. }
  2453. return (int)$objectIds['id'];
  2454. }
  2455. /**
  2456. * return legacy endpoint principal name to new principal name
  2457. *
  2458. * @param $principalUri
  2459. * @param $toV2
  2460. * @return string
  2461. */
  2462. private function convertPrincipal($principalUri, $toV2) {
  2463. if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
  2464. list(, $name) = Uri\split($principalUri);
  2465. if ($toV2 === true) {
  2466. return "principals/users/$name";
  2467. }
  2468. return "principals/$name";
  2469. }
  2470. return $principalUri;
  2471. }
  2472. /**
  2473. * adds information about an owner to the calendar data
  2474. *
  2475. * @param $calendarInfo
  2476. */
  2477. private function addOwnerPrincipal(&$calendarInfo) {
  2478. $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
  2479. $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
  2480. if (isset($calendarInfo[$ownerPrincipalKey])) {
  2481. $uri = $calendarInfo[$ownerPrincipalKey];
  2482. } else {
  2483. $uri = $calendarInfo['principaluri'];
  2484. }
  2485. $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
  2486. if (isset($principalInformation['{DAV:}displayname'])) {
  2487. $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
  2488. }
  2489. }
  2490. }