Compare commits

..

13 Commits

Author SHA1 Message Date
osaajani 85b64ada1a fix mms phone not set 2023-02-18 05:44:42 +01:00
osaajani f9e64aee65 add preview of group members 2023-02-17 05:18:57 +01:00
osaajani 59d3e28489 Add a setting to force gsm alphabet conversion 2023-02-06 20:18:35 +01:00
osaajani fdbc6a0878 fix date format 2023-02-06 05:52:49 +01:00
osaajani 4f0c585f78 finally, we will just do limit checking all over again during sending phase 2023-02-06 05:32:30 +01:00
osaajani 55fe91619b see to use forcefail 2023-02-06 05:05:18 +01:00
osaajani 47b81c1af3 fix a few error, undefined vars, update phpstan, fix adapter data preset on edit phone 2023-02-06 04:35:08 +01:00
osaajani 715afd79ec Update sending functions to correctly use phone limits and priority 2023-02-06 03:42:03 +01:00
osaajani 9b7907ad18 Start work to update get_smss_to_send to use priority and phone limits 2023-02-06 03:19:36 +01:00
osaajani 69619d0bef Add notion of priority to phones 2023-02-05 23:11:58 +01:00
osaajani 6353d5115b Add phone limits to list 2023-02-04 01:45:59 +01:00
osaajani fb58802240 Add update to phones 2023-02-04 01:15:36 +01:00
osaajani 298bba0c39 Add phone limit creation to phone creation 2023-02-02 01:12:30 +01:00
31 changed files with 1591 additions and 353 deletions

View File

@ -119,9 +119,9 @@ interface AdapterInterface
* @param array $medias : Array of medias to link to the MMS, [['http_url' => HTTP public url of the media et 'local_uri' => local uri to media file]] * @param array $medias : Array of medias to link to the MMS, [['http_url' => HTTP public url of the media et 'local_uri' => local uri to media file]]
* *
* @return array : [ * @return array : [
* bool 'error' => false if no error, true else * bool 'error' => false if no error, true else,
* ?string 'error_message' => null if no error, else error message * ?string 'error_message' => null if no error, else error message,
* array 'uid' => Uid of the sms created on success * array 'uid' => Uid of the sms created on success,
* ] * ]
*/ */
public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []): array; public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []): array;

View File

@ -311,7 +311,8 @@ footer img
} }
/* SCHEDULEDS */ /* SCHEDULEDS */
.add-number-button .add-number-button,
.add-phone-limit-button
{ {
display: inline-block; display: inline-block;
color: #DADFE1; color: #DADFE1;
@ -319,7 +320,8 @@ footer img
vertical-align: top; vertical-align: top;
} }
.add-number-button:hover .add-number-button:hover,
.add-phone-limit-button:hover
{ {
color: #3498DB; color: #3498DB;
cursor: pointer; cursor: pointer;
@ -373,7 +375,8 @@ footer img
text-align: right; text-align: right;
} }
.scheduleds-number-groupe .scheduleds-number-groupe,
.phone-limits-group
{ {
padding-top: 15px; padding-top: 15px;
padding-bottom: 15px; padding-bottom: 15px;
@ -383,7 +386,8 @@ footer img
position: relative; position: relative;
} }
.scheduleds-number-groupe-remove .scheduleds-number-groupe-remove,
.phone-limits-group-remove
{ {
position: absolute; position: absolute;
top: 15px; top: 15px;
@ -391,7 +395,8 @@ footer img
color: #888; color: #888;
} }
.scheduleds-number-groupe-remove:hover .scheduleds-number-groupe-remove:hover,
.phone-limits-group-remove:hover
{ {
color: #555; color: #555;
} }
@ -466,6 +471,16 @@ footer img
color: #9b2420; color: #9b2420;
} }
/* PREVIEW CONTACT */
.preview-contact-name
{
font-weight: bold;
}
.preview-contact-number
{
font-style: italic;
}
/* PHONE */ /* PHONE */
#adapter-data-container #adapter-data-container

View File

@ -14,8 +14,7 @@
"symfony/yaml": "^5.0", "symfony/yaml": "^5.0",
"phpmailer/phpmailer": "^6.1", "phpmailer/phpmailer": "^6.1",
"xantios/mimey": ">=2.1", "xantios/mimey": ">=2.1",
"kreait/firebase-php": "^5.14" "kreait/firebase-php": "^5.14",
}, "benmorel/gsm-charset-converter": "^0.3.0"
"require-dev": {
} }
} }

View File

@ -73,21 +73,6 @@ namespace controllers\internals;
return $this->get_model()->insert($event); return $this->get_model()->insert($event);
} }
/**
* Gets events for a type, since a date and eventually until a date (both included).
*
* @param int $id_user : User id
* @param string $type : Event type we want
* @param \DateTime $since : Date to get events since
* @param ?\DateTime $until (optional) : Date until wich we want events, if not specified no limit
*
* @return array
*/
public function get_events_by_type_and_date_for_user(int $id_user, string $type, \DateTime $since, ?\DateTime $until = null)
{
$this->get_model()->get_events_by_type_and_date_for_user($id_user, $type, $since, $until);
}
/** /**
* Get the model for the Controller. * Get the model for the Controller.
*/ */

View File

@ -44,11 +44,6 @@ class ExpressionProvider implements ExpressionFunctionProviderInterface
return sprintf('isset(%1$s) && is_a(%1$s, \'DateTime\') && %1$s->format(\'m-d\') == (new \\DateTime())->format(\'m-d\')', $birthdate); return sprintf('isset(%1$s) && is_a(%1$s, \'DateTime\') && %1$s->format(\'m-d\') == (new \\DateTime())->format(\'m-d\')', $birthdate);
}, function ($arguments, DateTime $birthdate) }, function ($arguments, DateTime $birthdate)
{ {
if (!($birthdate ?? false))
{
return false;
}
return $birthdate->format('m-d') == (new DateTime())->format('m-d'); return $birthdate->format('m-d') == (new DateTime())->format('m-d');
}); });

View File

