Overview

Packages

  • None
  • Wicked

Classes

  • Text_Wiki_Parse_Heading2
  • Text_Wiki_Parse_Toc2
  • Text_Wiki_Render_Latex_Heading2
  • Text_Wiki_Render_Latex_Toc2
  • Text_Wiki_Render_Plain_Heading2
  • Text_Wiki_Render_Plain_Toc2
  • Text_Wiki_Render_Rst
  • Text_Wiki_Render_Rst_Blockquote
  • Text_Wiki_Render_Rst_Bold
  • Text_Wiki_Render_Rst_Code
  • Text_Wiki_Render_Rst_Deflist
  • Text_Wiki_Render_Rst_Emphasis
  • Text_Wiki_Render_Rst_Freelink
  • Text_Wiki_Render_Rst_Heading2
  • Text_Wiki_Render_Rst_Links
  • Text_Wiki_Render_Rst_List
  • Text_Wiki_Render_Rst_Newline
  • Text_Wiki_Render_Rst_Paragraph
  • Text_Wiki_Render_Rst_Raw
  • Text_Wiki_Render_Rst_Toc2
  • Text_Wiki_Render_Rst_Tt
  • Text_Wiki_Render_Rst_Url
  • Text_Wiki_Render_Xhtml_Attribute
  • Text_Wiki_Render_Xhtml_Code2
  • Text_Wiki_Render_Xhtml_Freelink2
  • Text_Wiki_Render_Xhtml_Heading2
  • Text_Wiki_Render_Xhtml_Image2
  • Text_Wiki_Render_Xhtml_Interwiki
  • Text_Wiki_Render_Xhtml_Registrylink
  • Text_Wiki_Render_Xhtml_Toc2
  • Text_Wiki_Render_Xhtml_Url
  • Text_Wiki_Render_Xhtml_Wickedblock
  • Text_Wiki_Render_Xhtml_Wikilink2
  • Wicked
  • Wicked_Api
  • Wicked_Driver
  • Wicked_Driver_Sql
  • Wicked_Exception
  • Wicked_Factory_Driver
  • Wicked_Page
  • Wicked_Page_AddPage
  • Wicked_Page_AllPages
  • Wicked_Page_AttachedFiles
  • Wicked_Page_BackLinks
  • Wicked_Page_DeletePage
  • Wicked_Page_EditPage
  • Wicked_Page_LeastPopular
  • Wicked_Page_LikePages
  • Wicked_Page_MergeOrRename
  • Wicked_Page_MostPopular
  • Wicked_Page_NewPage
  • Wicked_Page_RecentChanges
  • Wicked_Page_RevertPage
  • Wicked_Page_Search
  • Wicked_Page_StandardHistoryPage
  • Wicked_Page_StandardPage
  • Wicked_Page_SyncDiff
  • Wicked_Page_SyncPages
  • Wicked_Sync
  • Wicked_Sync_Wicked
  • Wicked_Test
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * @package Wicked
  4:  */
  5: 
  6: /**
  7:  * Wicked storage implementation for the Horde_Db database abstraction layer.
  8:  *
  9:  * @author  Tyler Colbert <tyler@colberts.us>
 10:  * @author  Chuck Hagenbuch <chuck@horde.org>
 11:  * @author  Jan Schneider <jan@horde.org>
 12:  * @package Wicked
 13:  */
 14: class Wicked_Driver_Sql extends Wicked_Driver
 15: {
 16:     /**
 17:      * Handle for the current database connection.
 18:      *
 19:      * @var Horde_Db_Adapter
 20:      */
 21:     protected $_db;
 22: 
 23:     /**
 24:      * A cached list of all available page names.
 25:      *
 26:      * @var array
 27:      */
 28:     protected $_pageNames;
 29: 
 30:     /**
 31:      * Constructor.
 32:      *
 33:      * @param array $params  A hash containing connection parameters.
 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:      * Retrieves the page of a particular name from the database.
 54:      *
 55:      * @param string $pagename The name of the page to retrieve.
 56:      *
 57:      * @return array
 58:      * @throws Wicked_Exception
 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:      * Retrieves a historic version of a page.
 75:      *
 76:      * @param string $pagename  The name of the page to retrieve.
 77:      * @param string $version   The version to retrieve.
 78:      *
 79:      * @return array  The page hash.
 80:      * @throws Wicked_Exception
 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:      * Returns the most recently changed pages.
115:      *
116:      * @param integer $days  The number of days to look back.
117:      *
118:      * @return array  Pages.
119:      * @throws Wicked_Exception
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:      * Returns the most popular pages.
135:      *
136:      * @param integer $limit  The number of most popular pages to return.
137:      *
138:      * @return array  Pages.
139:      * @throws Wicked_Exception
140:      */
141:     public function mostPopular($limit = 10)
142:     {
143:         return $this->_retrieve($this->_params['table'], '',
144:                                 'page_hits DESC', $limit);
145:     }
146: 
147:     /**
148:      * Returns the least popular pages.
149:      *
150:      * @param integer $limit  The number of least popular pages to return.
151:      *
152:      * @return array  Pages.
153:      * @throws Wicked_Exception
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:      * Finds pages with matches in text or title.
174:      *
175:      * @param string $searchtext  The search expression (Google-like).
176:      * @param boolean $title      Search both page title and text?
177:      *
178:      * @return array  A list of pages.
179:      * @throws Wicked_Exception
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:         /* We've cast a wide net, so now we filter out pages which don't
217:          * actually refer to $pagename. */
218:         /* @todo this should match the current wiki engine's syntax. */
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:             /* Short circuit the simple case. */
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:             /* Get the first and last word of the page name. */
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:      * Retrieves data on files attached to a page.
321:      *
322:      * @param string $pageId        This is the Id of the page for which we'd
323:      *                              like to find attached files.
324:      * @param boolean $allversions  Whether to include all versions. If false
325:      *                              or omitted, only the most recent version
326:      *                              of each attachment is returned.
327:      * @return array  An array of key/value arrays describing the attached
328:      *                files.
329:      * @throws Wicked_Exception
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:      * Removes a single version or all versions of an attachment from
360:      * $pageId.
361:      *
362:      * @param integer $pageId     The Id of the page the file is attached to.
363:      * @param string $attachment  The name of the file.
364:      * @param string $version     If specified, the version to delete. If null,
365:      *                            then all versions of $attachment will be
366:      *                            removed.
367:      *
368:      * @throws Wicked_Exception
369:      */
370:     public function removeAttachment($pageId, $attachment, $version = null)
371:     {
372:         /* Try to delete from the VFS first. */
373:         parent::removeAttachment($pageId, $attachment, $version);
374: 
375:         /* First try against the current attachments table. */
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:             /* Now try against the attachment history table. $params is
389:              * unchanged. */
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:      * Removes all attachments from a page.
405:      *
406:      * @param integer $pageId  A page ID.
407:      *
408:      * @throws Wicked_Exception
409:      */
410:     public function removeAllAttachments($pageId)
411:     {
412:         /* Try to delete from the VFS first. */
413:         $result = parent::removeAllAttachments($pageId);
414: 
415:         $params = array((int)$pageId);
416:         try {
417:             $this->_db->beginDbTransaction();
418:             /* First try against the current attachments table. */
419:             $result = $this->_db->delete(
420:                 'DELETE FROM ' . $this->_params['attachmenttable']
421:                 . ' WHERE page_id = ?',
422:                 $params);
423: 
424:             /* Now try against the attachment history table. $params is
425:              * unchanged. */
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:      * Handles the driver-specific portion of attaching a file.
439:      *
440:      * Wicked_Driver::attachFile() calls down to this method for the driver-
441:      * specific portion, and then uses VFS to store the attachment.
442:      *
443:      * @param array $file  See Wicked_Driver::attachFile().
444:      *
445:      * @return integer  The new version of the file attached.
446:      * @throws Wicked_Exception
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:      * Logs a page view.
506:      *
507:      * @param string $pagename  The page that was viewed.
508:      *
509:      * @throws Wicked_Exception
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:      * Logs an attachment download.
525:      *
526:      * @param integer $pageid     The page with the attachment.
527:      * @param string $attachment  The attachment name.
528:      *
529:      * @throws Wicked_Exception
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:      * Creates a new page.
546:      *
547:      * @param string $pagename  The new page's name.
548:      * @param string $text      The new page's text.
549:      *
550:      * @throws Wicked_Exception
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:         /* Attempt the insertion/update query. */
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:         /* Send notification. */
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:         /* Call getPages with no caching so that the new list of pages is
588:          * read in. */
589:         $this->getPages(true, true);
590: 
591:         return $page_id;
592:     }
593: 
594:     /**
595:      * Renames a page, keeping the page's history.
596:      *
597:      * @param string $pagename  The name of the page to rename.
598:      * @param string $newname   The page's new name.
599:      *
600:      * @throws Wicked_Exception
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:         /* Call getPages with no caching so that the new list of pages is
627:          * read in. */
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:         /* Copy the old version into the page history. */
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:             /* Now move on to updating the record. */
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:         /* We need to know if we're deleting the current version. */
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:             /* Removing a historical revision - we can just slice it out of the
710:              * history table. $values is unchanged. */
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:         /* We're deleting the current version. Have to promote the next-most
723:          * revision from the history table. */
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:             /* Replace the current version of the page with the version being
732:              * promoted. */
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:             /* Finally, remove the version that we promoted from the history
747:              * table. */
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:         /* Remove attachments and do other cleanup. */
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:      * Retrieves a set of pages matching an SQL WHERE clause.
789:      *
790:      * @param string $table        Table to retrieve pages from.
791:      * @param array|string $where  Where clause for sql statement (without the
792:      *                             'WHERE'). If an array the 1st element is the
793:      *                             clause with placeholder, the 2nd element the
794:      *                             values.
795:      * @param string $orderBy      Order results by this column.
796:      * @param integer $limit       Maximum number of pages to fetch.
797:      *
798:      * @return array  A list of page hashes.
799:      * @throws Wicked_Exception
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:      * Returns the charset used by the backend.
850:      *
851:      * @return string  The backend's charset
852:      */
853:     public function getCharset()
854:     {
855:         return $this->_db->getOption('charset');
856:     }
857: 
858:     /**
859:      * Converts a value from the driver's charset to the default charset.
860:      *
861:      * @param mixed $value  A value to convert.
862:      *
863:      * @return mixed  The converted value.
864:      */
865:     protected function _convertFromDriver($value)
866:     {
867:         return Horde_String::convertCharset($value, $this->getCharset(), 'UTF-8');
868:     }
869: 
870:     /**
871:      * Converts a value from the default charset to the driver's charset.
872:      *
873:      * @param mixed $value  A value to convert.
874:      *
875:      * @return mixed  The converted value.
876:      */
877:     protected function _convertToDriver($value)
878:     {
879:         return Horde_String::convertCharset($value, 'UTF-8', $this->getCharset());
880:     }
881: }
882: 
API documentation generated by ApiGen