Compare commits

...

16 Commits

Author SHA1 Message Date
osaajani 017c7fee53 style validation fix 2021-06-17 00:51:33 +02:00
osaajani f1d47a25ed update version to 3.1.4 2021-06-17 00:30:57 +02:00
osaajani 31acbdd1d5 add migration to create new settings with default values 2021-06-16 04:16:58 +02:00
osaajani fa8ee399e9 add credit estimation on message 2021-06-15 01:49:47 +02:00
osaajani 7a20cbb286 disable password autocomplete and fix password not hashed on update 2021-06-14 21:43:03 +02:00
osaajani 17d91873d4 add setting to hide menus for users 2021-06-14 21:33:06 +02:00
osaajani ca9b7c7c6e improve icons for credit 2021-06-14 20:21:06 +02:00
osaajani c3637ab3ab add stats about credit in use account and update dates for home graph 2021-06-14 19:48:42 +02:00
osaajani 03ae69b82a add quota to user list 2021-06-13 00:14:54 +02:00
osaajani 4a39865903 add capacity to modify user 2021-06-12 23:23:15 +02:00
osaajani f9e0312c89 update template add user to add quota, still need to show/hidde quota based on enable or not and impement
backend processing
2021-06-11 02:16:41 +02:00
osaajani 4c47de3fc5 add quota renewal 2021-06-10 01:31:53 +02:00
osaajani 9b7fb75eec add email settings for quota alerting and user settings for quota alerting 2021-06-09 01:22:11 +02:00
osaajani 8d6236113e add setting 2021-06-09 01:20:42 +02:00
osaajani cb38447feb Pass quota limit as a console method 2021-06-08 21:01:26 +02:00
osaajani 120f56fad7 First step of quota and using daemon 2021-06-08 02:00:48 +02:00
60 changed files with 2041 additions and 433 deletions

View File

@ -1 +1 @@
v3.1.3
v3.1.4

View File

@ -84,22 +84,22 @@ interface AdapterInterface
public static function meta_support_status_change(): bool;
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool;
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool;
/**
* Does the implemented service support inbound call callback
* Does the implemented service support inbound call callback.
*/
public static function meta_support_inbound_call_callback(): bool;
/**
* Does the implemented service support end call callback
* Does the implemented service support end call callback.
*/
public static function meta_support_end_call_callback(): bool;
@ -118,23 +118,23 @@ interface AdapterInterface
* 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;
/**
* Method called to read SMSs of the number.
*
* @return array : [
* bool 'error' => false if no error, true else
* ?string 'error_message' => null if no error, else error message
* array 'smss' => Array of the sms reads [[
* (optional) bool 'mms' => default to false, true if mms
* (optional) array 'medias' => default to [], list of array representing medias to link to sms, with [
* 'filepath' => local file copy of the media,
* 'extension' (optional) => extension of the media,
* 'mimetype' (optional) => mimetype of the media
* ]
* ], ...]
* ]
* bool 'error' => false if no error, true else
* ?string 'error_message' => null if no error, else error message
* array 'smss' => Array of the sms reads [[
* (optional) bool 'mms' => default to false, true if mms
* (optional) array 'medias' => default to [], list of array representing medias to link to sms, with [
* 'filepath' => local file copy of the media,
* 'extension' (optional) => extension of the media,
* 'mimetype' (optional) => mimetype of the media
* ]
* ], ...]
* ]
*/
public function read(): array;
@ -157,50 +157,49 @@ interface AdapterInterface
* Method called on reception of a sms notification.
*
* @return array : [
* bool 'error' => false on success, true on error
* ?string 'error_message' => null on success, error message else
* array 'sms' => array [
* string 'at' : Recepetion date format Y-m-d H:i:s,
* string 'text' : SMS body,
* string 'origin' : SMS sender,
* (optional) array 'medias' => default to [], list of array representing medias to link to sms, with [
* 'filepath' => local file copy of the media,
* 'extension' (optional) => extension of the media,
* 'mimetype' (optional) => mimetype of the media
* ]
* ]
* ]
* bool 'error' => false on success, true on error
* ?string 'error_message' => null on success, error message else
* array 'sms' => array [
* string 'at' : Recepetion date format Y-m-d H:i:s,
* string 'text' : SMS body,
* string 'origin' : SMS sender,
* (optional) array 'medias' => default to [], list of array representing medias to link to sms, with [
* 'filepath' => local file copy of the media,
* 'extension' (optional) => extension of the media,
* 'mimetype' (optional) => mimetype of the media
* ]
* ]
* ]
*/
public static function reception_callback(): array;
/**
* Method called on reception of an inbound_call notification
* Method called on reception of an inbound_call notification.
*
* @return array : [
* bool 'error' => false on success, true on error
* ?string 'error_message' => null on success, error message else
* array 'call' => array [
* string 'uid' : Uid of the call on the adapter plateform
* string 'start' : Start of the call date format Y-m-d H:i:s,
* ?string 'end' : End of the call date format Y-m-d H:i:s. If no known end, NULL
* string 'origin' : Emitter phone call number. International format.
* ]
* ]
* bool 'error' => false on success, true on error
* ?string 'error_message' => null on success, error message else
* array 'call' => array [
* string 'uid' : Uid of the call on the adapter plateform
* string 'start' : Start of the call date format Y-m-d H:i:s,
* ?string 'end' : End of the call date format Y-m-d H:i:s. If no known end, NULL
* string 'origin' : Emitter phone call number. International format.
* ]
* ]
*/
public function inbound_call_callback(): array;
/**
* Method called on reception of a end call notification
* Method called on reception of a end call notification.
*
* @return array : [
* bool 'error' => false on success, true on error
* ?string 'error_message' => null on success, error message else
* array 'call' => array [
* string 'uid' : Uid of the call on the adapter plateform. Used to find the raspisms local call to update.
* string 'end' : End of the call date format Y-m-d H:i:s.
* ]
* ]
* bool 'error' => false on success, true on error
* ?string 'error_message' => null on success, error message else
* array 'call' => array [
* string 'uid' : Uid of the call on the adapter plateform. Used to find the raspisms local call to update.
* string 'end' : End of the call date format Y-m-d H:i:s.
* ]
* ]
*/
public function end_call_callback(): array;
}

View File

