<?php
/*
 * This code is part of GOsa (http://www.gosa-project.org)
 * Copyright (C) 2003-2008 GONICUS GmbH
 *
 * ID: $$Id: class_plugin.inc 15705 2010-02-25 08:26:11Z hickert $$
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*! \brief   The plugin base class
  \author  Cajus Pollmeier <pollmeier@gonicus.de>
  \version 2.00
  \date    24.07.2003

  This is the base class for all plugins. It can be used standalone or
  can be included by the tabs class. All management should be done 
  within this class. Extend your plugins from this class.
 */

class plugin
{
  /*!
    \brief Reference to parent object

    This variable is used when the plugin is included in tabs
    and keeps reference to the tab class. Communication to other
    tabs is possible by 'name'. So the 'fax' plugin can ask the
    'userinfo' plugin for the fax number.

    \sa tab
   */
  var $parent= NULL;

  /*!
    \brief Configuration container

    Access to global configuration
   */
  var $config= NULL;

  /*!
    \brief Mark plugin as account

    Defines whether this plugin is defined as an account or not.
    This has consequences for the plugin to be saved from tab
    mode. If it is set to 'FALSE' the tab will call the delete
    function, else the save function. Should be set to 'TRUE' if
    the construtor detects a valid LDAP object.

    \sa plugin::plugin()
   */
  var $is_account= FALSE;
  var $initially_was_account= FALSE;

  /*!
    \brief Mark plugin as template

    Defines whether we are creating a template or a normal object.
    Has conseqences on the way execute() shows the formular and how
    save() puts the data to LDAP.

    \sa plugin::save() plugin::execute()
   */
  var $is_template= FALSE;
  var $ignore_account= FALSE;
  var $is_modified= FALSE;

  /*!
    \brief Represent temporary LDAP data

    This is only used internally.
   */
  var $attrs= array();

  /* Keep set of conflicting plugins */
  var $conflicts= array();

  /* Save unit tags */
  var $gosaUnitTag= "";
  var $skipTagging= FALSE;

  /*!
    \brief Used standard values

    dn
   */
  var $dn= "";
  var $uid= "";
  var $sn= "";
  var $givenName= "";
  var $acl= "*none*";
  var $dialog= FALSE;
  var $snapDialog = NULL;

  /* attribute list for save action */
  var $attributes= array();
  var $objectclasses= array();
  var $is_new= TRUE;
  var $saved_attributes= array();

  var $acl_base= "";
  var $acl_category= "";
  var $read_only = FALSE; // Used when the entry is opened as "readonly" due to locks.

  /* This can be set to render the tabulators in another stylesheet */
  var $pl_notify= FALSE;

  /* Object entry CSN */
  var $entryCSN         = "";
  var $CSN_check_active = FALSE;

  /* This variable indicates that this class can handle multiple dns at once. */
  var $multiple_support = FALSE;
  var $multi_attrs      = array();
  var $multi_attrs_all  = array(); 

  /* This aviable indicates, that we are currently in multiple edit handle */
  var $multiple_support_active = FALSE; 
  var $selected_edit_values = array();
  var $multi_boxes = array();

  /*! \brief plugin constructor

    If 'dn' is set, the node loads the given 'dn' from LDAP

    \param dn Distinguished name to initialize plugin from
    \sa plugin()
   */
  function plugin (&$config, $dn= NULL, $object= NULL)
  {
    /* Configuration is fine, allways */
    $this->config= &$config;	
    $this->dn= $dn;

    // Ensure that we've a valid acl_category set.
    if(empty($this->acl_category)){
      $tmp = $this->plInfo();
      if (isset($tmp['plCategory'])) {
        $c = key($tmp['plCategory']);
        if(is_numeric($c)){
          $c = $tmp['plCategory'][0];
        }
        $this->acl_category = $c."/";
      }
    }

    /* Handle new accounts, don't read information from LDAP */
    if ($dn == "new"){
      return;
    }

    /* Check if this entry was opened in read only mode */
    if(isset($_POST['open_readonly'])){
      if(session::global_is_set("LOCK_CACHE")){
        $cache = &session::get("LOCK_CACHE");
        if(isset($cache['READ_ONLY'][$this->dn])){
          $this->read_only = TRUE;
        }
      }
    }

    /* Save current dn as acl_base */
    $this->acl_base= $dn;

    /* Get LDAP descriptor */
    if ($dn !== NULL){

      /* Load data to 'attrs' and save 'dn' */
      if ($object !== NULL){
        $this->attrs= $object->attrs;
      } else {
        $ldap= $this->config->get_ldap_link();
        $ldap->cat ($dn);
        $this->attrs= $ldap->fetch();
      }

      /* Copy needed attributes */
      foreach ($this->attributes as $val){
        $found= array_key_ics($val, $this->attrs);
        if ($found != ""){
          $this->$val= $found[0];
        }
      }

      /* gosaUnitTag loading... */
      if (isset($this->attrs['gosaUnitTag'][0])){
        $this->gosaUnitTag= $this->attrs['gosaUnitTag'][0];
      }

      /* Set the template flag according to the existence of objectClass
         gosaUserTemplate */
      if (isset($this->attrs['objectClass'])){
        if (in_array_ics ("gosaUserTemplate", $this->attrs['objectClass'])){
          $this->is_template= TRUE;
          @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
              "found", "Template check");
        }
      }

      /* Is Account? */
      $found= TRUE;
      foreach ($this->objectclasses as $obj){
        if (preg_match('/top/i', $obj)){
          continue;
        }
        if (!isset($this->attrs['objectClass']) || !in_array_ics ($obj, $this->attrs['objectClass'])){
          $found= FALSE;
          break;
        }
      }
      if ($found){
        $this->is_account= TRUE;
        @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
            "found", "Object check");
      }

      /* Prepare saved attributes */
      $this->saved_attributes= $this->attrs;
      foreach ($this->saved_attributes as $index => $value){
        if (is_numeric($index)){
          unset($this->saved_attributes[$index]);
          continue;
        }

        if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)){
          unset($this->saved_attributes[$index]);
          continue;
        }

        if (isset($this->saved_attributes[$index][0])){
          if(!isset($this->saved_attributes[$index]["count"])){
            $this->saved_attributes[$index]["count"] = count($this->saved_attributes[$index]);
          }
          if($this->saved_attributes[$index]["count"] == 1){
            $tmp= $this->saved_attributes[$index][0];
            unset($this->saved_attributes[$index]);
            $this->saved_attributes[$index]= $tmp;
            continue;
          }
        }
        unset($this->saved_attributes["$index"]["count"]);
      }

      if(isset($this->attrs['gosaUnitTag'])){
        $this->saved_attributes['gosaUnitTag'] = $this->attrs['gosaUnitTag'][0];
      }
    }

    /* Save initial account state */
    $this->initially_was_account= $this->is_account;
  }


  /*! \brief Generates the html output for this node
   */
  function execute()
  {
    /* This one is empty currently. Fabian - please fill in the docu code */
    session::global_set('current_class_for_help',get_class($this));

    /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
    session::set('LOCK_VARS_TO_USE',array());
    session::set('LOCK_VARS_USED_GET',array());
    session::set('LOCK_VARS_USED_POST',array());
    session::set('LOCK_VARS_USED_REQUEST',array());
  }

  /*! \brief Removes object from parent
   */
  function remove_from_parent()
  {
    /* include global link_info */
    $ldap= $this->config->get_ldap_link();

    /* Get current objectClasses in order to add the required ones */
    $ldap->cat($this->dn);
    $tmp= $ldap->fetch ();
    $oc= array();
    if (isset($tmp['objectClass'])){
      $oc= $tmp['objectClass'];
      unset($oc['count']);
    }

    /* Remove objectClasses from entry */
    $ldap->cd($this->dn);
    $this->attrs= array();
    $this->attrs['objectClass']= array_remove_entries_ics($this->objectclasses,$oc);

    /* Unset attributes from entry */
    foreach ($this->attributes as $val){
      $this->attrs["$val"]= array();
    }

    /* Unset account info */
    $this->is_account= FALSE;

    /* Do not write in plugin base class, this must be done by
       children, since there are normally additional attribs,
       lists, etc. */
    /*
       $ldap->modify($this->attrs);
     */
  }


  /*! \brief Save HTML posted data to object 
   */
  function save_object()
  {
    /* Update entry CSN if it is empty. */
    if(empty($this->entryCSN) && $this->CSN_check_active){
      $this->entryCSN = getEntryCSN($this->dn);
    }

    /* Save values to object */
    foreach ($this->attributes as $val){
      if ($this->acl_is_writeable($val) && isset ($_POST["$val"])){
        /* Check for modifications */
        if (get_magic_quotes_gpc()) {
          $data= stripcslashes($_POST["$val"]);
        } else {
          $data= $this->$val = $_POST["$val"];
        }
        if ($this->$val != $data){
          $this->is_modified= TRUE;
        }
    
        /* Okay, how can I explain this fix ... 
         * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds. 
         * So IE posts these 'unselectable' option, with value = chr(194) 
         * chr(194) seems to be the &nbsp; in between the ...option>&nbsp;</option.. because there is no value=".." specified in these option fields  
         * This &nbsp; was added for W3c compliance, but now causes these ... ldap errors ... 
         * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
         */
        if(isset($data[0]) && $data[0] == chr(194)) {
          $data = "";  
        }
        $this->$val= $data;
      }
    }
  }


  /*! \brief Save data to LDAP, depending on is_account we save or delete */
  function save()
  {
    /* include global link_info */
    $ldap= $this->config->get_ldap_link();

    /* Save all plugins */
    $this->entryCSN = "";

    /* Start with empty array */
    $this->attrs= array();

    /* Get current objectClasses in order to add the required ones */
    $ldap->cat($this->dn);
    
    $tmp= $ldap->fetch ();

    $oc= array();
    if (isset($tmp['objectClass'])){
      $oc= $tmp["objectClass"];
      $this->is_new= FALSE;
      unset($oc['count']);
    } else {
      $this->is_new= TRUE;
    }

    /* Load (minimum) attributes, add missing ones */
    $this->attrs['objectClass']= gosa_array_merge($oc,$this->objectclasses);

    /* Copy standard attributes */
    foreach ($this->attributes as $val){
      if ($this->$val != ""){
        $this->attrs["$val"]= $this->$val;
      } elseif (!$this->is_new) {
        $this->attrs["$val"]= array();
      }
    }

    /* Handle tagging */
    $this->tag_attrs($this->attrs);
  }


  function cleanup()
  {
    foreach ($this->attrs as $index => $value){
      
      /* Convert arrays with one element to non arrays, if the saved
         attributes are no array, too */
      if (is_array($this->attrs[$index]) && 
          count ($this->attrs[$index]) == 1 &&
          isset($this->saved_attributes[$index]) &&
          !is_array($this->saved_attributes[$index])){
          
        $tmp= $this->attrs[$index][0];
        $this->attrs[$index]= $tmp;
      }

      /* Remove emtpy arrays if they do not differ */
      if (is_array($this->attrs[$index]) &&
          count($this->attrs[$index]) == 0 &&
          !isset($this->saved_attributes[$index])){
          
        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove single attributes that do not differ */
      if (!is_array($this->attrs[$index]) &&
          isset($this->saved_attributes[$index]) &&
          !is_array($this->saved_attributes[$index]) &&
          $this->attrs[$index] == $this->saved_attributes[$index]){

        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove arrays that do not differ */
      if (is_array($this->attrs[$index]) && 
          isset($this->saved_attributes[$index]) &&
          is_array($this->saved_attributes[$index])){
          
        if (!array_differs($this->attrs[$index],$this->saved_attributes[$index])){
          unset ($this->attrs[$index]);
          continue;
        }
      }
    }

    /* Update saved attributes and ensure that next cleanups will be successful too */
    foreach($this->attrs as $name => $value){
      $this->saved_attributes[$name] = $value;
    }
  }

  /*! \brief Check formular input */
  function check()
  {
    $message= array();

    /* Skip if we've no config object */
    if (!isset($this->config) || !is_object($this->config)){
      return $message;
    }

    /* Find hooks entries for this class */
    $command= $this->config->search(get_class($this), "CHECK", array('menu', 'tabs'));

    if ($command != ""){

      if (!check_command($command)){
        $message[]= msgPool::cmdnotfound("CHECK", get_class($this));
      } else {

        /* Generate "ldif" for check hook */
        $ldif= "dn: $this->dn\n";
        
        /* ... objectClasses */
        foreach ($this->objectclasses as $oc){
          $ldif.= "objectClass: $oc\n";
        }
        
        /* ... attributes */
        foreach ($this->attributes as $attr){
          if ($this->$attr == ""){
            continue;
          }
          if (is_array($this->$attr)){
            foreach ($this->$attr as $val){
              $ldif.= "$attr: $val\n";
            }
          } else {
              $ldif.= "$attr: ".$this->$attr."\n";
          }
        }

        /* Append empty line */
        $ldif.= "\n";

        /* Feed "ldif" into hook and retrieve result*/
        $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
        $fh= proc_open($command, $descriptorspec, $pipes);
        if (is_resource($fh)) {
          fwrite ($pipes[0], $ldif);
          fclose($pipes[0]);
          
          $result= stream_get_contents($pipes[1]);
          if ($result != ""){
            $message[]= $result;
          }
          
          fclose($pipes[1]);
          fclose($pipes[2]);
          proc_close($fh);
        }
      }

    }

    /* Check entryCSN */
    if($this->CSN_check_active){
      $current_csn = getEntryCSN($this->dn);
      if($current_csn != $this->entryCSN && !empty($this->entryCSN) && !empty($current_csn)){
        $this->entryCSN = $current_csn;
        $message[] = _("The object has changed since opened in GOsa. All changes that may be done by others get lost if you save this entry!");
      }
    }
    return ($message);
  }

  /* Adapt from template, using 'dn' */
  function adapt_from_template($dn, $skip= array())
  {
    /* Include global link_info */
    $ldap= $this->config->get_ldap_link();

    /* Load requested 'dn' to 'attrs' */
    $ldap->cat ($dn);
    $this->attrs= $ldap->fetch();

    /* Walk through attributes */
    foreach ($this->attributes as $val){

      /* Skip the ones in skip list */
      if (in_array($val, $skip)){
        continue;
      }

      if (isset($this->attrs["$val"][0])){

        /* If attribute is set, replace dynamic parts: 
           %sn, %givenName and %uid. Fill these in our local variables. */
        $value= $this->attrs["$val"][0];

        foreach (array("sn", "givenName", "uid") as $repl){
          if (preg_match("/%$repl/i", $value)){
            $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
          }
        }
        $this->$val= $value;
      }
    }

    /* Is Account? */
    $found= TRUE;
    foreach ($this->objectclasses as $obj){
      if (preg_match('/top/i', $obj)){
        continue;
      }
      if (!in_array_ics ($obj, $this->attrs['objectClass'])){
        $found= FALSE;
        break;
      }
    }
    if ($found){
      $this->is_account= TRUE;
    }
  }

  /* \brief Indicate whether a password change is needed or not */
  function password_change_needed()
  {
    return FALSE;
  }


  /*! \brief Show header message for tab dialogs */
  function show_enable_header($button_text, $text, $disabled= FALSE)
  {
    if (($disabled == TRUE) || (!$this->acl_is_createable())){
      $state= "disabled";
    } else {
      $state= "";
    }
    $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
    $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".$state.
      "><p class=\"seperator\">&nbsp;</p></td></tr></table>";

    return($display);
  }


  /*! \brief Show header message for tab dialogs */
  function show_disable_header($button_text, $text, $disabled= FALSE)
  {
    if (($disabled == TRUE) || !$this->acl_is_removeable()){
      $state= "disabled";
    } else {
      $state= "";
    }
    $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
    $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".$state.
      "><p class=\"seperator\">&nbsp;</p></td></tr></table>";

    return($display);
  }


  /*! \brief Show header message for tab dialogs */
  function show_header($button_text, $text, $disabled= FALSE)
  {
    echo "FIXME: show_header should be replaced by show_disable_header and show_enable_header<br>";
    if ($disabled == TRUE){
      $state= "disabled";
    } else {
      $state= "";
    }
    $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
    $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".
      ($this->acl_is_createable()?'':'disabled')." ".$state.
      "><p class=\"seperator\">&nbsp;</p></td></tr></table>";

    return($display);
  }

  /*! \brief Executes commands after an object has been created */
  function postcreate($add_attrs= array())
  {
    /* Find postcreate entries for this class */
    $command= $this->config->search(get_class($this), "POSTCREATE",array('menu', 'tabs'));

    if ($command != ""){

      /* Walk through attribute list */
      foreach ($this->attributes as $attr){
        if (!is_array($this->$attr)){
          $add_attrs[$attr] = $this->$attr;
        }
      }
      $add_attrs['dn']=$this->dn;

      $tmp = array();
      foreach($add_attrs as $name => $value){
        $tmp[$name] =  strlen($name);
      }
      arsort($tmp);
      
      /* Additional attributes */
      foreach ($tmp as $name => $len){
        $value = $add_attrs[$name];
        $command= str_replace("%$name", "$value", $command);
      }

      if (check_command($command)){
        @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
            $command, "Execute");
        exec($command,$arr);
        foreach($arr as $str){
          @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
            $command, "Result: ".$str);
        }
      } else {
        $message= msgPool::cmdnotfound("POSTCREATE", get_class($this));
        msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
      }
    }
  }

  /*! \brief Execute commands after an object has been modified */
  function postmodify($add_attrs= array())
  {
    /* Find postcreate entries for this class */
    $command= $this->config->search(get_class($this), "POSTMODIFY",array('menu','tabs'));

    if ($command != ""){

      /* Walk through attribute list */
      foreach ($this->attributes as $attr){
        if (!is_array($this->$attr)){
          $add_attrs[$attr] = $this->$attr;
        }
      }
      $add_attrs['dn']=$this->dn;

      $tmp = array();
      foreach($add_attrs as $name => $value){
        $tmp[$name] =  strlen($name);
      }
      arsort($tmp);
      
      /* Additional attributes */
      foreach ($tmp as $name => $len){
        $value = $add_attrs[$name];
        $command= str_replace("%$name", "$value", $command);
      }

      if (check_command($command)){
        @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,$command, "Execute");
        exec($command,$arr);
        foreach($arr as $str){
          @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
            $command, "Result: ".$str);
        }
      } else {
        $message= msgPool::cmdnotfound("POSTMODIFY", get_class($this));
        msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
      }
    }
  }

  /*! \brief Executes a command after an object has been removed */
  function postremove($add_attrs= array())
  {
    /* Find postremove entries for this class */
    $command= $this->config->search(get_class($this), "POSTREMOVE",array('menu','tabs'));
    if ($command != ""){

      /* Walk through attribute list */
      foreach ($this->attributes as $attr){
        if (!is_array($this->$attr)){
          $add_attrs[$attr] = $this->$attr;
        }
      }
      $add_attrs['dn']=$this->dn;

      $tmp = array();
      foreach($add_attrs as $name => $value){
        $tmp[$name] =  strlen($name);
      }
      arsort($tmp);
      
      /* Additional attributes */
      foreach ($tmp as $name => $len){
        $value = $add_attrs[$name];
        $command= str_replace("%$name", "$value", $command);
      }

      if (check_command($command)){
        @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
            $command, "Execute");

        exec($command,$arr);
        foreach($arr as $str){
          @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
            $command, "Result: ".$str);
        }
      } else {
        $message= msgPool::cmdnotfound("POSTREMOVE", get_class($this));
        msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
      }
    }
  }


  /* Create unique DN */
  function create_unique_dn2($data, $base)
  {
    $ldap= $this->config->get_ldap_link();
    $base= preg_replace("/^,*/", "", $base);

    /* Try to use plain entry first */
    $dn= "$data,$base";
    $attribute= preg_replace('/=.*$/', '', $data);
    $ldap->cat ($dn, array('dn'));
    if (!$ldap->fetch()){
      return ($dn);
    }

    /* Look for additional attributes */
    foreach ($this->attributes as $attr){
      if ($attr == $attribute || $this->$attr == ""){
        continue;
      }

      $dn= "$data+$attr=".$this->$attr.",$base";
      $ldap->cat ($dn, array('dn'));
      if (!$ldap->fetch()){
        return ($dn);
      }
    }

    /* None found */
    return ("none");
  }


  /*! \brief Create unique DN */
  function create_unique_dn($attribute, $base)
  {
    $ldap= $this->config->get_ldap_link();
    $base= preg_replace("/^,*/", "", $base);

    /* Try to use plain entry first */
    $dn= "$attribute=".$this->$attribute.",$base";
    $ldap->cat ($dn, array('dn'));
    if (!$ldap->fetch()){
      return ($dn);
    }

    /* Look for additional attributes */
    foreach ($this->attributes as $attr){
      if ($attr == $attribute || $this->$attr == ""){
        continue;
      }

      $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
      $ldap->cat ($dn, array('dn'));
      if (!$ldap->fetch()){
        return ($dn);
      }
    }

    /* None found */
    return ("none");
  }


  function rebind($ldap, $referral)
  {
    $credentials= LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
    if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
      $this->error = "Success";
      $this->hascon=true;
      $this->reconnect= true;
      return (0);
    } else {
      $this->error = "Could not bind to " . $credentials['ADMIN'];
      return NULL;
    }
  }


  /* Recursively copy ldap object */
  function _copy($src_dn,$dst_dn)
  {
    $ldap=$this->config->get_ldap_link();
    $ldap->cat($src_dn);
    $attrs= $ldap->fetch();

    /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
    $ds= ldap_connect($this->config->current['SERVER']);
    ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
    if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
      ldap_set_rebind_proc($ds, array(&$this, "rebind"));
    }

    $pwd = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
    $r=ldap_bind($ds,$this->config->current['ADMINDN'], $pwd);
    $sr=ldap_read($ds, LDAP::fix($src_dn), "objectClass=*");

    /* Fill data from LDAP */
    $new= array();
    if ($sr) {
      $ei=ldap_first_entry($ds, $sr);
      if ($ei) {
        foreach($attrs as $attr => $val){
          if ($info = @ldap_get_values_len($ds, $ei, $attr)){
            for ($i= 0; $i<$info['count']; $i++){
              if ($info['count'] == 1){
                $new[$attr]= $info[$i];
              } else {
                $new[$attr][]= $info[$i];
              }
            }
          }
        }
      }
    }

    /* close conncetion */
    ldap_unbind($ds);

    /* Adapt naming attribute */
    $dst_name= preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
    $dst_val = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
    $new[$dst_name]= LDAP::fix($dst_val);

    /* Check if this is a department.
     * If it is a dep. && there is a , override in his ou 
     *  change \2C to , again, else this entry can't be saved ...
     */
    if((isset($new['ou'])) &&( preg_match("/\\,/",$new['ou']))){
      $new['ou'] = str_replace("\\\\,",",",$new['ou']);
    }

    /* Save copy */
    $ldap->connect();
    $ldap->cd($this->config->current['BASE']);
    
    $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));

    /* FAIvariable=.../..., cn=.. 
        could not be saved, because the attribute FAIvariable was different to 
        the dn FAIvariable=..., cn=... */

    if(!is_array($new['objectClass'])) $new['objectClass'] = array($new['objectClass']);

    if(in_array_ics("FAIdebconfInfo",$new['objectClass'])){
      $new['FAIvariable'] = $ldap->fix($new['FAIvariable']);
    }
    $ldap->cd($dst_dn);
    $ldap->add($new);

    if (!$ldap->success()){
      trigger_error("Trying to save $dst_dn failed.",
          E_USER_WARNING);
      return(FALSE);
    }
    return(TRUE);
  }


  /* This is a workaround function. */
  function copy($src_dn, $dst_dn)
  {
    /* Rename dn in possible object groups */
    $ldap= $this->config->get_ldap_link();
    $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.@LDAP::prepare4filter($src_dn).'))',
        array('cn'));
    while ($attrs= $ldap->fetch()){
      $og= new ogroup($this->config, $ldap->getDN());
      unset($og->member[$src_dn]);
      $og->member[$dst_dn]= $dst_dn;
      $og->save ();
    }

    $ldap->cat($dst_dn);
    $attrs= $ldap->fetch();
    if (count($attrs)){
      trigger_error("Trying to overwrite ".LDAP::fix($dst_dn).", which already exists.",
          E_USER_WARNING);
      return (FALSE);
    }

    $ldap->cat($src_dn);
    $attrs= $ldap->fetch();
    if (!count($attrs)){
      trigger_error("Trying to move ".LDAP::fix($src_dn).", which does not seem to exist.",
          E_USER_WARNING);
      return (FALSE);
    }

    $ldap->cd($src_dn);
    $ldap->search("objectClass=*",array("dn"));
    while($attrs = $ldap->fetch()){
      $src = $attrs['dn'];
      $dst = preg_replace("/".preg_quote($src_dn, '/')."$/",$dst_dn,$attrs['dn']);
      $this->_copy($src,$dst);
    }
    return (TRUE);
  }



  /*! \brief  Rename/Move a given src_dn to the given dest_dn
   *
   * Move a given ldap object indentified by $src_dn to the
   * given destination $dst_dn
   *
   * - Ensure that all references are updated (ogroups)
   * - Update ACLs   
   * - Update accessTo
   *
   * \param  string  'src_dn' the source DN.
   * \param  string  'dst_dn' the destination DN.
   * \return boolean TRUE on success else FALSE.
   */
  function rename($src_dn, $dst_dn)
  {
    $start = microtime(1);

    /* Try to move the source entry to the destination position */
    $ldap = $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
    $ldap->create_missing_trees(preg_replace("/^[^,]+,/","",$dst_dn));
    if (!$ldap->rename_dn($src_dn,$dst_dn)){
#      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $src_dn, "", get_class()));
      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());
      @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,"Rename failed FROM: $src_dn  -- TO:  $dst_dn", 
          "Ldap Protocol v3 implementation error, falling back to maunal method.");
      return(FALSE);
    }

    /* Get list of users,groups and roles within this tree,
        maybe we have to update ACL references.
     */
    $leaf_objs = get_list("(|(objectClass=posixGroup)(objectClass=gosaAccount)(objectClass=gosaRole))",array("all"),$dst_dn,
          array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
    foreach($leaf_objs as $obj){
      $new_dn = $obj['dn'];
      $old_dn = preg_replace("/".preg_quote(LDAP::convert($dst_dn), '/')."$/i",$src_dn,LDAP::convert($new_dn));
      $this->update_acls($old_dn,$new_dn); 
    }

    // Migrate objectgroups if needed
    $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);

    // Walk through all objectGroups
    foreach($ogroups as $ogroup){
      // Migrate old to new dn
      $o_ogroup= new ogroup($this->config,$ogroup['dn']);
      if (isset($o_ogroup->member[$src_dn])) {
        unset($o_ogroup->member[$src_dn]);
      }
      $o_ogroup->member[$dst_dn]= $dst_dn;
      
      // Save object group
      $o_ogroup->save();
    }

    // Migrate rfc groups if needed
    $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);

    // Walk through all POSIX groups
    foreach($groups as $group){

      // Migrate old to new dn
      $o_group= new group($this->config,$group['dn']);
      $o_group->save();
    }

    /* Update roles to use the new entry dn */
    $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);

    // Walk through all roles
    foreach($roles as $role){
      $role = new roleGeneric($this->config,$role['dn']);
      $key= array_search($src_dn, $role->roleOccupant);      
      if($key !== FALSE){
        $role->roleOccupant[$key] = $dst_dn;
        $role->save();
      }
    }

    // Update 'manager' attributes from gosaDepartment and inetOrgPerson 
    $filter = "(&(objectClass=inetOrgPerson)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn))."))";
    $ocs = $ldap->get_objectclasses();
    if(isset($ocs['gosaDepartment']['MAY']) && in_array('manager', $ocs['gosaDepartment']['MAY'])){
      $filter = "(|".$filter."(&(objectClass=gosaDepartment)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn)).")))";
    }
    $leaf_deps=  get_list($filter,array("all"),$this->config->current['BASE'], 
        array("manager","dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
    foreach($leaf_deps as $entry){
      $update = array('manager' => $dst_dn);
      $ldap->cd($entry['dn']);
      $ldap->modify($update);
      if(!$ldap->success()){
        trigger_error(sprintf("Failed to update manager for '%s', error was '%s'", $entry['dn'], $ldap->get_error()));
      }
    }
 
    /* Check if there are gosa departments moved. 
       If there were deps moved, the force reload of config->deps.
     */
    $leaf_deps=  get_list("(objectClass=gosaDepartment)",array("all"),$dst_dn,
          array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
  
    if(count($leaf_deps)){
      $this->config->get_departments();
      $this->config->make_idepartments();
      session::global_set("config",$this->config);
      $ui =get_userinfo();
      $ui->reset_acl_cache();
    }

    return(TRUE); 
  }


 
  function move($src_dn, $dst_dn)
  {
    /* Do not copy if only upper- lowercase has changed */
    if(strtolower($src_dn) == strtolower($dst_dn)){
      return(TRUE);
    }

    
    /* Try to move the entry instead of copy & delete
     */
    if(TRUE){

      /* Try to move with ldap routines, if this was not successfull
          fall back to the old style copy & remove method 
       */
      if($this->rename($src_dn, $dst_dn)){
        return(TRUE);
      }else{
        // See code below.
      }
    }

    /* Copy source to destination */
    if (!$this->copy($src_dn, $dst_dn)){
      return (FALSE);
    }

    /* Delete source */
    $ldap= $this->config->get_ldap_link();
    $ldap->rmdir_recursive($src_dn);
    if (!$ldap->success()){
      trigger_error("Trying to delete $src_dn failed.",
          E_USER_WARNING);
      return (FALSE);
    }

    return (TRUE);
  }


  /* \brief Move/Rename complete trees */
  function recursive_move($src_dn, $dst_dn)
  {
    /* Check if the destination entry exists */
    $ldap= $this->config->get_ldap_link();

    /* Check if destination exists - abort */
    $ldap->cat($dst_dn, array('dn'));
    if ($ldap->fetch()){
      trigger_error("recursive_move $dst_dn already exists.",
          E_USER_WARNING);
      return (FALSE);
    }

    $this->copy($src_dn, $dst_dn);

    /* Remove src_dn */
    $ldap->cd($src_dn);
    $ldap->recursive_remove($src_dn);
    return (TRUE);
  }


  function handle_post_events($mode, $add_attrs= array())
  {
    switch ($mode){
      case "add":
        $this->postcreate($add_attrs);
      break;

      case "modify":
        $this->postmodify($add_attrs);
      break;

      case "remove":
        $this->postremove($add_attrs);
      break;
    }
  }


  function saveCopyDialog(){
  }


  function getCopyDialog(){
    return(array("string"=>"","status"=>""));
  }


  /*! \brief Prepare for Copy & Paste */
  function PrepareForCopyPaste($source)
  {
    $todo = $this->attributes;
    if(isset($this->CopyPasteVars)){
      $todo = array_merge($todo,$this->CopyPasteVars);
    }

    if(count($this->objectclasses)){
      $this->is_account = TRUE;
      foreach($this->objectclasses as $class){
        if(!in_array($class,$source['objectClass'])){
          $this->is_account = FALSE;
        }
      }
    }

    foreach($todo as $var){
      if (isset($source[$var])){
        if(isset($source[$var]['count'])){
          if($source[$var]['count'] > 1){
            $tmp= $source[$var];
            unset($tmp['count']);
            $this->$var = $tmp;
          }else{
            $this->$var = $source[$var][0];
          }
        }else{
          $this->$var= $source[$var];
        }
      }
    }
  }

  /*! \brief Get gosaUnitTag for the given DN
       If this is called from departmentGeneric, we have to skip this
        tagging procedure. 
    */
  function tag_attrs(&$at, $dn= "", $tag= "", $show= false)
  {
    /* Skip tagging? */
    if($this->skipTagging){
      return;
    }

    /* No dn? Self-operation... */
    if ($dn == ""){
      $dn= $this->dn;

      /* No tag? Find it yourself... */
      if ($tag == ""){
        $len= strlen($dn);

        @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "No tag for $dn - looking for one...", "Tagging");
        $relevant= array();
        foreach ($this->config->adepartments as $key => $ntag){

          /* This one is bigger than our dn, its not relevant... */
          if ($len < strlen($key)){
            continue;
          }

          /* This one matches with the latter part. Break and don't fix this entry */
          if (preg_match('/(^|,)'.preg_quote($key, '/').'$/', $dn)){
            @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "DEBUG: Possibly relevant: $key", "Tagging");
            $relevant[strlen($key)]= $ntag;
            continue;
          }

        }

        /* If we've some relevant tags to set, just get the longest one */
        if (count($relevant)){
          ksort($relevant);
          $tmp= array_keys($relevant);
          $idx= end($tmp);
          $tag= $relevant[$idx];
          $this->gosaUnitTag= $tag;
        }
      }
    }

  /*! \brief Add unit tag */ 
    /* Remove tags that may already be here... */
    remove_objectClass("gosaAdministrativeUnitTag", $at);
    if (isset($at['gosaUnitTag'])){
        unset($at['gosaUnitTag']);
    }

    /* Set tag? */
    if ($tag != ""){
      add_objectClass("gosaAdministrativeUnitTag", $at);
      $at['gosaUnitTag']= $tag;
    }

    /* Initially this object was tagged. 
       - But now, it is no longer inside a tagged department. 
       So force the remove of the tag.
       (objectClass was already removed obove)
     */
    if($tag == "" && $this->gosaUnitTag){
      $at['gosaUnitTag'] = array();
    }
  }


  /*! \brief Test for removability of the object
   *
   * Allows testing of conditions for removal of object. If removal should be aborted
   * the function needs to remove an error message.
   * */
  function allow_remove()
  {
    $reason= "";
    return $reason;
  }


  /*! \brief Create a snapshot of the current object */
  function create_snapshot($type= "snapshot", $description= array())
  {

    /* Check if snapshot functionality is enabled */
    if(!$this->snapshotEnabled()){
      return;
    }

    /* Get configuration from gosa.conf */
    $config = $this->config;

    /* Create lokal ldap connection */
    $ldap= $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);

    /* check if there are special server configurations for snapshots */
    if($config->get_cfg_value("snapshotURI") == ""){

      /* Source and destination server are both the same, just copy source to dest obj */
      $ldap_to      = $ldap;
      $snapldapbase = $this->config->current['BASE'];

    }else{
      $server         = $config->get_cfg_value("snapshotURI");
      $user           = $config->get_cfg_value("snapshotAdminDn");
      $password       = $this->config->get_credentials($config->get_cfg_value("snapshotAdminPassword"));
      $snapldapbase   = $config->get_cfg_value("snapshotBase");

      $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
      $ldap_to -> cd($snapldapbase);

      if (!$ldap_to->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
      }

    }

    /* check if the dn exists */ 
    if ($ldap->dn_exists($this->dn)){

      /* Extract seconds & mysecs, they are used as entry index */
      list($usec, $sec)= explode(" ", microtime());

      /* Collect some infos */
      $base           = $this->config->current['BASE'];
      $snap_base      = $config->get_cfg_value("snapshotBase");
      $base_of_object = preg_replace ('/^[^,]+,/i', '', $this->dn);
      $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;

      /* Create object */
#$data             = preg_replace('/^dn:.*\n/', '', $ldap->gen_ldif($this->dn,"(!(objectClass=gosaDepartment))"));
      $data             = $ldap->gen_ldif($this->dn,"(&(!(objectClass=gosaDepartment))(!(objectClass=FAIclass)))");
      $newName          = str_replace(".", "", $sec."-".$usec);
      $target= array();
      $target['objectClass']            = array("top", "gosaSnapshotObject");
      $target['gosaSnapshotData']       = gzcompress($data, 6);
      $target['gosaSnapshotType']       = $type;
      $target['gosaSnapshotDN']         = $this->dn;
      $target['description']            = $description;
      $target['gosaSnapshotTimestamp']  = $newName;

      /* Insert the new snapshot 
         But we have to check first, if the given gosaSnapshotTimestamp
         is already used, in this case we should increment this value till there is 
         an unused value. */ 
      $new_dn                           = "gosaSnapshotTimestamp=".$newName.",".$new_base;
      $ldap_to->cat($new_dn);
      while($ldap_to->count()){
        $ldap_to->cat($new_dn);
        $newName = str_replace(".", "", $sec."-".($usec++));
        $new_dn                           = "gosaSnapshotTimestamp=".$newName.",".$new_base;
        $target['gosaSnapshotTimestamp']  = $newName;
      } 

      /* Inset this new snapshot */
      $ldap_to->cd($snapldapbase);
      $ldap_to->create_missing_trees($snapldapbase);
      $ldap_to->create_missing_trees($new_base);
      $ldap_to->cd($new_dn);
      $ldap_to->add($target);
      if (!$ldap_to->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $new_dn, LDAP_ADD, get_class()));
      }

      if (!$ldap->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $new_base, "", get_class()));
      }

    }
  }

  /*! \brief Remove a snapshot */
  function remove_snapshot($dn)
  {
    $ui       = get_userinfo();
    $old_dn   = $this->dn; 
    $this->dn = $dn;
    $ldap = $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
    $ldap->rmdir_recursive($this->dn);
    if(!$ldap->success()){
      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn));
    }
    $this->dn = $old_dn;
  }


  /*! \brief Test if snapshotting is enabled
   *
   * Test weither snapshotting is enabled or not. There will also be some errors posted,
   * if the configuration failed 
   * \return TRUE if snapshots are enabled, and FALSE if it is disabled
   */
  function snapshotEnabled()
  {
    return $this->config->snapshotEnabled();
  }


  /* \brief Return available snapshots for the given base */
  function Available_SnapsShots($dn,$raw = false)
  {
    if(!$this->snapshotEnabled()) return(array());

    /* Create an additional ldap object which
       points to our ldap snapshot server */
    $ldap= $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
    $cfg= &$this->config->current;

    /* check if there are special server configurations for snapshots */
    if($this->config->get_cfg_value("snapshotURI") == ""){
      $ldap_to      = $ldap;
    }else{
      $server         = $this->config->get_cfg_value("snapshotURI");
      $user           = $this->config->get_cfg_value("snapshotAdminDn");
      $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
      $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
      $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
      $ldap_to -> cd($snapldapbase);
      if (!$ldap_to->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
      }
    }

    /* Prepare bases and some other infos */
    $base           = $this->config->current['BASE'];
    $snap_base      = $this->config->get_cfg_value("snapshotBase");
    $base_of_object = preg_replace ('/^[^,]+,/i', '', $dn);
    $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;
    $tmp            = array(); 

    /* Fetch all objects with  gosaSnapshotDN=$dn */
    $ldap_to->cd($new_base);
    $ldap_to->ls("(&(objectClass=gosaSnapshotObject)(gosaSnapshotDN=".$dn."))",$new_base,
        array("gosaSnapshotType","gosaSnapshotTimestamp","gosaSnapshotDN","description")); 

    /* Put results into a list and add description if missing */
    while($entry = $ldap_to->fetch()){ 
      if(!isset($entry['description'][0])){
        $entry['description'][0]  = "";
      }
      $tmp[] = $entry; 
    }

    /* Return the raw array, or format the result */
    if($raw){
      return($tmp);
    }else{  
      $tmp2 = array();
      foreach($tmp as $entry){
        $tmp2[base64_encode($entry['dn'])] = $entry['description'][0]; 
      }
    }
    return($tmp2);
  }


  function getAllDeletedSnapshots($base_of_object,$raw = false)
  {
    if(!$this->snapshotEnabled()) return(array());

    /* Create an additional ldap object which
       points to our ldap snapshot server */
    $ldap= $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
    $cfg= &$this->config->current;

    /* check if there are special server configurations for snapshots */
    if($this->config->get_cfg_value("snapshotURI") == ""){
      $ldap_to      = $ldap;
    }else{
      $server         = $this->config->get_cfg_value("snapshotURI");
      $user           = $this->config->get_cfg_value("snapshotAdminDn");
      $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
      $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
      $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
      $ldap_to -> cd($snapldapbase);
      if (!$ldap_to->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
      }
    }

    /* Prepare bases */ 
    $base           = $this->config->current['BASE'];
    $snap_base      = $this->config->get_cfg_value("snapshotBase");
    $new_base       = preg_replace("/".preg_quote($base, '/')."$/","",$base_of_object).$snap_base;

    /* Fetch all objects and check if they do not exist anymore */
    $ui = get_userinfo();
    $tmp = array();
    $ldap_to->cd($new_base);
    $ldap_to->ls("(objectClass=gosaSnapshotObject)",$new_base,array("gosaSnapshotType","gosaSnapshotTimestamp","gosaSnapshotDN","description"));
    while($entry = $ldap_to->fetch()){

      $chk =  str_replace($new_base,"",$entry['dn']);
      if(preg_match("/,ou=/",$chk)) continue;

      if(!isset($entry['description'][0])){
        $entry['description'][0]  = "";
      }
      $tmp[] = $entry; 
    }

    /* Check if entry still exists */
    foreach($tmp as $key => $entry){
      $ldap->cat($entry['gosaSnapshotDN'][0]);
      if($ldap->count()){
        unset($tmp[$key]);
      }
    }

    /* Format result as requested */
    if($raw) {
      return($tmp);
    }else{
      $tmp2 = array();
      foreach($tmp as $key => $entry){
        $tmp2[base64_encode($entry['dn'])] = $entry['description'][0]; 
      }
    }
    return($tmp2);
  } 


  /* \brief Restore selected snapshot */
  function restore_snapshot($dn)
  {
    if(!$this->snapshotEnabled()) return(array());

    $ldap= $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
    $cfg= &$this->config->current;

    /* check if there are special server configurations for snapshots */
    if($this->config->get_cfg_value("snapshotURI") == ""){
      $ldap_to      = $ldap;
    }else{
      $server         = $this->config->get_cfg_value("snapshotURI");
      $user           = $this->config->get_cfg_value("snapshotAdminDn");
      $password       = $this->config->get_credentials($this->config->get_cfg_value("snapshotAdminPassword"));
      $snapldapbase   = $this->config->get_cfg_value("snapshotBase");
      $ldap_to        = new ldapMultiplexer(new LDAP($user,$password, $server));
      $ldap_to -> cd($snapldapbase);
      if (!$ldap_to->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap_to->get_error(), $snapldapbase, "", get_class()));
      }
    }

    /* Get the snapshot */ 
    $ldap_to->cat($dn);
    $restoreObject = $ldap_to->fetch();

    /* Prepare import string */
    $data  = gzuncompress($ldap_to->get_attribute($dn,'gosaSnapshotData'));

    /* Import the given data */
    $err = "";
    $ldap->import_complete_ldif($data,$err,false,false);
    if (!$ldap->success()){
      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, "", get_class()));
    }
  }


  function showSnapshotDialog($base,$baseSuffixe,&$parent)
  {
    $once = true;
    $ui = get_userinfo();
    $this->parent = $parent;

    foreach($_POST as $name => $value){

      /* Create a new snapshot, display a dialog */
      if(preg_match("/^CreateSnapShotDialog_[^_]*_[xy]$/",$name) && $once){

			  $entry = base64_decode(preg_replace("/^CreateSnapShotDialog_([^_]*)_[xy]$/","\\1",$name));
        $once = false;
        $entry = preg_replace("/^CreateSnapShotDialog_/","",$entry);

        if(!empty($entry) && $ui->allow_snapshot_create($entry,$this->parent->acl_module)){
          $this->snapDialog = new SnapShotDialog($this->config,$entry,$this);
        }else{
          msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to create a snapshot for %s."),$entry),ERROR_DIALOG);
        }
      }  
  
      /* Restore a snapshot, display a dialog with all snapshots of the current object */
      if(preg_match("/^RestoreSnapShotDialog_/",$name) && $once){
        $once = false;
        $entry = base64_decode(preg_replace("/^RestoreSnapShotDialog_([^_]*)_[xy]$/i","\\1",$name));
        if(!empty($entry) && $ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
          $this->snapDialog = new SnapShotDialog($this->config,$entry,$this);
          $this->snapDialog->display_restore_dialog = true;
        }else{
          msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$entry),ERROR_DIALOG);
        }
      }

      /* Restore one of the already deleted objects */
      if(((isset($_POST['menu_action']) && $_POST['menu_action'] == "RestoreDeletedSnapShot") 
          || preg_match("/^RestoreDeletedSnapShot_/",$name)) && $once){
        $once = false;

        if($ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
          $this->snapDialog = new SnapShotDialog($this->config,"",$this);
          $this->snapDialog->set_snapshot_bases($baseSuffixe);
          $this->snapDialog->display_restore_dialog      = true;
          $this->snapDialog->display_all_removed_objects  = true;
        }else{
          msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$base),ERROR_DIALOG);
        }
      }

      /* Restore selected snapshot */
      if(preg_match("/^RestoreSnapShot_/",$name) && $once){
        $once = false;
        $entry = base64_decode(preg_replace("/^RestoreSnapShot_([^_]*)_[xy]$/i","\\1",$name));

        if(!empty($entry) && $ui->allow_snapshot_restore($this->dn,$this->parent->acl_module)){
          $this->restore_snapshot($entry);
          $this->snapDialog = NULL;
        }else{
          msg_dialog::display(_("Permission"),sprintf(_("You are not allowed to restore a snapshot for %s."),$entry),ERROR_DIALOG);
        }
      }
    }

    /* Create a new snapshot requested, check
       the given attributes and create the snapshot*/
    if(isset($_POST['CreateSnapshot']) && is_object($this->snapDialog)){
      $this->snapDialog->save_object();
      $msgs = $this->snapDialog->check();
      if(count($msgs)){
        foreach($msgs as $msg){
          msg_dialog::display(_("Error"), $msg, ERROR_DIALOG);
        }
      }else{
        $this->dn =  $this->snapDialog->dn;
        $this->create_snapshot("snapshot",$this->snapDialog->CurrentDescription);
        $this->snapDialog = NULL;
      }
    }

    /* Restore is requested, restore the object with the posted dn .*/
    if((isset($_POST['RestoreSnapshot'])) && (isset($_POST['SnapShot']))){
    }

    if(isset($_POST['CancelSnapshot'])){
      $this->snapDialog = NULL;
    }

    if(is_object($this->snapDialog )){
      $this->snapDialog->save_object();
      return($this->snapDialog->execute());
    }
  }


  /*! \brief Return plugin informations for acl handling */
  static function plInfo()
  {
    return array();
  }


  function set_acl_base($base)
  {
    $this->acl_base= $base;
  }


  function set_acl_category($category)
  {
    $this->acl_category= "$category/";
  }


  function acl_is_writeable($attribute,$skip_write = FALSE)
  {
    if($this->read_only) return(FALSE);
    $ui= get_userinfo();
    return preg_match('/w/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute, $skip_write));
  }


  function acl_is_readable($attribute)
  {
    $ui= get_userinfo();
    return preg_match('/r/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute));
  }


  function acl_is_createable($base ="")
  {
    if($this->read_only) return(FALSE);
    $ui= get_userinfo();
    if($base == "") $base = $this->acl_base;
    return preg_match('/c/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
  }


  function acl_is_removeable($base ="")
  {
    if($this->read_only) return(FALSE);
    $ui= get_userinfo();
    if($base == "") $base = $this->acl_base;
    return preg_match('/d/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
  }


  function acl_is_moveable($base = "")
  {
    if($this->read_only) return(FALSE);
    $ui= get_userinfo();
    if($base == "") $base = $this->acl_base;
    return preg_match('/m/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
  }


  function acl_have_any_permissions()
  {
  }


  function getacl($attribute,$skip_write= FALSE)
  {
    $ui= get_userinfo();
    $skip_write |= $this->read_only;
    return  $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute,$skip_write);
  }


  /*! \brief Returns a list of all available departments for this object.
   * 
   * If this object is new, all departments we are allowed to create a new user in
   * are returned. If this is an existing object, return all deps. 
   * We are allowed to move tis object too.
   * \return array [dn] => "..name"  // All deps. we are allowed to act on.
  */
  function get_allowed_bases()
  {
    $ui = get_userinfo();
    $deps = array();

    /* Is this a new object ? Or just an edited existing object */
    if(!$this->initially_was_account && $this->is_account){
      $new = true;
    }else{
      $new = false;
    }

    foreach($this->config->idepartments as $dn => $name){
      if($new && $this->acl_is_createable($dn)){
        $deps[$dn] = $name;
      }elseif(!$new && $this->acl_is_moveable($dn)){
        $deps[$dn] = $name;
      }
    }

    /* Add current base */      
    if(isset($this->base) && isset($this->config->idepartments[$this->base])){
      $deps[$this->base] = $this->config->idepartments[$this->base];
    }elseif(strtolower($this->dn) == strtolower($this->config->current['BASE'])){

    }else{
      trigger_error("Cannot return list of departments, no default base found in class ".get_class($this).". ".$this->base);
    }
    return($deps);
  }


  /* This function updates ACL settings if $old_dn was used.
   *  \param string 'old_dn' specifies the actually used dn
   *  \param string 'new_dn' specifies the destiantion dn
   */
  function update_acls($old_dn,$new_dn,$output_changes = FALSE)
  {
    /* Check if old_dn is empty. This should never happen */
    if(empty($old_dn) || empty($new_dn)){
      trigger_error("Failed to check acl dependencies, wrong dn given.");
      return;
    }

    /* Update userinfo if necessary */
    $ui = session::global_get('ui');
    if($ui->dn == $old_dn){
      $ui->dn = $new_dn;
      session::global_set('ui',$ui);
      new log("view","acl/".get_class($this),$this->dn,array(),"Updated current object dn from '".$old_dn."' to '".$new_dn."'");
    }

    /* Object was moved, ensure that all acls will be moved too */
    if($new_dn != $old_dn && $old_dn != "new"){

      /* get_ldap configuration */
      $update = array();
      $ldap = $this->config->get_ldap_link();
      $ldap->cd ($this->config->current['BASE']);
      $ldap->search("(&(objectClass=gosaAcl)(gosaAclEntry=*".base64_encode($old_dn)."*))",array("cn","gosaAclEntry"));
      while($attrs = $ldap->fetch()){
        $acls = array();
        $found = false;
        for($i = 0 ; $i <  $attrs['gosaAclEntry']['count'] ; $i ++ ){
          $acl_parts = explode(":",$attrs['gosaAclEntry'][$i]);

          /* Roles uses antoher data storage order, members are stored int the third part, 
             while the members in direct ACL assignments are stored in the second part.
           */
          $id = ($acl_parts[1] == "role") ? 3 : 2;

          /* Update member entries to use $new_dn instead of old_dn
           */
          $members = explode(",",$acl_parts[$id]);
          foreach($members as $key => $member){
            $member = base64_decode($member);
            if($member == $old_dn){
              $members[$key] = base64_encode($new_dn);
              $found = TRUE;
            }
          } 

          /* Check if the selected role has to updated
           */
          if($acl_parts[1] == "role" && $acl_parts[2] == base64_encode($old_dn)){
            $acl_parts[2] = base64_encode($new_dn);
            $found = TRUE;
          }

          /* Build new acl string */ 
          $acl_parts[$id] = implode($members,",");
          $acls[] = implode($acl_parts,":");
        }

        /* Acls for this object must be adjusted */
        if($found){

          $debug_info=  _("Changing ACL dn")."&nbsp;:&nbsp;<br>&nbsp;-"._("from")."&nbsp;<b>&nbsp;".
            $old_dn."</b><br>&nbsp;-"._("to")."&nbsp;<b>".$new_dn."</b><br>";
          @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,$debug_info,"ACL");

          $update[$attrs['dn']] =array();
          foreach($acls as $acl){
            $update[$attrs['dn']]['gosaAclEntry'][] = $acl;
          }
        }
      }

      /* Write updated acls */
      foreach($update as $dn => $attrs){
        $ldap->cd($dn);
        $ldap->modify($attrs);
      }
    }
  }

  

  /*! \brief Enable the Serial ID check
   *
   * This function enables the entry Serial ID check.  If an entry was edited while
   * we have edited the entry too, an error message will be shown. 
   * To configure this check correctly read the FAQ.
   */    
  function enable_CSN_check()
  {
    $this->CSN_check_active =TRUE;
    $this->entryCSN = getEntryCSN($this->dn);
  }


  /*! \brief  Prepares the plugin to be used for multiple edit
   *          Update plugin attributes with given array of attribtues.
   *  \param  array   Array with attributes that must be updated.
   */
  function init_multiple_support($attrs,$all)
  {
    $ldap= $this->config->get_ldap_link();
    $this->multi_attrs    = $attrs;
    $this->multi_attrs_all= $all;

    /* Copy needed attributes */
    foreach ($this->attributes as $val){
      $found= array_key_ics($val, $this->multi_attrs);
 
      if ($found != ""){
        if(isset($this->multi_attrs["$val"][0])){
          $this->$val= $this->multi_attrs["$val"][0];
        }
      }
    }
  }

 
  /*! \brief  Enables multiple support for this plugin
   */
  function enable_multiple_support()
  {
    $this->ignore_account = TRUE;
    $this->multiple_support_active = TRUE;
  }


  /*! \brief  Returns all values that have been modfied in multiple edit mode.
      \return array Cotaining all modified values. 
   */
  function get_multi_edit_values()
  {
    $ret = array();
    foreach($this->attributes as $attr){
      if(in_array($attr,$this->multi_boxes)){
        $ret[$attr] = $this->$attr;
      }
    }
    return($ret);
  }

  
  /*! \brief  Update class variables with values collected by multiple edit.
   */
  function set_multi_edit_values($attrs)
  {
    foreach($attrs as $name => $value){
      $this->$name = $value;
    }
  }


  /*! \brief Generates the html output for this node for multi edit*/
  function multiple_execute()
  {
    /* This one is empty currently. Fabian - please fill in the docu code */
    session::global_set('current_class_for_help',get_class($this));

    /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
    session::set('LOCK_VARS_TO_USE',array());
    session::set('LOCK_VARS_USED_GET',array());
    session::set('LOCK_VARS_USED_POST',array());
    session::set('LOCK_VARS_USED_REQUEST',array());
    
    return("Multiple edit is currently not implemented for this plugin.");
  }


  /*! \brief Save HTML posted data to object for multiple edit
   */
  function multiple_save_object()
  {
    if(empty($this->entryCSN) && $this->CSN_check_active){
      $this->entryCSN = getEntryCSN($this->dn);
    }

    /* Save values to object */
    $this->multi_boxes = array();
    foreach ($this->attributes as $val){
  
      /* Get selected checkboxes from multiple edit */
      if(isset($_POST["use_".$val])){
        $this->multi_boxes[] = $val;
      }

      if ($this->acl_is_writeable($val) && isset ($_POST["$val"])){

        /* Check for modifications */
        if (get_magic_quotes_gpc()) {
          $data= stripcslashes($_POST["$val"]);
        } else {
          $data= $this->$val = $_POST["$val"];
        }
        if ($this->$val != $data){
          $this->is_modified= TRUE;
        }
    
        /* IE post fix */
        if(isset($data[0]) && $data[0] == chr(194)) {
          $data = "";  
        }
        $this->$val= $data;
      }
    }
  }


  /*! \brief Returns all attributes of this plugin, 
               to be able to detect multiple used attributes 
               in multi_plugg::detect_multiple_used_attributes().
      @return array Attributes required for intialization of multi_plug
   */
  public function get_multi_init_values()
  {
    $attrs = $this->attrs;
    return($attrs);
  }


  /*! \brief  Check given values in multiple edit
      \return array Error messages
   */
  function multiple_check()
  {
    $message = plugin::check();
    return($message);
  }


  /*! \brief  Returns the snapshot header part for "Actions" menu in management dialogs 
      \param  $layer_menu  
   */   
  function get_snapshot_header($base,$category)
  {
    $str = "";
    $ui = get_userinfo();
    if($this->snapshotEnabled() && $ui->allow_snapshot_restore($base,$category)){

      $ok = false;
      foreach($this->get_used_snapshot_bases() as $base){
        $ok |= count($this->getAllDeletedSnapshots($base)) >= 1 ;
      }

      if($ok){
        $str = "..|<img class='center' src='images/lists/restore.png' ".
          "alt='"._("Restore")."'>&nbsp;"._("Restore").                       "|RestoreDeletedSnapShot|\n";
      }else{
        $str = "..|<img class='center' src='images/lists/restore_grey.png' alt=''>&nbsp;"._("Restore")."||\n";
      }
    }
    return($str);
  }


  function get_snapshot_action($base,$category)
  {
    $str= ""; 
    $ui = get_userinfo();
    if($this->snapshotEnabled()){
      if ($ui->allow_snapshot_restore($base,$category)){

        if(count($this->Available_SnapsShots($base))){
          $str.= "<input class='center' type='image' src='images/lists/restore.png'
            alt='"._("Restore snapshot")."' name='RestoreSnapShotDialog_".base64_encode($base)."' title='"._("Restore snapshot")."'>&nbsp;";
        } else {
          $str = "<img class='center' src='images/lists/restore_grey.png' alt=''>&nbsp;";
        }
      }
      if($ui->allow_snapshot_create($base,$category)){
        $str.= "<input class='center' type='image' src='images/snapshot.png'
          alt='"._("Create snapshot")."' name='CreateSnapShotDialog_".base64_encode($base)."' 
          title='"._("Create a new snapshot from this object")."'>&nbsp;";
      }else{
        $str = "<img class='center' src='images/empty.png' alt=' '>&nbsp;";
      }
    }

    return($str);
  }


  function get_copypaste_action($base,$category,$class,$copy = TRUE, $cut = TRUE)
  {
    $ui = get_userinfo();
    $action = "";
    if($this->CopyPasteHandler){
      if($cut){
        if($ui->is_cutable($base,$category,$class)){
          $action .= "<input class='center' type='image'
            src='images/lists/cut.png' alt='"._("cut")."' name='cut_%KEY%' title='"._("Cut this entry")."'>&nbsp;";
        }else{
          $action.="<img src='images/empty.png' alt=' ' class='center'>&nbsp;";
        }
      }
      if($copy){
        if($ui->is_copyable($base,$category,$class)){
          $action.= "<input class='center' type='image'
            src='images/lists/copy.png' alt='"._("copy")."' name='copy_%KEY%' title='"._("Copy this entry")."'>&nbsp;";
        }else{
          $action.="<img src='images/empty.png' alt=' ' class='center'>&nbsp;";
        }
      }
    }

    return($action); 
  }


  function get_copypaste_header($base,$category,$copy = TRUE, $cut = TRUE)
  {
    $s = "";
    $ui =get_userinfo();

    if(!is_array($category)){
      $category = array($category);
    }

    /* Check permissions for each category, if there is at least one category which 
        support read or paste permissions for the given base, then display the specific actions.
     */
    $readable = $pasteable = false;
    foreach($category as $cat){
      $readable= $readable || preg_match('/r/', $ui->get_category_permissions($base, $cat));
      $pasteable= $pasteable || $ui->is_pasteable($base, $cat) == 1;
    }
  
    if(($cut || $copy) && isset($this->CopyPasteHandler) && is_object($this->CopyPasteHandler)){
      if($readable){
        $s.= "..|---|\n";
        if($copy){
          $s.= "..|<img src='images/lists/copy.png' alt='' border='0' class='center'>".
            "&nbsp;"._("Copy")."|"."multiple_copy_systems|\n";
        }
        if($cut){
          $s.= "..|<img src='images/lists/cut.png' alt='' border='0' class='center'>".
            "&nbsp;"._("Cut")."|"."multiple_cut_systems|\n";
        }
      }

      if($pasteable){
        if($this->CopyPasteHandler->entries_queued()){
          $img = "<img border='0' class='center' src='images/lists/paste.png' alt=''>";
          $s.="..|".$img."&nbsp;"._("Paste")."|editPaste|\n";
        }else{
          $img = "<img border='0' class='center' src='images/lists/paste-grey.png' alt=''>";
          $s.="..|".$img."&nbsp;"._("Paste")."\n";
        }
      }
    }
    return($s);
  }


  function get_used_snapshot_bases()
  {
     return(array());
  }

  function is_modal_dialog()
  {
    return(isset($this->dialog) && $this->dialog);
  }
}

// vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
?>
