link grauphel 0.6.3 download
[grauphel.git] / lib / notestorage.php
1 <?php
2 /**
3  * Part of grauphel
4  *
5  * PHP version 5
6  *
7  * @category  Tools
8  * @package   Grauphel
9  * @author    Christian Weiske <cweiske@cweiske.de>
10  * @copyright 2014 Christian Weiske
11  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12  * @link      http://cweiske.de/grauphel.htm
13  */
14 namespace OCA\Grauphel\Lib;
15
16 /**
17  * Flat file storage for notes
18  *
19  * @category  Tools
20  * @package   Grauphel
21  * @author    Christian Weiske <cweiske@cweiske.de>
22  * @copyright 2014 Christian Weiske
23  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
24  * @version   Release: @package_version@
25  * @link      http://cweiske.de/grauphel.htm
26  */
27 class NoteStorage
28 {
29     /**
30      * @var \OCP\IDBConnection
31      */
32     protected $db;
33
34     protected $urlGen;
35     protected $username;
36
37     public function __construct($urlGen)
38     {
39         $this->urlGen = $urlGen;
40         $this->db     = \OC::$server->getDatabaseConnection();
41     }
42
43     public function setUsername($username)
44     {
45         $this->username = $username;
46     }
47
48     /**
49      * Create a new sync data object for fresh users.
50      * Used by loadSyncData()
51      *
52      * @return SyncData New synchronization statistics
53      */
54     protected function getNewSyncData()
55     {
56         $syncdata = new SyncData();
57         $syncdata->initNew($this->username);
58         return $syncdata;
59     }
60
61     public function getTags()
62     {
63         $result = $this->db->executeQuery(
64             'SELECT `note_tags` FROM `*PREFIX*grauphel_notes`'
65             . ' WHERE note_user = ?',
66             array($this->username)
67         );
68
69         $tags = array();
70         while ($row = $result->fetch()) {
71             $tags = array_merge($tags, json_decode($row['note_tags']));
72         }
73         return array_unique($tags);
74     }
75
76     /**
77      * Updates the given $note object with data from $noteUpdate.
78      * Sets the last-sync-revision to $syncRevision
79      *
80      * @param object  $note         Original note object
81      * @param object  $noteUpdate   Update note object from a PUT to the API
82      * @param integer $syncRevision Current sync revision number
83      *
84      * @return void
85      */
86     public function update($note, $noteUpdate, $syncRevision)
87     {
88         static $updateFields = array(
89             'create-date',
90             'last-change-date',
91             'last-metadata-change-date',
92             'note-content',
93             'note-content-version',
94             'open-on-startup',
95             'pinned',
96             'tags',
97             'title',
98         );
99
100         $changed = array();
101         foreach ($updateFields as $field) {
102             $changed[$field] = false;
103             if (isset($noteUpdate->$field)) {
104                 if ($note->$field != $noteUpdate->$field) {
105                     $note->$field = $noteUpdate->$field;
106                     $changed[$field] = true;
107                 }
108             }
109         }
110
111         if (!isset($noteUpdate->{'last-change-date'})
112             && ($changed['title'] || $changed['note-content'])
113         ) {
114             //no idea how to get the microseconds in there
115             $note->{'last-change-date'} = date('c');
116         }
117
118         if (!isset($noteUpdate->{'last-metadata-change-date'})) {
119             //no idea how to get the microseconds in there
120             $note->{'last-metadata-change-date'} = date('c');
121         }
122
123         if (isset($noteUpdate->{'node-content'})
124             && $note->{'note-content-version'} == 0
125         ) {
126             $note->{'note-content-version'} = 0.3;
127         }
128
129         $note->{'last-sync-revision'} = $syncRevision;
130     }
131
132     /**
133      * Loads synchronization data for the given user.
134      * Creates fresh sync data if there are none for the user.
135      *
136      * @return SyncData Synchronization statistics (revision, sync guid)
137      */
138     public function loadSyncData()
139     {
140         $row = $this->db->executeQuery(
141             'SELECT * FROM `*PREFIX*grauphel_syncdata`'
142             . ' WHERE `syncdata_user` = ?',
143             array($this->username)
144         )->fetch();
145
146         if ($row === false) {
147             $syncdata = $this->getNewSyncData();
148             $this->saveSyncData($syncdata);
149         } else {
150             $syncdata = new SyncData();
151             $syncdata->latestSyncRevision = (int) $row['syncdata_latest_sync_revision'];
152             $syncdata->currentSyncGuid    = $row['syncdata_current_sync_guid'];
153         }
154
155         return $syncdata;
156     }
157
158     /**
159      * Save synchronization data for the given user.
160      *
161      * @param SyncData $syncdata Synchronization data object
162      *
163      * @return void
164      */
165     public function saveSyncData(SyncData $syncdata)
166     {
167         $row = $this->db->executeQuery(
168             'SELECT * FROM `*PREFIX*grauphel_syncdata`'
169             . ' WHERE `syncdata_user` = ?',
170             array($this->username)
171         )->fetch();
172
173         if ($row === false) {
174             //INSERT
175             $sql = 'INSERT INTO `*PREFIX*grauphel_syncdata`'
176                 . '(`syncdata_user`, `syncdata_latest_sync_revision`, `syncdata_current_sync_guid`)'
177                 . ' VALUES(?, ?, ?)';
178             $params = array(
179                 $this->username,
180                 $syncdata->latestSyncRevision,
181                 $syncdata->currentSyncGuid
182             );
183         } else {
184             //UPDATE
185             $data = array(
186                 'syncdata_latest_sync_revision' => $syncdata->latestSyncRevision,
187                 'syncdata_current_sync_guid'    => $syncdata->currentSyncGuid,
188             );
189             $sql = 'UPDATE `*PREFIX*grauphel_syncdata` SET'
190                 . ' `' . implode('` = ?, `', array_keys($data)) . '` = ?'
191                 . ' WHERE `syncdata_user` = ?';
192             $params = array_values($data);
193             $params[] = $this->username;
194         }
195         $this->db->executeQuery($sql, $params);
196     }
197
198     /**
199      * Delete synchronization data for the given user.
200      *
201      * @param SyncData $syncdata Synchronization data object
202      *
203      * @return void
204      */
205     public function deleteSyncData()
206     {
207         $this->db->executeQuery(
208             'DELETE FROM `*PREFIX*grauphel_syncdata`'
209             . ' WHERE `syncdata_user` = ?',
210             array($this->username)
211         );
212     }
213
214     /**
215      * Load a note from the storage.
216      *
217      * @param string  $guid      Note identifier
218      * @param boolean $createNew Create a new note if it does not exist
219      *
220      * @return object Note object, NULL if !$createNew and note does not exist
221      */
222     public function load($guid, $createNew = true)
223     {
224         $row = $this->db->executeQuery(
225             'SELECT * FROM `*PREFIX*grauphel_notes`'
226             . ' WHERE `note_user` = ? AND `note_guid` = ?',
227             array($this->username, $guid)
228         )->fetch();
229
230         if ($row === false) {
231             if (!$createNew) {
232                 return null;
233             }
234             return (object) array(
235                 'guid' => $guid,
236
237                 'create-date'               => null,
238                 'last-change-date'          => null,
239                 'last-metadata-change-date' => null,
240
241                 'title'                => null,
242                 'note-content'         => null,
243                 'note-content-version' => 0.3,
244
245                 'open-on-startup' => false,
246                 'pinned'          => false,
247                 'tags'            => array(),
248             );
249         }
250
251         return $this->noteFromRow($row);
252     }
253
254     /**
255      * Load a GUID of a note by the note title.
256      *
257      * The note title is stored html-escaped in the database because we
258      * get it that way from tomboy. Thus we have to escape the search
259      * input, too.
260      *
261      * @param string $title Note title.
262      *
263      * @return string GUID, NULL if note could not be found
264      */
265     public function loadGuidByTitle($title)
266     {
267         $row = $this->db->executeQuery(
268             'SELECT note_guid FROM `*PREFIX*grauphel_notes`'
269             . ' WHERE `note_user` = ? AND `note_title` = ?',
270             array($this->username, htmlspecialchars($title))
271         )->fetch();
272
273         if ($row === false) {
274             return null;
275         }
276
277         return $row['note_guid'];
278     }
279
280     /**
281      * Search for a note
282      *
283      * @param array $keywords arrays of query strings within keys AND and NOT
284      *
285      * @return array Database rows with note_guid and note_title
286      */
287     public function search($keywordGroups)
288     {
289         if (!isset($keywordGroups['AND'])) {
290             $keywordGroups['AND'] = array();
291         }
292         if (!isset($keywordGroups['NOT'])) {
293             $keywordGroups['NOT'] = array();
294         }
295
296         $sqlTplAnd = ' AND (note_title ILIKE ? OR note_tags ILIKE ? OR note_content ILIKE ?)';
297         $sqlTplNot = ' AND NOT (note_title ILIKE ? OR note_tags ILIKE ? OR note_content ILIKE ?)';
298         $arData = array(
299             $this->username
300         );
301         foreach (array('AND', 'NOT') as $group) {
302             $keywords = $keywordGroups[$group];
303             foreach ($keywords as $keyword) {
304                 $arData[] = '%' . $keyword . '%';//title
305                 $arData[] = '%' . $keyword . '%';//tags
306                 $arData[] = '%' . $keyword . '%';//content
307             }
308         }
309
310         $result = $this->db->executeQuery(
311             'SELECT `note_guid`, `note_title`'
312             . ' FROM `*PREFIX*grauphel_notes`'
313             . ' WHERE note_user = ?'
314             . str_repeat($sqlTplAnd, count($keywordGroups['AND']))
315             . str_repeat($sqlTplNot, count($keywordGroups['NOT'])),
316             $arData
317         );
318
319         $notes = array();
320         while ($row = $result->fetch()) {
321             $notes[] = $row;
322         }
323         return $notes;
324     }
325
326     /**
327      * Save a note into storage.
328      *
329      * @param object $note Note to save
330      *
331      * @return void
332      */
333     public function save($note)
334     {
335         $row = $this->db->executeQuery(
336             'SELECT * FROM `*PREFIX*grauphel_notes`'
337             . ' WHERE `note_user` = ? AND `note_guid` = ?',
338             array($this->username, $note->guid)
339         )->fetch();
340
341         $data = $this->rowFromNote($note);
342         if ($row === false) {
343             //INSERT
344             $data['note_user'] = $this->username;
345             $sql = 'INSERT INTO `*PREFIX*grauphel_notes`'
346                 . ' (`' . implode('`, `', array_keys($data)) . '`)'
347                 . ' VALUES(' . implode(', ', array_fill(0, count($data), '?')) . ')';
348             $params = array_values($data);
349         } else {
350             //UPDATE
351             $sql = 'UPDATE `*PREFIX*grauphel_notes` SET '
352                 . '`' . implode('` = ?, `', array_keys($data)) . '` = ?'
353                 . ' WHERE `note_user` = ? AND `note_guid` = ?';
354             $params = array_values($data);
355             $params[] = $this->username;
356             $params[] = $note->guid;
357         }
358         $this->db->executeQuery($sql, $params);
359     }
360
361     /**
362      * Delete a note from storage.
363      *
364      * @param object $guid ID of the note
365      *
366      * @return void
367      */
368     public function delete($guid)
369     {
370         $this->db->executeQuery(
371             'DELETE FROM `*PREFIX*grauphel_notes`'
372             . ' WHERE `note_user` = ? AND `note_guid` = ?',
373             array($this->username, $guid)
374         );
375     }
376
377     /**
378      * Delete all notes from storage.
379      *
380      * @return void
381      */
382     public function deleteAll()
383     {
384         $this->db->executeQuery(
385             'DELETE FROM `*PREFIX*grauphel_notes`'
386             . ' WHERE `note_user` = ?',
387             array($this->username)
388         );
389     }
390
391     /**
392      * Load notes for the given user in short form.
393      * Optionally only those changed after $since revision
394      *
395      * @param integer $since       Revision number after which the notes changed
396      * @param string  $rawtag      Filter by tag. Special tags:
397      *                             - grauphel:special:all
398      *                             - grauphel:special:untagged
399      * @param boolean $includeDate Load the last modification date or not
400      *
401      * @return array Array of short note objects
402      */
403     public function loadNotesOverview(
404         $since = null, $rawtag = null, $includeDate = false
405     ) {
406         $sql = 'SELECT `note_guid`, `note_title`'
407             . ', `note_last_sync_revision`, `note_tags`'
408             . ', `note_last_change_date`'
409             . ' FROM `*PREFIX*grauphel_notes`'
410             . ' WHERE note_user = ?';
411         $sqlData = array($this->username);
412
413         if ($since !== null) {
414             $sqlData[] = $since;
415             $sql .= ' AND note_last_sync_revision > ?';
416         }
417
418         if ($rawtag == 'grauphel:special:all') {
419             $rawtag = null;
420         } else if ($rawtag == 'grauphel:special:untagged') {
421             $jsRawtag = json_encode(array());
422         } else {
423             $jsRawtag = json_encode($rawtag);
424         }
425         if ($rawtag !== null) {
426             $sqlData[] = '%' . $jsRawtag . '%';
427             $sql .= ' AND note_tags LIKE ?';
428         }
429
430         $result = $this->db->executeQuery($sql, $sqlData);
431         $notes = array();
432         while ($row = $result->fetch()) {
433             $note = array(
434                 'guid' => $row['note_guid'],
435                 'ref'  => array(
436                     'api-ref' => $this->urlGen->getAbsoluteURL(
437                         $this->urlGen->linkToRoute(
438                             'grauphel.api.note',
439                             array(
440                                 'username' => $this->username,
441                                 'guid' => $row['note_guid']
442                             )
443                         )
444                     ),
445                     'href' => $this->urlGen->getAbsoluteURL(
446                         $this->urlGen->linkToRoute(
447                             'grauphel.gui.note',
448                             array(
449                                 'guid' => $row['note_guid']
450                             )
451                         )
452                     ),
453                 ),
454                 'title' => $row['note_title'],
455             );
456             if ($includeDate) {
457                 $note['last-change-date'] = $row['note_last_change_date'];
458             }
459             $notes[] = $note;
460         }
461
462         return $notes;
463     }
464
465     /**
466      * Load notes for the given user in full form.
467      * Optionally only those changed after $since revision
468      *
469      * @param integer $since Revision number after which the notes changed
470      *
471      * @return array Array of full note objects
472      */
473     public function loadNotesFull($since = null)
474     {
475         $result = $this->db->executeQuery(
476             'SELECT * FROM `*PREFIX*grauphel_notes`'
477             . ' WHERE note_user = ?',
478             array($this->username)
479         );
480
481         $notes = array();
482         while ($row = $result->fetch()) {
483             if ($since !== null && $row['note_last_sync_revision'] <= $since) {
484                 continue;
485             }
486             $notes[] = $this->noteFromRow($row);
487         }
488
489         return $notes;
490     }
491
492     protected function fixDate($date)
493     {
494         if (strlen($date) == 32) {
495             //Bug in grauphel 0.1.1; date fields in DB had only 32 instead of 33
496             // characters. The last digit of the time zone was missing
497             $date .= '0';
498         }
499         return $date;
500     }
501
502     protected function noteFromRow($row)
503     {
504         return (object) array(
505             'guid'  => $row['note_guid'],
506
507             'create-date'               => $this->fixDate($row['note_create_date']),
508             'last-change-date'          => $this->fixDate($row['note_last_change_date']),
509             'last-metadata-change-date' => $this->fixDate($row['note_last_metadata_change_date']),
510
511             'title'                => $row['note_title'],
512             'note-content'         => $row['note_content'],
513             'note-content-version' => $row['note_content_version'],
514
515             'open-on-startup' => (bool) $row['note_open_on_startup'],
516             'pinned'          => (bool) $row['note_pinned'],
517             'tags'            => json_decode($row['note_tags']),
518
519             'last-sync-revision' => (int) $row['note_last_sync_revision'],
520         );
521     }
522
523     protected function rowFromNote($note)
524     {
525         return array(
526             'note_guid'  => $note->guid,
527             'note_title' => (string) $note->title,
528
529             'note_content'         => (string) $note->{'note-content'},
530             'note_content_version' => (string) $note->{'note-content-version'},
531
532             'note_create_date'               => $note->{'create-date'},
533             'note_last_change_date'          => $note->{'last-change-date'},
534             'note_last_metadata_change_date' => $note->{'last-metadata-change-date'},
535
536             'note_open_on_startup' => (int) $note->{'open-on-startup'},
537             'note_pinned'          => (int) $note->pinned,
538             'note_tags'            => json_encode($note->tags),
539
540             'note_last_sync_revision' => $note->{'last-sync-revision'},
541         );
542     }
543 }
544 ?>