@ -44,15 +44,15 @@ namespace controllers\internals;
} }
/** /**
* Return a phone by his name. * Return a list of phone limits
* *
* @param string $name : Phone name * @param int $id_phone : Phone id
* *
* @return array * @return array
*/ */
public function get_by_name(string $name) public function get_limits(int $id_phone)
{ {
return $this->get_model()->get_by_name($name); return $this->get_model()->get_limits($id_phone);
} }
/** /**
@ -137,19 +137,46 @@ namespace controllers\internals;
* @param string $name : The name of the phone * @param string $name : The name of the phone
* @param string $adapter : The adapter to use the phone * @param string $adapter : The adapter to use the phone
* @param string json $adapter_data : A JSON string representing adapter's data (for example credentials for an api) * @param string json $adapter_data : A JSON string representing adapter's data (for example credentials for an api)
* @param int $priority : Priority with which to use phone to send SMS. Default 0.
* @param array $limits : An array of limits for this phone. Each limit must be an array with a key volume and a key startpoint
* *
* @return bool|int : false on error, new id on success * @return bool|int : false on error, new id on success
*/ */
public function create(int $id_user, string $name, string $adapter, string $adapter_data) public function create(int $id_user, string $name, string $adapter, string $adapter_data, int $priority = 0, array $limits = [])
{ {
$phone = [ $phone = [
'id_user' => $id_user, 'id_user' => $id_user,
'name' => $name, 'name' => $name,
'priority' => $priority,
'adapter' => $adapter, 'adapter' => $adapter,
'adapter_data' => $adapter_data, 'adapter_data' => $adapter_data,
]; ];
return $this->get_model()->insert($phone); //Use transaction to garanty atomicity
$this->bdd->beginTransaction();
$new_phone_id = $this->get_model()->insert($phone);
if (!$new_phone_id)
{
$this->bdd->rollBack();
return false;
}
foreach ($limits as $limit)
{
$limit_id = $this->get_model()->insert_phone_limit($new_phone_id, $limit['volume'], $limit['startpoint']);
if (!$limit_id)
{
$this->bdd->rollBack();
return false;
}
}
$success = $this->bdd->commit();
return ($success ? $new_phone_id : false);
} }
/** /**
@ -159,20 +186,54 @@ namespace controllers\internals;
* @param int $id : Phone id * @param int $id : Phone id
* @param string $name : The name of the phone * @param string $name : The name of the phone
* @param string $adapter : The adapter to use the phone * @param string $adapter : The adapter to use the phone
* @param array $adapter_data : An array of the data of the adapter (for example credentials for an api) * @param string json $adapter_data : A JSON string representing adapter's data (for example credentials for an api)
* @param int $priority : Priority with which to use phone to send SMS. Default 0.
* @param array $limits : An array of limits for this phone. Each limit must be an array with a key volume and a key startpoint
* *
* @return bool : false on error, true on success * @return bool : false on error, true on success
*/ */
public function update_for_user(int $id_user, int $id, string $name, string $adapter, array $adapter_data): bool public function update_for_user(int $id_user, int $id, string $name, string $adapter, string $adapter_data, int $priority = 0, array $limits = []): bool
{ {
$phone = [ $phone = [
'id_user' => $id_user, 'id_user' => $id_user,
'name' => $name, 'name' => $name,
'adapter' => $adapter, 'adapter' => $adapter,
'adapter_data' => json_encode($adapter_data), 'adapter_data' => $adapter_data,
'priority' => $priority,
]; ];
return (bool) $this->get_model()->update_for_user($id_user, $id, $phone); //Use transaction to garanty atomicity
$this->bdd->beginTransaction();
$nb_delete = $this->get_model()->delete_phone_limits($id);
foreach ($limits as $limit)
{
$limit_id = $this->get_model()->insert_phone_limit($id, $limit['volume'], $limit['startpoint']);
if (!$limit_id)
{
$this->bdd->rollBack();
return false;
}
}
$nb_update = $this->get_model()->update_for_user($id_user, $id, $phone);
$success = $this->bdd->commit();
if (!$success)
{
return false;
}
if ($nb_update == 0 && count($limits) == 0)
{
return false;
}
return true;
} }
/** /**

View File

@ -274,236 +274,6 @@ use Monolog\Logger;
return $this->get_model()->gets_after_date_for_number_and_user($id_user, $date, $number); return $this->get_model()->gets_after_date_for_number_and_user($id_user, $date, $number);
} }
/**
* Get all messages to send and the number to use to send theme.
*
* @return array : List of smss to send at this time per scheduled id ['1' => [['id_scheduled', 'text', 'id_phone', 'destination', 'flash', 'mms', 'medias'], ...], ...]
*/
public function get_smss_to_send()
{
$smss_to_send_per_scheduled = [];
$internal_templating = new \controllers\internals\Templating();
$internal_setting = new \controllers\internals\Setting($this->bdd);
$internal_group = new \controllers\internals\Group($this->bdd);
$internal_conditional_group = new \controllers\internals\ConditionalGroup($this->bdd);
$internal_phone = new \controllers\internals\Phone($this->bdd);
$internal_smsstop = new \controllers\internals\SmsStop($this->bdd);
$users_smsstops = [];
$users_settings = [];
$users_phones = [];
$users_mms_phones = [];
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:s');
$scheduleds = $this->get_model()->gets_before_date($now);
foreach ($scheduleds as $scheduled)
{
$smss_to_send_per_scheduled[$scheduled['id']] = [];
if (!isset($users_settings[$scheduled['id_user']]))
{
$users_settings[$scheduled['id_user']] = [];
$settings = $internal_setting->gets_for_user($scheduled['id_user']);
foreach ($settings as $name => $value)
{
$users_settings[$scheduled['id_user']][$name] = $value;
}
}
if (!isset($users_smsstops[$scheduled['id_user']]) && $users_settings[$scheduled['id_user']]['smsstop'])
{
$users_smsstops[$scheduled['id_user']] = [];
$smsstops = $internal_smsstop->gets_for_user($scheduled['id_user']);
foreach ($smsstops as $smsstop)
{
$users_smsstops[$scheduled['id_user']][] = $smsstop['number'];
}
}
if (!isset($users_phones[$scheduled['id_user']]))
{
$phones = $internal_phone->gets_for_user($scheduled['id_user']);
$mms_phones = $internal_phone->gets_phone_supporting_mms_for_user($scheduled['id_user'], $internal_phone::MMS_SENDING);
$users_phones[$scheduled['id_user']] = $phones ?: [];
$users_mms_phones[$scheduled['id_user']] = $mms_phones ?: [];
}
//Add medias to mms
$scheduled['medias'] = [];
if ($scheduled['mms'])
{
$internal_media = new Media($this->bdd);
$scheduled['medias'] = $internal_media->gets_for_scheduled($scheduled['id']);
}
$phone_to_use = null;
foreach ($users_phones[$scheduled['id_user']] as $phone)
{
if ($phone['id'] !== $scheduled['id_phone'])
{
continue;
}
$phone_to_use = $phone;
}
$messages = [];
//Add messages for numbers
$numbers = $this->get_numbers($scheduled['id']);
foreach ($numbers as $number)
{
if (null === $phone_to_use)
{
if ($scheduled['mms'] && count($users_mms_phones))
{
$rnd_key = array_rand($users_mms_phones[$scheduled['id_user']]);
$random_phone = $users_mms_phones[$scheduled['id_user']][$rnd_key];
}
else
{
$rnd_key = array_rand($users_phones[$scheduled['id_user']]);
$random_phone = $users_phones[$scheduled['id_user']][$rnd_key];
}
}
$message = [
'id_user' => $scheduled['id_user'],
'id_scheduled' => $scheduled['id'],
'id_phone' => $phone_to_use['id'] ?? $random_phone['id'],
'destination' => $number['number'],
'flash' => $scheduled['flash'],
'mms' => $scheduled['mms'],
'medias' => $scheduled['medias'],
];
if ((int) ($users_settings[$scheduled['id_user']]['templating'] ?? false))
{
$number['data'] = json_decode($number['data'] ?? '[]', true);
$metas = ['number' => $number['number']];
$data = ['contact' => $number['data'], 'contact_metas' => $metas];
$render = $internal_templating->render($scheduled['text'], $data);
if (!$render['success'])
{
continue;
}
$message['text'] = $render['result'];
}
else
{
$message['text'] = $scheduled['text'];
}
$messages[] = $message;
}
//Add messages for contacts
$contacts = $this->get_contacts($scheduled['id']);
$groups = $this->get_groups($scheduled['id']);
foreach ($groups as $group)
{
$contacts_to_add = $internal_group->get_contacts($group['id']);
$contacts = array_merge($contacts, $contacts_to_add);
}
$conditional_groups = $this->get_conditional_groups($scheduled['id']);
foreach ($conditional_groups as $conditional_group)
{
$contacts_to_add = $internal_conditional_group->get_contacts_for_condition_and_user($scheduled['id_user'], $conditional_group['condition']);
$contacts = array_merge($contacts, $contacts_to_add);
}
$added_contacts = [];
foreach ($contacts as $contact)
{
if ($added_contacts[$contact['id']] ?? false)
{
continue;
}
$added_contacts[$contact['id']] = true;
if (null === $phone_to_use)
{
if ($scheduled['mms'] && count($users_mms_phones))
{
$rnd_key = array_rand($users_mms_phones[$scheduled['id_user']]);
$random_phone = $users_mms_phones[$scheduled['id_user']][$rnd_key];
}
else
{
$rnd_key = array_rand($users_phones[$scheduled['id_user']]);
$random_phone = $users_phones[$scheduled['id_user']][$rnd_key];
}
}
$message = [
'id_user' => $scheduled['id_user'],
'id_scheduled' => $scheduled['id'],
'id_phone' => $phone_to_use['id'] ?? $random_phone['id'],
'destination' => $contact['number'],
'flash' => $scheduled['flash'],
'mms' => $scheduled['mms'],
'medias' => $scheduled['medias'],
];
if ((int) ($users_settings[$scheduled['id_user']]['templating'] ?? false))
{
$contact['data'] = json_decode($contact['data'], true);
//Add metas of contact by adding contact without data
$metas = $contact;
unset($metas['data'], $metas['id_user']);
$data = ['contact' => $contact['data'], 'contact_metas' => $metas];
$render = $internal_templating->render($scheduled['text'], $data);
if (!$render['success'])
{
continue;
}
$message['text'] = $render['result'];
}
else
{
$message['text'] = $scheduled['text'];
}
$messages[] = $message;
}
foreach ($messages as $message)
{
//Remove empty messages
if ('' === trim($message['text']) && !$message['medias'])
{
continue;
}
//Remove messages to smsstops numbers
if (($users_smsstops[$scheduled['id_user']] ?? false) && in_array($message['destination'], $users_smsstops[$scheduled['id_user']]))
{
continue;
}
$smss_to_send_per_scheduled[$scheduled['id']][] = $message;
}
}
return $smss_to_send_per_scheduled;
}
/** /**
* Parse a CSV file of numbers, potentially associated with datas. * Parse a CSV file of numbers, potentially associated with datas.
* *
@ -627,4 +397,292 @@ use Monolog\Logger;
return $this->model; return $this->model;
} }
/**
* Get all messages to send and the number to use to send theme.
*
* @return array : List of smss to send at this time per scheduled id ['1' => [['id_scheduled', 'text', 'id_phone', 'destination', 'flash', 'mms', 'medias'], ...], ...]
*/
public function get_smss_to_send()
{
$sms_per_scheduled = [];
$internal_templating = new \controllers\internals\Templating();
$internal_setting = new \controllers\internals\Setting($this->bdd);
$internal_group = new \controllers\internals\Group($this->bdd);
$internal_conditional_group = new \controllers\internals\ConditionalGroup($this->bdd);
$internal_phone = new \controllers\internals\Phone($this->bdd);
$internal_smsstop = new \controllers\internals\SmsStop($this->bdd);
$internal_sended = new \controllers\internals\Sended($this->bdd);
$users_smsstops = [];
$users_settings = [];
$users_phones = [];
$users_mms_phones = [];
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:s');
$scheduleds = $this->get_model()->gets_before_date($now);
foreach ($scheduleds as $scheduled)
{
$id_scheduled = $scheduled['id'];
$id_user = $scheduled['id_user'];
$sms_per_scheduled[$id_scheduled] = [];
// Forge cache of data about users, sms stops, phones, etc.
if (!isset($users_settings[$id_user]))
{
$users_settings[$id_user] = [];
$settings = $internal_setting->gets_for_user($id_user);
foreach ($settings as $name => $value)
{
$users_settings[$id_user][$name] = $value;
}
}
if (!isset($users_smsstops[$id_user]) && $users_settings[$id_user]['smsstop'])
{
$users_smsstops[$id_user] = [];
$smsstops = $internal_smsstop->gets_for_user($id_user);
foreach ($smsstops as $smsstop)
{
$users_smsstops[$id_user][] = $smsstop['number'];
}
}
if (!isset($users_phones[$id_user]))
{
$users_phones[$id_user] = [];
$users_mms_phones[$id_user] = [];
$phones = $internal_phone->gets_for_user($id_user);
foreach ($phones as &$phone)
{
$limits = $internal_phone->get_limits($phone['id']);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $internal_sended->count_since_for_phone_and_user($id_user, $phone['id'], $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
$phone['remaining_volume'] = $remaining_volume;
$users_phones[$id_user][$phone['id']] = $phone;
}
$mms_phones = $internal_phone->gets_phone_supporting_mms_for_user($id_user, $internal_phone::MMS_SENDING);
foreach ($mms_phones as &$mms_phone)
{
$limits = $internal_phone->get_limits($mms_phone['id']);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $internal_sended->count_since_for_phone_and_user($id_user, $mms_phone['id'], $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
$mms_phone['remaining_volume'] = $remaining_volume;
$users_mms_phones[$id_user][$mms_phone['id']] = $mms_phone;
}
}
//Add medias to mms
$scheduled['medias'] = [];
if ($scheduled['mms'])
{
$internal_media = new Media($this->bdd);
$scheduled['medias'] = $internal_media->gets_for_scheduled($id_scheduled);
}
$phone_to_use = null;
if ($scheduled['id_phone'])
{
$phone_to_use = $users_phones[$id_user][$scheduled['id_phone']] ?? null;
}
// We turn all contacts, groups and conditional groups into just contacts
$contacts = $this->get_contacts($id_scheduled);
$groups = $this->get_groups($id_scheduled);
foreach ($groups as $group)
{
$contacts_to_add = $internal_group->get_contacts($group['id']);
$contacts = array_merge($contacts, $contacts_to_add);
}
$conditional_groups = $this->get_conditional_groups($id_scheduled);
foreach ($conditional_groups as $conditional_group)
{
$contacts_to_add = $internal_conditional_group->get_contacts_for_condition_and_user($id_user, $conditional_group['condition']);
$contacts = array_merge($contacts, $contacts_to_add);
}
// We turn all numbers and contacts into simple targets with number, data and meta so we can forge all messages from onlye one data source
$targets = [];
$numbers = $this->get_numbers($id_scheduled);
foreach ($numbers as $number)
{
$metas = ['number' => $number['number']];
$targets[] = [
'number' => $number['number'],
'data' => $number['data'],
'metas' => $metas,
];
}
foreach ($contacts as $contact)
{
$metas = $contact;
unset($metas['data'], $metas['id_user']);
$targets[] = [
'number' => $contact['number'],
'data' => $contact['data'],
'metas' => $metas,
];
}
// Pass on all targets to deduplicate destinations, remove number in sms stops, etc.
$used_destinations = [];
foreach ($targets as $key => $target)
{
if (in_array($target['number'], $used_destinations))
{
unset($targets[$key]);
continue;
}
//Remove messages to smsstops numbers
if (($users_smsstops[$id_user] ?? false) && in_array($target['number'], $users_smsstops[$id_user]))
{
continue;
}
$used_destinations[] = $target['number'];
}
// Finally, we forge all messages and select phone to use
foreach ($targets as $target)
{
// Forge message if templating enable
$text = $scheduled['text'];
if ((int) ($users_settings[$id_user]['templating'] ?? false)) // Cast to int because it is more reliable than bool on strings
{
$target['data'] = json_decode($target['data'], true);
$data = ['contact' => $target['data'], 'contact_metas' => $target['metas']];
$render = $internal_templating->render($scheduled['text'], $data);
if (!$render['success'])
{
continue;
}
$text = $render['result'];
}
// Ignore empty messages
if ('' === trim($text) && !$scheduled['medias'])
{
continue;
}
// If we must force GSM 7 alphabet
if ((int) ($users_settings[$id_user]['force_gsm_alphabet'] ?? false))
{
$text = Tool::convert_to_gsm0338($text);
}
/*
Choose phone if no phone defined for message
Phones are choosen using type, priority and remaining volume :
1 - If sms is a mms, try to use mms phone if any available. If mms phone available use mms phone, else use default.
2 - In group of phones, keep only phones with remaining volume. If no phones with remaining volume, use all phones instead.
3 - Groupe keeped phones by priority get group with biggest priority.
4 - Get a random phone in this group.
5 - If their is no phone matching, keep phone at null so sender will directly mark it as failed
*/
$random_phone = null;
if (null === $phone_to_use)
{
$phones_subset = $users_phones[$id_user];
if ($scheduled['mms'])
{
$phones_subset = $users_mms_phones[$id_user] ?: $phones_subset;
}
$remaining_volume_phones = array_filter($phones_subset, function ($phone) {
return $phone['remaining_volume'] > 0;
});
$phones_subset = $remaining_volume_phones ?: $phones_subset;
$max_priority_phones = [];
$max_priority = PHP_INT_MIN;
foreach ($phones_subset as $phone)
{
if ($phone['priority'] < $max_priority)
{
continue;
}
elseif ($phone['priority'] == $max_priority)
{
$max_priority_phones[] = $phone;
}
elseif ($phone['priority'] > $max_priority)
{
$max_priority_phones = [$phone];
$max_priority = $phone['priority'];
}
}
$phones_subset = $max_priority_phones;
if ($phones_subset)
{
$random_phone = $phones_subset[array_rand($phones_subset)];
}
}
// This should only happen if the user try to send a message without any phone in his account, then we simply ignore.
if (!$random_phone && !$phone_to_use)
{
continue;
}
$id_phone = $phone_to_use['id'] ?? $random_phone['id'];
$sms_per_scheduled[$id_scheduled][] = [
'id_user' => $id_user,
'id_scheduled' => $id_scheduled,
'id_phone' => $id_phone,
'destination' => $target['number'],
'flash' => $scheduled['flash'],
'mms' => $scheduled['mms'],
'medias' => $scheduled['medias'],
'text' => $text,
];
// Consume one sms from remaining volume of phone, dont forget to do the same for the entry in mms phones
$users_phones[$id_user][$id_phone]['remaining_volume'] --;
if ($users_mms_phones[$id_user][$id_phone] ?? false)
{
$users_mms_phones[$id_user][$id_phone] --;
}
}
}
return $sms_per_scheduled;
}
} }

View File

@ -179,6 +179,20 @@ namespace controllers\internals;
return $this->get_model()->get_by_uid_and_adapter_for_user($id_user, $uid, $adapter); return $this->get_model()->get_by_uid_and_adapter_for_user($id_user, $uid, $adapter);
} }
/**
* Get number of sended SMS since a date for a phone
*
* @param int $id_user : User id
* @param int $id_phone : Phone id we want the number of sended message for
* @param \DateTime $since : Date since which we want sended number
*
* @return int
*/
public function count_since_for_phone_and_user(int $id_user, int $id_phone, \DateTime $since): int
{
return $this->get_model()->count_since_for_phone_and_user($id_user, $id_phone, $since);
}
/** /**
* Get number of sended SMS for every date since a date for a specific user. * Get number of sended SMS for every date since a date for a specific user.
* *
@ -239,17 +253,6 @@ namespace controllers\internals;
'error_message' => null, 'error_message' => null,
]; ];
//If we reached our max quota, do not send the message
$internal_quota = new Quota($this->bdd);
$nb_credits = $internal_quota::compute_credits_for_message($text); //Calculate how much credit the message require
if (!$internal_quota->has_enough_credit($id_user, $nb_credits))
{
$return['error'] = false;
$return['error_message'] = 'Not enough credit to send message.';
return $return;
}
$at = (new \DateTime())->format('Y-m-d H:i:s'); $at = (new \DateTime())->format('Y-m-d H:i:s');
$media_uris = []; $media_uris = [];
foreach ($medias as $media) foreach ($medias as $media)
@ -272,38 +275,56 @@ namespace controllers\internals;
$text .= "\n" . join(' - ', $media_urls); $text .= "\n" . join(' - ', $media_urls);
} }
//If we reached our max quota, do not send the message
$internal_quota = new Quota($this->bdd);
$nb_credits = $internal_quota::compute_credits_for_message($text); //Calculate how much credit the message require
if (!$internal_quota->has_enough_credit($id_user, $nb_credits))
{
$return['error'] = true;
$return['error_message'] = 'Not enough credit to send message.';
}
//If we reached limit for this phone, do not send the message
$internal_phone = new Phone($this->bdd);
$internal_sended = new Sended($this->bdd);
$limits = $internal_phone->get_limits($id_phone);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $internal_sended->count_since_for_phone_and_user($id_user, $id_phone, $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
if ($remaining_volume < 1)
{
$return['error'] = true;
$return['error_message'] = 'Phone send limit have been reached.';
}
$uid = uniqid();
if (!$return['error'])
{
$response = $adapter->send($destination, $text, $flash, $mms, $media_uris); $response = $adapter->send($destination, $text, $flash, $mms, $media_uris);
$uid = $response['uid'] ?? $uid;
if ($response['error']) if ($response['error'])
{ {
$return['error'] = true; $return['error'] = true;
$return['error_message'] = $response['error_message']; $return['error_message'] = $response['error_message'];
$status = \models\Sended::STATUS_FAILED; }
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $originating_scheduled, $status); else // If send with success, consume credit
{
$sended = [ $internal_quota->consume_credit($id_user, $nb_credits);
'id' => $sended_id, }
'at' => $at,
'status' => $status,
'text' => $text,
'destination' => $destination,
'origin' => $id_phone,
'mms' => $mms,
'medias' => $medias,
'originating_scheduled' => $originating_scheduled,
];
$internal_webhook = new Webhook($this->bdd);
$internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $sended);
return $return;
} }
$internal_quota->consume_credit($id_user, $nb_credits); // If we fail to send or not, we will always save message as sended, only the status will change.
$status = $return['error'] ? \models\Sended::STATUS_FAILED : \models\Sended::STATUS_UNKNOWN;
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $uid, $adapter->meta_classname(), $flash, $mms, $medias, $originating_scheduled, $status);
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $originating_scheduled, $status); $webhook_body = [
$sended = [
'id' => $sended_id, 'id' => $sended_id,
'at' => $at, 'at' => $at,
'status' => $status, 'status' => $status,
@ -316,7 +337,7 @@ namespace controllers\internals;
]; ];
$internal_webhook = new Webhook($this->bdd); $internal_webhook = new Webhook($this->bdd);
$internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $sended); $internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $webhook_body);
return $return; return $return;
} }

View File

@ -11,6 +11,8 @@
namespace controllers\internals; namespace controllers\internals;
use BenMorel\GsmCharsetConverter\Converter;
/** /**
* Some tools frequently used. * Some tools frequently used.
* Not a standard controller as it's not linked to a model in any way. * Not a standard controller as it's not linked to a model in any way.
@ -165,6 +167,24 @@ namespace controllers\internals;
return $objectDate && $objectDate->format($format) === $date; return $objectDate && $objectDate->format($format) === $date;
} }
/**
* Check if a relative format date (see https://www.php.net/manual/en/datetime.formats.relative.php) is valid.
*
* @param string $date : Relative date
*
* @return bool : True if valid, false else
*/
public static function validate_relative_date($date)
{
try {
$d = new \DateTime($date);
} catch (\Throwable $th) {
return false;
}
return true;
}
/** /**
* Check if a sting represent a valid PHP period for creating an interval. * Check if a sting represent a valid PHP period for creating an interval.
* *
@ -394,4 +414,17 @@ namespace controllers\internals;
return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}"; return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
} }
/**
* Transform an UTF-8 string into a valid GSM 7 string (GSM 03.38), remplacing invalid chars with best equivalent whenever possible
*
* @param string $text : Input text to convert into gsm string
* @return string : An UTF-8 string with GSM alphabet only
*/
public static function convert_to_gsm0338(string $text): string
{
$converter = new Converter();
return $converter->cleanUpUtf8String($text, true, '?');
}
} }

