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 }
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 }
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){
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 }
322 }
325 class gosaProperty
326 {
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 }
434 return($match);
435 }
437 static function isString($message,$class,$name,$value, $type)
438 {
439 $match = TRUE;
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);
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);
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);
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){
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 }
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 }
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 }
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 }
753 }
757 interface propertyMigration
758 {
759 function __construct($config,$property);
760 }
763 ?>