*/ class LSldapObject extends LSlog_staticLoggerClass { /** * Object configuration * @var array */ var $config = array(); /** * Object type name * @var string */ var $type_name; /** * Object attributes * @var array */ var $attrs = array(); /** * Object active forms * @var array */ var $forms; /** * Active LSview * @var LSform */ var $view; /** * Object DN * @var string|boolean */ var $dn=false; /** * Object old DN (before moving it in LDAP directory) * @var string|boolean */ var $oldDn=false; /** * Array of registred values, used to add custom info to compute LSformat * @var array */ var $other_values=array(); /** * Cache of LSsession::whoami() result for the current connected user * @var array|null */ var $_whoami=NULL; /** * Cache of related objects * @see LSldapObject::updateLSrelationsCache() * @var array */ var $_LSrelationsCache=array(); /** * Object registered hooks on event * @see LSldapObject::addEvent() * @see LSldapObject::fireEvent() * @var array */ var $_events=array(); /** * Object registered hooks on event trigger object methods * @see LSldapObject::addObjectEvent() * @see LSldapObject::fireEvent() * @var array */ var $_objectEvents=array(); /** * Chache of the object computed information * @see LSldapObject::__get() * @var array */ var $cache=array(); /** * Constructeur * * Cette methode construit l'objet et définis la configuration. * Elle lance la construction du tableau d'attributs représentés par un objet LSattribute. * * @author Benjamin Renard * * @return void */ public function __construct() { $this -> type_name = get_class($this); $config = LSconfig :: get('LSobjects.'.$this -> type_name); if(is_array($config)) { $this -> config = $config; } else { LSerror :: addErrorCode('LSldapObject_01'); return; } foreach($this -> getConfig('attrs', array()) as $attr_name => $attr_config) { if(!$this -> attrs[$attr_name]=new LSattribute($attr_name,$attr_config,$this)) { return; } } } /** * Load object data from LDAP * * This method set object DN and load its data from LDAP * * @author Benjamin Renard * * @param string $dn The object DN * @param string|Net_LDAP2_Filter|null $additional_filter A custom LDAP filter that LDAP object * must respect to permit its data loading * * @return boolean True if object data loaded, false otherwise */ public function loadData($dn, $additional_filter=null) { $this -> dn = $dn; $filter = $this -> getObjectFilter(); if ($additional_filter) { if (!is_a($additional_filter, 'Net_LDAP2_Filter')) $additional_filter = Net_LDAP2_Filter :: parse($additional_filter); $filter = Net_LDAP2_Filter :: combine( 'and', array ($filter, $additional_filter) ); } $data = LSldap :: getAttrs($dn, $filter, array_keys($this -> attrs)); if(is_array($data) && !empty($data)) { foreach($this -> attrs as $attr_name => $attr) { if( !$this -> attrs[$attr_name] -> loadData( (isset($data[$attr_name])?$data[$attr_name]:NULL) ) ) return false; } $this->cache=array(); return true; } return false; } /** * Recharge les données de l'objet * * @author Benjamin Renard * * @return boolean true si la rechargement a réussi, false sinon. */ public function reloadData() { $data = LSldap :: getAttrs($this -> dn); foreach($this -> attrs as $attr_name => $attr) { if(!$this -> attrs[$attr_name] -> reloadData( (isset($data[$attr_name])?$data[$attr_name]:NULL) )) return false; } return true; } /** * Retourne le format d'affichage de l'objet * * @author Benjamin Renard * * @return string Format d'affichage de l'objet. */ public function getDisplayNameFormat() { return $this -> getConfig('display_name_format'); } /** * Retourne la valeur descriptive d'affichage de l'objet * * Cette fonction retourne la valeur descriptive d'affichage de l'objet en fonction * du format défini dans la configuration de l'objet ou spécifié en paramètre. * * @author Benjamin Renard * * @param string $spe string Format d'affichage de l'objet * @param bool $full boolean True pour afficher en plus le * subDnName * * @return string Valeur descriptive d'affichage de l'objet */ public function getDisplayName($spe=null, $full=false) { if (is_null($spe)) $spe = $this -> getDisplayNameFormat(); $val = $this -> getDisplayFData($spe); if (LSsession :: haveSubDn() && $full) { $val.=' ('.$this -> subDnName.')'; } return $val; } /** * Chaine formatée * * Cette fonction retourne la valeur d'une chaine formatée en prennant les valeurs * de l'objet. * * @author Benjamin Renard * * @param string $format Format de la chaine * * @return string Valeur d'une chaine formatée */ public function getFData($format) { $format=getFData($format,$this,'getValue'); return $format; } /** * Chaine formatee * * Cette fonction retourne la valeur d'une chaine formatee en prennant les valeurs * d'affichage de l'objet. * * @author Benjamin Renard * * @param LSformat $format String LSformat * @param array|null $extraValues Extra values to compute the LSformat * * @return string Valeur d'une chaine formatee */ public function getDisplayFData($format, $extraValues=null) { if (is_array($extraValues)) foreach($extraValues as $key => $val) $this -> registerOtherValue($key, $val); return getFData($format,$this,'getDisplayValue'); } /** * Constuct a form for this object * * This method create a LSform from the object and its attributes configurations. * * @param string $idForm Form identifier (required) * @param string $load DN of a similar object. If defined, attributes values of this object will be loaded in the form. * @param boolean $api_mode Enable API mode (defaut: false) * * @author Benjamin Renard * * @return LSform The created LSform object */ public function getForm($idForm, $load=NULL, $api_mode=false) { LSsession :: loadLSclass('LSform'); $LSform = new LSform($this, $idForm, null, $api_mode); $this -> forms[$idForm] = array($LSform, $load); $loadObject = null; if ($load) { $type = $this -> getType(); $loadObject = new $type(); if (!$loadObject -> loadData($load)) { $load = false; $loadObject = null; } } if ($load) { foreach($this -> attrs as $attr_name => $attr) { if(!$this -> attrs[$attr_name] -> addToForm($LSform,$idForm,$this,$loadObject -> attrs[$attr_name] -> getFormVal())) { $LSform -> can_validate = false; } } } else { foreach($this -> attrs as $attr_name => $attr) { if(!$this -> attrs[$attr_name] -> addToForm($LSform,$idForm,$this)) { $LSform -> can_validate = false; } } } return $LSform; } /** * Constuct a view for this object * * This method create a LSform in view mode from the object and its attributes configurations. * * @param boolean $api_mode Enable API mode (defaut: false) * * @author Benjamin Renard * * @return LSform The created LSform object */ public function getView($api_mode=false) { LSsession :: loadLSclass('LSform'); $this -> view = new LSform($this, 'view', null, $api_mode); foreach($this -> attrs as $attr_name => $attr) { $this -> attrs[$attr_name] -> addToView($this -> view, $api_mode); } $this -> view -> can_validate = false; return $this -> view; } /** * Rafraichis le formulaire de l'objet * * Cette méthode recharge les données d'un formulaire LSform. * * @param string $idForm Identifiant du formulaire a créer * * @author Benjamin Renard * * @return boolean true sile formulaire a été rafraichis, false sinon */ public function refreshForm($idForm) { $LSform = $this -> forms[$idForm][0]; foreach($this -> attrs as $attr_name => $attr) { if(!$this -> attrs[$attr_name] -> refreshForm($LSform,$idForm)) { return false; } } return true; } /** * Update LDAP object data from one form specify by it's ID. * * This method just valid form ID, extract form data and call * _updateData() protected method. * * @param string $idForm Form ID * @param boolean $justCheck to enable just check mode * * @see _updateData() * * @author Benjamin Renard * * @return boolean true if object data was updated, false otherwise */ public function updateData($idForm=NULL, $justCheck=False) { if($idForm!=NULL) { if(isset($this -> forms[$idForm])) $LSform = $this -> forms[$idForm][0]; else { LSerror :: addErrorCode('LSldapObject_02',$this -> getType()); return false; } } else { if(count($this -> forms) > 0) { reset($this -> forms); $idForm = key($this -> forms); $LSform = current($this -> forms); $config = $LSform[1]; $LSform = $LSform[0]; } else { LSerror :: addErrorCode('LSldapObject_03',$this -> getType()); return false; } } $new_data = $LSform -> exportValues(); self :: log_debug($this. "->updateData($idForm, $justCheck): new data=".varDump($new_data)); return $this -> _updateData($new_data, $idForm, $justCheck); } /** * Update LDAP object data from one form specify by it's ID. * * This method implement the continuation and the end of the object data * udpate. * * @param array $new_data New of object data * @param string $idForm Form ID * @param boolean $justCheck to enable just check mode * * @author Benjamin Renard * * @return boolean true if object data was updated, false otherwise * * @see updateData() * @see validateAttrsData() * @see submitChange() */ protected function _updateData($new_data, $idForm=null, $justCheck=False) { if(!is_array($new_data)) { return false; } foreach($new_data as $attr_name => $attr_val) { if(isset($this -> attrs[$attr_name])) { $this -> attrs[$attr_name] -> setUpdateData($attr_val); } } if($this -> validateAttrsData($idForm, $justCheck)) { self :: log_debug($this."->_updateData(): Update data are validated"); if ($justCheck) { self :: log_debug($this."->_updateData(): Just validate mode"); return True; } if (!$this -> fireEvent('before_modify')) { return false; } // $this -> attrs[ {inNewData} ] -> fireEvent('before_modify') foreach($new_data as $attr_name => $attr_val) { if ($this -> attrs[$attr_name] -> isUpdate() && !$this -> attrs[$attr_name] -> fireEvent('before_modify')) { return false; } } $new = $this -> isNew(); if ($this -> submitChange($idForm)) { self :: log_debug($this."->_updateData(): changes are submited"); // Event After Modify if (!$new && $idForm != 'create') { $this -> fireEvent('after_modify'); // $this -> attrs[*] => After Modify foreach($new_data as $attr_name => $attr_val) { if ($this -> attrs[$attr_name] -> isUpdate()) { $this -> attrs[$attr_name] -> fireEvent('after_modify'); } } } $this -> reloadData(); $this -> refreshForm($idForm); } else { return false; } return true; } else { return false; } } /** * Validate data returned by form * * @param string $idForm The source LSform ID * @param boolean $justCheck Set to true to enable just check mode (do not validate data using LDAP search) * * @author Benjamin Renard * * @return boolean True if form data are valid, False otherwise */ public function validateAttrsData($idForm=null, $justCheck=False) { $retval = true; if ($idForm) { $LSform=$this -> forms[$idForm][0]; } else { $LSform=false; } $forceGeneration = ( $LSform && $LSform -> dataEntryForm? LSconfig::get( 'forceGeneration', array(), 'array', $LSform -> dataEntryFormConfig ):array() ); foreach($this -> attrs as $attr_name => $attr) { $attr_values = $attr -> getValue(); if (!$attr -> isValidate()) { if($attr -> isUpdate()) { if (!$this -> validateAttrData($LSform, $attr, $justCheck)) { $retval = false; } } else if( in_array($attr_name, $forceGeneration) || (empty($attr_values) && $attr -> isRequired()) ) { if ( $attr -> canBeGenerated()) { if ($attr -> generateValue()) { if (!$this -> validateAttrData($LSform, $attr, $justCheck)) { LSerror :: addErrorCode('LSattribute_08',$attr -> getLabel()); $retval = false; } } else { LSerror :: addErrorCode('LSattribute_07',$attr -> getLabel()); $retval = false; } } else { // Don't blame on non-create form for attributes not-present in form (or freezed) if ($LSform && $idForm != 'create' && (!$LSform -> hasElement($attr_name) || $LSform -> isFreeze($attr_name))) continue; LSerror :: addErrorCode('LSattribute_06',$attr -> getLabel()); $retval = false; } } } } return $retval; } /** * Validate one attribute's data returned by form * * @param LSform $LSform The source LSform * @param LSattribute &$attr The attribute to validate * @param boolean $justCheck Set to true to enable just check mode (do not validate data using LDAP search) * * @author Benjamin Renard * * @return boolean True if form data for this attribute are valid, False otherwise */ public function validateAttrData(&$LSform, &$attr, $justCheck=false) { $retval = true; $vconfig = $attr -> getValidateConfig(); // Validate attribute values if ($justCheck) { self :: log_trace( "validateAttrData(".$LSform->idForm.", ".$attr->name."): ". "just check mode: do not validate attribute data using LDAP search" ); } else { foreach($attr -> getConfig('validation', array(), 'array') as $test) { self :: log_trace( "validateAttrData(".$LSform->idForm.", ".$attr->name."): ". "run validation with following config:\n".varDump($test) ); // Generate error message $msg_error = LSconfig :: get('msg', null, 'string', $test); if ($msg_error) { $msg_error = $this -> getFData(__($msg_error)); } else { $msg_error = getFData(_("The attribute %{attr} is not valid."), $attr -> getLabel()); } // Iter on attribute values to check all of it foreach(ensureIsArray($attr -> getUpdateData()) as $val) { // Validation using LDAP search if((isset($test['filter']) || isset($test['basedn'])) && isset($test['result'])) { $this -> other_values['val'] = $val; $sparams = array( 'scope' => LSconfig :: get('scope', 'sub', 'string', $test), 'onlyAccessible' => False, ); // Handle filter parameter $sfilter_user = LSconfig :: get('filter', null, 'string', $test); if ($sfilter_user) { $sfilter_user = $this -> getFData($test['filter']); // Check if filter format is surronded by '()' if ($sfilter_user[0] != '(') $sfilter_user = "($sfilter_user)"; $sfilter_user = Net_LDAP2_Filter::parse($sfilter_user); } // Handle object_type parameter & compose final LDAP filter string $object_type = LSconfig :: get('object_type', null, 'string', $test); if($object_type && LSsession :: loadLSobject($object_type) ) { $sfilter = self :: _getObjectFilter($object_type); if ($sfilter_user) $sfilter = LSldap::combineFilters('and', array($sfilter_user, $sfilter)); } else { $sfilter = $sfilter_user; } // Handle base_dn parameter $sbasedn = LSconfig :: get('basedn', null, 'string', $test); if ($sbasedn) $sbasedn = $this -> getFData($sbasedn); // If except_current_object (and not create form), list matching objets to exclude current one if (LSconfig :: get('except_current_object', false, 'bool', $test) && $LSform -> idForm != 'create') { $sret = LSldap :: search($sfilter, $sbasedn, $sparams); $dn = $this->getDn(); $ret = 0; foreach($sret as $obj) if ($obj['dn'] != $dn) $ret++; } else { // Otherwise, just retrieve number of matching objets $ret = LSldap :: getNumberResult($sfilter, $sbasedn, $sparams); if (!is_int($ret)) { // An error occured $retval = false; continue; } } // Check result $configured_result = LSconfig :: get('result', null, 'int', $test); if( ($configured_result == 0 && $ret != 0) || ($configured_result > 0 && $ret <= 0) ) { $retval = false; self :: log_warning( "validateAttrData(".$LSform->idForm.", ".$attr->name."): ". "validation with LDAP search on base DN='$sbasedn' and ". "filter='".($sfilter?$sfilter->as_string():null)."' error ($ret object(s) found)" ); if ($LSform) $LSform -> setElementError($attr, $msg_error); } else { self :: log_trace( "validateAttrData(".$LSform->idForm.", ".$attr->name."): ". "validation with LDAP search on base DN='$sbasedn' and ". "filter='".($sfilter?$sfilter->as_string():null)."' success ($ret object(s) found)" ); } } // Validation using external function else if(isset($test['function'])) { if (function_exists($test['function'])) { if(!call_user_func_array($test['function'], array(&$this))) { if ($LSform) $LSform -> setElementError($attr,$msg_error); $retval = false; } } else { LSerror :: addErrorCode( 'LSldapObject_04', array( 'attr' => $attr->name, 'obj' => $this->getType(), 'func' => $test['function'] ) ); $retval = false; } } else { LSerror :: addErrorCode( 'LSldapObject_05', array( 'attr' => $attr->name, 'obj' => $this->getType() ) ); $retval = false; } } } } // Generate values of dependent attributes $dependsAttrs = $attr->getDependsAttrs(); if (empty($dependsAttrs)) { self :: log_trace( "validateAttrData(".$LSform->idForm.", ".$attr->name."): ". "no dependent attribute" ); } else { foreach($dependsAttrs as $dependAttr) { if(!isset($this -> attrs[$dependAttr])){ LSerror :: addErrorCode( 'LSldapObject_14', array('attr_depend' => $dependAttr, 'attr' => $attr -> getLabel()) ); continue; } self :: log_debug( "validateAttrData(".$LSform->idForm.", ".$attr->name."): ". 'Attribute '.$attr->name.' updated: generate new value for attribute '.$dependAttr ); if($this -> attrs[$dependAttr] -> canBeGenerated()) { if (!$this -> attrs[$dependAttr] -> generateValue()) { LSerror :: addErrorCode('LSattribute_07', $this -> attrs[$dependAttr] -> getLabel()); $retval = false; } elseif (!$this -> validateAttrData($LSform, $this -> attrs[$dependAttr], $justCheck)) { LSerror :: addErrorCode('LSattribute_08', $this -> attrs[$dependAttr] -> getLabel()); $retval = false; } } else { LSerror :: addErrorCode('LSattribute_06',$this -> attrs[$dependAttr] -> getLabel()); $retval = false; } } } $attr -> validate(); unset($this -> other_values['val']); return $retval; } /** * Submit changes made to LDAP directory * * @param string $idForm The source LSform ID * * @author Benjamin Renard * * @return boolean True if changes are successfully push to LDAP directory, False otherwise */ public function submitChange($idForm) { $submitData=array(); $new = $this -> isNew(); if (!$new && $idForm == 'create') self :: log_fatal("submitChange($idForm): object isn't considered as new !?"); foreach($this -> attrs as $attr) { if(($attr -> isUpdate())&&($attr -> isValidate())) { if(($attr -> name == $this -> getConfig('rdn')) && (!$new)) { self :: log_debug("$this -> submitChange($idForm): renaming detected"); if (!$this -> fireEvent('before_rename')) { LSerror :: addErrorCode('LSldapObject_16'); return false; } $oldDn = $this -> getDn(); $this -> dn = false; $newDn = $this -> getDn(); if ($newDn) { self :: log_debug("$this -> submitChange($idForm): Rename me from '$oldDn' to '$newDn'"); if (!LSldap :: move($oldDn, $newDn)) { self :: log_error("$this -> submitChange($idForm): Fail to rename me from '$oldDn' to '$newDn'. Stop."); return false; } $this -> dn = $newDn; $this -> oldDn = $oldDn; // PHP Net_LDAP2 does not remove old RDN value : replace RDN value $submitData[$attr -> name] = $attr -> getUpdateData(); if (!$this -> fireEvent('after_rename')) { LSerror :: addErrorCode('LSldapObject_17'); return false; } } else { self :: log_error("$this -> submitChange($idForm): fail to retrieve new DN"); return false; } } else { $submitData[$attr -> name] = $attr -> getUpdateData(); } } } if(!empty($submitData)) { $dn = $this -> getDn(); if (!$dn) { LSerror :: addErrorCode('LSldapObject_13'); return false; } $this -> dn = $dn; self :: log_debug($this." -> submitChange($idForm): submitData=".varDump($submitData)); if ($new) { // Check DN is not already exist if (LSldap :: exists($dn)) { return false; } if (!$this -> fireEvent('before_create')) { LSerror :: addErrorCode('LSldapObject_20'); return false; } foreach ($submitData as $attr_name => $attr) { if (!$this -> attrs[$attr_name] -> fireEvent('before_create')) { LSerror :: addErrorCode('LSldapObject_20'); return false; } } } if (!LSldap :: update($this -> getType(),$dn, $submitData)) { self :: log_debug($this." -> submitChange($idForm): LSldap :: update() failed"); return false; } self :: log_debug($this." -> submitChange($idForm): changes applied in LDAP"); if ($new) { if (!$this -> fireEvent('after_create')) { LSerror :: addErrorCode('LSldapObject_21'); return false; } foreach ($submitData as $attr_name => $attr) { if (!$this -> attrs[$attr_name] -> fireEvent('after_create')) { LSerror :: addErrorCode('LSldapObject_21'); return false; } } } return true; } else { self :: log_debug($this." -> submitChange($idForm): no change"); return true; } } /** * Retourne les informations issus d'un DN * * @param string $dn Un DN. * * @author Benjamin Renard * * @return array|false Tableau : * - [0] : le premier paramètre * - [1] : les paramètres suivants */ public static function getDnInfos($dn) { $infos = ldap_explode_dn($dn, 0); if(!$infos) return false; $first = true; $basedn = ""; for($i=1; $i<$infos['count']; $i++) if($first) { $basedn .= $infos[$i]; $first = false; } else $basedn .= ','.$infos[$i]; return array($infos[0], $basedn); } /** * Retourne le filtre correpondants aux objetcClass de l'objet courant * * @author Benjamin Renard * * @return Net_LDAP2_Filter le filtre ldap correspondant au type de l'objet */ public function getObjectFilter() { return self :: _getObjectFilter($this -> type_name); } /** * Retourne le filtre correpondants aux objetcClass de l'objet * * @author Benjamin Renard * * @return Net_LDAP2_Filter|false le filtre ldap correspondant au type de l'objet, ou false */ public static function _getObjectFilter($type) { $oc=LSconfig::get("LSobjects.$type.objectclass"); if(!is_array($oc)) return false; $filters=array(); foreach ($oc as $class) { $filters[]=Net_LDAP2_Filter::create('objectClass','equals',$class); } $filter=LSconfig::get("LSobjects.$type.filter"); if ($filter) { $filters[]=Net_LDAP2_Filter::parse($filter); } $filter = LSldap::combineFilters('and',$filters); if ($filter) return $filter; LSerror :: addErrorCode('LSldapObject_30',$type); return false; } /** * Retourne le filtre correpondants au pattern passé * * @author Benjamin Renard * * @param string $pattern Le mot clé recherché * @param bool $approx booléen Booléen activant ou non la recherche approximative * * @return string|null le filtre ldap correspondant ou null si le pattern spécifié et vide */ public function getPatternFilter($pattern=null,$approx=null) { if ($pattern) { $search = new LSsearch($this -> type_name, 'LSldapObject', array('approx' => (bool)$approx)); $filter = $search -> getFilterFromPattern($pattern); if ($filter instanceof Net_LDAP2_Filter) { return $filter -> asString(); } } return NULL; } /** * Retourne une liste d'objet du même type. * * Effectue une recherche en fonction des paramètres passé et retourne un * tableau d'objet correspond au resultat de la recherche. * * @author Benjamin Renard * * @param Net_LDAP2_Filter|string|array|null $filter Filtre de recherche Ldap / Tableau de filtres de recherche * @param string|null $basedn DN de base pour la recherche * @param array $params Search parameters (as expected by Net_LDAP2::search()) * * @return array|false Tableau d'objets correspondant au resultat de la recherche, ou false */ public function listObjects($filter=NULL,$basedn=NULL,$params=array()) { if (!LSsession :: loadLSclass('LSsearch')) { LSerror::addErrorCode('LSsession_05','LSsearch'); return false; } $sparams = array( 'basedn' => $basedn, 'filter' => $filter ); if (is_array($params)) { $sparams=array_merge($sparams,$params); } $LSsearch = new LSsearch($this -> type_name,'LSldapObject::listObjects',$sparams,true); $LSsearch -> run(); return $LSsearch -> listObjects(); } /** * Retourne une liste d'objet du même type et retourne leur noms * * Effectue une recherche en fonction des paramètres passé et retourne un * tableau (dn => nom) correspondant au resultat de la recherche. * * @author Benjamin Renard * * @param Net_LDAP2_Filter|string|null $filter LDAP search filter * @param string|null $sbasedn Base DN of the search * @param array $sparams Search parameters (as expected by Net_LDAP2::search()) * @param string|false $displayFormat LSformat of objects's display name * @param bool $cache Enable/disable cache (default: true) * * @return array|false Tableau dn => name correspondant au resultat de la recherche, ou false */ public function listObjectsName($filter=NULL,$sbasedn=NULL,$sparams=array(),$displayFormat=false,$cache=true) { if (!LSsession :: loadLSclass('LSsearch')) { LSerror::addErrorCode('LSsession_05','LSsearch'); return false; } if (!$displayFormat) { $displayFormat = $this -> getDisplayNameFormat(); } $params = array( 'displayFormat' => $displayFormat, 'basedn' => $sbasedn, 'filter' => $filter ); if (is_array($sparams)) { $params=array_merge($sparams,$params); } $LSsearch = new LSsearch($this -> type_name,'LSldapObject::listObjectsName',$params,true); $LSsearch -> run($cache); return $LSsearch -> listObjectsName(); } /** * Recherche un objet à partir de la valeur exact de son RDN ou d'un filtre de * recherche LDAP sous la forme d'un LSformat qui sera construit avec la valeur * de $name. * * @author Benjamin Renard * * @param string $name RDN value or value to compute filter * @param string|null $basedn Base DN of the search * @param Net_LDAP2_Filter|string|null $filter LDAP search filter * @param array $params Search parameters (as expected by Net_LDAP2::search()) * * @return array Tableau d'objets correspondant au resultat de la recherche */ public function searchObject($name,$basedn=NULL,$filter=NULL,$params=NULL) { if (!$filter) { $filter = '('.$this -> getConfig('rdn').'='.$name.')'; } else { $filter = getFData($filter,$name); } return $this -> listObjects($filter,$basedn,$params); } /** * Return an objet value * * The value returned depends of the $val parameter. If the requested value is unknown, the returned value * will be $default (default: a space caracter = ' '). * * Supported values: * - 'dn' ou '%{dn} : The object DN * - [attribute name] : the value of the corresponding attribute * - [key of $this -> other_values] : the value of corresponding key in $this -> other_values * * @author Benjamin Renard * * @param string $val The requested value * @param boolean $first If true and the result is an array, return only the first value (optional, default: false) * @param string $default Default value if unknown (optional, default: a space caracter = ' ') * * @return mixed The requested value or $default if unknown */ public function getValue($val, $first=false, $default=' ') { $return = $default; if(($val=='dn')||($val=='%{dn}')) { $return = $this -> dn; } else if(($val=='rdn')||($val=='%{rdn}')) { $return = $this -> rdn; } else if(($val=='subDn')||($val=='%{subDn}')) { $return = $this -> subDnValue; } else if(($val=='subDnName')||($val=='%{subDnName}')) { $return = $this -> subDnName; } else if(isset($this -> attrs[$val])){ if (method_exists($this -> attrs[$val],'getValue')) $return = ensureIsArray($this -> attrs[$val] -> getValue()); } else if(isset($this -> other_values[$val])){ $return = $this -> other_values[$val]; } if (is_array($return) && $first) $return = (count($return) >= 1?$return[0]:null); return $return; } /** * Retourne une valeur d'affichage de l'objet * * @author Benjamin Renard * * @param string $val Le nom de la valeur demandee * * @return string la valeur demandee ou ' ' si celle-ci est inconnue. */ public function getDisplayValue($val) { if(isset($this -> attrs[$val])){ if (method_exists($this -> attrs[$val],'getDisplayValue')) return $this -> attrs[$val] -> getDisplayValue(); else return ' '; } else { return $this -> getValue($val); } } /** * Ajoute une valeur dans le tableau $this -> other_values * * @param string $name Le nom de la valeur * @param string $value La valeur * * @return void **/ public function registerOtherValue($name,$value) { $this -> other_values[$name]=$value; } /** * Retourn un tableau pour un select d'un objet du même type * * @author Benjamin Renard * * @return array('dn' => 'display') */ public function getSelectArray($pattern=NULL,$topDn=NULL,$displayFormat=NULL,$approx=false,$cache=true,$filter=NULL,$sparams=array()) { $sparams['pattern']=$pattern; return $this -> listObjectsName($filter,$topDn,$sparams,$displayFormat,$cache); } /** * Retourne le DN de l'objet * * Cette methode retourne le DN de l'objet. Si celui-ci n'existe pas, il le construit à partir de la * configuration de l'objet et la valeur de son attribut rdn. * * @author Benjamin Renard * * @return string|false Le DN de l'objet, ou false */ public function getDn() { if($this -> dn) { return $this -> dn; } else { $container_dn=$this -> getContainerDn(); if ($container_dn) { $rdn_attr = $this -> getConfig('rdn'); if( $rdn_attr && isset($this -> attrs[$rdn_attr]) ) { $rdn_val=$this -> attrs[$rdn_attr] -> getUpdateData(); if (!empty($rdn_val)) { return $rdn_attr.'='.$rdn_val[0].','.$container_dn; } else { LSerror :: addErrorCode('LSldapObject_12', $rdn_attr); return false; } } else { LSerror :: addErrorCode('LSldapObject_11',$this -> getType()); return false; } } return false; } } /** * Retourne le container DN de l'objet * * Cette methode retourne le container DN de l'objet. * * @author Benjamin Renard * * @return string|false Le container DN de l'objet, ou false */ public function getContainerDn() { $topDn = LSsession :: getTopDn(); $generate_container_dn = $this -> getConfig('generate_container_dn'); $container_dn = $this -> getConfig('container_dn'); if ($generate_container_dn) { if (is_callable($generate_container_dn)) { try { $container_dn = call_user_func_array($generate_container_dn, array(&$this)); return $container_dn.','.$topDn; } catch (Exception $e) { LSerror :: addErrorCode('LSldapObject_34',$e); } } else { LSerror :: addErrorCode('LSldapObject_33', $generate_container_dn); } } else if ($container_dn && $topDn) { return $container_dn.','.$topDn; } else { LSerror :: addErrorCode('LSldapObject_11',$this -> getType()); } LSerror :: addErrorCode('LSldapObject_32'); return false; } /** * Retourne le type de l'objet * * @author Benjamin Renard * * @return string Le type de l'objet ($this -> type_name) */ public function getType() { return $this -> type_name; } /** * Return connected user's LSprofiles on this object * * @author Benjamin Renard * * @return array Array of LSprofiles of connected user's LSprofiles on this object */ public function whoami() { if (!$this -> _whoami) $this -> _whoami = LSsession :: whoami($this -> dn); return $this -> _whoami; } /** * Retrieve object type translated label * * @param string $type The object type (optional, default: called class name) * * @author Benjamin Renard * * @return string|false The translated object type label, or false */ public static function getLabel($type=null) { if (is_null($type)) $type = get_called_class(); self :: log_debug("getLabel($type): LSobjects.$type.label"); $label = LSconfig::get("LSobjects.$type.label"); if (!$label) return false; return __($label); } /** * Supprime l'objet dans l'annuaire * * @author Benjamin Renard * * @return boolean True si l'objet à été supprimé, false sinon */ public function remove() { if ($this -> fireEvent('before_delete')) { if (LSldap :: remove($this -> getDn())) { if ($this -> fireEvent('after_delete')) { return true; } LSerror :: addErrorCode('LSldapObject_19'); } } else { LSerror :: addErrorCode('LSldapObject_18'); } return false; } /** * L'objet est-il nouveau * * @author Benjamin Renard * * @return boolean True si l'objet est nouveau, false sinon */ public function isNew() { return (!$this -> dn); } /** * Retourne la valeur (DN) du subDn de l'objet * * @param string $dn Un DN * * @return string La valeur du subDn de l'object */ public static function getSubDnValue($dn) { $subDn_value=''; $subDnLdapServer = LSsession :: getSortSubDnLdapServer(); foreach ($subDnLdapServer as $subDn => $subDn_name) { if (isCompatibleDNs($subDn,$dn)&&($subDn!=$dn)) { $subDn_value=$subDn; break; } } return $subDn_value; } /** * Retourne la nom du subDn de l'objet * * @param string $dn Un DN * * @return string Le nom du subDn de l'object */ public static function getSubDnName($dn) { $subDnLdapServer = LSsession :: getSortSubDnLdapServer(); return $subDnLdapServer[self :: getSubDnValue($dn)]; } /** * Methode créant la liste des objets en relations avec l'objet courant et qui * la met en cache ($this -> _LSrelationsCache) * * @return bool True en cas de cas ce succès, False sinon. */ private function updateLSrelationsCache() { $this -> _LSrelationsCache=array(); $LSrelations = $this -> getConfig('LSrelation'); if (is_array($LSrelations) && LSsession :: loadLSclass('LSrelation')) { $type = $this -> getType(); $me = new $type(); $me -> loadData($this -> getDn()); foreach($LSrelations as $relation_name => $relation_conf) { $relation = new LSrelation($me, $relation_name); $list = $relation -> listRelatedObjects(); if (is_array($list)) { $this -> _LSrelationsCache[$relation_name] = array( 'list' => $list, 'keyvalue' => $relation -> getRelatedKeyValue() ); } else { self :: log_debug('Problème durant la mise en cache de la relation '.$relation_name); return false; } } } return true; } /** * Methode executant les actions nécéssaires avant le changement du DN de * l'objet. * * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite * pour les objets plus complexe. * * @return bool True en cas de cas ce succès, False sinon. */ private function beforeRename() { // LSrelations return $this -> updateLSrelationsCache(); } /** * Methode executant les actions nécéssaires après le changement du DN de * l'objet. * * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite * pour les objets plus complexe. * * @return bool True en cas de cas ce succès, False sinon. */ private function afterRename() { $error = 0; // Change LSsession -> userObject Dn if(LSsession :: getLSuserObjectDn() == $this -> oldDn) { LSsession :: changeAuthUser($this); } // LSrelations foreach($this -> _LSrelationsCache as $relation_name => $objInfos) { $relation = new LSrelation($this, $relation_name); if (is_array($objInfos['list'])) { foreach($objInfos['list'] as $obj) { if (!$relation -> renameRelationWithObject($obj, $objInfos['keyvalue'])) { $error=1; } } } } return !$error; } /** * Methode executant les actions nécéssaires avant la suppression de * l'objet. * * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite * pour les objets plus complexe. * * @return bool True en cas de cas ce succès, False sinon. */ private function beforeDelete() { $return = $this -> updateLSrelationsCache(); foreach(array_keys($this -> attrs) as $attr_name) { if (!$this -> attrs[$attr_name] -> fireEvent('before_delete')) { $return = false; } } return $return; } /** * Methode executant les actions nécéssaires après la suppression de * l'objet. * * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite * pour les objets plus complexe. * * @return bool True en cas de cas ce succès, False sinon. */ private function afterDelete() { $error = 0; // LSrelations foreach($this -> _LSrelationsCache as $relation_name => $objInfos) { $relation = new LSrelation($this, $relation_name); if (is_array($objInfos['list'])) { foreach($objInfos['list'] as $obj) { if (!$relation -> canEditRelationWithObject($obj)) { LSerror :: addErrorCode('LSsession_11'); } elseif (!$relation -> removeRelationWithObject($obj)) $error=1; } } } // Binding LSattributes foreach(array_keys($this -> attrs) as $attr_name) { if (!$this -> attrs[$attr_name] -> fireEvent('after_delete')) { $error = true; } } // LSsearch : Purge LSobject cache if (LSsession :: loadLSclass('LSsearch')) { LSsearch :: purgeCache($this -> type_name); } return !$error; } /** * Methode executant les actions nécéssaires après la création de * l'objet. * * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite * pour les objets plus complexe. * * @return bool True en cas de cas ce succès, False sinon. */ private function afterCreate() { self :: log_debug('after'); $error = 0; // container_auto_create if (LSsession :: isSubDnLSobject($this -> getType())) { if (is_array(LSsession :: $ldapServer['subDn']['LSobject'][$this -> getType()]['LSobjects'])) { foreach(LSsession :: $ldapServer['subDn']['LSobject'][$this -> getType()]['LSobjects'] as $type) { if (LSsession :: loadLSobject($type)) { $conf_type=LSconfig :: get("LSobjects.$type"); if (isset($conf_type['container_auto_create'])&&isset($conf_type['container_dn'])) { $dn = $conf_type['container_dn'].','.$this -> getDn(); if(!LSldap :: getNewEntry($dn,$conf_type['container_auto_create']['objectclass'],$conf_type['container_auto_create']['attrs'],true)) { self :: log_debug("Impossible de créer l'entrée fille : ".print_r( array( 'dn' => $dn, 'objectClass' => $conf_type['container_auto_create']['objectclass'], 'attrs' => $conf_type['container_auto_create']['attrs'] ) ,true)); $error=1; } } } else { $error=1; } } } } // LSsearch : Purge LSobject cache if (LSsession :: loadLSclass('LSsearch')) { LSsearch :: purgeCache($this -> type_name); } return !$error; } /** * Methode executant les actions nécéssaires après la modification de * l'objet. * * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite * pour les objets plus complexe. * * @return bool True en cas de cas ce succès, False sinon. */ private function afterModify() { $error = 0; // LSsearch : Purge LSobject cache if (LSsession :: loadLSclass('LSsearch')) { LSsearch :: purgeCache($this -> type_name); } return !$error; } /** * Retourne la valeur clef d'un objet en relation * * @param LSldapObject $object Un object de type $objectType * @param string $objectType Le type d'objet en relation * @param array|string $attrValues La/les valeur(s) que doit/peut avoir l'attribut : * - soit le dn (par defaut) * - soit une des valeurs d'un attribut * * @return array|false La liste des valeurs clefs d'un objet en relation, ou false **/ public static function getObjectKeyValueInRelation($object, $objectType, $attrValues='dn') { if (!$objectType) { LSerror :: addErrorCode('LSrelation_05','getObjectKeyValueInRelation'); return false; } $keyValues = array(); foreach (ensureIsArray($attrValues) as $attrValue) { if ($attrValue == 'dn') { $dn=$object -> getDn(); if (!in_array($dn, $keyValues)) $keyValues[] = $dn; } else { $values = $object -> getValue($attrValue); if (is_array($values)) foreach ($values as $keyValue) if (!in_array($keyValue, $keyValues)) $keyValues[] = $keyValue; } } return $keyValues; } /** * Retourne la liste des relations pour l'objet en fonction de sa présence * dans un des attributs * * Retourne un tableau de d'objet (type : $objectType) correspondant à la * relation entre l'objet $object et les objets de type $objectType. Cette relation * est établis par la présence de la valeur de référence à l'objet dans * l'attribut des objets de type $objectType. * * @param LSldapObject $object Un object de type $objectType * @param string $attr L'attribut dans lequel l'objet doit apparaitre * @param string $objectType Le type d'objet en relation * @param array|string $attrValues La/les valeur(s) que doit/peut avoir l'attribut : * - soit le dn (par defaut) * - soit une des valeurs d'un attribut * * @return array|false Array of $objectType Les objets en relations, or false **/ public function listObjectsInRelation($object, $attr, $objectType, $attrValues='dn') { if ((!$attr)||(!$objectType)) { LSerror :: addErrorCode('LSrelation_05','listObjectsInRelation'); return false; } $keyValues = self :: getObjectKeyValueInRelation($object, $objectType, $attrValues); if (!empty($keyValues)) { $keyValuesFilters = array(); foreach($keyValues as $keyValue) { $keyValuesFilters[] = Net_LDAP2_Filter::create($attr,'equals',$keyValue); } $filter = LSldap::combineFilters('or', $keyValuesFilters); return $this -> listObjects( $filter, LSsession :: getRootDn(), array( 'scope' => 'sub', 'recursive' => true, 'withoutCache'=>true, 'onlyAccessible' => false ) ); } return array(); } /** * Ajoute un objet en relation dans l'attribut $attr de $this * * @param LSldapObject $object Un objet de type $objectType à ajouter * @param string $attr L'attribut dans lequel l'objet doit être ajouté * @param string $objectType Le type d'objet en relation * @param array|string $attrValue La valeur que doit avoir l'attribut : * - soit le dn (par defaut) * - soit la valeur [0] d'un attribut * @param string $canEditFunction Le nom de la fonction pour vérifier que la * relation avec l'objet est éditable par le user * * @return boolean true si l'objet à été ajouté, False sinon **/ public function addOneObjectInRelation($object,$attr,$objectType,$attrValue='dn',$canEditFunction=NULL) { if ((!$attr)||(!$objectType)) { LSerror :: addErrorCode('LSrelation_05','addOneObjectInRelation'); return false; } if ($object instanceof $objectType) { if ($canEditFunction) { if (!$this -> $canEditFunction()) { LSerror :: addErrorCode('LSsession_11'); return false; } } elseif (!LSsession::canEdit($this -> getType(), $this -> getDn(), $attr)) { LSerror :: addErrorCode('LSsession_11'); return false; } if ($this -> attrs[$attr] instanceof LSattribute) { if ($attrValue=='dn') { $val = $object -> getDn(); } else { $val = $object -> getValue($attrValue); $val = $val[0]; } $values = $this -> attrs[$attr] -> getValue(); if ($this -> attrs[$attr] -> config['multiple']) { if (!is_array($values)) { $updateData = array($val); } else if (!in_array($val,$values)) { $values[]=$val; $updateData = $values; } } else { if (($values[0]!=$val)&&($values!=$val)) { $updateData = array($val); } } if (isset($updateData)) { return $this -> _updateData(array($attr => $updateData)); } return true; } } return false; } /** * Supprime un objet en relation dans l'attribut $attr de $this * * @param LSldapObject $object Un objet de type $objectType à supprimer * @param string $attr L'attribut dans lequel l'objet doit être supprimé * @param string $objectType Le type d'objet en relation * @param string $attrValue La valeur que doit avoir l'attribut : * - soit le dn (par defaut) * - soit la valeur [0] d'un attribut * @param string|null $canEditFunction Le nom de la fonction pour vérifier que la * relation avec l'objet est éditable par le user * @param array $attrValues L'ensembe des valeurs que peut avoir l'attribut avant mise à jour : * - soit le dn (par defaut) * - soit une des valeurs d'un attribut * * @return boolean true si l'objet à été supprimé, False sinon **/ public function deleteOneObjectInRelation($object,$attr,$objectType,$attrValue='dn',$canEditFunction=NULL,$attrValues=null) { if ((!$attr)||(!$objectType)) { LSerror :: addErrorCode('LSrelation_05','deleteOneObjectInRelation'); return false; } if ($object instanceof $objectType) { if ($canEditFunction) { if (!$this -> $canEditFunction()) { LSerror :: addErrorCode('LSsession_11'); return false; } } elseif (!LSsession::canEdit($this -> getType(), $this -> getDn(), $attr)) { LSerror :: addErrorCode('LSsession_11'); return false; } if ($this -> attrs[$attr] instanceof LSattribute) { if (!$attrValues) $attrValues = array($attrValue); $keyValues = self :: getObjectKeyValueInRelation($object, $objectType, $attrValues); $values = ensureIsArray($this -> attrs[$attr] -> getValue()); if ($values) { $updateData = array(); foreach($values as $value) { if (!in_array($value, $keyValues)) { $updateData[] = $value; } } return $this -> _updateData(array($attr => $updateData)); } } } return false; } /** * Renome un objet en relation dans l'attribut $attr de $this * * @param LSldapObject $object Un objet de type $objectType à renomer * @param string|array[string] $oldValues Le(s) ancienne(s) valeur(s possible(s) * faisant référence à l'objet * @param string $attr L'attribut dans lequel l'objet doit être supprimé * @param string $objectType Le type d'objet en relation * @param array|string $attrValue La valeur que doit avoir l'attribut : * - soit le dn (par defaut) * - soit la valeur [0] d'un attribut * * @return boolean True en cas de succès, False sinon */ public function renameOneObjectInRelation($object, $oldValues, $attr, $objectType, $attrValue='dn') { if ((!$attr)||(!$objectType)) { LSerror :: addErrorCode('LSrelation_05','renameOneObjectInRelation'); return false; } if (!($object instanceof $objectType)) return false; if (!($this -> attrs[$attr] instanceof LSattribute)) return false; $values = ensureIsArray($this -> attrs[$attr] -> getValue()); if (!$values) return false; $updateData = array(); $oldValues = ensureIsArray($oldValues); foreach($values as $value) { if (!in_array($value, $oldValues)) { $updateData[] = $value; } else { if ($attrValue == 'dn') { $val = $object -> getDn(); } else { $val = $object -> getValue($attrValue); $val = $val[0]; } $updateData[] = $val; } } return $this -> _updateData(array($attr => $updateData)); } /** * Met à jour les objets du meme type que $this en relation avec l'objet $object * de type $objectType en modifiant la valeur de leur attribut $attr des objets * en relation * * @param LSldapObject $object Un object (type : $this -> userObjectType) : l'utilisateur * @param array[string] $listDns Un tableau des DNs des objets en relation * @param string $attr L'attribut dans lequel l'utilisateur doit apparaitre * @param string $objectType Le type d'objet en relation * @param string $attrValue La valeur que doit avoir l'attribut : * - soit le dn (par defaut) * - soit la valeur [0] d'un attribut * @param string|null $canEditFunction Le nom de la fonction pour vérifier que la * relation avec l'objet est éditable par le user * @param array|string|null $attrValues L'ensembe des valeurs que peut avoir l'attribut avant mise à jour : * - soit le dn (par defaut) * - soit une des valeurs d'un attribut * * @return boolean true si tout c'est bien passé, False sinon **/ public function updateObjectsInRelation($object,$listDns,$attr,$objectType,$attrValue='dn',$canEditFunction=NULL,$attrValues=null) { if ((!$attr)||(!$objectType)) { LSerror :: addErrorCode('LSrelation_05','updateObjectsInRelation'); return false; } if (!$attrValues) $attrValues = array($attrValue); $currentDns = array(); $currentObjects = $this -> listObjectsInRelation($object, $attr, $objectType, $attrValues); if(!is_array($currentObjects)) return false; for ($i=0; $i getDn(); } self :: log_trace("updateObjectsInRelation($object, $attr, $objectType, $attrValue): current DNs=".varDump($currentDns)); $dontTouch = array_intersect($listDns, $currentDns); self :: log_trace("updateObjectsInRelation($object, $attr, $objectType, $attrValue): dontTouch=".varDump($dontTouch)); for($i=0; $i getDn(), $dontTouch)) continue; if (!$currentObjects[$i] -> deleteOneObjectInRelation($object, $attr, $objectType, $attrValue, $canEditFunction, $attrValues)) { return false; } } $type = $this -> getType(); foreach($listDns as $dn) { if (in_array($dn, $dontTouch)) continue; $obj = new $type(); if ($obj -> loadData($dn)) { if (!$obj -> addOneObjectInRelation($object, $attr, $objectType, $attrValue, $canEditFunction)) { return false; } } else { return false; } } return true; } /** * Ajouter une action lors d'un événement * * @param string $event Le nom de l'événement * @param string $fct Le nom de la fonction à exectuer * @param array $param Paramètre pour le lancement de la fonction * @param string $class Nom de la classe possèdant la méthode $fct à executer * * @return void */ public function addEvent($event,$fct,$param=NULL,$class=NULL) { $this -> _events[$event][] = array( 'function' => $fct, 'param' => $param, 'class' => $class ); } /** * Ajouter une action sur un objet lors d'un événement * * @param string $event Le nom de l'événement * @param object $obj L'objet dont la méthode doit être executé * @param string $meth Le nom de la méthode * @param string $param Paramètre d'execution de la méthode * * @return void */ public function addObjectEvent($event,&$obj,$meth,$param=NULL) { $this -> _objectEvents[$event][] = array( 'obj' => &$obj, 'meth' => $meth, 'param' => $param ); } /** * Lance les actions à executer lors d'un événement * * @param string $event Le nom de l'événement * * @return boolean True si tout c'est bien passé, false sinon */ public function fireEvent($event) { self :: log_debug(strval($this)." -> fireEvent($event)"); // Object event $return = $this -> fireObjectEvent($event); // Config $funcs = $this -> getConfig($event); if($funcs) { if (!is_array($funcs)) $funcs = array($this -> config[$event]); foreach($funcs as $func) { if(function_exists($func)) { self :: log_debug(strval($this)." -> fireEvent($event): run ".format_callable($func)); if(!call_user_func_array($func, array(&$this))) { $return = false; LSerror :: addErrorCode('LSldapObject_07',array('func' => $func,'event' => $event)); } } else { $return = false; LSerror :: addErrorCode('LSldapObject_06',array('func' => $func,'event' => $event)); } } } // Binding via addEvent if (isset($this -> _events[$event]) && is_array($this -> _events[$event])) { foreach ($this -> _events[$event] as $e) { if ($e['class']) { if (class_exists($e['class'])) { $obj = new $e['class'](); if (method_exists($obj,$e['fct'])) { try { self :: log_debug(strval($this)." -> fireEvent($event): run ".format_callable(array($obj, $e['fct']))); call_user_func_array(array($obj,$e['fct']),array(&$e['param'])); } catch(Exception $er) { self :: log_exception($er, strval($this)." -> fireEvent($event): exception occured running ".format_callable(array($obj, $e['fct']))); LSerror :: addErrorCode('LSldapObject_10',array('class' => $e['class'],'meth' => $e['fct'],'event' => $event)); $return = false; } } else { LSerror :: addErrorCode('LSldapObject_09',array('class' => $e['class'],'meth' => $e['fct'],'event' => $event)); $return = false; } } else { LSerror :: addErrorCode('LSldapObject_08',array('class' => $e['class'],'meth' => $e['fct'],'event' => $event)); $return = false; } } else { if (function_exists($e['fct'])) { try { self :: log_debug(strval($this)." -> fireEvent($event): run ".format_callable($e['fct'])); call_user_func_array($e['fct'],array(&$e['param'])); } catch(Exception $er) { self :: log_exception($er, strval($this)." -> fireEvent($event): exception occured running ".format_callable($e['fct'])); LSerror :: addErrorCode('LSldapObject_27',array('func' => $e['fct'],'event' => $event)); $return = false; } } else { LSerror :: addErrorCode('LSldapObject_26',array('func' => $e['fct'],'event' => $event)); $return = false; } } } } // Binding via addObjectEvent if (isset($this -> _objectEvents[$event]) && is_array($this -> _objectEvents[$event])) { foreach ($this -> _objectEvents[$event] as $e) { if (method_exists($e['obj'],$e['meth'])) { try { self :: log_debug(strval($this)." -> fireEvent($event): run ".format_callable(array($e['obj'], $e['meth']))); call_user_func_array(array($e['obj'], $e['meth']),array(&$e['param'])); } catch(Exception $er) { self :: log_exception($er, strval($this)." -> fireEvent($event): exception occured running ".format_callable(array($e['obj'], $e['meth']))); LSerror :: addErrorCode('LSldapObject_29',array('meth' => $e['meth'],'event' => $event)); $return = false; } } else { LSerror :: addErrorCode('LSldapObject_28',array('meth' => $e['meth'],'event' => $event)); $return = false; } } } return $return; } /** * Lance les actions à executer lors d'un événement sur l'objet lui-même * * @param string $event Le nom de l'événement * * @return boolean True si tout c'est bien passé, false sinon **/ public function fireObjectEvent($event) { switch($event) { case 'after_create': return $this -> afterCreate(); case 'after_delete': return $this -> afterDelete(); case 'after_rename': return $this -> afterRename(); case 'after_modify': return $this -> afterModify(); /* case 'before_create': return $this -> beforeCreate(); */ case 'before_delete': return $this -> beforeDelete(); case 'before_rename': return $this -> beforeRename(); /* case 'before_modify': return $this -> beforeModify(); */ } return true; } /** * Access to infos of the object * * @param string $key The name of the value * * @return mixed The value **/ public function __get($key) { if ($key=='subDnValue') { if (isset($this -> cache['subDnValue'])) { return $this -> cache['subDnValue']; } $this -> cache['subDnValue'] = self :: getSubDnValue($this -> dn); return $this -> cache['subDnValue']; } elseif ($key=='subDnName') { if (isset($this -> cache['subDnName']) && $this -> cache['subDnName']) { return $this -> cache['subDnName']; } $this -> cache['subDnName'] = self :: getSubDnName($this -> dn); return $this -> cache['subDnName']; } elseif ($key=='rdn') { $rdn_attr = $this -> getConfig('rdn'); if ($rdn_attr && isset($this -> attrs[ $rdn_attr ])) { return $this -> attrs[ $rdn_attr ] -> getValue(); } return false; } elseif ($key=='type') { return $this -> getType(); } // Unknown key, log warning self :: log_warning("__get($key): invalid property requested\n".LSlog :: get_debug_backtrace_context()); } /** * Check if object type have a specified attribute * * @param string $attr_name The attribute name * * @return boolean True if object type have a attribute of this name, False otherwise */ public static function hasAttr($attr_name) { return array_key_exists($attr_name, LSconfig :: get('LSobjects.'.get_called_class().'.attrs', array())); } /** * List IOformats of this object type * * @return array Array of valid IOformats of this object type **/ public function listValidIOformats() { $ret = array(); $ioFormats = $this -> getConfig('ioFormat'); if (is_array($ioFormats)) { foreach($ioFormats as $name => $conf) { $ret[$name] = _((isset($conf['label'])?$conf['label']:$name)); } } return $ret; } /** * Check if an IOformat is valid for this object type * * @param string $f The IOformat name to check * * @return boolean True if it's a valid IOformat, false otherwise **/ public function isValidIOformat($f) { return is_array($this -> getConfig("ioFormat.$f")); } /** * 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) { return LSconfig :: get($param, $default, $cast, $this -> config); } /** * Allow conversion of LdapObject to string * * @return string The string representation of the LdapObject */ public function __toString() { return $this -> toString(true); } /** * Return string representation of the object * * @param boolean $enclosed If true, result will be enclosed using "< >" (optional, default: true) * * @return string The string representation of the LdapObject */ public function toString($enclosed=true) { $str = "LdapObject ".$this -> getType(); if ($this -> dn) $str .= " ".$this -> dn; else { $str .= " (new)"; $rdn_attr = $this -> getConfig('rdn'); if( $rdn_attr && isset($this -> attrs[$rdn_attr]) ) { $rdn_val = $this -> attrs[$rdn_attr] -> getUpdateData(); if (!empty($rdn_val)) $str .= " $rdn_attr=".$rdn_val[0]; } } if ($enclosed) return "<$str>"; return $str; } /** * CLI show command * * @param array $command_args Command arguments : * - Positional arguments : * - LSobject type * - object DN * - Optional arguments : * - -r|--raw-values : show raw values (instead of display ones) * * @return boolean True on success, false otherwise **/ public static function cli_show($command_args) { $objType = null; $dn = null; $raw_values = false; $json = false; $pretty = false; foreach ($command_args as $arg) { if ($arg == '-r' || $arg == '--raw-values') $raw_values = true; elseif ($arg == '-j' || $arg == '--json') $json = true; elseif ($arg == '-p' || $arg == '--pretty') $pretty = true; elseif (is_null($objType)) { $objType = $arg; } elseif (is_null($dn)) { $dn = $arg; } else LScli :: usage("Invalid $arg parameter."); } if (is_null($objType) || is_null($dn)) LScli :: usage('You must provide LSobject type and DN.'); if (!LSsession :: loadLSobject($objType)) return false; $obj = new $objType(); if (!$obj->loadData($dn)) { self :: log_fatal("Fail to load object $dn data from LDAP"); return false; } if ($json) $obj -> _cli_json_export($raw_values, $pretty); else $obj -> _cli_show($raw_values); return true; } /** * Args autocompleter for CLI show search * * @param array[string] $command_args List of already typed words of the command * @param int $comp_word_num The command word number to autocomplete * @param string $comp_word The command word to autocomplete * @param array[string] $opts List of global available options * * @return array[string] List of available options for the word to autocomplete **/ public static function cli_show_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) { $opts = array_merge($opts, array ('-r', '--raw-values', '-j', '--json', '-p', '--pretty')); // Handle positional args $objType = null; $objType_arg_num = null; $dn = null; $dn_arg_num = null; for ($i=0; $i < count($command_args); $i++) { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { // Defined it $objType = $command_args[$i]; LScli :: unquote_word($objType); $objType_arg_num = $i; // Check object type exists $objTypes = LScli :: autocomplete_LSobject_types($objType); // Load it if exist and not trying to complete it if (in_array($objType, $objTypes) && $i != $comp_word_num) { LSsession :: loadLSobject($objType, false); } } elseif (is_null($dn)) { $dn = $command_args[$i]; LScli :: unquote_word($dn); $dn_arg_num = $i; } } } // If objType not already choiced (or currently autocomplete), add LSobject types to available options if (!$objType || $objType_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word)); // If dn not alreay choiced (or currently autocomplete), try autocomplete it elseif (!$dn || $dn_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_dn($objType, $comp_word)); return LScli :: autocomplete_opts($opts, $comp_word); } /** * CLI helper to show the object info * * @param bool $raw_values Show attributes raw values (instead of display ones) * * @return void **/ public function _cli_show($raw_values=false) { echo $this -> type_name." (".($this -> dn?$this -> dn:'new').") :\n"; // Show attributes if (is_array($this -> getConfig('LSform.layout'))) { foreach($this -> getConfig('LSform.layout') as $tab_name => $tab) { echo " - ".(isset($tab['label'])?$tab['label']:$tab_name)." :\n"; foreach ($tab['args'] as $attr_name) { echo $this -> _cli_show_attr($attr_name, $raw_values, " "); } echo "\n"; } } else { foreach ($this -> attrs as $attr_name => $attr) { echo $this -> _cli_show_attr($attr_name, $raw_values); } echo "\n"; } // Show LSrelations if (LSsession :: loadLSclass('LSrelation') && is_array($this -> getConfig('LSrelation'))) { foreach ($this -> getConfig('LSrelation') as $rel_name => $rel_conf) { echo " - ".(isset($rel_conf['label'])?$rel_conf['label']." (relation $rel_name)":$rel_name)." :\n"; $relation = new LSrelation($this, $rel_name); $list = $relation -> listRelatedObjects(); if (is_array($list)) { foreach($list as $o) { echo " - ".$o -> getDisplayName(NULL,true)." (".$o -> getDn().")\n"; } if (empty($list)) { echo " => ".(isset($rel_conf['emptyText'])?$rel_conf['emptyText']:"No objects.")."\n"; } } else { self :: log_error("Fail to load related objects."); } echo "\n"; } } } /** * CLI helper to show the attribute * * @param string $attr_name The attribute name * @param bool $raw_values Show attributes raw values (instead of display ones) * @param string $prefix Prefix for each line displayed (optional, default: no prefix) * * @return string|false The formated attribute message, or false **/ private function _cli_show_attr($attr_name, $raw_values=false, $prefix="") { if (!isset($this -> attrs[$attr_name])) return false; $values = ($raw_values?$this -> attrs[$attr_name]->getValue():$this -> attrs[$attr_name]->getDisplayValue()); return self :: _cli_show_attr_values($attr_name, $values, $prefix); } /** * CLI helper to show the attribute values * * @param string $attr_name The attribute name * @param string|array[string] $values Attribute values * @param string $prefix Prefix for each line displayed (optional, default: no prefix) * * @return string The formated attribute values message **/ private function _cli_show_attr_values($attr_name, $values, $prefix="") { $return = "$prefix - ".$this -> attrs[$attr_name]->getLabel()." ($attr_name) :"; if (empty($values)) { return $return." empty\n"; } $values = ensureIsArray($values); // Truncate values if too long for ($i=0; $i < count($values); $i++) if (strlen($values[$i]) > 70) $values[$i] = substr($values[$i], 0, 65)."[...]"; $return .= (count($values) > 1?"\n$prefix - ":" "); $return .= implode("\n$prefix - ", $values); return $return."\n"; } /** * CLI helper to show the attributes values * * @param array $attrs_values attrs The attributes values * @param string|null $label The label text to show before attributes values * @param string $prefix Prefix for each line displayed (optional, default: no prefix) * * @return string The formated attributes values message **/ private function _cli_show_attrs_values($attrs_values, $label=null, $prefix="") { $return = ""; if ($label) $return .= "$prefix$label\n"; foreach ($attrs_values as $attr => $values) $return .= self :: _cli_show_attr_values($attr, $values, $prefix); return $return; } /** * CLI helper to export the object info as JSON * * @param bool $raw_values Export attributes raw values (instead of display ones) * @param bool $pretty Prettify JSON export * * @return void **/ private function _cli_json_export($raw_values=false, $pretty=false) { $export = array( 'dn' => $this -> dn, 'attrs' => array(), 'relations' => array(), ); // Export attributes values foreach (array_keys($this -> attrs) as $attr_name) { $export['attrs'][$attr_name] = array( 'label' => $this -> attrs[$attr_name]->getLabel(), 'values' => ( $raw_values? $this -> attrs[$attr_name]->getValue(): $this -> attrs[$attr_name]->getDisplayValue() ), ); // If attribute value can't be encoded as JSON, encode it as base64 if ($export['attrs'][$attr_name]['values'] && json_encode($export['attrs'][$attr_name]['values']) === false ) { $base64_values = array(); foreach($export['attrs'][$attr_name]['values'] as $value) $base64_values[] = base64_encode($value); $export['attrs'][$attr_name]['values'] = $base64_values; } } // Export LSrelations if (LSsession :: loadLSclass('LSrelation') && is_array($this -> getConfig('LSrelation'))) { foreach ($this -> getConfig('LSrelation') as $rel_name => $rel_conf) { $export['relations'][$rel_name] = array( 'label' => (isset($rel_conf['label'])?$rel_conf['label']:null), 'relatedObjects' => array(), ); $relation = new LSrelation($this, $rel_name); $list = $relation -> listRelatedObjects(); if (is_array($list)) { foreach($list as $o) { $export['relations'][$rel_name]['relatedObjects'][$o -> getDn()] = array( 'name' => $o -> getDisplayName(NULL,true), 'type' => get_class($o), ); } } else { self :: log_error("Fail to load related objects."); } } } $output = json_encode($export, ($pretty?JSON_PRETTY_PRINT:0)); if ($output === false) self :: log_error("Fail to encode data as JSON"); else echo $output; } /** * CLI remove command * * @param array $command_args Command arguments : * - Positional arguments : * - LSobject type * - object DN * - Optional arguments : * - -N|--no-confirm : Do not ask for confirmation * * @return boolean True on success, false otherwise **/ public static function cli_remove($command_args) { $objType = null; $dn = null; $confirm = true; foreach ($command_args as $arg) { if ($arg == '-N' || $arg == '--no-confirm') $confirm = false; elseif (is_null($objType)) { $objType = $arg; } elseif (is_null($dn)) { $dn = $arg; } else LScli :: usage("Invalid $arg parameter."); } if (is_null($objType) || is_null($dn)) LScli :: usage('You must provide LSobject type and DN.'); if (!LSsession :: loadLSobject($objType)) return false; $obj = new $objType(); if (!$obj->loadData($dn)) { self :: log_fatal("Fail to load object $dn data from LDAP"); return false; } if ($confirm) { $obj -> _cli_show(); // Sure ? if (!LScli :: confirm("\nAre you sure you want to delete this object?")) return True; } if ($obj -> remove()) { self :: log_info("Object ".$obj->getDn()." removed."); return true; } self :: log_error("Fail to remove object ".$obj->getDn()."."); return false; } /** * Args autocompleter for CLI remove command * * @param array[string] $command_args List of already typed words of the command * @param int $comp_word_num The command word number to autocomplete * @param string $comp_word The command word to autocomplete * @param array[string] $opts List of global available options * * @return array[string] List of available options for the word to autocomplete **/ public static function cli_remove_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) { $opts = array_merge($opts, array ('-N', '--no-confirm')); // Handle positional args $objType = null; $objType_arg_num = null; $dn = null; $dn_arg_num = null; for ($i=0; $i < count($command_args); $i++) { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { // Defined it $objType = $command_args[$i]; LScli :: unquote_word($objType); $objType_arg_num = $i; // Check object type exists $objTypes = LScli :: autocomplete_LSobject_types($objType); // Load it if exist and not trying to complete it if (in_array($objType, $objTypes) && $i != $comp_word_num) { LSsession :: loadLSobject($objType, false); } } elseif (is_null($dn)) { $dn = $command_args[$i]; LScli :: unquote_word($dn); $dn_arg_num = $i; } } } // If objType not already choiced (or currently autocomplete), add LSobject types to available options if (!$objType || $objType_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word)); // If dn not alreay choiced (or currently autocomplete), try autocomplete it elseif (!$dn || $dn_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_dn($objType, $comp_word)); return LScli :: autocomplete_opts($opts, $comp_word); } /** * CLI helper to parse attribute values given via command argument in format : * * attr=value1|value2 * * @param LSldapObject $obj The LDAP object * @param array &$attrs_values The reference of the array that store attributes values * @param string $command_arg The command argument to parse * @param string $delimiter The delimiter of multiple-value attribute (optional, default: '|') * * @return boolean True if attribute values successfully parsed, False otherwise **/ function _cli_parse_attr_values($obj, &$attrs_values, $command_arg, $delimiter='|') { $arg_parts = explode('=', $command_arg); if (count($arg_parts) < 2) LScli :: usage("Invalid parameter '$command_arg'."); $attr = $arg_parts[0]; if (!isset($obj -> attrs[$attr])) LScli :: usage("Invalid parameter '$command_arg': ".$obj->type_name." object as no attribute '$attr'."); // Split an check values $values = array(); foreach (explode($delimiter, implode('=', array_slice($arg_parts, 1))) as $value) if (!empty($value)) $values[] = $value; if (!array_key_exists($attr, $attrs_values)) $attrs_values[$attr] = $values; else $attrs_values[$attr] = array_merge($attrs_values[$attr], $values); return true; } /** * CLI create command * * @param string $command_args Command arguments : * - Positional arguments : * - LSobject type * - object DN * - attributes values in format attr=value1|value2 * - Optional arguments : * - -D|--delimiter : delimiter for multiple values attributes (default: "|") * - -N|--no-confirm : Do not ask for confirmation * - -j|--just-try : Just-try mode = validate provided data but do not really * create LDAP object data * * Note: for multiple-values attributes, you also could specify attribute and value * multiple time, for instance : attr1=value1 attr1=value2 * * @return boolean True on success, false otherwise **/ public static function cli_create($command_args) { $objType = $obj = null; $delimiter = "|"; $confirm = true; $just_try = false; $attrs_values = array(); for ($i=0; $i < count($command_args); $i++) { switch ($command_args[$i]) { case '-d': case '--delimiter': $delimiter = $command_args[++$i]; if ($delimiter == '=') LScli :: usage("Invalid delimiter: you can't use '=' as delimiter."); break; case '-N': case '--no-confirm': $confirm = false; break; case '-j': case '--just-try': $just_try = true; break; default: if (is_null($objType)) { $objType = $command_args[$i]; if (!LSsession :: loadLSobject($objType)) return false; $obj = new $objType(); } else { // Change on an attribute $obj -> _cli_parse_attr_values($obj, $attrs_values, $command_args[$i], $delimiter); } } } // @phpstan-ignore-next-line if (is_null($objType) || empty($attrs_values)) LScli :: usage('You must provide LSobject type, DN and at least one change.'); // Instanciate a create LSform (in API mode) $form = $obj -> getForm('create', null, true); // Check all changed attributes are in modify form and are'nn freezed foreach ($attrs_values as $attr => $value) { if (!$form -> hasElement($attr)) LScli :: usage("Change on attribute '$attr' is not possible (attribute not in create form)."); if ($form -> isFreeze($attr)) LScli :: usage("Change on attribute '$attr' is not possible (attribute freezed in create form)."); } // Set form data from inputed data, validate form (only for present data) and validate data (just-validation) if ($form -> setPostData($attrs_values, true) && $form -> validate(true) && $obj -> updateData('create', true)) { // Data validated, get user confirmation if ($confirm) { echo $obj -> _cli_show(false); // Sure ? if (!LScli :: confirm("Are you sure you want to create an $objType with this attributes's values?")) return True; } // Just-try mode: stop if ($just_try) { self :: log_info($obj -> getDn().": Just-try mode : provided information validated but object not updated."); return True; } // Create object in LDAP if ($obj -> updateData('create')) { self :: log_info($obj -> getDn().": Object created."); return True; } } // An error occured: show/log it $error_msg = "Validation errors occured on provided information:"; $errors = $form->getErrors(); if (is_array($errors) && !empty($errors)) $error_msg .= "\n".$obj -> _cli_show_attrs_values($errors, ""); else $error_msg .= " unknown error"; LSlog :: error($error_msg); return false; } /** * Args autocompleter for CLI create command * * @param array[string] $command_args List of already typed words of the command * @param int $comp_word_num The command word number to autocomplete * @param string $comp_word The command word to autocomplete * @param array[string] $opts List of global available options * * @return array[string] List of available options for the word to autocomplete **/ public static function cli_create_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) { $opts = array_merge($opts, array ('-j', '--just-try', '-D', '--delimiter', '-N', '--no-confirm')); // Handle positional args $objType = null; $objType_arg_num = null; for ($i=0; $i < count($command_args); $i++) { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { // Defined it $objType = $command_args[$i]; LScli :: unquote_word($objType); $objType_arg_num = $i; // Check object type exists $objTypes = LScli :: autocomplete_LSobject_types($objType); // Load it if exist and not trying to complete it if (in_array($objType, $objTypes) && $i != $comp_word_num) { LSsession :: loadLSobject($objType, false); } } } else { // All args accept option, increase $i $i++; } } self :: log_debug("obj type :'$objType' (#$objType_arg_num)"); // Handle completion of args value self :: log_debug("Last complete word = '".$command_args[$comp_word_num-1]."'"); switch ($command_args[$comp_word_num-1]) { case '-D': case '--delimiter': return array('|', ';'); } // If objType not already choiced (or currently autocomplete), add LSobject types to available options if (!$objType || $objType_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word)); // Otherwise, autocomplete on attribute=value elseif ($objType && class_exists($objType)) { LScli :: need_ldap_con(); $obj = new $objType(); $form = $obj -> getForm('create', null, true); // Instanciate create form in API mode $form -> autocomplete_attrs_values($opts, $comp_word); } return LScli :: autocomplete_opts($opts, $comp_word); } /** * CLI modify command * * @param string $command_args Command arguments : * - Positional arguments : * - LSobject type * - object DN * - changes to made on object in format attr=value1|value2 * - Optional arguments : * - -D|--delimiter : delimiter for multiple values attributes (default: "|") * - -N|--no-confirm : Do not ask for confirmation * - -j|--just-try : Just-try mode = validate changes but do not really update LDAP object data * * Note: for multiple-values attributes, you also could specify attribute and value * multiple time, for instance : attr1=value1 attr1=value2 * * @return boolean True on success, false otherwise **/ public static function cli_modify($command_args) { $objType = $obj = null; $dn = null; $delimiter = "|"; $confirm = true; $just_try = false; $changes = array(); for ($i=0; $i < count($command_args); $i++) { switch ($command_args[$i]) { case '-D': case '--delimiter': $delimiter = $command_args[++$i]; if ($delimiter == '=') LScli :: usage("Invalid delimiter: you can't use '=' as delimiter."); break; case '-N': case '--no-confirm': $confirm = false; break; case '-j': case '--just-try': $just_try = true; break; default: if (is_null($objType)) { $objType = $command_args[$i]; if (!LSsession :: loadLSobject($objType)) return false; $obj = new $objType(); } elseif (is_null($dn)) { $dn = $command_args[$i]; } else { // Change on an attribute $obj -> _cli_parse_attr_values($obj, $changes, $command_args[$i], $delimiter); } } } // @phpstan-ignore-next-line if (is_null($objType) || is_null($dn) || empty($changes)) LScli :: usage('You must provide LSobject type, DN and at least one change.'); if (!$obj->loadData($dn)) { self :: log_fatal("Fail to load object $dn data from LDAP"); return false; } if ($confirm) { echo $obj -> _cli_show_attrs_values($changes, "Attributes's values changes:"); // Sure ? if (!LScli :: confirm("Are you sure you want to change attributes's values of this object as specified?")) return True; } // Instanciate a modify LSform (in API mode) $form = $obj -> getForm('modify', null, true); // Check all changed attributes are in modify form and are'nn freezed foreach ($changes as $attr => $value) { if (!$form -> hasElement($attr)) LScli :: usage("Change on attribute '$attr' is not possible (attribute not in modify form)."); if ($form -> isFreeze($attr)) LScli :: usage("Change on attribute '$attr' is not possible (attribute freezed in modify form)."); } // Set form data from inputed data, validate form (only for present data) and validate data if (!$form -> setPostData($changes, true) || !$form -> validate(true) || !$obj -> updateData('modify', $just_try)) { $error_msg = "Validation errors occured on your changes:"; $errors = $form->getErrors(); if (is_array($errors) && !empty($errors)) $error_msg .= "\n".$obj -> _cli_show_attrs_values($errors, ""); else $error_msg .= " unknown error"; LSlog :: error($error_msg); return false; } elseif ($just_try) { self :: log_info("$dn: Just-try mode : changes validated but object not updated."); } else { self :: log_info("$dn: Object updated."); } return True; } /** * Args autocompleter for CLI modify command * * @param array[string] $command_args List of already typed words of the command * @param int $comp_word_num The command word number to autocomplete * @param string $comp_word The command word to autocomplete * @param array[string] $opts List of global available options * * @return array[string] List of available options for the word to autocomplete **/ public static function cli_modify_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) { $opts = array_merge($opts, array ('-j', '--just-try', '-D', '--delimiter', '-N', '--no-confirm')); // Handle positional args $objType = null; $objType_arg_num = null; $dn = null; $dn_arg_num = null; for ($i=0; $i < count($command_args); $i++) { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { // Defined it $objType = $command_args[$i]; LScli :: unquote_word($objType); $objType_arg_num = $i; // Check object type exists $objTypes = LScli :: autocomplete_LSobject_types($objType); // Load it if exist and not trying to complete it if (in_array($objType, $objTypes) && $i != $comp_word_num) { LSsession :: loadLSobject($objType, false); } } elseif (is_null($dn)) { $dn = $command_args[$i]; LScli :: unquote_word($dn); $dn_arg_num = $i; } } else { // All args accept option, increase $i $i++; } } self :: log_debug("obj type :'$objType' (#$objType_arg_num) / dn :'$dn' (#$dn_arg_num)"); // Handle completion of args value self :: log_debug("Last complete word = '".$command_args[$comp_word_num-1]."'"); switch ($command_args[$comp_word_num-1]) { case '-D': case '--delimiter': return array('|', ';'); } // If objType not already choiced (or currently autocomplete), add LSobject types to available options if (!$objType || $objType_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word)); // If dn not already choiced (or currently autocomplete), try autocomplete it elseif (!$dn || $dn_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_dn($objType, $comp_word)); // Otherwise, autocomplete on attribute=value elseif ($objType && class_exists($objType) && $dn) { LScli :: need_ldap_con(); $obj = new $objType(); if (!$obj->loadData($dn)) { self :: log_error("Fail to load object $dn data from LDAP"); } else { $form = $obj -> getForm('modify', null, True); // Instanciate modify form in API mode $form -> autocomplete_attrs_values($opts, $comp_word); } } return LScli :: autocomplete_opts($opts, $comp_word); } /** * CLI relation command * * @param string $command_args Command arguments : * - Positional arguments : * - LSobject type * - object DN * - relation ID * - Optional arguments : * - -a|--add : Add related object (specified by DN) * - -r|--remove : Remove related object (specified by DN) * * @return boolean True on success, false otherwise **/ public static function cli_relation($command_args) { $objType = null; $dn = null; $relName = null; $add = array(); $remove = array(); for ($i=0; $i < count($command_args); $i++) { if ($command_args[$i] == '-a' || $command_args[$i] == '--add') $add[] = $command_args[++$i]; elseif ($command_args[$i] == '-r' || $command_args[$i] == '--remove') $remove[] = $command_args[++$i]; elseif (is_null($objType)) { $objType = $command_args[$i]; } elseif (is_null($dn)) { $dn = $command_args[$i]; } elseif (is_null($relName)) { $relName = $command_args[$i]; } else LScli :: usage("Invalid ".$command_args[$i]." parameter."); } if (is_null($objType) || is_null($dn) || is_null($relName)) LScli :: usage('You must provide LSobject type, DN and relation ID.'); if (empty($add) && empty($remove)) LScli :: usage('You must provide at least one DN of related object to add/remove.'); if (!LSsession :: loadLSobject($objType)) return false; $obj = new $objType(); if (!$obj->loadData($dn)) { self :: log_fatal("Fail to load object $dn data from LDAP"); return false; } if (!LSsession :: loadLSclass('LSrelation')) { self :: log_fatal("Fail to load LSrelation class."); return false; } if (!is_array($obj -> getConfig("LSrelation.$relName"))) { self :: log_fatal("LSobject $objType have no relation '$relName'."); return false; } $relation = new LSrelation($obj, $relName); // List current related objects $list = $relation -> listRelatedObjects(); $listDns = array(); if (is_array($list)) { foreach($list as $o) { $listDns[] = $o -> getDn(); } } self :: log_debug("Current related object(s) :".varDump($listDns)); // Keep a copy of initial related objects list $initialListDns = $listDns; // Handle add $relatedLSobject = $obj -> getConfig("LSrelation.$relName.LSobject"); $search = new LSsearch( $relatedLSobject, "LSldapObject.cli_relation.$objType.$relName", array( 'scope' => 'base', ) ); foreach ($add as $dn) { // Check if DN is already in relation if (in_array($dn, $listDns)) { self :: log_debug("LSobject $relatedLSobject $dn is already in relation with ".$obj -> getDn()."."); continue; } // Check DN refer to a related object $search -> setParam('basedn', $dn); $search -> run(false); $result = $search -> listObjectsDn(); if (!is_array($result) || count($result) != 1) { self :: log_error("No $relatedLSobject found for DN $dn"); return false; } $listDns[] = $dn; } // Handle remove foreach ($remove as $dn) { $found = false; while(true) { $key = array_search($dn, $listDns); if ($key === false) break; $found = true; unset($listDns[$key]); } if (!$found) self :: log_debug("LSobject $relatedLSobject $dn is not in relation with ".$obj -> getDn()."."); } if ($initialListDns == $listDns) { self :: log_info('No changes done.'); return True; } self :: log_debug("New related object(s) list: ".varDump($listDns)); if ($relation -> updateRelations($listDns)) { self :: log_info('Objects in relation updated.'); return True; } self :: log_error("Fail to update objects in relation"); return False; } /** * Args autocompleter for CLI relation command * * @param array[string] $command_args List of already typed words of the command * @param int $comp_word_num The command word number to autocomplete * @param string $comp_word The command word to autocomplete * @param array[string] $opts List of global available options * * @return array[string] List of available options for the word to autocomplete **/ public static function cli_relation_args_autocompleter($command_args, $comp_word_num, $comp_word, $opts) { $opts = array_merge($opts, array ('-a', '--add', '-r', '--remove')); // Handle positional args $objType = null; $objType_arg_num = null; $dn = null; $dn_arg_num = null; $relation_id = null; $relation_id_arg_num = null; for ($i=0; $i < count($command_args); $i++) { if (!in_array($command_args[$i], $opts)) { // If object type not defined if (is_null($objType)) { // Defined it $objType = $command_args[$i]; LScli :: unquote_word($objType); $objType_arg_num = $i; // Check object type exists $objTypes = LScli :: autocomplete_LSobject_types($objType); // Load it if exist and not trying to complete it if (in_array($objType, $objTypes) && $i != $comp_word_num) { LSsession :: loadLSobject($objType, false); } } elseif (is_null($dn)) { $dn = $command_args[$i]; LScli :: unquote_word($dn); $dn_arg_num = $i; } elseif (is_null($relation_id)) { $relation_id = $command_args[$i]; LScli :: unquote_word($relation_id); $relation_id_arg_num = $i; } } else { // All args accept option, increase $i $i++; } } self :: log_debug("obj type :'$objType' (#$objType_arg_num) / dn :'$dn' (#$dn_arg_num) / relation_id:'$relation_id' (#$relation_id_arg_num)"); // Handle completion of args value self :: log_debug("Last complete word = '".$command_args[$comp_word_num-1]."'"); switch ($command_args[$comp_word_num-1]) { case '-a': case '--add': if (!$objType || !$dn || !$relation_id) return array(); $related_obj_type = LSconfig :: get("LSobjects.$objType.LSrelation.$relation_id.LSobject"); self :: log_debug("Related obj type='$related_obj_type'"); if ($related_obj_type) return LScli :: autocomplete_LSobject_dn($related_obj_type, $comp_word); return array(); case '-r': case '-remove': if (!$objType || !$dn || !$relation_id) return array(); if (!LSsession :: loadLSobject($objType)) { self :: log_error("Invalid object type $objType"); return array(); } LScli :: need_ldap_con(); $obj = new $objType(); if (!$obj->loadData($dn)) { self :: log_error("Fail to load object $dn data from LDAP"); return array(); } if (!LSsession :: loadLSclass('LSrelation')) { self :: log_error("Fail to load LSrelation class."); return array(); } if (!is_array($obj -> getConfig("LSrelation.$relation_id"))) { self :: log_error("LSobject $objType have no relation '$relation_id'."); return array(); } $relation = new LSrelation($obj, $relation_id); // List current related objects $list = $relation -> listRelatedObjects(); $listDns = array(); if (is_array($list)) { foreach($list as $o) { $listDns[] = $o -> getDn(); } } return LScli :: autocomplete_opts($listDns, $comp_word, false); } // If objType not already choiced (or currently autocomplete), add LSobject types to available options if (!$objType || $objType_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word)); // If dn not already choiced (or currently autocomplete), try autocomplete it elseif (!$dn || $dn_arg_num == $comp_word_num) $opts = array_merge($opts, LScli :: autocomplete_LSobject_dn($objType, $comp_word)); // If relation_id not already choiced (or currently autocomplete), try autocomplete it elseif (!$relation_id || $relation_id_arg_num == $comp_word_num) { $relations = LSconfig :: get("LSobjects.$objType.LSrelation"); if (is_array($relations)) $opts = array_merge($opts, array_keys($relations)); } return LScli :: autocomplete_opts($opts, $comp_word); } /** * Check validity of a LSobject type name * * A LSobjet type name must only contain letter, digits or dash or * underscore. * * @param string $name The LSobject type name to check * * @return boolean True is type name is valid, False otherwise */ public static function isValidTypeName($name) { if (preg_match('/^[a-zA-Z0-9\_\-]+$/', $name)) return True; self :: log_warning("isValidTypeName($name): Invalid LSobject type name !"); return False; } } /** * Error Codes **/ LSerror :: defineError('LSldapObject_01', ___("LSldapObject : Object type unknown.") ); LSerror :: defineError('LSldapObject_02', ___("LSldapObject : Update form is not defined for the object %{obj}.") ); LSerror :: defineError('LSldapObject_03', ___("LSldapObject : No form exists for the object %{obj}.") ); LSerror :: defineError('LSldapObject_04', ___("LSldapObject : The function %{func} to validate the attribute %{attr} the object %{obj} is unknow.") ); LSerror :: defineError('LSldapObject_05', ___("LSldapObject : Configuration data are missing to validate the attribute %{attr} of the object %{obj}.") ); LSerror :: defineError('LSldapObject_06', ___("LSldapObject : The function %{func} to be executed on the object event %{event} doesn't exist.") ); LSerror :: defineError('LSldapObject_07', ___("LSldapObject : The %{func} execution on the object event %{event} failed.") ); LSerror :: defineError('LSldapObject_08', ___("LSldapObject : Class %{class}, which method %{meth} to be executed on the object event %{event}, doesn't exist.") ); LSerror :: defineError('LSldapObject_09', ___("LSldapObject : Method %{meth} within %{class} class to be executed on object event %{event}, doesn't exist.") ); LSerror :: defineError('LSldapObject_10', ___("LSldapObject : Error during execute %{meth} method within %{class} class, to be executed on object event %{event}.") ); LSerror :: defineError('LSldapObject_11', ___("LSldapObject : Some configuration data of the object type %{obj} are missing to generate the DN of the new object.") ); LSerror :: defineError('LSldapObject_12', ___("LSldapObject : The attibute %{attr} of the object is not yet defined. Can't generate DN.") ); LSerror :: defineError('LSldapObject_13', ___("LSldapObject : Without DN, the object could not be changed.") ); LSerror :: defineError('LSldapObject_14', ___("LSldapObject : The attribute %{attr_depend} depending on the attribute %{attr} doesn't exist.") ); LSerror :: defineError('LSldapObject_15', ___("LSldapObject : Error during deleting the object %{objectname}.") ); LSerror :: defineError('LSldapObject_16', ___("LSldapObject : Error during actions to be executed before renaming the objet.") ); LSerror :: defineError('LSldapObject_17', ___("LSldapObject : Error during actions to be executed after renaming the objet.") ); LSerror :: defineError('LSldapObject_18', ___("LSldapObject : Error during actions to be executed before deleting the objet.") ); LSerror :: defineError('LSldapObject_19', ___("LSldapObject : Error during actions to be executed after deleting the objet.") ); LSerror :: defineError('LSldapObject_20', ___("LSldapObject : Error during the actions to be executed before creating the object.") ); LSerror :: defineError('LSldapObject_21', ___("LSldapObject : Error during the actions to be executed after creating the object. It was created anyway.") ); LSerror :: defineError('LSldapObject_22', ___("LSldapObject : The function %{func} to be executed before creating the object doesn't exist.") ); LSerror :: defineError('LSldapObject_23', ___("LSldapObject : Error executing the function %{func} to be execute after deleting the object.") ); LSerror :: defineError('LSldapObject_24', ___("LSldapObject : The function %{func} to be executed after deleting the object doesn't exist.") ); LSerror :: defineError('LSldapObject_25', ___("LSldapObject : Error executing the function %{func} to be execute after creating the object.") ); LSerror :: defineError('LSldapObject_26', ___("LSldapObject : %{func} function, to be executed on object event %{event}, doesn't exist.") ); LSerror :: defineError('LSldapObject_27', ___("LSldapObject : Error during the execution of %{func} function on object event %{event}.") ); LSerror :: defineError('LSldapObject_28', ___("LSldapObject : %{meth} method, to be executed on object event %{event}, doesn't exist.") ); LSerror :: defineError('LSldapObject_29', ___("LSldapObject : Error during execution of %{meth} method on object event %{event}.") ); LSerror :: defineError('LSldapObject_30', ___("LSldapObject : Error during generate LDAP filter for %{LSobject}.") ); LSerror :: defineError('LSldapObject_31', ___("LSldapObject : Error during execution of the custom action %{customAction} on %{objectname}.") ); LSerror :: defineError('LSldapObject_32', ___("LSldapObject : Fail to retrieve container DN.") ); LSerror :: defineError('LSldapObject_33', ___("LSldapObject : The function %{func} to generate container DN is not callable.") ); LSerror :: defineError('LSldapObject_34', ___("LSldapObject : Error during generating container DN : %{error}") ); LSerror :: defineError('LSldapObject_35', ___("LSldapObject : An LDAP object with the same DN as generated for this new one already exists. Please verify your configuration.") ); // LSrelation LSerror :: defineError('LSrelation_05', ___("LSrelation : Some parameters are missing in the call of methods to handle standard relations (Method : %{meth}).") ); // LScli LScli :: add_command( 'show', array('LSldapObject', 'cli_show'), 'Show an LSobject', '[object type] [dn] [-r|--raw-values] [-j|--json [-p|--pretty]]', null, true, array('LSldapObject', 'cli_show_args_autocompleter') ); LScli :: add_command( 'create', array('LSldapObject', 'cli_create'), 'Create an LSobject', '[object type] [-N|--no-confirm] [-D|--delimiter] attr1=value1|value2 attr2=value3', array( ' - Positional arguments :', ' - LSobject type', ' - attributes values in format:', '', ' attr_name=value1|value2', '', ' Note: for multiple-values attributes, you also could specify', ' attribute and value multiple time, for instance :', '', ' attr1=value1 attr1=value2', '', ' - Optional arguments :', ' -j|--just-try Just-try mode: validate provided informations', ' but do not really create LDAP object. ', ' -D|--delimiter Delimiter for multiple values attributes', ' (default: "|")', ' -N|--no-confirm Do not ask for confirmation', ), true, array('LSldapObject', 'cli_create_args_autocompleter') ); LScli :: add_command( 'modify', array('LSldapObject', 'cli_modify'), 'Modify an LSobject', '[object type] [dn] [-N|--no-confirm] [-D|--delimiter] attr1=value1|value2 attr2=value3', array( ' - Positional arguments :', ' - LSobject type', ' - LSobject DN', ' - attributes values to modify in format:', '', ' attr_name=value1|value2', '', ' Note: for multiple-values attributes, you also could specify', ' attribute and value multiple time, for instance :', '', ' attr1=value1 attr1=value2', '', ' - Optional arguments :', ' -j|--just-try Just-try mode: validate changes but do not', ' really update LDAP object data', ' -D|--delimiter Delimiter for multiple values attributes', ' (default: "|")', ' -N|--no-confirm Do not ask for confirmation', ), true, array('LSldapObject', 'cli_modify_args_autocompleter') ); LScli :: add_command( 'remove', array('LSldapObject', 'cli_remove'), 'Remove an LSobject', '[object type] [dn] [-N|--no-confirm]', null, true, array('LSldapObject', 'cli_remove_args_autocompleter') ); LScli :: add_command( 'relation', array('LSldapObject', 'cli_relation'), 'Manage LSobject related objects', '[object type] [dn] [relation ID] [-a|--add DN] [-r|--remove DN]', null, true, array('LSldapObject', 'cli_relation_args_autocompleter') );