Commit | Line | Data |
---|---|---|
6855525e JL |
1 | <?php |
2 | /* | |
3 | * Copyright (C) 2003-2004 Polytechnique.org | |
4 | * http://opensource.polytechnique.org/ | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | */ | |
20 | ||
21 | ||
4be8b457 | 22 | require_once 'VCS/Spool.php'; |
6855525e JL |
23 | // dependency on PEAR |
24 | require_once 'System.php'; | |
25 | ||
26 | /** This class handles Diogenes RCS operations. | |
27 | */ | |
cb8988c6 | 28 | class Diogenes_VCS_RCS extends Diogenes_VCS_Spool { |
6855525e JL |
29 | /** Absolute directory location for the barrel's RCS files. */ |
30 | var $rcsdir; | |
31 | ||
32 | /** The username */ | |
33 | var $login; | |
34 | /** The constructor. | |
35 | * | |
36 | * @param caller | |
37 | * @param alias | |
38 | * @param login the current user's login | |
39 | * @param init should create this module? | |
40 | */ | |
cb8988c6 | 41 | function Diogenes_VCS_RCS(&$caller,$alias,$login,$init = false) { |
6855525e | 42 | global $globals; |
cb8988c6 | 43 | $this->Diogenes_VCS_Spool($caller,$alias); |
6855525e JL |
44 | $this->rcsdir = "{$globals->rcsroot}/$alias"; |
45 | $this->login = $login; | |
46 | ||
47 | // if we were asked to, created directories | |
48 | if ($init) { | |
49 | if (!is_dir($this->rcsdir)) | |
50 | mkdir($this->rcsdir, 0700); | |
51 | if (!is_dir($this->datadir)) | |
52 | mkdir($this->datadir, 0700); | |
53 | } | |
54 | ||
55 | // check RCS directory | |
56 | if (!is_dir($this->rcsdir) || !is_writable($this->rcsdir)) | |
57 | $this->kill("'{$this->rcsdir}' is not a writable directory"); | |
58 | ||
59 | // check spool directory | |
60 | if (!is_dir($this->datadir) || !is_writable($this->datadir)) | |
61 | $this->kill("'{$this->datadir}' is not a writable directory"); | |
62 | } | |
63 | ||
64 | ||
65 | /** Return the path of an RCS "item" (file or directory). | |
66 | * | |
67 | * @param parent parent directory (optional) | |
68 | * @param entry the item | |
69 | */ | |
70 | function rcsPath($parent="",$entry="") { | |
71 | $this->checkPath($parent,$entry); | |
72 | return $this->rcsdir.($parent ? "/$parent": "") . ($entry ? "/$entry" : ""); | |
73 | } | |
74 | ||
75 | ||
76 | /** Return the path of an RCS file (something,v). | |
77 | * | |
78 | * @param dir parent directory | |
79 | * @param file the RCS entry | |
80 | */ | |
81 | function rcsFile($dir,$file) { | |
82 | return $this->rcsPath($dir,$file).",v"; | |
83 | } | |
84 | ||
85 | ||
86 | /** Check whether a file is registered in RCS. | |
87 | * | |
88 | * @param dir parent directory | |
89 | * @param file the RCS entry | |
90 | */ | |
91 | function checkFile($dir, $file) { | |
92 | return is_file($this->rcsFile($dir, $file)); | |
93 | } | |
94 | ||
95 | ||
96 | /** Perform sanity check on an RCS directory | |
97 | * and the corresponding checkout in the spool | |
98 | * | |
99 | * @param dir | |
100 | */ | |
101 | function checkDir($dir) { | |
102 | return is_dir($this->rcsPath($dir)) | |
103 | && is_writable($this->rcsPath($dir)) | |
104 | && is_dir($this->spoolPath($dir)) | |
105 | && is_writable($this->spoolPath($dir)); | |
106 | } | |
107 | ||
108 | ||
109 | /** Do a checkout of an RCS item to a given location. | |
110 | * | |
111 | * @param dir parent directory | |
112 | * @param file the RCS entry | |
113 | * @param rev the revision to check out | |
114 | * @param output the directory to which we want to perform the checkout | |
115 | */ | |
116 | function checkout($dir,$file,$rev,$output) | |
117 | { | |
118 | $this->info("RCS : checkout out $file ($rev).."); | |
119 | $rfile = $this->rcsFile($dir,$file); | |
9d2cbe83 | 120 | if ($this->cmdExec("co -q -r".escapeshellarg($rev)." ".escapeshellarg($rfile)." ".escapeshellarg("$output/$file"))) |
6855525e JL |
121 | { |
122 | $this->info("RCS : Error, checkout failed!"); | |
123 | $this->info($this->cmdStatus()); | |
124 | return; | |
125 | } | |
126 | return "$output/$file"; | |
127 | } | |
128 | ||
129 | ||
130 | /** Commit an RCS item. Returns true for succes, false for an error. | |
131 | * | |
132 | * @param dir parent directory | |
133 | * @param file the RCS entry | |
134 | * @param content the contents of the new revision | |
135 | * @param message the log message for this revision | |
136 | */ | |
137 | function commit($dir,$file,$content,$message="") | |
138 | { | |
139 | $this->info("RCS : checking in '$file'.."); | |
140 | ||
141 | // check directories | |
142 | if (!$this->checkDir($dir)) { | |
143 | // error | |
144 | $this->info("RCS : Error, RCS sanity check for '$dir' failed!"); | |
145 | return false; | |
146 | } | |
147 | ||
148 | // log commit attempt | |
149 | $this->log("rcs_commit","{$this->alias}:$dir/$file:$message"); | |
150 | ||
151 | $sfile = $this->spoolPath($dir,$file); | |
152 | $rfile = $this->rcsFile($dir,$file); | |
153 | ||
154 | // if the RCS file does not exist, create it | |
155 | if (!file_exists($rfile)) { | |
9d2cbe83 | 156 | if ($this->cmdExec("echo '' | rcs -q -i ".escapeshellarg($rfile))) |
6855525e JL |
157 | { |
158 | // error | |
159 | $this->info("RCS : Error, could not initialise RCS file '$rfile'!"); | |
160 | $this->info($this->cmdStatus()); | |
161 | return false; | |
162 | } | |
163 | } | |
164 | ||
165 | // lock the spool file | |
9d2cbe83 | 166 | if ($this->cmdExec("co -q -l ".escapeshellarg($rfile)." ".escapeshellarg($sfile))) |
6855525e JL |
167 | { |
168 | // error | |
169 | $this->info("RCS : Error, could not get RCS lock on file '$file'!"); | |
170 | $this->info($this->cmdStatus()); | |
171 | return false; | |
172 | } | |
173 | if ($fp = fopen($sfile,"w")) { | |
174 | fwrite($fp,$content); | |
175 | fclose($fp); | |
176 | ||
9d2cbe83 | 177 | if ($this->cmdExec("ci -q -w".escapeshellarg($this->login). ($message ? " -m".escapeshellarg($message) : ""). |
6855525e JL |
178 | " ". escapeshellarg($sfile). " ". escapeshellarg($rfile))) |
179 | { | |
180 | // error | |
181 | $this->info("RCS : Error, checkin failed!"); | |
182 | $this->info($this->cmdStatus()); | |
183 | return false; | |
184 | } | |
185 | ||
9d2cbe83 | 186 | if ($this->cmdExec("co -q ".escapeshellarg($rfile)." ".escapeshellarg($sfile))) |
6855525e JL |
187 | { |
188 | // error | |
189 | $this->info("RCS : Error, checkout after checkin failed!"); | |
190 | $this->info($this->cmdStatus()); | |
191 | return false; | |
192 | } | |
193 | } | |
194 | return true; | |
195 | } | |
196 | ||
197 | ||
198 | ||
199 | /** Make a copy of an RCS item to a given location. | |
200 | * | |
201 | * @param sdir the source directory | |
202 | * @param sfile the source RCS entry | |
203 | * @param ddir the destination directory | |
204 | * @param dfile the destination RCS entry | |
205 | */ | |
206 | function copy($sdir,$sfile,$ddir,$dfile) | |
207 | { | |
208 | $this->info("RCS : copying '$sfile' to '$ddir/$dfile'.."); | |
209 | ||
210 | $spath = $this->spoolPath($sdir, $sfile); | |
211 | if (!is_file($spath)) { | |
212 | $this->info("Error: source file '$spath' does not exist!"); | |
213 | return false; | |
214 | } | |
215 | if (!$this->checkDir($ddir)) | |
216 | { | |
217 | $this->info("Error: directory '$ddir' does not exist!"); | |
218 | return false; | |
219 | } | |
220 | if ($this->checkFile($ddir, $dfile)) | |
221 | { | |
222 | $this->info("Error: file '$dfile' already exists in '$ddir'!"); | |
223 | return false; | |
224 | } | |
225 | return $this->commit($ddir,$dfile, | |
226 | file_get_contents($spath), | |
227 | "copied from '$ddir/$sfile'"); | |
228 | } | |
229 | ||
230 | ||
231 | /** Delete an RCS file and its corresponding spool entry. | |
232 | * | |
233 | * @param dir parent directory | |
234 | * @param file the RCS entry | |
235 | */ | |
236 | function del($dir,$file) { | |
237 | $this->info("RCS : deleting '$file'.."); | |
238 | $this->log("rcs_delete","{$this->alias}:$dir/$file"); | |
239 | @unlink($this->spoolPath($dir,$file)); | |
240 | @unlink($this->rcsFile($dir,$file)); | |
241 | } | |
242 | ||
243 | ||
244 | /** Retrieve differences between two version of a file. | |
245 | * | |
246 | * @param dir parent directory | |
247 | * @param file the RCS entry | |
248 | * @param r1 the first revision | |
249 | * @param r2 the second revision | |
250 | */ | |
251 | function diff($dir,$file,$r1,$r2) | |
252 | { | |
253 | $rfile = $this->rcsFile($dir,$file); | |
254 | $this->info("RCS : diffing '$file' ($r1 to $r2).."); | |
255 | $this->cmdExec("rcsdiff -r".escapeshellarg($r1). " -r".escapeshellarg($r2)." ".escapeshellarg($rfile)); | |
256 | return $this->cmd_output; | |
257 | } | |
258 | ||
259 | ||
260 | /** Converts a Word document to HTML and commits the resulting | |
261 | * HTML and images. | |
262 | * | |
263 | * @param dir | |
264 | * @param htmlfile | |
265 | * @param wordfile | |
266 | */ | |
267 | function importWordFile($dir,$htmlfile,$wordfile) | |
268 | { | |
269 | global $globals; | |
270 | ||
271 | if (!$globals->word_import) { | |
272 | $this->info("Error : support for word import is disabled!"); | |
273 | return false; | |
274 | } | |
275 | ||
276 | $func = "importWordFile_{$globals->word_import}"; | |
277 | ||
278 | if (!method_exists($this, $func)) | |
279 | { | |
280 | $this->info("Error : the utility '$globals->word_import' is not supported!"); | |
281 | return false; | |
282 | } | |
283 | ||
284 | return $this->$func($dir, $htmlfile, $wordfile); | |
285 | } | |
286 | ||
287 | ||
288 | /** Converts a Word document to HTML using wvHtml and commits the resulting | |
289 | * HTML and images. | |
290 | * | |
291 | * @param dir | |
292 | * @param htmlfile | |
293 | * @param wordfile | |
294 | */ | |
295 | function importWordFile_wvHtml($dir,$htmlfile,$wordfile) | |
296 | { | |
297 | $tmphtmlfile = "importWord.html"; | |
298 | if (($tmpdir = System::mktemp('-d')) == false) { | |
299 | $this->info("Error : could not create temporary directory!"); | |
300 | return false; | |
301 | } | |
302 | ||
303 | if ($this->cmdExec("wvHtml --targetdir=".escapeshellarg($tmpdir). | |
304 | " --charset=iso-8859-15 ". | |
305 | escapeshellarg($wordfile)." ".escapeshellarg($tmphtmlfile))) | |
306 | { | |
307 | $this->info("Error : wvHtml returned an error!"); | |
308 | $this->info($this->cmdStatus()); | |
309 | return false; | |
310 | } | |
311 | ||
312 | if (!$dh = opendir($tmpdir)) { | |
313 | $this->info("Error : could not find temporary directory '$tmpdir'!"); | |
314 | return false; | |
315 | } | |
316 | ||
317 | // process the files generated by wvHtml | |
318 | $ok = true; | |
319 | while (($myentry = readdir($dh)) != false) { | |
320 | if (is_file($myfile = "$tmpdir/$myentry")) { | |
321 | if ($myentry == $tmphtmlfile) { | |
322 | $ok = $ok && | |
323 | $this->commit($dir,$htmlfile, | |
324 | $this->importHtmlString(file_get_contents($myfile)), | |
325 | "Word file import"); | |
326 | } else { | |
327 | $ok = $ok && | |
328 | $this->commit($dir,$myentry,file_get_contents($myfile), | |
329 | "Word file import"); | |
330 | } | |
331 | } | |
332 | } | |
333 | closedir($dh); | |
334 | ||
335 | return $ok; | |
336 | } | |
337 | ||
338 | ||
339 | /** Returns raw log entries for an RCS item. | |
340 | * | |
341 | * @param dir parent directory | |
342 | * @param file the RCS entry | |
343 | */ | |
344 | function logEntries($dir,$file) { | |
345 | $rfile = $this->rcsFile($dir,$file); | |
346 | $this->cmdExec("rlog ".escapeshellarg($rfile)); | |
347 | return $this->cmd_output; | |
348 | } | |
349 | ||
350 | ||
351 | /** Parse the log entries for an RCS item into an array. | |
352 | * | |
353 | * @param dir parent directory | |
354 | * @param file the RCS entry | |
355 | */ | |
356 | function logParse($dir,$file) | |
357 | { | |
358 | // get the log, drop last 2 lines | |
359 | $lines = $this->logEntries($dir,$file); | |
360 | array_pop($lines); | |
361 | array_pop($lines); | |
362 | ||
363 | // split into revision, drop first block | |
364 | $revs = split("----------------------------\n", implode("\n",$lines)); | |
365 | array_shift($revs); | |
366 | ||
367 | // parse info about the revisions | |
368 | $revinfo = array(); | |
369 | foreach ($revs as $rev) { | |
370 | $myrev = array(); | |
371 | $lines = explode("\n",$rev); | |
372 | preg_match("/^revision (.+)$/",array_shift($lines),$res); | |
373 | $myrev['rev'] = $res[1]; | |
374 | preg_match("/^date: ([^;]+); author: ([^;]+); .*$/",array_shift($lines),$res); | |
375 | $myrev['date'] = $res[1]; | |
376 | $myrev['author'] = $res[2]; | |
377 | $myrev['log'] = implode("\n",$lines); | |
378 | array_push($revinfo,$myrev); | |
379 | } | |
380 | return $revinfo; | |
381 | } | |
382 | ||
383 | ||
384 | /** Move an RCS item to a given location. | |
385 | * | |
386 | * @param sdir the source directory | |
387 | * @param sfile the source RCS entry | |
388 | * @param ddir the destination directory | |
389 | * @param dfile the destination RCS entry | |
390 | */ | |
391 | function move($sdir,$sfile,$ddir,$dfile) | |
392 | { | |
393 | $this->info("RCS : moving '$sfile' to '$ddir/$dfile'.."); | |
394 | ||
395 | ||
396 | // check source files | |
397 | $spath = $this->spoolPath($sdir, $sfile); | |
398 | $srpath = $this->rcsFile($sdir, $sfile); | |
399 | ||
400 | if (!is_file($spath)) { | |
401 | $this->info("Error: source file '$spath' does not exist!"); | |
402 | return false; | |
403 | } | |
404 | ||
405 | if (!is_file($srpath)) { | |
406 | $this->info("Error: source RCS file '$srpath' does not exist!"); | |
407 | return false; | |
408 | } | |
409 | ||
410 | // check destination | |
411 | $dpath = $this->spoolPath($ddir, $dfile); | |
412 | $drpath = $this->rcsFile($ddir, $dfile); | |
413 | ||
414 | if (!$this->checkDir($ddir)) | |
415 | { | |
416 | $this->info("Error: directory '$ddir' does not exist!"); | |
417 | return false; | |
418 | } | |
419 | ||
420 | if (file_exists($dpath)) { | |
421 | $this->info("Error: file '$dfile' already exists in '$ddir'!"); | |
422 | return false; | |
423 | } | |
424 | ||
425 | if (file_exists($drpath)) { | |
426 | $this->info("Error: file '".basename($drpath)."' already exists in '$ddir'!"); | |
427 | return false; | |
428 | } | |
429 | ||
430 | if (!rename($spath, $dpath)) | |
431 | { | |
432 | $this->info("Error: failed to move '".basename($spath)."' to '".basename($dpath)."' in '$ddir'!"); | |
433 | return false; | |
434 | } | |
435 | ||
436 | if (!rename($srpath, $drpath)) | |
437 | { | |
438 | $this->info("Error: failed to move '".basename($srpath)."' to '".basename($drpath)."' in '$ddir'!"); | |
439 | return false; | |
440 | } | |
441 | ||
442 | ||
443 | //$this->log("rcs_move","{$this->alias}:$dir/$file"); | |
444 | ||
445 | return true; | |
446 | } | |
447 | ||
448 | ||
449 | /** Add a new RCS-managed directory. | |
450 | * | |
451 | * @param parent the parent directory | |
452 | * @param dir the directory to add | |
453 | */ | |
454 | function newdir($parent,$dir) | |
455 | { | |
456 | @mkdir($this->rcsPath($parent,$dir),0700); | |
457 | @mkdir($this->spoolPath($parent,$dir),0700); | |
458 | } | |
459 | ||
460 | ||
461 | /** Retrieve differences between two version of a file and prepare for output. | |
462 | * | |
463 | * @param dir parent directory | |
464 | * @param file the RCS entry | |
465 | * @param r1 the first revision | |
466 | * @param r2 the second revision | |
467 | */ | |
468 | function dispDiff($dir,$file,$r1,$r2) | |
469 | { | |
470 | $lns = "[0-9]+|[0-9]+,[0-9]+"; | |
471 | ||
472 | // get diff, strip any leading comments | |
473 | $lines = $this->diff($dir,$file,$r1,$r2); | |
474 | $line = ""; | |
475 | while (!preg_match("/^($lns)([acd])($lns)/",$line)) | |
476 | $line = array_shift($lines); | |
477 | array_unshift($lines,$line); | |
478 | $raw = implode("\n",$lines); | |
479 | ||
480 | $blocks = preg_split("/($lns)([acd])($lns)\n/",$raw,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); | |
481 | $out = array(); | |
482 | ||
483 | $lold = array_shift($blocks); | |
484 | while ($lold!='') | |
485 | { | |
486 | $type = array_shift($blocks); | |
487 | $lnew = array_shift($blocks); | |
488 | $diff = array_shift($blocks); | |
489 | ||
490 | switch ($type) { | |
491 | case 'c': | |
492 | list($a,$b) = split("---\n",$diff,2); | |
493 | break; | |
494 | case 'a': | |
495 | $a = ""; | |
496 | $b = $diff; | |
497 | break; | |
498 | case 'd': | |
499 | $a = $diff; | |
500 | $b = ""; | |
501 | break; | |
502 | } | |
503 | array_push($out,array($lold,$type,$lnew,$a,$b)); | |
504 | $lold = array_shift($blocks); | |
505 | } | |
506 | //array_push($out,array($type,$a,$b)); | |
507 | return $out; | |
508 | } | |
509 | ||
510 | ||
511 | /** Return an RCS-managed directory. | |
512 | * | |
513 | * @param dir the directory | |
514 | * @param loc the visible location for the directory | |
515 | * @param canedit can we edit files in this directory? | |
516 | */ | |
517 | function dispDir($dir,$loc,$canedit) { | |
518 | $entries = array(); | |
519 | ||
520 | if ($pdir = @opendir($this->rcsPath($dir))) { | |
521 | while ( ($file = readdir($pdir)) !== false) { | |
522 | if ( ($file != ".") && ($file != "..") ) | |
523 | { | |
524 | $entry = $this->dispEntry($dir,$loc,$file,$canedit); | |
525 | if (!empty($entry)) | |
526 | array_push($entries, $entry); | |
527 | } | |
528 | } | |
529 | closedir($pdir); | |
530 | } | |
531 | return $entries; | |
532 | } | |
533 | ||
534 | ||
535 | /** Returns an RCS "item" (file or directory). | |
536 | * | |
537 | * @param dir parent directory | |
538 | * @param loc visible location for parent directory | |
539 | * @param file the RCS "item" | |
540 | * @param canedit can we edit files this entry? | |
541 | */ | |
542 | function dispEntry($dir,$loc,$file,$canedit) { | |
543 | global $globals; | |
544 | ||
545 | $view = $edit = $del = $size = $rev = ""; | |
546 | ||
547 | $myitem = $this->rcsPath($dir,$file); | |
548 | ||
549 | // check the RCS entry exists | |
550 | if (!file_exists($myitem)) { | |
551 | $this->info("RCS entry '$myitem' does not exist!"); | |
552 | return; | |
553 | } | |
554 | ||
555 | if (is_dir($myitem)) | |
556 | { | |
557 | // this is a directory, this should not happen! | |
558 | $this->info("Unexpected directory in RCS, skipping : '$myitem'"); | |
559 | return; | |
560 | } | |
561 | else if (substr($file,-2) == ",v") | |
562 | { | |
563 | // this is an RCS file | |
564 | $file = substr($file,0,-2); | |
565 | ||
566 | // check we have a working copy of this item | |
567 | $spoolitem = $this->spoolPath($dir,$file); | |
568 | if (!is_file($spoolitem)) | |
569 | { | |
570 | $this->info("Could not find working copy '$spoolitem'!"); | |
571 | $size = ""; | |
572 | $icon = ""; | |
573 | } else { | |
574 | $size = $this->dispSize(filesize($spoolitem)); | |
575 | $icon = $globals->icons->get_mime_icon($spoolitem); | |
576 | } | |
577 | ||
578 | // revision info | |
579 | $myrev = array_shift($tmparr = $this->logParse($dir,$file)); | |
580 | ||
581 | return array( | |
582 | "icon" => $icon, | |
583 | "file" => $file, | |
584 | "rev" => array($myrev['rev'],$rev), | |
585 | "date" => $myrev['date'], | |
586 | "author" => $myrev['author'], | |
587 | "size" => $size | |
588 | ); | |
589 | ||
590 | } | |
591 | else | |
592 | { | |
593 | $this->info("Unknown RCS entry type : '$myitem'"); | |
594 | return; | |
595 | } | |
596 | ||
597 | } | |
598 | ||
599 | ||
600 | /** Format a file size for display. | |
601 | * | |
602 | * @param size the size, in bytes | |
603 | */ | |
604 | function dispSize($size) | |
605 | { | |
606 | if ($size < 1000) | |
607 | return "$size B"; | |
608 | else if ($size < 1000000) | |
609 | return floor($size/1000)." kB"; | |
610 | else | |
611 | return floor($size/1000000)." MB"; | |
612 | } | |
613 | ||
614 | } | |
615 | ||
616 | ?> |