]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/PearDB.php
fix quoting
[SourceForge/phpwiki.git] / lib / WikiDB / backend / PearDB.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PearDB.php,v 1.82 2005-01-18 08:55:51 rurban Exp $');
3
4 require_once('lib/WikiDB/backend.php');
5 //require_once('lib/FileFinder.php');
6 //require_once('lib/ErrorManager.php');
7
8 class WikiDB_backend_PearDB
9 extends WikiDB_backend
10 {
11     var $_dbh;
12
13     function WikiDB_backend_PearDB ($dbparams) {
14         // Find and include PEAR's DB.php. maybe we should force our private version again...
15         // if DB would have exported its version number, it would be easier.
16         @require_once('DB/common.php'); // Either our local pear copy or the system one
17         // check the version!
18         $name = check_php_version(5) ? "escapeSimple" : strtolower("escapeSimple");
19         if (!in_array($name, get_class_methods("DB_common"))) {
20             $finder = new FileFinder;
21             $dir = dirname(__FILE__)."/../../pear";
22             $finder->_prepend_to_include_path($dir);
23             include_once("$dir/DB/common.php"); // use our version instead.
24             if (!in_array($name, get_class_methods("DB_common"))) {
25                 $pearFinder = new PearFileFinder("lib/pear");
26                 $pearFinder->includeOnce('DB.php');
27             } else {
28                 include_once("$dir/DB.php");
29             }
30         } else {
31           include_once("DB.php");
32         }
33
34         // Install filter to handle bogus error notices from buggy DB.php's.
35         // TODO: check the Pear_DB version, but how?
36         if (0) {
37             global $ErrorManager;
38             $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_pear_notice_filter'));
39             $this->_pearerrhandler = true;
40         }
41         
42         // Open connection to database
43         $this->_dsn = $dbparams['dsn'];
44         $this->_dbparams = $dbparams;
45         $dboptions = array('persistent' => true,
46                            'debug' => 2);
47         if (preg_match('/^pgsql/',$this->_dsn))
48             $dboptions['persistent'] = false;
49         $this->_dbh = DB::connect($this->_dsn, $dboptions);
50         $dbh = &$this->_dbh;
51         if (DB::isError($dbh)) {
52             trigger_error(sprintf("Can't connect to database: %s",
53                                   $this->_pear_error_message($dbh)),
54                           E_USER_ERROR);
55         }
56         $dbh->setErrorHandling(PEAR_ERROR_CALLBACK,
57                                array($this, '_pear_error_callback'));
58         $dbh->setFetchMode(DB_FETCHMODE_ASSOC);
59
60         $prefix = isset($dbparams['prefix']) ? $dbparams['prefix'] : '';
61         $this->_table_names
62             = array('page_tbl'     => $prefix . 'page',
63                     'version_tbl'  => $prefix . 'version',
64                     'link_tbl'     => $prefix . 'link',
65                     'recent_tbl'   => $prefix . 'recent',
66                     'nonempty_tbl' => $prefix . 'nonempty');
67         $page_tbl = $this->_table_names['page_tbl'];
68         $version_tbl = $this->_table_names['version_tbl'];
69         $this->page_tbl_fields = "$page_tbl.id AS id, $page_tbl.pagename AS pagename, $page_tbl.hits AS hits";
70         $this->version_tbl_fields = "$version_tbl.version AS version, $version_tbl.mtime AS mtime, ".
71             "$version_tbl.minor_edit AS minor_edit, $version_tbl.content AS content, $version_tbl.versiondata AS versiondata";
72
73         $this->_expressions
74             = array('maxmajor'     => "MAX(CASE WHEN minor_edit=0 THEN version END)",
75                     'maxminor'     => "MAX(CASE WHEN minor_edit<>0 THEN version END)",
76                     'maxversion'   => "MAX(version)",
77                     'notempty'     => "<>''",
78                     'iscontent'    => "content<>''");
79         
80         $this->_lock_count = 0;
81     }
82     
83     /**
84      * Close database connection.
85      */
86     function close () {
87         if (!$this->_dbh)
88             return;
89         if ($this->_lock_count) {
90             trigger_error( "WARNING: database still locked " . '(lock_count = $this->_lock_count)' . "\n<br />",
91                           E_USER_WARNING);
92         }
93         $this->_dbh->setErrorHandling(PEAR_ERROR_PRINT);        // prevent recursive loops.
94         $this->unlock('force');
95
96         $this->_dbh->disconnect();
97
98         if (!empty($this->_pearerrhandler)) {
99             $GLOBALS['ErrorManager']->popErrorHandler();
100         }
101     }
102
103
104     /*
105      * Test fast wikipage.
106      */
107     function is_wiki_page($pagename) {
108         $dbh = &$this->_dbh;
109         extract($this->_table_names);
110         return $dbh->getOne(sprintf("SELECT $page_tbl.id as id"
111                                     . " FROM $nonempty_tbl, $page_tbl"
112                                     . " WHERE $nonempty_tbl.id=$page_tbl.id"
113                                     . "   AND pagename='%s'",
114                                     $dbh->escapeSimple($pagename)));
115     }
116         
117     function get_all_pagenames() {
118         $dbh = &$this->_dbh;
119         extract($this->_table_names);
120         return $dbh->getCol("SELECT pagename"
121                             . " FROM $nonempty_tbl, $page_tbl"
122                             . " WHERE $nonempty_tbl.id=$page_tbl.id");
123     }
124
125     function numPages($filter=false, $exclude='') {
126         $dbh = &$this->_dbh;
127         extract($this->_table_names);
128         return $dbh->getOne("SELECT count(*)"
129                             . " FROM $nonempty_tbl, $page_tbl"
130                             . " WHERE $nonempty_tbl.id=$page_tbl.id");
131     }
132     
133     function increaseHitCount($pagename) {
134         $dbh = &$this->_dbh;
135         // Hits is the only thing we can update in a fast manner.
136         // Note that this will fail silently if the page does not
137         // have a record in the page table.  Since it's just the
138         // hit count, who cares?
139         $dbh->query(sprintf("UPDATE %s SET hits=hits+1 WHERE pagename='%s'",
140                             $this->_table_names['page_tbl'],
141                             $dbh->escapeSimple($pagename)));
142         return;
143     }
144
145     /**
146      * Read page information from database.
147      */
148     function get_pagedata($pagename) {
149         $dbh = &$this->_dbh;
150         //trigger_error("GET_PAGEDATA $pagename", E_USER_NOTICE);
151         $result = $dbh->getRow(sprintf("SELECT hits,pagedata FROM %s WHERE pagename='%s'",
152                                        $this->_table_names['page_tbl'],
153                                        $dbh->escapeSimple($pagename)),
154                                DB_FETCHMODE_ASSOC);
155         return $result ? $this->_extract_page_data($result) : false;
156     }
157
158     function  _extract_page_data($data) {
159         if (empty($data)) return array();
160         elseif (empty($data['pagedata'])) return $data;
161         else {
162             $data = array_merge($data, $this->_unserialize($data['pagedata']));
163             unset($data['pagedata']);
164             return $data;
165         }
166     }
167
168     function update_pagedata($pagename, $newdata) {
169         $dbh = &$this->_dbh;
170         $page_tbl = $this->_table_names['page_tbl'];
171
172         // Hits is the only thing we can update in a fast manner.
173         if (count($newdata) == 1 && isset($newdata['hits'])) {
174             // Note that this will fail silently if the page does not
175             // have a record in the page table.  Since it's just the
176             // hit count, who cares?
177             $dbh->query(sprintf("UPDATE $page_tbl SET hits=%d WHERE pagename='%s'",
178                                 $newdata['hits'], $dbh->escapeSimple($pagename)));
179             return;
180         }
181
182         $this->lock(array($page_tbl), true);
183         $data = $this->get_pagedata($pagename);
184         if (!$data) {
185             $data = array();
186             $this->_get_pageid($pagename, true); // Creates page record
187         }
188         
189         @$hits = (int)$data['hits'];
190         unset($data['hits']);
191
192         foreach ($newdata as $key => $val) {
193             if ($key == 'hits')
194                 $hits = (int)$val;
195             else if (empty($val))
196                 unset($data[$key]);
197             else
198                 $data[$key] = $val;
199         }
200
201         /* Portability issue -- not all DBMS supports huge strings 
202          * so we need to 'bind' instead of building a simple SQL statment.
203          * Note that we do not need to escapeSimple when we bind
204         $dbh->query(sprintf("UPDATE $page_tbl"
205                             . " SET hits=%d, pagedata='%s'"
206                             . " WHERE pagename='%s'",
207                             $hits,
208                             $dbh->escapeSimple($this->_serialize($data)),
209                             $dbh->escapeSimple($pagename)));
210         */
211         $sth = $dbh->query("UPDATE $page_tbl"
212                            . " SET hits=?, pagedata=?"
213                            . " WHERE pagename=?",
214                            array($hits, $this->_serialize($data), $pagename));
215         $this->unlock(array($page_tbl));
216     }
217
218     function get_cached_html($pagename) {
219         $dbh = &$this->_dbh;
220         $page_tbl = $this->_table_names['page_tbl'];
221         return $dbh->GetOne(sprintf("SELECT cached_html FROM $page_tbl WHERE pagename='%s'",
222                                     $dbh->escapeSimple($pagename)));
223     }
224
225     function set_cached_html($pagename, $data) {
226         $dbh = &$this->_dbh;
227         $page_tbl = $this->_table_names['page_tbl'];
228         $sth = $dbh->query("UPDATE $page_tbl"
229                            . " SET cached_html=?"
230                            . " WHERE pagename=?",
231                            array($data, $pagename));
232     }
233
234     function _get_pageid($pagename, $create_if_missing = false) {
235         
236         // check id_cache
237         global $request;
238         $cache =& $request->_dbi->_cache->_id_cache;
239         if (isset($cache[$pagename])) {
240             if ($cache[$pagename] or !$create_if_missing) {
241                 return $cache[$pagename];
242             }
243         }
244
245         $dbh = &$this->_dbh;
246         $page_tbl = $this->_table_names['page_tbl'];
247         
248         $query = sprintf("SELECT id FROM $page_tbl WHERE pagename='%s'",
249                          $dbh->escapeSimple($pagename));
250
251         if (!$create_if_missing)
252             return $dbh->getOne($query);
253
254         $id = $dbh->getOne($query);
255         if (empty($id)) {
256             $this->lock(array($page_tbl), true); // write lock
257             $max_id = $dbh->getOne("SELECT MAX(id) FROM $page_tbl");
258             $id = $max_id + 1;
259             $dbh->query(sprintf("INSERT INTO $page_tbl"
260                                 . " (id,pagename,hits)"
261                                 . " VALUES (%d,'%s',0)",
262                                 $id, $dbh->escapeSimple($pagename)));
263             $this->unlock(array($page_tbl));
264         }
265         return $id;
266     }
267
268     function get_latest_version($pagename) {
269         $dbh = &$this->_dbh;
270         extract($this->_table_names);
271         return
272             (int)$dbh->getOne(sprintf("SELECT latestversion"
273                                       . " FROM $page_tbl, $recent_tbl"
274                                       . " WHERE $page_tbl.id=$recent_tbl.id"
275                                       . "  AND pagename='%s'",
276                                       $dbh->escapeSimple($pagename)));
277     }
278
279     function get_previous_version($pagename, $version) {
280         $dbh = &$this->_dbh;
281         extract($this->_table_names);
282         
283         return
284             (int)$dbh->getOne(sprintf("SELECT version"
285                                       . " FROM $version_tbl, $page_tbl"
286                                       . " WHERE $version_tbl.id=$page_tbl.id"
287                                       . "  AND pagename='%s'"
288                                       . "  AND version < %d"
289                                       . " ORDER BY version DESC",
290                                       /* Non portable and useless anyway with getOne
291                                       . " LIMIT 1",
292                                       */
293                                       $dbh->escapeSimple($pagename),
294                                       $version));
295     }
296     
297     /**
298      * Get version data.
299      *
300      * @param $version int Which version to get.
301      *
302      * @return hash The version data, or false if specified version does not
303      *              exist.
304      */
305     function get_versiondata($pagename, $version, $want_content = false) {
306         $dbh = &$this->_dbh;
307         extract($this->_table_names);
308         extract($this->_expressions);
309
310         assert(is_string($pagename) and $pagename != "");
311         assert($version > 0);
312         
313         //trigger_error("GET_REVISION $pagename $version $want_content", E_USER_NOTICE);
314         // FIXME: optimization: sometimes don't get page data?
315         if ($want_content) {
316             $fields = $this->page_tbl_fields 
317                 . ",$page_tbl.pagedata as pagedata," 
318                 . $this->version_tbl_fields;
319         }
320         else {
321             $fields = $this->page_tbl_fields . ","
322                 . "mtime, minor_edit, versiondata,"
323                 . "$iscontent AS have_content";
324         }
325
326         $result = $dbh->getRow(sprintf("SELECT $fields"
327                                        . " FROM $page_tbl, $version_tbl"
328                                        . " WHERE $page_tbl.id=$version_tbl.id"
329                                        . "  AND pagename='%s'"
330                                        . "  AND version=%d",
331                                        $dbh->escapeSimple($pagename), $version),
332                                DB_FETCHMODE_ASSOC);
333
334         return $this->_extract_version_data($result);
335     }
336
337     function _extract_version_data($query_result) {
338         if (!$query_result)
339             return false;
340
341         $data = $this->_unserialize($query_result['versiondata']);
342         
343         $data['mtime'] = $query_result['mtime'];
344         $data['is_minor_edit'] = !empty($query_result['minor_edit']);
345         
346         if (isset($query_result['content']))
347             $data['%content'] = $query_result['content'];
348         elseif ($query_result['have_content'])
349             $data['%content'] = true;
350         else
351             $data['%content'] = '';
352
353         // FIXME: this is ugly.
354         if (isset($query_result['pagedata'])) {
355             // Query also includes page data.
356             // We might as well send that back too...
357             unset($query_result['versiondata']);
358             $data['%pagedata'] = $this->_extract_page_data($query_result);
359         }
360
361         return $data;
362     }
363
364
365     /**
366      * Create a new revision of a page.
367      */
368     function set_versiondata($pagename, $version, $data) {
369         $dbh = &$this->_dbh;
370         $version_tbl = $this->_table_names['version_tbl'];
371         
372         $minor_edit = (int) !empty($data['is_minor_edit']);
373         unset($data['is_minor_edit']);
374         
375         $mtime = (int)$data['mtime'];
376         unset($data['mtime']);
377         assert(!empty($mtime));
378
379         @$content = (string) $data['%content'];
380         unset($data['%content']);
381
382         unset($data['%pagedata']);
383         
384         $this->lock();
385         $id = $this->_get_pageid($pagename, true);
386
387         // FIXME: optimize: mysql can do this with one REPLACE INTO (I think).
388         $dbh->query(sprintf("DELETE FROM $version_tbl"
389                             . " WHERE id=%d AND version=%d",
390                             $id, $version));
391
392         /* mysql optimized version. 
393         $dbh->query(sprintf("INSERT INTO $version_tbl"
394                             . " (id,version,mtime,minor_edit,content,versiondata)"
395                             . " VALUES(%d,%d,%d,%d,'%s','%s')",
396                             $id, $version, $mtime, $minor_edit,
397                             $dbh->quoteSmart($content),
398                             $dbh->quoteSmart($this->_serialize($data))));
399         */
400         // generic slow PearDB bind eh quoting.
401         $dbh->query("INSERT INTO $version_tbl"
402                     . " (id,version,mtime,minor_edit,content,versiondata)"
403                     . " VALUES(?, ?, ?, ?, ?, ?)",
404                     array($id, $version, $mtime, $minor_edit, $content,
405                     $this->_serialize($data)));
406
407         $this->_update_recent_table($id);
408         $this->_update_nonempty_table($id);
409         
410         $this->unlock();
411     }
412     
413     /**
414      * Delete an old revision of a page.
415      */
416     function delete_versiondata($pagename, $version) {
417         $dbh = &$this->_dbh;
418         extract($this->_table_names);
419
420         $this->lock();
421         if ( ($id = $this->_get_pageid($pagename)) ) {
422             $dbh->query("DELETE FROM $version_tbl"
423                         . " WHERE id=$id AND version=$version");
424             $this->_update_recent_table($id);
425             // This shouldn't be needed (as long as the latestversion
426             // never gets deleted.)  But, let's be safe.
427             $this->_update_nonempty_table($id);
428         }
429         $this->unlock();
430     }
431
432     /**
433      * Delete page from the database with backup possibility.
434      * i.e save_page('') and DELETE nonempty id
435      * Can be undone and is seen in RecentChanges.
436      */
437     /* // see parent backend.php
438     function delete_page($pagename) {
439         $mtime = time();
440         $user =& $GLOBALS['request']->_user;
441         $vdata = array('author' => $user->getId(),
442                        'author_id' => $user->getAuthenticatedId(),
443                        'mtime' => $mtime);
444
445         $this->lock();
446         $version = $this->get_latest_version($pagename);
447         $this->set_versiondata($pagename, $version+1, $vdata);
448         $this->set_links($pagename, false);
449         $pagedata = get_pagedata($pagename);
450         $this->update_pagedata($pagename, array('hits' => $pagedata['hits']));
451         $this->unlock();
452     }
453     */
454
455     /**
456      * Delete page completely from the database.
457      * I'm not sure if this is what we want. Maybe just delete the revisions
458      */
459     function purge_page($pagename) {
460         $dbh = &$this->_dbh;
461         extract($this->_table_names);
462         
463         $this->lock();
464         if ( ($id = $this->_get_pageid($pagename, false)) ) {
465             $dbh->query("DELETE FROM $version_tbl  WHERE id=$id");
466             $dbh->query("DELETE FROM $recent_tbl   WHERE id=$id");
467             $dbh->query("DELETE FROM $nonempty_tbl WHERE id=$id");
468             $dbh->query("DELETE FROM $link_tbl     WHERE linkfrom=$id");
469             $nlinks = $dbh->getOne("SELECT COUNT(*) FROM $link_tbl WHERE linkto=$id");
470             if ($nlinks) {
471                 // We're still in the link table (dangling link) so we can't delete this
472                 // altogether.
473                 $dbh->query("UPDATE $page_tbl SET hits=0, pagedata='' WHERE id=$id");
474                 $result = 0;
475             }
476             else {
477                 $dbh->query("DELETE FROM $page_tbl WHERE id=$id");
478                 $result = 1;
479             }
480             $this->_update_recent_table();
481             $this->_update_nonempty_table();
482         } else {
483             $result = -1; // already purged or not existing
484         }
485         $this->unlock();
486         return $result;
487     }
488
489     // The only thing we might be interested in updating which we can
490     // do fast in the flags (minor_edit).   I think the default
491     // update_versiondata will work fine...
492     //function update_versiondata($pagename, $version, $data) {
493     //}
494
495     function set_links($pagename, $links) {
496         // Update link table.
497         // FIXME: optimize: mysql can do this all in one big INSERT.
498
499         $dbh = &$this->_dbh;
500         extract($this->_table_names);
501
502         $this->lock();
503         $pageid = $this->_get_pageid($pagename, true);
504
505         $dbh->query("DELETE FROM $link_tbl WHERE linkfrom=$pageid");
506
507         if ($links) {
508             foreach($links as $link) {
509                 if (isset($linkseen[$link]))
510                     continue;
511                 $linkseen[$link] = true;
512                 $linkid = $this->_get_pageid($link, true);
513                 $dbh->query("INSERT INTO $link_tbl (linkfrom, linkto)"
514                             . " VALUES ($pageid, $linkid)");
515             }
516         }
517         $this->unlock();
518     }
519     
520     /**
521      * Find pages which link to or are linked from a page.
522      */
523     function get_links($pagename, $reversed=true, $include_empty=false,
524                        $sortby=false, $limit=false, $exclude='') {
525         $dbh = &$this->_dbh;
526         extract($this->_table_names);
527
528         if ($reversed)
529             list($have,$want) = array('linkee', 'linker');
530         else
531             list($have,$want) = array('linker', 'linkee');
532         $orderby = $this->sortby($sortby, 'db', array('pagename'));
533         if ($orderby) $orderby = ' ORDER BY $want.' . $orderby;
534         if ($exclude) // array of pagenames
535             $exclude = " AND $want.pagename NOT IN ".$this->_sql_set($exclude);
536         else 
537             $exclude='';
538
539         $qpagename = $dbh->escapeSimple($pagename);
540         $sql = "SELECT $want.id as id, $want.pagename as pagename, $want.hits as hits"
541             // Looks like 'AS' in column alias is a MySQL thing, Oracle does not like it
542             // and the PostgresSQL manual does not have it either
543             // Since it is optional in mySQL, just remove it...
544             . " FROM $link_tbl, $page_tbl linker, $page_tbl linkee"
545             . (!$include_empty ? ", $nonempty_tbl" : '')
546             . " WHERE linkfrom=linker.id AND linkto=linkee.id"
547             . "  AND $have.pagename='$qpagename'"
548             . (!$include_empty ? " AND $nonempty_tbl.id=$want.id" : "")
549             //. " GROUP BY $want.id"
550             . $exclude
551             . $orderby;
552         if ($limit) {
553             // extract from,count from limit
554             list($from,$count) = $this->limit($limit);
555             $result = $dbh->limitQuery($sql, $from, $count);
556         } else {
557             $result = $dbh->query($sql);
558         }
559         
560         return new WikiDB_backend_PearDB_iter($this, $result);
561     }
562
563     /**
564      * Find if a page links to another page
565      */
566     function exists_link($pagename, $link, $reversed=false) {
567         $dbh = &$this->_dbh;
568         extract($this->_table_names);
569
570         if ($reversed)
571             list($have, $want) = array('linkee', 'linker');
572         else
573             list($have, $want) = array('linker', 'linkee');
574         $qpagename = $dbh->escapeSimple($pagename);
575         $qlink = $dbh->escapeSimple($link);
576         $row = $dbh->GetRow("SELECT IF($want.pagename,1,0) as result"
577                                 . " FROM $link_tbl, $page_tbl linker, $page_tbl linkee, $nonempty_tbl"
578                                 . " WHERE linkfrom=linker.id AND linkto=linkee.id"
579                                 . " AND $have.pagename='$qpagename'"
580                                 . " AND $want.pagename='$qlink'"
581                                 . "LIMIT 1");
582         return $row['result'];
583     }
584
585     function get_all_pages($include_empty=false, $sortby=false, $limit=false, $exclude='') {
586         $dbh = &$this->_dbh;
587         extract($this->_table_names);
588         $orderby = $this->sortby($sortby, 'db');
589         if ($orderby) $orderby = ' ORDER BY ' . $orderby;
590         if ($exclude) // array of pagenames
591             $exclude = " AND $page_tbl.pagename NOT IN ".$this->_sql_set($exclude);
592         else 
593             $exclude='';
594
595         if (strstr($orderby, 'mtime ')) { // multiple columns possible
596             if ($include_empty) {
597                 $sql = "SELECT "
598                     . $this->page_tbl_fields
599                     . " FROM $page_tbl, $recent_tbl, $version_tbl"
600                     . " WHERE $page_tbl.id=$recent_tbl.id"
601                     . " AND $page_tbl.id=$version_tbl.id AND latestversion=version"
602                     . $exclude
603                     . $orderby;
604             }
605             else {
606                 $sql = "SELECT "
607                     . $this->page_tbl_fields
608                     . " FROM $nonempty_tbl, $page_tbl, $recent_tbl, $version_tbl"
609                     . " WHERE $nonempty_tbl.id=$page_tbl.id"
610                     . " AND $page_tbl.id=$recent_tbl.id"
611                     . " AND $page_tbl.id=$version_tbl.id AND latestversion=version"
612                     . $exclude
613                     . $orderby;
614             }
615         } else {
616             if ($include_empty) {
617                 $sql = "SELECT "
618                     . $this->page_tbl_fields 
619                     ." FROM $page_tbl"
620                     . $exclude ? " WHERE $exclude" : ''
621                     . $orderby;
622             }
623             else {
624                 $sql = "SELECT "
625                     . $this->page_tbl_fields
626                     . " FROM $nonempty_tbl, $page_tbl"
627                     . " WHERE $nonempty_tbl.id=$page_tbl.id"
628                     . $exclude
629                     . $orderby;
630             }
631         }
632         if ($limit) {
633             // extract from,count from limit
634             list($from,$count) = $this->limit($limit);
635             $result = $dbh->limitQuery($sql, $from, $count);
636         } else {
637             $result = $dbh->query($sql);
638         }
639         return new WikiDB_backend_PearDB_iter($this, $result);
640     }
641         
642     /**
643      * Title search.
644      */
645     function text_search($search, $fulltext=false) {
646         $dbh = &$this->_dbh;
647         extract($this->_table_names);
648
649         $searchclass = get_class($this)."_search";
650         // no need to define it everywhere and then fallback. memory!
651         if (!class_exists($searchclass))
652             $searchclass = "WikiDB_backend_PearDB_search";
653         $searchobj = new $searchclass($search, $dbh);
654         
655         $table = "$nonempty_tbl, $page_tbl";
656         $join_clause = "$nonempty_tbl.id=$page_tbl.id";
657         $fields = $this->page_tbl_fields;
658
659         if ($fulltext) {
660             $table .= ", $recent_tbl";
661             $join_clause .= " AND $page_tbl.id=$recent_tbl.id";
662
663             $table .= ", $version_tbl";
664             $join_clause .= " AND $page_tbl.id=$version_tbl.id AND latestversion=version";
665
666             $fields .= ", $page_tbl.pagedata as pagedata, " . $this->version_tbl_fields;
667             $callback = new WikiMethodCb($searchobj, "_fulltext_match_clause");
668         } else {
669             $callback = new WikiMethodCb($searchobj, "_pagename_match_clause");
670         }
671         $search_clause = $search->makeSqlClauseObj($callback);
672         
673         $result = $dbh->query("SELECT $fields FROM $table"
674                               . " WHERE $join_clause"
675                               . "  AND ($search_clause)"
676                               . " ORDER BY pagename");
677         
678         return new WikiDB_backend_PearDB_iter($this, $result);
679     }
680
681     //Todo: check if the better Mysql MATCH operator is supported,
682     // (ranked search) and also google like expressions.
683     function _sql_match_clause($word) {
684         $word = preg_replace('/(?=[%_\\\\])/', "\\", $word);
685         $word = $this->_dbh->escapeSimple($word);
686         //$page_tbl = $this->_table_names['page_tbl'];
687         //Note: Mysql 4.1.0 has a bug which fails with binary fields.
688         //      e.g. if word is lowercased.
689         // http://bugs.mysql.com/bug.php?id=1491
690         return "LOWER(pagename) LIKE '%$word%'";
691     }
692     function _sql_casematch_clause($word) {
693         $word = preg_replace('/(?=[%_\\\\])/', "\\", $word);
694         $word = $this->_dbh->escapeSimple($word);
695         return "pagename LIKE '%$word%'";
696     }
697     function _fullsearch_sql_match_clause($word) {
698         $word = preg_replace('/(?=[%_\\\\])/', "\\", $word);
699         $word = $this->_dbh->escapeSimple($word);
700         //$page_tbl = $this->_table_names['page_tbl'];
701         //Mysql 4.1.1 has a bug which fails here if word is lowercased.
702         return "LOWER(pagename) LIKE '%$word%' OR content LIKE '%$word%'";
703     }
704     function _fullsearch_sql_casematch_clause($word) {
705         $word = preg_replace('/(?=[%_\\\\])/', "\\", $word);
706         $word = $this->_dbh->escapeSimple($word);
707         return "pagename LIKE '%$word%' OR content LIKE '%$word%'";
708     }
709
710     /**
711      * Find highest or lowest hit counts.
712      */
713     function most_popular($limit=0, $sortby='-hits') {
714         $dbh = &$this->_dbh;
715         extract($this->_table_names);
716         if ($limit < 0){ 
717             $order = "hits ASC";
718             $limit = -$limit;
719             $where = ""; 
720         } else {
721             $order = "hits DESC";
722             $where = " AND hits > 0";
723         }
724         $orderby = '';
725         if ($sortby != '-hits') {
726             if ($order = $this->sortby($sortby, 'db'))
727                 $orderby = " ORDER BY " . $order;
728         } else {
729             $orderby = " ORDER BY $order";
730         }
731         //$limitclause = $limit ? " LIMIT $limit" : '';
732         $sql = "SELECT "
733             . $this->page_tbl_fields
734             . " FROM $nonempty_tbl, $page_tbl"
735             . " WHERE $nonempty_tbl.id=$page_tbl.id" 
736             . $where
737             . $orderby;
738          if ($limit) {
739              list($from, $count) = $this->limit($limit);
740              $result = $dbh->limitQuery($sql, $from, $count);
741          } else {
742              $result = $dbh->query($sql);
743          }
744
745         return new WikiDB_backend_PearDB_iter($this, $result);
746     }
747
748     /**
749      * Find recent changes.
750      */
751     function most_recent($params) {
752         $limit = 0;
753         $since = 0;
754         $include_minor_revisions = false;
755         $exclude_major_revisions = false;
756         $include_all_revisions = false;
757         extract($params);
758
759         $dbh = &$this->_dbh;
760         extract($this->_table_names);
761
762         $pick = array();
763         if ($since)
764             $pick[] = "mtime >= $since";
765                         
766         
767         if ($include_all_revisions) {
768             // Include all revisions of each page.
769             $table = "$page_tbl, $version_tbl";
770             $join_clause = "$page_tbl.id=$version_tbl.id";
771
772             if ($exclude_major_revisions) {
773                 // Include only minor revisions
774                 $pick[] = "minor_edit <> 0";
775             }
776             elseif (!$include_minor_revisions) {
777                 // Include only major revisions
778                 $pick[] = "minor_edit = 0";
779             }
780         }
781         else {
782             $table = "$page_tbl, $recent_tbl";
783             $join_clause = "$page_tbl.id=$recent_tbl.id";
784             $table .= ", $version_tbl";
785             $join_clause .= " AND $version_tbl.id=$page_tbl.id";
786             
787             if ($exclude_major_revisions) {
788                 // Include only most recent minor revision
789                 $pick[] = 'version=latestminor';
790             }
791             elseif (!$include_minor_revisions) {
792                 // Include only most recent major revision
793                 $pick[] = 'version=latestmajor';
794             }
795             else {
796                 // Include only the latest revision (whether major or minor).
797                 $pick[] ='version=latestversion';
798             }
799         }
800         $order = "DESC";
801         if($limit < 0){
802             $order = "ASC";
803             $limit = -$limit;
804         }
805         // $limitclause = $limit ? " LIMIT $limit" : '';
806         $where_clause = $join_clause;
807         if ($pick)
808             $where_clause .= " AND " . join(" AND ", $pick);
809
810         // FIXME: use SQL_BUFFER_RESULT for mysql?
811         $sql = "SELECT " 
812                . $this->page_tbl_fields . ", " . $this->version_tbl_fields
813                . " FROM $table"
814                . " WHERE $where_clause"
815                . " ORDER BY mtime $order";
816         if ($limit) {
817              list($from, $count) = $this->limit($limit);
818              $result = $dbh->limitQuery($sql, $from, $count);
819         } else {
820             $result = $dbh->query($sql);
821         }
822         return new WikiDB_backend_PearDB_iter($this, $result);
823     }
824
825     /**
826      * Find referenced empty pages.
827      */
828     function wanted_pages($exclude_from='', $exclude='', $sortby=false, $limit=false) {
829         $dbh = &$this->_dbh;
830         extract($this->_table_names);
831         if ($orderby = $this->sortby($sortby, 'db', array('pagename','wantedfrom')))
832             $orderby = 'ORDER BY ' . $orderby;
833
834         if ($exclude_from) // array of pagenames
835             $exclude_from = " AND linked.pagename NOT IN ".$this->_sql_set($exclude_from);
836         if ($exclude) // array of pagenames
837             $exclude = " AND $page_tbl.pagename NOT IN ".$this->_sql_set($exclude);
838
839         $sql = "SELECT $page_tbl.pagename,linked.pagename as wantedfrom"
840             . " FROM $link_tbl,$page_tbl as linked "
841             . " LEFT JOIN $page_tbl ON($link_tbl.linkto=$page_tbl.id)"
842             . " LEFT JOIN $nonempty_tbl ON($link_tbl.linkto=$nonempty_tbl.id)" 
843             . " WHERE ISNULL($nonempty_tbl.id) AND linked.id=$link_tbl.linkfrom"
844             . $exclude_from
845             . $exclude
846             . $orderby;
847         if ($limit) {
848             list($from, $count) = $this->limit($limit);
849             $result = $dbh->limitQuery($sql, $from, $count * 3);
850         } else {
851             $result = $dbh->query($sql);
852         }
853         return new WikiDB_backend_PearDB_generic_iter($this, $result);
854     }
855
856     function _sql_set(&$pagenames) {
857         $s = '(';
858         foreach ($pagenames as $p) {
859             $s .= ("'".$this->_dbh->escapeSimple($p)."',");
860         }
861         return substr($s,0,-1).")";
862     }
863
864     /**
865      * Rename page in the database.
866      */
867     function rename_page($pagename, $to) {
868         $dbh = &$this->_dbh;
869         extract($this->_table_names);
870         
871         $this->lock();
872         if ( ($id = $this->_get_pageid($pagename, false)) ) {
873             if ($new = $this->_get_pageid($to, false)) {
874                 //cludge alert!
875                 //this page does not exist (already verified before), but exists in the page table.
876                 //so we delete this page.
877                 $dbh->query("DELETE FROM $page_tbl WHERE id=$id");
878             }
879             $dbh->query(sprintf("UPDATE $page_tbl SET pagename='%s' WHERE id=$id",
880                                 $dbh->escapeSimple($to)));
881         }
882         $this->unlock();
883         return $id;
884     }
885
886     function _update_recent_table($pageid = false) {
887         $dbh = &$this->_dbh;
888         extract($this->_table_names);
889         extract($this->_expressions);
890
891         $pageid = (int)$pageid;
892
893         $this->lock();
894         $dbh->query("DELETE FROM $recent_tbl"
895                     . ( $pageid ? " WHERE id=$pageid" : ""));
896         $dbh->query( "INSERT INTO $recent_tbl"
897                      . " (id, latestversion, latestmajor, latestminor)"
898                      . " SELECT id, $maxversion, $maxmajor, $maxminor"
899                      . " FROM $version_tbl"
900                      . ( $pageid ? " WHERE id=$pageid" : "")
901                      . " GROUP BY id" );
902         $this->unlock();
903     }
904
905     function _update_nonempty_table($pageid = false) {
906         $dbh = &$this->_dbh;
907         extract($this->_table_names);
908
909         $pageid = (int)$pageid;
910
911         extract($this->_expressions);
912         $this->lock();
913         $dbh->query("DELETE FROM $nonempty_tbl"
914                     . ( $pageid ? " WHERE id=$pageid" : ""));
915         $dbh->query("INSERT INTO $nonempty_tbl (id)"
916                     . " SELECT $recent_tbl.id"
917                     . " FROM $recent_tbl, $version_tbl"
918                     . " WHERE $recent_tbl.id=$version_tbl.id"
919                     . "       AND version=latestversion"
920                     // We have some specifics here (Oracle)
921                     //. "  AND content<>''"
922                     . "  AND content $notempty"
923                     . ( $pageid ? " AND $recent_tbl.id=$pageid" : ""));
924         
925         $this->unlock();
926     }
927
928
929     /**
930      * Grab a write lock on the tables in the SQL database.
931      *
932      * Calls can be nested.  The tables won't be unlocked until
933      * _unlock_database() is called as many times as _lock_database().
934      *
935      * @access protected
936      */
937     function lock($tables = false, $write_lock = true) {
938         if ($this->_lock_count++ == 0)
939             $this->_lock_tables($write_lock);
940     }
941
942     /**
943      * Actually lock the required tables.
944      */
945     function _lock_tables($write_lock) {
946         trigger_error("virtual", E_USER_ERROR);
947     }
948     
949     /**
950      * Release a write lock on the tables in the SQL database.
951      *
952      * @access protected
953      *
954      * @param $force boolean Unlock even if not every call to lock() has been matched
955      * by a call to unlock().
956      *
957      * @see _lock_database
958      */
959     function unlock($tables = false, $force = false) {
960         if ($this->_lock_count == 0)
961             return;
962         if (--$this->_lock_count <= 0 || $force) {
963             $this->_unlock_tables();
964             $this->_lock_count = 0;
965         }
966     }
967
968     /**
969      * Actually unlock the required tables.
970      */
971     function _unlock_tables($write_lock) {
972         trigger_error("virtual", E_USER_ERROR);
973     }
974
975
976     /**
977      * Serialize data
978      */
979     function _serialize($data) {
980         if (empty($data))
981             return '';
982         assert(is_array($data));
983         return serialize($data);
984     }
985
986     /**
987      * Unserialize data
988      */
989     function _unserialize($data) {
990         return empty($data) ? array() : unserialize($data);
991     }
992     
993     /**
994      * Callback for PEAR (DB) errors.
995      *
996      * @access protected
997      *
998      * @param A PEAR_error object.
999      */
1000     function _pear_error_callback($error) {
1001         if ($this->_is_false_error($error))
1002             return;
1003         
1004         $this->_dbh->setErrorHandling(PEAR_ERROR_PRINT);        // prevent recursive loops.
1005         $this->close();
1006         trigger_error($this->_pear_error_message($error), E_USER_ERROR);
1007     }
1008
1009     /**
1010      * Detect false errors messages from PEAR DB.
1011      *
1012      * The version of PEAR DB which ships with PHP 4.0.6 has a bug in that
1013      * it doesn't recognize "LOCK" and "UNLOCK" as SQL commands which don't
1014      * return any data.  (So when a "LOCK" command doesn't return any data,
1015      * DB reports it as an error, when in fact, it's not.)
1016      *
1017      * @access private
1018      * @return bool True iff error is not really an error.
1019      */
1020     function _is_false_error($error) {
1021         if ($error->getCode() != DB_ERROR)
1022             return false;
1023
1024         $query = $this->_dbh->last_query;
1025
1026         if (! preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE|CREATE'
1027                          . '|DROP|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s/', $query)) {
1028             // Last query was not of the sort which doesn't return any data.
1029             //" <--kludge for brain-dead syntax coloring
1030             return false;
1031         }
1032         
1033         if (! in_array('ismanip', get_class_methods('DB'))) {
1034             // Pear shipped with PHP 4.0.4pl1 (and before, presumably)
1035             // does not have the DB::isManip method.
1036             return true;
1037         }
1038         
1039         if (DB::isManip($query)) {
1040             // If Pear thinks it's an isManip then it wouldn't have thrown
1041             // the error we're testing for....
1042             return false;
1043         }
1044
1045         return true;
1046     }
1047
1048     function _pear_error_message($error) {
1049         $class = get_class($this);
1050         $message = "$class: fatal database error\n"
1051              . "\t" . $error->getMessage() . "\n"
1052              . "\t(" . $error->getDebugInfo() . ")\n";
1053
1054         // Prevent password from being exposed during a connection error
1055         $safe_dsn = preg_replace('| ( :// .*? ) : .* (?=@) |xs',
1056                                  '\\1:XXXXXXXX', $this->_dsn);
1057         return str_replace($this->_dsn, $safe_dsn, $message);
1058     }
1059
1060     /**
1061      * Filter PHP errors notices from PEAR DB code.
1062      *
1063      * The PEAR DB code which ships with PHP 4.0.6 produces spurious
1064      * errors and notices.  This is an error callback (for use with
1065      * ErrorManager which will filter out those spurious messages.)
1066      * @see _is_false_error, ErrorManager
1067      * @access private
1068      */
1069     function _pear_notice_filter($err) {
1070         return ( $err->isNotice()
1071                  && preg_match('|DB[/\\\\]common.php$|', $err->errfile)
1072                  && $err->errline == 126
1073                  && preg_match('/Undefined offset: +0\b/', $err->errstr) );
1074     }
1075
1076     /* some variables and functions for DB backend abstraction (action=upgrade) */
1077     function database () {
1078         return $this->_dbh->dsn['database'];
1079     }
1080     function backendType() {
1081         return $this->_dbh->phptype;
1082     }
1083     function connection() {
1084         return $this->_dbh->connection;
1085     }
1086
1087     function listOfTables() {
1088         return $this->_dbh->getListOf('tables');
1089     }
1090     function listOfFields($database,$table) {
1091         if ($this->backendType() == 'mysql') {
1092             $fields = array();
1093             assert(!empty($database));
1094             assert(!empty($table));
1095             $result = mysql_list_fields($database, $table, $this->_dbh->connection) or 
1096                 trigger_error(__FILE__.':'.__LINE__.' '.mysql_error(), E_USER_WARNING);
1097             if (!$result) return array();
1098               $columns = mysql_num_fields($result);
1099             for ($i = 0; $i < $columns; $i++) {
1100                 $fields[] = mysql_field_name($result, $i);
1101             }
1102             mysql_free_result($result);
1103             return $fields;
1104         } else {
1105             // TODO: try ADODB version?
1106             trigger_error("Unsupported dbtype and backend. Either switch to ADODB or check it manually.");
1107         }
1108     }
1109
1110 };
1111
1112 /**
1113  * This class is a generic iterator.
1114  *
1115  * WikiDB_backend_PearDB_iter only iterates over things that have
1116  * 'pagename', 'pagedata', etc. etc.
1117  *
1118  * Probably WikiDB_backend_PearDB_iter and this class should be merged
1119  * (most of the code is cut-and-paste :-( ), but I am trying to make
1120  * changes that could be merged easily.
1121  *
1122  * @author: Dan Frankowski
1123  */
1124 class WikiDB_backend_PearDB_generic_iter
1125 extends WikiDB_backend_iterator
1126 {
1127     function WikiDB_backend_PearDB_generic_iter($backend, $query_result, $field_list = NULL) {
1128         if (DB::isError($query_result)) {
1129             // This shouldn't happen, I thought.
1130             $backend->_pear_error_callback($query_result);
1131         }
1132         
1133         $this->_backend = &$backend;
1134         $this->_result = $query_result;
1135     }
1136
1137     function count() {
1138         if (!$this->_result)
1139             return false;
1140         return $this->_result->numRows();
1141     }
1142     
1143     function next() {
1144         $backend = &$this->_backend;
1145         if (!$this->_result)
1146             return false;
1147
1148         $record = $this->_result->fetchRow(DB_FETCHMODE_ASSOC);
1149         if (!$record) {
1150             $this->free();
1151             return false;
1152         }
1153         
1154         return $record;
1155     }
1156
1157     function free () {
1158         if ($this->_result) {
1159             $this->_result->free();
1160             $this->_result = false;
1161         }
1162     }
1163 }
1164
1165 class WikiDB_backend_PearDB_iter
1166 extends WikiDB_backend_PearDB_generic_iter
1167 {
1168
1169     function next() {
1170         $backend = &$this->_backend;
1171         if (!$this->_result)
1172             return false;
1173
1174         $record = $this->_result->fetchRow(DB_FETCHMODE_ASSOC);
1175         if (!$record) {
1176             $this->free();
1177             return false;
1178         }
1179         
1180         $pagedata = $backend->_extract_page_data($record);
1181         $rec = array('pagename' => $record['pagename'],
1182                      'pagedata' => $pagedata);
1183
1184         if (!empty($record['version'])) {
1185             $rec['versiondata'] = $backend->_extract_version_data($record);
1186             $rec['version'] = $record['version'];
1187         }
1188         
1189         return $rec;
1190     }
1191 }
1192
1193 // word search
1194 class WikiDB_backend_PearDB_search
1195 extends WikiDB_backend_search
1196 {
1197     function WikiDB_backend_PearDB_search(&$search, &$dbh) {
1198         $this->_dbh = $dbh;
1199         $this->_case_exact = $search->_case_exact;
1200     }
1201     function _pagename_match_clause($node) { 
1202         $word = $node->sql();
1203         if ($node->op == 'REGEX') { // posix regex extensions
1204             if (preg_match("/mysql/i", $this->_dbh->phptype))
1205                 return "pagename REGEXP '$word'";
1206         } else {
1207             return $this->_case_exact ? "pagename LIKE '$word'" 
1208                                       : "LOWER(pagename) LIKE '$word'";
1209         }
1210     }
1211     function _fulltext_match_clause($node) { 
1212         $word = $node->sql();
1213         return $this->_pagename_match_clause($node)
1214                // probably convert this MATCH AGAINST or SUBSTR/POSITION without wildcards
1215                . ($this->_case_exact ? " OR content LIKE '$word'" 
1216                                      : " OR LOWER(content) LIKE '$word'");
1217     }
1218 }
1219
1220 // $Log: not supported by cvs2svn $
1221 // Revision 1.81  2005/01/17 08:53:09  rurban
1222 // pagedata fix by Charles Corrigan
1223 //
1224 // Revision 1.80  2004/12/22 18:33:31  rurban
1225 // fix page _id_cache logic for _get_pageid create_if_missing
1226 //
1227 // Revision 1.79  2004/12/10 02:45:27  rurban
1228 // SQL optimization:
1229 //   put _cached_html from pagedata into a new seperate blob, not huge serialized string.
1230 //   it is only rarelely needed: for current page only, if-not-modified
1231 //   but was extracted for every simple page iteration.
1232 //
1233 // Revision 1.78  2004/12/08 12:55:51  rurban
1234 // support new non-destructive delete_page via generic backend method
1235 //
1236 // Revision 1.77  2004/12/06 19:50:04  rurban
1237 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
1238 // renamed delete_page to purge_page.
1239 // enable action=edit&version=-1 to force creation of a new version.
1240 // added BABYCART_PATH config
1241 // fixed magiqc in adodb.inc.php
1242 // and some more docs
1243 //
1244 // Revision 1.76  2004/11/30 17:45:53  rurban
1245 // exists_links backend implementation
1246 //
1247 // Revision 1.75  2004/11/28 20:42:33  rurban
1248 // Optimize PearDB _extract_version_data and _extract_page_data.
1249 //
1250 // Revision 1.74  2004/11/27 14:39:05  rurban
1251 // simpified regex search architecture:
1252 //   no db specific node methods anymore,
1253 //   new sql() method for each node
1254 //   parallel to regexp() (which returns pcre)
1255 //   regex types bitmasked (op's not yet)
1256 // new regex=sql
1257 // clarified WikiDB::quote() backend methods:
1258 //   ->quote() adds surrounsing quotes
1259 //   ->qstr() (new method) assumes strings and adds no quotes! (in contrast to ADODB)
1260 //   pear and adodb have now unified quote methods for all generic queries.
1261 //
1262 // Revision 1.73  2004/11/26 18:39:02  rurban
1263 // new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
1264 //
1265 // Revision 1.72  2004/11/25 17:20:51  rurban
1266 // and again a couple of more native db args: backlinks
1267 //
1268 // Revision 1.71  2004/11/23 13:35:48  rurban
1269 // add case_exact search
1270 //
1271 // Revision 1.70  2004/11/21 11:59:26  rurban
1272 // remove final \n to be ob_cache independent
1273 //
1274 // Revision 1.69  2004/11/20 17:49:39  rurban
1275 // add fast exclude support to SQL get_all_pages
1276 //
1277 // Revision 1.68  2004/11/20 17:35:58  rurban
1278 // improved WantedPages SQL backends
1279 // PageList::sortby new 3rd arg valid_fields (override db fields)
1280 // WantedPages sql pager inexact for performance reasons:
1281 //   assume 3 wantedfrom per page, to be correct, no getTotal()
1282 // support exclude argument for get_all_pages, new _sql_set()
1283 //
1284 // Revision 1.67  2004/11/10 19:32:24  rurban
1285 // * optimize increaseHitCount, esp. for mysql.
1286 // * prepend dirs to the include_path (phpwiki_dir for faster searches)
1287 // * Pear_DB version logic (awful but needed)
1288 // * fix broken ADODB quote
1289 // * _extract_page_data simplification
1290 //
1291 // Revision 1.66  2004/11/10 15:29:21  rurban
1292 // * requires newer Pear_DB (as the internal one): quote() uses now escapeSimple for strings
1293 // * ACCESS_LOG_SQL: fix cause request not yet initialized
1294 // * WikiDB: moved SQL specific methods upwards
1295 // * new Pear_DB quoting: same as ADODB and as newer Pear_DB.
1296 //   fixes all around: WikiGroup, WikiUserNew SQL methods, SQL logging
1297 //
1298 // Revision 1.65  2004/11/09 17:11:17  rurban
1299 // * revert to the wikidb ref passing. there's no memory abuse there.
1300 // * use new wikidb->_cache->_id_cache[] instead of wikidb->_iwpcache, to effectively
1301 //   store page ids with getPageLinks (GleanDescription) of all existing pages, which
1302 //   are also needed at the rendering for linkExistingWikiWord().
1303 //   pass options to pageiterator.
1304 //   use this cache also for _get_pageid()
1305 //   This saves about 8 SELECT count per page (num all pagelinks).
1306 // * fix passing of all page fields to the pageiterator.
1307 // * fix overlarge session data which got broken with the latest ACCESS_LOG_SQL changes
1308 //
1309 // Revision 1.64  2004/11/07 16:02:52  rurban
1310 // new sql access log (for spam prevention), and restructured access log class
1311 // dbh->quote (generic)
1312 // pear_db: mysql specific parts seperated (using replace)
1313 //
1314 // Revision 1.63  2004/11/01 10:43:58  rurban
1315 // seperate PassUser methods into seperate dir (memory usage)
1316 // fix WikiUser (old) overlarge data session
1317 // remove wikidb arg from various page class methods, use global ->_dbi instead
1318 // ...
1319 //
1320 // Revision 1.62  2004/10/14 19:19:34  rurban
1321 // loadsave: check if the dumped file will be accessible from outside.
1322 // and some other minor fixes. (cvsclient native not yet ready)
1323 //
1324 // Revision 1.61  2004/10/14 17:19:17  rurban
1325 // allow most_popular sortby arguments
1326 //
1327 // Revision 1.60  2004/07/09 10:06:50  rurban
1328 // Use backend specific sortby and sortable_columns method, to be able to
1329 // select between native (Db backend) and custom (PageList) sorting.
1330 // Fixed PageList::AddPageList (missed the first)
1331 // Added the author/creator.. name to AllPagesBy...
1332 //   display no pages if none matched.
1333 // Improved dba and file sortby().
1334 // Use &$request reference
1335 //
1336 // Revision 1.59  2004/07/08 21:32:36  rurban
1337 // Prevent from more warnings, minor db and sort optimizations
1338 //
1339 // Revision 1.58  2004/07/08 16:56:16  rurban
1340 // use the backendType abstraction
1341 //
1342 // Revision 1.57  2004/07/05 12:57:54  rurban
1343 // add mysql timeout
1344 //
1345 // Revision 1.56  2004/07/04 10:24:43  rurban
1346 // forgot the expressions
1347 //
1348 // Revision 1.55  2004/07/03 16:51:06  rurban
1349 // optional DBADMIN_USER:DBADMIN_PASSWD for action=upgrade (if no ALTER permission)
1350 // added atomic mysql REPLACE for PearDB as in ADODB
1351 // fixed _lock_tables typo links => link
1352 // fixes unserialize ADODB bug in line 180
1353 //
1354 // Revision 1.54  2004/06/29 08:52:24  rurban
1355 // Use ...version() $need_content argument in WikiDB also:
1356 // To reduce the memory footprint for larger sets of pagelists,
1357 // we don't cache the content (only true or false) and
1358 // we purge the pagedata (_cached_html) also.
1359 // _cached_html is only cached for the current pagename.
1360 // => Vastly improved page existance check, ACL check, ...
1361 //
1362 // Now only PagedList info=content or size needs the whole content, esp. if sortable.
1363 //
1364 // Revision 1.53  2004/06/27 10:26:03  rurban
1365 // oci8 patch by Philippe Vanhaesendonck + some ADODB notes+fixes
1366 //
1367 // Revision 1.52  2004/06/25 14:15:08  rurban
1368 // reduce memory footprint by caching only requested pagedate content (improving most page iterators)
1369 //
1370 // Revision 1.51  2004/05/12 10:49:55  rurban
1371 // require_once fix for those libs which are loaded before FileFinder and
1372 //   its automatic include_path fix, and where require_once doesn't grok
1373 //   dirname(__FILE__) != './lib'
1374 // upgrade fix with PearDB
1375 // navbar.tmpl: remove spaces for IE &nbsp; button alignment
1376 //
1377 // Revision 1.50  2004/05/06 17:30:39  rurban
1378 // CategoryGroup: oops, dos2unix eol
1379 // improved phpwiki_version:
1380 //   pre -= .0001 (1.3.10pre: 1030.099)
1381 //   -p1 += .001 (1.3.9-p1: 1030.091)
1382 // improved InstallTable for mysql and generic SQL versions and all newer tables so far.
1383 // abstracted more ADODB/PearDB methods for action=upgrade stuff:
1384 //   backend->backendType(), backend->database(),
1385 //   backend->listOfFields(),
1386 //   backend->listOfTables(),
1387 //
1388 // Revision 1.49  2004/05/03 21:35:30  rurban
1389 // don't use persistent connections with postgres
1390 //
1391 // Revision 1.48  2004/04/26 20:44:35  rurban
1392 // locking table specific for better databases
1393 //
1394 // Revision 1.47  2004/04/20 00:06:04  rurban
1395 // themable paging support
1396 //
1397 // Revision 1.46  2004/04/19 21:51:41  rurban
1398 // php5 compatibility: it works!
1399 //
1400 // Revision 1.45  2004/04/16 14:19:39  rurban
1401 // updated ADODB notes
1402 //
1403
1404 // (c-file-style: "gnu")
1405 // Local Variables:
1406 // mode: php
1407 // tab-width: 8
1408 // c-basic-offset: 4
1409 // c-hanging-comment-ender-p: nil
1410 // indent-tabs-mode: nil
1411 // End:   
1412 ?>