<?php
/*******************************************************************************
 * Copyright (C) 2007 Easter-eggs
 * https://ldapsaisie.org
 *
 * Author: See AUTHORS file in top-level directory.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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.

******************************************************************************/

LSsession :: loadLSclass('LSlog_staticLoggerClass');

/**
 * This class implement relation between LDAP objects
 *
 * @property-read string $name
 * @property-read bool $correctly_configured
 * @property-read string $LSobject
 * @property-read string $linkAttribute
 * @property-read string $linkAttributeValue
 * @property-read string $linkAttributeOtherValues
 * @property-read string $list_function
 * @property-read string $getkeyvalue_function
 * @property-read string $update_function
 * @property-read string $remove_function
 * @property-read string $rename_function
 * @property-read string $canEdit_function
 * @property-read string $canEdit_attribute
 * @property-read array<string> $linkAttributeValues
 * @property-read string $relatedEditableAttribute
 */
class LSrelation extends LSlog_staticLoggerClass {

  /**
   * Reference to the LSldapObject
   * @var LSldapObject|null
   */
  private $obj = null;

  /**
   * Relation name
   * @var string
   */
  private $name = null;

  /**
   * Relation config
   * @see self::__construct()
   * @see self::checkConfig()
   * @see self::getConfig()
   * @var array
   */
  private $config = array();

  /**
   * LSrelation constructor
   *
   * An LSrelation object focus on one type of relations of a specific
   * object. All non-static method are designed to manipulate this type
   * of relation of the object specified at constuct time.
   *
   * @param LSldapObject &$obj   A reference to the LSldapObject
   * @param string $relationName The relation name
   *
   * @return void
   */
  public function __construct(&$obj, $relationName) {
    $this -> obj =& $obj;
    $this -> name = $relationName;
    $config = $obj -> getConfig("LSrelation.$relationName");
    if (is_array($config)) {
      $this -> config = $config;
      if ($this -> checkConfig())
        return;
      // Bad configuration
      $this -> config = array();
    }
    LSerror :: addErrorCode(
      'LSrelation_02',
      array(
        'relation' => $relationName,
        'LSobject' => $obj -> getType(),
      )
    );
  }

  /**
   * Allow conversion of LSrelation to string
   *
   * @return string The string representation of the LSrelation
   */
  public function __toString() {
    return "<LSrelation ".$this -> name." on ".$this -> obj -> toString(false).">";
  }

  /**
   * Return a configuration parameter (or default value)
   *
   * @param string $param      The configuration parameter
   * @param mixed $default    The default value (default : null)
   * @param string $cast       Cast resulting value in specific type (default : disabled)
   *
   * @return mixed The configuration parameter value or default value if not set
   **/
  public function getConfig($param, $default=null, $cast=null) {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;
    return LSconfig :: get($param, $default, $cast, $this -> config);
  }

  /**
   * Get relation info
   *
   * @param string $key The info name
   *
   * @return mixed The info value
   */
  public function __get($key) {
    switch ($key) {
      case 'name':
        return $this -> name;
      case 'correctly_configured':
        return boolval($this -> config);
      case 'LSobject':
      case 'linkAttribute':
      case 'linkAttributeValue':
      case 'linkAttributeOtherValues':
      case 'list_function':
      case 'getkeyvalue_function':
      case 'update_function':
      case 'remove_function':
      case 'rename_function':
      case 'canEdit_function':
      case 'canEdit_attribute':
        return $this -> getConfig($key);
      case 'linkAttributeValues':
        $linkAttributeValues = ensureIsArray($this -> linkAttributeOtherValues);
        if ($this -> linkAttributeValue)
          $linkAttributeValues[] = $this -> linkAttributeValue;
        return $linkAttributeValues;
      case 'relatedEditableAttribute':
        return $this -> getConfig(
          'canEdit_attribute',
          $this -> getConfig('linkAttribute', false)
        );
    }
    // Unknown key, log warning
    self :: log_warning("__get($key): invalid property requested\n".LSlog :: get_debug_backtrace_context());
  }