@ -125,9 +125,9 @@ namespace adapters;
{
return false;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -135,24 +135,24 @@ namespace adapters;
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -230,12 +230,12 @@ namespace adapters;
{
return true;
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -135,9 +135,9 @@ namespace adapters;
{
return false;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -145,24 +145,24 @@ namespace adapters;
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -306,12 +306,12 @@ namespace adapters;
{
return [];
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -172,9 +172,9 @@ class OctopushShortcodeAdapter implements AdapterInterface
{
return true;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -182,24 +182,24 @@ class OctopushShortcodeAdapter implements AdapterInterface
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -417,12 +417,12 @@ class OctopushShortcodeAdapter implements AdapterInterface
return $response;
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -177,9 +177,9 @@ class OctopushVirtualNumberAdapter implements AdapterInterface
{
return true;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -187,24 +187,24 @@ class OctopushVirtualNumberAdapter implements AdapterInterface
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -417,12 +417,12 @@ class OctopushVirtualNumberAdapter implements AdapterInterface
return $response;
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -169,9 +169,9 @@ namespace adapters;
{
return false;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -179,24 +179,24 @@ namespace adapters;
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -369,12 +369,12 @@ namespace adapters;
{
return [];
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -180,9 +180,9 @@ namespace adapters;
{
return false;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -190,24 +190,24 @@ namespace adapters;
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -368,12 +368,12 @@ namespace adapters;
{
return [];
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -130,9 +130,9 @@ namespace adapters;
{
return false;
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -140,24 +140,24 @@ namespace adapters;
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return true;
}
public static function meta_support_inbound_call_callback(): bool
{
return true;
}
public static function meta_support_end_call_callback(): bool
{
return true;
}
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
{
$response = [
'error' => false,
@ -194,8 +194,8 @@ namespace adapters;
* "mms" : true,
* "origin" : "+33612345678",
* "text" : "SMS Text"
* }
*/
* }.
*/
public function read(): array
{
$response = [
@ -323,7 +323,7 @@ namespace adapters;
{
return [];
}
public function inbound_call_callback(): array
{
$response = [
@ -352,7 +352,7 @@ namespace adapters;
return $response;
}
public function end_call_callback(): array
{
$response = [

View File

@ -176,7 +176,7 @@ class TwilioVirtualNumberAdapter implements AdapterInterface
}
/**
* Does the implemented service support mms reception
* Does the implemented service support mms reception.
*/
public static function meta_support_mms_reception(): bool
{
@ -184,24 +184,24 @@ class TwilioVirtualNumberAdapter implements AdapterInterface
}
/**
* Does the implemented service support mms sending
* Does the implemented service support mms sending.
*/
public static function meta_support_mms_sending(): bool
{
return false;
}
public static function meta_support_inbound_call_callback(): bool
{
return false;
}
public static function meta_support_end_call_callback(): bool
{
return false;
}
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
{
$response = [
'error' => false,
@ -346,12 +346,12 @@ class TwilioVirtualNumberAdapter implements AdapterInterface
{
return [];
}
public function inbound_call_callback(): array
{
return [];
}
public function end_call_callback(): array
{
return [];

View File

@ -89,6 +89,13 @@ footer img
opacity: 0.7;
}
/** DASHBOARD **/
.dashboard-panel-chart .panel-title
{
margin-bottom: 10px;
}
/** GROUPES **/
.list-contacts
{
@ -360,6 +367,12 @@ footer img
font-weight: bold;
}
.credit-estimation-container
{
margin-top: 10px;
text-align: right;
}
/* AUDIO RECEPTION MESSAGE */
#reception-sound
{

View File

@ -118,8 +118,8 @@ namespace controllers\internals;
}
/**
* List all adapters for a meta value
*
* List all adapters for a meta value.
*
* @param $search_name : Name of the meta
* @param $search_value : Value of the meta
*
@ -128,7 +128,9 @@ namespace controllers\internals;
public function list_adapters_with_meta_equal($search_name, $search_value)
{
$adapters = $this->list_adapters();
return array_filter($adapters, function($metas) use ($search_name, $search_value) {
return array_filter($adapters, function ($metas) use ($search_name, $search_value)
{
$match = false;
foreach ($metas as $name => $value)
{

View File

@ -18,11 +18,11 @@ namespace controllers\internals;
/**
* Create a call.
*
* @param int $id_user : Id of the user
* @param int $id_phone : Id of the phone that emitted (outbound) or received (inbound) the call
* @param string $uid : Uid of the phone call
* @param string $direction : Direction of the call, \models\Call::DIRECTION_INBOUND | \models\Call::DIRECTION_OUTBOUND
* @param string $start : Date of the call beginning
* @param int $id_user : Id of the user
* @param int $id_phone : Id of the phone that emitted (outbound) or received (inbound) the call
* @param string $uid : Uid of the phone call
* @param string $direction : Direction of the call, \models\Call::DIRECTION_INBOUND | \models\Call::DIRECTION_OUTBOUND
* @param string $start : Date of the call beginning
* @param ?string $end : Date of the call end
* @param ?string $origin : Origin of the call or null if outbound
* @param ?string $destination : Destination of the call or null if inbound
@ -49,15 +49,23 @@ namespace controllers\internals;
switch ($direction)
{
case \models\Call::DIRECTION_OUTBOUND :
if (null === $destination) { return false; }
break;
case \models\Call::DIRECTION_INBOUND :
if (null === $origin) { return false; }
case \models\Call::DIRECTION_OUTBOUND:
if (null === $destination)
{
return false;
}
break;
default :
case \models\Call::DIRECTION_INBOUND:
if (null === $origin)
{
return false;
}
break;
default:
return false;
}
@ -75,7 +83,7 @@ namespace controllers\internals;
{
return false;
}
$new_call_id = $this->get_model()->insert($call);
if (!$new_call_id)
{
@ -83,21 +91,20 @@ namespace controllers\internals;
}
$call['id'] = $new_call_id;
$internal_webhook = new Webhook($this->bdd);
$internal_webhook->trigger($id_user, \models\Webhook::TYPE_INBOUND_CALL, $call);
return $new_call_id;
}
/**
* End a call
* End a call.
*
* @param int $id_user : Id of the user to end call for
* @param int $id_phone : If of the phone to end call for
* @param string $uid : Uid of the call to end
* @param string $end : End date of the call, format Y-m-d H:i:s
* @param int $id_user : Id of the user to end call for
* @param int $id_phone : If of the phone to end call for
* @param string $uid : Uid of the call to end
* @param string $end : End date of the call, format Y-m-d H:i:s
*
* @return bool : False if cannot end phone call, true else
*/
@ -125,7 +132,7 @@ namespace controllers\internals;
return (bool) $this->get_model()->update_for_user($id_user, $call['id'], $datas);
}
/**
* Get the model for the Controller.
*/

View File

@ -103,7 +103,15 @@ namespace controllers\internals;
if ($user)
{
$api_key = $api_key ?? $internal_user->generate_random_api_key();
$success = $internal_user->update($user['id'], $email, $password, $admin, $api_key, $status, $encrypt_password);
$user = [
'email' => $email,
'password' => $encrypt_password ? password_hash($password, PASSWORD_DEFAULT) : $password,
'admin' => $admin,
'api_key' => $api_key,
'status' => $status,
];
$success = $internal_user->update($user['id'], $user);
echo json_encode(['id' => $user['id']]);
exit($success ? 0 : 1);
@ -152,9 +160,8 @@ namespace controllers\internals;
exit($success ? 0 : 1);
}
/**
* Delete medias that are no longer usefull
* Delete medias that are no longer usefull.
*/
public function clean_unused_medias()
{
@ -167,7 +174,27 @@ namespace controllers\internals;
{
$success = $internal_media->delete_for_user($media['id_user'], $media['id']);
echo ($success === false ? '[KO]' : '[OK]') . ' - ' . $media['path'] . "\n";
echo (false === $success ? '[KO]' : '[OK]') . ' - ' . $media['path'] . "\n";
}
}
/**
* Do alerting for quota limits.
*/
public function quota_limit_alerting()
{
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD, 'UTF8');
$internal_quota = new \controllers\internals\Quota($bdd);
$internal_quota->alerting_for_limit_close_and_reached();
}
/**
* Do quota renewal.
*/
public function renew_quotas()
{
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD, 'UTF8');
$internal_quota = new \controllers\internals\Quota($bdd);
$internal_quota->renew_quotas();
}
}

View File

@ -56,6 +56,21 @@ namespace controllers\internals;
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.
*/

View File

@ -20,8 +20,8 @@ class Media extends StandardController
/**
* Create a media.
*
* @param int $id_user : Id of the user
* @param string $tmpfile_path : Path of the temporary local copy of the media
* @param int $id_user : Id of the user
* @param string $tmpfile_path : Path of the temporary local copy of the media
* @param ?string $extension : Extension to use for the media
*
* @return int : Exception on error, new media id else
@ -33,13 +33,13 @@ class Media extends StandardController
{
throw new \Exception('File ' . $tmpfile_path . ' does not exists.');
}
if (!is_readable($tmpfile_path))
{
throw new \Exception('File ' . $tmpfile_path . ' is not readable.');
}
$mimey = new \Mimey\MimeTypes;
$mimey = new \Mimey\MimeTypes();
$extension = $extension ?? $mimey->getExtension(mime_content_type($tmpfile_path));
$new_file_name = \controllers\internals\Tool::random_uuid() . '.' . $extension;
@ -60,7 +60,7 @@ class Media extends StandardController
{
throw new \Exception('Cannot give file ' . $new_file_path . ' to user : ' . fileowner($user_path));
}
if (!chgrp($new_file_path, filegroup($user_path)))
{
throw new \Exception('Cannot give file ' . $new_file_path . ' to group : ' . filegroup($user_path));
@ -86,16 +86,17 @@ class Media extends StandardController
}
/**
* Upload and create a media
*
* @param int $id_user : Id of the user
* @param array $file : array representing uploaded file, extracted from $_FILES['yourfile']
* Upload and create a media.
*
* @param int $id_user : Id of the user
* @param array $file : array representing uploaded file, extracted from $_FILES['yourfile']
*
* @return int : Raise exception on error or return new media id on success
*/
public function create_from_uploaded_file_for_user(int $id_user, array $file)
{
$upload_result = \controllers\internals\Tool::read_uploaded_file($file);
if ($upload_result['success'] !== true)
if (true !== $upload_result['success'])
{
throw new \Exception($upload_result['content']);
}
@ -110,15 +111,16 @@ class Media extends StandardController
{
throw new \Exception('Cannot move uploaded file to : ' . $tmp_file);
}
return $this->create($id_user, $tmp_file, $upload_result['extension']);
}
/**
* Link a media to a scheduled, a received or a sended message
* @param int $id_media : Id of the media
* Link a media to a scheduled, a received or a sended message.
*
* @param int $id_media : Id of the media
* @param string $resource_type : Type of resource to link the media to ('scheduled', 'received' or 'sended')
* @param int $resource_id : Id of the resource to link the media to
* @param int $resource_id : Id of the resource to link the media to
*
* @return mixed bool|int : false on error, the new link id else
*/
@ -128,14 +130,17 @@ class Media extends StandardController
{
case 'scheduled':
return $this->get_model()->insert_media_scheduled($id_media, $resource_id);
break;
case 'received':
return $this->get_model()->insert_media_received($id_media, $resource_id);
break;
case 'sended':
return $this->get_model()->insert_media_sended($id_media, $resource_id);
break;
default:
@ -143,12 +148,12 @@ class Media extends StandardController
}
}
/**
* Unlink a media of a scheduled, a received or a sended message
* @param int $id_media : Id of the media
* Unlink a media of a scheduled, a received or a sended message.
*
* @param int $id_media : Id of the media
* @param string $resource_type : Type of resource to unlink the media of ('scheduled', 'received' or 'sended')
* @param int $resource_id : Id of the resource to unlink the media of
* @param int $resource_id : Id of the resource to unlink the media of
*
* @return mixed bool : false on error, true on success
*/
@ -158,14 +163,17 @@ class Media extends StandardController
{
case 'scheduled':
return $this->get_model()->delete_media_scheduled($id_media, $resource_id);
break;
case 'received':
return $this->get_model()->delete_media_received($id_media, $resource_id);
break;
case 'sended':
return $this->get_model()->delete_media_sended($id_media, $resource_id);
break;
default:
@ -174,9 +182,10 @@ class Media extends StandardController
}
/**
* Unlink all medias of a scheduled, a received or a sended message
* Unlink all medias of a scheduled, a received or a sended message.
*
* @param string $resource_type : Type of resource to unlink the media of ('scheduled', 'received' or 'sended')
* @param int $resource_id : Id of the resource to unlink the media of
* @param int $resource_id : Id of the resource to unlink the media of
*
* @return mixed bool : false on error, true on success
*/
@ -186,14 +195,17 @@ class Media extends StandardController
{
case 'scheduled':
return $this->get_model()->delete_all_for_scheduled($resource_id);
break;
case 'received':
return $this->get_model()->delete_all_for_received($resource_id);
break;
case 'sended':
return $this->get_model()->delete_all_for_sended($resource_id);
break;
default:
@ -204,9 +216,9 @@ class Media extends StandardController
/**
* Update a media for a user.
*
* @param int $id_user : user id
* @param int $id_media : Media id
* @param string $path : Path of the file
* @param int $id_user : user id
* @param int $id_media : Media id
* @param string $path : Path of the file
*
* @return bool : false on error, true on success
*/
@ -289,7 +301,8 @@ class Media extends StandardController
}
/**
* Find medias that are not used
* Find medias that are not used.
*
* @return array
*/
public function gets_unused()

View File

@ -44,13 +44,14 @@ namespace controllers\internals;
}
/**
* Check if a phone support mms
* Check if a phone support mms.
*
* @param int $id : id of the phone to check
* @param $type : type of sms support, a const from Phone, MMS_SENDING, MMS_RECEPTION or MMS_BOTH
* @param $type : type of sms support, a const from Phone, MMS_SENDING, MMS_RECEPTION or MMS_BOTH
*
* @return bool : true if support, false else
*/
public function support_mms (int $id, string $type)
public function support_mms(int $id, string $type)
{
$phone = $this->get_model()->get($id);
if (!$phone)
@ -60,16 +61,19 @@ namespace controllers\internals;
switch ($type)
{
case self::MMS_SENDING :
case self::MMS_SENDING:
return $phone['adapter']::meta_support_mms_sending();
break;
case self::MMS_RECEPTION :
case self::MMS_RECEPTION:
return $phone['adapter']::meta_support_mms_reception();
break;
case self::MMS_BOTH :
case self::MMS_BOTH:
return $phone['adapter']::meta_support_mms_sending() && $phone['adapter']::meta_support_mms_reception();
break;
default:
@ -78,13 +82,14 @@ namespace controllers\internals;
}
/**
* Get all phones supporting mms for a user
* Get all phones supporting mms for a user.
*
* @param int $id_user : id of the user
* @param $type : type of sms support, a const from Phone, MMS_SENDING, MMS_RECEPTION or MMS_BOTH
* @param $type : type of sms support, a const from Phone, MMS_SENDING, MMS_RECEPTION or MMS_BOTH
*
* @return array : array of phones supporting mms
*/
public function gets_phone_supporting_mms_for_user (int $id_user, string $type)
public function gets_phone_supporting_mms_for_user(int $id_user, string $type)
{
$phones = $this->get_model()->gets_for_user($id_user);

View File

@ -0,0 +1,297 @@
<?php
/*
* This file is part of RaspiSMS.
*
* (c) Pierre-Lin Bonnemaison <plebwebsas@gmail.com>
*
* This source file is subject to the GPL-3.0 license that is bundled
* with this source code in the file LICENSE.
*/
namespace controllers\internals;
class Quota extends StandardController
{
protected $model;
/**
* Create a new quota.
*
* @param int $id_user : User id
* @param int $credit : Credit for this quota
* @param int $additional : Additionals credits
* @param bool $report_unused : Should unused credits be re-credited
* @param bool $report_unused_additional : Should unused additional credits be re-credited
* @param bool $auto_renew : Should the quota be automatically renewed after expiration_date
* @param string $renew_interval : Period to use for setting new expiration_date on renewal (format ISO_8601#Durations)
* @param \DateTime $start_date : Starting date for the quota
* @param \DateTime $expiration_date : Ending date for the quota
*
* @return mixed bool|int : False if cannot create quota, id of the new quota else
*/
public function create(int $id_user, int $credit, int $additional, bool $report_unused, bool $report_unused_additional, bool $auto_renew, string $renew_interval, \DateTime $start_date, \DateTime $expiration_date)
{
$quota = [
'id_user' => $id_user,
'credit' => $credit,
'report_unused' => $report_unused,
'report_unused_additional' => $report_unused_additional,
'start_date' => $start_date->format('Y-m-d H:i:s'),
'expiration_date' => $expiration_date->format('Y-m-d H:i:s'),
'auto_renew' => $auto_renew,
'renew_interval' => $renew_interval,
'additional' => $additional,
];
return $this->get_model()->insert($quota);
}
/**
* Update a quota.
*
* @param int $id_user : User id
* @param int $id_quota : Quota to update id
* @param array $quota : Fields to update whith new values
*
* @return int : number of updated lines
*/
public function update_for_user(int $id_user, $id_quota, array $quota)
{
return $this->get_model()->update_for_user($id_user, $id_quota, $quota);
}
/**
* Check if we have enough credit.
*
* @param int $id_user : User id
* @param int $needed : Number of credits we need
*
* @return bool : true if we have enough credit, false else
*/
public function has_enough_credit(int $id_user, int $needed)
{
$remaining_credit = $this->get_model()->get_remaining_credit($id_user, new \DateTime());
return $remaining_credit >= $needed;
}
/**
* Consume some credit.
*
* @param int $id_user : User id
* @param int $quantity : Number of credits to consume
*
* @return bool : True on success, false else
*/
public function consume_credit(int $id_user, int $quantity)
{
$result = $this->get_model()->consume_credit($id_user, $quantity);
//Write event
$internal_event = new Event($this->bdd);
$internal_event->create($id_user, 'QUOTA_CONSUME', 'Consume ' . $quantity . ' credits of SMS quota.');
return $result;
}
/**
* Get quota usage percentage.
*
* @param int $id_user : User id
*
* @return float : percentage of quota used
*/
public function get_usage_percentage(int $id_user)
{
return $this->get_model()->get_usage_percentage($id_user, new \DateTime());
}
/**
* Compute how many credit a message represent
* this function count 160 chars per SMS if it can be send as GSM 03.38 encoding and 70 chars per SMS if it can only be send as UTF8.
*
* @param string $text : Message to send
*
* @return int : Number of credit to send this message
*/
public static function compute_credits_for_message($text)
{
//Gsm 03.38 charset to detect if message is compatible or must use utf8
$gsm0338 = [
'@', 'Δ', ' ', '0', '¡', 'P', '¿', 'p',
'£', '_', '!', '1', 'A', 'Q', 'a', 'q',
'$', 'Φ', '"', '2', 'B', 'R', 'b', 'r',
'¥', 'Γ', '#', '3', 'C', 'S', 'c', 's',
'è', 'Λ', '¤', '4', 'D', 'T', 'd', 't',
'é', 'Ω', '%', '5', 'E', 'U', 'e', 'u',
'ù', 'Π', '&', '6', 'F', 'V', 'f', 'v',
'ì', 'Ψ', '\'', '7', 'G', 'W', 'g', 'w',
'ò', 'Σ', '(', '8', 'H', 'X', 'h', 'x',
'Ç', 'Θ', ')', '9', 'I', 'Y', 'i', 'y',
"\n", 'Ξ', '*', ':', 'J', 'Z', 'j', 'z',
'Ø', "\x1B", '+', ';', 'K', 'Ä', 'k', 'ä',
'ø', 'Æ', ',', '<', 'L', 'Ö', 'l', 'ö',
"\r", 'æ', '-', '=', 'M', 'Ñ', 'm', 'ñ',
'Å', 'ß', '.', '>', 'N', 'Ü', 'n', 'ü',
'å', 'É', '/', '?', 'O', '§', 'o', 'à',
];
$is_gsm0338 = true;
$len = mb_strlen($text);
for ($i = 0; $i < $len; ++$i)
{
if (!in_array(mb_substr($text, $i, 1), $gsm0338))
{
$is_gsm0338 = false;
break;
}
}
return $is_gsm0338 ? ceil($len / 160) : ceil($len / 70);
}
/**
* Do email alerting for quotas limit close and quotas limit reached.
*/
public function alerting_for_limit_close_and_reached()
{
$internal_user = new User($this->bdd);
$internal_event = new Event($this->bdd);
$quotas_limit_close = $this->get_model()->get_quotas_for_limit_close();
$quotas_limit_reached = $this->get_model()->get_quotas_for_limit_reached();
foreach ($quotas_limit_close as $quota)
{
$user = $internal_user->get($quota['id_user']);
if (!$user)
{
continue;
}
$quota_percentage = $quota['consumed'] / ($quota['credit'] + $quota['additional']);
$mailer = new \controllers\internals\Mailer();
$success = $mailer->enqueue($user['email'], EMAIL_QUOTA_LIMIT_CLOSE, ['percent' => $quota_percentage]);
if (!$success)
{
echo 'Cannot enqueue alert for quota limit close for quota : ' . $quota['id'] . "\n";
continue;
}
echo 'Enqueue alert for quota limit close for quota : ' . $quota['id'] . "\n";
$internal_event->create($quota['id_user'], 'QUOTA_LIMIT_CLOSE', round($quota_percentage * 100, 2) . '% of SMS quota limit reached.');
}
foreach ($quotas_limit_reached as $quota)
{
$user = $internal_user->get($quota['id_user']);
if (!$user)
{
continue;
}
$quota_percentage = $quota['consumed'] / ($quota['credit'] + $quota['additional']);
$mailer = new \controllers\internals\Mailer();
$success = $mailer->enqueue($user['email'], EMAIL_QUOTA_LIMIT_REACHED, ['expiration_date' => $quota['expiration_date']]);
if (!$success)
{
echo 'Cannot enqueue alert for quota limit reached for quota : ' . $quota['id'] . "\n";
continue;
}
echo 'Enqueue alert for quota limit reached for quota : ' . $quota['id'] . "\n";
$internal_event->create($quota['id_user'], 'QUOTA_LIMIT_REACHED', 'Reached SMS quota limit.');
}
}
/**
* Do quota renewing.
*/
public function renew_quotas()
{
$internal_user = new User($this->bdd);
$internal_event = new Event($this->bdd);
$quotas = $this->get_model()->get_quotas_to_be_renewed(new \DateTime());
foreach ($quotas as $quota)
{
$user = $internal_user->get($quota['id_user']);
if (!$user)
{
continue;
}
$unused_credit = $quota['credit'] - $quota['consumed'];
$unused_additional = $unused_credit > 0 ? $quota['additional'] : $quota['additional'] + $unused_credit;
$renew_interval = $quota['renew_interval'] ?? 'P0D';
$new_start_date = new \DateTime($quota['expiration_date']);
$new_expiration_date = clone $new_start_date;
$new_expiration_date->add(new \DateInterval($quota['renew_interval']));
$report = 0;
if ($quota['report_unused'] && $unused_credit > 0)
{
$report += $unused_credit;
}
if ($quota['report_unused_additional'] && $unused_additional > 0)
{
$report += $unused_additional;
}
$updated_fields = [
'start_date' => $new_start_date->format('Y-m-d H:i:s'),
'expiration_date' => $new_expiration_date->format('Y-m-d H:i:s'),
'additional' => $report,
'consumed' => 0,
];
$success = $this->update_for_user($user['id'], $quota['id'], $updated_fields);
if (!$success)
{
echo 'Cannot update quota : ' . $quota['id'] . "\n";
continue;
}
echo 'Update quota : ' . $quota['id'] . "\n";
$internal_event->create($quota['id_user'], 'QUOTA_RENEWAL', 'Renew quota and report ' . $report . ' credits.');
}
}
/**
* Return the quota for a user if it exists.
*
* @param int $id_user : user id
*
* @return array
*/
public function get_user_quota(int $id_user)
{
return $this->get_model()->get_user_quota($id_user);
}
/**
* Get the model for the Controller.
*/
protected function get_model(): \descartes\Model
{
$this->model = $this->model ?? new \models\Quota($this->bdd);
return $this->model;
}
}

View File

@ -36,11 +36,11 @@ 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 $origin : Number of the sender
* @param string $status : Status of the received message
* @param bool $command : Is the sms a command
* @param bool $mms : Is the sms a mms
* @param array $media_ids : Ids of the medias to link to received
* @param string $origin : Number of the sender
* @param string $status : Status of the received message
* @param bool $command : Is the sms a command
* @param bool $mms : Is the sms a mms
* @param array $media_ids : Ids of the medias to link to received
*
* @return mixed : false on error, new received id else
*/
@ -64,6 +64,7 @@ namespace controllers\internals;
if (!$id_received)
{
$this->bdd->rollBack();
return false;
}
@ -75,6 +76,7 @@ namespace controllers\internals;
if (!$id_media_received)
{
$this->bdd->rollBack();
return false;
}
}
@ -167,6 +169,7 @@ namespace controllers\internals;
* @param int $id_user : User id
* @param string $since : Date we want messages since format Y-m-d H:i:s
* @param string $origin : Number who sent the message
*
* @return array
*/
public function gets_since_date_by_origin_and_user(int $id_user, string $since, string $origin)
@ -256,11 +259,11 @@ namespace controllers\internals;
* @param string $origin : Number of the sender
* @param ?string $at : Message reception date, if null use current date
* @param string $status : Status of a the sms. By default \models\Received::STATUS_UNREAD
* @param bool $mms : Is the sms a mms
* @param array $medias : Empty array if no medias, or medias to create and link to the received message. Format : [[
* string 'filepath' => local path to a readable copy of the media,
* ?string 'extension' => extension to use for the file or null
* ], ...]
* @param bool $mms : Is the sms a mms
* @param array $medias : Empty array if no medias, or medias to create and link to the received message. Format : [[
* string 'filepath' => local path to a readable copy of the media,
* ?string 'extension' => extension to use for the file or null
* ], ...]
*
* @return array : [
* bool 'error' => false if success, true else
@ -285,7 +288,7 @@ namespace controllers\internals;
$is_command = true;
$text = $response;
}
//We create medias to link to the sms
$internal_media = new Media($this->bdd);
$media_ids = [];
@ -301,6 +304,7 @@ namespace controllers\internals;
catch (\Throwable $t)
{
$return['error_message'] = $t->getMessage();
continue; //Better loose the media than the message
}
}

View File

@ -43,7 +43,7 @@ namespace controllers\internals;
'mms' => $mms,
];
if ($text === '')
if ('' === $text)
{
return false;
}
@ -66,6 +66,7 @@ namespace controllers\internals;
if (!$id_scheduled)
{
$this->bdd->rollBack();
return false;
}
@ -76,11 +77,11 @@ namespace controllers\internals;
if (!$id_media_scheduled)
{
$this->bdd->rollBack();
return false;
}
}
foreach ($numbers as $number)
{
$this->get_model()->insert_scheduled_number($id_scheduled, $number);
@ -193,6 +194,7 @@ namespace controllers\internals;
if (!$id_media_scheduled)
{
$this->bdd->rollBack();
return false;
}
}
@ -254,7 +256,7 @@ namespace controllers\internals;
{
return $this->get_model()->gets_before_date_for_number_and_user($id_user, $date, $number);
}
/**
* Get messages scheduled after a date for a number and a user.
*
@ -349,7 +351,7 @@ namespace controllers\internals;
$random_phone = $users_phones[$scheduled['id_user']][$rnd_key];
}
}
$message = [
'id_user' => $scheduled['id_user'],
'id_scheduled' => $scheduled['id'],
@ -405,7 +407,7 @@ namespace controllers\internals;
}
$added_contacts[$contact['id']] = true;
if (null === $phone_to_use)
{
if ($scheduled['mms'] && count($users_mms_phones))

View File

@ -27,7 +27,7 @@ namespace controllers\internals;
* @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 array $medias : Array of medias to link to the MMS
* @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN
*
* @return mixed : false on error, new sended id else
@ -49,14 +49,15 @@ namespace controllers\internals;
//Ensure atomicity
$this->bdd->beginTransaction();
$id_sended = $this->get_model()->insert($sended);
if (!$id_sended)
{
$this->bdd->rollback();
return false;
}
//Link medias
$internal_media = new Media($this->bdd);
foreach ($medias as $media)
@ -132,13 +133,14 @@ namespace controllers\internals;
{
return $this->get_model()->gets_by_destination_and_user($id_user, $origin);
}
/**
* Return sendeds for a destination and a user since a date.
*
* @param int $id_user : User id
* @param string $since : Date we want messages since format Y-m-d H:i:s
* @param string $origin : Number who sent the message
*
* @return array
*/
public function gets_since_date_by_destination_and_user(int $id_user, string $since, string $origin)
@ -204,7 +206,7 @@ namespace controllers\internals;
* @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 array $medias : Array of medias to link to the MMS
* @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN
*
* @return array : [
@ -219,6 +221,17 @@ namespace controllers\internals;
'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');
$media_uris = [];
foreach ($medias as $media)
@ -253,6 +266,8 @@ namespace controllers\internals;
return $return;
}
$internal_quota->consume_credit($id_user, $nb_credits);
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $status);
$sended = [

View File

@ -123,6 +123,26 @@ namespace controllers\internals;
break;
case 'QUOTA_LIMIT_CLOSE':
$logo = 'fa-exclamation';
break;
case 'QUOTA_LIMIT_REACHED':
$logo = 'fa-exclamation-triangle';
break;
case 'QUOTA_RENEWAL':
$logo = 'fa-retweet';
break;
case 'QUOTA_CONSUME':
$logo = 'fa-euro';
break;
default:
$logo = 'fa-question';
}
@ -145,6 +165,27 @@ namespace controllers\internals;
return $objectDate && $objectDate->format($format) === $date;
}
/**
* Check if a sting represent a valid PHP period for creating an interval.
*
* @param string $period : Period string to check
*
* @return bool : True if valid period, false else
*/
public static function validate_period($period)
{
try
{
$interval = new \DateInterval($period);
}
catch (\Throwable $e)
{
return false;
}
return true;
}
/**
* Cette fonction retourne un mot de passe généré aléatoirement.
*
@ -279,26 +320,27 @@ namespace controllers\internals;
}
/**
* Generate a highly random uuid based on timestamp and strong cryptographic random
* Generate a highly random uuid based on timestamp and strong cryptographic random.
*
* @return string
*/
public static function random_uuid()
{
$bytes = random_bytes(16);
return time() . '-' . bin2hex($bytes);
}
/**
* Create a user data public path
* Create a user data public path.
*
* @param int $id_user : The user id
*
* @return string : The created path
*
* @exception Raise exception on error
*/
public static function create_user_public_path (int $id_user)
public static function create_user_public_path(int $id_user)
{
$new_dir = PWD_DATA_PUBLIC . '/' . $id_user;
if (file_exists($new_dir))
@ -313,18 +355,18 @@ namespace controllers\internals;
}
//We do chmod in two times because else umask fuck mkdir permissions
if (!chmod($new_dir, fileperms(PWD_DATA_PUBLIC) & 0777)) //Fileperms return garbage in addition to perms. Perms are only in weak bytes. We must use an octet notation with 0
{
if (!chmod($new_dir, fileperms(PWD_DATA_PUBLIC) & 0777))
{ //Fileperms return garbage in addition to perms. Perms are only in weak bytes. We must use an octet notation with 0
throw new \Exception('Cannot give dir ' . $new_dir . ' rights : ' . decoct(fileperms(PWD_DATA_PUBLIC) & 0777)); //Show error in dec
}
if (posix_getuid() === 0 && !chown($new_dir, fileowner(PWD_DATA_PUBLIC))) //If we are root, try to give the file to a proper user
{
if (0 === posix_getuid() && !chown($new_dir, fileowner(PWD_DATA_PUBLIC)))
{ //If we are root, try to give the file to a proper user
throw new \Exception('Cannot give dir ' . $new_dir . ' to user : ' . fileowner(PWD_DATA_PUBLIC));
}
if (posix_getuid() === 0 && !chgrp($new_dir, filegroup(PWD_DATA_PUBLIC))) //If we are root, try to give the file to a proper group
{
if (0 === posix_getuid() && !chgrp($new_dir, filegroup(PWD_DATA_PUBLIC)))
{ //If we are root, try to give the file to a proper group
throw new \Exception('Cannot give dir ' . $new_dir . ' to group : ' . filegroup(PWD_DATA_PUBLIC));
}

View File

@ -16,6 +16,7 @@ namespace controllers\internals;
*/
class User extends \descartes\InternalController
{
private $bdd;
private $model_user;
private $internal_event;
private $internal_setting;
@ -23,12 +24,25 @@ namespace controllers\internals;
public function __construct(\PDO $bdd)
{
$this->bdd = $bdd;
$this->model_user = new \models\User($bdd);
$this->internal_event = new \controllers\internals\Event($bdd);
$this->internal_setting = new \controllers\internals\Setting($bdd);
$this->internal_phone = new Phone($bdd);
}
/**
* Return a list of users by their ids.
*
* @param array $ids : ids of entries to find
*
* @return array
*/
public function gets_in_by_id(array $ids)
{
return $this->model_user->gets_in_by_id($ids);
}
/**
* Return list of users as an array.
*
@ -180,27 +194,56 @@ namespace controllers\internals;
/**
* Update a user by his id.
*
* @param mixed $id
* @param mixed $email
* @param mixed $password
* @param mixed $admin
* @param mixed $api_key
* @param string $status : User status
* @param bool $encrypt_password : Should the password be encrypted, by default true
* @param mixed $id : User id
* @param array $user : Array of fields to update for user
* @param mixed (?array|bool) $quota : Quota to update for the user, by default null -> no update, if false, remove quota
*
* @return int : Number of modified user
* @return bool : True on success, false on error
*/
public function update($id, $email, $password, $admin, $api_key, $status, bool $encrypt_password = true)
public function update($id, array $user, $quota = null)
{
$user = [
'email' => $email,
'password' => $encrypt_password ? password_hash($password, PASSWORD_DEFAULT) : $password,
'admin' => $admin,
'api_key' => $api_key,
'status' => $status,
];
$internal_quota = new Quota($this->bdd);
$current_quota = $internal_quota->get_user_quota($id);
return $this->model_user->update($id, $user);
$this->bdd->beginTransaction();
$this->model_user->update($id, $user);
if ($current_quota && false === $quota)
{
$success = $internal_quota->delete_for_user($id, $current_quota['id']);
if (!$success)
{
$this->bdd->rollback();
return false;
}
}
if ($quota)
{
if ($current_quota)
{
$internal_quota->update_for_user($id, $current_quota['id'], $quota);
}
else
{
$success = $internal_quota->create($id, $quota['credit'], $quota['additional'], $quota['report_unused'], $quota['report_unused_additional'], $quota['auto_renew'], $quota['renew_interval'], new \DateTime($quota['start_date']), new \DateTime($quota['expiration_date']));
if (!$success)
{
$this->bdd->rollback();
return false;
}
}
}
if (!$this->bdd->commit())
{
return false;
}
return true;
}
/**
@ -212,10 +255,11 @@ namespace controllers\internals;
* @param ?string $api_key : The api key of the user, if null generate randomly
* @param string $status : User status, default \models\User::STATUS_ACTIVE
* @param bool $encrypt_password : Should the password be encrypted, by default true
* @param ?array $quota : Quota to create for the user, by default null -> no quota
*
* @return mixed bool|int : false on error, id of the new user else
*/
public function create($email, $password, $admin, ?string $api_key = null, string $status = \models\User::STATUS_ACTIVE, bool $encrypt_password = true)
public function create($email, $password, $admin, ?string $api_key = null, string $status = \models\User::STATUS_ACTIVE, bool $encrypt_password = true, ?array $quota = null)
{
$user = [
'email' => $email,
@ -225,22 +269,39 @@ namespace controllers\internals;
'status' => $status,
];
$new_id_user = $this->model_user->insert($user);
$this->bdd->beginTransaction();
$new_id_user = $this->model_user->insert($user);
if (!$new_id_user)
{
return false;
}
$success = $this->internal_setting->create_defaults_for_user($new_id_user);
if (!$success)
{
$this->delete($new_id_user);
$this->bdd->rollback();
return false;
}
if (null !== $quota)
{
$internal_quota = new Quota($this->bdd);
$success = $internal_quota->create($new_id_user, $quota['credit'], $quota['additional'], $quota['report_unused'], $quota['report_unused_additional'], $quota['auto_renew'], $quota['renew_interval'], $quota['start_date'], $quota['expiration_date']);
if (!$success)
{
$this->bdd->rollback();
return false;
}
}
if (!$this->bdd->commit())
{
return false;
}
return $new_id_user;
}
@ -291,9 +352,9 @@ namespace controllers\internals;
}
$mailer = new Mailer();
$attachments = [];
foreach ($received['medias'] ?? [] as $media)
{
$attachments[] = PWD_DATA_PUBLIC . '/' . $media['path'];

View File

@ -14,11 +14,13 @@ namespace controllers\publics;
class Account extends \descartes\Controller
{
public $internal_user;
public $internal_quota;
public function __construct()
{
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_user = new \controllers\internals\User($bdd);
$this->internal_quota = new \controllers\internals\Quota($bdd);
\controllers\internals\Tool::verifyconnect();
}
@ -28,7 +30,9 @@ namespace controllers\publics;
*/
public function show()
{
$this->render('account/show');
$quota = $this->internal_quota->get_user_quota($_SESSION['user']['id']);
$quota_percent = $this->internal_quota->get_usage_percentage($_SESSION['user']['id']);
$this->render('account/show', ['quota' => $quota, 'quota_percent' => $quota_percent]);
}
/**

View File

@ -231,16 +231,16 @@ namespace controllers\publics;
//Iterate over files to re-create individual $_FILES array
$files_arrays = [];
if ($files === false)
if (false === $files)
{
$files_arrays = [];
}
elseif (!is_array($files['name'])) //Only one file uploaded
{
elseif (!is_array($files['name']))
{ //Only one file uploaded
$files_arrays[] = $files;
}
else //multiple files
{
else
{ //multiple files
foreach ($files as $property_name => $files_values)
{
foreach ($files_values as $file_key => $property_value)
@ -320,8 +320,8 @@ namespace controllers\publics;
if ($id_phone)
{
$phone = $this->internal_phone->get_for_user($this->user['id'], $id_phone);
}
}
if ($id_phone && !$phone)
{
$return = self::DEFAULT_RETURN;

View File

@ -34,7 +34,7 @@ namespace controllers\publics;
}
/**
* Page for showing calls list
* Page for showing calls list.
*/
public function list()
{
@ -51,12 +51,14 @@ namespace controllers\publics;
{
switch ($entity['direction'])
{
case \models\Call::DIRECTION_INBOUND :
case \models\Call::DIRECTION_INBOUND:
$entity['origin_formatted'] = \controllers\internals\Tool::phone_link($entity['origin']);
break;
case \models\Call::DIRECTION_OUTBOUND :
case \models\Call::DIRECTION_OUTBOUND:
$entity['destination_formatted'] = \controllers\internals\Tool::phone_link($entity['destination']);
break;
}
}
@ -66,10 +68,10 @@ namespace controllers\publics;
}
/**
* Delete a list of calls
* Delete a list of calls.
*
* @param array int $_GET['ids'] : Ids of calls to delete
* @param string $csrf : csrf token
* @param string $csrf : csrf token
*
* @return boolean;
*/

View File

@ -206,13 +206,12 @@ use Monolog\Logger;
return true;
}
/**
* Function call on call reception notification
* We return nothing, and we let the adapter do his things.
*
* @param int $id_phone : Phone id
* @param int $id_phone : Phone id
*
* @return bool : true on success, false on error
*/
@ -269,16 +268,15 @@ use Monolog\Logger;
}
$this->logger->info('Callback inbound_call successfully received inbound call : ' . json_encode($call));
return true;
}
/**
* Function call on end call notification
* We return nothing, and we let the adapter do his things.
*
* @param int $id_phone : Phone id
* @param int $id_phone : Phone id
*
* @return bool : true on success, false on error
*/
@ -334,7 +332,7 @@ use Monolog\Logger;
}
$this->logger->info('Callback end call successfully update call : ' . json_encode($call));
return true;
}
}

View File

@ -241,7 +241,7 @@ namespace controllers\publics;
if ($how_many_more > 0)
{
$result_text .= ", et $how_many_more autres.";
$result_text .= ", et {$how_many_more} autres.";
}
$return['result'] = $result_text;

View File

@ -85,12 +85,12 @@ namespace controllers\publics;
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
/**
* This function will delete a list of contacts depending on a condition
* This function will delete a list of contacts depending on a condition.
*
* @param string $_POST['condition'] : Condition to use to delete contacts
* @param mixed $csrf
* @param mixed $csrf
*
* @return boolean;
*/
@ -338,11 +338,11 @@ namespace controllers\publics;
break;
default:
if ($read_file['extension'] === 'csv')
if ('csv' === $read_file['extension'])
{
$result = $this->internal_contact->import_csv($id_user, $read_file['content']);
}
elseif ($read_file['extension'] === 'json')
elseif ('json' === $read_file['extension'])
{
$result = $this->internal_contact->import_json($id_user, $read_file['content']);
}

View File

@ -22,6 +22,7 @@ namespace controllers\publics;
private $internal_group;
private $internal_scheduled;
private $internal_event;
private $internal_quota;
/**
* Cette fonction est appelée avant toute les autres :
@ -39,6 +40,7 @@ namespace controllers\publics;
$this->internal_group = new \controllers\internals\Group($bdd);
$this->internal_scheduled = new \controllers\internals\Scheduled($bdd);
$this->internal_event = new \controllers\internals\Event($bdd);
$this->internal_quota = new \controllers\internals\Quota($bdd);
\controllers\internals\Tool::verifyconnect();
}
@ -60,40 +62,52 @@ namespace controllers\publics;
$nb_sendeds = $this->internal_sended->count_for_user($id_user);
$nb_receiveds = $this->internal_received->count_for_user($id_user);
//Création de la date d'il y a une semaine
$now = new \DateTime();
$one_week = new \DateInterval('P7D');
$date = $now->sub($one_week);
$formated_date = $date->format('Y-m-d');
//Récupération des 10 derniers Sms envoyés, Sms reçus et evenements enregistrés. Par date.
$sendeds = $this->internal_sended->get_lasts_by_date_for_user($id_user, 10);
$receiveds = $this->internal_received->get_lasts_by_date_for_user($id_user, 10);
$events = $this->internal_event->get_lasts_by_date_for_user($id_user, 10);
//Récupération du nombre de Sms envoyés et reçus depuis les 7 derniers jours
$nb_sendeds_by_day = $this->internal_sended->count_by_day_since_for_user($id_user, $formated_date);
$nb_receiveds_by_day = $this->internal_received->count_by_day_since_for_user($id_user, $formated_date);
//Récupération du nombre de Sms envoyés et reçus depuis 1 mois jours ou depuis le début du quota si il existe
//Création de la date d'il y a 30 jours
$now = new \DateTime();
$one_month = new \DateInterval('P1M');
$stats_start_date = clone $now;
$stats_start_date->sub($one_month);
$stats_start_date_formated = $stats_start_date->format('Y-m-d');
//If user have a quota and the quota start before today, use quota start date instead
$quota_limit = false;
$quota = $this->internal_quota->get_user_quota($id_user);
if ($quota && (new \DateTime($quota['start_date']) <= $now) && (new \DateTime($quota['expiration_date']) > $now))
{
$quota_limit = $quota['credit'] + $quota['additional'];
$stats_start_date = new \DateTime($quota['start_date']);
$stats_start_date_formated = $stats_start_date->format('Y-m-d');
}
$nb_sendeds_by_day = $this->internal_sended->count_by_day_since_for_user($id_user, $stats_start_date_formated);
$nb_receiveds_by_day = $this->internal_received->count_by_day_since_for_user($id_user, $stats_start_date_formated);
//On va traduire ces données pour les afficher en graphique
$array_area_chart = [];
$today_less_7_day = new \DateTime();
$today_less_7_day->sub(new \DateInterval('P7D'));
$increment_day = new \DateInterval('P1D');
$date = clone $stats_start_date;
$one_day = new \DateInterval('P1D');
$i = 0;
//On va construire un tableau avec la date en clef, et les données pour chaque date
while ($i < 7)
while ($date <= $now)
{
$today_less_7_day->add($increment_day);
++$i;
$date_f = $today_less_7_day->format('Y-m-d');
$date_f = $date->format('Y-m-d');
$array_area_chart[$date_f] = [
'period' => $date_f,
'sendeds' => 0,
'receiveds' => 0,
];
$date->add($one_day);
}
$total_sendeds = 0;
@ -112,8 +126,9 @@ namespace controllers\publics;
$total_receiveds += $nb_received;
}
$avg_sendeds = round($total_sendeds / 7, 2);
$avg_receiveds = round($total_receiveds / 7, 2);
$nb_days = $stats_start_date->diff($now)->days + 1;
$avg_sendeds = round($total_sendeds / $nb_days, 2);
$avg_receiveds = round($total_receiveds / $nb_days, 2);
$array_area_chart = array_values($array_area_chart);
@ -126,6 +141,7 @@ namespace controllers\publics;
'nb_unreads' => $nb_unreads,
'avg_sendeds' => $avg_sendeds,
'avg_receiveds' => $avg_receiveds,
'quota_limit' => $quota_limit,
'sendeds' => $sendeds,
'receiveds' => $receiveds,
'events' => $events,

View File

@ -125,7 +125,7 @@ namespace controllers\publics;
}
$message = [
'uid' => 'sended-' . $sended['id'],
'uid' => 'sended-' . $sended['id'],
'date' => htmlspecialchars($sended['at']),
'text' => htmlspecialchars($sended['text']),
'type' => 'sended',
@ -133,7 +133,6 @@ namespace controllers\publics;
'status' => $sended['status'],
];
$messages[] = $message;
}
@ -143,7 +142,7 @@ namespace controllers\publics;
{
$this->internal_received->mark_as_read_for_user($id_user, $received['id']);
}
$medias = [];
if ($received['mms'])
{
@ -155,7 +154,7 @@ namespace controllers\publics;
}
$messages[] = [
'uid' => 'received-' . $received['id'],
'uid' => 'received-' . $received['id'],
'date' => htmlspecialchars($received['at']),
'text' => htmlspecialchars($received['text']),
'type' => 'received',
@ -176,7 +175,7 @@ namespace controllers\publics;
}
$messages[] = [
'uid' => 'scheduled-' . $scheduled['id'],
'uid' => 'scheduled-' . $scheduled['id'],
'date' => htmlspecialchars($scheduled['at']),
'text' => htmlspecialchars($scheduled['text']),
'type' => 'inprogress',
@ -210,7 +209,7 @@ namespace controllers\publics;
* @param string $_POST['text'] : Le contenu du Sms
* @param string $_POST['destination'] : Number to send sms to
* @param string $_POST['id_phone'] : If of phone to send sms with
* @param array $_FILES['medias'] : Medias to upload and link to sms
* @param array $_FILES['medias'] : Medias to upload and link to sms
*
* @return string : json string Le statut de l'envoi
*/
@ -237,9 +236,9 @@ namespace controllers\publics;
$destination = $_POST['destination'] ?? false;
$id_phone = $_POST['id_phone'] ?? false;
$files = $_FILES['medias'] ?? false;
//Iterate over files to re-create individual $_FILES array
$files_arrays = [];
$files_arrays = [];
if ($files && is_array($files['name']))
{
foreach ($files as $property_name => $files_values)
@ -248,7 +247,7 @@ namespace controllers\publics;
{
if (!isset($files_arrays[$file_key]))
{
$files_arrays[$file_key] = [];
$files_arrays[$file_key] = [];
}
$files_arrays[$file_key][$property_name] = $property_value;
@ -259,7 +258,7 @@ namespace controllers\publics;
//Remove empty files input
foreach ($files_arrays as $key => $file)
{
if ($file['error'] === UPLOAD_ERR_NO_FILE)
if (UPLOAD_ERR_NO_FILE === $file['error'])
{
unset($files_arrays[$key]);
}
@ -288,7 +287,6 @@ namespace controllers\publics;
$id_phone = null;
}
//If mms is enable and we have medias uploaded
$media_ids = [];
if ($_SESSION['user']['settings']['mms'] && $files_arrays)

View File

@ -75,12 +75,12 @@ class Phone extends \descartes\Controller
{
$phone['callback_status'] = \descartes\Router::url('Callback', 'update_sended_status', ['adapter_uid' => $adapter['meta_uid']], ['api_key' => $api_key]);
}
if ($adapter['meta_support_inbound_call_callback'])
{
$phone['callback_inbound_call'] = \descartes\Router::url('Callback', 'inbound_call', ['id_phone' => $phone['id']], ['api_key' => $api_key]);
}
if ($adapter['meta_support_end_call_callback'])
{
$phone['callback_end_call'] = \descartes\Router::url('Callback', 'end_call', ['id_phone' => $phone['id']], ['api_key' => $api_key]);

View File

@ -242,7 +242,7 @@ namespace controllers\publics;
* @param ?array $_POST['contacts'] : Numbers to send the message to
* @param ?array $_POST['groups'] : Numbers to send the message to
* @param ?array $_POST['conditional_groups'] : Numbers to send the message to
* @param ?array $_FILES['medias'] : The media to link to a scheduled
* @param ?array $_FILES['medias'] : The media to link to a scheduled
*/
public function create($csrf)
{
@ -265,7 +265,7 @@ namespace controllers\publics;
$files = $_FILES['medias'] ?? false;
//Iterate over files to re-create individual $_FILES array
$files_arrays = [];
$files_arrays = [];
if ($files && is_array($files['name']))
{
foreach ($files as $property_name => $files_values)
@ -281,11 +281,11 @@ namespace controllers\publics;
}
}
}
//Remove empty files input
foreach ($files_arrays as $key => $file)
{
if ($file['error'] === UPLOAD_ERR_NO_FILE)
if (UPLOAD_ERR_NO_FILE === $file['error'])
{
unset($files_arrays[$key]);
}
@ -325,7 +325,7 @@ namespace controllers\publics;
return $this->redirect(\descartes\Router::url('Scheduled', 'add'));
}
//If mms is enable and we have medias uploaded
$media_ids = [];
if ($_SESSION['user']['settings']['mms'] && $files_arrays)
@ -339,15 +339,16 @@ namespace controllers\publics;
catch (\Exception $e)
{
\FlashMessage\FlashMessage::push('danger', 'Impossible d\'upload et d\'enregistrer le fichier ' . $file['name'] . ':' . $e->getMessage());
return $this->redirect(\descartes\Router::url('Scheduled', 'add'));
}
$media_ids[] = $new_media_id;
$media_ids[] = $new_media_id;
}
}
$mms = (bool) count($media_ids);
$scheduled_id = $this->internal_scheduled->create($id_user, $at, $text, $id_phone, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups, $media_ids);
if (!$scheduled_id)
{
@ -403,7 +404,7 @@ namespace controllers\publics;
}
//Iterate over files to re-create individual $_FILES array
$files_arrays = [];
$files_arrays = [];
if ($files && is_array($files['name']))
{
foreach ($files as $property_name => $files_values)
@ -423,7 +424,7 @@ namespace controllers\publics;
//Remove empty files input
foreach ($files_arrays as $key => $file)
{
if ($file['error'] === UPLOAD_ERR_NO_FILE)
if (UPLOAD_ERR_NO_FILE === $file['error'])
{
unset($files_arrays[$key]);
}
@ -456,7 +457,7 @@ namespace controllers\publics;
{
continue;
}
//If mms is enable and we have medias uploaded
if ($_SESSION['user']['settings']['mms'] && $files_arrays)
{
@ -471,7 +472,7 @@ namespace controllers\publics;
continue 2;
}
$media_ids[] = $new_media_id;
$media_ids[] = $new_media_id;
}
}
@ -488,7 +489,7 @@ namespace controllers\publics;
$mms = (bool) count($media_ids);
$this->internal_scheduled->update_for_user($id_user, $id_scheduled, $at, $text, $id_phone, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups, $media_ids);
$nb_update++;
++$nb_update;
}
if ($nb_update !== \count($scheduleds))

View File

@ -39,7 +39,8 @@ namespace controllers\publics;
*
* @param string $setting_name : Name of the setting to modify
* @param $csrf : CSRF token
* @param string $_POST['setting_value'] : Setting's new value
* @param string $_POST['setting_value'] : Setting's new value
* @param bool $_POST['allow_no_value'] : Default false, if true then allow $_POST['setting_value'] to dont exists, and treat it as empty string
*
* @return boolean;
*/
@ -53,6 +54,13 @@ namespace controllers\publics;
}
$setting_value = $_POST['setting_value'] ?? false;
$allow_no_value = $_POST['allow_no_value'] ?? false;
//if no value allowed and no value fund, default to ''
if ($allow_no_value && (false === $setting_value))
{
$setting_value = '';
}
if (false === $setting_value)
{
@ -61,6 +69,12 @@ namespace controllers\publics;
return $this->redirect(\descartes\Router::url('Setting', 'show'));
}
//If setting is an array, join with comas
if (is_array($setting_value))
{
$setting_value = json_encode($setting_value);
}
$update_setting_result = $this->internal_setting->update_for_user($_SESSION['user']['id'], $setting_name, $setting_value);
if (false === $update_setting_result)
{

View File

@ -15,6 +15,7 @@ namespace controllers\publics;
{
private $internal_contact;
private $internal_templating;
private $internal_quota;
/**
* Cette fonction est appelée avant toute les autres :
@ -27,6 +28,7 @@ namespace controllers\publics;
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_contact = new \controllers\internals\Contact($bdd);
$this->internal_templating = new \controllers\internals\Templating();
$this->internal_quota = new \controllers\internals\Quota($bdd);
\controllers\internals\Tool::verifyconnect();
}
@ -44,6 +46,7 @@ namespace controllers\publics;
$return = [
'success' => false,
'result' => 'Une erreur inconnue est survenue.',
'estimation_credit' => 0,
];
$template = $_POST['template'] ?? false;
@ -79,11 +82,15 @@ namespace controllers\publics;
$result = $this->internal_templating->render($template, $data);
$return = $result;
if (!trim($result['result']))
{
$return['result'] = 'Message vide, il ne sera pas envoyé.';
}
//Add credit estimation
$return['estimation_credit'] = $this->internal_quota->compute_credits_for_message($return['result']);
echo json_encode($return);
return true;

View File

@ -17,6 +17,7 @@ namespace controllers\publics;
class User extends \descartes\Controller
{
private $internal_user;
private $internal_quota;
/**
* Cette fonction est appelée avant toute les autres :
@ -28,6 +29,7 @@ class User extends \descartes\Controller
{
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_user = new \controllers\internals\User($bdd);
$this->internal_quota = new \controllers\internals\Quota($bdd);
\controllers\internals\Tool::verifyconnect();
@ -51,6 +53,24 @@ class User extends \descartes\Controller
public function list_json()
{
$entities = $this->internal_user->list();
foreach ($entities as &$entity)
{
$quota_percentage = $this->internal_quota->get_usage_percentage($entity['id']);
$entity['quota_percentage'] = $quota_percentage * 100;
$quota = $this->internal_quota->get_user_quota($entity['id']);
if (!$quota)
{
continue;
}
if (new \DateTime() > new \DateTime($quota['expiration_date']))
{
$entity['quota_expired_at'] = $quota['expiration_date'];
}
}
header('Content-Type: application/json');
echo json_encode(['data' => $entities]);
}
@ -58,9 +78,9 @@ class User extends \descartes\Controller
/**
* Update status of users.
*
* @param array int $_GET['ids'] : User ids
* @param array int $_GET['user_ids'] : User ids
* @param mixed $csrf
* @param int $status : 1 -> active, 0 -> suspended
* @param int $status : 1 -> active, 0 -> suspended
*
* @return boolean;
*/
@ -82,7 +102,7 @@ class User extends \descartes\Controller
$status = \models\User::STATUS_ACTIVE;
}
$ids = $_GET['ids'] ?? [];
$ids = $_GET['user_ids'] ?? [];
foreach ($ids as $id)
{
$this->internal_user->update_status($id, $status);
@ -94,7 +114,7 @@ class User extends \descartes\Controller
/**
* Cette fonction va supprimer une liste de users.
*
* @param array int $_GET['ids'] : Les id des useres à supprimer
* @param array int $_GET['user_ids'] : Les id des useres à supprimer
* @param mixed $csrf
*
* @return boolean;
@ -115,7 +135,7 @@ class User extends \descartes\Controller
return $this->redirect(\descartes\Router::url('User', 'list'));
}
$ids = $_GET['ids'] ?? [];
$ids = $_GET['user_ids'] ?? [];
foreach ($ids as $id)
{
$this->internal_user->delete($id);
@ -129,18 +149,28 @@ class User extends \descartes\Controller
*/
public function add()
{
return $this->render('user/add');
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:00');
return $this->render('user/add', ['now' => $now]);
}
/**
* Cette fonction insert un nouveau user.
*
* @param $csrf : Le jeton CSRF
* @param string $_POST['email'] : L'email de l'utilisateur
* @param string $_POST['email_confirm'] : Verif de l'email de l'utilisateur
* @param optional string $_POST['password'] : Le mot de passe de l'utilisateur (si vide, généré automatiquement)
* @param optional string $_POST['password_confirm'] : Confirmation du mot de passe de l'utilisateur
* @param optional boolean $_POST['admin'] : Si vrai, l'utilisateur est admin, si vide non
* @param string $_POST['email'] : User email
* @param optional string $_POST['password'] : User password, (if empty the password is randomly generated)
* @param optional boolean $_POST['admin'] : If true user is admin
* @param optional boolean $_POST['quota_enable'] : If true create a quota for the user
* @param bool $_POST['quota_enable'] : If true create a quota for the user
* @param optional int $_POST['quota_credit'] : credit for quota
* @param optional int $_POST['quota_additional'] : additional credit
* @param optional string $_POST['quota_start_date'] : quota beginning date
* @param optional string $_POST['quota_renewal_interval'] : period to use on renewal to calculate new expiration date. Also use to calculate first expiration date.
* @param optional boolean $_POST['quota_auto_renew'] : Should the quota be automatically renewed on expiration
* @param optional boolean $_POST['quota_report_unused'] : Should unused credit be reported next month
* @param optional boolean $_POST['quota_report_unused_additional'] : Should unused additional credit be transfered next month
*/
public function create($csrf)
{
@ -155,6 +185,14 @@ class User extends \descartes\Controller
$password = !empty($_POST['password']) ? $_POST['password'] : \controllers\internals\Tool::generate_password(rand(6, 12));
$admin = $_POST['admin'] ?? false;
$status = 'active';
$quota_enable = $_POST['quota_enable'] ?? false;
$quota_credit = $_POST['quota_credit'] ?? false;
$quota_additional = $_POST['quota_additional'] ?? false;
$quota_start_date = $_POST['quota_start_date'] ?? false;
$quota_renew_interval = $_POST['quota_renew_interval'] ?? false;
$quota_auto_renew = $_POST['quota_auto_renew'] ?? false;
$quota_report_unused = $_POST['quota_report_unused'] ?? false;
$quota_report_unused_additional = $_POST['quota_report_unused_additional'] ?? false;
if (!$email)
{
@ -170,10 +208,42 @@ class User extends \descartes\Controller
return $this->redirect(\descartes\Router::url('User', 'add'));
}
$id_user = $this->internal_user->create($email, $password, $admin);
//Forge quota for user if needed
$quota = null;
if ($quota_enable)
{
$quota = [];
$quota['credit'] = (int) $quota_credit;
$quota['additional'] = (int) $quota_additional;
if (false === $quota_start_date || !\controllers\internals\Tool::validate_date($quota_start_date, 'Y-m-d H:i:s'))
{
\FlashMessage\FlashMessage::push('danger', 'Vous devez définir une date de début valide pour le quota.');
return $this->redirect(\descartes\Router::url('User', 'add'));
}
$quota['start_date'] = new \DateTime($quota_start_date);
if (false === $quota_renew_interval || !\controllers\internals\Tool::validate_period($quota_renew_interval))
{
\FlashMessage\FlashMessage::push('danger', 'Vous devez définir une durée de quota parmis la liste proposée.');
return $this->redirect(\descartes\Router::url('User', 'add'));
}
$quota['renew_interval'] = $quota_renew_interval;
$quota['expiration_date'] = clone $quota['start_date'];
$quota['expiration_date']->add(new \DateInterval($quota_renew_interval));
$quota['auto_renew'] = (bool) $quota_auto_renew;
$quota['report_unused'] = (bool) $quota_report_unused;
$quota['report_unused_additional'] = (bool) $quota_report_unused_additional;
}
$id_user = $this->internal_user->create($email, $password, $admin, null, \models\User::STATUS_ACTIVE, true, $quota);
if (!$id_user)
{
\FlashMessage\FlashMessage::push('danger', 'Impossible de créer ce user.');
\FlashMessage\FlashMessage::push('danger', 'Impossible de créer cet utilisateur.');
return $this->redirect(\descartes\Router::url('User', 'add'));
}
@ -189,4 +259,152 @@ class User extends \descartes\Controller
return $this->redirect(\descartes\Router::url('User', 'list'));
}
/**
* Return the edition page for the users.
*
* @param int... $ids : users ids
*/
public function edit()
{
$ids = $_GET['user_ids'] ?? [];
$id_user = $_SESSION['user']['id'];
$users = $this->internal_user->gets_in_by_id($ids);
if (!$users)
{
return $this->redirect(\descartes\Router::url('User', 'list'));
}
foreach ($users as &$user)
{
$user['quota'] = $this->internal_quota->get_user_quota($user['id']);
}
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:00');
$this->render('user/edit', [
'users' => $users,
'now' => $now,
]);
}
/**
* Update a list of users.
*
* @param $csrf : Le jeton CSRF
* @param array $_POST['users'] : Array of the users and new values, id as key. Quota may also be defined.
*/
public function update($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('User', 'add'));
}
$nb_update = 0;
$users = $_POST['users'] ?? [];
foreach ($users as $id_user => $user)
{
$email = $user['email'] ?? false;
$password = !empty($user['password']) ? $user['password'] : null;
$admin = $user['admin'] ?? false;
$quota_enable = $user['quota_enable'] ?? false;
$quota_consumed = $user['quota_consumed'] ?? false;
$quota_credit = $user['quota_credit'] ?? false;
$quota_additional = $user['quota_additional'] ?? false;
$quota_start_date = $user['quota_start_date'] ?? false;
$quota_renew_interval = $user['quota_renew_interval'] ?? false;
$quota_auto_renew = $user['quota_auto_renew'] ?? false;
$quota_report_unused = $user['quota_report_unused'] ?? false;
$quota_report_unused_additional = $user['quota_report_unused_additional'] ?? false;
if (!$email)
{
\FlashMessage\FlashMessage::push('danger', 'L\'utilisateur #' . (int) $id_user . ' n\'as pas pu être mis à jour car l\'adresse e-mail n\'as pas été fournie.');
continue;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL))
{
\FlashMessage\FlashMessage::push('danger', 'L\'utilisateur #' . (int) $id_user . ' n\'as pas pu être mis à jour car l\'adresse e-mail fournie n\'est pas valide.');
return $this->redirect(\descartes\Router::url('User', 'add'));
}
//Forge quota for user if needed
$quota = false;
if ($quota_enable)
{
$quota = [];
$quota['credit'] = (int) $quota_credit;
$quota['consumed'] = (int) $quota_consumed;
$quota['additional'] = (int) $quota_additional;
if (false === $quota_start_date || !\controllers\internals\Tool::validate_date($quota_start_date, 'Y-m-d H:i:s'))
{
\FlashMessage\FlashMessage::push('danger', 'L\'utilisateur #' . (int) $id_user . ' n\'as pas pu être mis à jour car la date de début du quota associé n\'est pas valide.');
continue;
}
$quota['start_date'] = new \DateTime($quota_start_date);
if (false === $quota_renew_interval || !\controllers\internals\Tool::validate_period($quota_renew_interval))
{
\FlashMessage\FlashMessage::push('danger', 'L\'utilisateur #' . (int) $id_user . ' n\'as pas pu être mis à jour car la durée du quota associé n\'est pas valide.');
continue;
}
$quota['renew_interval'] = $quota_renew_interval;
$quota['expiration_date'] = clone $quota['start_date'];
$quota['expiration_date']->add(new \DateInterval($quota_renew_interval));
$quota['auto_renew'] = (bool) $quota_auto_renew;
$quota['report_unused'] = (bool) $quota_report_unused;
$quota['report_unused_additional'] = (bool) $quota_report_unused_additional;
//Format dates
$quota['start_date'] = $quota['start_date']->format('Y-m-d H:i:s');
$quota['expiration_date'] = $quota['expiration_date']->format('Y-m-d H:i:s');
}
$updated_user = [
'email' => $email,
'admin' => $admin,
];
if ($password)
{
$updated_user['password'] = password_hash($password, PASSWORD_DEFAULT);
}
$success = $this->internal_user->update($id_user, $updated_user, $quota);
if (!$success)
{
\FlashMessage\FlashMessage::push('danger', 'L\'utilisateur #' . (int) $id_user . ' n\'as pas pu être mis à jour.');
continue;
}
++$nb_update;
}
if ($nb_update != count($users))
{
\FlashMessage\FlashMessage::push('danger', 'Certains utilisateurs n\'ont pas pu être mis à jour.');
return $this->redirect(\descartes\Router::url('User', 'list'));
}
\FlashMessage\FlashMessage::push('success', 'Tous les utilisateurs ont bien été mis à jour.');
return $this->redirect(\descartes\Router::url('User', 'list'));
}
}

View File

@ -0,0 +1,52 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddQuotas 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('quota');
$table->addColumn('id_user', 'integer', ['null' => false])
->addColumn('consumed', 'integer', ['null' => false, 'default' => 0])
->addColumn('credit', 'integer', ['null' => false])
->addColumn('additional', 'integer', ['null' => false, 'default' => 0])
->addColumn('report_unused', 'boolean', ['null' => false])
->addColumn('report_unused_additional', 'boolean', ['null' => false])
->addColumn('auto_renew', 'boolean', ['null' => false, 'default' => false])
->addColumn('renew_interval', 'string', ['null' => false, 'default' => NULL])
->addColumn('start_date', 'datetime', ['null' => false])
->addColumn('expiration_date', 'datetime', ['null' => false])
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
->addColumn('updated_at', 'timestamp', ['null' => true, 'update' => 'CURRENT_TIMESTAMP'])
->addForeignKey('id_user', 'user', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addIndex(['id_user'], ['unique' => true])
->create();
}
}

View File

@ -0,0 +1,48 @@
<?php
use Phinx\Migration\AbstractMigration;
class CreateNewSettingsDefaults 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()
{
$query = 'INSERT INTO setting (id_user, name, value)
SELECT id, \'hide_menus\', \'\' FROM user WHERE id NOT IN (SELECT id_user FROM setting WHERE name = \'hide_menus\')';
$this->execute($query);
$query = 'INSERT INTO setting (id_user, name, value)
SELECT id, \'alert_quota_limit_reached\', \'1\' FROM user WHERE id NOT IN (SELECT id_user FROM setting WHERE name = \'alert_quota_limit_reached\')';
$this->execute($query);
$query = 'INSERT INTO setting (id_user, name, value)
SELECT id, \'alert_quota_limit_close\', \'0.9\' FROM user WHERE id NOT IN (SELECT id_user FROM setting WHERE name = \'alert_quota_limit_close\')';
$this->execute($query);
}
}

