<?php
require_once(__DIR__.DIRECTORY_SEPARATOR.'DynamicField.class.php');
/**
 * Define a dynamicform.
 * @author Charles DUBOIS
 * @category Plugin
 * @license MIT
 */
class DynamicForm extends Entity{

	public $id;
	public $slug; //Slug (Texte)
	public $color; //Couleur (Couleur)
	public $icon; //Icônes (Icône)
	public $label; //Libellé (Texte)
	public $state; //State (Texte)
	public $firm; //Etablissement (Number)

	protected $TABLE_NAME = 'dynamicform_form';
	public $entityLabel = 'Formulaire';
	public $fields = array(
		'id' => 'key',
		'slug' => array('type'=>'string', 'label' => 'Slug'),
		'color' => array('type'=>'string', 'label' => 'Couleur'),
		'icon' => array('type'=>'string', 'label' => 'Icônes'),
		'state' => array('type'=>'string', 'label' => 'Etat'),
		'firm' => array('type'=>'int', 'label' => 'Etablissement','link'=>'class/Firm.class.php'),
		'label' => array('type'=>'string', 'label' => 'Libellé')
	);



	//Colonnes indexées
	public $indexes = array('slug','firm');

	public static function remove($slug,$options = array()){
		Plugin::need('dynamicform/DynamicField,dynamicform/DynamicValue');
		$form = self::load(array('slug'=>$slug));
		if(!$form) return;
		$query = 'DELETE FROM {{table}} WHERE field IN(SELECT id FROM '.DynamicField::tableName().' fi WHERE fi.form=?) ';
		$data = array($form->id);

		if(empty($options['scope'])){
			$query .= ' AND scope=? ';
			$data[] = $options['scope'];
		}
		if(empty($options['uid'])){
			$query .= ' AND uid=? ';
			$data[] = $options['uid'];
		}

		DynamicValue::staticQuery($query,$data);
	}

	public static function check_required($slug,$options = array(),$newValues){
		Plugin::need('dynamicform/DynamicField');

		$types = FieldType::available();

		$query = "SELECT fi.id,fi.type,fi.label,fi.meta,fi.slug,fi.mandatory,fi.readonly
			FROM {{table}} fi
			LEFT JOIN ".self::tableName()." fo ON fi.form = fo.id
			WHERE fo.slug = ? ";
		$data = array($slug);

		$fields = DynamicField::staticQuery($query,$data,true,1);

		foreach($fields as $field)
			if(empty($newValues[$field->slug]) && $field->mandatory) throw new Exception('Le champ '.$field->label.' est obligatoire');
		//pour réutilisation de performance par la méthode record, souvant appellée juste après
		return $fields;
	}

	public static function record($slug,$options = array(),$newValues){
		Plugin::need('dynamicform/DynamicField');

		$types = FieldType::available();

		//si on a pas récupéré les fields dans un opération précédente (ex check_required), on les récupere ici
		if(!isset($options['fields'])){
			$query = "SELECT fi.id,fi.type,fi.label,fi.meta,fi.slug,fi.mandatory,fi.readonly
				FROM {{table}} fi
				LEFT JOIN ".self::tableName()." fo ON fi.form = fo.id
				WHERE fo.slug = ? ";
			$data = array($slug);

			$fields = DynamicField::staticQuery($query,$data,true,1);
		}else{
			$fields = $options['fields'];
			//gain de perf
			unset($options['fields']);
		}

		//on affiche chaque champ dynamique du formulaire
		foreach($fields as $field){
			if(empty($newValues[$field->slug]) && $field->mandatory) throw new Exception('Le champ '.$field->label.' est obligatoire');
			//on verifie qu'une valeur a été donnée
			if(!isset($newValues[$field->slug])) continue;
			//si le champ est en lecture seule on n'enregistre pas de valeur
			if($field->readonly) continue;
			DynamicField::record(array('field'=>$field,'value'=>$newValues[$field->slug],'options'=>$options,'types'=>$types));
		}
	}

	/**
	 * Affiche un formulaire en fonction de son slug et de ses options de contexte
	 * @param <String> slug du formulaire ciblé
	 * @param <Array> options des filtres de sélection des champs
	 * @return <Array> tableau des options de ciblage des champs à récupérer
	 */
	public static function show($slug,$options = array()){
		global $myFirm;
		Plugin::need('dynamicform/DynamicField,dynamicform/DynamicValue');

		$options = array_merge(array(
			'label' => 'block', // inline (input group) / block (label classique) / none (pas de label)
			'legend' => 'block', // block (span text muted) / none (pas de label)
			'input-class' => '', // classes additionnelles a placer sur les inputs
            'input-group-class' => '', // classes additionnelles a placer sur les groupes d'inputs
			'scope' => '', // ex : client
			'uid' => '', // ex : 12
			'firm' => $myFirm->id // ex : 1
		),$options);

		$options['format'] = 'table';

		$types = FieldType::available();

		//Initialisation des options de sélection des champs scope / firm etc
		$listOptions = self::list_options($slug, $options);

		//Si pas de slug on affiche rien
		if(empty($listOptions))return;

		//Récupération du tableau d'objets champs
		$fieldsObjects = self::get_fields($listOptions);

		//Mise au format table (mise en page ligne colonne) de la liste des champs
		$fieldsTable = self::fields_layout($fieldsObjects, $types, $options);

		//Mise au format liste (à plat) de la liste des champs
		$options['format'] = 'list';
		$fieldsList = self::fields_layout($fieldsObjects, $types, $options);

		$stream = '';

		$values = self::get_values($fieldsList,$options);

		if(isset($options['arrayOutput']) && $options['arrayOutput']){
			return self::get_values_as_array($fieldsList,$values);
		}

		//mise en page
		foreach($fieldsTable as $i => $columns) {
			$stream .= '<div class="row">';
			foreach ($columns as $u => $fields) {
				$stream .= '<div class="col-md column">';
				foreach($fields as $field){
					$value = isset($values[$field['id']]) ? $values[$field['id']] : '';
					$stream .= DynamicField::show(array('field'=>$field,'value'=>$value,'types'=>$types,'options'=>$options));
				}
				$stream .= '</div>';
			}
			$stream .= '</div>';
		}
		return $stream;
	}

	/**
	 * Retourne un tableau de champs disponibles pour ce slug et cette firm
	 * @param <String> slug du formulaire ciblé
	 * @param <Array> options des filtres de sélection des champs
	 * @return <Array> tableau des options de ciblage des champs à récupérer
	 */
	public static function list($slug,$options = array()){
		$types = FieldType::available();
		$options = self::list_options($slug,$options);
		if(empty($options)) return array();
		$fields = self::get_fields($options);
		$rows = self::fields_layout($fields,$types,$options);
		return $rows;
	}


	/**
	 * Initialise les options de filtrage pour récupération des champs
	 * @param <String> slug du formulaire ciblé
	 * @param <Array> options des filtres de sélection des champs
	 * @return <Array> tableau des options de ciblage des champs à récupérer
	 */
	public static function list_options($slug,$options = array()){
		global $myFirm;

		$options = array_merge(array(
			'firm' => $myFirm->id, // ex : 1
			'format' => 'list' // table (mise en page ligne colonne) ou liste (a plat)
		),$options);

		$forms = self::loadAll(array('slug'=>$slug,'firm:IN'=>array($options['firm'],'0')));
		if(!$forms || count($forms)==0) return array();

		$formIds = array();
		foreach ($forms as $form)
			$formIds[] = $form->id;

		$options['slug'] = $slug;
		$options['form'] = $formIds;
		$options['firm'] = array($options['firm'],'0');

		return $options;
	}

	/**
	 * Retourne un tableau de champs (de type array) formaté en liste ou table selon l'option 'format'
	 * @param <Array> tableau d'options/ filtres de sélection
	 * @return <Array> tableau brut de champs (de type object) selon les options settées
	 */
	public static function get_fields_layout($options = array()){
		$types = FieldType::available();
		$fields = self::get_fields($options);
		return self::fields_layout($fields,$types,$options);
	}


	/**
	 * Retourne un tableau brut de champs (de type object) selon les options settées
	 * @param <Array> tableau d'options/ filtres de sélection
	 * @return <Array> tableau brut de champs (de type object) selon les options settées
	 */
	public static function get_fields($options = array()){
		Plugin::need('dynamicform/DynamicField');
		$data = array();

		if(empty($options['format'])) $options['format'] = 'table';

		$query = 'SELECT fi.* FROM {{table}} fi LEFT JOIN '.self::tableName().' fo ON fi.form = fo.id WHERE 1 ';
		$query .= ' AND fo.state=? ';
		$data[] = self::ACTIVE;
		if(isset($options['slug'])){
			$query .= ' AND fo.slug=? ';
			$data[] = $options['slug'];
		}else if(isset($options['id'])){
			$query .= ' AND fo.id=? ';
			$data[] = $options['id'];
		}

		if(isset($options['form'])){
			$query .= ' AND fo.id IN ('.implode(',',array_fill(0,count($options['form']),'?')).')';
			foreach ($options['form'] as $form)
				$data[] = $form;
		}

		if(isset($options['firm'])){
			$query .= ' AND fo.firm IN ('.implode(',',array_fill(0,count($options['firm']),'?')).')';
			foreach ($options['firm'] as $firm)
				$data[] = $firm;
		}

		$query .= ' ORDER BY fi.row,fi.column,fi.sort';

		$dynamicfields = array();
        foreach(DynamicField::staticQuery($query,$data,true) as $dynamicField){
            if(!is_null($dynamicField->meta) && !empty($dynamicField->meta))
                $dynamicField->meta = base64_encode($dynamicField->meta);
            $dynamicfields[] = $dynamicField;
        }

		return $dynamicfields;
	}


	/**
	 * Retourne un tableau de champs (de type array) formaté en liste ou table selon l'option 'format'
	 * @param <Array> tableau brut d'objets champs dynamiques
	 * @param <Array> tableau des fieldtypes disponibles
	 * @param <Array> tableau des options de sélection et de format
	 * @return <Array> tableau formaté (table ou liste) des champs dynamiques ciblés
	 */
	public static function fields_layout($fields,$types,$options = array()){
		if(is_null($types)) $types = FieldType::available();
		if(empty($options['format'])) $options['format'] = 'table';
		$rows = array();
		foreach($fields as $field){
			$row = $field->toArray();
			$row['type'] = $types[$row['type']];

			if($options['format'] == 'table'){
				if(!isset($rows[$field->row])) $rows[$field->row] = array();
				if(!isset($rows[$field->row][$field->column])) $rows[$field->row][$field->column] = array();
				$rows[$field->row][$field->column][] = $row;
			}else{
				$rows[] = $row;
			}
		}
		return $rows;
	}

	/**
	 * Retourne un tableau brut de valeurs pour un set de champs en fonction du scope / firm
	 * @param <Array> tableau brut de champs dynamiques (sous forme d'arrays)
	 * @param <Array> tableau des options de sélection et de format
	 * @return <Array> tableau brut des valeurs des champs dynamiques ciblés avec comme clé l'identifiant du champ dynamique
	 */
	public static function get_values($fields,$options){
		Plugin::need('dynamicform/DynamicValue');
		$values = array();
		$fieldIds = array();

		//Récuperation des id de fields
		foreach($fields as $field)
			$fieldIds[] = $field['id'];

		if(empty($fieldIds)) return $values;

		$valueFilters = array('field:IN'=>$fieldIds);
		if(!empty($options['firm'])) $valueFilters['firm:IN'] = array($options['firm'],0);
		if(!empty($options['scope'])) $valueFilters['scope'] = $options['scope'];
		$valueFilters['uid'] = $options['uid'];
		//récuperation des valeurs déja enregistrées pour ces fields
		foreach (DynamicValue::loadAll($valueFilters) as $key => $value)
			$values[$value->field] = $value->value;

		return $values;
	}

