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