Code

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