	/**
	 * Retourne le tableau formaté des valeurs de champs pour export
	 * @category manipulation de tableaux
	 * @param <Array> tableau brut des valeurs des champs dynamiques
	 * @return <Array> tableau des valeurs des champs dynamiques utilisables pour l'export
	 */
	public static function get_values_as_array($fields,$values){
		$arrayOutput = array();
		foreach($fields as $field){
			$field['value']= isset($values[$field['id']]) ? $values[$field['id']] : '';
			$arrayOutput[] = $field;
		}
		return $arrayOutput;
	}

	/**
	 * Ajoute à la requete originelle les valeurs des champs dynamiques aliasisés par le slug du champ dynamique
	 * @category manipulation de chaine pour création dynamiqe de requête de sélection en base
	 * @param <Array> tableau des champs dynamiques potentiellement affichables
	 * @param <String> requête originelle de sélection en base à modifier
	 * @param <String> alias de la table mère présente dans le FROM de la requête originelle de sélection
	 * @return <String> requête de sélection modifiée avec les valeurs des champs dynamiques
	 */
	public static function query_column_add($fields,&$query,$alias = ''){
		global $_;
		Plugin::need('dynamicform/DynamicValue');
		$columns = array();

		//Tableau des champs dynamiques disponibles
 		foreach($fields as $field)
 			$columns[$field['id']] = $field['slug'];

 		//Tableau des champs dynamiques sélectionnés
		$selected = isset($_['columns']) && isset($_['columns']['added']) ? $_['columns']['added'] : array();

		//On ne garde que les champs sélectionés et présents dans les champs disponibles ET les champs requetés en filtres
		if(isset($_['filters']['advanced'])){
			foreach($_['filters']['advanced'] as $field){
				$parts = explode('dynamicField_',$field['column']);
				//on ne traite que les field dynamiques
				if(count($parts)<=1) continue;
				//pour retrouver le slug du custom field (slug-fitlre) depuis le slug du filtre (dynamicField_{{id}})
				$dynamicSlug = $columns[str_replace('.value','',$parts[1])];
				$selected[] = $dynamicSlug;
			}
		}

		$columns = array_intersect($columns,$selected);

		//On crée la chaine de sélection des valeurs
		$selectFields = array();
 		foreach($columns as $id=>$slug)
 			$selectFields[] = 'dynamicField_'.$id.'.value AS "'.$slug.'"';

 		//On récupère ce qu'il y a entre select et from et on le concatène à la chaine de sélection des valeurs créé
		$selectFields = empty($selectFields) ? ' ' : ','.implode(',',$selectFields);
		$query = preg_replace("/(?<=SELECT)(.*)(?=FROM)/i","$0".$selectFields.' ',$query);

		//on récupère ce qu'il y a entre select et where et on le concat avec les left joins pour chaque champs dynamiques à afficher
		$joinFields = array();
		foreach($columns as $id=>$slug)
			$joinFields[] = ' LEFT JOIN '.DynamicValue::tableName().' dynamicField_'.$id.' ON dynamicField_'.$id.'.uid = '.(empty($alias) ? 'id' : '`'.$alias.'`.id').' AND dynamicField_'.$id.'.field = '.$id;
		$joinFields = implode(' ',$joinFields);

		$query = preg_replace("/(?<=SELECT)(.*)(?=WHERE)/is","$0".$joinFields.' ',$query);

	}

	//Retourne un set de filtres de colonnes dynamiques en fonction de la liste des
	//champs custom fournie
	public static function get_dynamic_columns($fields){
		$columns = array();
		foreach ($fields as $field) {
            $columns[$field['slug']] =
                array(
                    'head' => '<th data-sortable="dynamicField_'.$field['id'].'.value" data-available="'.$field['slug'].'">'.$field['label'].'</th>',
                    'body' => '<td class="align-middle text-center">{{{'.$field['slug'].'}}}</td>',
                );
        }
        return  $columns;
	}

