diff --git a/doc/conf/LSattribute/LSattr_html.docbook b/doc/conf/LSattribute/LSattr_html.docbook
index 2c2bdd1b..59fdd074 100644
--- a/doc/conf/LSattribute/LSattr_html.docbook
+++ b/doc/conf/LSattribute/LSattr_html.docbook
@@ -6,6 +6,7 @@
&conf-LSattr_html_boolean;
&conf-LSattr_html_date;
&conf-LSattr_html_image;
+ &conf-LSattr_html_jsonCompositeAttribute;
&conf-LSattr_html_mail;
&conf-LSattr_html_maildir;
&conf-LSattr_html_mailQuota;
diff --git a/doc/conf/LSattribute/LSattr_html/LSattr_html.entities.xml b/doc/conf/LSattribute/LSattr_html/LSattr_html.entities.xml
index 3be2d267..3e342b14 100644
--- a/doc/conf/LSattribute/LSattr_html/LSattr_html.entities.xml
+++ b/doc/conf/LSattribute/LSattr_html/LSattr_html.entities.xml
@@ -2,6 +2,7 @@
+
@@ -17,3 +18,5 @@
+
+LSattr_html_select_list">
diff --git a/doc/conf/LSattribute/LSattr_html/LSattr_html_jsonCompositeAttribute.docbook b/doc/conf/LSattribute/LSattr_html/LSattr_html_jsonCompositeAttribute.docbook
new file mode 100644
index 00000000..09fe1097
--- /dev/null
+++ b/doc/conf/LSattribute/LSattr_html/LSattr_html_jsonCompositeAttribute.docbook
@@ -0,0 +1,96 @@
+
+ LSattr_html_jsonCompositeAttribute
+ Ce type est utilisé pour la gestion des attributs dont les valeurs sont
+ des dictionnaires de valeurs encodées aux formats JSON.
+
+
+Exemple de valeur gérée
+
+
+
+ Le principe est que ces dictionnaires contienent plusieurs composants référencés
+ par leur clé et stockant une valeur dont le type peut être un texte libre ou
+ bien être issue d'une liste déroulante configurable selon le même principe que
+ le type d'attribut &LSattr_html_select_list;.
+
+
+Structure...
+ array (
+ 'components' => array (
+ '[clé composant 1]' => array (
+ 'label' => '[Label du composant]',
+ 'type' => '[Type de la valeur stocké]',
+ 'required' => [Booléen],
+ 'check_data' => => array (
+ // Régle de vérification syntaxique des données saisies
+ ),
+ ),
+ '[clé composant 2]' => array (
+ 'label' => '[Label du composant 2]',
+ 'type' => 'select_list',
+ 'required' => [Booléen],
+ 'options' => array (
+ [Configuration équivalente à un attribut LSattr_html_select_list]
+ )
+ ),
+ [...]
+ ),
+),]]>
+...
+
+
+
+Paramètres de configuration
+
+
+ components
+
+ Tableau associatif obligatoire contenant en valeur clé, l'identifiant des
+ composants, correspondant à la clé dans le dictionnaire JSON,
+ et en valeurs associés, la configuration du composant.
+
+
+
+ label
+
+ Le label du composant.
+
+
+
+
+ type
+
+ Le type de valeur du composant. Les types possibles sont
+ text ou select_list pour respectivement
+ soit une valeur saisie librement, soit une valeur sélectionnée parmis une liste
+ déroulante.
+
+
+
+
+ options
+
+ Dans le cadre d'un composant de type select_list, cela
+ correspond à la configuration de la liste déroulante. Cette configuration utilise la
+ même syntaxe de configuration que celle du type d'attribut &LSattr_html_select_list;
+ et son paramètre html_options.
+
+
+
+
+ check_data
+
+ Tableau associatif contenant les règles de vérification syntaxique
+ des données du composant. Ces règles sont configurables de la même manière
+ que les celles des valeurs attributs.
+ Voir la section concernée.
+
+
+
+
+
+
+
+
+
+
diff --git a/public_html/css/default/LSformElement_jsonCompositeAttribute.css b/public_html/css/default/LSformElement_jsonCompositeAttribute.css
new file mode 100644
index 00000000..28b641f3
--- /dev/null
+++ b/public_html/css/default/LSformElement_jsonCompositeAttribute.css
@@ -0,0 +1,34 @@
+ul.LSformElement_jsonCompositeAttribute li {
+ border: 1px dotted #CCC;
+ padding: 2px;
+}
+
+ul.LSformElement_jsonCompositeAttribute li.noValue {
+ border: none;
+}
+
+ul.LSformElement_jsonCompositeAttribute p {
+ margin: 0;
+}
+
+ul.LSformElement_jsonCompositeAttribute p label {
+ font-weight: bold;
+}
+
+div.jsonCompositeAttribute_possibleValues ul {
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+ border: 1px solid #CCC;
+ border-collapse: collapse;
+}
+
+div.jsonCompositeAttribute_possibleValues li {
+ border-bottom: 1px solid #CCC;
+ margin-bottom: 2px;
+}
+
+div.jsonCompositeAttribute_possibleValues li:hover {
+ background-color: #CCC;
+ cursor: pointer;
+}
diff --git a/public_html/includes/class/class.LSattr_html_jsonCompositeAttribute.php b/public_html/includes/class/class.LSattr_html_jsonCompositeAttribute.php
new file mode 100644
index 00000000..82e26ebb
--- /dev/null
+++ b/public_html/includes/class/class.LSattr_html_jsonCompositeAttribute.php
@@ -0,0 +1,32 @@
+
+ */
+class LSattr_html_jsonCompositeAttribute extends LSattr_html {
+
+ var $LSformElement_type = 'jsonCompositeAttribute';
+
+}
diff --git a/public_html/includes/class/class.LSformElement_jsonCompositeAttribute.php b/public_html/includes/class/class.LSformElement_jsonCompositeAttribute.php
new file mode 100644
index 00000000..ee061617
--- /dev/null
+++ b/public_html/includes/class/class.LSformElement_jsonCompositeAttribute.php
@@ -0,0 +1,268 @@
+
+ */
+
+class LSformElement_jsonCompositeAttribute extends LSformElement {
+
+ var $template = 'LSformElement_jsonCompositeAttribute.tpl';
+ var $fieldTemplate = 'LSformElement_jsonCompositeAttribute_field.tpl';
+
+ function LSformElement_jsonCompositeAttribute (&$form, $name, $label, $params,&$attr_html){
+ parent :: LSformElement($form, $name, $label, $params,$attr_html);
+ if (is_array($this -> params['html_options']['components'])) {
+ $this -> components = $this -> params['html_options']['components'];
+ }
+ }
+
+ /*
+ * Value components :
+ *
+ * Format :
+ * array (
+ * '[component1_key]' => array (
+ * 'label' => '[component label]',
+ * 'type' => '[component type]',
+ * 'required' => '[booléen]',
+ * 'check_data' => array([config LSform_rule])
+ * ),
+ * '[component2_key]' => array (
+ * 'label' => 'label2',
+ * 'type' => 'select_list',
+ * 'options' => array([config as LSattr_html_select_list html_options]),
+ * ),
+ * [...]
+ * )
+ * Types :
+ * - 'select_list' => Component feed by a list of valeur configured like an
+ * atribute LSattr_html :: select_list.
+ * - 'text' => manual entry
+ *
+ */
+ var $components = array();
+
+ /**
+ * Retourne les infos d'affichage de l'élément
+ *
+ * Cette méthode retourne les informations d'affichage de l'élement
+ *
+ * @retval array
+ */
+ function getDisplay(){
+ $return = $this -> getLabelInfos();
+
+ $parseValues=array();
+ $invalidValues=array();
+ foreach($this -> values as $val) {
+ $decodedValue=json_decode($val, true);
+ if (is_array($decodedValue)) {
+ $parseValue=array('value' => $val);
+ foreach($decodedValue as $c => $cvalue) {
+ $parseValue[$c]=$this -> translateComponentValue($c,$cvalue);
+ }
+ $parseValues[]=$parseValue;
+ }
+ else {
+ $invalidValues[]=$val;
+ }
+ }
+
+ $components = $this -> components;
+ foreach($components as $c => $cconf) {
+ if ($cconf['type']=='select_list') {
+ $components[$c]['possible_values']=$this -> getSelectListComponentPossibleValues($c);
+ }
+ }
+
+ $return['html'] = $this -> fetchTemplate(NULL,
+ array(
+ 'parseValues' => $parseValues,
+ 'components' => $components
+ )
+ );
+ LSsession :: addCssFile('LSformElement_jsonCompositeAttribute.css');
+ return $return;
+ }
+
+
+ /**
+ * Return HTML code of an empty field
+ *
+ * @retval string HTML code of an empty field.
+ */
+ function getEmptyField() {
+ return $this -> fetchTemplate($this -> fieldTemplate,array('components' => $this -> components));
+ }
+
+ /**
+ * Translate componant value
+ *
+ * Return an array containing :
+ * - value : untranslated value
+ * - translated : translated value
+ *
+ * @param[in] $c string The component name
+ * @param[in] $value string The value
+ *
+ * @retval array
+ **/
+ function translateComponentValue($c,$value) {
+ $retval = array (
+ 'translated' => $value,
+ 'value' => $value,
+ );
+ if (isset($this -> components[$c])) {
+ if ($this -> components[$c]['type']=='select_list') {
+ $retval['translated'] = $this -> getSelectListComponentValueLabel($c,$value);
+ }
+ //elseif type == 'text' => no transformation
+ }
+ return $retval;
+ }
+
+ /**
+ * Retreive possible values of an select_list component
+ *
+ * @param[in] $c string The component name
+ *
+ * @retval array
+ **/
+ protected $_cache_getSelectListComponentPossibleValues=array();
+ protected function getSelectListComponentPossibleValues($c) {
+ if (!isset($this -> _cache_getSelectListComponentPossibleValues[$c])) {
+ if (!LSsession :: loadLSclass('LSattr_html_select_list')) return;
+ $this -> _cache_getSelectListComponentPossibleValues[$c]=LSattr_html_select_list :: getPossibleValues($this -> components[$c]['options'], $this -> name, $this->attr_html->attribute->ldapObject);
+ }
+ return $this -> _cache_getSelectListComponentPossibleValues[$c];
+ }
+
+ /**
+ * Retreive value's label of an select_list component
+ *
+ * @param[in] $c string The component name
+ * @param[in] $value string The value
+ *
+ * @retval array
+ **/
+ protected function getSelectListComponentValueLabel($c,$value) {
+ if ($this -> getSelectListComponentPossibleValues($c)) {
+ foreach ($this -> _cache_getSelectListComponentPossibleValues[$c] as $v => $label) {
+ if (is_array($label)) {
+ if (!isset($label['possible_values'])) continue;
+ foreach ($label['possible_values'] as $vk => $vl)
+ if ($vk == $$value) return $vl;
+ }
+ if ($v == $value) return $label;
+ }
+ }
+ return;
+ }
+
+ /**
+ * Retreive LSformElement value from POST data
+ *
+ * This method check present of this element's value in POST data and retreive
+ * it to feed the array passed in paramater.
+ *
+ * @param[] array Reference of the array for retreived values
+ *
+ * @retval boolean true if value is in POST data, false instead
+ */
+ function getPostData(&$return) {
+ if($this -> isFreeze()) {
+ return true;
+ }
+
+ $count=0;
+ $end=false;
+ $return[$this -> name]=array();
+ while ($end==false) {
+ $value=array();
+ $parseValue=array();
+ $errors=array();
+ $unemptyComponents=array();
+ foreach ($this -> components as $c => $cconf) {
+ if (isset($_POST[$this -> name.'__'.$c][$count])) {
+ $parseValue[$c]=$_POST[$this -> name.'__'.$c][$count];
+ if ($cconf['required'] && empty($parseValue[$c])) {
+ $errors[]=getFData(__('Component %{c} must be defined'),__($cconf['label']));
+ continue;
+ }
+ if (empty($parseValue[$c])) {
+ continue;
+ }
+ $unemptyComponents[]=$c;
+ if ($cconf['type']=='select_list') {
+ if (!$this -> getSelectListComponentValueLabel($c, $parseValue[$c])) {
+ $errors[]=getFData(__('Invalid value for component %{c}.'),__($cconf['label']));
+ }
+ }
+ if (is_array($cconf['check_data'])) {
+ foreach($cconf['check_data'] as $ruleType => $rconf) {
+ $className='LSformRule_'.$ruleType;
+ if (LSsession::loadLSclass($className)) {
+ $r=new $className();
+ if (!$r -> validate($parseValue[$c],$rconf,$this)) {
+ if (isset($rconf['msg'])) {
+ $errors[]=getFData(__($rconf['msg']),__($cconf['label']));
+ }
+ else {
+ $errors[]=getFData(__('Invalid value for component %{c}.'),__($cconf['label']));
+ }
+ }
+ }
+ else {
+ $errors[]=getFData(__("Can't validate value of component %{c}."),__($cconf['label']));
+ }
+ }
+ }
+ $value[$c]=$parseValue[$c];
+ }
+ else {
+ // end of value break
+ $end=true;
+ break;
+ }
+
+ }
+ if (!$end) {
+ if (!empty($unemptyComponents)) {
+ foreach($errors as $e) {
+ $this -> form -> setElementError($this -> attr_html,$e);
+ }
+ $return[$this -> name][]=json_encode($value);
+ }
+ $count++;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/public_html/templates/default/LSformElement_jsonCompositeAttribute.tpl b/public_html/templates/default/LSformElement_jsonCompositeAttribute.tpl
new file mode 100644
index 00000000..7c1cccd9
--- /dev/null
+++ b/public_html/templates/default/LSformElement_jsonCompositeAttribute.tpl
@@ -0,0 +1,7 @@
+
diff --git a/public_html/templates/default/LSformElement_jsonCompositeAttribute_field.tpl b/public_html/templates/default/LSformElement_jsonCompositeAttribute_field.tpl
new file mode 100644
index 00000000..31030e13
--- /dev/null
+++ b/public_html/templates/default/LSformElement_jsonCompositeAttribute_field.tpl
@@ -0,0 +1,36 @@
+{if $freeze}
+ {if isset($parseValue)}
+ {foreach $components as $c => $cconf}
+ {if !isset($parseValue[$c])}{continue}{/if}
+
+
+ {$parseValue[$c].translated}
+
+ {/foreach}
+ {else}
+ {$noValueTxt}
+ {/if}
+{else}
+ {foreach $components as $c => $cconf}
+
+
+ {if $cconf.type=='select_list'}
+
+ {else}
+
+ {/if}
+
+ {/foreach}
+{/if}