<?php

/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2003-2010  Cajus Pollmeier
  Copyright (C) 2011-2015  FusionDirectory

  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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/

//!  The Phone Macro Class: Handles Macro Contents, and some attributes.
/*!
     This class handles the basic information about phone macros, like
     cn base description displayName goFonMacroContent goFonMacroVisible

     This is not the only Class that manages phone Macros, there is also the class_goFonMacroParameter.
*/

class macro extends plugin
{
  /*! Macro attributes,  */
  var $generate_error= "";

  /*! The name of the Macro in the openldap drirectory */
  var $cn               = "";

  /*! Display error once */
  var $error_shown = false;

  /*! This ist the variable that contains the description of the macro*/
  var $description      = "";

  /*! The base of the macro, is used to save the macro in the correct directory tree */
  var $base             = "";

  /*! This is the name of the macro which the enduser will see, instead of the cn */
  var $displayName      = "";

  /*! Here is the macro content, the real macroscript */
  var $goFonMacroContent= "";

  /*! To allow user to use this macro this var must be true, else false */
  var $goFonMacroVisible= 0;

  /*! attribute list for save action */
  var $attributes     = array("cn","base", "description","displayName","goFonMacroContent","goFonMacroVisible");
  var $view_logged = FALSE;
  var $orig_cn = "";
  var $orig_base = "";
  var $orig_dn = "";
  /*! Objectclasses that this calls handles */
  var $objectclasses  = array("top", "goFonMacro");

  var $goFonHomeServers = array(); // Contains all available asterisk database server
  var $baseSelector;

  //! The Konstructor
  /*!  Konstructor, load class with  attributes of the given dn*/
  function macro (&$config, $dn= NULL, $parent= NULL)
  {
    parent::__construct($config, $dn, $parent);

    /* This is always an account */
    $this->is_account= TRUE;

    /* Edit or new one ?*/
    if ($this->dn == "new"){
      if(session::is_set('CurrentMainBase')){
        $this->base  = session::get('CurrentMainBase');
      }else{
        $ui= get_userinfo();
        $this->base= dn2base($ui->dn);
      }
    } else {
      $this->orig_cn=$this->cn;
      $this->base= preg_replace ("/^[^,]+,".preg_quote(get_ou("phoneMacroRDN"), '/')."/i", "", $this->dn);
    }

    /* Check server configurations
     * Load all server configuration in $this->goFonHomeServers if available
     */
    $a_SETUP= array();
    if(isset($config->data['SERVERS']['FON'])){

      /* Set available server */
      $this->goFonHomeServers = $config->data['SERVERS']['FON'];

      /* Remove default entry, not necessary here */
      if(isset($this->goFonHomeServers[0])){
        unset($this->goFonHomeServers[0]);
      }
    }

    $this->orig_base = $this->base;
    $this->orig_dn = $this->dn;

    /* Instanciate base selector */
    $this->baseSelector= new baseSelector($this->get_allowed_bases(), $this->base);
    $this->baseSelector->setSubmitButton(false);
    $this->baseSelector->setHeight(300);
    $this->baseSelector->update(true);
  }


  /*!  Execute this plugin */
  function execute()
  {
    /* Call parent execute */
    parent::execute();

    /* Log view */
    if($this->is_account && !$this->view_logged){
      $this->view_logged = TRUE;
      new log("view","gofonmacro/".get_class($this),$this->dn);
    }

    /* Variables */
    $vars   = "";
    $tmp    = array();
    $number = 0;

    /* Fill templating stuff */
    $smarty= get_smarty();
    $smarty->assign("usePrototype", "true");

    $tmp = $this->plInfo();
    foreach($tmp['plProvidedAcls'] as $name => $translation){
      $smarty->assign($name."ACL",$this->getacl($name));
    }

    if($this->acl_is_writeable("base")){
      $smarty->assign("baseSelect",true);
    }else{
      $smarty->assign("baseSelect",false);
    }


    /* Assign all vars to Smarty */
    foreach($this->attributes as $ar){
      $smarty->assign($ar, $this->$ar);
    }

    $smarty->assign("base", $this->baseSelector->render());

    /* Checkboxes */
    $smarty->assign("vars", $vars);

    if($this->goFonMacroVisible){
      $smarty->assign("goFonMacroVisibleChecked"," checked ");
    }else{
      $smarty->assign("goFonMacroVisibleChecked","");
    }

    $smarty->assign("cnACL",$this->getacl("cn",$this->initially_was_account));
    $smarty->assign("cn",$this->cn);

    /* Ensure that macro content is displayed correctly encoded */
    $smarty->assign("goFonMacroContent",htmlentities(utf8_decode ($this->goFonMacroContent)));

    /* Show main page */
    return($smarty->fetch (get_template_path('generic.tpl', TRUE)));
  }

