1<?php 2 3namespace Sabre\CalDAV\Backend; 4 5use Sabre\CalDAV; 6use Sabre\DAV; 7 8/** 9 * Simple PDO CalDAV backend. 10 * 11 * This class is basically the most minimum example to get a caldav backend up 12 * and running. This class uses the following schema (MySQL example): 13 * 14 * CREATE TABLE simple_calendars ( 15 * id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 16 * uri VARBINARY(200) NOT NULL, 17 * principaluri VARBINARY(200) NOT NULL 18 * ); 19 * 20 * CREATE TABLE simple_calendarobjects ( 21 * id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 22 * calendarid INT UNSIGNED NOT NULL, 23 * uri VARBINARY(200) NOT NULL, 24 * calendardata MEDIUMBLOB 25 * ) 26 * 27 * To make this class work, you absolutely need to have the PropertyStorage 28 * plugin enabled. 29 * 30 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). 31 * @author Evert Pot (http://evertpot.com/) 32 * @license http://sabre.io/license/ Modified BSD License 33 */ 34class SimplePDO extends AbstractBackend { 35 36 /** 37 * pdo 38 * 39 * @var \PDO 40 */ 41 protected $pdo; 42 43 /** 44 * Creates the backend 45 * 46 * @param \PDO $pdo 47 */ 48 function __construct(\PDO $pdo) { 49 50 $this->pdo = $pdo; 51 52 } 53 54 /** 55 * Returns a list of calendars for a principal. 56 * 57 * Every project is an array with the following keys: 58 * * id, a unique id that will be used by other functions to modify the 59 * calendar. This can be the same as the uri or a database key. 60 * * uri. This is just the 'base uri' or 'filename' of the calendar. 61 * * principaluri. The owner of the calendar. Almost always the same as 62 * principalUri passed to this method. 63 * 64 * Furthermore it can contain webdav properties in clark notation. A very 65 * common one is '{DAV:}displayname'. 66 * 67 * Many clients also require: 68 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set 69 * For this property, you can just return an instance of 70 * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet. 71 * 72 * If you return {http://sabredav.org/ns}read-only and set the value to 1, 73 * ACL will automatically be put in read-only mode. 74 * 75 * @param string $principalUri 76 * @return array 77 */ 78 function getCalendarsForUser($principalUri) { 79 80 // Making fields a comma-delimited list 81 $stmt = $this->pdo->prepare("SELECT id, uri FROM simple_calendars WHERE principaluri = ? ORDER BY id ASC"); 82 $stmt->execute([$principalUri]); 83 84 $calendars = []; 85 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { 86 87 $calendars[] = [ 88 'id' => $row['id'], 89 'uri' => $row['uri'], 90 'principaluri' => $principalUri, 91 ]; 92 93 } 94 95 return $calendars; 96 97 } 98 99 /** 100 * Creates a new calendar for a principal. 101 * 102 * If the creation was a success, an id must be returned that can be used 103 * to reference this calendar in other methods, such as updateCalendar. 104 * 105 * @param string $principalUri 106 * @param string $calendarUri 107 * @param array $properties 108 * @return string 109 */ 110 function createCalendar($principalUri, $calendarUri, array $properties) { 111 112 $stmt = $this->pdo->prepare("INSERT INTO simple_calendars (principaluri, uri) VALUES (?, ?)"); 113 $stmt->execute([$principalUri, $calendarUri]); 114 115 return $this->pdo->lastInsertId(); 116 117 } 118 119 /** 120 * Delete a calendar and all it's objects 121 * 122 * @param string $calendarId 123 * @return void 124 */ 125 function deleteCalendar($calendarId) { 126 127 $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ?'); 128 $stmt->execute([$calendarId]); 129 130 $stmt = $this->pdo->prepare('DELETE FROM simple_calendars WHERE id = ?'); 131 $stmt->execute([$calendarId]); 132 133 } 134 135 /** 136 * Returns all calendar objects within a calendar. 137 * 138 * Every item contains an array with the following keys: 139 * * calendardata - The iCalendar-compatible calendar data 140 * * uri - a unique key which will be used to construct the uri. This can 141 * be any arbitrary string, but making sure it ends with '.ics' is a 142 * good idea. This is only the basename, or filename, not the full 143 * path. 144 * * lastmodified - a timestamp of the last modification time 145 * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: 146 * ' "abcdef"') 147 * * size - The size of the calendar objects, in bytes. 148 * * component - optional, a string containing the type of object, such 149 * as 'vevent' or 'vtodo'. If specified, this will be used to populate 150 * the Content-Type header. 151 * 152 * Note that the etag is optional, but it's highly encouraged to return for 153 * speed reasons. 154 * 155 * The calendardata is also optional. If it's not returned 156 * 'getCalendarObject' will be called later, which *is* expected to return 157 * calendardata. 158 * 159 * If neither etag or size are specified, the calendardata will be 160 * used/fetched to determine these numbers. If both are specified the 161 * amount of times this is needed is reduced by a great degree. 162 * 163 * @param string $calendarId 164 * @return array 165 */ 166 function getCalendarObjects($calendarId) { 167 168 $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ?'); 169 $stmt->execute([$calendarId]); 170 171 $result = []; 172 foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { 173 $result[] = [ 174 'id' => $row['id'], 175 'uri' => $row['uri'], 176 'etag' => '"' . md5($row['calendardata']) . '"', 177 'calendarid' => $calendarId, 178 'size' => strlen($row['calendardata']), 179 'calendardata' => $row['calendardata'], 180 ]; 181 } 182 183 return $result; 184 185 } 186 187 /** 188 * Returns information from a single calendar object, based on it's object 189 * uri. 190 * 191 * The object uri is only the basename, or filename and not a full path. 192 * 193 * The returned array must have the same keys as getCalendarObjects. The 194 * 'calendardata' object is required here though, while it's not required 195 * for getCalendarObjects. 196 * 197 * This method must return null if the object did not exist. 198 * 199 * @param string $calendarId 200 * @param string $objectUri 201 * @return array|null 202 */ 203 function getCalendarObject($calendarId, $objectUri) { 204 205 $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?'); 206 $stmt->execute([$calendarId, $objectUri]); 207 $row = $stmt->fetch(\PDO::FETCH_ASSOC); 208 209 if (!$row) return null; 210 211 return [ 212 'id' => $row['id'], 213 'uri' => $row['uri'], 214 'etag' => '"' . md5($row['calendardata']) . '"', 215 'calendarid' => $calendarId, 216 'size' => strlen($row['calendardata']), 217 'calendardata' => $row['calendardata'], 218 ]; 219 220 } 221 222 /** 223 * Creates a new calendar object. 224 * 225 * The object uri is only the basename, or filename and not a full path. 226 * 227 * It is possible return an etag from this function, which will be used in 228 * the response to this PUT request. Note that the ETag must be surrounded 229 * by double-quotes. 230 * 231 * However, you should only really return this ETag if you don't mangle the 232 * calendar-data. If the result of a subsequent GET to this object is not 233 * the exact same as this request body, you should omit the ETag. 234 * 235 * @param mixed $calendarId 236 * @param string $objectUri 237 * @param string $calendarData 238 * @return string|null 239 */ 240 function createCalendarObject($calendarId, $objectUri, $calendarData) { 241 242 $stmt = $this->pdo->prepare('INSERT INTO simple_calendarobjects (calendarid, uri, calendardata) VALUES (?,?,?)'); 243 $stmt->execute([ 244 $calendarId, 245 $objectUri, 246 $calendarData 247 ]); 248 249 return '"' . md5($calendarData) . '"'; 250 251 } 252 253 /** 254 * Updates an existing calendarobject, based on it's uri. 255 * 256 * The object uri is only the basename, or filename and not a full path. 257 * 258 * It is possible return an etag from this function, which will be used in 259 * the response to this PUT request. Note that the ETag must be surrounded 260 * by double-quotes. 261 * 262 * However, you should only really return this ETag if you don't mangle the 263 * calendar-data. If the result of a subsequent GET to this object is not 264 * the exact same as this request body, you should omit the ETag. 265 * 266 * @param mixed $calendarId 267 * @param string $objectUri 268 * @param string $calendarData 269 * @return string|null 270 */ 271 function updateCalendarObject($calendarId, $objectUri, $calendarData) { 272 273 $stmt = $this->pdo->prepare('UPDATE simple_calendarobjects SET calendardata = ? WHERE calendarid = ? AND uri = ?'); 274 $stmt->execute([$calendarData, $calendarId, $objectUri]); 275 276 return '"' . md5($calendarData) . '"'; 277 278 } 279 280 /** 281 * Deletes an existing calendar object. 282 * 283 * The object uri is only the basename, or filename and not a full path. 284 * 285 * @param string $calendarId 286 * @param string $objectUri 287 * @return void 288 */ 289 function deleteCalendarObject($calendarId, $objectUri) { 290 291 $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?'); 292 $stmt->execute([$calendarId, $objectUri]); 293 294 } 295 296} 297