1: <?php
2: 3: 4:
5:
6: 7: 8: 9: 10: 11: 12: 13:
14: class Wicked_Driver_Sql extends Wicked_Driver
15: {
16: 17: 18: 19: 20:
21: protected $_db;
22:
23: 24: 25: 26: 27:
28: protected $_pageNames;
29:
30: 31: 32: 33: 34:
35: public function __construct($params = array())
36: {
37: if (!isset($params['db'])) {
38: throw new InvalidArgumentException('Missing db parameter.');
39: }
40: $this->_db = $params['db'];
41: unset($params['db']);
42:
43: $params = array_merge(array(
44: 'table' => 'wicked_pages',
45: 'historytable' => 'wicked_history',
46: 'attachmenttable' => 'wicked_attachments',
47: 'attachmenthistorytable' => 'wicked_attachment_history'
48: ), $params);
49: parent::__construct($params);
50: }
51:
52: 53: 54: 55: 56: 57: 58: 59:
60: public function retrieveByName($pagename)
61: {
62: $pages = $this->_retrieve(
63: $this->_params['table'],
64: array('page_name = ?', array($this->_convertToDriver($pagename))));
65:
66: if (!empty($pages[0])) {
67: return $pages[0];
68: }
69:
70: throw new Wicked_Exception($pagename . ' not found');
71: }
72:
73: 74: 75: 76: 77: 78: 79: 80: 81:
82: public function retrieveHistory($pagename, $version)
83: {
84: if (!preg_match('/^\d+$/', $version)) {
85: throw new Wicked_Exception('invalid version number');
86: }
87:
88: return $this->_retrieve(
89: $this->_params['historytable'],
90: array('page_name = ? AND page_version = ?',
91: array($this->_convertToDriver($pagename), (int)$version)));
92: }
93:
94: public function getPageById($id)
95: {
96: return $this->_retrieve($this->_params['table'],
97: array('page_id = ?', array((int)$id)));
98: }
99:
100: public function getAllPages()
101: {
102: return $this->_retrieve($this->_params['table'], '', 'page_name');
103: }
104:
105: public function getHistory($pagename)
106: {
107: return $this->_retrieve(
108: $this->_params['historytable'],
109: array('page_name = ?', array($this->_convertToDriver($pagename))),
110: 'page_version DESC');
111: }
112:
113: 114: 115: 116: 117: 118: 119: 120:
121: public function getRecentChanges($days = 3)
122: {
123: $where = array('version_created > ?', array(time() - (86400 * $days)));
124: $result = $this->_retrieve($this->_params['table'],
125: $where,
126: 'version_created DESC');
127: $result2 = $this->_retrieve($this->_params['historytable'],
128: $where,
129: 'version_created DESC');
130: return array_merge($result, $result2);
131: }
132:
133: 134: 135: 136: 137: 138: 139: 140:
141: public function mostPopular($limit = 10)
142: {
143: return $this->_retrieve($this->_params['table'], '',
144: 'page_hits DESC', $limit);
145: }
146:
147: 148: 149: 150: 151: 152: 153: 154:
155: public function leastPopular($limit = 10)
156: {
157: return $this->_retrieve($this->_params['table'], '',
158: 'page_hits ASC', $limit);
159: }
160:
161: public function searchTitles($searchtext)
162: {
163: $searchtext = $this->_convertToDriver($searchtext);
164: try {
165: $where = $this->_db->buildClause('page_name', 'LIKE', $searchtext);
166: } catch (Horde_Db_Exception $e) {
167: throw new Wicked_Exception($e);
168: }
169: return $this->_retrieve($this->_params['table'], $where);
170: }
171:
172: 173: 174: 175: 176: 177: 178: 179: 180:
181: public function searchText($searchtext, $title = true)
182: {
183: $searchtext = $this->_convertToDriver($searchtext);
184:
185: try {
186: $textClause = Horde_Db_SearchParser::parse('page_text', $searchtext);
187: } catch (Horde_Db_Exception $e) {
188: throw new Wicked_Exception($e);
189: }
190:
191: if ($title) {
192: try {
193: $nameClause = Horde_Db_SearchParser::parse('page_name', $searchtext);
194: } catch (Horde_Db_Exception $e) {
195: throw new Wicked_Exception($e);
196: }
197:
198: $where = '(' . $nameClause . ') OR (' . $textClause . ')';
199: } else {
200: $where = $textClause;
201: }
202:
203: return $this->_retrieve($this->_params['table'], $where);
204: }
205:
206: public function getBackLinks($pagename)
207: {
208: try {
209: $where = $this->_db->buildClause(
210: 'page_text', 'LIKE', $this->_convertToDriver($pagename));
211: } catch (Horde_Db_Exception $e) {
212: throw new Wicked_Exception($e);
213: }
214: $pages = $this->_retrieve($this->_params['table'], $where);
215:
216: 217:
218:
219: $patterns = array('/\(\(' . preg_quote($pagename, '/') . '(?:\|[^)]+)?\)\)/');
220: if (preg_match('/^' . Wicked::REGEXP_WIKIWORD . '$/', $pagename)) {
221: $patterns[] = '/\b' . preg_quote($pagename, '/') . '\b/';
222: }
223:
224: foreach ($pages as $key => $page) {
225: $match = false;
226: foreach ($patterns as $pattern) {
227: if (preg_match($pattern, $page['page_text'])) {
228: $match = true;
229: break;
230: }
231: }
232: if (!$match) {
233: unset($pages[$key]);
234: }
235: }
236:
237: return $pages;
238: }
239:
240: public function getMatchingPages($searchtext,
241: $matchType = Wicked_Page::MATCH_ANY)
242: {
243: $searchtext = strtolower($searchtext);
244:
245: try {
246:
247: if ($matchType == Wicked_Page::MATCH_ANY) {
248: return $this->_retrieve(
249: $this->_params['table'],
250: 'LOWER(page_name) LIKE ' . $this->_db->quote('%' . $searchtext . '%'));
251: }
252:
253: $clauses = array();
254: if ($matchType & Wicked_Page::MATCH_LEFT) {
255: $clauses[] = 'LOWER(page_name) LIKE ' . $this->_db->quote($searchtext . '%');
256: }
257: if ($matchType & Wicked_Page::MATCH_RIGHT) {
258: $clauses[] = 'LOWER(page_name) LIKE ' . $this->_db->quote('%' . $searchtext);
259: }
260: } catch (Horde_Db_Exception $e) {
261: throw new Wicked_Exception($e);
262: }
263:
264: if (!$clauses) {
265: return array();
266: }
267:
268: return $this->_retrieve($this->_params['table'],
269: implode(' OR ', $clauses));
270: }
271:
272: public function getLikePages($pagename)
273: {
274: if (Horde_String::isUpper($pagename, 'UTF-8')) {
275: $firstword = $pagename;
276: $lastword = null;
277: } else {
278:
279: $count = preg_match_all('/[A-Z][a-z]*/', $pagename, $matches);
280: if (!$count) {
281: return array();
282: }
283: $matches = $matches[0];
284:
285: $firstword = $matches[0];
286: $lastword = $matches[$count - 1];
287:
288: if (strlen($firstword) == 1 && strlen($matches[1]) == 1) {
289: for ($i = 1; $i < $count; $i++) {
290: $firstword .= $matches[$i];
291: if (isset($matches[$i + 1]) && strlen($matches[$i + 1]) > 1) {
292: break;
293: }
294: }
295: }
296:
297: if (strlen($lastword) == 1 && strlen($matches[$count - 2]) == 1) {
298: for ($i = $count - 2; $i > 0; $i--) {
299: $lastword = $matches[$i] . $lastword;
300: if (isset($matches[$i - 1]) && strlen($matches[$i - 1]) > 1) {
301: break;
302: }
303: }
304: }
305: }
306:
307: try {
308: $where = $this->_db->buildClause('page_name', 'LIKE', $firstword);
309: if (!empty($lastword) && $lastword != $firstword) {
310: $where .= ' OR ' . $this->_db->buildClause('page_name', 'LIKE', $lastword);
311: }
312: } catch (Horde_Db_Exception $e) {
313: throw new Wicked_Exception($e);
314: }
315:
316: return $this->_retrieve($this->_params['table'], $where);
317: }
318:
319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330:
331: public function getAttachedFiles($pageId, $allversions = false)
332: {
333: $where = array('page_id = ?', array((int)$pageId));
334: $data = $this->_retrieve($this->_params['attachmenttable'], $where);
335:
336: if ($allversions) {
337: $more_data = $this->_retrieve(
338: $this->_params['attachmenthistorytable'], $where);
339: $data = array_merge($data, $more_data);
340: }
341:
342: foreach (array_keys($data) as $key) {
343: $data[$key]['attachment_name'] = $this->_convertFromDriver($data[$key]['attachment_name']);
344: }
345: usort($data, array($this, '_getAttachedFiles_usort'));
346:
347: return $data;
348: }
349:
350: protected function _getAttachedFiles_usort($a, $b)
351: {
352: if ($res = strcmp($a['attachment_name'], $b['attachment_name'])) {
353: return $res;
354: }
355: return ($a['attachment_version'] - $b['attachment_version']);
356: }
357:
358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369:
370: public function removeAttachment($pageId, $attachment, $version = null)
371: {
372:
373: parent::removeAttachment($pageId, $attachment, $version);
374:
375:
376: $sql = 'DELETE FROM ' . $this->_params['attachmenttable'] .
377: ' WHERE page_id = ? AND attachment_name = ?';
378: $params = array((int)$pageId, $attachment);
379: if (!is_null($version)) {
380: $sql .= ' AND attachment_version = ?';
381: $params[] = (int)$version;
382: }
383:
384: try {
385: $this->_db->beginDbTransaction();
386: $result = $this->_db->delete($sql, $params);
387:
388: 389:
390: $sql = 'DELETE FROM ' . $this->_params['attachmenthistorytable'] .
391: ' WHERE page_id = ? AND attachment_name = ?';
392: if (!is_null($version)) {
393: $sql .= ' AND attachment_version = ?';
394: }
395: $this->_db->delete($sql, $params);
396: $this->_db->commitDbTransaction();
397: } catch (Horde_Db_Exception $e) {
398: $this->_db->rollbackDbTransaction();
399: throw new Wicked_Exception($e);
400: }
401: }
402:
403: 404: 405: 406: 407: 408: 409:
410: public function removeAllAttachments($pageId)
411: {
412:
413: $result = parent::removeAllAttachments($pageId);
414:
415: $params = array((int)$pageId);
416: try {
417: $this->_db->beginDbTransaction();
418:
419: $result = $this->_db->delete(
420: 'DELETE FROM ' . $this->_params['attachmenttable']
421: . ' WHERE page_id = ?',
422: $params);
423:
424: 425:
426: $this->_db->delete(
427: 'DELETE FROM ' . $this->_params['attachmenthistorytable']
428: . ' WHERE page_id = ?',
429: $params);
430: $this->_db->commitDbTransaction();
431: } catch (Horde_Db_Exception $e) {
432: $this->_db->rollbackDbTransaction();
433: throw new Wicked_Exception($e);
434: }
435: }
436:
437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447:
448: protected function _attachFile($file)
449: {
450: if ($file['change_author'] === false) {
451: $file['change_author'] = null;
452: }
453:
454: $attachments = $this->_retrieve(
455: $this->_params['attachmenttable'],
456: array('page_id = ? AND attachment_name = ?',
457: array((int)$file['page_id'], $file['attachment_name'])));
458:
459: if ($attachments) {
460: $version = $attachments[0]['attachment_version'] + 1;
461:
462: try {
463: $this->_db->beginDbTransaction();
464: $this->_db->insert(
465: sprintf('INSERT INTO %s (page_id, attachment_name, attachment_version, attachment_created, change_author, change_log) SELECT page_id, attachment_name, attachment_version, attachment_created, change_author, change_log FROM %s WHERE page_id = ? AND attachment_name = ?',
466: $this->_params['attachmenthistorytable'],
467: $this->_params['attachmenttable']),
468: array((int)$file['page_id'],
469: $file['attachment_name']));
470:
471: $this->_db->update(
472: sprintf('UPDATE %s SET attachment_version = ?, change_log = ?, change_author = ?, attachment_created = ? WHERE page_id = ? AND attachment_name = ?',
473: $this->_params['attachmenttable']),
474: array((int)$version,
475: $this->_convertToDriver($file['change_log']),
476: $this->_convertToDriver($file['change_author']),
477: time(),
478: (int)$file['page_id'],
479: $this->_convertToDriver($file['attachment_name'])));
480: $this->_db->commitDbTransaction();
481: } catch (Horde_Db_Exception $e) {
482: $this->_db->rollbackDbTransaction();
483: throw new Wicked_Exception($e);
484: }
485: } else {
486: $version = 1;
487: try {
488: $this->_db->insert(
489: sprintf('INSERT INTO %s (page_id, attachment_version, change_log, change_author, attachment_created, attachment_name) VALUES (?, 1, ?, ?, ?, ?)',
490: $this->_params['attachmenttable']),
491: array((int)$file['page_id'],
492: $this->_convertToDriver($file['change_log']),
493: $this->_convertToDriver($file['change_author']),
494: time(),
495: $this->_convertToDriver($file['attachment_name'])));
496: } catch (Horde_Db_Exception $e) {
497: throw new Wicked_Exception($e);
498: }
499: }
500:
501: return $version;
502: }
503:
504: 505: 506: 507: 508: 509: 510:
511: public function logPageView($pagename)
512: {
513: try {
514: return $this->_db->update(
515: 'UPDATE ' . $this->_params['table']
516: . ' SET page_hits = page_hits + 1 WHERE page_name = ?',
517: array($this->_convertToDriver($pagename)));
518: } catch (Horde_Db_Exception $e) {
519: throw new Wicked_Exception($e);
520: }
521: }
522:
523: 524: 525: 526: 527: 528: 529: 530:
531: public function logAttachmentDownload($pageid, $attachment)
532: {
533: try {
534: return $this->_db->update(
535: 'UPDATE ' . $this->_params['attachmenttable']
536: . ' SET attachment_hits = attachment_hits + 1'
537: . ' WHERE page_id = ? AND attachment_name = ?',
538: array((int)$pageid, $this->_convertToDriver($attachment)));
539: } catch (Horde_Db_Exception $e) {
540: throw new Wicked_Exception($e);
541: }
542: }
543:
544: 545: 546: 547: 548: 549: 550: 551:
552: public function newPage($pagename, $text)
553: {
554: if (!strlen($pagename)) {
555: throw new Wicked_Exception(_("Page name must not be empty"));
556: }
557:
558: if ($GLOBALS['browser']->isRobot()) {
559: throw new Wicked_Exception(_("Robots are not allowed to create pages"));
560: }
561:
562: $author = $GLOBALS['registry']->getAuth();
563: if ($author === false) {
564: $author = null;
565: }
566:
567:
568: try {
569: $page_id = $this->_db->insert(
570: 'INSERT INTO ' . $this->_params['table']
571: . ' (page_name, page_text, version_created, page_version,'
572: . ' page_hits, change_author) VALUES (?, ?, ?, 1, 0, ?)',
573: array($this->_convertToDriver($pagename),
574: $this->_convertToDriver($text),
575: time(),
576: $author));
577: } catch (Horde_Db_Exception $e) {
578: throw new Wicked_Exception($e);
579: }
580:
581:
582: $url = Wicked::url($pagename, true, -1);
583: Wicked::mail("Created page: $url\n\n$text\n",
584: array('Subject' => '[' . $GLOBALS['registry']->get('name')
585: . '] created: ' . $pagename));
586:
587: 588:
589: $this->getPages(true, true);
590:
591: return $page_id;
592: }
593:
594: 595: 596: 597: 598: 599: 600: 601:
602: public function renamePage($pagename, $newname)
603: {
604: try {
605: $this->_db->beginDbTransaction();
606: $this->_db->update(
607: 'UPDATE ' . $this->_params['table']
608: . ' SET page_name = ? WHERE page_name = ?',
609: array($this->_convertToDriver($newname),
610: $this->_convertToDriver($pagename)));
611:
612: $this->_db->update(
613: 'UPDATE ' . $this->_params['historytable']
614: . ' SET page_name = ? WHERE page_name = ?',
615: array($this->_convertToDriver($newname),
616: $this->_convertToDriver($pagename)));
617: $this->_db->commitDbTransaction();
618: } catch (Horde_Db_Exception $e) {
619: $this->_db->rollbackDbTransaction();
620: throw new Wicked_Exception($e);
621: }
622:
623: $changelog = sprintf(_("Renamed page from %s"), $pagename);
624: $newPage = $this->retrieveByName($newname);
625:
626: 627:
628: $this->getPages(true, true);
629:
630: return $this->updateText($newname, $newPage['page_text'], $changelog);
631: }
632:
633: public function updateText($pagename, $text, $changelog)
634: {
635: if (!$this->pageExists($pagename)) {
636: return $this->newPage($pagename, $text);
637: }
638:
639:
640: Horde::logMessage('Page ' . $pagename . ' saved with user agent ' . $GLOBALS['browser']->getAgentString(), 'DEBUG');
641:
642: $author = $GLOBALS['registry']->getAuth();
643: if ($author === false) {
644: $author = null;
645: }
646:
647: try {
648: $this->_db->beginDbTransaction();
649: $this->_db->insert(
650: sprintf('INSERT INTO %s (page_id, page_name, page_text, page_version, version_created, change_author, change_log) SELECT page_id, page_name, page_text, page_version, version_created, change_author, change_log FROM %s WHERE page_name = ?',
651: $this->_params['historytable'],
652: $this->_params['table']),
653: array($this->_convertToDriver($pagename)));
654:
655:
656: $this->_db->update(
657: 'UPDATE ' . $this->_params['table']
658: . ' SET change_author = ?, page_text = ?, change_log = ?,'
659: . ' version_created = ?, page_version = page_version + 1'
660: . ' WHERE page_name = ?',
661: array($author,
662: $this->_convertToDriver($text),
663: $this->_convertToDriver($changelog),
664: time(),
665: $this->_convertToDriver($pagename)));
666: $this->_db->commitDbTransaction();
667: } catch (Horde_Db_Exception $e) {
668: $this->_db->rollbackDbTransaction();
669: throw new Wicked_Exception($e);
670: }
671: }
672:
673: public function getPages($special = true, $no_cache = false)
674: {
675: if (!isset($this->_pageNames) || $no_cache) {
676: try {
677: $result = $this->_db->selectAssoc(
678: 'SELECT page_id, page_name FROM ' . $this->_params['table']);
679: } catch (Horde_Db_Exception $e) {
680: throw new Wicked_Exception($e);
681: }
682: $this->_pageNames = $this->_convertFromDriver($result);
683: }
684:
685: if ($special) {
686: $this->_pageNames += $this->getSpecialPages();
687: }
688:
689: return $this->_pageNames;
690: }
691:
692: 693:
694: public function removeVersion($pagename, $version)
695: {
696: $values = array($this->_convertToDriver($pagename), (int)$version);
697:
698:
699: try {
700: $result = $this->_db->selectValue(
701: 'SELECT 1 FROM ' . $this->_params['table']
702: . ' WHERE page_name = ? AND page_version = ?',
703: $values);
704: } catch (Horde_Db_Exception $e) {
705: $result = false;
706: }
707:
708: if (!$result) {
709: 710:
711: try {
712: $this->_db->delete(
713: 'DELETE FROM ' . $this->_params['historytable']
714: . ' WHERE page_name = ? and page_version = ?',
715: $values);
716: } catch (Horde_Db_Exception $e) {
717: throw new Wicked_Exception($e);
718: }
719: return;
720: }
721:
722: 723:
724: try {
725: $query = 'SELECT * FROM ' . $this->_params['historytable'] .
726: ' WHERE page_name = ? ORDER BY page_version DESC';
727: $query = $this->_db->addLimitOffset($query, array('limit' => 1));
728: $revision = $this->_db->selectOne(
729: $query, array($this->_convertToDriver($pagename)));
730:
731: 732:
733: $this->_db->beginDbTransaction();
734: $this->_db->update(
735: 'UPDATE ' . $this->_params['table'] . ' SET' .
736: ' page_text = ?, page_version = ?,' .
737: ' version_created = ?, change_author = ?, change_log = ?' .
738: ' WHERE page_name = ?',
739: array($revision['page_text'],
740: (int)$revision['page_version'],
741: (int)$revision['version_created'],
742: $revision['change_author'],
743: $revision['change_log'],
744: $this->_convertToDriver($pagename)));
745:
746: 747:
748: $this->_db->delete(
749: 'DELETE FROM ' . $this->_params['historytable'] .
750: ' WHERE page_name = ? and page_version = ?',
751: array($this->_convertToDriver($pagename),
752: (int)$revision['page_version']));
753: $this->_db->commitDbTransaction();
754: } catch (Horde_Db_Exception $e) {
755: $this->_db->rollbackDbTransaction();
756: throw new Wicked_Exception($e);
757: }
758: }
759:
760: 761:
762: public function removeAllVersions($pagename)
763: {
764:
765: parent::removeAllVersions($pagename);
766:
767: $this->_pageNames = null;
768:
769: try {
770: $this->_db->beginDbTransaction();
771: $this->_db->delete(
772: 'DELETE FROM ' . $this->_params['table']
773: . ' WHERE page_name = ?',
774: array($this->_convertToDriver($pagename)));
775:
776: $this->_db->delete(
777: 'DELETE FROM ' . $this->_params['historytable']
778: . ' WHERE page_name = ?',
779: array($this->_convertToDriver($pagename)));
780: $this->_db->commitDbTransaction();
781: } catch (Horde_Db_Exception $e) {
782: $this->_db->rollbackDbTransaction();
783: throw new Wicked_Exception($e);
784: }
785: }
786:
787: 788: 789: 790: 791: 792: 793: 794: 795: 796: 797: 798: 799: 800:
801: protected function _retrieve($table, $where, $orderBy = null, $limit = null)
802: {
803: $query = 'SELECT * FROM ' . $table;
804: $values = array();
805: if (!empty($where)) {
806: $query .= ' WHERE ';
807: if (is_array($where)) {
808: $query .= $where[0];
809: $values = $where[1];
810: } else {
811: $query .= $where;
812: }
813: }
814: if (!empty($orderBy)) {
815: $query .= ' ORDER BY ' . $orderBy;
816: }
817: if (!empty($limit)) {
818: try {
819: $query = $this->_db->addLimitOffset($query, array('limit' => $limit));
820: } catch (Horde_Db_Exception $e) {
821: throw new Wicked_Exception($e);
822: }
823: }
824:
825: try {
826: $result = $this->_db->select($query, $values);
827: } catch (Horde_Db_Exception $e) {
828: throw new Wicked_Exception($e);
829: }
830:
831: $pages = array();
832: foreach ($result as $row) {
833: if (isset($row['page_name'])) {
834: $row['page_name'] = $this->_convertFromDriver($row['page_name']);
835: }
836: if (isset($row['page_text'])) {
837: $row['page_text'] = $this->_convertFromDriver($row['page_text']);
838: }
839: if (isset($row['change_log'])) {
840: $row['change_log'] = $this->_convertFromDriver($row['change_log']);
841: }
842: $pages[] = $row;
843: }
844:
845: return $pages;
846: }
847:
848: 849: 850: 851: 852:
853: public function getCharset()
854: {
855: return $this->_db->getOption('charset');
856: }
857:
858: 859: 860: 861: 862: 863: 864:
865: protected function _convertFromDriver($value)
866: {
867: return Horde_String::convertCharset($value, $this->getCharset(), 'UTF-8');
868: }
869:
870: 871: 872: 873: 874: 875: 876:
877: protected function _convertToDriver($value)
878: {
879: return Horde_String::convertCharset($value, 'UTF-8', $this->getCharset());
880: }
881: }
882: