From ff6b3e79dfcdf3d0db141873cdee2e5e2428f965 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Fri, 19 Mar 2021 02:45:12 +0100 Subject: [PATCH 01/40] start adding mms to a lot of places, no tests, not done --- adapters/AdapterInterface.php | 253 +++++------ adapters/BenchmarkAdapter.php | 33 +- adapters/GammuAdapter.php | 61 +-- adapters/OctopushShortcodeAdapter.php | 61 +-- adapters/OctopushVirtualNumberAdapter.php | 61 +-- adapters/OvhSmsShortcodeAdapter.php | 61 +-- adapters/OvhSmsVirtualNumberAdapter.php | 61 +-- adapters/TestAdapter.php | 63 +-- adapters/TwilioVirtualNumberAdapter.php | 61 +-- composer.json | 3 +- controllers/internals/Adapter.php | 31 +- controllers/internals/Media.php | 175 +++++--- controllers/internals/Phone.php | 61 +++ controllers/internals/Received.php | 45 +- controllers/internals/Scheduled.php | 89 +++- controllers/internals/Sended.php | 48 ++- controllers/internals/Tool.php | 103 ++--- controllers/publics/Api.php | 105 ++++- controllers/publics/Callback.php | 54 ++- controllers/publics/Scheduled.php | 5 +- daemons/Phone.php | 2 +- .../20210317214910_add_media_links_to_sms.php | 87 ++++ db/migrations/20210318142759_add_is_mms.php | 46 ++ models/Media.php | 393 +++++++++--------- 24 files changed, 1174 insertions(+), 788 deletions(-) create mode 100644 db/migrations/20210317214910_add_media_links_to_sms.php create mode 100644 db/migrations/20210318142759_add_is_mms.php diff --git a/adapters/AdapterInterface.php b/adapters/AdapterInterface.php index 8760620..2940452 100644 --- a/adapters/AdapterInterface.php +++ b/adapters/AdapterInterface.php @@ -11,140 +11,155 @@ namespace adapters; +/** + * Interface for phones adapters + * Phone's adapters allow RaspiSMS to use a platform to communicate with a phone number. + * Its an adapter between internal and external code, as an API, command line software, physical modem, etc. + * + * All Phone Adapters must implement this interface + */ +interface AdapterInterface +{ /** - * Interface for phones adapters - * Phone's adapters allow RaspiSMS to use a platform to communicate with a phone number. - * Its an adapter between internal and external code, as an API, command line software, physical modem, etc. + * Adapter constructor, called when instanciated by RaspiSMS. * - * All Phone Adapters must implement this interface + * @param json string $data : JSON string of the data to configure interaction with the implemented service */ - interface AdapterInterface - { - /** - * Adapter constructor, called when instanciated by RaspiSMS. - * - * @param json string $data : JSON string of the data to configure interaction with the implemented service - */ - public function __construct(string $data); + public function __construct(string $data); - /** - * Classname of the adapter. - */ - public static function meta_classname(): string; + /** + * Classname of the adapter. + */ + public static function meta_classname(): string; - /** - * Uniq name of the adapter - * It should be the classname of the adapter un snakecase. - */ - public static function meta_uid(): string; + /** + * Uniq name of the adapter + * It should be the classname of the adapter un snakecase. + */ + public static function meta_uid(): string; - /** - * Should this adapter be hidden in user interface for phone creation and - * available to creation through API only. - */ - public static function meta_hidden(): bool; + /** + * Should this adapter be hidden in user interface for phone creation and + * available to creation through API only. + */ + public static function meta_hidden(): bool; - /** - * Name of the adapter. - * It should probably be the name of the service it adapt (e.g : Gammu SMSD, OVH SMS, SIM800L, etc.). - */ - public static function meta_name(): string; + /** + * Name of the adapter. + * It should probably be the name of the service it adapt (e.g : Gammu SMSD, OVH SMS, SIM800L, etc.). + */ + public static function meta_name(): string; - /** - * Description of the adapter. - * A short description of the service the adapter implements. - */ - public static function meta_description(): string; + /** + * Description of the adapter. + * A short description of the service the adapter implements. + */ + public static function meta_description(): string; - /** - * List of entries we want in data for the adapter. - * - * @return array : Eachline line is a field as an array with keys : name, title, description, required - */ - public static function meta_data_fields(): array; + /** + * List of entries we want in data for the adapter. + * + * @return array : Eachline line is a field as an array with keys : name, title, description, required + */ + public static function meta_data_fields(): array; - /** - * Does the implemented service support flash smss. - */ - public static function meta_support_flash(): bool; + /** + * Does the implemented service support flash smss. + */ + public static function meta_support_flash(): bool; - /** - * Does the implemented service support reading smss. - */ - public static function meta_support_read(): bool; + /** + * Does the implemented service support reading smss. + */ + public static function meta_support_read(): bool; - /** - * Does the implemented service support reception callback. - */ - public static function meta_support_reception(): bool; + /** + * Does the implemented service support reception callback. + */ + public static function meta_support_reception(): bool; - /** - * Does the implemented service support status change callback. - */ - public static function meta_support_status_change(): bool; + /** + * Does the implemented service support status change callback. + */ + public static function meta_support_status_change(): bool; - /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * ?string 'uid' => Uid of the sms created on success - * ] - */ - public function send(string $destination, string $text, bool $flash = false); + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool; - /** - * 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 - * [ - * [ - * string 'at' => sms reception date, - * string 'text' => sms text, - * string 'origin' => phone number who sent the sms - * ], - * ... - * ] - * ] - */ - public function read(): array; + /** + * Does the implemented service support mms sending + */ + public static function meta_support_mms_sending(): bool; - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ - public function test(): bool; + /** + * Method called to send a SMS to a number. + * + * @param string $destination : Phone number to send the sms to + * @param string $text : Text of the SMS to send + * @param bool $flash : Is the SMS a Flash SMS + * @param bool $mms : Is the SMS a MMS + * @param array $medias : Array of medias to link to the MMS, [['http_url' => HTTP public url of the media et 'local_uri' => local uri to media file]] + * + * @return array : [ + * bool 'error' => false if no error, true else + * ?string 'error_message' => null if no error, else error message + * 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; - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ - public static function status_change_callback(); + /** + * 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 + * ] + * ], ...] + * ] + */ + public function read(): array; - /** - * 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, - * ] - * - * ] - */ - public static function reception_callback(): array; - } + /** + * Method called to verify if the adapter is working correctly + * should be use for exemple to verify that credentials and number are both valid. + * + * @return bool : False on error, true else + */ + public function test(): bool; + + /** + * Method called on reception of a status update notification for a SMS. + * + * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] + */ + public static function status_change_callback(); + + /** + * 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 + * ] + * ] + * ] + */ + public static function reception_callback(): array; +} diff --git a/adapters/BenchmarkAdapter.php b/adapters/BenchmarkAdapter.php index e874c8f..45b7ef6 100644 --- a/adapters/BenchmarkAdapter.php +++ b/adapters/BenchmarkAdapter.php @@ -125,21 +125,24 @@ namespace adapters; { return false; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return false; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * int 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -213,12 +216,6 @@ namespace adapters; return []; } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { return true; diff --git a/adapters/GammuAdapter.php b/adapters/GammuAdapter.php index cf1bb18..4829ab1 100644 --- a/adapters/GammuAdapter.php +++ b/adapters/GammuAdapter.php @@ -135,21 +135,24 @@ namespace adapters; { return false; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return false; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * ?string 'uid' => Uid of the sms created on success, null on error - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -230,15 +233,6 @@ namespace adapters; return $response; } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { $response = [ @@ -287,42 +281,17 @@ namespace adapters; return $response; } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { //Always return true as we cannot test because we would be needing a root account return true; } - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ public static function status_change_callback() { return false; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { return []; diff --git a/adapters/OctopushShortcodeAdapter.php b/adapters/OctopushShortcodeAdapter.php index 667a872..04e54bc 100644 --- a/adapters/OctopushShortcodeAdapter.php +++ b/adapters/OctopushShortcodeAdapter.php @@ -172,21 +172,24 @@ class OctopushShortcodeAdapter implements AdapterInterface { return true; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return false; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * array 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -268,26 +271,11 @@ class OctopushShortcodeAdapter implements AdapterInterface } } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { return []; } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { try @@ -339,11 +327,6 @@ class OctopushShortcodeAdapter implements AdapterInterface } } - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ public static function status_change_callback() { header('Connection: close'); @@ -383,20 +366,6 @@ class OctopushShortcodeAdapter implements AdapterInterface return ['uid' => $uid, 'status' => $status]; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { $response = [ diff --git a/adapters/OctopushVirtualNumberAdapter.php b/adapters/OctopushVirtualNumberAdapter.php index 1fc5305..d0d7cdc 100644 --- a/adapters/OctopushVirtualNumberAdapter.php +++ b/adapters/OctopushVirtualNumberAdapter.php @@ -177,21 +177,24 @@ class OctopushVirtualNumberAdapter implements AdapterInterface { return true; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return false; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * array 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -268,26 +271,11 @@ class OctopushVirtualNumberAdapter implements AdapterInterface } } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { return []; } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { try @@ -339,11 +327,6 @@ class OctopushVirtualNumberAdapter implements AdapterInterface } } - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ public static function status_change_callback() { header('Connection: close'); @@ -383,20 +366,6 @@ class OctopushVirtualNumberAdapter implements AdapterInterface return ['uid' => $uid, 'status' => $status]; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { $response = [ diff --git a/adapters/OvhSmsShortcodeAdapter.php b/adapters/OvhSmsShortcodeAdapter.php index dd68aff..aa98bbd 100644 --- a/adapters/OvhSmsShortcodeAdapter.php +++ b/adapters/OvhSmsShortcodeAdapter.php @@ -169,21 +169,24 @@ namespace adapters; { return false; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return false; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * ?string 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -241,15 +244,6 @@ namespace adapters; } } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { $response = [ @@ -306,12 +300,6 @@ namespace adapters; } } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { try @@ -335,11 +323,6 @@ namespace adapters; } } - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ public static function status_change_callback() { $uid = $_GET['id'] ?? false; @@ -372,20 +355,6 @@ namespace adapters; return ['uid' => $uid, 'status' => $status]; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { return []; diff --git a/adapters/OvhSmsVirtualNumberAdapter.php b/adapters/OvhSmsVirtualNumberAdapter.php index 1217295..1f8995f 100644 --- a/adapters/OvhSmsVirtualNumberAdapter.php +++ b/adapters/OvhSmsVirtualNumberAdapter.php @@ -180,21 +180,24 @@ namespace adapters; { return false; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return false; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * array 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -245,15 +248,6 @@ namespace adapters; } } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { $response = [ @@ -304,12 +298,6 @@ namespace adapters; } } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { try @@ -334,11 +322,6 @@ namespace adapters; } } - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ public static function status_change_callback() { $uid = $_GET['id'] ?? false; @@ -371,20 +354,6 @@ namespace adapters; return ['uid' => $uid, 'status' => $status]; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { return []; diff --git a/adapters/TestAdapter.php b/adapters/TestAdapter.php index 2851fb6..6a20baf 100644 --- a/adapters/TestAdapter.php +++ b/adapters/TestAdapter.php @@ -130,21 +130,24 @@ namespace adapters; { return false; } + + /** + * Does the implemented service support mms reception + */ + public static function meta_support_mms_reception(): bool + { + return true; + } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * array 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms sending */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_sending(): bool + { + return true; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -154,8 +157,10 @@ namespace adapters; $uid = uniqid(); + $medias = []; + $at = (new \DateTime())->format('Y-m-d H:i:s'); - $success = file_put_contents($this->test_file_write, json_encode(['uid' => $uid, 'at' => $at, 'destination' => $destination, 'text' => $text, 'flash' => $flash]) . "\n", FILE_APPEND); + $success = file_put_contents($this->test_file_write, json_encode(['uid' => $uid, 'at' => $at, 'destination' => $destination, 'text' => $text, 'flash' => $flash, 'mms' => $mms, 'medias' => $medias]) . "\n", FILE_APPEND); if (false === $success) { $response['error'] = true; @@ -169,15 +174,6 @@ namespace adapters; return $response; } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { $response = [ @@ -231,20 +227,11 @@ namespace adapters; } } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { return true; } - /** - * Method called on reception of a status update notification for a SMS. - */ public static function status_change_callback() { $uid = $_GET['uid'] ?? false; @@ -281,20 +268,6 @@ namespace adapters; return $return; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { return []; diff --git a/adapters/TwilioVirtualNumberAdapter.php b/adapters/TwilioVirtualNumberAdapter.php index 2835d84..b5065e7 100644 --- a/adapters/TwilioVirtualNumberAdapter.php +++ b/adapters/TwilioVirtualNumberAdapter.php @@ -176,19 +176,22 @@ class TwilioVirtualNumberAdapter implements AdapterInterface } /** - * Method called to send a SMS to a number. - * - * @param string $destination : Phone number to send the sms to - * @param string $text : Text of the SMS to send - * @param bool $flash : Is the SMS a Flash SMS - * - * @return array : [ - * bool 'error' => false if no error, true else - * ?string 'error_message' => null if no error, else error message - * array 'uid' => Uid of the sms created on success - * ] + * Does the implemented service support mms reception */ - public function send(string $destination, string $text, bool $flash = false) + public static function meta_support_mms_reception(): bool + { + return false; + } + + /** + * Does the implemented service support mms sending + */ + public static function meta_support_mms_sending(): bool + { + return false; + } + + public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array { $response = [ 'error' => false, @@ -228,15 +231,6 @@ class TwilioVirtualNumberAdapter implements AdapterInterface } } - /** - * 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 'sms' => Array of the sms reads - * ] - */ public function read(): array { $response = [ @@ -282,12 +276,6 @@ class TwilioVirtualNumberAdapter implements AdapterInterface } } - /** - * Method called to verify if the adapter is working correctly - * should be use for exemple to verify that credentials and number are both valid. - * - * @return bool : False on error, true else - */ public function test(): bool { try @@ -313,11 +301,6 @@ class TwilioVirtualNumberAdapter implements AdapterInterface } } - /** - * Method called on reception of a status update notification for a SMS. - * - * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms (\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED)] - */ public static function status_change_callback() { $sid = $_REQUEST['MessageSid'] ?? false; @@ -349,20 +332,6 @@ class TwilioVirtualNumberAdapter implements AdapterInterface return ['uid' => $sid, 'status' => $status]; } - /** - * 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, - * ] - * - * ] - */ public static function reception_callback(): array { return []; diff --git a/composer.json b/composer.json index 608752a..4173614 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "ovh/ovh": "^2.0", "twilio/sdk": "^6.1", "symfony/yaml": "^5.0", - "phpmailer/phpmailer": "^6.1" + "phpmailer/phpmailer": "^6.1", + "ralouphie/mimey": "^2.1" }, "require-dev": { } diff --git a/controllers/internals/Adapter.php b/controllers/internals/Adapter.php index 50ef643..1fae465 100644 --- a/controllers/internals/Adapter.php +++ b/controllers/internals/Adapter.php @@ -20,9 +20,9 @@ namespace controllers\internals; private const ADAPTERS_META_START = 'meta_'; /** - * List adapters using internal metas. + * List adapters with filepath and internal metas. * - * @return array + * @return array : ['adapter_filepath' => ['meta...' => value, ...], ...] */ public function list_adapters() { @@ -42,7 +42,7 @@ namespace controllers\internals; continue; } - $adapters[] = $metas; + $adapters[$file] = $metas; } return $adapters; @@ -116,4 +116,29 @@ namespace controllers\internals; return $metas; } + + /** + * List all adapters for a meta value + * + * @param $search_name : Name of the meta + * @param $search_value : Value of the meta + * + * @return array : Array with ['adapter filepath' => ['search_name' => value, ...], ...] + */ + 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) { + $match = false; + foreach ($metas as $name => $value) + { + if ($name === $search_name && $value === $search_value) + { + $match = true; + } + } + + return $match; + }); + } } diff --git a/controllers/internals/Media.php b/controllers/internals/Media.php index b02ff5f..4dd6580 100644 --- a/controllers/internals/Media.php +++ b/controllers/internals/Media.php @@ -19,32 +19,105 @@ namespace controllers\internals; * Create a media. * * @param int $id_user : Id of the user - * @param int $id_scheduled : Id of the scheduled - * @param array $media : $_FILES media array + * @param string $path : path of the media in data dir * - * @return bool : false on error, new media id else + * @return mixed bool|int : false on error, new media id else */ - public function create(int $id_user, int $id_scheduled, array $media): bool + public function create(int $id_user, string $path): bool { - $internal_scheduled = new Scheduled($this->bdd); - $scheduled = $internal_scheduled->get_for_user($id_user, $id_scheduled); - if (!$scheduled) - { - return false; - } - - $result_upload_media = \controllers\internals\Tool::upload_file($media); - if (false === $result_upload_media['success']) - { - return false; - } - $data = [ - 'id_scheduled' => $id_scheduled, - 'path' => $result_upload_media['content'], + 'path' => $path, + 'id_user' => $id_user, ]; - return (bool) $this->get_model()->insert($data); + return $this->get_model()->insert($data); + } + + /** + * 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 + * + * @return mixed bool|int : false on error, the new link id else + */ + public function link_to(int $id_media, int $resource_type, int $resource_id) + { + switch ($resource_type) + { + 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: + return false; + } + } + + + /** + * 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 + * + * @return mixed bool : false on error, true on success + */ + public function unlink_of(int $id_media, int $resource_type, int $resource_id) + { + switch ($resource_type) + { + 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: + return false; + } + } + + /** + * 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 + * + * @return mixed bool : false on error, true on success + */ + public function unlink_all_of(int $resource_type, int $resource_id) + { + switch ($resource_type) + { + 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: + return false; + } } /** @@ -52,25 +125,16 @@ namespace controllers\internals; * * @param int $id_user : user id * @param int $id_media : Media id - * @param int $id_scheduled : Id of the scheduled * @param string $path : Path of the file * * @return bool : false on error, true on success */ - public function update_for_user(int $id_user, int $id_media, int $id_scheduled, string $path): bool + public function update_for_user(int $id_user, int $id_media, string $path): bool { $media = [ - 'id_scheduled' => $id_scheduled, 'path' => $path, ]; - $internal_scheduled = new Scheduled($this->bdd); - $scheduled = $this->get_for_user($id_user, $id_scheduled); - if (!$scheduled) - { - return false; - } - return (bool) $this->get_model()->update_for_user($id_user, $id_media, $media); } @@ -95,36 +159,43 @@ namespace controllers\internals; return $this->get_model()->delete_for_user($id_user, $id_media); } - /** - * Delete a media for a scheduled and a user. - * - * @param int $id_user : User id - * @param int $id_scheduled : Scheduled id to delete medias for - * - * @return int : Number of removed rows - */ - public function delete_for_scheduled_and_user(int $id_user, int $id_scheduled): bool - { - $media = $this->get_model()->get_for_scheduled_and_user($id_user, $id_scheduled); - if ($media) - { - unlink($media['path']); - } - - return $this->get_model()->delete_for_scheduled_and_user($id_user, $id_scheduled); - } - /** * Find medias for a scheduled and a user. * * @param int $id_user : User id - * @param int $id_scheduled : Scheduled id to delete medias for + * @param int $id_scheduled : Scheduled id to fin medias for * * @return mixed : Medias || false */ - public function get_for_scheduled_and_user(int $id_user, int $id_scheduled) + public function gets_for_scheduled_and_user(int $id_user, int $id_scheduled) { - return $this->get_model()->get_for_scheduled_and_user($id_user, $id_scheduled); + return $this->get_model()->gets_for_scheduled_and_user($id_user, $id_scheduled); + } + + /** + * Find medias for a sended and a user. + * + * @param int $id_user : User id + * @param int $id_sended : Scheduled id to fin medias for + * + * @return mixed : Medias || false + */ + public function gets_for_sended_and_user(int $id_user, int $id_sended) + { + return $this->get_model()->gets_for_sended_and_user($id_user, $id_sended); + } + + /** + * Find medias for a received and a user. + * + * @param int $id_user : User id + * @param int $id_received : Scheduled id to fin medias for + * + * @return mixed : Medias || false + */ + public function gets_for_received_and_user(int $id_user, int $id_received) + { + return $this->get_model()->gets_for_received_and_user($id_user, $id_received); } /** diff --git a/controllers/internals/Phone.php b/controllers/internals/Phone.php index 63a0b96..3b54c86 100644 --- a/controllers/internals/Phone.php +++ b/controllers/internals/Phone.php @@ -13,6 +13,10 @@ namespace controllers\internals; class Phone extends StandardController { + const MMS_SENDING = 'sending'; + const MMS_RECEPTION = 'reception'; + const MMS_BOTH = 'both'; + protected $model; /** @@ -39,6 +43,63 @@ namespace controllers\internals; return $this->get_model()->get_by_name($name); } + /** + * 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 + * @return bool : true if support, false else + */ + public function support_mms (int $id, string $type) + { + $phone = $this->get_model()->get($id); + if (!$phone) + { + return false; + } + + switch ($type) + { + case self::MMS_SENDING : + return $phone['adapter']::meta_support_mms_sending(); + break; + + case self::MMS_RECEPTION : + return $phone['adapter']::meta_support_mms_reception(); + break; + + case self::MMS_BOTH : + return $phone['adapter']::meta_support_mms_sending() && $phone['adapter']::meta_support_mms_reception(); + break; + + default: + return false; + } + } + + /** + * 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 + * @return array : array of phones supporting mms + */ + public function gets_phone_supporting_mms_for_user (int $id_user, string $type) + { + $phones = $this->get_model()->gets_for_user($id_user); + + $valid_phones = []; + foreach ($phones as $phone) + { + if ($this->support_mms($phone['id'], $type)) + { + $valid_phones[] = $phone; + } + } + + return $valid_phones; + } + /** * Return a phone for a user by a name. * diff --git a/controllers/internals/Received.php b/controllers/internals/Received.php index 2293693..0f0388a 100644 --- a/controllers/internals/Received.php +++ b/controllers/internals/Received.php @@ -39,10 +39,12 @@ namespace controllers\internals; * @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 */ - public function create(int $id_user, int $id_phone, $at, string $text, string $origin, string $status = 'unread', bool $command = false) + public function create(int $id_user, int $id_phone, $at, string $text, string $origin, string $status = 'unread', bool $command = false, bool $mms = false, array $media_ids = []) { $received = [ 'id_user' => $id_user, @@ -52,9 +54,39 @@ namespace controllers\internals; 'origin' => $origin, 'status' => $status, 'command' => $command, + 'mms' => $mms, ]; - return $this->get_model()->insert($received); + //use a transaction to ensure received and media links are created at the same time + $this->bdd->beginTransaction(); + + $id_received = $this->get_model()->insert($received); + if (!$id_received) + { + $this->bdd->rollBack(); + return false; + } + + //Link medias + $internal_media = new Media($this->bdd); + foreach ($media_ids as $media_id) + { + $id_media_received = $internal_media->link_to($media_id, 'received', $id_received); + if (!$id_media_received) + { + $this->bdd->rollBack(); + return false; + } + } + + //All ok, commit + $success = $this->bdd->commit(); + if (!$success) + { + return false; + } + + return $id_received; } /** @@ -211,13 +243,15 @@ 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 $media_ids : Ids of the medias to link to received * * @return array : [ * bool 'error' => false if success, true else * ?string 'error_message' => null if success, error message else * ] */ - public function receive(int $id_user, int $id_phone, string $text, string $origin, ?string $at = null, string $status = \models\Received::STATUS_UNREAD): array + public function receive(int $id_user, int $id_phone, string $text, string $origin, ?string $at = null, string $status = \models\Received::STATUS_UNREAD, bool $mms = false, array $media_ids = []): array { $return = [ 'error' => false, @@ -236,7 +270,7 @@ namespace controllers\internals; $text = $response; } - $received_id = $this->create($id_user, $id_phone, $at, $text, $origin, $status, $is_command); + $received_id = $this->create($id_user, $id_phone, $at, $text, $origin, $status, $is_command, $mms, $media_ids); if (!$received_id) { $return['error'] = true; @@ -251,6 +285,9 @@ namespace controllers\internals; 'text' => $text, 'destination' => $id_phone, 'origin' => $origin, + 'command' => $is_command, + 'mms' => $mms, + 'medias' => $media_ids, ]; $internal_webhook = new Webhook($this->bdd); diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index ae99361..25d1a35 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -23,14 +23,16 @@ namespace controllers\internals; * @param string $text : Text of the message * @param ?int $id_phone : Id of the phone to send message with, null by default * @param bool $flash : Is the sms a flash sms, by default false + * @param bool $mms : Is the sms a mms, by default false * @param array $numbers : Numbers to send message to * @param array $contacts_ids : Contact ids to send message to * @param array $groups_ids : Group ids to send message to * @param array $conditional_group_ids : Conditional Groups ids to send message to + * @param array $media_ids : Ids of the medias to link to scheduled message * * @return bool : false on error, new id on success */ - public function create(int $id_user, $at, string $text, ?int $id_phone = null, bool $flash = false, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = []) + public function create(int $id_user, $at, string $text, ?int $id_phone = null, bool $flash = false, bool $mms = false, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = [], array $media_ids = []) { $scheduled = [ 'id_user' => $id_user, @@ -38,6 +40,7 @@ namespace controllers\internals; 'text' => $text, 'id_phone' => $id_phone, 'flash' => $flash, + 'mms' => $mms, ]; if (null !== $id_phone) @@ -51,12 +54,28 @@ namespace controllers\internals; } } + //Use transaction to garanty atomicity + $this->bdd->beginTransaction(); + $id_scheduled = $this->get_model()->insert($scheduled); if (!$id_scheduled) { + $this->bdd->rollBack(); return false; } + $internal_media = new Media($this->bdd); + foreach ($media_ids as $media_id) + { + $id_media_scheduled = $internal_media->link_to($media_id, 'scheduled', $id_scheduled); + if (!$id_media_scheduled) + { + $this->bdd->rollBack(); + return false; + } + } + + foreach ($numbers as $number) { $this->get_model()->insert_scheduled_number($id_scheduled, $number); @@ -98,6 +117,12 @@ namespace controllers\internals; $this->get_model()->insert_scheduled_conditional_group_relation($id_scheduled, $conditional_group_id); } + $success = $this->bdd->commit(); + if (!$success) + { + return false; + } + $date = date('Y-m-d H:i:s'); $internal_event = new Event($this->bdd); $internal_event->create($id_user, 'SCHEDULED_ADD', 'Ajout d\'un Sms pour le ' . $date . '.'); @@ -114,20 +139,23 @@ namespace controllers\internals; * @param string $text : Text of the message * @param ?int $id_phone : Id of the phone to send message with, null by default * @param bool $flash : Is the sms a flash sms, by default false + * @param bool $mms : Is the sms a mms, by default false * @param array $numbers : Numbers to send message to * @param array $contacts_ids : Contact ids to send message to * @param array $groups_ids : Group ids to send message to * @param array $conditional_group_ids : Conditional Groups ids to send message to + * @param array $media_ids : Ids of the medias to link to scheduled message * - * @return bool : false on error, new id on success + * @return bool : false on error, true on success */ - public function update_for_user(int $id_user, int $id_scheduled, $at, string $text, ?string $id_phone = null, bool $flash = false, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = []) + public function update_for_user(int $id_user, int $id_scheduled, $at, string $text, ?string $id_phone = null, bool $flash = false, bool $mms = false, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = [], array $media_ids = []) { $scheduled = [ 'id_user' => $id_user, 'at' => $at, 'text' => $text, 'id_phone' => $id_phone, + 'mms' => $mms, 'flash' => $flash, ]; @@ -142,12 +170,27 @@ namespace controllers\internals; } } + //Ensure atomicity + $this->bdd->beginTransaction(); + $success = (bool) $this->get_model()->update_for_user($id_user, $id_scheduled, $scheduled); $this->get_model()->delete_scheduled_numbers($id_scheduled); $this->get_model()->delete_scheduled_contact_relations($id_scheduled); $this->get_model()->delete_scheduled_group_relations($id_scheduled); $this->get_model()->delete_scheduled_conditional_group_relations($id_scheduled); + $internal_media = new Media($this->bdd); + $internal_media->unlink_all_of('scheduled', $id_scheduled); + + foreach ($media_ids as $media_id) + { + $id_media_scheduled = $internal_media->link_to($media_id, 'scheduled', $id_scheduled); + if (!$id_media_scheduled) + { + $this->bdd->rollBack(); + return false; + } + } foreach ($numbers as $number) { @@ -190,7 +233,7 @@ namespace controllers\internals; $this->get_model()->insert_scheduled_conditional_group_relation($id_scheduled, $conditional_group_id); } - return true; + return $this->bdd->commit(); } /** @@ -224,6 +267,7 @@ namespace controllers\internals; $users_settings = []; $users_phones = []; + $users_mms_phones = []; $now = new \DateTime(); $now = $now->format('Y-m-d H:i:s'); @@ -244,7 +288,16 @@ namespace controllers\internals; if (!isset($users_phones[$scheduled['id_user']])) { $phones = $internal_phone->gets_for_user($scheduled['id_user']); + $mms_phones = $internal_phone->gets_phone_supporting_mms_for_user($scheduled['id_user'], $internal_phone::MMS_SENDING); $users_phones[$scheduled['id_user']] = $phones ?: []; + $users_mms_phones[$scheduled['id_user']] = $mms_phones ?: []; + } + + //Add medias to mms + if ($scheduled['mms']) + { + $internal_media = new Media($this->bdd); + $scheduled['medias'] = $internal_media->gets_for_scheduled_and_user($scheduled['id_user'], $scheduled['id']); } $phone_to_use = null; @@ -266,8 +319,16 @@ namespace controllers\internals; { if (null === $phone_to_use) { - $rnd_key = array_rand($users_phones[$scheduled['id_user']]); - $random_phone = $users_phones[$scheduled['id_user']][$rnd_key]; + if ($scheduled['mms']) + { + $rnd_key = array_rand($users_mms_phones[$scheduled['id_user']]); + $random_phone = $users_mms_phones[$scheduled['id_user']][$rnd_key]; + } + else + { + $rnd_key = array_rand($users_phones[$scheduled['id_user']]); + $random_phone = $users_phones[$scheduled['id_user']][$rnd_key]; + } } $message = [ @@ -276,6 +337,8 @@ namespace controllers\internals; 'id_phone' => $phone_to_use['id'] ?? $random_phone['id'], 'destination' => $number['number'], 'flash' => $scheduled['flash'], + 'mms' => $scheduled['mms'], + 'medias' => $scheduled['medias'], ]; if ((int) ($users_settings[$scheduled['id_user']]['templating'] ?? false)) @@ -326,8 +389,16 @@ namespace controllers\internals; if (null === $phone_to_use) { - $rnd_key = array_rand($users_phones[$scheduled['id_user']]); - $random_phone = $users_phones[$scheduled['id_user']][$rnd_key]; + if ($scheduled['mms']) + { + $rnd_key = array_rand($users_mms_phones[$scheduled['id_user']]); + $random_phone = $users_mms_phones[$scheduled['id_user']][$rnd_key]; + } + else + { + $rnd_key = array_rand($users_phones[$scheduled['id_user']]); + $random_phone = $users_phones[$scheduled['id_user']][$rnd_key]; + } } $message = [ @@ -336,6 +407,8 @@ namespace controllers\internals; 'id_phone' => $phone_to_use['id'] ?? $random_phone['id'], 'destination' => $contact['number'], 'flash' => $scheduled['flash'], + 'mms' => $scheduled['mms'], + 'medias' => $scheduled['medias'], ]; if ((int) ($users_settings[$scheduled['id_user']]['templating'] ?? false)) diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index 82bd14e..132f082 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -26,11 +26,13 @@ namespace controllers\internals; * @param string $uid : Uid of the sms on the adapter service used * @param string $adapter : Name of the adapter service used to send the message * @param bool $flash : Is the sms a flash + * @param bool $mms : Is the sms a MMS. By default false. + * @param array $medias : Array of medias to link to the MMS. * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN * * @return mixed : false on error, new sended id else */ - public function create(int $id_user, int $id_phone, $at, string $text, string $destination, string $uid, string $adapter, bool $flash = false, ?string $status = \models\Sended::STATUS_UNKNOWN) + public function create(int $id_user, int $id_phone, $at, string $text, string $destination, string $uid, string $adapter, bool $flash = false, bool $mms = false, array $medias = [], ?string $status = \models\Sended::STATUS_UNKNOWN) { $sended = [ 'id_user' => $id_user, @@ -41,10 +43,33 @@ namespace controllers\internals; 'uid' => $uid, 'adapter' => $adapter, 'flash' => $flash, + 'mms' => $mms, 'status' => $status, ]; - return $this->get_model()->insert($sended); + //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) + { + $internal_media->link_to($media['id'], 'sended', $id_sended); //No rollback on error, keeping track of mms is more important than integrity + } + + if (!$this->bdd->commit()) + { + return false; + } + + return $id_sended; } /** @@ -165,6 +190,8 @@ namespace controllers\internals; * @param $text : Text of the message * @param string $destination : Number of the receiver * @param bool $flash : Is the sms a flash. By default false. + * @param bool $mms : Is the sms a MMS. By default false. + * @param array $medias : Array of medias to link to the MMS. * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN * * @return array : [ @@ -172,7 +199,7 @@ namespace controllers\internals; * ?string 'error_message' => null if success, error message else * ] */ - public function send(\adapters\AdapterInterface $adapter, int $id_user, int $id_phone, string $text, string $destination, bool $flash = false, string $status = \models\Sended::STATUS_UNKNOWN): array + public function send(\adapters\AdapterInterface $adapter, int $id_user, int $id_phone, string $text, string $destination, bool $flash = false, bool $mms = false, array $medias = [], string $status = \models\Sended::STATUS_UNKNOWN): array { $return = [ 'error' => false, @@ -180,19 +207,28 @@ namespace controllers\internals; ]; $at = (new \DateTime())->format('Y-m-d H:i:s'); - $response = $adapter->send($destination, $text, $flash); + $media_uris = []; + foreach ($medias as $media) + { + $media_uris[] = [ + 'http_url' => HTTP_PWD . '/data/' . $media['path'], + 'local_uri' => PWD_DATA . '/data/' . $media['path'], + ]; + } + + $response = $adapter->send($destination, $text, $flash, $mms, $media_uris); if ($response['error']) { $return['error'] = true; $return['error_message'] = $response['error_message']; $status = \models\Sended::STATUS_FAILED; - $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $status); + $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $status); return $return; } - $sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $status); + $sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $status); $sended = [ 'id' => $sended_id, diff --git a/controllers/internals/Tool.php b/controllers/internals/Tool.php index 843d066..0f42360 100644 --- a/controllers/internals/Tool.php +++ b/controllers/internals/Tool.php @@ -266,7 +266,8 @@ namespace controllers\internals; return $result; } - $result['extension'] = pathinfo($file['name'])['extension']; + $result['tmp_name'] = $tmp_filename; + $result['extension'] = pathinfo($file['name'], PATHINFO_EXTENSION); $result['mime_type'] = mime_content_type($tmp_filename); $file_handler = fopen($tmp_filename, 'r'); @@ -277,13 +278,18 @@ namespace controllers\internals; } /** - * Allow to upload file. + * Allow to save an uploaded file from the $_FILE['file'] array * * @param array $file : The array extracted from $_FILES['file'] + * @param string $dirpath : The directory to save the file in + * @param bool $override : If true, override the file if another file with this name exists + * @param ?string $filename : The name to use for the file, if null use a highly random name + * @param ?string $extension : The extension to use for the file, if null try to determine it using original file extension, then mime_type + * @param bool $use_mimetype : If true, ignore original file extension to determine final file extension and use file real mimetype instead * - * @return array : ['success' => bool, 'content' => file path | error message, 'error_code' => $file['error']] + * @return array : ['success' => bool, 'content' => new file name | error message, 'error_code' => $file['error']] */ - public static function upload_file(array $file) + public static function save_uploaded_file(array $file, string $dirpath, bool $override = false, ?string $filename = null, ?string $extension = null, bool $use_mimetype = false) { $result = [ 'success' => false, @@ -291,82 +297,61 @@ namespace controllers\internals; 'error_code' => $file['error'] ?? 99, ]; - if (UPLOAD_ERR_OK !== $file['error']) + $upload_info = self::read_uploaded_file($file); + if (!$upload_info['success']) { - switch ($file['error']) + $result['content'] = $upload_info['content']; + return $result; + } + + if ($extension === null) + { + $extension = $upload_info['extension']; + if ($extension === '' || $use_mimetype) { - case UPLOAD_ERR_INI_SIZE: - $result['content'] = 'Impossible de télécharger le fichier car il dépasse les ' . ini_get('upload_max_filesize') / (1000 * 1000) . ' Mégaoctets.'; - - break; - - case UPLOAD_ERR_FORM_SIZE: - $result['content'] = 'Le fichier dépasse la limite de taille.'; - - break; - - case UPLOAD_ERR_PARTIAL: - $result['content'] = 'L\'envoi du fichier a été interrompu.'; - - break; - - case UPLOAD_ERR_NO_FILE: - $result['content'] = 'Aucun fichier n\'a été envoyé.'; - - break; - - case UPLOAD_ERR_NO_TMP_DIR: - $result['content'] = 'Le serveur ne dispose pas de fichier temporaire permettant l\'envoi de fichiers.'; - - break; - - case UPLOAD_ERR_CANT_WRITE: - $result['content'] = 'Impossible d\'envoyer le fichier car il n\'y a plus de place sur le serveur.'; - - break; - - case UPLOAD_ERR_EXTENSION: - $result['content'] = 'Le serveur a interrompu l\'envoi du fichier.'; - - break; + $mimey = new \Mimey\MimeTypes; + $extension = $mimey->getExtension($upload_info['mime_type']); } - - return $result; } - $tmp_filename = $file['tmp_name'] ?? false; - if (!$tmp_filename || !is_readable($tmp_filename)) + if ($filename === null) { - return $result; + $filename = self::random_uuid(); } - $md5_filename = md5_file($tmp_filename); - if (!$md5_filename) + $filename = $filename . '.' . $extension; + $filepath = $dirpath . '/' . $filename; + + if (file_exists($filepath) && !$override) { - return $result; - } - - $new_file_path = PWD_DATA . '/' . $md5_filename; - - if (file_exists($new_file_path)) - { - $result['success'] = true; - $result['content'] = $new_file_path; + $result['content'] = 'Le fichier ' . $filepath . ' existe déjà.'; return $result; } - $success = move_uploaded_file($tmp_filename, $new_file_path); + $success = move_uploaded_file($upload_info['tmp_name'], $filepath); if (!$success) { - $result['content'] = 'Impossible d\'écrire le fichier sur le serveur.'; + $result['content'] = 'Impossible de délplacer le fichier vers ' . $filepath; return $result; } $result['success'] = true; - $result['content'] = $new_file_path; + $result['content'] = $filename; return $result; } + + + /** + * 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); + } } diff --git a/controllers/publics/Api.php b/controllers/publics/Api.php index 02b09d7..6e0ab20 100644 --- a/controllers/publics/Api.php +++ b/controllers/publics/Api.php @@ -32,6 +32,7 @@ namespace controllers\publics; 'CANNOT_CREATE' => 8, 'SUSPENDED_USER' => 16, 'CANNOT_DELETE' => 32, + 'CANNOT_UPLOAD_FILE' => 64, ]; const ERROR_MESSAGES = [ @@ -41,6 +42,7 @@ namespace controllers\publics; 'CANNOT_CREATE' => 'Cannot create a new entry.', 'SUSPENDED_USER' => 'This user account is currently suspended.', 'CANNOT_DELETE' => 'Cannot delete this entry.', + 'CANNOT_UPLOAD_FILE' => 'Failed to upload or save an uploaded file : ', ]; private $internal_user; @@ -52,6 +54,8 @@ namespace controllers\publics; private $internal_group; private $internal_conditional_group; private $internal_adapter; + private $internal_media; + private $internal_setting; private $user; /** @@ -73,6 +77,8 @@ namespace controllers\publics; $this->internal_group = new \controllers\internals\Group($bdd); $this->internal_conditional_group = new \controllers\internals\ConditionalGroup($bdd); $this->internal_adapter = new \controllers\internals\Adapter(); + $this->internal_media = new \controllers\internals\Media($bdd); + $this->internal_setting = new \controllers\internals\Setting($bdd); //If no user, quit with error $this->user = false; @@ -93,6 +99,8 @@ namespace controllers\publics; exit(self::ERROR_CODES['INVALID_CREDENTIALS']); } + $this->user['settings'] = $this->internal_setting->gets_for_user($this->user['id']); + if (\models\User::STATUS_ACTIVE !== $this->user['status']) { $return = self::DEFAULT_RETURN; @@ -108,14 +116,14 @@ namespace controllers\publics; /** * List all entries of a certain type for the current user, sorted by id. * - * @param string $entry_type : Type of entries we want to list ['sended', 'received', 'scheduled', 'contact', 'group', 'conditional_group', 'phone'] + * @param string $entry_type : Type of entries we want to list ['sended', 'received', 'scheduled', 'contact', 'group', 'conditional_group', 'phone', 'media'] * @param int $page : Pagination number, Default = 0. Group of 25 results. * * @return : List of entries */ public function get_entries(string $entry_type, int $page = 0) { - $entry_types = ['sended', 'received', 'scheduled', 'contact', 'group', 'conditional_group', 'phone']; + $entry_types = ['sended', 'received', 'scheduled', 'contact', 'group', 'conditional_group', 'phone', 'media']; if (!\in_array($entry_type, $entry_types, true)) { @@ -179,6 +187,7 @@ namespace controllers\publics; * @param string $_POST['text'] : Text of the message to send * @param string $_POST['id_phone'] : Default null. Id of phone to send the message from. If null use a random phone * @param string $_POST['flash'] : Default false. Is the sms a flash sms. + * @param string $_POST['mms'] : Default false. Is the sms a mms. * @param string $_POST['numbers'] : Array of numbers to send message to * @param string $_POST['contacts'] : Array of ids of contacts to send message to * @param string $_POST['groups'] : Array of ids of groups to send message to @@ -192,16 +201,20 @@ namespace controllers\publics; $text = $_POST['text'] ?? false; $id_phone = empty($_POST['id_phone']) ? null : $_POST['id_phone']; $flash = (bool) ($_POST['flash'] ?? false); + $mms = (bool) ($_POST['mms'] ?? false); $numbers = $_POST['numbers'] ?? []; $contacts = $_POST['contacts'] ?? []; $groups = $_POST['groups'] ?? []; $conditional_groups = $_POST['conditional_groups'] ?? []; + $files = $_FILES ?? []; $numbers = \is_array($numbers) ? $numbers : [$numbers]; $contacts = \is_array($contacts) ? $contacts : [$contacts]; $groups = \is_array($groups) ? $groups : [$groups]; $conditional_groups = \is_array($conditional_groups) ? $conditional_groups : [$conditional_groups]; + $media_ids = []; + if (!$at) { $at = (new \DateTime())->format('Y-m-d H:i:s'); @@ -227,6 +240,17 @@ namespace controllers\publics; return $this->json($return); } + //TODO : Check if phone adapter support mms and if mms are enabled + if (($this->user['settings']['mms'] ?? false) && $mms) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'mms is set to true, but mms are disabled in settings.'; + $this->auto_http_code(false); + + return $this->json($return); + } + foreach ($numbers as $key => $number) { $number = \controllers\internals\Tool::parse_phone($number); @@ -251,7 +275,13 @@ namespace controllers\publics; return $this->json($return); } - if ($id_phone && !$this->internal_phone->get_for_user($this->user['id'], $id_phone)) + $phone = null; + if ($id_phone) + { + $phone = $this->internal_phone->get_for_user($this->user['id'], $id_phone); + } + + if ($id_phone && !$phone) { $return = self::DEFAULT_RETURN; $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; @@ -261,7 +291,74 @@ namespace controllers\publics; return $this->json($return); } - $scheduled_id = $this->internal_scheduled->create($this->user['id'], $at, $text, $id_phone, $flash, $numbers, $contacts, $groups, $conditional_groups); + if ($id_phone && $mms && !$this->internal_phone->support_mms($id_phone, $this->internal_phone::MMS_SENDING)) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'mms : You try to send a mms with a phone that does not support mms.'; + $this->auto_http_code(false); + + return $this->json($return); + } + + //if try to send mms and no available phone support mms, return error + if (!$id_phone && $mms) + { + $phones_supporting_mms = $this->internal_phone->gets_phone_supporting_mms_for_user($this->user['id'], $this->internal_phone::MMS_SENDING); + if (!count($phones_supporting_mms)) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'mms : You try to send a mms but you dont have any phone supporting mms. Please add at least one phone supporting mms before trying to send one.'; + $this->auto_http_code(false); + + return $this->json($return); + } + } + + foreach ($files as $file) + { + $user_media_path = PWD_DATA . '/medias/' . $this->user['id']; + + //Create user medias dir if not exists + if (!file_exists($user_media_path)) + { + if (!mkdir($user_media_path, fileperms(PWD_DATA), true)) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; + $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . ' : Because cannot create medias dir on server for the user.'; + $this->auto_http_code(false); + + return $this->json($return); + } + } + + $result = \controllers\internals\Tool::save_uploaded_file($file, $user_media_path); + if ($result['success'] !== true) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; + $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . $file['name'] . ' with error : ' . $result['content']; + $this->auto_http_code(false); + + return $this->json($return); + } + + $new_filepath = 'medias/' . $this->user['id'] . '/' . $result['content']; + $new_media_id = $this->internal_media->create($this->user['id'], $new_filepath); + if (!$new_media_id) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['CANNOT_CREATE']; + $return['message'] = self::ERROR_MESSAGES['CANNOT_CREATE']; + $this->auto_http_code(false); + + return $this->json($return); + } + } + + $scheduled_id = $this->internal_scheduled->create($this->user['id'], $at, $text, $id_phone, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups, $media_ids); if (!$scheduled_id) { $return = self::DEFAULT_RETURN; diff --git a/controllers/publics/Callback.php b/controllers/publics/Callback.php index c7d905d..1a4c951 100644 --- a/controllers/publics/Callback.php +++ b/controllers/publics/Callback.php @@ -25,6 +25,7 @@ use Monolog\Logger; private $internal_sended; private $internal_received; private $internal_adapter; + private $internal_media; public function __construct() { @@ -33,6 +34,7 @@ use Monolog\Logger; $this->internal_user = new \controllers\internals\User($bdd); $this->internal_sended = new \controllers\internals\Sended($bdd); $this->internal_received = new \controllers\internals\Received($bdd); + $this->internal_media = new \controllers\internals\Media($bdd); $this->internal_adapter = new \controllers\internals\Adapter(); //Logger @@ -177,8 +179,58 @@ use Monolog\Logger; } $sms = $response['sms']; + $mms = !empty($sms['mms']); + $medias = empty($sms['medias']) ? [] : $sms['medias']; + $media_ids = []; - $response = $this->internal_received->receive($this->user['id'], $id_phone, $sms['text'], $sms['origin'], $sms['at']); + //We create medias to link to the sms + if ($mms) + { + foreach ($medias as $media) + { + try + { + $media['mimetype'] = empty($media['mimetype']) ? mime_content_type($media['filepath']) : $media['mimetype']; + + $mimey = new \Mimey\MimeTypes; + $extension = empty($media['extension']) ? $mimey->getExtension($media['mimetype']) : $media['extension']; + + $new_filename = \controllers\internals\Tool::random_uuid() . '.' . $extension; + $new_filedir = PWD_DATA . '/medias/' . $this->user['id']; + $new_filerelpath = 'medias/' . $this->user['id'] . '/' . $new_filename; + $new_filepath = $new_filedir . '/' . $new_filename; + + //Create user dir if not exists + if (!file_exists($new_filedir)) + { + if (!mkdir($new_filedir, fileperms(PWD_DATA), true)) + { + throw new \Exception('Cannot create dir ' . $new_filedir . ' to copy media : ' . json_encode($media)); + } + } + + if (!rename($media['filepath'], $new_filepath)) + { + throw new \Exception('Cannot copy media : ' . json_encode($media) . ' to ' . $new_filepath); + } + + $new_media_id = $this->internal_media->create($this->user['id'], $new_filerelpath); + if (!$new_media_id) + { + throw new \Exception('Cannot save into db media : ' . json_encode($media)); + } + + $media_ids[] = $new_media_id; + } + catch (\Throwable $t) + { + $this->logger->error($t->getMessage()); + continue; + } + } + } + + $response = $this->internal_received->receive($this->user['id'], $id_phone, $sms['text'], $sms['origin'], $sms['at'], \models\Received::STATUS_UNREAD, $mms, $media_ids); if ($response['error']) { $this->logger->error('Failed receive message : ' . json_encode($sms) . ' with error : ' . $response['error_message']); diff --git a/controllers/publics/Scheduled.php b/controllers/publics/Scheduled.php index 8f270df..81ccd3b 100644 --- a/controllers/publics/Scheduled.php +++ b/controllers/publics/Scheduled.php @@ -250,6 +250,7 @@ namespace controllers\publics; $at = $_POST['at'] ?? false; $text = $_POST['text'] ?? false; $flash = (bool) ($_POST['flash'] ?? false); + $mms = $_FILES['media'] ?? false; $id_phone = empty($_POST['id_phone']) ? null : $_POST['id_phone']; $numbers = $_POST['numbers'] ?? []; $contacts = $_POST['contacts'] ?? []; @@ -292,7 +293,7 @@ namespace controllers\publics; return $this->redirect(\descartes\Router::url('Scheduled', 'add')); } - $scheduled_id = $this->internal_scheduled->create($id_user, $at, $text, $id_phone, $flash, $numbers, $contacts, $groups, $conditional_groups); + $scheduled_id = $this->internal_scheduled->create($id_user, $at, $text, $id_phone, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups); if (!$scheduled_id) { \FlashMessage\FlashMessage::push('danger', 'Impossible de créer le Sms.'); @@ -300,7 +301,7 @@ namespace controllers\publics; return $this->redirect(\descartes\Router::url('Scheduled', 'add')); } - //If mms is enabled, try to process a media to link to the scheduled + //If mms is disabled or no media uploaded, do not process if (!($_SESSION['user']['settings']['mms'] ?? false) || !$media) { \FlashMessage\FlashMessage::push('success', 'Le Sms a bien été créé pour le ' . $at . '.'); diff --git a/daemons/Phone.php b/daemons/Phone.php index 83a4b01..69ef0f2 100644 --- a/daemons/Phone.php +++ b/daemons/Phone.php @@ -139,7 +139,7 @@ class Phone extends AbstractDaemon //Do message sending $this->logger->info('Try send message : ' . json_encode($message)); - $response = $internal_sended->send($this->adapter, $this->phone['id_user'], $this->phone['id'], $message['text'], $message['destination'], $message['flash']); + $response = $internal_sended->send($this->adapter, $this->phone['id_user'], $this->phone['id'], $message['text'], $message['destination'], $message['flash'], $message['mms'], $message['medias']); if ($response['error']) { $this->logger->error('Failed send message : ' . json_encode($message) . ' with error : ' . $response['error_message']); diff --git a/db/migrations/20210317214910_add_media_links_to_sms.php b/db/migrations/20210317214910_add_media_links_to_sms.php new file mode 100644 index 0000000..93e63a2 --- /dev/null +++ b/db/migrations/20210317214910_add_media_links_to_sms.php @@ -0,0 +1,87 @@ +table('media'); + + if ($table->hasColumn('id_scheduled')) + { + if ($table->hasForeignKey('id_scheduled')) + { + $table->dropForeignKey('id_scheduled'); + } + + $table->removeColumn('id_scheduled'); + $table->update(); + } + + if ($table->hasColumn('id_user')) + { + if ($table->hasForeignKey('id_user')) + { + $table->dropForeignKey('id_user'); + } + + $table->removeColumn('id_user'); + $table->update(); + } + + $table->addColumn('id_user', 'integer') + ->addForeignKey('id_user', 'user', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->update(); + + //Add table to join scheduled and media + $table = $this->table('media_scheduled'); + $table->addColumn('id_media', 'integer') + ->addColumn('id_scheduled', 'integer') + ->addForeignKey('id_media', 'media', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->addForeignKey('id_scheduled', 'scheduled', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->create(); + + //Add table to join sended and media + $table = $this->table('media_sended'); + $table->addColumn('id_media', 'integer') + ->addColumn('id_sended', 'integer') + ->addForeignKey('id_media', 'media', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->addForeignKey('id_sended', 'sended', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->create(); + + //Add table to join received and media + $table = $this->table('media_received'); + $table->addColumn('id_media', 'integer') + ->addColumn('id_received', 'integer') + ->addForeignKey('id_media', 'media', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->addForeignKey('id_received', 'received', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->create(); + } +} diff --git a/db/migrations/20210318142759_add_is_mms.php b/db/migrations/20210318142759_add_is_mms.php new file mode 100644 index 0000000..1e7ec14 --- /dev/null +++ b/db/migrations/20210318142759_add_is_mms.php @@ -0,0 +1,46 @@ +table('scheduled'); + $table->addColumn('mms', 'boolean', ['default' => 0, 'null' => false]) + ->update(); + + $table = $this->table('sended'); + $table->addColumn('mms', 'boolean', ['default' => 0, 'null' => false]) + ->update(); + + $table = $this->table('received'); + $table->addColumn('mms', 'boolean', ['default' => 0, 'null' => false]) + ->update(); + } +} diff --git a/models/Media.php b/models/Media.php index 7b7cd83..12da786 100644 --- a/models/Media.php +++ b/models/Media.php @@ -16,53 +16,6 @@ namespace models; */ class Media extends StandardModel { - /** - * Return an entry by his id for a user. - * - * @param int $id_user : user id - * @param int $id : entry id - * - * @return array - */ - public function get_for_user(int $id_user, int $id) - { - $query = ' - SELECT * FROM `' . $this->get_table_name() . '` - WHERE id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - AND id = :id - '; - - $params = [ - 'id' => $id, - 'id_user' => $id_user, - ]; - - $receiveds = $this->_run_query($query, $params); - - return $receiveds[0] ?? []; - } - - /** - * Return all entries for a user. - * - * @param int $id_user : user id - * - * @return array - */ - public function gets_for_user(int $id_user) - { - $query = ' - SELECT * FROM `' . $this->get_table_name() . '` - WHERE id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - '; - - $params = [ - 'id_user' => $id_user, - ]; - - $receiveds = $this->_run_query($query, $params); - } - /** * Return a media for a user and a scheduled. * @@ -71,12 +24,15 @@ namespace models; * * @return array */ - public function get_for_scheduled_and_user(int $id_user, int $id_scheduled) + public function gets_for_scheduled_and_user(int $id_user, int $id_scheduled) { $query = ' - SELECT * FROM `' . $this->get_table_name() . '` - WHERE id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - AND id_scheduled = :id_scheduled + SELECT m.id as id, m.user_id as user_id, m.path as path + FROM `' . $this->get_table_name() . '` as m + INNER JOIN media_scheduled as ms + ON m.id = ms.id_media + WHERE m.id_user = :id_user + AND ms.id_scheduled = :id_scheduled '; $params = [ @@ -84,159 +40,218 @@ namespace models; 'id_scheduled' => $id_scheduled, ]; - $receiveds = $this->_run_query($query, $params); - if (!$receiveds) - { - return false; - } - - return $receiveds[0]; - } - - /** - * Return a list of media for a user. - * - * @param int $id_user : User id - * @param int $limit : Max results to return - * @param int $offset : Number of results to ignore - */ - public function list_for_user($id_user, $limit, $offset) - { - $limit = (int) $limit; - $offset = (int) $offset; - - $query = ' - SELECT * FROM media - WHERE id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - LIMIT ' . $limit . ' OFFSET ' . $offset; - - $params = [ - 'id_user' => $id_user, - ]; - return $this->_run_query($query, $params); } - + /** - * Return a list of medias in a group of ids and for a user. + * Return a media for a user and a sended. * - * @param int $id_user : user id - * @param array $ids : ids of medias to find + * @param int $id_user : user id + * @param int $id_sended : sended id * * @return array */ - public function gets_in_for_user(int $id_user, $ids) + public function gets_for_sended_and_user(int $id_user, int $id_sended) { $query = ' - SELECT * FROM media - WHERE id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - AND id '; - - //On génère la clause IN et les paramètres adaptés depuis le tableau des id - $generated_in = $this->_generate_in_from_array($ids); - $query .= $generated_in['QUERY']; - $params = $generated_in['PARAMS']; - $params['id_user'] = $id_user; - - return $this->_run_query($query, $params); - } - - /** - * Delete a entry by his id for a user. - * - * @param int $id_user : User id - * @param int $id : Entry id - * - * @return int : Number of removed rows - */ - public function delete_for_user(int $id_user, int $id) - { - $query = ' - DELETE FROM media - WHERE id = :id - AND id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - '; - - $params = ['id_user' => $id_user, 'id' => $id]; - - return $this->_run_query($query, $params, self::ROWCOUNT); - } - - /** - * Delete a entry by his id for a user. - * - * @param int $id_user : User id - * @param int $id_scheduled : Scheduled id - * - * @return int : Number of removed rows - */ - public function delete_for_scheduled_and_user(int $id_user, int $id_scheduled) - { - $query = ' - DELETE FROM media - WHERE id_scheduled = :id_scheduled - AND id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - '; - - $params = ['id_user' => $id_user, 'id_scheduled' => $id_scheduled]; - - return $this->_run_query($query, $params, self::ROWCOUNT); - } - - /** - * Update a media sms for a user. - * - * @param int $id_user : User id - * @param int $id : Entry id - * @param array $data : data to update - * - * @return int : number of modified rows - */ - public function update_for_user(int $id_user, int $id, array $data) - { - $params = []; - $sets = []; - - foreach ($data as $label => $value) - { - $label = preg_replace('#[^a-zA-Z0-9_]#', '', $label); - $params['set_' . $label] = $value; - $sets[] = '`' . $label . '` = :set_' . $label . ' '; - } - - $query = ' - UPDATE `media` - SET ' . implode(', ', $sets) . ' - WHERE id = :id - AND id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) - '; - - $params['id'] = $id; - $params['id_user'] = $id_user; - - return $this->_run_query($query, $params, self::ROWCOUNT); - } - - /** - * Count number of media sms for user. - * - * @param int $id_user : user id - * - * @return int : Number of media SMS for user - */ - public function count_for_user($id_user) - { - $query = ' - SELECT COUNT(id) as nb - FROM media - WHERE id_scheduled IN (SELECT id FROM scheduled WHERE id_user = :id_user) + SELECT m.id as id, m.user_id as user_id, m.path as path + FROM `' . $this->get_table_name() . '` as m + INNER JOIN media_sended as ms + ON m.id = ms.id_media + WHERE m.id_user = :id_user + AND ms.id_sended = :id_sended '; $params = [ 'id_user' => $id_user, + 'id_sended' => $id_sended, ]; - return $this->_run_query($query, $params)[0]['nb'] ?? 0; + return $this->_run_query($query, $params); + } + + /** + * Return a media for a user and a received. + * + * @param int $id_user : user id + * @param int $id_received : received id + * + * @return array + */ + public function gets_for_received_and_user(int $id_user, int $id_received) + { + $query = ' + SELECT m.id as id, m.user_id as user_id, m.path as path + FROM `' . $this->get_table_name() . '` as m + INNER JOIN media_received as mr + ON m.id = mr.id_media + WHERE m.id_user = :id_user + AND mr.id_received = :id_received + '; + + $params = [ + 'id_user' => $id_user, + 'id_received' => $id_received, + ]; + + return $this->_run_query($query, $params); + } + + /** + * 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) + { + $entry = [ + 'id_media' => $id_media, + 'id_scheduled' => $id_scheduled, + ]; + + return $this->_insert('media_scheduled', $entry) ? $this->_last_id() : false; + } + + /** + * 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) + { + $entry = [ + 'id_media' => $id_media, + 'id_received' => $id_received, + ]; + + return $this->_insert('media_received', $entry) ? $this->_last_id() : false; + } + + /** + * 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) + { + $entry = [ + 'id_media' => $id_media, + 'id_sended' => $id_sended, + ]; + + return $this->_insert('media_sended', $entry) ? $this->_last_id() : false; + } + + /** + * 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) + { + $where = [ + 'id_media' => $id_media, + 'id_scheduled' => $id_scheduled, + ]; + + return $this->_delete('media_scheduled', $where); + } + + /** + * 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) + { + $where = [ + 'id_media' => $id_media, + 'id_received' => $id_received, + ]; + + return $this->_delete('media_received', $where); + } + + /** + * 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) + { + $where = [ + 'id_media' => $id_media, + 'id_sended' => $id_sended, + ]; + + return $this->_delete('media_sended', $where); + } + + + /** + * Unlink all medias of a scheduled + * + * @param int $id_scheduled : Scheduled id + * + * @return bool | int + */ + public function delete_all_for_scheduled (int $id_scheduled) + { + $where = [ + 'id_scheduled' => $id_scheduled, + ]; + + return $this->_delete('media_scheduled', $where); + } + + /** + * Unlink all medias of a received + * + * @param int $id_received : Scheduled id + * + * @return bool | int + */ + public function delete_all_for_received (int $id_received) + { + $where = [ + 'id_received' => $id_received, + ]; + + return $this->_delete('media_received', $where); + } + + /** + * Unlink all medias of a sended + * + * @param int $id_sended : Scheduled id + * + * @return bool | int + */ + public function delete_all_for_sended (int $id_sended) + { + $where = [ + 'id_sended' => $id_sended, + ]; + + return $this->_delete('media_sended', $where); } /** From 136b3f76ce2d60e56c3624ca65d5f13d907cdf56 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Fri, 19 Mar 2021 21:59:49 +0100 Subject: [PATCH 02/40] working api scheduled + working daemons for phone and sender + working adapters --- controllers/internals/Scheduled.php | 2 +- daemons/Sender.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index 25d1a35..977478f 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -253,7 +253,7 @@ namespace controllers\internals; /** * Get all messages to send and the number to use to send theme. * - * @return array : [['id_scheduled', 'text', 'id_phone', 'destination', 'flash'], ...] + * @return array : [['id_scheduled', 'text', 'id_phone', 'destination', 'flash', 'mms', 'medias'], ...] */ public function get_smss_to_send() { diff --git a/daemons/Sender.php b/daemons/Sender.php index 202349f..a5504bb 100644 --- a/daemons/Sender.php +++ b/daemons/Sender.php @@ -76,6 +76,8 @@ class Sender extends AbstractDaemon 'id_phone' => $sms['id_phone'], 'destination' => $sms['destination'], 'flash' => $sms['flash'], + 'mms' => $sms['mms'], + 'medias' => $sms['medias'] ?? [], ]; msg_send($this->queues[$queue_id], QUEUE_TYPE_SEND_MSG, $msg); From 0dac72cf545310725aba4f5e6bbe5baeb1cd08d6 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sun, 21 Mar 2021 17:08:05 +0100 Subject: [PATCH 03/40] Update api for scheduled + update get medias for + update htaccess to make medias accessibles --- .gitignore | 1 + .htaccess | 1 + controllers/internals/Media.php | 21 +++--- controllers/internals/Scheduled.php | 2 +- controllers/publics/Api.php | 103 ++++++++++++++++++++-------- models/Media.php | 33 ++++----- 6 files changed, 99 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index 2b007f4..7438c69 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ phinx.* data/test_write_sms.json data/test_read_sms.json +data/medias/ !*.dist diff --git a/.htaccess b/.htaccess index e0be5de..ad6df7b 100644 --- a/.htaccess +++ b/.htaccess @@ -1,4 +1,5 @@ RewriteEngine on RewriteRule ^assets - [L] RewriteRule ^.well-known - [L] +RewriteRule ^data/medias - [L] RewriteRule . index.php diff --git a/controllers/internals/Media.php b/controllers/internals/Media.php index 4dd6580..e7f9bae 100644 --- a/controllers/internals/Media.php +++ b/controllers/internals/Media.php @@ -23,7 +23,7 @@ namespace controllers\internals; * * @return mixed bool|int : false on error, new media id else */ - public function create(int $id_user, string $path): bool + public function create(int $id_user, string $path) { $data = [ 'path' => $path, @@ -41,7 +41,7 @@ namespace controllers\internals; * * @return mixed bool|int : false on error, the new link id else */ - public function link_to(int $id_media, int $resource_type, int $resource_id) + public function link_to(int $id_media, string $resource_type, int $resource_id) { switch ($resource_type) { @@ -160,42 +160,39 @@ namespace controllers\internals; } /** - * Find medias for a scheduled and a user. + * Find medias for a scheduled. * - * @param int $id_user : User id * @param int $id_scheduled : Scheduled id to fin medias for * * @return mixed : Medias || false */ - public function gets_for_scheduled_and_user(int $id_user, int $id_scheduled) + public function gets_for_scheduled(int $id_scheduled) { - return $this->get_model()->gets_for_scheduled_and_user($id_user, $id_scheduled); + return $this->get_model()->gets_for_scheduled($id_scheduled); } /** * Find medias for a sended and a user. * - * @param int $id_user : User id * @param int $id_sended : Scheduled id to fin medias for * * @return mixed : Medias || false */ - public function gets_for_sended_and_user(int $id_user, int $id_sended) + public function gets_for_sended(int $id_sended) { - return $this->get_model()->gets_for_sended_and_user($id_user, $id_sended); + return $this->get_model()->gets_for_sended($id_sended); } /** * Find medias for a received and a user. * - * @param int $id_user : User id * @param int $id_received : Scheduled id to fin medias for * * @return mixed : Medias || false */ - public function gets_for_received_and_user(int $id_user, int $id_received) + public function gets_for_received(int $id_received) { - return $this->get_model()->gets_for_received_and_user($id_user, $id_received); + return $this->get_model()->gets_for_received($id_received); } /** diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index 977478f..f21cced 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -297,7 +297,7 @@ namespace controllers\internals; if ($scheduled['mms']) { $internal_media = new Media($this->bdd); - $scheduled['medias'] = $internal_media->gets_for_scheduled_and_user($scheduled['id_user'], $scheduled['id']); + $scheduled['medias'] = $internal_media->gets_for_scheduled($scheduled['id']); } $phone_to_use = null; diff --git a/controllers/publics/Api.php b/controllers/publics/Api.php index 6e0ab20..7943bce 100644 --- a/controllers/publics/Api.php +++ b/controllers/publics/Api.php @@ -151,6 +151,21 @@ namespace controllers\publics; $entries[$key]['contacts'] = $this->internal_scheduled->get_contacts($entry['id']); $entries[$key]['groups'] = $this->internal_scheduled->get_groups($entry['id']); $entries[$key]['conditional_groups'] = $this->internal_scheduled->get_conditional_groups($entry['id']); + $entries[$key]['medias'] = $this->internal_media->gets_for_scheduled($entry['id']); + } + } + elseif ('received' === $entry_type) + { + foreach ($entries as $key => $entry) + { + $entries[$key]['medias'] = $this->internal_media->gets_for_received($entry['id']); + } + } + elseif ('sended' === $entry_type) + { + foreach ($entries as $key => $entry) + { + $entries[$key]['medias'] = $this->internal_media->gets_for_sended($entry['id']); } } //Special case for group we must add contact because its a join @@ -206,13 +221,40 @@ namespace controllers\publics; $contacts = $_POST['contacts'] ?? []; $groups = $_POST['groups'] ?? []; $conditional_groups = $_POST['conditional_groups'] ?? []; - $files = $_FILES ?? []; + $files = $_FILES['medias'] ?? false; $numbers = \is_array($numbers) ? $numbers : [$numbers]; $contacts = \is_array($contacts) ? $contacts : [$contacts]; $groups = \is_array($groups) ? $groups : [$groups]; $conditional_groups = \is_array($conditional_groups) ? $conditional_groups : [$conditional_groups]; + //Iterate over files to re-create individual $_FILES array + $files_arrays = []; + + if ($files === false) + { + $files_arrays = []; + } + elseif (!is_array($files['name'])) //Only one file uploaded + { + $files_arrays[] = $files; + } + else //multiple files + { + foreach ($files as $property_name => $files_values) + { + foreach ($files_values as $file_key => $property_value) + { + if (!isset($files_arrays[$file_key])) + { + $files_arrays[$file_key] = []; + } + + $files_arrays[$file_key][$property_name] = $property_value; + } + } + } + $media_ids = []; if (!$at) @@ -316,45 +358,50 @@ namespace controllers\publics; } } - foreach ($files as $file) + if ($mms) { - $user_media_path = PWD_DATA . '/medias/' . $this->user['id']; - - //Create user medias dir if not exists - if (!file_exists($user_media_path)) + foreach ($files_arrays as $file) { - if (!mkdir($user_media_path, fileperms(PWD_DATA), true)) + $user_media_path = PWD_DATA . '/medias/' . $this->user['id']; + + //Create user medias dir if not exists + if (!file_exists($user_media_path)) + { + if (!mkdir($user_media_path, fileperms(PWD_DATA), true)) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; + $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . ' : Because cannot create medias dir on server for the user.'; + $this->auto_http_code(false); + + return $this->json($return); + } + } + + $result = \controllers\internals\Tool::save_uploaded_file($file, $user_media_path); + if ($result['success'] !== true) { $return = self::DEFAULT_RETURN; $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; - $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . ' : Because cannot create medias dir on server for the user.'; + $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . $file['name'] . ' with error : ' . $result['content']; $this->auto_http_code(false); return $this->json($return); } - } - $result = \controllers\internals\Tool::save_uploaded_file($file, $user_media_path); - if ($result['success'] !== true) - { - $return = self::DEFAULT_RETURN; - $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; - $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . $file['name'] . ' with error : ' . $result['content']; - $this->auto_http_code(false); + $new_filepath = 'medias/' . $this->user['id'] . '/' . $result['content']; + $new_media_id = $this->internal_media->create($this->user['id'], $new_filepath); + if (!$new_media_id) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['CANNOT_CREATE']; + $return['message'] = self::ERROR_MESSAGES['CANNOT_CREATE']; + $this->auto_http_code(false); - return $this->json($return); - } + return $this->json($return); + } - $new_filepath = 'medias/' . $this->user['id'] . '/' . $result['content']; - $new_media_id = $this->internal_media->create($this->user['id'], $new_filepath); - if (!$new_media_id) - { - $return = self::DEFAULT_RETURN; - $return['error'] = self::ERROR_CODES['CANNOT_CREATE']; - $return['message'] = self::ERROR_MESSAGES['CANNOT_CREATE']; - $this->auto_http_code(false); - - return $this->json($return); + $media_ids[] = $new_media_id; } } diff --git a/models/Media.php b/models/Media.php index 12da786..827bcc1 100644 --- a/models/Media.php +++ b/models/Media.php @@ -17,26 +17,23 @@ namespace models; class Media extends StandardModel { /** - * Return a media for a user and a scheduled. + * Return all medias for a scheduled. * - * @param int $id_user : user id * @param int $id_scheduled : scheduled id * * @return array */ - public function gets_for_scheduled_and_user(int $id_user, int $id_scheduled) + public function gets_for_scheduled(int $id_scheduled) { $query = ' - SELECT m.id as id, m.user_id as user_id, m.path as path + SELECT m.id as id, m.id_user as id_user, m.path as path FROM `' . $this->get_table_name() . '` as m INNER JOIN media_scheduled as ms ON m.id = ms.id_media - WHERE m.id_user = :id_user - AND ms.id_scheduled = :id_scheduled + WHERE ms.id_scheduled = :id_scheduled '; $params = [ - 'id_user' => $id_user, 'id_scheduled' => $id_scheduled, ]; @@ -44,26 +41,23 @@ namespace models; } /** - * Return a media for a user and a sended. + * Return all medias for a sended. * - * @param int $id_user : user id * @param int $id_sended : sended id * * @return array */ - public function gets_for_sended_and_user(int $id_user, int $id_sended) + public function gets_for_sended(int $id_sended) { $query = ' - SELECT m.id as id, m.user_id as user_id, m.path as path + SELECT m.id as id, m.id_user as id_user, m.path as path FROM `' . $this->get_table_name() . '` as m INNER JOIN media_sended as ms ON m.id = ms.id_media - WHERE m.id_user = :id_user - AND ms.id_sended = :id_sended + WHERE ms.id_sended = :id_sended '; $params = [ - 'id_user' => $id_user, 'id_sended' => $id_sended, ]; @@ -71,26 +65,23 @@ namespace models; } /** - * Return a media for a user and a received. + * Return all medias for a received. * - * @param int $id_user : user id * @param int $id_received : received id * * @return array */ - public function gets_for_received_and_user(int $id_user, int $id_received) + public function gets_for_received(int $id_received) { $query = ' - SELECT m.id as id, m.user_id as user_id, m.path as path + SELECT m.id as id, m.id_user as id_user, m.path as path FROM `' . $this->get_table_name() . '` as m INNER JOIN media_received as mr ON m.id = mr.id_media - WHERE m.id_user = :id_user - AND mr.id_received = :id_received + WHERE mr.id_received = :id_received '; $params = [ - 'id_user' => $id_user, 'id_received' => $id_received, ]; From 70d01be0415bf8145bc2d976a16670f6f54bb6c1 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sun, 21 Mar 2021 17:14:52 +0100 Subject: [PATCH 04/40] add HTTP_PWD_DATA --- controllers/internals/Sended.php | 4 ++-- env.php.dist | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index 132f082..dfac195 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -211,8 +211,8 @@ namespace controllers\internals; foreach ($medias as $media) { $media_uris[] = [ - 'http_url' => HTTP_PWD . '/data/' . $media['path'], - 'local_uri' => PWD_DATA . '/data/' . $media['path'], + 'http_url' => HTTP_PWD_DATA . '/' . $media['path'], + 'local_uri' => PWD_DATA . '/' . $media['path'], ]; } diff --git a/env.php.dist b/env.php.dist index 3c18bd8..dd77747 100644 --- a/env.php.dist +++ b/env.php.dist @@ -16,6 +16,7 @@ 'HTTP_PWD_SOUND' => HTTP_PWD_ASSETS . '/sounds', 'PWD_ADAPTERS' => PWD . '/adapters', 'PWD_DATA' => PWD . '/data', + 'HTTP_PWD_DATA' => HTTP_PWD . '/data', 'PWD_LOGS' => '/var/log/raspisms', 'PWD_PID' => '/var/run/raspisms', 'APP_SECRET' => '%APP_SECRET%', From f330312b55543f29cb8ea2a9a6e5b3affb48984c Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sun, 21 Mar 2021 18:29:23 +0100 Subject: [PATCH 05/40] Add medias link to mms into received + sended + scheduled + discussion --- controllers/publics/Discussion.php | 40 +++++++++++++++++++++++++++++- controllers/publics/Received.php | 10 ++++++++ controllers/publics/Scheduled.php | 7 ++++++ controllers/publics/Sended.php | 6 +++++ templates/discussion/show.php | 13 ++++++++-- templates/received/list.php | 20 ++++++++++++--- templates/scheduled/list.php | 16 +++++++++++- templates/sended/list.php | 20 ++++++++++++--- 8 files changed, 122 insertions(+), 10 deletions(-) diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index 7c7b79a..da30f5d 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -21,6 +21,7 @@ namespace controllers\publics; private $internal_received; private $internal_contact; private $internal_phone; + private $internal_media; /** * Cette fonction est appelée avant toute les autres : @@ -37,6 +38,7 @@ namespace controllers\publics; $this->internal_received = new \controllers\internals\Received($bdd); $this->internal_contact = new \controllers\internals\Contact($bdd); $this->internal_phone = new \controllers\internals\Phone($bdd); + $this->internal_media = new \controllers\internals\Media($bdd); \controllers\internals\Tool::verifyconnect(); } @@ -112,12 +114,26 @@ namespace controllers\publics; foreach ($sendeds as $sended) { - $messages[] = [ + $medias = []; + if ($sended['mms']) + { + $medias = $this->internal_media->gets_for_sended($sended['id']); + foreach ($medias as &$media) + { + $media = HTTP_PWD_DATA . '/' . $media['path']; + } + } + + $message = [ 'date' => htmlspecialchars($sended['at']), 'text' => htmlspecialchars($sended['text']), 'type' => 'sended', + 'medias' => $medias, 'status' => $sended['status'], ]; + + + $messages[] = $message; } foreach ($receiveds as $received) @@ -126,21 +142,43 @@ namespace controllers\publics; { $this->internal_received->mark_as_read_for_user($id_user, $received['id']); } + + $medias = []; + if ($sended['mms']) + { + $medias = $this->internal_media->gets_for_received($received['id']); + foreach ($medias as &$media) + { + $media = HTTP_PWD_DATA . '/' . $media['path']; + } + } $messages[] = [ 'date' => htmlspecialchars($received['at']), 'text' => htmlspecialchars($received['text']), 'type' => 'received', 'md5' => md5($received['at'] . $received['text']), + 'medias' => $medias, ]; } foreach ($scheduleds as $scheduled) { + $medias = []; + if ($sended['mms']) + { + $medias = $this->internal_media->gets_for_scheduled($scheduled['id']); + foreach ($medias as &$media) + { + $media = HTTP_PWD_DATA . '/' . $media['path']; + } + } + $messages[] = [ 'date' => htmlspecialchars($scheduled['at']), 'text' => htmlspecialchars($scheduled['text']), 'type' => 'inprogress', + 'medias' => $medias, ]; } diff --git a/controllers/publics/Received.php b/controllers/publics/Received.php index e5ba67e..d6ab5a4 100644 --- a/controllers/publics/Received.php +++ b/controllers/publics/Received.php @@ -19,6 +19,7 @@ namespace controllers\publics; private $internal_received; private $internal_contact; private $internal_phone; + private $internal_media; /** * Cette fonction est appelée avant toute les autres : @@ -32,6 +33,7 @@ namespace controllers\publics; $this->internal_received = new \controllers\internals\Received($bdd); $this->internal_contact = new \controllers\internals\Contact($bdd); $this->internal_phone = new \controllers\internals\Phone($bdd); + $this->internal_media = new \controllers\internals\Media($bdd); \controllers\internals\Tool::verifyconnect(); } @@ -53,6 +55,10 @@ namespace controllers\publics; foreach ($entities as &$entity) { $entity['origin_formatted'] = \controllers\internals\Tool::phone_link($entity['origin']); + if ($entity['mms']) + { + $entity['medias'] = $this->internal_media->gets_for_received($entity['id']); + } } header('Content-Type: application/json'); @@ -76,6 +82,10 @@ namespace controllers\publics; foreach ($entities as &$entity) { $entity['origin_formatted'] = \controllers\internals\Tool::phone_link($entity['origin']); + if ($entity['mms']) + { + $entity['medias'] = $this->internal_media->gets_for_received($entity['id']); + } } header('Content-Type: application/json'); diff --git a/controllers/publics/Scheduled.php b/controllers/publics/Scheduled.php index 81ccd3b..2e4d8a7 100644 --- a/controllers/publics/Scheduled.php +++ b/controllers/publics/Scheduled.php @@ -56,6 +56,13 @@ namespace controllers\publics; public function list_json() { $entities = $this->internal_scheduled->list_for_user($_SESSION['user']['id']); + foreach ($entities as &$entity) + { + if ($entity['mms']) + { + $entity['medias'] = $this->internal_media->gets_for_scheduled($entity['id']); + } + } header('Content-Type: application/json'); echo json_encode(['data' => $entities]); diff --git a/controllers/publics/Sended.php b/controllers/publics/Sended.php index fa0a67c..8227e38 100644 --- a/controllers/publics/Sended.php +++ b/controllers/publics/Sended.php @@ -19,6 +19,7 @@ namespace controllers\publics; private $internal_sended; private $internal_phone; private $internal_contact; + private $internal_media; /** * Cette fonction est appelée avant toute les autres : @@ -32,6 +33,7 @@ namespace controllers\publics; $this->internal_sended = new \controllers\internals\Sended($bdd); $this->internal_phone = new \controllers\internals\Phone($bdd); $this->internal_contact = new \controllers\internals\Contact($bdd); + $this->internal_media = new \controllers\internals\Media($bdd); \controllers\internals\Tool::verifyconnect(); } @@ -55,6 +57,10 @@ namespace controllers\publics; foreach ($entities as &$entity) { $entity['destination_formatted'] = \controllers\internals\Tool::phone_link($entity['destination']); + if ($entity['mms']) + { + $entity['medias'] = $this->internal_media->gets_for_sended($entity['id']); + } } header('Content-Type: application/json'); diff --git a/templates/discussion/show.php b/templates/discussion/show.php index bb6a357..835761a 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -76,7 +76,7 @@ //On ajoute la detection de lien dans le texte du message message.text = Autolinker.link(message.text, {newWindow:true}); - + switch (message.type) { @@ -85,7 +85,10 @@ '
' + '
' + '
' + message.text + '
' + - '
' + message.date + '
' + + '
' + message.medias.map((mediaUrl, index) => { + return 'Voir le fichier ' + (index + 1) + ''; + }).join(' - ') + '
' + + '
' + message.date + '
' + '
' + '
'; @@ -101,6 +104,9 @@ '
' + '
' + '
' + message.text + '
' + + '
' + message.medias.map((mediaUrl, index) => { + return 'Voir le fichier ' + (index + 1) + ''; + }).join(' - ') + '
' + '
' + message.date + ' ' + (message.status == 'delivered' ? '' : (message.status == 'failed' ? '' : '' )) + '
' + '
' + '
'; @@ -111,6 +117,9 @@ '
' + '
' + '
' + message.text + '
' + + '
' + message.medias.map((mediaUrl, index) => { + return 'Voir le fichier ' + (index + 1) + ''; + }).join(' - ') + '
' + '
' + message.date + '
' + '
' + ''; diff --git a/templates/received/list.php b/templates/received/list.php index 5611fb0..763a28e 100644 --- a/templates/received/list.php +++ b/templates/received/list.php @@ -45,8 +45,8 @@ - - + + @@ -104,7 +104,21 @@ jQuery(document).ready(function () }, }, {data: 'phone_name', render: jQuery.fn.dataTable.render.text()}, - {data: 'text', render: jQuery.fn.dataTable.render.text()}, + { + data: 'text', + render: function (data, type, row, meta) { + if (row.mms == 1) { + var medias = []; + for (i = 0; i < row.medias.length; i++) { + medias.push('Fichier ' + (i + 1) + ''); + } + html = data + '
' + medias.join(' - '); + return html; + } + + return data; + }, + }, {data: 'at', render: jQuery.fn.dataTable.render.text()}, { data: 'status', diff --git a/templates/scheduled/list.php b/templates/scheduled/list.php index cfeda14..ecfd708 100644 --- a/templates/scheduled/list.php +++ b/templates/scheduled/list.php @@ -87,7 +87,21 @@ jQuery(document).ready(function () }, "columns" : [ {data: 'at', render: jQuery.fn.dataTable.render.text()}, - {data: 'text', render: jQuery.fn.dataTable.render.text()}, + { + data: 'text', + render: function (data, type, row, meta) { + if (row.mms == 1) { + var medias = []; + for (i = 0; i < row.medias.length; i++) { + medias.push('Fichier ' + (i + 1) + ''); + } + html = data + '
' + medias.join(' - '); + return html; + } + + return data; + }, + }, { data: 'id', render: function (data, type, row, meta) { diff --git a/templates/sended/list.php b/templates/sended/list.php index 726cb2b..d8c6289 100644 --- a/templates/sended/list.php +++ b/templates/sended/list.php @@ -39,8 +39,8 @@
DeÀExpéditeurDestinataire Message Date Status
- - + + @@ -94,7 +94,21 @@ jQuery(document).ready(function () return row.destination_formatted; }, }, - {data: 'text', render: jQuery.fn.dataTable.render.text()}, + { + data: 'text', + render: function (data, type, row, meta) { + if (row.mms == 1) { + var medias = []; + for (i = 0; i < row.medias.length; i++) { + medias.push('Fichier ' + (i + 1) + ''); + } + html = data + '
' + medias.join(' - '); + return html; + } + + return data; + }, + }, {data: 'at', render: jQuery.fn.dataTable.render.text()}, { data: 'status', From 3fff7e0abd7ed3aa33d30c2d48a22f296e909e91 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Mon, 22 Mar 2021 01:29:06 +0100 Subject: [PATCH 06/40] add medias to scheduled creation --- controllers/internals/Media.php | 32 ++++++- controllers/publics/Api.php | 32 +------ controllers/publics/Scheduled.php | 145 +++++++++++++++++++++--------- templates/scheduled/add.php | 10 ++- templates/scheduled/edit.php | 25 +++--- 5 files changed, 157 insertions(+), 87 deletions(-) diff --git a/controllers/internals/Media.php b/controllers/internals/Media.php index e7f9bae..2f11b76 100644 --- a/controllers/internals/Media.php +++ b/controllers/internals/Media.php @@ -33,6 +33,36 @@ namespace controllers\internals; return $this->get_model()->insert($data); } + /** + * 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 mixed bool | int : False on error, or new media id on success + */ + public function upload_and_create_for_user(int $id_user, array $file) + { + $user_media_path = PWD_DATA . '/medias/' . $id_user; + + //Create user medias dir if not exists + if (!file_exists($user_media_path)) + { + if (!mkdir($user_media_path, fileperms(PWD_DATA), true)) + { + return false; + } + } + + $upload_result = \controllers\internals\Tool::save_uploaded_file($file, $user_media_path); + if ($upload_result['success'] !== true) + { + return false; + } + + $new_filepath = 'medias/' . $id_user . '/' . $upload_result['content']; + return $this->create($id_user, $new_filepath); + } + /** * Link a media to a scheduled, a received or a sended message * @param int $id_media : Id of the media @@ -99,7 +129,7 @@ namespace controllers\internals; * * @return mixed bool : false on error, true on success */ - public function unlink_all_of(int $resource_type, int $resource_id) + public function unlink_all_of(string $resource_type, int $resource_id) { switch ($resource_type) { diff --git a/controllers/publics/Api.php b/controllers/publics/Api.php index 7943bce..a726345 100644 --- a/controllers/publics/Api.php +++ b/controllers/publics/Api.php @@ -362,40 +362,12 @@ namespace controllers\publics; { foreach ($files_arrays as $file) { - $user_media_path = PWD_DATA . '/medias/' . $this->user['id']; - - //Create user medias dir if not exists - if (!file_exists($user_media_path)) - { - if (!mkdir($user_media_path, fileperms(PWD_DATA), true)) - { - $return = self::DEFAULT_RETURN; - $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; - $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . ' : Because cannot create medias dir on server for the user.'; - $this->auto_http_code(false); - - return $this->json($return); - } - } - - $result = \controllers\internals\Tool::save_uploaded_file($file, $user_media_path); - if ($result['success'] !== true) - { - $return = self::DEFAULT_RETURN; - $return['error'] = self::ERROR_CODES['CANNOT_UPLOAD_FILE']; - $return['message'] = self::ERROR_MESSAGES['CANNOT_UPLOAD_FILE'] . $file['name'] . ' with error : ' . $result['content']; - $this->auto_http_code(false); - - return $this->json($return); - } - - $new_filepath = 'medias/' . $this->user['id'] . '/' . $result['content']; - $new_media_id = $this->internal_media->create($this->user['id'], $new_filepath); + $new_media_id = $this->internal_media->upload_and_create_for_user($this->user['id'], $file); if (!$new_media_id) { $return = self::DEFAULT_RETURN; $return['error'] = self::ERROR_CODES['CANNOT_CREATE']; - $return['message'] = self::ERROR_MESSAGES['CANNOT_CREATE']; + $return['message'] = self::ERROR_MESSAGES['CANNOT_CREATE'] . ' : Cannot upload and create media file ' . $file['name']; $this->auto_http_code(false); return $this->json($return); diff --git a/controllers/publics/Scheduled.php b/controllers/publics/Scheduled.php index 2e4d8a7..677da5e 100644 --- a/controllers/publics/Scheduled.php +++ b/controllers/publics/Scheduled.php @@ -212,8 +212,8 @@ namespace controllers\publics; $scheduleds[$key]['groups'][] = (int) $group['id']; } - $media = $this->internal_media->get_for_scheduled_and_user($id_user, $scheduled['id']); - $scheduleds[$key]['media'] = $media; + $medias = $this->internal_media->gets_for_scheduled($scheduled['id']); + $scheduleds[$key]['medias'] = $medias; $conditional_groups = $this->internal_scheduled->get_conditional_groups($scheduled['id']); foreach ($conditional_groups as $conditional_group) @@ -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['media'] : The media to link to a scheduled + * @param ?array $_FILES['medias'] : The media to link to a scheduled */ public function create($csrf) { @@ -257,13 +257,39 @@ namespace controllers\publics; $at = $_POST['at'] ?? false; $text = $_POST['text'] ?? false; $flash = (bool) ($_POST['flash'] ?? false); - $mms = $_FILES['media'] ?? false; $id_phone = empty($_POST['id_phone']) ? null : $_POST['id_phone']; $numbers = $_POST['numbers'] ?? []; $contacts = $_POST['contacts'] ?? []; $groups = $_POST['groups'] ?? []; $conditional_groups = $_POST['conditional_groups'] ?? []; - $media = $_FILES['media'] ?? false; + $files = $_FILES['medias'] ?? false; + + //Iterate over files to re-create individual $_FILES array + $files_arrays = []; + if ($files && is_array($files['name'])) + { + foreach ($files as $property_name => $files_values) + { + foreach ($files_values as $file_key => $property_value) + { + if (!isset($files_arrays[$file_key])) + { + $files_arrays[$file_key] = []; + } + + $files_arrays[$file_key][$property_name] = $property_value; + } + } + } + + //Remove empty files input + foreach ($files_arrays as $key => $file) + { + if ($file['error'] === UPLOAD_ERR_NO_FILE) + { + unset($files_arrays[$key]); + } + } if (empty($text)) { @@ -299,8 +325,27 @@ 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) + { + foreach ($files_arrays as $file) + { + $new_media_id = $this->internal_media->upload_and_create_for_user($_SESSION['user']['id'], $file); + if (!$new_media_id) + { + \FlashMessage\FlashMessage::push('danger', 'Impossible d\'upload et d\'enregistrer le fichier ' . $file['name']); + return $this->redirect(\descartes\Router::url('Scheduled', 'add')); + } - $scheduled_id = $this->internal_scheduled->create($id_user, $at, $text, $id_phone, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups); + $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) { \FlashMessage\FlashMessage::push('danger', 'Impossible de créer le Sms.'); @@ -308,22 +353,6 @@ namespace controllers\publics; return $this->redirect(\descartes\Router::url('Scheduled', 'add')); } - //If mms is disabled or no media uploaded, do not process - if (!($_SESSION['user']['settings']['mms'] ?? false) || !$media) - { - \FlashMessage\FlashMessage::push('success', 'Le Sms a bien été créé pour le ' . $at . '.'); - - return $this->redirect(\descartes\Router::url('Scheduled', 'list')); - } - - $success = $this->internal_media->create($id_user, $scheduled_id, $media); - if (!$success) - { - \FlashMessage\FlashMessage::push('success', 'Le SMS a bien été créé mais le média n\'as pas pu être enregistré.'); - - return $this->redirect(\descartes\Router::url('Scheduled', 'list')); - } - \FlashMessage\FlashMessage::push('success', 'Le Sms a bien été créé pour le ' . $at . '.'); return $this->redirect(\descartes\Router::url('Scheduled', 'list')); @@ -360,13 +389,43 @@ namespace controllers\publics; $contacts = $scheduled['contacts'] ?? []; $groups = $scheduled['groups'] ?? []; $conditional_groups = $scheduled['conditional_groups'] ?? []; + $files = $_FILES['scheduleds_' . $id_scheduled . '_medias'] ?? false; + $media_ids = $scheduled['media_ids'] ?? []; + //Check scheduled exists and belong to user $scheduled = $this->internal_scheduled->get($id_scheduled); if (!$scheduled || $scheduled['id_user'] !== $id_user) { continue; } + //Iterate over files to re-create individual $_FILES array + $files_arrays = []; + if ($files && is_array($files['name'])) + { + foreach ($files as $property_name => $files_values) + { + foreach ($files_values as $file_key => $property_value) + { + if (!isset($files_arrays[$file_key])) + { + $files_arrays[$file_key] = []; + } + + $files_arrays[$file_key][$property_name] = $property_value; + } + } + } + + //Remove empty files input + foreach ($files_arrays as $key => $file) + { + if ($file['error'] === UPLOAD_ERR_NO_FILE) + { + unset($files_arrays[$key]); + } + } + if (empty($text)) { continue; @@ -394,32 +453,36 @@ namespace controllers\publics; { continue; } - - $success = $this->internal_scheduled->update_for_user($id_user, $id_scheduled, $at, $text, $id_phone, $flash, $numbers, $contacts, $groups, $conditional_groups); - - //Check for media - /* - $current_media = $scheduled['current_media'] ?? false; - if (!$current_media) + + //If mms is enable and we have medias uploaded + if ($_SESSION['user']['settings']['mms'] && $files_arrays) { - $this->internal_media->delete_for_scheduled_and_user($id_user, $id_scheduled); + foreach ($files_arrays as $file) + { + $new_media_id = $this->internal_media->upload_and_create_for_user($_SESSION['user']['id'], $file); + if (!$new_media_id) + { + continue 2; + } + + $media_ids[] = $new_media_id; + } } - $media = $_FILES['media_' . $id_scheduled] ?? false; - if (!$media) + //Ensure media_ids point to medias belongings to the current user + foreach ($media_ids as $key => $media_id) { - $nb_update += (int) $success; - continue; + $media = $this->internal_media->get($media_id); + if (!$media || $media['id_user'] !== $_SESSION['user']['id']) + { + unset($media_ids[$key]); + } } - $success = $this->internal_media->create($id_user, $id_scheduled, $media); - if (!$success) - { - continue; - } - */ + $mms = (bool) count($media_ids); - ++$nb_update; + $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++; } if ($nb_update !== \count($scheduleds)) diff --git a/templates/scheduled/add.php b/templates/scheduled/add.php index 13ca007..4ce6619 100644 --- a/templates/scheduled/add.php +++ b/templates/scheduled/add.php @@ -64,12 +64,14 @@ -
- +
+

- Le média sera utilisé uniquement si le téléphone utilisé supporte l'envoi de MMS. Pour plus d'information, consultez la documentation sur l'utilisation des MMS. + 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 l'utilisation des MMS..

- +
+ +
diff --git a/templates/scheduled/edit.php b/templates/scheduled/edit.php index 794cd0f..bfd788d 100644 --- a/templates/scheduled/edit.php +++ b/templates/scheduled/edit.php @@ -64,17 +64,21 @@

- Le média sera utilisé uniquement si le téléphone utilisé supporte l'envoi de MMS. Pour plus d'information, consultez la documentation sur l'utilisation des MMS. + 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 l'utilisation des MMS..

- -
- -

Un média est déjà lié à ce message.

- Supprimer le média +
+ +
+ +
+ + $media) { ?> +

+ +
Voir le média Supprimer le média +

+
- - -
@@ -246,8 +250,7 @@ jQuery('body').on('click', '.btn-delete-media', function (e) { e.preventDefault(); - jQuery(this).parents('.form-group').find('input').removeClass('hidden'); - jQuery(this).parents('.form-group').find('.current-media-container').remove(); + jQuery(this).parents('.current-media').remove(); }); jQuery('body').on('click', '.preview-button', function (e) From 0f6bf9ccde8fe09d2bf37769e4bd72a3f8770fc6 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Mon, 22 Mar 2021 02:08:59 +0100 Subject: [PATCH 07/40] add medias to discussion --- assets/css/style.css | 6 +++ controllers/internals/Scheduled.php | 7 +++- controllers/publics/Discussion.php | 63 ++++++++++++++++++++++++++++- templates/discussion/show.php | 1 + 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index 8be577c..c852c62 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -254,6 +254,12 @@ footer img margin-top: 8px; } +.message-input input[type="file"] +{ + margin-top: 8px; + display: inline-block; +} + .message-in-progress-hover { position: absolute; diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index f21cced..b45c6ce 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -43,6 +43,11 @@ namespace controllers\internals; 'mms' => $mms, ]; + if ($text === '') + { + return false; + } + if (null !== $id_phone) { $internal_phone = new Phone($this->bdd); @@ -441,7 +446,7 @@ namespace controllers\internals; foreach ($messages as $message) { //Remove empty messages - if ('' === trim($message['text'])) + if ('' === trim($message['text']) && !$message['medias']) { continue; } diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index da30f5d..42cc010 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -203,6 +203,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 * * @return string : json string Le statut de l'envoi */ @@ -228,6 +229,43 @@ namespace controllers\publics; $text = $_POST['text'] ?? ''; $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 = []; + if ($files && is_array($files['name'])) + { + foreach ($files as $property_name => $files_values) + { + foreach ($files_values as $file_key => $property_value) + { + if (!isset($files_arrays[$file_key])) + { + $files_arrays[$file_key] = []; + } + + $files_arrays[$file_key][$property_name] = $property_value; + } + } + } + + //Remove empty files input + foreach ($files_arrays as $key => $file) + { + if ($file['error'] === UPLOAD_ERR_NO_FILE) + { + unset($files_arrays[$key]); + } + } + + if (!$text) + { + $return['success'] = false; + $return['message'] = 'Vous devez renseigner le texte de votre sms.'; + echo json_encode($return); + + return false; + } if (!$destination) { @@ -243,10 +281,33 @@ namespace controllers\publics; $id_phone = null; } + + //If mms is enable and we have medias uploaded + $media_ids = []; + if ($_SESSION['user']['settings']['mms'] && $files_arrays) + { + foreach ($files_arrays as $file) + { + $new_media_id = $this->internal_media->upload_and_create_for_user($_SESSION['user']['id'], $file); + if (!$new_media_id) + { + $return['success'] = false; + $return['message'] = 'Impossible d\'upload et d\'enregistrer le fichier ' . $file['name']; + echo json_encode($return); + + return false; + } + + $media_ids[] = $new_media_id; + } + } + + $mms = (bool) count($media_ids); + //Destinations must be an array of number $destinations = [$destination]; - if (!$this->internal_scheduled->create($id_user, $at, $text, $id_phone, false, $destinations)) + if (!$this->internal_scheduled->create($id_user, $at, $text, $id_phone, false, $mms, $destinations, [], [], [], $media_ids)) { $return['success'] = false; $return['message'] = 'Impossible de créer le Sms'; diff --git a/templates/discussion/show.php b/templates/discussion/show.php index 835761a..13402c3 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -42,6 +42,7 @@ +
From a081567a3956c4cd47cb5a8b0a98de089bf3bff0 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Mon, 22 Mar 2021 17:08:29 +0100 Subject: [PATCH 08/40] add preview of images for mms in discussion --- assets/css/style.css | 27 +++++++++++++++++++++++++++ templates/discussion/show.php | 33 ++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index c852c62..a15c2e1 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -205,6 +205,33 @@ footer img font-size: 0.9em; } +.discussion-message-medias img +{ + transition-duration: 0.1s; + max-width: 150px; + max-height: 150px; + line-height: 150px; + width: auto; + height: auto; +} + +.discussion-message-medias img:hover +{ + opacity: 0.6; +} + +.discussion-message-media +{ + line-height: 160px; + width: 160px; + height: 160px; + padding: 5px; + margin-bottom: 15px; + background-color: rgba(255, 255, 255, 0.3); + vertical-align: middle; + text-align: center; +} + .message-container { margin-bottom: 10px; diff --git a/templates/discussion/show.php b/templates/discussion/show.php index 13402c3..2dbaa47 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -79,16 +79,27 @@ message.text = Autolinker.link(message.text, {newWindow:true}); + var medias = message.medias.map((mediaUrl, index) => { + var extension = mediaUrl.split('.').pop(); + if (['jpg', 'jpeg', 'png', 'gif'].includes(extension)) + { + return '
'; + } + else + { + return ''; + } + }); + var medias_html = '
' + medias.join('') + '
'; + switch (message.type) - { + { case 'received' : var texte = '' + '
' + '
' + - '
' + message.text + '
' + - '
' + message.medias.map((mediaUrl, index) => { - return 'Voir le fichier ' + (index + 1) + ''; - }).join(' - ') + '
' + + '
' + message.text + '
' + + medias.html + '
' + message.date + '
' + '
' + '
'; @@ -105,10 +116,8 @@ '
' + '
' + '
' + message.text + '
' + - '
' + message.medias.map((mediaUrl, index) => { - return 'Voir le fichier ' + (index + 1) + ''; - }).join(' - ') + '
' + - '
' + message.date + ' ' + (message.status == 'delivered' ? '' : (message.status == 'failed' ? '' : '' )) + '
' + + medias_html + + '
' + message.date + ' ' + (message.status == 'delivered' ? '' : (message.status == 'failed' ? '' : '' )) + '
' + '
' + '
'; break; @@ -117,10 +126,8 @@ '
' + '
' + '
' + - '
' + message.text + '
' + - '
' + message.medias.map((mediaUrl, index) => { - return 'Voir le fichier ' + (index + 1) + ''; - }).join(' - ') + '
' + + '
' + message.text + '
' + + medias_html + '
' + message.date + '
' + '
' + '
'; From e339dc4758f6bbc87c9906382ea1dfce3c13cb49 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Mon, 22 Mar 2021 17:10:07 +0100 Subject: [PATCH 09/40] inline mms medias --- assets/css/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/css/style.css b/assets/css/style.css index a15c2e1..ba50ec0 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -222,14 +222,15 @@ footer img .discussion-message-media { + display: inline-block; line-height: 160px; width: 160px; height: 160px; padding: 5px; - margin-bottom: 15px; background-color: rgba(255, 255, 255, 0.3); vertical-align: middle; text-align: center; + margin: 5px; } .message-container From 52a0302dc24b9ee8befc6a03d14b1b1970e4b32f Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 23 Mar 2021 04:31:13 +0100 Subject: [PATCH 10/40] start adding call support --- controllers/internals/Received.php | 4 +- controllers/internals/Sended.php | 4 +- controllers/internals/Webhook.php | 16 +-- controllers/publics/Call.php | 81 +++++++++++++ controllers/publics/Callback.php | 110 ++++++++++++++++++ .../20210322193953_update_webhook_types.php | 16 +++ db/migrations/20210322223812_create_call.php | 47 ++++++++ models/Call.php | 29 +++++ models/Webhook.php | 5 +- routes.php | 1 + templates/webhook/add.php | 1 + templates/webhook/list.php | 2 + 12 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 controllers/publics/Call.php create mode 100644 db/migrations/20210322193953_update_webhook_types.php create mode 100644 db/migrations/20210322223812_create_call.php create mode 100644 models/Call.php diff --git a/controllers/internals/Received.php b/controllers/internals/Received.php index 0f0388a..c685adb 100644 --- a/controllers/internals/Received.php +++ b/controllers/internals/Received.php @@ -287,11 +287,11 @@ namespace controllers\internals; 'origin' => $origin, 'command' => $is_command, 'mms' => $mms, - 'medias' => $media_ids, + 'medias' => $this->get_model()->gets_in_for_user($id_user, $media_ids), ]; $internal_webhook = new Webhook($this->bdd); - $internal_webhook->trigger($id_user, \models\Webhook::TYPE_RECEIVE, $received); + $internal_webhook->trigger($id_user, \models\Webhook::TYPE_RECEIVE_SMS, $received); $internal_user = new User($this->bdd); $internal_user->transfer_received($id_user, $received); diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index dfac195..ee3ea04 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -236,10 +236,12 @@ namespace controllers\internals; 'text' => $text, 'destination' => $destination, 'origin' => $id_phone, + 'mms' => $mms, + 'medias' => $medias, ]; $internal_webhook = new Webhook($this->bdd); - $internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND, $sended); + $internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $sended); return $return; } diff --git a/controllers/internals/Webhook.php b/controllers/internals/Webhook.php index 8d25108..3cbaff0 100644 --- a/controllers/internals/Webhook.php +++ b/controllers/internals/Webhook.php @@ -94,17 +94,11 @@ class Webhook extends StandardController * * @param int $id_user : User to trigger the webhook for * @param string $type : Type of webhook to trigger - * @param array $sms : The sms [ - * int 'id' => SMS id, - * string 'at' => SMS date, - * string 'text' => sms body, - * string 'origin' => sms origin (number or phone id) - * string 'destination' => sms destination (number or phone id) - * ] + * @param array $body : The body, an array depending on webhook type * * @return bool : False if no trigger, true else */ - public function trigger(int $id_user, string $type, array $sms) + public function trigger(int $id_user, string $type, array $body) { $internal_setting = new Setting($this->bdd); $internal_user = new User($this->bdd); @@ -137,11 +131,7 @@ class Webhook extends StandardController 'webhook_type' => $webhook['type'], 'webhook_random_id' => $webhook_random_id, 'webhook_signature' => $webhook_signature, - 'id' => $sms['id'], - 'at' => $sms['at'], - 'text' => $sms['text'], - 'origin' => $sms['origin'], - 'destination' => $sms['destination'], + 'body' => json_encode($body), ], ]; diff --git a/controllers/publics/Call.php b/controllers/publics/Call.php new file mode 100644 index 0000000..55acbb2 --- /dev/null +++ b/controllers/publics/Call.php @@ -0,0 +1,81 @@ + + * + * 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 Call extends StandardController + { + protected $model; + + /** + * Create a media. + * + * @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 + * + * @return mixed bool|int : false on error, new call id else + */ + public function create(int $id_user, int $id_phone, string $uid, string $direction, string $start, ?string $end = null, ?string $origin = null, ?string $destination = null) + { + $call = [ + 'id_user' => $id_user, + 'id_phone' => $id_phone, + 'uid' => $uid, + 'start' => $start, + 'end' => $end, + 'direction' => $direction, + 'origin' => $origin, + 'destination' => $destination, + ]; + + if (!$origin && !$destination) + { + return false; + } + + switch ($direction) + { + case \models\Call::DIRECTION_OUTBOUND : + null === $destination ?: return false; + break; + + case \models\Call::DIRECTION_INBOUND : + null === $origin ?: return false; + break; + + default : + return false; + } + + if (!\controllers\internals\Tool::validate_date($start, 'Y-m-d H:i:s')) + { + return false; + } + + if (null !== $end && !\controllers\internals\Tool::validate_date($end, 'Y-m-d H:i:s')) + { + return false; + } + + if (null !== $end && new \DateTime($end) < new \DateTime($start)) + { + return false; + } + + return $this->get_model()->insert($call); + } + } diff --git a/controllers/publics/Callback.php b/controllers/publics/Callback.php index 1a4c951..5c6e32e 100644 --- a/controllers/publics/Callback.php +++ b/controllers/publics/Callback.php @@ -26,6 +26,7 @@ use Monolog\Logger; private $internal_received; private $internal_adapter; private $internal_media; + private $internal_phone; public function __construct() { @@ -36,6 +37,7 @@ use Monolog\Logger; $this->internal_received = new \controllers\internals\Received($bdd); $this->internal_media = new \controllers\internals\Media($bdd); $this->internal_adapter = new \controllers\internals\Adapter(); + $this->internal_phone = new \controllers\internals\Phone(); //Logger $this->logger = new Logger('Callback ' . uniqid()); @@ -242,4 +244,112 @@ 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 + * + * @return bool : true on success, false on error + */ + public function inbound_call(int $id_phone) + { + $this->logger->info('Callback reception call with phone : ' . $id_phone); + $phone = $this->internal_phone->get_for_user($this->user['id'], $id_phone); + + if (!$phone) + { + $this->logger->error('Callback inbound_call use non existing phone : ' . $id_phone); + + return false; + } + + if (!class_exists($phone['adapter'])) + { + $this->logger->error('Callback inbound_call use non existing adapter : ' . $phone['adapter']); + + return false; + } + + if (!$phone['adapter']::meta_support_inbound_call_callback()) + { + $this->logger->error('Callback inbound_call use adapter ' . $phone['adapter'] . ' which does not support inbound_call callback.'); + + return false; + } + + $response = $phone['adapter']::inbound_call_callback(); + if ($response['error']) + { + $this->logger->error('Callback reception with adapter ' . $adapter_uid . ' failed : ' . $response['error_message']); + + return false; + } + + $sms = $response['sms']; + $mms = !empty($sms['mms']); + $medias = empty($sms['medias']) ? [] : $sms['medias']; + $media_ids = []; + + //We create medias to link to the sms + if ($mms) + { + foreach ($medias as $media) + { + try + { + $media['mimetype'] = empty($media['mimetype']) ? mime_content_type($media['filepath']) : $media['mimetype']; + + $mimey = new \Mimey\MimeTypes; + $extension = empty($media['extension']) ? $mimey->getExtension($media['mimetype']) : $media['extension']; + + $new_filename = \controllers\internals\Tool::random_uuid() . '.' . $extension; + $new_filedir = PWD_DATA . '/medias/' . $this->user['id']; + $new_filerelpath = 'medias/' . $this->user['id'] . '/' . $new_filename; + $new_filepath = $new_filedir . '/' . $new_filename; + + //Create user dir if not exists + if (!file_exists($new_filedir)) + { + if (!mkdir($new_filedir, fileperms(PWD_DATA), true)) + { + throw new \Exception('Cannot create dir ' . $new_filedir . ' to copy media : ' . json_encode($media)); + } + } + + if (!rename($media['filepath'], $new_filepath)) + { + throw new \Exception('Cannot copy media : ' . json_encode($media) . ' to ' . $new_filepath); + } + + $new_media_id = $this->internal_media->create($this->user['id'], $new_filerelpath); + if (!$new_media_id) + { + throw new \Exception('Cannot save into db media : ' . json_encode($media)); + } + + $media_ids[] = $new_media_id; + } + catch (\Throwable $t) + { + $this->logger->error($t->getMessage()); + continue; + } + } + } + + $response = $this->internal_received->receive($this->user['id'], $id_phone, $sms['text'], $sms['origin'], $sms['at'], \models\Received::STATUS_UNREAD, $mms, $media_ids); + if ($response['error']) + { + $this->logger->error('Failed receive message : ' . json_encode($sms) . ' with error : ' . $response['error_message']); + + return false; + } + + $this->logger->info('Callback reception successfully received message : ' . json_encode($sms)); + + return true; + } } diff --git a/db/migrations/20210322193953_update_webhook_types.php b/db/migrations/20210322193953_update_webhook_types.php new file mode 100644 index 0000000..80f2392 --- /dev/null +++ b/db/migrations/20210322193953_update_webhook_types.php @@ -0,0 +1,16 @@ +execute('ALTER TABLE `webhook` MODIFY `type` ENUM(\'send_sms\', \'receive_sms\', \'inbound_call\')'); + } + + public function down() + { + $this->execute('ALTER TABLE `webhook` MODIFY `type` ENUM(\'send_sms\', \'receive_sms\')'); + } +} diff --git a/db/migrations/20210322223812_create_call.php b/db/migrations/20210322223812_create_call.php new file mode 100644 index 0000000..dda93d0 --- /dev/null +++ b/db/migrations/20210322223812_create_call.php @@ -0,0 +1,47 @@ +table('call'); + $table->addColumn('id_user', 'integer') + ->addColumn('id_phone', 'integer', ['null' => true]) + ->addColumn('uid', 'string', ['limit' => 500]) + ->addColumn('start', 'datetime') + ->addColumn('end', 'datetime', ['null' => true]) + ->addColumn('direction', 'enum', ['values' => ['inbound', 'outbound']]) + ->addColumn('origin', 'string', ['limit' => 20, 'null' => true]) + ->addColumn('destination', 'string', ['limit' => 20, 'null' => true]) + ->addForeignKey('id_user', 'user', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']) + ->addForeignKey('id_phone', 'user', 'id', ['delete' => 'SET_NULL', 'update' => 'CASCADE']) + ->create(); + } +} diff --git a/models/Call.php b/models/Call.php new file mode 100644 index 0000000..fee82b3 --- /dev/null +++ b/models/Call.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the GPL-3.0 license that is bundled + * with this source code in the file LICENSE. + */ + +namespace models; + + /** + * Manage bdd operations for calls + */ + class Call extends StandardModel + { + const DIRECTION_INBOUND = 'inbound'; + const DIRECTION_OUTBOUND = 'outbound'; + + /** + * Return table name. + */ + protected function get_table_name(): string + { + return 'call'; + } + } diff --git a/models/Webhook.php b/models/Webhook.php index 945cd82..48d4998 100644 --- a/models/Webhook.php +++ b/models/Webhook.php @@ -13,8 +13,9 @@ namespace models; class Webhook extends StandardModel { - const TYPE_SEND = 'send_sms'; - const TYPE_RECEIVE = 'receive_sms'; + const TYPE_SEND_SMS = 'send_sms'; + const TYPE_RECEIVE_SMS = 'receive_sms'; + const TYPE_INBOUND_CALL = 'inbound_call'; /** * Find all webhooks for a user and for a type of webhook. diff --git a/routes.php b/routes.php index 121d4ba..bf66579 100644 --- a/routes.php +++ b/routes.php @@ -168,6 +168,7 @@ 'Callback' => [ 'update_sended_status' => '/callback/status/{adapter_uid}/', 'reception' => '/callback/reception/{adapter_uid}/{id_phone}/', + 'inbound_call' => '/callback/inbound_call/{id_phone}/', ], 'Api' => [ diff --git a/templates/webhook/add.php b/templates/webhook/add.php index 3fc35b9..6e53c43 100644 --- a/templates/webhook/add.php +++ b/templates/webhook/add.php @@ -47,6 +47,7 @@
Annuler diff --git a/templates/webhook/list.php b/templates/webhook/list.php index 0b306dc..20a99b9 100644 --- a/templates/webhook/list.php +++ b/templates/webhook/list.php @@ -92,6 +92,8 @@ jQuery(document).ready(function () return 'Envoi de SMS'; case 'receive_sms': return 'Réception de SMS'; + case 'inbound_call': + return 'Réception d\'un appel téléphonique'; default: return 'Inconnu'; } From 41c3b3b86e3e6de337c3c2f1e2c93fa4c9f38fdf Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 23 Mar 2021 17:39:13 +0100 Subject: [PATCH 11/40] Add callback for phone calls, not tested yet --- adapters/AdapterInterface.php | 41 +++++++ adapters/BenchmarkAdapter.php | 20 ++++ adapters/GammuAdapter.php | 20 ++++ adapters/OctopushShortcodeAdapter.php | 20 ++++ adapters/OctopushVirtualNumberAdapter.php | 20 ++++ adapters/OvhSmsShortcodeAdapter.php | 20 ++++ adapters/OvhSmsVirtualNumberAdapter.php | 20 ++++ adapters/TestAdapter.php | 64 +++++++++++ adapters/TwilioVirtualNumberAdapter.php | 20 ++++ controllers/{publics => internals}/Call.php | 52 ++++++++- controllers/publics/Callback.php | 114 +++++++++++--------- models/Call.php | 20 ++++ models/StandardModel.php | 4 +- 13 files changed, 378 insertions(+), 57 deletions(-) rename controllers/{publics => internals}/Call.php (62%) diff --git a/adapters/AdapterInterface.php b/adapters/AdapterInterface.php index 2940452..b7fa981 100644 --- a/adapters/AdapterInterface.php +++ b/adapters/AdapterInterface.php @@ -92,6 +92,16 @@ interface AdapterInterface * Does the implemented service support mms sending */ public static function meta_support_mms_sending(): bool; + + /** + * 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 + */ + public static function meta_support_end_call_callback(): bool; /** * Method called to send a SMS to a number. @@ -162,4 +172,35 @@ interface AdapterInterface * ] */ public static function reception_callback(): array; + + /** + * 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. + * ] + * ] + */ + public function inbound_call_callback(): array; + + + /** + * 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. + * ] + * ] + */ + public function end_call_callback(): array; } diff --git a/adapters/BenchmarkAdapter.php b/adapters/BenchmarkAdapter.php index 45b7ef6..c872782 100644 --- a/adapters/BenchmarkAdapter.php +++ b/adapters/BenchmarkAdapter.php @@ -141,6 +141,16 @@ namespace adapters; { 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 { @@ -220,4 +230,14 @@ namespace adapters; { return true; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } } diff --git a/adapters/GammuAdapter.php b/adapters/GammuAdapter.php index 4829ab1..3fd8a45 100644 --- a/adapters/GammuAdapter.php +++ b/adapters/GammuAdapter.php @@ -151,6 +151,16 @@ namespace adapters; { 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 { @@ -296,6 +306,16 @@ namespace adapters; { return []; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } /** * Function to unlock pin. diff --git a/adapters/OctopushShortcodeAdapter.php b/adapters/OctopushShortcodeAdapter.php index 04e54bc..e2d4240 100644 --- a/adapters/OctopushShortcodeAdapter.php +++ b/adapters/OctopushShortcodeAdapter.php @@ -188,6 +188,16 @@ class OctopushShortcodeAdapter implements AdapterInterface { 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 { @@ -407,4 +417,14 @@ class OctopushShortcodeAdapter implements AdapterInterface return $response; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } } diff --git a/adapters/OctopushVirtualNumberAdapter.php b/adapters/OctopushVirtualNumberAdapter.php index d0d7cdc..d38eb29 100644 --- a/adapters/OctopushVirtualNumberAdapter.php +++ b/adapters/OctopushVirtualNumberAdapter.php @@ -193,6 +193,16 @@ class OctopushVirtualNumberAdapter implements AdapterInterface { 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 { @@ -407,4 +417,14 @@ class OctopushVirtualNumberAdapter implements AdapterInterface return $response; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } } diff --git a/adapters/OvhSmsShortcodeAdapter.php b/adapters/OvhSmsShortcodeAdapter.php index aa98bbd..9c6137b 100644 --- a/adapters/OvhSmsShortcodeAdapter.php +++ b/adapters/OvhSmsShortcodeAdapter.php @@ -185,6 +185,16 @@ namespace adapters; { 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 { @@ -359,4 +369,14 @@ namespace adapters; { return []; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } } diff --git a/adapters/OvhSmsVirtualNumberAdapter.php b/adapters/OvhSmsVirtualNumberAdapter.php index 1f8995f..35925a3 100644 --- a/adapters/OvhSmsVirtualNumberAdapter.php +++ b/adapters/OvhSmsVirtualNumberAdapter.php @@ -196,6 +196,16 @@ namespace adapters; { 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 { @@ -358,4 +368,14 @@ namespace adapters; { return []; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } } diff --git a/adapters/TestAdapter.php b/adapters/TestAdapter.php index 6a20baf..8a318a4 100644 --- a/adapters/TestAdapter.php +++ b/adapters/TestAdapter.php @@ -146,6 +146,16 @@ namespace adapters; { return true; } + + 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 { @@ -272,4 +282,58 @@ namespace adapters; { return []; } + + public function inbound_call_callback(): array + { + $response = [ + 'error' => false, + 'error_message' => null, + 'call' => [], + ]; + + $uid = $_POST['uid'] ?? false; + $start = $_POST['start'] ?? false; + $end = $_POST['end'] ?? null; + $origin = $_POST['origin'] ?? false; + + if (!$uid || !$start || !$origin) + { + $response['error'] = true; + $response['error_message'] = 'Missing required argument.'; + + return $response; + } + + $response['call']['uid'] = $uid; + $response['call']['start'] = $start; + $response['call']['end'] = $end; + $response['call']['origin'] = $origin; + + return $response; + } + + public function end_call_callback(): array + { + $response = [ + 'error' => false, + 'error_message' => null, + 'call' => [], + ]; + + $uid = $_POST['uid'] ?? false; + $end = $_POST['end'] ?? null; + + if (!$uid || !$end) + { + $response['error'] = true; + $response['error_message'] = 'Missing required argument.'; + + return $response; + } + + $response['call']['uid'] = $uid; + $response['call']['end'] = $end; + + return $response; + } } diff --git a/adapters/TwilioVirtualNumberAdapter.php b/adapters/TwilioVirtualNumberAdapter.php index b5065e7..a753d9e 100644 --- a/adapters/TwilioVirtualNumberAdapter.php +++ b/adapters/TwilioVirtualNumberAdapter.php @@ -190,6 +190,16 @@ class TwilioVirtualNumberAdapter implements AdapterInterface { 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 { @@ -336,4 +346,14 @@ class TwilioVirtualNumberAdapter implements AdapterInterface { return []; } + + public function inbound_call_callback(): array + { + return []; + } + + public function end_call_callback(): array + { + return []; + } } diff --git a/controllers/publics/Call.php b/controllers/internals/Call.php similarity index 62% rename from controllers/publics/Call.php rename to controllers/internals/Call.php index 55acbb2..5e3dcf7 100644 --- a/controllers/publics/Call.php +++ b/controllers/internals/Call.php @@ -16,7 +16,7 @@ namespace controllers\internals; protected $model; /** - * Create a media. + * 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 @@ -50,11 +50,11 @@ namespace controllers\internals; switch ($direction) { case \models\Call::DIRECTION_OUTBOUND : - null === $destination ?: return false; + if (null === $destination) { return false; } break; case \models\Call::DIRECTION_INBOUND : - null === $origin ?: return false; + if (null === $origin) { return false; } break; default : @@ -78,4 +78,50 @@ namespace controllers\internals; return $this->get_model()->insert($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 + * + * @return bool : False if cannot end phone call, true else + */ + public function end(int $id_user, int $id_phone, string $uid, string $end) + { + if (!\controllers\internals\Tool::validate_date($end, 'Y-m-d H:i:s')) + { + return false; + } + + $call = $this->get_model()->get_by_uid_and_phone_for_user($id_user, $id_phone, $uid); + if (!$call) + { + return false; + } + + if (new \DateTime($end) < new \DateTime($call['start'])) + { + return false; + } + + $datas = [ + 'end' => $end, + ]; + + return (bool) $this->get_model()->update_for_user($id_user, $call['id'], $datas); + } + + /** + * Get the model for the Controller. + */ + protected function get_model(): \descartes\Model + { + $this->model = $this->model ?? new \models\Call($this->bdd); + + return $this->model; + } } diff --git a/controllers/publics/Callback.php b/controllers/publics/Callback.php index 5c6e32e..4f2160c 100644 --- a/controllers/publics/Callback.php +++ b/controllers/publics/Callback.php @@ -27,6 +27,7 @@ use Monolog\Logger; private $internal_adapter; private $internal_media; private $internal_phone; + private $internal_call; public function __construct() { @@ -37,7 +38,8 @@ use Monolog\Logger; $this->internal_received = new \controllers\internals\Received($bdd); $this->internal_media = new \controllers\internals\Media($bdd); $this->internal_adapter = new \controllers\internals\Adapter(); - $this->internal_phone = new \controllers\internals\Phone(); + $this->internal_phone = new \controllers\internals\Phone($bdd); + $this->internal_call = new \controllers\internals\Call($bdd); //Logger $this->logger = new Logger('Callback ' . uniqid()); @@ -283,73 +285,81 @@ use Monolog\Logger; $response = $phone['adapter']::inbound_call_callback(); if ($response['error']) { - $this->logger->error('Callback reception with adapter ' . $adapter_uid . ' failed : ' . $response['error_message']); + $this->logger->error('Callback inbound_call failed : ' . $response['error_message']); return false; } - $sms = $response['sms']; - $mms = !empty($sms['mms']); - $medias = empty($sms['medias']) ? [] : $sms['medias']; - $media_ids = []; + $call = $response['call']; + $result = $this->internal_call->create($this->user['id'], $id_phone, $call['uid'], \models\Call::DIRECTION_INBOUND, $call['start'], $call['end'] ?? null, $call['origin']); - //We create medias to link to the sms - if ($mms) + if (!$result) { - foreach ($medias as $media) - { - try - { - $media['mimetype'] = empty($media['mimetype']) ? mime_content_type($media['filepath']) : $media['mimetype']; + $this->logger->error('Callback inbound_call failed because cannot create call ' . json_encode($call)); - $mimey = new \Mimey\MimeTypes; - $extension = empty($media['extension']) ? $mimey->getExtension($media['mimetype']) : $media['extension']; - - $new_filename = \controllers\internals\Tool::random_uuid() . '.' . $extension; - $new_filedir = PWD_DATA . '/medias/' . $this->user['id']; - $new_filerelpath = 'medias/' . $this->user['id'] . '/' . $new_filename; - $new_filepath = $new_filedir . '/' . $new_filename; - - //Create user dir if not exists - if (!file_exists($new_filedir)) - { - if (!mkdir($new_filedir, fileperms(PWD_DATA), true)) - { - throw new \Exception('Cannot create dir ' . $new_filedir . ' to copy media : ' . json_encode($media)); - } - } - - if (!rename($media['filepath'], $new_filepath)) - { - throw new \Exception('Cannot copy media : ' . json_encode($media) . ' to ' . $new_filepath); - } - - $new_media_id = $this->internal_media->create($this->user['id'], $new_filerelpath); - if (!$new_media_id) - { - throw new \Exception('Cannot save into db media : ' . json_encode($media)); - } - - $media_ids[] = $new_media_id; - } - catch (\Throwable $t) - { - $this->logger->error($t->getMessage()); - continue; - } - } + return false; } - $response = $this->internal_received->receive($this->user['id'], $id_phone, $sms['text'], $sms['origin'], $sms['at'], \models\Received::STATUS_UNREAD, $mms, $media_ids); + $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 + * + * @return bool : true on success, false on error + */ + public function end_call(int $id_phone) + { + $this->logger->info('Callback end call with phone : ' . $id_phone); + $phone = $this->internal_phone->get_for_user($this->user['id'], $id_phone); + + if (!$phone) + { + $this->logger->error('Callback end call use non existing phone : ' . $id_phone); + + return false; + } + + if (!class_exists($phone['adapter'])) + { + $this->logger->error('Callback end call use non existing adapter : ' . $phone['adapter']); + + return false; + } + + if (!$phone['adapter']::meta_support_end_call_callback()) + { + $this->logger->error('Callback end call use adapter ' . $phone['adapter'] . ' which does not support end call callback.'); + + return false; + } + + $response = $phone['adapter']::end_call_callback(); if ($response['error']) { - $this->logger->error('Failed receive message : ' . json_encode($sms) . ' with error : ' . $response['error_message']); + $this->logger->error('Callback end call failed : ' . $response['error_message']); return false; } - $this->logger->info('Callback reception successfully received message : ' . json_encode($sms)); + $call = $response['call']; + $result = $this->internal_call->end($this->user['id'], $id_phone, $call['uid'], $call['end']); + if (!$result) + { + $this->logger->error('Callback end call failed because cannot update call ' . json_encode($call)); + + return false; + } + + $this->logger->info('Callback end call successfully update call : ' . json_encode($call)); + return true; } } diff --git a/models/Call.php b/models/Call.php index fee82b3..dd3e8a9 100644 --- a/models/Call.php +++ b/models/Call.php @@ -19,6 +19,26 @@ namespace models; const DIRECTION_INBOUND = 'inbound'; const DIRECTION_OUTBOUND = 'outbound'; + /** + * 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 + * + * @return array : the call or an empty array + */ + public function get_by_uid_and_phone_for_user($id_user, $id_phone, $uid) + { + $where = [ + 'id_user' => $id_user, + 'id_phone' => $id_phone, + 'uid' => $uid, + ]; + + return $this->_select_one($this->get_table_name(), $where); + } + /** * Return table name. */ diff --git a/models/StandardModel.php b/models/StandardModel.php index 32b9686..45bf967 100644 --- a/models/StandardModel.php +++ b/models/StandardModel.php @@ -158,9 +158,9 @@ namespace models; * * @return int : number of modified rows */ - public function update_for_user(int $id_user, int $id, array $entry) + public function update_for_user(int $id_user, int $id, array $data) { - return $this->_update($this->get_table_name(), $entry, ['id_user' => $id_user, 'id' => $id]); + return $this->_update($this->get_table_name(), $data, ['id_user' => $id_user, 'id' => $id]); } /** From c63f3bebba5732791a94f450cf6fc5e179228188 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Wed, 24 Mar 2021 00:22:17 +0100 Subject: [PATCH 12/40] add page for calls --- adapters/TestAdapter.php | 4 +- controllers/publics/Call.php | 93 +++++++++++++++++ controllers/publics/Phone.php | 48 ++------- models/Call.php | 42 ++++++++ routes.php | 12 ++- templates/call/list.php | 145 +++++++++++++++++++++++++++ templates/conditional_group/list.php | 2 +- templates/incs/nav.php | 5 +- templates/phone/list.php | 17 ++++ 9 files changed, 323 insertions(+), 45 deletions(-) create mode 100644 controllers/publics/Call.php create mode 100644 templates/call/list.php diff --git a/adapters/TestAdapter.php b/adapters/TestAdapter.php index 8a318a4..4ddb9c6 100644 --- a/adapters/TestAdapter.php +++ b/adapters/TestAdapter.php @@ -149,12 +149,12 @@ namespace adapters; public static function meta_support_inbound_call_callback(): bool { - return false; + return true; } public static function meta_support_end_call_callback(): bool { - return false; + return true; } public function send(string $destination, string $text, bool $flash = false, bool $mms = false, array $medias = []) : array diff --git a/controllers/publics/Call.php b/controllers/publics/Call.php new file mode 100644 index 0000000..fb05bc2 --- /dev/null +++ b/controllers/publics/Call.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the GPL-3.0 license that is bundled + * with this source code in the file LICENSE. + */ + +namespace controllers\publics; + + /** + * Page des calls. + */ + class Call extends \descartes\Controller + { + private $internal_call; + + /** + * Cette fonction est appelée avant toute les autres : + * Elle vérifie que l'utilisateur est bien connecté. + * + * @return void; + */ + public function __construct() + { + $bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD); + + $this->internal_call = new \controllers\internals\Call($bdd); + + \controllers\internals\Tool::verifyconnect(); + } + + /** + * Page for showing calls list + */ + public function list() + { + $this->render('call/list'); + } + + /** + * Return calls list as json. + */ + public function list_json() + { + $entities = $this->internal_call->list_for_user($_SESSION['user']['id']); + foreach ($entities as &$entity) + { + switch ($entity['direction']) + { + case \models\Call::DIRECTION_INBOUND : + $entity['origin_formatted'] = \controllers\internals\Tool::phone_link($entity['origin']); + break; + + case \models\Call::DIRECTION_OUTBOUND : + $entity['destination_formatted'] = \controllers\internals\Tool::phone_link($entity['destination']); + break; + } + } + + header('Content-Type: application/json'); + echo json_encode(['data' => $entities]); + } + + /** + * Delete a list of calls + * + * @param array int $_GET['ids'] : Ids of calls to delete + * @param string $csrf : csrf token + * + * @return boolean; + */ + public function delete(string $csrf) + { + if (!$this->verify_csrf($csrf)) + { + \FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !'); + + return $this->redirect(\descartes\Router::url('Call', 'list')); + } + + $ids = $_GET['ids'] ?? []; + foreach ($ids as $id) + { + $this->internal_call->delete_for_user($_SESSION['user']['id'], $id); + } + + return $this->redirect(\descartes\Router::url('Call', 'list')); + } + } diff --git a/controllers/publics/Phone.php b/controllers/publics/Phone.php index 329572a..81d6249 100644 --- a/controllers/publics/Phone.php +++ b/controllers/publics/Phone.php @@ -33,43 +33,7 @@ class Phone extends \descartes\Controller */ public function list() { - $id_user = $_SESSION['user']['id']; - $api_key = $_SESSION['user']['api_key']; - $phones = $this->internal_phone->list_for_user($id_user); - - $adapters = []; - $adapters = $this->internal_adapter->list_adapters(); - foreach ($adapters as $key => $adapter) - { - unset($adapters[$key]); - $adapters[$adapter['meta_classname']] = $adapter; - } - - foreach ($phones as &$phone) - { - $adapter = $adapters[$phone['adapter']] ?? false; - - if (!$adapter) - { - $phone['adapter'] = 'Inconnu'; - - continue; - } - - $phone['adapter'] = $adapter['meta_name']; - - if ($adapter['meta_support_reception']) - { - $phone['callback_reception'] = \descartes\Router::url('Callback', 'reception', ['adapter_uid' => $adapter['meta_uid'], 'id_phone' => $phone['id']], ['api_key' => $api_key]); - } - - if ($adapter['meta_support_status_change']) - { - $phone['callback_status'] = \descartes\Router::url('Callback', 'update_sended_status', ['adapter_uid' => $adapter['meta_uid']], ['api_key' => $api_key]); - } - } - - $this->render('phone/list', ['phones' => $phones]); + $this->render('phone/list'); } /** @@ -111,6 +75,16 @@ 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]); + } } header('Content-Type: application/json'); diff --git a/models/Call.php b/models/Call.php index dd3e8a9..e8e08ce 100644 --- a/models/Call.php +++ b/models/Call.php @@ -18,6 +18,48 @@ namespace models; { 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. + * + * @param int $id_user : user id + * @param ?int $limit : Number of entry to return or null + * @param ?int $offset : Number of entry to ignore or null + * + * @return array + */ + public function list_for_user(int $id_user, $limit, $offset) + { + $query = ' + SELECT `call`.*, contact.name as contact_name, phone.name as phone_name + FROM `call` + LEFT JOIN contact + ON contact.number = `call`.destination + OR contact.number = `call`.origin + LEFT JOIN phone + ON phone.id = `call`.id_phone + WHERE `call`.id_user = :id_user + '; + + if (null !== $limit) + { + $limit = (int) $limit; + + $query .= ' LIMIT ' . $limit; + if (null !== $offset) + { + $offset = (int) $offset; + $query .= ' OFFSET ' . $offset; + } + } + + $params = [ + 'id_user' => $id_user, + ]; + + return $this->_run_query($query, $params); + } /** * Get a call for a user by his phone and uid diff --git a/routes.php b/routes.php index bf66579..0384728 100644 --- a/routes.php +++ b/routes.php @@ -58,10 +58,7 @@ ], 'Event' => [ - 'list' => [ - '/event/', - '/event/p/{page}/', - ], + 'list' => '/event/', 'list_json' => '/event/json/', 'delete' => '/event/delete/{csrf}/', ], @@ -155,6 +152,12 @@ 'delete' => '/phone/delete/{csrf}/', ], + 'Call' => [ + 'list' => '/call/', + 'list_json' => '/call/json/', + 'delete' => '/call/delete/{csrf}/', + ], + 'Webhook' => [ 'list' => '/webhook/', 'list_json' => '/webhook/json/', @@ -169,6 +172,7 @@ 'update_sended_status' => '/callback/status/{adapter_uid}/', 'reception' => '/callback/reception/{adapter_uid}/{id_phone}/', 'inbound_call' => '/callback/inbound_call/{id_phone}/', + 'end_call' => '/callback/end_call/{id_phone}/', ], 'Api' => [ diff --git a/templates/call/list.php b/templates/call/list.php new file mode 100644 index 0000000..7fed56a --- /dev/null +++ b/templates/call/list.php @@ -0,0 +1,145 @@ +render('incs/head', ['title' => 'Appels - Show All']) +?> +
+render('incs/nav', ['page' => 'calls']) +?> +
+
+ +
+
+

+ Dashboard Appels +

+ +
+
+ + +
+
+
+
+

Liste des appels

+
+
+
+
+
DeÀExpéditeurDestinataire Message Date Statut
+ + + + + + + + + + + +
OrigineDestinataireDébut de l'appelFin de l'appelDirection
+ +
+
+ Action pour la séléction : + +
+
+ + + + + + + + + +render('incs/footer'); diff --git a/templates/conditional_group/list.php b/templates/conditional_group/list.php index ecb104e..b8ce2ad 100644 --- a/templates/conditional_group/list.php +++ b/templates/conditional_group/list.php @@ -1,7 +1,7 @@ render('incs/head', ['title' => 'ConditionalGroupes Conditionnels - Show All']) + $this->render('incs/head', ['title' => 'Groupes Conditionnels - Show All']) ?>
  • Logs -
  • diff --git a/templates/scheduled/list.php b/templates/scheduled/list.php index ecfd708..63576e2 100644 --- a/templates/scheduled/list.php +++ b/templates/scheduled/list.php @@ -93,7 +93,7 @@ jQuery(document).ready(function () if (row.mms == 1) { var medias = []; for (i = 0; i < row.medias.length; i++) { - medias.push('Fichier ' + (i + 1) + ''); + medias.push('Fichier ' + (i + 1) + ''); } html = data + '
    ' + medias.join(' - '); return html; diff --git a/templates/sended/list.php b/templates/sended/list.php index d8c6289..15e837c 100644 --- a/templates/sended/list.php +++ b/templates/sended/list.php @@ -100,7 +100,7 @@ jQuery(document).ready(function () if (row.mms == 1) { var medias = []; for (i = 0; i < row.medias.length; i++) { - medias.push('Fichier ' + (i + 1) + ''); + medias.push('Fichier ' + (i + 1) + ''); } html = data + '
    ' + medias.join(' - '); return html; From 6acf28e3ceb9df8adb9527a4917da5990f1104e4 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sat, 27 Mar 2021 01:15:09 +0100 Subject: [PATCH 21/40] Properly working mms reception with public dir in data and valid rights, etc. --- adapters/TestAdapter.php | 2 +- controllers/internals/Media.php | 26 ++++++++++++++++++++++---- controllers/internals/Tool.php | 15 +++++++++++---- controllers/publics/Discussion.php | 2 +- templates/discussion/show.php | 4 ++-- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/adapters/TestAdapter.php b/adapters/TestAdapter.php index b92d78c..07abfcf 100644 --- a/adapters/TestAdapter.php +++ b/adapters/TestAdapter.php @@ -186,7 +186,7 @@ namespace adapters; /** * Read from a files to simulate sms reception. - * In the file we expect a json string representing an array of sms of format : + * In the file we expect a series of lines, each line beeing a SMS as a json string of format : * { * "at" : "2021-03-26 11:21:48", * "medias" : [ diff --git a/controllers/internals/Media.php b/controllers/internals/Media.php index e2f5dd5..3056e57 100644 --- a/controllers/internals/Media.php +++ b/controllers/internals/Media.php @@ -29,7 +29,12 @@ class Media extends StandardController public function create(int $id_user, string $tmpfile_path, ?string $extension = null) { $user_path = \controllers\internals\Tool::create_user_public_path($id_user); - if (!file_exists($tmpfile_path) || !is_readable($tmpfile_path)) + if (!file_exists($tmpfile_path)) + { + throw new \Exception('File ' . $tmpfile_path . ' does not exists.'); + } + + if (!is_readable($tmpfile_path)) { throw new \Exception('File ' . $tmpfile_path . ' is not readable.'); } @@ -41,6 +46,11 @@ class Media extends StandardController $new_file_path = $user_path . '/' . $new_file_name; $new_file_relpath = $id_user . '/' . $new_file_name; + if (!file_put_contents($new_file_path, 'a')) + { + throw new \Exception('pute de merde'); + } + if (!rename($tmpfile_path, $new_file_path)) { throw new \Exception('Cannot create file ' . $new_file_path); @@ -90,10 +100,18 @@ class Media extends StandardController throw new \Exception($upload_result['content']); } - return $this->create($id_user, $upload_result['tmp_name'], $upload_result['extension']); + //Move uploaded file to a tmp file + if (!$tmp_file = tempnam('/tmp', 'raspisms-media-')) + { + throw new \Exception('Cannot create tmp file in /tmp to store the uploaded file.'); + } - $new_filepath = 'medias/' . $id_user . '/' . $upload_result['content']; - return $this->create($id_user, $new_filepath); + if (!move_uploaded_file($upload_result['tmp_name'], $tmp_file)) + { + throw new \Exception('Cannot move uploaded file to : ' . $tmp_file); + } + + return $this->create($id_user, $tmp_file, $upload_result['extension']); } /** diff --git a/controllers/internals/Tool.php b/controllers/internals/Tool.php index b1a795e..e5b71f9 100644 --- a/controllers/internals/Tool.php +++ b/controllers/internals/Tool.php @@ -306,19 +306,26 @@ namespace controllers\internals; return $new_dir; } - if (!mkdir($new_dir, fileperms(PWD_DATA_PUBLIC))) + clearstatcache(); + if (!mkdir($new_dir)) { throw new \Exception('Cannot create dir ' . $new_dir); } - if (!chown($new_dir, fileowner(PWD_DATA_PUBLIC))) + //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 { - throw new \Exception('Cannot give dir ' . $new_dir . ' to user : ' . fileowner(PWD_DATA)); + 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 + { + throw new \Exception('Cannot give dir ' . $new_dir . ' to user : ' . fileowner(PWD_DATA_PUBLIC)); } if (!chgrp($new_dir, filegroup(PWD_DATA_PUBLIC))) { - throw new \Exception('Cannot give dir ' . $new_dir . ' to group : ' . filegroup(PWD_DATA)); + throw new \Exception('Cannot give dir ' . $new_dir . ' to group : ' . filegroup(PWD_DATA_PUBLIC)); } return $new_dir; diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index b8fae9a..6f26b80 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -144,7 +144,7 @@ namespace controllers\publics; } $medias = []; - if ($sended['mms']) + if ($received['mms']) { $medias = $this->internal_media->gets_for_received($received['id']); foreach ($medias as &$media) diff --git a/templates/discussion/show.php b/templates/discussion/show.php index 674474d..72636fc 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -87,7 +87,7 @@ } else { - return '
    Voir le fichier ' + ((int)index + 1) + '
    '; + return '
    Voir le fichier ' + (Number(index) + 1) + '
    '; } }); var medias_html = '
    ' + medias.join('') + '
    '; @@ -99,7 +99,7 @@ '
    ' + '
    ' + '
    ' + message.text + '
    ' + - medias.html + + medias_html + '
    ' + message.date + '
    ' + '
    ' + '
    '; From c326e27babc41e7980655525d408b59aa41fd766 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sat, 27 Mar 2021 01:16:24 +0100 Subject: [PATCH 22/40] add data/public dir --- data/public/.tokeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/public/.tokeep diff --git a/data/public/.tokeep b/data/public/.tokeep new file mode 100644 index 0000000..e69de29 From d29424ff0c5aa4f32692bddae99a33f44d603b96 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sat, 27 Mar 2021 01:18:02 +0100 Subject: [PATCH 23/40] fix delete to use proper dir --- controllers/internals/Media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/internals/Media.php b/controllers/internals/Media.php index 3056e57..2722aab 100644 --- a/controllers/internals/Media.php +++ b/controllers/internals/Media.php @@ -238,7 +238,7 @@ class Media extends StandardController //Delete file try { - $filepath = PWD_DATA . '/' . $media['path']; + $filepath = PWD_DATA_PUBLIC . '/' . $media['path']; if (file_exists($filepath)) { unlink($filepath); From e06079e6af093c7824802fadddf8bfd8a3f1de96 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sat, 3 Apr 2021 18:19:08 +0200 Subject: [PATCH 24/40] add mms files to mail transfert --- controllers/internals/Mailer.php | 4 +++- controllers/internals/User.php | 10 +++++++++- daemons/Mailer.php | 2 +- env.php.dist | 4 +++- templates/email/transfer-sms.php | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/controllers/internals/Mailer.php b/controllers/internals/Mailer.php index 2df5d3c..04d7204 100755 --- a/controllers/internals/Mailer.php +++ b/controllers/internals/Mailer.php @@ -101,10 +101,11 @@ class Mailer extends \descartes\Controller * @param string $destination : email address to send email to * @param array $settings : Email settings * @param array $data : Data to inject into email template + * @param array $attachments : List of paths of files to attach to the mail * * @return bool : true on success, false on error */ - public function enqueue(string $destination, array $settings, array $data): bool + public function enqueue(string $destination, array $settings, array $data, array $attachments = []): bool { $response = $this->generate_body($settings, $data); @@ -113,6 +114,7 @@ class Mailer extends \descartes\Controller 'subject' => $settings['subject'], 'body' => $response['body'], 'alt_body' => $response['alt_body'], + 'attachments' => $attachments, ]; $error_code = null; diff --git a/controllers/internals/User.php b/controllers/internals/User.php index b0b8eeb..427cceb 100644 --- a/controllers/internals/User.php +++ b/controllers/internals/User.php @@ -291,12 +291,20 @@ namespace controllers\internals; } $mailer = new Mailer(); + + $attachments = []; + + foreach ($received['medias'] ?? [] as $media) + { + $attachments[] = PWD_DATA_PUBLIC . '/' . $media['path']; + } return $mailer->enqueue($user['email'], EMAIL_TRANSFER_SMS, [ 'at' => $received['at'], 'origin' => $received['origin'], 'destination' => $phone['name'], 'text' => $received['text'], - ]); + 'mms' => $received['mms'] ?? false, + ], $attachments); } } diff --git a/daemons/Mailer.php b/daemons/Mailer.php index c9f1ac9..a25b253 100644 --- a/daemons/Mailer.php +++ b/daemons/Mailer.php @@ -74,7 +74,7 @@ class Mailer extends AbstractDaemon $this->logger->info('Try sending email : ' . json_encode($message)); $mailer = new \controllers\internals\Mailer(); - $success = $mailer->send($message['destinations'], $message['subject'], $message['body'], $message['alt_body']); + $success = $mailer->send($message['destinations'], $message['subject'], $message['body'], $message['alt_body'], $message['attachments']); if (!$success) { $this->logger->error('Failed sending email'); diff --git a/env.php.dist b/env.php.dist index dd77747..5aa3260 100644 --- a/env.php.dist +++ b/env.php.dist @@ -17,6 +17,8 @@ 'PWD_ADAPTERS' => PWD . '/adapters', 'PWD_DATA' => PWD . '/data', 'HTTP_PWD_DATA' => HTTP_PWD . '/data', + 'PWD_DATA_PUBLIC' => PWD . '/data/public', + 'HTTP_PWD_DATA_PUBLIC' => HTTP_PWD . '/data/public', 'PWD_LOGS' => '/var/log/raspisms', 'PWD_PID' => '/var/run/raspisms', 'APP_SECRET' => '%APP_SECRET%', @@ -67,7 +69,7 @@ 'preferred_phone_country' => 'fr,be,ca', 'default_phone_country' => 'fr', 'authorized_phone_country' => 'fr,be,ca', - 'mms' => 0, + 'mms' => 1, ], ]; diff --git a/templates/email/transfer-sms.php b/templates/email/transfer-sms.php index e75a0a9..9b5612d 100644 --- a/templates/email/transfer-sms.php +++ b/templates/email/transfer-sms.php @@ -1,4 +1,4 @@ -Vous avez reçu un nouveau SMS : +Vous avez reçu un nouveau : Date : Origine : From 7c85753bfa5716271f40b860ddfb9ba2be04bc26 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Sat, 3 Apr 2021 18:30:05 +0200 Subject: [PATCH 25/40] only root try to give another group to file --- controllers/internals/Tool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/internals/Tool.php b/controllers/internals/Tool.php index e5b71f9..a207185 100644 --- a/controllers/internals/Tool.php +++ b/controllers/internals/Tool.php @@ -323,7 +323,7 @@ namespace controllers\internals; throw new \Exception('Cannot give dir ' . $new_dir . ' to user : ' . fileowner(PWD_DATA_PUBLIC)); } - if (!chgrp($new_dir, filegroup(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 { throw new \Exception('Cannot give dir ' . $new_dir . ' to group : ' . filegroup(PWD_DATA_PUBLIC)); } From f992fe21bc0db16d2b9de4cc3fdf8228f9522111 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 6 Apr 2021 20:52:07 +0200 Subject: [PATCH 26/40] show media input on discussion only if mms is enabled --- templates/discussion/show.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/discussion/show.php b/templates/discussion/show.php index 72636fc..eed789e 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -42,7 +42,9 @@ - + + + From 78fce650cc2ed78b53e7e9ddc43a2749583bcfc5 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 6 Apr 2021 21:42:30 +0200 Subject: [PATCH 27/40] add env.descartes.php.dist --- env.descartes.php.dist | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 env.descartes.php.dist diff --git a/env.descartes.php.dist b/env.descartes.php.dist new file mode 100644 index 0000000..803c5cf --- /dev/null +++ b/env.descartes.php.dist @@ -0,0 +1,21 @@ + $http_pwd, + ]; From 2918087856a2fc3939eb7e732000f4720b2e4eaf Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 6 Apr 2021 21:44:30 +0200 Subject: [PATCH 28/40] revert --- env.descartes.php.dist | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 env.descartes.php.dist diff --git a/env.descartes.php.dist b/env.descartes.php.dist deleted file mode 100644 index 803c5cf..0000000 --- a/env.descartes.php.dist +++ /dev/null @@ -1,21 +0,0 @@ - $http_pwd, - ]; From 48e8eacc3496f54e7a341747c3a38d5f18d9076a Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 6 Apr 2021 23:20:00 +0200 Subject: [PATCH 29/40] Change http_url for medias --- controllers/internals/Sended.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index c9575c9..3847185 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -211,7 +211,7 @@ namespace controllers\internals; foreach ($medias as $media) { $media_uris[] = [ - 'http_url' => HTTP_PWD_DATA_PUBLIC . '/' . $media['path'], + 'path' => $media['path'], 'local_uri' => PWD_DATA_PUBLIC . '/' . $media['path'], ]; } From fb919ff59f6532a77182126493bc8836b6bd384c Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 13 Apr 2021 02:01:03 +0200 Subject: [PATCH 30/40] highly improve discussion perfs + fix initial sound playing and erratic scrolling --- assets/js/custom.js | 11 +++--- controllers/internals/Received.php | 13 +++++++ controllers/internals/Scheduled.php | 14 +++++++ controllers/internals/Sended.php | 13 +++++++ controllers/publics/Discussion.php | 51 +++++++++++++++++++++----- models/Received.php | 28 ++++++++++++++ models/Scheduled.php | 57 +++++++++++++++++++++++++++++ models/Sended.php | 29 +++++++++++++++ routes.php | 5 ++- templates/discussion/show.php | 49 ++++++++++++++++++------- 10 files changed, 242 insertions(+), 28 deletions(-) diff --git a/assets/js/custom.js b/assets/js/custom.js index eb2b6f9..b25910a 100644 --- a/assets/js/custom.js +++ b/assets/js/custom.js @@ -45,8 +45,9 @@ function verifReceived() /** * Cette fonction permet de scroller au dernier message + * @param force: bool : should we force the scroll */ -function scrollDownDiscussion() +function scrollDownDiscussion(force) { var discussion_height = jQuery('.discussion-container').innerHeight(); var discussion_scroll_height = jQuery('.discussion-container')[0].scrollHeight; @@ -54,7 +55,7 @@ function scrollDownDiscussion() var scroll_before_end = discussion_scroll_height - (discussion_scroll_top + discussion_height); //On scroll uniquement si on a pas remonté plus haut que la moitié de la fenetre de discussion - if (scroll_before_end <= discussion_height / 2) + if (force || scroll_before_end <= discussion_height / 2) { jQuery('.discussion-container').animate({scrollTop: 1000000}); } @@ -115,8 +116,8 @@ jQuery(document).ready(function() var form = jQuery(this); var message = form.find('textarea').val(); var formData = new FormData(form[0]); - jQuery('.discussion-container').find('#send-message-spiner').remove(); - jQuery('.discussion-container').append('
    '); + jQuery('.discussion-container').find('#send-message-spinner').remove(); + jQuery('.discussion-container').append('
    '); scrollDownDiscussion(); jQuery.ajax({ url: form.attr('action'), @@ -130,7 +131,7 @@ jQuery(document).ready(function() if (!data.success) { showMessage(data.message.replace(//g, ">"), 0); - jQuery('.discussion-container').find('#send-message-spiner').remove(); + jQuery('.discussion-container').find('#send-message-spinner').remove(); } } }).done(function() diff --git a/controllers/internals/Received.php b/controllers/internals/Received.php index 285a8ca..c7c2b79 100644 --- a/controllers/internals/Received.php +++ b/controllers/internals/Received.php @@ -161,6 +161,19 @@ namespace controllers\internals; return $this->get_model()->gets_by_origin_and_user($id_user, $origin); } + /** + * Return receiveds for an origin 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_origin_and_user(int $id_user, string $since, string $origin) + { + return $this->get_model()->gets_since_date_by_origin_and_user($id_user, $since, $origin); + } + /** * Get number of sended SMS for every date since a date for a specific user. * diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index b45c6ce..da65da2 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -254,6 +254,20 @@ 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. + * + * @param int $id_user : User id + * @param $date : Date after which we want messages + * @param string $number : Number for which we want messages + * + * @return array + */ + public function gets_after_date_for_number_and_user(int $id_user, $date, string $number) + { + return $this->get_model()->gets_after_date_for_number_and_user($id_user, $date, $number); + } /** * Get all messages to send and the number to use to send theme. diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index 3847185..8b8d2d6 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -132,6 +132,19 @@ 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) + { + return $this->get_model()->gets_since_date_by_destination_and_user($id_user, $since, $origin); + } /** * Return sended for an uid and an adapter. diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index 6f26b80..13d2031 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -98,17 +98,34 @@ namespace controllers\publics; * * @param string $number : Le numéro cible * @param string $transaction_id : Le numéro unique de la transaction ajax (sert à vérifier si la requete doit être prise en compte) + * @param ?int $since : La date à partir de laquelle on veut les messages, sous forme de timestamp */ - public function get_messages($number, $transaction_id) + public function get_messages($number, $transaction_id, ?int $since = null) { $now = new \DateTime(); $now = $now->format('Y-m-d H:i:s'); $id_user = $_SESSION['user']['id']; - $sendeds = $this->internal_sended->gets_by_destination_and_user($id_user, $number); - $receiveds = $this->internal_received->gets_by_origin_and_user($id_user, $number); - $scheduleds = $this->internal_scheduled->gets_before_date_for_number_and_user($id_user, $now, $number); + if ($since && !($since = date('Y-m-d H:i:s', $since))) + { + echo json_encode(['transaction_id' => $transaction_id, 'messages' => [], 'error' => true, 'error_message' => 'Not a valid date.']); + + return false; + } + + if ($since) + { + $sendeds = $this->internal_sended->gets_since_date_by_destination_and_user($id_user, $since, $number); + $receiveds = $this->internal_received->gets_since_date_by_origin_and_user($id_user, $since, $number); + $scheduleds = $this->internal_scheduled->gets_after_date_for_number_and_user($id_user, $since, $number); + } + else + { + $sendeds = $this->internal_sended->gets_by_destination_and_user($id_user, $number); + $receiveds = $this->internal_received->gets_by_origin_and_user($id_user, $number); + $scheduleds = $this->internal_scheduled->gets_before_date_for_number_and_user($id_user, $now, $number); + } $messages = []; @@ -125,6 +142,7 @@ namespace controllers\publics; } $message = [ + 'uid' => 'sended-' . $sended['id'], 'date' => htmlspecialchars($sended['at']), 'text' => htmlspecialchars($sended['text']), 'type' => 'sended', @@ -154,10 +172,10 @@ namespace controllers\publics; } $messages[] = [ + 'uid' => 'received-' . $received['id'], 'date' => htmlspecialchars($received['at']), 'text' => htmlspecialchars($received['text']), 'type' => 'received', - 'md5' => md5($received['at'] . $received['text']), 'medias' => $medias, ]; } @@ -165,7 +183,7 @@ namespace controllers\publics; foreach ($scheduleds as $scheduled) { $medias = []; - if ($sended['mms']) + if ($scheduled['mms']) { $medias = $this->internal_media->gets_for_scheduled($scheduled['id']); foreach ($medias as &$media) @@ -175,6 +193,7 @@ namespace controllers\publics; } $messages[] = [ + 'uid' => 'scheduled-' . $scheduled['id'], 'date' => htmlspecialchars($scheduled['at']), 'text' => htmlspecialchars($scheduled['text']), 'type' => 'inprogress', @@ -188,10 +207,24 @@ namespace controllers\publics; return strtotime($a['date']) - strtotime($b['date']); }); - //On récupère uniquement les 25 derniers messages sur l'ensemble - $messages = \array_slice($messages, -25); + //Sans limite de temps, on récupère uniquement les 25 derniers messages sur l'ensemble pour limiter la charge + if (!$since) + { + $messages = \array_slice($messages, -25); + } - echo json_encode(['transaction_id' => $transaction_id, 'messages' => $messages]); + $response = [ + 'transaction_id' => $transaction_id, + 'messages' => $messages, + ]; + + if ($messages) + { + $new_limit_date = (new \DateTime($messages[count($messages) - 1]['date']))->getTimestamp(); //Use latest message as the new limit date to search + $response['new_limit_date'] = $new_limit_date; + } + + echo json_encode($response); return true; } diff --git a/models/Received.php b/models/Received.php index 23a8c05..28fabef 100644 --- a/models/Received.php +++ b/models/Received.php @@ -178,6 +178,34 @@ 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 string $since : Date we want messages since + * @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) + { + $query = ' + SELECT * + FROM received + WHERE id_user = :id_user + AND origin = :origin + AND at > :since + '; + + $params = [ + 'id_user' => $id_user, + 'origin' => $origin, + 'since' => $since, + ]; + + return $this->_run_query($query, $params); + } /** * Get number of sended SMS for every date since a date for a specific user. diff --git a/models/Scheduled.php b/models/Scheduled.php index 7d7bc90..f1b2cdd 100644 --- a/models/Scheduled.php +++ b/models/Scheduled.php @@ -233,6 +233,63 @@ namespace models; return $this->_run_query($query, $params); } + + + /** + * Get messages scheduled after a date for a number and a user. + * + * @param int $id_user : User id + * @param $date : Date after which we want messages + * @param string $number : Number for which we want messages + * + * @return array + */ + public function gets_after_date_for_number_and_user(int $id_user, $date, string $number) + { + $query = ' + SELECT * + FROM scheduled + WHERE at > :date + AND id_user = :id_user + AND ( + id IN ( + SELECT id_scheduled + FROM scheduled_number + WHERE number = :number + ) + OR id IN ( + SELECT id_scheduled + FROM scheduled_contact + WHERE id_contact IN ( + SELECT id + FROM contact + WHERE number = :number + ) + ) + OR id IN ( + SELECT id_scheduled + FROM scheduled_group + WHERE id_group IN ( + SELECT id_group + FROM `group_contact` + WHERE id_contact IN ( + SELECT id + FROM contact + WHERE number = :number + ) + ) + ) + ) + '; + + $params = [ + 'id_user' => $id_user, + 'date' => $date, + 'number' => $number, + ]; + + return $this->_run_query($query, $params); + } /** * Get scheduleds before a date. diff --git a/models/Sended.php b/models/Sended.php index f0235ba..fa91346 100644 --- a/models/Sended.php +++ b/models/Sended.php @@ -112,6 +112,35 @@ 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 $destination : Number who sent the message + * + * @return array + */ + public function gets_since_date_by_destination_and_user(int $id_user, string $since, string $destination) + { + $query = ' + SELECT * + FROM sended + WHERE id_user = :id_user + AND destination = :destination + AND at > :since + '; + + $params = [ + 'id_user' => $id_user, + 'destination' => $destination, + 'since' => $since, + ]; + + return $this->_run_query($query, $params); + } /** * Return sended for an uid and an adapter. diff --git a/routes.php b/routes.php index 0384728..db8f771 100644 --- a/routes.php +++ b/routes.php @@ -54,7 +54,10 @@ 'list_json' => '/discussion/json/', 'show' => '/discussion/show/{number}/', 'send' => '/discussion/send/{csrf}/', - 'get_messages' => '/discussion/getmessage/{number}/{transaction_id}/', + 'get_messages' => [ + '/discussion/getmessage/{number}/{transaction_id}/', + '/discussion/getmessage/{number}/{transaction_id}/{since}/', + ], ], 'Event' => [ diff --git a/templates/discussion/show.php b/templates/discussion/show.php index eed789e..be4b21f 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -32,7 +32,7 @@
    -
    +
    @@ -57,22 +57,35 @@ jQuery(document).ready(function () { var alreadyReceivedMessages = []; + var limit_date = false; /** * Cette fonction vérifie régulièrement les sms pour mettre à jour l'affichage */ function getmessages () { - ajaxTransactionId = Date.now(); - jQuery.getJSON(HTTP_PWD + "/discussion/getmessage//" + ajaxTransactionId , function( data ) { + ajaxTransactionId = Date.now(); + if (limit_date == false) + { + var first_load = true; + url = HTTP_PWD + "/discussion/getmessage//" + ajaxTransactionId; + limit_date = Date.now(); //After first load, we will only search for new messages + } + else + { + var first_load = false; + url = HTTP_PWD + "/discussion/getmessage//" + ajaxTransactionId + "/" + limit_date + "/"; + } + + jQuery.getJSON(url, function( data ) { if (data.transaction_id != ajaxTransactionId) { return false; - } - - jQuery('.discussion-container').html(''); + } + jQuery('.discussion-container #load-message-spinner').remove(); + jQuery('.discussion-container #send-message-spinner').remove(); $.each(data.messages, function(key, message) { @@ -87,6 +100,10 @@ { return '
    '; } + else if (['webm', 'ogg', 'ogv', 'mp4'].includes(extension)) + { + return ''; + } else { return ''; @@ -106,10 +123,10 @@ '
    ' + '
    '; - if (alreadyReceivedMessages.indexOf(message.md5) == -1) + if (alreadyReceivedMessages.indexOf(message.uid) == -1 && !first_load) //If new message received and not first time loading { playReceptionSound(); - alreadyReceivedMessages.push(message.md5); + alreadyReceivedMessages.push(message.uid); } break; @@ -140,8 +157,17 @@ } jQuery('.discussion-container').append(texte); - }); - scrollDownDiscussion(); + }); + + //Update limit_date if set + if (data.new_limit_date !== undefined) + { + limit_date = data.new_limit_date; + } + + if (data.messages.length) { + scrollDownDiscussion(first_load); + } }); } @@ -155,9 +181,6 @@ var messageInputContainer = jQuery('.message-input-container').outerHeight(); var footerHeight = jQuery('footer').outerHeight(); - console.log(windowHeight); - console.log(containerPosition.top); - var containerHeight = Math.floor(windowHeight - (containerPosition.top + messageInputContainer) - 20); //-20 px for aesthetic jQuery('.discussion-container').outerHeight(containerHeight); From 697300b5755e90eda7c9c3324a5e5f40a5ccc305 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 13 Apr 2021 02:07:47 +0200 Subject: [PATCH 31/40] add audio balise --- templates/discussion/show.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/discussion/show.php b/templates/discussion/show.php index be4b21f..31f15d6 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -100,10 +100,14 @@ { return '
    '; } - else if (['webm', 'ogg', 'ogv', 'mp4'].includes(extension)) + else if (['webm', 'ogv', 'mp4'].includes(extension)) { return ''; } + else if (['wav', 'ogg', 'mp3'].includes(extension)) + { + return ''; + } else { return '
    Voir le fichier ' + (Number(index) + 1) + '
    '; From b9641345a1ecd27225a26f6b8daac0a2ef36bf58 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Tue, 13 Apr 2021 02:33:30 +0200 Subject: [PATCH 32/40] do not duplicatd in progress msg --- controllers/publics/Discussion.php | 14 ++++++++++++-- templates/discussion/show.php | 5 ++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index 13d2031..7d4f215 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -220,8 +220,18 @@ namespace controllers\publics; if ($messages) { - $new_limit_date = (new \DateTime($messages[count($messages) - 1]['date']))->getTimestamp(); //Use latest message as the new limit date to search - $response['new_limit_date'] = $new_limit_date; + for ($i = count($messages); $i > 0; $i--) + { + $message = $messages[$i - 1]; + if ($message['type'] == 'inprogress') + { + continue; + } + + $new_limit_date = (new \DateTime($message['date']))->getTimestamp(); //Use latest not inprogress message as the new limit date to search + $response['new_limit_date'] = $new_limit_date; + break; + } } echo json_encode($response); diff --git a/templates/discussion/show.php b/templates/discussion/show.php index 31f15d6..caae5c6 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -87,6 +87,9 @@ jQuery('.discussion-container #load-message-spinner').remove(); jQuery('.discussion-container #send-message-spinner').remove(); + //We also remove all in-progress messages because they are added again in the new response if not sended yet, and if sended they should not appear in double + jQuery('.discussion-container .message-in-progress-container').remove(); + $.each(data.messages, function(key, message) { @@ -146,7 +149,7 @@ break; case 'inprogress' : var texte = '' + - '
    ' + + '
    ' + '
    ' + '
    ' + '
    ' + message.text + '
    ' + From 4cd52ae9ec3ca8aac945648c77d7df2ac59cee11 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Fri, 16 Apr 2021 17:11:24 +0200 Subject: [PATCH 33/40] fix discussion to correctly update status whithout replacing the all conversation for each sms --- controllers/publics/Discussion.php | 41 +++------------------ templates/discussion/show.php | 59 ++++++++++++++++-------------- 2 files changed, 37 insertions(+), 63 deletions(-) diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index 7d4f215..fd27fd6 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -98,9 +98,8 @@ namespace controllers\publics; * * @param string $number : Le numéro cible * @param string $transaction_id : Le numéro unique de la transaction ajax (sert à vérifier si la requete doit être prise en compte) - * @param ?int $since : La date à partir de laquelle on veut les messages, sous forme de timestamp */ - public function get_messages($number, $transaction_id, ?int $since = null) + public function get_messages($number, $transaction_id) { $now = new \DateTime(); $now = $now->format('Y-m-d H:i:s'); @@ -114,18 +113,9 @@ namespace controllers\publics; return false; } - if ($since) - { - $sendeds = $this->internal_sended->gets_since_date_by_destination_and_user($id_user, $since, $number); - $receiveds = $this->internal_received->gets_since_date_by_origin_and_user($id_user, $since, $number); - $scheduleds = $this->internal_scheduled->gets_after_date_for_number_and_user($id_user, $since, $number); - } - else - { - $sendeds = $this->internal_sended->gets_by_destination_and_user($id_user, $number); - $receiveds = $this->internal_received->gets_by_origin_and_user($id_user, $number); - $scheduleds = $this->internal_scheduled->gets_before_date_for_number_and_user($id_user, $now, $number); - } + $sendeds = $this->internal_sended->gets_by_destination_and_user($id_user, $number); + $receiveds = $this->internal_received->gets_by_origin_and_user($id_user, $number); + $scheduleds = $this->internal_scheduled->gets_before_date_for_number_and_user($id_user, $now, $number); $messages = []; @@ -207,33 +197,14 @@ namespace controllers\publics; return strtotime($a['date']) - strtotime($b['date']); }); - //Sans limite de temps, on récupère uniquement les 25 derniers messages sur l'ensemble pour limiter la charge - if (!$since) - { - $messages = \array_slice($messages, -25); - } + //Récupère uniquement les 25 derniers messages sur l'ensemble pour limiter la charge + $messages = \array_slice($messages, -25); $response = [ 'transaction_id' => $transaction_id, 'messages' => $messages, ]; - if ($messages) - { - for ($i = count($messages); $i > 0; $i--) - { - $message = $messages[$i - 1]; - if ($message['type'] == 'inprogress') - { - continue; - } - - $new_limit_date = (new \DateTime($message['date']))->getTimestamp(); //Use latest not inprogress message as the new limit date to search - $response['new_limit_date'] = $new_limit_date; - break; - } - } - echo json_encode($response); return true; diff --git a/templates/discussion/show.php b/templates/discussion/show.php index caae5c6..dd7134a 100644 --- a/templates/discussion/show.php +++ b/templates/discussion/show.php @@ -56,26 +56,18 @@