]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/cvs.php
Remove commented code
[SourceForge/phpwiki.git] / lib / WikiDB / backend / cvs.php
1 <?php
2
3 /**
4  * Backend for handling CVS repository.
5  *
6  * ASSUMES: that the shell commands 'cvs', 'grep', 'rm', are all located
7  * ASSUMES: in the path of the server calling this script.
8  *
9  * Author: Gerrit Riessen, gerrit.riessen@open-source-consultants.de
10  */
11
12 require_once 'lib/WikiDB/backend.php';
13 require_once 'lib/ErrorManager.php';
14
15 /**
16  * Constants used by the CVS backend
17  **/
18 // these are the parameters defined in db_params
19 define('CVS_DOC_DIR', 'doc_dir');
20 define('CVS_REPOSITORY', 'repository');
21 define('CVS_CHECK_FOR_REPOSITORY', 'check_for_repository');
22 define('CVS_DEBUG_FILE', 'debug_file');
23 define('CVS_PAGE_SOURCE', 'pgsrc');
24 define('CVS_MODULE_NAME', 'module_name');
25
26 // these are the things that are defined in the page hash
27 // CMD == Cvs Meta Data
28 define('CMD_LAST_MODIFIED', 'lastmodified');
29 define('CMD_CONTENT', '%content');
30 define('CMD_CREATED', 'created');
31 define('CMD_VERSION', 'version');
32 define('CMD_AUTHOR', 'author');
33 define('CMD_LINK_ATT', '_links_');
34
35 // file names used to store specific information
36 define('CVS_MP_FILE', '.most_popular');
37 define('CVS_MR_FILE', '.most_recent');
38
39 class WikiDB_backend_cvs
40     extends WikiDB_backend
41 {
42     public $_docDir;
43     public $_repository;
44     public $_module_name;
45     public $_debug_file;
46
47     /**
48      * In the following parameters should be defined in dbparam:
49      *   . wiki ==> directory where the pages should be stored
50      *              this is not the CVS repository location
51      *   . repository ==> local directory where the repository should be
52      *                    created. This can also be a :pserver: but then
53      *                    set check_for_repository to false and checkout
54      *                    the documents beforehand. (This is basically CVSROOT)
55      *   . check_for_repository ==> boolean flag to indicate whether the
56      *                              repository should be created, this only
57      *                              applies to local directories, for pserver
58      *                              set this to false and check out the
59      *                              document base beforehand
60      *   . debug_file ==> file name where debug information should be sent.
61      *                    If file doesn't exist then it's created, if this
62      *                    is empty, then debugging is turned off.
63      *   . pgsrc ==> directory name where the default wiki pages are stored.
64      *               This is only required if the backend is to create a
65      *               new CVS repository.
66      *
67      * The class also adds a parameter 'module_name' to indicate the name
68      * of the cvs module that is being used to version the documents. The
69      * module_name is assumed to be the base name of directory given in
70      * wiki, e.g. if wiki == '/some/path/to/documents' then module_name
71      * becomes 'documents' and this module will be created in the CVS
72      * repository or assumed to exist. If on the other hand the parameter
73      * already exists, then it is not overwritten.
74      */
75     function __construct($dbparam)
76     {
77         // setup all the instance values.
78         $this->_docDir = $dbparam{CVS_DOC_DIR};
79         $this->_repository = $dbparam{CVS_REPOSITORY};
80         if (!$dbparam{CVS_MODULE_NAME}) {
81             $this->_module_name = basename($this->_docDir);
82             $dbparam{CVS_MODULE_NAME} = $this->_module_name;
83         } else {
84             $this->_module_name = $dbparam{CVS_MODULE_NAME};
85         }
86         $this->_debug_file = $dbparam{CVS_DEBUG_FILE};
87
88         if ($dbparam{CVS_CHECK_FOR_REPOSITORY}
89             && !(is_dir($this->_repository)
90                 && is_dir($this->_repository . "/CVSROOT")
91                 && is_dir($this->_repository . "/" . $this->_module_name))
92         ) {
93
94             $this->_cvsDebug(sprintf("Creating new repository [%s]", $this->_repository));
95
96             // doesn't exist, need to create it and the replace the wiki
97             // document directory.
98             $this->_mkdir($this->_repository, 0775);
99
100             // assume that the repository is a local directory, prefix :local:
101             if (!ereg("^:local:", $this->_repository)) {
102                 $this->_repository = ":local:" . $this->_repository;
103             }
104
105             $cmdLine = sprintf("cvs -d \"%s\" init", $this->_repository);
106             $this->_execCommand($cmdLine, $cmdOutput, true);
107
108             $this->_mkdir($this->_docDir, 0775);
109             $cmdLine = sprintf("cd %s; cvs -d \"%s\" import -m no_message "
110                     . "%s V R", $this->_docDir, $this->_repository,
111                 $this->_module_name);
112             $this->_execCommand($cmdLine, $cmdOutput, true);
113
114             // remove the wiki directory and check it out from the
115             // CVS repository
116             $cmdLine = sprintf("rm -fr %s; cd %s; cvs -d \"%s\" co %s",
117                 $this->_docDir, dirname($this->_docDir),
118                 $this->_repository, $this->_module_name);
119             $this->_execCommand($cmdLine, $cmdOutput, true);
120
121             // add the default pages using the update_pagedata
122             $metaData = array();
123             $metaData[$AUTHOR] = "PhpWiki -- CVS Backend";
124
125             if (is_dir($dbparam[CVS_PAGE_SOURCE])) {
126                 $d = opendir($dbparam[CVS_PAGE_SOURCE]);
127                 while ($entry = readdir($d)) {
128                     $filename = $dbparam[CVS_PAGE_SOURCE] . "/" . $entry;
129                     $this->_cvsDebug(sprintf("Found [%s] in [%s]", $entry, $dbparam[CVS_PAGE_SOURCE]));
130
131                     if (is_file($filename)) {
132                         $metaData[CMD_CONTENT] = join('', file($filename));
133                         $this->update_pagedata($entry, $metaData);
134                     }
135                 }
136                 closedir($d);
137             }
138
139             // ensure that the results of the is_dir are cleared
140             clearstatcache();
141         }
142     }
143
144     /**
145      * Return: metadata about page
146      */
147     function get_pagedata($pagename)
148     {
149         // the metadata information about a page is stored in the
150         // CVS directory of the document root in serialized form. The
151         // file always has the name, i.e. '_$pagename'.
152         $metaFile = $this->_docDir . "/CVS/_" . $pagename;
153
154         if (file_exists($metaFile)) {
155
156             $megaHash =
157                 unserialize(join('', $this->_readFileWithPath($metaFile)));
158
159             $filename = $this->_docDir . "/" . $pagename;
160             if (file_exists($filename)) {
161                 $megaHash[CMD_CONTENT] = $this->_readFileWithPath($filename);
162             } else {
163                 $megaHash[CMD_CONTENT] = "";
164             }
165
166             $this->_updateMostRecent($pagename);
167             $this->_updateMostPopular($pagename);
168
169             return $megaHash;
170         } else {
171             return false;
172         }
173     }
174
175     /**
176      * This will create a new page if page being requested does not
177      * exist.
178      */
179     function update_pagedata($pagename, $newdata = array())
180     {
181         // check argument
182         if (!is_array($newdata)) {
183             trigger_error("update_pagedata: Argument 'newdata' was not array",
184                 E_USER_WARNING);
185         }
186
187         // retrieve the meta data
188         $metaData = $this->get_pagedata($pagename);
189
190         if (!$metaData) {
191             $this->_cvsDebug("update_pagedata: no meta data found");
192             // this means that the page does not exist, we need to create
193             // it.
194             $metaData = array();
195
196             $metaData[CMD_CREATED] = time();
197             $metaData[CMD_VERSION] = "1";
198
199             if (!isset($newdata[CMD_CONTENT])) {
200                 $metaData[CMD_CONTENT] = "";
201             } else {
202                 $metaData[CMD_CONTENT] = $newdata[CMD_CONTENT];
203             }
204
205             // create an empty page ...
206             $this->_writePage($pagename, $metaData[CMD_CONTENT]);
207             $this->_addPage($pagename);
208
209             // make sure that the page is written and committed a second time
210             unset($newdata[CMD_CONTENT]);
211             unset($metaData[CMD_CONTENT]);
212         }
213
214         // change any meta data information
215         foreach ($newdata as $key => $value) {
216             if ($value == false || empty($value)) {
217                 unset($metaData[$key]);
218             } else {
219                 $metaData[$key] = $value;
220             }
221         }
222
223         // update the page data, if required. Use newdata because it could
224         // be empty and thus unset($metaData[CMD_CONTENT]).
225         if (isset($newdata[CMD_CONTENT])) {
226             $this->_writePage($pagename, $newdata[CMD_CONTENT]);
227         }
228
229         // remove any content from the meta data before storing it
230         unset($metaData[CMD_CONTENT]);
231         $metaData[CMD_LAST_MODIFIED] = time();
232
233         $metaData[CMD_VERSION] = $this->_commitPage($pagename, $metaData);
234         $this->_writeMetaInfo($pagename, $metaData);
235     }
236
237     function get_latest_version($pagename)
238     {
239         $metaData = $this->get_pagedata($pagename);
240         if ($metaData) {
241             // the version number is everything after the '1.'
242             return $metaData[CMD_VERSION];
243         } else {
244             $this->_cvsDebug(sprintf("get_latest_versioned FAILED for [%s]", $pagename));
245             return 0;
246         }
247     }
248
249     function get_previous_version($pagename, $version)
250     {
251         // cvs increments the version numbers, so this is real easy ;-)
252         return ($version > 0 ? $version - 1 : 0);
253     }
254
255     /**
256      * the version parameter is assumed to be everything after the '1.'
257      * in the CVS versioning system.
258      */
259     function get_versiondata($pagename, $version, $want_content = false)
260     {
261         $this->_cvsDebug("get_versiondata: [$pagename] [$version] [$want_content]");
262
263         $filedata = "";
264         if ($want_content) {
265             // retrieve the version from the repository
266             $cmdLine = sprintf("cvs -d \"%s\" co -p -r 1.%d %s/%s 2>&1",
267                 $this->_repository, $version,
268                 $this->_module_name, $pagename);
269             $this->_execCommand($cmdLine, $filedata, true);
270
271             // TODO: DEBUG: 5 is a magic number here, depending on the
272             // TODO: DEBUG: version of cvs used here, 5 might have to
273             // TODO: DEBUG: change. Basically find a more reliable way of
274             // TODO: DEBUG: doing this.
275             // the first 5 lines contain various bits of
276             // administrative information that can be ignored.
277             for ($i = 0; $i < 5; $i++) {
278                 array_shift($filedata);
279             }
280         }
281
282         /**
283          * Now obtain the rest of the pagehash information, this is contained
284          * in the log message for the revision in serialized form.
285          */
286         $cmdLine = sprintf("cd %s; cvs log -r1.%d %s", $this->_docDir,
287             $version, $pagename);
288         $this->_execCommand($cmdLine, $logdata, true);
289
290         // shift log data until we get to the 'revision X.X' line
291         // FIXME: ensure that we don't enter an endless loop here
292         while (!ereg("^revision 1.([0-9]+)$", $logdata[0], $revInfo)) {
293             array_shift($logdata);
294         }
295
296         // serialized hash information now stored in position 2
297         $rVal = unserialize(_unescape($logdata[2]));
298
299         // version information is incorrect
300         $rVal[CMD_VERSION] = $revInfo[1];
301         $rVal[CMD_CONTENT] = $filedata;
302
303         foreach ($rVal as $key => $value) {
304             $this->_cvsDebug("$key == [$value]");
305         }
306
307         return $rVal;
308     }
309
310     /**
311      * See ADODB for a better delete_page(), which can be undone and is seen in RecentChanges.
312      * See backend.php
313      */
314     //function delete_page($pagename) { $this->purge_page($pagename); }
315
316     /**
317      * This returns false if page was not deleted or could not be deleted
318      * else return true.
319      */
320     function purge_page($pagename)
321     {
322         $this->_cvsDebug("delete_page [$pagename]");
323         $filename = $this->_docDir . "/" . $pagename;
324         $metaFile = $this->_docDir . "/CVS/_" . $pagename;
325
326         // obtain a write block before deleting the file
327         if ($this->_deleteFile($filename) == false) {
328             return false;
329         }
330
331         $this->_deleteFile($metaFile);
332
333         $this->_removePage($pagename);
334
335         return true;
336     }
337
338     /**
339      * For now delete and create a new one.
340      *
341      * This returns false if page was not renamed,
342      * else return true.
343      */
344     function rename_page($pagename, $to)
345     {
346         $this->_cvsDebug("rename_page [$pagename,$to]");
347         $data = get_pagedata($pagename);
348         if (isset($data['pagename']))
349             $data['pagename'] = $to;
350         //$version = $this->get_latest_version($pagename);
351         //$vdata = get_versiondata($pagename, $version, 1);
352         //$data[CMD_CONTENT] = $vdata[CMD_CONTENT];
353         $this->delete_page($pagename);
354         $this->update_pagedata($to, $data);
355         return true;
356     }
357
358     function delete_versiondata($pagename, $version)
359     {
360         // TODO: Not Implemented.
361         // TODO: This is, for CVS, difficult because it implies removing a
362         // TODO: revision somewhere in the middle of a revision tree, and
363         // TODO: this is basically not possible!
364         trigger_error("delete_versiondata: Not Implemented", E_USER_WARNING);
365     }
366
367     function set_versiondata($pagename, $version, $data)
368     {
369         // TODO: Not Implemented.
370         // TODO: requires changing the log(commit) message for a particular
371         // TODO: version and this can't be done??? (You can edit the repository
372         // TODO: file directly but i don't know of a way of doing it via
373         // TODO: the cvs tools).
374         trigger_error("set_versiondata: Not Implemented", E_USER_WARNING);
375     }
376
377     function update_versiondata($pagename, $version, $newdata)
378     {
379         // TODO: same problem as set_versiondata
380         trigger_error("set_versiondata: Not Implemented", E_USER_WARNING);
381     }
382
383     function set_links($pagename, $links)
384     {
385         // TODO: needs to be tested ....
386         $megaHash = get_pagedata($pagename);
387         $megaHash[CMD_LINK_ATT] = $links;
388         $this->_writeMetaInfo($pagename, $megaHash);
389     }
390
391     function get_links($pagename, $reversed = true, $include_empty = false,
392                        $sortby = '', $limit = '', $exclude = '')
393     {
394         // TODO: ignores the $reversed argument and returns
395         // TODO: the value of _links_ attribute of the meta information
396         // TODO: to implement a reversed version, i guess, we going to
397         // TODO: need to do a grep on all files for the pagename in
398         // TODO: in question and return all those page names that contained
399         // TODO: the required pagename!
400         $megaHash = get_pagedata($pagename);
401         return $megaHash[CMD_LINK_ATT];
402     }
403
404     /* function get_all_revisions($pagename) {
405         // TODO: should replace this with something more efficient
406         include_once 'lib/WikiDB/backend/dumb/AllRevisionsIter.php';
407         return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename);
408     } */
409
410     public function get_all_pages($include_empty = false,
411                                   $sortby = '', $limit = '', $exclude = '')
412     {
413         // FIXME: this ignores the parameters.
414         return new Cvs_Backend_Array_Iterator(
415             $this->_getAllFileNamesInDir($this->_docDir));
416     }
417
418     public function text_search($search, $fulltext = false,
419                                 $sortby = '', $limit = '', $exclude = '')
420     {
421         if ($fulltext) {
422             $iter = new Cvs_Backend_Full_Search_Iterator(
423                 $this->_getAllFileNamesInDir($this->_docDir),
424                 $search,
425                 $this->_docDir);
426             $iter->stoplisted =& $search->stoplisted;
427             return $iter;
428         } else {
429             return new Cvs_Backend_Title_Search_Iterator(
430                 $this->_getAllFileNamesInDir($this->_docDir),
431                 $search);
432         }
433     }
434
435     public function most_popular($limit = 20, $sortby = '')
436     {
437         // TODO: needs to be tested ...
438         $mp = $this->_getMostPopular();
439         if ($limit < 0) {
440             asort($mp, SORT_NUMERIC);
441             $limit = -$limit;
442         } else {
443             arsort($mp, SORT_NUMERIC);
444         }
445         $returnVal = array();
446
447         while ((list($key, $val) = each($a)) && $limit > 0) {
448             $returnVal[] = $key;
449             $limit--;
450         }
451         return $returnVal;
452     }
453
454     /**
455      * This only accepts the 'since' and 'limit' attributes, everything
456      * else is ignored.
457      */
458     public function most_recent($params)
459     {
460         // TODO: needs to be tested ...
461         // most recent are those pages with the highest time value ...
462         $mr = $this->_getMostRecent();
463         $rev = false;
464         $returnVal = array();
465         if (isset($params['limit'])) {
466             $limit = $params['limit'];
467             $rev = $limit < 0;
468         }
469         if ($rev) {
470             arsort($mr, SORT_NUMERIC);
471         } else {
472             asort($mr, SORT_NUMERIC);
473         }
474         if (isset($limit)) {
475             while ((list($key, $val) = each($a)) && $limit > 0) {
476                 $returnVal[] = $key;
477                 $limit--;
478             }
479         } elseif (isset($params['since'])) {
480             while ((list($key, $val) = each($a))) {
481
482                 if ($val > $params['since']) {
483                     $returnVal[] = $key;
484                 }
485             }
486         }
487
488         return new Cvs_Backend_Array_Iterator($returnVal);
489     }
490
491     function lock($tables = array(), $write_lock = true)
492     {
493         // TODO: to be implemented
494         trigger_error("lock: Not Implemented", E_USER_WARNING);
495     }
496
497     function unlock($tables = array(), $force = false)
498     {
499         // TODO: to be implemented
500         trigger_error("unlock: Not Implemented", E_USER_WARNING);
501     }
502
503     function close()
504     {
505     }
506
507     function sync()
508     {
509     }
510
511     function optimize()
512     {
513         return true;
514     }
515
516     /**
517      * What we do here is take a listing of the documents directory and
518      * check that each page has metadata file. If not, then a metadata
519      * file is created for the page.
520      *
521      * This can happen if rebuild() was called and someone has added
522      * files to the CVS repository not via PhpWiki. These files are
523      * added to the document directory but without any metadata files.
524      */
525     function check()
526     {
527         // TODO:
528         // TODO: test this .... i.e. add test to unit test file.
529         // TODO:
530         $page_names = $this->_getAllFileNamesInDir($this->_docDir);
531         $meta_names = $this->_getAllFileNamesInDir($this->_docDir . "/CVS");
532
533         array_walk($meta_names, '_strip_leading_underscore');
534         reset($meta_names);
535         $no_meta_files = array_diff($page_names, $meta_names);
536
537         array_walk($no_meta_files, '_create_meta_file', $this);
538
539         return true;
540     }
541
542     /**
543      * Do an update of the CVS repository
544      */
545     function rebuild()
546     {
547         // TODO:
548         // TODO: test this .... i.e. add test to unit test file.
549         // TODO:
550         $cmdLine = sprintf("cd %s; cvs update -d 2>&1", $this->_docDir);
551         $this->_execCommand($cmdLine, $cmdOutput, true);
552         return true;
553     }
554
555     //
556     // ..-.-..-.-..-.-.. .--..-......-.--. --.-....----.....
557     // The rest are all internal methods, not to be used
558     // directly.
559     // ..-.-..-.-..-.-.. .--..-......-.--. --.-....----.....
560     //
561     function _create_meta_file($page_name, $key, &$backend)
562     {
563         // this is used as part of an array walk and therefore takes
564         // the backend argument
565         $backend->_cvsDebug(sprintf("Creating meta file for [%s]", $page_name));
566         $backend->update_pagedata($page_name, array());
567     }
568
569     function _strip_leading_underscore(&$item)
570     {
571         $item = ereg_replace("^_", "", $item);
572     }
573
574     /**
575      * update the most popular information by incrementing the count
576      * for the following page. If the page was not defined, it is entered
577      * with a value of 1.
578      */
579     function _updateMostPopular($pagename)
580     {
581         $mp = $this->_getMostPopular();
582         if (isset($mp[$pagename])) {
583             $mp[$pagename]++;
584         } else {
585             $mp[$pagename] = 1;
586         }
587         $this->_writeFileWithPath($this->_docDir . "/CVS/" . CVS_MP_FILE,
588             serialize($mp));
589     }
590
591     /**
592      * Returns an array containing the most popular information. This
593      * creates the most popular file if it does not exist.
594      */
595     function _getMostPopular()
596     {
597         $mostPopular = $this->_docDir . "/CVS/" . CVS_MP_FILE;
598         if (!file_exists($mostPopular)) {
599             $this->_writeFileWithPath($mostPopular, serialize(array()));
600         }
601         return unserialize(join('', $this->_readFileWithPath($mostPopular)));
602     }
603
604     function _getMostRecent()
605     {
606         $mostRecent = $this->_docDir . "/CVS/" . CVS_MR_FILE;
607         if (!file_exists($mostRecent)) {
608             $this->_writeFileWithPath($mostRecent, serialize(array()));
609         }
610         return unserialize(join('', $this->_readFileWithPath($mostRecent)));
611     }
612
613     function _updateMostRecent($pagename)
614     {
615         $mr = $this->_getMostRecent();
616         $mr[$pagename] = time();
617         $this->_writeFileWithPath($this->_docDir . "/CVS/" . CVS_MR_FILE,
618             serialize($mr));
619     }
620
621     function _writeMetaInfo($pagename, $hashInfo)
622     {
623         $this->_writeFileWithPath($this->_docDir . "/CVS/_" . $pagename,
624             serialize($hashInfo));
625     }
626
627     function _writePage($pagename, $content)
628     {
629         $this->_writeFileWithPath($this->_docDir . "/" . $pagename, $content);
630     }
631
632     function _removePage($pagename)
633     {
634         $cmdLine = sprintf("cd %s; cvs remove %s 2>&1; cvs commit -m '%s' "
635                 . "%s 2>&1", $this->_docDir, $pagename,
636             "remove page", $pagename);
637
638         $this->_execCommand($cmdLine, $cmdRemoveOutput, true);
639     }
640
641     /**
642      * this returns the new version number of the file.
643      */
644     function _commitPage($pagename, &$meta_data)
645     {
646         $cmdLine = sprintf("cd %s; cvs commit -m \"%s\" %s 2>&1",
647             $this->_docDir,
648             escapeshellcmd(serialize($meta_data)),
649             $pagename);
650         $this->_execCommand($cmdLine, $cmdOutput, true);
651
652         $cmdOutput = implode("\n", $cmdOutput);
653         $revInfo = array();
654         ereg("\nnew revision: 1[.]([0-9]+); previous revision: ", $cmdOutput,
655             $revInfo);
656
657         $this->_cvsDebug("CP: revInfo 0: $revInfo[0]");
658         $this->_cvsDebug("CP: $cmdOutput");
659         if (isset($revInfo[1])) {
660             $this->_cvsDebug("CP: got revision information");
661             return $revInfo[1];
662         } else {
663             ereg("\ninitial revision: 1[.]([0-9]+)", $cmdOutput, $revInfo);
664             if (isset($revInfo[1])) {
665                 $this->_cvsDebug("CP: is initial release");
666                 return 1;
667             }
668             $this->_cvsDebug("CP: returning old version");
669             return $meta_data[CMD_VERSION];
670         }
671     }
672
673     function _addPage($pagename)
674     {
675         // TODO: need to add a check for the mimetype so that binary
676         // TODO: files are added as binary files
677         $cmdLine = sprintf("cd %s; cvs add %s 2>&1", $this->_docDir,
678             $pagename);
679         $this->_execCommand($cmdLine, $cmdAddOutput, true);
680     }
681
682     /**
683      * Returns an array containing all the names of files contained
684      * in a particular directory. The list is sorted according the
685      * string representation of the filenames.
686      */
687     function _getAllFileNamesInDir($dirName)
688     {
689         $namelist = array();
690         $d = opendir($dirName);
691         while ($entry = readdir($d)) {
692             $namelist[] = $entry;
693         }
694         closedir($d);
695         sort($namelist, SORT_STRING);
696         return $namelist;
697     }
698
699     /**
700      * Recursively create all directories.
701      */
702     function _mkdir($path, $mode)
703     {
704         $directoryName = dirname($path);
705         if ($directoryName != "/" && $directoryName != "\\"
706             && !is_dir($directoryName) && $directoryName != ""
707         ) {
708             $rVal = $this->_mkdir($directoryName, $mode);
709         } else {
710             $rVal = true;
711         }
712
713         return ($rVal && @mkdir($path, $mode));
714     }
715
716     /**
717      * Recursively create all directories and then the file.
718      */
719     function _createFile($path, $mode)
720     {
721         $this->_mkdir(dirname($path), $mode);
722         touch($path);
723         chmod($path, $mode);
724     }
725
726     /**
727      * The lord giveth, and the lord taketh.
728      */
729     function _deleteFile($filename)
730     {
731         if ($fd = fopen($filename, 'a')) {
732
733             $locked = flock($fd, 2); // Exclusive blocking lock
734
735             if (!$locked) {
736                 $this->_cvsError("Unable to delete file, lock was not obtained.",
737                     __LINE__, $filename, EM_NOTICE_ERRORS);
738             }
739
740             if (($rVal = unlink($filename)) != 0) {
741                 $this->_cvsDebug("[$filename] --> Unlink returned [$rVal]");
742             }
743
744             return $rVal;
745         } else {
746             $this->_cvsError("deleteFile: Unable to open file",
747                 __LINE__, $filename, EM_NOTICE_ERRORS);
748             return false;
749         }
750     }
751
752     /**
753      * Called when something happened that causes the CVS backend to
754      * fail.
755      */
756     function _cvsError($msg = "no message",
757                        $errline = 0,
758                        $errfile = "lib/WikiDB/backend/cvs.php",
759                        $errno = EM_FATAL_ERRORS)
760     {
761         $err = new PhpError($errno, "[CVS(be)]: " . $msg, $errfile, $errline);
762         // send error to the debug routine
763         $this->_cvsDebug($err->asXML());
764         // send the error to the error manager
765         $GLOBALS['ErrorManager']->handleError($err);
766     }
767
768     /**
769      * Debug function specifically for the CVS database functions.
770      * Can be deactived by setting the WikiDB['debug_file'] to ""
771      */
772     function _cvsDebug($msg)
773     {
774         if ($this->_debug_file == "") {
775             return;
776         }
777
778         if (!file_exists($this->_debug_file)) {
779             $this->_createFile($this->_debug_file, 0755);
780         }
781
782         if ($fdlock = @fopen($this->_debug_file, 'a')) {
783             $locked = flock($fdlock, 2);
784             if (!$locked) {
785                 fclose($fdlock);
786                 return;
787             }
788
789             $fdappend = @fopen($this->_debug_file, 'a');
790             fwrite($fdappend, ($msg . "\n"));
791             fclose($fdappend);
792             fclose($fdlock);
793         } else {
794             // TODO: this should be replaced ...
795             printf("unable to locate/open [%s], turning debug off\n", $filename);
796             $this->_debug_file = "";
797         }
798     }
799
800     /**
801      * Execute a command and potentially exit if the flag exitOnNonZero is
802      * set to true and the return value was nonZero
803      */
804     function _execCommand($cmdLine, &$cmdOutput, $exitOnNonZero)
805     {
806         $this->_cvsDebug(sprintf("Preparing to execute [%s]", $cmdLine));
807         exec($cmdLine, $cmdOutput, $cmdReturnVal);
808         if ($exitOnNonZero && ($cmdReturnVal != 0)) {
809             $this->_cvsDebug(sprintf("Command failed [%s], Output: ", $cmdLine) . "[" .
810                 join("\n", $cmdOutput) . "]");
811             $this->_cvsError(sprintf("Command failed [%s], Return value: %s", $cmdLine, $cmdReturnVal),
812                 __LINE__);
813         }
814         $this->_cvsDebug("Done execution [" . join("\n", $cmdOutput) . "]");
815
816         return $cmdReturnVal;
817     }
818
819     /**
820      * Read locks a file, reads it, and returns it contents
821      */
822     function _readFileWithPath($filename)
823     {
824         if ($fd = @fopen($filename, "r")) {
825             $locked = flock($fd, 1); // read lock
826             if (!$locked) {
827                 fclose($fd);
828                 $this->_cvsError("Unable to obtain read lock.", __LINE__);
829             }
830
831             $content = file($filename);
832             fclose($fd);
833             return $content;
834         } else {
835             $this->_cvsError(sprintf("Unable to open file '%s' for reading", $filename),
836                 __LINE__);
837             return false;
838         }
839     }
840
841     /**
842      * Either replace the contents of an existing file or create a
843      * new file in the particular store using the page name as the
844      * file name.
845      *
846      * Nothing is returned, might be useful to return something ;-)
847      */
848     function _writeFileWithPath($filename, $contents)
849     {
850         // TODO: $contents should probably be a reference parameter ...
851         if ($fd = fopen($filename, 'a')) {
852             $locked = flock($fd, 2); // Exclusive blocking lock
853             if (!$locked) {
854                 $this->_cvsError("Timeout while obtaining lock.", __LINE__);
855             }
856
857             // Second filehandle -- we use this to write the contents
858             $fdsafe = fopen($filename, 'w');
859             fwrite($fdsafe, $contents);
860             fclose($fdsafe);
861             fclose($fd);
862         } else {
863             $this->_cvsError(sprintf("Could not open file '%s' for writing", $filename),
864                 __LINE__);
865         }
866     }
867
868     /**
869      * Copy the contents of the source directory to the destination directory.
870      */
871     function _copyFilesFromDirectory($src, $dest)
872     {
873         $this->_cvsDebug(sprintf("Copying from [%s] to [%s]", $src, $dest));
874
875         if (is_dir($src) && is_dir($dest)) {
876             $this->_cvsDebug("Copying ");
877             $d = opendir($src);
878             while ($entry = readdir($d)) {
879                 if (is_file($src . "/" . $entry)
880                     && copy($src . "/" . $entry, $dest . "/" . $entry)
881                 ) {
882                     $this->_cvsDebug(sprintf("Copied to [%s]", "$dest/$entry"));
883                 } else {
884                     $this->_cvsDebug(sprintf("Failed to copy [%s]", "$src/$entry"));
885                 }
886             }
887             closedir($d);
888             return true;
889         } else {
890             $this->_cvsDebug("Not copying");
891             return false;
892         }
893     }
894
895     /**
896      * Unescape a string value. Normally this comes from doing an
897      * escapeshellcmd. This converts the following:
898      *    \{ --> {
899      *    \} --> }
900      *    \; --> ;
901      *    \" --> "
902      */
903     function _unescape($val)
904     {
905         $val = str_replace("\\{", "{", $val);
906         $val = str_replace("\\}", "}", $val);
907         $val = str_replace("\\;", ";", $val);
908         $val = str_replace("\\\"", "\"", $val);
909
910         return $val;
911     }
912
913     /**
914      * Function for removing the newlines from the ends of the
915      * file data returned from file(..). This is used in retrievePage
916      */
917     function _strip_newlines(&$item, $key)
918     {
919         $item = ereg_replace("\n$", "", $item);
920     }
921
922 } /* End of WikiDB_backend_cvs class */
923
924 /**
925  * Generic iterator for stepping through an array of values.
926  */
927 class Cvs_Backend_Array_Iterator
928     extends WikiDB_backend_iterator
929 {
930     public $_array;
931
932     function Cvs_Backend_Iterator($arrayValue = Array())
933     {
934         $this->_array = $arrayValue;
935     }
936
937     function next()
938     {
939         while (($rVal = array_pop($this->_array)) != NULL) {
940             return $rVal;
941         }
942         return false;
943     }
944
945     function count()
946     {
947         return count($this->_array);
948     }
949
950     function free()
951     {
952         unset($this->_array);
953     }
954 }
955
956 class Cvs_Backend_Full_Search_Iterator
957     extends Cvs_Backend_Array_Iterator
958 {
959     public $_searchString = '';
960     public $_docDir = "";
961
962     function __construct($arrayValue = array(),
963                          $searchString = "",
964                          $documentDir = ".")
965     {
966         $this->Cvs_Backend_Array_Iterator($arrayValue);
967         $this->_searchString = $searchString;
968         $this->_docDir = $documentDir;
969     }
970
971     function next()
972     {
973         do {
974             $pageName = Cvs_Backend_Array_Iterator::next();
975         } while (!$this->_searchFile($this->_searchString,
976             $this->_docDir . "/" . $pageName));
977
978         return $pageName;
979     }
980
981     /**
982      * Does nothing more than a grep and search the entire contents
983      * of the given file. Returns TRUE of the searchstring was found,
984      * false if the search string wasn't find or the file was a directory
985      * or could not be read.
986      */
987     function _searchFile($searchString, $fileName)
988     {
989         // TODO: using grep here, it might make more sense to use
990         // TODO: some sort of inbuilt/language specific method for
991         // TODO: searching files.
992         $cmdLine = sprintf("grep -E -i '%s' %s > /dev/null 2>&1",
993             $searchString, $fileName);
994
995         return (WikiDB_backend_cvs::_execCommand($cmdLine, $cmdOutput,
996             false) == 0);
997     }
998 }
999
1000 /**
1001  * Iterator used for doing a title search.
1002  */
1003 class Cvs_Backend_Title_Search_Iterator
1004     extends Cvs_Backend_Array_Iterator
1005 {
1006     public $_searchString = '';
1007
1008     function __construct($arrayValue = array(),
1009                          $searchString = "")
1010     {
1011         parent::__construct($arrayValue);
1012         $this->_searchString = $searchString;
1013     }
1014
1015     function next()
1016     {
1017         do {
1018             $pageName = Cvs_Backend_Array_Iterator::next();
1019         } while (!eregi($this->_searchString, $pageName));
1020
1021         return $pageName;
1022     }
1023 }
1024
1025 // Local Variables:
1026 // mode: php
1027 // tab-width: 8
1028 // c-basic-offset: 4
1029 // c-hanging-comment-ender-p: nil
1030 // indent-tabs-mode: nil
1031 // End: