Code

Backport from trunk
[gosa.git] / gosa-core / update-gosa
1 #!/usr/bin/php
2 <?php
3 /*
4  * This code is part of GOsa (http://www.gosa-project.org)
5  * Copyright (C) 2003-2010 GONICUS GmbH
6  *
7  * ID: $$Id: main.php 9254 2008-03-03 15:57:49Z cajus $$
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
24 define ("GOSA_HOME", dirname(__FILE__));
25 define ("LOCALE_DIR", GOSA_HOME."/locale");
26 define ("PLUGSTATE_DIR", GOSA_HOME."/state");
28 function print_usage()
29 {
30         ?>
31 update-gosa - class cache updated and plugin manager for GOsa
32 Usage: update-gosa install dsc     Install the plugin using the dsc information
33                                    placed in the plugin source directory.
35        update-gosa remove plugin   Remove the plugin named "plugin" from
36                                    the current configuration.
38        update-gosa list           Lists installed plugins
40        update-gosa rescan-i18n     Rebuilds the translations
42        update-gosa rescan-images   Rebuilds the themes master image
44        update-gosa rescan-classes  Rebuilds the class list
45        
46        update-gosa                 Shortcut for rescan-classes and rescan-i18n
47 <?php
48         exit (1);
49 }
52 function rmdirRecursive($path, $followLinks=false) {
53   $dir= opendir($path);
54   while($entry= readdir($dir)) {
55     if(is_file($path."/".$entry) || ((!$followLinks) && is_link($path."/".$entry))) {
56       unlink($path."/".$entry);
57     } elseif (is_dir($path."/".$entry) && $entry!='.' && $entry!='..') {
58       rmdirRecursive($path."/".$entry);
59     }
60   }
61   closedir($dir);
62   return rmdir($path);
63 }
66 function get_themes()
67 {
68   $themes= array();
69   $d = dir(GOSA_HOME."/html/themes");
70   while (false !== ($entry = $d->read())) {
71     if ($entry[0] != '.') {
72       $themes[]= basename($entry);
73     }
74   }
75   $d->close();
77   return $themes;
78 }
80 /* Function to include all class_ files starting at a given directory base */
81 function get_classes($folder= ".")
82 {
83   static $base_dir= "";
84   static $result= array();
86   if ($base_dir == ""){
87     if ($folder == "."){
88       $base_dir= getcwd();
89     } else {
90       $base_dir= $folder;
91     }
92   }
94   $currdir=getcwd();
95   if ($folder){
96     chdir("$folder");
97   }
99   $dh = opendir(".");
100   while(is_resource($dh) && false !== ($file = readdir($dh))){
102     if (preg_match("/.*\.svn.*/", $file) ||
103         preg_match("/.*smarty.*/i",$file) ||
104         preg_match("/.*\.tpl.*/",$file) ||
105         ($file==".") ||($file =="..")){
106       continue;
107     }
109     /* Recurse through all "common" directories */
110     if (is_dir($file) && !file_exists("{$file}/excludeFromAutoLoad")){
111       get_classes($file);
112       continue;
113     }
115     /* Only take care about .inc and .php files... */
116     if (!(preg_match('/\.php$/', $file) || preg_match('/\.inc$/', $file))){
117       continue;
118     }
120     /* Include existing class_ files */
121     $contents= file($file);
122     foreach($contents as $line){
123       $line= chop($line);
124       if (preg_match('/^\s*class\s*\w.*$/', $line)){
125         $class= preg_replace('/^\s*class\s*(\w+).*$/', '\1', $line);
126         $result[$class]= preg_replace("%$base_dir/%", "", "$currdir/$folder/$file");
127       }
128     }
129   }
131   @closedir($dh);
132   chdir($currdir);
134   return ($result);
138 function rescan_classes()
140         echo "Updating class cache...\n";
141         $class_mapping= get_classes();
142         $filename= GOSA_HOME."/include/class_location.inc";
144         /* Sanity checks */
145         if (!file_exists($filename) || is_writable($filename)) {
147             if (!$handle= fopen($filename, 'w')) {
148                  echo "Cannot open file \"$filename\" - aborted\n";
149                  exit (1);
150             }
152         } else {
153             echo "File \"$filename\" is not writable - aborted\n";
154             exit (2);
155         }
157         fwrite ($handle, "<?php\n\$class_mapping= array(\n");
158         foreach ($class_mapping as $key => $value){
159           fwrite ($handle, "                \"$key\" => \"$value\",\n");
160         }
161         fwrite ($handle, " );\n");
163         fclose($handle);
167 function rescan_i18n()
169         echo "Updating internationalization...\n";
170         $languages= array();
171         $size= strlen(LOCALE_DIR);
173         /* Get all available messages.po files, sort them for languages */
174         $dir= new RecursiveDirectoryIterator(LOCALE_DIR);
175         $all= new RecursiveIteratorIterator($dir);
176         foreach ( $all as $element ){
177                 if ($element->isFile() && preg_match('/\/LC_MESSAGES\/messages.po$/', $element->getPathname())){
178                         $lang= preg_replace('/^.*\/([^\/]+)\/LC_MESSAGES\/.*$/', '\1', $element);
179                         if (!isset($languages[$lang])){
180                                 $languages[$lang]= array();
181                         }
182                         $languages[$lang][]= substr($element->getPathName(), $size+1);
183                 }
184         }
186         /* For each language, merge the target .mo to the compiled directory. */
187         foreach ($languages as $language => $po_files){
188                 if (!is_dir(LOCALE_DIR."/compiled/${language}/LC_MESSAGES")){
189                         if (!mkdir (LOCALE_DIR."/compiled/${language}/LC_MESSAGES", 0755, TRUE)){
190                                 echo "Failed to create '".LOCALE_DIR."/compiled/${language}/LC_MESSAGES'- aborted";
191                                 exit (3);
192                         }
193                 }
195                 /* Cat all these po files into one single file */
196                 system ("(cd ".LOCALE_DIR." && msgcat --use-first ".implode(" ", $po_files)." > compiled/${language}/LC_MESSAGES/messages.po)", $val);
197                 if ($val != 0){
198                         echo "Merging of message files failed - aborted";
199                         exit (4);
200                 }
201                 system ("(cd ".LOCALE_DIR."/compiled/${language}/LC_MESSAGES && msgfmt -o messages.mo messages.po && rm messages.po)", $val);
202                 if ($val != 0){
203                         echo "Compiling of message files failed - aborted";
204                         exit (5);
205                 }
206         }
208         echo "! Warning: you may need to reload your webservice!\n";
212 function parse_ini($file)
214         global $description, $provides, $depends, $versions, $conflicts;
216         $res= "";
217         if (file_exists($file)){
218                 $tmp= parse_ini_file($file, TRUE);
220                 if (isset($tmp['gosa-plugin'])){
221                         $plugin= &$tmp['gosa-plugin'];
222                         if (isset($plugin['name'])&& isset($plugin['description'])){
223                                 $res= $plugin['name'];
224                                 $description[$res]= $plugin['description'];
225                                 $versions[$res]= $plugin['version'];
226                                 $provides[$res]= $res;
227                                 if (isset($plugin['depends'])){
228                                         $depends[$res]= explode(',', preg_replace('/\s+/', '', $plugin['depends']));
229                                 }
230                                 if (isset($plugin['conflicts'])){
231                                         $conflicts[$res]= explode(',', preg_replace('/\s+/', '', $plugin['conflicts']));
232                                 }
233                         }
234                 }
235         }
237         return $res;
241 function dependency_check()
243         global $description, $provides, $depends;
245         foreach ($depends as $name => $pl_depends){
246                 foreach ($pl_depends as $pl){
247                         if (!in_array($pl, $provides)){
248                                 echo "! Error: plugin '$name' depends on '$pl' which is not provided by any plugin\n\n";
249                                 exit (1);
250                         }
251                 }
252         }
256 function load_plugins()
258         if (!is_dir(PLUGSTATE_DIR)){
259                 if (!mkdir (PLUGSTATE_DIR, 0755, TRUE)){
260                         echo "Cannot create plugstate dir '".PLUGSTATE_DIR."' - aborted\n";
261                         exit (2);
262                 }
263         }
264         $dir= new DirectoryIterator(PLUGSTATE_DIR);
265         foreach ($dir as $entry){
266                 if ($dir->isDir() && !preg_match('/^\./', $dir->__toString())){
267                         $file= $dir->getPathName()."/plugin.dsc";
268                         if (parse_ini($file) == ""){
269                                 echo "! Warning: plugin ".$dir->getPathName()." is missing declarations\n";
270                         }
271                 }
272         }
276 function list_plugins()
278         global $description, $versions;
279         $count= 0;
281         /* Load plugin list */
282         load_plugins();
284         /* Show plugins */
285         foreach ($description as $name => $dsc){
286                 if ($count == 0){
287                         echo "Plugin\t\t|Version |Description\n";
288                         echo "----------------------------------------------------------------------------\n";
289                 }
290                 $ver= $versions[$name];
291                 echo "$name\t\t|$ver\t |$dsc\n";
292                 $count++;
293         }
295         /* Yell about non existing plugins... */
296         if ($count == 0){
297                 echo "No plugins found...\n\n";
298         } else {
299                 # Check for dependencies
300                 dependency_check();
301                 echo "\n";
302         }
306 function install_plugin($file)
308         global $description, $provides, $depends, $conflicts;
310         /* Load plugin list */
311         load_plugins();
313         /* Load .dsc file */
314         if (file_exists($file)){
315                 $tmp= parse_ini_file($file, TRUE);
317                 if (isset($tmp['gosa-plugin'])){
318                         $plugin= &$tmp['gosa-plugin'];
319                         if (isset($plugin['name'])&& isset($plugin['description'])){
320                                 $name= $plugin['name'];
321                                 $description= $plugin['description'];
322                                 $depends= array();
323                                 if (isset($plugin['depends'])){
324                                         $depends= explode(',', preg_replace('/\s+/', '', $plugin['depends']));
325                                 }
327                                 /* Already installed? */
328                                 if (isset($provides[$name])){
329                                         echo "! Error: plugin already installed\n\n";
330                                         exit (3);
331                                 }
333                                 /* Check if dependencies are fullfilled */
334                                 foreach ($depends as $dep){
335                                         $found= false;
336                                         foreach ($provides as $provide => $dummy){
337                                                 if ($dep == $provide){
338                                                         $found= true;
339                                                         break;
340                                                 }
341                                         }
342                                         if (!$found){
343                                                 echo "! Error: plugin depends on '$dep', but this is not installed\n\n";
344                                                 exit (3);
345                                         }
346                                 }
348                                 /* Check for conflicts */
349                                 foreach ($conflicts as $conf){
350                                         if (!in_array($conf, $provides)){
351                                                 echo "! Warning: plugin conflicts with '$conf'\n\n";
352                                         }
353                                 }
355                                 /* Create plugstate directory and touch plugin.lst */
356                                 if (!mkdir (PLUGSTATE_DIR."/$name", 0755, TRUE)){
357                                         echo "Failed to create '".PLUGSTATE_DIR."/$name - aborted";
358                                         exit (3);
359                                 }
360                                 if (!$handle= fopen(PLUGSTATE_DIR."/$name/plugin.lst", 'w')) {
361                                         echo "Cannot open file '$filename' - aborted\n";
362                                         exit (1);
363                                 }
365                                 echo "Installing plugin '$name'...\n";
367                                 /* Copy and fill plugin.lst */
368                                 $path= dirname($file);
369                                 $dir= new RecursiveDirectoryIterator($path);
370                                 $all= new RecursiveIteratorIterator($dir);
371                                 foreach ( $all as $entry ){
372                                         $source= $path."/".substr($entry->getPathName(), strlen($path) + 1);
374                                         /* Skip description - it belongs to the state dir */
375                                         if (preg_match('/\/plugin.dsc$/', $source)){
376                                                 copy ($source, PLUGSTATE_DIR."/$name/plugin.dsc");
377                                                 continue;
378                                         }
380                                         /* Skip well known directories */
381                                         if (preg_match('/^\.+$/', $source) || preg_match('/\/\.svn\//', $source)) {
382                                                 continue;
383                                         }
385                                         /* Calculate destination */
386                                         if (preg_match("%^.*locale/%", $source)){
387                                                 $dest= GOSA_HOME."/locale/plugins/$name/".preg_replace("%^.*locale/%", "", $source);
388                                         } elseif (preg_match("%^.*help/%", $source)) {
389                                                 $dest= GOSA_HOME."/doc/plugins/$name/".preg_replace("%^.*help/%", "", $source);
390                                         } elseif (preg_match("%^.*html/%", $source)) {
391                                                 $dest= GOSA_HOME."/html/plugins/$name/".preg_replace("%^.*html/%", "", $source);
392                                         } else {
393                                                 $dest= GOSA_HOME."/plugins/".substr($entry->getPathName(), strlen($path) + 1);
394                                         }
396                                         /* Destination exists in case of directories? */
397                                         if ($entry->isDir()){
398                                                 if (!is_dir($dest)){
399                                                         mkdir($dest, 0755, TRUE);
400                                                         fwrite ($handle, "$dest\n");
401                                                 }
402                                         } else {
403                                                 if (!is_dir(dirname($dest))){
404                                                         mkdir(dirname($dest), 0755, TRUE);
405                                                         fwrite ($handle, dirname($dest)."\n");
406                                                 }
407                                         }
409                                         /* Copy files */
410                                         if ($entry->isFile()){
411                                                 copy ($source, $dest);
412                                         }
414                                         /* Note what we did... */
415                                         fwrite ($handle, "$dest\n");
416                                 }
418                                 fclose($handle);
419                         }
420                 }
421         }
422         
423         /* Update caches */
424         rescan_classes();
425         rescan_i18n();
429 function remove_plugin($name)
431         global $description, $depends;
433         /* Load plugin list */
434         load_plugins();
436         /* Present? */
437         if (!isset($description[$name])){
438                 echo "! Error: cannot find a plugin named '$name'\n\n";
439                 exit (1);
440         }
442         /* Depends? */
443         foreach ($depends as $sname => $pl_depends){
444                 if (in_array($name, $pl_depends)){
445                         echo "! Error: plugin '$sname' depends on '$name' - cannot remove it\n\n";
446                         exit (1);
447                 }
448         }
450         /* Load information */
451         if (!file_exists(PLUGSTATE_DIR."/$name/plugin.lst")){
452                 echo "! Error: cannot remove plugin '$name' - no install history found\n\n";
453                 exit (1);
454         }
456         echo "Removing plugin '$name'...\n";
457         $contents= file(PLUGSTATE_DIR."/$name/plugin.lst");
458         $cnv= array();
459         foreach($contents as $line){
460                 $entry= chop($line);
461                 $cnv[strlen($entry).":$entry"]= $entry;
462         }
463         krsort($cnv);
465         /* Remove files first */
466         clearstatcache();
467         foreach ($cnv as $entry){
468                 if (is_dir($entry)){
469                         rmdir($entry);
470                         continue;
471                 }
472                 if (file_exists($entry)){
473                         unlink($entry);
474                 }
475         }
477         /* Remove state directory for plugin */
478         rmdirRecursive(PLUGSTATE_DIR."/$name");
480         /* Update caches */
481         rescan_classes();
482         rescan_i18n();
486 function rescan_images($path, $theme)
488   $widths= array();
489   $heights= array();
490   $paths= array();
491   $posX= array();
492   $posY= array();
493   $baseLength= strlen($path);
494   $heightStats= array();
495   $warnings= array();
496   $checksums= array();
497   $styles= array();
498   $duplicates= array();
500   echo "Updating master image for theme '$theme'...";
502   // Check for image magick convert
503   if (!function_exists("imageFilter")){
504     exec("which convert", $res, $ret);
505     if ($ret != 0) {
506       die("Your system has no bundled gd support for imageFilter function. Please install imagemagick in order to use an external command.\n");
507     }
508   }
509   
510   // Scan for images in the given path
511   flush();
512   foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $fileInfo) {
513   
514       // We're only interested in png files
515       $indexPath= substr($fileInfo->getPathname(), $baseLength + 1);
516       $path= $fileInfo->getPathname();
517       if (preg_match('/\.png$/', $indexPath) && !preg_match('/\.svn/', $path) && !preg_match('/themes\/[^\/]+\/images\/img.png$/', $path)){
518   
519          // Grey image if it is not already one
520          if (preg_match('/grey/', $indexPath)) {
521            echo "!";
522            $warnings[]= "! Warning: skipped possible *grey* image $path";
523            flush();
524            continue;
525          }
527          // New image if it is not already one
528          if (preg_match('/new/', $indexPath) && !preg_match('/new\.png$/', $indexPath)) {
529            echo "!";
530            $warnings[]= "! Warning: skipped possible *new* image $path";
531            flush();
532            continue;
533          }
535          // Catch available themes
536          if (preg_match('/themes\//', $indexPath) && !preg_match('/themes\/'.$theme.'\//', $indexPath)) {
537            continue;
538          }
540          // Load image
541          $img= imageCreateFromPng($path);
542          $width= imageSX($img);
543          $height= imageSY($img);
544          imageDestroy($img);
545          $greyIndexPath= preg_replace('/\.png$/', '-grey.png', $indexPath);
547          // Is this image already there?
548          $checksum= md5_file($path);
549          if (in_array($checksum, $checksums)) {
550            $warnings[]= "! Warning: images $indexPath seems to be a duplicate of ".array_search($checksum, $checksums);
551            $duplicates[$indexPath]= array_search($checksum, $checksums);
552            $duplicates[$greyIndexPath]= preg_replace('/\.png$/', '-grey.png', array_search($checksum, $checksums));
553            continue;
554          } else {
555            $checksums[$indexPath]= $checksum;
556          }
558          // Ordinary image
559          $widths[$indexPath]= $width;
560          $heights[$indexPath]= $height;
561          $paths[$indexPath]= $path;
563          // Grey image
564          $widths[$greyIndexPath]= $width;
565          $heights[$greyIndexPath]= $height;
566          $paths[$greyIndexPath]= $path;
568          // Feed height statistics
569          if (!isset($heightStats[$height])) {
570            $heightStats[$height]= 1;
571          } else {
572            $heightStats[$height]++;
573          }
574       }
575   
576     echo ".";
577     flush();
578   }
579   echo "\n";
581   // Do some stupid height calculation
582   arsort($heightStats, SORT_NUMERIC);
583   reset($heightStats);
584   $popular= current($heightStats);
586   krsort($heightStats);
587   reset($heightStats);
588   $max= current($heightStats);
590   $maxHeight= (floor($max / $popular) + 1) * $popular * 6;
591   
592   // Sort to get biggest values
593   arsort($widths, SORT_NUMERIC);
594   reset($widths);
595   echo "Calculating master image dimensions: ";
596   flush();
597   
598   // Build container image
599   $cursorX= 0;
600   $cursorY= 0;
601   $colWidth= 0;
602   $rowHeight= 0;
603   $colX= 0;
604   $colY= 0;
605   $maxY= 0;
606   $maxX= 0;
608   // Walk thru width sorted images
609   foreach ($widths as $imagePath => $imageWidth) {
610     $imageHeight= $heights[$imagePath];
612     // First element in this column
613     if ($colWidth == 0) {
614       $colWidth= $imageWidth;
615     }
617     // First element in this row
618     if ($rowHeight < $imageHeight) {
619       $rowHeight= $imageHeight;
620     }
622     // Does the image match here?
623     if ($cursorX + $imageWidth > $colX + $colWidth) {
625       if ($cursorY + $imageHeight >= $maxHeight) {
627         // Reached max height, move to the next column
628         $colX+= $colWidth;
629         $cursorX= $colX;
630         $colWidth= $imageWidth;
631         $rowHeight= $imageHeight;
632         $colY= $cursorY= 0;
634       } else {
636         // Next row
637         $colY+= $rowHeight;
638         $cursorY= $colY;
639         $cursorX= $colX;
640         $rowHeight= $imageHeight;
641       }
643       $maxY=($colY + $imageHeight > $maxY)?$colY+$imageHeight:$maxY;
644     }
646     // Save calculated position
647     $posX[$imagePath]= $cursorX;
648     $posY[$imagePath]= $cursorY;
650     // Move X cursor to the next position
651     $cursorX+= $imageWidth;
653     $maxX=($colX+$imageWidth > $maxX)?$colX+$imageWidth:$maxX;
654   }
655   
656   // Print maximum dimensions
657   echo $maxY."x".$maxX."\n";
658   echo "Processing";
659   flush();
661   // Create result image
662   $dst= imageCreateTrueColor($maxX, $maxY);
663   imageAlphaBlending($dst, true);
664   $transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
665   imageFill($dst, 0, 0, $transparent);
666   imageSaveAlpha($dst, true);
668   // Finally assemble picture
669   foreach ($heights as $imagePath => $imageHeight) {
670     $imageWidth= $widths[$imagePath];
671     $x= $posX[$imagePath];
672     $y= $posY[$imagePath];
674     // Insert source image...
676     // Eventually convert it to grey before
677     if (preg_match('/-grey\.png$/', $imagePath)) {
678       if (!function_exists("imageFilter")){
679         exec("convert ".$paths[$imagePath]." -colorspace Gray /tmp/grey-converted.png");
680         $src= imageCreateFromPng("/tmp/grey-converted.png");
681       } else {
682         $src= imageCreateFromPng($paths[$imagePath]);
683         imageFilter($src, IMG_FILTER_GRAYSCALE);
684       }
685     } else {
686       $src= imageCreateFromPng($paths[$imagePath]);
687     }
689     // Merge image
690     imageCopyResampled($dst, $src, $x, $y, 0, 0, $imageWidth, $imageHeight, $imageWidth, $imageHeight);
691     imageDestroy($src);
693     // Store style
694     $styles[$imagePath]= "background-position:-".$x."px -".$y."px;width:".$imageWidth."px;height:".$imageHeight."px";
696     echo ".";
697     flush();
698   }
700   /* Add duplicates */
701   foreach ($duplicates as $imagePath => $realPath) {
702     $styles[$imagePath]= $styles[$realPath];
703   }
705   imagePNG($dst, GOSA_HOME."/html/themes/$theme/images/img.png", 9);
706   imageDestroy($dst);
708   // Show warnings images
709   foreach ($warnings as $warn) {
710     echo "$warn\n";
711   }
713   // Write styles
714   echo "Writing styles...";
715   $fp = fopen(GOSA_HOME."/ihtml/themes/$theme/img.styles", 'w');
716   fwrite($fp, serialize($styles));
717   fclose($fp);
719   echo "\n";
723 /* Fill global values */
724 $description= $provides= $depends= $versions= $conflicts= array();
726 /* Action specified? */
727 if ($argc < 2){
728         rescan_classes();
729         rescan_i18n();
730     foreach (get_themes() as $theme) {
731       rescan_images(GOSA_HOME."/html", $theme);
732     }
733     exit (0);
736 switch ($argv[1]){
737         case 'install':
738                 if (isset($argv[2])){
739                         install_plugin($argv[2]);
740                 } else {
741                         echo "Usage: update-gosa install dsc-file\n\n";
742                         exit (1);
743                 }
744                 break;
745         case 'list':
746                 list_plugins();
747                 break;
748         case 'remove':
749                 if (isset($argv[2])){
750                         remove_plugin($argv[2]);
751                 } else {
752                         echo "Usage: update-gosa remove plugin-name\n\n";
753                         exit (1);
754                 }
755                 break;
756         case 'rescan-i18n':
757                 rescan_i18n();
758                 break;
759         case 'rescan-classes':
760                 rescan_classes();
761                 break;
762         case 'rescan-images':
763                 foreach (get_themes() as $theme) {
764                   rescan_images("html", $theme);
765                 }
766                 break;
767         default:
768                 echo "Error: Supplied command not known\n\n";
769                 print_usage();
770                 break;
774 ?>