  /* Remove current macro from all asterisk server.
   * First of all check if we have access to all databases.
   * - Remove old entries
   */
  function remove_from_database($save)
  {
    /* Create query string */
    $context  = addslashes("macro-".$this->cn);

    /* Remove current macro from each server available */
    if ($save) {
      foreach($this->goFonHomeServers as $dn => $Server){
        $query = "DELETE FROM ".$Server['EXT_TABLE']." WHERE context='".$context."';";
        $link = databaseManagement::connectDatabase($Server);
        if (PEAR::isError($link)) {
          new log("debug","GOfon/".get_class($this),"",array(),$link->getMessage());
          return msgPool::dbconnect("GOfon",$link->getMessage(),$Server['SERVER']);
        }
        $res = $link->exec($query);
        @DEBUG (DEBUG_DB, __LINE__, __FUNCTION__, __FILE__,$query, "Database query");
        if(PEAR::isError($res)){
          new log("debug","gofonmacro/".get_class($this),"",array(),$res->getMessage());
          @DEBUG (DEBUG_DB, __LINE__, __FUNCTION__, __FILE__,"<b>".$query."</b>", "ERROR");
          trigger_error($res->getMessage());
          return(
            sprintf(
              _("Macro could not be removed from server '%s', please check database consistency!"),
              $Server['SERVER']));
        }
        $link->disconnect();
      }
    }
  }


  /* Add current macro to all asterisk server.
   * First of all check if we have access to all databases.
   * - Remove old entries
   * - Add new entries
   */
  function add_to_database($save)
  {
    /* Remove old entries first. Else we got duplicated entries */
    $str = $this->remove_from_database($save);
    if($str){
      return($str);
    }

    /* Create query string */
    $context = "macro-".$this->cn;

    /************
     * Parse Macro content
     ************/
    $sql =
      "INSERT INTO %TABLENAME% ".
      " (context,exten,priority,app,appdata) ".
      " VALUES ";

    $a_contentLines = preg_split("/\n/",$this->goFonMacroContent);

    // Keep all macro priorities to avoid duplicates!
    $priorities = array();
    foreach($a_contentLines as $i_linenum => $s_linestr){

      /* Remove the 'exten => ' string in front of the macro content line
       *  example line  'exten => s,2,GotoIf(${ARG3}?3:5)'
       * Remove comments introduced by ;
       * Skip empty lines
       */
      $s_linestr = trim($s_linestr);
      $s_linestr = preg_replace("/;.*$/","",$s_linestr) ;
      $s_linestr = preg_replace ("/^.*=\> /","",$s_linestr);

      if(empty($s_linestr)){
        continue;
      }

      /* A line that passes the check above should look like this
       *  s,1,SetLanguage(de)
       * 3 parts seperated by ,
       * If there are more or less parts, abort.
       * The preg_replace exclude parameters from split ..
       */
      $tmp  = explode(",", $s_linestr,3);

      /* Check if there is an application given */
      if(!isset($tmp[2]) || $tmp[2]  == ""){
        return(sprintf(_("Application missing in line %s!"),$i_linenum));
      }
      if(!isset($tmp[1]) || $tmp[1] == ""){
        return(sprintf(_("Priority missing in line %s!"),$i_linenum));
      }
      /* Check if there is an extension given */
      if(!isset($tmp[0]) || $tmp[0] == ""){
        return(sprintf(_("Extension missing in line %s!"),$i_linenum));
      }

      // Table 'extensions' has a primary key over the following fields
      //  'context', 'exten' and 'priority' -
      // Ensure that we do not write duplicates here.
      $du = $context.$tmp[0].$tmp[1];
      if(!isset($priorities[$du])){
        $priorities[$du] = 1;
      }else{
        return(sprintf(_("Duplicate combination of 'extension' and 'priority' in line %s!"),$i_linenum));
      }

      /* Create extension entry for current line
       *  and add this line to an array that will be inserted
       *  to each database.
       */
      $exten  = addslashes($tmp[0]);
      $prio   = addslashes($tmp[1]);
      $app    = addslashes(preg_replace("/\(.*\).*$/","",$tmp[2]));
      $para   = addslashes(preg_replace("/^[^(]*\((.*)\)[^)]*$/", "$1", $tmp[2]));
      $sql.= " ('".$context."','".$exten."','".$prio."','".$app."','".$para."'),";
    }

    /* Remove last , from query string */
    $sql = preg_replace("/,$/","",$sql);

    /* Save current changes to the database */
    if($save){

      /* Macro are spread to each asterisk server */
      foreach($this->goFonHomeServers as $dn => $cfg){
        $link = databaseManagement::connectDatabase($cfg);
        if (PEAR::isError($link)) {
          new log("debug","GOfon/".get_class($this),"",array(),$link->getMessage());
          return msgPool::dbconnect("GOfon",$link->getMessage(),$cfg['SERVER']);
        }
        $query  = preg_replace("/%TABLENAME%/",$cfg['EXT_TABLE'],$sql);
        $res    = $link->exec($query);
        @DEBUG (DEBUG_DB, __LINE__, __FUNCTION__, __FILE__,$query, "Database query");
        if(PEAR::isError($res)){
          new log("debug","gofonmacro/".get_class($this),"",array(),$res->getMessage());
          @DEBUG (DEBUG_DB, __LINE__, __FUNCTION__, __FILE__,"<b>".$query."</b>", "ERROR");
          trigger_error($res->getMessage());
          return(
            sprintf(
              _("Macro could not be added to server '%s', please check database consistency!"),
              $cfg['SERVER']));
        }
        $link->disconnect();
      }
    }
  }