View File

@ -137,10 +137,10 @@ class Webhook extends StandardController
$error_code = null; $error_code = null;
$queue = msg_get_queue(QUEUE_ID_WEBHOOK); $queue = msg_get_queue(QUEUE_ID_WEBHOOK);
$success = msg_send($queue, QUEUE_TYPE_WEBHOOK, $message, true, true, $error_code); msg_send($queue, QUEUE_TYPE_WEBHOOK, $message, true, true, $error_code);
return (bool) $success;
} }
return true;
} }
/** /**

View File

@ -506,6 +506,8 @@ namespace controllers\publics;
* @param string $_POST['name'] : Phone name * @param string $_POST['name'] : Phone name
* @param string $_POST['adapter'] : Phone adapter * @param string $_POST['adapter'] : Phone adapter
* @param array $_POST['adapter_data'] : Phone adapter data * @param array $_POST['adapter_data'] : Phone adapter data
* @param int $priority : Priority with which to use phone to send SMS. Default 0.
* @param ?array $_POST['limits'] : Array of limits in number of SMS for a period to be applied to this phone.
* *
* @return int : id phone the new phone on success * @return int : id phone the new phone on success
*/ */
@ -516,6 +518,10 @@ namespace controllers\publics;
$name = $_POST['name'] ?? false; $name = $_POST['name'] ?? false;
$adapter = $_POST['adapter'] ?? false; $adapter = $_POST['adapter'] ?? false;
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : []; $adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : [];
$priority = $_POST['priority'] ?? 0;
$priority = max(((int) $priority), 0);
$limits = $_POST['limits'] ?? [];
$limits = is_array($limits) ? $limits : [$limits];
if (!$name) if (!$name)
{ {
@ -535,7 +541,7 @@ namespace controllers\publics;
return $this->json($return); return $this->json($return);
} }
$name_exist = $this->internal_phone->get_by_name($name); $name_exist = $this->internal_phone->get_by_name_and_user($this->user['id'], $name);
if ($name_exist) if ($name_exist)
{ {
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; $return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
@ -545,6 +551,36 @@ namespace controllers\publics;
return $this->json($return); return $this->json($return);
} }
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters(); $adapters = $this->internal_adapter->list_adapters();
$find_adapter = false; $find_adapter = false;
foreach ($adapters as $metas) foreach ($adapters as $metas)
@ -627,7 +663,7 @@ namespace controllers\publics;
return $this->json($return); return $this->json($return);
} }
$phone_id = $this->internal_phone->create($this->user['id'], $name, $adapter, $adapter_data); $phone_id = $this->internal_phone->create($this->user['id'], $name, $adapter, $adapter_data, $priority, $limits);
if (false === $phone_id) if (false === $phone_id)
{ {
$return['error'] = self::ERROR_CODES['CANNOT_CREATE']; $return['error'] = self::ERROR_CODES['CANNOT_CREATE'];
@ -650,6 +686,7 @@ namespace controllers\publics;
* @param string (optionnal) $_POST['name'] : New phone name * @param string (optionnal) $_POST['name'] : New phone name
* @param string (optionnal) $_POST['adapter'] : New phone adapter * @param string (optionnal) $_POST['adapter'] : New phone adapter
* @param array (optionnal) $_POST['adapter_data'] : New phone adapter data * @param array (optionnal) $_POST['adapter_data'] : New phone adapter data
* @param int $priority : Priority with which to use phone to send SMS. Default 0.
* *
* @return int : id phone the new phone on success * @return int : id phone the new phone on success
*/ */
@ -667,10 +704,16 @@ namespace controllers\publics;
return $this->json($return); return $this->json($return);
} }
$limits = $this->internal_phone->get_limits(($phone['id']));
$name = $_POST['name'] ?? $phone['name']; $name = $_POST['name'] ?? $phone['name'];
$priority = $_POST['priority'] ?? $phone['priority'];
$priority = max(((int) $priority), 0);
$adapter = $_POST['adapter'] ?? $phone['adapter']; $adapter = $_POST['adapter'] ?? $phone['adapter'];
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : json_decode($phone['adapter_data']); $adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : json_decode($phone['adapter_data']);
$adapter_data = is_array($adapter_data) ? $adapter_data : [$adapter_data]; $adapter_data = is_array($adapter_data) ? $adapter_data : [$adapter_data];
$limits = $_POST['limits'] ?? $limits;
$limits = is_array($limits) ? $limits : [$limits];
if (!$name && !$adapter && !$adapter_data) if (!$name && !$adapter && !$adapter_data)
@ -683,7 +726,7 @@ namespace controllers\publics;
} }
$phone_with_same_name = $this->internal_phone->get_by_name($name); $phone_with_same_name = $this->internal_phone->get_by_name_and_user($this->user['id'], $name);
if ($phone_with_same_name && $phone_with_same_name['id'] != $phone['id']) if ($phone_with_same_name && $phone_with_same_name['id'] != $phone['id'])
{ {
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; $return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
@ -693,6 +736,36 @@ namespace controllers\publics;
return $this->json($return); return $this->json($return);
} }
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters(); $adapters = $this->internal_adapter->list_adapters();
$find_adapter = false; $find_adapter = false;
foreach ($adapters as $metas) foreach ($adapters as $metas)
@ -775,7 +848,7 @@ namespace controllers\publics;
return $this->json($return); return $this->json($return);
} }
$success = $this->internal_phone->update_for_user($this->user['id'], $phone['id'], $name, $adapter, $adapter_data); $success = $this->internal_phone->update_for_user($this->user['id'], $phone['id'], $name, $adapter, $adapter_data_json, $priority, $limits);
if (!$success) if (!$success)
{ {
$return['error'] = self::ERROR_CODES['CANNOT_UPDATE']; $return['error'] = self::ERROR_CODES['CANNOT_UPDATE'];

View File

@ -185,6 +185,45 @@ namespace controllers\publics;
return $this->redirect(\descartes\Router::url('ConditionalGroup', 'list')); return $this->redirect(\descartes\Router::url('ConditionalGroup', 'list'));
} }
/**
* Return contacts of a group as json array
* @param int $id_group = Group id
*
* @return json
*/
public function preview (int $id_group)
{
$return = [
'success' => false,
'result' => 'Une erreur inconnue est survenue.',
];
$group = $this->internal_conditional_group->get_for_user($_SESSION['user']['id'], $id_group);
if (!$group)
{
$return['result'] = 'Ce groupe n\'existe pas.';
echo json_encode($return);
return false;
}
$contacts = $this->internal_conditional_group->get_contacts_for_condition_and_user($_SESSION['user']['id'], $group['condition']);
if (!$contacts)
{
$return['result'] = 'Aucun contact dans le groupe.';
echo json_encode($return);
return false;
}
$return['success'] = true;
$return['result'] = $contacts;
echo json_encode($return);
return true;
}
/** /**
* Try to get the preview of contacts for a conditionnal group. * Try to get the preview of contacts for a conditionnal group.
* *

View File

@ -372,6 +372,7 @@ namespace controllers\publics;
else else
{ {
$invalid_type = true; $invalid_type = true;
$result = false;
} }
} }

View File

@ -191,6 +191,45 @@ namespace controllers\publics;
return $this->redirect(\descartes\Router::url('Group', 'list')); return $this->redirect(\descartes\Router::url('Group', 'list'));
} }
/**
* Return contacts of a group as json array
* @param int $id_group = Group id
*
* @return json
*/
public function preview (int $id_group)
{
$return = [
'success' => false,
'result' => 'Une erreur inconnue est survenue.',
];
$group = $this->internal_group->get_for_user($_SESSION['user']['id'], $id_group);
if (!$group)
{
$return['result'] = 'Ce groupe n\'existe pas.';
echo json_encode($return);
return false;
}
$contacts = $this->internal_group->get_contacts($id_group);
if (!$contacts)
{
$return['result'] = 'Aucun contact dans le groupe.';
echo json_encode($return);
return false;
}
$return['success'] = true;
$return['result'] = $contacts;
echo json_encode($return);
return true;
}
/** /**
* Cette fonction retourne la liste des groups sous forme JSON. * Cette fonction retourne la liste des groups sous forme JSON.
*/ */

View File