  /**
   * Check relation configuration
   *
   * @return boolean True if relation is properly configured, False otherwise
   */
  public function checkConfig() {
    // Check LSobject parameter
    if (!$this -> LSobject) {
      LSerror :: addErrorCode(
        'LSrelation_07',
        array(
          'parameter' => 'LSobject',
          'relation' => $this -> name,
          'LSobject' => $this -> LSobject
        )
      );
      return false;
    }

    // Load related object type
    if (!LSsession :: loadLSobject($this -> LSobject)) {
      LSerror :: addErrorCode(
        'LSrelation_04',
        array(
          'relation' => $this -> name,
          'LSobject' => $this -> LSobject
        )
      );
      return false;
    }

    // Check if it's a simple relation
    if ($this -> linkAttribute && $this -> linkAttributeValue) {
      // Check linkAttribute refered to an existing related object type attribute
      if (!call_user_func(array($this -> LSobject, 'hasAttr'), $this -> linkAttribute)) {
        LSerror :: addErrorCode(
          'LSrelation_08',
          array(
            'parameter' => 'linkAttribute',
            'relation' => $this -> name,
            'LSobject' => $this -> LSobject
          )
        );
        return false;
      }

      // Check linkAttributeValue
      if ($this -> linkAttributeValue != 'dn' && !$this -> obj -> hasAttr($this -> linkAttributeValue)) {
        LSerror :: addErrorCode(
          'LSrelation_08',
          array(
            'parameter' => 'linkAttributeValue',
            'relation' => $this -> name,
            'LSobject' => $this -> LSobject
          )
        );
        return false;
      }

      return true;
    }

    // Advanced relation: check all required parameters refered to related objects
    // methods
    $required_parameters = array(
      'list_function', 'getkeyvalue_function', 'update_function',
      'remove_function', 'rename_function', 'canEdit_function',
    );
    foreach($required_parameters as $p) {
      // Check parameter is defined
      if (!$this -> $p) {
        LSerror :: addErrorCode(
          'LSrelation_07',
          array(
            'parameter' => $p,
            'relation' => $this -> name,
            'LSobject' => $this -> LSobject
          )
        );
        return false;
      }

      // Check parameter refered to an existing related object class method
      if (!method_exists($this -> LSobject, $this -> $p)) {
        LSerror :: addErrorCode(
          'LSrelation_01',
          array(
            'parameter' => $p,
            'function' => $this -> $p,
            'LSobject' => $this -> LSobject,
            'relation' => $this -> name,
          )
        );
        return false;
      }
    }
    return true;
  }

  /**
   * Check a relation exist
   *
   * @param string $object_type  The object type
   * @param string $relation_name  The relation name
   *
   * @return boolean True if relation exist, false otherwise
   */
  public static function exists($object_type, $relation_name) {
    if ($object_type && LSsession :: loadLSobject($object_type)) {
      return is_array(LSconfig :: get("LSobjects.$object_type.LSrelation.$relation_name"));
    }
    return false;
  }

  /**
   * Get relation name
   *
   * @return string The relation name
   */
  public function getName() {
    return $this -> name;
  }

  /**
   * Check if user can edit this relation
   *
   * @return boolean True if user can edit this relation, false otherwise
   */
  public function canEdit() {
    return LSsession :: relationCanEdit($this -> obj -> getValue('dn'),$this -> obj -> getType(),$this -> name);
  }

  /**
   * Check if user can create a related object
   *
   * @return boolean True if user can create a related object, false otherwise
   */
  public function canCreate() {
    return LSsession :: canCreate($this -> LSobject);
  }

  /**
   * List related objects
   *
   * @return array|false An array of related objects (LSldapObject), of false in case of error
   */
  public function listRelatedObjects() {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;

    // Load related object type
    if (!LSsession :: loadLSobject($this -> LSobject)) {
      LSerror :: addErrorCode(
        'LSrelation_04',
        array(
          'relation' => $this -> name,
          'LSobject' => $this -> LSobject
        )
      );
      return false;
    }

    // Instanciate related object
    $objRel = new $this -> LSobject();

    // Use list_function
    if ($this -> list_function) {
      if (method_exists($this -> LSobject, $this -> list_function)) {
        return call_user_func_array(
          array($objRel, $this -> list_function),
          array(&$this -> obj)
        );
      }
      LSerror :: addErrorCode(
        'LSrelation_01',
        array(
          'parameter' => 'list_function',
          'function' => $this -> list_function,
          'LSobject' =>  $objRel -> getType(),
          'relation' => $this -> name,
        )
      );
      return False;
    }

    // Use linkAttribute & linkAttributeValue
    if ($this -> linkAttribute && $this -> linkAttributeValue) {
      return $objRel -> listObjectsInRelation(
        $this -> obj,
        $this -> linkAttribute,
        $this -> obj -> getType(),
        $this -> linkAttributeValues
      );
    }

    // Configuration problem
    LSerror :: addErrorCode(
      'LSrelation_05',
      array(
        'relation' => $this -> name,
        'LSobject' => $this -> LSobject,
        'action' => _('listing related objects')
      )
    );
    return false;
  }

  /**
   * Get the value to store to created the relation with $this -> obj
   *
   * @return array|false List of value of the link attribute, or false
   */
  public function getRelatedKeyValue() {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;

    // Load related object type
    if (!LSsession :: loadLSobject($this -> LSobject)) {
      LSerror :: addErrorCode(
        'LSrelation_04',
        array(
          'relation' => $this -> name,
          'LSobject' => $this -> LSobject
        )
      );
      return false;
    }

    // Instanciate related object
    $objRel = new $this -> LSobject();

    // Use getkeyvalue_function
    if ($this -> getkeyvalue_function) {
      if (method_exists($this -> LSobject, $this -> getkeyvalue_function)) {
        return call_user_func_array(
          array($objRel, $this -> getkeyvalue_function),
          array(&$this -> obj)
        );
      }
      LSerror :: addErrorCode(
        'LSrelation_01',
        array(
          'parameter' => 'getkeyvalue_function',
          'function' => $this -> getkeyvalue_function,
          'LSobject' =>  $objRel -> getType(),
          'relation' => $this -> name,
        )
      );
      return false;
    }

    // Use linkAttribute & linkAttributeValue
    if ($this -> linkAttribute && $this -> linkAttributeValue) {
      return $objRel -> getObjectKeyValueInRelation(
        $this -> obj,
        $this -> obj -> getType(),
        $this -> linkAttributeValue
      );
    }

    // Configuration problem
    LSerror :: addErrorCode(
      'LSrelation_05',
      array(
        'relation' => $this -> name,
        'LSobject' => $this -> LSobject,
        'action' => _('getting key value')
      )
    );
    return false;
  }

  /**
   * Check if user can edit the relation with the specified object
   *
   * @param LSldapObject &$objRel  A reference to the related object
   *
   * @return boolean True if user can edit the relation with the specified object, false otherwise
   */
  public function canEditRelationWithObject(&$objRel) {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;

    if (!$this -> canEdit()) return false;

    // Use canEdit_function
    if ($this -> canEdit_function) {
      if (method_exists($objRel, $this -> canEdit_function)) {
        return call_user_func(array($objRel, $this -> canEdit_function));
      }
      LSerror :: addErrorCode(
        'LSrelation_01',
        array(
          'parameter' => 'canEdit_function',
          'function' => $this -> canEdit_function,
          'LSobject' =>  $objRel -> getType(),
          'relation' => $this -> name,
        )
      );
      return False;
    }

    // Use related editable attribute
    if ($this -> relatedEditableAttribute) {
      return LSsession :: canEdit(
        $objRel -> getType(),
        $objRel -> getDn(),
        $this -> relatedEditableAttribute
      );
    }

    // Configuration problem
    LSerror :: addErrorCode(
      'LSrelation_05',
      array(
        'relation' => $this -> name,
        'LSobject' => $this -> LSobject,
        'action' => _('checking right on relation with specific object')
      )
    );
    return false;
  }

  /**
   * Remove relation with the specified object
   *
   * @param LSldapObject &$objRel  A reference to the related object
   *
   * @return boolean True if relation removed, false otherwise
   */
  public function removeRelationWithObject(&$objRel) {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;

    // Use remove_function
    if ($this -> remove_function) {
      if (method_exists($this -> LSobject, $this -> remove_function)) {
        return call_user_func_array(
          array($objRel, $this -> remove_function),
          array(&$this -> obj)
        );
      }
      LSerror :: addErrorCode(
        'LSrelation_01',
        array(
          'parameter' => 'remove_function',
          'function' => $this -> remove_function,
          'LSobject' =>  $objRel -> getType(),
          'relation' => $this -> name,
        )
      );
      return False;
    }

    // Use linkAttribute & linkAttributeValue
    if ($this -> linkAttribute && $this -> linkAttributeValue) {
      return $objRel -> deleteOneObjectInRelation($this -> obj, $this -> linkAttribute, $this -> obj -> getType(), $this -> linkAttributeValue, null, $this -> linkAttributeValues);
    }

    // Configuration problem
    LSerror :: addErrorCode(
      'LSrelation_05',
      array(
        'relation' => $this -> name,
        'LSobject' => $this -> LSobject,
        'action' => _('removing relation with specific object')
      )
    );
    return false;
  }

  /**
   * Rename relation with the specified object
   *
   * @param LSldapObject &$objRel  A reference to the related object
   * @param string $oldKeyValue The old key value of the relation
   *
   * @return boolean True if relation rename, false otherwise
   */
  public function renameRelationWithObject(&$objRel, $oldKeyValue) {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;

    // Use rename_function
    if ($this -> rename_function) {
      if (method_exists($objRel,$this -> rename_function)) {
        return call_user_func_array(array($objRel, $this -> rename_function), array(&$this -> obj, $oldKeyValue));
      }
      LSerror :: addErrorCode(
        'LSrelation_01',
        array(
          'parameter' => 'rename_function',
          'function' => $this -> rename_function,
          'LSobject' =>  $objRel -> getType(),
          'relation' => $this -> name,
        )
      );
      return False;
    }

    // Use linkAttribute & linkAttributeValue
    if ($this -> linkAttribute && $this -> linkAttributeValue) {
      return $objRel -> renameOneObjectInRelation(
        $this -> obj,
        $oldKeyValue,
        $this -> linkAttribute,
        $this -> obj -> getType(),
        $this -> linkAttributeValue
      );
    }

    // Configuration problem
    LSerror :: addErrorCode(
      'LSrelation_05',
      array(
        'relation' => $this -> name,
        'LSobject' => $this -> LSobject,
        'action' => _('checking right on relation with specific object')
      )
    );
    return false;
  }

  /**
   * Update relation with the specified DN objects list
   *
   * @param array $listDns Array of DN of the related objects
   *
   * @return boolean True if relations updated, false otherwise
   */
  public function updateRelations($listDns) {
    // Check relation is correctly configured
    if (!$this -> correctly_configured)
      return false;

    // Load related objects type
    if (!LSsession :: loadLSobject($this -> LSobject)) {
      LSerror :: addErrorCode(
        'LSrelation_04',
        array(
          'relation' => $this -> name,
          'LSobject' => $this -> LSobject
        )
      );
      return false;
    }

    // Instanciate related object
    $objRel = new $this -> LSobject();

    // Use update_function
    if ($this -> update_function) {
      if (method_exists($objRel, $this -> update_function)) {
        return call_user_func_array(
          array($objRel, $this -> update_function),
          array(&$this -> obj, $listDns)
        );
      }
      LSerror :: addErrorCode(
        'LSrelation_01',
        array(
          'parameter' => 'update_function',
          'function' => $this -> update_function,
          'LSobject' =>  $objRel -> getType(),
          'relation' => $this -> name,
        )
      );
      return false;
    }

    // Use linkAttribute & linkAttributeValue
    if ($this -> linkAttribute && $this -> linkAttributeValue) {
      return $objRel -> updateObjectsInRelation(
        $this -> obj,
        $listDns,
        $this -> linkAttribute,
        $this -> obj -> getType(),
        $this -> linkAttributeValue,
        null,
        $this -> linkAttributeValues
      );
    }

    // Configuration problem
    LSerror :: addErrorCode(
      'LSrelation_05',
      array(
        'relation' => $this -> name,
        'LSobject' => $this -> LSobject,
        'action' => _('updating relations')
      )
    );
    return false;
  }

 /*
  * Load display dependencies
  *
  * @return void
  */
  public static function loadDependenciesDisplay() {
    if (LSsession :: loadLSclass('LSselect')) {
      LSselect :: loadDependenciesDisplay();
    }
    LStemplate :: addJSscript('LSrelation.js');
    LStemplate :: addCssFile('LSrelation.css');

    LStemplate :: addJSconfigParam('LSrelation_labels', array(
      'close_confirm_text'      => _('Do you really want to delete'),
      'close_confirm_title'     => _('Warning'),
      'close_confirm_validate'  => _('Delete')
    ));
  }

 /**
  * Load LSrelations information of an object to display it on LSview.
  *
  * LSrelations information are provided to template by usind LSrelations variable.
  *
  * @param LSldapObject $object  L'objet dont on cherche les LSrelations
  *
  * @return void
  */
  public static function displayInLSview($object) {
    if (!($object instanceof LSldapObject))
      return;

    if (!is_array($object -> getConfig('LSrelation')))
      return;

    $LSrelations=array();
    $LSrelation_JSparams=array();
    foreach($object -> getConfig('LSrelation') as $relationName => $relationConf) {
      // Check user access
      if (!LSsession :: relationCanAccess($object -> getValue('dn'), $object->getType(), $relationName)) {
        self :: log_debug("User have no access to relation $relationName of ".$object->getType());
        continue;
      }

      $return=array(
        'label' => __($relationConf['label']),
        'LSobject' => $relationConf['LSobject']
      );

      if (isset($relationConf['emptyText'])) {
        $return['emptyText'] = __($relationConf['emptyText']);
      }
      else {
        $return['emptyText'] = _('No object.');
      }

      $id=rand();
      $return['id']=$id;
      $LSrelation_JSparams[$id]=array(
        'emptyText' => $return['emptyText']
      );
      $_SESSION['LSrelation'][$id] = array(
        'relationName' => $relationName,
        'objectType' => $object -> getType(),
        'objectDn' => $object -> getDn(),
      );
      $relation = new LSrelation($object, $relationName);
      if (!$relation -> correctly_configured)
        continue;

      if ($relation -> canEdit()) {
        $return['actions'][] = array(
          'label' => _('Modify'),
          'url' => "object/select/$id",
          'action' => 'modify',
          'class' => 'LSrelation_modify',
          'data' => array(
            'relation-id' => $id,
          )
        );
      }
      if ($relation -> canCreate()) {
         $return['actions'][] = array(
          'label' => _('New'),
          'url' => 'object/'.$relationConf['LSobject'].'/create?LSrelation='.$relationName.'&amp;relatedLSobject='.$object->getType().'&amp;relatedLSobjectDN='.urlencode($object -> getValue('dn')),
          'action' => 'create',
          'class' => null,
        );
      }

      $list = $relation -> listRelatedObjects();
      if (is_array($list)) {
        foreach($list as $o) {
          $return['objectList'][] = array(
            'text' => $o -> getDisplayName(
              LSconfig :: get('display_name_format', null, null, $relationConf),
              true
            ),
            'dn' => $o -> getDn(),
            'canEdit' => $relation -> canEditRelationWithObject($o)
          );
        }
      }
      else {
        $return['objectList']=array();
      }
      $LSrelations[]=$return;
    }

    self :: loadDependenciesDisplay();
    LStemplate :: assign('LSrelations',$LSrelations);
    LStemplate :: addJSconfigParam('LSrelations',$LSrelation_JSparams);
  }

  /*
   * AJAX methods
   */

  /**
   * Helper to check AJAX method call and instanciate corresponding
   * LSldapObject and LSrelation objects.
   *
   * @param array &$data Reference to AJAX returned data array
   * @param array &$conf Reference to the configuration array of the relation
   * @param LSldapObject &$object Reference to the related LSldapObject
   * @param array|string|null $additional_required_parameters List of additional required parameter
   *
   * @return bool
   */
  public static function _ajax_check_call(&$data, &$conf, &$object, &$relation, $additional_required_parameters=null) {
    $data['success'] = false;
    // Check parameters
    if (!isset($_REQUEST['id'])) {
      self :: log_warning("Parameter 'id' is missing.");
      LSerror :: addErrorCode('LSsession_12');
      return false;
    }

    // Check additional required parameters
    if ($additional_required_parameters) {
      foreach(ensureIsArray($additional_required_parameters) as $p) {
        if (!isset($_REQUEST[$p])) {
          self :: log_warning("Parameter '$p' is missing.");
          LSerror :: addErrorCode('LSsession_12');
          return false;
        }
        $data[$p] = $_REQUEST[$p];
      }
    }

    // Check relation exists in session
    if (!isset($_SESSION['LSrelation'][$_REQUEST['id']])) {
      self :: log_warning("No relation '".$_REQUEST['id']."' in session");
      return false;
    }

    // Load object type
    $conf = $_SESSION['LSrelation'][$_REQUEST['id']];
    if (!LSsession ::loadLSobject($conf['objectType'])) {
      self :: log_warning("Fail to load '".$conf['objectType']."'");
      return false;
    }
    $data['id'] = $_REQUEST['id'];

    // Check relation exists
    if (!self :: exists($conf['objectType'], $conf['relationName'])) {
      self :: log_warning("Relation '".$conf['relationName']."' not found in ".$conf['objectType']." configuration");
      return false;
    }

    // Instanciate object and load its data
    $object = new $conf['objectType']();
    if (!$object -> loadData($conf['objectDn'])) {
      self :: log_warning("Fail to load data of '".$conf['objectDn']."'");
      return false;
    }

    // Instanciate relation
    $relation = new LSrelation($object, $conf['relationName']);

    // Check relation is correctly configured
    if (!$relation -> correctly_configured)
      return false;

    // Check user access to it relation
    if (!$relation -> canEdit()) {
      LSerror :: addErrorCode('LSsession_11');
      return false;
    }

    self :: log_debug("_ajax_check_call(): ok");
    return true;
  }


  /**
   * Init LSselect for a relation
   *
   * @param array &$data Reference to returned data array
   *
   * @return void
   */
  public static function ajax_initSelection(&$data) {
    $conf = null;
    $object = null;
    $relation = null;
    if (!self :: _ajax_check_call($data, $conf, $object, $relation, 'href')) {
      return;
    }

    // Load LSselect
    if(!LSsession :: loadLSclass('LSselect', null, true)) {
      return;
    }

    // List related objects
    $list = $relation -> listRelatedObjects();
    if (!is_array($list)) {
      self :: log_warning('Fail to list related objects');
      return;
    }

    // Forge selected object list for LSselect
    $selected_objects = array();
    foreach($list as $o) {
      $selected_objects[$o -> getDn()] = array(
        'object_type' => $o -> getType(),
        'editableAttr' => $relation -> relatedEditableAttribute,
      );
    }

    // Init LSselect
    LSselect :: init(
      $_REQUEST['id'],
      array (
        $relation -> LSobject => array(
          'object_type' => $relation -> LSobject,
        )
      ),
      true,
      $selected_objects
    );

    // Set success
    $data['success'] = true;
  }

