Code

Updated post event handling
[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   /*!
36     \brief Reference to parent object
38     This variable is used when the plugin is included in tabs
39     and keeps reference to the tab class. Communication to other
40     tabs is possible by 'name'. So the 'fax' plugin can ask the
41     'userinfo' plugin for the fax number.
43     \sa tab
44    */
45   var $parent= NULL;
47   /*!
48     \brief Configuration container
50     Access to global configuration
51    */
52   var $config= NULL;
54   /*!
55     \brief Mark plugin as account
57     Defines whether this plugin is defined as an account or not.
58     This has consequences for the plugin to be saved from tab
59     mode. If it is set to 'FALSE' the tab will call the delete
60     function, else the save function. Should be set to 'TRUE' if
61     the construtor detects a valid LDAP object.
63     \sa plugin::plugin()
64    */
65   var $is_account= FALSE;
66   var $initially_was_account= FALSE;
68   /*!
69     \brief Mark plugin as template
71     Defines whether we are creating a template or a normal object.
72     Has conseqences on the way execute() shows the formular and how
73     save() puts the data to LDAP.
75     \sa plugin::save() plugin::execute()
76    */
77   var $is_template= FALSE;
78   var $ignore_account= FALSE;
79   var $is_modified= FALSE;
81   /*!
82     \brief Represent temporary LDAP data
84     This is only used internally.
85    */
86   var $attrs= array();
88   /* Keep set of conflicting plugins */
89   var $conflicts= array();
91   /* Save unit tags */
92   var $gosaUnitTag= "";
93   var $skipTagging= FALSE;
95   /*!
96     \brief Used standard values
98     dn
99    */
100   var $dn= "";
101   var $uid= "";
102   var $sn= "";
103   var $givenName= "";
104   var $acl= "*none*";
105   var $dialog= FALSE;
106   var $snapDialog = NULL;
108   /* attribute list for save action */
109   var $attributes= array();
110   var $objectclasses= array();
111   var $is_new= TRUE;
112   var $saved_attributes= array();
114   var $acl_base= "";
115   var $acl_category= "";
116   var $read_only = FALSE; // Used when the entry is opened as "readonly" due to locks.
118   /* This can be set to render the tabulators in another stylesheet */
119   var $pl_notify= FALSE;
121   /* Object entry CSN */
122   var $entryCSN         = "";
123   var $CSN_check_active = FALSE;
125   /* This variable indicates that this class can handle multiple dns at once. */
126   var $multiple_support = FALSE;
127   var $multi_attrs      = array();
128   var $multi_attrs_all  = array(); 
130   /* This aviable indicates, that we are currently in multiple edit handle */
131   var $multiple_support_active = FALSE; 
132   var $selected_edit_values = array();
133   var $multi_boxes = array();
135   /*! \brief plugin constructor
137     If 'dn' is set, the node loads the given 'dn' from LDAP
139     \param dn Distinguished name to initialize plugin from
140     \sa plugin()
141    */
142   function plugin (&$config, $dn= NULL, $object= NULL)
143   {
144     /* Configuration is fine, allways */
145     $this->config= &$config;    
146     $this->dn= $dn;
148     // Ensure that we've a valid acl_category set.
149     if(empty($this->acl_category)){
150       $tmp = $this->plInfo();
151       if (isset($tmp['plCategory'])) {
152         $c = key($tmp['plCategory']);
153         if(is_numeric($c)){
154           $c = $tmp['plCategory'][0];
155         }
156         $this->acl_category = $c."/";
157       }
158     }
160     /* Handle new accounts, don't read information from LDAP */
161     if ($dn == "new"){
162       return;
163     }
165     /* Check if this entry was opened in read only mode */
166     if(isset($_POST['open_readonly'])){
167       if(session::global_is_set("LOCK_CACHE")){
168         $cache = &session::get("LOCK_CACHE");
169         if(isset($cache['READ_ONLY'][$this->dn])){
170           $this->read_only = TRUE;
171         }
172       }
173     }
175     /* Save current dn as acl_base */
176     $this->acl_base= $dn;
178     /* Get LDAP descriptor */
179     if ($dn !== NULL){
181       /* Load data to 'attrs' and save 'dn' */
182       if ($object !== NULL){
183         $this->attrs= $object->attrs;
184       } else {
185         $ldap= $this->config->get_ldap_link();
186         $ldap->cat ($dn);
187         $this->attrs= $ldap->fetch();
188       }
190       /* Copy needed attributes */
191       foreach ($this->attributes as $val){
192         $found= array_key_ics($val, $this->attrs);
193         if ($found != ""){
194           $this->$val= $found[0];
195         }
196       }
198       /* gosaUnitTag loading... */
199       if (isset($this->attrs['gosaUnitTag'][0])){
200         $this->gosaUnitTag= $this->attrs['gosaUnitTag'][0];
201       }
203       /* Set the template flag according to the existence of objectClass
204          gosaUserTemplate */
205       if (isset($this->attrs['objectClass'])){
206         if (in_array_ics ("gosaUserTemplate", $this->attrs['objectClass'])){
207           $this->is_template= TRUE;
208           @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
209               "found", "Template check");
210         }
211       }
213       /* Is Account? */
214       $found= TRUE;
215       foreach ($this->objectclasses as $obj){
216         if (preg_match('/top/i', $obj)){
217           continue;
218         }
219         if (!isset($this->attrs['objectClass']) || !in_array_ics ($obj, $this->attrs['objectClass'])){
220           $found= FALSE;
221           break;
222         }
223       }
224       if ($found){
225         $this->is_account= TRUE;
226         @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
227             "found", "Object check");
228       }
230       /* Prepare saved attributes */
231       $this->saved_attributes= $this->attrs;
232       foreach ($this->saved_attributes as $index => $value){
233         if (is_numeric($index)){
234           unset($this->saved_attributes[$index]);
235           continue;
236         }
238         if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)){
239           unset($this->saved_attributes[$index]);
240           continue;
241         }
243         if (isset($this->saved_attributes[$index][0])){
244           if(!isset($this->saved_attributes[$index]["count"])){
245             $this->saved_attributes[$index]["count"] = count($this->saved_attributes[$index]);
246           }
247           if($this->saved_attributes[$index]["count"] == 1){
248             $tmp= $this->saved_attributes[$index][0];
249             unset($this->saved_attributes[$index]);
250             $this->saved_attributes[$index]= $tmp;
251             continue;
252           }
253         }
254         unset($this->saved_attributes["$index"]["count"]);
255       }
257       if(isset($this->attrs['gosaUnitTag'])){
258         $this->saved_attributes['gosaUnitTag'] = $this->attrs['gosaUnitTag'][0];
259       }
260     }
262     /* Save initial account state */
263     $this->initially_was_account= $this->is_account;
264   }
267   /*! \brief Generates the html output for this node
268    */
269   function execute()
270   {
271     /* This one is empty currently. Fabian - please fill in the docu code */
272     session::global_set('current_class_for_help',get_class($this));
274     /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
275     session::set('LOCK_VARS_TO_USE',array());
276     session::set('LOCK_VARS_USED_GET',array());
277     session::set('LOCK_VARS_USED_POST',array());
278     session::set('LOCK_VARS_USED_REQUEST',array());
279   }
281   /*! \brief Removes object from parent
282    */
283   function remove_from_parent()
284   {
285     /* include global link_info */
286     $ldap= $this->config->get_ldap_link();
288     /* Get current objectClasses in order to add the required ones */
289     $ldap->cat($this->dn);
290     $tmp= $ldap->fetch ();
291     $oc= array();
292     if (isset($tmp['objectClass'])){
293       $oc= $tmp['objectClass'];
294       unset($oc['count']);
295     }
297     /* Remove objectClasses from entry */
298     $ldap->cd($this->dn);
299     $this->attrs= array();
300     $this->attrs['objectClass']= array_remove_entries_ics($this->objectclasses,$oc);
302     /* Unset attributes from entry */
303     foreach ($this->attributes as $val){
304       $this->attrs["$val"]= array();
305     }
307     /* Unset account info */
308     $this->is_account= FALSE;
310     /* Do not write in plugin base class, this must be done by
311        children, since there are normally additional attribs,
312        lists, etc. */
313     /*
314        $ldap->modify($this->attrs);
315      */
316   }
319   /*! \brief Save HTML posted data to object 
320    */
321   function save_object()
322   {
323     /* Update entry CSN if it is empty. */
324     if(empty($this->entryCSN) && $this->CSN_check_active){
325       $this->entryCSN = getEntryCSN($this->dn);
326     }
328     /* Save values to object */
329     foreach ($this->attributes as $val){
330       if ($this->acl_is_writeable($val) && isset ($_POST["$val"])){
331         /* Check for modifications */
332         if (get_magic_quotes_gpc()) {
333           $data= stripcslashes($_POST["$val"]);
334         } else {
335           $data= $this->$val = $_POST["$val"];
336         }
337         if ($this->$val != $data){
338           $this->is_modified= TRUE;
339         }
340     
341         /* Okay, how can I explain this fix ... 
342          * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds. 
343          * So IE posts these 'unselectable' option, with value = chr(194) 
344          * chr(194) seems to be the &nbsp; in between the ...option>&nbsp;</option.. because there is no value=".." specified in these option fields  
345          * This &nbsp; was added for W3c compliance, but now causes these ... ldap errors ... 
346          * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
347          */
348         if(isset($data[0]) && $data[0] == chr(194)) {
349           $data = "";  
350         }
351         $this->$val= $data;
352       }
353     }
354   }
357   /*! \brief Save data to LDAP, depending on is_account we save or delete */
358   function save()
359   {
360     /* include global link_info */
361     $ldap= $this->config->get_ldap_link();
363     /* Save all plugins */
364     $this->entryCSN = "";
366     /* Start with empty array */
367     $this->attrs= array();
369     /* Get current objectClasses in order to add the required ones */
370     $ldap->cat($this->dn);
371     
372     $tmp= $ldap->fetch ();
374     $oc= array();
375     if (isset($tmp['objectClass'])){
376       $oc= $tmp["objectClass"];
377       $this->is_new= FALSE;
378       unset($oc['count']);
379     } else {
380       $this->is_new= TRUE;
381     }
383     /* Load (minimum) attributes, add missing ones */
384     $this->attrs['objectClass']= gosa_array_merge($oc,$this->objectclasses);
386     /* Copy standard attributes */
387     foreach ($this->attributes as $val){
388       if ($this->$val != ""){
389         $this->attrs["$val"]= $this->$val;
390       } elseif (!$this->is_new) {
391         $this->attrs["$val"]= array();
392       }
393     }
395     /* Handle tagging */
396     $this->tag_attrs($this->attrs);
397   }
400   function cleanup()
401   {
402     foreach ($this->attrs as $index => $value){
403       
404       /* Convert arrays with one element to non arrays, if the saved
405          attributes are no array, too */
406       if (is_array($this->attrs[$index]) && 
407           count ($this->attrs[$index]) == 1 &&
408           isset($this->saved_attributes[$index]) &&
409           !is_array($this->saved_attributes[$index])){
410           
411         $tmp= $this->attrs[$index][0];
412         $this->attrs[$index]= $tmp;
413       }
415       /* Remove emtpy arrays if they do not differ */
416       if (is_array($this->attrs[$index]) &&
417           count($this->attrs[$index]) == 0 &&
418           !isset($this->saved_attributes[$index])){
419           
420         unset ($this->attrs[$index]);
421         continue;
422       }
424       /* Remove single attributes that do not differ */
425       if (!is_array($this->attrs[$index]) &&
426           isset($this->saved_attributes[$index]) &&
427           !is_array($this->saved_attributes[$index]) &&
428           $this->attrs[$index] == $this->saved_attributes[$index]){
430         unset ($this->attrs[$index]);
431         continue;
432       }
434       /* Remove arrays that do not differ */
435       if (is_array($this->attrs[$index]) && 
436           isset($this->saved_attributes[$index]) &&
437           is_array($this->saved_attributes[$index])){
438           
439         if (!array_differs($this->attrs[$index],$this->saved_attributes[$index])){
440           unset ($this->attrs[$index]);
441           continue;
442         }
443       }
444     }
446     /* Update saved attributes and ensure that next cleanups will be successful too */
447     foreach($this->attrs as $name => $value){
448       $this->saved_attributes[$name] = $value;
449     }
450   }
452   /*! \brief Check formular input */
453   function check()
454   {
455     $message= array();
457     /* Skip if we've no config object */
458     if (!isset($this->config) || !is_object($this->config)){
459       return $message;
460     }
462     /* Find hooks entries for this class */
463     $command= $this->config->search(get_class($this), "CHECK", array('menu', 'tabs'));
465     if ($command != ""){
467       if (!check_command($command)){
468         $message[]= msgPool::cmdnotfound("CHECK", get_class($this));
469       } else {
471         /* Generate "ldif" for check hook */
472         $ldif= "dn: $this->dn\n";
473         
474         /* ... objectClasses */
475         foreach ($this->objectclasses as $oc){
476           $ldif.= "objectClass: $oc\n";
477         }
478         
479         /* ... attributes */
480         foreach ($this->attributes as $attr){
481           if ($this->$attr == ""){
482             continue;
483           }
484           if (is_array($this->$attr)){
485             foreach ($this->$attr as $val){
486               $ldif.= "$attr: $val\n";
487             }
488           } else {
489               $ldif.= "$attr: ".$this->$attr."\n";
490           }
491         }
493         /* Append empty line */
494         $ldif.= "\n";
496         /* Feed "ldif" into hook and retrieve result*/
497         $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
498         $fh= proc_open($command, $descriptorspec, $pipes);
499         if (is_resource($fh)) {
500           fwrite ($pipes[0], $ldif);
501           fclose($pipes[0]);
502           
503           $result= stream_get_contents($pipes[1]);
504           if ($result != ""){
505             $message[]= $result;
506           }
507           
508           fclose($pipes[1]);
509           fclose($pipes[2]);
510           proc_close($fh);
511         }
512       }
514     }
516     /* Check entryCSN */
517     if($this->CSN_check_active){
518       $current_csn = getEntryCSN($this->dn);
519       if($current_csn != $this->entryCSN && !empty($this->entryCSN) && !empty($current_csn)){
520         $this->entryCSN = $current_csn;
521         $message[] = _("The object has changed since opened in GOsa. All changes that may be done by others get lost if you save this entry!");
522       }
523     }
524     return ($message);
525   }
527   /* Adapt from template, using 'dn' */
528   function adapt_from_template($dn, $skip= array())
529   {
530     /* Include global link_info */
531     $ldap= $this->config->get_ldap_link();
533     /* Load requested 'dn' to 'attrs' */
534     $ldap->cat ($dn);
535     $this->attrs= $ldap->fetch();
537     /* Walk through attributes */
538     foreach ($this->attributes as $val){
540       /* Skip the ones in skip list */
541       if (in_array($val, $skip)){
542         continue;
543       }
545       if (isset($this->attrs["$val"][0])){
547         /* If attribute is set, replace dynamic parts: 
548            %sn, %givenName and %uid. Fill these in our local variables. */
549         $value= $this->attrs["$val"][0];
551         foreach (array("sn", "givenName", "uid") as $repl){
552           if (preg_match("/%$repl/i", $value)){
553             $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
554           }
555         }
556         $this->$val= $value;
557       }
558     }
560     /* Is Account? */
561     $found= TRUE;
562     foreach ($this->objectclasses as $obj){
563       if (preg_match('/top/i', $obj)){
564         continue;
565       }
566       if (!in_array_ics ($obj, $this->attrs['objectClass'])){
567         $found= FALSE;
568         break;
569       }
570     }
571     if ($found){
572       $this->is_account= TRUE;
573     }
574   }
576   /* \brief Indicate whether a password change is needed or not */
577   function password_change_needed()
578   {
579     return FALSE;
580   }
583   /*! \brief Show header message for tab dialogs */
584   function show_enable_header($button_text, $text, $disabled= FALSE)
585   {
586     if (($disabled == TRUE) || (!$this->acl_is_createable())){
587       $state= "disabled";
588     } else {
589       $state= "";
590     }
591     $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
592     $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".$state.
593       "><p class=\"seperator\">&nbsp;</p></td></tr></table>";
595     return($display);
596   }
599   /*! \brief Show header message for tab dialogs */
600   function show_disable_header($button_text, $text, $disabled= FALSE)
601   {
602     if (($disabled == TRUE) || !$this->acl_is_removeable()){
603       $state= "disabled";
604     } else {
605       $state= "";
606     }
607     $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
608     $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".$state.
609       "><p class=\"seperator\">&nbsp;</p></td></tr></table>";
611     return($display);
612   }
615   /*! \brief Show header message for tab dialogs */
616   function show_header($button_text, $text, $disabled= FALSE)
617   {
618     echo "FIXME: show_header should be replaced by show_disable_header and show_enable_header<br>";
619     if ($disabled == TRUE){
620       $state= "disabled";
621     } else {
622       $state= "";
623     }
624     $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
625     $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".
626       ($this->acl_is_createable()?'':'disabled')." ".$state.
627       "><p class=\"seperator\">&nbsp;</p></td></tr></table>";
629     return($display);
630   }
633   /* Create unique DN */
634   function create_unique_dn2($data, $base)
635   {
636     $ldap= $this->config->get_ldap_link();
637     $base= preg_replace("/^,*/", "", $base);
639     /* Try to use plain entry first */
640     $dn= "$data,$base";
641     $attribute= preg_replace('/=.*$/', '', $data);
642     $ldap->cat ($dn, array('dn'));
643     if (!$ldap->fetch()){
644       return ($dn);
645     }
647     /* Look for additional attributes */
648     foreach ($this->attributes as $attr){
649       if ($attr == $attribute || $this->$attr == ""){
650         continue;
651       }
653       $dn= "$data+$attr=".$this->$attr.",$base";
654       $ldap->cat ($dn, array('dn'));
655       if (!$ldap->fetch()){
656         return ($dn);
657       }
658     }
660     /* None found */
661     return ("none");
662   }
665   /*! \brief Create unique DN */
666   function create_unique_dn($attribute, $base)
667   {
668     $ldap= $this->config->get_ldap_link();
669     $base= preg_replace("/^,*/", "", $base);
671     /* Try to use plain entry first */
672     $dn= "$attribute=".$this->$attribute.",$base";
673     $ldap->cat ($dn, array('dn'));
674     if (!$ldap->fetch()){
675       return ($dn);
676     }
678     /* Look for additional attributes */
679     foreach ($this->attributes as $attr){
680       if ($attr == $attribute || $this->$attr == ""){
681         continue;
682       }
684       $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
685       $ldap->cat ($dn, array('dn'));
686       if (!$ldap->fetch()){
687         return ($dn);
688       }
689     }
691     /* None found */
692     return ("none");
693   }
696   function rebind($ldap, $referral)
697   {
698     $credentials= LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
699     if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
700       $this->error = "Success";
701       $this->hascon=true;
702       $this->reconnect= true;
703       return (0);
704     } else {
705       $this->error = "Could not bind to " . $credentials['ADMIN'];
706       return NULL;
707     }
708   }
711   /* Recursively copy ldap object */
712   function _copy($src_dn,$dst_dn)
713   {
714     $ldap=$this->config->get_ldap_link();
715     $ldap->cat($src_dn);
716     $attrs= $ldap->fetch();
718     /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
719     $ds= ldap_connect($this->config->current['SERVER']);
720     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
721     if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
722       ldap_set_rebind_proc($ds, array(&$this, "rebind"));
723     }
725     $pwd = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
726     $r=ldap_bind($ds,$this->config->current['ADMINDN'], $pwd);
727     $sr=ldap_read($ds, LDAP::fix($src_dn), "objectClass=*");
729     /* Fill data from LDAP */
730     $new= array();
731     if ($sr) {
732       $ei=ldap_first_entry($ds, $sr);
733       if ($ei) {
734         foreach($attrs as $attr => $val){
735           if ($info = @ldap_get_values_len($ds, $ei, $attr)){
736             for ($i= 0; $i<$info['count']; $i++){
737               if ($info['count'] == 1){
738                 $new[$attr]= $info[$i];
739               } else {
740                 $new[$attr][]= $info[$i];
741               }
742             }
743           }
744         }
745       }
746     }
748     /* close conncetion */
749     ldap_unbind($ds);
751     /* Adapt naming attribute */
752     $dst_name= preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
753     $dst_val = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
754     $new[$dst_name]= LDAP::fix($dst_val);
756     /* Check if this is a department.
757      * If it is a dep. && there is a , override in his ou 
758      *  change \2C to , again, else this entry can't be saved ...
759      */
760     if((isset($new['ou'])) &&( preg_match("/\\,/",$new['ou']))){
761       $new['ou'] = str_replace("\\\\,",",",$new['ou']);
762     }
764     /* Save copy */
765     $ldap->connect();
766     $ldap->cd($this->config->current['BASE']);
767     
768     $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
770     /* FAIvariable=.../..., cn=.. 
771         could not be saved, because the attribute FAIvariable was different to 
772         the dn FAIvariable=..., cn=... */
774     if(!is_array($new['objectClass'])) $new['objectClass'] = array($new['objectClass']);
776     if(in_array_ics("FAIdebconfInfo",$new['objectClass'])){
777       $new['FAIvariable'] = $ldap->fix($new['FAIvariable']);
778     }
779     $ldap->cd($dst_dn);
780     $ldap->add($new);
782     if (!$ldap->success()){
783       trigger_error("Trying to save $dst_dn failed.",
784           E_USER_WARNING);
785       return(FALSE);
786     }
787     return(TRUE);
788   }
791   /* This is a workaround function. */
792   function copy($src_dn, $dst_dn)
793   {
794     /* Rename dn in possible object groups */
795     $ldap= $this->config->get_ldap_link();
796     $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.@LDAP::prepare4filter($src_dn).'))',
797         array('cn'));
798     while ($attrs= $ldap->fetch()){
799       $og= new ogroup($this->config, $ldap->getDN());
800       unset($og->member[$src_dn]);
801       $og->member[$dst_dn]= $dst_dn;
802       $og->save ();
803     }
805     $ldap->cat($dst_dn);
806     $attrs= $ldap->fetch();
807     if (count($attrs)){
808       trigger_error("Trying to overwrite ".LDAP::fix($dst_dn).", which already exists.",
809           E_USER_WARNING);
810       return (FALSE);
811     }
813     $ldap->cat($src_dn);
814     $attrs= $ldap->fetch();
815     if (!count($attrs)){
816       trigger_error("Trying to move ".LDAP::fix($src_dn).", which does not seem to exist.",
817           E_USER_WARNING);
818       return (FALSE);
819     }
821     $ldap->cd($src_dn);
822     $ldap->search("objectClass=*",array("dn"));
823     while($attrs = $ldap->fetch()){
824       $src = $attrs['dn'];
825       $dst = preg_replace("/".preg_quote($src_dn, '/')."$/",$dst_dn,$attrs['dn']);
826       $this->_copy($src,$dst);
827     }
828     return (TRUE);
829   }
833   /*! \brief  Rename/Move a given src_dn to the given dest_dn
834    *
835    * Move a given ldap object indentified by $src_dn to the
836    * given destination $dst_dn
837    *
838    * - Ensure that all references are updated (ogroups)
839    * - Update ACLs   
840    * - Update accessTo
841    *
842    * \param  string  'src_dn' the source DN.
843    * \param  string  'dst_dn' the destination DN.
844    * \return boolean TRUE on success else FALSE.
845    */
846   function rename($src_dn, $dst_dn)
847   {
848     $start = microtime(1);
850     /* Try to move the source entry to the destination position */
851     $ldap = $this->config->get_ldap_link();
852     $ldap->cd($this->config->current['BASE']);
853     $ldap->create_missing_trees(preg_replace("/^[^,]+,/","",$dst_dn));
854     if (!$ldap->rename_dn($src_dn,$dst_dn)){
855 #      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $src_dn, "", get_class()));
856       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());
857       @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,"Rename failed FROM: $src_dn  -- TO:  $dst_dn", 
858           "Ldap Protocol v3 implementation error, falling back to maunal method.");
859       return(FALSE);
860     }
862     /* Get list of users,groups and roles within this tree,
863         maybe we have to update ACL references.
864      */
865     $leaf_objs = get_list("(|(objectClass=posixGroup)(objectClass=gosaAccount)(objectClass=gosaRole))",array("all"),$dst_dn,
866           array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
867     foreach($leaf_objs as $obj){
868       $new_dn = $obj['dn'];
869       $old_dn = preg_replace("/".preg_quote(LDAP::convert($dst_dn), '/')."$/i",$src_dn,LDAP::convert($new_dn));
870       $this->update_acls($old_dn,$new_dn); 
871     }
873     // Migrate objectgroups if needed
874     $ogroups = get_sub_list("(&(objectClass=gosaGroupOfNames)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))",
875       "ogroups", array(get_ou("ogroupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
877     // Walk through all objectGroups
878     foreach($ogroups as $ogroup){
879       // Migrate old to new dn
880       $o_ogroup= new ogroup($this->config,$ogroup['dn']);
881       if (isset($o_ogroup->member[$src_dn])) {
882         unset($o_ogroup->member[$src_dn]);
883       }
884       $o_ogroup->member[$dst_dn]= $dst_dn;
885       
886       // Save object group
887       $o_ogroup->save();
888     }
890     // Migrate rfc groups if needed
891     $groups = get_sub_list("(&(objectClass=posixGroup)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))","groups", array(get_ou("groupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
893     // Walk through all POSIX groups
894     foreach($groups as $group){
896       // Migrate old to new dn
897       $o_group= new group($this->config,$group['dn']);
898       $o_group->save();
899     }
901     /* Update roles to use the new entry dn */
902     $roles = get_sub_list("(&(objectClass=organizationalRole)(roleOccupant=".LDAP::prepare4filter(LDAP::fix($src_dn))."))","roles", array(get_ou("roleRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
904     // Walk through all roles
905     foreach($roles as $role){
906       $role = new roleGeneric($this->config,$role['dn']);
907       $key= array_search($src_dn, $role->roleOccupant);      
908       if($key !== FALSE){
909         $role->roleOccupant[$key] = $dst_dn;
910         $role->save();
911       }
912     }
914     // Update 'manager' attributes from gosaDepartment and inetOrgPerson 
915     $filter = "(&(objectClass=inetOrgPerson)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn))."))";
916     $ocs = $ldap->get_objectclasses();
917     if(isset($ocs['gosaDepartment']['MAY']) && in_array('manager', $ocs['gosaDepartment']['MAY'])){
918       $filter = "(|".$filter."(&(objectClass=gosaDepartment)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn)).")))";
919     }
920     $leaf_deps=  get_list($filter,array("all"),$this->config->current['BASE'], 
921         array("manager","dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
922     foreach($leaf_deps as $entry){
923       $update = array('manager' => $dst_dn);
924       $ldap->cd($entry['dn']);
925       $ldap->modify($update);
926       if(!$ldap->success()){
927         trigger_error(sprintf("Failed to update manager for '%s', error was '%s'", $entry['dn'], $ldap->get_error()));
928       }
929     }
930  
931     /* Check if there are gosa departments moved. 
932        If there were deps moved, the force reload of config->deps.
933      */
934     $leaf_deps=  get_list("(objectClass=gosaDepartment)",array("all"),$dst_dn,
935           array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
936   
937     if(count($leaf_deps)){
938       $this->config->get_departments();
939       $this->config->make_idepartments();
940       session::global_set("config",$this->config);
941       $ui =get_userinfo();
942       $ui->reset_acl_cache();
943     }
945     return(TRUE); 
946   }
949  
950   function move($src_dn, $dst_dn)
951   {
952     /* Do not copy if only upper- lowercase has changed */
953     if(strtolower($src_dn) == strtolower($dst_dn)){
954       return(TRUE);
955     }
957     
958     /* Try to move the entry instead of copy & delete
959      */
960     if(TRUE){
962       /* Try to move with ldap routines, if this was not successfull
963           fall back to the old style copy & remove method 
964        */
965       if($this->rename($src_dn, $dst_dn)){
966         return(TRUE);
967       }else{
968         // See code below.
969       }
970     }
972     /* Copy source to destination */
973     if (!$this->copy($src_dn, $dst_dn)){
974       return (FALSE);
975     }
977     /* Delete source */
978     $ldap= $this->config->get_ldap_link();
979     $ldap->rmdir_recursive($src_dn);
980     if (!$ldap->success()){
981       trigger_error("Trying to delete $src_dn failed.",
982           E_USER_WARNING);
983       return (FALSE);
984     }
986     return (TRUE);
987   }
990   /* \brief Move/Rename complete trees */
991   function recursive_move($src_dn, $dst_dn)
992   {
993     /* Check if the destination entry exists */
994     $ldap= $this->config->get_ldap_link();
996     /* Check if destination exists - abort */
997     $ldap->cat($dst_dn, array('dn'));
998     if ($ldap->fetch()){
999       trigger_error("recursive_move $dst_dn already exists.",
1000           E_USER_WARNING);
1001       return (FALSE);
1002     }
1004     $this->copy($src_dn, $dst_dn);
1006     /* Remove src_dn */
1007     $ldap->cd($src_dn);
1008     $ldap->recursive_remove($src_dn);
1009     return (TRUE);
1010   }
1013   function saveCopyDialog(){
1014   }
1017   function getCopyDialog(){
1018     return(array("string"=>"","status"=>""));
1019   }
1022   /*! \brief Prepare for Copy & Paste */
1023   function PrepareForCopyPaste($source)
1024   {
1025     $todo = $this->attributes;
1026     if(isset($this->CopyPasteVars)){
1027       $todo = array_merge($todo,$this->CopyPasteVars);
1028     }
1030     if(count($this->objectclasses)){
1031       $this->is_account = TRUE;
1032       foreach($this->objectclasses as $class){
1033         if(!in_array($class,$source['objectClass'])){
1034           $this->is_account = FALSE;
1035         }
1036       }
1037     }
1039     foreach($todo as $var){
1040       if (isset($source[$var])){
1041         if(isset($source[$var]['count'])){
1042           if($source[$var]['count'] > 1){
1043             $tmp= $source[$var];
1044             unset($tmp['count']);
1045             $this->$var = $tmp;
1046           }else{
1047             $this->$var = $source[$var][0];
1048           }
1049         }else{
1050           $this->$var= $source[$var];
1051         }
1052       }
1053     }
1054   }
1056   /*! \brief Get gosaUnitTag for the given DN
1057        If this is called from departmentGeneric, we have to skip this
1058         tagging procedure. 
1059     */
1060   function tag_attrs(&$at, $dn= "", $tag= "", $show= false)
1061   {
1062     /* Skip tagging? */
1063     if($this->skipTagging){
1064       return;
1065     }
1067     /* No dn? Self-operation... */
1068     if ($dn == ""){
1069       $dn= $this->dn;
1071       /* No tag? Find it yourself... */
1072       if ($tag == ""){
1073         $len= strlen($dn);
1075         @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "No tag for $dn - looking for one...", "Tagging");
1076         $relevant= array();
1077         foreach ($this->config->adepartments as $key => $ntag){
1079           /* This one is bigger than our dn, its not relevant... */
1080           if ($len < strlen($key)){
1081             continue;
1082           }
1084           /* This one matches with the latter part. Break and don't fix this entry */
1085           if (preg_match('/(^|,)'.preg_quote($key, '/').'$/', $dn)){
1086             @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "DEBUG: Possibly relevant: $key", "Tagging");
1087             $relevant[strlen($key)]= $ntag;
1088             continue;
1089           }
1091         }
1093         /* If we've some relevant tags to set, just get the longest one */
1094         if (count($relevant)){
1095           ksort($relevant);
1096           $tmp= array_keys($relevant);
1097           $idx= end($tmp);
1098           $tag= $relevant[$idx];
1099           $this->gosaUnitTag= $tag;
1100         }
1101       }
1102     }
1104   /*! \brief Add unit tag */ 
1105     /* Remove tags that may already be here... */
1106     remove_objectClass("gosaAdministrativeUnitTag", $at);
1107     if (isset($at['gosaUnitTag'])){
1108         unset($at['gosaUnitTag']);
1109     }
1111     /* Set tag? */
1112     if ($tag != ""){
1113       add_objectClass("gosaAdministrativeUnitTag", $at);
1114       $at['gosaUnitTag']= $tag;
1115     }
1117     /* Initially this object was tagged. 
1118        - But now, it is no longer inside a tagged department. 
1119        So force the remove of the tag.
1120        (objectClass was already removed obove)
1121      */
1122     if($tag == "" && $this->gosaUnitTag){
1123       $at['gosaUnitTag'] = array();
1124     }
1125   }
1128   /*! \brief Test for removability of the object
1129    *
1130    * Allows testing of conditions for removal of object. If removal should be aborted
1131    * the function needs to remove an error message.
1132    * */
1133   function allow_remove()
1134   {
1135     $reason= "";
1136     return $reason;
1137   }
1140   /*! \brief Create a snapshot of the current object */
1141   function create_snapshot($type= "snapshot", $description= array())
1142   {
1144     /* Check if snapshot functionality is enabled */
1145     if(!$this->snapshotEnabled()){
1146       return;
1147     }
1149     /* Get configuration from gosa.conf */
1150     $config = $this->config;
1152     /* Create lokal ldap connection */
1153     $ldap= $this->config->get_ldap_link();
1154     $ldap->cd($this->config->current['BASE']);
1156     /* check if there are special server configurations for snapshots */
1157     if($config->get_cfg_value("snapshotURI") == ""){
1159       /* Source and destination server are both the same, just copy source to dest obj */
1160       $ldap_to      = $ldap;
1161       $snapldapbase = $this->config->current['BASE'];
1163     }else{
1164       $server         = $config->get_cfg_value("snapshotURI");
1165       $user           = $config->get_cfg_value("snapshotAdminDn");
1166       $password       = $this->config->get_credentials($config->get_cfg_value("snapshotAdminPassword"));
1167       $snapldapbase   = $config->get_cfg_value("snapshotBase");
1169       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1170       $ldap_to -> cd($snapldapbase);
1172       if (!$ldap_to->success()){
1173         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1174       }
1176     }
1178     /* check if the dn exists */ 
1179     if ($ldap->dn_exists($this->dn)){
1181       /* Extract seconds & mysecs, they are used as entry index */
1182       list($usec, $sec)= explode(" ", microtime());
1184       /* Collect some infos */
1185       $base           = $this->config->current['BASE'];
1186       $snap_base      = $config->get_cfg_value("snapshotBase");
1187       $base_of_object = preg_replace ('/^[^,]+,/i', '', $this->dn);
1188       $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
1190       /* Create object */
1191 #$data             = preg_replace('/^dn:.*\n/', '', $ldap->gen_ldif($this->dn,"(!(objectClass=gosaDepartment))"));
1192       $data             = $ldap->gen_ldif($this->dn,"(&(!(objectClass=gosaDepartment))(!(objectClass=FAIclass)))");
1193       $newName          = str_replace(".", "", $sec."-".$usec);
1194       $target= array();
1195       $target['objectClass']            = array("top", "gosaSnapshotObject");
1196       $target['gosaSnapshotData']       = gzcompress($data, 6);
1197       $target['gosaSnapshotType']       = $type;
1198       $target['gosaSnapshotDN']         = $this->dn;
1199       $target['description']            = $description;
1200       $target['gosaSnapshotTimestamp']  = $newName;
1202       /* Insert the new snapshot 
1203          But we have to check first, if the given gosaSnapshotTimestamp
1204          is already used, in this case we should increment this value till there is 
1205          an unused value. */ 
1206       $new_dn                           = "gosaSnapshotTimestamp=".$newName.",".$new_base;
1207       $ldap_to->cat($new_dn);
1208       while($ldap_to->count()){
1209         $ldap_to->cat($new_dn);
1210         $newName = str_replace(".", "", $sec."-".($usec++));
1211         $new_dn                           = "gosaSnapshotTimestamp=".$newName.",".$new_base;
1212         $target['gosaSnapshotTimestamp']  = $newName;
1213       } 
1215       /* Inset this new snapshot */
1216       $ldap_to->cd($snapldapbase);
1217       $ldap_to->create_missing_trees($snapldapbase);
1218       $ldap_to->create_missing_trees($new_base);
1219       $ldap_to->cd($new_dn);
1220       $ldap_to->add($target);
1221       if (!$ldap_to->success()){
1222         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $new_dn, LDAP_ADD, get_class()));
1223       }
1225       if (!$ldap->success()){
1226         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $new_base, "", get_class()));
1227       }
1229     }
1230   }
1232   /*! \brief Remove a snapshot */
1233   function remove_snapshot($dn)
1234   {
1235     $ui       = get_userinfo();
1236     $old_dn   = $this->dn; 
1237     $this->dn = $dn;
1238     $ldap = $this->config->get_ldap_link();
1239     $ldap->cd($this->config->current['BASE']);
1240     $ldap->rmdir_recursive($this->dn);
1241     if(!$ldap->success()){
1242       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn));
1243     }
1244     $this->dn = $old_dn;
1245   }
1248   /*! \brief Test if snapshotting is enabled
1249    *
1250    * Test weither snapshotting is enabled or not. There will also be some errors posted,
1251    * if the configuration failed 
1252    * \return TRUE if snapshots are enabled, and FALSE if it is disabled
1253    */
1254   function snapshotEnabled()
1255   {
1256     return $this->config->snapshotEnabled();
1257   }
1260   /* \brief Return available snapshots for the given base */
1261   function Available_SnapsShots($dn,$raw = false)
1262   {
1263     if(!$this->snapshotEnabled()) return(array());
1265     /* Create an additional ldap object which
1266        points to our ldap snapshot server */
1267     $ldap= $this->config->get_ldap_link();
1268     $ldap->cd($this->config->current['BASE']);
1269     $cfg= &$this->config->current;
1271     /* check if there are special server configurations for snapshots */
1272     if($this->config->get_cfg_value("snapshotURI") == ""){
1273       $ldap_to      = $ldap;
1274     }else{
1275       $server         = $this->config->get_cfg_value("snapshotURI");
1276       $user           = $this->config->get_cfg_value("snapshotAdminDn");
1277       $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
1278       $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
1279       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1280       $ldap_to -> cd($snapldapbase);
1281       if (!$ldap_to->success()){
1282         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1283       }
1284     }
1286     /* Prepare bases and some other infos */
1287     $base           = $this->config->current['BASE'];
1288     $snap_base      = $this->config->get_cfg_value("snapshotBase");
1289     $base_of_object = preg_replace ('/^[^,]+,/i', '', $dn);
1290     $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
1291     $tmp            = array(); 
1293     /* Fetch all objects with  gosaSnapshotDN=$dn */
1294     $ldap_to->cd($new_base);
1295     $ldap_to->ls("(&(objectClass=gosaSnapshotObject)(gosaSnapshotDN=".$dn."))",$new_base,
1296         array("gosaSnapshotType","gosaSnapshotTimestamp","gosaSnapshotDN","description")); 
1298     /* Put results into a list and add description if missing */
1299     while($entry = $ldap_to->fetch()){ 
1300       if(!isset($entry['description'][0])){
1301         $entry['description'][0]  = "";
1302       }
1303       $tmp[] = $entry; 
1304     }
1306     /* Return the raw array, or format the result */
1307     if($raw){
1308       return($tmp);
1309     }else{  
1310       $tmp2 = array();
1311       foreach($tmp as $entry){
1312         $tmp2[base64_encode($entry['dn'])] = $entry['description'][0]; 
1313       }
1314     }
1315     return($tmp2);
1316   }
1319   function getAllDeletedSnapshots($base_of_object,$raw = false)
1320   {
1321     if(!$this->snapshotEnabled()) return(array());
1323     /* Create an additional ldap object which
1324        points to our ldap snapshot server */
1325     $ldap= $this->config->get_ldap_link();
1326     $ldap->cd($this->config->current['BASE']);
1327     $cfg= &$this->config->current;
1329     /* check if there are special server configurations for snapshots */
1330     if($this->config->get_cfg_value("snapshotURI") == ""){
1331       $ldap_to      = $ldap;
1332     }else{
1333       $server         = $this->config->get_cfg_value("snapshotURI");
1334       $user           = $this->config->get_cfg_value("snapshotAdminDn");
1335       $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
1336       $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
1337       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1338       $ldap_to -> cd($snapldapbase);
1339       if (!$ldap_to->success()){
1340         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1341       }
1342     }
1344     /* Prepare bases */ 
1345     $base           = $this->config->current['BASE'];
1346     $snap_base      = $this->config->get_cfg_value("snapshotBase");
1347     $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
1349     /* Fetch all objects and check if they do not exist anymore */
1350     $ui = get_userinfo();
1351     $tmp = array();
1352     $ldap_to->cd($new_base);
1353     $ldap_to->ls("(objectClass=gosaSnapshotObject)",$new_base,array("gosaSnapshotType","gosaSnapshotTimestamp","gosaSnapshotDN","description"));
1354     while($entry = $ldap_to->fetch()){
1356       $chk =  str_replace($new_base,"",$entry['dn']);
1357       if(preg_match("/,ou=/",$chk)) continue;
1359       if(!isset($entry['description'][0])){
1360         $entry['description'][0]  = "";
1361       }
1362       $tmp[] = $entry; 
1363     }
1365     /* Check if entry still exists */
1366     foreach($tmp as $key => $entry){
1367       $ldap->cat($entry['gosaSnapshotDN'][0]);
1368       if($ldap->count()){
1369         unset($tmp[$key]);
1370       }
1371     }
1373     /* Format result as requested */
1374     if($raw) {
1375       return($tmp);
1376     }else{
1377       $tmp2 = array();
1378       foreach($tmp as $key => $entry){
1379         $tmp2[base64_encode($entry['dn'])] = $entry['description'][0]; 
1380       }
1381     }
1382     return($tmp2);
1383   } 
1386   /* \brief Restore selected snapshot */
1387   function restore_snapshot($dn)
1388   {
1389     if(!$this->snapshotEnabled()) return(array());
1391     $ldap= $this->config->get_ldap_link();
1392     $ldap->cd($this->config->current['BASE']);
1393     $cfg= &$this->config->current;
1395     /* check if there are special server configurations for snapshots */
1396     if($this->config->get_cfg_value("snapshotURI") == ""){
1397       $ldap_to      = $ldap;
1398     }else{
1399       $server         = $this->config->get_cfg_value("snapshotURI");
1400       $user           = $this->config->get_cfg_value("snapshotAdminDn");
1401       $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
1402       $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
1403       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1404       $ldap_to -> cd($snapldapbase);
1405       if (!$ldap_to->success()){
1406         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1407       }
1408     }
1410     /* Get the snapshot */ 
1411     $ldap_to->cat($dn);
1412     $restoreObject = $ldap_to->fetch();
1414     /* Prepare import string */
1415     $data  = gzuncompress($ldap_to->get_attribute($dn,'gosaSnapshotData'));
1417     /* Import the given data */
1418     $err = "";
1419     $ldap->import_complete_ldif($data,$err,false,false);
1420     if (!$ldap->success()){
1421       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, "", get_class()));
1422     }
1423   }
1426   function showSnapshotDialog($base,$baseSuffixe,&$parent)
1427   {
1428     $once = true;
1429     $ui = get_userinfo();
1430     $this->parent = $parent;
1432     foreach($_POST as $name => $value){
1434       /* Create a new snapshot, display a dialog */
1435       if(preg_match("/^CreateSnapShotDialog_[^_]*_[xy]$/",$name) && $once){
1437                           $entry = base64_decode(preg_replace("/^CreateSnapShotDialog_([^_]*)_[xy]$/","\\1",$name));
1438         $once = false;
1439         $entry = preg_replace("/^CreateSnapShotDialog_/","",$entry);
1441         if(!empty($entry) && $ui->allow_snapshot_create($entry,$this->parent->acl_module)){
1442           $this->snapDialog = new SnapShotDialog($this->config,$entry,$this);
1443         }else{
1444           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to create a snapshot for %s."),$entry),ERROR_DIALOG);
1445         }
1446       }  
1447   
1448       /* Restore a snapshot, display a dialog with all snapshots of the current object */
1449       if(preg_match("/^RestoreSnapShotDialog_/",$name) && $once){
1450         $once = false;
1451         $entry = base64_decode(preg_replace("/^RestoreSnapShotDialog_([^_]*)_[xy]$/i","\\1",$name));
1452         if(!empty($entry) && $ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
1453           $this->snapDialog = new SnapShotDialog($this->config,$entry,$this);
1454           $this->snapDialog->display_restore_dialog = true;
1455         }else{
1456           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$entry),ERROR_DIALOG);
1457         }
1458       }
1460       /* Restore one of the already deleted objects */
1461       if(((isset($_POST['menu_action']) && $_POST['menu_action'] == "RestoreDeletedSnapShot") 
1462           || preg_match("/^RestoreDeletedSnapShot_/",$name)) && $once){
1463         $once = false;
1465         if($ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
1466           $this->snapDialog = new SnapShotDialog($this->config,"",$this);
1467           $this->snapDialog->set_snapshot_bases($baseSuffixe);
1468           $this->snapDialog->display_restore_dialog      = true;
1469           $this->snapDialog->display_all_removed_objects  = true;
1470         }else{
1471           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$base),ERROR_DIALOG);
1472         }
1473       }
1475       /* Restore selected snapshot */
1476       if(preg_match("/^RestoreSnapShot_/",$name) && $once){
1477         $once = false;
1478         $entry = base64_decode(preg_replace("/^RestoreSnapShot_([^_]*)_[xy]$/i","\\1",$name));
1480         if(!empty($entry) && $ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
1481           $this->restore_snapshot($entry);
1482           $this->snapDialog = NULL;
1483         }else{
1484           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$entry),ERROR_DIALOG);
1485         }
1486       }
1487     }
1489     /* Create a new snapshot requested, check
1490        the given attributes and create the snapshot*/
1491     if(isset($_POST['CreateSnapshot']) && is_object($this->snapDialog)){
1492       $this->snapDialog->save_object();
1493       $msgs = $this->snapDialog->check();
1494       if(count($msgs)){
1495         foreach($msgs as $msg){
1496           msg_dialog::display(_("Error"), $msg, ERROR_DIALOG);
1497         }
1498       }else{
1499         $this->dn =  $this->snapDialog->dn;
1500         $this->create_snapshot("snapshot",$this->snapDialog->CurrentDescription);
1501         $this->snapDialog = NULL;
1502       }
1503     }
1505     /* Restore is requested, restore the object with the posted dn .*/
1506     if((isset($_POST['RestoreSnapshot'])) && (isset($_POST['SnapShot']))){
1507     }
1509     if(isset($_POST['CancelSnapshot'])){
1510       $this->snapDialog = NULL;
1511     }
1513     if(is_object($this->snapDialog )){
1514       $this->snapDialog->save_object();
1515       return($this->snapDialog->execute());
1516     }
1517   }
1520   /*! \brief Return plugin informations for acl handling */
1521   static function plInfo()
1522   {
1523     return array();
1524   }
1527   function set_acl_base($base)
1528   {
1529     $this->acl_base= $base;
1530   }
1533   function set_acl_category($category)
1534   {
1535     $this->acl_category= "$category/";
1536   }
1539   function acl_is_writeable($attribute,$skip_write = FALSE)
1540   {
1541     if($this->read_only) return(FALSE);
1542     $ui= get_userinfo();
1543     return preg_match('/w/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute, $skip_write));
1544   }
1547   function acl_is_readable($attribute)
1548   {
1549     $ui= get_userinfo();
1550     return preg_match('/r/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute));
1551   }
1554   function acl_is_createable($base ="")
1555   {
1556     if($this->read_only) return(FALSE);
1557     $ui= get_userinfo();
1558     if($base == "") $base = $this->acl_base;
1559     return preg_match('/c/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1560   }
1563   function acl_is_removeable($base ="")
1564   {
1565     if($this->read_only) return(FALSE);
1566     $ui= get_userinfo();
1567     if($base == "") $base = $this->acl_base;
1568     return preg_match('/d/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1569   }
1572   function acl_is_moveable($base = "")
1573   {
1574     if($this->read_only) return(FALSE);
1575     $ui= get_userinfo();
1576     if($base == "") $base = $this->acl_base;
1577     return preg_match('/m/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1578   }
1581   function acl_have_any_permissions()
1582   {
1583   }
1586   function getacl($attribute,$skip_write= FALSE)
1587   {
1588     $ui= get_userinfo();
1589     $skip_write |= $this->read_only;
1590     return  $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute,$skip_write);
1591   }
1594   /*! \brief Returns a list of all available departments for this object.
1595    * 
1596    * If this object is new, all departments we are allowed to create a new user in
1597    * are returned. If this is an existing object, return all deps. 
1598    * We are allowed to move tis object too.
1599    * \return array [dn] => "..name"  // All deps. we are allowed to act on.
1600   */
1601   function get_allowed_bases()
1602   {
1603     $ui = get_userinfo();
1604     $deps = array();
1606     /* Is this a new object ? Or just an edited existing object */
1607     if(!$this->initially_was_account && $this->is_account){
1608       $new = true;
1609     }else{
1610       $new = false;
1611     }
1613     foreach($this->config->idepartments as $dn => $name){
1614       if($new && $this->acl_is_createable($dn)){
1615         $deps[$dn] = $name;
1616       }elseif(!$new && $this->acl_is_moveable($dn)){
1617         $deps[$dn] = $name;
1618       }
1619     }
1621     /* Add current base */      
1622     if(isset($this->base) && isset($this->config->idepartments[$this->base])){
1623       $deps[$this->base] = $this->config->idepartments[$this->base];
1624     }elseif(strtolower($this->dn) == strtolower($this->config->current['BASE'])){
1626     }else{
1627       trigger_error("Cannot return list of departments, no default base found in class ".get_class($this).". ".$this->base);
1628     }
1629     return($deps);
1630   }
1633   /* This function updates ACL settings if $old_dn was used.
1634    *  \param string 'old_dn' specifies the actually used dn
1635    *  \param string 'new_dn' specifies the destiantion dn
1636    */
1637   function update_acls($old_dn,$new_dn,$output_changes = FALSE)
1638   {
1639     /* Check if old_dn is empty. This should never happen */
1640     if(empty($old_dn) || empty($new_dn)){
1641       trigger_error("Failed to check acl dependencies, wrong dn given.");
1642       return;
1643     }
1645     /* Update userinfo if necessary */
1646     $ui = session::global_get('ui');
1647     if($ui->dn == $old_dn){
1648       $ui->dn = $new_dn;
1649       session::global_set('ui',$ui);
1650       new log("view","acl/".get_class($this),$this->dn,array(),"Updated current object dn from '".$old_dn."' to '".$new_dn."'");
1651     }
1653     /* Object was moved, ensure that all acls will be moved too */
1654     if($new_dn != $old_dn && $old_dn != "new"){
1656       /* get_ldap configuration */
1657       $update = array();
1658       $ldap = $this->config->get_ldap_link();
1659       $ldap->cd ($this->config->current['BASE']);
1660       $ldap->search("(&(objectClass=gosaAcl)(gosaAclEntry=*".base64_encode($old_dn)."*))",array("cn","gosaAclEntry"));
1661       while($attrs = $ldap->fetch()){
1662         $acls = array();
1663         $found = false;
1664         for($i = 0 ; $i <  $attrs['gosaAclEntry']['count'] ; $i ++ ){
1665           $acl_parts = explode(":",$attrs['gosaAclEntry'][$i]);
1667           /* Roles uses antoher data storage order, members are stored int the third part, 
1668              while the members in direct ACL assignments are stored in the second part.
1669            */
1670           $id = ($acl_parts[1] == "role") ? 3 : 2;
1672           /* Update member entries to use $new_dn instead of old_dn
1673            */
1674           $members = explode(",",$acl_parts[$id]);
1675           foreach($members as $key => $member){
1676             $member = base64_decode($member);
1677             if($member == $old_dn){
1678               $members[$key] = base64_encode($new_dn);
1679               $found = TRUE;
1680             }
1681           } 
1683           /* Check if the selected role has to updated
1684            */
1685           if($acl_parts[1] == "role" && $acl_parts[2] == base64_encode($old_dn)){
1686             $acl_parts[2] = base64_encode($new_dn);
1687             $found = TRUE;
1688           }
1690           /* Build new acl string */ 
1691           $acl_parts[$id] = implode($members,",");
1692           $acls[] = implode($acl_parts,":");
1693         }
1695         /* Acls for this object must be adjusted */
1696         if($found){
1698           $debug_info=  _("Changing ACL dn")."&nbsp;:&nbsp;<br>&nbsp;-"._("from")."&nbsp;<b>&nbsp;".
1699             $old_dn."</b><br>&nbsp;-"._("to")."&nbsp;<b>".$new_dn."</b><br>";
1700           @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,$debug_info,"ACL");
1702           $update[$attrs['dn']] =array();
1703           foreach($acls as $acl){
1704             $update[$attrs['dn']]['gosaAclEntry'][] = $acl;
1705           }
1706         }
1707       }
1709       /* Write updated acls */
1710       foreach($update as $dn => $attrs){
1711         $ldap->cd($dn);
1712         $ldap->modify($attrs);
1713       }
1714     }
1715   }
1717   
1719   /*! \brief Enable the Serial ID check
1720    *
1721    * This function enables the entry Serial ID check.  If an entry was edited while
1722    * we have edited the entry too, an error message will be shown. 
1723    * To configure this check correctly read the FAQ.
1724    */    
1725   function enable_CSN_check()
1726   {
1727     $this->CSN_check_active =TRUE;
1728     $this->entryCSN = getEntryCSN($this->dn);
1729   }
1732   /*! \brief  Prepares the plugin to be used for multiple edit
1733    *          Update plugin attributes with given array of attribtues.
1734    *  \param  array   Array with attributes that must be updated.
1735    */
1736   function init_multiple_support($attrs,$all)
1737   {
1738     $ldap= $this->config->get_ldap_link();
1739     $this->multi_attrs    = $attrs;
1740     $this->multi_attrs_all= $all;
1742     /* Copy needed attributes */
1743     foreach ($this->attributes as $val){
1744       $found= array_key_ics($val, $this->multi_attrs);
1745  
1746       if ($found != ""){
1747         if(isset($this->multi_attrs["$val"][0])){
1748           $this->$val= $this->multi_attrs["$val"][0];
1749         }
1750       }
1751     }
1752   }
1754  
1755   /*! \brief  Enables multiple support for this plugin
1756    */
1757   function enable_multiple_support()
1758   {
1759     $this->ignore_account = TRUE;
1760     $this->multiple_support_active = TRUE;
1761   }
1764   /*! \brief  Returns all values that have been modfied in multiple edit mode.
1765       \return array Cotaining all modified values. 
1766    */
1767   function get_multi_edit_values()
1768   {
1769     $ret = array();
1770     foreach($this->attributes as $attr){
1771       if(in_array($attr,$this->multi_boxes)){
1772         $ret[$attr] = $this->$attr;
1773       }
1774     }
1775     return($ret);
1776   }
1778   
1779   /*! \brief  Update class variables with values collected by multiple edit.
1780    */
1781   function set_multi_edit_values($attrs)
1782   {
1783     foreach($attrs as $name => $value){
1784       $this->$name = $value;
1785     }
1786   }
1789   /*! \brief Generates the html output for this node for multi edit*/
1790   function multiple_execute()
1791   {
1792     /* This one is empty currently. Fabian - please fill in the docu code */
1793     session::global_set('current_class_for_help',get_class($this));
1795     /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
1796     session::set('LOCK_VARS_TO_USE',array());
1797     session::set('LOCK_VARS_USED_GET',array());
1798     session::set('LOCK_VARS_USED_POST',array());
1799     session::set('LOCK_VARS_USED_REQUEST',array());
1800     
1801     return("Multiple edit is currently not implemented for this plugin.");
1802   }
1805   /*! \brief Save HTML posted data to object for multiple edit
1806    */
1807   function multiple_save_object()
1808   {
1809     if(empty($this->entryCSN) && $this->CSN_check_active){
1810       $this->entryCSN = getEntryCSN($this->dn);
1811     }
1813     /* Save values to object */
1814     $this->multi_boxes = array();
1815     foreach ($this->attributes as $val){
1816   
1817       /* Get selected checkboxes from multiple edit */
1818       if(isset($_POST["use_".$val])){
1819         $this->multi_boxes[] = $val;
1820       }
1822       if ($this->acl_is_writeable($val) && isset ($_POST["$val"])){
1824         /* Check for modifications */
1825         if (get_magic_quotes_gpc()) {
1826           $data= stripcslashes($_POST["$val"]);
1827         } else {
1828           $data= $this->$val = $_POST["$val"];
1829         }
1830         if ($this->$val != $data){
1831           $this->is_modified= TRUE;
1832         }
1833     
1834         /* IE post fix */
1835         if(isset($data[0]) && $data[0] == chr(194)) {
1836           $data = "";  
1837         }
1838         $this->$val= $data;
1839       }
1840     }
1841   }
1844   /*! \brief Returns all attributes of this plugin, 
1845                to be able to detect multiple used attributes 
1846                in multi_plugg::detect_multiple_used_attributes().
1847       @return array Attributes required for intialization of multi_plug
1848    */
1849   public function get_multi_init_values()
1850   {
1851     $attrs = $this->attrs;
1852     return($attrs);
1853   }
1856   /*! \brief  Check given values in multiple edit
1857       \return array Error messages
1858    */
1859   function multiple_check()
1860   {
1861     $message = plugin::check();
1862     return($message);
1863   }
1866   /*! \brief  Returns the snapshot header part for "Actions" menu in management dialogs 
1867       \param  $layer_menu  
1868    */   
1869   function get_snapshot_header($base,$category)
1870   {
1871     $str = "";
1872     $ui = get_userinfo();
1873     if($this->snapshotEnabled() && $ui->allow_snapshot_restore($base,$category)){
1875       $ok = false;
1876       foreach($this->get_used_snapshot_bases() as $base){
1877         $ok |= count($this->getAllDeletedSnapshots($base)) >= 1 ;
1878       }
1880       if($ok){
1881         $str = "..|<img class='center' src='images/lists/restore.png' ".
1882           "alt='"._("Restore")."'>&nbsp;"._("Restore").                       "|RestoreDeletedSnapShot|\n";
1883       }else{
1884         $str = "..|<img class='center' src='images/lists/restore_grey.png' alt=''>&nbsp;"._("Restore")."||\n";
1885       }
1886     }
1887     return($str);
1888   }
1891   function get_snapshot_action($base,$category)
1892   {
1893     $str= ""; 
1894     $ui = get_userinfo();
1895     if($this->snapshotEnabled()){
1896       if ($ui->allow_snapshot_restore($base,$category)){
1898         if(count($this->Available_SnapsShots($base))){
1899           $str.= "<input class='center' type='image' src='images/lists/restore.png'
1900             alt='"._("Restore snapshot")."' name='RestoreSnapShotDialog_".base64_encode($base)."' title='"._("Restore snapshot")."'>&nbsp;";
1901         } else {
1902           $str = "<img class='center' src='images/lists/restore_grey.png' alt=''>&nbsp;";
1903         }
1904       }
1905       if($ui->allow_snapshot_create($base,$category)){
1906         $str.= "<input class='center' type='image' src='images/snapshot.png'
1907           alt='"._("Create snapshot")."' name='CreateSnapShotDialog_".base64_encode($base)."' 
1908           title='"._("Create a new snapshot from this object")."'>&nbsp;";
1909       }else{
1910         $str = "<img class='center' src='images/empty.png' alt=' '>&nbsp;";
1911       }
1912     }
1914     return($str);
1915   }
1918   function get_copypaste_action($base,$category,$class,$copy = TRUE, $cut = TRUE)
1919   {
1920     $ui = get_userinfo();
1921     $action = "";
1922     if($this->CopyPasteHandler){
1923       if($cut){
1924         if($ui->is_cutable($base,$category,$class)){
1925           $action .= "<input class='center' type='image'
1926             src='images/lists/cut.png' alt='"._("cut")."' name='cut_%KEY%' title='"._("Cut this entry")."'>&nbsp;";
1927         }else{
1928           $action.="<img src='images/empty.png' alt=' ' class='center'>&nbsp;";
1929         }
1930       }
1931       if($copy){
1932         if($ui->is_copyable($base,$category,$class)){
1933           $action.= "<input class='center' type='image'
1934             src='images/lists/copy.png' alt='"._("copy")."' name='copy_%KEY%' title='"._("Copy this entry")."'>&nbsp;";
1935         }else{
1936           $action.="<img src='images/empty.png' alt=' ' class='center'>&nbsp;";
1937         }
1938       }
1939     }
1941     return($action); 
1942   }
1945   function get_copypaste_header($base,$category,$copy = TRUE, $cut = TRUE)
1946   {
1947     $s = "";
1948     $ui =get_userinfo();
1950     if(!is_array($category)){
1951       $category = array($category);
1952     }
1954     /* Check permissions for each category, if there is at least one category which 
1955         support read or paste permissions for the given base, then display the specific actions.
1956      */
1957     $readable = $pasteable = false;
1958     foreach($category as $cat){
1959       $readable= $readable || preg_match('/r/', $ui->get_category_permissions($base, $cat));
1960       $pasteable= $pasteable || $ui->is_pasteable($base, $cat) == 1;
1961     }
1962   
1963     if(($cut || $copy) && isset($this->CopyPasteHandler) && is_object($this->CopyPasteHandler)){
1964       if($readable){
1965         $s.= "..|---|\n";
1966         if($copy){
1967           $s.= "..|<img src='images/lists/copy.png' alt='' border='0' class='center'>".
1968             "&nbsp;"._("Copy")."|"."multiple_copy_systems|\n";
1969         }
1970         if($cut){
1971           $s.= "..|<img src='images/lists/cut.png' alt='' border='0' class='center'>".
1972             "&nbsp;"._("Cut")."|"."multiple_cut_systems|\n";
1973         }
1974       }
1976       if($pasteable){
1977         if($this->CopyPasteHandler->entries_queued()){
1978           $img = "<img border='0' class='center' src='images/lists/paste.png' alt=''>";
1979           $s.="..|".$img."&nbsp;"._("Paste")."|editPaste|\n";
1980         }else{
1981           $img = "<img border='0' class='center' src='images/lists/paste-grey.png' alt=''>";
1982           $s.="..|".$img."&nbsp;"._("Paste")."\n";
1983         }
1984       }
1985     }
1986     return($s);
1987   }
1990   function get_used_snapshot_bases()
1991   {
1992      return(array());
1993   }
1995   function is_modal_dialog()
1996   {
1997     return(isset($this->dialog) && $this->dialog);
1998   }
2001   /*! \brief    Forward command execution request
2002    *             to the correct method. 
2003    */
2004   function handle_post_events($mode, $addAttrs= array())
2005   {
2006     if(!in_array($mode, array('add','remove','modify'))){
2007       trigger_error(sprintf("Invalid post event type given '%s'! Valid types are [add,modify,remove].", $mode));
2008       return;
2009     }
2010     switch ($mode){
2011       case "add":
2012         plugin::callHook($this,"POSTCREATE", $addAttrs);
2013       break;
2015       case "modify":
2016         plugin::callHook($this,"POSTMODIFY", $addAttrs);
2017       break;
2019       case "remove":
2020         plugin::callHook($this,"POSTREMOVE", $addAttrs);
2021       break;
2022     }
2023   }
2026   /*! \brief    Calls external hooks which are defined for this plugin (gosa.conf)
2027    *            Replaces placeholder by class values of this plugin instance.
2028    *  @param    Allows to a add special replacements.
2029    */
2030   static function callHook($plugin, $cmd, $addAttrs= array())
2031   {
2032     global $config;
2033     $command= $config->search(get_class($plugin), $cmd,array('menu','tabs'));
2034     if ($command != ""){
2036       // Walk trough attributes list and add the plugins attributes. 
2037       foreach ($plugin->attributes as $attr){
2038         if (!is_array($plugin->$attr)){
2039           $addAttrs[$attr] = $plugin->$attr;
2040         }
2041       }
2042       $ui = get_userinfo();
2043       $addAttrs['callerDN']=$ui->dn;
2044       $addAttrs['dn']=$plugin->dn;
2046       // Sort attributes by length, ensures correct replacement
2047       $tmp = array();
2048       foreach($addAttrs as $name => $value){
2049         $tmp[$name] =  strlen($name);
2050       }
2051       arsort($tmp);
2053       // Now replace the placeholder 
2054       foreach ($tmp as $name => $len){
2055         $value = $addAttrs[$name];
2056         $command= str_replace("%$name", "$value", $command);
2057       }
2059       // If there are still some %.. in our command, try to fill these with some other class vars 
2060       if(preg_match("/%/",$command)){
2061         $attrs = get_object_vars($plugin);
2062         foreach($attrs as $name => $value){
2063           if(is_array($value)){
2064             $s = "";
2065             foreach($value as $val){
2066               if(is_string($val) || is_int($val) || is_float($val) || is_bool($val)){
2067                 $s .= '"'.$val.'",'; 
2068               }
2069             }
2070             $value = '['.trim($s,',').']';
2071           }
2072           if(!is_string($value) && !is_int($value) && !is_float($value) && !is_bool($value)){
2073             continue;
2074           }
2075           $command= preg_replace("/%$name/", $value, $command);
2076         }
2077       }
2079       if (check_command($command)){
2080         @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,$command,"Execute");
2081         exec($command,$arr);
2082         if(is_array($arr)){
2083           $str = implode("\n",$arr);
2084           @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Result: ".$str);
2085         }
2086       } else {
2087         $message= msgPool::cmdnotfound("POSTCREATE", get_class($plugin));
2088         msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
2089       }
2090     }
2091   }
2094 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
2095 ?>