From 7483b9a8ae38e79f1be97575f2eb068fabb2a6a8 Mon Sep 17 00:00:00 2001 From: osaajani <> Date: Fri, 24 Feb 2023 16:29:10 +0100 Subject: [PATCH] Add support for tag in sms campaigns --- controllers/internals/Scheduled.php | 29 ++++---- controllers/internals/Sended.php | 19 +++-- controllers/publics/Api.php | 72 ++++++++++++++++++- controllers/publics/Discussion.php | 3 +- controllers/publics/Scheduled.php | 6 +- controllers/publics/Sended.php | 5 +- daemons/Phone.php | 2 +- daemons/Sender.php | 1 + .../20230224133515_add_scheduled_tag.php | 42 +++++++++++ descartes/Model.php | 15 ++-- models/Sended.php | 28 +++++++- routes.php | 1 + templates/scheduled/add.php | 11 +++ templates/scheduled/edit.php | 9 +++ templates/sended/list.php | 4 +- 15 files changed, 213 insertions(+), 34 deletions(-) create mode 100644 db/migrations/20230224133515_add_scheduled_tag.php diff --git a/controllers/internals/Scheduled.php b/controllers/internals/Scheduled.php index 85d964a..1b61a90 100644 --- a/controllers/internals/Scheduled.php +++ b/controllers/internals/Scheduled.php @@ -23,20 +23,21 @@ use Monolog\Logger; * * @param int $id_user : User to insert scheduled for * @param $at : Scheduled date to send - * @param string $text : Text of the message - * @param ?int $id_phone : Id of the phone to send message with, null by default - * @param ?int $id_phone_group : Id of the phone group 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 : Array of numbers to send message to, a number is an array ['number' => '+33XXX', 'data' => '{"key":"value", ...}'] - * @param array $contacts_ids : Contact ids to send message to - * @param array $groups_ids : Group ids to send message to - * @param array $conditional_group_ids : Conditional Groups ids to send message to - * @param array $media_ids : Ids of the medias to link to scheduled message + * @param string $text : Text of the message + * @param ?int $id_phone : Id of the phone to send message with, null by default + * @param ?int $id_phone_group : Id of the phone group 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 ?string $tag : A string tag to associate to sended SMS + * @param array $numbers : Array of numbers to send message to, a number is an array ['number' => '+33XXX', 'data' => '{"key":"value", ...}'] + * @param array $contacts_ids : Contact ids to send message to + * @param array $groups_ids : Group ids to send message to + * @param array $conditional_group_ids : Conditional Groups ids to send message to + * @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, ?int $id_phone_group = null, bool $flash = false, bool $mms = false, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = [], array $media_ids = []) + public function create(int $id_user, $at, string $text, ?int $id_phone = null, ?int $id_phone_group = null, bool $flash = false, bool $mms = false, ?string $tag = null, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = [], array $media_ids = []) { $scheduled = [ 'id_user' => $id_user, @@ -46,6 +47,7 @@ use Monolog\Logger; 'id_phone_group' => $id_phone_group, 'flash' => $flash, 'mms' => $mms, + 'tag' => $tag, ]; if ('' === $text) @@ -163,6 +165,7 @@ use Monolog\Logger; * @param ?int $id_phone_group : Id of the phone group 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 ?string $tag : A string tag to associate to sended SMS * @param array $numbers : Array of numbers to send message to, a number is an array ['number' => '+33XXX', 'data' => '{"key":"value", ...}'] * @param array $contacts_ids : Contact ids to send message to * @param array $groups_ids : Group ids to send message to @@ -171,7 +174,7 @@ use Monolog\Logger; * * @return bool : false on error, true on success */ - public function update_for_user(int $id_user, int $id_scheduled, $at, string $text, ?int $id_phone = null, ?int $id_phone_group = null, bool $flash = false, bool $mms = false, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = [], array $media_ids = []) + public function update_for_user(int $id_user, int $id_scheduled, $at, string $text, ?int $id_phone = null, ?int $id_phone_group = null, bool $flash = false, bool $mms = false, ?string $tag = null, array $numbers = [], array $contacts_ids = [], array $groups_ids = [], array $conditional_group_ids = [], array $media_ids = []) { $scheduled = [ 'id_user' => $id_user, @@ -181,6 +184,7 @@ use Monolog\Logger; 'id_phone_group' => $id_phone_group, 'mms' => $mms, 'flash' => $flash, + 'tag' => $tag, ]; if (null !== $id_phone) @@ -729,6 +733,7 @@ use Monolog\Logger; 'destination' => $target['number'], 'flash' => $scheduled['flash'], 'mms' => $scheduled['mms'], + 'tag' => $scheduled['tag'], 'medias' => $scheduled['medias'], 'text' => $text, ]; diff --git a/controllers/internals/Sended.php b/controllers/internals/Sended.php index d8badf4..e58577c 100644 --- a/controllers/internals/Sended.php +++ b/controllers/internals/Sended.php @@ -46,13 +46,14 @@ use Exception; * @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 ?string $tag : A string tag to associate to sended SMS * @param array $medias : Array of medias to link to the MMS * @param ?int $originating_scheduled : Id of the scheduled message that was responsible for sending this message. By default null. * @param string $status : Status of a the sms. By default \models\Sended::STATUS_UNKNOWN * * @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, bool $mms = false, array $medias = [], ?int $originating_scheduled = null, ?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, ?string $tag = null, array $medias = [], ?int $originating_scheduled = null, ?string $status = \models\Sended::STATUS_UNKNOWN) { $sended = [ 'id_user' => $id_user, @@ -64,6 +65,7 @@ use Exception; 'adapter' => $adapter, 'flash' => $flash, 'mms' => $mms, + 'tag' => $tag, 'status' => $status, 'originating_scheduled' => $originating_scheduled, ]; @@ -186,13 +188,15 @@ use Exception; * * @param int $id_user : User id * @param int $id_phone : Phone id we want the number of sended message for - * @param \DateTime $since : Date since which we want sended number + * @param ?\DateTime $since : Date since which we want sended number. Default to null. + * @param ?\DateTime $before : Date up to which we want sended number. Default to null. + * @param ?string $tag_like : Tag to filter sms by, this is not a = but a LIKE operator * * @return int */ - public function count_since_for_phone_and_user(int $id_user, int $id_phone, \DateTime $since): int + public function count_since_for_phone_and_user(int $id_user, int $id_phone, ?\DateTime $since, ?\DateTime $before = null, ?string $tag_like = null): int { - return $this->get_model()->count_since_for_phone_and_user($id_user, $id_phone, $since); + return $this->get_model()->count_since_for_phone_and_user($id_user, $id_phone, $since, $before, $tag_like); } /** @@ -238,6 +242,7 @@ use Exception; * @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 ?string $tag : A string tag to associate to sended SMS * @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 @@ -248,7 +253,7 @@ use Exception; * ?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, bool $mms = false, array $medias = [], $originating_scheduled = null, 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, ?string $tag = null, array $medias = [], $originating_scheduled = null, string $status = \models\Sended::STATUS_UNKNOWN): array { $return = [ 'error' => false, @@ -299,7 +304,7 @@ use Exception; } //If we reached limit for this phone and phone limits are enabled, do not send the message - if ((int) ($users_settings['phone_limit'] ?? false)) + if ((int) ($user_settings['phone_limit'] ?? false)) { $limits = $internal_phone->get_limits($id_phone); @@ -342,7 +347,7 @@ use Exception; finally { $uid = $uid ?? uniqid(); - $sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $uid, $adapter->meta_classname(), $flash, $mms, $medias, $originating_scheduled, $status); + $sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $uid, $adapter->meta_classname(), $flash, $mms, $tag, $medias, $originating_scheduled, $status); $webhook_body = [ 'id' => $sended_id, diff --git a/controllers/publics/Api.php b/controllers/publics/Api.php index 54b31d8..064c8f4 100644 --- a/controllers/publics/Api.php +++ b/controllers/publics/Api.php @@ -232,6 +232,74 @@ namespace controllers\publics; return $this->json($return); } + /** + * Return info about volume of sms sended for a period + * + * @param ?string $_POST['start'] : Date from which to get sms volume, format Y-m-d H:i:s. Default to null. + * @param ?string $_POST['end'] : Date up to which to get sms volume, format Y-m-d H:i:s. Default to null. + * @param ?string $_POST['tag'] : Tag to filter SMS by. If set, only sended sms with a matching tag will be counted. Default to null. + * + * @return : List of entries + */ + public function get_usage() + { + $start = $_GET['start'] ?? null; + $end = $_GET['end'] ?? null; + $tag = $_GET['tag'] ?? null; + + $return = self::DEFAULT_RETURN; + + if ($start) + { + if (!\controllers\internals\Tool::validate_date($start, 'Y-m-d H:i:s')) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'start must be a date of format "Y-m-d H:i:s".'; + $this->auto_http_code(false); + + return $this->json($return); + } + + $start = new \DateTime($start); + } + + if ($end) + { + if (!\controllers\internals\Tool::validate_date($end, 'Y-m-d H:i:s')) + { + $return = self::DEFAULT_RETURN; + $return['error'] = self::ERROR_CODES['INVALID_PARAMETER']; + $return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'end must be a date of format "Y-m-d H:i:s".'; + $this->auto_http_code(false); + + return $this->json($return); + } + + $end = new \DateTime($end); + } + + $total_sended = 0; + $phones_volumes = []; + + $phones = $this->internal_phone->gets_for_user($this->user['id']); + foreach ($phones as $phone) + { + $nb_sended = $this->internal_sended->count_since_for_phone_and_user($this->user['id'], $phone['id'], $start, $end, $tag); + $total_sended += $nb_sended; + $phones_volumes[$phone['id']] = $nb_sended; + } + + $return['response'] = [ + 'total' => $total_sended, + 'phones_volumes' => $phones_volumes, + ]; + + $this->auto_http_code(true); + + return $this->json($return); + } + /** * Schedule a message to be send. * @@ -241,6 +309,7 @@ namespace controllers\publics; * @param string $_POST['id_phone_group'] : Default null. Id of phone group to send the message from. If null abd id_phone 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['tag'] : Default null. Tag to associate to every sms of the campaign. * @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 @@ -257,6 +326,7 @@ namespace controllers\publics; $id_phone_group = empty($_POST['id_phone_group']) ? null : $_POST['id_phone_group']; $flash = (bool) ($_POST['flash'] ?? false); $mms = (bool) ($_POST['mms'] ?? false); + $tag = $_POST['tag'] ?? null; $numbers = $_POST['numbers'] ?? []; $contacts = $_POST['contacts'] ?? []; $groups = $_POST['groups'] ?? []; @@ -505,7 +575,7 @@ namespace controllers\publics; } } - $scheduled_id = $this->internal_scheduled->create($this->user['id'], $at, $text, $id_phone, $id_phone_group, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups, $media_ids); + $scheduled_id = $this->internal_scheduled->create($this->user['id'], $at, $text, $id_phone, $id_phone_group, $flash, $mms, $tag, $numbers, $contacts, $groups, $conditional_groups, $media_ids); if (!$scheduled_id) { $return = self::DEFAULT_RETURN; diff --git a/controllers/publics/Discussion.php b/controllers/publics/Discussion.php index ae58195..77e6829 100644 --- a/controllers/publics/Discussion.php +++ b/controllers/publics/Discussion.php @@ -234,6 +234,7 @@ namespace controllers\publics; $at = $now; $text = $_POST['text'] ?? ''; $destination = $_POST['destination'] ?? false; + $tag = $_POST['tag'] ?? null; $id_phone = $_POST['id_phone'] ?? false; $files = $_FILES['medias'] ?? false; @@ -315,7 +316,7 @@ namespace controllers\publics; //Destinations must be an array of number $destinations = [['number' => $destination, 'data' => '[]']]; - if (!$this->internal_scheduled->create($id_user, $at, $text, $id_phone, null, false, $mms, $destinations, [], [], [], $media_ids)) + if (!$this->internal_scheduled->create($id_user, $at, $text, $id_phone, null, false, $mms, $tag, $destinations, [], [], [], $media_ids)) { $return['success'] = false; $return['message'] = 'Impossible de créer le Sms'; diff --git a/controllers/publics/Scheduled.php b/controllers/publics/Scheduled.php index d64df34..eb2decb 100644 --- a/controllers/publics/Scheduled.php +++ b/controllers/publics/Scheduled.php @@ -264,6 +264,7 @@ namespace controllers\publics; $at = $_POST['at'] ?? false; $text = $_POST['text'] ?? false; $flash = (bool) ($_POST['flash'] ?? false); + $tag = ($_POST['tag'] ?? null) ?: null; $id_phone = empty($_POST['id_phone']) ? null : $_POST['id_phone']; $numbers = $_POST['numbers'] ?? []; $numbers = is_array($numbers) ? $numbers : [$numbers]; @@ -444,7 +445,7 @@ namespace controllers\publics; $id_phone = str_starts_with($original_id_phone, 'phone_') ? mb_substr($original_id_phone, mb_strlen('phone_')) : null; $id_phone_group = str_starts_with($original_id_phone, 'phonegroup_') ? mb_substr($original_id_phone, mb_strlen('phonegroup_')) : null; - $scheduled_id = $this->internal_scheduled->create($id_user, $at, $text, $id_phone, $id_phone_group, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups, $media_ids); + $scheduled_id = $this->internal_scheduled->create($id_user, $at, $text, $id_phone, $id_phone_group, $flash, $mms, $tag, $numbers, $contacts, $groups, $conditional_groups, $media_ids); if (!$scheduled_id) { \FlashMessage\FlashMessage::push('danger', 'Impossible de créer le Sms.'); @@ -484,6 +485,7 @@ namespace controllers\publics; $text = $scheduled['text'] ?? false; $id_phone = empty($scheduled['id_phone']) ? null : $scheduled['id_phone']; $flash = (bool) ($scheduled['flash'] ?? false); + $tag = ($scheduled['tag'] ?? null) ?: null; $numbers = $scheduled['numbers'] ?? []; $contacts = $scheduled['contacts'] ?? []; $groups = $scheduled['groups'] ?? []; @@ -665,7 +667,7 @@ namespace controllers\publics; $id_phone = str_starts_with($original_id_phone, 'phone_') ? mb_substr($original_id_phone, mb_strlen('phone_')) : null; $id_phone_group = str_starts_with($original_id_phone, 'phonegroup_') ? mb_substr($original_id_phone, mb_strlen('phonegroup_')) : null; - $this->internal_scheduled->update_for_user($id_user, $id_scheduled, $at, $text, $id_phone, $id_phone_group, $flash, $mms, $numbers, $contacts, $groups, $conditional_groups, $media_ids); + $this->internal_scheduled->update_for_user($id_user, $id_scheduled, $at, $text, $id_phone, $id_phone_group, $flash, $mms, $tag, $numbers, $contacts, $groups, $conditional_groups, $media_ids); ++$nb_update; } diff --git a/controllers/publics/Sended.php b/controllers/publics/Sended.php index d82bed6..7761352 100644 --- a/controllers/publics/Sended.php +++ b/controllers/publics/Sended.php @@ -59,8 +59,9 @@ namespace controllers\publics; 0 => 'phone_name', 1 => 'searchable_destination', 2 => 'text', - 3 => 'at', - 4 => 'status', + 3 => 'tag', + 4 => 'at', + 5 => 'status', ]; $search = $_GET['search']['value'] ?? null; diff --git a/daemons/Phone.php b/daemons/Phone.php index 585ce20..1348a9e 100644 --- a/daemons/Phone.php +++ b/daemons/Phone.php @@ -144,7 +144,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'], $message['mms'], $message['medias'], $message['id_scheduled']); + $response = $internal_sended->send($this->adapter, $this->phone['id_user'], $this->phone['id'], $message['text'], $message['destination'], $message['flash'], $message['mms'], $message['tag'], $message['medias'], $message['id_scheduled']); if ($response['error']) { $this->logger->error('Failed send message : ' . json_encode($message) . ' with error : ' . $response['error_message']); diff --git a/daemons/Sender.php b/daemons/Sender.php index d462739..9acb00a 100644 --- a/daemons/Sender.php +++ b/daemons/Sender.php @@ -80,6 +80,7 @@ class Sender extends AbstractDaemon 'destination' => $sms['destination'], 'flash' => $sms['flash'], 'mms' => $sms['mms'], + 'tag' => $sms['tag'], 'medias' => $sms['medias'] ?? [], ]; diff --git a/db/migrations/20230224133515_add_scheduled_tag.php b/db/migrations/20230224133515_add_scheduled_tag.php new file mode 100644 index 0000000..2ca4e08 --- /dev/null +++ b/db/migrations/20230224133515_add_scheduled_tag.php @@ -0,0 +1,42 @@ +table('scheduled'); + $table->addColumn('tag', 'string', ['default' => NULL, 'null' => true, 'limit' => 1000]) + ->update(); + + $table = $this->table('sended'); + $table->addColumn('tag', 'string', ['default' => NULL, 'null' => true, 'limit' => 255]) + ->update(); + } +} diff --git a/descartes/Model.php b/descartes/Model.php index ad14075..0580cde 100644 --- a/descartes/Model.php +++ b/descartes/Model.php @@ -144,7 +144,7 @@ /** * Generate IN query params and values - * @param string $values : Values to generate in array from + * @param array $values : Values to generate in array from * @return array : Array ['QUERY' => string 'IN(...)', 'PARAMS' => [parameters to pass to execute]] */ protected function _generate_in_from_array ($values) @@ -214,6 +214,11 @@ $operator = '>'; break; + case ('%' == $first_char) : + $true_fieldname = mb_substr($fieldname, 1); + $operator = 'LIKE'; + break; + case ('=' == $first_char) : $true_fieldname = mb_substr($fieldname, 1); $operator = '='; @@ -227,8 +232,11 @@ //Protect against injection in fieldname $true_fieldname = preg_replace('#[^a-zA-Z0-9_]#', '', $true_fieldname); - $query = '`' . $true_fieldname . '` ' . $operator . ' :where_' . $true_fieldname; - $param = ['where_' . $true_fieldname => $value]; + // Add a uid to fieldname so we can combine multiple rules on same field + $uid = uniqid(); + + $query = '`' . $true_fieldname . '` ' . $operator . ' :where_' . $true_fieldname . '_' . $uid; + $param = ['where_' . $true_fieldname . '_' . $uid => $value]; return ['QUERY' => $query, 'PARAM' => $param]; } @@ -358,7 +366,6 @@ } $query = "SELECT COUNT(*) as `count` FROM `" . $table . "` WHERE 1 " . (count($wheres) ? 'AND ' : '') . implode(' AND ', $wheres); - $query = $this->pdo->prepare($query); foreach ($params as $label => &$param) diff --git a/models/Sended.php b/models/Sended.php index 0acdb20..3644dee 100644 --- a/models/Sended.php +++ b/models/Sended.php @@ -183,13 +183,35 @@ namespace models; * * @param int $id_user : User id * @param int $id_phone : Phone id we want the number of sended message for - * @param \DateTime $since : Date since which we want sended number + * @param ?\DateTime $since : Date since which we want sended Number. Default to null. + * @param ?\DateTime $before : Date up to which we want sended number. Default to null. + * @param ?string $tag_like : Tag to filter sms by, this is not a = but a LIKE operator * * @return int */ - public function count_since_for_phone_and_user(int $id_user, int $id_phone, \DateTime $since) : int + public function count_since_for_phone_and_user(int $id_user, int $id_phone, ?\DateTime $since = null, ?\DateTime $before = null, ?string $tag_like = null) : int { - return $this->_count('sended', ['id_user' => $id_user, 'id_phone' => $id_phone, '>=at' => $since->format('c')]); + $data = [ + 'id_user' => $id_user, + 'id_phone' => $id_phone, + ]; + + if ($since) + { + $data['>=at'] = $since->format('c'); + } + + if ($before) + { + $data['<=at'] = $before->format('c'); + } + + if ($tag_like) + { + $data['%tag'] = $tag_like; + } + + return $this->_count('sended', $data); } /** diff --git a/routes.php b/routes.php index 26170ca..ccb7279 100644 --- a/routes.php +++ b/routes.php @@ -208,6 +208,7 @@ '/api/list/{entry_type}/', '/api/list/{entry_type}/{page}/', ], + 'get_usage' => '/api/usage/', 'post_scheduled' => [ '/api/scheduled/', ], diff --git a/templates/scheduled/add.php b/templates/scheduled/add.php index d59e77d..0c3349f 100644 --- a/templates/scheduled/add.php +++ b/templates/scheduled/add.php @@ -131,6 +131,17 @@ + +
+ +

