]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend.php
use And by default; cleanup
[SourceForge/phpwiki.git] / lib / WikiDB / backend.php
1 <?php
2
3 /*
4  * Copyright 2004-2010 Reini Urban
5  *
6  * This file is part of PhpWiki.
7  *
8  * PhpWiki is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * PhpWiki is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22
23 /*
24   Pagedata
25
26    maintained by WikiPage
27     //:latestversion
28     //:deleted (*)     (Set if latest content is empty.)
29     //:pagename (*)
30
31     hits
32     is_locked
33
34   Versiondata
35
36     %content (?should this be here?)
37     _supplanted : Time version ceased to be the current version
38
39     mtime (*)   : Time of version edit.
40     orig_mtime
41     is_minor_edit (*)
42     author      : nominal author
43     author_id   : authenticated author
44     summary
45
46     //version
47     //created (*)
48     //%superceded
49
50     //:serial
51
52      (types are scalars: strings, ints, bools)
53 */
54
55 /**
56  * A WikiDB_backend handles the storage and retrieval of data for a WikiDB.
57  *
58  * It does not have to be this way, of course, but the standard WikiDB uses
59  * a WikiDB_backend.  (Other WikiDB's could be written which use some other
60  * method to access their underlying data store.)
61  *
62  * The interface outlined here seems to work well with both RDBM based
63  * and flat DBM/hash based methods of data storage.
64  *
65  * Though it contains some default implementation of certain methods,
66  * this is an abstract base class.  It is expected that most efficient
67  * backends will override nearly all the methods in this class.
68  *
69  * @access protected
70  * @see WikiDB
71  */
72 class WikiDB_backend
73 {
74     /**
75      * Get page meta-data from database.
76      *
77      * @param $pagename string Page name.
78      * @return hash
79      * Returns a hash containing the page meta-data.
80      * Returns an empty array if there is no meta-data for the requested page.
81      * Keys which might be present in the hash are:
82      * <dl>
83      *  <dt> locked  <dd> If the page is locked.
84      *  <dt> hits    <dd> The page hit count.
85      *  <dt> created <dd> Unix time of page creation. (FIXME: Deprecated: I
86      *                    don't think we need this...)
87      * </dl>
88      */
89     function get_pagedata($pagename)
90     {
91         trigger_error("virtual", E_USER_ERROR);
92     }
93
94     /**
95      * Update the page meta-data.
96      *
97      * Set page meta-data.
98      *
99      * Only meta-data whose keys are preset in $newdata is affected.
100      *
101      * For example:
102      * <pre>
103      *   $backend->update_pagedata($pagename, array('locked' => 1));
104      * </pre>
105      * will set the value of 'locked' to 1 for the specified page, but it
106      * will not affect the value of 'hits' (or whatever other meta-data
107      * may have been stored for the page.)
108      *
109      * To delete a particular piece of meta-data, set its value to false.
110      * <pre>
111      *   $backend->update_pagedata($pagename, array('locked' => false));
112      * </pre>
113      *
114      * @param $pagename string Page name.
115      * @param $newdata hash New meta-data.
116      */
117     function update_pagedata($pagename, $newdata)
118     {
119         trigger_error("virtual", E_USER_ERROR);
120     }
121
122     /**
123      * Get the current version number for a page.
124      *
125      * @param $pagename string Page name.
126      * @return int The latest version number for the page.  Returns zero if
127      *  no versions of a page exist.
128      */
129     function get_latest_version($pagename)
130     {
131         trigger_error("virtual", E_USER_ERROR);
132     }
133
134     /**
135      * Get preceding version number.
136      *
137      * @param $pagename string Page name.
138      * @param $version int Find version before this one.
139      * @return int The version number of the version in the database which
140      *  immediately preceeds $version.
141      */
142     function get_previous_version($pagename, $version)
143     {
144         trigger_error("virtual", E_USER_ERROR);
145     }
146
147     /**
148      * Get revision meta-data and content.
149      *
150      * @param $pagename string Page name.
151      * @param $version integer Which version to get.
152      * @param $want_content boolean
153      *  Indicates the caller really wants the page content.  If this
154      *  flag is not set, the backend is free to skip fetching of the
155      *  page content (as that may be expensive).  If the backend omits
156      *  the content, the backend might still want to set the value of
157      *  '%content' to the empty string if it knows there's no content.
158      *
159      * @return hash The version data, or false if specified version does not
160      *    exist.
161      *
162      * Some keys which might be present in the $versiondata hash are:
163      * <dl>
164      * <dt> %content
165      *  <dd> This is a pseudo-meta-data element (since it's actually
166      *       the page data, get it?) containing the page content.
167      *       If the content was not fetched, this key may not be present.
168      * </dl>
169      * For description of other version meta-data see WikiDB_PageRevision::get().
170      * @see WikiDB_PageRevision::get
171      */
172     function get_versiondata($pagename, $version, $want_content = false)
173     {
174         trigger_error("virtual", E_USER_ERROR);
175     }
176
177     /**
178      * Delete page from the database with backup possibility.
179      * This should remove all links (from the named page) from
180      * the link database.
181      *
182      * @param $pagename string Page name.
183      * i.e save_page('') and DELETE nonempty id
184      * Can be undone and is seen in RecentChanges.
185      */
186     function delete_page($pagename)
187     {
188         $mtime = time();
189         $user =& $GLOBALS['request']->_user;
190         $vdata = array('author' => $user->getId(),
191             'author_id' => $user->getAuthenticatedId(),
192             'mtime' => $mtime);
193
194         $this->lock(); // critical section:
195         $version = $this->get_latest_version($pagename);
196         $this->set_versiondata($pagename, $version + 1, $vdata);
197         $this->set_links($pagename, false); // links are purged.
198         // SQL needs to invalidate the non_empty id
199         if (!WIKIDB_NOCACHE_MARKUP) {
200             // need the hits, perms and LOCKED, otherwise you can reset the perm
201             // by action=remove and re-create it with default perms
202             $pagedata = $this->get_pagedata($pagename);
203             unset($pagedata['_cached_html']);
204             $this->update_pagedata($pagename, $pagedata);
205         }
206         $this->unlock();
207     }
208
209     /**
210      * Delete page (and all its revisions) from the database.
211      *
212      */
213     function purge_page($pagename)
214     {
215         trigger_error("virtual", E_USER_ERROR);
216     }
217
218     /**
219      * Delete an old revision of a page.
220      *
221      * Note that one is never allowed to delete the most recent version,
222      * but that this requirement is enforced by WikiDB not by the backend.
223      *
224      * In fact, to be safe, backends should probably allow the deletion of
225      * the most recent version.
226      *
227      * @param $pagename string Page name.
228      * @param $version integer Version to delete.
229      */
230     function delete_versiondata($pagename, $version)
231     {
232         trigger_error("virtual", E_USER_ERROR);
233     }
234
235     /**
236      * Create a new page revision.
237      *
238      * If the given ($pagename,$version) is already in the database,
239      * this method completely overwrites any stored data for that version.
240      *
241      * @param $pagename string Page name.
242      * @param $version int New revisions content.
243      * @param $data hash New revision metadata.
244      *
245      * @see get_versiondata
246      */
247     function set_versiondata($pagename, $version, $data)
248     {
249         trigger_error("virtual", E_USER_ERROR);
250     }
251
252     /**
253      * Update page version meta-data.
254      *
255      * If the given ($pagename,$version) is already in the database,
256      * this method only changes those meta-data values whose keys are
257      * explicity listed in $newdata.
258      *
259      * @param $pagename string Page name.
260      * @param $version int New revisions content.
261      * @param $newdata hash New revision metadata.
262      * @see set_versiondata, get_versiondata
263      */
264     function update_versiondata($pagename, $version, $newdata)
265     {
266         $data = $this->get_versiondata($pagename, $version, true);
267         if (!$data) {
268             assert($data);
269             return;
270         }
271         foreach ($newdata as $key => $val) {
272             if (empty($val))
273                 unset($data[$key]);
274             else
275                 $data[$key] = $val;
276         }
277         $this->set_versiondata($pagename, $version, $data);
278     }
279
280     /**
281      * Set links for page.
282      *
283      * @param $pagename string Page name.
284      *
285      * @param $links array List of page(names) which page links to.
286      */
287     function set_links($pagename, $links)
288     {
289         trigger_error("virtual", E_USER_ERROR);
290     }
291
292     /**
293      * Find pages which link to or are linked from a page.
294      *
295      * @param $pagename string Page name.
296      * @param $reversed boolean True to get backlinks.
297      *
298      * FIXME: array or iterator?
299      * @return object A WikiDB_backend_iterator.
300      */
301     function get_links($pagename, $reversed, $include_empty = false,
302                        $sortby = '', $limit = '', $exclude = '')
303     {
304         //FIXME: implement simple (but slow) link finder.
305         die("FIXME get_links");
306     }
307
308     /**
309      * Get all revisions of a page.
310      *
311      * @param $pagename string The page name.
312      * @return object A WikiDB_backend_iterator.
313      */
314     function get_all_revisions($pagename)
315     {
316         include_once 'lib/WikiDB/backend/dumb/AllRevisionsIter.php';
317         return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename);
318     }
319
320     /**
321      * Get all pages in the database.
322      *
323      * Pages should be returned in alphabetical order if that is
324      * feasable.
325      *
326      * @access protected
327      *
328      * @param $include_defaulted boolean
329      * If set, even pages with no content will be returned
330      * --- but still only if they have at least one revision (not
331      * counting the default revision 0) entered in the database.
332      *
333      * Normally pages whose current revision has empty content
334      * are not returned as these pages are considered to be
335      * non-existing.
336      *
337      * @return object A WikiDB_backend_iterator.
338      */
339     function get_all_pages($include_defaulted, $orderby = false, $limit = '', $exclude = '')
340     {
341         trigger_error("virtual", E_USER_ERROR);
342     }
343
344     /**
345      * Title or full text search.
346      *
347      * Pages should be returned in alphabetical order if that is
348      * feasable.
349      *
350      * @access protected
351      *
352      * @param $search object A TextSearchQuery object describing the parsed query string,
353      *                       with efficient methods for SQL and PCRE match.
354      *
355      * @param $fullsearch boolean If true, a full text search is performed,
356      *  otherwise a title search is performed.
357      *
358      * @return object A WikiDB_backend_iterator.
359      *
360      * @see WikiDB::titleSearch
361      */
362     function text_search($search, $fulltext = false, $sortby = '',
363                          $limit = '', $exclude = '')
364     {
365         // This method implements a simple linear search
366         // through all the pages in the database.
367         //
368         // It is expected that most backends will overload
369         // this method with something more efficient.
370         include_once 'lib/WikiDB/backend/dumb/TextSearchIter.php';
371         // ignore $limit
372         $pages = $this->get_all_pages(false, $sortby, false, $exclude);
373         return new WikiDB_backend_dumb_TextSearchIter($this, $pages, $search, $fulltext,
374             array('limit' => $limit,
375                 'exclude' => $exclude));
376     }
377
378     /**
379      *
380      * @access protected
381      * @param $pages     object A TextSearchQuery object.
382      * @param $linkvalue object A TextSearchQuery object for the linkvalues
383      *                          (linkto, relation or backlinks or attribute values).
384      * @param $linktype  string One of the 4 linktypes.
385      * @param $relation  object A TextSearchQuery object or false.
386      * @param $options   array Currently ignored. hash of sortby, limit, exclude.
387      * @return object A WikiDB_backend_iterator.
388      * @see WikiDB::linkSearch
389      */
390     function link_search($pages, $linkvalue, $linktype, $relation = false, $options = array())
391     {
392         include_once 'lib/WikiDB/backend/dumb/LinkSearchIter.php';
393         $pageiter = $this->text_search($pages);
394         return new WikiDB_backend_dumb_LinkSearchIter($this, $pageiter, $linkvalue, $linktype, $relation, $options);
395     }
396
397     /**
398      * Find pages with highest hit counts.
399      *
400      * Find the pages with the highest hit counts.  The pages should
401      * be returned in reverse order by hit count.
402      *
403      * @access protected
404      * @param  integer $limit No more than this many pages
405      * @return object  A WikiDB_backend_iterator.
406      */
407     function most_popular($limit, $sortby = '-hits')
408     {
409         // This is method fetches all pages, then
410         // sorts them by hit count.
411         // (Not very efficient.)
412         //
413         // It is expected that most backends will overload
414         // method with something more efficient.
415         include_once 'lib/WikiDB/backend/dumb/MostPopularIter.php';
416         $pages = $this->get_all_pages(false, $sortby, false);
417         return new WikiDB_backend_dumb_MostPopularIter($this, $pages, $limit);
418     }
419
420     /**
421      * Find recent changes.
422      *
423      * @access protected
424      * @param $params hash See WikiDB::mostRecent for a description
425      *  of parameters which can be included in this hash.
426      * @return object A WikiDB_backend_iterator.
427      * @see WikiDB::mostRecent
428      */
429     function most_recent($params)
430     {
431         // This method is very inefficient and searches through
432         // all pages for the most recent changes.
433         //
434         // It is expected that most backends will overload
435         // method with something more efficient.
436         include_once 'lib/WikiDB/backend/dumb/MostRecentIter.php';
437         $pages = $this->get_all_pages(true, '-mtime');
438         return new WikiDB_backend_dumb_MostRecentIter($this, $pages, $params);
439     }
440
441     function wanted_pages($exclude_from = '', $exclude = '', $sortby = '', $limit = '')
442     {
443         include_once 'lib/WikiDB/backend/dumb/WantedPagesIter.php';
444         $allpages = $this->get_all_pages(true, false, false, $exclude_from);
445         return new WikiDB_backend_dumb_WantedPagesIter($this, $allpages, $exclude, $sortby, $limit);
446     }
447
448     /**
449      * Lock backend database.
450      *
451      * Calls may be nested.
452      *
453      * @param $write_lock boolean Unless this is set to false, a write lock
454      *     is acquired, otherwise a read lock.  If the backend doesn't support
455      *     read locking, then it should make a write lock no matter which type
456      *     of lock was requested.
457      *
458      *     All backends <em>should</em> support write locking.
459      */
460     function lock($write_lock = true)
461     {
462     }
463
464     /**
465      * Unlock backend database.
466      *
467      * @param $force boolean Normally, the database is not unlocked until
468      *  unlock() is called as many times as lock() has been.  If $force is
469      *  set to true, the the database is unconditionally unlocked.
470      */
471     function unlock($force = false)
472     {
473     }
474
475
476     /**
477      * Close database.
478      */
479     function close()
480     {
481     }
482
483     /**
484      * Synchronize with filesystem.
485      *
486      * This should flush all unwritten data to the filesystem.
487      */
488     function sync()
489     {
490     }
491
492     /**
493      * Optimize the database.
494      */
495     function optimize()
496     {
497     }
498
499     /**
500      * Check database integrity.
501      *
502      * This should check the validity of the internal structure of the database.
503      * Errors should be reported via:
504      * <pre>
505      *   trigger_error("Message goes here.", E_USER_WARNING);
506      * </pre>
507      *
508      * @return boolean True iff database is in a consistent state.
509      */
510     function check($args = false)
511     {
512     }
513
514     /**
515      * Put the database into a consistent state
516      * by reparsing and restoring all pages.
517      *
518      * This should put the database into a consistent state.
519      * (I.e. rebuild indexes, etc...)
520      *
521      * @return boolean True iff successful.
522      */
523     function rebuild($args = false)
524     {
525         global $request;
526         $dbh = $request->getDbh();
527         $iter = $dbh->getAllPages(false);
528         while ($page = $iter->next()) {
529             $current = $page->getCurrentRevision(true);
530             $pagename = $page->getName();
531             $meta = $current->_data;
532             $version = $current->getVersion();
533             $content =& $meta['%content'];
534             $formatted = new TransformedText($page, $content, $current->getMetaData());
535             $type = $formatted->getType();
536             $meta['pagetype'] = $type->getName();
537             $links = $formatted->getWikiPageLinks(); // linkto => relation
538             $this->lock(array('version', 'page', 'recent', 'link', 'nonempty'));
539             $this->set_versiondata($pagename, $version, $meta);
540             $this->set_links($pagename, $links);
541             $this->unlock(array('version', 'page', 'recent', 'link', 'nonempty'));
542         }
543     }
544
545     function _parse_searchwords($search)
546     {
547         $search = strtolower(trim($search));
548         if (!$search)
549             return array(array(), array());
550
551         $words = preg_split('/\s+/', $search);
552         $exclude = array();
553         foreach ($words as $key => $word) {
554             if ($word[0] == '-' && $word != '-') {
555                 $word = substr($word, 1);
556                 $exclude[] = preg_quote($word);
557                 unset($words[$key]);
558             }
559         }
560         return array($words, $exclude);
561     }
562
563     /**
564      * Split the given limit parameter into offset,limit. (offset is optional. default: 0)
565      * Duplicate the PageList function here to avoid loading the whole PageList.php
566      * Usage:
567      *   list($offset,$count) = $this->limit($args['limit']);
568      */
569     function limit($limit)
570     {
571         if (strstr($limit, ',')) {
572             list($from, $limit) = explode(',', $limit);
573             if ((!empty($from) && !is_numeric($from)) or (!empty($limit) && !is_numeric($limit))) {
574                 return $this->error(_("Illegal 'limit' argument: must be numeric"));
575             }
576             return array($from, $limit);
577         } else {
578             if (!empty($limit) && !is_numeric($limit)) {
579                 return $this->error(_("Illegal 'limit' argument: must be numeric"));
580             }
581             return array(0, $limit);
582         }
583     }
584
585     /**
586      * Handle sortby requests for the DB iterator and table header links.
587      * Prefix the column with + or - like "+pagename","-mtime", ...
588      * supported actions: 'flip_order' "mtime" => "+mtime" => "-mtime" ...
589      *                    'db'         "-pagename" => "pagename DESC"
590      * In PageList all columns are sortable. (patch by DanFr)
591      * Here with the backend only some, the rest is delayed to PageList.
592      * (some kind of DumbIter)
593      * Duplicate the PageList function here to avoid loading the whole
594      * PageList.php, and it forces the backend specific sortable_columns()
595      */
596     function sortby($column, $action, $sortable_columns = false)
597     {
598         if (empty($column)) return '';
599         //support multiple comma-delimited sortby args: "+hits,+pagename"
600         if (strstr($column, ',')) {
601             $result = array();
602             foreach (explode(',', $column) as $col) {
603                 if (empty($this))
604                     $result[] = WikiDB_backend::sortby($col, $action);
605                 else
606                     $result[] = $this->sortby($col, $action);
607             }
608             return join(",", $result);
609         }
610         if (substr($column, 0, 1) == '+') {
611             $order = '+';
612             $column = substr($column, 1);
613         } elseif (substr($column, 0, 1) == '-') {
614             $order = '-';
615             $column = substr($column, 1);
616         }
617         // default order: +pagename, -mtime, -hits
618         if (empty($order))
619             if (in_array($column, array('mtime', 'hits')))
620                 $order = '-';
621             else
622                 $order = '+';
623         if ($action == 'flip_order') {
624             return ($order == '+' ? '-' : '+') . $column;
625         } elseif ($action == 'init') {
626             $this->_sortby[$column] = $order;
627             return $order . $column;
628         } elseif ($action == 'check') {
629             return (!empty($this->_sortby[$column]) or
630                 ($GLOBALS['request']->getArg('sortby') and
631                     strstr($GLOBALS['request']->getArg('sortby'), $column)));
632         } elseif ($action == 'db') {
633             // native sort possible?
634             if (!empty($this) and !$sortable_columns)
635                 $sortable_columns = $this->sortable_columns();
636             if (in_array($column, $sortable_columns))
637                 // asc or desc: +pagename, -pagename
638                 return $column . ($order == '+' ? ' ASC' : ' DESC');
639             else
640                 return '';
641         }
642         return '';
643     }
644
645     function sortable_columns()
646     {
647         return array('pagename' /*,'mtime','author_id','author'*/);
648     }
649
650     // adds surrounding quotes
651     function quote($s)
652     {
653         return "'" . $s . "'";
654     }
655
656     // no surrounding quotes because we know it's a string
657     function qstr($s)
658     {
659         return $s;
660     }
661
662     function isSQL()
663     {
664         return in_array(DATABASE_TYPE, array('SQL', 'ADODB', 'PDO'));
665     }
666
667     function backendType()
668     {
669         return DATABASE_TYPE;
670     }
671
672     function write_accesslog(&$entry)
673     {
674         global $request;
675         if (!$this->isSQL()) return;
676         $dbh = &$this->_dbh;
677         $log_tbl = $entry->_accesslog->logtable;
678         // duration problem: sprintf "%f" might use comma e.g. "100,201" in european locales
679         $dbh->query("INSERT INTO $log_tbl"
680                 . " (time_stamp,remote_host,remote_user,request_method,request_line,request_args,"
681                 . "request_uri,request_time,status,bytes_sent,referer,agent,request_duration)"
682                 . " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
683             array(
684                 // Problem: date formats are backend specific. Either use unixtime as %d (long),
685                 // or the native timestamp format.
686                 $entry->time,
687                 $entry->host,
688                 $entry->user,
689                 $entry->request_method,
690                 $entry->request,
691                 $entry->request_args,
692                 $entry->request_uri,
693                 $entry->_ncsa_time($entry->time),
694                 $entry->status,
695                 (int)$entry->size,
696                 $entry->referer,
697                 $entry->user_agent,
698                 $entry->duration));
699     }
700 }
701
702 /**
703  * Iterator returned by backend methods which (possibly) return
704  * multiple records.
705  *
706  * FIXME: This might be two separate classes: page_iter and version_iter.
707  * For the versions we have WikiDB_backend_dumb_AllRevisionsIter.
708  */
709 class WikiDB_backend_iterator
710 {
711     /**
712      * Get the next record in the iterator set.
713      *
714      * This returns a hash. The hash may contain the following keys:
715      * <dl>
716      * <dt> pagename <dt> (string) the page name or linked page name on link iterators
717      * <dt> version  <dt> (int) the version number
718      * <dt> pagedata <dt> (hash) page meta-data (as returned from backend::get_pagedata().)
719      * <dt> versiondata <dt> (hash) page meta-data (as returned from backend::get_versiondata().)
720      * <dt> linkrelation <dt> (string) the page naming the relation (e.g. isa:=page <=> isa)
721      *
722      * If this is a page iterator, it must contain the 'pagename' entry --- the others
723      * are optional.
724      *
725      * If this is a version iterator, the 'pagename', 'version', <strong>and</strong> 'versiondata'
726      * entries are mandatory.  ('pagedata' is optional.)
727      *
728      * If this is a link iterator, the 'pagename' is mandatory, 'linkrelation' is optional.
729      */
730     function next()
731     {
732         trigger_error("virtual", E_USER_ERROR);
733     }
734
735     function count()
736     {
737         if (!empty($this->_pages))
738             return count($this->_pages);
739         else
740             return 0;
741     }
742
743     function asArray()
744     {
745         if (!empty($this->_pages)) {
746             reset($this->_pages);
747             return $this->_pages;
748         } else {
749             $result = array();
750             while ($page = $this->next())
751                 $result[] = $page;
752             return $result;
753         }
754     }
755
756     /**
757      * limit - if empty the pagelist iterator will do nothing.
758      * Some backends limit the result set itself (dba, file, flatfile),
759      * Some SQL based leave it to WikiDB/PageList - deferred filtering in the iterator.
760      */
761     function limit()
762     {
763         return empty($this->_options['limit']) ? 0 : $this->_options['limit'];
764     }
765
766     /**
767      * Release resources held by this iterator.
768      */
769     function free()
770     {
771     }
772 }
773
774 /**
775  * search baseclass, pcre-specific
776  */
777 class WikiDB_backend_search
778 {
779     function WikiDB_backend_search($search, &$dbh)
780     {
781         $this->_dbh = $dbh;
782         $this->_case_exact = $search->_case_exact;
783         $this->_stoplist =& $search->_stoplist;
784         $this->stoplisted = array();
785     }
786
787     function _quote($word)
788     {
789         return preg_quote($word, "/");
790     }
791
792     //TODO: use word anchors
793     function EXACT($word)
794     {
795         return "^" . $this->_quote($word) . "$";
796     }
797
798     function STARTS_WITH($word)
799     {
800         return "^" . $this->_quote($word);
801     }
802
803     function ENDS_WITH($word)
804     {
805         return $this->_quote($word) . "$";
806     }
807
808     function WORD($word)
809     {
810         return $this->_quote($word);
811     }
812
813     function REGEX($word)
814     {
815         return $word;
816     }
817
818     //TESTME
819     function _pagename_match_clause($node)
820     {
821         $method = $node->op;
822         $word = $this->$method($node->word);
823         return "preg_match(\"/\".$word.\"/\"" . ($this->_case_exact ? "i" : "") . ")";
824     }
825
826     /* Eliminate stoplist words.
827      *  Keep a list of Stoplisted words to inform the poor user.
828      */
829     function isStoplisted($node)
830     {
831         // check only on WORD or EXACT fulltext search
832         if ($node->op != 'WORD' and $node->op != 'EXACT')
833             return false;
834         if (preg_match("/^" . $this->_stoplist . "$/i", $node->word)) {
835             array_push($this->stoplisted, $node->word);
836             return true;
837         }
838         return false;
839     }
840
841     function getStoplisted($word)
842     {
843         return $this->stoplisted;
844     }
845 }
846
847 /**
848  * search baseclass, sql-specific
849  */
850 class WikiDB_backend_search_sql extends WikiDB_backend_search
851 {
852     function _pagename_match_clause($node)
853     {
854         // word already quoted by TextSearchQuery_node_word::sql_quote()
855         $word = $node->sql();
856         if ($word == '%') // ALL shortcut
857             return "1=1";
858         else
859             return ($this->_case_exact
860                 ? "pagename LIKE '$word'"
861                 : "LOWER(pagename) LIKE '$word'");
862     }
863
864     function _fulltext_match_clause($node)
865     {
866         // force word-style %word% for fulltext search
867         $word = '%' . $node->_sql_quote($node->word) . '%';
868         // eliminate stoplist words
869         if ($this->isStoplisted($node))
870             return "1=1"; // and (pagename or 1) => and 1
871         else
872             return $this->_pagename_match_clause($node)
873                 // probably convert this MATCH AGAINST or SUBSTR/POSITION without wildcards
874                 . ($this->_case_exact ? " OR content LIKE '$word'"
875                     : " OR LOWER(content) LIKE '$word'");
876     }
877 }
878
879 // Local Variables:
880 // mode: php
881 // tab-width: 8
882 // c-basic-offset: 4
883 // c-hanging-comment-ender-p: nil
884 // indent-tabs-mode: nil
885 // End: