From 81fb9877408bc8b7867627c308f34bbb37102f7a Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 15 Mar 2022 02:24:28 +0100 Subject: [PATCH] Add support for numbers with data on scheduled + add support for sending sms to a csv file --- adapters/KannelAdapter.php | 29 ++- assets/css/style.css | 49 ++++- controllers/internals/Console.php | 2 +- controllers/internals/Contact.php | 2 +- controllers/internals/Quota.php | 2 +- controllers/internals/Ruler.php | 8 +- controllers/internals/Scheduled.php | 74 +++++++- controllers/internals/Sended.php | 27 +-- controllers/internals/StandardController.php | 2 +- controllers/internals/Tool.php | 26 +-- controllers/publics/Api.php | 59 +++++- controllers/publics/Contact.php | 1 - controllers/publics/Discussion.php | 2 +- controllers/publics/Event.php | 2 +- controllers/publics/Received.php | 2 +- controllers/publics/Scheduled.php | 104 ++++++++++- daemons/Phone.php | 4 +- ...313173653_add_data_to_scheduled_number.php | 38 ++++ models/Scheduled.php | 5 +- templates/contact/add.php | 4 +- templates/scheduled/add.php | 132 +++++++++++-- templates/scheduled/edit.php | 175 +++++++++++++++--- 22 files changed, 635 insertions(+), 114 deletions(-) create mode 100644 db/migrations/20220313173653_add_data_to_scheduled_number.php diff --git a/adapters/KannelAdapter.php b/adapters/KannelAdapter.php index 2ef13db..2df2bf6 100644 --- a/adapters/KannelAdapter.php +++ b/adapters/KannelAdapter.php @@ -13,7 +13,6 @@ namespace adapters; use controllers\internals\Quota; use controllers\internals\Tool; -use descartes\Router; /** * Kannel adapter. @@ -31,15 +30,15 @@ class KannelAdapter implements AdapterInterface const KANNEL_CODING_UCS_2 = 2; /** - * DLR mask to transmit to kannel - * + * DLR mask to transmit to kannel. + * * 1 -> Delivered to phone * 2 -> not delivered * 16 -> non delivered to SMSC - * + * * (see https://gist.github.com/grantpullen/3d550f31c454e80fda8fc0d5b9105fd0) */ - const KANNEL_DLR_BITMASK = 1 + 2 + 16; + const KANNEL_DLR_BITMASK = 1 + 2 + 16; /** * Data used to configure interaction with the implemented service. (e.g : Api credentials, ports numbers, etc.). @@ -47,7 +46,7 @@ class KannelAdapter implements AdapterInterface private $data; /** - * Kannel send-sms service url + * Kannel send-sms service url. */ private $kannel_sendsms_url; @@ -62,17 +61,17 @@ class KannelAdapter implements AdapterInterface private $password; /** - * Phone number of the sender, this number may or may not actually be overrided by the SMSC + * Phone number of the sender, this number may or may not actually be overrided by the SMSC. */ private $from; /** - * SMSC's id to use for sending the message + * SMSC's id to use for sending the message. */ private $smsc; /** - * SMS Delivery Report Url + * SMS Delivery Report Url. */ private $dlr_url; @@ -91,7 +90,7 @@ class KannelAdapter implements AdapterInterface $this->password = $this->data['password']; $this->from = $this->data['from']; $this->dlr_url = $this->data['dlr_url']; - + $this->smsc = $this->data['smsc'] ?? null; } @@ -265,7 +264,6 @@ class KannelAdapter implements AdapterInterface //in order to retrieve it in raspisms and update the status $sms_uid = Tool::random_uuid(); - //Forge dlr Url by adding new query parts to url provided within phone settings $dlr_url_parts = parse_url($this->dlr_url); @@ -278,7 +276,6 @@ class KannelAdapter implements AdapterInterface $dlr_url_parts['query'] = http_build_query($dlr_url_query_parts) . '&type=%d'; //Kannel will replace %d by the delivery report value. We cannot set type in bild query or it get double encoded $forged_dlr_url = Tool::unparse_url($dlr_url_parts); - $data = [ 'username' => $this->username, @@ -389,11 +386,11 @@ class KannelAdapter implements AdapterInterface switch (true) { - case 403 == $http_code : //Bad credentials - case 404 == $http_code : //Cannot find url + case 403 == $http_code: //Bad credentials + case 404 == $http_code: //Cannot find url return false; - case $http_code >= 500 : //Server error + case $http_code >= 500: //Server error return false; } @@ -414,7 +411,7 @@ class KannelAdapter implements AdapterInterface { $status = $_GET['type'] ?? false; $uid = $_GET['sms_uid'] ?? false; - + if (!$status || !$uid) { return false; diff --git a/assets/css/style.css b/assets/css/style.css index 1cc9809..b518b32 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -373,6 +373,46 @@ footer img text-align: right; } +.scheduleds-number-groupe +{ + padding-top: 15px; + padding-bottom: 15px; + background-color: #eee; + border-radius: 4px; + margin-bottom: 15px; + position: relative; +} + +.scheduleds-number-groupe-remove +{ + position: absolute; + top: 15px; + right: 15px; + color: #888; +} + +.scheduleds-number-groupe-remove:hover +{ + color: #555; +} + +.scheduleds-number-data-container .form-group:last-of-type +{ + margin-bottom: 0; +} + +.scheduled-number-data-name +{ + width: 30%; + display: inline-block; +} + +.scheduled-number-data-value +{ + width: 65%; + display: inline-block; +} + /* AUDIO RECEPTION MESSAGE */ #reception-sound { @@ -414,11 +454,18 @@ footer img width: 50%; } -.contact-data-container .contact-data-remove +.contact-data-container .contact-data-remove, +.scheduled-number-data-remove { color: #c9302c; } +.contact-data-container .contact-data-remove:hover, +.scheduled-number-data-remove:hover +{ + color: #9b2420; +} + /* PHONE */ #adapter-data-container diff --git a/controllers/internals/Console.php b/controllers/internals/Console.php index 12f7e48..3451252 100644 --- a/controllers/internals/Console.php +++ b/controllers/internals/Console.php @@ -81,7 +81,7 @@ namespace controllers\internals; exit($user ? 0 : 1); } - + /** * Check if a user exists based on id. * diff --git a/controllers/internals/Contact.php b/controllers/internals/Contact.php index b9d19f9..ffb37a9 100644 --- a/controllers/internals/Contact.php +++ b/controllers/internals/Contact.php @@ -31,7 +31,7 @@ namespace controllers\internals; { return $this->get_model()->datatable_list_for_user($id_user, $limit, $offset, $search, $search_columns, $order_column, $order_desc, $count); } - + /** * Return a contact for a user by a number. * diff --git a/controllers/internals/Quota.php b/controllers/internals/Quota.php index 46ab081..2ada9c6 100644 --- a/controllers/internals/Quota.php +++ b/controllers/internals/Quota.php @@ -108,7 +108,7 @@ class Quota extends StandardController } /** - * Check if a message can be encoded as gsm0338 or if it must be UTF8 + * Check if a message can be encoded as gsm0338 or if it must be UTF8. * * @param string $text : Message to send * diff --git a/controllers/internals/Ruler.php b/controllers/internals/Ruler.php index 0dc3103..f90e4f0 100644 --- a/controllers/internals/Ruler.php +++ b/controllers/internals/Ruler.php @@ -48,8 +48,8 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; return true; } - catch (\Throwable $t) //Catch both, exceptions and php error - { + catch (\Throwable $t) + { //Catch both, exceptions and php error return false; } } @@ -71,8 +71,8 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; return (bool) $result; } - catch (\Throwable $t) //Catch both, exceptions and php error - { + catch (\Throwable $t) + { //Catch both, exceptions and php error return null; } } diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index 941c01b..819e23e 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -24,7 +24,7 @@ namespace controllers\internals; * @param ?int $id_phone : Id of the phone to send message with, null by default * @param bool $flash : Is the sms a flash sms, by default false * @param bool $mms : Is the sms a mms, by default false - * @param array $numbers : Numbers to send message to + * @param array $numbers : Array of numbers to send message to, a number is an array ['number' => '+33XXX', 'data' => '{"key":"value", ...}'] * @param array $contacts_ids : Contact ids to send message to * @param array $groups_ids : Group ids to send message to * @param array $conditional_group_ids : Conditional Groups ids to send message to @@ -84,7 +84,7 @@ namespace controllers\internals; foreach ($numbers as $number) { - $this->get_model()->insert_scheduled_number($id_scheduled, $number); + $this->get_model()->insert_scheduled_number($id_scheduled, $number['number'], $number['data']); } $internal_contact = new Contact($this->bdd); @@ -146,7 +146,7 @@ namespace controllers\internals; * @param ?int $id_phone : Id of the phone to send message with, null by default * @param bool $flash : Is the sms a flash sms, by default false * @param bool $mms : Is the sms a mms, by default false - * @param array $numbers : Numbers to send message to + * @param array $numbers : Array of numbers to send message to, a number is an array ['number' => '+33XXX', 'data' => '{"key":"value", ...}'] * @param array $contacts_ids : Contact ids to send message to * @param array $groups_ids : Group ids to send message to * @param array $conditional_group_ids : Conditional Groups ids to send message to @@ -201,7 +201,7 @@ namespace controllers\internals; foreach ($numbers as $number) { - $this->get_model()->insert_scheduled_number($id_scheduled, $number); + $this->get_model()->insert_scheduled_number($id_scheduled, $number['number'], $number['data']); } $internal_contact = new Contact($this->bdd); @@ -496,6 +496,72 @@ namespace controllers\internals; return $smss_to_send_per_scheduled; } + /** + * Parse a CSV file of numbers, potentially associated with datas. + * + * @param resource $file_handler : File handler pointing to the file + * + * @throws Exception : raise exception if file is not valid + * + * @return mixed : array of numbers ['number' => '+XXXX...', 'data' => ['key' => 'value', ...]] + */ + public function parse_csv_numbers_file($file_handler) + { + $numbers = []; + + $head = null; + $line_nb = 0; + while ($line = fgetcsv($file_handler)) + { + ++$line_nb; + if (null === $head) + { + $head = $line; + + continue; + } + + //Padding line with '' entries to make sure its same length as head + //this allow to mix users with data with users without data + $line = array_pad($line, \count($head), ''); + + $line = array_combine($head, $line); + if (false === $line) + { + continue; + } + + $phone_number = \controllers\internals\Tool::parse_phone($line[array_keys($line)[0]] ?? ''); + if (!$phone_number) + { + throw new \Exception('Erreur à la ligne ' . $line_nb . ' colonne 1, numéro de téléphone invalide.'); + } + + $data = []; + $i = 0; + foreach ($line as $key => $value) + { + ++$i; + if ($i < 2) + { // Ignore first column + continue; + } + + if ('' === $value) + { + continue; + } + + $key = mb_ereg_replace('[\W]', '', $key); + $data[$key] = $value; + } + + $numbers[] = ['number' => $phone_number, 'data' => $data]; + } + + return $numbers; + } + /** * Return numbers for a scheduled message. * diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index a492aac..9cce107 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -39,14 +39,14 @@ namespace controllers\internals; * @param int $id_phone : Id of the number the message was send with * @param $at : Reception date * @param $text : Text of the message - * @param string $destination : Number of the receiver - * @param string $uid : Uid of the sms on the adapter service used - * @param string $adapter : Name of the adapter service used to send the message - * @param bool $flash : Is the sms a flash - * @param bool $mms : Is the sms a MMS. By default false. - * @param array $medias : Array of medias to link to the MMS + * @param string $destination : Number of the receiver + * @param string $uid : Uid of the sms on the adapter service used + * @param string $adapter : Name of the adapter service used to send the message + * @param bool $flash : Is the sms a flash + * @param bool $mms : Is the sms a MMS. By default false. + * @param array $medias : Array of medias to link to the MMS * @param ?int $originating_scheduled : Id of the scheduled message that was responsible for sending this message. By default null. - * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN + * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN * * @return mixed : false on error, new sended id else */ @@ -220,11 +220,12 @@ namespace controllers\internals; * @param int $id_user : Id of user to create sended message for * @param int $id_phone : Id of the phone the message was send with * @param $text : Text of the message - * @param string $destination : Number of the receiver - * @param bool $flash : Is the sms a flash. By default false. - * @param bool $mms : Is the sms a MMS. By default false. - * @param array $medias : Array of medias to link to the MMS - * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN + * @param string $destination : Number of the receiver + * @param bool $flash : Is the sms a flash. By default false. + * @param bool $mms : Is the sms a MMS. By default false. + * @param array $medias : Array of medias to link to the MMS + * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN + * @param null|mixed $originating_scheduled * * @return array : [ * bool 'error' => false if success, true else @@ -291,7 +292,7 @@ namespace controllers\internals; 'medias' => $medias, 'originating_scheduled' => $originating_scheduled, ]; - + $internal_webhook = new Webhook($this->bdd); $internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $sended); diff --git a/controllers/internals/StandardController.php b/controllers/internals/StandardController.php index 58d693e..7018244 100644 --- a/controllers/internals/StandardController.php +++ b/controllers/internals/StandardController.php @@ -137,7 +137,7 @@ namespace controllers\internals; /** * Get the model for the Controller. - * + * * @return \models\StandardModel */ abstract protected function get_model(); diff --git a/controllers/internals/Tool.php b/controllers/internals/Tool.php index d677721..e6b8994 100644 --- a/controllers/internals/Tool.php +++ b/controllers/internals/Tool.php @@ -374,22 +374,24 @@ namespace controllers\internals; } /** - * Forge back an url parsed with PHP parse_url function - * + * Forge back an url parsed with PHP parse_url function. + * * @param array $parsed_url : Parsed url returned by parse_url function + * * @return string : The url as a string */ public static function unparse_url(array $parsed_url) { - $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; - $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; - $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; - $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; - $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; - $pass = ($user || $pass) ? "$pass@" : ''; - $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; - $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; + $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $pass = ($user || $pass) ? "{$pass}@" : ''; + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; - return "$scheme$user$pass$host$port$path$query$fragment"; - } + + return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}"; + } } diff --git a/controllers/publics/Api.php b/controllers/publics/Api.php index c7afe41..65f7ae9 100644 --- a/controllers/publics/Api.php +++ b/controllers/publics/Api.php @@ -222,6 +222,7 @@ namespace controllers\publics; $groups = $_POST['groups'] ?? []; $conditional_groups = $_POST['conditional_groups'] ?? []; $files = $_FILES['medias'] ?? false; + $csv_file = $_FILES['numbers_csv'] ?? false; $numbers = \is_array($numbers) ? $numbers : [$numbers]; $contacts = \is_array($contacts) ? $contacts : [$contacts]; @@ -315,22 +316,66 @@ namespace controllers\publics; return $this->json($return); } + if ($csv_file) + { + $uploaded_file = \controllers\internals\Tool::read_uploaded_file($csv_file); + if (!$uploaded_file['success']) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'csv : ' . $uploaded_file['content']; + $this->auto_http_code(false); + + return $this->json($return); + } + + try + { + $csv_numbers = $this->internal_scheduled->parse_csv_numbers_file($uploaded_file['content'], true); + if (!$csv_numbers) + { + throw new \Exception('no valid number in csv file.'); + } + + foreach ($csv_numbers as $csv_number) + { + $csv_number['data'] = json_encode($csv_number['data']); + $numbers[] = $csv_number; + } + } + catch (\Exception $e) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'csv : ' . $e->getMessage(); + $this->auto_http_code(false); + + return $this->json($return); + } + } + foreach ($numbers as $key => $number) { - if (!is_string($number)) + // If number is not an array turn it into an array + $number = is_array($number) ? $number : ['number' => $number, 'data' => '[]']; + $number['data'] = $number['data'] ?? '[]'; + $number['number'] = \controllers\internals\Tool::parse_phone($number['number'] ?? ''); + + if (!$number['number']) { unset($numbers[$key]); continue; } - $number = \controllers\internals\Tool::parse_phone($number); - - if (!$number) + if (null === json_decode($number['data'])) { - unset($numbers[$key]); + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'number data must be a valid json or leave not set.'; + $this->auto_http_code(false); - continue; + return $this->json($return); } $numbers[$key] = $number; @@ -368,7 +413,7 @@ namespace controllers\publics; { try { - $new_media_id = $this->internal_media->upload_and_create_for_user($this->user['id'], $file); + $new_media_id = $this->internal_media->create_from_uploaded_file_for_user($this->user['id'], $file); } catch (\Exception $e) { diff --git a/controllers/publics/Contact.php b/controllers/publics/Contact.php index 6fed079..78da100 100644 --- a/controllers/publics/Contact.php +++ b/controllers/publics/Contact.php @@ -45,7 +45,6 @@ namespace controllers\publics; return $this->render('contact/list'); } - /** * Return contacts as json. */ diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index 0a49e25..d4404a5 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -313,7 +313,7 @@ namespace controllers\publics; $mms = (bool) count($media_ids); //Destinations must be an array of number - $destinations = [$destination]; + $destinations = [['number' => $destination]]; if (!$this->internal_scheduled->create($id_user, $at, $text, $id_phone, false, $mms, $destinations, [], [], [], $media_ids)) { diff --git a/controllers/publics/Event.php b/controllers/publics/Event.php index 5f93b98..172fe7c 100644 --- a/controllers/publics/Event.php +++ b/controllers/publics/Event.php @@ -42,7 +42,7 @@ namespace controllers\publics; { $this->render('event/list'); } - + /** * Return events as json. */ diff --git a/controllers/publics/Received.php b/controllers/publics/Received.php index ac13eb3..ae97d54 100644 --- a/controllers/publics/Received.php +++ b/controllers/publics/Received.php @@ -173,7 +173,7 @@ namespace controllers\publics; foreach ($receiveds as $key => $received) { $receiveds[$key]['text'] = $this->s($received['text'], false, true, false); - + if (!$contact = $this->internal_contact->get_by_number_and_user($_SESSION['user']['id'], $received['origin'])) { continue; diff --git a/controllers/publics/Scheduled.php b/controllers/publics/Scheduled.php index 3668247..b1cfaa4 100644 --- a/controllers/publics/Scheduled.php +++ b/controllers/publics/Scheduled.php @@ -197,7 +197,8 @@ namespace controllers\publics; $numbers = $this->internal_scheduled->get_numbers($scheduled['id']); foreach ($numbers as $number) { - $scheduleds[$key]['numbers'][] = $number['number']; + $number['data'] = json_decode($number['data'] ?? '[]'); + $scheduleds[$key]['numbers'][] = $number; } $contacts = $this->internal_scheduled->get_contacts($scheduled['id']); @@ -259,10 +260,12 @@ namespace controllers\publics; $flash = (bool) ($_POST['flash'] ?? false); $id_phone = empty($_POST['id_phone']) ? null : $_POST['id_phone']; $numbers = $_POST['numbers'] ?? []; + $numbers = is_array($numbers) ? $numbers : [$numbers]; $contacts = $_POST['contacts'] ?? []; $groups = $_POST['groups'] ?? []; $conditional_groups = $_POST['conditional_groups'] ?? []; $files = $_FILES['medias'] ?? false; + $csv_file = $_FILES['csv'] ?? false; //Iterate over files to re-create individual $_FILES array $files_arrays = []; @@ -326,17 +329,64 @@ namespace controllers\publics; return $this->redirect(\descartes\Router::url('Scheduled', 'add')); } + if ($csv_file) + { + $uploaded_file = \controllers\internals\Tool::read_uploaded_file($csv_file); + if (!$uploaded_file['success']) + { + \FlashMessage\FlashMessage::push('danger', 'Impossible de traiter ce fichier CSV : ' . $uploaded_file['content']); + + return $this->redirect(\descartes\Router::url('Scheduled', 'add')); + } + + try + { + $csv_numbers = $this->internal_scheduled->parse_csv_numbers_file($uploaded_file['content']); + if (!$csv_numbers) + { + \FlashMessage\FlashMessage::push('danger', 'Aucun destinataire valide dans le fichier CSV, assurez-vous de fournir un fichier CSV valide.'); + + return $this->redirect(\descartes\Router::url('Scheduled', 'add')); + } + + $numbers = array_merge($csv_numbers, $numbers); + } + catch (\Exception $e) + { + \FlashMessage\FlashMessage::push('danger', 'Impossible de traiter ce fichier CSV : ' . $e->getMessage()); + + return $this->redirect(\descartes\Router::url('Scheduled', 'add')); + } + } + foreach ($numbers as $key => $number) { - $number = \controllers\internals\Tool::parse_phone($number); + // If number is not an array turn it into an array + $number = is_array($number) ? $number : ['number' => $number, 'data' => []]; + $number['data'] = $number['data'] ?? []; + $number['number'] = \controllers\internals\Tool::parse_phone($number['number'] ?? ''); - if (!$number) + if (!$number['number']) { unset($numbers[$key]); continue; } + $clean_data = []; + foreach ($number['data'] as $data_key => $value) + { + if ('' === $value) + { + continue; + } + + $data_key = mb_ereg_replace('[\W]', '', $data_key); + $clean_data[$data_key] = (string) $value; + } + $clean_data = json_encode($clean_data); + $number['data'] = $clean_data; + $numbers[$key] = $number; } @@ -415,6 +465,7 @@ namespace controllers\publics; $groups = $scheduled['groups'] ?? []; $conditional_groups = $scheduled['conditional_groups'] ?? []; $files = $_FILES['scheduleds_' . $id_scheduled . '_medias'] ?? false; + $csv_file = $_FILES['scheduleds_' . $id_scheduled . '_csv'] ?? false; $media_ids = $scheduled['media_ids'] ?? []; //Check scheduled exists and belong to user @@ -482,16 +533,59 @@ namespace controllers\publics; continue; } + if ($csv_file) + { + $uploaded_file = \controllers\internals\Tool::read_uploaded_file($csv_file); + if (!$uploaded_file['success']) + { + continue; + } + + try + { + $csv_numbers = $this->internal_scheduled->parse_csv_numbers_file($uploaded_file['content']); + if (!$csv_numbers) + { + continue; + } + + $numbers = array_merge($csv_numbers, $numbers); + } + catch (\Exception $e) + { + continue; + } + } + + $numbers = is_array($numbers) ? $numbers : [$numbers]; foreach ($numbers as $key => $number) { - $number = \controllers\internals\Tool::parse_phone($number); - if (!$number) + // If number is not an array turn it into an array + $number = is_array($number) ? $number : ['number' => $number, 'data' => []]; + $number['data'] = $number['data'] ?? []; + $number['number'] = \controllers\internals\Tool::parse_phone($number['number'] ?? ''); + + if (!$number['number']) { unset($numbers[$key]); continue; } + $clean_data = []; + foreach ($number['data'] as $data_key => $value) + { + if ('' === $value) + { + continue; + } + + $data_key = mb_ereg_replace('[\W]', '', $data_key); + $clean_data[$data_key] = (string) $value; + } + $clean_data = json_encode($clean_data); + $number['data'] = $clean_data; + $numbers[$key] = $number; } diff --git a/daemons/Phone.php b/daemons/Phone.php index 72c2eac..4f67a4e 100644 --- a/daemons/Phone.php +++ b/daemons/Phone.php @@ -58,7 +58,7 @@ class Phone extends AbstractDaemon { usleep(0.5 * 1000000); //Micro sleep for perfs - $this->read_tick += 1; + ++$this->read_tick; $this->bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD); @@ -155,7 +155,7 @@ class Phone extends AbstractDaemon continue; } - + $this->logger->info('Successfully send message : ' . json_encode($message)); } } diff --git a/db/migrations/20220313173653_add_data_to_scheduled_number.php b/db/migrations/20220313173653_add_data_to_scheduled_number.php new file mode 100644 index 0000000..ef1c9eb --- /dev/null +++ b/db/migrations/20220313173653_add_data_to_scheduled_number.php @@ -0,0 +1,38 @@ +table('scheduled_number'); + $table->addColumn('data', 'text', ['default' => null, 'null' => true, 'after' => 'number']) + ->update(); + } +} diff --git a/models/Scheduled.php b/models/Scheduled.php index 8911ca4..33e317b 100644 --- a/models/Scheduled.php +++ b/models/Scheduled.php @@ -75,12 +75,13 @@ namespace models; * * @param int $id_scheduled : Scheduled id * @param string $number : Number + * @param string $data : Data to link to number * * @return mixed (bool|int) : False on error, new row id else */ - public function insert_scheduled_number(int $id_scheduled, string $number) + public function insert_scheduled_number(int $id_scheduled, string $number, string $data) { - $success = $this->_insert('scheduled_number', ['id_scheduled' => $id_scheduled, 'number' => $number]); + $success = $this->_insert('scheduled_number', ['id_scheduled' => $id_scheduled, 'number' => $number, 'data' => $data]); return $success ? $this->_last_id() : false; } diff --git a/templates/contact/add.php b/templates/contact/add.php index 786951c..df4d448 100644 --- a/templates/contact/add.php +++ b/templates/contact/add.php @@ -61,7 +61,7 @@ $value) { ?>
- + : @@ -136,7 +136,7 @@ '' + ' : ' + '' + - ' ' + + ' ' + '
'; jQuery('.contact-data-container').append(template); }); diff --git a/templates/scheduled/add.php b/templates/scheduled/add.php index 491bb42..81ed599 100644 --- a/templates/scheduled/add.php +++ b/templates/scheduled/add.php @@ -80,12 +80,23 @@
-
-
- - +
+
+ +
+
+ +
+
+ +
+ + : + +
+
-
+
@@ -102,6 +113,15 @@
+
+ +

+ Le SMS sera envoyé à tous les numéros inclus dans le fichier CSV. Assurez-vous que le fichier CSV respecte le format indiqué dans la documentation sur l'envoi de SMS à un fichier CSV. +

+
+ +
+
@@ -156,6 +176,7 @@ jQuery(document).ready(function() { var number_inputs = []; + toto = number_inputs; jQuery('.add-contacts').each(function() { @@ -206,16 +227,27 @@ { var random_id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); var newScheduledsNumberGroupe = '' + - '
' + - '' + - ' ' + - '
'; + '
' + + '
' + + '
' + + '' + + '
' + + '
' + + '' + + '
' + + '' + + ' : ' + + '' + + '
' + + '
' + + '' + + '
'; - jQuery(this).before(newScheduledsNumberGroupe); + jQuery(this).parent('div').before(newScheduledsNumberGroupe); var number_input = jQuery('#' + random_id)[0]; var iti_number_input = window.intlTelInput(number_input, { - hiddenInput: 'numbers[]', + hiddenInput: 'numbers[' + random_id + '][number]', defaultCountry: 's($_SESSION['user']['settings']['default_phone_country']); ?>', preferredCountries: s(json_encode(explode(',', $_SESSION['user']['settings']['preferred_phone_country'])), false, false); ?>, @@ -231,9 +263,16 @@ }); }); + jQuery('body').on('click', '.scheduleds-number-groupe-remove', function (e) + { + e.preventDefault(); + jQuery(this).parent('.scheduleds-number-groupe').remove(); + return false; + }); + var number_input = jQuery('.phone-international-input')[0]; var iti_number_input = window.intlTelInput(number_input, { - hiddenInput: 'numbers[]', + hiddenInput: 'numbers[' + jQuery(number_input).attr('data-uid') + '][number]', defaultCountry: 's($_SESSION['user']['settings']['default_phone_country']); ?>', preferredCountries: s(json_encode(explode(',', $_SESSION['user']['settings']['preferred_phone_country'])), false, false); ?>, @@ -277,6 +316,75 @@ dataType: 'json' }); }); + + jQuery('.scheduleds-number-groupe-container').on('input', '.scheduled-number-data-value, .scheduled-number-data-name', function (e) + { + var scheduled_number = jQuery(this).parents('.scheduleds-number-groupe'); + var focus_group = jQuery(this).parent('.form-group'); + var focus_input = this; + var input_name = focus_group.find('.scheduled-number-data-name'); + var input_value = focus_group.find('.scheduled-number-data-value'); + var uid = focus_group.attr('data-uid') + + scheduled_number.find('.form-group').each(function (e) + { + var current_input_name = jQuery(this).find('.scheduled-number-data-name'); + var current_input_value = jQuery(this).find('.scheduled-number-data-value'); + + if (current_input_value.is(focus_input) || current_input_name.is(focus_input)) + { + return true; + } + + if (jQuery(current_input_name).val() === '' && jQuery(current_input_value).val() === '') + { + jQuery(this).remove(); + } + + return true; + }); + + if (input_name.val() === '' || input_value.val() === '') + { + return true; + } + + var template = '' + + '
' + + '' + + ' : ' + + '' + + ' ' + + '
'; + scheduled_number.find('.scheduleds-number-data-container').append(template); + }); + + jQuery('.scheduleds-number-groupe-container').on('click', '.scheduled-number-data-remove', function (e) + { + e.preventDefault(); + if (jQuery('.scheduleds-number-data-container .form-group').length > 1) + { + jQuery(this).parent('.form-group').remove(); + } + + return false; + }); + + + jQuery('form').on('submit', function (e) + { + jQuery('.scheduleds-number-data-container .form-group').each(function () + { + var name = jQuery(this).find('.scheduled-number-data-name').val(); + name = name.replace(/\W/g, ''); + var uid = jQuery(this).attr('data-uid'); + name = 'numbers[' + uid + '][data][' + name + ']'; + jQuery(this).find('.scheduled-number-data-value').attr('name', name); + }); + + return true; + }); + }); -
-
- -
- -
- - -
- -
-
+
+ +
+ $number) { ?> +
+
+
+ +
+
+ + $data_value) { ?> +
+ + : + + +
+ +
+ + : + +
+
+ + + + +
+ +
+
+
@@ -112,6 +134,15 @@
+
+ +

+ Le SMS sera envoyé à tous les numéros inclus dans le fichier CSV. Assurez-vous que le fichier CSV respecte le format indiqué dans la documentation sur l'envoi de SMS à un fichier CSV. +

+
+ +
+
@@ -210,7 +241,9 @@ jQuery('.phone-international-input').each(function () { - var hidden_input_name = 'scheduleds[' + jQuery(this).attr('scheduled-id') + '][numbers][]'; + var scheduledId = jQuery(this).parents('.scheduleds-number-groupe-container').attr('data-scheduled-id'); + var uid = jQuery(this).attr('data-uid'); + var hidden_input_name = 'scheduleds[' + scheduledId + '][numbers][' + uid + '][number]'; window.intlTelInput(this, { hiddenInput: hidden_input_name, defaultCountry: 's($_SESSION['user']['settings']['default_phone_country']); ?>', @@ -223,22 +256,32 @@ }); }); - jQuery('body').on('click', '.add-number-button', function(e) + jQuery('body').on('click', '.add-number-button', function(e) { var random_id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); - var scheduledId = jQuery(this).parents('.scheduleds-number-groupe-container').attr('scheduled-id'); + var scheduledId = jQuery(this).parents('.scheduleds-number-groupe-container').attr('data-scheduled-id'); var newScheduledsNumberGroupe = '' + - '
' + - '' + - ' ' + - '
'; + '
' + + '
' + + '
' + + '' + + '
' + + '
' + + '' + + '
' + + '' + + ' : ' + + '' + + '
' + + '
' + + '' + + '
'; - jQuery(this).before(newScheduledsNumberGroupe); - - var hidden_input_name = 'scheduleds[' + scheduledId + '][numbers][]'; - var phone_input = jQuery('#' + random_id)[0]; - window.intlTelInput(phone_input, { - hiddenInput: hidden_input_name, + jQuery(this).parent('div').before(newScheduledsNumberGroupe); + + var number_input = jQuery('#' + random_id)[0]; + var iti_number_input = window.intlTelInput(number_input, { + hiddenInput: 'scheduleds[' + scheduledId + '][numbers][' + random_id + '][number]', defaultCountry: 's($_SESSION['user']['settings']['default_phone_country']); ?>', preferredCountries: s(json_encode(explode(',', $_SESSION['user']['settings']['preferred_phone_country'])), false, false); ?>, @@ -246,8 +289,19 @@ nationalMode: true, utilsScript: '/intlTelInput/utils.js' - }); + }); + number_inputs.push({ + 'number_input': number_input, + 'iti_number_input': iti_number_input, + }); + }); + + jQuery('body').on('click', '.scheduleds-number-groupe-remove', function (e) + { + e.preventDefault(); + jQuery(this).parent('.scheduleds-number-groupe').remove(); + return false; }); jQuery('body').on('click', '.btn-delete-media', function (e) @@ -285,6 +339,75 @@ dataType: 'json' }); }); + + jQuery('.scheduleds-number-groupe-container').on('input', '.scheduled-number-data-value, .scheduled-number-data-name', function (e) + { + var scheduled_number = jQuery(this).parents('.scheduleds-number-groupe'); + var focus_group = jQuery(this).parent('.form-group'); + var focus_input = this; + var input_name = focus_group.find('.scheduled-number-data-name'); + var input_value = focus_group.find('.scheduled-number-data-value'); + var uid = focus_group.attr('data-uid') + + scheduled_number.find('.form-group').each(function (e) + { + var current_input_name = jQuery(this).find('.scheduled-number-data-name'); + var current_input_value = jQuery(this).find('.scheduled-number-data-value'); + + if (current_input_value.is(focus_input) || current_input_name.is(focus_input)) + { + return true; + } + + if (jQuery(current_input_name).val() === '' && jQuery(current_input_value).val() === '') + { + jQuery(this).remove(); + } + + return true; + }); + + if (input_name.val() === '' || input_value.val() === '') + { + return true; + } + + var template = '' + + '
' + + '' + + ' : ' + + '' + + ' ' + + '
'; + scheduled_number.find('.scheduleds-number-data-container').append(template); + }); + + jQuery('.scheduleds-number-groupe-container').on('click', '.scheduled-number-data-remove', function (e) + { + e.preventDefault(); + if (jQuery('.scheduleds-number-data-container .form-group').length > 1) + { + jQuery(this).parent('.form-group').remove(); + } + + return false; + }); + + + jQuery('form').on('submit', function (e) + { + jQuery('.scheduleds-number-data-container .form-group').each(function () + { + var name = jQuery(this).find('.scheduled-number-data-name').val(); + name = name.replace(/\W/g, ''); + var scheduled_id = jQuery(this).parents('.scheduleds-number-groupe-container').attr('data-scheduled-id') + var uid = jQuery(this).attr('data-uid'); + name = 'scheduleds[' + scheduled_id + '][numbers][' + uid + '][data][' + name + ']'; + jQuery(this).find('.scheduled-number-data-value').attr('name', name); + }); + + return true; + }); });