+ Vous pouvez renseigner une chaine de caractère qui sera associée à tous les SMS envoyés. Utile pour associer un identifiant interne à vos systèmes. Laissez vide si vous ne voulez pas associé de chaine. +

+
+ +
+
+
diff --git a/templates/scheduled/edit.php b/templates/scheduled/edit.php index 155ddc9..30f8d27 100644 --- a/templates/scheduled/edit.php +++ b/templates/scheduled/edit.php @@ -152,6 +152,15 @@
+
+ +

+ Vous pouvez renseigner une chaine de caractère qui sera associée à tous les SMS envoyés. Utile pour associer un identifiant interne à vos systèmes. Laissez vide si vous ne voulez pas associé de chaine. +

+
+ +
+
@@ -74,7 +75,7 @@ jQuery(document).ready(function () "url": HTTP_PWD + "/assets/js/datatables/french.json", }, "orderMulti": false, - "order": [[3, "desc"]], + "order": [[4, "desc"]], "columnDefs": [{ 'targets': 'checkcolumn', 'orderable': false, @@ -111,6 +112,7 @@ jQuery(document).ready(function () return jQuery.fn.dataTable.render.text().display(data); }, }, + {data: 'tag', render: jQuery.fn.dataTable.render.text()}, {data: 'at', render: jQuery.fn.dataTable.render.text()}, { data: 'status',