DynamicForm.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <?php
  2. require_once(__DIR__.DIRECTORY_SEPARATOR.'DynamicField.class.php');
  3. /**
  4. * Define a dynamicform.
  5. * @author Charles DUBOIS
  6. * @category Plugin
  7. * @license MIT
  8. */
  9. class DynamicForm extends Entity{
  10. public $id;
  11. public $slug; //Slug (Texte)
  12. public $color; //Couleur (Couleur)
  13. public $icon; //Icônes (Icône)
  14. public $label; //Libellé (Texte)
  15. public $state; //State (Texte)
  16. public $firm; //Etablissement (Number)
  17. protected $TABLE_NAME = 'dynamicform_form';
  18. public $entityLabel = 'Formulaire';
  19. public $fields = array(
  20. 'id' => 'key',
  21. 'slug' => array('type'=>'string', 'label' => 'Slug'),
  22. 'color' => array('type'=>'string', 'label' => 'Couleur'),
  23. 'icon' => array('type'=>'string', 'label' => 'Icônes'),
  24. 'state' => array('type'=>'string', 'label' => 'Etat'),
  25. 'firm' => array('type'=>'int', 'label' => 'Etablissement','link'=>'class/Firm.class.php'),
  26. 'label' => array('type'=>'string', 'label' => 'Libellé')
  27. );
  28. //Colonnes indexées
  29. public $indexes = array('slug','firm');
  30. public static function remove($slug,$options = array()){
  31. Plugin::need('dynamicform/DynamicField,dynamicform/DynamicValue');
  32. $form = self::load(array('slug'=>$slug));
  33. if(!$form) return;
  34. $query = 'DELETE FROM {{table}} WHERE field IN(SELECT id FROM '.DynamicField::tableName().' fi WHERE fi.form=?) ';
  35. $data = array($form->id);
  36. if(empty($options['scope'])){
  37. $query .= ' AND scope=? ';
  38. $data[] = $options['scope'];
  39. }
  40. if(empty($options['uid'])){
  41. $query .= ' AND uid=? ';
  42. $data[] = $options['uid'];
  43. }
  44. DynamicValue::staticQuery($query,$data);
  45. }
  46. public static function check_required($slug,$options = array(),$newValues){
  47. Plugin::need('dynamicform/DynamicField');
  48. $types = FieldType::available();
  49. $query = "SELECT fi.id,fi.type,fi.label,fi.meta,fi.slug,fi.mandatory,fi.readonly
  50. FROM {{table}} fi
  51. LEFT JOIN ".self::tableName()." fo ON fi.form = fo.id
  52. WHERE fo.slug = ? ";
  53. $data = array($slug);
  54. $fields = DynamicField::staticQuery($query,$data,true,1);
  55. foreach($fields as $field)
  56. if(empty($newValues[$field->slug]) && $field->mandatory) throw new Exception('Le champ '.$field->label.' est obligatoire');
  57. //pour réutilisation de performance par la méthode record, souvant appellée juste après
  58. return $fields;
  59. }
  60. public static function record($slug,$options = array(),$newValues){
  61. Plugin::need('dynamicform/DynamicField');
  62. $types = FieldType::available();
  63. //si on a pas récupéré les fields dans un opération précédente (ex check_required), on les récupere ici
  64. if(!isset($options['fields'])){
  65. $query = "SELECT fi.id,fi.type,fi.label,fi.meta,fi.slug,fi.mandatory,fi.readonly
  66. FROM {{table}} fi
  67. LEFT JOIN ".self::tableName()." fo ON fi.form = fo.id
  68. WHERE fo.slug = ? ";
  69. $data = array($slug);
  70. $fields = DynamicField::staticQuery($query,$data,true,1);
  71. }else{
  72. $fields = $options['fields'];
  73. //gain de perf
  74. unset($options['fields']);
  75. }
  76. //on affiche chaque champ dynamique du formulaire
  77. foreach($fields as $field){
  78. if(empty($newValues[$field->slug]) && $field->mandatory) throw new Exception('Le champ '.$field->label.' est obligatoire');
  79. //on verifie qu'une valeur a été donnée
  80. if(!isset($newValues[$field->slug])) continue;
  81. //si le champ est en lecture seule on n'enregistre pas de valeur
  82. if($field->readonly) continue;
  83. DynamicField::record(array('field'=>$field,'value'=>$newValues[$field->slug],'options'=>$options,'types'=>$types));
  84. }
  85. }
  86. /**
  87. * Affiche un formulaire en fonction de son slug et de ses options de contexte
  88. * @param <String> slug du formulaire ciblé
  89. * @param <Array> options des filtres de sélection des champs
  90. * @return <Array> tableau des options de ciblage des champs à récupérer
  91. */
  92. public static function show($slug,$options = array()){
  93. global $myFirm;
  94. Plugin::need('dynamicform/DynamicField,dynamicform/DynamicValue');
  95. $options = array_merge(array(
  96. 'label' => 'block', // inline (input group) / block (label classique) / none (pas de label)
  97. 'legend' => 'block', // block (span text muted) / none (pas de label)
  98. 'input-class' => '', // classes additionnelles a placer sur les inputs
  99. 'input-group-class' => '', // classes additionnelles a placer sur les groupes d'inputs
  100. 'scope' => '', // ex : client
  101. 'uid' => '', // ex : 12
  102. 'firm' => $myFirm->id // ex : 1
  103. ),$options);
  104. $options['format'] = 'table';
  105. $types = FieldType::available();
  106. //Initialisation des options de sélection des champs scope / firm etc
  107. $listOptions = self::list_options($slug, $options);
  108. //Si pas de slug on affiche rien
  109. if(empty($listOptions))return;
  110. //Récupération du tableau d'objets champs
  111. $fieldsObjects = self::get_fields($listOptions);
  112. //Mise au format table (mise en page ligne colonne) de la liste des champs
  113. $fieldsTable = self::fields_layout($fieldsObjects, $types, $options);
  114. //Mise au format liste (à plat) de la liste des champs
  115. $options['format'] = 'list';
  116. $fieldsList = self::fields_layout($fieldsObjects, $types, $options);
  117. $stream = '';
  118. $values = self::get_values($fieldsList,$options);
  119. if(isset($options['arrayOutput']) && $options['arrayOutput']){
  120. return self::get_values_as_array($fieldsList,$values);
  121. }
  122. //mise en page
  123. foreach($fieldsTable as $i => $columns) {
  124. $stream .= '<div class="row">';
  125. foreach ($columns as $u => $fields) {
  126. $stream .= '<div class="col-md column">';
  127. foreach($fields as $field){
  128. $value = isset($values[$field['id']]) ? $values[$field['id']] : '';
  129. $stream .= DynamicField::show(array('field'=>$field,'value'=>$value,'types'=>$types,'options'=>$options));
  130. }
  131. $stream .= '</div>';
  132. }
  133. $stream .= '</div>';
  134. }
  135. return $stream;
  136. }
  137. /**
  138. * Retourne un tableau de champs disponibles pour ce slug et cette firm
  139. * @param <String> slug du formulaire ciblé
  140. * @param <Array> options des filtres de sélection des champs
  141. * @return <Array> tableau des options de ciblage des champs à récupérer
  142. */
  143. public static function list($slug,$options = array()){
  144. $types = FieldType::available();
  145. $options = self::list_options($slug,$options);
  146. if(empty($options)) return array();
  147. $fields = self::get_fields($options);
  148. $rows = self::fields_layout($fields,$types,$options);
  149. return $rows;
  150. }
  151. /**
  152. * Initialise les options de filtrage pour récupération des champs
  153. * @param <String> slug du formulaire ciblé
  154. * @param <Array> options des filtres de sélection des champs
  155. * @return <Array> tableau des options de ciblage des champs à récupérer
  156. */
  157. public static function list_options($slug,$options = array()){
  158. global $myFirm;
  159. $options = array_merge(array(
  160. 'firm' => $myFirm->id, // ex : 1
  161. 'format' => 'list' // table (mise en page ligne colonne) ou liste (a plat)
  162. ),$options);
  163. $forms = self::loadAll(array('slug'=>$slug,'firm:IN'=>array($options['firm'],'0')));
  164. if(!$forms || count($forms)==0) return array();
  165. $formIds = array();
  166. foreach ($forms as $form)
  167. $formIds[] = $form->id;
  168. $options['slug'] = $slug;
  169. $options['form'] = $formIds;
  170. $options['firm'] = array($options['firm'],'0');
  171. return $options;
  172. }
  173. /**
  174. * Retourne un tableau de champs (de type array) formaté en liste ou table selon l'option 'format'
  175. * @param <Array> tableau d'options/ filtres de sélection
  176. * @return <Array> tableau brut de champs (de type object) selon les options settées
  177. */
  178. public static function get_fields_layout($options = array()){
  179. $types = FieldType::available();
  180. $fields = self::get_fields($options);
  181. return self::fields_layout($fields,$types,$options);
  182. }
  183. /**
  184. * Retourne un tableau brut de champs (de type object) selon les options settées
  185. * @param <Array> tableau d'options/ filtres de sélection
  186. * @return <Array> tableau brut de champs (de type object) selon les options settées
  187. */
  188. public static function get_fields($options = array()){
  189. Plugin::need('dynamicform/DynamicField');
  190. $data = array();
  191. if(empty($options['format'])) $options['format'] = 'table';
  192. $query = 'SELECT fi.* FROM {{table}} fi LEFT JOIN '.self::tableName().' fo ON fi.form = fo.id WHERE 1 ';
  193. $query .= ' AND fo.state=? ';
  194. $data[] = self::ACTIVE;
  195. if(isset($options['slug'])){
  196. $query .= ' AND fo.slug=? ';
  197. $data[] = $options['slug'];
  198. }else if(isset($options['id'])){
  199. $query .= ' AND fo.id=? ';
  200. $data[] = $options['id'];
  201. }
  202. if(isset($options['form'])){
  203. $query .= ' AND fo.id IN ('.implode(',',array_fill(0,count($options['form']),'?')).')';
  204. foreach ($options['form'] as $form)
  205. $data[] = $form;
  206. }
  207. if(isset($options['firm'])){
  208. $query .= ' AND fo.firm IN ('.implode(',',array_fill(0,count($options['firm']),'?')).')';
  209. foreach ($options['firm'] as $firm)
  210. $data[] = $firm;
  211. }
  212. $query .= ' ORDER BY fi.row,fi.column,fi.sort';
  213. $dynamicfields = array();
  214. foreach(DynamicField::staticQuery($query,$data,true) as $dynamicField){
  215. if(!is_null($dynamicField->meta) && !empty($dynamicField->meta))
  216. $dynamicField->meta = base64_encode($dynamicField->meta);
  217. $dynamicfields[] = $dynamicField;
  218. }
  219. return $dynamicfields;
  220. }
  221. /**
  222. * Retourne un tableau de champs (de type array) formaté en liste ou table selon l'option 'format'
  223. * @param <Array> tableau brut d'objets champs dynamiques
  224. * @param <Array> tableau des fieldtypes disponibles
  225. * @param <Array> tableau des options de sélection et de format
  226. * @return <Array> tableau formaté (table ou liste) des champs dynamiques ciblés
  227. */
  228. public static function fields_layout($fields,$types,$options = array()){
  229. if(is_null($types)) $types = FieldType::available();
  230. if(empty($options['format'])) $options['format'] = 'table';
  231. $rows = array();
  232. foreach($fields as $field){
  233. $row = $field->toArray();
  234. $row['type'] = $types[$row['type']];
  235. if($options['format'] == 'table'){
  236. if(!isset($rows[$field->row])) $rows[$field->row] = array();
  237. if(!isset($rows[$field->row][$field->column])) $rows[$field->row][$field->column] = array();
  238. $rows[$field->row][$field->column][] = $row;
  239. }else{
  240. $rows[] = $row;
  241. }
  242. }
  243. return $rows;
  244. }
  245. /**
  246. * Retourne un tableau brut de valeurs pour un set de champs en fonction du scope / firm
  247. * @param <Array> tableau brut de champs dynamiques (sous forme d'arrays)
  248. * @param <Array> tableau des options de sélection et de format
  249. * @return <Array> tableau brut des valeurs des champs dynamiques ciblés avec comme clé l'identifiant du champ dynamique
  250. */
  251. public static function get_values($fields,$options){
  252. Plugin::need('dynamicform/DynamicValue');
  253. $values = array();
  254. $fieldIds = array();
  255. //Récuperation des id de fields
  256. foreach($fields as $field)
  257. $fieldIds[] = $field['id'];
  258. if(empty($fieldIds)) return $values;
  259. $valueFilters = array('field:IN'=>$fieldIds);
  260. if(!empty($options['firm'])) $valueFilters['firm:IN'] = array($options['firm'],0);
  261. if(!empty($options['scope'])) $valueFilters['scope'] = $options['scope'];
  262. $valueFilters['uid'] = $options['uid'];
  263. //récuperation des valeurs déja enregistrées pour ces fields
  264. foreach (DynamicValue::loadAll($valueFilters) as $key => $value)
  265. $values[$value->field] = $value->value;
  266. return $values;
  267. }
  268. /**
  269. * Retourne le tableau formaté des valeurs de champs pour export
  270. * @category manipulation de tableaux
  271. * @param <Array> tableau brut des valeurs des champs dynamiques
  272. * @return <Array> tableau des valeurs des champs dynamiques utilisables pour l'export
  273. */
  274. public static function get_values_as_array($fields,$values){
  275. $arrayOutput = array();
  276. foreach($fields as $field){
  277. $field['value']= isset($values[$field['id']]) ? $values[$field['id']] : '';
  278. $arrayOutput[] = $field;
  279. }
  280. return $arrayOutput;
  281. }
  282. /**
  283. * Ajoute à la requete originelle les valeurs des champs dynamiques aliasisés par le slug du champ dynamique
  284. * @category manipulation de chaine pour création dynamiqe de requête de sélection en base
  285. * @param <Array> tableau des champs dynamiques potentiellement affichables
  286. * @param <String> requête originelle de sélection en base à modifier
  287. * @param <String> alias de la table mère présente dans le FROM de la requête originelle de sélection
  288. * @return <String> requête de sélection modifiée avec les valeurs des champs dynamiques
  289. */
  290. public static function query_column_add($fields,&$query,$alias = ''){
  291. global $_;
  292. Plugin::need('dynamicform/DynamicValue');
  293. $columns = array();
  294. //Tableau des champs dynamiques disponibles
  295. foreach($fields as $field)
  296. $columns[$field['id']] = $field['slug'];
  297. //Tableau des champs dynamiques sélectionnés
  298. $selected = isset($_['columns']) && isset($_['columns']['added']) ? $_['columns']['added'] : array();
  299. //On ne garde que les champs sélectionés et présents dans les champs disponibles ET les champs requetés en filtres
  300. if(isset($_['filters']['advanced'])){
  301. foreach($_['filters']['advanced'] as $field){
  302. $parts = explode('dynamicField_',$field['column']);
  303. //on ne traite que les field dynamiques
  304. if(count($parts)<=1) continue;
  305. //pour retrouver le slug du custom field (slug-fitlre) depuis le slug du filtre (dynamicField_{{id}})
  306. $dynamicSlug = $columns[str_replace('.value','',$parts[1])];
  307. $selected[] = $dynamicSlug;
  308. }
  309. }
  310. $columns = array_intersect($columns,$selected);
  311. //On crée la chaine de sélection des valeurs
  312. $selectFields = array();
  313. foreach($columns as $id=>$slug)
  314. $selectFields[] = 'dynamicField_'.$id.'.value AS "'.$slug.'"';
  315. //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éé
  316. $selectFields = empty($selectFields) ? ' ' : ','.implode(',',$selectFields);
  317. $query = preg_replace("/(?<=SELECT)(.*)(?=FROM)/i","$0".$selectFields.' ',$query);
  318. //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
  319. $joinFields = array();
  320. foreach($columns as $id=>$slug)
  321. $joinFields[] = ' LEFT JOIN '.DynamicValue::tableName().' dynamicField_'.$id.' ON dynamicField_'.$id.'.uid = '.(empty($alias) ? 'id' : '`'.$alias.'`.id').' AND dynamicField_'.$id.'.field = '.$id;
  322. $joinFields = implode(' ',$joinFields);
  323. $query = preg_replace("/(?<=SELECT)(.*)(?=WHERE)/is","$0".$joinFields.' ',$query);
  324. }
  325. //Retourne un set de filtres de colonnes dynamiques en fonction de la liste des
  326. //champs custom fournie
  327. public static function get_dynamic_columns($fields){
  328. $columns = array();
  329. foreach ($fields as $field) {
  330. $columns[$field['slug']] =
  331. array(
  332. 'head' => '<th data-sortable="dynamicField_'.$field['id'].'.value" data-available="'.$field['slug'].'">'.$field['label'].'</th>',
  333. 'body' => '<td class="align-middle text-center">{{{'.$field['slug'].'}}}</td>',
  334. );
  335. }
  336. return $columns;
  337. }
  338. //Convertion des valeurs retours d'une db pour des colonnes dynamiques en fonction de leurs types
  339. public static function search_values(&$row,$options){
  340. if(!isset($options['force-raw'])) $options['force-raw'] = false;
  341. //Récupération des champs afin d'avoir les metas pour affichaqge fieldtypes exotique list, dictionary...
  342. $meta = array();
  343. foreach(DynamicField::loadAll(array('slug:IN'=>array_keys($options['slugs']))) as $field)
  344. $meta[$field->slug] = $field->meta;
  345. //pour chaque champ, s'il fait partie des types qui possèdent une propriété onHtmlDisplay, on l'applique à la valeur du champ
  346. foreach($options['slugs'] as $slug=>$value){
  347. if(!isset($options['types'][$slug])) continue;
  348. $type = $options['types'][$slug];
  349. $displayOptions = array('meta'=>$meta[$slug],'type'=>$type);
  350. //tansmission des parametres de contexte
  351. foreach($options as $key=>$option){
  352. if(!is_string($option)) continue;
  353. $displayOptions[$key] = $option;
  354. }
  355. if(isset($row['id'])) $displayOptions['uid'] = $row['id'];
  356. if(property_exists($type,"onHtmlDisplay") && !$options['force-raw']){
  357. $method = $type->onHtmlDisplay;
  358. $displayOptions['decoration'] = 'true';
  359. $value = $method($value,$displayOptions);
  360. }elseif(property_exists($type,"onRawDisplay")){
  361. $method = $type->onRawDisplay;
  362. $value = $method($value,$displayOptions);
  363. }
  364. $row[$slug] = $value;
  365. }
  366. }
  367. //Récuperation des valeurs de champs dynamic pour un formulaire, un scope et un item donné sous la forme slug field => valeur
  368. public static function values($formSlug,$options){
  369. require_once(__DIR__.SLASH.'DynamicForm.class.php');
  370. require_once(__DIR__.SLASH.'DynamicField.class.php');
  371. require_once(__DIR__.SLASH.'DynamicValue.class.php');
  372. $query = 'SELECT dfi.slug,dva.value FROM {{table}} dva
  373. LEFT JOIN '.DynamicField::tableName().' dfi ON dfi.id = dva.field
  374. LEFT JOIN '.DynamicForm::tableName().' dfo ON dfo.id = dfi.form
  375. WHERE dfo.slug = ? AND dva.scope = ? AND dva.uid = ?';
  376. $data = array($formSlug,$options['scope'],$options['uid']);
  377. $values = array();
  378. foreach (DynamicValue::staticQuery($query,$data,true) as $key => $value)
  379. $values[$value->foreign('slug')] = $value->value;
  380. return $values;
  381. }
  382. //Retourne un set de filtres de recherche avancée en fonction de la liste des
  383. //champs custom fournie
  384. public static function get_filters($fields){
  385. $filters = array();
  386. foreach($fields as $field){
  387. $filterOptions = '';
  388. $filterType = $field['type']->slug;
  389. $meta = isset($field['meta']) ? json_decode(base64_decode($field['meta']),true) : (object) array();
  390. //le field doit comprendre le meta show-filter pour afficher le filtre (case a cocher dans la construction du formulaire)
  391. if(!is_array($meta) || empty($meta['show-filter'])) continue;
  392. $filterOptions = ' data-filter-type="'.$filterType.'" ';
  393. if(is_array($meta)){
  394. foreach($meta as $key=>$value)
  395. $filterOptions .= 'data-'.$key.'=\''.(is_string($value) ? $value : str_replace("'","\'",json_encode($value))).'\' ';
  396. }
  397. //si le type de champ ne propose pas de filtre on l'ignore
  398. if(!property_exists($field['type'],'filter')) continue;
  399. $filters[$field['slug']] = '<option value="dynamicField_'.$field['id'].'.value"'.$filterOptions.'>'.$field['label'].'</option>';
  400. }
  401. return $filters;
  402. }
  403. }
  404. ?>