Code

Updated class filter and listing.
[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 = "<div class='plugin-enable-header'>\n";
592     $display.= "<p>$text</p>\n";
593     $display.= "<button type='submit' name=\"modify_state\" ".$state.">$button_text</button>\n";
594     $display.= "</div>\n";
596     return($display);
597   }
600   /*! \brief Show header message for tab dialogs */
601   function show_disable_header($button_text, $text, $disabled= FALSE)
602   {
603     if (($disabled == TRUE) || !$this->acl_is_removeable()){
604       $state= "disabled";
605     } else {
606       $state= "";
607     }
608     $display = "<div class='plugin-disable-header'>\n";
609     $display.= "<p>$text</p>\n";
610     $display.= "<button type='submit' name=\"modify_state\" ".$state.">$button_text</button>\n";
611     $display.= "</div>\n";
612     return($display);
613   }
617   /* Create unique DN */
618   function create_unique_dn2($data, $base)
619   {
620     $ldap= $this->config->get_ldap_link();
621     $base= preg_replace("/^,*/", "", $base);
623     /* Try to use plain entry first */
624     $dn= "$data,$base";
625     $attribute= preg_replace('/=.*$/', '', $data);
626     $ldap->cat ($dn, array('dn'));
627     if (!$ldap->fetch()){
628       return ($dn);
629     }
631     /* Look for additional attributes */
632     foreach ($this->attributes as $attr){
633       if ($attr == $attribute || $this->$attr == ""){
634         continue;
635       }
637       $dn= "$data+$attr=".$this->$attr.",$base";
638       $ldap->cat ($dn, array('dn'));
639       if (!$ldap->fetch()){
640         return ($dn);
641       }
642     }
644     /* None found */
645     return ("none");
646   }
649   /*! \brief Create unique DN */
650   function create_unique_dn($attribute, $base)
651   {
652     $ldap= $this->config->get_ldap_link();
653     $base= preg_replace("/^,*/", "", $base);
655     /* Try to use plain entry first */
656     $dn= "$attribute=".$this->$attribute.",$base";
657     $ldap->cat ($dn, array('dn'));
658     if (!$ldap->fetch()){
659       return ($dn);
660     }
662     /* Look for additional attributes */
663     foreach ($this->attributes as $attr){
664       if ($attr == $attribute || $this->$attr == ""){
665         continue;
666       }
668       $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
669       $ldap->cat ($dn, array('dn'));
670       if (!$ldap->fetch()){
671         return ($dn);
672       }
673     }
675     /* None found */
676     return ("none");
677   }
680   function rebind($ldap, $referral)
681   {
682     $credentials= LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
683     if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
684       $this->error = "Success";
685       $this->hascon=true;
686       $this->reconnect= true;
687       return (0);
688     } else {
689       $this->error = "Could not bind to " . $credentials['ADMIN'];
690       return NULL;
691     }
692   }
695   /* Recursively copy ldap object */
696   function _copy($src_dn,$dst_dn)
697   {
698     $ldap=$this->config->get_ldap_link();
699     $ldap->cat($src_dn);
700     $attrs= $ldap->fetch();
702     /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
703     $ds= ldap_connect($this->config->current['SERVER']);
704     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
705     if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
706       ldap_set_rebind_proc($ds, array(&$this, "rebind"));
707     }
709     $pwd = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
710     $r=ldap_bind($ds,$this->config->current['ADMINDN'], $pwd);
711     $sr=ldap_read($ds, LDAP::fix($src_dn), "objectClass=*");
713     /* Fill data from LDAP */
714     $new= array();
715     if ($sr) {
716       $ei=ldap_first_entry($ds, $sr);
717       if ($ei) {
718         foreach($attrs as $attr => $val){
719           if ($info = @ldap_get_values_len($ds, $ei, $attr)){
720             for ($i= 0; $i<$info['count']; $i++){
721               if ($info['count'] == 1){
722                 $new[$attr]= $info[$i];
723               } else {
724                 $new[$attr][]= $info[$i];
725               }
726             }
727           }
728         }
729       }
730     }
732     /* close conncetion */
733     ldap_unbind($ds);
735     /* Adapt naming attribute */
736     $dst_name= preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
737     $dst_val = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
738     $new[$dst_name]= LDAP::fix($dst_val);
740     /* Check if this is a department.
741      * If it is a dep. && there is a , override in his ou 
742      *  change \2C to , again, else this entry can't be saved ...
743      */
744     if((isset($new['ou'])) &&( preg_match("/\\,/",$new['ou']))){
745       $new['ou'] = str_replace("\\\\,",",",$new['ou']);
746     }
748     /* Save copy */
749     $ldap->connect();
750     $ldap->cd($this->config->current['BASE']);
751     
752     $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
754     /* FAIvariable=.../..., cn=.. 
755         could not be saved, because the attribute FAIvariable was different to 
756         the dn FAIvariable=..., cn=... */
758     if(!is_array($new['objectClass'])) $new['objectClass'] = array($new['objectClass']);
760     if(in_array_ics("FAIdebconfInfo",$new['objectClass'])){
761       $new['FAIvariable'] = $ldap->fix($new['FAIvariable']);
762     }
763     $ldap->cd($dst_dn);
764     $ldap->add($new);
766     if (!$ldap->success()){
767       trigger_error("Trying to save $dst_dn failed.",
768           E_USER_WARNING);
769       return(FALSE);
770     }
771     return(TRUE);
772   }
775   /* This is a workaround function. */
776   function copy($src_dn, $dst_dn)
777   {
778     /* Rename dn in possible object groups */
779     $ldap= $this->config->get_ldap_link();
780     $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.@LDAP::prepare4filter($src_dn).'))',
781         array('cn'));
782     while ($attrs= $ldap->fetch()){
783       $og= new ogroup($this->config, $ldap->getDN());
784       unset($og->member[$src_dn]);
785       $og->member[$dst_dn]= $dst_dn;
786       $og->save ();
787     }
789     $ldap->cat($dst_dn);
790     $attrs= $ldap->fetch();
791     if (count($attrs)){
792       trigger_error("Trying to overwrite ".LDAP::fix($dst_dn).", which already exists.",
793           E_USER_WARNING);
794       return (FALSE);
795     }
797     $ldap->cat($src_dn);
798     $attrs= $ldap->fetch();
799     if (!count($attrs)){
800       trigger_error("Trying to move ".LDAP::fix($src_dn).", which does not seem to exist.",
801           E_USER_WARNING);
802       return (FALSE);
803     }
805     $ldap->cd($src_dn);
806     $ldap->search("objectClass=*",array("dn"));
807     while($attrs = $ldap->fetch()){
808       $src = $attrs['dn'];
809       $dst = preg_replace("/".preg_quote($src_dn, '/')."$/",$dst_dn,$attrs['dn']);
810       $this->_copy($src,$dst);
811     }
812     return (TRUE);
813   }
817   /*! \brief  Rename/Move a given src_dn to the given dest_dn
818    *
819    * Move a given ldap object indentified by $src_dn to the
820    * given destination $dst_dn
821    *
822    * - Ensure that all references are updated (ogroups)
823    * - Update ACLs   
824    * - Update accessTo
825    *
826    * \param  string  'src_dn' the source DN.
827    * \param  string  'dst_dn' the destination DN.
828    * \return boolean TRUE on success else FALSE.
829    */
830   function rename($src_dn, $dst_dn)
831   {
832     $start = microtime(1);
834     /* Try to move the source entry to the destination position */
835     $ldap = $this->config->get_ldap_link();
836     $ldap->cd($this->config->current['BASE']);
837     $ldap->create_missing_trees(preg_replace("/^[^,]+,/","",$dst_dn));
838     if (!$ldap->rename_dn($src_dn,$dst_dn)){
839 #      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $src_dn, "", get_class()));
840       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());
841       @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,"Rename failed FROM: $src_dn  -- TO:  $dst_dn", 
842           "Ldap Protocol v3 implementation error, falling back to maunal method.");
843       return(FALSE);
844     }
846     /* Get list of users,groups and roles within this tree,
847         maybe we have to update ACL references.
848      */
849     $leaf_objs = get_list("(|(objectClass=posixGroup)(objectClass=gosaAccount)(objectClass=gosaRole))",array("all"),$dst_dn,
850           array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
851     foreach($leaf_objs as $obj){
852       $new_dn = $obj['dn'];
853       $old_dn = preg_replace("/".preg_quote(LDAP::convert($dst_dn), '/')."$/i",$src_dn,LDAP::convert($new_dn));
854       $this->update_acls($old_dn,$new_dn); 
855     }
857     // Migrate objectgroups if needed
858     $ogroups = get_sub_list("(&(objectClass=gosaGroupOfNames)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))",
859       "ogroups", array(get_ou("ogroupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
861     // Walk through all objectGroups
862     foreach($ogroups as $ogroup){
863       // Migrate old to new dn
864       $o_ogroup= new ogroup($this->config,$ogroup['dn']);
865       if (isset($o_ogroup->member[$src_dn])) {
866         unset($o_ogroup->member[$src_dn]);
867       }
868       $o_ogroup->member[$dst_dn]= $dst_dn;
869       
870       // Save object group
871       $o_ogroup->save();
872     }
874     // Migrate rfc groups if needed
875     $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);
877     // Walk through all POSIX groups
878     foreach($groups as $group){
880       // Migrate old to new dn
881       $o_group= new group($this->config,$group['dn']);
882       $o_group->save();
883     }
885     /* Update roles to use the new entry dn */
886     $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);
888     // Walk through all roles
889     foreach($roles as $role){
890       $role = new roleGeneric($this->config,$role['dn']);
891       $key= array_search($src_dn, $role->roleOccupant);      
892       if($key !== FALSE){
893         $role->roleOccupant[$key] = $dst_dn;
894         $role->save();
895       }
896     }
898     // Update 'manager' attributes from gosaDepartment and inetOrgPerson 
899     $filter = "(&(objectClass=inetOrgPerson)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn))."))";
900     $ocs = $ldap->get_objectclasses();
901     if(isset($ocs['gosaDepartment']['MAY']) && in_array('manager', $ocs['gosaDepartment']['MAY'])){
902       $filter = "(|".$filter."(&(objectClass=gosaDepartment)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn)).")))";
903     }
904     $leaf_deps=  get_list($filter,array("all"),$this->config->current['BASE'], 
905         array("manager","dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
906     foreach($leaf_deps as $entry){
907       $update = array('manager' => $dst_dn);
908       $ldap->cd($entry['dn']);
909       $ldap->modify($update);
910       if(!$ldap->success()){
911         trigger_error(sprintf("Failed to update manager for '%s', error was '%s'", $entry['dn'], $ldap->get_error()));
912       }
913     }
914  
915     /* Check if there are gosa departments moved. 
916        If there were deps moved, the force reload of config->deps.
917      */
918     $leaf_deps=  get_list("(objectClass=gosaDepartment)",array("all"),$dst_dn,
919           array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
920   
921     if(count($leaf_deps)){
922       $this->config->get_departments();
923       $this->config->make_idepartments();
924       session::global_set("config",$this->config);
925       $ui =get_userinfo();
926       $ui->reset_acl_cache();
927     }
929     return(TRUE); 
930   }
933  
934   function move($src_dn, $dst_dn)
935   {
936     /* Do not copy if only upper- lowercase has changed */
937     if(strtolower($src_dn) == strtolower($dst_dn)){
938       return(TRUE);
939     }
941     
942     /* Try to move the entry instead of copy & delete
943      */
944     if(TRUE){
946       /* Try to move with ldap routines, if this was not successfull
947           fall back to the old style copy & remove method 
948        */
949       if($this->rename($src_dn, $dst_dn)){
950         return(TRUE);
951       }else{
952         // See code below.
953       }
954     }
956     /* Copy source to destination */
957     if (!$this->copy($src_dn, $dst_dn)){
958       return (FALSE);
959     }
961     /* Delete source */
962     $ldap= $this->config->get_ldap_link();
963     $ldap->rmdir_recursive($src_dn);
964     if (!$ldap->success()){
965       trigger_error("Trying to delete $src_dn failed.",
966           E_USER_WARNING);
967       return (FALSE);
968     }
970     return (TRUE);
971   }
974   /* \brief Move/Rename complete trees */
975   function recursive_move($src_dn, $dst_dn)
976   {
977     /* Check if the destination entry exists */
978     $ldap= $this->config->get_ldap_link();
980     /* Check if destination exists - abort */
981     $ldap->cat($dst_dn, array('dn'));
982     if ($ldap->fetch()){
983       trigger_error("recursive_move $dst_dn already exists.",
984           E_USER_WARNING);
985       return (FALSE);
986     }
988     $this->copy($src_dn, $dst_dn);
990     /* Remove src_dn */
991     $ldap->cd($src_dn);
992     $ldap->recursive_remove($src_dn);
993     return (TRUE);
994   }
997   function saveCopyDialog(){
998   }
1001   function getCopyDialog(){
1002     return(array("string"=>"","status"=>""));
1003   }
1006   /*! \brief Prepare for Copy & Paste */
1007   function PrepareForCopyPaste($source)
1008   {
1009     $todo = $this->attributes;
1010     if(isset($this->CopyPasteVars)){
1011       $todo = array_merge($todo,$this->CopyPasteVars);
1012     }
1014     if(count($this->objectclasses)){
1015       $this->is_account = TRUE;
1016       foreach($this->objectclasses as $class){
1017         if(!in_array($class,$source['objectClass'])){
1018           $this->is_account = FALSE;
1019         }
1020       }
1021     }
1023     foreach($todo as $var){
1024       if (isset($source[$var])){
1025         if(isset($source[$var]['count'])){
1026           if($source[$var]['count'] > 1){
1027             $tmp= $source[$var];
1028             unset($tmp['count']);
1029             $this->$var = $tmp;
1030           }else{
1031             $this->$var = $source[$var][0];
1032           }
1033         }else{
1034           $this->$var= $source[$var];
1035         }
1036       }
1037     }
1038   }
1040   /*! \brief Get gosaUnitTag for the given DN
1041        If this is called from departmentGeneric, we have to skip this
1042         tagging procedure. 
1043     */
1044   function tag_attrs(&$at, $dn= "", $tag= "", $show= false)
1045   {
1046     /* Skip tagging? */
1047     if($this->skipTagging){
1048       return;
1049     }
1051     /* No dn? Self-operation... */
1052     if ($dn == ""){
1053       $dn= $this->dn;
1055       /* No tag? Find it yourself... */
1056       if ($tag == ""){
1057         $len= strlen($dn);
1059         @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "No tag for $dn - looking for one...", "Tagging");
1060         $relevant= array();
1061         foreach ($this->config->adepartments as $key => $ntag){
1063           /* This one is bigger than our dn, its not relevant... */
1064           if ($len < strlen($key)){
1065             continue;
1066           }
1068           /* This one matches with the latter part. Break and don't fix this entry */
1069           if (preg_match('/(^|,)'.preg_quote($key, '/').'$/', $dn)){
1070             @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "DEBUG: Possibly relevant: $key", "Tagging");
1071             $relevant[strlen($key)]= $ntag;
1072             continue;
1073           }
1075         }
1077         /* If we've some relevant tags to set, just get the longest one */
1078         if (count($relevant)){
1079           ksort($relevant);
1080           $tmp= array_keys($relevant);
1081           $idx= end($tmp);
1082           $tag= $relevant[$idx];
1083           $this->gosaUnitTag= $tag;
1084         }
1085       }
1086     }
1088   /*! \brief Add unit tag */ 
1089     /* Remove tags that may already be here... */
1090     remove_objectClass("gosaAdministrativeUnitTag", $at);
1091     if (isset($at['gosaUnitTag'])){
1092         unset($at['gosaUnitTag']);
1093     }
1095     /* Set tag? */
1096     if ($tag != ""){
1097       add_objectClass("gosaAdministrativeUnitTag", $at);
1098       $at['gosaUnitTag']= $tag;
1099     }
1101     /* Initially this object was tagged. 
1102        - But now, it is no longer inside a tagged department. 
1103        So force the remove of the tag.
1104        (objectClass was already removed obove)
1105      */
1106     if($tag == "" && $this->gosaUnitTag){
1107       $at['gosaUnitTag'] = array();
1108     }
1109   }
1112   /*! \brief Test for removability of the object
1113    *
1114    * Allows testing of conditions for removal of object. If removal should be aborted
1115    * the function needs to remove an error message.
1116    * */
1117   function allow_remove()
1118   {
1119     $reason= "";
1120     return $reason;
1121   }
1124   /*! \brief Create a snapshot of the current object */
1125   function create_snapshot($type= "snapshot", $description= array())
1126   {
1128     /* Check if snapshot functionality is enabled */
1129     if(!$this->snapshotEnabled()){
1130       return;
1131     }
1133     /* Get configuration from gosa.conf */
1134     $config = $this->config;
1136     /* Create lokal ldap connection */
1137     $ldap= $this->config->get_ldap_link();
1138     $ldap->cd($this->config->current['BASE']);
1140     /* check if there are special server configurations for snapshots */
1141     if($config->get_cfg_value("snapshotURI") == ""){
1143       /* Source and destination server are both the same, just copy source to dest obj */
1144       $ldap_to      = $ldap;
1145       $snapldapbase = $this->config->current['BASE'];
1147     }else{
1148       $server         = $config->get_cfg_value("snapshotURI");
1149       $user           = $config->get_cfg_value("snapshotAdminDn");
1150       $password       = $this->config->get_credentials($config->get_cfg_value("snapshotAdminPassword"));
1151       $snapldapbase   = $config->get_cfg_value("snapshotBase");
1153       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1154       $ldap_to -> cd($snapldapbase);
1156       if (!$ldap_to->success()){
1157         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1158       }
1160     }
1162     /* check if the dn exists */ 
1163     if ($ldap->dn_exists($this->dn)){
1165       /* Extract seconds & mysecs, they are used as entry index */
1166       list($usec, $sec)= explode(" ", microtime());
1168       /* Collect some infos */
1169       $base           = $this->config->current['BASE'];
1170       $snap_base      = $config->get_cfg_value("snapshotBase");
1171       $base_of_object = preg_replace ('/^[^,]+,/i', '', $this->dn);
1172       $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
1174       /* Create object */
1175 #$data             = preg_replace('/^dn:.*\n/', '', $ldap->gen_ldif($this->dn,"(!(objectClass=gosaDepartment))"));
1176       $data             = $ldap->gen_ldif($this->dn,"(&(!(objectClass=gosaDepartment))(!(objectClass=FAIclass)))");
1177       $newName          = str_replace(".", "", $sec."-".$usec);
1178       $target= array();
1179       $target['objectClass']            = array("top", "gosaSnapshotObject");
1180       $target['gosaSnapshotData']       = gzcompress($data, 6);
1181       $target['gosaSnapshotType']       = $type;
1182       $target['gosaSnapshotDN']         = $this->dn;
1183       $target['description']            = $description;
1184       $target['gosaSnapshotTimestamp']  = $newName;
1186       /* Insert the new snapshot 
1187          But we have to check first, if the given gosaSnapshotTimestamp
1188          is already used, in this case we should increment this value till there is 
1189          an unused value. */ 
1190       $new_dn                           = "gosaSnapshotTimestamp=".$newName.",".$new_base;
1191       $ldap_to->cat($new_dn);
1192       while($ldap_to->count()){
1193         $ldap_to->cat($new_dn);
1194         $newName = str_replace(".", "", $sec."-".($usec++));
1195         $new_dn                           = "gosaSnapshotTimestamp=".$newName.",".$new_base;
1196         $target['gosaSnapshotTimestamp']  = $newName;
1197       } 
1199       /* Inset this new snapshot */
1200       $ldap_to->cd($snapldapbase);
1201       $ldap_to->create_missing_trees($snapldapbase);
1202       $ldap_to->create_missing_trees($new_base);
1203       $ldap_to->cd($new_dn);
1204       $ldap_to->add($target);
1205       if (!$ldap_to->success()){
1206         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $new_dn, LDAP_ADD, get_class()));
1207       }
1209       if (!$ldap->success()){
1210         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $new_base, "", get_class()));
1211       }
1213     }
1214   }
1216   /*! \brief Remove a snapshot */
1217   function remove_snapshot($dn)
1218   {
1219     $ui       = get_userinfo();
1220     $old_dn   = $this->dn; 
1221     $this->dn = $dn;
1222     $ldap = $this->config->get_ldap_link();
1223     $ldap->cd($this->config->current['BASE']);
1224     $ldap->rmdir_recursive($this->dn);
1225     if(!$ldap->success()){
1226       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn));
1227     }
1228     $this->dn = $old_dn;
1229   }
1232   /*! \brief Test if snapshotting is enabled
1233    *
1234    * Test weither snapshotting is enabled or not. There will also be some errors posted,
1235    * if the configuration failed 
1236    * \return TRUE if snapshots are enabled, and FALSE if it is disabled
1237    */
1238   function snapshotEnabled()
1239   {
1240     return $this->config->snapshotEnabled();
1241   }
1244   /* \brief Return available snapshots for the given base */
1245   function Available_SnapsShots($dn,$raw = false)
1246   {
1247     if(!$this->snapshotEnabled()) return(array());
1249     /* Create an additional ldap object which
1250        points to our ldap snapshot server */
1251     $ldap= $this->config->get_ldap_link();
1252     $ldap->cd($this->config->current['BASE']);
1253     $cfg= &$this->config->current;
1255     /* check if there are special server configurations for snapshots */
1256     if($this->config->get_cfg_value("snapshotURI") == ""){
1257       $ldap_to      = $ldap;
1258     }else{
1259       $server         = $this->config->get_cfg_value("snapshotURI");
1260       $user           = $this->config->get_cfg_value("snapshotAdminDn");
1261       $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
1262       $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
1263       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1264       $ldap_to -> cd($snapldapbase);
1265       if (!$ldap_to->success()){
1266         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1267       }
1268     }
1270     /* Prepare bases and some other infos */
1271     $base           = $this->config->current['BASE'];
1272     $snap_base      = $this->config->get_cfg_value("snapshotBase");
1273     $base_of_object = preg_replace ('/^[^,]+,/i', '', $dn);
1274     $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
1275     $tmp            = array(); 
1277     /* Fetch all objects with  gosaSnapshotDN=$dn */
1278     $ldap_to->cd($new_base);
1279     $ldap_to->ls("(&(objectClass=gosaSnapshotObject)(gosaSnapshotDN=".$dn."))",$new_base,
1280         array("gosaSnapshotType","gosaSnapshotTimestamp","gosaSnapshotDN","description")); 
1282     /* Put results into a list and add description if missing */
1283     while($entry = $ldap_to->fetch()){ 
1284       if(!isset($entry['description'][0])){
1285         $entry['description'][0]  = "";
1286       }
1287       $tmp[] = $entry; 
1288     }
1290     /* Return the raw array, or format the result */
1291     if($raw){
1292       return($tmp);
1293     }else{  
1294       $tmp2 = array();
1295       foreach($tmp as $entry){
1296         $tmp2[base64_encode($entry['dn'])] = $entry['description'][0]; 
1297       }
1298     }
1299     return($tmp2);
1300   }
1303   function getAllDeletedSnapshots($base_of_object,$raw = false)
1304   {
1305     if(!$this->snapshotEnabled()) return(array());
1307     /* Create an additional ldap object which
1308        points to our ldap snapshot server */
1309     $ldap= $this->config->get_ldap_link();
1310     $ldap->cd($this->config->current['BASE']);
1311     $cfg= &$this->config->current;
1313     /* check if there are special server configurations for snapshots */
1314     if($this->config->get_cfg_value("snapshotURI") == ""){
1315       $ldap_to      = $ldap;
1316     }else{
1317       $server         = $this->config->get_cfg_value("snapshotURI");
1318       $user           = $this->config->get_cfg_value("snapshotAdminDn");
1319       $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
1320       $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
1321       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1322       $ldap_to -> cd($snapldapbase);
1323       if (!$ldap_to->success()){
1324         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1325       }
1326     }
1328     /* Prepare bases */ 
1329     $base           = $this->config->current['BASE'];
1330     $snap_base      = $this->config->get_cfg_value("snapshotBase");
1331     $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
1333     /* Fetch all objects and check if they do not exist anymore */
1334     $ui = get_userinfo();
1335     $tmp = array();
1336     $ldap_to->cd($new_base);
1337     $ldap_to->ls("(objectClass=gosaSnapshotObject)",$new_base,array("gosaSnapshotType","gosaSnapshotTimestamp","gosaSnapshotDN","description"));
1338     while($entry = $ldap_to->fetch()){
1340       $chk =  str_replace($new_base,"",$entry['dn']);
1341       if(preg_match("/,ou=/",$chk)) continue;
1343       if(!isset($entry['description'][0])){
1344         $entry['description'][0]  = "";
1345       }
1346       $tmp[] = $entry; 
1347     }
1349     /* Check if entry still exists */
1350     foreach($tmp as $key => $entry){
1351       $ldap->cat($entry['gosaSnapshotDN'][0]);
1352       if($ldap->count()){
1353         unset($tmp[$key]);
1354       }
1355     }
1357     /* Format result as requested */
1358     if($raw) {
1359       return($tmp);
1360     }else{
1361       $tmp2 = array();
1362       foreach($tmp as $key => $entry){
1363         $tmp2[base64_encode($entry['dn'])] = $entry['description'][0]; 
1364       }
1365     }
1366     return($tmp2);
1367   } 
1370   /* \brief Restore selected snapshot */
1371   function restore_snapshot($dn)
1372   {
1373     if(!$this->snapshotEnabled()) return(array());
1375     $ldap= $this->config->get_ldap_link();
1376     $ldap->cd($this->config->current['BASE']);
1377     $cfg= &$this->config->current;
1379     /* check if there are special server configurations for snapshots */
1380     if($this->config->get_cfg_value("snapshotURI") == ""){
1381       $ldap_to      = $ldap;
1382     }else{
1383       $server         = $this->config->get_cfg_value("snapshotURI");
1384       $user           = $this->config->get_cfg_value("snapshotAdminDn");
1385       $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
1386       $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
1387       $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
1388       $ldap_to -> cd($snapldapbase);
1389       if (!$ldap_to->success()){
1390         msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
1391       }
1392     }
1394     /* Get the snapshot */ 
1395     $ldap_to->cat($dn);
1396     $restoreObject = $ldap_to->fetch();
1398     /* Prepare import string */
1399     $data  = gzuncompress($ldap_to->get_attribute($dn,'gosaSnapshotData'));
1401     /* Import the given data */
1402     $err = "";
1403     $ldap->import_complete_ldif($data,$err,false,false);
1404     if (!$ldap->success()){
1405       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, "", get_class()));
1406     }
1407   }
1410   function showSnapshotDialog($base,$baseSuffixe,&$parent)
1411   {
1412     $once = true;
1413     $ui = get_userinfo();
1414     $this->parent = $parent;
1416     foreach($_POST as $name => $value){
1418       /* Create a new snapshot, display a dialog */
1419       if(preg_match("/^CreateSnapShotDialog_[^_]*_[xy]$/",$name) && $once){
1421                           $entry = base64_decode(preg_replace("/^CreateSnapShotDialog_([^_]*)_[xy]$/","\\1",$name));
1422         $once = false;
1423         $entry = preg_replace("/^CreateSnapShotDialog_/","",$entry);
1425         if(!empty($entry) && $ui->allow_snapshot_create($entry,$this->parent->acl_module)){
1426           $this->snapDialog = new SnapShotDialog($this->config,$entry,$this);
1427         }else{
1428           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to create a snapshot for %s."),$entry),ERROR_DIALOG);
1429         }
1430       }  
1431   
1432       /* Restore a snapshot, display a dialog with all snapshots of the current object */
1433       if(preg_match("/^RestoreSnapShotDialog_/",$name) && $once){
1434         $once = false;
1435         $entry = base64_decode(preg_replace("/^RestoreSnapShotDialog_([^_]*)_[xy]$/i","\\1",$name));
1436         if(!empty($entry) && $ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
1437           $this->snapDialog = new SnapShotDialog($this->config,$entry,$this);
1438           $this->snapDialog->display_restore_dialog = true;
1439         }else{
1440           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$entry),ERROR_DIALOG);
1441         }
1442       }
1444       /* Restore one of the already deleted objects */
1445       if(((isset($_POST['menu_action']) && $_POST['menu_action'] == "RestoreDeletedSnapShot") 
1446           || preg_match("/^RestoreDeletedSnapShot_/",$name)) && $once){
1447         $once = false;
1449         if($ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
1450           $this->snapDialog = new SnapShotDialog($this->config,"",$this);
1451           $this->snapDialog->set_snapshot_bases($baseSuffixe);
1452           $this->snapDialog->display_restore_dialog      = true;
1453           $this->snapDialog->display_all_removed_objects  = true;
1454         }else{
1455           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$base),ERROR_DIALOG);
1456         }
1457       }
1459       /* Restore selected snapshot */
1460       if(preg_match("/^RestoreSnapShot_/",$name) && $once){
1461         $once = false;
1462         $entry = base64_decode(preg_replace("/^RestoreSnapShot_([^_]*)_[xy]$/i","\\1",$name));
1464         if(!empty($entry) && $ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
1465           $this->restore_snapshot($entry);
1466           $this->snapDialog = NULL;
1467         }else{
1468           msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$entry),ERROR_DIALOG);
1469         }
1470       }
1471     }
1473     /* Create a new snapshot requested, check
1474        the given attributes and create the snapshot*/
1475     if(isset($_POST['CreateSnapshot']) && is_object($this->snapDialog)){
1476       $this->snapDialog->save_object();
1477       $msgs = $this->snapDialog->check();
1478       if(count($msgs)){
1479         foreach($msgs as $msg){
1480           msg_dialog::display(_("Error"), $msg, ERROR_DIALOG);
1481         }
1482       }else{
1483         $this->dn =  $this->snapDialog->dn;
1484         $this->create_snapshot("snapshot",$this->snapDialog->CurrentDescription);
1485         $this->snapDialog = NULL;
1486       }
1487     }
1489     /* Restore is requested, restore the object with the posted dn .*/
1490     if((isset($_POST['RestoreSnapshot'])) && (isset($_POST['SnapShot']))){
1491     }
1493     if(isset($_POST['CancelSnapshot'])){
1494       $this->snapDialog = NULL;
1495     }
1497     if(is_object($this->snapDialog )){
1498       $this->snapDialog->save_object();
1499       return($this->snapDialog->execute());
1500     }
1501   }
1504   /*! \brief Return plugin informations for acl handling */
1505   static function plInfo()
1506   {
1507     return array();
1508   }
1511   function set_acl_base($base)
1512   {
1513     $this->acl_base= $base;
1514   }
1517   function set_acl_category($category)
1518   {
1519     $this->acl_category= "$category/";
1520   }
1523   function acl_is_writeable($attribute,$skip_write = FALSE)
1524   {
1525     if($this->read_only) return(FALSE);
1526     $ui= get_userinfo();
1527     return preg_match('/w/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute, $skip_write));
1528   }
1531   function acl_is_readable($attribute)
1532   {
1533     $ui= get_userinfo();
1534     return preg_match('/r/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute));
1535   }
1538   function acl_is_createable($base ="")
1539   {
1540     if($this->read_only) return(FALSE);
1541     $ui= get_userinfo();
1542     if($base == "") $base = $this->acl_base;
1543     return preg_match('/c/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1544   }
1547   function acl_is_removeable($base ="")
1548   {
1549     if($this->read_only) return(FALSE);
1550     $ui= get_userinfo();
1551     if($base == "") $base = $this->acl_base;
1552     return preg_match('/d/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1553   }
1556   function acl_is_moveable($base = "")
1557   {
1558     if($this->read_only) return(FALSE);
1559     $ui= get_userinfo();
1560     if($base == "") $base = $this->acl_base;
1561     return preg_match('/m/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1562   }
1565   function acl_have_any_permissions()
1566   {
1567   }
1570   function getacl($attribute,$skip_write= FALSE)
1571   {
1572     $ui= get_userinfo();
1573     $skip_write |= $this->read_only;
1574     return  $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute,$skip_write);
1575   }
1578   /*! \brief Returns a list of all available departments for this object.
1579    * 
1580    * If this object is new, all departments we are allowed to create a new user in
1581    * are returned. If this is an existing object, return all deps. 
1582    * We are allowed to move tis object too.
1583    * \return array [dn] => "..name"  // All deps. we are allowed to act on.
1584   */
1585   function get_allowed_bases()
1586   {
1587     $ui = get_userinfo();
1588     $deps = array();
1590     /* Is this a new object ? Or just an edited existing object */
1591     if(!$this->initially_was_account && $this->is_account){
1592       $new = true;
1593     }else{
1594       $new = false;
1595     }
1597     foreach($this->config->idepartments as $dn => $name){
1598       if($new && $this->acl_is_createable($dn)){
1599         $deps[$dn] = $name;
1600       }elseif(!$new && $this->acl_is_moveable($dn)){
1601         $deps[$dn] = $name;
1602       }
1603     }
1605     /* Add current base */      
1606     if(isset($this->base) && isset($this->config->idepartments[$this->base])){
1607       $deps[$this->base] = $this->config->idepartments[$this->base];
1608     }elseif(strtolower($this->dn) == strtolower($this->config->current['BASE'])){
1610     }else{
1611       trigger_error("Cannot return list of departments, no default base found in class ".get_class($this).". ".$this->base);
1612     }
1613     return($deps);
1614   }
1617   /* This function updates ACL settings if $old_dn was used.
1618    *  \param string 'old_dn' specifies the actually used dn
1619    *  \param string 'new_dn' specifies the destiantion dn
1620    */
1621   function update_acls($old_dn,$new_dn,$output_changes = FALSE)
1622   {
1623     /* Check if old_dn is empty. This should never happen */
1624     if(empty($old_dn) || empty($new_dn)){
1625       trigger_error("Failed to check acl dependencies, wrong dn given.");
1626       return;
1627     }
1629     /* Update userinfo if necessary */
1630     $ui = session::global_get('ui');
1631     if($ui->dn == $old_dn){
1632       $ui->dn = $new_dn;
1633       session::global_set('ui',$ui);
1634       new log("view","acl/".get_class($this),$this->dn,array(),"Updated current object dn from '".$old_dn."' to '".$new_dn."'");
1635     }
1637     /* Object was moved, ensure that all acls will be moved too */
1638     if($new_dn != $old_dn && $old_dn != "new"){
1640       /* get_ldap configuration */
1641       $update = array();
1642       $ldap = $this->config->get_ldap_link();
1643       $ldap->cd ($this->config->current['BASE']);
1644       $ldap->search("(&(objectClass=gosaAcl)(gosaAclEntry=*".base64_encode($old_dn)."*))",array("cn","gosaAclEntry"));
1645       while($attrs = $ldap->fetch()){
1646         $acls = array();
1647         $found = false;
1648         for($i = 0 ; $i <  $attrs['gosaAclEntry']['count'] ; $i ++ ){
1649           $acl_parts = explode(":",$attrs['gosaAclEntry'][$i]);
1651           /* Roles uses antoher data storage order, members are stored int the third part, 
1652              while the members in direct ACL assignments are stored in the second part.
1653            */
1654           $id = ($acl_parts[1] == "role") ? 3 : 2;
1656           /* Update member entries to use $new_dn instead of old_dn
1657            */
1658           $members = explode(",",$acl_parts[$id]);
1659           foreach($members as $key => $member){
1660             $member = base64_decode($member);
1661             if($member == $old_dn){
1662               $members[$key] = base64_encode($new_dn);
1663               $found = TRUE;
1664             }
1665           } 
1667           /* Check if the selected role has to updated
1668            */
1669           if($acl_parts[1] == "role" && $acl_parts[2] == base64_encode($old_dn)){
1670             $acl_parts[2] = base64_encode($new_dn);
1671             $found = TRUE;
1672           }
1674           /* Build new acl string */ 
1675           $acl_parts[$id] = implode($members,",");
1676           $acls[] = implode($acl_parts,":");
1677         }
1679         /* Acls for this object must be adjusted */
1680         if($found){
1682           $debug_info=  _("Changing ACL dn")."&nbsp;:&nbsp;<br>&nbsp;-"._("from")."&nbsp;<b>&nbsp;".
1683             $old_dn."</b><br>&nbsp;-"._("to")."&nbsp;<b>".$new_dn."</b><br>";
1684           @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,$debug_info,"ACL");
1686           $update[$attrs['dn']] =array();
1687           foreach($acls as $acl){
1688             $update[$attrs['dn']]['gosaAclEntry'][] = $acl;
1689           }
1690         }
1691       }
1693       /* Write updated acls */
1694       foreach($update as $dn => $attrs){
1695         $ldap->cd($dn);
1696         $ldap->modify($attrs);
1697       }
1698     }
1699   }
1701   
1703   /*! \brief Enable the Serial ID check
1704    *
1705    * This function enables the entry Serial ID check.  If an entry was edited while
1706    * we have edited the entry too, an error message will be shown. 
1707    * To configure this check correctly read the FAQ.
1708    */    
1709   function enable_CSN_check()
1710   {
1711     $this->CSN_check_active =TRUE;
1712     $this->entryCSN = getEntryCSN($this->dn);
1713   }
1716   /*! \brief  Prepares the plugin to be used for multiple edit
1717    *          Update plugin attributes with given array of attribtues.
1718    *  \param  array   Array with attributes that must be updated.
1719    */
1720   function init_multiple_support($attrs,$all)
1721   {
1722     $ldap= $this->config->get_ldap_link();
1723     $this->multi_attrs    = $attrs;
1724     $this->multi_attrs_all= $all;
1726     /* Copy needed attributes */
1727     foreach ($this->attributes as $val){
1728       $found= array_key_ics($val, $this->multi_attrs);
1729  
1730       if ($found != ""){
1731         if(isset($this->multi_attrs["$val"][0])){
1732           $this->$val= $this->multi_attrs["$val"][0];
1733         }
1734       }
1735     }
1736   }
1738  
1739   /*! \brief  Enables multiple support for this plugin
1740    */
1741   function enable_multiple_support()
1742   {
1743     $this->ignore_account = TRUE;
1744     $this->multiple_support_active = TRUE;
1745   }
1748   /*! \brief  Returns all values that have been modfied in multiple edit mode.
1749       \return array Cotaining all modified values. 
1750    */
1751   function get_multi_edit_values()
1752   {
1753     $ret = array();
1754     foreach($this->attributes as $attr){
1755       if(in_array($attr,$this->multi_boxes)){
1756         $ret[$attr] = $this->$attr;
1757       }
1758     }
1759     return($ret);
1760   }
1762   
1763   /*! \brief  Update class variables with values collected by multiple edit.
1764    */
1765   function set_multi_edit_values($attrs)
1766   {
1767     foreach($attrs as $name => $value){
1768       $this->$name = $value;
1769     }
1770   }
1773   /*! \brief Generates the html output for this node for multi edit*/
1774   function multiple_execute()
1775   {
1776     /* This one is empty currently. Fabian - please fill in the docu code */
1777     session::global_set('current_class_for_help',get_class($this));
1779     /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
1780     session::set('LOCK_VARS_TO_USE',array());
1781     session::set('LOCK_VARS_USED_GET',array());
1782     session::set('LOCK_VARS_USED_POST',array());
1783     session::set('LOCK_VARS_USED_REQUEST',array());
1784     
1785     return("Multiple edit is currently not implemented for this plugin.");
1786   }
1789   /*! \brief Save HTML posted data to object for multiple edit
1790    */
1791   function multiple_save_object()
1792   {
1793     if(empty($this->entryCSN) && $this->CSN_check_active){
1794       $this->entryCSN = getEntryCSN($this->dn);
1795     }
1797     /* Save values to object */
1798     $this->multi_boxes = array();
1799     foreach ($this->attributes as $val){
1800   
1801       /* Get selected checkboxes from multiple edit */
1802       if(isset($_POST["use_".$val])){
1803         $this->multi_boxes[] = $val;
1804       }
1806       if ($this->acl_is_writeable($val) && isset ($_POST["$val"])){
1808         /* Check for modifications */
1809         if (get_magic_quotes_gpc()) {
1810           $data= stripcslashes($_POST["$val"]);
1811         } else {
1812           $data= $this->$val = $_POST["$val"];
1813         }
1814         if ($this->$val != $data){
1815           $this->is_modified= TRUE;
1816         }
1817     
1818         /* IE post fix */
1819         if(isset($data[0]) && $data[0] == chr(194)) {
1820           $data = "";  
1821         }
1822         $this->$val= $data;
1823       }
1824     }
1825   }
1828   /*! \brief Returns all attributes of this plugin, 
1829                to be able to detect multiple used attributes 
1830                in multi_plugg::detect_multiple_used_attributes().
1831       @return array Attributes required for intialization of multi_plug
1832    */
1833   public function get_multi_init_values()
1834   {
1835     $attrs = $this->attrs;
1836     return($attrs);
1837   }
1840   /*! \brief  Check given values in multiple edit
1841       \return array Error messages
1842    */
1843   function multiple_check()
1844   {
1845     $message = plugin::check();
1846     return($message);
1847   }
1850   /*! \brief  Returns the snapshot header part for "Actions" menu in management dialogs 
1851       \param  $layer_menu  
1852    */   
1853   function get_snapshot_header($base,$category)
1854   {
1855     $str = "";
1856     $ui = get_userinfo();
1857     if($this->snapshotEnabled() && $ui->allow_snapshot_restore($base,$category)){
1859       $ok = false;
1860       foreach($this->get_used_snapshot_bases() as $base){
1861         $ok |= count($this->getAllDeletedSnapshots($base)) >= 1 ;
1862       }
1864       if($ok){
1865         $str = "..|<img class='center' src='images/lists/restore.png' ".
1866           "alt='"._("Restore")."'>&nbsp;"._("Restore").                       "|RestoreDeletedSnapShot|\n";
1867       }else{
1868         $str = "..|<img class='center' src='images/lists/restore_grey.png' alt=''>&nbsp;"._("Restore")."||\n";
1869       }
1870     }
1871     return($str);
1872   }
1875   function get_snapshot_action($base,$category)
1876   {
1877     $str= ""; 
1878     $ui = get_userinfo();
1879     if($this->snapshotEnabled()){
1880       if ($ui->allow_snapshot_restore($base,$category)){
1882         if(count($this->Available_SnapsShots($base))){
1883           $str.= "<input class='center' type='image' src='images/lists/restore.png'
1884             alt='"._("Restore snapshot")."' name='RestoreSnapShotDialog_".base64_encode($base)."' title='"._("Restore snapshot")."'>&nbsp;";
1885         } else {
1886           $str = "<img class='center' src='images/lists/restore_grey.png' alt=''>&nbsp;";
1887         }
1888       }
1889       if($ui->allow_snapshot_create($base,$category)){
1890         $str.= "<input class='center' type='image' src='images/snapshot.png'
1891           alt='"._("Create snapshot")."' name='CreateSnapShotDialog_".base64_encode($base)."' 
1892           title='"._("Create a new snapshot from this object")."'>&nbsp;";
1893       }else{
1894         $str = "<img class='center' src='images/empty.png' alt=' '>&nbsp;";
1895       }
1896     }
1898     return($str);
1899   }
1902   function get_copypaste_action($base,$category,$class,$copy = TRUE, $cut = TRUE)
1903   {
1904     $ui = get_userinfo();
1905     $action = "";
1906     if($this->CopyPasteHandler){
1907       if($cut){
1908         if($ui->is_cutable($base,$category,$class)){
1909           $action .= "<input class='center' type='image'
1910             src='images/lists/cut.png' alt='"._("cut")."' name='cut_%KEY%' title='"._("Cut this entry")."'>&nbsp;";
1911         }else{
1912           $action.="<img src='images/empty.png' alt=' ' class='center'>&nbsp;";
1913         }
1914       }
1915       if($copy){
1916         if($ui->is_copyable($base,$category,$class)){
1917           $action.= "<input class='center' type='image'
1918             src='images/lists/copy.png' alt='"._("copy")."' name='copy_%KEY%' title='"._("Copy this entry")."'>&nbsp;";
1919         }else{
1920           $action.="<img src='images/empty.png' alt=' ' class='center'>&nbsp;";
1921         }
1922       }
1923     }
1925     return($action); 
1926   }
1929   function get_copypaste_header($base,$category,$copy = TRUE, $cut = TRUE)
1930   {
1931     $s = "";
1932     $ui =get_userinfo();
1934     if(!is_array($category)){
1935       $category = array($category);
1936     }
1938     /* Check permissions for each category, if there is at least one category which 
1939         support read or paste permissions for the given base, then display the specific actions.
1940      */
1941     $readable = $pasteable = false;
1942     foreach($category as $cat){
1943       $readable= $readable || preg_match('/r/', $ui->get_category_permissions($base, $cat));
1944       $pasteable= $pasteable || $ui->is_pasteable($base, $cat) == 1;
1945     }
1946   
1947     if(($cut || $copy) && isset($this->CopyPasteHandler) && is_object($this->CopyPasteHandler)){
1948       if($readable){
1949         $s.= "..|---|\n";
1950         if($copy){
1951           $s.= "..|<img src='images/lists/copy.png' alt='' border='0' class='center'>".
1952             "&nbsp;"._("Copy")."|"."multiple_copy_systems|\n";
1953         }
1954         if($cut){
1955           $s.= "..|<img src='images/lists/cut.png' alt='' border='0' class='center'>".
1956             "&nbsp;"._("Cut")."|"."multiple_cut_systems|\n";
1957         }
1958       }
1960       if($pasteable){
1961         if($this->CopyPasteHandler->entries_queued()){
1962           $img = "<img border='0' class='center' src='images/lists/paste.png' alt=''>";
1963           $s.="..|".$img."&nbsp;"._("Paste")."|editPaste|\n";
1964         }else{
1965           $img = "<img border='0' class='center' src='images/lists/paste-grey.png' alt=''>";
1966           $s.="..|".$img."&nbsp;"._("Paste")."\n";
1967         }
1968       }
1969     }
1970     return($s);
1971   }
1974   function get_used_snapshot_bases()
1975   {
1976      return(array());
1977   }
1979   function is_modal_dialog()
1980   {
1981     return(isset($this->dialog) && $this->dialog);
1982   }
1985   /*! \brief    Forward command execution requests
1986    *             to the hook execution method. 
1987    */
1988   function handle_post_events($mode, $addAttrs= array())
1989   {
1990     if(!in_array($mode, array('add','remove','modify'))){
1991       trigger_error(sprintf("Invalid post event type given '%s'! Valid types are [add,modify,remove].", $mode));
1992       return;
1993     }
1994     switch ($mode){
1995       case "add":
1996         plugin::callHook($this,"POSTCREATE", $addAttrs);
1997       break;
1999       case "modify":
2000         plugin::callHook($this,"POSTMODIFY", $addAttrs);
2001       break;
2003       case "remove":
2004         plugin::callHook($this,"POSTREMOVE", $addAttrs);
2005       break;
2006     }
2007   }
2010   /*! \brief    Calls external hooks which are defined for this plugin (gosa.conf)
2011    *            Replaces placeholder by class values of this plugin instance.
2012    *  @param    Allows to a add special replacements.
2013    */
2014   static function callHook($plugin, $cmd, $addAttrs= array())
2015   {
2016     global $config;
2017     $command= $config->search(get_class($plugin), $cmd,array('menu','tabs'));
2018     if ($command != ""){
2020       // Walk trough attributes list and add the plugins attributes. 
2021       foreach ($plugin->attributes as $attr){
2022         if (!is_array($plugin->$attr)){
2023           $addAttrs[$attr] = $plugin->$attr;
2024         }
2025       }
2026       $ui = get_userinfo();
2027       $addAttrs['callerDN']=$ui->dn;
2028       $addAttrs['dn']=$plugin->dn;
2030       // Sort attributes by length, ensures correct replacement
2031       $tmp = array();
2032       foreach($addAttrs as $name => $value){
2033         $tmp[$name] =  strlen($name);
2034       }
2035       arsort($tmp);
2037       // Now replace the placeholder 
2038       foreach ($tmp as $name => $len){
2039         $value = $addAttrs[$name];
2040         $command= str_replace("%$name", "$value", $command);
2041       }
2043       // If there are still some %.. in our command, try to fill these with some other class vars 
2044       if(preg_match("/%/",$command)){
2045         $attrs = get_object_vars($plugin);
2046         foreach($attrs as $name => $value){
2047           if(is_array($value)){
2048             $s = "";
2049             foreach($value as $val){
2050               if(is_string($val) || is_int($val) || is_float($val) || is_bool($val)){
2051                 $s .= '"'.$val.'",'; 
2052               }
2053             }
2054             $value = '['.trim($s,',').']';
2055           }
2056           if(!is_string($value) && !is_int($value) && !is_float($value) && !is_bool($value)){
2057             continue;
2058           }
2059           $command= preg_replace("/%$name/", $value, $command);
2060         }
2061       }
2063       if (check_command($command)){
2064         @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,$command,"Execute");
2065         exec($command,$arr);
2066         if(is_array($arr)){
2067           $str = implode("\n",$arr);
2068           @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Result: ".$str);
2069         }
2070       } else {
2071         $message= msgPool::cmdnotfound("POSTCREATE", get_class($plugin));
2072         msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
2073       }
2074     }
2075   }
2078 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
2079 ?>