Code

First - But not finished commit of stats plugin
[gosa.git] / gosa-core / include / class_plugin.inc
1 <?php
2 /*
3  * This code is part of GOsa (http://www.gosa-project.org)
4  * Copyright (C) 2003-2008 GONICUS GmbH
5  *
6  * ID: $$Id$$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
23 /*! \brief   The plugin base class
24   \author  Cajus Pollmeier <pollmeier@gonicus.de>
25   \version 2.00
26   \date    24.07.2003
28   This is the base class for all plugins. It can be used standalone or
29   can be included by the tabs class. All management should be done 
30   within this class. Extend your plugins from this class.
31  */
33 class plugin
34
35   /*! \brief    The title shown in path menu while this plugin is visible.
36    */
37   var $pathTitle = "";
39   /*!
40     \brief Reference to parent object
42     This variable is used when the plugin is included in tabs
43     and keeps reference to the tab class. Communication to other
44     tabs is possible by 'name'. So the 'fax' plugin can ask the
45     'userinfo' plugin for the fax number.
47     \sa tab
48    */
49   var $parent= NULL;
51   /*!
52     \brief Configuration container
54     Access to global configuration
55    */
56   var $config= NULL;
58   /*!
59     \brief Mark plugin as account
61     Defines whether this plugin is defined as an account or not.
62     This has consequences for the plugin to be saved from tab
63     mode. If it is set to 'FALSE' the tab will call the delete
64     function, else the save function. Should be set to 'TRUE' if
65     the construtor detects a valid LDAP object.
67     \sa plugin::plugin()
68    */
69   var $is_account= FALSE;
70   var $initially_was_account= FALSE;
72   /*!
73     \brief Mark plugin as template
75     Defines whether we are creating a template or a normal object.
76     Has conseqences on the way execute() shows the formular and how
77     save() puts the data to LDAP.
79     \sa plugin::save() plugin::execute()
80    */
81   var $is_template= FALSE;
82   var $ignore_account= FALSE;
83   var $is_modified= FALSE;
85   /*!
86     \brief Represent temporary LDAP data
88     This is only used internally.
89    */
90   var $attrs= array();
92   /* Keep set of conflicting plugins */
93   var $conflicts= array();
95   /* Save unit tags */
96   var $gosaUnitTag= "";
97   var $skipTagging= FALSE;
99   /*!
100     \brief Used standard values
102     dn
103    */
104   var $dn= "";
105   var $uid= "";
106   var $sn= "";
107   var $givenName= "";
108   var $acl= "*none*";
109   var $dialog= FALSE;
110   var $snapDialog = NULL;
112   /* attribute list for save action */
113   var $attributes= array();
114   var $objectclasses= array();
115   var $is_new= TRUE;
116   var $saved_attributes= array();
118   var $acl_base= "";
119   var $acl_category= "";
120   var $read_only = FALSE; // Used when the entry is opened as "readonly" due to locks.
122   /* This can be set to render the tabulators in another stylesheet */
123   var $pl_notify= FALSE;
125   /* Object entry CSN */
126   var $entryCSN         = "";
127   var $CSN_check_active = FALSE;
129   /* This variable indicates that this class can handle multiple dns at once. */
130   var $multiple_support = FALSE;
131   var $multi_attrs      = array();
132   var $multi_attrs_all  = array(); 
134   /* This aviable indicates, that we are currently in multiple edit handle */
135   var $multiple_support_active = FALSE; 
136   var $selected_edit_values = array();
137   var $multi_boxes = array();
139   /*! \brief plugin constructor
141     If 'dn' is set, the node loads the given 'dn' from LDAP
143     \param dn Distinguished name to initialize plugin from
144     \sa plugin()
145    */
146   function plugin (&$config, $dn= NULL, $object= NULL)
147   {
149     $this->initTime = microtime(TRUE);
150     stats::log('plugin', $class = get_class($this), $action = 'open', $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
152     /* Configuration is fine, allways */
153     $this->config= &$config;    
154     $this->dn= $dn;
156     // Ensure that we've a valid acl_category set.
157     if(empty($this->acl_category)){
158       $tmp = $this->plInfo();
159       if (isset($tmp['plCategory'])) {
160         $c = key($tmp['plCategory']);
161         if(is_numeric($c)){
162           $c = $tmp['plCategory'][0];
163         }
164         $this->acl_category = $c."/";
165       }
166     }
168     /* Handle new accounts, don't read information from LDAP */
169     if ($dn == "new"){
170       return;
171     }
173     /* Check if this entry was opened in read only mode */
174     if(isset($_POST['open_readonly'])){
175       if(session::global_is_set("LOCK_CACHE")){
176         $cache = &session::get("LOCK_CACHE");
177         if(isset($cache['READ_ONLY'][$this->dn])){
178           $this->read_only = TRUE;
179         }
180       }
181     }
183     /* Save current dn as acl_base */
184     $this->acl_base= $dn;
186     /* Get LDAP descriptor */
187     if ($dn !== NULL){
189       /* Load data to 'attrs' and save 'dn' */
190       if ($object !== NULL){
191         $this->attrs= $object->attrs;
192       } else {
193         $ldap= $this->config->get_ldap_link();
194         $ldap->cat ($dn);
195         $this->attrs= $ldap->fetch();
196       }
198       /* Copy needed attributes */
199       foreach ($this->attributes as $val){
200         $found= array_key_ics($val, $this->attrs);
201         if ($found != ""){
202           $this->$val= $found[0];
203         }
204       }
206       /* gosaUnitTag loading... */
207       if (isset($this->attrs['gosaUnitTag'][0])){
208         $this->gosaUnitTag= $this->attrs['gosaUnitTag'][0];
209       }
211       /* Set the template flag according to the existence of objectClass
212          gosaUserTemplate */
213       if (isset($this->attrs['objectClass'])){
214         if (in_array_ics ("gosaUserTemplate", $this->attrs['objectClass'])){
215           $this->is_template= TRUE;
216           @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
217               "found", "Template check");
218         }
219       }
221       /* Is Account? */
222       $found= TRUE;
223       foreach ($this->objectclasses as $obj){
224         if (preg_match('/top/i', $obj)){
225           continue;
226         }
227         if (!isset($this->attrs['objectClass']) || !in_array_ics ($obj, $this->attrs['objectClass'])){
228           $found= FALSE;
229           break;
230         }
231       }
232       if ($found){
233         $this->is_account= TRUE;
234         @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
235             "found", "Object check");
236       }
238       /* Prepare saved attributes */
239       $this->saved_attributes= $this->attrs;
240       foreach ($this->saved_attributes as $index => $value){
241         if (is_numeric($index)){
242           unset($this->saved_attributes[$index]);
243           continue;
244         }
246         if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)){
247           unset($this->saved_attributes[$index]);
248           continue;
249         }
251         if (isset($this->saved_attributes[$index][0])){
252           if(!isset($this->saved_attributes[$index]["count"])){
253             $this->saved_attributes[$index]["count"] = count($this->saved_attributes[$index]);
254           }
255           if($this->saved_attributes[$index]["count"] == 1){
256             $tmp= $this->saved_attributes[$index][0];
257             unset($this->saved_attributes[$index]);
258             $this->saved_attributes[$index]= $tmp;
259             continue;
260           }
261         }
262         unset($this->saved_attributes["$index"]["count"]);
263       }
265       if(isset($this->attrs['gosaUnitTag'])){
266         $this->saved_attributes['gosaUnitTag'] = $this->attrs['gosaUnitTag'][0];
267       }
268     }
270     /* Save initial account state */
271     $this->initially_was_account= $this->is_account;
272   }
275   /*! \brief Generates the html output for this node
276    */
277   function execute()
278   {
279     /* This one is empty currently. Fabian - please fill in the docu code */
280     session::global_set('current_class_for_help',get_class($this));
282     /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
283     session::set('LOCK_VARS_TO_USE',array());
284     session::set('LOCK_VARS_USED_GET',array());
285     session::set('LOCK_VARS_USED_POST',array());
286     session::set('LOCK_VARS_USED_REQUEST',array());
288     pathNavigator::registerPlugin($this);
289     stats::log('plugin', $class = get_class($this), $action = 'view', $amount = 1, $duration = microtime(TRUE) - $this->initTime);
290   }
292   /*! \brief Removes object from parent
293    */
294   function remove_from_parent()
295   {
296     /* include global link_info */
297     $ldap= $this->config->get_ldap_link();
299     /* Get current objectClasses in order to add the required ones */
300     $ldap->cat($this->dn);
301     $tmp= $ldap->fetch ();
302     $oc= array();
303     if (isset($tmp['objectClass'])){
304       $oc= $tmp['objectClass'];
305       unset($oc['count']);
306     }
308     /* Remove objectClasses from entry */
309     $ldap->cd($this->dn);
310     $this->attrs= array();
311     $this->attrs['objectClass']= array_remove_entries_ics($this->objectclasses,$oc);
313     /* Unset attributes from entry */
314     foreach ($this->attributes as $val){
315       $this->attrs["$val"]= array();
316     }
318     /* Unset account info */
319     $this->is_account= FALSE;
321     /* Do not write in plugin base class, this must be done by
322        children, since there are normally additional attribs,
323        lists, etc. */
324     /*
325        $ldap->modify($this->attrs);
326      */
327     if($this->initially_was_account){
328         $this->handle_pre_events('remove');
329         stats::log('plugin', $class = get_class($this), $action = 'remove', $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
330     }
331   }
334   /*! \brief Save HTML posted data to object 
335    */
336   function save_object()
337   {
338     /* Update entry CSN if it is empty. */
339     if(empty($this->entryCSN) && $this->CSN_check_active){
340       $this->entryCSN = getEntryCSN($this->dn);
341     }
343     /* Save values to object */
344     foreach ($this->attributes as $val){
345       if (isset ($_POST["$val"]) && $this->acl_is_writeable($val)){
346   
347         /* Check for modifications */
348         $data= get_post($val);
349         if ($this->$val != $data){
350           $this->is_modified= TRUE;
351         }
352         $this->$val = $data;
353     
354         /* Okay, how can I explain this fix ... 
355          * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds. 
356          * So IE posts these 'unselectable' option, with value = chr(194) 
357          * chr(194) seems to be the &nbsp; in between the ...option>&nbsp;</option.. because there is no value=".." specified in these option fields  
358          * This &nbsp; was added for W3c compliance, but now causes these ... ldap errors ... 
359          * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
360          */
361         if(isset($data[0]) && $data[0] == chr(194)) {
362           $data = "";  
363         }
364         $this->$val= $data;
365       }
366     }
367   }
370   /*! \brief Save data to LDAP, depending on is_account we save or delete */
371   function save()
372   {
373     /* include global link_info */
374     $ldap= $this->config->get_ldap_link();
376     /* Save all plugins */
377     $this->entryCSN = "";
379     /* Start with empty array */
380     $this->attrs= array();
382     /* Get current objectClasses in order to add the required ones */
383     $ldap->cat($this->dn);
384     
385     $tmp= $ldap->fetch ();
387     $oc= array();
388     if (isset($tmp['objectClass'])){
389       $oc= $tmp["objectClass"];
390       $this->is_new= FALSE;
391       unset($oc['count']);
392     } else {
393       $this->is_new= TRUE;
394     }
396     /* Load (minimum) attributes, add missing ones */
397     $this->attrs['objectClass']= gosa_array_merge($oc,$this->objectclasses);
399     /* Copy standard attributes */
400     foreach ($this->attributes as $val){
401       if ($this->$val != ""){
402         $this->attrs["$val"]= $this->$val;
403       } elseif (!$this->is_new) {
404         $this->attrs["$val"]= array();
405       }
406     }
408     /* Handle tagging */
409     $this->tag_attrs($this->attrs);
411     if($this->is_new){
412         $this->handle_pre_events('add');
413         stats::log('plugin', $class = get_class($this), $action = 'create', $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
414     }else{
415         $this->handle_pre_events('modify');
416         stats::log('plugin', $class = get_class($this), $action = 'modify', $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
417     }
418   }
421   /*! \brief    Forward command execution requests
422    *             to the hook execution method.
423    */
424   function handle_pre_events($mode, $addAttrs= array())
425   {
426     if(!in_array($mode, array('add','remove','modify'))){
427       trigger_error(sprintf("Invalid pre event type given %s! Valid types are [add,modify,remove].", $mode));
428       return;
429     }
430     switch ($mode){
431       case "add":
432         plugin::callHook($this,"PRECREATE", $addAttrs);
433       break;
435       case "modify":
436         plugin::callHook($this,"PREMODIFY", $addAttrs);
437       break;
439       case "remove":
440         plugin::callHook($this,"PREREMOVE", $addAttrs);
441       break;
442     }
443   }
446   function cleanup()
447   {
448     foreach ($this->attrs as $index => $value){
449       
450       /* Convert arrays with one element to non arrays, if the saved
451          attributes are no array, too */
452       if (is_array($this->attrs[$index]) && 
453           count ($this->attrs[$index]) == 1 &&
454           isset($this->saved_attributes[$index]) &&
455           !is_array($this->saved_attributes[$index])){
456           
457         $tmp= $this->attrs[$index][0];
458         $this->attrs[$index]= $tmp;
459       }
461       /* Remove emtpy arrays if they do not differ */
462       if (is_array($this->attrs[$index]) &&
463           count($this->attrs[$index]) == 0 &&
464           !isset($this->saved_attributes[$index])){
465           
466         unset ($this->attrs[$index]);
467         continue;
468       }
470       /* Remove single attributes that do not differ */
471       if (!is_array($this->attrs[$index]) &&
472           isset($this->saved_attributes[$index]) &&
473           !is_array($this->saved_attributes[$index]) &&
474           $this->attrs[$index] == $this->saved_attributes[$index]){
476         unset ($this->attrs[$index]);
477         continue;
478       }
480       /* Remove arrays that do not differ */
481       if (is_array($this->attrs[$index]) && 
482           isset($this->saved_attributes[$index]) &&
483           is_array($this->saved_attributes[$index])){
484           
485         if (!array_differs($this->attrs[$index],$this->saved_attributes[$index])){
486           unset ($this->attrs[$index]);
487           continue;
488         }
489       }
490     }
492     /* Update saved attributes and ensure that next cleanups will be successful too */
493     foreach($this->attrs as $name => $value){
494       $this->saved_attributes[$name] = $value;
495     }
496   }
498   /*! \brief Check formular input */
499   function check()
500   {
501     $message= array();
503     /* Skip if we've no config object */
504     if (!isset($this->config) || !is_object($this->config)){
505       return $message;
506     }
508     /* Find hooks entries for this class */
509     $command = $this->config->configRegistry->getPropertyValue(get_class($this),"check");
510     if ($command != ""){
512       if (!check_command($command)){
513         $message[]= msgPool::cmdnotfound("CHECK", get_class($this));
514       } else {
516         /* Generate "ldif" for check hook */
517         $ldif= "dn: $this->dn\n";
518         
519         /* ... objectClasses */
520         foreach ($this->objectclasses as $oc){
521           $ldif.= "objectClass: $oc\n";
522         }
523         
524         /* ... attributes */
525         foreach ($this->attributes as $attr){
526           if ($this->$attr == ""){
527             continue;
528           }
529           if (is_array($this->$attr)){
530             foreach ($this->$attr as $val){
531               $ldif.= "$attr: $val\n";
532             }
533           } else {
534               $ldif.= "$attr: ".$this->$attr."\n";
535           }
536         }
538         /* Append empty line */
539         $ldif.= "\n";
541         /* Feed "ldif" into hook and retrieve result*/
542         $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
543         $fh= proc_open($command, $descriptorspec, $pipes);
544         if (is_resource($fh)) {
545           fwrite ($pipes[0], $ldif);
546           fclose($pipes[0]);
547           
548           $result= stream_get_contents($pipes[1]);
549           if ($result != ""){
550             $message[]= $result;
551           }
552           
553           fclose($pipes[1]);
554           fclose($pipes[2]);
555           proc_close($fh);
556         }
557       }
559     }
561     /* Check entryCSN */
562     if($this->CSN_check_active){
563       $current_csn = getEntryCSN($this->dn);
564       if($current_csn != $this->entryCSN && !empty($this->entryCSN) && !empty($current_csn)){
565         $this->entryCSN = $current_csn;
566         $message[] = _("The current object has been altered while beeing edited. If you save this entry, changes that have been made by others will be discarded!");
567       }
568     }
569     return ($message);
570   }
572   /* Adapt from template, using 'dn' */
573   function adapt_from_template($dn, $skip= array())
574   {
575     /* Include global link_info */
576     $ldap= $this->config->get_ldap_link();
578     /* Load requested 'dn' to 'attrs' */
579     $ldap->cat ($dn);
580     $this->attrs= $ldap->fetch();
582     /* Walk through attributes */
583     foreach ($this->attributes as $val){
585       /* Skip the ones in skip list */
586       if (in_array($val, $skip)){
587         continue;
588       }
590       if (isset($this->attrs["$val"][0])){
592         /* If attribute is set, replace dynamic parts: 
593            %sn, %givenName and %uid. Fill these in our local variables. */
594         $value= $this->attrs["$val"][0];
596         foreach (array("sn", "givenName", "uid") as $repl){
597           if (preg_match("/%$repl/i", $value)){
598             $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
599           }
600         }
601         $this->$val= $value;
602       }
603     }
605     /* Is Account? */
606     $found= TRUE;
607     foreach ($this->objectclasses as $obj){
608       if (preg_match('/top/i', $obj)){
609         continue;
610       }
611       if (!in_array_ics ($obj, $this->attrs['objectClass'])){
612         $found= FALSE;
613         break;
614       }
615     }
616     if ($found){
617       $this->is_account= TRUE;
618     }
619   }
621   /* \brief Indicate whether a password change is needed or not */
622   function password_change_needed()
623   {
624     return FALSE;
625   }
628   /*! \brief Show header message for tab dialogs */
629   function show_enable_header($button_text, $text, $disabled= FALSE)
630   {
631     if (($disabled == TRUE) || (!$this->acl_is_createable())){
632       $state= "disabled";
633     } else {
634       $state= "";
635     }
636     $display = "<div class='plugin-enable-header'>\n";
637     $display.= "<p>$text</p>\n";
638     $display.= "<button type='submit' name=\"modify_state\" ".$state.">$button_text</button>\n";
639     $display.= "</div>\n";
641     return($display);
642   }
645   /*! \brief Show header message for tab dialogs */
646   function show_disable_header($button_text, $text, $disabled= FALSE)
647   {
648     if (($disabled == TRUE) || !$this->acl_is_removeable()){
649       $state= "disabled";
650     } else {
651       $state= "";
652     }
653     $display = "<div class='plugin-disable-header'>\n";
654     $display.= "<p>$text</p>\n";
655     $display.= "<button type='submit' name=\"modify_state\" ".$state.">$button_text</button>\n";
656     $display.= "</div>\n";
657     return($display);
658   }
662   /* Create unique DN */
663   function create_unique_dn2($data, $base)
664   {
665     $ldap= $this->config->get_ldap_link();
666     $base= preg_replace("/^,*/", "", $base);
668     /* Try to use plain entry first */
669     $dn= "$data,$base";
670     $attribute= preg_replace('/=.*$/', '', $data);
671     $ldap->cat ($dn, array('dn'));
672     if (!$ldap->fetch()){
673       return ($dn);
674     }
676     /* Look for additional attributes */
677     foreach ($this->attributes as $attr){
678       if ($attr == $attribute || $this->$attr == ""){
679         continue;
680       }
682       $dn= "$data+$attr=".$this->$attr.",$base";
683       $ldap->cat ($dn, array('dn'));
684       if (!$ldap->fetch()){
685         return ($dn);
686       }
687     }
689     /* None found */
690     return ("none");
691   }
694   /*! \brief Create unique DN */
695   function create_unique_dn($attribute, $base)
696   {
697     $ldap= $this->config->get_ldap_link();
698     $base= preg_replace("/^,*/", "", $base);
700     /* Try to use plain entry first */
701     $dn= "$attribute=".$this->$attribute.",$base";
702     $ldap->cat ($dn, array('dn'));
703     if (!$ldap->fetch()){
704       return ($dn);
705     }
707     /* Look for additional attributes */
708     foreach ($this->attributes as $attr){
709       if ($attr == $attribute || $this->$attr == ""){
710         continue;
711       }
713       $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
714       $ldap->cat ($dn, array('dn'));
715       if (!$ldap->fetch()){
716         return ($dn);
717       }
718     }
720     /* None found */
721     return ("none");
722   }
725   function rebind($ldap, $referral)
726   {
727     $credentials= LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
728     if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
729       $this->error = "Success";
730       $this->hascon=true;
731       $this->reconnect= true;
732       return (0);
733     } else {
734       $this->error = "Could not bind to " . $credentials['ADMIN'];
735       return NULL;
736     }
737   }
740   /* Recursively copy ldap object */
741   function _copy($src_dn,$dst_dn)
742   {
743     $ldap=$this->config->get_ldap_link();
744     $ldap->cat($src_dn);
745     $attrs= $ldap->fetch();
747     /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
748     $ds= ldap_connect($this->config->current['SERVER']);
749     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
750     if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
751       ldap_set_rebind_proc($ds, array(&$this, "rebind"));
752     }
754     $pwd = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
755     $r=ldap_bind($ds,$this->config->current['ADMINDN'], $pwd);
756     $sr=ldap_read($ds, LDAP::fix($src_dn), "objectClass=*");
758     /* Fill data from LDAP */
759     $new= array();
760     if ($sr) {
761       $ei=ldap_first_entry($ds, $sr);
762       if ($ei) {
763         foreach($attrs as $attr => $val){
764           if ($info = @ldap_get_values_len($ds, $ei, $attr)){
765             for ($i= 0; $i<$info['count']; $i++){
766               if ($info['count'] == 1){
767                 $new[$attr]= $info[$i];
768               } else {
769                 $new[$attr][]= $info[$i];
770               }
771             }
772           }
773         }
774       }
775     }
777     /* close conncetion */
778     ldap_unbind($ds);
780     /* Adapt naming attribute */
781     $dst_name= preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
782     $dst_val = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
783     $new[$dst_name]= LDAP::fix($dst_val);
785     /* Check if this is a department.
786      * If it is a dep. && there is a , override in his ou 
787      *  change \2C to , again, else this entry can't be saved ...
788      */
789     if((isset($new['ou'])) &&( preg_match("/\\,/",$new['ou']))){
790       $new['ou'] = str_replace("\\\\,",",",$new['ou']);
791     }
793     /* Save copy */
794     $ldap->connect();
795     $ldap->cd($this->config->current['BASE']);
796     
797     $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
799     /* FAIvariable=.../..., cn=.. 
800         could not be saved, because the attribute FAIvariable was different to 
801         the dn FAIvariable=..., cn=... */
803     if(!is_array($new['objectClass'])) $new['objectClass'] = array($new['objectClass']);
805     if(in_array_ics("FAIdebconfInfo",$new['objectClass'])){
806       $new['FAIvariable'] = $ldap->fix($new['FAIvariable']);
807     }
808     $ldap->cd($dst_dn);
809     $ldap->add($new);
811     if (!$ldap->success()){
812       trigger_error("Trying to save $dst_dn failed.",
813           E_USER_WARNING);
814       return(FALSE);
815     }
816     return(TRUE);
817   }
820   /* This is a workaround function. */
821   function copy($src_dn, $dst_dn)
822   {
823     /* Rename dn in possible object groups */
824     $ldap= $this->config->get_ldap_link();
825     $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.@LDAP::prepare4filter($src_dn).'))',
826         array('cn'));
827     while ($attrs= $ldap->fetch()){
828       $og= new ogroup($this->config, $ldap->getDN());
829       unset($og->member[$src_dn]);
830       $og->member[$dst_dn]= $dst_dn;
831       $og->save ();
832     }
834     $ldap->cat($dst_dn);
835     $attrs= $ldap->fetch();
836     if (count($attrs)){
837       trigger_error("Trying to overwrite ".LDAP::fix($dst_dn).", which already exists.",
838           E_USER_WARNING);
839       return (FALSE);
840     }
842     $ldap->cat($src_dn);
843     $attrs= $ldap->fetch();
844     if (!count($attrs)){
845       trigger_error("Trying to move ".LDAP::fix($src_dn).", which does not seem to exist.",
846           E_USER_WARNING);
847       return (FALSE);
848     }
850     $ldap->cd($src_dn);
851     $ldap->search("objectClass=*",array("dn"));
852     while($attrs = $ldap->fetch()){
853       $src = $attrs['dn'];
854       $dst = preg_replace("/".preg_quote($src_dn, '/')."$/",$dst_dn,$attrs['dn']);
855       $this->_copy($src,$dst);
856     }
857     return (TRUE);
858   }
862   /*! \brief  Rename/Move a given src_dn to the given dest_dn
863    *
864    * Move a given ldap object indentified by $src_dn to the
865    * given destination $dst_dn
866    *
867    * - Ensure that all references are updated (ogroups)
868    * - Update ACLs   
869    * - Update accessTo
870    *
871    * \param  string  'src_dn' the source DN.
872    * \param  string  'dst_dn' the destination DN.
873    * \return boolean TRUE on success else FALSE.
874    */
875   function rename($src_dn, $dst_dn)
876   {
877     $start = microtime(1);
879     /* Try to move the source entry to the destination position */
880     $ldap = $this->config->get_ldap_link();
881     $ldap->cd($this->config->current['BASE']);
882     $ldap->create_missing_trees(preg_replace("/^[^,]+,/","",$dst_dn));
883     if (!$ldap->rename_dn($src_dn,$dst_dn)){
884       new log("debug","LDAP protocol v3 implementation error, ldap_rename failed, falling back to manual copy.","FROM: $src_dn  -- TO: $dst_dn",array(),$ldap->get_error());
885       @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,"Rename failed FROM: $src_dn  -- TO:  $dst_dn", 
886           "Ldap Protocol v3 implementation error, falling back to maunal method.");
887       return(FALSE);
888     }
890     /* Get list of users,groups and roles within this tree,
891         maybe we have to update ACL references.
892      */
893     $leaf_objs = get_list("(|(objectClass=posixGroup)(objectClass=gosaAccount)(objectClass=gosaRole))",array("all"),$dst_dn,
894           array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
895     foreach($leaf_objs as $obj){
896       $new_dn = $obj['dn'];
897       $old_dn = preg_replace("/".preg_quote(LDAP::convert($dst_dn), '/')."$/i",$src_dn,LDAP::convert($new_dn));
898       $this->update_acls($old_dn,$new_dn); 
899     }
901     // Migrate objectgroups if needed
902     $ogroups = get_sub_list("(&(objectClass=gosaGroupOfNames)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))",
903       "ogroups", array(get_ou("group", "ogroupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
905     // Walk through all objectGroups
906     foreach($ogroups as $ogroup){
907       // Migrate old to new dn
908       $o_ogroup= new ogroup($this->config,$ogroup['dn']);
909       if (isset($o_ogroup->member[$src_dn])) {
910         unset($o_ogroup->member[$src_dn]);
911       }
912       $o_ogroup->member[$dst_dn]= $dst_dn;
913       
914       // Save object group
915       $o_ogroup->save();
916     }
918     // Migrate rfc groups if needed
919     $groups = get_sub_list("(&(objectClass=posixGroup)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))","groups", array(get_ou("core", "groupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
921     // Walk through all POSIX groups
922     foreach($groups as $group){
924       // Migrate old to new dn
925       $o_group= new group($this->config,$group['dn']);
926       $o_group->save();
927     }
929     /* Update roles to use the new entry dn */
930     $roles = get_sub_list("(&(objectClass=organizationalRole)(roleOccupant=".LDAP::prepare4filter(LDAP::fix($src_dn))."))","roles", array(get_ou("roleGeneric", "roleRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
932     // Walk through all roles
933     foreach($roles as $role){
934       $role = new roleGeneric($this->config,$role['dn']);
935       $key= array_search($src_dn, $role->roleOccupant);      
936       if($key !== FALSE){
937         $role->roleOccupant[$key] = $dst_dn;
938         $role->save();
939       }
940     }
942     // Update 'manager' attributes from gosaDepartment and inetOrgPerson 
943     $filter = "(&(objectClass=inetOrgPerson)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn))."))";
944     $ocs = $ldap->get_objectclasses();
945     if(isset($ocs['gosaDepartment']['MAY']) && in_array('manager', $ocs['gosaDepartment']['MAY'])){
946       $filter = "(|".$filter."(&(objectClass=gosaDepartment)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn)).")))";
947     }
948     $leaf_deps=  get_list($filter,array("all"),$this->config->current['BASE'], 
949         array("manager","dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
950     foreach($leaf_deps as $entry){
951       $update = array('manager' => $dst_dn);
952       $ldap->cd($entry['dn']);
953       $ldap->modify($update);
954       if(!$ldap->success()){
955         trigger_error(sprintf("Failed to update manager for %s: %s", bold($entry['dn']), $ldap->get_error()));
956       }
957     }
959     // Migrate 'dyn-groups' here. labeledURIObject
960     if(class_available('DynamicLdapGroup')) {
961         DynamicLdapGroup::moveDynGroup($this->config,$src_dn,$dst_dn);
962     }
963  
964     /* Check if there are gosa departments moved. 
965        If there were deps moved, the force reload of config->deps.
966      */
967     $leaf_deps=  get_list("(objectClass=gosaDepartment)",array("all"),$dst_dn,
968           array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
969   
970     if(count($leaf_deps)){
971       $this->config->get_departments();
972       $this->config->make_idepartments();
973       session::global_set("config",$this->config);
974       $ui =get_userinfo();
975       $ui->reset_acl_cache();
976     }
978     return(TRUE); 
979   }
982  
983   function move($src_dn, $dst_dn)
984   {
985     /* Do not copy if only upper- lowercase has changed */
986     if(strtolower($src_dn) == strtolower($dst_dn)){
987       return(TRUE);
988     }
990     stats::log('plugin', $class = get_class($this), $action = 'move', $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
991     
992     /* Try to move the entry instead of copy & delete
993      */
994     if(TRUE){
996       /* Try to move with ldap routines, if this was not successfull
997           fall back to the old style copy & remove method 
998        */
999       if($this->rename($src_dn, $dst_dn)){
1000         return(TRUE);
1001       }else{
1002         // See code below.
1003       }
1004     }
1006     /* Copy source to destination */
1007     if (!$this->copy($src_dn, $dst_dn)){
1008       return (FALSE);
1009     }
1011     /* Delete source */
1012     $ldap= $this->config->get_ldap_link();
1013     $ldap->rmdir_recursive($src_dn);
1014     if (!$ldap->success()){
1015       trigger_error("Trying to delete $src_dn failed.",
1016           E_USER_WARNING);
1017       return (FALSE);
1018     }
1020     return (TRUE);
1021   }
1024   /* \brief Move/Rename complete trees */
1025   function recursive_move($src_dn, $dst_dn)
1026   {
1027     /* Check if the destination entry exists */
1028     $ldap= $this->config->get_ldap_link();
1030     /* Check if destination exists - abort */
1031     $ldap->cat($dst_dn, array('dn'));
1032     if ($ldap->fetch()){
1033       trigger_error("recursive_move $dst_dn already exists.",
1034           E_USER_WARNING);
1035       return (FALSE);
1036     }
1038     $this->copy($src_dn, $dst_dn);
1040     /* Remove src_dn */
1041     $ldap->cd($src_dn);
1042     $ldap->recursive_remove($src_dn);
1043     return (TRUE);
1044   }
1047   function saveCopyDialog(){
1048   }
1051   function getCopyDialog(){
1052     return(array("string"=>"","status"=>""));
1053   }
1056   /*! \brief Prepare for Copy & Paste */
1057   function PrepareForCopyPaste($source)
1058   {
1059     $todo = $this->attributes;
1060     if(isset($this->CopyPasteVars)){
1061       $todo = array_merge($todo,$this->CopyPasteVars);
1062     }
1064     if(count($this->objectclasses)){
1065       $this->is_account = TRUE;
1066       foreach($this->objectclasses as $class){
1067         if(!in_array($class,$source['objectClass'])){
1068           $this->is_account = FALSE;
1069         }
1070       }
1071     }
1073     foreach($todo as $var){
1074       if (isset($source[$var])){
1075         if(isset($source[$var]['count'])){
1076           if($source[$var]['count'] > 1){
1077             $tmp= $source[$var];
1078             unset($tmp['count']);
1079             $this->$var = $tmp;
1080           }else{
1081             $this->$var = $source[$var][0];
1082           }
1083         }else{
1084           $this->$var= $source[$var];
1085         }
1086       }
1087     }
1088   }
1090   /*! \brief Get gosaUnitTag for the given DN
1091        If this is called from departmentGeneric, we have to skip this
1092         tagging procedure. 
1093     */
1094   function tag_attrs(&$at, $dn= "", $tag= "", $show= false)
1095   {
1096     /* Skip tagging? */
1097     if($this->skipTagging){
1098       return;
1099     }
1101     /* No dn? Self-operation... */
1102     if ($dn == ""){
1103       $dn= $this->dn;
1105       /* No tag? Find it yourself... */
1106       if ($tag == ""){
1107         $len= strlen($dn);
1109         @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "No tag for $dn - looking for one...", "Tagging");
1110         $relevant= array();
1111         foreach ($this->config->adepartments as $key => $ntag){
1113           /* This one is bigger than our dn, its not relevant... */
1114           if ($len < strlen($key)){
1115             continue;
1116           }
1118           /* This one matches with the latter part. Break and don't fix this entry */
1119           if (preg_match('/(^|,)'.preg_quote($key, '/').'$/', $dn)){
1120             @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "DEBUG: Possibly relevant: $key", "Tagging");
1121             $relevant[strlen($key)]= $ntag;
1122             continue;
1123           }
1125         }
1127         /* If we've some relevant tags to set, just get the longest one */
1128         if (count($relevant)){
1129           ksort($relevant);
1130           $tmp= array_keys($relevant);
1131           $idx= end($tmp);
1132           $tag= $relevant[$idx];
1133           $this->gosaUnitTag= $tag;
1134         }
1135       }
1136     }
1138   /*! \brief Add unit tag */ 
1139     /* Remove tags that may already be here... */
1140     remove_objectClass("gosaAdministrativeUnitTag", $at);
1141     if (isset($at['gosaUnitTag'])){
1142         unset($at['gosaUnitTag']);
1143     }
1145     /* Set tag? */
1146     if ($tag != ""){
1147       add_objectClass("gosaAdministrativeUnitTag", $at);
1148       $at['gosaUnitTag']= $tag;
1149     }
1151     /* Initially this object was tagged. 
1152        - But now, it is no longer inside a tagged department. 
1153        So force the remove of the tag.
1154        (objectClass was already removed obove)
1155      */
1156     if($tag == "" && $this->gosaUnitTag){
1157       $at['gosaUnitTag'] = array();
1158     }
1159   }
1162   /*! \brief Test for removability of the object
1163    *
1164    * Allows testing of conditions for removal of object. If removal should be aborted
1165    * the function needs to remove an error message.
1166    * */
1167   function allow_remove()
1168   {
1169     $reason= "";
1170     return $reason;
1171   }
1174   /*! \brief Test if snapshotting is enabled
1175    *
1176    * Test weither snapshotting is enabled or not. There will also be some errors posted,
1177    * if the configuration failed 
1178    * \return TRUE if snapshots are enabled, and FALSE if it is disabled
1179    */
1180   function snapshotEnabled()
1181   {
1182       return $this->config->snapshotEnabled();
1183   }
1186   /*! \brief Return plugin informations for acl handling 
1187    *         See class_core.inc for examples.
1188    */
1189   static function plInfo()
1190   {
1191     return array();
1192   }
1195   function set_acl_base($base)
1196   {
1197     @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,"<b>".$base."</b>","<b>ACL-Base:</b> ");
1198     $this->acl_base= $base;
1199   }
1202   function set_acl_category($category)
1203   {
1204     @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,"<b>".$category."</b>(/".get_class($this).")","<b>ACL-Category:</b> ");
1205     $this->acl_category= "$category/";
1206   }
1209   function acl_is_writeable($attribute,$skip_write = FALSE)
1210   {
1211     if($this->read_only) return(FALSE);
1212     $ui= get_userinfo();
1213     return preg_match('/w/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute, $skip_write));
1214   }
1217   function acl_is_readable($attribute)
1218   {
1219     $ui= get_userinfo();
1220     return preg_match('/r/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute));
1221   }
1224   function acl_is_createable($base ="")
1225   {
1226     if($this->read_only) return(FALSE);
1227     $ui= get_userinfo();
1228     if($base == "") $base = $this->acl_base;
1229     return preg_match('/c/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1230   }
1233   function acl_is_removeable($base ="")
1234   {
1235     if($this->read_only) return(FALSE);
1236     $ui= get_userinfo();
1237     if($base == "") $base = $this->acl_base;
1238     return preg_match('/d/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1239   }
1242   function acl_is_moveable($base = "")
1243   {
1244     if($this->read_only) return(FALSE);
1245     $ui= get_userinfo();
1246     if($base == "") $base = $this->acl_base;
1247     return preg_match('/m/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1248   }
1251   function acl_have_any_permissions()
1252   {
1253   }
1256   function getacl($attribute,$skip_write= FALSE)
1257   {
1258     $ui= get_userinfo();
1259     $skip_write |= $this->read_only;
1260     return  $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute,$skip_write);
1261   }
1264   /*! \brief Returns a list of all available departments for this object.
1265    * 
1266    * If this object is new, all departments we are allowed to create a new user in
1267    * are returned. If this is an existing object, return all deps. 
1268    * We are allowed to move tis object too.
1269    * \return array [dn] => "..name"  // All deps. we are allowed to act on.
1270   */
1271   function get_allowed_bases()
1272   {
1273     $ui = get_userinfo();
1274     $deps = array();
1276     /* Is this a new object ? Or just an edited existing object */
1277     if(!$this->initially_was_account && $this->is_account){
1278       $new = true;
1279     }else{
1280       $new = false;
1281     }
1283     foreach($this->config->idepartments as $dn => $name){
1284       if($new && $this->acl_is_createable($dn)){
1285         $deps[$dn] = $name;
1286       }elseif(!$new && $this->acl_is_moveable($dn)){
1287         $deps[$dn] = $name;
1288       }
1289     }
1291     /* Add current base */      
1292     if(isset($this->base) && isset($this->config->idepartments[$this->base])){
1293       $deps[$this->base] = $this->config->idepartments[$this->base];
1294     }elseif(strtolower($this->dn) == strtolower($this->config->current['BASE'])){
1296     }else{
1297       trigger_error("Cannot return list of departments, no default base found in class ".get_class($this).". ".$this->base);
1298     }
1299     return($deps);
1300   }
1303   /* This function updates ACL settings if $old_dn was used.
1304    *  \param string 'old_dn' specifies the actually used dn
1305    *  \param string 'new_dn' specifies the destiantion dn
1306    */
1307   function update_acls($old_dn,$new_dn,$output_changes = FALSE)
1308   {
1309     /* Check if old_dn is empty. This should never happen */
1310     if(empty($old_dn) || empty($new_dn)){
1311       trigger_error("Failed to check acl dependencies, wrong dn given.");
1312       return;
1313     }
1315     /* Update userinfo if necessary */
1316     $ui = session::global_get('ui');
1317     if($ui->dn == $old_dn){
1318       $ui->dn = $new_dn;
1319       session::global_set('ui',$ui);
1320       new log("view","acl/".get_class($this),$this->dn,array(),"Updated current object dn from '".$old_dn."' to '".$new_dn."'");
1321     }
1323     /* Object was moved, ensure that all acls will be moved too */
1324     if($new_dn != $old_dn && $old_dn != "new"){
1326       /* get_ldap configuration */
1327       $update = array();
1328       $ldap = $this->config->get_ldap_link();
1329       $ldap->cd ($this->config->current['BASE']);
1330       $ldap->search("(&(objectClass=gosaAcl)(gosaAclEntry=*".base64_encode($old_dn)."*))",array("cn","gosaAclEntry"));
1331       while($attrs = $ldap->fetch()){
1332         $acls = array();
1333         $found = false;
1334         for($i = 0 ; $i <  $attrs['gosaAclEntry']['count'] ; $i ++ ){
1335           $acl_parts = explode(":",$attrs['gosaAclEntry'][$i]);
1337           /* Roles uses antoher data storage order, members are stored int the third part, 
1338              while the members in direct ACL assignments are stored in the second part.
1339            */
1340           $id = ($acl_parts[1] == "role") ? 3 : 2;
1342           /* Update member entries to use $new_dn instead of old_dn
1343            */
1344           $members = explode(",",$acl_parts[$id]);
1345           foreach($members as $key => $member){
1346             $member = base64_decode($member);
1347             if($member == $old_dn){
1348               $members[$key] = base64_encode($new_dn);
1349               $found = TRUE;
1350             }
1351           } 
1353           /* Check if the selected role has to updated
1354            */
1355           if($acl_parts[1] == "role" && $acl_parts[2] == base64_encode($old_dn)){
1356             $acl_parts[2] = base64_encode($new_dn);
1357             $found = TRUE;
1358           }
1360           /* Build new acl string */ 
1361           $acl_parts[$id] = implode($members,",");
1362           $acls[] = implode($acl_parts,":");
1363         }
1365         /* Acls for this object must be adjusted */
1366         if($found){
1368           $debug_info= sprintf(_("Changing ACL DN from %s to %s"), bold($old_dn), bold($new_dn));
1369           @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,$debug_info,"ACL");
1371           $update[$attrs['dn']] =array();
1372           foreach($acls as $acl){
1373             $update[$attrs['dn']]['gosaAclEntry'][] = $acl;
1374           }
1375         }
1376       }
1378       /* Write updated acls */
1379       foreach($update as $dn => $attrs){
1380         $ldap->cd($dn);
1381         $ldap->modify($attrs);
1382       }
1383     }
1384   }
1386   
1388   /*! \brief Enable the Serial ID check
1389    *
1390    * This function enables the entry Serial ID check.  If an entry was edited while
1391    * we have edited the entry too, an error message will be shown. 
1392    * To configure this check correctly read the FAQ.
1393    */    
1394   function enable_CSN_check()
1395   {
1396     $this->CSN_check_active =TRUE;
1397     $this->entryCSN = getEntryCSN($this->dn);
1398   }
1401   /*! \brief  Prepares the plugin to be used for multiple edit
1402    *          Update plugin attributes with given array of attribtues.
1403    *  \param  array   Array with attributes that must be updated.
1404    */
1405   function init_multiple_support($attrs,$all)
1406   {
1407     $ldap= $this->config->get_ldap_link();
1408     $this->multi_attrs    = $attrs;
1409     $this->multi_attrs_all= $all;
1411     /* Copy needed attributes */
1412     foreach ($this->attributes as $val){
1413       $found= array_key_ics($val, $this->multi_attrs);
1414  
1415       if ($found != ""){
1416         if(isset($this->multi_attrs["$val"][0])){
1417           $this->$val= $this->multi_attrs["$val"][0];
1418         }
1419       }
1420     }
1421   }
1423  
1424   /*! \brief  Enables multiple support for this plugin
1425    */
1426   function enable_multiple_support()
1427   {
1428     $this->ignore_account = TRUE;
1429     $this->multiple_support_active = TRUE;
1430   }
1433   /*! \brief  Returns all values that have been modfied in multiple edit mode.
1434       \return array Cotaining all modified values. 
1435    */
1436   function get_multi_edit_values()
1437   {
1438     $ret = array();
1439     foreach($this->attributes as $attr){
1440       if(in_array($attr,$this->multi_boxes)){
1441         $ret[$attr] = $this->$attr;
1442       }
1443     }
1444     return($ret);
1445   }
1447   
1448   /*! \brief  Update class variables with values collected by multiple edit.
1449    */
1450   function set_multi_edit_values($attrs)
1451   {
1452     foreach($attrs as $name => $value){
1453       $this->$name = $value;
1454     }
1455   }
1458   /*! \brief Generates the html output for this node for multi edit*/
1459   function multiple_execute()
1460   {
1461     /* This one is empty currently. Fabian - please fill in the docu code */
1462     session::global_set('current_class_for_help',get_class($this));
1464     /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
1465     session::set('LOCK_VARS_TO_USE',array());
1466     session::set('LOCK_VARS_USED_GET',array());
1467     session::set('LOCK_VARS_USED_POST',array());
1468     session::set('LOCK_VARS_USED_REQUEST',array());
1469     
1470     return("Multiple edit is currently not implemented for this plugin.");
1471   }
1474   /*! \brief Save HTML posted data to object for multiple edit
1475    */
1476   function multiple_save_object()
1477   {
1478     if(empty($this->entryCSN) && $this->CSN_check_active){
1479       $this->entryCSN = getEntryCSN($this->dn);
1480     }
1482     /* Save values to object */
1483     $this->multi_boxes = array();
1484     foreach ($this->attributes as $val){
1485   
1486       /* Get selected checkboxes from multiple edit */
1487       if(isset($_POST["use_".$val])){
1488         $this->multi_boxes[] = $val;
1489       }
1491       if (isset ($_POST["$val"]) && $this->acl_is_writeable($val)){
1493         $data= $this->$val = get_post($val);
1494         if ($this->$val != $data){
1495           $this->is_modified= TRUE;
1496         }
1497     
1498         /* IE post fix */
1499         if(isset($data[0]) && $data[0] == chr(194)) {
1500           $data = "";  
1501         }
1502         $this->$val= $data;
1503       }
1504     }
1505   }
1508   /*! \brief Returns all attributes of this plugin, 
1509                to be able to detect multiple used attributes 
1510                in multi_plugg::detect_multiple_used_attributes().
1511       @return array Attributes required for intialization of multi_plug
1512    */
1513   public function get_multi_init_values()
1514   {
1515     $attrs = $this->attrs;
1516     return($attrs);
1517   }
1520   /*! \brief  Check given values in multiple edit
1521       \return array Error messages
1522    */
1523   function multiple_check()
1524   {
1525     $message = plugin::check();
1526     return($message);
1527   }
1529   function get_used_snapshot_bases()
1530   {
1531      return(array());
1532   }
1534   function is_modal_dialog()
1535   {
1536     return(isset($this->dialog) && $this->dialog);
1537   }
1540   /*! \brief    Forward command execution requests
1541    *             to the hook execution method. 
1542    */
1543   function handle_post_events($mode, $addAttrs= array())
1544   {
1545     if(!in_array($mode, array('add','remove','modify'))){
1546       trigger_error(sprintf("Invalid post event type given %s! Valid types are [add,modify,remove].", bold($mode)));
1547       return;
1548     }
1549     switch ($mode){
1550       case "add":
1551         plugin::callHook($this,"POSTCREATE", $addAttrs);
1552       break;
1554       case "modify":
1555         plugin::callHook($this,"POSTMODIFY", $addAttrs);
1556       break;
1558       case "remove":
1559         plugin::callHook($this,"POSTREMOVE", $addAttrs);
1560       break;
1561     }
1562   }
1565   /*! \brief    Calls external hooks which are defined for this plugin (gosa.conf)
1566    *            Replaces placeholder by class values of this plugin instance.
1567    *  @param    Allows to a add special replacements.
1568    */
1569   static function callHook($plugin, $cmd, $addAttrs= array(), &$returnOutput = array(), &$returnCode = NULL)
1570   {
1571     global $config;
1572     $command = $config->configRegistry->getPropertyValue(get_class($plugin),$cmd);
1573    
1574     if ($command != ""){
1576       // Walk trough attributes list and add the plugins attributes. 
1577       foreach ($plugin->attributes as $attr){
1578         if (!is_array($plugin->$attr)){
1579           $addAttrs[$attr] = $plugin->$attr;
1580         }
1581       }
1582       $ui = get_userinfo();
1583       $addAttrs['callerDN']=$ui->dn;
1584       $addAttrs['dn']=$plugin->dn;
1585       $addAttrs['location']=$config->current['NAME'];
1587       // Sort attributes by length, ensures correct replacement
1588       $tmp = array();
1589       foreach($addAttrs as $name => $value){
1590         $tmp[$name] =  strlen($name);
1591       }
1592       arsort($tmp);
1594       // Now replace the placeholder 
1595       foreach ($tmp as $name => $len){
1596         $value = $addAttrs[$name];
1597         $command= str_replace("%$name", "$value", $command);
1598       }
1600       // If there are still some %.. in our command, try to fill these with some other class vars 
1601       if(preg_match("/%/",$command)){
1602         $attrs = get_object_vars($plugin);
1603         foreach($attrs as $name => $value){
1604           if(is_array($value)){
1605             $s = "";
1606             foreach($value as $val){
1607               if(is_string($val) || is_int($val) || is_float($val) || is_bool($val)){
1608                 $s .= '"'.$val.'",'; 
1609               }
1610             }
1611             $value = '['.trim($s,',').']';
1612           }
1613           if(!is_string($value) && !is_int($value) && !is_float($value) && !is_bool($value)){
1614             continue;
1615           }
1616           $command= preg_replace("/%$name/", $value, $command);
1617         }
1618       }
1620       if (check_command($command)){
1622         @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,$command,"Execute");
1623         exec($command, $arr, $returnCode);
1624         $returnOutput = $arr;
1626         if($returnCode != 0){
1627           $str = implode("\n",$arr);
1628           @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execution failed code: ".$returnCode);
1629           $message= msgPool::cmdexecfailed($cmd,$command, get_class($plugin));
1630           msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
1631         }elseif(is_array($arr)){
1632           $str = implode("\n",$arr);
1633           @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Result: ".$str);
1634         }
1635       } else {
1636         $message= msgPool::cmdinvalid($cmd,$command, get_class($plugin));
1637         msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
1638       }
1639     }
1640   }
1643 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1644 ?>