Code

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