  /**
   * Update related object from LSselect result
   *
   * @param array &$data Reference to returned data array
   *
   * @return void
   */
  public static function ajax_updateFromSelection(&$data) {
    $conf = null;
    $object = null;
    $relation = null;
    if (!self :: _ajax_check_call($data, $conf, $object, $relation)) {
      return;
    }

    $LSobjectInRelation = $object->getConfig("LSrelation.".$conf['relationName'].".LSobject");
    $relationConf = $object->getConfig("LSrelation.".$conf['relationName']);

    // Load LSselect
    if(!LSsession :: loadLSclass('LSselect', null, true)) {
      return;
    }

    // Retrieve selected object from LSselect
    $selected_objects = LSselect :: getSelectedObjects($_REQUEST['id']);
    if (!is_array($selected_objects)) {
      self :: log_warning("Fail to retrieve selected object from LSselect");
      return;
    }
    self :: log_debug('Selected objects: '.varDump($selected_objects));

    // Update related objects
    if (!$relation -> updateRelations(array_keys($selected_objects))) {
      LSerror :: addErrorCode('LSrelation_03', $conf['relationName']);
      self :: log_warning("Fail to update objects in relation");
      return;
    }
    self :: log_debug('Related objects updated');

    // List related objects
    $list = $relation -> listRelatedObjects();
    if (is_array($list) && !empty($list)) {
      $data['html']="";
      foreach($list as $o) {
        if ($relation -> canEditRelationWithObject($o)) {
          $class=' LSrelation_editable';
        }
        else {
          $class='';
        }
        $data['html'].= "<li class='LSrelation'><a href='object/$LSobjectInRelation/".urlencode($o -> getDn())."' class='LSrelation$class' id='LSrelation_".$_REQUEST['id']."_".$o -> getDn()."'>".$o -> getDisplayName(NULL,true)."</a></li>\n";
      }
    }
    else {
      if (isset($relationConf['emptyText'])) {
        $data['html'] = "<li>".__($relationConf['emptyText'])."</li>\n";
      }
      else {
        $data['html'] = "<li>"._('No object.')."</li>\n";
      }
    }
    $data['success'] = true;
  }

  /**
   * Remove related object specify by DN
   *
   * @param array &$data Reference to returned data array
   *
   * @return void
   */
  public static function ajax_deleteByDn(&$data) {
    $conf = null;
    $object = null;
    $relation = null;
    if (!self :: _ajax_check_call($data, $conf, $object, $relation, 'dn')) {
      return;
    }

    // List related objects
    $list = $relation -> listRelatedObjects();
    if (!is_array($list)) {
      self :: log_warning('Fail to list related objects');
      LSerror :: addErrorCode('LSrelation_03',$conf['relationName']);
      return;
    }

    // For each related objects:
    // - check if DN match
    // - check user can edit relation with specific object
    // - remove relation
    $found = false;
    foreach($list as $o) {
      if($o -> getDn() == $_REQUEST['dn']) {
        if (!$relation -> canEditRelationWithObject($o)) {
          LSerror :: addErrorCode('LSsession_11');
          return;
        }
        if (!$relation -> removeRelationWithObject($o)) {
          LSerror :: addErrorCode('LSrelation_03', $conf['relationName']);
          return;
        }
        else {
          $found = true;
        }
        break;
      }
    }

    // Check object found
    if (!$found) {
      self :: log_warning("Object '".$_REQUEST['dn']."' not found in related objects list.");
      LSerror :: addErrorCode('LSrelation_03', $conf['relationName']);
      return;
    }

    // Set success
    $data['dn'] = $_REQUEST['dn'];
    $data['success'] = true;
  }

}

/**
 * Error Codes
 **/
LSerror :: defineError('LSrelation_01',
___("LSrelation : Invalid parameter '%{parameter}' of the relation %{relation}: objects %{LSobject} have no function '%{function}'.")
);
LSerror :: defineError('LSrelation_02',
___("LSrelation : Relation %{relation} of object type %{LSobject} unknown.")
);
LSerror :: defineError('LSrelation_03',
___("LSrelation : Error during relation update of the relation %{relation}.")
);
LSerror :: defineError('LSrelation_04',
___("LSrelation : Object type %{LSobject} unknown (Relation : %{relation}).")
);
LSerror :: defineError('LSrelation_05',
___("LSrelation : Incomplete configuration for LSrelation %{relation} of object type %{LSobject} for action : %{action}.")
);
LSerror :: defineError('LSrelation_06',
___("LSrelation : Invalid editable attribute for LSrelation %{relation} with LSobject %{LSobject}.")
);
LSerror :: defineError('LSrelation_07',
___("LSrelation : The configuration parameter '%{parameter}' of the relation %{relation} of %{LSobject} is missing.")
);
LSerror :: defineError('LSrelation_08',
___("LSrelation : The configuration parameter '%{parameter}' of the relation %{relation} of %{LSobject} is invalid.")
);
