]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/cvs.php
initial implementation of a cvs backend ...
[SourceForge/phpwiki.git] / lib / WikiDB / backend / cvs.php
1 <?php
2 rcs_id('$Id: cvs.php,v 1.2 2001-10-01 21:55:54 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
34
35 class WikiDB_backend_cvs
36 extends WikiDB_backend
37 {
38     var $_docDir;
39     var $_repository;
40     var $_module_name;
41     var $_debug_file;
42
43     /**
44      * In the following parameters should be defined in dbparam:
45      *   . wiki ==> directory where the pages should be stored
46      *              this is not the CVS repository location
47      *   . repository ==> local directory where the repository should be 
48      *                    created. This can also be a :pserver: but then
49      *                    set check_for_repository to false and checkout
50      *                    the documents beforehand. (This is basically CVSROOT)
51      *   . check_for_repository ==> boolean flag to indicate whether the 
52      *                              repository should be created, this only
53      *                              applies to local directories, for pserver
54      *                              set this to false and check out the 
55      *                              document base beforehand
56      *   . debug_file ==> file name where debug information should be set.
57      *                    If file doesn't exist then it's created, if this
58      *                    is empty, then no debugging is done.
59      *   . pgsrc ==> directory name where the default wiki pages are stored.
60      *               This is only required if the backend is to create a
61      *               new CVS repository.
62      *
63      * The class also adds a parameter 'module_name' to indicate the name
64      * of the cvs module that is being used to version the documents. The
65      * module_name is assumed to be the base name of directory given in
66      * wiki, e.g. if wiki == '/some/path/to/documents' then module_name 
67      * becomes 'documents' and this module will be created in the CVS 
68      * repository or assumed to exist. If on the other hand the parameter
69      * already exists, then it is not overwritten.
70      */
71     function WikiDB_backend_cvs( $dbparam ) 
72     {
73         // setup all the instance values.
74         $this->_docDir = $dbparam{CVS_DOC_DIR};
75         $this->_repository = $dbparam{CVS_REPOSITORY};
76         if ( ! $dbparam{CVS_MODULE_NAME} ) {
77             $this->_module_name = basename( $this->_docDir );
78             $dbparam{CVS_MODULE_NAME} = $this->_module_name;
79         } else {
80             $this->_module_name = $dbparam{CVS_MODULE_NAME};
81         }
82         $this->_debug_file = $dbparam{CVS_DEBUG_FILE};
83
84         if ( !($dbparam{CVS_CHECK_FOR_REPOSITORY}
85                 && is_dir( $this->_repository )
86                 && is_dir( $this->_repository . "/CVSROOT" )
87                 && is_dir( $this->_repository . "/" . $this->_module_name ))) {
88
89             $this->_cvsDebug( "Creating new repository [$this->_repository]" );
90
91             // doesn't exist, need to create it and the replace the wiki 
92             // document directory.
93             $this->_mkdir( $this->_repository, 0775 );
94     
95             // assume that the repository is a local directory, prefix :local:
96             if ( !ereg( "^:local:", $this->_repository ) ) {
97                 $this->_repository = ":local:" . $this->_repository;
98             }
99             
100             $cmdLine = sprintf( "cvs -d \"%s\" init", $this->_repository);
101             $this->_execCommand( $cmdLine, $cmdOutput, true );
102
103             $this->_mkdir( $this->_docDir, 0775 );
104             $cmdLine = sprintf("cd %s; cvs -d \"%s\" import -m no_message "
105                                ."%s V R", $this->_docDir, $this->_repository,
106                                $this->_module_name );
107             $this->_execCommand( $cmdLine, $cmdOutput, true );
108             
109             // remove the wiki directory and check it out from the 
110             // CVS repository
111             $cmdLine = sprintf( "rm -fr %s; cd %s; cvs -d \"%s\" co %s",
112                                 $this->_docDir, dirname($this->_docDir), 
113                                 $this->_repository, $this->_module_name);
114             $this->_execCommand( $cmdLine, $cmdOutput, true );
115             
116             // add the default pages using the update_pagedata
117             $metaData = array();
118             $metaData[$AUTHOR] = "PhpWiki -- CVS Backend";
119
120             if ( is_dir( $dbparam[CVS_PAGE_SOURCE] ) ) {
121                 $d = opendir( $dbparam[CVS_PAGE_SOURCE] );
122                 while ( $entry = readdir( $d ) ) {
123                     $filename = $dbparam[CVS_PAGE_SOURCE] . "/" . $entry;
124                     $this->_cvsDebug("Found [$entry] in ["
125                                      . $dbparam[CVS_PAGE_SOURCE]."]");
126                     
127                     if ( is_file( $filename ) ) {
128                         $metaData[CMD_CONTENT] = join('',file($filename));
129                         $this->update_pagedata( $entry, $metaData );
130                     }
131                 }
132                 closedir( $d );
133             }
134             
135             // ensure that the results of the is_dir are cleared
136             clearstatcache();
137         }
138     }
139
140     /**
141      * Return: metadata about page
142      */
143     function get_pagedata($pagename) 
144     {
145         // the metadata information about a page is stored in the 
146         // CVS directory of the document root in serialized form. The
147         // file always has the name _$pagename.
148         $metaFile = $this->_docDir . "/CVS/_" . $pagename;
149   
150         if ( $fd = @fopen( $metaFile, "r" ) )  {
151
152             $locked = flock( $fd, 1 ); // read lock
153             if ( !$locked ) {
154                 fclose( $fd );
155                 $this->_cvsError( "Unable to obtain read lock.",__LINE__);
156             }
157
158             $pagehash = unserialize( join( '', file($metaFile) ));
159             // need to strip off the new lines at the end of each line
160             // hmmmm, content expects an array of lines where each line 
161             // has it's newline stripped off. Unfortunately because i don't
162             // want to store the files in serialize or wiki specific format,
163             // i need to add newlines when storing but need to strip them
164             // off again when i retrieve the file because file() doesn't remove
165             // newlines!
166 //          $pagehash['content'] = file($filename);
167 //          array_walk( $pagehash['content'], '_strip_newlines' );
168 //          reset( $pagehash['content'] );
169
170             fclose( $fd );
171             return $pagehash;
172         }
173
174         return false;
175     }
176
177     /**
178      * This will create a new page if page being requested does not
179      * exist.
180      */
181     function update_pagedata($pagename, $newdata) 
182     {
183         // retrieve the meta data
184         $metaData = $this->get_pagedata( $pagename );
185
186         if ( ! $metaData ) {
187             $this->_cvsDebug( "update_pagedata: no meta data found" );
188             // this means that the page does not exist, we need to create
189             // it.
190             $metaData = array();
191
192             $metaData[CMD_CREATED] = time();
193             $metaData[CMD_VERSION] = "1";
194
195             if ( ! isset($newdata[$CONTENT])) {
196                 $metaData[CMD_CONTENT] = "";
197             } else {
198                 $metaData[CMD_CONTENT] = $newdata[CMD_CONTENT];
199             }
200
201             // create an empty page ...
202             $this->_writePage( $pagename, $metaData[CMD_CONTENT] );
203             $this->_addPage( $pagename );
204
205             // make sure that the page is written and committed a second time
206             unset( $newdata[CMD_CONTENT] );
207             unset( $metaData[CMD_CONTENT] );
208         }
209
210         // change any meta data information
211         foreach ( $newdata as $key => $value ) {
212             if ( $value == false || empty( $value ) ) {
213                 unset( $metaData[$key] );
214             } else {
215                 $metaData[$key] = $value;
216             }
217         }
218
219         // update the page data, if required
220         if ( isset( $metaData[CMD_CONTENT] ) ) {
221             $this->_writePage( $pagename, $newdata[CMD_CONTENT] );
222         }
223
224         // remove any content from the meta data before storing it
225         unset( $metaData[CMD_CONTENT] );
226         $metaData[CMD_LAST_MODIFIED] = time();
227         $this->_writeMetaInfo( $pagename, $metaData );
228
229         $this->_commitPage( $pagename, serialize( $metaData ) );
230     }
231
232     function get_latest_version($pagename) 
233     {
234         $metaData = $this->get_pagedata( $pagename );
235         if ( $metaData ) {
236             // the version number is everything about the '1.'
237             return $metaData[CMD_VERSION];
238         } else {
239             $this->_cvsDebug( "get_latest_versioned FAILED for [$pagename]" );
240             return 0;
241         }
242     }
243
244     function get_previous_version($pagename, $version) 
245     {
246         // cvs increments the version numbers, so this is real easy ;-)
247         return ($version > 0 ? $version - 1 : 0);
248     }
249
250     /**
251      * the version parameter is assumed to be everything about the '1.'
252      * in the CVS versioning system.
253      */
254     function get_versiondata($pagename, $version, $want_content = false) 
255     {
256         $this->_cvsDebug( "get_versiondata: [$pagename] [$version] [$want_content]" );
257       
258         $filedata = "";
259         if ( $want_content ) {
260             // retrieve the version from the repository
261             $cmdLine = sprintf("cvs -d \"%s\" co -p -r 1.%d %s/%s 2>&1", 
262                                $this->_repository, $version, 
263                                $this->_module_name, $pagename );
264             $this->_execCommand( $cmdLine, $filedata, true );
265         
266             // TODO: DEBUG: 5 is a magic number here, depending on the
267             // TODO: DEBUG: version of cvs used here, 5 might have to
268             // TODO: DEBUG: change. Basically find a more reliable way of
269             // TODO: DEBUG: doing this.
270             // the first 5 lines contain various bits of 
271             // administrative information that can be ignored.
272             for ( $i = 0; $i < 5; $i++ ) {
273                 array_shift( $filedata );
274             }
275         }
276
277         /**
278          * Now obtain the rest of the pagehash information, this is contained
279          * in the log message for the revision in serialized form.
280          */
281         $cmdLine = sprintf("cd %s; cvs log -r1.%d %s", $this->_docDir,
282                            $version, $pagename );
283         $this->_execCommand( $cmdLine, $logdata, true );
284
285         // shift log data until we get to the 'revision X.X' line
286         // FIXME: ensure that we don't enter an endless loop here
287         while ( !ereg( "^revision ([^ ]+)$", $logdata[0], $revInfo ) ) {
288             array_shift( $logdata );
289         }
290
291         // serialized hash information now stored in position 2
292         $rVal = unserialize( _unescape( $logdata[2] ) );
293
294         // version information is incorrect
295         $rVal[CMD_VERSION] = ereg_replace( "^1[.]", "", $revInfo[1] );
296         $rVal[CMD_CONTENT] = $filedata;
297
298         foreach ( $rVal as $key => $value ) {
299             $this->_cvsDebug( "$key == [$value]" );
300         }
301       
302         return $rVal;
303     }
304
305     /**
306      * This returns false if page was not deleted or could not be deleted
307      * else true is returned.
308      */
309     function delete_page($pagename) 
310     {
311         $this->_cvsDebug( "delete_page [$pagename]" );
312         $filename = $this->_docDir . "/" . $pagename;
313         $metaFile = $this->_docDir . "/CVS/_" . $pagename;
314         
315         // obtain a write block before deleting the file
316         if ( $this->_deleteFile( $filename ) == false ) {
317             return false;
318         }
319         
320         $this->_deleteFile( $metaFile );
321         
322         $this->_removePage( $pagename );
323
324         $this->_cvsDebug( "CvsRemoveOutput [$cmdRemoveOutput]" );
325
326         return true;
327     }
328
329     function delete_versiondata($pagename, $version) 
330     {
331         // TODO: Not Implemented.
332         // TODO: This is, for CVS, difficult because it implies removing a
333         // TODO: revision somewhere in the middle of a revision tree, and
334         // TODO: this is basically not possible!
335         trigger_error("delete_versiondata: Not Implemented", E_USER_WARNING);
336     }
337
338     function set_versiondata($pagename, $version, $data) 
339     {
340         // TODO: Not Implemented.
341         // TODO: requires changing the log(commit) message for a particular
342         // TODO: version and this can't be done??? (You can edit the repository
343         // TODO: file directly but i don't know of a way of doing it via
344         // TODO: the cvs tools).
345         trigger_error("set_versiondata: Not Implemented", E_USER_WARNING);
346     }
347
348     function update_versiondata($pagename, $version, $newdata) 
349     {
350         // TODO: same problem as set_versiondata
351         trigger_error("set_versiondata: Not Implemented", E_USER_WARNING);
352     }
353
354     function set_links($pagename, $links) 
355     {
356         // TODO: to be implemented
357         trigger_error("set_links: Not Implemented", E_USER_WARNING);
358     }
359
360     function get_links($pagename, $reversed) 
361     {
362         // TODO: to be implemented
363         trigger_error("get_links: Not Implemented", E_USER_WARNING);
364     }
365
366     function get_all_revisions($pagename) 
367     {
368         // TODO: should replace this with something more efficient
369         include_once('lib/WikiDB/backend/dumb/AllRevisionsIter.php');
370         return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename);
371     }
372
373     function get_all_pages($include_defaulted) 
374     {
375         // FIXME: this ignores the include_defaulted parameter.
376         return new Cvs_Backend_Array_Iterator( 
377                                     $this->_getAllFileNamesInDir( $this->_docDir ));
378     }
379
380     function text_search($search = '', $fullsearch = false) 
381     {
382         if ( $fullsearch ) {
383             return new Cvs_Backend_Full_Search_Iterator(
384                                     $this->_getAllFileNamesInDir( $this->_docDir ), 
385                                     $search, 
386                                     $this->_docDir );
387         } else {
388             return new Cvs_Backend_Title_Search_Iterator(
389                                     $this->_getAllFileNamesInDir( $this->_docDir ),
390                                     $search);
391         }
392     }
393
394     function most_popular($limit) 
395     {
396     }
397
398     function most_recent($params) 
399     {
400     }
401
402     function lock($write_lock = true) 
403     {
404     }
405
406     function unlock($force = false) 
407     {
408     }
409
410     function close () 
411     {
412     }
413
414     function sync() 
415     {
416     }
417
418     function optimize() 
419     {
420     }
421
422     function check() 
423     {
424     }
425
426     function rebuild() 
427     {
428     }
429
430     // 
431     // ..-.-..-.-..-.-.. .--..-......-.--. --.-....----.....
432     // The rest are all internal methods, not to be used 
433     // directly.
434     // ..-.-..-.-..-.-.. .--..-......-.--. --.-....----.....
435     //
436    
437     function _writeMetaInfo( $pagename, $hashInfo )
438     {
439         $this->_writeFileWithPath( $this->_docDir . "/CVS/_" . $pagename, 
440                             serialize( $hashInfo ) );
441     }
442     function _writePage( $pagename, $content )
443     {
444         $this->_writeFileWithPath( $this->_docDir . "/". $pagename, $content );
445     }
446     function _removePage( $pagename )
447     {
448         $cmdLine = sprintf("cd %s; cvs remove %s 2>&1; cvs commit -m '%s' "
449                            ."%s 2>&1", $this->_docDir, $pagename, 
450                            "remove page", $pagename );
451         
452         $this->_execCommand( $cmdLine, $cmdRemoveOutput, true );
453     }
454     function _commitPage( $pagename, $commitMsg = "no_message" )
455     {
456         $cmdLine = sprintf( "cd %s; cvs commit -m \"%s\" %s 2>&1", 
457                             $this->_docDir, escapeshellcmd( $commitMsg ), 
458                             $pagename );
459         $this->_execCommand( $cmdLine, $cmdOutput, true );
460     }
461     function _addPage( $pagename )
462     {
463         // TODO: need to add a check for the mimetype so that binary
464         // TODO: files are added as binary files
465         $cmdLine = sprintf("cd %s; cvs add %s 2>&1", $this->_docDir, 
466                            $pagename );
467         $this->_execCommand( $cmdLine, $cmdAddOutput, true );
468     }
469
470     /**
471      * Returns an array containing all the names of files contained
472      * in a particular directory. The list is sorted according the 
473      * string representation of the filenames.
474      */
475     function _getAllFileNamesInDir( $dirName ) 
476     {
477         $namelist = array();
478         $d = opendir( $dirName );
479         while ( $entry = readdir( $d ) ) {
480             $namelist[] = $entry;
481         }
482         closedir( $d );
483         sort( $namelist, SORT_STRING );
484         return $namelist;
485     }
486
487     /**
488      * Recursively create all directories.
489      */
490     function _mkdir( $path, $mode ) 
491     {
492         $directoryName = dirname( $path );
493         if ( $directoryName != "/" && $directoryName != "\\"  
494              && !is_dir( $directoryName ) && $directoryName != "" ) {
495             $rVal = $this->_mkdir( $directoryName, $mode );
496         }
497         else {
498             $rVal = true;
499         }
500       
501         return ($rVal && @mkdir( $path, $mode ) );
502     }
503
504     /**
505      * Recursively create all directories and then the file.
506      */
507     function _createFile( $path, $mode ) 
508     {
509         $this->_mkdir( dirname( $path ), $mode );
510         touch( $path );
511         chmod( $path, $mode );
512     }
513     /**
514      * The lord giveth, and the lord taketh.
515      */
516     function _deleteFile( $filename )
517     {
518         if( $fd = fopen($filename, 'a') ) { 
519             
520             $locked = flock($fd,2);  // Exclusive blocking lock 
521
522             if (!$locked) { 
523                 $this->_cvsError("Unable to delete file, lock was not obtained.",
524                           __LINE__, $filename, EM_NOTICE_ERRORS );
525             } 
526
527             if ( ($rVal = unlink( $filename )) == 0 ) {
528                 /* if successful, then do nothing */
529             } else {
530                 $this->_cvsDebug( "[$filename] --> Unlink returned [$rVal]" );
531             }
532
533             return $rVal;
534         } else {
535             $this->_cvsError("deleteFile: Unable to open file",
536                       __LINE__, $filename, EM_NOTICE_ERRORS );
537             return false;
538         }
539     }
540
541     /**
542      * Called when something happened that causes the CVS backend to 
543      * fail.
544      */
545     function _cvsError( $msg     = "no message", 
546                         $errline = 0, 
547                         $errfile = "lib/WikiDB/backend/cvs.php",
548                         $errno   = EM_FATAL_ERRORS)
549     {
550         $err = new PhpError( $errno, "[CVS(be)]: " . $msg, $errfile, $errline);
551         // send error to the debug routine
552         $this->_cvsDebug( $err->getDetail() );
553         // send the error to the error manager
554         $GLOBALS['ErrorManager']->handleError( $err );
555     }
556
557     /**
558      * Debug function specifically for the CVS database functions.
559      * Can be deactived by setting the WikiDB['debug_file'] to ""
560      */
561     function _cvsDebug( $msg )  
562     {
563         if ( $this->_debug_file == "" ) {
564             return;
565         }
566         
567         if ( !file_exists( $this->_debug_file  ) ) {
568             $this->_createFile( $this->_debug_file, 0755 );
569         }
570
571         if ( $fdlock = @fopen( $this->_debug_file, 'a' ) ) {
572             $locked = flock( $fdlock, 2 );
573             if ( !$locked ) {
574                 fclose( $fdlock );
575                 return;
576             }
577             
578             $fdappend = @fopen( $this->_debug_file, 'a' );
579             fwrite( $fdappend, ($msg . "\n") );
580             fclose( $fdappend );
581             fclose( $fdlock );
582         }
583         else {
584             // TODO: this should be replaced ...
585             print( "unable to locate/open [$filename], turning debug off\n" );
586             $this->_debug_file = "";
587         }
588     }
589
590     /**
591      * Execute a command and potentially exit if the flag exitOnNonZero is 
592      * set to true and the return value was nonZero
593      */
594     function _execCommand( $cmdLine, &$cmdOutput, $exitOnNonZero )
595     {
596         $this->_cvsDebug( "Preparing to execute [$cmdLine]" );
597         exec( $cmdLine, $cmdOutput, $cmdReturnVal );
598         if ( $exitOnNonZero && ($cmdReturnVal != 0) ) {
599             $this->_cvsError("Command failed [$cmdLine], Return value: $cmdReturnVal",
600                       __LINE__ );
601             $this->_cvsDebug( "Command failed [$cmdLine], Output: [" . 
602                        join("\n",$cmdOutput) . "]" );
603         }
604         $this->_cvsDebug( "Done execution [" . join("\n", $cmdOutput ) . "]" );
605
606         return $cmdReturnVal;
607     }
608
609     /**
610      * Either replace the contents of an existing file or create a 
611      * new file in the particular store using the page name as the
612      * file name.
613      *
614      * Returns true if all went well, else false.
615      */
616 //      function _WriteFile( $pagename, $storename, $contents )
617 //      {
618 //          global $WikiDB;
619 //          $filename = $WikiDB[$storename] . "/" . $pagename;
620 //          _WriteFileWithPath( $filename, $contents );
621 //      }
622
623     function _writeFileWithPath( $filename, $contents )
624     { 
625         if( $fd = fopen($filename, 'a') ) { 
626             $locked = flock($fd,2);  // Exclusive blocking lock 
627             if (!$locked) { 
628                 $this->_cvsError("Timeout while obtaining lock.",__LINE__);
629             } 
630
631             //Second (actually used) filehandle 
632             $fdsafe = fopen($filename, 'w'); 
633             fwrite($fdsafe, $contents); 
634             fclose($fdsafe); 
635             fclose($fd);
636         } else {
637             $this->_cvsError( "Could not open file [$filename]", __LINE__ );
638         }
639     }
640
641    /**
642     * Copy the contents of the source directory to the destination directory.
643     */
644     function _copyFilesFromDirectory( $src, $dest )
645     {
646         $this->_cvsDebug( "Copying from [$src] to [$dest]" );
647
648         if ( is_dir( $src ) && is_dir( $dest ) ) {
649             $this->_cvsDebug( "Copying " );
650             $d = opendir( $src );
651             while ( $entry = readdir( $d ) ) {
652                 if ( is_file( $src . "/" . $entry )
653                      && copy( $src . "/" . $entry, $dest . "/" . $entry ) ) {
654                     $this->_cvsDebug( "Copied to [$dest/$entry]" );
655                 } else {
656                     $this->_cvsDebug( "Failed to copy [$src/$entry]" );
657                 }
658             }
659             closedir( $d );
660             return true;
661         } else {
662             $this->_cvsDebug( "Not copying" );
663             return false;
664         }
665     }
666
667     /**
668      * Unescape a string value. Normally this comes from doing an 
669      * escapeshellcmd. This converts the following:
670      *    \{ --> {
671      *    \} --> }
672      *    \; --> ;
673      *    \" --> "
674      */
675     function _unescape( $val )
676     {
677         $val = str_replace( "\\{", "{", $val );
678         $val = str_replace( "\\}", "}", $val );
679         $val = str_replace( "\\;", ";", $val );
680         $val = str_replace( "\\\"", "\"", $val );
681         
682         return $val;
683     }
684
685     /**
686      * Function for removing the newlines from the ends of the
687      * file data returned from file(..). This is used in retrievePage
688      */
689     function _strip_newlines( &$item, $key )
690     {
691         $item = ereg_replace( "\n$", "", $item );
692     }
693
694 } /* End of WikiDB_backend_cvs class */
695
696 /**
697  * Generic iterator for stepping through an array of values.
698  */
699 class Cvs_Backend_Array_Iterator
700 extends WikiDB_backend_iterator
701 {
702     var $_array;
703
704     function Cvs_Backend_Iterator( $arrayValue = Array() )
705     {
706         $this->_array = $arrayValue;
707     }
708
709     function next() 
710     {
711         while ( ($rVal = array_pop( $this->_array )) != NULL ) {
712             return $rVal;
713         }
714         return false;
715     }
716
717     function free()
718     {
719         unset( $this->_array );
720     }
721 }
722
723 class Cvs_Backend_Full_Search_Iterator
724 extends Cvs_Backend_Array_Iterator
725 {
726     var $_searchString = '';
727     var $_docDir = "";
728
729     function Cvs_Backend_Title_Search_Iterator( $arrayValue = Array(),
730                                                 $searchString  = "",
731                                                 $documentDir = ".")
732     {
733         $this->Cvs_Backend_Array_Iterator( $arrayValue );
734         $_searchString = $searchString;
735         $_docDir = $documentDir;
736     }
737
738     function next()
739     {
740         do {
741             $pageName = Cvs_Backend_Array_Iterator::next();
742         } while ( !$this->_searchFile( $_searchString, 
743                                        $_docDir . "/" . $pageName ));
744
745         return $pageName;
746     }
747
748     /**
749      * Does nothing more than a grep and search the entire contents
750      * of the given file. Returns TRUE of the searchstring was found, 
751      * false if the search string wasn't find or the file was a directory
752      * or could not be read.
753      */
754     function _searchFile( $searchString, $fileName )
755     {
756         // TODO: using grep here, it might make more sense to use
757         // TODO: some sort of inbuilt/language specific method for
758         // TODO: searching files.
759         $cmdLine = sprintf( "grep -E -i '%s' %s > /dev/null 2>&1", 
760                             $searchString, $fileName );
761         
762         return ( WikiDB_backend_cvs::_execCommand( $cmdLine, $cmdOutput, 
763                                                    false ) == 0 );
764     }
765 }
766
767 /**
768  * Iterator used for doing a title search.
769  */
770 class Cvs_Backend_Title_Search_Iterator
771 extends Cvs_Backend_Array_Iterator
772 {
773     var $_searchString = '';
774
775     function Cvs_Backend_Title_Search_Iterator( $arrayValue = Array(),
776                                                 $searchString  = "")
777     {
778         $this->Cvs_Backend_Array_Iterator( $arrayValue );
779         $_searchString = $searchString;
780     }
781
782     function next()
783     {
784         do {
785             $pageName = Cvs_Backend_Array_Iterator::next();
786         } while ( !eregi( $this->_searchString, $pageName ) );
787
788         return $pageName;
789     }
790 }
791
792
793
794 ?>