@ -55,6 +55,9 @@ class Phone extends \descartes\Controller
foreach ($phones as &$phone) foreach ($phones as &$phone)
{ {
$limits = $this->internal_phone->get_limits($phone['id']);
$phone['limits'] = $limits;
$adapter = $adapters[$phone['adapter']] ?? false; $adapter = $adapters[$phone['adapter']] ?? false;
if (!$adapter) if (!$adapter)
@ -133,7 +136,9 @@ class Phone extends \descartes\Controller
* @param $csrf : CSRF token * @param $csrf : CSRF token
* @param string $_POST['name'] : Phone name * @param string $_POST['name'] : Phone name
* @param string $_POST['adapter'] : Phone adapter * @param string $_POST['adapter'] : Phone adapter
* @param array $_POST['adapter_data'] : Phone adapter data * @param ?array $_POST['adapter_data'] : Phone adapter data
* @param ?array $_POST['limits'] : Array of limits in number of SMS for a period to be applied to this phone.
* @param int $_POST['priority'] : Priority with which to use phone to send SMS. Default 0.
*/ */
public function create($csrf) public function create($csrf)
{ {
@ -146,8 +151,12 @@ class Phone extends \descartes\Controller
$id_user = $_SESSION['user']['id']; $id_user = $_SESSION['user']['id'];
$name = $_POST['name'] ?? false; $name = $_POST['name'] ?? false;
$priority = $_POST['priority'] ?? 0;
$priority = max(((int) $priority), 0);
$adapter = $_POST['adapter'] ?? false; $adapter = $_POST['adapter'] ?? false;
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : []; $adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : [];
$limits = $_POST['limits'] ?? [];
$limits = is_array($limits) ? $limits : [$limits];
if (!$name || !$adapter) if (!$name || !$adapter)
{ {
@ -156,7 +165,7 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'add')); return $this->redirect(\descartes\Router::url('Phone', 'add'));
} }
$name_exist = $this->internal_phone->get_by_name($name); $name_exist = $this->internal_phone->get_by_name_and_user($id_user, $name);
if ($name_exist) if ($name_exist)
{ {
\FlashMessage\FlashMessage::push('danger', 'Ce nom est déjà utilisé pour un autre téléphone.'); \FlashMessage\FlashMessage::push('danger', 'Ce nom est déjà utilisé pour un autre téléphone.');
@ -164,6 +173,36 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'add')); return $this->redirect(\descartes\Router::url('Phone', 'add'));
} }
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters(); $adapters = $this->internal_adapter->list_adapters();
$find_adapter = false; $find_adapter = false;
foreach ($adapters as $metas) foreach ($adapters as $metas)
@ -245,7 +284,7 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'add')); return $this->redirect(\descartes\Router::url('Phone', 'add'));
} }
$success = $this->internal_phone->create($id_user, $name, $adapter, $adapter_data); $success = $this->internal_phone->create($id_user, $name, $adapter, $adapter_data, $priority, $limits);
if (!$success) if (!$success)
{ {
\FlashMessage\FlashMessage::push('danger', 'Impossible de créer ce téléphone.'); \FlashMessage\FlashMessage::push('danger', 'Impossible de créer ce téléphone.');
@ -257,4 +296,207 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'list')); return $this->redirect(\descartes\Router::url('Phone', 'list'));
} }
/**
* Return the edit page for phones
*
* @param int... $ids : Phones ids
*/
public function edit()
{
$ids = $_GET['ids'] ?? [];
$id_user = $_SESSION['user']['id'];
$phones = $this->internal_phone->gets_in_for_user($id_user, $ids);
if (!$phones)
{
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
$adapters = $this->internal_adapter->list_adapters();
foreach ($phones as &$phone)
{
$limits = $this->internal_phone->get_limits($phone['id']);
$phone['limits'] = $limits;
}
$this->render('phone/edit', [
'phones' => $phones,
'adapters' => $adapters,
]);
}
/**
* Update multiple phones.
*
* @param $csrf : CSRF token
* @param string $_POST['phones']['id']['name'] : Phone name
* @param string $_POST['phones']['id']['adapter'] : Phone adapter
* @param ?array $_POST['phones']['id']['adapter_data'] : Phone adapter data
* @param ?array $_POST['phones']['id']['limits'] : Array of limits in number of SMS for a period to be applied to this phone.
* @param int $_POST['phones']['id']['priority'] : Priority with which to use phone to send SMS. Default 0.
*/
public function update($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('Phone', 'add'));
}
if (!$_POST['phones'])
{
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
$id_user = $_SESSION['user']['id'];
$nb_update = 0;
foreach ($_POST['phones'] as $id_phone => $phone)
{
$name = $phone['name'] ?? false;
$priority = $phone['priority'] ?? 0;
$priority = max(((int) $priority), 0);
$adapter = $phone['adapter'] ?? false;
$adapter_data = !empty($phone['adapter_data']) ? $phone['adapter_data'] : [];
$limits = $phone['limits'] ?? [];
$limits = is_array($limits) ? $limits : [$limits];
if (!$name || !$adapter)
{
continue;
}
$phone_with_same_name = $this->internal_phone->get_by_name_and_user($id_user, $name);
if ($phone_with_same_name && $phone_with_same_name['id'] != $id_phone)
{
continue;
}
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters();
$find_adapter = false;
foreach ($adapters as $metas)
{
if ($metas['meta_classname'] === $adapter)
{
$find_adapter = $metas;
break;
}
}
if (!$find_adapter)
{
continue;
}
if ($find_adapter['meta_hidden'])
{
continue;
}
//If missing required data fields, error
foreach ($find_adapter['meta_data_fields'] as $field)
{
if (false === $field['required'])
{
continue;
}
if (!empty($adapter_data[$field['name']]))
{
continue;
}
continue 2;
}
//If field phone number is invalid
foreach ($find_adapter['meta_data_fields'] as $field)
{
if ('phone_number' !== ($field['type'] ?? false))
{
continue;
}
if (!empty($adapter_data[$field['name']]))
{
$adapter_data[$field['name']] = \controllers\internals\Tool::parse_phone($adapter_data[$field['name']]);
if ($adapter_data[$field['name']])
{
continue;
}
}
continue 2;
}
$adapter_data = json_encode($adapter_data);
//Check adapter is working correctly with thoses names and data
$adapter_classname = $find_adapter['meta_classname'];
$adapter_instance = new $adapter_classname($adapter_data);
$adapter_working = $adapter_instance->test();
if (!$adapter_working)
{
continue;
}
$success = $this->internal_phone->update_for_user($id_user, $id_phone, $name, $adapter, $adapter_data, $priority, $limits);
if (!$success)
{
continue;
}
$nb_update ++;
}
if ($nb_update !== \count($_POST['phones']))
{
\FlashMessage\FlashMessage::push('danger', 'Certains téléphones n\'ont pas pu êtres mis à jour.');
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
\FlashMessage\FlashMessage::push('success', 'Tous les téléphones ont été modifiés avec succès.');
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
} }

View File

@ -83,6 +83,12 @@ namespace controllers\publics;
$result = $this->internal_templating->render($template, $data); $result = $this->internal_templating->render($template, $data);
$return = $result; $return = $result;
// If we must force GSM 7 alphabet
if ((int) ($_SESSION['user']['settings']['force_gsm_alphabet'] ?? false))
{
$return['result'] = \controllers\internals\Tool::convert_to_gsm0338($return['result']);
}
if (!trim($result['result'])) if (!trim($result['result']))
{ {
$return['result'] = 'Message vide, il ne sera pas envoyé.'; $return['result'] = 'Message vide, il ne sera pas envoyé.';

View File

@ -22,6 +22,7 @@ class Sender extends AbstractDaemon
private $internal_phone; private $internal_phone;
private $internal_scheduled; private $internal_scheduled;
private $internal_received; private $internal_received;
private $internal_sended;
private $bdd; private $bdd;
private $msg_queue; private $msg_queue;
@ -45,6 +46,7 @@ class Sender extends AbstractDaemon
{ {
//Create the internal controllers //Create the internal controllers
$this->internal_scheduled = new \controllers\internals\Scheduled($this->bdd); $this->internal_scheduled = new \controllers\internals\Scheduled($this->bdd);
$this->internal_sended = new \controllers\internals\Sended($this->bdd);
//Get smss and transmit order to send to appropriate phone daemon //Get smss and transmit order to send to appropriate phone daemon
$smss_per_scheduled = $this->internal_scheduled->get_smss_to_send(); $smss_per_scheduled = $this->internal_scheduled->get_smss_to_send();
@ -63,7 +65,7 @@ class Sender extends AbstractDaemon
foreach ($smss_per_scheduled as $id_scheduled => $smss) foreach ($smss_per_scheduled as $id_scheduled => $smss)
{ {
//If queue not already exists //If queue not already exists
if (!msg_queue_exists(QUEUE_ID_PHONE) || !isset($this->queue)) if (!msg_queue_exists(QUEUE_ID_PHONE) || !isset($this->msg_queue))
{ {
$this->msg_queue = msg_get_queue(QUEUE_ID_PHONE); $this->msg_queue = msg_get_queue(QUEUE_ID_PHONE);
} }

View File

@ -0,0 +1,43 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddPhoneLimits extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* addCustomColumn
* renameColumn
* addIndex
* addForeignKey
*
* Any other destructive changes will result in an error when trying to
* rollback the migration.
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('phone_limit');
$table->addColumn('id_phone', 'integer', ['null' => false])
->addColumn('volume', 'integer', ['null' => false])
->addColumn('startpoint', 'string', ['null' => false, 'limit' => 254]) # A relative time to use as startpoint for counting volume. See https://www.php.net/manual/en/datetime.formats.relative.php
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
->addColumn('updated_at', 'timestamp', ['null' => true, 'update' => 'CURRENT_TIMESTAMP'])
->addForeignKey('id_phone', 'phone', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->create();
}
}

View File

@ -0,0 +1,43 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddPhonePriority extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* addCustomColumn
* renameColumn
* addIndex
* addForeignKey
*
* Any other destructive changes will result in an error when trying to
* rollback the migration.
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('phone');
$table->addColumn('priority', 'integer', [
'null' => false,
'default' => 0,
'comment' => 'Priority with which the phone will be used. The higher the more prioritary.',
'after' => 'name'
])
->update();
}
}

View File

@ -82,6 +82,7 @@
'alert_quota_limit_reached' => 1, 'alert_quota_limit_reached' => 1,
'alert_quota_limit_close' => 0.9, 'alert_quota_limit_close' => 0.9,
'hide_menus' => '', 'hide_menus' => '',
'force_gsm_alphabet' => 0,
], ],
]; ];

View File

