3 rcs_id('$Id: dbmlib.php,v 1.7.2.4 2001-11-07 23:19:16 dairiki Exp $');
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)
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 @.
33 function OpenDataBase($dbname) {
34 global $WikiDB; // hash of all the DBM file names
37 while (list($key, $file) = each($WikiDB)) {
38 while (($dbi[$key] = @dbmopen($file, "c")) < 1) {
40 if ($numattempts > MAX_DBM_ATTEMPTS) {
41 ExitWiki("Cannot open database '$key' : '$file', giving up.");
50 function CloseDataBase($dbi) {
52 while (list($dbmfile, $dbihandle) = each($dbi)) {
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
63 function PadSerializedData($data) {
64 // calculate the next largest number divisible by 500
65 $nextincr = 500 * ceil(strlen($data) / 500);
67 $data = sprintf("%-${nextincr}s", $data);
71 // strip trailing whitespace from the serialized data
73 function UnPadSerializedData($data) {
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;
92 // Either insert or replace a key/value (a page)
93 function InsertPage($dbi, $pagename, $pagehash, $pagestore='wiki') {
95 if ($pagestore == 'wiki') { // a bit of a hack
96 $linklist = ExtractWikiPageLinks($pagehash['content']);
97 SetWikiPageLinks($dbi, $pagename, $linklist);
100 $pagedata = PadSerializedData(serialize($pagehash));
102 if (dbminsert($dbi[$pagestore], $pagename, $pagedata)) {
103 if (dbmreplace($dbi[$pagestore], $pagename, $pagedata)) {
104 ExitWiki("Error inserting page '$pagename'");
110 // for archiving pages to a separate dbm
111 function SaveCopyToArchive($dbi, $pagename, $pagehash) {
112 global $ArchivePageStore;
114 $pagedata = PadSerializedData(serialize($pagehash));
116 if (dbminsert($dbi[$ArchivePageStore], $pagename, $pagedata)) {
117 if (dbmreplace($dbi['archive'], $pagename, $pagedata)) {
118 ExitWiki("Error storing '$pagename' into archive");
124 function IsWikiPage($dbi, $pagename) {
125 return dbmexists($dbi['wiki'], $pagename);
129 function IsInArchive($dbi, $pagename) {
130 return dbmexists($dbi['archive'], $pagename);
134 function RemovePage($dbi, $pagename) {
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
140 $linkinfo = RetrievePage($dbi, $pagename, 'wikilinks');
142 // remove page from fromlinks of pages it had links to
143 if (is_array($linkinfo)) { // page exists?
144 $tolinks = $linkinfo['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');
160 // remove page itself
161 dbmdelete($dbi['wikilinks'], $pagename);
166 // setup for title-search
167 function InitTitleSearch($dbi, $search) {
168 $pos['search'] = '=' . preg_quote($search) . '=i';
169 $pos['key'] = dbmfirstkey($dbi['wiki']);
175 // iterating through database
176 function TitleSearchNextMatch($dbi, &$pos) {
177 while ($pos['key']) {
179 $pos['key'] = dbmnextkey($dbi['wiki'], $pos['key']);
181 if (preg_match($pos['search'], $page)) {
189 // setup for full-text search
190 function InitFullSearch($dbi, $search) {
191 return InitTitleSearch($dbi, $search);
195 //iterating through database
196 function FullSearchNextMatch($dbi, &$pos) {
197 while ($pos['key']) {
199 $pos['key'] = dbmnextkey($dbi['wiki'], $pos['key']);
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'];
214 ////////////////////////
215 // new database features
217 // Compute PCRE suitable for searching for links to the given page.
218 function MakeBackLinkSearchRegexp($pagename) {
219 global $WikiNameRegexp;
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])=";
230 // Note from author: Sorry. :-/
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).
244 // setup for back-link search
245 function InitBackLinkSearch($dbi, $pagename) {
246 $pos['search'] = MakeBackLinkSearchRegexp($pagename);
247 $pos['key'] = dbmfirstkey($dbi['wiki']);
252 // iterating through back-links
253 function BackLinkSearchNextMatch($dbi, &$pos) {
254 while ($pos['key']) {
256 $pos['key'] = dbmnextkey($dbi['wiki'], $pos['key']);
258 $rawdata = dbmfetch($dbi['wiki'], $page);
259 if ( ! preg_match($pos['search'], $rawdata))
262 $pagedata = unserialize(UnPadSerializedData($rawdata));
263 while (list($i, $line) = each($pagedata['content'])) {
264 if (preg_match($pos['search'], $line))
271 function IncreaseHitCount($dbi, $pagename) {
273 if (dbmexists($dbi['hitcount'], $pagename)) {
274 // increase the hit count
275 // echo "$pagename there, incrementing...<br>\n";
276 $count = dbmfetch($dbi['hitcount'], $pagename);
278 dbmreplace($dbi['hitcount'], $pagename, $count);
280 // add it, set the hit count to one
282 dbminsert($dbi['hitcount'], $pagename, $count);
287 function GetHitCount($dbi, $pagename) {
289 if (dbmexists($dbi['hitcount'], $pagename)) {
290 // increase the hit count
291 $count = dbmfetch($dbi['hitcount'], $pagename);
299 function InitMostPopular($dbi, $limit) {
300 // iterate through the whole dbm file for hit counts
301 // sort the results highest to lowest, and return
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.
309 $pagename = dbmfirstkey($dbi['hitcount']);
310 $score = dbmfetch($dbi['hitcount'], $pagename);
311 $res = array($pagename => (int) $score);
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)
319 $res[$pagename] = (int) $score; // add page to $res
320 } elseif ($score > $lowest) {
321 $oldres = $res; // save old result
323 $removed = 0; // nothing removed yet
324 $newlowest = $score; // new lowest score
325 $res[$pagename] = (int) $score; // add page to $res
327 while(list($pname, $pscore) = each($oldres)) {
328 if (!$removed and ($pscore = $lowest))
329 $removed = 1; // don't copy this entry
331 $res[$pname] = (int) $pscore;
332 if ($pscore < $newlowest)
333 $newlowest = $pscore;
336 $lowest = $newlowest;
340 arsort($res); // sort
347 function MostPopularNextMatch($dbi, &$res) {
349 // the return result is a two element array with 'hits'
350 // and 'pagename' as the keys
352 if (list($pagename, $hits) = each($res)) {
355 "pagename" => $pagename
364 function GetAllWikiPagenames($dbi) {
368 $namelist[$ctr] = $key = dbmfirstkey($dbi);
370 while ($key = dbmnextkey($dbi, $key)) {
372 $namelist[$ctr] = $key;
379 ////////////////////////////////////////////
380 // functionality for the wikilinks DBM file
382 // format of the 'wikilinks' DBM file :
384 // { tolinks => ( pagename => 1}, fromlinks => { pagename => 1 } }
386 // takes a page name, returns array of scored incoming and outgoing links
387 function GetWikiPageLinks($dbi, $pagename) {
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
396 $fromlinks = array();
397 // look up pages that link to $pagename
398 $pname = dbmfirstkey($dbi['wikilinks']);
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);
407 // get and sort the outgoing links
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']);
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);
421 // get and sort the incoming links
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']);
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);
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);
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) {
458 // Phase 1: fetch the relevant pairs from 'wikilinks' into $cache
459 // ---------------------------------------------------------------
461 // first the info for $pagename
462 $linkinfo = RetrievePage($dbi, $pagename, 'wikilinks');
463 if (is_array($linkinfo)) // page exists?
464 $cache[$pagename] = $linkinfo;
466 // create info for page
467 $cache[$pagename] = array( 'fromlinks' => array(),
470 // look up pages that link to $pagename
471 $pname = dbmfirstkey($dbi['wikilinks']);
473 $linkinfo = RetrievePage($dbi, $pname, 'wikilinks');
474 if ($linkinfo['tolinks'][$pagename])
475 $cache[$pagename]['fromlinks'][$pname] = 1;
476 $pname = dbmnextkey($dbi['wikilinks'], $pname);
480 // then the info for the pages that $pagename used to point to
481 $oldTolinks = $cache[$pagename]['tolinks'];
483 while (list($link, $dummy) = each($oldTolinks)) {
484 $linkinfo = RetrievePage($dbi, $link, 'wikilinks');
485 if (is_array($linkinfo))
486 $cache[$link] = $linkinfo;
489 // finally the info for the pages that $pagename will point to
491 while (list($link, $dummy) = each($linklist)) {
492 $linkinfo = RetrievePage($dbi, $link, 'wikilinks');
493 if (is_array($linkinfo))
494 $cache[$link] = $linkinfo;
497 // Phase 2: delete the old links
498 // ---------------------------------------------------------------
500 // delete the old tolinks for $pagename
501 // $cache[$pagename]['tolinks'] = array();
502 // (overwritten anyway in Phase 3)
504 // remove $pagename from the fromlinks of pages in $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;
519 // Phase 3: add the new links
520 // ---------------------------------------------------------------
522 // set the new tolinks for $pagename
523 $cache[$pagename]['tolinks'] = $linklist;
525 // add $pagename to the fromlinks of pages in $linklist
527 while (list($link, $dummy) = each($linklist)) {
528 if ($cache[$link]) // existing page?
529 $cache[$link]['fromlinks'][$pagename] = 1;
532 // Phase 4: write $cache back to 'wikilinks'
533 // ---------------------------------------------------------------
536 while (list($link,$fromAndTolinks) = each($cache))
537 InsertPage($dbi, $link, $fromAndTolinks, 'wikilinks');