can('client','read') && !$myUser->can('client_sheet','read',0,array('contains'=>true))) throw new Exception("Permission non accordée"); require_once(__DIR__.SLASH.'Client.class.php'); // OPTIONS DE RECHERCHE, A ACTIVER POUR UNE RECHERCHE AVANCEE $query = 'SELECT DISTINCT main.*,phone.value as phone,mail.value as mail,parent.id parentId,parent.label parentLabel,address.street,address.complement,address.zip,address.city,'.Firm::joinString('fi').','.Dictionary::joinString('category').' FROM {{table}} main LEFT JOIN '.Client::tableName().' parent ON main.parent=parent.id LEFT JOIN '.Firm::tableName().' fi ON main.firm=fi.id LEFT JOIN '.Dictionary::tableName().' category ON main.category=category.id LEFT JOIN '.Contact::tableName().' phone ON phone.uid=main.id AND phone.scope="client" AND phone.type="mobile" LEFT JOIN '.Contact::tableName().' mail ON mail.uid=main.id AND mail.scope="client" AND mail.type="professional_mail" LEFT JOIN '.Address::tableName().' address ON address.uid=main.id AND address.scope="client" AND address.type="'.Address::MAIN.'" WHERE 1'; $data = array(); $allowedFields = array(); $fields = array(); $hasDynamicPlugin = $myFirm->has_plugin('fr.core.dynamicform') || ($myFirm->id==-1 && Plugin::is_active('fr.core.dynamicform')); //ajout des champs dynamiques dans la recherche if($hasDynamicPlugin){ Plugin::need('dynamicform/DynamicForm'); //le premier argument contient le slug du formulaire contenant toutes les colonnes possibles, le second les colonnes non choisies,la requete, l'alias si nécessaire $dynamicFields = DynamicForm::list('client-sheet-custom',array('firm'=>$myFirm->id)); DynamicForm::query_column_add($dynamicFields,$query,'main'); //On récupère les types de champs qui possèdent une propriété onLoad afin de l'appliquer si on a un champ dynamique $fieldTypes = array(); $fieldCustom = array(); foreach($dynamicFields as $field){ $fieldTypes[$field['slug']] = $field['type']; $fieldCustom[$field['slug']] = $field; $allowedFields[] = 'dynamicField_'.$field['id'].'.value'; } } //selection des colonnes à récuperer // le premier argument contient toutes les colonnes possibles, le second les colonnes non choisies column_secure_query(Client::fields(),$_,$query); //Recherche simple if(!empty($_['filters']['keyword'])){ $query .= ' AND ( (main.type=? AND (main.label LIKE ? OR main.pseudonym LIKE ?) ) OR (main.type=? AND (main.name LIKE ? OR main.firstname LIKE ?)) OR main.comment LIKE ?)'; $data[] = 'firm'; $data[] = '%'.$_['filters']['keyword'].'%'; $data[] = '%'.$_['filters']['keyword'].'%'; $data[] = 'individual'; $data[] = '%'.$_['filters']['keyword'].'%'; $data[] = '%'.$_['filters']['keyword'].'%'; $data[] = '%'.$_['filters']['keyword'].'%'; } if(isset($_['selected'])){ $query .= ' AND main.id IN ('.implode(',',array_fill(0, count($_['selected']), '?')).')'; foreach ($_['selected'] as $selected) $data[] = $selected; } $query .= ' AND main.state != ?'; $data[] = Client::INACTIVE; //on vérifie les droits spéciaux qui peuvent bypasser //récuperation des droits spéciaux sur les fiches //on initialise a 0 pour eviter le plantage si aucun droit $rights = array(0); if(isset($myUser->rights['client_sheet'][0])) $rights += $myUser->rights['client_sheet'][0]; if(isset($myUser->rights['client_sheet'][$myFirm->id])) $rights += $myUser->rights['client_sheet'][$myFirm->id]; //tableau des id deletables $deletableSheets = array(); //var_dump($rights); foreach($rights as $uid => $right){ $data[] = $uid; if(isset($right['delete']) && $right['delete']===true) $deletableSheets[] = $uid; } $query .= ' AND (main.id IN('.implode(',',array_fill(0, count($rights), '?')).') OR main.firm IN(?,?) ) '; $data[] = 0; $data[] = $myFirm->id; Plugin::callHook('client_search',array(&$query,&$data)); //Recherche avancée if(isset($_['filters']['advanced'])){ //traitement filtre custom pour recherche par contact interne $_['filters']['advanced'] = filter_alteration($_['filters']['advanced'],'internal_contact',function($filter) use(&$query,&$data){ global $_; switch($filter['operator']){ case '=': case '!=': $query.=' AND main.id '.($filter['operator']=='!='?' NOT ':'').' IN (SELECT uid FROM '.ContactPerson::tablename().' cp WHERE cp.scope="client" AND type="user" AND account=?) '; $data[] = $filter['value'][0]; break; case 'null': case 'not null': $query.=' AND (SELECT count(id) FROM '.ContactPerson::tablename().' cp WHERE cp.scope="client" AND type="user" AND uid=main.id) '.($filter['operator']=='null'?'=':'!=').' 0 '; break; } return null; }); filter_secure_query($_['filters']['advanced'],array_merge($allowedFields,array('main.id','main.label','address.city','address.zip','main.type','main.condition','main.creator','main.job','main.code','main.siret','main.created','mail.value','phone.value','main.comment','fi.id')),$query,$data); } //Tri des colonnes if(isset($_['sort'])) sort_secure_query($_['sort'],array_merge($allowedFields,array('main.label','main.condition','main.type','main.state','mail.value','address.city','phone.value','fi.label')),$query,$data); //Pagination $itemPerPage = 20; if($_['export'] == 'true') $itemPerPage = 5000; $response['pagination'] = Client::paginate($itemPerPage,(!empty($_['page'])?$_['page']:0),$query,$data,'main'); $clients = Client::staticQuery($query,$data,true,1); $ids = array(); foreach($clients as $client) $ids[]= $client->id; $subsites = array(); if(!empty($ids)){ foreach(Client::loadAll(array('parent:IN'=>$ids,'state'=>Client::ACTIVE)) as $subsite){ if(!isset($subsites[$subsite->parent])) $subsites[$subsite->parent] = array(); $subsites[$subsite->parent][] = $subsite->toArray(); } } foreach($clients as $client){ $row = $client->toArray(); $row['deletable'] = true; if($myFirm->id!=$client->firm && !in_array($client->id, $deletableSheets)) $row['deletable'] = false; if(!$myUser->can('client','delete')) $row['deletable'] = false; $row['phone'] = $client->foreign('phone'); $row['mail'] = $client->foreign('mail'); $row['category'] = $client->join('category'); $firm = $client->firm!= 0 ? $client->join('firm') : new Firm(); $row['firm'] = $firm->toArray(); $mainAddress = new Address(); $mainAddress->street = $client->foreign('street'); $mainAddress->complement = $client->foreign('complement'); $mainAddress->zip = $client->foreign('zip'); $mainAddress->city = $client->foreign('city'); $row['address'] = $mainAddress->toArray(); $row['address']['mapurl'] = $mainAddress->mapUrl(); $row['type'] = $client->type(); $row['condition'] = $client->condition(); $row['logo'] = $client->logo(); $row['holding'] = false; $row['affiliate'] = false; $row['meta'] = $client->meta(); if($client->parent != 0){ $holding = new Client(); $holding->id = $client->foreign('parentId'); $holding->label = $client->foreign('parentLabel'); if($_['export'] == 'true'){ $row['holding'] = $holding->toArray(); }else{ $row['parent'] = $holding->label; } } if($_['export'] == 'true'){ $row['created'] = date('d-m-Y',$row['created']); $row['updated'] = date('d-m-Y',$row['updated']); $row['state'] = $row['state'] == Client::ACTIVE ? 'Actif' : 'Supprimé' ; } //Gestion des champs dynamiques if($hasDynamicPlugin){ DynamicForm::search_values($row,array( 'slugs'=> $client->foreign(), 'types'=> $fieldTypes, 'scope' => 'client', 'force-raw' => $_['export'] == 'true' )); } if(isset($subsites[$client->id])){ $row['affiliate']['rows'] = $subsites[$client->id]; $row['affiliate']['count'] = count($subsites[$client->id]); } $response['rows']['client-'.$client->id] = $row; } /* Mode export */ if($_['export'] == 'true'){ $fieldsMapping = array(); foreach (Client::fields(false) as $key => $value) $fieldsMapping[$value['label']] = $key ; if($myFirm->has_plugin('fr.core.dynamicform')){ foreach ($fieldCustom as $slug => $value) $fieldsMapping[$value['label']] = $slug; } $fieldsMapping['E-mail'] = 'mail'; $fieldsMapping['Téléphone'] = 'phone'; $stream = Excel::exportArray($response['rows'],$fieldsMapping,'Export'); File::downloadStream($stream,'export-clients-'.date('d-m-Y').'.xlsx'); exit(); } }); Action::register('client_client_mail_copy',function(&$response){ global $myUser,$_; if(!$myUser->can('client','read')) throw new Exception('Vous n\'avez pas la permission de copier cet e-mail'); if(count($_['ids'])==0) return; $response['mails'] = array(); foreach(Contact::loadAll(array('scope'=>'client','uid:IN'=>$_['ids'],'type'=>'professional_mail')) as $contact){ $response['mails'][] = $contact->value; } }); //Fusion de deux clients Action::register('client_client_merge',function(&$response){ global $myUser,$_,$conf; User::check_access('client','configure'); require_once(__DIR__.SLASH.'Client.class.php'); $response['rows'] = array(); if(empty($_['left'])) throw new Exception($conf->get('client_label_singular')." de base non définis"); if(empty($_['right'])) throw new Exception($conf->get('client_label_singular')." a fusionner non définis"); if($_['right'] == $_['left']) throw new Exception("Vous ne pouvez pas fusionner un ".$conf->get('client_label_singular')." avec lui même"); $left = Client::getById($_['left']); $right = Client::getById($_['right']); if(empty($left->id)) throw new Exception($conf->get('client_label_singular')." de base introuvable"); if(empty($right->id)) throw new Exception($conf->get('client_label_singular')." de a fusionner introuvable"); if(empty($_['keep'])) throw new Exception("Tableau d'écrasement non définis"); $response['logs'] = $left->merge($right,$_['keep']); }); //Vérifie les duplicatas possibles avec les clients existants Action::register('client_client_check_duplicate',function(&$response){ global $myUser,$_; if(!$myUser->can('client','read')) throw new Exception("Permission non accordée"); require_once(__DIR__.SLASH.'Client.class.php'); $response['rows'] = array(); if(empty($_['label'])) return $response; if(!empty($_['id'])) return $response; $response['rows'] = Client::check_duplicate($_['field'],$_['label'], isset($_['id'])?$_['id']:null ); }); Action::register('client_relation_add',function(&$response){ global $myUser,$_,$conf; if(!$myUser->can('client','read')) throw new Exception("Permission non accordée"); require_once(__DIR__.SLASH.'Client.class.php'); if(empty($_['id'])) throw new Exception($conf->get('client_label_singular')." non spécifié"); if(empty($_['relation'])) throw new Exception("Relation non spécifiée"); if(empty($_['level'])) throw new Exception("Type de relation non spécifiée"); $site = Client::getById($_['id']); $relation = Client::getById($_['relation']); if(!$site || $site->id == 0) throw new Exception($conf->get('client_label_singular')." introuvable en base de donnée"); if(!$relation || $relation->id == 0) throw new Exception($conf->get('client_label_singular')." à lier introuvable en base de donnée"); if($_['level'] == 'holding'){ if($relation->parent == $site->id) throw new Exception("Impossible d'ajouter en holding un établissement déjà filiale"); $site->parent = $relation->id; $site->save(); }else{ if($site->parent == $relation->id) throw new Exception("Impossible d'ajouter en filiale un établissement déjà holding"); $relation->parent = $site->id; $relation->save(); } }); Action::register('client_relation_delete',function(&$response){ global $myUser,$_,$conf; User::check_access('client','read'); require_once(__DIR__.SLASH.'Client.class.php'); if(empty($_['id'])) throw new Exception($conf->get('client_label_singular')." non spécifié"); if(empty($_['relation'])) throw new Exception("Relation non spécifiée"); if(empty($_['level'])) throw new Exception("Type de relation non spécifiée"); switch($_['level']){ case 'holding': $site = Client::getById($_['id']); $relation = Client::getById($_['relation']); if(!$site || $site->id == 0) throw new Exception($conf->get('client_label_singular')." introuvable en base de donnée"); if(!$relation || $relation->id == 0) throw new Exception($conf->get('client_label_singular')." à lier introuvable en base de donnée"); $site->parent = null; $site->save(); break; case 'subsite': $site = Client::getById($_['id']); $relation = Client::getById($_['relation']); if(!$site || $site->id == 0) throw new Exception($conf->get('client_label_singular')." introuvable en base de donnée"); if(!$relation || $relation->id == 0) throw new Exception($conf->get('client_label_singular')." à lier introuvable en base de donnée"); $relation->parent = null; $relation->save(); break; case 'memberof': ContactPerson::remove($_['relation']); break; } }); //Retourne la liste des relations (filiales et holding) Action::register('client_relation_search',function(&$response){ global $myUser,$_,$myFirm; if(!$myUser->can('client','read') && $_['client']!=0 && !$myUser->can('client_sheet','read',$_['client'])) throw new Exception("Permission non accordée sur cette fiche"); require_once(__DIR__.SLASH.'Client.class.php'); $response['rows'] = array(); if(empty($_['client'])) return $response['rows']; $filters = array('parent'=>$_['client'],'firm:IN'=>array(0,$myFirm->id),'state'=>Client::ACTIVE); //si l'user n'est pas en acces tout client, on ne lui affiche que les relation client auxquelles il a acces if(!$myUser->can('client','read')){ $allowedSheets = array(); foreach($myUser->rights['client_sheet'] as $firm => $rights){ foreach($rights as $uid=>$right){ if($right['read']) $allowedSheets[] = $uid; } } $filters['id:IN'] = $allowedSheets; } $clients = Client::loadAll($filters,array('label')); foreach($clients as $client){ $row = $client->toArray(); $addresses = $client->addresses('global'); $row['address'] = isset($addresses[0]) ? $addresses[0] : new Address(); $row['level'] = 'subsite'; $response['rows'][] = $row; } $client = Client::getById($_['client']); if(!empty($client->parent)){ $holdingObject = $client->holding(); $holding = $holdingObject->toArray(); $addresses = $holdingObject->addresses('global'); $holding['address'] = isset($addresses[0]) ? $addresses[0] : new Address(); $holding['level'] = 'holding'; $response['rows'][] = $holding; } foreach(Client::staticQuery('SELECT c.*,cp.id as relation_id FROM '.ContactPerson::tablename().' cp LEFT JOIN {{table}} c ON c.id=cp.uid WHERE cp.account=? AND cp.scope=? and cp.type=?',array($_['client'],'client','client'),true) as $client){ $row = $client->toArray(); $addresses = $client->addresses('global'); $row['address'] = isset($addresses[0]) ? $addresses[0] : new Address(); $row['level'] = 'memberof'; $row['id'] = $client->foreign('relation_id'); $response['rows'][] = $row; } }); Action::register('client_api_search',function(&$response){ global $myUser,$_,$conf; if(!$myUser->can('client','read')) throw new Exception("Permission non accordée"); require_once(__DIR__.SLASH.'Client.class.php'); if(empty($conf->get('client_api_search_url'))) throw new Exception("L'api de recherche ".$conf->get('client_label_singular')." n'est pas configurée, merci de contacter un administrateur"); //ex : https://entreprise.data.gouv.fr/api/sirene/v1/full_text/toxgen $url = str_replace('{{label}}',rawurlencode($_['label']),$conf->get('client_api_search_url')); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $stream = curl_exec($ch); $info = curl_getinfo($ch); curl_close($ch); $json = json_decode($stream,true); if(!$json) throw new Exception("Impossible de contacter l'api de recherche ".$conf->get('client_label_singular')); if($info['http_code'] == 404 || !isset($json['etablissement']) || count($json['etablissement']) ==0 ) throw new Exception($conf->get('client_label_singular')." non trouvé avec ce libellé."); foreach ($json['etablissement'] as $row) { $line = array(); $line['label'] = $row['nom_raison_sociale']; $line['siren'] = $row['siren']; $line['siret'] = $row['siret']; $line['created'] = $row['date_creation']; if(strlen($line['created'])== 8){ $line['created'] = substr($line['created'], -2).'/'.substr($line['created'], -4,2).'/'.substr($line['created'], 0,4); }else{ $line['created'] = ''; } $line['street'] = $row['numero_voie'].' '.$row['type_voie'].' '.$row['libelle_voie']; $line['city'] = $row['libelle_commune']; $line['zip'] = $row['code_postal']; $line['size'] = $row['categorie_entreprise']; $line['status'] = $row['libelle_nature_juridique_entreprise']; $line['employees'] = $row['tranche_effectif_salarie']; $line['job'] = $row['libelle_activite_principale']; $response['rows'][] = $line; } }); Action::register('client_tab_load',function(&$response){ global $myUser,$_; if(!$myUser->can('client','read') && $_['id']!=0 && !$myUser->can('client_sheet','read',$_['id'])) throw new Exception("Permission non accordée sur cette fiche"); $tab = $_['tab']; Plugin::callHook('client_page',array($tab)); exit(); }); //Ajout ou modification d'élément client Action::register('client_client_save',function(&$response){ global $myUser, $_, $conf,$myFirm; User::check_access('client','edit'); require_once(__DIR__.SLASH.'Client.class.php'); $item = Client::provide(); $oldItem = clone $item; if($item->id!=0 && $item->firm!=$myFirm->id && !$myUser->can('client_sheet','edit',$item->id) ) throw new Exception("Vous n'avez pas la permission pour editer cette fiche"); if(empty($_['label'])) throw new Exception("Libellé obligatoire"); $item->label = $_['label']; if(isset($_['firstname']) && !empty($_['firstname'])) $item->firstname = ucfirst(mb_strtolower(trim($_['firstname']))); if(isset($_['name']) && !empty($_['name'])) $item->name = mb_strtoupper(trim($_['name'])); if(isset($_['rate']) && !empty($_['rate'])) $item->rate = $_['rate']; if(isset($_['siret'])) $item->siret = $_['siret']; if(isset($_['comment'])) $item->comment = trim($_['comment']); if(isset($_['parent']) && !empty($_['parent'])) $item->parent = $_['parent']; if(isset($_['type']) && !empty($_['type'])) $item->type = $_['type']; if(!empty($conf->get('client_condition')) && $conf->get('client_condition')!='both'){ $item->condition = $conf->get('client_condition'); }else{ if(isset($_['condition']) && !empty($_['condition'])) $item->condition = $_['condition']; } if(isset($_['category'])) $item->category = $_['category']; if(isset($_['job'])) $item->job = $_['job']; if(isset($_['pseudonym'])) $item->pseudonym = $_['pseudonym']; $item->state = Client::ACTIVE; $item->firm = $myFirm->id; Plugin::callHook('client_save',array(&$item,$oldItem)); $item->save(); Plugin::callHook('client_saved',array(&$item,$oldItem)); //Gestion des champs dynamiques if($myFirm->has_plugin('fr.core.dynamicform')){ Plugin::need('dynamicform/DynamicForm'); Dynamicform::record('client-sheet-custom',array( 'scope'=>'client', 'uid'=>$item->id ),$_); } //trigger pour utilisation sur le workflow if($myFirm->has_plugin('fr.core.workflow')){ Plugin::need('workflow/WorkflowEvent'); WorkflowEvent::trigger('client-client-'.($oldItem->id==0?'create':'update'),array('old'=>$oldItem,'current'=>$item)); } $history = new History(); $history->scope = 'client'; $history->uid = $item->id; if(empty($_['id'])){ $history->comment = "Création ".$conf->get('client_label_singular'); $history->save(); }else{ $history->comment = "Modification ".$conf->get('client_label_singular')." :"; $differences = Entity::compare($oldItem,$item); $fields = Client::fields(false); foreach ($differences as $difference) { $field = $difference['field']; if(isset($fields[$difference['field']])) $field = $fields[$difference['field']]['label']; $history->comment.="\n".$field.' : de '.truncate($difference['value1']).' à '.truncate($difference['value2'].''); } if(count($differences)>0) $history->save(); } if(!empty($_FILES['logo'])){ //Suppression ancien logo foreach (glob($item->dir()."logo.*") as $filename) unlink($filename); $logo = File::upload('logo','client'.SLASH.$item->id.SLASH.'logo.{{ext}}',104857060,array('jpg','png','jpeg','gif')); Image::resize($logo['absolute'],150,150); } if(!empty($_FILES['cover'])){ //Suppression ancienne bannière foreach (glob($item->dir()."cover.*") as $filename) unlink($filename); $cover = File::upload('cover','client'.SLASH.$item->id.SLASH.'cover.{{ext}}',104857060,array('jpg','png','jpeg','gif')); $path = Image::toJpg($cover['absolute']); Image::resize($path,500,500,true,false); } $response = $item->toArray(); if(isset($_['phone']) || isset($_['mail'])){ $contacts = $item->contacts(); if(isset($_['phone'])){ $phone = isset($contacts[Contact::MOBILE]) ? $contacts[Contact::MOBILE] : new Contact(); if(empty($_['phone']) && $phone->id !=0){ Contact::deleteById($phone->id); }else{ $phone->type = Contact::MOBILE; $phone->state = Contact::ACTIVE; $phone->value = str_replace(' ', '', $_['phone']); $phone->uid = $item->id ; $phone->scope = 'client' ; $phone->save(); } } if(isset($_['mail'])){ $mail = isset($contacts[Contact::PROFESSIONAL_MAIL]) ? $contacts[Contact::PROFESSIONAL_MAIL] : new Contact(); if(empty($_['mail']) && $mail->id !=0){ Contact::deleteById($mail->id); }else{ $mail->type = Contact::PROFESSIONAL_MAIL; $mail->state = Contact::ACTIVE; $mail->value = trim($_['mail']); $mail->uid = $item->id ; $mail->scope = 'client' ; $mail->save(); } } } }); //Ajout ou modification d'élément client Action::register('client_assets_load',function(&$response){ global $myUser,$_; if(!$myUser->can('client','read') && !$myUser->can('client_sheet','read',0,array('contains'=>true))) throw new Exception("Permission non accordée"); require_once(__DIR__.SLASH.'Client.class.php'); switch($_['type']){ case 'logo': $client = new Client; if(isset($_['client']) && !empty($_['client'])) $client->id = $_['client']; $path = $client->logo(true); break; case 'cover': $client = new Client; if(isset($_['client']) && !empty($_['client'])) $client->id = $_['client']; $path = $client->cover(true); break; case 'contact': $contact = ContactPerson::getById($_['contact']); $path = $contact->avatar(); break; default: $path = ''; break; } File::downloadFile($path); }); //Suppression d'élement client Action::register('client_client_delete',function(&$response){ global $myUser,$_,$myFirm; User::check_access('client','delete'); require_once(__DIR__.SLASH.'Client.class.php'); $client = Client::getById($_['id']); if($client->firm!=$myFirm->id && !$myUser->can('client_sheet','delete',$client->id) ) throw new Exception("Vous n'avez pas la permission pour supprimer cette fiche"); $client->remove($_['recursive']==1); }); //Sauvegarde des configurations de client Action::register('client_setting_save',function(&$response){ global $myUser,$_,$conf; User::check_access('client','configure'); //Si input file "multiple", possibilité de normlaiser le //tableau $_FILES récupéré avec la fonction => normalize_php_files(); foreach(Configuration::setting('client') as $key=>$value){ if(!is_array($value)) continue; $allowed[] = $key; } foreach ($_['fields'] as $key => $value) { if(in_array($key, $allowed)) $conf->put($key,$value); } }); /** CLIENTCONTACT **/ //Récuperation d'une liste de clientcontact Action::register('client_contact_search',function(&$response){ global $myUser,$_,$myFirm; require_once(__DIR__.SLASH.'Client.class.php'); if(!$myUser->can('client','read') && $_['client']!=0 && !$myUser->can('client_sheet','read',$_['client'])) throw new Exception("Permission non accordée sur cette fiche"); if(empty($_['client'])) return $response; $client = Client::getById($_['client']); //if($client->id!=0 && !in_array($client->firm,array(0,$myFirm->id))) throw new Exception("Ces contacts de fiche ne sont pas disponibles pour votre etablissement"); //si l'user n'est pas en acces tout client, on ne lui affiche que les relation client auxquelles il a acces if(!$myUser->can('client','read')){ $allowedSheets = array(); foreach($myUser->rights['client_sheet'] as $firm => $rights){ foreach($rights as $uid=>$right){ if($right['read']) $allowedSheets[] = $uid; } } } foreach(ContactPerson::getAll(array('where'=>array('scope'=>'client','uid'=>$client->id))) as $clientcontact){ if($clientcontact->type=="client" && isset($allowedSheets) && !in_array($clientcontact->account,$allowedSheets)) continue; $row = $clientcontact->toArray(); $row['fullName'] = $clientcontact->fullName(); $row['avatar'] = 'action.php?action=client_assets_load&type=contact&contact='.$clientcontact->id; $row['tag'] = $clientcontact->getTags(); $row['hasTag'] = !empty($row['tag']); switch($clientcontact->type){ case 'client': require_once(__DIR__.SLASH.'Client.class.php'); $client = Client::getById($clientcontact->account); if(!$client) continue 2; $row['avatar'] = $client->logo(); $row['fullName'] = $client->label(); $row['link'] = 'index.php?module=client&page=sheet.client&id='.$row['account']; $contacts = $client->contacts(); if(isset($contacts['professional_mail'])) $row['mail'] = $contacts['professional_mail']->value; if(isset($contacts['professional_mail'])) $row['mobile'] = $contacts['mobile']->value; if(!empty($client->job)) $row['job'] = $client->job; break; case 'user': $user = User::byLogin($clientcontact->account); $row['avatar'] = $user->getAvatar(); $row['fullName'] = $user->fullName(); $row['mobile'] = $user->mobile; $row['job'] = $user->function; $row['phone'] = $user->phone; $row['mail'] = $user->mail; break; } $response['rows'][] = $row; } }); //Récuperation ou edition d'élément clientcontact Action::register('client_contact_edit',function(&$response){ global $myUser,$_; User::check_access('client','edit'); $contact = ContactPerson::provide(); $contact->meta(); $response = $contact->toArray(); $response['tag'] = $contact->getTags(); }); //Ajout ou modification d'élément clientcontact Action::register('client_contact_save',function(&$response){ global $myUser,$_,$conf; User::check_access('client','edit'); Plugin::need('client/Client'); $item = ContactPerson::provide(); $oldItem = clone $item; $item->type = $_['type']; //Contact référent utilisteur ou client interne if(!empty($_['account'])) $item->account = $_['account']; if($item->type=='external'){ if(empty($_['name']) && empty($_['firstname']) && empty($_['mail']) && empty($_['phone'])) throw new Exception("Au moins l'un des champs Prénom, Nom, Mail ou Téléphone est requis"); $item->name = $_['name']; $item->firstname = $_['firstname']; $item->civility = $_['civility']; $item->meta['type'] = 'contact'; if(!empty($_['mail']) && !check_mail(trim($_['mail']))) throw new Exception("Mail invalide"); $item->mail = trim($_['mail']); if(!empty($_['phone']) && !check_phone_number($_['phone'])) throw new Exception("Téléphone invalide"); $item->phone = normalize_phone_number($_['phone']); if(!empty($_['mobile']) && !check_phone_number($_['mobile'])) throw new Exception("Téléphone mobile invalide"); $item->mobile = normalize_phone_number($_['mobile']); } $item->scope = 'client'; $item->uid = $_['client']; $item->job = $_['job']; $item->comment = $_['comment']; $item->setTags($_['tag']); $item->state = ContactPerson::ACTIVE; $item->save_all(); $history = new History(); $history->scope = 'client'; $history->uid = $_['client']; if(empty($_['id'])){ $history->comment = "Création du contact ".$conf->get('client_label_singular')." ".$item->fullName(); $history->save(); }else{ $history->comment = "Modification du contact ".$conf->get('client_label_singular')." ".$item->fullName().' : '; $differences = Entity::compare($oldItem,$item); $fields = Client::fields(false); foreach ($differences as $difference) { $field = $difference['field']; if(isset($fields[$difference['field']])) $field = $fields[$difference['field']]['label']; $history->comment.="\n".$field.' : de '.truncate($difference['value1']).' à '.truncate($difference['value2'].''); } if(count($differences)>0) $history->save(); } }); //Suppression d'élement clientcontact Action::register('client_contact_delete',function(&$response){ global $myUser,$_,$conf; User::check_access('client','delete'); $item = ContactPerson::provide(); $history = new History(); $history->scope = 'client'; $history->uid = $item->uid; $history->comment = "Suppression du contact ".$conf->get('client_label_singular')." ".$item->fullName(); $history->save(); ContactPerson::remove($item->id); }); //Récupération card d'un contact Action::register('client_card',function(&$response){ global $myUser,$myFirm,$_; require_once(__DIR__.SLASH.'Client.class.php'); $client = Client::provide(); if(!$myUser->can('client','read') && $client->id!=0 && !$myUser->can('client_sheet','read',$client->id)) throw new Exception("Permission non accordée sur cette fiche"); ob_start(); require_once(__DIR__.SLASH.'card.client.php'); $stream = ob_get_clean(); $response['content'] = $stream; }); //composant client Action::register('client_autocomplete',function(&$response){ global $myUser,$_,$myFirm; require_once(__DIR__.SLASH.'Client.class.php'); if (!$myUser->connected()) throw new Exception("Vous devez être connecté", 401); $response['rows'] = array(); $data = array("%".$_['keyword']."%","%".$_['keyword']."%",Client::ACTIVE); //retourne en priorité les matchs à 100%, pour les match keyword%, puis les autres $query = 'SELECT c.*,(SELECT label FROM {{table}} p WHERE p.id=c.parent) parentLabel FROM {{table}} c WHERE (c.label LIKE ? OR pseudonym LIKE ?) AND c.state=? '; if(isset($_['data']['parent']) && !empty($_['data']['parent']) && is_numeric($_['data']['parent'])){ $query .= ' AND c.parent=? '; $data[] = $_['data']['parent']; } $query .= ' AND c.firm=? '; if(isset($_['data']['firm']) && !empty($_['data']['firm']) && is_numeric($_['data']['firm'])){ $data[] = $_['data']['firm']; }else{ $data[] = $myFirm->id; } $query .= ' ORDER BY CASE WHEN c.label = ? THEN 1 WHEN c.label LIKE ? THEN 2 ELSE 3 END LIMIT 10'; $data[] = $_['keyword']; $data[] = '%'.$_['keyword']; $clients = Client::staticQuery($query,$data,true); foreach($clients as $client){ $response['rows'][] = array( 'label'=>html_entity_decode($client->label, ENT_QUOTES).(!empty($client->pseudonym) ? ' ('.html_entity_decode($client->pseudonym, ENT_QUOTES).')' :''), 'parentLabel'=>html_entity_decode($client->foreign('parentLabel'), ENT_QUOTES), 'id'=>$client->id, 'logo' => $client->logo() ); } if(isset($_['data']) && isset($_['data']['before']) && isset($_['data']['before'])!=''){ $list = json_decode(html_entity_decode($_['data']['before']),true); if(is_array($list)){ foreach ($list as $key=>$value) { if(preg_match('/'.$_['keyword'].'/i', $value)) array_unshift($response['rows'],array('label'=>$value,'id'=>$key)); } } } }); Action::register('client_by_uid',function(&$response){ global $myUser,$_; if (!$myUser->connected()) throw new Exception("Vous devez être connecté",401); require_once(__DIR__.SLASH.'Client.class.php'); $response['items'] = array(); $query = 'SELECT main.*,p.label as parentLabel FROM {{table}} main LEFT JOIN {{table}} p ON main.parent=p.id WHERE main.id IN('; $query .= implode(',', array_fill(0, count($_['items']), '?')); $query .= ')'; foreach(Client::staticQuery($query,$_['items'],true) as $client) { $row = $client->toArray(); unset($row['parent']); if(!empty($client->foreign('parentLabel'))) $row['parent'] = array('label'=>$client->foreign('parentLabel')); $row['label'] = html_entity_decode($row['label'], ENT_QUOTES); if(!empty($row['pseudonym'])) $row['label'].= ' ('.html_entity_decode($row['pseudonym'], ENT_QUOTES).')' ; $response['items'][$row['id']] = $row; } if(isset($_['before']) && $_['before']!=''){ $list = json_decode(html_entity_decode($_['before']),true); if(is_array($list)){ if(isset($list[$_['id']])) $response['items'][] = array('label' => $list[$_['id']], 'id'=>$_['id']); } } }); //ADDRESSES //Récuperation d'une liste d'adresses Action::register('client_address_search',function(&$response){ global $myUser,$_,$myFirm; require_once(__DIR__.SLASH.'Client.class.php'); if(!$myUser->can('client','read') && $_['client']!=0 && !$myUser->can('client_sheet','read',$_['client'])) throw new Exception("Permission non accordée sur cette fiche"); if(empty($_['client'])) return $response; $client = Client::getById($_['client']); //if($client->id!=0 && !in_array($client->firm,array(0,$myFirm->id))) throw new Exception("Ces addresses de fiche ne sont pas disponibles pour votre etablissement"); $addresses = Address::loadAll(array('scope'=>'client','uid'=>$_['client'])); foreach($addresses as $address){ $row = $address->toArray(); $row['type'] = Address::types($row['type']); $row['type']['slug'] = $address->type; $response['rows'][] = $row; } }); //Récuperation ou edition d'élément addresse Action::register('client_address_edit',function(&$response){ global $myUser,$_; User::check_access('client','edit'); $address = Address::provide(); if($address->id!=0 && $address->scope != 'client') throw new Exception("Entité non conforme"); $response = $address->toArray(); }); //Ajout ou modification d'élément clientcontact Action::register('client_address_save',function(&$response){ global $myUser,$_; require_once(__DIR__.SLASH.'Client.class.php'); User::check_access('client','edit'); if(empty($_['client'])) throw new Exception("Client non spécifié"); if(empty($_['street']) && empty($_['zip']) && empty($_['city'])) throw new Exception("Vous devez spécifier au minimum une ville, un code postal ou une rue"); $address = Address::provide(); $address->fromArray($_); $address->scope = 'client' ; $address->uid = $_['client'] ; $address->save(); //Enregistrement des coordonées gps dans les méta client si l'adresse est la principale if($address->type==Address::MAIN){ $coordinates = $address->locationCoordinates(); $client = Client::getById($address->uid); $client->meta('longitude', $coordinates['longitude']); $client->meta('latitude', $coordinates['latitude']); $client->save(); } History::put('client',$_['client'],(empty($_['id']) ? 'Création ': ' Modification ' )." d'une adresse de type : ".Address::types($address->type)['label'].' : '.$address->fullName()); }); //Suppression d'élement clientcontact Action::register('client_address_delete',function(&$response){ global $myUser,$_; User::check_access('client','delete'); require_once(__DIR__.SLASH.'Client.class.php'); $address = Address::getById($_['id']); if($address->scope!='client') throw new Exception("Entité spécifiée non conforme",403); Address::deleteById($address->id); History::put('client',$address->uid,"Suppression d'une adresse de type : ".Address::types($address->type)['label']); }); /*WIDGET*/ Action::register('client_widget_load',function(&$response){ global $myUser,$_; if(!$myUser->can('client','read')) throw new Exception("Permission non accordée"); Plugin::need('dashboard/DashboardWidget'); $widget = DashboardWidget::current(); $widget->title = 'Widget Client'; ob_start(); //Décommenter après avoir créé widget.php require_once(__DIR__.SLASH.'client.widget.php'); $widget->content = ob_get_clean(); echo json_encode($widget); exit; }); /** HISTORY **/ //Récuperation d'une liste de history Action::register('client_core_history_search',function(&$response){ global $myUser,$_; if(!$myUser->can('client_history','read')) throw new Exception("Permission non accordée sur cette fiche"); if(!$myUser->can('client','read') && $_['id']!=0 && !$myUser->can('client_sheet','read',$_['id'])) throw new Exception("Permission non accordée sur cette fiche"); // OPTIONS DE RECHERCHE, A ACTIVER POUR UNE RECHERCHE AVANCEE $query = 'SELECT * FROM {{table}} WHERE 1'; $data = array(); //scopé sur le client $query .= ' AND scope = ? AND uid = ?'; $data[] = 'client'; $data[] = $_['id']; //Recherche simple if(!empty($_['filters']['keyword'])){ $query .= ' AND comment LIKE ?'; $data[] = '%'.$_['filters']['keyword'].'%'; } //Recherche avancée if(isset($_['filters']['advanced'])) filter_secure_query($_['filters']['advanced'],array('comment','created','creator'),$query,$data); $query .= ' ORDER BY created DESC'; //Pagination $response['pagination'] = History::paginate(20,(!empty($_['page'])?$_['page']:0),$query,$data); $historys = History::staticQuery($query,$data,true,0); foreach($historys as $history){ $row = $history->toArray(); $monthName = month_name(date('m',$history->created)); $row['created'] = array( 'dayShortName' => day_name(date('N',$history->created)), 'day' => date('d',$history->created), 'monthName' => strlen($monthName)>4 ? substr(month_name(date('m',$history->created)), 0, 4).'.' : $monthName, 'time' => date('H:i',$history->created) ); $row['comment'] = html_entity_decode($row['comment']); if($_['export'] == 'true'){ $row['created'] = date('d-m-Y',$history->created); $row['updated'] = date('d-m-Y',$history->updated); } $creator = User::byLogin($row['creator']); $row['creator'] = $creator->toArray(); $row['editable'] = $row['type'] == History::TYPE_COMMENT ; $row['creator']['fullName'] = $creator->fullName(); $response['rows'][] = $row; } /* Mode export */ if($_['export'] == 'true'){ $fieldsMapping = array(); foreach (History::fields(false) as $key => $value) $fieldsMapping[$value['label']] = $key ; $stream = Excel::exportArray($response['rows'],$fieldsMapping,'Export'); File::downloadStream($stream,'historique-client-'.date('d-m-Y').'.xlsx'); exit(); } }); Action::register('client_core_history_save',function(&$response){ global $myUser,$_,$myFirm; User::check_access('client_history','edit'); if(!isset($_['client'])) throw new Exception('Id client manquant'); if(!isset($_['comment'])) throw new Exception('Message manquant'); $history = History::provide(); $history->scope = 'client'; $history->uid = $_['client']; $history->comment = $_['comment']; $history->type = History::TYPE_COMMENT; $history->save(); }); Action::register('client_history_edit',function(&$response){ global $myUser,$_,$myFirm; User::check_access('client_history','edit'); require_once(__DIR__.SLASH.'Client.class.php'); $history = History::getById($_['id']); if($history->scope!='client') throw new Exception('Vous ne pouvez editer ce type d\'historique'); $response = $history->toArray(); $response['comment'] = html_entity_decode($response['comment']); }); Action::register('client_core_history_delete',function(&$response){ global $myUser,$_,$myFirm; User::check_access('client_history','delete'); require_once(__DIR__.SLASH.'Client.class.php'); $history = History::getById($_['id']); if($history->scope!='client') throw new Exception('Vous ne pouvez supprimer ce type d\'historique'); History::deleteById($history->id); }); Action::register('client_import_component',function(&$response){ global $myUser,$_; if(!$myUser->can('client','configure')) throw new Exception("Permission non accordée"); File::handle_component(array( 'namespace' => 'client', //stockés dans file/client/*.* 'access' => 'client', // crud sur summary, 'size' => '1000000000', // taille max 'extension' => 'csv', 'storage' => 'client/import/*' //chemin complet vers le fichier stocké ),$response); }); Action::register('client_import',function(&$response){ global $myUser,$_,$myFirm; if(!$myUser->can('client','configure')) throw new Exception("Permission non accordée"); if(count($_['attachments'])==0) throw new Exception("Merci d'envoyer un fichier au format csv avant d'appuyer sur le boutton import"); if($_['attachments'][0]['extension']!='csv') throw new Exception("Extension invalide (accepté: csv)"); if($_['attachments'][0]['size'] > 1000000000 ) throw new Exception("Taille trop importante (max accepté: 1Go)"); require_once(__DIR__.SLASH.'Client.class.php'); $clientManager = new Client(); $keyLabelMap = array(); foreach ($clientManager->fields as $key => $meta) { $keyLabelMap[$meta['label']] = $key; } $isDynamic = array(); if($myFirm->has_plugin('fr.core.dynamicform')){ Plugin::need('dynamicform/DynamicForm'); foreach (DynamicForm::list('client-sheet-custom') as $key => $meta) { $keyLabelMap[$meta['label']] = $meta['slug']; $isDynamic[$meta['slug']] = $meta; } } $keyLabelMap['Contacts'] = 'contacts'; $keyLabelMap['Adresses'] = 'addresses'; $keyLabelMap['Client Parent'] = 'parent'; $lines = explode("\r\n",utf8_encode(file_get_contents(File::temp().str_replace('/tmp/','',$_['attachments'][0]['path'])))); if(count($lines)<2) throw new Exception("Le fichier d'import est vide"); $headers = array_shift($lines); $headers = explode(';',$headers); $headersColumnMap = array(); foreach($headers as $i => $header){ if(!isset($keyLabelMap[$header])) continue; $headersColumnMap[$i] = $keyLabelMap[html_entity_decode($header)]; } $response['warnings'] = array(); $transformMapping = array(); //convertion des types label -> bdd $typesLabel = array(); foreach (Client::types() as $typeKey=>$type) $typesLabel[$type['label']] = $typeKey; $transformMapping['type'] = function($value,$client) use ($typesLabel){ return isset($typesLabel[$value]) ? $typesLabel[$value] : ''; }; //convertion des conditions label -> bdd $conditionsLabel = array(); foreach (Client::conditions() as $conditionKey=>$condition) $conditionsLabel[$condition['label']] = $conditionKey; $transformMapping['condition'] = function($value,$client) use ($conditionsLabel){ return isset($conditionsLabel[$value])? $conditionsLabel[$value] : ''; }; //convertion des catégories label -> bdd $categoriesLabel = array(); $categoriesDictionary = Dictionary::bySlug('client_category'); foreach (Dictionary::slugToArray('client_category',true) as $categoryKey=>$category) $categoriesLabel[$category->label] = $categoryKey; //Convertion des adresses /* le format des adresse doit être le suivant dans le csv : add1\nadd2[:type], ex : "122 avenue de saint emilion 33600 Martignas sur jalles 344 rue frédéric sévène 33400 Talence:facturation" */ $transformMapping['addresses'] = function($value,$client){ //suppression des "" de chaque coté de la valeur si existants if(preg_match('/^"(.*)"$/si', $value,$match)) $value = $match[1]; foreach(explode("\n",$value) as $rawAddress){ $address = new Address(); $type = Address::MAIN; $rawAddress = explode(':',$rawAddress); if(isset($rawAddress[1])) $type = $rawAddress[1]; $street = ''; $zip = ''; $city = ''; $country = ''; $complement = ''; if(preg_match('/([0-9]*)? ?(.*) ([0-9]{5}) ([^,]*),?\s*(.*)?/i', $rawAddress[0],$match)){ if(!empty($match[1]) || !empty($match[2])){ $street = (!empty($match[1]) ? $match[1] : '').(!empty($match[2]) ? ' '.$match[2] : ''); if(!empty($match[3])) $zip = $match[3]; if(!empty($match[4])) $city = $match[4]; if(!empty($match[5])) $country = $match[5]; } }else{ //Si on ne parviens pas a parser l'adresse //On essaye de récuperer le CP if(preg_match('/ ([0-9]{5}) /i', $rawAddress[0],$match)){ $zip = $match[1]; } //on stoque tout dans la rue $street = $rawAddress[0]; } $address->scope = 'client'; $address->label = ''; $address->uid = $client->id; $address->type = $type; $address->street = $street; $address->complement = $complement; $address->city = $city; $address->zip = $zip; $address->country = $country; $address->state = Address::ACTIVE; //récuperation latitude et longitude //Enregistrement des coordonées gps dans les méta client si l'adresse est la principale if($address->type==Address::MAIN && isset($coordinates['longitude']) && isset($coordinates['latitude'])){ $coordinates = $address->locationCoordinates(); $client->meta('longitude', $coordinates['longitude']); $client->meta('latitude', $coordinates['latitude']); $client->save(); } $address->save(); } //on returne false pour indiquer de ne pas remplir l'objet client avec cette info return false; }; //Convertion des contacts /* Exemple de format de contact dans la case du csv "jessica ALBA:jalba@alba.com:0681129753:0781129753:PDG:beauty,star angelina JOLIE:angie@jolie.com" */ $transformMapping['contacts'] = function($value,$client){ //suppression des "" de chaque coté de la valeur si existants if(preg_match('/^"(.*)"$/si', $value,$match)) $value = $match[1]; foreach(explode("\n",$value) as $rawContact){ $contact = new ContactPerson(); $rawContact = explode(':',$rawContact); if(isset($rawContact[1])) $type = $rawContact[1]; $nameFirstName = explode(' ',$rawContact[0]); $contact->firstname = $nameFirstName[0]; if(isset($nameFirstName[1])) $contact->name = $nameFirstName[1]; if(!empty($rawContact[1])) $contact->mail = $rawContact[1]; if(!empty($rawContact[2])) $contact->phone = $rawContact[2]; if(!empty($rawContact[3])) $contact->mobile = $rawContact[3]; if(!empty($rawContact[4])) $contact->job = $rawContact[4]; if(!empty($rawContact[5])) $contact->tag = $rawContact[5]; $contact->scope = 'client'; $contact->uid = $client->id; $contact->type = ContactPerson::TYPE_EXTERNAL; $contact->state = ContactPerson::ACTIVE; $contact->save(); } //on returne false pour indiquer de ne pas remplir l'objet client avec cette info return false; }; //transformation du libellé parent en libellé enfant $transformMapping['parent'] = function($value,$client){ if(empty($value)) return ''; $parent = Client::load(array('label'=>$value)); if(!$parent) return; return $parent->id; }; $transformMapping['category'] = function($value,$client) use ($categoriesLabel,$categoriesDictionary){ if(!isset($categoriesLabel[$value])){ $dictionary = new Dictionary(); $dictionary->label = $value; $dictionary->state = Dictionary::ACTIVE; $dictionary->slug = $categoriesDictionary->slug.'_'.slugify($value); $dictionary->parent = $categoriesDictionary->id; $dictionary->save(); $categoriesLabel[$value] = $dictionary->id; } return $categoriesLabel[$value]; }; foreach ($lines as $i=>$line) { if(empty($line)) continue; try{ $columns = explode(';',$line); if(count($columns)==0) throw new Exception("Colonnes vides"); $newClient = new Client(); $newClient->save(); //on presave pour obtenir un id foreach ($columns as $u=>$column) { try{ if(!isset($headersColumnMap[$u])) throw new Exception("La colonne '".$headers[$u]."' ne correspond a rien dans l'erp"); $key = $headersColumnMap[$u]; if(isset($transformMapping[$key])){ $method = $transformMapping[$key]; $column = $method($column,$newClient); //si false, ne pas remplir l'objet client avec cette info (info relationnelle) if($column === false) continue; } if($myFirm->has_plugin('fr.core.dynamicform') && isset($isDynamic[$key]) ){ Plugin::need('dynamicform/DynamicValue'); $dynamicValue = new DynamicValue(); $dynamicValue->field = $isDynamic[$key]['id']; if(property_exists($isDynamic[$key]['type'],"fromRawDisplay")){ $method = $isDynamic[$key]['type']->fromRawDisplay; $options = array(); if($isDynamic[$key]['type']->slug =='dictionary'){ $parent = Dictionary::bySlug($isDynamic[$key]['meta']->slug); $options['slug'] = $parent->id; } $column = $method($column,$options); } $dynamicValue->value= $column; $dynamicValue->firm = $myFirm->id; $dynamicValue->scope = 'client'; $dynamicValue->uid = $newClient->id; $dynamicValue->save(); }else{ $newClient->$key = $column; } }catch(Exception $e){ $response['warnings'][] = 'L'.($i+1).' Col'.($u+1).':'.$e->getMessage(); } if(empty($newClient->code)) $newClient->code = CLient::code($newClient); if(empty($newClient->slug)) $newClient->slug = slugify($client->label); $newClient->save(); } }catch(Exception $e){ //si un pb sur la ligne on supprime le client qu'on a commencé a enregistrer en base if($newClient->id!=0) Client::deleteById($newClient->id); $response['warnings'][] = 'L'.($i+1).':'.$e->getMessage(); } } }); Action::register('client_import_model',function(&$response){ global $myUser,$_,$myFirm; if(!$myUser->can('client','configure')) throw new Exception("Permission non accordée"); require_once(__DIR__.SLASH.'Client.class.php'); $client = new Client(); $stream = ''; foreach ($client->fields as $key => $meta) { if( in_array($key, array('id','state','slug','meta'))) continue; $stream .= $meta['label'].';'; } $stream .= 'Contacts;'; $stream .= 'Adresses;'; $stream .= 'Client Parent;'; if($myFirm->has_plugin('fr.core.dynamicform')){ Plugin::need('dynamicform/DynamicForm'); foreach (DynamicForm::list('client-sheet-custom') as $key => $meta) { $stream .= $meta['label'].';'; } } $stream .= PHP_EOL; File::downloadStream(utf8_decode($stream),'Trame import client.csv', 'application/octet-stream'); exit(); }); ?>