	//Convertion des valeurs retours d'une db pour des colonnes dynamiques en fonction de leurs types
	public static function search_values(&$row,$options){

		if(!isset($options['force-raw'])) $options['force-raw'] = false;

		//Récupération des champs afin d'avoir les metas pour affichaqge fieldtypes exotique list, dictionary...
		$meta = array();
		foreach(DynamicField::loadAll(array('slug:IN'=>array_keys($options['slugs']))) as $field)
			$meta[$field->slug] = $field->meta;


		//pour chaque champ, s'il fait partie des types qui possèdent une propriété onHtmlDisplay, on l'applique à la valeur du champ
		foreach($options['slugs'] as $slug=>$value){
			if(!isset($options['types'][$slug])) continue;
			$type = $options['types'][$slug];

			$displayOptions = array('meta'=>$meta[$slug],'type'=>$type);

			//tansmission des parametres de contexte
			foreach($options as $key=>$option){
				if(!is_string($option)) continue;
				$displayOptions[$key] = $option;
			}
			if(isset($row['id'])) $displayOptions['uid'] = $row['id'];

			if(property_exists($type,"onHtmlDisplay") && !$options['force-raw']){
				$method = $type->onHtmlDisplay;
				$displayOptions['decoration']  = 'true';
				$value = $method($value,$displayOptions);
			}elseif(property_exists($type,"onRawDisplay")){
				$method = $type->onRawDisplay;
				$value = $method($value,$displayOptions);
			}

			$row[$slug] = $value;
		}
	}

	//Récuperation des valeurs de champs dynamic pour un formulaire, un scope et un item donné sous la forme slug field => valeur
	public static function values($formSlug,$options){
		require_once(__DIR__.SLASH.'DynamicForm.class.php');
		require_once(__DIR__.SLASH.'DynamicField.class.php');
		require_once(__DIR__.SLASH.'DynamicValue.class.php');

		$query  = 'SELECT dfi.slug,dva.value FROM {{table}} dva
		LEFT JOIN '.DynamicField::tableName().' dfi ON dfi.id = dva.field
		LEFT JOIN '.DynamicForm::tableName().' dfo ON  dfo.id = dfi.form
		WHERE dfo.slug = ? AND dva.scope = ? AND dva.uid = ?';
		$data = array($formSlug,$options['scope'],$options['uid']);
		$values = array();
		foreach (DynamicValue::staticQuery($query,$data,true) as $key => $value)
			$values[$value->foreign('slug')] = $value->value;

		return $values;
	}


	//Retourne un set de filtres de recherche avancée en fonction de la liste des
	//champs custom fournie
	public static function get_filters($fields){
		$filters = array();

		foreach($fields as $field){
        	$filterOptions = '';

			$filterType = $field['type']->slug;
			$meta = isset($field['meta']) ? json_decode(base64_decode($field['meta']),true) : (object) array();

			//le field doit comprendre le meta show-filter pour afficher le filtre (case a cocher dans la construction du formulaire)
			if(!is_array($meta) || empty($meta['show-filter'])) continue;

			$filterOptions = ' data-filter-type="'.$filterType.'" ';
			if(is_array($meta)){
				foreach($meta as $key=>$value)
					$filterOptions .= 'data-'.$key.'=\''.(is_string($value) ? $value : str_replace("'","\'",json_encode($value))).'\' ';
			}

            //si le type de champ ne propose pas de filtre on l'ignore
            if(!property_exists($field['type'],'filter')) continue;

            $filters[$field['slug']] = '<option value="dynamicField_'.$field['id'].'.value"'.$filterOptions.'>'.$field['label'].'</option>';

        }
        return $filters;
	}




}
?>