  function save_object()
  {
    if (isset($_POST['gofonMacroGenericPosted'])){

      $old_cn       = $this->cn;
      $old_visible  = $this->goFonMacroVisible;

      /* Create a base backup and reset the
         base directly after calling parent::save_object();
         Base will be set seperatly a few lines below */
      $base_tmp = $this->base;
      parent::save_object();
      $this->base = $base_tmp;

      /* Refresh base */
      if ($this->acl_is_moveable($this->base)){
        if (!$this->baseSelector->update()) {
          msg_dialog::display(_("Error"), msgPool::permMove(), ERROR_DIALOG);
        }
        if ($this->base != $this->baseSelector->getBase()) {
          $this->base= $this->baseSelector->getBase();
          $this->is_modified= TRUE;
        }
      }

      /* Restore old cn if we have insuficient acls to change cn ... */
      if(!$this->acl_is_writeable("cn",$this->initially_was_account)){
        $this->cn = $old_cn;
      }

      /* check if we are allowed to toggle visibility */
      if($this->acl_is_writeable("goFonMacroVisible")) {

        /* Checkbox selected ? */
        if(isset($_POST['goFonMacroVisible'])) {
          $this->goFonMacroVisible= 1 ;
        }else  {
          if(isset($_POST['displayName'])){
            $this->goFonMacroVisible= 0 ;
          }
        }
      }else{
        $this->goFonMacroVisible = $old_visible;
      }
    }
  }


  /*! Check values */
  function check()
  {
    /* Call common method to give check the hook */
    $message= plugin::check();

    if(!count($this->goFonHomeServers)){
      $message[] = msgPool::noserver(_("GOfon"));
    }

    /* Check if insert/replace is possible and all servers are available */
    $str = $this->add_to_database(false);
    if($str){
      $message[] = $str;
    }

    /* Check if cn is already used  */
    if(($this->dn=="new")||($this->orig_cn!=$this->cn)){
      $ldap = $this->config->get_ldap_link();
      $ldap->search("(&(objectClass=goFonMacro)(cn=".$this->cn."))",array("cn"));
      if($ldap->count()>0){
        $message[]= msgPool::duplicated(_("Name"));
      }
    }

    // Check if a wrong base was supplied
    if(!$this->baseSelector->checkLastBaseUpdate()){
      $message[]= msgPool::check_base();;
    }

    /* Check if display name is set */
    if($this->displayName == ""){
      $message[] = msgPool::required(_("Name"));
    }
    /* CN is restricted to 20 chars */
    if(strlen("Makro-".$this->cn)>20 ){
      $message[]=_("Name can be 20 characters at maximum!");
    }

    /* If this macro is still in use we should not change the visible for user flag to invisible */
    if(!$this->goFonMacroVisible){
      $ldap = $this->config->get_ldap_link();
      $res = $ldap->search("(&(objectClass=goFonAccount)(objectClass=gosaAccount)(goFonMacro=*))", array("goFonMacro"));
      while ($val = $ldap->fetch()){
        if(strstr($val['goFonMacro'][0],$this->dn)){
          $message[] = _("Macro is still in use!");
          return($message);
        }
      }
    }

    if(empty($this->goFonMacroContent)){
      $message[] = _("Macro is empty!");
    }

    /* Check if we are allowed to create or move this object
     */
    if($this->orig_dn == "new" && !$this->acl_is_createable($this->base)){
      $message[] = msgPool::permCreate();
    }elseif($this->orig_dn != "new" && $this->base != $this->orig_base && !$this->acl_is_moveable($this->base)){
      $message[] = msgPool::permMove();
    }

    return $message;
  }