@ -84,32 +84,6 @@ namespace models;
return $this->_select('event', ['id_user' => $id_user], 'at', true, $nb_entry); return $this->_select('event', ['id_user' => $id_user], 'at', true, $nb_entry);
} }
/**
* Gets events for a type, since a date and eventually until a date (both included).
*
* @param int $id_user : User id
* @param string $type : Event type we want
* @param \DateTime $since : Date to get events since
* @param ?\DateTime $until (optional) : Date until wich we want events, if not specified no limit
*
* @return array
*/
public function get_events_by_type_and_date_for_user(int $id_user, string $type, \DateTime $since, ?\DateTime $until = null)
{
$where = [
'id_user' => $id_user,
'type' => $type,
'>=at' => $since->format('Y-m-d H:i:s'),
];
if (null !== $until)
{
$where['<=at'] = $until->format('Y-m-d H:i:s');
}
return $this->_select('event', $where, 'at');
}
/** /**
* Return table name. * Return table name.
*/ */

View File

@ -15,7 +15,7 @@ namespace models;
{ {
/** /**
* Return all hones that belongs to active users * Return all phones that belongs to active users
* *
* @return array * @return array
*/ */
@ -63,6 +63,48 @@ namespace models;
return $this->_select_one('phone', ['name' => $name]); return $this->_select_one('phone', ['name' => $name]);
} }
/**
* Return a list of phone limits
*
* @param int $id_phone : Phone id
*
* @return array
*/
public function get_limits(int $id_phone)
{
return $this->_select('phone_limit', ['id_phone' => $id_phone]);
}
/**
* Add a limit for a phone.
*
* @param int $id_phone : Phone id
* @param int $volume : Limit in volume of SMS
* @param string $startpoint : A relative time to use as startpoint for counting volume. See https://www.php.net/manual/en/datetime.formats.relative.php
*
* @return mixed (bool|int) : False on error, new row id else
*/
public function insert_phone_limit(int $id_phone, int $volume, string $startpoint)
{
$success = $this->_insert('phone_limit', ['id_phone' => $id_phone, 'volume' => $volume, 'startpoint' => $startpoint]);
return $success ? $this->_last_id() : false;
}
/**
* Delete limits for a phone
*
* @param array $id_phone : Phone id
*
* @return array
*/
public function delete_phone_limits(int $id_phone)
{
return $this->_delete('phone_limit', ['id_phone' => $id_phone]);
}
/** /**
* Return table name. * Return table name.
*/ */

View File

