]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/dbmlib.php
PhpWiki 1.2.7 backport cvs release-1_2-branch enhancements never released with 1...
[SourceForge/phpwiki.git] / lib / dbmlib.php
1 <?php  
2
3    rcs_id('$Id: dbmlib.php,v 1.7.2.7 2005-01-07 14:23:04 rurban Exp $');
4
5    /*
6       Database functions:
7
8       OpenDataBase($table)
9       CloseDataBase($dbi)
10       RetrievePage($dbi, $pagename, $pagestore)
11       InsertPage($dbi, $pagename, $pagehash)
12       SaveCopyToArchive($dbi, $pagename, $pagehash) 
13       IsWikiPage($dbi, $pagename)
14       InitTitleSearch($dbi, $search)
15       TitleSearchNextMatch($dbi, $res)
16       InitFullSearch($dbi, $search)
17       FullSearchNextMatch($dbi, $res)
18       MakeBackLinkSearchRegexp($pagename)
19       InitBackLinkSearch($dbi, $pagename) 
20       BackLinkSearchNextMatch($dbi, &$pos) 
21       IncreaseHitCount($dbi, $pagename)
22       GetHitCount($dbi, $pagename)
23       InitMostPopular($dbi, $limit)
24       MostPopularNextMatch($dbi, $res)
25    */
26
27
28    // open a database and return the handle
29    // loop until we get a handle; php has its own
30    // locking mechanism, thank god.
31    // Suppress ugly error message with @.
32
33    function OpenDataBase($dbname) {
34       global $WikiDB; // hash of all the DBM file names
35
36       reset($WikiDB);
37       while (list($key, $file) = each($WikiDB)) {
38          while (($dbi[$key] = @dbmopen($file, "c")) < 1) {
39             $numattempts++;
40             if ($numattempts > MAX_DBM_ATTEMPTS) {
41                ExitWiki("Cannot open database '$key' : '$file', giving up.");
42             }
43             sleep(1);
44          }
45       }
46       return $dbi;
47    }
48
49
50    function CloseDataBase($dbi) {
51       reset($dbi);
52       while (list($dbmfile, $dbihandle) = each($dbi)) {
53          dbmclose($dbihandle);
54       }
55       return;
56    }
57
58
59    // take a serialized hash, return same padded out to
60    // the next largest number bytes divisible by 500. This
61    // is to save disk space in the long run, since DBM files
62    // leak memory.
63    function PadSerializedData($data) {
64       // calculate the next largest number divisible by 500
65       $nextincr = 500 * ceil(strlen($data) / 500);
66       // pad with spaces
67       $data = sprintf("%-${nextincr}s", $data);
68       return $data;
69    }
70
71    // strip trailing whitespace from the serialized data 
72    // structure.
73    function UnPadSerializedData($data) {
74       return chop($data);
75    }
76
77
78
79    // Return hash of page + attributes or default
80    function RetrievePage($dbi, $pagename, $pagestore) {
81       if ($data = dbmfetch($dbi[$pagestore], $pagename)) {
82          // unserialize $data into a hash
83          $pagehash = unserialize(UnPadSerializedData($data));
84          $pagehash['pagename'] = $pagename;
85          return $pagehash;
86       } else {
87          return -1;
88       }
89    }
90
91
92    // Either insert or replace a key/value (a page)
93    function InsertPage($dbi, $pagename, $pagehash, $pagestore='wiki') {
94
95       if ($pagestore == 'wiki') {       // a bit of a hack
96          $linklist = ExtractWikiPageLinks($pagehash['content']);
97          SetWikiPageLinks($dbi, $pagename, $linklist);
98       }
99
100       $pagedata = PadSerializedData(serialize($pagehash));
101
102       if (dbminsert($dbi[$pagestore], $pagename, $pagedata)) {
103          if (dbmreplace($dbi[$pagestore], $pagename, $pagedata)) {
104             ExitWiki("Error inserting page '$pagename'");
105          }
106       } 
107    }
108
109
110    // for archiving pages to a separate dbm
111    function SaveCopyToArchive($dbi, $pagename, $pagehash) {
112       global $ArchivePageStore;
113
114       $pagedata = PadSerializedData(serialize($pagehash));
115
116       if (dbminsert($dbi[$ArchivePageStore], $pagename, $pagedata)) {
117          if (dbmreplace($dbi['archive'], $pagename, $pagedata)) {
118             ExitWiki("Error storing '$pagename' into archive");
119          }
120       } 
121    }
122
123
124    function IsWikiPage($dbi, $pagename) {
125       return dbmexists($dbi['wiki'], $pagename);
126    }
127
128
129    function IsInArchive($dbi, $pagename) {
130       return dbmexists($dbi['archive'], $pagename);
131    }
132
133
134    function RemovePage($dbi, $pagename) {
135
136       dbmdelete($dbi['wiki'], $pagename);       // report error if this fails? 
137       dbmdelete($dbi['archive'], $pagename);    // no error if this fails
138       dbmdelete($dbi['hitcount'], $pagename);   // no error if this fails
139
140       $linkinfo = RetrievePage($dbi, $pagename, 'wikilinks');
141       
142       // remove page from fromlinks of pages it had links to
143       if (is_array($linkinfo)) {        // page exists?
144          $tolinks = $linkinfo['tolinks'];       
145          reset($tolinks);                       
146          while (list($tolink, $dummy) = each($tolinks)) {
147             $tolinkinfo = RetrievePage($dbi, $tolink, 'wikilinks');
148             if (is_array($tolinkinfo)) {                // page found?
149                $oldFromlinks = $tolinkinfo['fromlinks'];
150                $tolinkinfo['fromlinks'] = array();      // erase fromlinks
151                reset($oldFromlinks);
152                while (list($fromlink, $dummy) = each($oldFromlinks)) {
153                   if ($fromlink != $pagename)           // not to be erased? 
154                      $tolinkinfo['fromlinks'][$fromlink] = 1; // put link back
155                }                        // put link info back in DBM file
156                InsertPage($dbi, $tolink, $tolinkinfo, 'wikilinks');
157             }
158          }
159
160          // remove page itself     
161          dbmdelete($dbi['wikilinks'], $pagename);      
162       }
163    }
164
165
166    // setup for title-search
167    function InitTitleSearch($dbi, $search) {
168       $pos['search'] = '=' . preg_quote($search) . '=i';
169       $pos['key'] = dbmfirstkey($dbi['wiki']);
170
171       return $pos;
172    }
173
174
175    // iterating through database
176    function TitleSearchNextMatch($dbi, &$pos) {
177       while ($pos['key']) {
178          $page = $pos['key'];
179          $pos['key'] = dbmnextkey($dbi['wiki'], $pos['key']);
180
181          if (preg_match($pos['search'], $page)) {
182             return $page;
183          }
184       }
185       return 0;
186    }
187
188
189    // setup for full-text search
190    function InitFullSearch($dbi, $search) {
191       return InitTitleSearch($dbi, $search);
192    }
193
194
195    //iterating through database
196    function FullSearchNextMatch($dbi, &$pos) {
197       while ($pos['key']) {
198          $key = $pos['key'];
199          $pos['key'] = dbmnextkey($dbi['wiki'], $pos['key']);
200
201          $pagedata = dbmfetch($dbi['wiki'], $key);
202          // test the serialized data
203          if (preg_match($pos['search'], $pagedata)) {
204             $page['pagename'] = $key;
205             $pagedata = unserialize(UnPadSerializedData($pagedata));
206             $page['content'] = $pagedata['content'];
207             return $page;
208          }
209       }
210       return 0;
211    }
212
213
214    ////////////////////////
215    // new database features
216
217    // Compute PCRE suitable for searching for links to the given page.
218    function MakeBackLinkSearchRegexp($pagename) {
219       global $WikiNameRegexp;
220
221       // Note that in (at least some) PHP 3.x's, preg_quote only takes
222       // (at most) one argument.  Also it doesn't quote '/'s.
223       // It does quote '='s, so we'll use that for the delimeter.
224       $quoted_pagename = preg_quote($pagename);
225       if (preg_match("/^$WikiNameRegexp\$/", $pagename)) {
226          # FIXME: This may need modification for non-standard (non-english) $WikiNameRegexp.
227          return "=(?<![A-Za-z0-9!])$quoted_pagename(?![A-Za-z0-9])=";
228       }
229       else {
230          // Note from author: Sorry. :-/
231          return ( '='
232                   . '(?<!\[)\[(?!\[)' // Single, isolated '['
233                   . '([^]|]*\|)?'     // Optional stuff followed by '|'
234                   . '\s*'             // Optional space
235                   . $quoted_pagename  // Pagename
236                   . '\s*\]=' );       // Optional space, followed by ']'
237          // FIXME: the above regexp is still not quite right.
238          // Consider the text: " [ [ test page ]".  This is a link to a page
239          // named '[ test page'.  The above regexp will recognize this
240          // as a link either to '[ test page' (good) or to 'test page' (wrong).
241       } 
242    }
243
244    // setup for back-link search
245    function InitBackLinkSearch($dbi, $pagename) {
246       $pos['search'] = MakeBackLinkSearchRegexp($pagename);
247       $pos['key'] = dbmfirstkey($dbi['wiki']);
248
249       return $pos;
250    }
251
252    // iterating through back-links
253    function BackLinkSearchNextMatch($dbi, &$pos) {
254       while ($pos['key']) {
255          $page = $pos['key'];
256          $pos['key'] = dbmnextkey($dbi['wiki'], $pos['key']);
257
258          $rawdata = dbmfetch($dbi['wiki'], $page);
259          if ( ! preg_match($pos['search'], $rawdata))
260              continue;
261          
262          $pagedata = unserialize(UnPadSerializedData($rawdata));
263          while (list($i, $line) = each($pagedata['content'])) {
264             if (preg_match($pos['search'], $line))
265                return $page;
266          }
267       }
268       return 0;
269    }
270
271    function IncreaseHitCount($dbi, $pagename) {
272
273       if (dbmexists($dbi['hitcount'], $pagename)) {
274          // increase the hit count
275          // echo "$pagename there, incrementing...<br>\n";
276          $count = dbmfetch($dbi['hitcount'], $pagename);
277          $count++;
278          dbmreplace($dbi['hitcount'], $pagename, $count);
279       } else {
280          // add it, set the hit count to one
281          $count = 1;
282          dbminsert($dbi['hitcount'], $pagename, $count);
283       }
284    }
285
286
287    function GetHitCount($dbi, $pagename) {
288
289       if (dbmexists($dbi['hitcount'], $pagename)) {
290          // increase the hit count
291          $count = dbmfetch($dbi['hitcount'], $pagename);
292          return $count;
293       } else {
294          return 0;
295       }
296    }
297
298
299    function InitMostPopular($dbi, $limit) {
300       // iterate through the whole dbm file for hit counts
301       // sort the results highest to lowest, and return 
302       // n..$limit results
303
304       // Because sorting all the pages may be a lot of work
305       // we only get the top $limit. A page is only added if it's score is
306       // higher than the lowest score in the list. If the list is full then
307       // one of the pages with the lowest scores is removed.
308
309       $pagename = dbmfirstkey($dbi['hitcount']);
310       $score = dbmfetch($dbi['hitcount'], $pagename);
311       $res = array($pagename => (int) $score);
312       $lowest = $score;
313
314       while ($pagename = dbmnextkey($dbi['hitcount'], $pagename)) {
315          $score = dbmfetch($dbi['hitcount'], $pagename);      
316          if (count($res) < $limit) {    // room left in $res?
317             if ($score < $lowest)
318                $lowest = $score;
319             $res[$pagename] = (int) $score;     // add page to $res
320          } elseif ($score > $lowest) {
321             $oldres = $res;             // save old result
322             $res = array();
323             $removed = 0;               // nothing removed yet
324             $newlowest = $score;        // new lowest score
325             $res[$pagename] = (int) $score;     // add page to $res         
326             reset($oldres);
327             while(list($pname, $pscore) = each($oldres)) {
328                if (!$removed and ($pscore = $lowest))
329                   $removed = 1;         // don't copy this entry
330                else {
331                   $res[$pname] = (int) $pscore;
332                   if ($pscore < $newlowest)
333                      $newlowest = $pscore;
334                }
335             }
336             $lowest = $newlowest;
337          }
338       }
339        
340       arsort($res);             // sort
341       reset($res);
342        
343       return($res);
344    }
345
346
347    function MostPopularNextMatch($dbi, &$res) {
348
349       // the return result is a two element array with 'hits'
350       // and 'pagename' as the keys
351
352       if (list($pagename, $hits) = each($res)) {
353          $nextpage = array(
354             "hits" => $hits,
355             "pagename" => $pagename
356          );
357          return $nextpage;
358       } else {
359          return 0;
360       }
361    } 
362
363
364    function GetAllWikiPagenames($dbi) {
365       $namelist = array();
366       $ctr = 0;
367
368       $namelist[$ctr] = $key = dbmfirstkey($dbi['wiki']);
369
370       while ($key = dbmnextkey($dbi['wiki'], $key)) {
371          $ctr++;
372          $namelist[$ctr] = $key;
373       }
374
375       return $namelist;
376    }
377
378
379    ////////////////////////////////////////////
380    // functionality for the wikilinks DBM file
381
382    // format of the 'wikilinks' DBM file :
383    // pagename =>
384    //    { tolinks => ( pagename => 1}, fromlinks => { pagename => 1 } }
385
386    // takes a page name, returns array of scored incoming and outgoing links
387    function GetWikiPageLinks($dbi, $pagename) {
388
389       $linkinfo = RetrievePage($dbi, $pagename, 'wikilinks');
390       if (is_array($linkinfo))  {               // page exists?
391          $tolinks = $linkinfo['tolinks'];       // outgoing links
392          $fromlinks = $linkinfo['fromlinks'];   // incoming links
393       } else {          // new page, but pages may already point to it
394          // create info for page
395          $tolinks = array();
396          $fromlinks = array();
397          // look up pages that link to $pagename
398          $pname = dbmfirstkey($dbi['wikilinks']);
399          while ($pname) {
400             $linkinfo = RetrievePage($dbi, $pname, 'wikilinks');
401             if ($linkinfo['tolinks'][$pagename]) // $pname links to $pagename?
402                $fromlinks[$pname] = 1;
403             $pname = dbmnextkey($dbi['wikilinks'], $pname);
404          }
405       }
406
407       // get and sort the outgoing links
408       $outlinks = array();      
409       reset($tolinks);                  // look up scores for tolinks
410       while(list($tolink, $dummy) = each($tolinks)) {
411          $toPage = RetrievePage($dbi, $tolink, 'wikilinks');
412          if (is_array($toPage))         // link to internal page?
413             $outlinks[$tolink] = count($toPage['fromlinks']);
414       }
415       arsort($outlinks);                // sort on score
416       $links['out'] = array();
417       reset($outlinks);                 // convert to right format
418       while(list($link, $score) = each($outlinks))
419          $links['out'][] = array($link, $score);
420
421       // get and sort the incoming links
422       $inlinks = array();
423       reset($fromlinks);                // look up scores for fromlinks
424       while(list($fromlink, $dummy) = each($fromlinks)) {
425          $fromPage = RetrievePage($dbi, $fromlink, 'wikilinks');
426          $inlinks[$fromlink] = count($fromPage['fromlinks']);
427       } 
428       arsort($inlinks);                 // sort on score
429       $links['in'] = array();
430       reset($inlinks);                  // convert to right format
431       while(list($link, $score) = each($inlinks))
432          $links['in'][] = array($link, $score);
433
434       // sort all the incoming and outgoing links
435       $allLinks = $outlinks;            // copy the outlinks
436       reset($inlinks);                  // add the inlinks
437       while(list($key, $value) = each($inlinks))
438          $allLinks[$key] = $value;
439       reset($allLinks);                 // lookup hits
440       while(list($key, $value) = each($allLinks))
441          $allLinks[$key] = (int) dbmfetch($dbi['hitcount'], $key);
442       arsort($allLinks);                // sort on hits
443       $links['popular'] = array();
444       reset($allLinks);                 // convert to right format
445       while(list($link, $hits) = each($allLinks))
446          $links['popular'][] = array($link, $hits);
447
448       return $links;
449    }
450
451
452    // takes page name, list of links it contains
453    // the $linklist is an array where the keys are the page names
454    function SetWikiPageLinks($dbi, $pagename, $linklist) {
455
456       $cache = array();
457
458       // Phase 1: fetch the relevant pairs from 'wikilinks' into $cache
459       // ---------------------------------------------------------------
460
461       // first the info for $pagename
462       $linkinfo = RetrievePage($dbi, $pagename, 'wikilinks');
463       if (is_array($linkinfo))          // page exists?
464          $cache[$pagename] = $linkinfo;
465       else {
466          // create info for page
467          $cache[$pagename] = array( 'fromlinks' => array(),
468                                     'tolinks' => array()
469                              );
470          // look up pages that link to $pagename
471          $pname = dbmfirstkey($dbi['wikilinks']);
472          while ($pname) {
473             $linkinfo = RetrievePage($dbi, $pname, 'wikilinks');
474             if ($linkinfo['tolinks'][$pagename])
475                $cache[$pagename]['fromlinks'][$pname] = 1;
476             $pname = dbmnextkey($dbi['wikilinks'], $pname);
477          }
478       }
479                              
480       // then the info for the pages that $pagename used to point to 
481       $oldTolinks = $cache[$pagename]['tolinks'];
482       reset($oldTolinks);
483       while (list($link, $dummy) = each($oldTolinks)) {
484          $linkinfo = RetrievePage($dbi, $link, 'wikilinks');
485          if (is_array($linkinfo))
486             $cache[$link] = $linkinfo;
487       }
488
489       // finally the info for the pages that $pagename will point to
490       reset($linklist);
491       while (list($link, $dummy) = each($linklist)) {
492          $linkinfo = RetrievePage($dbi, $link, 'wikilinks');
493          if (is_array($linkinfo))
494             $cache[$link] = $linkinfo;
495       }
496               
497       // Phase 2: delete the old links
498       // ---------------------------------------------------------------
499
500       // delete the old tolinks for $pagename
501       // $cache[$pagename]['tolinks'] = array();
502       // (overwritten anyway in Phase 3)
503
504       // remove $pagename from the fromlinks of pages in $oldTolinks
505
506       reset($oldTolinks);
507       while (list($oldTolink, $dummy) = each($oldTolinks)) {
508          if ($cache[$oldTolink]) {      // links to existing page?
509             $oldFromlinks = $cache[$oldTolink]['fromlinks'];
510             $cache[$oldTolink]['fromlinks'] = array();  // erase fromlinks
511             reset($oldFromlinks);                       // comp. new fr.links
512             while (list($fromlink, $dummy) = each($oldFromlinks)) {
513                if ($fromlink != $pagename)
514                   $cache[$oldTolink]['fromlinks'][$fromlink] = 1;
515             }
516          }
517       }
518
519       // Phase 3: add the new links
520       // ---------------------------------------------------------------
521
522       // set the new tolinks for $pagename
523       $cache[$pagename]['tolinks'] = $linklist;
524
525       // add $pagename to the fromlinks of pages in $linklist
526       reset($linklist);
527       while (list($link, $dummy) = each($linklist)) {
528          if ($cache[$link])     // existing page?
529             $cache[$link]['fromlinks'][$pagename] = 1;
530       }
531
532       // Phase 4: write $cache back to 'wikilinks'
533       // ---------------------------------------------------------------
534
535       reset($cache);
536       while (list($link,$fromAndTolinks) = each($cache))
537          InsertPage($dbi, $link, $fromAndTolinks, 'wikilinks');
538
539    }
540
541 ?>