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