]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/cvs.php
implemented the most_recent and most_popular functions, and minor
[SourceForge/phpwiki.git] / lib / WikiDB / backend / cvs.php
1 <?php
2 rcs_id('$Id: cvs.php,v 1.4 2001-11-26 09:25:36 riessen Exp $');
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     var $_docDir;
43     var $_repository;
44     var $_module_name;
45     var $_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 WikiDB_backend_cvs( $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             $this->_cvsDebug( "Creating new repository [$this->_repository]" );
94
95             // doesn't exist, need to create it and the replace the wiki 
96             // document directory.
97             $this->_mkdir( $this->_repository, 0775 );
98     
99             // assume that the repository is a local directory, prefix :local:
100             if ( !ereg( "^:local:", $this->_repository ) ) {
101                 $this->_repository = ":local:" . $this->_repository;
102             }
103             
104             $cmdLine = sprintf( "cvs -d \"%s\" init", $this->_repository);
105             $this->_execCommand( $cmdLine, $cmdOutput, true );
106
107             $this->_mkdir( $this->_docDir, 0775 );
108             $cmdLine = sprintf("cd %s; cvs -d \"%s\" import -m no_message "
109                                ."%s V R", $this->_docDir, $this->_repository,
110                                $this->_module_name );
111             $this->_execCommand( $cmdLine, $cmdOutput, true );
112             
113             // remove the wiki directory and check it out from the 
114             // CVS repository
115             $cmdLine = sprintf( "rm -fr %s; cd %s; cvs -d \"%s\" co %s",
116                                 $this->_docDir, dirname($this->_docDir), 
117                                 $this->_repository, $this->_module_name);
118             $this->_execCommand( $cmdLine, $cmdOutput, true );
119             
120             // add the default pages using the update_pagedata
121             $metaData = array();
122             $metaData[$AUTHOR] = "PhpWiki -- CVS Backend";
123
124             if ( is_dir( $dbparam[CVS_PAGE_SOURCE] ) ) {
125                 $d = opendir( $dbparam[CVS_PAGE_SOURCE] );
126                 while ( $entry = readdir( $d ) ) {
127                     $filename = $dbparam[CVS_PAGE_SOURCE] . "/" . $entry;
128                     $this->_cvsDebug("Found [$entry] in ["
129                                      . $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( "get_latest_versioned FAILED for [$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] "
262                           . "[$version] [$want_content]" );
263       
264         $filedata = "";
265         if ( $want_content ) {
266             // retrieve the version from the repository
267             $cmdLine = sprintf("cvs -d \"%s\" co -p -r 1.%d %s/%s 2>&1", 
268                                $this->_repository, $version, 
269                                $this->_module_name, $pagename );
270             $this->_execCommand( $cmdLine, $filedata, true );
271         
272             // TODO: DEBUG: 5 is a magic number here, depending on the
273             // TODO: DEBUG: version of cvs used here, 5 might have to
274             // TODO: DEBUG: change. Basically find a more reliable way of
275             // TODO: DEBUG: doing this.
276             // the first 5 lines contain various bits of 
277             // administrative information that can be ignored.
278             for ( $i = 0; $i < 5; $i++ ) {
279                 array_shift( $filedata );
280             }
281         }
282
283         /**
284          * Now obtain the rest of the pagehash information, this is contained
285          * in the log message for the revision in serialized form.
286          */
287         $cmdLine = sprintf("cd %s; cvs log -r1.%d %s", $this->_docDir,
288                            $version, $pagename );
289         $this->_execCommand( $cmdLine, $logdata, true );
290
291         // shift log data until we get to the 'revision X.X' line
292         // FIXME: ensure that we don't enter an endless loop here
293         while ( !ereg( "^revision 1.([0-9]+)$", $logdata[0], $revInfo ) ) {
294             array_shift( $logdata );
295         }
296
297         // serialized hash information now stored in position 2
298         $rVal = unserialize( _unescape( $logdata[2] ) );
299
300         // version information is incorrect
301         $rVal[CMD_VERSION] = $revInfo[1];
302         $rVal[CMD_CONTENT] = $filedata;
303
304         foreach ( $rVal as $key => $value ) {
305             $this->_cvsDebug( "$key == [$value]" );
306         }
307       
308         return $rVal;
309     }
310
311     /**
312      * This returns false if page was not deleted or could not be deleted
313      * else return true.
314      */
315     function delete_page($pagename) 
316     {
317         $this->_cvsDebug( "delete_page [$pagename]" );
318         $filename = $this->_docDir . "/" . $pagename;
319         $metaFile = $this->_docDir . "/CVS/_" . $pagename;
320         
321         // obtain a write block before deleting the file
322         if ( $this->_deleteFile( $filename ) == false ) {
323             return false;
324         }
325         
326         $this->_deleteFile( $metaFile );
327         
328         $this->_removePage( $pagename );
329
330         return true;
331     }
332
333     function delete_versiondata($pagename, $version) 
334     {
335         // TODO: Not Implemented.
336         // TODO: This is, for CVS, difficult because it implies removing a
337         // TODO: revision somewhere in the middle of a revision tree, and
338         // TODO: this is basically not possible!
339         trigger_error("delete_versiondata: Not Implemented", E_USER_WARNING);
340     }
341
342     function set_versiondata($pagename, $version, $data) 
343     {
344         // TODO: Not Implemented.
345         // TODO: requires changing the log(commit) message for a particular
346         // TODO: version and this can't be done??? (You can edit the repository
347         // TODO: file directly but i don't know of a way of doing it via
348         // TODO: the cvs tools).
349         trigger_error("set_versiondata: Not Implemented", E_USER_WARNING);
350     }
351
352     function update_versiondata($pagename, $version, $newdata) 
353     {
354         // TODO: same problem as set_versiondata
355         trigger_error("set_versiondata: Not Implemented", E_USER_WARNING);
356     }
357
358     function set_links($pagename, $links) 
359     {
360         // TODO: needs to be tested ....
361         $megaHash = get_pagedata( $pagename );
362         $megaHash[CMD_LINK_ATT] = $links;
363         $this->_writeMetaInfo( $pagename, $megaHash );
364     }
365
366     function get_links($pagename, $reversed) 
367     {
368         // TODO: ignores the $reversed argument and returns
369         // TODO: the value of _links_ attribute of the meta information
370         // TODO: to implement a reversed version, i guess, we going to
371         // TODO: need to do a grep on all files for the pagename in 
372         // TODO: in question and return all those page names that contained
373         // TODO: the required pagename!
374         $megaHash = get_pagedata( $pagename );
375         return $megaHash[CMD_LINK_ATT];
376     }
377
378     function get_all_revisions($pagename) 
379     {
380         // TODO: should replace this with something more efficient
381         include_once('lib/WikiDB/backend/dumb/AllRevisionsIter.php');
382         return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename);
383     }
384
385     function get_all_pages($include_defaulted) 
386     {
387         // FIXME: this ignores the include_defaulted parameter.
388         return new Cvs_Backend_Array_Iterator( 
389                               $this->_getAllFileNamesInDir( $this->_docDir ));
390     }
391
392     function text_search($search = '', $fullsearch = false) 
393     {
394         if ( $fullsearch ) {
395             return new Cvs_Backend_Full_Search_Iterator(
396                                $this->_getAllFileNamesInDir( $this->_docDir ), 
397                                $search, 
398                                $this->_docDir );
399         } else {
400             return new Cvs_Backend_Title_Search_Iterator(
401                                $this->_getAllFileNamesInDir( $this->_docDir ),
402                                $search);
403         }
404     }
405
406     function most_popular($limit) 
407     {
408         // TODO: needs to be tested ...
409         $mp = $this->_getMostPopular();
410         arsort( $mp, SORT_NUMERIC );
411         $returnVal = array();
412
413         while ( (list($key, $val) = each($a)) && $limit > 0 ) {
414             $returnVal[] = $key;
415             $limit--;
416         }
417         return $returnVal;
418     }
419
420     /**
421      * This only accepts the 'since' and 'limit' attributes, everything
422      * else is ignored.
423      */
424     function most_recent($params) 
425     {
426         // TODO: needs to be tested ...
427         // most recent are those pages with the highest time value ...
428         $mr = $this->_getMostRecent();
429         arsort( $mp, SORT_NUMERIC );
430         $returnVal = array();
431
432         if ( isset( $params['limit'] ) ) {
433             $limit = $params['limit'];
434             while ( (list($key, $val) = each($a)) && $limit > 0 ) {
435                 $returnVal[] = $key;
436                 $limit--;
437             }
438         } else if ( isset( $params['since'] ) ) {
439             while ( (list($key, $val) = each($a)) ) {
440                 if ( $val > $params['since'] ) {
441                     $returnVal[] = $key;
442                 }
443             }
444         }
445
446         return new Cvs_Backend_Array_Iterator( $returnVal );
447     }
448
449     function lock($write_lock = true) 
450     {
451         // TODO: to be implemented
452         trigger_error("lock: Not Implemented", E_USER_WARNING);
453     }
454
455     function unlock($force = false) 
456     {
457         // TODO: to be implemented
458         trigger_error("unlock: Not Implemented", E_USER_WARNING);
459     }
460
461     function close () 
462     {
463     }
464
465     function sync() 
466     {
467     }
468
469     function optimize() 
470     {
471     }
472
473     /**
474      * What we do here is take a listing of the documents directory and
475      * check that each page has metadata file. If not, then a metadata
476      * file is created for the page.
477      *
478      * This can happen if rebuild() was called and someone has added
479      * files to the CVS repository not via PhpWiki. These files are 
480      * added to the document directory but without any metadata files.
481      */
482     function check() 
483     {
484         // TODO:
485         // TODO: test this .... i.e. add test to unit test file.
486         // TODO:
487         $page_names = $this->_getAllFileNamesInDir($this->_docDir);
488         $meta_names = $this->_getAllFileNamesInDir($this->_docDir . "/CVS");
489
490         array_walk( $meta_names, '_strip_leading_underscore' );
491         reset( $meta_names );
492         $no_meta_files = array_diff( $page_names, $meta_names );
493
494         array_walk( $no_meta_files, '_create_meta_file', $this );
495
496         return true;
497     }
498
499     /**
500      * Do an update of the CVS repository 
501      */
502     function rebuild() 
503     {
504         // TODO:
505         // TODO: test this .... i.e. add test to unit test file.
506         // TODO:
507         $cmdLine = sprintf( "cd %s; cvs update -d 2>&1", $this->_docDir );
508         $this->_execCommand( $cmdLine, $cmdOutput, true );
509         return true;
510     }
511     
512     // 
513     // ..-.-..-.-..-.-.. .--..-......-.--. --.-....----.....
514     // The rest are all internal methods, not to be used 
515     // directly.
516     // ..-.-..-.-..-.-.. .--..-......-.--. --.-....----.....
517     //
518     function _create_meta_file( $page_name, $key, &$backend )
519     {
520         // this is used as part of an array walk and therefore takes
521         // the backend argument
522         $backend->_cvsDebug( "Creating meta file for [$page_name]" );
523         $backend->update_pagedata( $page_name, array() );
524     }
525
526     function _strip_leading_underscore( &$item ) 
527     {
528         $item = ereg_replace( "^_", "", $item );
529     }
530
531     /**
532      * update the most popular information by incrementing the count
533      * for the following page. If the page was not defined, it is entered
534      * with a value of 1.
535      */
536     function _updateMostPopular( $pagename )
537     {
538         $mp = $this->_getMostPopular();
539         if ( isset( $mp[$pagename] ) ) {
540             $mp[$pagename]++;
541         } else {
542             $mp[$pagename] = 1;
543         }
544         $this->_writeFileWithPath( $this->_docDir . "/CVS/" . CVS_MP_FILE, 
545                                    serialize( $mp ) );
546     }
547
548
549     /**
550      * Returns an array containing the most popular information. This
551      * creates the most popular file if it does not exist.
552      */
553     function _getMostPopular()
554     {
555         $mostPopular = $this->_docDir . "/CVS/" . CVS_MP_FILE;
556         if ( !file_exists( $mostPopular ) ) {
557             $this->_writeFileWithPath( $mostPopular, serialize( array() ) );
558         }
559         return unserialize(join( '',$this->_readFileWithPath($mostPopular)));
560     }
561
562     function _getMostRecent()
563     {
564         $mostRecent = $this->_docDir . "/CVS/" . CVS_MR_FILE;
565         if ( !file_exists( $mostRecent ) ) {
566             $this->_writeFileWithPath( $mostRecent, serialize( array() ) );
567         }
568         return unserialize(join( '',$this->_readFileWithPath($mostRecent)));
569     }
570
571     function _updateMostRecent( $pagename )
572     {
573         $mr = $this->_getMostRecent();
574         $mr[$pagename] = time();
575         $this->_writeFileWithPath( $this->_docDir . "/CVS/" . CVS_MR_FILE, 
576                                    serialize( $mr ) );
577     }
578
579     function _writeMetaInfo( $pagename, $hashInfo )
580     {
581         $this->_writeFileWithPath( $this->_docDir . "/CVS/_" . $pagename, 
582                                    serialize( $hashInfo ) );
583     }
584     function _writePage( $pagename, $content )
585     {
586         $this->_writeFileWithPath( $this->_docDir . "/". $pagename, $content );
587     }
588     function _removePage( $pagename )
589     {
590         $cmdLine = sprintf("cd %s; cvs remove %s 2>&1; cvs commit -m '%s' "
591                            ."%s 2>&1", $this->_docDir, $pagename, 
592                            "remove page", $pagename );
593         
594         $this->_execCommand( $cmdLine, $cmdRemoveOutput, true );
595     }
596
597     /**
598      * this returns the new version number of the file.
599      */
600     function _commitPage( $pagename, &$meta_data )
601     {
602         $cmdLine = sprintf( "cd %s; cvs commit -m \"%s\" %s 2>&1", 
603                             $this->_docDir, 
604                             escapeshellcmd( serialize( $meta_data ) ),
605                             $pagename );
606         $this->_execCommand( $cmdLine, $cmdOutput, true );
607
608         $cmdOutput = implode( "\n", $cmdOutput );
609         $revInfo = array();
610         ereg( "\nnew revision: 1[.]([0-9]+); previous revision: ", $cmdOutput,
611               $revInfo );
612
613         $this->_cvsDebug( "CP: revInfo 0: $revInfo[0]" );
614         $this->_cvsDebug( "CP: $cmdOutput" );
615         if ( isset( $revInfo[1] ) ) {
616             $this->_cvsDebug( "CP: got revision information" );
617             return $revInfo[1];
618         } else {
619             ereg( "\ninitial revision: 1[.]([0-9]+)", $cmdOutput, $revInfo );
620             if ( isset( $revInfo[1] ) ) {
621                 $this->_cvsDebug( "CP: is initial release" );
622                 return 1;
623             }
624             $this->_cvsDebug( "CP: returning old version" );
625             return $meta_data[CMD_VERSION];
626         }
627     }
628     function _addPage( $pagename )
629     {
630         // TODO: need to add a check for the mimetype so that binary
631         // TODO: files are added as binary files
632         $cmdLine = sprintf("cd %s; cvs add %s 2>&1", $this->_docDir, 
633                            $pagename );
634         $this->_execCommand( $cmdLine, $cmdAddOutput, true );
635     }
636
637     /**
638      * Returns an array containing all the names of files contained
639      * in a particular directory. The list is sorted according the 
640      * string representation of the filenames.
641      */
642     function _getAllFileNamesInDir( $dirName ) 
643     {
644         $namelist = array();
645         $d = opendir( $dirName );
646         while ( $entry = readdir( $d ) ) {
647             $namelist[] = $entry;
648         }
649         closedir( $d );
650         sort( $namelist, SORT_STRING );
651         return $namelist;
652     }
653
654     /**
655      * Recursively create all directories.
656      */
657     function _mkdir( $path, $mode ) 
658     {
659         $directoryName = dirname( $path );
660         if ( $directoryName != "/" && $directoryName != "\\"  
661              && !is_dir( $directoryName ) && $directoryName != "" ) {
662             $rVal = $this->_mkdir( $directoryName, $mode );
663         }
664         else {
665             $rVal = true;
666         }
667       
668         return ($rVal && @mkdir( $path, $mode ) );
669     }
670
671     /**
672      * Recursively create all directories and then the file.
673      */
674     function _createFile( $path, $mode ) 
675     {
676         $this->_mkdir( dirname( $path ), $mode );
677         touch( $path );
678         chmod( $path, $mode );
679     }
680
681     /**
682      * The lord giveth, and the lord taketh.
683      */
684     function _deleteFile( $filename )
685     {
686         if( $fd = fopen($filename, 'a') ) { 
687             
688             $locked = flock($fd,2);  // Exclusive blocking lock 
689
690             if (!$locked) { 
691                 $this->_cvsError("Unable to delete file, "
692                                  . "lock was not obtained.",
693                                  __LINE__, $filename, EM_NOTICE_ERRORS );
694             } 
695
696             if ( ($rVal = unlink( $filename )) != 0 ) {
697                 $this->_cvsDebug( "[$filename] --> Unlink returned [$rVal]" );
698             }
699
700             return $rVal;
701         } else {
702             $this->_cvsError("deleteFile: Unable to open file",
703                       __LINE__, $filename, EM_NOTICE_ERRORS );
704             return false;
705         }
706     }
707
708     /**
709      * Called when something happened that causes the CVS backend to 
710      * fail.
711      */
712     function _cvsError( $msg     = "no message", 
713                         $errline = 0, 
714                         $errfile = "lib/WikiDB/backend/cvs.php",
715                         $errno   = EM_FATAL_ERRORS)
716     {
717         $err = new PhpError( $errno, "[CVS(be)]: " . $msg, $errfile, $errline);
718         // send error to the debug routine
719         $this->_cvsDebug( $err->getDetail() );
720         // send the error to the error manager
721         $GLOBALS['ErrorManager']->handleError( $err );
722     }
723
724     /**
725      * Debug function specifically for the CVS database functions.
726      * Can be deactived by setting the WikiDB['debug_file'] to ""
727      */
728     function _cvsDebug( $msg )  
729     {
730         if ( $this->_debug_file == "" ) {
731             return;
732         }
733         
734         if ( !file_exists( $this->_debug_file  ) ) {
735             $this->_createFile( $this->_debug_file, 0755 );
736         }
737
738         if ( $fdlock = @fopen( $this->_debug_file, 'a' ) ) {
739             $locked = flock( $fdlock, 2 );
740             if ( !$locked ) {
741                 fclose( $fdlock );
742                 return;
743             }
744             
745             $fdappend = @fopen( $this->_debug_file, 'a' );
746             fwrite( $fdappend, ($msg . "\n") );
747             fclose( $fdappend );
748             fclose( $fdlock );
749         }
750         else {
751             // TODO: this should be replaced ...
752             print( "unable to locate/open [$filename], turning debug off\n" );
753             $this->_debug_file = "";
754         }
755     }
756
757     /**
758      * Execute a command and potentially exit if the flag exitOnNonZero is 
759      * set to true and the return value was nonZero
760      */
761     function _execCommand( $cmdLine, &$cmdOutput, $exitOnNonZero )
762     {
763         $this->_cvsDebug( "Preparing to execute [$cmdLine]" );
764         exec( $cmdLine, $cmdOutput, $cmdReturnVal );
765         if ( $exitOnNonZero && ($cmdReturnVal != 0) ) {
766             $this->_cvsDebug( "Command failed [$cmdLine], Output: [" . 
767                               join("\n",$cmdOutput) . "]" );
768             $this->_cvsError("Command failed [$cmdLine], "
769                              . "Return value: $cmdReturnVal",
770                              __LINE__ );
771         }
772         $this->_cvsDebug( "Done execution [" . join("\n", $cmdOutput ) . "]" );
773
774         return $cmdReturnVal;
775     }
776
777     /**
778      * Read locks a file, reads it, and returns it contents
779      */
780     function _readFileWithPath( $filename ) 
781     {
782         if ( $fd = @fopen( $filename, "r" ) )  {
783             $locked = flock( $fd, 1 ); // read lock
784             if ( !$locked ) {
785                 fclose( $fd );
786                 $this->_cvsError( "Unable to obtain read lock.",__LINE__);
787             }
788
789             $content = file( $filename );
790             fclose( $fd );
791             return $content;
792         } else {
793             $this->_cvsError( "Unable to open file '$filename' for reading",
794                               __LINE__ );
795             return false;
796         }
797     }
798
799     /**
800      * Either replace the contents of an existing file or create a 
801      * new file in the particular store using the page name as the
802      * file name.
803      * 
804      * Nothing is returned, might be useful to return something ;-)
805      */
806     function _writeFileWithPath( $filename, $contents )
807     { 
808         // TODO: $contents should probably be a reference parameter ...
809         if( $fd = fopen($filename, 'a') ) { 
810             $locked = flock($fd,2);  // Exclusive blocking lock 
811             if (!$locked) { 
812                 $this->_cvsError("Timeout while obtaining lock.",__LINE__);
813             } 
814
815             // Second filehandle -- we use this to write the contents
816             $fdsafe = fopen($filename, 'w'); 
817             fwrite($fdsafe, $contents); 
818             fclose($fdsafe); 
819             fclose($fd);
820         } else {
821             $this->_cvsError( "Could not open file '$filename' for writing", 
822                               __LINE__ );
823         }
824     }
825
826    /**
827     * Copy the contents of the source directory to the destination directory.
828     */
829     function _copyFilesFromDirectory( $src, $dest )
830     {
831         $this->_cvsDebug( "Copying from [$src] to [$dest]" );
832
833         if ( is_dir( $src ) && is_dir( $dest ) ) {
834             $this->_cvsDebug( "Copying " );
835             $d = opendir( $src );
836             while ( $entry = readdir( $d ) ) {
837                 if ( is_file( $src . "/" . $entry )
838                      && copy( $src . "/" . $entry, $dest . "/" . $entry ) ) {
839                     $this->_cvsDebug( "Copied to [$dest/$entry]" );
840                 } else {
841                     $this->_cvsDebug( "Failed to copy [$src/$entry]" );
842                 }
843             }
844             closedir( $d );
845             return true;
846         } else {
847             $this->_cvsDebug( "Not copying" );
848             return false;
849         }
850     }
851
852     /**
853      * Unescape a string value. Normally this comes from doing an 
854      * escapeshellcmd. This converts the following:
855      *    \{ --> {
856      *    \} --> }
857      *    \; --> ;
858      *    \" --> "
859      */
860     function _unescape( $val )
861     {
862         $val = str_replace( "\\{", "{", $val );
863         $val = str_replace( "\\}", "}", $val );
864         $val = str_replace( "\\;", ";", $val );
865         $val = str_replace( "\\\"", "\"", $val );
866         
867         return $val;
868     }
869
870     /**
871      * Function for removing the newlines from the ends of the
872      * file data returned from file(..). This is used in retrievePage
873      */
874     function _strip_newlines( &$item, $key )
875     {
876         $item = ereg_replace( "\n$", "", $item );
877     }
878
879 } /* End of WikiDB_backend_cvs class */
880
881 /**
882  * Generic iterator for stepping through an array of values.
883  */
884 class Cvs_Backend_Array_Iterator
885 extends WikiDB_backend_iterator
886 {
887     var $_array;
888
889     function Cvs_Backend_Iterator( $arrayValue = Array() )
890     {
891         $this->_array = $arrayValue;
892     }
893
894     function next() 
895     {
896         while ( ($rVal = array_pop( $this->_array )) != NULL ) {
897             return $rVal;
898         }
899         return false;
900     }
901
902     function free()
903     {
904         unset( $this->_array );
905     }
906 }
907
908 class Cvs_Backend_Full_Search_Iterator
909 extends Cvs_Backend_Array_Iterator
910 {
911     var $_searchString = '';
912     var $_docDir = "";
913
914     function Cvs_Backend_Title_Search_Iterator( $arrayValue = Array(),
915                                                 $searchString  = "",
916                                                 $documentDir = ".")
917     {
918         $this->Cvs_Backend_Array_Iterator( $arrayValue );
919         $_searchString = $searchString;
920         $_docDir = $documentDir;
921     }
922
923     function next()
924     {
925         do {
926             $pageName = Cvs_Backend_Array_Iterator::next();
927         } while ( !$this->_searchFile( $_searchString, 
928                                        $_docDir . "/" . $pageName ));
929
930         return $pageName;
931     }
932
933     /**
934      * Does nothing more than a grep and search the entire contents
935      * of the given file. Returns TRUE of the searchstring was found, 
936      * false if the search string wasn't find or the file was a directory
937      * or could not be read.
938      */
939     function _searchFile( $searchString, $fileName )
940     {
941         // TODO: using grep here, it might make more sense to use
942         // TODO: some sort of inbuilt/language specific method for
943         // TODO: searching files.
944         $cmdLine = sprintf( "grep -E -i '%s' %s > /dev/null 2>&1", 
945                             $searchString, $fileName );
946         
947         return ( WikiDB_backend_cvs::_execCommand( $cmdLine, $cmdOutput, 
948                                                    false ) == 0 );
949     }
950 }
951
952 /**
953  * Iterator used for doing a title search.
954  */
955 class Cvs_Backend_Title_Search_Iterator
956 extends Cvs_Backend_Array_Iterator
957 {
958     var $_searchString = '';
959
960     function Cvs_Backend_Title_Search_Iterator( $arrayValue = Array(),
961                                                 $searchString  = "")
962     {
963         $this->Cvs_Backend_Array_Iterator( $arrayValue );
964         $_searchString = $searchString;
965     }
966
967     function next()
968     {
969         do {
970             $pageName = Cvs_Backend_Array_Iterator::next();
971         } while ( !eregi( $this->_searchString, $pageName ) );
972
973         return $pageName;
974     }
975 }
976
977
978
979 ?>