Code

Updated registry dialog
[gosa.git] / gosa-core / include / class_configRegistry.inc
1 <?php
3 class configRegistry{
5     public $config = NULL;
6     public $properties = array();
7     public $ldapStoredProperties = array(); 
8     public $fileStoredProperties = array(); 
9     public $classToName = array(); 
11     public $status = 'none';
13     // Excludes property values defined in ldap 
14     public $ignoreLdapProperties = FALSE;
16     // Contains all classes with plInfo
17     public $classesWithInfo = array();
18     public $pluginRequirements  = array();
19     public $categoryToClass  = array();
21     public $objectClasses = array();
23     public $schemaCheckFinished = FALSE;
25     function __construct($config)
26     {
27         $this->config = &$config;
29         // Detect classes that have a plInfo method 
30         global $class_mapping;
31         foreach ($class_mapping as $cname => $path){
32             $cmethods = get_class_methods($cname);
33             if (is_array($cmethods) && in_array_ics('plInfo',$cmethods)){
35                 // Get plugin definitions
36                 $def = call_user_func(array($cname, 'plInfo'));;
38                 // Register Post Events (postmodfiy,postcreate,postremove,checkhook)
39                 if(count($def)){
40                     $this->classesWithInfo[$cname] = $def;
41                 }
42             }
43         }
45         // (Re)Load properties
46         $this->reload();
47     }
50     function validateSchemata($force = FALSE, $disableIncompatiblePlugins = FALSE,$displayMessage = FALSE)
51     {
52         // We can check the schemata only with a valid LDAP connection
53         if(empty($this->config->current['CONFIG'])){
54             return(TRUE); 
55         }
57         // Don't do things twice unless forced
58         if($this->schemaCheckFinished && !$force) return; 
60         // Read objectClasses from ldap
61         if(!count($this->objectClasses)){
62             $ldap = $this->config->get_ldap_link();
63             $ldap->cd($this->config->current['BASE']);
64             $this->objectClasses = $ldap->get_objectclasses();
65         }
67         // Collect required schema infos
68         $this->pluginRequirements = array('ldapSchema' => array());
69         $this->categoryToClass = array();
70         foreach($this->classesWithInfo as $cname => $defs){
71             if(isset($defs['plRequirements']['ldapSchema'])){
72                 $this->pluginRequirements['ldapSchema'][$cname] = $defs['plRequirements']['ldapSchema'];
73             }
74         }
76         // Check schema requirements now
77         $missing = $invalid = array();
78         foreach($this->pluginRequirements['ldapSchema'] as $cname => $requirements){
79             foreach($requirements as $oc => $version){
80                 if(!$this->ocAvailable($oc)){
81                     $missing[] = $oc;
82                 }elseif(!empty($version)){
83                     $currentVersion = $this->getObjectClassVersion($oc);
84                     if(!empty($currentVersion) && !$this->ocVersionMatch($version, $currentVersion)){
85                         if($currentVersion == -1){
86                             $currentVersion = _("unknown");
87                         }
88                         $invalid[] = sprintf(_("%s has version %s but %s required!"), bold($oc),bold($currentVersion),bold($version));
89                     }
90                 }
91             }
92         }
94         if($displayMessage && count($missing)){
95             msg_dialog::display(_("Schema validation error"), 
96                  _("The following objectClasses are missing:").
97                 "<div class='scrollContainer' style='height:100px'>".msgPool::buildList($missing)."</div>",
98                     ERROR_DIALOG);
99         }    
100         if($displayMessage && count($invalid)){
101             msg_dialog::display(_("Schema validation error"), 
102                 _("The following objectClasses do not match the version requirements:").
103                 "<div class='scrollContainer' style='height:100px'>".msgPool::buildList($invalid)."</div>",
104                     ERROR_DIALOG);
105         }    
107         $this->schemaCheckFinished =TRUE;
108     }
110     function ocVersionMatch($required, $installed)
111     {
112         $operator = preg_replace('/^([=<>]*).*$/',"\\1",$required);
113         $required = preg_replace('/^[=<>]*(.*)$/',"\\1",$required);
114         return(version_compare($installed,$required, $operator)); 
115     }
117     
118     function getObjectClassVersion($oc)
119     {
120         if(!isset($this->objectClasses[$oc])){
121             return(NULL);
122         }else{
123             $version = -1; // unknown
124             if(preg_match("/(v[^)]*)/", $this->objectClasses[$oc]['DESC'])){
125                 $version = preg_replace('/^.*\(v([^)]*)\).*$/',"\\1", $this->objectClasses[$oc]['DESC']);
126             }
127         }
128         return($version);
129     }
130     
132     // check wheter an objectClass is installed or not.
133     function ocAvailable($name)
134     {
135         return(isset($this->objectClasses[$name]));
136     }
139     function reload($force = FALSE)
140     {
141         // Do not reload the properties everytime, once we have  
142         //  everything loaded and registrered skip the reload.
143         // Status is 'finished' once we had a ldap connection (logged in)
144         if(!$force && $this->status == 'finished') return;
146         // Reset everything
147         $this->ldapStoredProperties = array();
148         $this->fileStoredProperties = array();
149         $this->properties = array();
150         $this->mapByName = array();
152         // Search for config flags defined in the config file (TAB section)
153         foreach($this->config->data['TABS'] as $tabname => $tabdefs){
154             foreach($tabdefs as $info){
156                 // Check if the info is valid
157                 if(isset($info['NAME']) && isset($info['CLASS'])){
159                     // Check if there is nore than just the plugin definition
160                     if(count($info) > 2){
161                         foreach($info as $name => $value){
162                             
163                             if(!in_array($name, array('CLASS','NAME'))){
164                                 $class= $info['CLASS'];    
165                                 $this->fileStoredProperties[$class][strtolower($name)] = $value;
166                             }
167                         }
168                     }
169                 }
170             }
171         }
173         // Search for config flags defined in the config file (MENU section)
174         foreach($this->config->data['MENU'] as $section => $entries){
175             foreach($entries as $entry){
176                 if(count($entry) > 2 && isset($entry['CLASS'])){
177                     $class = $entry['CLASS'];
178                     foreach($entry as $name => $value){
179                         if(!in_array($name, array('CLASS','ACL'))){
180                             $this->fileStoredProperties[strtolower($class)][strtolower($name)] = $value;
181                         }
182                     }
183                 }
184             }
185         }
187         // Search for config flags defined in the config file (MAIN section)
188         foreach($this->config->data['MAIN'] as $name => $value){
189             $this->fileStoredProperties['core'][strtolower($name)] = $value;
190         }
192         // Search for config flags defined in the config file (Current LOCATION section)
193         if(isset($this->config->current)){
194             foreach($this->config->current as $name => $value){
195                 $this->fileStoredProperties['core'][strtolower($name)] = $value;
196             }
197         }
199         // Skip searching for LDAP defined properties if 'ignoreLdapProperties' is set to 'true'
200         //  in the config. 
201         $this->ignoreLdapProperties = FALSE;
202         if(isset($this->fileStoredProperties['core'][strtolower('ignoreLdapProperties')]) && 
203             preg_match("/(true|on)/i", $this->fileStoredProperties['core'][strtolower('ignoreLdapProperties')])){
204             $this->ignoreLdapProperties = TRUE;
205         }
207         // Search for all config flags defined in the LDAP - BUT only if we ARE logged in. 
208         if(!empty($this->config->current['CONFIG'])){
209             $ldap = $this->config->get_ldap_link();
210             $ldap->cd($this->config->current['CONFIG']);
211             $ldap->search('(&(objectClass=gosaConfig)(gosaSetting=*))', array('cn','gosaSetting'));
212             while($attrs = $ldap->fetch()){
213                 $class = $attrs['cn'][0];
214                 for($i=0; $i<$attrs['gosaSetting']['count']; $i++){
215                     list($name,$value) = preg_split("/:/",$attrs['gosaSetting'][$i],2);
216                     $this->ldapStoredProperties[$class][$name] = $value;
217                 }
218             }
219             $this->status = 'finished';
220         }
222         // Register plugin properties.
223         foreach ($this->classesWithInfo as $cname => $def){
225             // Detect class name
226             $name = $cname;
227             $name = (isset($def['plShortName'])) ? $def['plShortName'] : $cname;
228             $name = (isset($def['plDescription'])) ? $def['plDescription'] : $cname;
230             // Register post events
231             $this->classToName[$cname] = $name;
232             $data = array('name' => 'postcreate','type' => 'command');
233             $this->register($cname, $data);    
234             $data = array('name' => 'postremove','type' => 'command');
235             $this->register($cname, $data);    
236             $data = array('name' => 'postmodify','type' => 'command');
237             $this->register($cname, $data);    
238             $data = array('name' => 'check', 'type' => 'command');
239             $this->register($cname, $data);    
241             // Register properties 
242             if(isset($def['plProperties'])){
243                 foreach($def['plProperties'] as $property){
244                     $this->register($cname, $property);
245                 }
246             }
247         }
248     }
250     function register($class,$data)
251     {
252         $id = count($this->properties);
253         $this->properties[$id] = new gosaProperty($this,$class,$data);
254         $p = strtolower("{$class}::{$data['name']}");
255         $this->mapByName[$p] = $id;
256     }
258     public function getAllProperties()
259     {
260         return($this->properties);
261     }
263     function propertyExists($class,$name)
264     {       
265         $p = strtolower("{$class}::{$name}");
266         return(isset($this->mapByName[$p]));
267     }
269     private function getId($class,$name)
270     {
271         $p = strtolower("{$class}::{$name}");
272         if(!isset($this->mapByName[$p])){
273             return(-1);
274         }       
275         return($this->mapByName[$p]);    
276     }
278     function getProperty($class,$name)
279     {
280         if($this->propertyExists($class,$name)){
281             return($this->properties[$this->getId($class,$name)]);
282         }
283         return(NULL); 
284     }
286     function getPropertyValue($class,$name)
287     {   
288         if($this->propertyExists($class,$name)){
289             $tmp = $this->getProperty($class,$name);
290             return($tmp->getValue());
291         }
292         return("");
293     }
295     function setPropertyValue($class,$name, $value)
296     {   
297         if($this->propertyExists($class,$name)){
298             $tmp = $this->getProperty($class,$name);
299             return($tmp->setValue($value));
300         }
301         return("");
302     }
304     function saveChanges()
305     {
306         $migrate = array();
307         foreach($this->properties as $prop){
309             // Is this property modified
310             if(in_array($prop->getStatus(),array('modified','removed'))){
312                 // Check if we've to migrate something before we can make the changes effective. 
313                 if($prop->migrationRequired()){
314                     $migrate[] = $prop;
315                 }else{
316                     $prop->save();
317                 }
318             }
319         }
320         return($migrate);
321     }
325 class gosaProperty
327     protected $name = "";
328     protected $class = "";
329     protected $value = "";
330     protected $tmp_value = "";  // Used when modified but not saved 
331     protected $type = "string";
332     protected $default = "";
333     protected $defaults = "";
334     protected $description = "";
335     protected $check = "";
336     protected $migrate = "";
337     protected $mandatory = FALSE;
338     protected $group = "default";
339     protected $parent = NULL;
340     protected $data = array();
342     protected $migrationClass = NULL;
344     /*!  The current property status
345      *     'ldap'       Property is stored in ldap 
346      *     'file'       Property is stored in the config file
347      *     'undefined'  Property is currently not stored anywhere
348      *     'modified'   Property has been modified (should be saved)
349      */
350     protected $status = 'undefined';
352     protected $attributes = array('name','type','default','description','check',
353             'migrate','mandatory','group','defaults');
358     function __construct($parent,$classname,$data)
359     {
360         // Set some basic infos 
361         $this->parent = &$parent;
362         $this->class = $classname;
363         $this->data  = $data;
365         // Get all relevant information from the data array (comes from plInfo)    
366         foreach($this->attributes as $aName){
367             if(isset($data[$aName])){
368                 $this->$aName = $data[$aName];
369             }
370         }      
372         // Initialize with the current value
373         $this->_restoreCurrentValue(); 
375     }
377     function migrationRequired()
378     {
379         // Instantiate migration class 
380         if(!empty($this->migrate) && $this->migrationClass == NULL){
381             if(!class_available($this->migrate)){
382                 trigger_error("Cannot start migration for gosaProperty::'{$this->getName()}' class not found ({$this->migrate})!");
383             }else{
384                 $class = $this->migrate;
385                 $tmp = new $class($this->parent->config,$this);
386                 if(! $tmp instanceof propertyMigration){ 
387                     trigger_error("Cannot start migration for gosaProperty::'{$this->getName()}' doesn't implement propertyMigration!");
388                 }else{
389                     $this->migrationClass = $tmp;
390                 }
391             }
392         }
393         if(empty($this->migrate) || $this->migrationClass == NULL){
394             return(FALSE);
395         }
396         return($this->migrationClass->checkForIssues());
397     }
399     function getMigrationClass()
400     {
401         return($this->migrationClass);
402     }
404     function check()
405     {
406         $val = $this->getValue(TRUE);
407         $return = TRUE;
408         if($this->mandatory && empty($val)){
409             $return = FALSE;
410         }
412         $check = $this->getCheck();
413         if(!empty($val) && !empty($check)){
414             $res = call_user_func(preg_split("/::/", $this->check),$messages=TRUE, $this->class,$this->name,$val, $this->type);
415             if(!$res){
416                 $return = FALSE;
417             }
418         }
419         return($return);
420     }
422     static function isBool($message,$class,$name,$value, $type)
423     {
424         $match = in_array($value,array('true','false',''));
426         // Display the reason for failing this check.         
427         if($message && ! $match){
428             msg_dialog::display(_("Warning"), 
429                     sprintf(_("The value '%s' specified for '%s:%s' is invalid. A bool value is required here!"), 
430                         bold($value),bold($class),bold($name)), 
431                     WARNING_DIALOG);
432         }
433     
434         return($match);
435     }
437     static function isString($message,$class,$name,$value, $type)
438     {
439         $match = TRUE;
440     
441         // Display the reason for failing this check.         
442         if($message && ! $match){
443             msg_dialog::display(_("Warning"), 
444                     sprintf(_("The value '%s' specified for '%s:%s' is invalid. A string value is required here!"), 
445                         bold($value),bold($class),bold($name)), 
446                     WARNING_DIALOG);
447         }
449         return($match);
450     }
452     static function isInteger($message,$class,$name,$value, $type)
453     {
454         $match = is_numeric($value) && !preg_match("/[^0-9]/", $value);
456         // Display the reason for failing this check.         
457         if($message && ! $match){
458             msg_dialog::display(_("Warning"), 
459                     sprintf(_("The value '%s' specified for '%s:%s' is invalid. A numeric value is required here!"), 
460                         bold($value),bold($class),bold($name)), 
461                     WARNING_DIALOG);
462         }
464         return($match);
465     }
467     static function isPath($message,$class,$name,$value, $type)
468     {
469         $match = preg_match("#^(/[^/]*/){1}#", $value);
470     
471         // Display the reason for failing this check.         
472         if($message && ! $match){
473             msg_dialog::display(_("Warning"), 
474                     sprintf(_("The path '%s' specified for '%s:%s' is invalid!"), 
475                         bold($value),bold($class),bold($name)), 
476                     WARNING_DIALOG);
477         }
479         return($match);
480     }
482     static function isReadablePath($message,$class,$name,$value, $type)
483     {
484         $match = !empty($value)&&is_dir($value)&&is_writeable($value);
485    
486         // Display the reason for failing this check.         
487         if($message && ! $match){
488             if(!is_dir($value)){
489                 msg_dialog::display(_("Warning"), 
490                         sprintf(_("The folder '%s' specified for '%s:%s' does not exists!"), 
491                             bold($value),bold($class),bold($name)), 
492                         WARNING_DIALOG);
493             }elseif(!is_readable($value)){
494                 msg_dialog::display(_("Warning"), 
495                         sprintf(_("The folder '%s' specified for '%s:%s' cannot be used for reading!"), 
496                             bold($value),bold($class),bold($name)), 
497                         WARNING_DIALOG);
498             }
499         }
501         return($match);
502     }
504     static function isWriteablePath($message,$class,$name,$value, $type)
505     {
506         $match = !empty($value)&&is_dir($value)&&is_writeable($value);
507    
508         // Display the reason for failing this check.         
509         if($message && ! $match){
510             if(!is_dir($value)){
511                 msg_dialog::display(_("Warning"), 
512                         sprintf(_("The folder '%s' specified for '%s:%s' does not exists!"), 
513                             bold($value),bold($class),bold($name)), 
514                         WARNING_DIALOG);
515             }elseif(!is_writeable($value)){
516                 msg_dialog::display(_("Warning"), 
517                         sprintf(_("The folder '%s' specified for '%s:%s' cannot be used for writing!"), 
518                             bold($value),bold($class),bold($name)), 
519                         WARNING_DIALOG);
520             }
521         }
523         return($match);
524     }
526     static function isReadableFile($message,$class,$name,$value, $type)
527     {
528         $match = !empty($value) && is_readable($value) && is_file($value);
530         // Display the reason for failing this check.         
531         if($message && ! $match){
532                 
533             if(!is_file($value)){
534                 msg_dialog::display(_("Warning"), 
535                         sprintf(_("The file '%s' specified for '%s:%s' does not exists!"), 
536                             bold($value),bold($class),bold($name)), 
537                         WARNING_DIALOG);
538             }elseif(!is_readable($value)){
539                 msg_dialog::display(_("Warning"), 
540                         sprintf(_("The file '%s' specified for '%s:%s' cannot be read!"), 
541                             bold($value),bold($class),bold($name)), 
542                         WARNING_DIALOG);
543             }
544         }
546         return($match);
547     }
549     static function isCommand($message,$class,$name,$value, $type)
550     {
551         $match = TRUE;
553         // Display the reason for failing this check.         
554         if($message && ! $match){
555             msg_dialog::display(_("Warning"), 
556                     sprintf(_("The command '%s' specified for '%s:%s' is invalid!"), 
557                         bold($value),bold($class),bold($name)), 
558                     WARNING_DIALOG);
559         }
560         
561         return($match);
562     }
564     static function isDn($message,$class,$name,$value, $type)
565     {
566         $match = preg_match("/^([a-z]*=[^=,]*,)*[^=]*=[^=]*$/i", $value);
568         // Display the reason for failing this check.         
569         if($message && ! $match){
570             msg_dialog::display(_("Warning"), 
571                     sprintf(_("The dn '%s' specified for '%s:%s' is invalid!"), 
572                         bold($value),bold($class),bold($name)), 
573                     WARNING_DIALOG);
574         }
575         
576         return($match);
577     }
579     static function isRdn($message,$class,$name,$value, $type)
580     {
581         $match = preg_match("/^([a-z]*=[^=,]*,)*[^=]*=[^=,]*,?$/i", $value);
583         // Display the reason for failing this check.         
584         if($message && ! $match){
585             msg_dialog::display(_("Warning"), 
586                     sprintf(_("The rdn '%s' specified for '%s:%s' is invalid!"), 
587                         bold($value),bold($class),bold($name)), 
588                     WARNING_DIALOG);
589         }
590         
591         return($match);
592     }
594     private function _restoreCurrentValue()
595     {
596         // First check for values in the LDAP Database.
597         if(isset($this->parent->ldapStoredProperties[$this->class][$this->name])){
598             $this->setStatus('ldap');
599             $this->value = $this->parent->ldapStoredProperties[$this->class][$this->name];
600             return;
601         }
603         // Second check for values in the config file.
604         if(isset($this->parent->fileStoredProperties[strtolower($this->class)][strtolower($this->name)])){
605             $this->setStatus('file');
606             $this->value = $this->parent->fileStoredProperties[strtolower($this->class)][strtolower($this->name)];
607             return;
608         }
610         // If there still wasn't found anything then fallback to the default.
611         if($this->getStatus() == 'undefined'){
612             $this->value = $this->getDefault();
613         }
614     }
616     function getMigrate() { return($this->migrate); }
617     function getCheck() { return($this->check); }
618     function getName() { return($this->name); }
619     function getClass() { return($this->class); }
620     function getGroup() { return($this->group); }
621     function getType() { return($this->type); }
622     function getDescription() { return($this->description); }
623     function getDefault() { return($this->default); }
624     function getDefaults() { return($this->defaults); }
625     function getStatus() { return($this->status); }
626     function isMandatory() { return($this->mandatory); }
628     function setValue($str) 
629     {
630         if(in_array($this->getStatus(), array('modified'))){
631             $this->tmp_value = $str; 
632         }elseif($this->value != $str){
633             $this->setStatus('modified'); 
634             $this->tmp_value = $str; 
635         }
636     }
638     function getValue($temporary = FALSE) 
639     { 
640         if($temporary){
641             if(in_array($this->getStatus(), array('modified','removed'))){
642                 return($this->tmp_value); 
643             }else{
644                 return($this->value); 
645             }
646         }else{ 
648             // Do not return ldap values if we've to ignore them.
649             if($this->parent->ignoreLdapProperties){
650                 if(isset($this->parent->fileStoredProperties[strtolower($this->class)][strtolower($this->name)])){
651                     return($this->parent->fileStoredProperties[strtolower($this->class)][strtolower($this->name)]);
652                 }else{
653                     return($this->getDefault());
654                 }
655             }else{
656                 return($this->value); 
657             }
658         }
659     }
661     function restoreDefault() 
662     {
663         if(in_array($this->getStatus(),array('ldap'))){
664             $this->setStatus('removed'); 
666             // Second check for values in the config file.
667             if(isset($this->parent->fileStoredProperties[strtolower($this->class)][strtolower($this->name)])){
668                 $this->tmp_value = $this->parent->fileStoredProperties[strtolower($this->class)][strtolower($this->name)];
669             }else{
670                 $this->tmp_value = $this->getDefault();
671             }
672         }
673     }
675     function save()
676     {
677         if($this->getStatus() == 'modified'){
678             $ldap = $this->parent->config->get_ldap_link();
679             $ldap->cd($this->parent->config->current['BASE']);
680             $dn = "cn={$this->class},".$this->parent->config->current['CONFIG'];
681             $ldap->cat($dn);
682             if(!$ldap->count()){
683                 $ldap->cd($dn);
684                 $data = array(
685                         'cn' => $this->class, 
686                         'objectClass' => array('top','gosaConfig'),
687                         'gosaSetting' => $this->name.":".$this->tmp_value);
689                 $ldap->add($data);
690                 if(!$ldap->success()){
691                     echo $ldap->get_error();
692                 }
694             }else{
695                 $attrs = $ldap->fetch();
696                 $data = array();
697                 $found = false;
698                 if(isset($attrs['gosaSetting']['count'])){
699                     for($i = 0;$i<$attrs['gosaSetting']['count']; $i ++){
700                         $set = $attrs['gosaSetting'][$i];
701                         if(preg_match("/^{$this->name}:/", $set)){
702                             $set = "{$this->name}:{$this->tmp_value}";
703                             $found = true;
704                         }
705                         $data['gosaSetting'][] = $set;
706                     }
707                 }
708                 if(!$found) $data['gosaSetting'][] = "{$this->name}:{$this->tmp_value}";
709                 $ldap->cd($dn);
710                 $ldap->modify($data);
711                 if(!$ldap->success()){
712                     echo $ldap->get_error();
713                 }
714             }
715             $this->value = $this->tmp_value;
716             $this->setStatus('ldap'); 
717         }elseif($this->getStatus() == 'removed'){
718             $ldap = $this->parent->config->get_ldap_link();
719             $ldap->cd($this->parent->config->current['BASE']);
720             $dn = "cn={$this->class},".$this->parent->config->current['CONFIG'];
721             $ldap->cat($dn);
722             $attrs = $ldap->fetch();
723             $data = array('gosaSetting' => array());
724             for($i = 0;$i<$attrs['gosaSetting']['count']; $i ++){
725                 $set = $attrs['gosaSetting'][$i];
726                 if(preg_match("/^{$this->name}:/", $set)){
727                     continue;
728                 }
729                 $data['gosaSetting'][] = $set;
730             }
731             $ldap->cd($dn);
732             $ldap->modify($data);
733             if(!$ldap->success()){
734                 echo $ldap->get_error();
735             }
736             $this->_restoreCurrentValue();
737         }
738     }
740     private function setStatus($state) 
741     {
742         if(!in_array($state, array('ldap','file','undefined','modified','removed'))) {
743             trigger_error("Unknown property status given '{$state}' for {$this->class}:{$this->name}!");
744         }else{
745             $this->status = $state; 
746         }
747     }
749     function isValid() 
750     { 
751         return(TRUE);    
752     }
757 interface propertyMigration
759     function __construct($config,$property);
763 ?>