View File

@ -41,6 +41,16 @@
'subject' => 'Vous avez reçu un SMS',
'template' => 'email/transfer-sms',
],
'EMAIL_QUOTA_LIMIT_CLOSE' => [
'type' => 'email_quota_limit_close',
'subject' => 'Vous avez presque atteint votre limite de SMS',
'template' => 'email/quota-limit-close',
],
'EMAIL_QUOTA_LIMIT_REACHED' => [
'type' => 'email_quota_limit_reached',
'subject' => 'Vous avez atteint votre limite de SMS',
'template' => 'email/quota-limit-reached',
],
//Phone messages types
'QUEUE_ID_PHONE_PREFIX' => ftok(__FILE__, 'p'),
@ -70,6 +80,9 @@
'default_phone_country' => 'fr',
'authorized_phone_country' => 'fr,be,ca',
'mms' => 1,
'alert_quota_limit_reached' => 1,
'alert_quota_limit_close' => 0.9,
'hide_menus' => '',
],
];

View File

@ -12,13 +12,13 @@
namespace models;
/**
* Manage bdd operations for calls
*/
* Manage bdd operations for calls.
*/
class Call extends StandardModel
{
const DIRECTION_INBOUND = 'inbound';
const DIRECTION_OUTBOUND = 'outbound';
/**
* Return a list of call for a user.
* Add a column contact_name and phone_name when available.
@ -62,11 +62,11 @@ namespace models;
}
/**
* Get a call for a user by his phone and uid
*
* @param int $id_user : user id
* Get a call for a user by his phone and uid.
*
* @param int $id_user : user id
* @param int $id_phone : phone id
* @param int $uid : call uid
* @param int $uid : call uid
*
* @return array : the call or an empty array
*/

View File

@ -26,6 +26,32 @@ namespace models;
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.
*/

View File

@ -39,7 +39,7 @@ namespace models;
return $this->_run_query($query, $params);
}
/**
* Return all medias for a sended.
*
@ -63,7 +63,7 @@ namespace models;
return $this->_run_query($query, $params);
}
/**
* Return all medias for a received.
*
@ -87,16 +87,16 @@ namespace models;
return $this->_run_query($query, $params);
}
/**
* Link a media to a scheduled
*
* @param int $id_media : Media id
* Link a media to a scheduled.
*
* @param int $id_media : Media id
* @param int $id_scheduled : Scheduled id
*
* @return bool | int
*/
public function insert_media_scheduled (int $id_media, int $id_scheduled)
public function insert_media_scheduled(int $id_media, int $id_scheduled)
{
$entry = [
'id_media' => $id_media,
@ -105,16 +105,16 @@ namespace models;
return $this->_insert('media_scheduled', $entry) ? $this->_last_id() : false;
}
/**
* Link a media to a received
*
* @param int $id_media : Media id
* Link a media to a received.
*
* @param int $id_media : Media id
* @param int $id_received : Scheduled id
*
* @return bool | int
*/
public function insert_media_received (int $id_media, int $id_received)
public function insert_media_received(int $id_media, int $id_received)
{
$entry = [
'id_media' => $id_media,
@ -123,16 +123,16 @@ namespace models;
return $this->_insert('media_received', $entry) ? $this->_last_id() : false;
}
/**
* Link a media to a sended
*
* @param int $id_media : Media id
* Link a media to a sended.
*
* @param int $id_media : Media id
* @param int $id_sended : Scheduled id
*
* @return bool | int
*/
public function insert_media_sended (int $id_media, int $id_sended)
public function insert_media_sended(int $id_media, int $id_sended)
{
$entry = [
'id_media' => $id_media,
@ -141,16 +141,16 @@ namespace models;
return $this->_insert('media_sended', $entry) ? $this->_last_id() : false;
}
/**
* Unlink a media of a scheduled
*
* @param int $id_media : Media id
* Unlink a media of a scheduled.
*
* @param int $id_media : Media id
* @param int $id_scheduled : Scheduled id
*
* @return bool | int
*/
public function delete_media_scheduled (int $id_media, int $id_scheduled)
public function delete_media_scheduled(int $id_media, int $id_scheduled)
{
$where = [
'id_media' => $id_media,
@ -161,14 +161,14 @@ namespace models;
}
/**
* Unlink a media of a received
*
* @param int $id_media : Media id
* Unlink a media of a received.
*
* @param int $id_media : Media id
* @param int $id_received : Scheduled id
*
* @return bool | int
*/
public function delete_media_received (int $id_media, int $id_received)
public function delete_media_received(int $id_media, int $id_received)
{
$where = [
'id_media' => $id_media,
@ -177,16 +177,16 @@ namespace models;
return $this->_delete('media_received', $where);
}
/**
* Unlink a media of a sended
*
* @param int $id_media : Media id
* Unlink a media of a sended.
*
* @param int $id_media : Media id
* @param int $id_sended : Scheduled id
*
* @return bool | int
*/
public function delete_media_sended (int $id_media, int $id_sended)
public function delete_media_sended(int $id_media, int $id_sended)
{
$where = [
'id_media' => $id_media,
@ -195,16 +195,15 @@ namespace models;
return $this->_delete('media_sended', $where);
}
/**
* Unlink all medias of a scheduled
*
* Unlink all medias of a scheduled.
*
* @param int $id_scheduled : Scheduled id
*
* @return bool | int
*/
public function delete_all_for_scheduled (int $id_scheduled)
public function delete_all_for_scheduled(int $id_scheduled)
{
$where = [
'id_scheduled' => $id_scheduled,
@ -212,15 +211,15 @@ namespace models;
return $this->_delete('media_scheduled', $where);
}
/**
* Unlink all medias of a received
*
* Unlink all medias of a received.
*
* @param int $id_received : Scheduled id
*
* @return bool | int
*/
public function delete_all_for_received (int $id_received)
public function delete_all_for_received(int $id_received)
{
$where = [
'id_received' => $id_received,
@ -230,13 +229,13 @@ namespace models;
}
/**
* Unlink all medias of a sended
*
* Unlink all medias of a sended.
*
* @param int $id_sended : Scheduled id
*
* @return bool | int
*/
public function delete_all_for_sended (int $id_sended)
public function delete_all_for_sended(int $id_sended)
{
$where = [
'id_sended' => $id_sended,
@ -246,10 +245,11 @@ namespace models;
}
/**
* Find all unused medias
* Find all unused medias.
*
* @return array
*/
public function gets_unused ()
public function gets_unused()
{
$query = '
SELECT `media`.*
@ -264,7 +264,7 @@ namespace models;
AND `media_received`.id IS NULL
AND `media_scheduled`.id IS NULL
';
return $this->_run_query($query);
}

200
models/Quota.php Normal file
View File

@ -0,0 +1,200 @@
<?php
/*
* This file is part of RaspiSMS.
*
* (c) Pierre-Lin Bonnemaison <plebwebsas@gmail.com>
*
* This source file is subject to the GPL-3.0 license that is bundled
* with this source code in the file LICENSE.
*/
namespace models;
class Quota extends StandardModel
{
/**
* Return the quota for a user if it exists.
*
* @param int $id_user : user id
*
* @return array : quota if found, else empty array
*/
public function get_user_quota(int $id_user)
{
return $this->_select_one($this->get_table_name(), ['id_user' => $id_user]);
}
/**
* Get remaining credit for a date
* if no quota for this user return max int.
*
* @param int $id_user : User id
* @param \DateTime $at : date to get credit at
*
* @return int : number of remaining credits
*/
public function get_remaining_credit(int $id_user, \DateTime $at): int
{
$query = '
SELECT (credit + additional - consumed) AS remaining_credit
FROM quota
WHERE id_user = :id_user
AND start_date <= :at
AND expiration_date > :at';
$params = [
'id_user' => $id_user,
'at' => $at->format('Y-m-d H:i:s'),
];
$result = $this->_run_query($query, $params);
return $result[0]['remaining_credit'] ?? PHP_INT_MAX;
}
/**
* Get credit usage percent for a date
* if no quota for this user return 0.
*
* @param int $id_user : User id
* @param \DateTime $at : date to get usage percent at
*
* @return float : percent of used credits
*/
public function get_usage_percentage(int $id_user, \DateTime $at): float
{
$query = '
SELECT (consumed / (credit + additional)) AS usage_percentage
FROM quota
WHERE id_user = :id_user
AND start_date <= :at
AND expiration_date > :at';
$params = [
'id_user' => $id_user,
'at' => $at->format('Y-m-d H:i:s'),
];
$result = $this->_run_query($query, $params);
return (float) ($result[0]['usage_percentage'] ?? 0);
}
/**
* Consume some credit for a user.
*
* @param int $id_user : User id
* @param int $quantity : Number of credits to consume
*
* @return bool
*/
public function consume_credit(int $id_user, int $quantity): int
{
$query = '
UPDATE quota
SET consumed = consumed + :quantity
WHERE id_user = :id_user';
$params = [
'id_user' => $id_user,
'quantity' => $quantity,
];
return (bool) $this->_run_query($query, $params, \descartes\Model::ROWCOUNT);
}
/**
* Get all quotas we need to send an alert for close limit to users they belongs to
* do not return quotas when user already had an event QUOTA_LIMIT_CLOSE since quota start_date.
*/
public function get_quotas_for_limit_close(): array
{
$query = '
SELECT quota.*
FROM quota
INNER JOIN setting
ON (
quota.id_user = setting.id_user
AND setting.NAME = :setting_name
AND setting.value != 0
)
WHERE
quota.consumed / ( quota.credit + quota.additional ) >= setting.value
AND (
SELECT COUNT(id)
FROM event
WHERE
id_user = quota.id_user
AND type = :event_type
AND at >= quota.start_date
) = 0;
';
$params = [
'setting_name' => 'alert_quota_limit_close',
'event_type' => 'QUOTA_LIMIT_CLOSE',
];
return $this->_run_query($query, $params);
}
/**
* Get all quotas we need to send an alert for limit reached to users they belongs to
* do not return quotas when user already had an event QUOTA_LIMIT_REACHED since quota start_date.
*/
public function get_quotas_for_limit_reached(): array
{
$query = '
SELECT quota.*
FROM quota
INNER JOIN setting
ON (
quota.id_user = setting.id_user
AND setting.NAME = :setting_name
AND setting.value = 1
)
WHERE
quota.consumed >= (quota.credit + quota.additional)
AND (
SELECT COUNT(id)
FROM event
WHERE
id_user = quota.id_user
AND type = :event_type
AND at >= quota.start_date
) = 0;
';
$params = [
'setting_name' => 'alert_quota_limit_reached',
'event_type' => 'QUOTA_LIMIT_REACHED',
];
return $this->_run_query($query, $params);
}
/**
* Get list of quotas to be renewed as to a date.
*
* @param \DateTime $at : Date to get quotas to be renewed before
*/
public function get_quotas_to_be_renewed(\DateTime $at): array
{
$at = $at->format('Y-m-d H:i:s');
$where = [
'<=expiration_date' => $at,
'auto_renew' => true,
];
return $this->_select('quota', $where);
}
/**
* Return table name.
*/
protected function get_table_name(): string
{
return 'quota';
}
}

View File

@ -178,13 +178,13 @@ namespace models;
return $this->_run_query($query, $params);
}
/**
* Return sendeds for an origin and a user since a date.
*
* @param int $id_user : User id
* @param int $id_user : User id
* @param string $since : Date we want messages since
* @param string $origin : Number who sent the message
* @param string $origin : Number who sent the message
*
* @return array
*/

View File

@ -233,8 +233,7 @@ namespace models;
return $this->_run_query($query, $params);
}
/**
* Get messages scheduled after a date for a number and a user.
*

View File

@ -112,13 +112,12 @@ namespace models;
return $this->_run_query($query, $params);
}
/**
* Return sendeds for an destination and a user since a date.
*
* @param int $id_user : User id
* @param string $since : Date we want messages since
* @param string $since : Date we want messages since
* @param string $destination : Number who sent the message
*
* @return array

View File

@ -31,6 +31,33 @@ namespace models;
return $this->_select_one('user', ['id' => $id]);
}
/**
* Find user by ids.
*
* @param array $ids : users ids
*
* @return array
*/
public function gets_in_by_id($ids)
{
if (!$ids)
{
return [];
}
$query = '
SELECT * FROM `user`
WHERE id ';
$params = [];
$generated_in = $this->_generate_in_from_array($ids);
$query .= $generated_in['QUERY'];
$params = $generated_in['PARAMS'];
return $this->_run_query($query, $params);
}
/**
* Find a user using his email.
*

View File

@ -16,6 +16,8 @@ namespace models;
const TYPE_SEND_SMS = 'send_sms';
const TYPE_RECEIVE_SMS = 'receive_sms';
const TYPE_INBOUND_CALL = 'inbound_call';
const TYPE_QUOTA_LEVEL_ALERT = 'quota_level';
const TYPE_QUOTA_REACHED = 'quota_reached';
/**
* Find all webhooks for a user and for a type of webhook.

View File

@ -145,6 +145,8 @@
'add' => '/user/add/',
'create' => '/user/create/{csrf}/',
'delete' => '/user/delete/{csrf}/',
'edit' => '/user/edit/',
'update' => '/user/update/{csrf}/',
'update_status' => '/user/delete/{status}/{csrf}/',
],

View File

@ -53,7 +53,7 @@
<form action="<?php echo \descartes\Router::url('Account', 'update_password', ['csrf' => $_SESSION['csrf']]); ?>" method="POST">
<div class="form-group">
<label>Mot de passe :</label>
<input name="password" type="password" class="form-control" placeholder="Nouveau mot de passe" />
<input name="password" type="password" class="form-control" placeholder="Nouveau mot de passe" autocomplete="new-password" />
</div>
<div class="text-center">
<button class="btn btn-success">Mettre à jour les données</button>
@ -81,7 +81,22 @@
</div>
<?php } ?>
</div>
<div class="col-xs-12 col-md-6">
<div class="col-xs-12 col-md-6">
<?php if ($quota) { ?>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-euro fa-fw"></i> Quota de SMS</h4>
</div>
<div class="panel-body">
<strong>Crédit de base :</strong> <?php $this->s($quota['credit']); ?><br/>
<strong>Crédit additionel :</strong> <?php $this->s($quota['additional']); ?><br/>
<strong>Crédit consommés :</strong> <?php $this->s($quota['consumed']); ?> (<?= $quota_percent * 100; ?>%)<br/>
<strong>Renouvellement automatique :</strong> <?php $this->s(($quota['auto_renew'] ? 'Oui, renouvellement le ' : 'Non, fin le ') . $quota['expiration_date']); ?><br/>
<strong>Report des crédits non utilisés :</strong> <?= $quota['report_unused'] ? 'Oui' : 'Non'; ?><br/>
<strong>Report des crédits additionels non utilisés :</strong> <?= $quota['report_unused_additional'] ? 'Oui' : 'Non'; ?><br/>
</div>
</div>
<?php } ?>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-at fa-fw"></i> Modifier e-mail</h4>

View File

@ -118,11 +118,15 @@
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel panel-default dashboard-panel-chart">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-area-chart fa-fw"></i> Activité de la semaine : </h3>
<span style="color: #5CB85C;">SMS envoyés (moyenne = <?php echo $avg_sendeds; ?> par jour).</span><br/>
<span style="color: #EDAB4D">SMS reçus (moyenne = <?php echo $avg_receiveds; ?> par jour).</span>
<?php if ($quota_limit) { ?>
<br/>
<span style="color: #d9534f">Limite max de SMS sur la période (<?= $quota_limit; ?>).</span>
<?php } ?>
</div>
<div class="panel-body">
<div id="morris-area-chart"></div>
@ -250,8 +254,8 @@
ykeys: ['sendeds', 'receiveds'],
labels: ['SMS envoyés', 'SMS reçus'],
lineColors: ['#5CB85C', '#EDAB4D'],
goals: [<?php echo $avg_sendeds; ?>, <?php echo $avg_receiveds; ?>],
goalLineColors: ['#5CB85C', '#EDAB4D'],
goals: [<?php echo $avg_sendeds; ?>, <?php echo $avg_receiveds; ?><?= $quota_limit ? ',' . $quota_limit : ''; ?>],
goalLineColors: ['#5CB85C', '#EDAB4D', '#d9534f'],
goalStrokeWidth: 2,
pointSize: 4,
hideHover: 'auto',

View File

@ -0,0 +1,4 @@
Vous avez atteint <?php echo $percent * 100; ?>% de votre quota de SMS.
--------------------------------------------------------------------------------------------
Pour plus d'informations sur le système RaspiSMS, rendez-vous sur le site https://raspisms.fr

View File

@ -0,0 +1,5 @@
Vous avez épuisé votre quota de SMS, vous ne pourrez plus envoyer de SMS tant que votre quota de SMS n'aura pas été augmenté ou remis à zéro.
<?php if ($expiration_date ?? false) { ?>Votre quota sera remis à zéro le <?php $this->s($expiration_date); ?>.<?php } ?>
--------------------------------------------------------------------------------------------
Pour plus d'informations sur le système RaspiSMS, rendez-vous sur le site https://raspisms.fr

View File

@ -68,37 +68,51 @@
</li>
<?php } ?>
</ul>
</li>
<li>
<a href="javascript:;" data-toggle="collapse" data-target="#logs"><i class="fa fa-fw fa-file-text"></i> Logs <i class="fa fa-fw fa-caret-down"></i></a>
<ul id="logs" class="collapse <?php echo in_array($page, array('events', 'smsstop', 'calls')) ? 'in' : ''; ?>">
<li <?php echo $page == 'smsstop' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('SmsStop', 'list'); ?>"><i class="fa fa-fw fa-ban"></i> SMS STOP</a>
</li>
<li <?php echo $page == 'calls' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Call', 'list'); ?>"><i class="fa fa-fw fa-file-audio-o"></i> Appels</a>
</li>
<li <?php echo $page == 'events' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Event', 'list'); ?>"><i class="fa fa-fw fa-clock-o"></i> Évènements</a>
</li>
</ul>
</li>
<?php if (ENABLE_COMMAND) { ?>
<li <?php echo $page == 'commands' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Command', 'list'); ?>"><i class="fa fa-fw fa-terminal"></i> Commandes</a>
<?php if (!in_array('log', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li>
<a href="javascript:;" data-toggle="collapse" data-target="#logs"><i class="fa fa-fw fa-file-text"></i> Logs <i class="fa fa-fw fa-caret-down"></i></a>
<ul id="logs" class="collapse <?php echo in_array($page, array('events', 'smsstop', 'calls')) ? 'in' : ''; ?>">
<?php if (!in_array('smsstop', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li <?php echo $page == 'smsstop' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('SmsStop', 'list'); ?>"><i class="fa fa-fw fa-ban"></i> SMS STOP</a>
</li>
<?php } ?>
<?php if (!in_array('calls', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li <?php echo $page == 'calls' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Call', 'list'); ?>"><i class="fa fa-fw fa-file-audio-o"></i> Appels</a>
</li>
<?php } ?>
<?php if (!in_array('events', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li <?php echo $page == 'events' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Event', 'list'); ?>"><i class="fa fa-fw fa-clock-o"></i> Évènements</a>
</li>
<?php } ?>
</ul>
</li>
<?php } ?>
<?php if (ENABLE_COMMAND) { ?>
<?php if (!in_array('commands', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li <?php echo $page == 'commands' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Command', 'list'); ?>"><i class="fa fa-fw fa-terminal"></i> Commandes</a>
</li>
<?php } ?>
<?php } ?>
<?php if ($_SESSION['user']['settings']['webhook'] ?? false) { ?>
<li <?php echo $page == 'webhooks' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Webhook', 'list'); ?>"><i class="fa fa-fw fa-plug"></i> Webhooks</a>
</li>
<?php } ?>
<li <?php echo $page == 'phones' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Phone', 'list'); ?>"><i class="fa fa-fw fa-phone"></i> Téléphones</a>
</li>
<li <?php echo $page == 'settings' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Setting', 'show'); ?>"><i class="fa fa-fw fa-cogs"></i> Réglages</a>
</li>
<?php if (!in_array('phones', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li <?php echo $page == 'phones' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Phone', 'list'); ?>"><i class="fa fa-fw fa-phone"></i> Téléphones</a>
</li>
<?php } ?>
<?php if (!in_array('settings', json_decode($_SESSION['user']['settings']['hide_menus'], true) ?? [])) { ?>
<li <?php echo $page == 'settings' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('Setting', 'show'); ?>"><i class="fa fa-fw fa-cogs"></i> Réglages</a>
</li>
<?php } ?>
<?php if (\controllers\internals\Tool::is_admin()) { ?>
<li <?php echo $page == 'users' ? 'class="active"' : ''; ?>>
<a href="<?php echo \descartes\Router::url('User', 'list'); ?>"><i class="fa fa-fw fa-user"></i> Utilisateurs</a>

View File

@ -45,8 +45,8 @@
<label>Texte du SMS</label>
<?php if ($_SESSION['user']['settings']['templating']) { ?>
<p class="italic small help description-scheduled-text">
Vous pouvez utilisez des fonctionnalités de templating pour indiquer des valeures génériques qui seront remplacées par les données du contact au moment de l'envoie. Pour plus d'information, consultez la documentation sur <a href="#">l'utilisation des templates.</a><br/>
Vous pouvez obtenir une prévisualisation du résultat pour un contact en cliquant sur le boutton <b>"Prévisualiser"</b>.
Vous pouvez utilisez des fonctionnalités de templating pour indiquer des valeures génériques qui seront remplacées par les données du contact au moment de l'envoie. Pour plus d'information, consultez la documentation sur <a href="https://documentation.raspisms.fr/users/templating/overview.html" target="_blank">l'utilisation des templates.</a><br/>
Vous pouvez obtenir une prévisualisation du résultat pour un contact, ainsi qu'une estimation du nombre de crédits qui seront utilisés par SMS, en cliquant sur le boutton <b>"Prévisualiser"</b>.
</p>
<?php } ?>
<textarea name="text" class="form-control" required><?php $this->s($_SESSION['previous_http_post']['text'] ?? '') ?></textarea>
@ -67,7 +67,7 @@
<div class="form-group scheduled-media-group">
<label>Ajouter un média au SMS</label>
<p class="italic small help description-scheduled-media">
L'ajout d'un média nécessite un téléphone supportant l'envoi de MMS. Pour plus d'information, consultez la documentation sur <a href="#">l'utilisation des MMS.</a>.
L'ajout d'un média nécessite un téléphone supportant l'envoi de MMS. Pour plus d'information, consultez la documentation sur <a href="https://documentation.raspisms.fr/users/mms/overview.html" target="_blank">l'utilisation des MMS.</a>
</p>
<div class="form-group">
<input class="" name="medias[]" value="" type="file" multiple />
@ -141,6 +141,9 @@
</div>
<div class="modal-body">
<pre></pre>
<p class="credit-estimation-container bold">
Ce message devrait coûter <span class="credit-estimation-value"></span> crédits par destinataire.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
@ -262,6 +265,13 @@
data: data,
success: function (data) {
jQuery('#scheduled-preview-text-modal').find('.modal-body pre').text(data.result);
if (data.estimation_credit !== 'undefined') {
jQuery('#scheduled-preview-text-modal').find('.modal-body .credit-estimation-value').text(data.estimation_credit);
} else {
jQuery('#scheduled-preview-text-modal').find('.modal-body .credit-estimation-value').text('0');
}
jQuery('#scheduled-preview-text-modal').modal({'keyboard': true});
},
dataType: 'json'

View File

@ -151,6 +151,9 @@
</div>
<div class="modal-body">
<pre></pre>
<p class="credit-estimation-container bold">
Ce message devrait coûter <span class="credit-estimation-value"></span> crédits par destinataire.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
@ -270,6 +273,13 @@
data: data,
success: function (data) {
jQuery('#scheduled-preview-text-modal').find('.modal-body pre').text(data.result);
if (data.estimation_credit !== 'undefined') {
jQuery('#scheduled-preview-text-modal').find('.modal-body .credit-estimation-value').text(data.estimation_credit);
} else {
jQuery('#scheduled-preview-text-modal').find('.modal-body .credit-estimation-value').text('0');
}
jQuery('#scheduled-preview-text-modal').modal({'keyboard': true});
},
dataType: 'json'

View File

@ -143,6 +143,25 @@
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-exclamation-triangle fa-fw"></i> Alerte limite de SMS atteinte</h4>
</div>
<div class="panel-body">
<form action="<?php echo \descartes\Router::url('Setting', 'update', ['setting_name' => 'alert_quota_limit_reached', 'csrf' => $_SESSION['csrf']]); ?>" method="POST">
<div class="form-group">
<label>Recevoir un e-mail quand la limite de SMS est atteinte :</label>
<select name="setting_value" class="form-control">
<option value="0">Non</option>
<option value="1" <?php echo $_SESSION['user']['settings']['alert_quota_limit_reached'] == 1 ? '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-heading">
<h4 class="panel-title"><i class="fa fa-question-circle fa-fw"></i> Affichage de l'aide</h4>
@ -275,6 +294,47 @@
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-exclamation fa-fw"></i> Alerte limite de SMS proche</h4>
</div>
<div class="panel-body">
<form action="<?php echo \descartes\Router::url('Setting', 'update', ['setting_name' => 'alert_quota_limit_close', 'csrf' => $_SESSION['csrf']]); ?>" method="POST">
<div class="form-group">
<label>Recevoir un e-mail quand le nombre de SMS envoyés dépasse un pourcentage de la limite : </label>
<select name="setting_value" class="form-control">
<option value="0">Désactivé</option>
<option value="0.7" <?php echo $_SESSION['user']['settings']['alert_quota_limit_close'] == 0.7 ? 'selected' : ''; ?>>70%</option>
<option value="0.75" <?php echo $_SESSION['user']['settings']['alert_quota_limit_close'] == 0.75 ? 'selected' : ''; ?>>75%</option>
<option value="0.8" <?php echo $_SESSION['user']['settings']['alert_quota_limit_close'] == 0.8 ? 'selected' : ''; ?>>80%</option>
<option value="0.85" <?php echo $_SESSION['user']['settings']['alert_quota_limit_close'] == 0.85 ? 'selected' : ''; ?>>85%</option>
<option value="0.9" <?php echo $_SESSION['user']['settings']['alert_quota_limit_close'] == 0.9 ? 'selected' : ''; ?>>90%</option>
<option value="0.95" <?php echo $_SESSION['user']['settings']['alert_quota_limit_close'] == 0.95 ? 'selected' : ''; ?>>95%</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-heading">
<h4 class="panel-title"><i class="fa fa-eye-slash fa-fw"></i> Cacher des menus</h4>
</div>
<div class="panel-body">
<form action="<?php echo \descartes\Router::url('Setting', 'update', ['setting_name' => 'hide_menus', 'csrf' => $_SESSION['csrf']]); ?>" method="POST">
<input type="hidden" name="allow_no_value" value="1" />
<div class="form-group">
<label>Cacher certains menus à l'utilisateur (ces menus restent accessibles par l'URL) : </label>
<input name="setting_value[]" class="add-hide-menus form-control" type="text" placeholder="Menus à cacher" autofocus value="<?php $this->s($_SESSION['user']['settings']['hide_menus']); ?>">
</div>
<div class="text-center">
<button class="btn btn-success">Mettre à jour les données</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@ -296,7 +356,27 @@
url += '/' + jQuery(this).val();
});
window.location = url;
});
});
jQuery('.add-hide-menus').each(function()
{
jQuery(this).magicSuggest({
data: [
{'id': 'logs', 'name': 'Logs'},
{"id": "smsstop", "name": "SMS Stop"},
{"id": "calls", "name": "Appels"},
{"id": "events", "name": "Évènements"},
{"id": "commands", "name": "Commandes"},
{"id": "phones", "name": "Téléphones"},
{"id": "settings", "name": "Réglages"},
],
valueField: 'id',
displayField: 'name',
name: 'hide_menus[]',
maxSelection: null,
});
});
});
</script>
<?php

View File

@ -49,18 +49,110 @@
<label>Mot de passe (laissez vide pour générer le mot de passe automatiquement)</label>
<div class="form-group input-group">
<span class="input-group-addon"><span class="fa fa-lock"></span></span>
<input name="password" class="form-control" type="password" placeholder="Mot de passe de l'utilisateur">
<input name="password" class="form-control" type="password" placeholder="Mot de passe de l'utilisateur" autocomplete="new-password" value="<?php $this->s($_SESSION['previous_http_post']['password'] ?? ''); ?>">
</div>
</div>
<?php if (isset($_SESSION['user']['admin']) && $_SESSION['user']['admin']) { ?>
<div class="form-group">
<label>Niveau administrateur : </label>
<div class="form-group">
<input name="admin" type="radio" value="1" required <?= (isset($_SESSION['previous_http_post']['admin']) && (bool) $_SESSION['previous_http_post']['admin']) ? 'checked' : ''; ?>/> Oui
<input name="admin" type="radio" value="0" required <?= (isset($_SESSION['previous_http_post']['admin']) && !(bool) $_SESSION['previous_http_post']['admin']) ? 'checked' : ''; ?>/> Non
</div>
</div>
<?php } ?>
<div class="form-group">
<label>Niveau administrateur : </label>
<div class="form-group">
<input name="admin" type="radio" value="1" required <?= (isset($_SESSION['previous_http_post']['admin']) && (bool) $_SESSION['previous_http_post']['admin']) ? 'checked' : ''; ?>/> Oui
<input name="admin" type="radio" value="0" required <?= (isset($_SESSION['previous_http_post']['admin']) && !(bool) $_SESSION['previous_http_post']['admin']) ? 'checked' : ''; ?>/> Non
</div>
</div>
<fieldset>
<legend>Quota de SMS</legend>
<div class="form-group">
<label>Définir un quota pour cet utilisateur : </label>
<p class="italic small help">
Définir un quota pour un utilisateur vous permet de choisir combien de SMS cet utilisateur pourras envoyer sur une période donnée.
</p>
<div class="form-group">
<input name="quota_enable" type="radio" value="1" required <?= (isset($_SESSION['previous_http_post']['quota_enable']) && (bool) $_SESSION['previous_http_post']['quota_enable']) ? 'checked' : ''; ?>/> Oui
<input name="quota_enable" type="radio" value="0" required <?= (isset($_SESSION['previous_http_post']['quota_enable']) && !(bool) $_SESSION['previous_http_post']['quota_enable']) ? 'checked' : ''; ?>/> Non
</div>
</div>
<div class="quota-settings hidden">
<div class="form-group">
<label>Nombre de SMS disponibles</label>
<input name="quota_credit" class="form-control" type="number" required disabled placeholder="Crédit de base" value="<?php $this->s($_SESSION['previous_http_post']['quota_credit'] ?? '') ?>">
</div>
<div class="form-group">
<label>SMS additionels</label>
<p class="italic small help">
SMS venants s'ajouter au crédit de base. Vous pouvez par exemple utiliser des SMS additionels pour augmenter temporairement la limite de SMS d'un utilisateur.
</p>
<input name="quota_additional" class="form-control" type="number" required disabled placeholder="Nombre de SMS additionel au crédit de base" value="<?php $this->s($_SESSION['previous_http_post']['quota_additional'] ?? '') ?>">
</div>
<div class="form-group">
<label>Date de début du quota</label>
<input name="quota_start_date" class="form-control form-datetime auto-width" type="text" required disabled readonly value="<?php $this->s($_SESSION['previous_http_post']['quota_start_date'] ?? $now) ?>">
</div>
<div class="form-group">
<label>Durée du quota : </label>
<p class="italic small help">
Sur quelle durée le quota doit-il s'appliqué. Une fois cette durée passée, le quota sera soit désactivé soit renouvelé automatiquement.
</p>
<div class="form-group">
<select name="quota_renew_interval" class="form-control" disabled required>
<option value="P1D" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P1D' ? 'selected' : '' ?>>1 jour</option>
<option value="P15D" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P15D' ? 'selected' : '' ?>>15 jours</option>
<option value="P28D" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P28D' ? 'selected' : '' ?>>28 jours</option>
<option value="P30D" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P30D' ? 'selected' : '' ?>>30 jours</option>
<option value="P31D" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P31D' ? 'selected' : '' ?>>31 jours</option>
<option value="P1W" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P1W' ? 'selected' : '' ?>>1 semaine</option>
<option value="P2W" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P2W' ? 'selected' : '' ?>>2 semaines</option>
<option value="P3W" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P3W' ? 'selected' : '' ?>>3 semaines</option>
<option value="P4W" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P4W' ? 'selected' : '' ?>>4 semaines</option>
<option value="P1M" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P1M' ? 'selected' : '' ?>>1 mois</option>
<option value="P2M" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P2M' ? 'selected' : '' ?>>2 mois</option>
<option value="P3M" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P3M' ? 'selected' : '' ?>>3 mois</option>
<option value="P6M" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P6M' ? 'selected' : '' ?>>6 mois</option>
<option value="P9M" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P9M' ? 'selected' : '' ?>>9 mois</option>
<option value="P12M" <?= ($_SESSION['previous_http_post']['quota_renew_interval'] ?? '') == 'P12M' ? 'selected' : '' ?>>12 mois</option>
</select>
</div>
</div>
<div class="form-group">
<label>Renouveler automatiquement le quota : </label>
<p class="italic small help">
Si activé, le crédit consommé sera automatiquement remis à zéro et le quota renouvelé pour la même durée à chaque fois qu'il arrivera à sa fin.
</p>
<div class="form-group">
<input name="quota_auto_renew" type="radio" value="1" disabled required <?= (isset($_SESSION['previous_http_post']['quota_auto_renew']) && (bool) $_SESSION['previous_http_post']['quota_auto_renew']) ? 'checked' : ''; ?>/> Oui
<input name="quota_auto_renew" type="radio" value="0" disabled required <?= (isset($_SESSION['previous_http_post']['quota_auto_renew']) && !(bool) $_SESSION['previous_http_post']['quota_auto_renew']) ? 'checked' : ''; ?>/> Non
</div>
</div>
<div class="form-group">
<label>Reporter les SMS non consommés à la fin de la période : </label>
<p class="italic small help">
Si activé, les SMS non consommés serons reportés au mois suivant sous la forme de crédit additionel. Sinon, les SMS non utilisés seront simplement perdus.
</p>
<div class="form-group">
<input name="quota_report_unused" type="radio" value="1" disabled required <?= (isset($_SESSION['previous_http_post']['quota_report_unused']) && (bool) $_SESSION['previous_http_post']['quota_report_unused']) ? 'checked' : ''; ?>/> Oui
<input name="quota_report_unused" type="radio" value="0" disabled required <?= (isset($_SESSION['previous_http_post']['quota_report_unused']) && !(bool) $_SESSION['previous_http_post']['quota_report_unused']) ? 'checked' : ''; ?>/> Non
</div>
</div>
<div class="form-group">
<label>Reporter les SMS additionels non consommés à la fin de la période : </label>
<p class="italic small help">
Si activé, les SMS additionels non consommés serons reportés au mois suivant sous la forme de crédit additionel. Sinon, les SMS additionels non utilisés seront simplement perdus.
</p>
<div class="form-group">
<input name="quota_report_unused_additional" type="radio" value="1" disabled required <?= (isset($_SESSION['previous_http_post']['quota_report_unused_additional']) && (bool) $_SESSION['previous_http_post']['quota_report_unused_additional']) ? 'checked' : ''; ?>/> Oui
<input name="quota_report_unused_additional" type="radio" value="0" disabled required <?= (isset($_SESSION['previous_http_post']['quota_report_unused_additional']) && !(bool) $_SESSION['previous_http_post']['quota_report_unused_additional']) ? 'checked' : ''; ?>/> Non
</div>
</div>
</div>
</fieldset>
<a class="btn btn-danger" href="<?php echo \descartes\Router::url('User', 'list'); ?>">Annuler</a>
<input type="submit" class="btn btn-success" value="Enregistrer le user" />
</form>
@ -71,5 +163,35 @@
</div>
</div>
</div>
<script>
jQuery(document).ready(function()
{
jQuery('.form-datetime').datetimepicker(
{
format: 'yyyy-mm-dd hh:ii:ss',
autoclose: true,
minuteStep: 1,
language: 'fr'
});
jQuery('input[name="quota_enable"]').on('change', function(event)
{
if (event.target.value == 0)
{
console.log('disable');
jQuery('.quota-settings').addClass('hidden');
jQuery('.quota-settings input, .quota-settings select').prop('disabled', true);
}
else
{
console.log('enable');
jQuery('.quota-settings').removeClass('hidden');
jQuery('.quota-settings input, .quota-settings select').prop('disabled', false);
}
})
jQuery('input[name="quota_enable"]:checked').trigger('change');
});
</script>
<?php
$this->render('incs/footer');

205
templates/user/edit.php Normal file
View File

@ -0,0 +1,205 @@
<?php
//Template dashboard
$this->render('incs/head', ['title' => 'Users - Show All'])
?>
<div id="wrapper">
<?php
$this->render('incs/nav', ['page' => 'users'])
?>
<div id="page-wrapper">
<div class="container-fluid">
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">
Nouvel utilisateur
</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-user"></i> <a href="<?php echo \descartes\Router::url('User', 'list'); ?>">Utilisateurs</a>
</li>
<li class="active">
<i class="fa fa-plus"></i> Nouveau
</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-user fa-fw"></i> Ajout d'un utilisateur</h3>
</div>
<div class="panel-body">
<form action="<?php echo \descartes\Router::url('User', 'update', ['csrf' => $_SESSION['csrf']]);?>" method="POST">
<?php foreach ($users as $user) { ?>
<div class="form-group">
<label>Adresse e-mail</label>
<div class="form-group input-group">
<span class="input-group-addon"><span class="fa fa-at"></span></span>
<input name="users[<?php $this->s($user['id']); ?>][email]" class="form-control" type="email" placeholder="Adresse e-mail de l'utilisateur" autofocus required value="<?php $this->s($user['email']) ?>">
</div>
</div>
<div class="form-group">
<label>Mot de passe (laissez vide pour conserver le mot de passe actuel)</label>
<div class="form-group input-group">
<span class="input-group-addon"><span class="fa fa-lock"></span></span>
<input name="users[<?php $this->s($user['id']); ?>][password]" class="form-control" type="password" autocomplete="new-password" placeholder="Mot de passe de l'utilisateur" value="">
</div>
</div>
<div class="form-group">
<label>Niveau administrateur : </label>
<div class="form-group">
<input name="users[<?php $this->s($user['id']); ?>][admin]" type="radio" value="1" required <?= ($user['admin'] ? 'checked' : ''); ?>/> Oui
<input name="users[<?php $this->s($user['id']); ?>][admin]" type="radio" value="0" required <?= ($user['admin'] ? '' : 'checked'); ?>/> Non
</div>
</div>
<fieldset>
<legend>Quota de SMS</legend>
<?php if (($user['quota']['expiration_date'] ?? false) && (new \DateTime() > new \DateTime($user['quota']['expiration_date']))) { ?>
<div class="alert alert-danger text-left">Le quota de cet utilisateur est expiré depuis le <b><?php $this->s($user['quota']['expiration_date']); ?></b> est n'as pas été renouvelé, il n'est donc plus appliqué !</div>
<?php } ?>
<div class="form-group">
<label>Définir un quota pour cet utilisateur : </label>
<p class="italic small help">
Définir un quota pour un utilisateur vous permet de choisir combien de SMS cet utilisateur pourras envoyer sur une période donnée.
</p>
<div class="form-group">
<input class="quota_enable_radio" name="users[<?php $this->s($user['id']); ?>][quota_enable]" type="radio" value="1" required <?= $user['quota'] ? 'checked' : ''; ?>/> Oui
<input class="quota_enable_radio" name="users[<?php $this->s($user['id']); ?>][quota_enable]" type="radio" value="0" required <?= $user['quota'] ? '' : 'checked'; ?>/> Non
</div>
</div>
<div class="quota-settings hidden">
<div class="form-group">
<label>Nombre de SMS disponibles</label>
<input name="users[<?php $this->s($user['id']); ?>][quota_credit]" class="form-control" type="number" required disabled placeholder="Crédit de base" value="<?php $this->s($user['quota']['credit'] ?? 0) ?>">
</div>
<div class="form-group">
<label>Nombre de SMS déjà consommés</label>
<input name="users[<?php $this->s($user['id']); ?>][quota_consumed]" class="form-control" type="number" required disabled placeholder="Crédit déjà consommé" value="<?php $this->s($user['quota']['consumed'] ?? 0) ?>">
</div>
<div class="form-group">
<label>SMS additionels</label>
<p class="italic small help">
SMS venants s'ajouter au crédit de base. Vous pouvez par exemple utiliser des SMS additionels pour augmenter temporairement la limite de SMS d'un utilisateur.
</p>
<input name="users[<?php $this->s($user['id']); ?>][quota_additional]" class="form-control" type="number" required disabled placeholder="Nombre de SMS additionel au crédit de base" value="<?php $this->s($user['quota']['additional'] ?? 0) ?>">
</div>
<div class="form-group">
<label>Date de début du quota</label>
<input name="users[<?php $this->s($user['id']); ?>][quota_start_date]" class="form-control form-datetime auto-width" type="text" required disabled readonly value="<?php $this->s($user['quota']['start_date'] ?? $now) ?>">
</div>
<div class="form-group">
<label>Durée du quota : </label>
<p class="italic small help">
Sur quelle durée le quota doit-il s'appliqué. Une fois cette durée passée, le quota sera soit désactivé soit renouvelé automatiquement.
</p>
<div class="form-group">
<select name="users[<?php $this->s($user['id']); ?>][quota_renew_interval]" class="form-control" disabled required>
<option value="P1D" <?= ($user['quota']['renew_interval'] ?? '') == 'P1D' ? 'selected' : '' ?>>1 jour</option>
<option value="P15D" <?= ($user['quota']['renew_interval'] ?? '') == 'P15D' ? 'selected' : '' ?>>15 jours</option>
<option value="P28D" <?= ($user['quota']['renew_interval'] ?? '') == 'P28D' ? 'selected' : '' ?>>28 jours</option>
<option value="P30D" <?= ($user['quota']['renew_interval'] ?? '') == 'P30D' ? 'selected' : '' ?>>30 jours</option>
<option value="P31D" <?= ($user['quota']['renew_interval'] ?? '') == 'P31D' ? 'selected' : '' ?>>31 jours</option>
<option value="P1W" <?= ($user['quota']['renew_interval'] ?? '') == 'P1W' ? 'selected' : '' ?>>1 semaine</option>
<option value="P2W" <?= ($user['quota']['renew_interval'] ?? '') == 'P2W' ? 'selected' : '' ?>>2 semaines</option>
<option value="P3W" <?= ($user['quota']['renew_interval'] ?? '') == 'P3W' ? 'selected' : '' ?>>3 semaines</option>
<option value="P4W" <?= ($user['quota']['renew_interval'] ?? '') == 'P4W' ? 'selected' : '' ?>>4 semaines</option>
<option value="P1M" <?= ($user['quota']['renew_interval'] ?? '') == 'P1M' ? 'selected' : '' ?>>1 mois</option>
<option value="P2M" <?= ($user['quota']['renew_interval'] ?? '') == 'P2M' ? 'selected' : '' ?>>2 mois</option>
<option value="P3M" <?= ($user['quota']['renew_interval'] ?? '') == 'P3M' ? 'selected' : '' ?>>3 mois</option>
<option value="P6M" <?= ($user['quota']['renew_interval'] ?? '') == 'P6M' ? 'selected' : '' ?>>6 mois</option>
<option value="P9M" <?= ($user['quota']['renew_interval'] ?? '') == 'P9M' ? 'selected' : '' ?>>9 mois</option>
<option value="P12M" <?= ($user['quota']['renew_interval'] ?? '') == 'P12M' ? 'selected' : '' ?>>12 mois</option>
</select>
</div>
</div>
<div class="form-group">
<label>Renouveler automatiquement le quota : </label>
<p class="italic small help">
Si activé, le crédit consommé sera automatiquement remis à zéro et le quota renouvelé pour la même durée à chaque fois qu'il arrivera à sa fin.
</p>
<div class="form-group">
<input name="users[<?php $this->s($user['id']); ?>][quota_auto_renew]" type="radio" value="1" disabled required <?= (($user['quota']['auto_renew'] ?? false) ? 'checked' : ''); ?>/> Oui
<input name="users[<?php $this->s($user['id']); ?>][quota_auto_renew]" type="radio" value="0" disabled required <?= (($user['quota']['auto_renew'] ?? false) ? '' : 'checked'); ?>/> Non
</div>
</div>
<div class="form-group">
<label>Reporter les SMS non consommés à la fin de la période : </label>
<p class="italic small help">
Si activé, les SMS non consommés serons reportés au mois suivant sous la forme de crédit additionel. Sinon, les SMS non utilisés seront simplement perdus.
</p>
<div class="form-group">
<input name="users[<?php $this->s($user['id']); ?>][quota_report_unused]" type="radio" value="1" disabled required <?= (($user['quota']['report_unused'] ?? false) ? 'checked' : ''); ?>/> Oui
<input name="users[<?php $this->s($user['id']); ?>][quota_report_unused]" type="radio" value="0" disabled required <?= (($user['quota']['report_unused'] ?? false) ? '' : 'checked'); ?>/> Non
</div>
</div>
<div class="form-group">
<label>Reporter les SMS additionels non consommés à la fin de la période : </label>
<p class="italic small help">
Si activé, les SMS additionels non consommés serons reportés au mois suivant sous la forme de crédit additionel. Sinon, les SMS additionels non utilisés seront simplement perdus.
</p>
<div class="form-group">
<input name="users[<?php $this->s($user['id']); ?>][quota_report_unused_additional]" type="radio" value="1" disabled required <?= (($user['quota']['report_unused_additional'] ?? false) ? 'checked' : ''); ?>/> Oui
<input name="users[<?php $this->s($user['id']); ?>][quota_report_unused_additional]" type="radio" value="0" disabled required <?= (($user['quota']['report_unused_additional'] ?? false) ? '' : 'checked'); ?>/> Non
</div>
</div>
</div>
</fieldset>
<hr/>
<?php } ?>
<a class="btn btn-danger" href="<?php echo \descartes\Router::url('User', 'list'); ?>">Annuler</a>
<input type="submit" class="btn btn-success" value="Enregistrer le user" />
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function()
{
jQuery('.form-datetime').datetimepicker(
{
format: 'yyyy-mm-dd hh:ii:ss',
autoclose: true,
minuteStep: 1,
language: 'fr'
});
jQuery('.quota_enable_radio').on('change', function(event)
{
if (event.target.value == 0)
{
jQuery(event.target).parents('fieldset').find('.quota-settings').addClass('hidden');
jQuery(event.target).parents('fieldset').find('.quota-settings input, .quota-settings select').prop('disabled', true);
}
else
{
jQuery(event.target).parents('fieldset').find('.quota-settings').removeClass('hidden');
jQuery(event.target).parents('fieldset').find('.quota-settings input, .quota-settings select').prop('disabled', false);
}
})
jQuery('.quota_enable_radio:checked').trigger('change');
});
</script>
<?php
$this->render('incs/footer');

View File

@ -36,12 +36,13 @@
<div class="panel-body">
<form method="GET">
<div class="table-responsive">
<table class="table table-bordered table-hover table-striped datatable" id="table-users">
<table class="table table-bordered table-hover table-striped datatable" id="table-users" style="width:100%">
<thead>
<tr>
<th>Email</th>
<th>Admin</th>
<th>Statut</th>
<th>Crédit utilisé</th>
<th class="checkcolumn">&#10003;</th>
</tr>
</thead>
@ -49,16 +50,16 @@
</tbody>
</table>
</div>
<div>
<div class="col-xs-6 no-padding">
<a class="btn btn-success" href="<?php echo \descartes\Router::url('User', 'add'); ?>"><span class="fa fa-plus"></span> Ajouter un utilisateur</a>
</div>
<div class="text-right col-xs-6 no-padding">
<strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('User', 'update_status', ['csrf' => $_SESSION['csrf'], 'status' => 0]); ?>"><span class="fa fa-pause"></span> Suspendre</button>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('User', 'update_status', ['csrf' => $_SESSION['csrf'], 'status' => 1]); ?>"><span class="fa fa-play"></span> Activer</button>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('User', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div>
<div>
<div class="col-xs-6 no-padding">
<a class="btn btn-success" href="<?php echo \descartes\Router::url('User', 'add'); ?>"><span class="fa fa-plus"></span> Ajouter un utilisateur</a>
</div>
<div class="text-right col-xs-6 no-padding">
<strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('User', 'update_status', ['csrf' => $_SESSION['csrf'], 'status' => 0]); ?>"><span class="fa fa-pause"></span> Suspendre</button>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('User', 'update_status', ['csrf' => $_SESSION['csrf'], 'status' => 1]); ?>"><span class="fa fa-play"></span> Activer</button>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('User', 'edit'); ?>"><span class="fa fa-edit"></span> Modifier</button>
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('User', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div>
</div>
</form>
@ -91,10 +92,20 @@ jQuery(document).ready(function ()
{data: 'email', render: jQuery.fn.dataTable.render.text()},
{data: 'admin', render: jQuery.fn.dataTable.render.text()},
{data: 'status', render: jQuery.fn.dataTable.render.text()},
{
data: 'quota_percentage',
render: function (data, type, row, meta) {
var html = jQuery.fn.dataTable.render.text().display(data) + "%";
if (row['quota_expired_at'] !== undefined) {
html += ' - <span class="danger">Quota expiré le ' + jQuery.fn.dataTable.render.text().display(row['quota_expired_at']) + '</span>';
}
return html;
},
},
{
data: 'id',
render: function (data, type, row, meta) {
return '<input name="ids[]" type="checkbox" value="' + data + '">';
return '<input name="user_ids[]" type="checkbox" value="' + data + '">';
},
},
],