@ -178,6 +178,20 @@ namespace models;
return $this->_select_one('sended', ['id_user' => $id_user, 'uid' => $uid, 'adapter' => $adapter]); return $this->_select_one('sended', ['id_user' => $id_user, 'uid' => $uid, 'adapter' => $adapter]);
} }
/**
* Get number of sended SMS since a date for a phone
*
* @param int $id_user : User id
* @param int $id_phone : Phone id we want the number of sended message for
* @param \DateTime $since : Date since which we want sended number
*
* @return int
*/
public function count_since_for_phone_and_user(int $id_user, int $id_phone, \DateTime $since) : int
{
return $this->_count('sended', ['id_user' => $id_user, 'id_phone' => $id_phone, '>=at' => $since->format('c')]);
}
/** /**
* Get number of sended SMS for every date since a date for a specific user. * Get number of sended SMS for every date since a date for a specific user.
* *

View File

@ -76,6 +76,7 @@
'delete' => '/group/delete/{csrf}/', 'delete' => '/group/delete/{csrf}/',
'edit' => '/group/edit/', 'edit' => '/group/edit/',
'update' => '/group/update/{csrf}/', 'update' => '/group/update/{csrf}/',
'preview' => '/group/preview/{id_group}/',
'json_list' => '/groups.json/', 'json_list' => '/groups.json/',
], ],
@ -88,6 +89,7 @@
'edit' => '/conditional_group/edit/', 'edit' => '/conditional_group/edit/',
'update' => '/conditional_group/update/{csrf}/', 'update' => '/conditional_group/update/{csrf}/',
'contacts_preview' => '/conditional_group/preview/', 'contacts_preview' => '/conditional_group/preview/',
'preview' => '/conditional_group/preview/{id_group}/',
'json_list' => '/conditional_groups.json/', 'json_list' => '/conditional_groups.json/',
], ],
@ -160,6 +162,8 @@
'add' => '/phone/add/', 'add' => '/phone/add/',
'create' => '/phone/create/{csrf}/', 'create' => '/phone/create/{csrf}/',
'delete' => '/phone/delete/{csrf}/', 'delete' => '/phone/delete/{csrf}/',
'edit' => '/phone/edit/',
'update' => '/phone/update/{csrf}/',
], ],
'Call' => [ 'Call' => [

View File

@ -43,6 +43,7 @@
<th>Condition</th> <th>Condition</th>
<th>Date de création</th> <th>Date de création</th>
<th>Dernière modification</th> <th>Dernière modification</th>
<th>Preview</th>
<th class="checkcolumn"><input type="checkbox" id="check-all"/></th> <th class="checkcolumn"><input type="checkbox" id="check-all"/></th>
</tr> </tr>
</thead> </thead>
@ -69,9 +70,56 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" tabindex="-1" id="preview-text-modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Prévisualisation des contacts</h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script> <script>
jQuery(document).ready(function () jQuery(document).ready(function ()
{ {
jQuery('body').on('click', '.preview-button', function (e)
{
e.preventDefault();
var group_id = jQuery(this).attr('data-id-group');
jQuery.ajax({
type: "GET",
url: HTTP_PWD + '/conditional_group/preview/' + group_id + '/',
success: function (data) {
if (!data.success) {
jQuery('#preview-text-modal').find('.modal-body').text(data.result);
} else {
html = '';
for (contact of data.result)
{
html += '<div class="preview-contact well">';
html += ' <div class="preview-contact-name">' + jQuery.fn.dataTable.render.text().display(contact.name) + '</div>'
html += ' <div class="preview-contact-number">' + jQuery.fn.dataTable.render.text().display(contact.number) + '</div>'
html += ' <code class="preview-contact-data">' + jQuery.fn.dataTable.render.text().display(contact.data) + '</code>'
html += '</div>';
console.log(contact);
}
jQuery('#preview-text-modal').find('.modal-body').html(html);
}
jQuery('#preview-text-modal').modal({'keyboard': true});
},
dataType: 'json'
});
});
jQuery('.datatable').DataTable({ jQuery('.datatable').DataTable({
"pageLength": 25, "pageLength": 25,
"lengthMenu": [[25, 50, 100, 1000, 10000, -1], [25, 50, 100, 1000, 10000, "All"]], "lengthMenu": [[25, 50, 100, 1000, 10000, -1], [25, 50, 100, 1000, 10000, "All"]],
@ -97,6 +145,12 @@ jQuery(document).ready(function ()
}, },
{data: 'created_at'}, {data: 'created_at'},
{data: 'updated_at'}, {data: 'updated_at'},
{
data: '_',
render: function (data, type, row, meta) {
return '<a class="btn btn-info preview-button" href="#" data-id-group="' + jQuery.fn.dataTable.render.text().display(row.id) + '"><span class="fa fa-eye"></span></a>';
},
},
{ {
data: 'id', data: 'id',
render: function (data, type, row, meta) { render: function (data, type, row, meta) {
@ -106,7 +160,6 @@ jQuery(document).ready(function ()
], ],
"deferRender": true "deferRender": true
}); });
}); });
</script> </script>
<?php <?php

View File

@ -43,6 +43,7 @@
<th>Nombre de contacts</th> <th>Nombre de contacts</th>
<th>Date de création</th> <th>Date de création</th>
<th>Dernière modification</th> <th>Dernière modification</th>
<th>Preview</th>
<th class="checkcolumn"><input type="checkbox" id="check-all"/></th> <th class="checkcolumn"><input type="checkbox" id="check-all"/></th>
</tr> </tr>
</thead> </thead>
@ -69,6 +70,21 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" tabindex="-1" id="preview-text-modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Prévisualisation des contacts</h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script> <script>
jQuery(document).ready(function () jQuery(document).ready(function ()
{ {
@ -92,6 +108,12 @@ jQuery(document).ready(function ()
{data: 'nb_contact', render: jQuery.fn.dataTable.render.text()}, {data: 'nb_contact', render: jQuery.fn.dataTable.render.text()},
{data: 'created_at'}, {data: 'created_at'},
{data: 'updated_at'}, {data: 'updated_at'},
{
data: '_',
render: function (data, type, row, meta) {
return '<a class="btn btn-info preview-button" href="#" data-id-group="' + jQuery.fn.dataTable.render.text().display(row.id) + '"><span class="fa fa-eye"></span></a>';
},
},
{ {
data: 'id', data: 'id',
render: function (data, type, row, meta) { render: function (data, type, row, meta) {
@ -102,6 +124,38 @@ jQuery(document).ready(function ()
"deferRender": true "deferRender": true
}); });
jQuery('body').on('click', '.preview-button', function (e)
{
e.preventDefault();
var group_id = jQuery(this).attr('data-id-group');
jQuery.ajax({
type: "GET",
url: HTTP_PWD + '/group/preview/' + group_id + '/',
success: function (data) {
if (!data.success) {
jQuery('#preview-text-modal').find('.modal-body').text(data.result);
} else {
html = '';
for (contact of data.result)
{
html += '<div class="preview-contact well">';
html += ' <div class="preview-contact-name">' + jQuery.fn.dataTable.render.text().display(contact.name) + '</div>'
html += ' <div class="preview-contact-number">' + jQuery.fn.dataTable.render.text().display(contact.number) + '</div>'
html += ' <code class="preview-contact-data">' + jQuery.fn.dataTable.render.text().display(contact.data) + '</code>'
html += '</div>';
console.log(contact);
}
jQuery('#preview-text-modal').find('.modal-body').html(html);
}
jQuery('#preview-text-modal').modal({'keyboard': true});
},
dataType: 'json'
});
});
}); });
</script> </script>
<?php <?php

View File

@ -48,7 +48,16 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Type de téléphone : </label> <label>Priorité d'utilisation du téléphone</label>
<p class="italic small help">
Lors de l'envoi de SMS sans téléphone spécifié, les téléphones avec la plus haute priorité seront utilisés en premier.
</p>
<div class="form-group">
<input required="required" name="priority" class="form-control" type="number" min="0" placeholder="Priorité d'utilisation" value="<?php $this->s($_SESSION['previous_http_post']['priority'] ?? 0) ?>">
</div>
</div>
<div class="form-group">
<label>Type de téléphone</label>
<p class="italic small help" id="description-adapter-general"> <p class="italic small help" id="description-adapter-general">
Le type de téléphone utilisé par RaspiSMS pour envoyer ou recevoir les SMS. Pour plus d'information, consultez <a href="https://documentation.raspisms.fr/users/adapters/overview.html" target="_blank">la documentation de RaspiSMS</a> concernant les différents types de téléphones. Le type de téléphone utilisé par RaspiSMS pour envoyer ou recevoir les SMS. Pour plus d'information, consultez <a href="https://documentation.raspisms.fr/users/adapters/overview.html" target="_blank">la documentation de RaspiSMS</a> concernant les différents types de téléphones.
</p> </p>
@ -67,7 +76,7 @@
<?php } ?> <?php } ?>
</select> </select>
</div> </div>
<div id="adapter-data-container" class="form-group"> <div id="adapter-data-container" class="form-group well">
<div id="adapter-data-description-container"> <div id="adapter-data-description-container">
<h4>Description du téléphone</h4> <h4>Description du téléphone</h4>
<div id="adapter-data-description"></div> <div id="adapter-data-description"></div>
@ -77,6 +86,15 @@
<h4>Réglages du téléphone</h4> <h4>Réglages du téléphone</h4>
<div id="adapter-data-fields"></div> <div id="adapter-data-fields"></div>
</div> </div>
</div>
<div class="form-group">
<label>Limites des volumes d'envoi du téléphone</label>
<p class="italic small help">
Défini le nombre maximum de SMS qui pourront être envoyés avec ce téléphone sur des périodes de temps données.
</p>
<div class="form-group phone-limits-container container-fluid">
<div class="text-center"><div class="add-phone-limit-button fa fa-plus-circle"></div></div>
</div>
</div> </div>
<a class="btn btn-danger" href="<?php echo \descartes\Router::url('Phone', 'list'); ?>">Annuler</a> <a class="btn btn-danger" href="<?php echo \descartes\Router::url('Phone', 'list'); ?>">Annuler</a>
<input type="submit" class="btn btn-success" value="Enregistrer le téléphone" /> <input type="submit" class="btn btn-success" value="Enregistrer le téléphone" />
@ -174,6 +192,47 @@
{ {
change_adapter(); change_adapter();
}); });
jQuery('body').on('click', '.phone-limits-group-remove', function (e)
{
e.preventDefault();
jQuery(this).parent('.phone-limits-group').remove();
return false;
});
jQuery('body').on('click', '.add-phone-limit-button', function(e)
{
var random_id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
var newLimit = '' +
'<div class="row phone-limits-group">'+
'<div class="col-xs-4">'+
'<label>Période</label><br/>'+
'<select name="limits[' + random_id + '][startpoint]" class="form-control" required>'+
'<option value="" disabled selected>Période sur laquelle appliquer la limite</option>'+
'<option value="today">Par jour</option>'+
'<option value="-24 hours">24 heures glissantes</option>'+
'<option value="this week midnight">Cette semaine</option>'+
'<option value="-7 days">7 jours glissants</option>'+
'<option value="this week midnight -1 week">Ces deux dernières semaines</option>'+
'<option value="-14 days">14 jours glissants</option>'+
'<option value="this month midnight">Ce mois</option>'+
'<option value="-1 month">1 mois glissant</option>'+
'<option value="-28 days">28 jours glissants</option>'+
'<option value="-30 days">30 jours glissants</option>'+
'<option value="-31 days">31 jours glissants</option>'+
'</select>'+
'</div>'+
'<div class="scheduleds-number-data-container col-xs-8">'+
'<label>Volume</label>'+
'<div class="form-group">'+
'<input name="limits[' + random_id + '][volume]" class="form-control" type="number" min="1" placeholder="Nombre de SMS maximum sur la période.">'+
'</div>'+
'</div>'+
'<a href="#" class="phone-limits-group-remove"><span class="fa fa-times"></span></a>'+
'</div>';
jQuery(this).parent('div').before(newLimit);
});
}); });
</script> </script>
<?php <?php

304
templates/phone/edit.php Normal file
View File

@ -0,0 +1,304 @@
<?php
//Template dashboard
$this->render('incs/head', ['title' => 'Phones - Edit'])
?>
<div id="wrapper">
<?php
$this->render('incs/nav', ['page' => 'phones'])
?>
<div id="page-wrapper">
<div class="container-fluid">
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">
Modification téléphones
</h1>
<ol class="breadcrumb">
<li>
<i class="fa fa-dashboard"></i> <a href="<?php echo \descartes\Router::url('Dashboard', 'show'); ?>">Dashboard</a>
</li>
<li>
<i class="fa fa-phone"></i> <a href="<?php echo \descartes\Router::url('Phone', 'list'); ?>">Téléphones</a>
</li>
<li class="active">
<i class="fa fa-edit"></i> Modifier
</li>
</ol>
</div>
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-phone fa-fw"></i> Modification de téléphones</h3>
</div>
<div class="panel-body">
<form action="<?php echo \descartes\Router::url('Phone', 'update', ['csrf' => $_SESSION['csrf']]);?>" method="POST">
<?php foreach ($phones as $phone) { ?>
<div class="entry-container" data-entry-id="<?php $this->s($phone['id']); ?>">
<div class="form-group">
<label>Nom du téléphone</label>
<p class="italic small help">
Le nom du téléphone qui enverra et recevra les messages.
</p>
<div class="form-group">
<input required="required" name="phones[<?php $this->s($phone['id']); ?>][name]" class="form-control" placeholder="Nom du téléphone" value="<?php $this->s($phone['name']); ?>">
</div>
</div>
<div class="form-group">
<label>Priorité d'utilisation du téléphone</label>
<p class="italic small help">
Lors de l'envoi de SMS sans téléphone spécifié, les téléphones avec la plus haute priorité seront utilisés en premier.
</p>
<div class="form-group">
<input required="required" name="phones[<?php $this->s($phone['id']); ?>][priority]" class="form-control" type="number" min="0" placeholder="Priorité d'utilisation" value="<?php $this->s($phone['priority']) ?>">
</div>
</div>
<div class="form-group">
<label>Type de téléphone</label>
<p class="italic small help description-adapter-general">
Le type de téléphone utilisé par RaspiSMS pour envoyer ou recevoir les SMS. Pour plus d'information, consultez <a href="https://documentation.raspisms.fr/users/adapters/overview.html" target="_blank">la documentation de RaspiSMS</a> concernant les différents types de téléphones.
</p>
<select name="phones[<?php $this->s($phone['id']); ?>][adapter]" class="form-control adapter-select">
<?php foreach ($adapters as $adapter) { ?>
<?php if ($adapter['meta_hidden'] === false) { ?>
<option
value="<?= $adapter['meta_classname'] ?>"
data-description="<?php $this->s($adapter['meta_description']); ?>"
data-data-fields="<?php $this->s(json_encode($adapter['meta_data_fields'])); ?>"
<?php if ($phone['adapter'] == $adapter['meta_classname']) { ?>
data-phone-adapter-data="<?php $this->s($phone['adapter_data']); ?>"
selected
<?php } ?>
>
<?php $this->s($adapter['meta_name']); ?>
</option>
<?php } ?>
<?php } ?>
</select>
</div>
<div class="form-group well adapter-data-container">
<div class="adapter-data-description-container">
<h4>Description du téléphone</h4>
<div class="adapter-data-description"></div>
</div>
<div class="adapter-data-fields-container">
<h4>Réglages du téléphone</h4>
<div class="adapter-data-fields"></div>
</div>
</div>
<div class="form-group">
<label>Limites des volumes d'envoi du téléphone</label>
<p class="italic small help">
Défini le nombre maximum de SMS qui pourront être envoyés avec ce téléphone sur des périodes de temps données.
</p>
<div class="form-group phone-limits-container container-fluid">
<?php foreach ($phone['limits'] as $limit) { ?>
<div class="row phone-limits-group">
<div class="col-xs-4">
<label>Période</label><br/>
<?php $random_id = uniqid(); ?>
<select name="phones[<?= $phone['id']; ?>][limits][<?= $random_id; ?>][startpoint]" class="form-control" required>
<option value="" disabled selected>Période sur laquelle appliquer la limite</option>
<option <?= $limit['startpoint'] == 'today' ? 'selected' : ''; ?> value="today">Par jour</option>
<option <?= $limit['startpoint'] == '-24 hours' ? 'selected' : ''; ?> value="-24 hours">24 heures glissantes</option>
<option <?= $limit['startpoint'] == 'this week midnight' ? 'selected' : ''; ?> value="this week midnight">Cette semaine</option>
<option <?= $limit['startpoint'] == '-7 days' ? 'selected' : ''; ?> value="-7 days">7 jours glissants</option>
<option <?= $limit['startpoint'] == 'this week midnight -1 week' ? 'selected' : ''; ?> value="this week midnight -1 week">Ces deux dernières semaines</option>
<option <?= $limit['startpoint'] == '-14 days' ? 'selected' : ''; ?> value="-14 days">14 jours glissants</option>
<option <?= $limit['startpoint'] == 'this month midnight' ? 'selected' : ''; ?> value="this month midnight">Ce mois</option>
<option <?= $limit['startpoint'] == '-1 month' ? 'selected' : ''; ?> value="-1 month">1 mois glissant</option>
<option <?= $limit['startpoint'] == '-28 days' ? 'selected' : ''; ?> value="-28 days">28 jours glissants</option>
<option <?= $limit['startpoint'] == '-30 days' ? 'selected' : ''; ?> value="-30 days">30 jours glissants</option>
<option <?= $limit['startpoint'] == '-31 days' ? 'selected' : ''; ?> value="-31 days">31 jours glissants</option>
</select>
</div>
<div class="scheduleds-number-data-container col-xs-8">
<label>Volume</label>
<div class="form-group">
<input name="phones[<?= $phone['id']; ?>][limits][<?= $random_id; ?>][volume]" class="form-control" type="number" min="1" value="<?php $this->s($limit['volume']); ?>" placeholder="Nombre de SMS maximum sur la période.">
</div>
</div>
<a href="#" class="phone-limits-group-remove"><span class="fa fa-times"></span></a>
</div>
<?php } ?>
<div class="text-center"><div class="add-phone-limit-button fa fa-plus-circle"></div></div>
</div>
</div>
</div>
<hr/>
<?php } ?>
<a class="btn btn-danger" href="<?php echo \descartes\Router::url('Phone', 'list'); ?>">Annuler</a>
<input type="submit" class="btn btn-success" value="Enregistrer le téléphone" />
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function change_adapter (target)
{
var phone_id = target.parents('.entry-container').attr('data-entry-id');
var option = target.find('option:selected');
target.parents('.entry-container').find('.adapter-data-description').html(option.attr('data-description'));
target.parents('.entry-container').find('.description-adapter-data').text(option.attr('data-data-help'));
var data_fields = option.attr('data-data-fields');
data_fields = JSON.parse(data_fields);
if (option.attr('data-phone-adapter-data'))
{
var phone_adapter_data = option.attr('data-phone-adapter-data');
phone_adapter_data = JSON.parse(phone_adapter_data);
}
var numbers = [];
var html = '';
jQuery.each(data_fields, function (index, field)
{
if (phone_adapter_data)
{
if (field.name in phone_adapter_data)
{
value = phone_adapter_data[field.name];
}
else
{
value = field.default_value ? field.default_value : null;
}
}
else
{
value = field.default_value ? field.default_value : null;
}
if (field.type == 'phone_number')
{
var random_id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
html += '' +
'<div class="form-group">' +
'<label>' + field.title + '</label>' +
'<p class="italic small help">' + field.description + '</p>' +
'<div class="form-group">' +
'<input name="" class="form-control phone-international-input" type="tel" id="' + random_id + '" ' + (field.required ? 'required' : '') + ' ' + (value ? 'value="' + value + '"' : '') + '>' +
'</div>' +
'</div>';
var number = {
'id': random_id,
'name': field.name,
};
numbers.push(number);
}
else if (field.type == 'boolean')
{
html += '' +
'<div class="form-group">' +
'<label>' + field.title + '</label>' +
'<p class="italic small help">' + field.description + '</p>' +
'<div class="form-group">' +
'<input type="checkbox" name="phones[' + phone_id + '][adapter_data][' + field.name + ']" class="form-control" ' + (field.required ? 'required' : '') + ' ' + (value ? 'value="' + value + '" checked' : 'value="1"') + '><label class="switch" for="adapter_data[' + field.name + ']"></label>' +
'</div>' +
'</div>';
}
else
{
html += '' +
'<div class="form-group">' +
'<label>' + field.title + '</label>' +
'<p class="italic small help">' + field.description + '</p>' +
'<div class="form-group">' +
'<input name="phones[' + phone_id + '][adapter_data][' + field.name + ']" class="form-control" ' + (field.required ? 'required' : '') + ' ' + (value ? 'value="' + value + '"' : '') + '>' +
'</div>' +
'</div>';
}
});
if (html == '')
{
html = 'Pas de réglages.';
}
target.parents('.entry-container').find('.adapter-data-fields').html(html);
for (i = 0; i < numbers.length; i++)
{
var iti_number_input = window.intlTelInput(document.getElementById(numbers[i].id), {
hiddenInput: 'phones[' + phone_id + '][adapter_data][' + numbers[i].name + ']',
defaultCountry: '<?php $this->s($_SESSION['user']['settings']['default_phone_country']); ?>',
preferredCountries: <?php $this->s(json_encode(explode(',', $_SESSION['user']['settings']['preferred_phone_country'])), false, false); ?>,
nationalMode: true,
utilsScript: '<?php echo HTTP_PWD_JS; ?>/intlTelInput/utils.js',
});
}
}
jQuery('document').ready(function($)
{
jQuery('.adapter-select').each(function () {
change_adapter(jQuery(this));
});
jQuery('.adapter-select').on('change', function (e)
{
change_adapter(jQuery(this));
});
jQuery('body').on('click', '.phone-limits-group-remove', function (e)
{
e.preventDefault();
jQuery(this).parent('.phone-limits-group').remove();
return false;
});
jQuery('body').on('click', '.add-phone-limit-button', function(e)
{
var phone_id = jQuery(this).parents('.entry-container').attr('data-entry-id');
var random_id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
var newLimit = '' +
'<div class="row phone-limits-group">'+
'<div class="col-xs-4">'+
'<label>Période</label><br/>'+
'<select name="phones[' + phone_id + '][limits][' + random_id + '][startpoint]" class="form-control adapter-select" required>'+
'<option value="" disabled selected>Période sur laquelle appliquer la limite</option>'+
'<option value="today">Par jour</option>'+
'<option value="-24 hours">24 heures glissantes</option>'+
'<option value="this week midnight">Cette semaine</option>'+
'<option value="-7 days">7 jours glissants</option>'+
'<option value="this week midnight -1 week">Ces deux dernières semaines</option>'+
'<option value="-14 days">14 jours glissants</option>'+
'<option value="this month midnight">Ce mois</option>'+
'<option value="-1 month">1 mois glissant</option>'+
'<option value="-28 days">28 jours glissants</option>'+
'<option value="-30 days">30 jours glissants</option>'+
'<option value="-31 days">31 jours glissants</option>'+
'</select>'+
'</div>'+
'<div class="scheduleds-number-data-container col-xs-8">'+
'<label>Volume</label>'+
'<div class="form-group">'+
'<input name="phones[' + phone_id + '][limits][' + random_id + '][volume]" class="form-control" type="number" min="1" placeholder="Nombre de SMS maximum sur la période.">'+
'</div>'+
'</div>'+
'<a href="#" class="phone-limits-group-remove"><span class="fa fa-times"></span></a>'+
'</div>';
jQuery(this).parent('div').before(newLimit);
});
});
</script>
<?php
$this->render('incs/footer');

View File

@ -41,8 +41,10 @@
<tr> <tr>
<th>ID</th> <th>ID</th>
<th>Nom</th> <th>Nom</th>
<th>Priorité</th>
<th>Type de téléphone</th> <th>Type de téléphone</th>
<th>Callbacks</th> <th>Callbacks</th>
<th>Limites</th>
<th class="checkcolumn"><input type="checkbox" id="check-all"/></th> <th class="checkcolumn"><input type="checkbox" id="check-all"/></th>
</tr> </tr>
</thead> </thead>
@ -56,6 +58,7 @@
</div> </div>
<div class="text-right col-xs-6 no-padding"> <div class="text-right col-xs-6 no-padding">
<strong>Action pour la séléction :</strong> <strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'edit'); ?>"><span class="fa fa-edit"></span> Modifier</button>
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button> <button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div> </div>
</div> </div>
@ -88,6 +91,7 @@ jQuery(document).ready(function ()
"columns" : [ "columns" : [
{data: 'id', render: jQuery.fn.dataTable.render.text()}, {data: 'id', render: jQuery.fn.dataTable.render.text()},
{data: 'name', render: jQuery.fn.dataTable.render.text()}, {data: 'name', render: jQuery.fn.dataTable.render.text()},
{data: 'priority', render: jQuery.fn.dataTable.render.text()},
{data: 'adapter', render: jQuery.fn.dataTable.render.text()}, {data: 'adapter', render: jQuery.fn.dataTable.render.text()},
{ {
data: '_', data: '_',
@ -123,6 +127,61 @@ jQuery(document).ready(function ()
return html; return html;
}, },
}, },
{
data: 'limits',
render: function (limits) {
if (!limits.length)
{
return 'Pas de limites.';
}
var html = '';
for (limit of limits)
{
switch (limit.startpoint)
{
case "today" :
var startpoint = 'Par jour';
break;
case "-24 hours" :
var startpoint = '24 heures glissantes';
break;
case "this week midnight" :
var startpoint = 'Cette semaine';
break;
case "-7 days" :
var startpoint = '7 jours glissants';
break;
case "this week midnight -1 week" :
var startpoint = 'Ces deux dernières semaines';
break;
case "-14 days" :
var startpoint = '14 jours glissants';
break;
case "this month midnight" :
var startpoint = 'Ce mois';
break;
case "-1 month" :
var startpoint = '1 mois glissant';
break;
case "-28 days" :
var startpoint = '28 jours glissants';
break;
case "-30 days" :
var startpoint = '30 jours glissants';
break;
case "-31 days" :
var startpoint = '31 jours glissants';
break;
default :
var startpoint = 'Inconnu'
}
html += '<div><span class="bold">' + jQuery.fn.dataTable.render.text().display(startpoint) + ' : </span>' + jQuery.fn.dataTable.render.text().display(limit.volume) + '</div>';
}
return html;
},
},
{ {
data: 'id', data: 'id',
render: function (data, type, row, meta) { render: function (data, type, row, meta) {

View File

@ -54,6 +54,25 @@
</form> </form>
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-font fa-fw"></i> Alphabet SMS optimisé</h4>
</div>
<div class="panel-body">
<form action="<?php echo \descartes\Router::url('Setting', 'update', ['setting_name' => 'force_gsm_alphabet', 'csrf' => $_SESSION['csrf']]); ?>" method="POST">
<div class="form-group">
<label>Optimiser la taille des SMS en remplaçant les caractères spéciaux par leur équivalent GSM 7-bit : </label>
<select name="setting_value" class="form-control">
<option value="0">Non</option>
<option value="1" <?php echo $_SESSION['user']['settings']['force_gsm_alphabet'] ? 'selected' : ''; ?>>Oui</option>
</select>
</div>
<div class="text-center">
<button class="btn btn-success">Mettre à jour les données</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-picture-o fa-fw"></i> Support des MMS</h4> <h4 class="panel-title"><i class="fa fa-picture-o fa-fw"></i> Support des MMS</h4>

Binary file not shown.