Add phone reliability features
This commit is contained in:
parent
44b855dd48
commit
52c849e043
|
@ -17,5 +17,8 @@
|
||||||
"kreait/firebase-php": "^7.0",
|
"kreait/firebase-php": "^7.0",
|
||||||
"benmorel/gsm-charset-converter": "^0.3.0",
|
"benmorel/gsm-charset-converter": "^0.3.0",
|
||||||
"google/cloud-pubsub": "^1.46"
|
"google/cloud-pubsub": "^1.46"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"fakerphp/faker": "^1.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
namespace controllers\internals;
|
namespace controllers\internals;
|
||||||
|
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
|
use Faker\Factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to call the console scripts.
|
* Class to call the console scripts.
|
||||||
|
@ -62,198 +63,415 @@ use DateInterval;
|
||||||
|
|
||||||
$phone = $internal_phone->get($id_phone);
|
$phone = $internal_phone->get($id_phone);
|
||||||
if (!$phone)
|
if (!$phone)
|
||||||
{
|
{
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
|
||||||
|
|
||||||
new \daemons\Phone($phone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
new \daemons\Phone($phone);
|
||||||
* Check if a user exists based on email.
|
}
|
||||||
*
|
|
||||||
* @param string $email : User email
|
/**
|
||||||
*/
|
* Check if a user exists based on email.
|
||||||
public function user_exists(string $email)
|
*
|
||||||
|
* @param string $email : User email
|
||||||
|
*/
|
||||||
|
public function user_exists(string $email)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_user = new \controllers\internals\User($bdd);
|
||||||
|
|
||||||
|
$user = $internal_user->get_by_email($email);
|
||||||
|
|
||||||
|
exit($user ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user exists based on id.
|
||||||
|
*
|
||||||
|
* @param string $id : User id
|
||||||
|
*/
|
||||||
|
public function user_id_exists(string $id)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_user = new \controllers\internals\User($bdd);
|
||||||
|
|
||||||
|
$user = $internal_user->get($id);
|
||||||
|
|
||||||
|
exit($user ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a user or update an existing user.
|
||||||
|
*
|
||||||
|
* @param $email : User email
|
||||||
|
* @param $password : User password
|
||||||
|
* @param $admin : Is user admin
|
||||||
|
* @param $api_key : User API key, if null random api key is generated
|
||||||
|
* @param $status : User status, default \models\User::STATUS_ACTIVE
|
||||||
|
* @param bool $encrypt_password : Should the password be encrypted, by default true
|
||||||
|
*
|
||||||
|
* exit code 0 on success | 1 on error
|
||||||
|
*/
|
||||||
|
public function create_update_user(string $email, string $password, bool $admin, ?string $api_key = null, string $status = \models\User::STATUS_ACTIVE, bool $encrypt_password = true)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_user = new \controllers\internals\User($bdd);
|
||||||
|
|
||||||
|
$user = $internal_user->get_by_email($email);
|
||||||
|
if ($user)
|
||||||
{
|
{
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
$api_key = $api_key ?? $internal_user->generate_random_api_key();
|
||||||
$internal_user = new \controllers\internals\User($bdd);
|
$update_datas = [
|
||||||
|
'email' => $email,
|
||||||
|
'password' => $encrypt_password ? password_hash($password, PASSWORD_DEFAULT) : $password,
|
||||||
|
'admin' => $admin,
|
||||||
|
'api_key' => $api_key,
|
||||||
|
'status' => $status,
|
||||||
|
];
|
||||||
|
|
||||||
$user = $internal_user->get_by_email($email);
|
$success = $internal_user->update($user['id'], $update_datas);
|
||||||
|
echo json_encode(['id' => $user['id']]);
|
||||||
exit($user ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a user exists based on id.
|
|
||||||
*
|
|
||||||
* @param string $id : User id
|
|
||||||
*/
|
|
||||||
public function user_id_exists(string $id)
|
|
||||||
{
|
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
|
||||||
$internal_user = new \controllers\internals\User($bdd);
|
|
||||||
|
|
||||||
$user = $internal_user->get($id);
|
|
||||||
|
|
||||||
exit($user ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a user or update an existing user.
|
|
||||||
*
|
|
||||||
* @param $email : User email
|
|
||||||
* @param $password : User password
|
|
||||||
* @param $admin : Is user admin
|
|
||||||
* @param $api_key : User API key, if null random api key is generated
|
|
||||||
* @param $status : User status, default \models\User::STATUS_ACTIVE
|
|
||||||
* @param bool $encrypt_password : Should the password be encrypted, by default true
|
|
||||||
*
|
|
||||||
* exit code 0 on success | 1 on error
|
|
||||||
*/
|
|
||||||
public function create_update_user(string $email, string $password, bool $admin, ?string $api_key = null, string $status = \models\User::STATUS_ACTIVE, bool $encrypt_password = true)
|
|
||||||
{
|
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
|
||||||
$internal_user = new \controllers\internals\User($bdd);
|
|
||||||
|
|
||||||
$user = $internal_user->get_by_email($email);
|
|
||||||
if ($user)
|
|
||||||
{
|
|
||||||
$api_key = $api_key ?? $internal_user->generate_random_api_key();
|
|
||||||
$update_datas = [
|
|
||||||
'email' => $email,
|
|
||||||
'password' => $encrypt_password ? password_hash($password, PASSWORD_DEFAULT) : $password,
|
|
||||||
'admin' => $admin,
|
|
||||||
'api_key' => $api_key,
|
|
||||||
'status' => $status,
|
|
||||||
];
|
|
||||||
|
|
||||||
$success = $internal_user->update($user['id'], $update_datas);
|
|
||||||
echo json_encode(['id' => $user['id']]);
|
|
||||||
|
|
||||||
exit($success ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$new_user_id = $internal_user->create($email, $password, $admin, $api_key, $status, $encrypt_password);
|
|
||||||
echo json_encode(['id' => $new_user_id]);
|
|
||||||
|
|
||||||
exit($new_user_id ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a user status.
|
|
||||||
*
|
|
||||||
* @param string $id : User id
|
|
||||||
* @param string $status : User status, default \models\User::STATUS_ACTIVE
|
|
||||||
*/
|
|
||||||
public function update_user_status(string $id, string $status)
|
|
||||||
{
|
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
|
||||||
$internal_user = new \controllers\internals\User($bdd);
|
|
||||||
|
|
||||||
$user = $internal_user->get($id);
|
|
||||||
if (!$user)
|
|
||||||
{
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$success = $internal_user->update_status($user['id'], $status);
|
|
||||||
|
|
||||||
exit($success ? 0 : 1);
|
exit($success ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
$new_user_id = $internal_user->create($email, $password, $admin, $api_key, $status, $encrypt_password);
|
||||||
* Delete a user.
|
echo json_encode(['id' => $new_user_id]);
|
||||||
*
|
|
||||||
* @param string $id : User id
|
exit($new_user_id ? 0 : 1);
|
||||||
*/
|
}
|
||||||
public function delete_user(string $id)
|
|
||||||
|
/**
|
||||||
|
* Update a user status.
|
||||||
|
*
|
||||||
|
* @param string $id : User id
|
||||||
|
* @param string $status : User status, default \models\User::STATUS_ACTIVE
|
||||||
|
*/
|
||||||
|
public function update_user_status(string $id, string $status)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_user = new \controllers\internals\User($bdd);
|
||||||
|
|
||||||
|
$user = $internal_user->get($id);
|
||||||
|
if (!$user)
|
||||||
{
|
{
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
exit(1);
|
||||||
$internal_user = new \controllers\internals\User($bdd);
|
|
||||||
|
|
||||||
$success = $internal_user->delete($id);
|
|
||||||
|
|
||||||
exit($success ? 0 : 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
$success = $internal_user->update_status($user['id'], $status);
|
||||||
* Delete medias that are no longer usefull.
|
|
||||||
*/
|
exit($success ? 0 : 1);
|
||||||
public function clean_unused_medias()
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a user.
|
||||||
|
*
|
||||||
|
* @param string $id : User id
|
||||||
|
*/
|
||||||
|
public function delete_user(string $id)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_user = new \controllers\internals\User($bdd);
|
||||||
|
|
||||||
|
$success = $internal_user->delete($id);
|
||||||
|
|
||||||
|
exit($success ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete medias that are no longer usefull.
|
||||||
|
*/
|
||||||
|
public function clean_unused_medias()
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_media = new \controllers\internals\Media($bdd);
|
||||||
|
|
||||||
|
$medias = $internal_media->gets_unused();
|
||||||
|
|
||||||
|
foreach ($medias as $media)
|
||||||
{
|
{
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
$success = $internal_media->delete_for_user($media['id_user'], $media['id']);
|
||||||
$internal_media = new \controllers\internals\Media($bdd);
|
|
||||||
|
|
||||||
$medias = $internal_media->gets_unused();
|
echo (false === $success ? '[KO]' : '[OK]') . ' - ' . $media['path'] . "\n";
|
||||||
|
|
||||||
foreach ($medias as $media)
|
|
||||||
{
|
|
||||||
$success = $internal_media->delete_for_user($media['id_user'], $media['id']);
|
|
||||||
|
|
||||||
echo (false === $success ? '[KO]' : '[OK]') . ' - ' . $media['path'] . "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do alerting for quota limits.
|
|
||||||
*/
|
|
||||||
public function quota_limit_alerting()
|
|
||||||
{
|
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
|
||||||
$internal_quota = new \controllers\internals\Quota($bdd);
|
|
||||||
$internal_quota->alerting_for_limit_close_and_reached();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do quota renewal.
|
|
||||||
*/
|
|
||||||
public function renew_quotas()
|
|
||||||
{
|
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
|
||||||
$internal_quota = new \controllers\internals\Quota($bdd);
|
|
||||||
$internal_quota->renew_quotas();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do some fake population renewal.
|
|
||||||
*/
|
|
||||||
public function f()
|
|
||||||
{
|
|
||||||
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
|
||||||
$internal_sended = new \controllers\internals\Sended($bdd);
|
|
||||||
|
|
||||||
$destinations = ['+33612345678','+33612345679','+33612345680',];
|
|
||||||
$statuses = [\models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED, \models\Sended::STATUS_UNKNOWN];
|
|
||||||
$day = new \DateTime();
|
|
||||||
$day->sub(new DateInterval('P30D'));
|
|
||||||
for ($i = 0; $i < 30; $i++)
|
|
||||||
{
|
|
||||||
$day->add(new DateInterval('P1D'));
|
|
||||||
$n = rand(0, 100);
|
|
||||||
for ($j = 0; $j < $n; $j++)
|
|
||||||
{
|
|
||||||
$id_user = 1;
|
|
||||||
$id_phone = rand(1, 2);
|
|
||||||
$destination = $destinations[array_rand($destinations)];
|
|
||||||
$status = $statuses[array_rand($statuses)];
|
|
||||||
$internal_sended->create(
|
|
||||||
$id_user,
|
|
||||||
$id_phone,
|
|
||||||
$day->format('Y-m-d H:i:s'),
|
|
||||||
"TEST N°$i:$j",
|
|
||||||
$destination,
|
|
||||||
uniqid(),
|
|
||||||
'adapters\TestAdapter',
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
[],
|
|
||||||
null,
|
|
||||||
$status,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do alerting for quota limits.
|
||||||
|
*/
|
||||||
|
public function quota_limit_alerting()
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_quota = new \controllers\internals\Quota($bdd);
|
||||||
|
$internal_quota->alerting_for_limit_close_and_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do quota renewal.
|
||||||
|
*/
|
||||||
|
public function renew_quotas()
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_quota = new \controllers\internals\Quota($bdd);
|
||||||
|
$internal_quota->renew_quotas();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do phone reliability verifications
|
||||||
|
*/
|
||||||
|
public function phone_reliability()
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_user = new \controllers\internals\User($bdd);
|
||||||
|
$internal_settings = new \controllers\internals\Setting($bdd);
|
||||||
|
$internal_sended = new \controllers\internals\Sended($bdd);
|
||||||
|
$internal_phone_reliability = new \controllers\internals\PhoneReliability($bdd);
|
||||||
|
$internal_phone = new \controllers\internals\Phone($bdd);
|
||||||
|
$internal_webhook = new \controllers\internals\Webhook($bdd);
|
||||||
|
$internal_mailer = new \controllers\internals\Mailer();
|
||||||
|
|
||||||
|
$users = $internal_user->get_all_active();
|
||||||
|
foreach ($users as $user)
|
||||||
|
{
|
||||||
|
$settings = $internal_settings->gets_for_user($user['id']);
|
||||||
|
echo "\nCheck phone reliability for user " . $user['id'] . ":\n";
|
||||||
|
if ($settings['phone_reliability_failed'])
|
||||||
|
{
|
||||||
|
$rate_limit = intval($settings['phone_reliability_failed_rate_limit']) / 100;
|
||||||
|
$min_volume = intval($settings['phone_reliability_failed_volume']);
|
||||||
|
$period = intval($settings['phone_reliability_failed_period']);
|
||||||
|
$grace_period = intval($settings['phone_reliability_failed_grace_period']);
|
||||||
|
|
||||||
|
echo " Check for failed SMS with rate > " . $rate_limit . " and volume > " . $min_volume . " on period " . $period . "s with grace period of " . $grace_period . "s.\n";
|
||||||
|
|
||||||
|
$unreliable_phones = $internal_phone_reliability->find_unreliable_phones($user['id'], \models\Sended::STATUS_FAILED, $rate_limit, $min_volume, $period, $grace_period);
|
||||||
|
foreach ($unreliable_phones as $unreliable_phone)
|
||||||
|
{
|
||||||
|
$phone = $internal_phone->get($unreliable_phone['id_phone']);
|
||||||
|
if (!$phone)
|
||||||
|
{
|
||||||
|
echo ' Cannot find phone: ' . $unreliable_phone['id_phone'] . "\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n Phone " . $phone['id'] . ' - ' . $phone['name'] . " failed rate = " . $unreliable_phone['rate'] . " > " . $rate_limit . " and volume " . $unreliable_phone['total'] . " > " . $min_volume . "\n";
|
||||||
|
|
||||||
|
$internal_phone_reliability->create($user['id'], $phone['id'], \models\Sended::STATUS_FAILED);
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_failed_email'])
|
||||||
|
{
|
||||||
|
$success = $internal_mailer->enqueue($user['email'], EMAIL_PHONE_RELIABILITY_FAILED, [
|
||||||
|
'phone' => $phone,
|
||||||
|
'period' => $period,
|
||||||
|
'total' => $unreliable_phone['total'],
|
||||||
|
'unreliable' => $unreliable_phone['unreliable'],
|
||||||
|
'rate' => $unreliable_phone['rate'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$success)
|
||||||
|
{
|
||||||
|
echo ' Cannot enqueue alert for unreliable failed phone: ' . $unreliable_phone['id_phone'] . "\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo " Alert mail for unreliable failed phone " . $phone['id'] . ' - ' . $phone['name'] . " added\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_failed_webhook'])
|
||||||
|
{
|
||||||
|
$webhook = [
|
||||||
|
'reliability_type' => \models\Sended::STATUS_FAILED,
|
||||||
|
'id_phone' => $unreliable_phone['id_phone'],
|
||||||
|
'period' => $period,
|
||||||
|
'total' => $unreliable_phone['total'],
|
||||||
|
'unreliable' => $unreliable_phone['unreliable'],
|
||||||
|
'rate' => $unreliable_phone['rate'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$internal_webhook->trigger($user['id'], \models\Webhook::TYPE_PHONE_RELIABILITY, $webhook);
|
||||||
|
|
||||||
|
echo " Webhook for unreliable failed phone " . $phone['id'] . ' - ' . $phone['name'] . " triggered\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_failed_auto_disable'])
|
||||||
|
{
|
||||||
|
$internal_phone->update_status($unreliable_phone['id_phone'], \models\Phone::STATUS_DISABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_unknown'])
|
||||||
|
{
|
||||||
|
$rate_limit = intval($settings['phone_reliability_unknown_rate_limit']) / 100;
|
||||||
|
$min_volume = intval($settings['phone_reliability_unknown_volume']);
|
||||||
|
$period = intval($settings['phone_reliability_unknown_period']);
|
||||||
|
$grace_period = intval($settings['phone_reliability_unknown_grace_period']);
|
||||||
|
|
||||||
|
echo "\n Check for unknown SMS with rate > " . $rate_limit . " and volume > " . $min_volume . " on period " . $period . "s with grace period of " . $grace_period . "s.\n";
|
||||||
|
|
||||||
|
$unreliable_phones = $internal_phone_reliability->find_unreliable_phones($user['id'], \models\Sended::STATUS_UNKNOWN, $rate_limit, $min_volume, $period, $grace_period);
|
||||||
|
foreach ($unreliable_phones as $unreliable_phone)
|
||||||
|
{
|
||||||
|
$phone = $internal_phone->get($unreliable_phone['id_phone']);
|
||||||
|
if (!$phone)
|
||||||
|
{
|
||||||
|
echo ' Cannot find phone: ' . $unreliable_phone['id_phone'] . "\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n Phone " . $phone['id'] . ' - ' . $phone['name'] . " unknown rate = " . $unreliable_phone['rate'] . " > " . $rate_limit . "\n";
|
||||||
|
|
||||||
|
$internal_phone_reliability->create($user['id'], $phone['id'], \models\Sended::STATUS_UNKNOWN);
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_unknown_email'])
|
||||||
|
{
|
||||||
|
$success = $internal_mailer->enqueue($user['email'], EMAIL_PHONE_RELIABILITY_UNKNOWN, [
|
||||||
|
'phone' => $phone,
|
||||||
|
'period' => $period,
|
||||||
|
'total' => $unreliable_phone['total'],
|
||||||
|
'unreliable' => $unreliable_phone['unreliable'],
|
||||||
|
'rate' => $unreliable_phone['rate'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$success)
|
||||||
|
{
|
||||||
|
echo ' Cannot enqueue alert for unreliable unknown phone: ' . $unreliable_phone['id_phone'] . "\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo " Alert mail for unreliable unknown phone " . $phone['id'] . ' - ' . $phone['name'] . " added\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_unknown_webhook'])
|
||||||
|
{
|
||||||
|
$webhook = [
|
||||||
|
'reliability_type' => \models\Sended::STATUS_UNKNOWN,
|
||||||
|
'id_phone' => $unreliable_phone['id_phone'],
|
||||||
|
'period' => $period,
|
||||||
|
'total' => $unreliable_phone['total'],
|
||||||
|
'unreliable' => $unreliable_phone['unreliable'],
|
||||||
|
'rate' => $unreliable_phone['rate'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$internal_webhook->trigger($user['id'], \models\Webhook::TYPE_PHONE_RELIABILITY, $webhook);
|
||||||
|
|
||||||
|
echo " Webhook for unreliable unknown phone " . $phone['id'] . ' - ' . $phone['name'] . " triggered\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings['phone_reliability_unknown_auto_disable'])
|
||||||
|
{
|
||||||
|
$internal_phone->update_status($unreliable_phone['id_phone'], \models\Phone::STATUS_DISABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to easily populate the database with fake data for testing.
|
||||||
|
*
|
||||||
|
* @param int $id_user : User ID for whom data is to be generated
|
||||||
|
* @param int $received_entries : Number of entries to add to the received table
|
||||||
|
* @param int $sended_entries : Number of entries to add to the sended table
|
||||||
|
* @param int $contact_entries : Number of entries to add to the contact table
|
||||||
|
*/
|
||||||
|
public function seed_database(int $id_user, int $received_entries, int $sended_entries, int $contact_entries)
|
||||||
|
{
|
||||||
|
$this->seed_received($id_user, $received_entries);
|
||||||
|
$this->seed_sended($id_user, $sended_entries);
|
||||||
|
$this->seed_contact($id_user, $contact_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill table received with fake data
|
||||||
|
*
|
||||||
|
* @param int $id_user : User to insert received for
|
||||||
|
* @param int $entries : How many received to insert
|
||||||
|
*/
|
||||||
|
public function seed_received(int $id_user, int $entries)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_received = new \controllers\internals\Received($bdd);
|
||||||
|
$internal_phone = new \controllers\internals\Phone($bdd);
|
||||||
|
$faker = Factory::create();
|
||||||
|
|
||||||
|
$phones = $internal_phone->gets_for_user($id_user);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $entries; $i++)
|
||||||
|
{
|
||||||
|
$id_phone = $faker->randomElement($phones)['id'];
|
||||||
|
$at = $faker->dateTimeBetween('-1 year', 'now')->format('Y-m-d H:i:s');
|
||||||
|
$text = $faker->sentence(rand(5,10), true);
|
||||||
|
$origin = $faker->e164PhoneNumber;
|
||||||
|
$status = $faker->randomElement(['read', 'unread']);
|
||||||
|
$command = false;
|
||||||
|
$mms = false;
|
||||||
|
$media_ids = [];
|
||||||
|
|
||||||
|
|
||||||
|
$internal_received->create($id_user, $id_phone, $at, $text, $origin, $status, $command, $mms, $media_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill table sended with fake data
|
||||||
|
*
|
||||||
|
* @param int $id_user : User to insert sended entries for
|
||||||
|
* @param int $entries : Number of entries to insert
|
||||||
|
*/
|
||||||
|
public function seed_sended(int $id_user, int $entries)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_sended = new \controllers\internals\Sended($bdd);
|
||||||
|
$internal_phone = new \controllers\internals\Phone($bdd);
|
||||||
|
$faker = Factory::create();
|
||||||
|
|
||||||
|
$phones = $internal_phone->gets_for_user($id_user);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $entries; $i++)
|
||||||
|
{
|
||||||
|
echo $i."\n";
|
||||||
|
$phone = $faker->randomElement($phones);
|
||||||
|
$id_phone = $phone['id'];
|
||||||
|
$at = $faker->dateTimeBetween('-1 year', 'now')->format('Y-m-d H:i:s');
|
||||||
|
$text = $faker->sentence(rand(5, 10), true);
|
||||||
|
$destination = $faker->e164PhoneNumber;
|
||||||
|
$uid = $faker->uuid;
|
||||||
|
$adapter = $phone['adapter'];
|
||||||
|
$flash = $faker->boolean;
|
||||||
|
$mms = $faker->boolean;
|
||||||
|
$tag = $faker->optional()->word;
|
||||||
|
$medias = []; // Add logic for media IDs if needed
|
||||||
|
$originating_scheduled = $faker->numberBetween(1, 100);
|
||||||
|
$status = $faker->randomElement([\models\Sended::STATUS_UNKNOWN, \models\Sended::STATUS_DELIVERED, \models\Sended::STATUS_FAILED]);
|
||||||
|
|
||||||
|
$internal_sended->create($id_user, $id_phone, $at, $text, $destination, $uid, $adapter, $flash, $mms, $tag, $medias, $originating_scheduled, $status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill table contact with fake data
|
||||||
|
*
|
||||||
|
* @param int $id_user : User to insert contacts for
|
||||||
|
* @param int $entries : Number of contacts to insert
|
||||||
|
*/
|
||||||
|
public function seed_contact(int $id_user, int $entries)
|
||||||
|
{
|
||||||
|
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
|
||||||
|
$internal_contact = new \controllers\internals\Contact($bdd);
|
||||||
|
$faker = Factory::create();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $entries; $i++)
|
||||||
|
{
|
||||||
|
$name = $faker->name;
|
||||||
|
$number = $faker->e164PhoneNumber;
|
||||||
|
$data = '[]';
|
||||||
|
|
||||||
|
$internal_contact->create($id_user, $number, $name, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of RaspiSMS.
|
||||||
|
*
|
||||||
|
* (c) Pierre-Lin Bonnemaison <plebwebsas@gmail.com>
|
||||||
|
*
|
||||||
|
* This source file is subject to the GPL-3.0 license that is bundled
|
||||||
|
* with this source code in the file LICENSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace controllers\internals;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PhoneReliability extends StandardController
|
||||||
|
{
|
||||||
|
protected $model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a phone reliability history entry.
|
||||||
|
*
|
||||||
|
* @param int $id_user : Id of user to create sended message for
|
||||||
|
* @param int $id_phone : Id of the number the message was send with
|
||||||
|
* @param $type : Type of reliability alert
|
||||||
|
* @return mixed : false on error, new sended id else
|
||||||
|
*/
|
||||||
|
public function create(int $id_user, int $id_phone, string $type)
|
||||||
|
{
|
||||||
|
return $this->get_model()->insert([
|
||||||
|
'id_user' => $id_user,
|
||||||
|
'id_phone' => $id_phone,
|
||||||
|
'type' => $type,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $id_sended;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all unreliable phones for a user, based on sended sms status, rate limit, etc.
|
||||||
|
*
|
||||||
|
* @param int $id_user : User id
|
||||||
|
* @param string $sms_status : Status of SMS to use to calculate rate
|
||||||
|
* @param float $rate_limit : Percentage of SMS matching status after which we consider the phone unreliable
|
||||||
|
* @param int $min_volume : Minimum number of sms we need to have to consider the statistic relevent
|
||||||
|
* @param int $period : The time span in minutes from which SMS counting should begin.
|
||||||
|
* @param int $grace_period : How long in minutes should we wait before including a SMS in counting
|
||||||
|
*
|
||||||
|
* @return array : A list of unreliable phone for the user, with phone id, total number of sms, and rate of failed sms
|
||||||
|
*/
|
||||||
|
public function find_unreliable_phones (int $id_user, string $sms_status, float $rate_limit, int $min_volume, int $period, int $grace_period)
|
||||||
|
{
|
||||||
|
return $this->get_model()->find_unreliable_phones($id_user, $sms_status, $rate_limit, $min_volume, $period, $grace_period);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the model for the Controller.
|
||||||
|
*/
|
||||||
|
protected function get_model(): \models\PhoneReliabilityHistory
|
||||||
|
{
|
||||||
|
$this->model = $this->model ?? new \models\PhoneReliabilityHistory($this->bdd);
|
||||||
|
|
||||||
|
return $this->model;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,17 @@ namespace controllers\internals;
|
||||||
$this->internal_phone = new Phone($bdd);
|
$this->internal_phone = new Phone($bdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all active users.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_all_active()
|
||||||
|
{
|
||||||
|
return $this->model_user->get_all_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of users by their ids.
|
* Return a list of users by their ids.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1034,6 +1034,15 @@ namespace controllers\publics;
|
||||||
return $this->json($return);
|
return $this->json($return);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($phone['status'] === \models\Phone::STATUS_DISABLED)
|
||||||
|
{
|
||||||
|
$return['error'] = self::ERROR_CODES['CANNOT_UPDATE'];
|
||||||
|
$return['message'] = self::ERROR_MESSAGES['CANNOT_UPDATE'] . 'Phone have been manually disabled, you need to re-enable it manually.';
|
||||||
|
$this->auto_http_code(false);
|
||||||
|
|
||||||
|
return $this->json($return);
|
||||||
|
}
|
||||||
|
|
||||||
// If user have activated phone limits, check if RaspiSMS phone limit have already been reached
|
// If user have activated phone limits, check if RaspiSMS phone limit have already been reached
|
||||||
$limit_reached = false;
|
$limit_reached = false;
|
||||||
if ((int) ($this->user['settings']['phone_limit'] ?? false))
|
if ((int) ($this->user['settings']['phone_limit'] ?? false))
|
||||||
|
@ -1073,6 +1082,42 @@ namespace controllers\publics;
|
||||||
return $this->json($return);
|
return $this->json($return);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually disable/enable phones
|
||||||
|
* @param int id : id of phone we want to update status
|
||||||
|
* @param string $_POST['new_status'] : New status of the phone, either 'disabled' or 'available'
|
||||||
|
* @param $csrf : CSRF token
|
||||||
|
*/
|
||||||
|
public function post_change_phone_status ($id)
|
||||||
|
{
|
||||||
|
$new_status = $_POST['status'] ?? '';
|
||||||
|
|
||||||
|
if (!in_array($new_status, [\models\Phone::STATUS_AVAILABLE, \models\Phone::STATUS_DISABLED]))
|
||||||
|
{
|
||||||
|
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
|
||||||
|
$return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . ' "status" must be "disabled" or "available".';
|
||||||
|
$this->auto_http_code(false);
|
||||||
|
|
||||||
|
return $this->json($return);
|
||||||
|
}
|
||||||
|
|
||||||
|
$phone = $this->internal_phone->get_for_user($this->user['id'], $id);
|
||||||
|
if (!$phone)
|
||||||
|
{
|
||||||
|
$return['error'] = self::ERROR_CODES['CANNOT_UPDATE'];
|
||||||
|
$return['message'] = self::ERROR_MESSAGES['CANNOT_UPDATE'];
|
||||||
|
$this->auto_http_code(false);
|
||||||
|
|
||||||
|
return $this->json($return);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_update = $this->internal_phone->update_status($id, $new_status);
|
||||||
|
$return['response'] = $new_status;
|
||||||
|
$this->auto_http_code(true);
|
||||||
|
|
||||||
|
return $this->json($return);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return statistics about status of sended sms for a period by phone
|
* Return statistics about status of sended sms for a period by phone
|
||||||
|
|
|
@ -535,6 +535,16 @@ class Phone extends \descartes\Controller
|
||||||
foreach ($ids as $id)
|
foreach ($ids as $id)
|
||||||
{
|
{
|
||||||
$phone = $this->internal_phone->get_for_user($id_user, $id);
|
$phone = $this->internal_phone->get_for_user($id_user, $id);
|
||||||
|
if (!$phone)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($phone['status'] === \models\Phone::STATUS_DISABLED)
|
||||||
|
{
|
||||||
|
\FlashMessage\FlashMessage::push('error', 'Certains téléphones ont été désactivés manuellements, vous devez les réactiver manuellement.');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If user have activated phone limits, check if RaspiSMS phone limit have already been reached
|
// If user have activated phone limits, check if RaspiSMS phone limit have already been reached
|
||||||
$limit_reached = false;
|
$limit_reached = false;
|
||||||
|
@ -581,6 +591,48 @@ class Phone extends \descartes\Controller
|
||||||
return $this->redirect(\descartes\Router::url('Phone', 'list'));
|
return $this->redirect(\descartes\Router::url('Phone', 'list'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually disable/enable phones
|
||||||
|
* @param array int $_GET['ids'] : ids of phones we want to update status
|
||||||
|
* @param string $new_status : New status of the phone, either 'disabled' or 'available'
|
||||||
|
* @param $csrf : CSRF token
|
||||||
|
*/
|
||||||
|
public function change_status ($new_status, $csrf)
|
||||||
|
{
|
||||||
|
if (!$this->verify_csrf($csrf))
|
||||||
|
{
|
||||||
|
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
|
||||||
|
|
||||||
|
return $this->redirect(\descartes\Router::url('Phone', 'add'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($new_status, [\models\Phone::STATUS_AVAILABLE, \models\Phone::STATUS_DISABLED]))
|
||||||
|
{
|
||||||
|
\FlashMessage\FlashMessage::push('danger', 'Seul les status disponibles et désactivés peuvent être définis manuellement.');
|
||||||
|
|
||||||
|
return $this->redirect(\descartes\Router::url('Phone', 'add'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = $_GET['ids'] ?? [];
|
||||||
|
$id_user = $_SESSION['user']['id'];
|
||||||
|
|
||||||
|
foreach ($ids as $id)
|
||||||
|
{
|
||||||
|
$phone = $this->internal_phone->get_for_user($id_user, $id);
|
||||||
|
if (!$phone)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_update = $this->internal_phone->update_status($id, $new_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
\FlashMessage\FlashMessage::push('success', 'Les status des téléphones ont bien été mis à jour manuellement.');
|
||||||
|
|
||||||
|
return $this->redirect(\descartes\Router::url('Phone', 'list'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of phones as a JSON array
|
* Return a list of phones as a JSON array
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
class AddStatusDisabledPhone extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* addCustomColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Any other destructive changes will result in an error when trying to
|
||||||
|
* rollback the migration.
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('phone');
|
||||||
|
$table->changeColumn('status', 'enum', ['values' => ['available', 'unavailable', 'no_credit', 'limit_reached', 'disabled'], 'default' => 'available']);
|
||||||
|
$table->save();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
class AddWebhookPhoneReliability extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* addCustomColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Any other destructive changes will result in an error when trying to
|
||||||
|
* rollback the migration.
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('webhook');
|
||||||
|
$table->changeColumn('type', 'enum', ['values' => ['send_sms','send_sms_status_change','receive_sms','inbound_call', 'phone_reliability']]);
|
||||||
|
$table->save();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
class AddTablePhoneReliabilityHistory extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
// Create the phone_reliability_history table
|
||||||
|
// This table store history of reliability alert for phones, so we can use last alert as min date
|
||||||
|
// for surveillance periode, preventing triggering same alert in a loop
|
||||||
|
$this->table('phone_reliability_history')
|
||||||
|
->addColumn('id_user', 'integer', ['null' => false])
|
||||||
|
->addColumn('id_phone', 'integer', ['null' => false])
|
||||||
|
->addColumn('type', 'string', ['null' => false, 'limit' => 100])
|
||||||
|
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
|
||||||
|
->addColumn('updated_at', 'timestamp', ['null' => true, 'update' => 'CURRENT_TIMESTAMP'])
|
||||||
|
->addForeignKey('id_user', 'user', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
|
||||||
|
->addForeignKey('id_phone', 'phone', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
16
env.php.dist
16
env.php.dist
|
@ -91,6 +91,22 @@
|
||||||
'shorten_url' => 0,
|
'shorten_url' => 0,
|
||||||
'smsstop_respond' => 1,
|
'smsstop_respond' => 1,
|
||||||
'smsstop_response' => 'Demande prise en compte, vous ne recevrez plus de messages.',
|
'smsstop_response' => 'Demande prise en compte, vous ne recevrez plus de messages.',
|
||||||
|
'phone_reliability_failed' => 1,
|
||||||
|
'phone_reliability_failed_rate_limit' => 10,
|
||||||
|
'phone_reliability_failed_volume' => 25,
|
||||||
|
'phone_reliability_failed_period' => 120,
|
||||||
|
'phone_reliability_failed_grace_period' => 1,
|
||||||
|
'phone_reliability_failed_email' => 0,
|
||||||
|
'phone_reliability_failed_webhook' => 1,
|
||||||
|
'phone_reliability_failed_auto_disable' => 0,
|
||||||
|
'phone_reliability_unknown' => 0,
|
||||||
|
'phone_reliability_unknown_rate_limit' => 25,
|
||||||
|
'phone_reliability_unknown_volume' => 25,
|
||||||
|
'phone_reliability_unknown_period' => 120,
|
||||||
|
'phone_reliability_unknown_grace_period' => 1,
|
||||||
|
'phone_reliability_unknown_email' => 0,
|
||||||
|
'phone_reliability_unknown_webhook' => 1,
|
||||||
|
'phone_reliability_unknown_auto_disable' => 0,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,11 @@ namespace models;
|
||||||
class Phone extends StandardModel
|
class Phone extends StandardModel
|
||||||
{
|
{
|
||||||
|
|
||||||
const STATUS_AVAILABLE = 'available';
|
const STATUS_AVAILABLE = 'available'; # Everything OK
|
||||||
const STATUS_UNAVAILABLE = 'unavailable';
|
const STATUS_UNAVAILABLE = 'unavailable'; # RaspiSMS cannot communication with the phone
|
||||||
const STATUS_NO_CREDIT = 'no_credit';
|
const STATUS_DISABLED = 'disabled'; # Phone have been manually or automatically disabled by user/system
|
||||||
const STATUS_LIMIT_REACHED = 'limit_reached';
|
const STATUS_NO_CREDIT = 'no_credit'; # Phone have no more credit available
|
||||||
|
const STATUS_LIMIT_REACHED = 'limit_reached'; # We reached the limit in of SMS in RaspiSMS for this phone
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all phones that belongs to active users
|
* Return all phones that belongs to active users
|
||||||
|
@ -109,7 +110,6 @@ namespace models;
|
||||||
return $this->_delete('phone_limit', ['id_phone' => $id_phone]);
|
return $this->_delete('phone_limit', ['id_phone' => $id_phone]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return table name.
|
* Return table name.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of RaspiSMS.
|
||||||
|
*
|
||||||
|
* (c) Pierre-Lin Bonnemaison <plebwebsas@gmail.com>
|
||||||
|
*
|
||||||
|
* This source file is subject to the GPL-3.0 license that is bundled
|
||||||
|
* with this source code in the file LICENSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace models;
|
||||||
|
|
||||||
|
class PhoneReliabilityHistory extends StandardModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find all unreliable phones for a user, based on sended sms status, rate limit, etc.
|
||||||
|
*
|
||||||
|
* @param int $id_user : User id
|
||||||
|
* @param string $sms_status : Status of SMS to use to calculate rate
|
||||||
|
* @param float $rate_limit : Percentage of SMS matching status after which we consider the phone unreliable
|
||||||
|
* @param int $min_volume : Minimum number of sms we need to have to consider the statistic relevent
|
||||||
|
* @param int $period : The time span in minutes from which SMS counting should begin.
|
||||||
|
* @param int $grace_period : How long in minutes should we wait before including a SMS in counting
|
||||||
|
*
|
||||||
|
* @return array : A list of unreliable phone for the user, with phone id, total number of sms, and rate of failed sms
|
||||||
|
*/
|
||||||
|
public function find_unreliable_phones (int $id_user, string $sms_status, float $rate_limit, int $min_volume, int $period, int $grace_period)
|
||||||
|
{
|
||||||
|
return $this->_run_query("
|
||||||
|
WITH recent_messages AS (
|
||||||
|
SELECT
|
||||||
|
sended.id_phone AS id_phone,
|
||||||
|
COUNT(sended.id) AS total,
|
||||||
|
SUM(sended.status = :sms_status) AS unreliable
|
||||||
|
FROM
|
||||||
|
sended
|
||||||
|
JOIN
|
||||||
|
phone
|
||||||
|
ON
|
||||||
|
sended.id_phone = phone.id
|
||||||
|
LEFT JOIN
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
id_phone,
|
||||||
|
MAX(created_at) AS last_alert_time
|
||||||
|
FROM
|
||||||
|
phone_reliability_history
|
||||||
|
WHERE
|
||||||
|
type = :sms_status
|
||||||
|
GROUP BY
|
||||||
|
id_phone
|
||||||
|
) AS last_alerts
|
||||||
|
ON
|
||||||
|
sended.id_phone = last_alerts.id_phone
|
||||||
|
WHERE
|
||||||
|
sended.id_user = :id_user
|
||||||
|
AND
|
||||||
|
phone.status != 'disabled'
|
||||||
|
AND
|
||||||
|
sended.at > IFNULL(last_alerts.last_alert_time, '1970-01-01')
|
||||||
|
AND
|
||||||
|
sended.at BETWEEN NOW() - INTERVAL :period MINUTE AND NOW() - INTERVAL :grace_period MINUTE
|
||||||
|
GROUP BY
|
||||||
|
id_phone
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id_phone,
|
||||||
|
total,
|
||||||
|
unreliable,
|
||||||
|
(unreliable / total) AS rate
|
||||||
|
FROM
|
||||||
|
recent_messages
|
||||||
|
WHERE
|
||||||
|
total >= :min_volume
|
||||||
|
AND
|
||||||
|
(unreliable / total) > :rate_limit;
|
||||||
|
", [
|
||||||
|
'id_user' => $id_user,
|
||||||
|
'sms_status' => $sms_status,
|
||||||
|
'period' => $period,
|
||||||
|
'grace_period' => $grace_period,
|
||||||
|
'min_volume' => $min_volume,
|
||||||
|
'rate_limit' => $rate_limit,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return table name.
|
||||||
|
*/
|
||||||
|
protected function get_table_name(): string
|
||||||
|
{
|
||||||
|
return 'phone_reliability_history';
|
||||||
|
}
|
||||||
|
}
|
|
@ -338,6 +338,7 @@ namespace models;
|
||||||
return $this->_run_query($query, $params);
|
return $this->_run_query($query, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return table name.
|
* Return table name.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -31,6 +31,16 @@ namespace models;
|
||||||
return $this->_select_one('user', ['id' => $id]);
|
return $this->_select_one('user', ['id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all active users.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_all_active()
|
||||||
|
{
|
||||||
|
return $this->_select('user', ['status' => self::STATUS_ACTIVE]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find user by ids.
|
* Find user by ids.
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace models;
|
||||||
const TYPE_INBOUND_CALL = 'inbound_call';
|
const TYPE_INBOUND_CALL = 'inbound_call';
|
||||||
const TYPE_QUOTA_LEVEL_ALERT = 'quota_level';
|
const TYPE_QUOTA_LEVEL_ALERT = 'quota_level';
|
||||||
const TYPE_QUOTA_REACHED = 'quota_reached';
|
const TYPE_QUOTA_REACHED = 'quota_reached';
|
||||||
|
const TYPE_PHONE_RELIABILITY = 'phone_reliability';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all webhooks for a user and for a type of webhook.
|
* Find all webhooks for a user and for a type of webhook.
|
||||||
|
|
|
@ -165,6 +165,7 @@
|
||||||
'edit' => '/phone/edit/',
|
'edit' => '/phone/edit/',
|
||||||
'update' => '/phone/update/{csrf}/',
|
'update' => '/phone/update/{csrf}/',
|
||||||
'update_status' => '/phone/update_status/{csrf}/',
|
'update_status' => '/phone/update_status/{csrf}/',
|
||||||
|
'change_status' => '/phone/change_status/{new_status}/{csrf}/',
|
||||||
'json_list' => '/phones.json/',
|
'json_list' => '/phones.json/',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -229,6 +230,9 @@
|
||||||
'post_update_phone_status' => [
|
'post_update_phone_status' => [
|
||||||
'/api/phone/{id}/status/',
|
'/api/phone/{id}/status/',
|
||||||
],
|
],
|
||||||
|
'post_change_phone_status' => [
|
||||||
|
'/api/phone/{id}/status/force/',
|
||||||
|
],
|
||||||
'delete_phone' => [
|
'delete_phone' => [
|
||||||
'/api/phone/{id}/',
|
'/api/phone/{id}/',
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
Le téléphone <?php $this->s($phone['name']); ?> semble rencontrer un taux de SMS échoués anormalement élevé.
|
||||||
|
|
||||||
|
Période prise en compte : <?php $this->s($period); ?> dernières minutes
|
||||||
|
Total de SMS : <?php $this->s($total); ?>
|
||||||
|
Nombre d'échecs : <?php $this->s($unreliable); ?>
|
||||||
|
Taux d'échecs : <?php $this->s($rate); ?>%
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------------------
|
||||||
|
Pour plus d'informations sur le système RaspiSMS, rendez-vous sur le site https://raspisms.fr
|
|
@ -0,0 +1,10 @@
|
||||||
|
Le téléphone <?php $this->s($phone['name']); ?> semble rencontrer un taux de SMS incconnus anormalement élevé.
|
||||||
|
|
||||||
|
Période prise en compte : <?php $this->s($period); ?> dernières minutes
|
||||||
|
Total de SMS : <?php $this->s($total); ?>
|
||||||
|
Nombre d'inconnus : <?php $this->s($unreliable); ?>
|
||||||
|
Taux d'inconnus : <?php $this->s($rate); ?>%
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------------------
|
||||||
|
Pour plus d'informations sur le système RaspiSMS, rendez-vous sur le site https://raspisms.fr
|
|
@ -62,9 +62,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right col-xs-6 no-padding">
|
<div class="text-right col-xs-6 no-padding">
|
||||||
<strong>Action pour la séléction :</strong>
|
<strong>Action pour la séléction :</strong>
|
||||||
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'update_status', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-refresh"></span> Rafraichir le status</button>
|
<button class="btn btn-default mb-2" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'update_status', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-refresh"></span> Rafraichir le status</button>
|
||||||
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'edit'); ?>"><span class="fa fa-edit"></span> Modifier</button>
|
<button class="btn btn-default mb-2" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'change_status', ['csrf' => $_SESSION['csrf'], 'new_status' => 'available']); ?>"><span class="fa fa-toggle-on"></span> Activer</button>
|
||||||
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
|
<button class="btn btn-default mb-2" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'change_status', ['csrf' => $_SESSION['csrf'], 'new_status' => 'disabled']); ?>"><span class="fa fa-toggle-off"></span> Désactiver</button>
|
||||||
|
<button class="btn btn-default mb-2" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'edit'); ?>"><span class="fa fa-edit"></span> Modifier</button>
|
||||||
|
<button class="btn btn-default btn-confirm mb-2" type="submit" formaction="<?php echo \descartes\Router::url('Phone', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -105,6 +107,10 @@ jQuery(document).ready(function ()
|
||||||
html += ' - <span class="text-success">Disponible</span>'
|
html += ' - <span class="text-success">Disponible</span>'
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'disabled':
|
||||||
|
html += ' - <span class="text-warning">Désactivé</span>'
|
||||||
|
break;
|
||||||
|
|
||||||
case 'unavailable':
|
case 'unavailable':
|
||||||
html += ' - <span class="text-danger">Indisponible</span>'
|
html += ' - <span class="text-danger">Indisponible</span>'
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
<option value="send_sms" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'send_sms' ? 'selected' : '' ?>>Envoi d'un SMS</option>
|
<option value="send_sms" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'send_sms' ? 'selected' : '' ?>>Envoi d'un SMS</option>
|
||||||
<option value="send_sms_status_change" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'send_sms_status_change' ? 'selected' : '' ?>>Mise à jour du statut d'un SMS envoyé</option>
|
<option value="send_sms_status_change" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'send_sms_status_change' ? 'selected' : '' ?>>Mise à jour du statut d'un SMS envoyé</option>
|
||||||
<option value="inbound_call" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'inbound_call' ? 'selected' : '' ?>>Réception d'un appel téléphonique</option>
|
<option value="inbound_call" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'inbound_call' ? 'selected' : '' ?>>Réception d'un appel téléphonique</option>
|
||||||
|
<option value="phone_reliability" <?= ($_SESSION['previous_http_post']['type'] ?? '') == 'phone_reliability' ? 'selected' : '' ?>>Détection d'un problème de fiabilité sur un téléphone</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-danger" href="<?php echo \descartes\Router::url('Webhook', 'list'); ?>">Annuler</a>
|
<a class="btn btn-danger" href="<?php echo \descartes\Router::url('Webhook', 'list'); ?>">Annuler</a>
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
<option <?php echo $webhook['type'] == 'send_sms' ? 'selected="selected"' : '' ?> value="send_sms">Envoi d'un SMS</option>
|
<option <?php echo $webhook['type'] == 'send_sms' ? 'selected="selected"' : '' ?> value="send_sms">Envoi d'un SMS</option>
|
||||||
<option <?php echo $webhook['type'] == 'send_sms_status_change' ? 'selected="selected"' : '' ?> value="send_sms_status_change">Mise à jour du statut d'un SMS envoyé</option>
|
<option <?php echo $webhook['type'] == 'send_sms_status_change' ? 'selected="selected"' : '' ?> value="send_sms_status_change">Mise à jour du statut d'un SMS envoyé</option>
|
||||||
<option <?php echo $webhook['type'] == 'inbound_call' ? 'selected="selected"' : '' ?> value="inbound_call">Réception d'un appel téléphonique</option>
|
<option <?php echo $webhook['type'] == 'inbound_call' ? 'selected="selected"' : '' ?> value="inbound_call">Réception d'un appel téléphonique</option>
|
||||||
|
<option <?php echo $webhook['type'] == 'phone_reliability' ? 'selected="selected"' : '' ?> value="phone_reliability">Détection d'un problème de fiabilité sur un téléphone</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
|
@ -96,6 +96,8 @@ jQuery(document).ready(function ()
|
||||||
return 'Réception de SMS';
|
return 'Réception de SMS';
|
||||||
case 'inbound_call':
|
case 'inbound_call':
|
||||||
return 'Réception d\'un appel téléphonique';
|
return 'Réception d\'un appel téléphonique';
|
||||||
|
case 'phone_reliability':
|
||||||
|
return 'Détection d\'un problème de fiabilité sur un téléphone';
|
||||||
default:
|
default:
|
||||||
return 'Inconnu';
|
return 'Inconnu';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue