2 rcs_id('$Id: backend.php,v 1.24 2005-09-10 21:30:16 rurban Exp $');
9 //:deleted (*) (Set if latest content is empty.)
17 %content (?should this be here?)
18 _supplanted : Time version ceased to be the current version
20 mtime (*) : Time of version edit.
23 author : nominal author
24 author_id : authenticated author
33 (types are scalars: strings, ints, bools)
37 * A WikiDB_backend handles the storage and retrieval of data for a WikiDB.
39 * A WikiDB_backend handles the storage and retrieval of data for a WikiDB.
40 * It does not have to be this way, of course, but the standard WikiDB uses
41 * a WikiDB_backend. (Other WikiDB's could be written which use some other
42 * method to access their underlying data store.)
44 * The interface outlined here seems to work well with both RDBM based
45 * and flat DBM/hash based methods of data storage.
47 * Though it contains some default implementation of certain methods,
48 * this is an abstract base class. It is expected that most effificient
49 * backends will override nearly all the methods in this class.
57 * Get page meta-data from database.
59 * @param $pagename string Page name.
61 * Returns a hash containing the page meta-data.
62 * Returns an empty array if there is no meta-data for the requested page.
63 * Keys which might be present in the hash are:
65 * <dt> locked <dd> If the page is locked.
66 * <dt> hits <dd> The page hit count.
67 * <dt> created <dd> Unix time of page creation. (FIXME: Deprecated: I
68 * don't think we need this...)
71 function get_pagedata($pagename) {
72 trigger_error("virtual", E_USER_ERROR);
76 * Update the page meta-data.
80 * Only meta-data whose keys are preset in $newdata is affected.
84 * $backend->update_pagedata($pagename, array('locked' => 1));
86 * will set the value of 'locked' to 1 for the specified page, but it
87 * will not affect the value of 'hits' (or whatever other meta-data
88 * may have been stored for the page.)
90 * To delete a particular piece of meta-data, set it's value to false.
92 * $backend->update_pagedata($pagename, array('locked' => false));
95 * @param $pagename string Page name.
96 * @param $newdata hash New meta-data.
98 function update_pagedata($pagename, $newdata) {
99 trigger_error("virtual", E_USER_ERROR);
104 * Get the current version number for a page.
106 * @param $pagename string Page name.
107 * @return int The latest version number for the page. Returns zero if
108 * no versions of a page exist.
110 function get_latest_version($pagename) {
111 trigger_error("virtual", E_USER_ERROR);
115 * Get preceding version number.
117 * @param $pagename string Page name.
118 * @param $version int Find version before this one.
119 * @return int The version number of the version in the database which
120 * immediately preceeds $version.
122 function get_previous_version($pagename, $version) {
123 trigger_error("virtual", E_USER_ERROR);
127 * Get revision meta-data and content.
129 * @param $pagename string Page name.
130 * @param $version integer Which version to get.
131 * @param $want_content boolean
132 * Indicates the caller really wants the page content. If this
133 * flag is not set, the backend is free to skip fetching of the
134 * page content (as that may be expensive). If the backend omits
135 * the content, the backend might still want to set the value of
136 * '%content' to the empty string if it knows there's no content.
138 * @return hash The version data, or false if specified version does not
141 * Some keys which might be present in the $versiondata hash are:
144 * <dd> This is a pseudo-meta-data element (since it's actually
145 * the page data, get it?) containing the page content.
146 * If the content was not fetched, this key may not be present.
148 * For description of other version meta-data see WikiDB_PageRevision::get().
149 * @see WikiDB_PageRevision::get
151 function get_versiondata($pagename, $version, $want_content = false) {
152 trigger_error("virtual", E_USER_ERROR);
156 * Delete page from the database with backup possibility.
157 * This should remove all links (from the named page) from
160 * @param $pagename string Page name.
161 * i.e save_page('') and DELETE nonempty id
162 * Can be undone and is seen in RecentChanges.
164 function delete_page($pagename) {
166 $user =& $GLOBALS['request']->_user;
167 $vdata = array('author' => $user->getId(),
168 'author_id' => $user->getAuthenticatedId(),
171 $this->lock(); // critical section:
172 $version = $this->get_latest_version($pagename);
173 $this->set_versiondata($pagename, $version+1, $vdata);
174 $this->set_links($pagename, false); // links are purged.
175 // SQL needs to invalidate the non_empty id
176 if (! WIKIDB_NOCACHE_MARKUP) {
177 // need the hits, perms and LOCKED, otherwise you can reset the perm
178 // by action=remove and re-create it with default perms
179 $pagedata = $this->get_pagedata($pagename);
180 unset($pagedata['_cached_html']);
181 $this->update_pagedata($pagename, $pagedata);
187 * Delete page (and all it's revisions) from the database.
190 function purge_page($pagename) {
191 trigger_error("virtual", E_USER_ERROR);
195 * Delete an old revision of a page.
197 * Note that one is never allowed to delete the most recent version,
198 * but that this requirement is enforced by WikiDB not by the backend.
200 * In fact, to be safe, backends should probably allow the deletion of
201 * the most recent version.
203 * @param $pagename string Page name.
204 * @param $version integer Version to delete.
206 function delete_versiondata($pagename, $version) {
207 trigger_error("virtual", E_USER_ERROR);
211 * Create a new page revision.
213 * If the given ($pagename,$version) is already in the database,
214 * this method completely overwrites any stored data for that version.
216 * @param $pagename string Page name.
217 * @param $version int New revisions content.
218 * @param $data hash New revision metadata.
220 * @see get_versiondata
222 function set_versiondata($pagename, $version, $data) {
223 trigger_error("virtual", E_USER_ERROR);
227 * Update page version meta-data.
229 * If the given ($pagename,$version) is already in the database,
230 * this method only changes those meta-data values whose keys are
231 * explicity listed in $newdata.
233 * @param $pagename string Page name.
234 * @param $version int New revisions content.
235 * @param $newdata hash New revision metadata.
236 * @see set_versiondata, get_versiondata
238 function update_versiondata($pagename, $version, $newdata) {
239 $data = $this->get_versiondata($pagename, $version, true);
244 foreach ($newdata as $key => $val) {
250 $this->set_versiondata($pagename, $version, $data);
254 * Set links for page.
256 * @param $pagename string Page name.
258 * @param $links array List of page(names) which page links to.
260 function set_links($pagename, $links) {
261 trigger_error("virtual", E_USER_ERROR);
265 * Find pages which link to or are linked from a page.
267 * @param $pagename string Page name.
268 * @param $reversed boolean True to get backlinks.
270 * FIXME: array or iterator?
271 * @return object A WikiDB_backend_iterator.
273 function get_links($pagename, $reversed, $include_empty=false,
274 $sortby=false, $limit=false, $exclude=false) {
275 //FIXME: implement simple (but slow) link finder.
276 die("FIXME get_links");
280 * Get all revisions of a page.
282 * @param $pagename string The page name.
283 * @return object A WikiDB_backend_iterator.
285 function get_all_revisions($pagename) {
286 include_once('lib/WikiDB/backend/dumb/AllRevisionsIter.php');
287 return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename);
291 * Get all pages in the database.
293 * Pages should be returned in alphabetical order if that is
298 * @param $include_defaulted boolean
299 * If set, even pages with no content will be returned
300 * --- but still only if they have at least one revision (not
301 * counting the default revision 0) entered in the database.
303 * Normally pages whose current revision has empty content
304 * are not returned as these pages are considered to be
307 * @return object A WikiDB_backend_iterator.
309 function get_all_pages($include_defaulted, $orderby=false, $limit=false, $exclude=false) {
310 trigger_error("virtual", E_USER_ERROR);
314 * Title or full text search.
316 * Pages should be returned in alphabetical order if that is
321 * @param $search object A TextSearchQuery object describing the parsed query string,
322 * with efficient methods for SQL and PCRE match.
324 * @param $fullsearch boolean If true, a full text search is performed,
325 * otherwise a title search is performed.
327 * @return object A WikiDB_backend_iterator.
329 * @see WikiDB::titleSearch
331 function text_search($search, $fulltext=false, $sortby=false, $limit=false, $exclude=false) {
332 // This is method implements a simple linear search
333 // through all the pages in the database.
335 // It is expected that most backends will overload
336 // this method with something more efficient.
337 include_once('lib/WikiDB/backend/dumb/TextSearchIter.php');
338 $pages = $this->get_all_pages(false, $sortby, $limit, $exclude);
339 return new WikiDB_backend_dumb_TextSearchIter($this, $pages, $search, $fulltext);
343 * Find pages with highest hit counts.
345 * Find the pages with the highest hit counts. The pages should
346 * be returned in reverse order by hit count.
349 * @param $limit integer No more than this many pages
350 * @return object A WikiDB_backend_iterator.
352 function most_popular($limit, $sortby='-hits') {
353 // This is method fetches all pages, then
354 // sorts them by hit count.
355 // (Not very efficient.)
357 // It is expected that most backends will overload
358 // method with something more efficient.
359 include_once('lib/WikiDB/backend/dumb/MostPopularIter.php');
360 $pages = $this->get_all_pages(false, $sortby, $limit);
361 return new WikiDB_backend_dumb_MostPopularIter($this, $pages, $limit);
365 * Find recent changes.
368 * @param $params hash See WikiDB::mostRecent for a description
369 * of parameters which can be included in this hash.
370 * @return object A WikiDB_backend_iterator.
371 * @see WikiDB::mostRecent
373 function most_recent($params) {
374 // This method is very inefficient and searches through
375 // all pages for the most recent changes.
377 // It is expected that most backends will overload
378 // method with something more efficient.
379 include_once('lib/WikiDB/backend/dumb/MostRecentIter.php');
380 $pages = $this->get_all_pages(true, '-mtime');
381 return new WikiDB_backend_dumb_MostRecentIter($this, $pages, $params);
384 function wanted_pages($exclude_from='', $exclude='', $sortby=false, $limit=false) {
385 include_once('lib/WikiDB/backend/dumb/WantedPagesIter.php');
386 $allpages = $this->get_all_pages(true,false,false,$exclude_from);
387 return new WikiDB_backend_dumb_WantedPagesIter($this, $allpages, $exclude, $sortby, $limit);
391 * Lock backend database.
393 * Calls may be nested.
395 * @param $write_lock boolean Unless this is set to false, a write lock
396 * is acquired, otherwise a read lock. If the backend doesn't support
397 * read locking, then it should make a write lock no matter which type
398 * of lock was requested.
400 * All backends <em>should</em> support write locking.
402 function lock($write_lock = true) {
406 * Unlock backend database.
408 * @param $force boolean Normally, the database is not unlocked until
409 * unlock() is called as many times as lock() has been. If $force is
410 * set to true, the the database is unconditionally unlocked.
412 function unlock($force = false) {
423 * Synchronize with filesystem.
425 * This should flush all unwritten data to the filesystem.
431 * Optimize the database.
433 function optimize() {
437 * Check database integrity.
439 * This should check the validity of the internal structure of the database.
440 * Errors should be reported via:
442 * trigger_error("Message goes here.", E_USER_WARNING);
445 * @return boolean True iff database is in a consistent state.
451 * Put the database into a consistent state.
453 * This should put the database into a consistent state.
454 * (I.e. rebuild indexes, etc...)
456 * @return boolean True iff successful.
461 function _parse_searchwords($search) {
462 $search = strtolower(trim($search));
464 return array(array(),array());
466 $words = preg_split('/\s+/', $search);
468 foreach ($words as $key => $word) {
469 if ($word[0] == '-' && $word != '-') {
470 $word = substr($word, 1);
471 $exclude[] = preg_quote($word);
475 return array($words, $exclude);
479 * Split the given limit parameter into offset,pagesize. (offset is optional. default: 0)
480 * Duplicate the PageList function here to avoid loading the whole PageList.php
482 * list($offset,$pagesize) = $this->limit($args['limit']);
484 function limit($limit) {
485 if (strstr($limit, ','))
486 return split(',', $limit);
488 return array(0, $limit);
492 * Handle sortby requests for the DB iterator and table header links.
493 * Prefix the column with + or - like "+pagename","-mtime", ...
494 * supported actions: 'flip_order' "mtime" => "+mtime" => "-mtime" ...
495 * 'db' "-pagename" => "pagename DESC"
496 * In PageList all columns are sortable. (patch by DanFr)
497 * Here with the backend only some, the rest is delayed to PageList.
498 * (some kind of DumbIter)
499 * Duplicate the PageList function here to avoid loading the whole
500 * PageList.php, and it forces the backend specific sortable_columns()
502 function sortby ($column, $action, $sortable_columns=false) {
503 if (empty($column)) return '';
504 //support multiple comma-delimited sortby args: "+hits,+pagename"
505 if (strstr($column, ',')) {
507 foreach (explode(',', $column) as $col) {
509 $result[] = WikiDB_backend::sortby($col, $action);
511 $result[] = $this->sortby($col, $action);
513 return join(",",$result);
515 if (substr($column,0,1) == '+') {
516 $order = '+'; $column = substr($column,1);
517 } elseif (substr($column,0,1) == '-') {
518 $order = '-'; $column = substr($column,1);
520 // default order: +pagename, -mtime, -hits
522 if (in_array($column,array('mtime','hits')))
526 if ($action == 'flip_order') {
527 return ($order == '+' ? '-' : '+') . $column;
528 } elseif ($action == 'init') {
529 $this->_sortby[$column] = $order;
530 return $order . $column;
531 } elseif ($action == 'check') {
532 return (!empty($this->_sortby[$column]) or
533 ($GLOBALS['request']->getArg('sortby') and
534 strstr($GLOBALS['request']->getArg('sortby'),$column)));
535 } elseif ($action == 'db') {
536 // native sort possible?
537 if (!empty($this) and !$sortable_columns)
538 $sortable_columns = $this->sortable_columns();
539 if (in_array($column, $sortable_columns))
540 // asc or desc: +pagename, -pagename
541 return $column . ($order == '+' ? ' ASC' : ' DESC');
548 function sortable_columns() {
549 return array('pagename'/*,'mtime','author_id','author'*/);
552 // adds surrounding quotes
553 function quote ($s) { return "'".$s."'"; }
554 // no surrounding quotes because we know it's a string
555 function qstr ($s) { return $s; }
558 return in_array(DATABASE_TYPE, array('SQL','ADODB','PDO'));
563 * Iterator returned by backend methods which (possibly) return
566 * FIXME: This might be two seperate classes: page_iter and version_iter.
567 * For the versions we have WikiDB_backend_dumb_AllRevisionsIter.
569 class WikiDB_backend_iterator
572 * Get the next record in the iterator set.
574 * This returns a hash. The hash may contain the following keys:
576 * <dt> pagename <dt> (string) the page name
577 * <dt> version <dt> (int) the version number
578 * <dt> pagedata <dt> (hash) page meta-data (as returned from backend::get_pagedata().)
579 * <dt> versiondata <dt> (hash) page meta-data (as returned from backend::get_versiondata().)
581 * If this is a page iterator, it must contain the 'pagename' entry --- the others
584 * If this is a version iterator, the 'pagename', 'version', <strong>and</strong> 'versiondata'
585 * entries are mandatory. ('pagedata' is optional.)
588 trigger_error("virtual", E_USER_ERROR);
592 return count($this->_pages);
596 * Release resources held by this iterator.
603 * search baseclass, pcre-specific. only overriden by sql backends.
605 class WikiDB_backend_search
607 function WikiDB_backend_search(&$dbh, $search) {
609 $this->_case_exact = $search->_case_exact;
611 function _quote($word) {
612 return preg_quote($word, "/");
614 //TODO: use word anchors
615 function EXACT($word) { return "^".$this->_quote($word)."$"; }
616 function STARTS_WITH($word) { return "^".$this->_quote($word); }
617 function ENDS_WITH($word) { return $this->_quote($word)."$"; }
618 function WORD($word) { return $this->_quote($word); }
619 function REGEX($word) { return $word; }
621 function _pagename_match_clause($node) {
623 $word = $this->$method($node->word);
624 return "preg_match(\"/\".$word.\"/\"".($this->_case_exact ? "i":"").")";
633 // c-hanging-comment-ender-p: nil
634 // indent-tabs-mode: nil