  /*! Remove makro from all given databases
   *   and ldap too.
   */
  function remove_from_parent()
  {
    $ldap= $this->config->get_ldap_link();

    /* Skip remove if this macro is still in use */
    $res = $ldap->search("(&(objectClass=goFonAccount)(objectClass=gosaAccount)(goFonMacro=*))", array("goFonMacro", "cn"));
    while ($val = $ldap->fetch()){
      if(strstr($val['goFonMacro'][0],$this->dn)){
        msg_dialog::display(_("Error"), sprintf(_("Cannot delete entry because it is still in use by '%s'!"), $val['cn'][0]), ERROR_DIALOG);
        return false;
      }
    }

    /* Try to remove from database */
    if(count($this->goFonHomeServers)){
      $str = $this->remove_from_database(true);
      if($str){
        msg_dialog::display(_("Error"), $str, ERROR_DIALOG);
        return false;
      }
    }else{
      msg_dialog::display(_("Configuration error"), msgPool::noserver(_("GOfon")), WARNING_DIALOG);
      return false;
    }

    /* Remove phone macro */
    $ldap->rmDir($this->dn);
    new log("remove","gofonmacro/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
    if (!$ldap->success()){
      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_DEL, get_class()), LDAP_ERROR);
    }

    /* Delete references to object groups */
    $ldap->cd ($this->config->current['BASE']);
    $ldap->search ("(&(objectClass=gosaGroupOfNames)(member=".LDAP::prepare4filter($this->dn)."))", array("cn"));
    while ($ldap->fetch()){
      $og= new ogroup($this->config, $ldap->getDN());
      unset($og->member[$this->dn]);
      $og->save ();
      if (!$ldap->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, 0, get_class()), LDAP_ERROR);
      }
    }
  }


  /*! Save to LDAP */
  function save()
  {
    parent::save();
    unset($this->attrs['base']);

    /* Try to add entries to databases */
    $str = $this->add_to_database(true);
    if($str){
      msg_dialog::display(_("Error"), $str, ERROR_DIALOG);
    }

    /* Write back to ldap */
    $ldap= $this->config->get_ldap_link();
    $ldap->cat($this->dn, array('dn'));
    $a= $ldap->fetch();

    if (count($a)){
        $ldap->cd($this->dn);
        $this->cleanup();
        $ldap->modify ($this->attrs);

        $this->handle_post_events("modify");
    } else {
        $ldap->cd($this->config->current['BASE']);
        $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $this->dn));
        $ldap->cd($this->dn);
        $ldap->add($this->attrs);
        $this->handle_post_events("add");
    }
    if (!$ldap->success()){
        msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, 0, get_class()), LDAP_ERROR);
    }

    /* Log last action */
    if($this->initially_was_account){
        new log("modify","gofonmacro/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
    }else{
        new log("create","gofonmacro/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
    }
  }


  static function plInfo()
  {
    return array(
      "plShortName"   => _("Asterisk macro"),
      "plDescription" => _("Asterisk macro management"),
      "plSelfModify"  => FALSE,
      "plCategory"    => array("gofonmacro" => array("description" => _("GOfon macro"),
                                                    "objectClass" => "gofonMacro")),
      "plObjectType"  => array("macro" => array("name" => _("Asterisk macro"),
                                                "filter" => "objectClass=gofonMacro")),

      "plProvidedAcls" => array(
        "cn"                            => _("Macro name"),
        "base"                          => _("Base"),
        "description"                   => _("Description"),
        "displayName"                   => _("Display name"),
        "goFonMacroContent"             => _("Macro content and parameter"),
        "goFonMacroVisible"             => _("Visibility flag")
      )
    );
  }

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