Compare commits

..

No commits in common. "master" and "v3.5.2" have entirely different histories.

113 changed files with 1481 additions and 9603 deletions

View File

@ -3,7 +3,3 @@ RewriteRule ^assets - [L]
RewriteRule ^.well-known - [L]
RewriteRule ^data/public/ - [L]
RewriteRule . index.php
<IfModule headers_module>
Header always set Content-Security-Policy "upgrade-insecure-requests;"
</ifModule>

View File

@ -1 +1 @@
v3.9.1
v3.5.2

View File

@ -79,11 +79,6 @@ interface AdapterInterface
*/
public static function meta_support_read(): bool;
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool;
/**
* Does the implemented service support reception callback.
*/
@ -124,9 +119,9 @@ interface AdapterInterface
* @param array $medias : Array of medias to link to the MMS, [['http_url' => HTTP public url of the media et 'local_uri' => local uri to media file]]
*
* @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,
* 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;
@ -157,15 +152,6 @@ interface AdapterInterface
*/
public function test(): bool;
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit', 'limit_reached'
*/
public function check_phone_status(): string;
/**
* Method called on reception of a status update notification for a SMS.
*
@ -207,7 +193,7 @@ interface AdapterInterface
* ]
* ]
*/
public static function inbound_call_callback(): array;
public function inbound_call_callback(): array;
/**
* Method called on reception of a end call notification.
@ -221,5 +207,5 @@ interface AdapterInterface
* ]
* ]
*/
public static function end_call_callback(): array;
public function end_call_callback(): array;
}

View File

@ -111,14 +111,6 @@ namespace adapters;
return false;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -234,16 +226,6 @@ namespace adapters;
return [];
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public static function status_change_callback()
{
return null;
@ -259,12 +241,12 @@ namespace adapters;
return true;
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

@ -121,14 +121,6 @@ namespace adapters;
return true;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -196,14 +188,13 @@ namespace adapters;
}
$command_parts = [
'LC_ALL=C',
'gammu',
'--config',
escapeshellarg($this->data['config_file']),
'sendsms',
'TEXT',
escapeshellarg($destination),
'-textutf8',
'-text',
escapeshellarg($text),
'-validity',
'MAX',
@ -280,7 +271,6 @@ namespace adapters;
$command_parts = [
PWD . '/bin/gammu_get_unread_sms.py',
escapeshellarg($this->data['config_file']),
'--delete'
];
$return = $this->exec_command($command_parts);
@ -310,16 +300,6 @@ namespace adapters;
return $response;
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
//Always return true as we cannot test because we would be needing a root account
@ -336,12 +316,12 @@ namespace adapters;
return [];
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}
@ -357,11 +337,8 @@ namespace adapters;
{
return true;
}
// The command returns 123 on failed execution (even if SIM is already unlocked), and returns 0 if unlock was successful
// We can directly return true if command was succesful
$command_parts = [
'LC_ALL=C',
'gammu',
'--config',
escapeshellarg($this->data['config_file']),
@ -371,15 +348,9 @@ namespace adapters;
];
$result = $this->exec_command($command_parts);
if (0 === $result['return'])
{
return true;
}
//Check security status
// The command returns 0 regardless of the SIM security state
$command_parts = [
'LC_ALL=C',
'gammu',
'--config',
escapeshellarg($this->data['config_file']),

View File

@ -209,14 +209,6 @@ class KannelAdapter implements AdapterInterface
return false;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -362,16 +354,6 @@ class KannelAdapter implements AdapterInterface
return [];
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
@ -508,12 +490,12 @@ class KannelAdapter implements AdapterInterface
return $response;
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

@ -174,14 +174,6 @@ class OctopushShortcodeAdapter implements AdapterInterface
return false;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -333,16 +325,6 @@ class OctopushShortcodeAdapter implements AdapterInterface
return [];
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
@ -486,12 +468,12 @@ class OctopushShortcodeAdapter implements AdapterInterface
return $response;
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

@ -173,14 +173,6 @@ class OctopushVirtualNumberAdapter implements AdapterInterface
return false;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -325,16 +317,6 @@ class OctopushVirtualNumberAdapter implements AdapterInterface
return [];
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
@ -479,12 +461,12 @@ class OctopushVirtualNumberAdapter implements AdapterInterface
return $response;
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

@ -1,511 +0,0 @@
<?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 adapters;
use DateTime;
/**
* Odyssey Messaging SMS service
*/
class OdysseyMessagingAdapter implements AdapterInterface
{
const EVENT_TYPES = [
'OPT_OUT' => 1,
'SYSTEM_ERROR' => 2,
'END_OF_ITEM' => 3,
'END_OF_JOB' => 4,
'JOB_STATUS_CHANGED' => 5,
'REAL_TIME_STATUS' => 6,
'RETRIEVE_FILE' => 7,
'INBOUND_SMS' => 8,
'ITEM_STATUS_CHANGED' => 9,
'DATA_COLLECTION_FILLED' => 10,
];
/**
* Data used to configure interaction with the implemented service. (e.g : Api credentials, ports numbers, etc.).
*/
private $data;
/**
* Odyssey login.
*/
private $login;
/**
* Odyssey password.
*/
private $password;
/**
* Sender name to use instead of shortcode.
*/
private $sender;
/**
* Odyssey api baseurl.
*/
private $api_url = 'https://api.odyssey-services.fr/api/v1';
/**
* 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)
{
$this->data = json_decode($data, true);
$this->login = $this->data['login'];
$this->password = $this->data['password'];
$this->sender = $this->data['sender'] ?? null;
}
/**
* Classname of the adapter.
*/
public static function meta_classname(): string
{
return __CLASS__;
}
/**
* Uniq name of the adapter
* It should be the classname of the adapter un snakecase.
*/
public static function meta_uid(): string
{
return 'odyssey_messaging_adapter';
}
/**
* Should this adapter be hidden in user interface for phone creation and
* available to creation through API only.
*/
public static function meta_hidden(): bool
{
return false;
}
/**
* Should this adapter data be hidden after creation
* this help to prevent API credentials to other service leak if an attacker gain access to RaspiSMS through user credentials.
*/
public static function meta_hide_data(): bool
{
return false;
}
/**
* 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
{
return 'Odyssey Messaging';
}
/**
* Description of the adapter.
* A short description of the service the adapter implements.
*/
public static function meta_description(): string
{
return '
Envoi de SMS avec <a target="_blank" href="https://www.odyssey-messaging.com/">Odyssey Messaging</a>.
Pour plus d\'information sur l\'utilisation de ce type de téléphone, reportez-vous à <a href="https://documentation.raspisms.fr/users/adapters/odyssey_messaging.html" target="_blank">la documentation sur le téléphone "Odyssey Messaging".</a>
';
}
/**
* List of entries we want in data for the adapter.
*
* @return array : Every line is a field as an array with keys : name, title, description, required
*/
public static function meta_data_fields(): array
{
return [
[
'name' => 'login',
'title' => 'Odyssey login',
'description' => 'Login du compte Odyssey à employer.',
'required' => true,
],
[
'name' => 'password',
'title' => 'Mot de passe',
'description' => 'Mot de passe du compte Odyssey à employer.',
'required' => true,
],
[
'name' => 'sender',
'title' => 'Nom de l\'expéditeur',
'description' => 'Nom de l\'expéditeur à afficher à la place du numéro (11 caractères max).<br/>
<b>Laissez vide pour ne pas utiliser d\'expéditeur nommé.</b><br/>
<b>Si vous utilisez un expéditeur nommé, le destinataire ne pourra pas répondre.</b>',
'required' => false,
],
];
}
/**
* Does the implemented service support reading smss.
*/
public static function meta_support_read(): bool
{
return false;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
public static function meta_support_flash(): bool
{
return false;
}
/**
* Does the implemented service support status change.
*/
public static function meta_support_status_change(): bool
{
return true;
}
/**
* Does the implemented service support reception callback.
*/
public static function meta_support_reception(): bool
{
return true;
}
/**
* Does the implemented service support mms reception.
*/
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 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
{
$response = [
'error' => false,
'error_message' => null,
'uid' => null,
];
try
{
$credentials = base64_encode($this->login . ':' . $this->password);
$headers = [
'Authorization: Basic ' . $credentials,
'Content-Type: application/json',
];
$data = [
'JobType' => 'SMS',
'Text' => $text,
'TrackingID' => uniqid(),
'AdhocRecipients' => [['Name' => uniqid(), 'Address' => str_replace('+', '00', $destination)]],
];
if ($this->sender)
{
$data['Parameter'] = ['Sender' => $this->sender, 'Media' => 1];
}
$data = json_encode($data);
$endpoint = $this->api_url . '/SMSJobs';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $endpoint);
curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$curl_response = curl_exec($curl);
$http_code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if (false === $curl_response)
{
$response['error'] = true;
$response['error_message'] = 'HTTP query failed.';
return $response;
}
$response_decode = json_decode($curl_response, true);
if (null === $response_decode)
{
$response['error'] = true;
$response['error_message'] = 'Invalid JSON for response.';
return $response;
}
if (200 !== $http_code)
{
$response['error'] = true;
$response['error_message'] = 'Response indicate error : ' . $response_decode['Message'] . ' -> """' . json_encode($response_decode['ModelState']) . '""" AND HTTP CODE -> ' . $http_code;
return $response;
}
$uid = $response_decode['JobNumber'] ?? false;
if (!$uid)
{
$response['error'] = true;
$response['error_message'] = 'Cannot extract SMS uid';
return $response;
}
$response['uid'] = $uid;
return $response;
}
catch (\Throwable $t)
{
$response['error'] = true;
$response['error_message'] = $t->getMessage();
return $response;
}
}
public function read(): array
{
return [];
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
{
if ($this->data['sender'] && (mb_strlen($this->data['sender']) < 3 || mb_strlen($this->data['sender'] > 11)))
{
return false;
}
if (!empty($this->data['sms_type']) && !in_array($this->data['sms_type'], ['premium', 'low cost']))
{
return false;
}
$credentials = base64_encode($this->login . ':' . $this->password);
$headers = [
'Authorization: Basic ' . $credentials,
'Content-Type: application/json',
];
//Check service name
$endpoint = $this->api_url . '/JobTypes';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $endpoint);
curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($curl);
$http_code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if (200 !== $http_code)
{
return false;
}
return true;
}
catch (\Throwable $t)
{
return false;
}
}
public static function status_change_callback()
{
header('Connection: close');
header('Content-Encoding: none');
header('Content-Length: 0');
$input = file_get_contents('php://input');
$content = json_decode($input, true);
if (null === $content)
{
return false;
}
$event_type = $content['EventType'] ?? false;
if ($event_type != self::EVENT_TYPES['ITEM_STATUS_CHANGED'])
{
return false;
}
$uid = $content['JobNumber'] ?? false;
$status = $content['Outcome'] ?? false;
if (false === $uid || false === $status)
{
return false;
}
switch ($status)
{
case 'S':
$status = \models\Sended::STATUS_DELIVERED;
break;
case 'B':
$status = \models\Sended::STATUS_UNKNOWN;
break;
default:
$status = \models\Sended::STATUS_FAILED;
break;
}
return ['uid' => $uid, 'status' => $status];
}
public static function reception_callback(): array
{
$response = [
'error' => false,
'error_message' => null,
'sms' => null,
];
header('Connection: close');
header('Content-Encoding: none');
header('Content-Length: 0');
$input = file_get_contents('php://input');
$content = json_decode($input, true);
if (null === $content)
{
$response['error'] = true;
$response['error_message'] = 'Cannot read input data from callback request.';
return $response;
}
$event_type = $content['EventType'] ?? false;
if ($event_type != self::EVENT_TYPES['INBOUND_SMS'])
{
$response['error'] = true;
$response['error_message'] = 'Invalid event type : ' . $event_type . '.';
return $response;
}
$number = $content['From'] ?? false;
$text = $content['Message'] ?? false;
$at = $content['EventDateTime'] ?? false;
if (!$number || !$text || !$at)
{
$response['error'] = true;
$response['error_message'] = 'One required data of the callback is missing.';
return $response;
}
$matches = null;
$match = preg_match('#/Date\(([0-9]+)\+([0-9]+)\)/#', $at, $matches);
$timestamp = ($matches[1] ?? null);
if (!$match || !$timestamp)
{
$response['error'] = true;
$response['error_message'] = 'Invalid date.';
return $response;
}
$at = DateTime::createFromFormat('U', $timestamp / 1000);
$at = $at->format('Y-m-d H:i:s');
$origin = \controllers\internals\Tool::parse_phone($number);
if (!$origin)
{
$response['error'] = true;
$response['error_message'] = 'Invalid origin number : ' . $number;
return $response;
}
$response['sms'] = [
'at' => $at,
'text' => $text,
'origin' => $origin,
];
return $response;
}
public static function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
{
return [];
}
}

View File

@ -162,14 +162,6 @@ namespace adapters;
return true;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -335,16 +327,6 @@ namespace adapters;
}
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
@ -406,12 +388,12 @@ namespace adapters;
return [];
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

@ -166,14 +166,6 @@ namespace adapters;
return true;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -325,16 +317,6 @@ namespace adapters;
}
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
@ -396,12 +378,12 @@ namespace adapters;
return [];
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

@ -116,14 +116,6 @@ namespace adapters;
return true;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -295,16 +287,6 @@ namespace adapters;
}
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
return true;
@ -351,7 +333,7 @@ namespace adapters;
return [];
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
$response = [
'error' => false,
@ -380,7 +362,7 @@ namespace adapters;
return $response;
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
$response = [
'error' => false,

View File

@ -160,14 +160,6 @@ class TwilioVirtualNumberAdapter implements AdapterInterface
return true;
}
/**
* Does the implemented service support updating phone status.
*/
public static function meta_support_phone_status(): bool
{
return false;
}
/**
* Does the implemented service support flash smss.
*/
@ -303,16 +295,6 @@ class TwilioVirtualNumberAdapter implements AdapterInterface
}
}
/**
* Method called to verify phone status
*
* @return string : Return one phone status among 'available', 'unavailable', 'no_credit'
*/
public function check_phone_status(): string
{
return \models\Phone::STATUS_AVAILABLE;
}
public function test(): bool
{
try
@ -374,12 +356,12 @@ class TwilioVirtualNumberAdapter implements AdapterInterface
return [];
}
public static function inbound_call_callback(): array
public function inbound_call_callback(): array
{
return [];
}
public static function end_call_callback(): array
public function end_call_callback(): array
{
return [];
}

View File

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

View File

@ -1,13 +1,13 @@
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
src: url('../fonts/fontawesome-webfont.eot?v=4.2.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
@ -64,19 +64,6 @@
border: solid 0.08em #eeeeee;
border-radius: .1em;
}
.fa-pull-left {
float: left;
}
.fa-pull-right {
float: right;
}
.fa.fa-pull-left {
margin-right: .3em;
}
.fa.fa-pull-right {
margin-left: .3em;
}
/* Deprecated as of 4.4.0 */
.pull-right {
float: right;
}
@ -93,10 +80,6 @@
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
@ -118,31 +101,31 @@
}
}
.fa-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.fa-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.fa-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.fa-flip-horizontal {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
@ -627,7 +610,6 @@
.fa-twitter:before {
content: "\f099";
}
.fa-facebook-f:before,
.fa-facebook:before {
content: "\f09a";
}
@ -640,7 +622,6 @@
.fa-credit-card:before {
content: "\f09d";
}
.fa-feed:before,
.fa-rss:before {
content: "\f09e";
}
@ -1278,8 +1259,7 @@
.fa-male:before {
content: "\f183";
}
.fa-gittip:before,
.fa-gratipay:before {
.fa-gittip:before {
content: "\f184";
}
.fa-sun-o:before {
@ -1383,7 +1363,7 @@
.fa-digg:before {
content: "\f1a6";
}
.fa-pied-piper-pp:before {
.fa-pied-piper:before {
content: "\f1a7";
}
.fa-pied-piper-alt:before {
@ -1509,7 +1489,6 @@
content: "\f1ce";
}
.fa-ra:before,
.fa-resistance:before,
.fa-rebel:before {
content: "\f1d0";
}
@ -1523,8 +1502,6 @@
.fa-git:before {
content: "\f1d3";
}
.fa-y-combinator-square:before,
.fa-yc-square:before,
.fa-hacker-news:before {
content: "\f1d4";
}
@ -1693,645 +1670,3 @@
.fa-meanpath:before {
content: "\f20c";
}
.fa-buysellads:before {
content: "\f20d";
}
.fa-connectdevelop:before {
content: "\f20e";
}
.fa-dashcube:before {
content: "\f210";
}
.fa-forumbee:before {
content: "\f211";
}
.fa-leanpub:before {
content: "\f212";
}
.fa-sellsy:before {
content: "\f213";
}
.fa-shirtsinbulk:before {
content: "\f214";
}
.fa-simplybuilt:before {
content: "\f215";
}
.fa-skyatlas:before {
content: "\f216";
}
.fa-cart-plus:before {
content: "\f217";
}
.fa-cart-arrow-down:before {
content: "\f218";
}
.fa-diamond:before {
content: "\f219";
}
.fa-ship:before {
content: "\f21a";
}
.fa-user-secret:before {
content: "\f21b";
}
.fa-motorcycle:before {
content: "\f21c";
}
.fa-street-view:before {
content: "\f21d";
}
.fa-heartbeat:before {
content: "\f21e";
}
.fa-venus:before {
content: "\f221";
}
.fa-mars:before {
content: "\f222";
}
.fa-mercury:before {
content: "\f223";
}
.fa-intersex:before,
.fa-transgender:before {
content: "\f224";
}
.fa-transgender-alt:before {
content: "\f225";
}
.fa-venus-double:before {
content: "\f226";
}
.fa-mars-double:before {
content: "\f227";
}
.fa-venus-mars:before {
content: "\f228";
}
.fa-mars-stroke:before {
content: "\f229";
}
.fa-mars-stroke-v:before {
content: "\f22a";
}
.fa-mars-stroke-h:before {
content: "\f22b";
}
.fa-neuter:before {
content: "\f22c";
}
.fa-genderless:before {
content: "\f22d";
}
.fa-facebook-official:before {
content: "\f230";
}
.fa-pinterest-p:before {
content: "\f231";
}
.fa-whatsapp:before {
content: "\f232";
}
.fa-server:before {
content: "\f233";
}
.fa-user-plus:before {
content: "\f234";
}
.fa-user-times:before {
content: "\f235";
}
.fa-hotel:before,
.fa-bed:before {
content: "\f236";
}
.fa-viacoin:before {
content: "\f237";
}
.fa-train:before {
content: "\f238";
}
.fa-subway:before {
content: "\f239";
}
.fa-medium:before {
content: "\f23a";
}
.fa-yc:before,
.fa-y-combinator:before {
content: "\f23b";
}
.fa-optin-monster:before {
content: "\f23c";
}
.fa-opencart:before {
content: "\f23d";
}
.fa-expeditedssl:before {
content: "\f23e";
}
.fa-battery-4:before,
.fa-battery:before,
.fa-battery-full:before {
content: "\f240";
}
.fa-battery-3:before,
.fa-battery-three-quarters:before {
content: "\f241";
}
.fa-battery-2:before,
.fa-battery-half:before {
content: "\f242";
}
.fa-battery-1:before,
.fa-battery-quarter:before {
content: "\f243";
}
.fa-battery-0:before,
.fa-battery-empty:before {
content: "\f244";
}
.fa-mouse-pointer:before {
content: "\f245";
}
.fa-i-cursor:before {
content: "\f246";
}
.fa-object-group:before {
content: "\f247";
}
.fa-object-ungroup:before {
content: "\f248";
}
.fa-sticky-note:before {
content: "\f249";
}
.fa-sticky-note-o:before {
content: "\f24a";
}
.fa-cc-jcb:before {
content: "\f24b";
}
.fa-cc-diners-club:before {
content: "\f24c";
}
.fa-clone:before {
content: "\f24d";
}
.fa-balance-scale:before {
content: "\f24e";
}
.fa-hourglass-o:before {
content: "\f250";
}
.fa-hourglass-1:before,
.fa-hourglass-start:before {
content: "\f251";
}
.fa-hourglass-2:before,
.fa-hourglass-half:before {
content: "\f252";
}
.fa-hourglass-3:before,
.fa-hourglass-end:before {
content: "\f253";
}
.fa-hourglass:before {
content: "\f254";
}
.fa-hand-grab-o:before,
.fa-hand-rock-o:before {
content: "\f255";
}
.fa-hand-stop-o:before,
.fa-hand-paper-o:before {
content: "\f256";
}
.fa-hand-scissors-o:before {
content: "\f257";
}
.fa-hand-lizard-o:before {
content: "\f258";
}
.fa-hand-spock-o:before {
content: "\f259";
}
.fa-hand-pointer-o:before {
content: "\f25a";
}
.fa-hand-peace-o:before {
content: "\f25b";
}
.fa-trademark:before {
content: "\f25c";
}
.fa-registered:before {
content: "\f25d";
}
.fa-creative-commons:before {
content: "\f25e";
}
.fa-gg:before {
content: "\f260";
}
.fa-gg-circle:before {
content: "\f261";
}
.fa-tripadvisor:before {
content: "\f262";
}
.fa-odnoklassniki:before {
content: "\f263";
}
.fa-odnoklassniki-square:before {
content: "\f264";
}
.fa-get-pocket:before {
content: "\f265";
}
.fa-wikipedia-w:before {
content: "\f266";
}
.fa-safari:before {
content: "\f267";
}
.fa-chrome:before {
content: "\f268";
}
.fa-firefox:before {
content: "\f269";
}
.fa-opera:before {
content: "\f26a";
}
.fa-internet-explorer:before {
content: "\f26b";
}
.fa-tv:before,
.fa-television:before {
content: "\f26c";
}
.fa-contao:before {
content: "\f26d";
}
.fa-500px:before {
content: "\f26e";
}
.fa-amazon:before {
content: "\f270";
}
.fa-calendar-plus-o:before {
content: "\f271";
}
.fa-calendar-minus-o:before {
content: "\f272";
}
.fa-calendar-times-o:before {
content: "\f273";
}
.fa-calendar-check-o:before {
content: "\f274";
}
.fa-industry:before {
content: "\f275";
}
.fa-map-pin:before {
content: "\f276";
}
.fa-map-signs:before {
content: "\f277";
}
.fa-map-o:before {
content: "\f278";
}
.fa-map:before {
content: "\f279";
}
.fa-commenting:before {
content: "\f27a";
}
.fa-commenting-o:before {
content: "\f27b";
}
.fa-houzz:before {
content: "\f27c";
}
.fa-vimeo:before {
content: "\f27d";
}
.fa-black-tie:before {
content: "\f27e";
}
.fa-fonticons:before {
content: "\f280";
}
.fa-reddit-alien:before {
content: "\f281";
}
.fa-edge:before {
content: "\f282";
}
.fa-credit-card-alt:before {
content: "\f283";
}
.fa-codiepie:before {
content: "\f284";
}
.fa-modx:before {
content: "\f285";
}
.fa-fort-awesome:before {
content: "\f286";
}
.fa-usb:before {
content: "\f287";
}
.fa-product-hunt:before {
content: "\f288";
}
.fa-mixcloud:before {
content: "\f289";
}
.fa-scribd:before {
content: "\f28a";
}
.fa-pause-circle:before {
content: "\f28b";
}
.fa-pause-circle-o:before {
content: "\f28c";
}
.fa-stop-circle:before {
content: "\f28d";
}
.fa-stop-circle-o:before {
content: "\f28e";
}
.fa-shopping-bag:before {
content: "\f290";
}
.fa-shopping-basket:before {
content: "\f291";
}
.fa-hashtag:before {
content: "\f292";
}
.fa-bluetooth:before {
content: "\f293";
}
.fa-bluetooth-b:before {
content: "\f294";
}
.fa-percent:before {
content: "\f295";
}
.fa-gitlab:before {
content: "\f296";
}
.fa-wpbeginner:before {
content: "\f297";
}
.fa-wpforms:before {
content: "\f298";
}
.fa-envira:before {
content: "\f299";
}
.fa-universal-access:before {
content: "\f29a";
}
.fa-wheelchair-alt:before {
content: "\f29b";
}
.fa-question-circle-o:before {
content: "\f29c";
}
.fa-blind:before {
content: "\f29d";
}
.fa-audio-description:before {
content: "\f29e";
}
.fa-volume-control-phone:before {
content: "\f2a0";
}
.fa-braille:before {
content: "\f2a1";
}
.fa-assistive-listening-systems:before {
content: "\f2a2";
}
.fa-asl-interpreting:before,
.fa-american-sign-language-interpreting:before {
content: "\f2a3";
}
.fa-deafness:before,
.fa-hard-of-hearing:before,
.fa-deaf:before {
content: "\f2a4";
}
.fa-glide:before {
content: "\f2a5";
}
.fa-glide-g:before {
content: "\f2a6";
}
.fa-signing:before,
.fa-sign-language:before {
content: "\f2a7";
}
.fa-low-vision:before {
content: "\f2a8";
}
.fa-viadeo:before {
content: "\f2a9";
}
.fa-viadeo-square:before {
content: "\f2aa";
}
.fa-snapchat:before {
content: "\f2ab";
}
.fa-snapchat-ghost:before {
content: "\f2ac";
}
.fa-snapchat-square:before {
content: "\f2ad";
}
.fa-pied-piper:before {
content: "\f2ae";
}
.fa-first-order:before {
content: "\f2b0";
}
.fa-yoast:before {
content: "\f2b1";
}
.fa-themeisle:before {
content: "\f2b2";
}
.fa-google-plus-circle:before,
.fa-google-plus-official:before {
content: "\f2b3";
}
.fa-fa:before,
.fa-font-awesome:before {
content: "\f2b4";
}
.fa-handshake-o:before {
content: "\f2b5";
}
.fa-envelope-open:before {
content: "\f2b6";
}
.fa-envelope-open-o:before {
content: "\f2b7";
}
.fa-linode:before {
content: "\f2b8";
}
.fa-address-book:before {
content: "\f2b9";
}
.fa-address-book-o:before {
content: "\f2ba";
}
.fa-vcard:before,
.fa-address-card:before {
content: "\f2bb";
}
.fa-vcard-o:before,
.fa-address-card-o:before {
content: "\f2bc";
}
.fa-user-circle:before {
content: "\f2bd";
}
.fa-user-circle-o:before {
content: "\f2be";
}
.fa-user-o:before {
content: "\f2c0";
}
.fa-id-badge:before {
content: "\f2c1";
}
.fa-drivers-license:before,
.fa-id-card:before {
content: "\f2c2";
}
.fa-drivers-license-o:before,
.fa-id-card-o:before {
content: "\f2c3";
}
.fa-quora:before {
content: "\f2c4";
}
.fa-free-code-camp:before {
content: "\f2c5";
}
.fa-telegram:before {
content: "\f2c6";
}
.fa-thermometer-4:before,
.fa-thermometer:before,
.fa-thermometer-full:before {
content: "\f2c7";
}
.fa-thermometer-3:before,
.fa-thermometer-three-quarters:before {
content: "\f2c8";
}
.fa-thermometer-2:before,
.fa-thermometer-half:before {
content: "\f2c9";
}
.fa-thermometer-1:before,
.fa-thermometer-quarter:before {
content: "\f2ca";
}
.fa-thermometer-0:before,
.fa-thermometer-empty:before {
content: "\f2cb";
}
.fa-shower:before {
content: "\f2cc";
}
.fa-bathtub:before,
.fa-s15:before,
.fa-bath:before {
content: "\f2cd";
}
.fa-podcast:before {
content: "\f2ce";
}
.fa-window-maximize:before {
content: "\f2d0";
}
.fa-window-minimize:before {
content: "\f2d1";
}
.fa-window-restore:before {
content: "\f2d2";
}
.fa-times-rectangle:before,
.fa-window-close:before {
content: "\f2d3";
}
.fa-times-rectangle-o:before,
.fa-window-close-o:before {
content: "\f2d4";
}
.fa-bandcamp:before {
content: "\f2d5";
}
.fa-grav:before {
content: "\f2d6";
}
.fa-etsy:before {
content: "\f2d7";
}
.fa-imdb:before {
content: "\f2d8";
}
.fa-ravelry:before {
content: "\f2d9";
}
.fa-eercast:before {
content: "\f2da";
}
.fa-microchip:before {
content: "\f2db";
}
.fa-snowflake-o:before {
content: "\f2dc";
}
.fa-superpowers:before {
content: "\f2dd";
}
.fa-wpexplorer:before {
content: "\f2de";
}
.fa-meetup:before {
content: "\f2e0";
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.sr-only-focusable:active,
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 280 KiB

View File

@ -7,15 +7,6 @@
border-radius: .1em;
}
.@{fa-css-prefix}-pull-left { float: left; }
.@{fa-css-prefix}-pull-right { float: right; }
.@{fa-css-prefix} {
&.@{fa-css-prefix}-pull-left { margin-right: .3em; }
&.@{fa-css-prefix}-pull-right { margin-left: .3em; }
}
/* Deprecated as of 4.4.0 */
.pull-right { float: right; }
.pull-left { float: left; }

View File

@ -3,10 +3,9 @@
.@{fa-css-prefix} {
display: inline-block;
font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
font: normal normal normal 14px/1 FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@ -1,5 +1,5 @@
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
@ -11,8 +11,7 @@
@import "fixed-width.less";
@import "list.less";
@import "bordered-pulled.less";
@import "animated.less";
@import "spinning.less";
@import "rotated-flipped.less";
@import "stacked.less";
@import "icons.less";
@import "screen-reader.less";

View File

@ -158,12 +158,10 @@
.@{fa-css-prefix}-bookmark-o:before { content: @fa-var-bookmark-o; }
.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; }
.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; }
.@{fa-css-prefix}-facebook-f:before,
.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; }
.@{fa-css-prefix}-github:before { content: @fa-var-github; }
.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; }
.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; }
.@{fa-css-prefix}-feed:before,
.@{fa-css-prefix}-rss:before { content: @fa-var-rss; }
.@{fa-css-prefix}-hdd-o:before { content: @fa-var-hdd-o; }
.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; }
@ -399,8 +397,7 @@
.@{fa-css-prefix}-trello:before { content: @fa-var-trello; }
.@{fa-css-prefix}-female:before { content: @fa-var-female; }
.@{fa-css-prefix}-male:before { content: @fa-var-male; }
.@{fa-css-prefix}-gittip:before,
.@{fa-css-prefix}-gratipay:before { content: @fa-var-gratipay; }
.@{fa-css-prefix}-gittip:before { content: @fa-var-gittip; }
.@{fa-css-prefix}-sun-o:before { content: @fa-var-sun-o; }
.@{fa-css-prefix}-moon-o:before { content: @fa-var-moon-o; }
.@{fa-css-prefix}-archive:before { content: @fa-var-archive; }
@ -438,7 +435,7 @@
.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; }
.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; }
.@{fa-css-prefix}-digg:before { content: @fa-var-digg; }
.@{fa-css-prefix}-pied-piper-pp:before { content: @fa-var-pied-piper-pp; }
.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; }
.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; }
.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; }
.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; }
@ -488,14 +485,11 @@
.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; }
.@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; }
.@{fa-css-prefix}-ra:before,
.@{fa-css-prefix}-resistance:before,
.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; }
.@{fa-css-prefix}-ge:before,
.@{fa-css-prefix}-empire:before { content: @fa-var-empire; }
.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; }
.@{fa-css-prefix}-git:before { content: @fa-var-git; }
.@{fa-css-prefix}-y-combinator-square:before,
.@{fa-css-prefix}-yc-square:before,
.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; }
.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; }
.@{fa-css-prefix}-qq:before { content: @fa-var-qq; }
@ -556,234 +550,3 @@
.@{fa-css-prefix}-sheqel:before,
.@{fa-css-prefix}-ils:before { content: @fa-var-ils; }
.@{fa-css-prefix}-meanpath:before { content: @fa-var-meanpath; }
.@{fa-css-prefix}-buysellads:before { content: @fa-var-buysellads; }
.@{fa-css-prefix}-connectdevelop:before { content: @fa-var-connectdevelop; }
.@{fa-css-prefix}-dashcube:before { content: @fa-var-dashcube; }
.@{fa-css-prefix}-forumbee:before { content: @fa-var-forumbee; }
.@{fa-css-prefix}-leanpub:before { content: @fa-var-leanpub; }
.@{fa-css-prefix}-sellsy:before { content: @fa-var-sellsy; }
.@{fa-css-prefix}-shirtsinbulk:before { content: @fa-var-shirtsinbulk; }
.@{fa-css-prefix}-simplybuilt:before { content: @fa-var-simplybuilt; }
.@{fa-css-prefix}-skyatlas:before { content: @fa-var-skyatlas; }
.@{fa-css-prefix}-cart-plus:before { content: @fa-var-cart-plus; }
.@{fa-css-prefix}-cart-arrow-down:before { content: @fa-var-cart-arrow-down; }
.@{fa-css-prefix}-diamond:before { content: @fa-var-diamond; }
.@{fa-css-prefix}-ship:before { content: @fa-var-ship; }
.@{fa-css-prefix}-user-secret:before { content: @fa-var-user-secret; }
.@{fa-css-prefix}-motorcycle:before { content: @fa-var-motorcycle; }
.@{fa-css-prefix}-street-view:before { content: @fa-var-street-view; }
.@{fa-css-prefix}-heartbeat:before { content: @fa-var-heartbeat; }
.@{fa-css-prefix}-venus:before { content: @fa-var-venus; }
.@{fa-css-prefix}-mars:before { content: @fa-var-mars; }
.@{fa-css-prefix}-mercury:before { content: @fa-var-mercury; }
.@{fa-css-prefix}-intersex:before,
.@{fa-css-prefix}-transgender:before { content: @fa-var-transgender; }
.@{fa-css-prefix}-transgender-alt:before { content: @fa-var-transgender-alt; }
.@{fa-css-prefix}-venus-double:before { content: @fa-var-venus-double; }
.@{fa-css-prefix}-mars-double:before { content: @fa-var-mars-double; }
.@{fa-css-prefix}-venus-mars:before { content: @fa-var-venus-mars; }
.@{fa-css-prefix}-mars-stroke:before { content: @fa-var-mars-stroke; }
.@{fa-css-prefix}-mars-stroke-v:before { content: @fa-var-mars-stroke-v; }
.@{fa-css-prefix}-mars-stroke-h:before { content: @fa-var-mars-stroke-h; }
.@{fa-css-prefix}-neuter:before { content: @fa-var-neuter; }
.@{fa-css-prefix}-genderless:before { content: @fa-var-genderless; }
.@{fa-css-prefix}-facebook-official:before { content: @fa-var-facebook-official; }
.@{fa-css-prefix}-pinterest-p:before { content: @fa-var-pinterest-p; }
.@{fa-css-prefix}-whatsapp:before { content: @fa-var-whatsapp; }
.@{fa-css-prefix}-server:before { content: @fa-var-server; }
.@{fa-css-prefix}-user-plus:before { content: @fa-var-user-plus; }
.@{fa-css-prefix}-user-times:before { content: @fa-var-user-times; }
.@{fa-css-prefix}-hotel:before,
.@{fa-css-prefix}-bed:before { content: @fa-var-bed; }
.@{fa-css-prefix}-viacoin:before { content: @fa-var-viacoin; }
.@{fa-css-prefix}-train:before { content: @fa-var-train; }
.@{fa-css-prefix}-subway:before { content: @fa-var-subway; }
.@{fa-css-prefix}-medium:before { content: @fa-var-medium; }
.@{fa-css-prefix}-yc:before,
.@{fa-css-prefix}-y-combinator:before { content: @fa-var-y-combinator; }
.@{fa-css-prefix}-optin-monster:before { content: @fa-var-optin-monster; }
.@{fa-css-prefix}-opencart:before { content: @fa-var-opencart; }
.@{fa-css-prefix}-expeditedssl:before { content: @fa-var-expeditedssl; }
.@{fa-css-prefix}-battery-4:before,
.@{fa-css-prefix}-battery:before,
.@{fa-css-prefix}-battery-full:before { content: @fa-var-battery-full; }
.@{fa-css-prefix}-battery-3:before,
.@{fa-css-prefix}-battery-three-quarters:before { content: @fa-var-battery-three-quarters; }
.@{fa-css-prefix}-battery-2:before,
.@{fa-css-prefix}-battery-half:before { content: @fa-var-battery-half; }
.@{fa-css-prefix}-battery-1:before,
.@{fa-css-prefix}-battery-quarter:before { content: @fa-var-battery-quarter; }
.@{fa-css-prefix}-battery-0:before,
.@{fa-css-prefix}-battery-empty:before { content: @fa-var-battery-empty; }
.@{fa-css-prefix}-mouse-pointer:before { content: @fa-var-mouse-pointer; }
.@{fa-css-prefix}-i-cursor:before { content: @fa-var-i-cursor; }
.@{fa-css-prefix}-object-group:before { content: @fa-var-object-group; }
.@{fa-css-prefix}-object-ungroup:before { content: @fa-var-object-ungroup; }
.@{fa-css-prefix}-sticky-note:before { content: @fa-var-sticky-note; }
.@{fa-css-prefix}-sticky-note-o:before { content: @fa-var-sticky-note-o; }
.@{fa-css-prefix}-cc-jcb:before { content: @fa-var-cc-jcb; }
.@{fa-css-prefix}-cc-diners-club:before { content: @fa-var-cc-diners-club; }
.@{fa-css-prefix}-clone:before { content: @fa-var-clone; }
.@{fa-css-prefix}-balance-scale:before { content: @fa-var-balance-scale; }
.@{fa-css-prefix}-hourglass-o:before { content: @fa-var-hourglass-o; }
.@{fa-css-prefix}-hourglass-1:before,
.@{fa-css-prefix}-hourglass-start:before { content: @fa-var-hourglass-start; }
.@{fa-css-prefix}-hourglass-2:before,
.@{fa-css-prefix}-hourglass-half:before { content: @fa-var-hourglass-half; }
.@{fa-css-prefix}-hourglass-3:before,
.@{fa-css-prefix}-hourglass-end:before { content: @fa-var-hourglass-end; }
.@{fa-css-prefix}-hourglass:before { content: @fa-var-hourglass; }
.@{fa-css-prefix}-hand-grab-o:before,
.@{fa-css-prefix}-hand-rock-o:before { content: @fa-var-hand-rock-o; }
.@{fa-css-prefix}-hand-stop-o:before,
.@{fa-css-prefix}-hand-paper-o:before { content: @fa-var-hand-paper-o; }
.@{fa-css-prefix}-hand-scissors-o:before { content: @fa-var-hand-scissors-o; }
.@{fa-css-prefix}-hand-lizard-o:before { content: @fa-var-hand-lizard-o; }
.@{fa-css-prefix}-hand-spock-o:before { content: @fa-var-hand-spock-o; }
.@{fa-css-prefix}-hand-pointer-o:before { content: @fa-var-hand-pointer-o; }
.@{fa-css-prefix}-hand-peace-o:before { content: @fa-var-hand-peace-o; }
.@{fa-css-prefix}-trademark:before { content: @fa-var-trademark; }
.@{fa-css-prefix}-registered:before { content: @fa-var-registered; }
.@{fa-css-prefix}-creative-commons:before { content: @fa-var-creative-commons; }
.@{fa-css-prefix}-gg:before { content: @fa-var-gg; }
.@{fa-css-prefix}-gg-circle:before { content: @fa-var-gg-circle; }
.@{fa-css-prefix}-tripadvisor:before { content: @fa-var-tripadvisor; }
.@{fa-css-prefix}-odnoklassniki:before { content: @fa-var-odnoklassniki; }
.@{fa-css-prefix}-odnoklassniki-square:before { content: @fa-var-odnoklassniki-square; }
.@{fa-css-prefix}-get-pocket:before { content: @fa-var-get-pocket; }
.@{fa-css-prefix}-wikipedia-w:before { content: @fa-var-wikipedia-w; }
.@{fa-css-prefix}-safari:before { content: @fa-var-safari; }
.@{fa-css-prefix}-chrome:before { content: @fa-var-chrome; }
.@{fa-css-prefix}-firefox:before { content: @fa-var-firefox; }
.@{fa-css-prefix}-opera:before { content: @fa-var-opera; }
.@{fa-css-prefix}-internet-explorer:before { content: @fa-var-internet-explorer; }
.@{fa-css-prefix}-tv:before,
.@{fa-css-prefix}-television:before { content: @fa-var-television; }
.@{fa-css-prefix}-contao:before { content: @fa-var-contao; }
.@{fa-css-prefix}-500px:before { content: @fa-var-500px; }
.@{fa-css-prefix}-amazon:before { content: @fa-var-amazon; }
.@{fa-css-prefix}-calendar-plus-o:before { content: @fa-var-calendar-plus-o; }
.@{fa-css-prefix}-calendar-minus-o:before { content: @fa-var-calendar-minus-o; }
.@{fa-css-prefix}-calendar-times-o:before { content: @fa-var-calendar-times-o; }
.@{fa-css-prefix}-calendar-check-o:before { content: @fa-var-calendar-check-o; }
.@{fa-css-prefix}-industry:before { content: @fa-var-industry; }
.@{fa-css-prefix}-map-pin:before { content: @fa-var-map-pin; }
.@{fa-css-prefix}-map-signs:before { content: @fa-var-map-signs; }
.@{fa-css-prefix}-map-o:before { content: @fa-var-map-o; }
.@{fa-css-prefix}-map:before { content: @fa-var-map; }
.@{fa-css-prefix}-commenting:before { content: @fa-var-commenting; }
.@{fa-css-prefix}-commenting-o:before { content: @fa-var-commenting-o; }
.@{fa-css-prefix}-houzz:before { content: @fa-var-houzz; }
.@{fa-css-prefix}-vimeo:before { content: @fa-var-vimeo; }
.@{fa-css-prefix}-black-tie:before { content: @fa-var-black-tie; }
.@{fa-css-prefix}-fonticons:before { content: @fa-var-fonticons; }
.@{fa-css-prefix}-reddit-alien:before { content: @fa-var-reddit-alien; }
.@{fa-css-prefix}-edge:before { content: @fa-var-edge; }
.@{fa-css-prefix}-credit-card-alt:before { content: @fa-var-credit-card-alt; }
.@{fa-css-prefix}-codiepie:before { content: @fa-var-codiepie; }
.@{fa-css-prefix}-modx:before { content: @fa-var-modx; }
.@{fa-css-prefix}-fort-awesome:before { content: @fa-var-fort-awesome; }
.@{fa-css-prefix}-usb:before { content: @fa-var-usb; }
.@{fa-css-prefix}-product-hunt:before { content: @fa-var-product-hunt; }
.@{fa-css-prefix}-mixcloud:before { content: @fa-var-mixcloud; }
.@{fa-css-prefix}-scribd:before { content: @fa-var-scribd; }
.@{fa-css-prefix}-pause-circle:before { content: @fa-var-pause-circle; }
.@{fa-css-prefix}-pause-circle-o:before { content: @fa-var-pause-circle-o; }
.@{fa-css-prefix}-stop-circle:before { content: @fa-var-stop-circle; }
.@{fa-css-prefix}-stop-circle-o:before { content: @fa-var-stop-circle-o; }
.@{fa-css-prefix}-shopping-bag:before { content: @fa-var-shopping-bag; }
.@{fa-css-prefix}-shopping-basket:before { content: @fa-var-shopping-basket; }
.@{fa-css-prefix}-hashtag:before { content: @fa-var-hashtag; }
.@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; }
.@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; }
.@{fa-css-prefix}-percent:before { content: @fa-var-percent; }
.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; }
.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; }
.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; }
.@{fa-css-prefix}-envira:before { content: @fa-var-envira; }
.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; }
.@{fa-css-prefix}-wheelchair-alt:before { content: @fa-var-wheelchair-alt; }
.@{fa-css-prefix}-question-circle-o:before { content: @fa-var-question-circle-o; }
.@{fa-css-prefix}-blind:before { content: @fa-var-blind; }
.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; }
.@{fa-css-prefix}-volume-control-phone:before { content: @fa-var-volume-control-phone; }
.@{fa-css-prefix}-braille:before { content: @fa-var-braille; }
.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; }
.@{fa-css-prefix}-asl-interpreting:before,
.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; }
.@{fa-css-prefix}-deafness:before,
.@{fa-css-prefix}-hard-of-hearing:before,
.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; }
.@{fa-css-prefix}-glide:before { content: @fa-var-glide; }
.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; }
.@{fa-css-prefix}-signing:before,
.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; }
.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; }
.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; }
.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; }
.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; }
.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; }
.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; }
.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; }
.@{fa-css-prefix}-first-order:before { content: @fa-var-first-order; }
.@{fa-css-prefix}-yoast:before { content: @fa-var-yoast; }
.@{fa-css-prefix}-themeisle:before { content: @fa-var-themeisle; }
.@{fa-css-prefix}-google-plus-circle:before,
.@{fa-css-prefix}-google-plus-official:before { content: @fa-var-google-plus-official; }
.@{fa-css-prefix}-fa:before,
.@{fa-css-prefix}-font-awesome:before { content: @fa-var-font-awesome; }
.@{fa-css-prefix}-handshake-o:before { content: @fa-var-handshake-o; }
.@{fa-css-prefix}-envelope-open:before { content: @fa-var-envelope-open; }
.@{fa-css-prefix}-envelope-open-o:before { content: @fa-var-envelope-open-o; }
.@{fa-css-prefix}-linode:before { content: @fa-var-linode; }
.@{fa-css-prefix}-address-book:before { content: @fa-var-address-book; }
.@{fa-css-prefix}-address-book-o:before { content: @fa-var-address-book-o; }
.@{fa-css-prefix}-vcard:before,
.@{fa-css-prefix}-address-card:before { content: @fa-var-address-card; }
.@{fa-css-prefix}-vcard-o:before,
.@{fa-css-prefix}-address-card-o:before { content: @fa-var-address-card-o; }
.@{fa-css-prefix}-user-circle:before { content: @fa-var-user-circle; }
.@{fa-css-prefix}-user-circle-o:before { content: @fa-var-user-circle-o; }
.@{fa-css-prefix}-user-o:before { content: @fa-var-user-o; }
.@{fa-css-prefix}-id-badge:before { content: @fa-var-id-badge; }
.@{fa-css-prefix}-drivers-license:before,
.@{fa-css-prefix}-id-card:before { content: @fa-var-id-card; }
.@{fa-css-prefix}-drivers-license-o:before,
.@{fa-css-prefix}-id-card-o:before { content: @fa-var-id-card-o; }
.@{fa-css-prefix}-quora:before { content: @fa-var-quora; }
.@{fa-css-prefix}-free-code-camp:before { content: @fa-var-free-code-camp; }
.@{fa-css-prefix}-telegram:before { content: @fa-var-telegram; }
.@{fa-css-prefix}-thermometer-4:before,
.@{fa-css-prefix}-thermometer:before,
.@{fa-css-prefix}-thermometer-full:before { content: @fa-var-thermometer-full; }
.@{fa-css-prefix}-thermometer-3:before,
.@{fa-css-prefix}-thermometer-three-quarters:before { content: @fa-var-thermometer-three-quarters; }
.@{fa-css-prefix}-thermometer-2:before,
.@{fa-css-prefix}-thermometer-half:before { content: @fa-var-thermometer-half; }
.@{fa-css-prefix}-thermometer-1:before,
.@{fa-css-prefix}-thermometer-quarter:before { content: @fa-var-thermometer-quarter; }
.@{fa-css-prefix}-thermometer-0:before,
.@{fa-css-prefix}-thermometer-empty:before { content: @fa-var-thermometer-empty; }
.@{fa-css-prefix}-shower:before { content: @fa-var-shower; }
.@{fa-css-prefix}-bathtub:before,
.@{fa-css-prefix}-s15:before,
.@{fa-css-prefix}-bath:before { content: @fa-var-bath; }
.@{fa-css-prefix}-podcast:before { content: @fa-var-podcast; }
.@{fa-css-prefix}-window-maximize:before { content: @fa-var-window-maximize; }
.@{fa-css-prefix}-window-minimize:before { content: @fa-var-window-minimize; }
.@{fa-css-prefix}-window-restore:before { content: @fa-var-window-restore; }
.@{fa-css-prefix}-times-rectangle:before,
.@{fa-css-prefix}-window-close:before { content: @fa-var-window-close; }
.@{fa-css-prefix}-times-rectangle-o:before,
.@{fa-css-prefix}-window-close-o:before { content: @fa-var-window-close-o; }
.@{fa-css-prefix}-bandcamp:before { content: @fa-var-bandcamp; }
.@{fa-css-prefix}-grav:before { content: @fa-var-grav; }
.@{fa-css-prefix}-etsy:before { content: @fa-var-etsy; }
.@{fa-css-prefix}-imdb:before { content: @fa-var-imdb; }
.@{fa-css-prefix}-ravelry:before { content: @fa-var-ravelry; }
.@{fa-css-prefix}-eercast:before { content: @fa-var-eercast; }
.@{fa-css-prefix}-microchip:before { content: @fa-var-microchip; }
.@{fa-css-prefix}-snowflake-o:before { content: @fa-var-snowflake-o; }
.@{fa-css-prefix}-superpowers:before { content: @fa-var-superpowers; }
.@{fa-css-prefix}-wpexplorer:before { content: @fa-var-wpexplorer; }
.@{fa-css-prefix}-meetup:before { content: @fa-var-meetup; }

View File

@ -3,58 +3,23 @@
.fa-icon() {
display: inline-block;
font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
font: normal normal normal 14px/1 FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.fa-icon-rotate(@degrees, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.fa-icon-flip(@horiz, @vert, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1);
-webkit-transform: scale(@horiz, @vert);
-ms-transform: scale(@horiz, @vert);
transform: scale(@horiz, @vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
.sr-only() {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
.sr-only-focusable() {
&:active,
&:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
}

View File

@ -5,11 +5,10 @@
font-family: 'FontAwesome';
src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'),
url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
// src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
// src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-weight: normal;
font-style: normal;
}

View File

@ -1,5 +0,0 @@
// Screen Readers
// -------------------------
.sr-only { .sr-only(); }
.sr-only-focusable { .sr-only-focusable(); }

View File

@ -1,4 +1,4 @@
// Animated Icons
// Spinning Icons
// --------------------------
.@{fa-css-prefix}-spin {
@ -6,11 +6,6 @@
animation: fa-spin 2s infinite linear;
}
.@{fa-css-prefix}-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);

View File

@ -2,29 +2,20 @@
// --------------------------
@fa-font-path: "../fonts";
@fa-font-size-base: 14px;
@fa-line-height-base: 1;
//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts"; // for referencing Bootstrap CDN font files directly
//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts"; // for referencing Bootstrap CDN font files directly
@fa-css-prefix: fa;
@fa-version: "4.7.0";
@fa-version: "4.2.0";
@fa-border-color: #eee;
@fa-inverse: #fff;
@fa-li-width: (30em / 14);
@fa-var-500px: "\f26e";
@fa-var-address-book: "\f2b9";
@fa-var-address-book-o: "\f2ba";
@fa-var-address-card: "\f2bb";
@fa-var-address-card-o: "\f2bc";
@fa-var-adjust: "\f042";
@fa-var-adn: "\f170";
@fa-var-align-center: "\f037";
@fa-var-align-justify: "\f039";
@fa-var-align-left: "\f036";
@fa-var-align-right: "\f038";
@fa-var-amazon: "\f270";
@fa-var-ambulance: "\f0f9";
@fa-var-american-sign-language-interpreting: "\f2a3";
@fa-var-anchor: "\f13d";
@fa-var-android: "\f17b";
@fa-var-angellist: "\f209";
@ -55,35 +46,16 @@
@fa-var-arrows-alt: "\f0b2";
@fa-var-arrows-h: "\f07e";
@fa-var-arrows-v: "\f07d";
@fa-var-asl-interpreting: "\f2a3";
@fa-var-assistive-listening-systems: "\f2a2";
@fa-var-asterisk: "\f069";
@fa-var-at: "\f1fa";
@fa-var-audio-description: "\f29e";
@fa-var-automobile: "\f1b9";
@fa-var-backward: "\f04a";
@fa-var-balance-scale: "\f24e";
@fa-var-ban: "\f05e";
@fa-var-bandcamp: "\f2d5";
@fa-var-bank: "\f19c";
@fa-var-bar-chart: "\f080";
@fa-var-bar-chart-o: "\f080";
@fa-var-barcode: "\f02a";
@fa-var-bars: "\f0c9";
@fa-var-bath: "\f2cd";
@fa-var-bathtub: "\f2cd";
@fa-var-battery: "\f240";
@fa-var-battery-0: "\f244";
@fa-var-battery-1: "\f243";
@fa-var-battery-2: "\f242";
@fa-var-battery-3: "\f241";
@fa-var-battery-4: "\f240";
@fa-var-battery-empty: "\f244";
@fa-var-battery-full: "\f240";
@fa-var-battery-half: "\f242";
@fa-var-battery-quarter: "\f243";
@fa-var-battery-three-quarters: "\f241";
@fa-var-bed: "\f236";
@fa-var-beer: "\f0fc";
@fa-var-behance: "\f1b4";
@fa-var-behance-square: "\f1b5";
@ -97,17 +69,12 @@
@fa-var-bitbucket: "\f171";
@fa-var-bitbucket-square: "\f172";
@fa-var-bitcoin: "\f15a";
@fa-var-black-tie: "\f27e";
@fa-var-blind: "\f29d";
@fa-var-bluetooth: "\f293";
@fa-var-bluetooth-b: "\f294";
@fa-var-bold: "\f032";
@fa-var-bolt: "\f0e7";
@fa-var-bomb: "\f1e2";
@fa-var-book: "\f02d";
@fa-var-bookmark: "\f02e";
@fa-var-bookmark-o: "\f097";
@fa-var-braille: "\f2a1";
@fa-var-briefcase: "\f0b1";
@fa-var-btc: "\f15a";
@fa-var-bug: "\f188";
@ -116,15 +83,10 @@
@fa-var-bullhorn: "\f0a1";
@fa-var-bullseye: "\f140";
@fa-var-bus: "\f207";
@fa-var-buysellads: "\f20d";
@fa-var-cab: "\f1ba";
@fa-var-calculator: "\f1ec";
@fa-var-calendar: "\f073";
@fa-var-calendar-check-o: "\f274";
@fa-var-calendar-minus-o: "\f272";
@fa-var-calendar-o: "\f133";
@fa-var-calendar-plus-o: "\f271";
@fa-var-calendar-times-o: "\f273";
@fa-var-camera: "\f030";
@fa-var-camera-retro: "\f083";
@fa-var-car: "\f1b9";
@ -136,13 +98,9 @@
@fa-var-caret-square-o-right: "\f152";
@fa-var-caret-square-o-up: "\f151";
@fa-var-caret-up: "\f0d8";
@fa-var-cart-arrow-down: "\f218";
@fa-var-cart-plus: "\f217";
@fa-var-cc: "\f20a";
@fa-var-cc-amex: "\f1f3";
@fa-var-cc-diners-club: "\f24c";
@fa-var-cc-discover: "\f1f2";
@fa-var-cc-jcb: "\f24b";
@fa-var-cc-mastercard: "\f1f1";
@fa-var-cc-paypal: "\f1f4";
@fa-var-cc-stripe: "\f1f5";
@ -164,14 +122,12 @@
@fa-var-chevron-right: "\f054";
@fa-var-chevron-up: "\f077";
@fa-var-child: "\f1ae";
@fa-var-chrome: "\f268";
@fa-var-circle: "\f111";
@fa-var-circle-o: "\f10c";
@fa-var-circle-o-notch: "\f1ce";
@fa-var-circle-thin: "\f1db";
@fa-var-clipboard: "\f0ea";
@fa-var-clock-o: "\f017";
@fa-var-clone: "\f24d";
@fa-var-close: "\f00d";
@fa-var-cloud: "\f0c2";
@fa-var-cloud-download: "\f0ed";
@ -180,26 +136,19 @@
@fa-var-code: "\f121";
@fa-var-code-fork: "\f126";
@fa-var-codepen: "\f1cb";
@fa-var-codiepie: "\f284";
@fa-var-coffee: "\f0f4";
@fa-var-cog: "\f013";
@fa-var-cogs: "\f085";
@fa-var-columns: "\f0db";
@fa-var-comment: "\f075";
@fa-var-comment-o: "\f0e5";
@fa-var-commenting: "\f27a";
@fa-var-commenting-o: "\f27b";
@fa-var-comments: "\f086";
@fa-var-comments-o: "\f0e6";
@fa-var-compass: "\f14e";
@fa-var-compress: "\f066";
@fa-var-connectdevelop: "\f20e";
@fa-var-contao: "\f26d";
@fa-var-copy: "\f0c5";
@fa-var-copyright: "\f1f9";
@fa-var-creative-commons: "\f25e";
@fa-var-credit-card: "\f09d";
@fa-var-credit-card-alt: "\f283";
@fa-var-crop: "\f125";
@fa-var-crosshairs: "\f05b";
@fa-var-css3: "\f13c";
@ -208,39 +157,27 @@
@fa-var-cut: "\f0c4";
@fa-var-cutlery: "\f0f5";
@fa-var-dashboard: "\f0e4";
@fa-var-dashcube: "\f210";
@fa-var-database: "\f1c0";
@fa-var-deaf: "\f2a4";
@fa-var-deafness: "\f2a4";
@fa-var-dedent: "\f03b";
@fa-var-delicious: "\f1a5";
@fa-var-desktop: "\f108";
@fa-var-deviantart: "\f1bd";
@fa-var-diamond: "\f219";
@fa-var-digg: "\f1a6";
@fa-var-dollar: "\f155";
@fa-var-dot-circle-o: "\f192";
@fa-var-download: "\f019";
@fa-var-dribbble: "\f17d";
@fa-var-drivers-license: "\f2c2";
@fa-var-drivers-license-o: "\f2c3";
@fa-var-dropbox: "\f16b";
@fa-var-drupal: "\f1a9";
@fa-var-edge: "\f282";
@fa-var-edit: "\f044";
@fa-var-eercast: "\f2da";
@fa-var-eject: "\f052";
@fa-var-ellipsis-h: "\f141";
@fa-var-ellipsis-v: "\f142";
@fa-var-empire: "\f1d1";
@fa-var-envelope: "\f0e0";
@fa-var-envelope-o: "\f003";
@fa-var-envelope-open: "\f2b6";
@fa-var-envelope-open-o: "\f2b7";
@fa-var-envelope-square: "\f199";
@fa-var-envira: "\f299";
@fa-var-eraser: "\f12d";
@fa-var-etsy: "\f2d7";
@fa-var-eur: "\f153";
@fa-var-euro: "\f153";
@fa-var-exchange: "\f0ec";
@ -248,21 +185,16 @@
@fa-var-exclamation-circle: "\f06a";
@fa-var-exclamation-triangle: "\f071";
@fa-var-expand: "\f065";
@fa-var-expeditedssl: "\f23e";
@fa-var-external-link: "\f08e";
@fa-var-external-link-square: "\f14c";
@fa-var-eye: "\f06e";
@fa-var-eye-slash: "\f070";
@fa-var-eyedropper: "\f1fb";
@fa-var-fa: "\f2b4";
@fa-var-facebook: "\f09a";
@fa-var-facebook-f: "\f09a";
@fa-var-facebook-official: "\f230";
@fa-var-facebook-square: "\f082";
@fa-var-fast-backward: "\f049";
@fa-var-fast-forward: "\f050";
@fa-var-fax: "\f1ac";
@fa-var-feed: "\f09e";
@fa-var-female: "\f182";
@fa-var-fighter-jet: "\f0fb";
@fa-var-file: "\f15b";
@ -288,8 +220,6 @@
@fa-var-filter: "\f0b0";
@fa-var-fire: "\f06d";
@fa-var-fire-extinguisher: "\f134";
@fa-var-firefox: "\f269";
@fa-var-first-order: "\f2b0";
@fa-var-flag: "\f024";
@fa-var-flag-checkered: "\f11e";
@fa-var-flag-o: "\f11d";
@ -302,13 +232,8 @@
@fa-var-folder-open: "\f07c";
@fa-var-folder-open-o: "\f115";
@fa-var-font: "\f031";
@fa-var-font-awesome: "\f2b4";
@fa-var-fonticons: "\f280";
@fa-var-fort-awesome: "\f286";
@fa-var-forumbee: "\f211";
@fa-var-forward: "\f04e";
@fa-var-foursquare: "\f180";
@fa-var-free-code-camp: "\f2c5";
@fa-var-frown-o: "\f119";
@fa-var-futbol-o: "\f1e3";
@fa-var-gamepad: "\f11b";
@ -317,87 +242,45 @@
@fa-var-ge: "\f1d1";
@fa-var-gear: "\f013";
@fa-var-gears: "\f085";
@fa-var-genderless: "\f22d";
@fa-var-get-pocket: "\f265";
@fa-var-gg: "\f260";
@fa-var-gg-circle: "\f261";
@fa-var-gift: "\f06b";
@fa-var-git: "\f1d3";
@fa-var-git-square: "\f1d2";
@fa-var-github: "\f09b";
@fa-var-github-alt: "\f113";
@fa-var-github-square: "\f092";
@fa-var-gitlab: "\f296";
@fa-var-gittip: "\f184";
@fa-var-glass: "\f000";
@fa-var-glide: "\f2a5";
@fa-var-glide-g: "\f2a6";
@fa-var-globe: "\f0ac";
@fa-var-google: "\f1a0";
@fa-var-google-plus: "\f0d5";
@fa-var-google-plus-circle: "\f2b3";
@fa-var-google-plus-official: "\f2b3";
@fa-var-google-plus-square: "\f0d4";
@fa-var-google-wallet: "\f1ee";
@fa-var-graduation-cap: "\f19d";
@fa-var-gratipay: "\f184";
@fa-var-grav: "\f2d6";
@fa-var-group: "\f0c0";
@fa-var-h-square: "\f0fd";
@fa-var-hacker-news: "\f1d4";
@fa-var-hand-grab-o: "\f255";
@fa-var-hand-lizard-o: "\f258";
@fa-var-hand-o-down: "\f0a7";
@fa-var-hand-o-left: "\f0a5";
@fa-var-hand-o-right: "\f0a4";
@fa-var-hand-o-up: "\f0a6";
@fa-var-hand-paper-o: "\f256";
@fa-var-hand-peace-o: "\f25b";
@fa-var-hand-pointer-o: "\f25a";
@fa-var-hand-rock-o: "\f255";
@fa-var-hand-scissors-o: "\f257";
@fa-var-hand-spock-o: "\f259";
@fa-var-hand-stop-o: "\f256";
@fa-var-handshake-o: "\f2b5";
@fa-var-hard-of-hearing: "\f2a4";
@fa-var-hashtag: "\f292";
@fa-var-hdd-o: "\f0a0";
@fa-var-header: "\f1dc";
@fa-var-headphones: "\f025";
@fa-var-heart: "\f004";
@fa-var-heart-o: "\f08a";
@fa-var-heartbeat: "\f21e";
@fa-var-history: "\f1da";
@fa-var-home: "\f015";
@fa-var-hospital-o: "\f0f8";
@fa-var-hotel: "\f236";
@fa-var-hourglass: "\f254";
@fa-var-hourglass-1: "\f251";
@fa-var-hourglass-2: "\f252";
@fa-var-hourglass-3: "\f253";
@fa-var-hourglass-end: "\f253";
@fa-var-hourglass-half: "\f252";
@fa-var-hourglass-o: "\f250";
@fa-var-hourglass-start: "\f251";
@fa-var-houzz: "\f27c";
@fa-var-html5: "\f13b";
@fa-var-i-cursor: "\f246";
@fa-var-id-badge: "\f2c1";
@fa-var-id-card: "\f2c2";
@fa-var-id-card-o: "\f2c3";
@fa-var-ils: "\f20b";
@fa-var-image: "\f03e";
@fa-var-imdb: "\f2d8";
@fa-var-inbox: "\f01c";
@fa-var-indent: "\f03c";
@fa-var-industry: "\f275";
@fa-var-info: "\f129";
@fa-var-info-circle: "\f05a";
@fa-var-inr: "\f156";
@fa-var-instagram: "\f16d";
@fa-var-institution: "\f19c";
@fa-var-internet-explorer: "\f26b";
@fa-var-intersex: "\f224";
@fa-var-ioxhost: "\f208";
@fa-var-italic: "\f033";
@fa-var-joomla: "\f1aa";
@ -411,7 +294,6 @@
@fa-var-lastfm: "\f202";
@fa-var-lastfm-square: "\f203";
@fa-var-leaf: "\f06c";
@fa-var-leanpub: "\f212";
@fa-var-legal: "\f0e3";
@fa-var-lemon-o: "\f094";
@fa-var-level-down: "\f149";
@ -425,7 +307,6 @@
@fa-var-link: "\f0c1";
@fa-var-linkedin: "\f0e1";
@fa-var-linkedin-square: "\f08c";
@fa-var-linode: "\f2b8";
@fa-var-linux: "\f17c";
@fa-var-list: "\f03a";
@fa-var-list-alt: "\f022";
@ -437,58 +318,32 @@
@fa-var-long-arrow-left: "\f177";
@fa-var-long-arrow-right: "\f178";
@fa-var-long-arrow-up: "\f176";
@fa-var-low-vision: "\f2a8";
@fa-var-magic: "\f0d0";
@fa-var-magnet: "\f076";
@fa-var-mail-forward: "\f064";
@fa-var-mail-reply: "\f112";
@fa-var-mail-reply-all: "\f122";
@fa-var-male: "\f183";
@fa-var-map: "\f279";
@fa-var-map-marker: "\f041";
@fa-var-map-o: "\f278";
@fa-var-map-pin: "\f276";
@fa-var-map-signs: "\f277";
@fa-var-mars: "\f222";
@fa-var-mars-double: "\f227";
@fa-var-mars-stroke: "\f229";
@fa-var-mars-stroke-h: "\f22b";
@fa-var-mars-stroke-v: "\f22a";
@fa-var-maxcdn: "\f136";
@fa-var-meanpath: "\f20c";
@fa-var-medium: "\f23a";
@fa-var-medkit: "\f0fa";
@fa-var-meetup: "\f2e0";
@fa-var-meh-o: "\f11a";
@fa-var-mercury: "\f223";
@fa-var-microchip: "\f2db";
@fa-var-microphone: "\f130";
@fa-var-microphone-slash: "\f131";
@fa-var-minus: "\f068";
@fa-var-minus-circle: "\f056";
@fa-var-minus-square: "\f146";
@fa-var-minus-square-o: "\f147";
@fa-var-mixcloud: "\f289";
@fa-var-mobile: "\f10b";
@fa-var-mobile-phone: "\f10b";
@fa-var-modx: "\f285";
@fa-var-money: "\f0d6";
@fa-var-moon-o: "\f186";
@fa-var-mortar-board: "\f19d";
@fa-var-motorcycle: "\f21c";
@fa-var-mouse-pointer: "\f245";
@fa-var-music: "\f001";
@fa-var-navicon: "\f0c9";
@fa-var-neuter: "\f22c";
@fa-var-newspaper-o: "\f1ea";
@fa-var-object-group: "\f247";
@fa-var-object-ungroup: "\f248";
@fa-var-odnoklassniki: "\f263";
@fa-var-odnoklassniki-square: "\f264";
@fa-var-opencart: "\f23d";
@fa-var-openid: "\f19b";
@fa-var-opera: "\f26a";
@fa-var-optin-monster: "\f23c";
@fa-var-outdent: "\f03b";
@fa-var-pagelines: "\f18c";
@fa-var-paint-brush: "\f1fc";
@ -498,24 +353,19 @@
@fa-var-paragraph: "\f1dd";
@fa-var-paste: "\f0ea";
@fa-var-pause: "\f04c";
@fa-var-pause-circle: "\f28b";
@fa-var-pause-circle-o: "\f28c";
@fa-var-paw: "\f1b0";
@fa-var-paypal: "\f1ed";
@fa-var-pencil: "\f040";
@fa-var-pencil-square: "\f14b";
@fa-var-pencil-square-o: "\f044";
@fa-var-percent: "\f295";
@fa-var-phone: "\f095";
@fa-var-phone-square: "\f098";
@fa-var-photo: "\f03e";
@fa-var-picture-o: "\f03e";
@fa-var-pie-chart: "\f200";
@fa-var-pied-piper: "\f2ae";
@fa-var-pied-piper: "\f1a7";
@fa-var-pied-piper-alt: "\f1a8";
@fa-var-pied-piper-pp: "\f1a7";
@fa-var-pinterest: "\f0d2";
@fa-var-pinterest-p: "\f231";
@fa-var-pinterest-square: "\f0d3";
@fa-var-plane: "\f072";
@fa-var-play: "\f04b";
@ -526,36 +376,28 @@
@fa-var-plus-circle: "\f055";
@fa-var-plus-square: "\f0fe";
@fa-var-plus-square-o: "\f196";
@fa-var-podcast: "\f2ce";
@fa-var-power-off: "\f011";
@fa-var-print: "\f02f";
@fa-var-product-hunt: "\f288";
@fa-var-puzzle-piece: "\f12e";
@fa-var-qq: "\f1d6";
@fa-var-qrcode: "\f029";
@fa-var-question: "\f128";
@fa-var-question-circle: "\f059";
@fa-var-question-circle-o: "\f29c";
@fa-var-quora: "\f2c4";
@fa-var-quote-left: "\f10d";
@fa-var-quote-right: "\f10e";
@fa-var-ra: "\f1d0";
@fa-var-random: "\f074";
@fa-var-ravelry: "\f2d9";
@fa-var-rebel: "\f1d0";
@fa-var-recycle: "\f1b8";
@fa-var-reddit: "\f1a1";
@fa-var-reddit-alien: "\f281";
@fa-var-reddit-square: "\f1a2";
@fa-var-refresh: "\f021";
@fa-var-registered: "\f25d";
@fa-var-remove: "\f00d";
@fa-var-renren: "\f18b";
@fa-var-reorder: "\f0c9";
@fa-var-repeat: "\f01e";
@fa-var-reply: "\f112";
@fa-var-reply-all: "\f122";
@fa-var-resistance: "\f1d0";
@fa-var-retweet: "\f079";
@fa-var-rmb: "\f157";
@fa-var-road: "\f018";
@ -568,18 +410,13 @@
@fa-var-rub: "\f158";
@fa-var-ruble: "\f158";
@fa-var-rupee: "\f156";
@fa-var-s15: "\f2cd";
@fa-var-safari: "\f267";
@fa-var-save: "\f0c7";
@fa-var-scissors: "\f0c4";
@fa-var-scribd: "\f28a";
@fa-var-search: "\f002";
@fa-var-search-minus: "\f010";
@fa-var-search-plus: "\f00e";
@fa-var-sellsy: "\f213";
@fa-var-send: "\f1d8";
@fa-var-send-o: "\f1d9";
@fa-var-server: "\f233";
@fa-var-share: "\f064";
@fa-var-share-alt: "\f1e0";
@fa-var-share-alt-square: "\f1e1";
@ -588,29 +425,16 @@
@fa-var-shekel: "\f20b";
@fa-var-sheqel: "\f20b";
@fa-var-shield: "\f132";
@fa-var-ship: "\f21a";
@fa-var-shirtsinbulk: "\f214";
@fa-var-shopping-bag: "\f290";
@fa-var-shopping-basket: "\f291";
@fa-var-shopping-cart: "\f07a";
@fa-var-shower: "\f2cc";
@fa-var-sign-in: "\f090";
@fa-var-sign-language: "\f2a7";
@fa-var-sign-out: "\f08b";
@fa-var-signal: "\f012";
@fa-var-signing: "\f2a7";
@fa-var-simplybuilt: "\f215";
@fa-var-sitemap: "\f0e8";
@fa-var-skyatlas: "\f216";
@fa-var-skype: "\f17e";
@fa-var-slack: "\f198";
@fa-var-sliders: "\f1de";
@fa-var-slideshare: "\f1e7";
@fa-var-smile-o: "\f118";
@fa-var-snapchat: "\f2ab";
@fa-var-snapchat-ghost: "\f2ac";
@fa-var-snapchat-square: "\f2ad";
@fa-var-snowflake-o: "\f2dc";
@fa-var-soccer-ball-o: "\f1e3";
@fa-var-sort: "\f0dc";
@fa-var-sort-alpha-asc: "\f15d";
@ -643,20 +467,13 @@
@fa-var-step-backward: "\f048";
@fa-var-step-forward: "\f051";
@fa-var-stethoscope: "\f0f1";
@fa-var-sticky-note: "\f249";
@fa-var-sticky-note-o: "\f24a";
@fa-var-stop: "\f04d";
@fa-var-stop-circle: "\f28d";
@fa-var-stop-circle-o: "\f28e";
@fa-var-street-view: "\f21d";
@fa-var-strikethrough: "\f0cc";
@fa-var-stumbleupon: "\f1a4";
@fa-var-stumbleupon-circle: "\f1a3";
@fa-var-subscript: "\f12c";
@fa-var-subway: "\f239";
@fa-var-suitcase: "\f0f2";
@fa-var-sun-o: "\f185";
@fa-var-superpowers: "\f2dd";
@fa-var-superscript: "\f12b";
@fa-var-support: "\f1cd";
@fa-var-table: "\f0ce";
@ -666,8 +483,6 @@
@fa-var-tags: "\f02c";
@fa-var-tasks: "\f0ae";
@fa-var-taxi: "\f1ba";
@fa-var-telegram: "\f2c6";
@fa-var-television: "\f26c";
@fa-var-tencent-weibo: "\f1d5";
@fa-var-terminal: "\f120";
@fa-var-text-height: "\f034";
@ -675,18 +490,6 @@
@fa-var-th: "\f00a";
@fa-var-th-large: "\f009";
@fa-var-th-list: "\f00b";
@fa-var-themeisle: "\f2b2";
@fa-var-thermometer: "\f2c7";
@fa-var-thermometer-0: "\f2cb";
@fa-var-thermometer-1: "\f2ca";
@fa-var-thermometer-2: "\f2c9";
@fa-var-thermometer-3: "\f2c8";
@fa-var-thermometer-4: "\f2c7";
@fa-var-thermometer-empty: "\f2cb";
@fa-var-thermometer-full: "\f2c7";
@fa-var-thermometer-half: "\f2c9";
@fa-var-thermometer-quarter: "\f2ca";
@fa-var-thermometer-three-quarters: "\f2c8";
@fa-var-thumb-tack: "\f08d";
@fa-var-thumbs-down: "\f165";
@fa-var-thumbs-o-down: "\f088";
@ -696,8 +499,6 @@
@fa-var-times: "\f00d";
@fa-var-times-circle: "\f057";
@fa-var-times-circle-o: "\f05c";
@fa-var-times-rectangle: "\f2d3";
@fa-var-times-rectangle-o: "\f2d4";
@fa-var-tint: "\f043";
@fa-var-toggle-down: "\f150";
@fa-var-toggle-left: "\f191";
@ -705,15 +506,10 @@
@fa-var-toggle-on: "\f205";
@fa-var-toggle-right: "\f152";
@fa-var-toggle-up: "\f151";
@fa-var-trademark: "\f25c";
@fa-var-train: "\f238";
@fa-var-transgender: "\f224";
@fa-var-transgender-alt: "\f225";
@fa-var-trash: "\f1f8";
@fa-var-trash-o: "\f014";
@fa-var-tree: "\f1bb";
@fa-var-trello: "\f181";
@fa-var-tripadvisor: "\f262";
@fa-var-trophy: "\f091";
@fa-var-truck: "\f0d1";
@fa-var-try: "\f195";
@ -721,45 +517,26 @@
@fa-var-tumblr: "\f173";
@fa-var-tumblr-square: "\f174";
@fa-var-turkish-lira: "\f195";
@fa-var-tv: "\f26c";
@fa-var-twitch: "\f1e8";
@fa-var-twitter: "\f099";
@fa-var-twitter-square: "\f081";
@fa-var-umbrella: "\f0e9";
@fa-var-underline: "\f0cd";
@fa-var-undo: "\f0e2";
@fa-var-universal-access: "\f29a";
@fa-var-university: "\f19c";
@fa-var-unlink: "\f127";
@fa-var-unlock: "\f09c";
@fa-var-unlock-alt: "\f13e";
@fa-var-unsorted: "\f0dc";
@fa-var-upload: "\f093";
@fa-var-usb: "\f287";
@fa-var-usd: "\f155";
@fa-var-user: "\f007";
@fa-var-user-circle: "\f2bd";
@fa-var-user-circle-o: "\f2be";
@fa-var-user-md: "\f0f0";
@fa-var-user-o: "\f2c0";
@fa-var-user-plus: "\f234";
@fa-var-user-secret: "\f21b";
@fa-var-user-times: "\f235";
@fa-var-users: "\f0c0";
@fa-var-vcard: "\f2bb";
@fa-var-vcard-o: "\f2bc";
@fa-var-venus: "\f221";
@fa-var-venus-double: "\f226";
@fa-var-venus-mars: "\f228";
@fa-var-viacoin: "\f237";
@fa-var-viadeo: "\f2a9";
@fa-var-viadeo-square: "\f2aa";
@fa-var-video-camera: "\f03d";
@fa-var-vimeo: "\f27d";
@fa-var-vimeo-square: "\f194";
@fa-var-vine: "\f1ca";
@fa-var-vk: "\f189";
@fa-var-volume-control-phone: "\f2a0";
@fa-var-volume-down: "\f027";
@fa-var-volume-off: "\f026";
@fa-var-volume-up: "\f028";
@ -767,33 +544,17 @@
@fa-var-wechat: "\f1d7";
@fa-var-weibo: "\f18a";
@fa-var-weixin: "\f1d7";
@fa-var-whatsapp: "\f232";
@fa-var-wheelchair: "\f193";
@fa-var-wheelchair-alt: "\f29b";
@fa-var-wifi: "\f1eb";
@fa-var-wikipedia-w: "\f266";
@fa-var-window-close: "\f2d3";
@fa-var-window-close-o: "\f2d4";
@fa-var-window-maximize: "\f2d0";
@fa-var-window-minimize: "\f2d1";
@fa-var-window-restore: "\f2d2";
@fa-var-windows: "\f17a";
@fa-var-won: "\f159";
@fa-var-wordpress: "\f19a";
@fa-var-wpbeginner: "\f297";
@fa-var-wpexplorer: "\f2de";
@fa-var-wpforms: "\f298";
@fa-var-wrench: "\f0ad";
@fa-var-xing: "\f168";
@fa-var-xing-square: "\f169";
@fa-var-y-combinator: "\f23b";
@fa-var-y-combinator-square: "\f1d4";
@fa-var-yahoo: "\f19e";
@fa-var-yc: "\f23b";
@fa-var-yc-square: "\f1d4";
@fa-var-yelp: "\f1e9";
@fa-var-yen: "\f157";
@fa-var-yoast: "\f2b1";
@fa-var-youtube: "\f167";
@fa-var-youtube-play: "\f16a";
@fa-var-youtube-square: "\f166";

View File

@ -7,15 +7,6 @@
border-radius: .1em;
}
.#{$fa-css-prefix}-pull-left { float: left; }
.#{$fa-css-prefix}-pull-right { float: right; }
.#{$fa-css-prefix} {
&.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
&.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
}
/* Deprecated as of 4.4.0 */
.pull-right { float: right; }
.pull-left { float: left; }

View File

@ -3,10 +3,9 @@
.#{$fa-css-prefix} {
display: inline-block;
font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
font: normal normal normal 14px/1 FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@ -158,12 +158,10 @@
.#{$fa-css-prefix}-bookmark-o:before { content: $fa-var-bookmark-o; }
.#{$fa-css-prefix}-phone-square:before { content: $fa-var-phone-square; }
.#{$fa-css-prefix}-twitter:before { content: $fa-var-twitter; }
.#{$fa-css-prefix}-facebook-f:before,
.#{$fa-css-prefix}-facebook:before { content: $fa-var-facebook; }
.#{$fa-css-prefix}-github:before { content: $fa-var-github; }
.#{$fa-css-prefix}-unlock:before { content: $fa-var-unlock; }
.#{$fa-css-prefix}-credit-card:before { content: $fa-var-credit-card; }
.#{$fa-css-prefix}-feed:before,
.#{$fa-css-prefix}-rss:before { content: $fa-var-rss; }
.#{$fa-css-prefix}-hdd-o:before { content: $fa-var-hdd-o; }
.#{$fa-css-prefix}-bullhorn:before { content: $fa-var-bullhorn; }
@ -399,8 +397,7 @@
.#{$fa-css-prefix}-trello:before { content: $fa-var-trello; }
.#{$fa-css-prefix}-female:before { content: $fa-var-female; }
.#{$fa-css-prefix}-male:before { content: $fa-var-male; }
.#{$fa-css-prefix}-gittip:before,
.#{$fa-css-prefix}-gratipay:before { content: $fa-var-gratipay; }
.#{$fa-css-prefix}-gittip:before { content: $fa-var-gittip; }
.#{$fa-css-prefix}-sun-o:before { content: $fa-var-sun-o; }
.#{$fa-css-prefix}-moon-o:before { content: $fa-var-moon-o; }
.#{$fa-css-prefix}-archive:before { content: $fa-var-archive; }
@ -438,7 +435,7 @@
.#{$fa-css-prefix}-stumbleupon:before { content: $fa-var-stumbleupon; }
.#{$fa-css-prefix}-delicious:before { content: $fa-var-delicious; }
.#{$fa-css-prefix}-digg:before { content: $fa-var-digg; }
.#{$fa-css-prefix}-pied-piper-pp:before { content: $fa-var-pied-piper-pp; }
.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; }
.#{$fa-css-prefix}-pied-piper-alt:before { content: $fa-var-pied-piper-alt; }
.#{$fa-css-prefix}-drupal:before { content: $fa-var-drupal; }
.#{$fa-css-prefix}-joomla:before { content: $fa-var-joomla; }
@ -488,14 +485,11 @@
.#{$fa-css-prefix}-life-ring:before { content: $fa-var-life-ring; }
.#{$fa-css-prefix}-circle-o-notch:before { content: $fa-var-circle-o-notch; }
.#{$fa-css-prefix}-ra:before,
.#{$fa-css-prefix}-resistance:before,
.#{$fa-css-prefix}-rebel:before { content: $fa-var-rebel; }
.#{$fa-css-prefix}-ge:before,
.#{$fa-css-prefix}-empire:before { content: $fa-var-empire; }
.#{$fa-css-prefix}-git-square:before { content: $fa-var-git-square; }
.#{$fa-css-prefix}-git:before { content: $fa-var-git; }
.#{$fa-css-prefix}-y-combinator-square:before,
.#{$fa-css-prefix}-yc-square:before,
.#{$fa-css-prefix}-hacker-news:before { content: $fa-var-hacker-news; }
.#{$fa-css-prefix}-tencent-weibo:before { content: $fa-var-tencent-weibo; }
.#{$fa-css-prefix}-qq:before { content: $fa-var-qq; }
@ -556,234 +550,3 @@
.#{$fa-css-prefix}-sheqel:before,
.#{$fa-css-prefix}-ils:before { content: $fa-var-ils; }
.#{$fa-css-prefix}-meanpath:before { content: $fa-var-meanpath; }
.#{$fa-css-prefix}-buysellads:before { content: $fa-var-buysellads; }
.#{$fa-css-prefix}-connectdevelop:before { content: $fa-var-connectdevelop; }
.#{$fa-css-prefix}-dashcube:before { content: $fa-var-dashcube; }
.#{$fa-css-prefix}-forumbee:before { content: $fa-var-forumbee; }
.#{$fa-css-prefix}-leanpub:before { content: $fa-var-leanpub; }
.#{$fa-css-prefix}-sellsy:before { content: $fa-var-sellsy; }
.#{$fa-css-prefix}-shirtsinbulk:before { content: $fa-var-shirtsinbulk; }
.#{$fa-css-prefix}-simplybuilt:before { content: $fa-var-simplybuilt; }
.#{$fa-css-prefix}-skyatlas:before { content: $fa-var-skyatlas; }
.#{$fa-css-prefix}-cart-plus:before { content: $fa-var-cart-plus; }
.#{$fa-css-prefix}-cart-arrow-down:before { content: $fa-var-cart-arrow-down; }
.#{$fa-css-prefix}-diamond:before { content: $fa-var-diamond; }
.#{$fa-css-prefix}-ship:before { content: $fa-var-ship; }
.#{$fa-css-prefix}-user-secret:before { content: $fa-var-user-secret; }
.#{$fa-css-prefix}-motorcycle:before { content: $fa-var-motorcycle; }
.#{$fa-css-prefix}-street-view:before { content: $fa-var-street-view; }
.#{$fa-css-prefix}-heartbeat:before { content: $fa-var-heartbeat; }
.#{$fa-css-prefix}-venus:before { content: $fa-var-venus; }
.#{$fa-css-prefix}-mars:before { content: $fa-var-mars; }
.#{$fa-css-prefix}-mercury:before { content: $fa-var-mercury; }
.#{$fa-css-prefix}-intersex:before,
.#{$fa-css-prefix}-transgender:before { content: $fa-var-transgender; }
.#{$fa-css-prefix}-transgender-alt:before { content: $fa-var-transgender-alt; }
.#{$fa-css-prefix}-venus-double:before { content: $fa-var-venus-double; }
.#{$fa-css-prefix}-mars-double:before { content: $fa-var-mars-double; }
.#{$fa-css-prefix}-venus-mars:before { content: $fa-var-venus-mars; }
.#{$fa-css-prefix}-mars-stroke:before { content: $fa-var-mars-stroke; }
.#{$fa-css-prefix}-mars-stroke-v:before { content: $fa-var-mars-stroke-v; }
.#{$fa-css-prefix}-mars-stroke-h:before { content: $fa-var-mars-stroke-h; }
.#{$fa-css-prefix}-neuter:before { content: $fa-var-neuter; }
.#{$fa-css-prefix}-genderless:before { content: $fa-var-genderless; }
.#{$fa-css-prefix}-facebook-official:before { content: $fa-var-facebook-official; }
.#{$fa-css-prefix}-pinterest-p:before { content: $fa-var-pinterest-p; }
.#{$fa-css-prefix}-whatsapp:before { content: $fa-var-whatsapp; }
.#{$fa-css-prefix}-server:before { content: $fa-var-server; }
.#{$fa-css-prefix}-user-plus:before { content: $fa-var-user-plus; }
.#{$fa-css-prefix}-user-times:before { content: $fa-var-user-times; }
.#{$fa-css-prefix}-hotel:before,
.#{$fa-css-prefix}-bed:before { content: $fa-var-bed; }
.#{$fa-css-prefix}-viacoin:before { content: $fa-var-viacoin; }
.#{$fa-css-prefix}-train:before { content: $fa-var-train; }
.#{$fa-css-prefix}-subway:before { content: $fa-var-subway; }
.#{$fa-css-prefix}-medium:before { content: $fa-var-medium; }
.#{$fa-css-prefix}-yc:before,
.#{$fa-css-prefix}-y-combinator:before { content: $fa-var-y-combinator; }
.#{$fa-css-prefix}-optin-monster:before { content: $fa-var-optin-monster; }
.#{$fa-css-prefix}-opencart:before { content: $fa-var-opencart; }
.#{$fa-css-prefix}-expeditedssl:before { content: $fa-var-expeditedssl; }
.#{$fa-css-prefix}-battery-4:before,
.#{$fa-css-prefix}-battery:before,
.#{$fa-css-prefix}-battery-full:before { content: $fa-var-battery-full; }
.#{$fa-css-prefix}-battery-3:before,
.#{$fa-css-prefix}-battery-three-quarters:before { content: $fa-var-battery-three-quarters; }
.#{$fa-css-prefix}-battery-2:before,
.#{$fa-css-prefix}-battery-half:before { content: $fa-var-battery-half; }
.#{$fa-css-prefix}-battery-1:before,
.#{$fa-css-prefix}-battery-quarter:before { content: $fa-var-battery-quarter; }
.#{$fa-css-prefix}-battery-0:before,
.#{$fa-css-prefix}-battery-empty:before { content: $fa-var-battery-empty; }
.#{$fa-css-prefix}-mouse-pointer:before { content: $fa-var-mouse-pointer; }
.#{$fa-css-prefix}-i-cursor:before { content: $fa-var-i-cursor; }
.#{$fa-css-prefix}-object-group:before { content: $fa-var-object-group; }
.#{$fa-css-prefix}-object-ungroup:before { content: $fa-var-object-ungroup; }
.#{$fa-css-prefix}-sticky-note:before { content: $fa-var-sticky-note; }
.#{$fa-css-prefix}-sticky-note-o:before { content: $fa-var-sticky-note-o; }
.#{$fa-css-prefix}-cc-jcb:before { content: $fa-var-cc-jcb; }
.#{$fa-css-prefix}-cc-diners-club:before { content: $fa-var-cc-diners-club; }
.#{$fa-css-prefix}-clone:before { content: $fa-var-clone; }
.#{$fa-css-prefix}-balance-scale:before { content: $fa-var-balance-scale; }
.#{$fa-css-prefix}-hourglass-o:before { content: $fa-var-hourglass-o; }
.#{$fa-css-prefix}-hourglass-1:before,
.#{$fa-css-prefix}-hourglass-start:before { content: $fa-var-hourglass-start; }
.#{$fa-css-prefix}-hourglass-2:before,
.#{$fa-css-prefix}-hourglass-half:before { content: $fa-var-hourglass-half; }
.#{$fa-css-prefix}-hourglass-3:before,
.#{$fa-css-prefix}-hourglass-end:before { content: $fa-var-hourglass-end; }
.#{$fa-css-prefix}-hourglass:before { content: $fa-var-hourglass; }
.#{$fa-css-prefix}-hand-grab-o:before,
.#{$fa-css-prefix}-hand-rock-o:before { content: $fa-var-hand-rock-o; }
.#{$fa-css-prefix}-hand-stop-o:before,
.#{$fa-css-prefix}-hand-paper-o:before { content: $fa-var-hand-paper-o; }
.#{$fa-css-prefix}-hand-scissors-o:before { content: $fa-var-hand-scissors-o; }
.#{$fa-css-prefix}-hand-lizard-o:before { content: $fa-var-hand-lizard-o; }
.#{$fa-css-prefix}-hand-spock-o:before { content: $fa-var-hand-spock-o; }
.#{$fa-css-prefix}-hand-pointer-o:before { content: $fa-var-hand-pointer-o; }
.#{$fa-css-prefix}-hand-peace-o:before { content: $fa-var-hand-peace-o; }
.#{$fa-css-prefix}-trademark:before { content: $fa-var-trademark; }
.#{$fa-css-prefix}-registered:before { content: $fa-var-registered; }
.#{$fa-css-prefix}-creative-commons:before { content: $fa-var-creative-commons; }
.#{$fa-css-prefix}-gg:before { content: $fa-var-gg; }
.#{$fa-css-prefix}-gg-circle:before { content: $fa-var-gg-circle; }
.#{$fa-css-prefix}-tripadvisor:before { content: $fa-var-tripadvisor; }
.#{$fa-css-prefix}-odnoklassniki:before { content: $fa-var-odnoklassniki; }
.#{$fa-css-prefix}-odnoklassniki-square:before { content: $fa-var-odnoklassniki-square; }
.#{$fa-css-prefix}-get-pocket:before { content: $fa-var-get-pocket; }
.#{$fa-css-prefix}-wikipedia-w:before { content: $fa-var-wikipedia-w; }
.#{$fa-css-prefix}-safari:before { content: $fa-var-safari; }
.#{$fa-css-prefix}-chrome:before { content: $fa-var-chrome; }
.#{$fa-css-prefix}-firefox:before { content: $fa-var-firefox; }
.#{$fa-css-prefix}-opera:before { content: $fa-var-opera; }
.#{$fa-css-prefix}-internet-explorer:before { content: $fa-var-internet-explorer; }
.#{$fa-css-prefix}-tv:before,
.#{$fa-css-prefix}-television:before { content: $fa-var-television; }
.#{$fa-css-prefix}-contao:before { content: $fa-var-contao; }
.#{$fa-css-prefix}-500px:before { content: $fa-var-500px; }
.#{$fa-css-prefix}-amazon:before { content: $fa-var-amazon; }
.#{$fa-css-prefix}-calendar-plus-o:before { content: $fa-var-calendar-plus-o; }
.#{$fa-css-prefix}-calendar-minus-o:before { content: $fa-var-calendar-minus-o; }
.#{$fa-css-prefix}-calendar-times-o:before { content: $fa-var-calendar-times-o; }
.#{$fa-css-prefix}-calendar-check-o:before { content: $fa-var-calendar-check-o; }
.#{$fa-css-prefix}-industry:before { content: $fa-var-industry; }
.#{$fa-css-prefix}-map-pin:before { content: $fa-var-map-pin; }
.#{$fa-css-prefix}-map-signs:before { content: $fa-var-map-signs; }
.#{$fa-css-prefix}-map-o:before { content: $fa-var-map-o; }
.#{$fa-css-prefix}-map:before { content: $fa-var-map; }
.#{$fa-css-prefix}-commenting:before { content: $fa-var-commenting; }
.#{$fa-css-prefix}-commenting-o:before { content: $fa-var-commenting-o; }
.#{$fa-css-prefix}-houzz:before { content: $fa-var-houzz; }
.#{$fa-css-prefix}-vimeo:before { content: $fa-var-vimeo; }
.#{$fa-css-prefix}-black-tie:before { content: $fa-var-black-tie; }
.#{$fa-css-prefix}-fonticons:before { content: $fa-var-fonticons; }
.#{$fa-css-prefix}-reddit-alien:before { content: $fa-var-reddit-alien; }
.#{$fa-css-prefix}-edge:before { content: $fa-var-edge; }
.#{$fa-css-prefix}-credit-card-alt:before { content: $fa-var-credit-card-alt; }
.#{$fa-css-prefix}-codiepie:before { content: $fa-var-codiepie; }
.#{$fa-css-prefix}-modx:before { content: $fa-var-modx; }
.#{$fa-css-prefix}-fort-awesome:before { content: $fa-var-fort-awesome; }
.#{$fa-css-prefix}-usb:before { content: $fa-var-usb; }
.#{$fa-css-prefix}-product-hunt:before { content: $fa-var-product-hunt; }
.#{$fa-css-prefix}-mixcloud:before { content: $fa-var-mixcloud; }
.#{$fa-css-prefix}-scribd:before { content: $fa-var-scribd; }
.#{$fa-css-prefix}-pause-circle:before { content: $fa-var-pause-circle; }
.#{$fa-css-prefix}-pause-circle-o:before { content: $fa-var-pause-circle-o; }
.#{$fa-css-prefix}-stop-circle:before { content: $fa-var-stop-circle; }
.#{$fa-css-prefix}-stop-circle-o:before { content: $fa-var-stop-circle-o; }
.#{$fa-css-prefix}-shopping-bag:before { content: $fa-var-shopping-bag; }
.#{$fa-css-prefix}-shopping-basket:before { content: $fa-var-shopping-basket; }
.#{$fa-css-prefix}-hashtag:before { content: $fa-var-hashtag; }
.#{$fa-css-prefix}-bluetooth:before { content: $fa-var-bluetooth; }
.#{$fa-css-prefix}-bluetooth-b:before { content: $fa-var-bluetooth-b; }
.#{$fa-css-prefix}-percent:before { content: $fa-var-percent; }
.#{$fa-css-prefix}-gitlab:before { content: $fa-var-gitlab; }
.#{$fa-css-prefix}-wpbeginner:before { content: $fa-var-wpbeginner; }
.#{$fa-css-prefix}-wpforms:before { content: $fa-var-wpforms; }
.#{$fa-css-prefix}-envira:before { content: $fa-var-envira; }
.#{$fa-css-prefix}-universal-access:before { content: $fa-var-universal-access; }
.#{$fa-css-prefix}-wheelchair-alt:before { content: $fa-var-wheelchair-alt; }
.#{$fa-css-prefix}-question-circle-o:before { content: $fa-var-question-circle-o; }
.#{$fa-css-prefix}-blind:before { content: $fa-var-blind; }
.#{$fa-css-prefix}-audio-description:before { content: $fa-var-audio-description; }
.#{$fa-css-prefix}-volume-control-phone:before { content: $fa-var-volume-control-phone; }
.#{$fa-css-prefix}-braille:before { content: $fa-var-braille; }
.#{$fa-css-prefix}-assistive-listening-systems:before { content: $fa-var-assistive-listening-systems; }
.#{$fa-css-prefix}-asl-interpreting:before,
.#{$fa-css-prefix}-american-sign-language-interpreting:before { content: $fa-var-american-sign-language-interpreting; }
.#{$fa-css-prefix}-deafness:before,
.#{$fa-css-prefix}-hard-of-hearing:before,
.#{$fa-css-prefix}-deaf:before { content: $fa-var-deaf; }
.#{$fa-css-prefix}-glide:before { content: $fa-var-glide; }
.#{$fa-css-prefix}-glide-g:before { content: $fa-var-glide-g; }
.#{$fa-css-prefix}-signing:before,
.#{$fa-css-prefix}-sign-language:before { content: $fa-var-sign-language; }
.#{$fa-css-prefix}-low-vision:before { content: $fa-var-low-vision; }
.#{$fa-css-prefix}-viadeo:before { content: $fa-var-viadeo; }
.#{$fa-css-prefix}-viadeo-square:before { content: $fa-var-viadeo-square; }
.#{$fa-css-prefix}-snapchat:before { content: $fa-var-snapchat; }
.#{$fa-css-prefix}-snapchat-ghost:before { content: $fa-var-snapchat-ghost; }
.#{$fa-css-prefix}-snapchat-square:before { content: $fa-var-snapchat-square; }
.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; }
.#{$fa-css-prefix}-first-order:before { content: $fa-var-first-order; }
.#{$fa-css-prefix}-yoast:before { content: $fa-var-yoast; }
.#{$fa-css-prefix}-themeisle:before { content: $fa-var-themeisle; }
.#{$fa-css-prefix}-google-plus-circle:before,
.#{$fa-css-prefix}-google-plus-official:before { content: $fa-var-google-plus-official; }
.#{$fa-css-prefix}-fa:before,
.#{$fa-css-prefix}-font-awesome:before { content: $fa-var-font-awesome; }
.#{$fa-css-prefix}-handshake-o:before { content: $fa-var-handshake-o; }
.#{$fa-css-prefix}-envelope-open:before { content: $fa-var-envelope-open; }
.#{$fa-css-prefix}-envelope-open-o:before { content: $fa-var-envelope-open-o; }
.#{$fa-css-prefix}-linode:before { content: $fa-var-linode; }
.#{$fa-css-prefix}-address-book:before { content: $fa-var-address-book; }
.#{$fa-css-prefix}-address-book-o:before { content: $fa-var-address-book-o; }
.#{$fa-css-prefix}-vcard:before,
.#{$fa-css-prefix}-address-card:before { content: $fa-var-address-card; }
.#{$fa-css-prefix}-vcard-o:before,
.#{$fa-css-prefix}-address-card-o:before { content: $fa-var-address-card-o; }
.#{$fa-css-prefix}-user-circle:before { content: $fa-var-user-circle; }
.#{$fa-css-prefix}-user-circle-o:before { content: $fa-var-user-circle-o; }
.#{$fa-css-prefix}-user-o:before { content: $fa-var-user-o; }
.#{$fa-css-prefix}-id-badge:before { content: $fa-var-id-badge; }
.#{$fa-css-prefix}-drivers-license:before,
.#{$fa-css-prefix}-id-card:before { content: $fa-var-id-card; }
.#{$fa-css-prefix}-drivers-license-o:before,
.#{$fa-css-prefix}-id-card-o:before { content: $fa-var-id-card-o; }
.#{$fa-css-prefix}-quora:before { content: $fa-var-quora; }
.#{$fa-css-prefix}-free-code-camp:before { content: $fa-var-free-code-camp; }
.#{$fa-css-prefix}-telegram:before { content: $fa-var-telegram; }
.#{$fa-css-prefix}-thermometer-4:before,
.#{$fa-css-prefix}-thermometer:before,
.#{$fa-css-prefix}-thermometer-full:before { content: $fa-var-thermometer-full; }
.#{$fa-css-prefix}-thermometer-3:before,
.#{$fa-css-prefix}-thermometer-three-quarters:before { content: $fa-var-thermometer-three-quarters; }
.#{$fa-css-prefix}-thermometer-2:before,
.#{$fa-css-prefix}-thermometer-half:before { content: $fa-var-thermometer-half; }
.#{$fa-css-prefix}-thermometer-1:before,
.#{$fa-css-prefix}-thermometer-quarter:before { content: $fa-var-thermometer-quarter; }
.#{$fa-css-prefix}-thermometer-0:before,
.#{$fa-css-prefix}-thermometer-empty:before { content: $fa-var-thermometer-empty; }
.#{$fa-css-prefix}-shower:before { content: $fa-var-shower; }
.#{$fa-css-prefix}-bathtub:before,
.#{$fa-css-prefix}-s15:before,
.#{$fa-css-prefix}-bath:before { content: $fa-var-bath; }
.#{$fa-css-prefix}-podcast:before { content: $fa-var-podcast; }
.#{$fa-css-prefix}-window-maximize:before { content: $fa-var-window-maximize; }
.#{$fa-css-prefix}-window-minimize:before { content: $fa-var-window-minimize; }
.#{$fa-css-prefix}-window-restore:before { content: $fa-var-window-restore; }
.#{$fa-css-prefix}-times-rectangle:before,
.#{$fa-css-prefix}-window-close:before { content: $fa-var-window-close; }
.#{$fa-css-prefix}-times-rectangle-o:before,
.#{$fa-css-prefix}-window-close-o:before { content: $fa-var-window-close-o; }
.#{$fa-css-prefix}-bandcamp:before { content: $fa-var-bandcamp; }
.#{$fa-css-prefix}-grav:before { content: $fa-var-grav; }
.#{$fa-css-prefix}-etsy:before { content: $fa-var-etsy; }
.#{$fa-css-prefix}-imdb:before { content: $fa-var-imdb; }
.#{$fa-css-prefix}-ravelry:before { content: $fa-var-ravelry; }
.#{$fa-css-prefix}-eercast:before { content: $fa-var-eercast; }
.#{$fa-css-prefix}-microchip:before { content: $fa-var-microchip; }
.#{$fa-css-prefix}-snowflake-o:before { content: $fa-var-snowflake-o; }
.#{$fa-css-prefix}-superpowers:before { content: $fa-var-superpowers; }
.#{$fa-css-prefix}-wpexplorer:before { content: $fa-var-wpexplorer; }
.#{$fa-css-prefix}-meetup:before { content: $fa-var-meetup; }

View File

@ -3,58 +3,23 @@
@mixin fa-icon() {
display: inline-block;
font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
font: normal normal normal 14px/1 FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@mixin fa-icon-rotate($degrees, $rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
-webkit-transform: rotate($degrees);
-ms-transform: rotate($degrees);
transform: rotate($degrees);
}
@mixin fa-icon-flip($horiz, $vert, $rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)";
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
-webkit-transform: scale($horiz, $vert);
-ms-transform: scale($horiz, $vert);
transform: scale($horiz, $vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
@mixin sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
@mixin sr-only-focusable {
&:active,
&:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
}

View File

@ -5,11 +5,10 @@
font-family: 'FontAwesome';
src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
// src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
//src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-weight: normal;
font-style: normal;
}

View File

@ -1,5 +0,0 @@
// Screen Readers
// -------------------------
.sr-only { @include sr-only(); }
.sr-only-focusable { @include sr-only-focusable(); }

View File

@ -6,11 +6,6 @@
animation: fa-spin 2s infinite linear;
}
.#{$fa-css-prefix}-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);

View File

@ -2,29 +2,20 @@
// --------------------------
$fa-font-path: "../fonts" !default;
$fa-font-size-base: 14px !default;
$fa-line-height-base: 1 !default;
//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly
//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts" !default; // for referencing Bootstrap CDN font files directly
$fa-css-prefix: fa !default;
$fa-version: "4.7.0" !default;
$fa-version: "4.2.0" !default;
$fa-border-color: #eee !default;
$fa-inverse: #fff !default;
$fa-li-width: (30em / 14) !default;
$fa-var-500px: "\f26e";
$fa-var-address-book: "\f2b9";
$fa-var-address-book-o: "\f2ba";
$fa-var-address-card: "\f2bb";
$fa-var-address-card-o: "\f2bc";
$fa-var-adjust: "\f042";
$fa-var-adn: "\f170";
$fa-var-align-center: "\f037";
$fa-var-align-justify: "\f039";
$fa-var-align-left: "\f036";
$fa-var-align-right: "\f038";
$fa-var-amazon: "\f270";
$fa-var-ambulance: "\f0f9";
$fa-var-american-sign-language-interpreting: "\f2a3";
$fa-var-anchor: "\f13d";
$fa-var-android: "\f17b";
$fa-var-angellist: "\f209";
@ -55,35 +46,16 @@ $fa-var-arrows: "\f047";
$fa-var-arrows-alt: "\f0b2";
$fa-var-arrows-h: "\f07e";
$fa-var-arrows-v: "\f07d";
$fa-var-asl-interpreting: "\f2a3";
$fa-var-assistive-listening-systems: "\f2a2";
$fa-var-asterisk: "\f069";
$fa-var-at: "\f1fa";
$fa-var-audio-description: "\f29e";
$fa-var-automobile: "\f1b9";
$fa-var-backward: "\f04a";
$fa-var-balance-scale: "\f24e";
$fa-var-ban: "\f05e";
$fa-var-bandcamp: "\f2d5";
$fa-var-bank: "\f19c";
$fa-var-bar-chart: "\f080";
$fa-var-bar-chart-o: "\f080";
$fa-var-barcode: "\f02a";
$fa-var-bars: "\f0c9";
$fa-var-bath: "\f2cd";
$fa-var-bathtub: "\f2cd";
$fa-var-battery: "\f240";
$fa-var-battery-0: "\f244";
$fa-var-battery-1: "\f243";
$fa-var-battery-2: "\f242";
$fa-var-battery-3: "\f241";
$fa-var-battery-4: "\f240";
$fa-var-battery-empty: "\f244";
$fa-var-battery-full: "\f240";
$fa-var-battery-half: "\f242";
$fa-var-battery-quarter: "\f243";
$fa-var-battery-three-quarters: "\f241";
$fa-var-bed: "\f236";
$fa-var-beer: "\f0fc";
$fa-var-behance: "\f1b4";
$fa-var-behance-square: "\f1b5";
@ -97,17 +69,12 @@ $fa-var-birthday-cake: "\f1fd";
$fa-var-bitbucket: "\f171";
$fa-var-bitbucket-square: "\f172";
$fa-var-bitcoin: "\f15a";
$fa-var-black-tie: "\f27e";
$fa-var-blind: "\f29d";
$fa-var-bluetooth: "\f293";
$fa-var-bluetooth-b: "\f294";
$fa-var-bold: "\f032";
$fa-var-bolt: "\f0e7";
$fa-var-bomb: "\f1e2";
$fa-var-book: "\f02d";
$fa-var-bookmark: "\f02e";
$fa-var-bookmark-o: "\f097";
$fa-var-braille: "\f2a1";
$fa-var-briefcase: "\f0b1";
$fa-var-btc: "\f15a";
$fa-var-bug: "\f188";
@ -116,15 +83,10 @@ $fa-var-building-o: "\f0f7";
$fa-var-bullhorn: "\f0a1";
$fa-var-bullseye: "\f140";
$fa-var-bus: "\f207";
$fa-var-buysellads: "\f20d";
$fa-var-cab: "\f1ba";
$fa-var-calculator: "\f1ec";
$fa-var-calendar: "\f073";
$fa-var-calendar-check-o: "\f274";
$fa-var-calendar-minus-o: "\f272";
$fa-var-calendar-o: "\f133";
$fa-var-calendar-plus-o: "\f271";
$fa-var-calendar-times-o: "\f273";
$fa-var-camera: "\f030";
$fa-var-camera-retro: "\f083";
$fa-var-car: "\f1b9";
@ -136,13 +98,9 @@ $fa-var-caret-square-o-left: "\f191";
$fa-var-caret-square-o-right: "\f152";
$fa-var-caret-square-o-up: "\f151";
$fa-var-caret-up: "\f0d8";
$fa-var-cart-arrow-down: "\f218";
$fa-var-cart-plus: "\f217";
$fa-var-cc: "\f20a";
$fa-var-cc-amex: "\f1f3";
$fa-var-cc-diners-club: "\f24c";
$fa-var-cc-discover: "\f1f2";
$fa-var-cc-jcb: "\f24b";
$fa-var-cc-mastercard: "\f1f1";
$fa-var-cc-paypal: "\f1f4";
$fa-var-cc-stripe: "\f1f5";
@ -164,14 +122,12 @@ $fa-var-chevron-left: "\f053";
$fa-var-chevron-right: "\f054";
$fa-var-chevron-up: "\f077";
$fa-var-child: "\f1ae";
$fa-var-chrome: "\f268";
$fa-var-circle: "\f111";
$fa-var-circle-o: "\f10c";
$fa-var-circle-o-notch: "\f1ce";
$fa-var-circle-thin: "\f1db";
$fa-var-clipboard: "\f0ea";
$fa-var-clock-o: "\f017";
$fa-var-clone: "\f24d";
$fa-var-close: "\f00d";
$fa-var-cloud: "\f0c2";
$fa-var-cloud-download: "\f0ed";
@ -180,26 +136,19 @@ $fa-var-cny: "\f157";
$fa-var-code: "\f121";
$fa-var-code-fork: "\f126";
$fa-var-codepen: "\f1cb";
$fa-var-codiepie: "\f284";
$fa-var-coffee: "\f0f4";
$fa-var-cog: "\f013";
$fa-var-cogs: "\f085";
$fa-var-columns: "\f0db";
$fa-var-comment: "\f075";
$fa-var-comment-o: "\f0e5";
$fa-var-commenting: "\f27a";
$fa-var-commenting-o: "\f27b";
$fa-var-comments: "\f086";
$fa-var-comments-o: "\f0e6";
$fa-var-compass: "\f14e";
$fa-var-compress: "\f066";
$fa-var-connectdevelop: "\f20e";
$fa-var-contao: "\f26d";
$fa-var-copy: "\f0c5";
$fa-var-copyright: "\f1f9";
$fa-var-creative-commons: "\f25e";
$fa-var-credit-card: "\f09d";
$fa-var-credit-card-alt: "\f283";
$fa-var-crop: "\f125";
$fa-var-crosshairs: "\f05b";
$fa-var-css3: "\f13c";
@ -208,39 +157,27 @@ $fa-var-cubes: "\f1b3";
$fa-var-cut: "\f0c4";
$fa-var-cutlery: "\f0f5";
$fa-var-dashboard: "\f0e4";
$fa-var-dashcube: "\f210";
$fa-var-database: "\f1c0";
$fa-var-deaf: "\f2a4";
$fa-var-deafness: "\f2a4";
$fa-var-dedent: "\f03b";
$fa-var-delicious: "\f1a5";
$fa-var-desktop: "\f108";
$fa-var-deviantart: "\f1bd";
$fa-var-diamond: "\f219";
$fa-var-digg: "\f1a6";
$fa-var-dollar: "\f155";
$fa-var-dot-circle-o: "\f192";
$fa-var-download: "\f019";
$fa-var-dribbble: "\f17d";
$fa-var-drivers-license: "\f2c2";
$fa-var-drivers-license-o: "\f2c3";
$fa-var-dropbox: "\f16b";
$fa-var-drupal: "\f1a9";
$fa-var-edge: "\f282";
$fa-var-edit: "\f044";
$fa-var-eercast: "\f2da";
$fa-var-eject: "\f052";
$fa-var-ellipsis-h: "\f141";
$fa-var-ellipsis-v: "\f142";
$fa-var-empire: "\f1d1";
$fa-var-envelope: "\f0e0";
$fa-var-envelope-o: "\f003";
$fa-var-envelope-open: "\f2b6";
$fa-var-envelope-open-o: "\f2b7";
$fa-var-envelope-square: "\f199";
$fa-var-envira: "\f299";
$fa-var-eraser: "\f12d";
$fa-var-etsy: "\f2d7";
$fa-var-eur: "\f153";
$fa-var-euro: "\f153";
$fa-var-exchange: "\f0ec";
@ -248,21 +185,16 @@ $fa-var-exclamation: "\f12a";
$fa-var-exclamation-circle: "\f06a";
$fa-var-exclamation-triangle: "\f071";
$fa-var-expand: "\f065";
$fa-var-expeditedssl: "\f23e";
$fa-var-external-link: "\f08e";
$fa-var-external-link-square: "\f14c";
$fa-var-eye: "\f06e";
$fa-var-eye-slash: "\f070";
$fa-var-eyedropper: "\f1fb";
$fa-var-fa: "\f2b4";
$fa-var-facebook: "\f09a";
$fa-var-facebook-f: "\f09a";
$fa-var-facebook-official: "\f230";
$fa-var-facebook-square: "\f082";
$fa-var-fast-backward: "\f049";
$fa-var-fast-forward: "\f050";
$fa-var-fax: "\f1ac";
$fa-var-feed: "\f09e";
$fa-var-female: "\f182";
$fa-var-fighter-jet: "\f0fb";
$fa-var-file: "\f15b";
@ -288,8 +220,6 @@ $fa-var-film: "\f008";
$fa-var-filter: "\f0b0";
$fa-var-fire: "\f06d";
$fa-var-fire-extinguisher: "\f134";
$fa-var-firefox: "\f269";
$fa-var-first-order: "\f2b0";
$fa-var-flag: "\f024";
$fa-var-flag-checkered: "\f11e";
$fa-var-flag-o: "\f11d";
@ -302,13 +232,8 @@ $fa-var-folder-o: "\f114";
$fa-var-folder-open: "\f07c";
$fa-var-folder-open-o: "\f115";
$fa-var-font: "\f031";
$fa-var-font-awesome: "\f2b4";
$fa-var-fonticons: "\f280";
$fa-var-fort-awesome: "\f286";
$fa-var-forumbee: "\f211";
$fa-var-forward: "\f04e";
$fa-var-foursquare: "\f180";
$fa-var-free-code-camp: "\f2c5";
$fa-var-frown-o: "\f119";
$fa-var-futbol-o: "\f1e3";
$fa-var-gamepad: "\f11b";
@ -317,87 +242,45 @@ $fa-var-gbp: "\f154";
$fa-var-ge: "\f1d1";
$fa-var-gear: "\f013";
$fa-var-gears: "\f085";
$fa-var-genderless: "\f22d";
$fa-var-get-pocket: "\f265";
$fa-var-gg: "\f260";
$fa-var-gg-circle: "\f261";
$fa-var-gift: "\f06b";
$fa-var-git: "\f1d3";
$fa-var-git-square: "\f1d2";
$fa-var-github: "\f09b";
$fa-var-github-alt: "\f113";
$fa-var-github-square: "\f092";
$fa-var-gitlab: "\f296";
$fa-var-gittip: "\f184";
$fa-var-glass: "\f000";
$fa-var-glide: "\f2a5";
$fa-var-glide-g: "\f2a6";
$fa-var-globe: "\f0ac";
$fa-var-google: "\f1a0";
$fa-var-google-plus: "\f0d5";
$fa-var-google-plus-circle: "\f2b3";
$fa-var-google-plus-official: "\f2b3";
$fa-var-google-plus-square: "\f0d4";
$fa-var-google-wallet: "\f1ee";
$fa-var-graduation-cap: "\f19d";
$fa-var-gratipay: "\f184";
$fa-var-grav: "\f2d6";
$fa-var-group: "\f0c0";
$fa-var-h-square: "\f0fd";
$fa-var-hacker-news: "\f1d4";
$fa-var-hand-grab-o: "\f255";
$fa-var-hand-lizard-o: "\f258";
$fa-var-hand-o-down: "\f0a7";
$fa-var-hand-o-left: "\f0a5";
$fa-var-hand-o-right: "\f0a4";
$fa-var-hand-o-up: "\f0a6";
$fa-var-hand-paper-o: "\f256";
$fa-var-hand-peace-o: "\f25b";
$fa-var-hand-pointer-o: "\f25a";
$fa-var-hand-rock-o: "\f255";
$fa-var-hand-scissors-o: "\f257";
$fa-var-hand-spock-o: "\f259";
$fa-var-hand-stop-o: "\f256";
$fa-var-handshake-o: "\f2b5";
$fa-var-hard-of-hearing: "\f2a4";
$fa-var-hashtag: "\f292";
$fa-var-hdd-o: "\f0a0";
$fa-var-header: "\f1dc";
$fa-var-headphones: "\f025";
$fa-var-heart: "\f004";
$fa-var-heart-o: "\f08a";
$fa-var-heartbeat: "\f21e";
$fa-var-history: "\f1da";
$fa-var-home: "\f015";
$fa-var-hospital-o: "\f0f8";
$fa-var-hotel: "\f236";
$fa-var-hourglass: "\f254";
$fa-var-hourglass-1: "\f251";
$fa-var-hourglass-2: "\f252";
$fa-var-hourglass-3: "\f253";
$fa-var-hourglass-end: "\f253";
$fa-var-hourglass-half: "\f252";
$fa-var-hourglass-o: "\f250";
$fa-var-hourglass-start: "\f251";
$fa-var-houzz: "\f27c";
$fa-var-html5: "\f13b";
$fa-var-i-cursor: "\f246";
$fa-var-id-badge: "\f2c1";
$fa-var-id-card: "\f2c2";
$fa-var-id-card-o: "\f2c3";
$fa-var-ils: "\f20b";
$fa-var-image: "\f03e";
$fa-var-imdb: "\f2d8";
$fa-var-inbox: "\f01c";
$fa-var-indent: "\f03c";
$fa-var-industry: "\f275";
$fa-var-info: "\f129";
$fa-var-info-circle: "\f05a";
$fa-var-inr: "\f156";
$fa-var-instagram: "\f16d";
$fa-var-institution: "\f19c";
$fa-var-internet-explorer: "\f26b";
$fa-var-intersex: "\f224";
$fa-var-ioxhost: "\f208";
$fa-var-italic: "\f033";
$fa-var-joomla: "\f1aa";
@ -411,7 +294,6 @@ $fa-var-laptop: "\f109";
$fa-var-lastfm: "\f202";
$fa-var-lastfm-square: "\f203";
$fa-var-leaf: "\f06c";
$fa-var-leanpub: "\f212";
$fa-var-legal: "\f0e3";
$fa-var-lemon-o: "\f094";
$fa-var-level-down: "\f149";
@ -425,7 +307,6 @@ $fa-var-line-chart: "\f201";
$fa-var-link: "\f0c1";
$fa-var-linkedin: "\f0e1";
$fa-var-linkedin-square: "\f08c";
$fa-var-linode: "\f2b8";
$fa-var-linux: "\f17c";
$fa-var-list: "\f03a";
$fa-var-list-alt: "\f022";
@ -437,58 +318,32 @@ $fa-var-long-arrow-down: "\f175";
$fa-var-long-arrow-left: "\f177";
$fa-var-long-arrow-right: "\f178";
$fa-var-long-arrow-up: "\f176";
$fa-var-low-vision: "\f2a8";
$fa-var-magic: "\f0d0";
$fa-var-magnet: "\f076";
$fa-var-mail-forward: "\f064";
$fa-var-mail-reply: "\f112";
$fa-var-mail-reply-all: "\f122";
$fa-var-male: "\f183";
$fa-var-map: "\f279";
$fa-var-map-marker: "\f041";
$fa-var-map-o: "\f278";
$fa-var-map-pin: "\f276";
$fa-var-map-signs: "\f277";
$fa-var-mars: "\f222";
$fa-var-mars-double: "\f227";
$fa-var-mars-stroke: "\f229";
$fa-var-mars-stroke-h: "\f22b";
$fa-var-mars-stroke-v: "\f22a";
$fa-var-maxcdn: "\f136";
$fa-var-meanpath: "\f20c";
$fa-var-medium: "\f23a";
$fa-var-medkit: "\f0fa";
$fa-var-meetup: "\f2e0";
$fa-var-meh-o: "\f11a";
$fa-var-mercury: "\f223";
$fa-var-microchip: "\f2db";
$fa-var-microphone: "\f130";
$fa-var-microphone-slash: "\f131";
$fa-var-minus: "\f068";
$fa-var-minus-circle: "\f056";
$fa-var-minus-square: "\f146";
$fa-var-minus-square-o: "\f147";
$fa-var-mixcloud: "\f289";
$fa-var-mobile: "\f10b";
$fa-var-mobile-phone: "\f10b";
$fa-var-modx: "\f285";
$fa-var-money: "\f0d6";
$fa-var-moon-o: "\f186";
$fa-var-mortar-board: "\f19d";
$fa-var-motorcycle: "\f21c";
$fa-var-mouse-pointer: "\f245";
$fa-var-music: "\f001";
$fa-var-navicon: "\f0c9";
$fa-var-neuter: "\f22c";
$fa-var-newspaper-o: "\f1ea";
$fa-var-object-group: "\f247";
$fa-var-object-ungroup: "\f248";
$fa-var-odnoklassniki: "\f263";
$fa-var-odnoklassniki-square: "\f264";
$fa-var-opencart: "\f23d";
$fa-var-openid: "\f19b";
$fa-var-opera: "\f26a";
$fa-var-optin-monster: "\f23c";
$fa-var-outdent: "\f03b";
$fa-var-pagelines: "\f18c";
$fa-var-paint-brush: "\f1fc";
@ -498,24 +353,19 @@ $fa-var-paperclip: "\f0c6";
$fa-var-paragraph: "\f1dd";
$fa-var-paste: "\f0ea";
$fa-var-pause: "\f04c";
$fa-var-pause-circle: "\f28b";
$fa-var-pause-circle-o: "\f28c";
$fa-var-paw: "\f1b0";
$fa-var-paypal: "\f1ed";
$fa-var-pencil: "\f040";
$fa-var-pencil-square: "\f14b";
$fa-var-pencil-square-o: "\f044";
$fa-var-percent: "\f295";
$fa-var-phone: "\f095";
$fa-var-phone-square: "\f098";
$fa-var-photo: "\f03e";
$fa-var-picture-o: "\f03e";
$fa-var-pie-chart: "\f200";
$fa-var-pied-piper: "\f2ae";
$fa-var-pied-piper: "\f1a7";
$fa-var-pied-piper-alt: "\f1a8";
$fa-var-pied-piper-pp: "\f1a7";
$fa-var-pinterest: "\f0d2";
$fa-var-pinterest-p: "\f231";
$fa-var-pinterest-square: "\f0d3";
$fa-var-plane: "\f072";
$fa-var-play: "\f04b";
@ -526,36 +376,28 @@ $fa-var-plus: "\f067";
$fa-var-plus-circle: "\f055";
$fa-var-plus-square: "\f0fe";
$fa-var-plus-square-o: "\f196";
$fa-var-podcast: "\f2ce";
$fa-var-power-off: "\f011";
$fa-var-print: "\f02f";
$fa-var-product-hunt: "\f288";
$fa-var-puzzle-piece: "\f12e";
$fa-var-qq: "\f1d6";
$fa-var-qrcode: "\f029";
$fa-var-question: "\f128";
$fa-var-question-circle: "\f059";
$fa-var-question-circle-o: "\f29c";
$fa-var-quora: "\f2c4";
$fa-var-quote-left: "\f10d";
$fa-var-quote-right: "\f10e";
$fa-var-ra: "\f1d0";
$fa-var-random: "\f074";
$fa-var-ravelry: "\f2d9";
$fa-var-rebel: "\f1d0";
$fa-var-recycle: "\f1b8";
$fa-var-reddit: "\f1a1";
$fa-var-reddit-alien: "\f281";
$fa-var-reddit-square: "\f1a2";
$fa-var-refresh: "\f021";
$fa-var-registered: "\f25d";
$fa-var-remove: "\f00d";
$fa-var-renren: "\f18b";
$fa-var-reorder: "\f0c9";
$fa-var-repeat: "\f01e";
$fa-var-reply: "\f112";
$fa-var-reply-all: "\f122";
$fa-var-resistance: "\f1d0";
$fa-var-retweet: "\f079";
$fa-var-rmb: "\f157";
$fa-var-road: "\f018";
@ -568,18 +410,13 @@ $fa-var-rss-square: "\f143";
$fa-var-rub: "\f158";
$fa-var-ruble: "\f158";
$fa-var-rupee: "\f156";
$fa-var-s15: "\f2cd";
$fa-var-safari: "\f267";
$fa-var-save: "\f0c7";
$fa-var-scissors: "\f0c4";
$fa-var-scribd: "\f28a";
$fa-var-search: "\f002";
$fa-var-search-minus: "\f010";
$fa-var-search-plus: "\f00e";
$fa-var-sellsy: "\f213";
$fa-var-send: "\f1d8";
$fa-var-send-o: "\f1d9";
$fa-var-server: "\f233";
$fa-var-share: "\f064";
$fa-var-share-alt: "\f1e0";
$fa-var-share-alt-square: "\f1e1";
@ -588,29 +425,16 @@ $fa-var-share-square-o: "\f045";
$fa-var-shekel: "\f20b";
$fa-var-sheqel: "\f20b";
$fa-var-shield: "\f132";
$fa-var-ship: "\f21a";
$fa-var-shirtsinbulk: "\f214";
$fa-var-shopping-bag: "\f290";
$fa-var-shopping-basket: "\f291";
$fa-var-shopping-cart: "\f07a";
$fa-var-shower: "\f2cc";
$fa-var-sign-in: "\f090";
$fa-var-sign-language: "\f2a7";
$fa-var-sign-out: "\f08b";
$fa-var-signal: "\f012";
$fa-var-signing: "\f2a7";
$fa-var-simplybuilt: "\f215";
$fa-var-sitemap: "\f0e8";
$fa-var-skyatlas: "\f216";
$fa-var-skype: "\f17e";
$fa-var-slack: "\f198";
$fa-var-sliders: "\f1de";
$fa-var-slideshare: "\f1e7";
$fa-var-smile-o: "\f118";
$fa-var-snapchat: "\f2ab";
$fa-var-snapchat-ghost: "\f2ac";
$fa-var-snapchat-square: "\f2ad";
$fa-var-snowflake-o: "\f2dc";
$fa-var-soccer-ball-o: "\f1e3";
$fa-var-sort: "\f0dc";
$fa-var-sort-alpha-asc: "\f15d";
@ -643,20 +467,13 @@ $fa-var-steam-square: "\f1b7";
$fa-var-step-backward: "\f048";
$fa-var-step-forward: "\f051";
$fa-var-stethoscope: "\f0f1";
$fa-var-sticky-note: "\f249";
$fa-var-sticky-note-o: "\f24a";
$fa-var-stop: "\f04d";
$fa-var-stop-circle: "\f28d";
$fa-var-stop-circle-o: "\f28e";
$fa-var-street-view: "\f21d";
$fa-var-strikethrough: "\f0cc";
$fa-var-stumbleupon: "\f1a4";
$fa-var-stumbleupon-circle: "\f1a3";
$fa-var-subscript: "\f12c";
$fa-var-subway: "\f239";
$fa-var-suitcase: "\f0f2";
$fa-var-sun-o: "\f185";
$fa-var-superpowers: "\f2dd";
$fa-var-superscript: "\f12b";
$fa-var-support: "\f1cd";
$fa-var-table: "\f0ce";
@ -666,8 +483,6 @@ $fa-var-tag: "\f02b";
$fa-var-tags: "\f02c";
$fa-var-tasks: "\f0ae";
$fa-var-taxi: "\f1ba";
$fa-var-telegram: "\f2c6";
$fa-var-television: "\f26c";
$fa-var-tencent-weibo: "\f1d5";
$fa-var-terminal: "\f120";
$fa-var-text-height: "\f034";
@ -675,18 +490,6 @@ $fa-var-text-width: "\f035";
$fa-var-th: "\f00a";
$fa-var-th-large: "\f009";
$fa-var-th-list: "\f00b";
$fa-var-themeisle: "\f2b2";
$fa-var-thermometer: "\f2c7";
$fa-var-thermometer-0: "\f2cb";
$fa-var-thermometer-1: "\f2ca";
$fa-var-thermometer-2: "\f2c9";
$fa-var-thermometer-3: "\f2c8";
$fa-var-thermometer-4: "\f2c7";
$fa-var-thermometer-empty: "\f2cb";
$fa-var-thermometer-full: "\f2c7";
$fa-var-thermometer-half: "\f2c9";
$fa-var-thermometer-quarter: "\f2ca";
$fa-var-thermometer-three-quarters: "\f2c8";
$fa-var-thumb-tack: "\f08d";
$fa-var-thumbs-down: "\f165";
$fa-var-thumbs-o-down: "\f088";
@ -696,8 +499,6 @@ $fa-var-ticket: "\f145";
$fa-var-times: "\f00d";
$fa-var-times-circle: "\f057";
$fa-var-times-circle-o: "\f05c";
$fa-var-times-rectangle: "\f2d3";
$fa-var-times-rectangle-o: "\f2d4";
$fa-var-tint: "\f043";
$fa-var-toggle-down: "\f150";
$fa-var-toggle-left: "\f191";
@ -705,15 +506,10 @@ $fa-var-toggle-off: "\f204";
$fa-var-toggle-on: "\f205";
$fa-var-toggle-right: "\f152";
$fa-var-toggle-up: "\f151";
$fa-var-trademark: "\f25c";
$fa-var-train: "\f238";
$fa-var-transgender: "\f224";
$fa-var-transgender-alt: "\f225";
$fa-var-trash: "\f1f8";
$fa-var-trash-o: "\f014";
$fa-var-tree: "\f1bb";
$fa-var-trello: "\f181";
$fa-var-tripadvisor: "\f262";
$fa-var-trophy: "\f091";
$fa-var-truck: "\f0d1";
$fa-var-try: "\f195";
@ -721,45 +517,26 @@ $fa-var-tty: "\f1e4";
$fa-var-tumblr: "\f173";
$fa-var-tumblr-square: "\f174";
$fa-var-turkish-lira: "\f195";
$fa-var-tv: "\f26c";
$fa-var-twitch: "\f1e8";
$fa-var-twitter: "\f099";
$fa-var-twitter-square: "\f081";
$fa-var-umbrella: "\f0e9";
$fa-var-underline: "\f0cd";
$fa-var-undo: "\f0e2";
$fa-var-universal-access: "\f29a";
$fa-var-university: "\f19c";
$fa-var-unlink: "\f127";
$fa-var-unlock: "\f09c";
$fa-var-unlock-alt: "\f13e";
$fa-var-unsorted: "\f0dc";
$fa-var-upload: "\f093";
$fa-var-usb: "\f287";
$fa-var-usd: "\f155";
$fa-var-user: "\f007";
$fa-var-user-circle: "\f2bd";
$fa-var-user-circle-o: "\f2be";
$fa-var-user-md: "\f0f0";
$fa-var-user-o: "\f2c0";
$fa-var-user-plus: "\f234";
$fa-var-user-secret: "\f21b";
$fa-var-user-times: "\f235";
$fa-var-users: "\f0c0";
$fa-var-vcard: "\f2bb";
$fa-var-vcard-o: "\f2bc";
$fa-var-venus: "\f221";
$fa-var-venus-double: "\f226";
$fa-var-venus-mars: "\f228";
$fa-var-viacoin: "\f237";
$fa-var-viadeo: "\f2a9";
$fa-var-viadeo-square: "\f2aa";
$fa-var-video-camera: "\f03d";
$fa-var-vimeo: "\f27d";
$fa-var-vimeo-square: "\f194";
$fa-var-vine: "\f1ca";
$fa-var-vk: "\f189";
$fa-var-volume-control-phone: "\f2a0";
$fa-var-volume-down: "\f027";
$fa-var-volume-off: "\f026";
$fa-var-volume-up: "\f028";
@ -767,33 +544,17 @@ $fa-var-warning: "\f071";
$fa-var-wechat: "\f1d7";
$fa-var-weibo: "\f18a";
$fa-var-weixin: "\f1d7";
$fa-var-whatsapp: "\f232";
$fa-var-wheelchair: "\f193";
$fa-var-wheelchair-alt: "\f29b";
$fa-var-wifi: "\f1eb";
$fa-var-wikipedia-w: "\f266";
$fa-var-window-close: "\f2d3";
$fa-var-window-close-o: "\f2d4";
$fa-var-window-maximize: "\f2d0";
$fa-var-window-minimize: "\f2d1";
$fa-var-window-restore: "\f2d2";
$fa-var-windows: "\f17a";
$fa-var-won: "\f159";
$fa-var-wordpress: "\f19a";
$fa-var-wpbeginner: "\f297";
$fa-var-wpexplorer: "\f2de";
$fa-var-wpforms: "\f298";
$fa-var-wrench: "\f0ad";
$fa-var-xing: "\f168";
$fa-var-xing-square: "\f169";
$fa-var-y-combinator: "\f23b";
$fa-var-y-combinator-square: "\f1d4";
$fa-var-yahoo: "\f19e";
$fa-var-yc: "\f23b";
$fa-var-yc-square: "\f1d4";
$fa-var-yelp: "\f1e9";
$fa-var-yen: "\f157";
$fa-var-yoast: "\f2b1";
$fa-var-youtube: "\f167";
$fa-var-youtube-play: "\f16a";
$fa-var-youtube-square: "\f166";

View File

@ -1,5 +1,5 @@
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
@ -11,8 +11,7 @@
@import "fixed-width";
@import "list";
@import "bordered-pulled";
@import "animated";
@import "spinning";
@import "rotated-flipped";
@import "stacked";
@import "icons";
@import "screen-reader";

View File

@ -1,236 +1,56 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# vim: expandtab sw=4 ts=4 sts=4:
# (C) 2003 - 2018 Michal Čihař <michal@cihar.com> - python-gammu
# (C) 2015 - 2021 Raspian France <raspbianfrance@gmail.com> - RaspianFrance/raspisms
# (C) 2022 - Orsiris de Jong <orsiris.dejong@netperfect.fr> - NetInvent SASU
#
from __future__ import print_function
__intname__ = "gammu_get_unread_sms.py"
__author__ = "Orsiris de Jong - <orsiris.dejong@netperfect.fr>"
__version__ = "2.0.2"
__build__ = "2022102501"
__compat__ = "python2.7+"
import os
import gammu
import sys
import json
import logging
from logging.handlers import RotatingFileHandler
import tempfile
from argparse import ArgumentParser
import subprocess
import re
LOG_FILE = "/var/log/{}.log".format(__intname__)
_DEBUG = os.environ.get("_DEBUG", False)
logger = logging.getLogger(__name__)
def get_logger(log_file):
# We would normally use ofunctions.logger_utils here with logger_get_logger(), but let's keep no dependencies
try:
try:
filehandler = RotatingFileHandler(
log_file, mode="a", encoding="utf-8", maxBytes=1048576, backupCount=3
)
except OSError:
try:
temp_log_file = tempfile.gettempdir() + os.sep + __name__ + ".log"
filehandler = RotatingFileHandler(
temp_log_file,
mode="a",
encoding="utf-8",
maxBytes=1048576,
backupCount=3,
)
except OSError as exc:
print("Cannot create log file: %s" % exc.__str__())
filehandler = None
_logger = logging.getLogger()
if _DEBUG:
_logger.setLevel(logging.DEBUG)
else:
_logger.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s :: %(levelname)s :: %(message)s")
if filehandler:
filehandler.setFormatter(formatter)
_logger.addHandler(filehandler)
consolehandler = logging.StreamHandler()
consolehandler.setFormatter(formatter)
_logger.addHandler(consolehandler)
return _logger
except Exception as exc:
print("Cannot create logger instance: %s" % exc.__str__())
def get_gammu_version():
# Quite badly coded, i'd use command_runner but I try to not have dependencies here
try:
proc = subprocess.Popen(
["LC_ALL=C gammu", "--version"], shell=True, stdout=subprocess.PIPE
)
stdout, _ = proc.communicate()
version = re.search(r"Gammu version ([0-9]+)\.([0-9]+)\.([0-9]+)", str(stdout))
# dont' bother to return version[0] since it's the whole match
return (int(version[1]), int(version[2]), int(version[3]))
except Exception as exc:
logger.error("Cannot get gammu version: %s" % exc.__str__())
return None
def get_gammu_handle(config_file):
def main():
state_machine = gammu.StateMachine()
if len(sys.argv) < 2:
sys.exit(1)
else :
state_machine.ReadConfig(Filename=sys.argv[1])
del sys.argv[1]
if config_file:
state_machine.ReadConfig(Filename=config_file)
else:
state_machine.Readconfig()
state_machine.Init()
return state_machine
def load_sms_from_gammu(state_machine):
"""
The actual function that retrieves SMS via GAMMU from your modem / phone
Also concatenates multiple SMS into single long SMS
"""
status = state_machine.GetSMSStatus()
remaining_sms = status["SIMUsed"] + status["PhoneUsed"] + status["TemplatesUsed"]
logger.debug("Found %s sms" % remaining_sms)
sms_list = []
remain = status['SIMUsed'] + status['PhoneUsed'] + status['TemplatesUsed']
start = True
try:
is_first_message = True
while remaining_sms > 0:
if is_first_message:
sms = state_machine.GetNextSMS(Start=is_first_message, Folder=0)
is_first_message = False
while remain > 0:
if start:
sms = state_machine.GetNextSMS(Start=True, Folder=0)
start = False
else:
sms = state_machine.GetNextSMS(Location=sms[0]["Location"], Folder=0)
remaining_sms = remaining_sms - len(sms)
sms_list.append(sms)
sms = state_machine.GetNextSMS(
Location=sms[0]['Location'], Folder=0
)
remain = remain - len(sms)
for m in sms :
if m['State'] != 'UnRead' :
continue
print(json.dumps({
'number': m['Number'],
'at': str(m['DateTime']),
'status': m['State'],
'text': m['Text'],
}))
except gammu.ERR_EMPTY:
logger.debug("Finished reading all messages")
# Concat multiple SMS into list of sms that go together using LinkSMS
return gammu.LinkSMS(sms_list)
#do noting
return True
def render_sms_as_json(state_machine, sms_list, delete_sms, show_read_sms):
"""
Provided sms_list is a list of lists of sms, eg
sms_list = [
[sms],
[sms1, sms2], # When two sms are in the same list, they form a long sms
[sms],
]
Concatenate long SMS from multiple sends and print them as JSON on stdout
"""
for sms in sms_list:
if sms[0]["State"] == "UnRead" or show_read_sms:
sms_text = ""
for to_concat_sms in sms:
sms_text += to_concat_sms["Text"]
print(
json.dumps(
{
"number": sms[0]["Number"],
"at": str(sms[0]["DateTime"]),
"status": sms[0]["State"],
"text": sms_text,
}
)
)
if delete_sms:
for to_concat_sms in sms:
try:
state_machine.DeleteSMS(
to_concat_sms["Folder"], to_concat_sms["Location"]
)
except Exception as exc:
logger.error("Cannot delete sms: %s" % exc.__str__())
def main(config_file, delete_sms, show_read):
# type: (bool, bool) -> None
logger.debug("Running gammu receiver with config {}".format(config_file))
try:
# Mandatory modem config file
# config_file = sys.argv[1]
state_machine = get_gammu_handle(config_file)
sms_list = load_sms_from_gammu(state_machine)
render_sms_as_json(state_machine, sms_list, delete_sms, show_read)
except Exception as exc:
logger.error("Could not retrieve SMS from Gammu: %s" % exc.__str__())
logger.debug("Trace:", exc_info=True)
if __name__ == "__main__":
parser = ArgumentParser("Gammu SMS retriever")
parser.add_argument(
"gammu_config_file", type=str, nargs="?", help="Gammu config file"
)
parser.add_argument("--debug", action="store_true", help="Activate debugging")
parser.add_argument(
"-l",
"--log-file",
type=str,
dest="log_file",
default=None,
help="Optional path to log file, defaults to /var/log",
)
parser.add_argument(
"--delete", action="store_true", help="Delete messages after they've been read"
)
parser.add_argument(
"--show-read", action="store_true", help="Also show already read messages"
)
args = parser.parse_args()
config_file = args.gammu_config_file
if args.log_file:
LOG_FILE = args.log_file
if args.debug:
_DEBUG = args.debug
_logger = get_logger(LOG_FILE)
if _logger:
logger = _logger
delete = False
if args.delete:
# We need to check if we have gammu >= 1.42.0 since deleting sms with lower versions fail with:
# Cannot delete sms: {'Text': 'The type of memory is not available or has been disabled.', 'Where': 'DeleteSMS', 'Code': 81}
# see https://github.com/gammu/gammu/issues/460
try:
gammu_version = get_gammu_version()
if gammu_version[0] > 1 or (gammu_version[0] == 1 and gammu_version[1] >= 42):
delete = True
else:
logger.warning("Cannot delete SMS. You need gammu >= 1.42.0.")
except TypeError:
logger.warning("Cannot get gammu version. SMS Deleting might not work properly.")
show_read = args.show_read
main(config_file, delete, show_read)
if __name__ == '__main__':
main()

View File

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

Binary file not shown.

View File

@ -11,8 +11,6 @@
namespace controllers\internals;
use DateInterval;
/**
* Class to call the console scripts.
*/
@ -214,46 +212,4 @@ use DateInterval;
$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,
);
}
}
}
}

View File

@ -141,11 +141,8 @@ namespace controllers\internals;
$nb_insert = 0;
$head = null;
$line_nb = 0;
while ($line = fgetcsv($file_handler))
{
$line_nb ++;
if (null === $head)
{
$head = $line;
@ -163,7 +160,7 @@ namespace controllers\internals;
continue;
}
if (!isset(array_keys($line)[0], array_keys($line)[1]))
if (!isset($line[array_keys($line)[0]], $line[array_keys($line)[1]]))
{
continue;
}
@ -188,16 +185,9 @@ namespace controllers\internals;
}
$data = json_encode($data);
$contact_name = $line[array_keys($line)[0]];
$phone_number = \controllers\internals\Tool::parse_phone($line[array_keys($line)[1]]);
if (!$phone_number)
{
throw new \Exception('Erreur à la ligne ' . $line_nb . ' colonne 1, numéro de téléphone invalide.');
}
try
{
$success = $this->create($id_user, $line[array_keys($line)[1]], $contact_name, $data);
$success = $this->create($id_user, $line[array_keys($line)[1]], $line[array_keys($line)[0]], $data);
if ($success)
{
++$nb_insert;

View File

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

View File

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

View File

@ -1,62 +0,0 @@
<?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;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
/**
* Mailing class.
*/
class LinkShortener
{
/**
* Shorten an URL using the configured YOURLS instance
*/
public static function shorten($url)
{
$api_url = URL_SHORTENER['HOST'] . '/yourls-api.php';
$data = [
'action' => 'shorturl',
'format' => 'json',
'username' => URL_SHORTENER['USER'],
'password' => URL_SHORTENER['PASS'],
'url' => $url,
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_HEADER, 0); // No header in the result
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Enable follow location
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return, do not echo result
curl_setopt($ch, CURLOPT_POST, 1); // This is a POST request
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
curl_close($ch);
try
{
$response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
}
catch (\Exception $e)
{
return false;
}
$shortlink = $response['shorturl'] ?? false;
return $shortlink;
}
}

0
controllers/internals/Mailer.php Normal file → Executable file
View File

View File

@ -48,7 +48,7 @@ class Media extends StandardController
if (!file_put_contents($new_file_path, 'a'))
{
throw new \Exception('Cannot write file ' . $new_file_path);
throw new \Exception('pute de merde');
}
if (!rename($tmpfile_path, $new_file_path))
@ -56,6 +56,16 @@ class Media extends StandardController
throw new \Exception('Cannot create file ' . $new_file_path);
}
if (!chown($new_file_path, fileowner($user_path)))
{
throw new \Exception('Cannot give file ' . $new_file_path . ' to user : ' . fileowner($user_path));
}
if (!chgrp($new_file_path, filegroup($user_path)))
{
throw new \Exception('Cannot give file ' . $new_file_path . ' to group : ' . filegroup($user_path));
}
if (!chmod($new_file_path, self::DEFAULT_CHMOD))
{
throw new \Exception('Cannot give file ' . $new_file_path . ' rights : ' . self::DEFAULT_CHMOD);

View File

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

View File

@ -1,139 +0,0 @@
<?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;
/**
* Classe des groups.
*/
class PhoneGroup extends StandardController
{
protected $model;
/**
* Create a new phone group for a user.
*
* @param int $id_user : user id
* @param stirng $name : Group name
* @param array $phones_ids : Ids of the phones of the group
*
* @return mixed bool|int : false on error, new group id
*/
public function create(int $id_user, string $name, array $phones_ids)
{
$group = [
'id_user' => $id_user,
'name' => $name,
];
$id_group = $this->get_model()->insert($group);
if (!$id_group)
{
return false;
}
$internal_phone = new Phone($this->bdd);
foreach ($phones_ids as $phone_id)
{
$phone = $internal_phone->get_for_user($id_user, $phone_id);
if (!$phone)
{
continue;
}
$this->get_model()->insert_phone_group_phone_relation($id_group, $phone_id);
}
$internal_event = new Event($this->bdd);
$internal_event->create($id_user, 'PHONE_GROUP_ADD', 'Ajout phone group : ' . $name);
return $id_group;
}
/**
* Update a phone group for a user.
*
* @param int $id_user : User id
* @param int $id_group : Group id
* @param stirng $name : Group name
* @param array $phones_ids : Ids of the phones of the group
*
* @return bool : False on error, true on success
*/
public function update_for_user(int $id_user, int $id_group, string $name, array $phones_ids)
{
$group = [
'name' => $name,
];
$result = $this->get_model()->update_for_user($id_user, $id_group, $group);
$this->get_model()->delete_phone_group_phone_relations($id_group);
$internal_phone = new Phone($this->bdd);
$nb_phone_insert = 0;
foreach ($phones_ids as $phone_id)
{
$phone = $internal_phone->get_for_user($id_user, $phone_id);
if (!$phone)
{
continue;
}
if ($this->get_model()->insert_phone_group_phone_relation($id_group, $phone_id))
{
++$nb_phone_insert;
}
}
if (!$result && $nb_phone_insert !== \count($phones_ids))
{
return false;
}
return true;
}
/**
* Return a group by his name for a user.
*
* @param int $id_user : User id
* @param string $name : Group name
*
* @return array
*/
public function get_by_name_for_user(int $id_user, string $name)
{
return $this->get_model()->get_by_name_for_user($id_user, $name);
}
/**
* Get groups phones.
*
* @param int $id_group : Group id
*
* @return array : phones of the group
*/
public function get_phones($id_group)
{
return $this->get_model()->get_phones($id_group);
}
/**
* Get the model for the Controller.
*/
protected function get_model(): \models\PhoneGroup
{
$this->model = $this->model ?? new \models\PhoneGroup($this->bdd);
return $this->model;
}
}

View File

@ -11,8 +11,6 @@
namespace controllers\internals;
use Exception;
class Received extends StandardController
{
protected $model;
@ -90,6 +88,14 @@ use Exception;
return false;
}
//Check if the received message is a SMS STOP and we must register it
$internal_smsstop = new SmsStop($this->bdd);
$is_stop = $internal_smsstop->check_for_stop($received['text']);
if ($is_stop)
{
$internal_smsstop->create($id_user, $origin);
}
//Link medias
$internal_media = new Media($this->bdd);
foreach ($media_ids as $media_id)
@ -110,32 +116,6 @@ use Exception;
return false;
}
//Check if the received message is a SMS STOP and we must register it
$internal_smsstop = new SmsStop($this->bdd);
$is_stop = $internal_smsstop->check_for_stop($received['text']);
if ($is_stop)
{
$stop_exists = (bool) $internal_smsstop->get_by_number_for_user($id_user, $origin);
if ($stop_exists)
{
return $id_received;
}
$internal_smsstop->create($id_user, $origin);
//If stop response enabled, respond to user
//(this will happen only for first stop, any further stop will not trigger responses)
$internal_setting = new Setting($this->bdd);
$user_settings = $internal_setting->gets_for_user($id_user);
if ((int) ($user_settings['smsstop_respond'] ?? false))
{
$response = $user_settings['smsstop_response'];
$internal_scheduled = new Scheduled($this->bdd);
$internal_scheduled->create($id_user, (new \DateTime())->format('Y-m-d H:i:s'), $response, $id_phone, null, false, false, \models\SmsStop::SMS_STOP_TAG, [['number' => $origin, 'data' => '[]']]);
}
}
return $id_received;
}

View File

@ -23,31 +23,27 @@ 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 ?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
* @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 : 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, ?string $tag = null, 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, 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,
'id_phone_group' => $id_phone_group,
'flash' => $flash,
'mms' => $mms,
'tag' => $tag,
];
if ('' === $text)
@ -66,17 +62,6 @@ use Monolog\Logger;
}
}
if (null !== $id_phone_group)
{
$internal_phone_group = new PhoneGroup($this->bdd);
$find_phone_group = $internal_phone_group->get_for_user($id_user, $id_phone_group);
if (!$find_phone_group)
{
return false;
}
}
//Use transaction to garanty atomicity
$this->bdd->beginTransaction();
@ -162,10 +147,8 @@ use Monolog\Logger;
* @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 ?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
@ -174,17 +157,15 @@ 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, ?string $tag = null, 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, ?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,
'id_phone_group' => $id_phone_group,
'mms' => $mms,
'flash' => $flash,
'tag' => $tag,
];
if (null !== $id_phone)
@ -198,17 +179,6 @@ use Monolog\Logger;
}
}
if (null !== $id_phone_group)
{
$internal_phone_group = new PhoneGroup($this->bdd);
$find_phone_group = $internal_phone_group->get_for_user($id_user, $id_phone_group);
if (!$find_phone_group)
{
return false;
}
}
//Ensure atomicity
$this->bdd->beginTransaction();
@ -304,6 +274,236 @@ use Monolog\Logger;
return $this->get_model()->gets_after_date_for_number_and_user($id_user, $date, $number);
}
/**
* Get all messages to send and the number to use to send theme.
*
* @return array : List of smss to send at this time per scheduled id ['1' => [['id_scheduled', 'text', 'id_phone', 'destination', 'flash', 'mms', 'medias'], ...], ...]
*/
public function get_smss_to_send()
{
$smss_to_send_per_scheduled = [];
$internal_templating = new \controllers\internals\Templating();
$internal_setting = new \controllers\internals\Setting($this->bdd);
$internal_group = new \controllers\internals\Group($this->bdd);
$internal_conditional_group = new \controllers\internals\ConditionalGroup($this->bdd);
$internal_phone = new \controllers\internals\Phone($this->bdd);
$internal_smsstop = new \controllers\internals\SmsStop($this->bdd);
$users_smsstops = [];
$users_settings = [];
$users_phones = [];
$users_mms_phones = [];
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:s');
$scheduleds = $this->get_model()->gets_before_date($now);
foreach ($scheduleds as $scheduled)
{
$smss_to_send_per_scheduled[$scheduled['id']] = [];
if (!isset($users_settings[$scheduled['id_user']]))
{
$users_settings[$scheduled['id_user']] = [];
$settings = $internal_setting->gets_for_user($scheduled['id_user']);
foreach ($settings as $name => $value)
{
$users_settings[$scheduled['id_user']][$name] = $value;
}
}
if (!isset($users_smsstops[$scheduled['id_user']]) && $users_settings[$scheduled['id_user']]['smsstop'])
{
$users_smsstops[$scheduled['id_user']] = [];
$smsstops = $internal_smsstop->gets_for_user($scheduled['id_user']);
foreach ($smsstops as $smsstop)
{
$users_smsstops[$scheduled['id_user']][] = $smsstop['number'];
}
}
if (!isset($users_phones[$scheduled['id_user']]))
{
$phones = $internal_phone->gets_for_user($scheduled['id_user']);
$mms_phones = $internal_phone->gets_phone_supporting_mms_for_user($scheduled['id_user'], $internal_phone::MMS_SENDING);
$users_phones[$scheduled['id_user']] = $phones ?: [];
$users_mms_phones[$scheduled['id_user']] = $mms_phones ?: [];
}
//Add medias to mms
$scheduled['medias'] = [];
if ($scheduled['mms'])
{
$internal_media = new Media($this->bdd);
$scheduled['medias'] = $internal_media->gets_for_scheduled($scheduled['id']);
}
$phone_to_use = null;
foreach ($users_phones[$scheduled['id_user']] as $phone)
{
if ($phone['id'] !== $scheduled['id_phone'])
{
continue;
}
$phone_to_use = $phone;
}
$messages = [];
//Add messages for numbers
$numbers = $this->get_numbers($scheduled['id']);
foreach ($numbers as $number)
{
if (null === $phone_to_use)
{
if ($scheduled['mms'] && count($users_mms_phones))
{
$rnd_key = array_rand($users_mms_phones[$scheduled['id_user']]);
$random_phone = $users_mms_phones[$scheduled['id_user']][$rnd_key];
}
else
{
$rnd_key = array_rand($users_phones[$scheduled['id_user']]);
$random_phone = $users_phones[$scheduled['id_user']][$rnd_key];
}
}
$message = [
'id_user' => $scheduled['id_user'],
'id_scheduled' => $scheduled['id'],
'id_phone' => $phone_to_use['id'] ?? $random_phone['id'],
'destination' => $number['number'],
'flash' => $scheduled['flash'],
'mms' => $scheduled['mms'],
'medias' => $scheduled['medias'],
];
if ((int) ($users_settings[$scheduled['id_user']]['templating'] ?? false))
{
$number['data'] = json_decode($number['data'] ?? '[]', true);
$metas = ['number' => $number['number']];
$data = ['contact' => $number['data'], 'contact_metas' => $metas];
$render = $internal_templating->render($scheduled['text'], $data);
if (!$render['success'])
{
continue;
}
$message['text'] = $render['result'];
}
else
{
$message['text'] = $scheduled['text'];
}
$messages[] = $message;
}
//Add messages for contacts
$contacts = $this->get_contacts($scheduled['id']);
$groups = $this->get_groups($scheduled['id']);
foreach ($groups as $group)
{
$contacts_to_add = $internal_group->get_contacts($group['id']);
$contacts = array_merge($contacts, $contacts_to_add);
}
$conditional_groups = $this->get_conditional_groups($scheduled['id']);
foreach ($conditional_groups as $conditional_group)
{
$contacts_to_add = $internal_conditional_group->get_contacts_for_condition_and_user($scheduled['id_user'], $conditional_group['condition']);
$contacts = array_merge($contacts, $contacts_to_add);
}
$added_contacts = [];
foreach ($contacts as $contact)
{
if ($added_contacts[$contact['id']] ?? false)
{
continue;
}
$added_contacts[$contact['id']] = true;
if (null === $phone_to_use)
{
if ($scheduled['mms'] && count($users_mms_phones))
{
$rnd_key = array_rand($users_mms_phones[$scheduled['id_user']]);
$random_phone = $users_mms_phones[$scheduled['id_user']][$rnd_key];
}
else
{
$rnd_key = array_rand($users_phones[$scheduled['id_user']]);
$random_phone = $users_phones[$scheduled['id_user']][$rnd_key];
}
}
$message = [
'id_user' => $scheduled['id_user'],
'id_scheduled' => $scheduled['id'],
'id_phone' => $phone_to_use['id'] ?? $random_phone['id'],
'destination' => $contact['number'],
'flash' => $scheduled['flash'],
'mms' => $scheduled['mms'],
'medias' => $scheduled['medias'],
];
if ((int) ($users_settings[$scheduled['id_user']]['templating'] ?? false))
{
$contact['data'] = json_decode($contact['data'], true);
//Add metas of contact by adding contact without data
$metas = $contact;
unset($metas['data'], $metas['id_user']);
$data = ['contact' => $contact['data'], 'contact_metas' => $metas];
$render = $internal_templating->render($scheduled['text'], $data);
if (!$render['success'])
{
continue;
}
$message['text'] = $render['result'];
}
else
{
$message['text'] = $scheduled['text'];
}
$messages[] = $message;
}
foreach ($messages as $message)
{
//Remove empty messages
if ('' === trim($message['text']) && !$message['medias'])
{
continue;
}
//Remove messages to smsstops numbers
if (($users_smsstops[$scheduled['id_user']] ?? false) && in_array($message['destination'], $users_smsstops[$scheduled['id_user']]))
{
continue;
}
$smss_to_send_per_scheduled[$scheduled['id']][] = $message;
}
}
return $smss_to_send_per_scheduled;
}
/**
* Parse a CSV file of numbers, potentially associated with datas.
*
@ -427,351 +627,4 @@ use Monolog\Logger;
return $this->model;
}
/**
* Get all messages to send and the number to use to send theme.
*
* @return array : List of smss to send at this time per scheduled id ['1' => [['id_scheduled', 'text', 'id_phone', 'destination', 'flash', 'mms', 'medias'], ...], ...]
*/
public function get_smss_to_send()
{
$sms_per_scheduled = [];
$internal_templating = new \controllers\internals\Templating();
$internal_setting = new \controllers\internals\Setting($this->bdd);
$internal_group = new \controllers\internals\Group($this->bdd);
$internal_conditional_group = new \controllers\internals\ConditionalGroup($this->bdd);
$internal_phone = new \controllers\internals\Phone($this->bdd);
$internal_phone_group = new \controllers\internals\PhoneGroup($this->bdd);
$internal_smsstop = new \controllers\internals\SmsStop($this->bdd);
$internal_sended = new \controllers\internals\Sended($this->bdd);
$users_smsstops = [];
$users_settings = [];
$users_phones = [];
$users_phone_groups = [];
$shortlink_cache = [];
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:s');
$scheduleds = $this->get_model()->gets_before_date($now);
foreach ($scheduleds as $scheduled)
{
$id_scheduled = $scheduled['id'];
$id_user = $scheduled['id_user'];
$sms_per_scheduled[$id_scheduled] = [];
// Forge cache of data about users, sms stops, phones, etc.
if (!isset($users_settings[$id_user]))
{
$users_settings[$id_user] = [];
$settings = $internal_setting->gets_for_user($id_user);
foreach ($settings as $name => $value)
{
$users_settings[$id_user][$name] = $value;
}
}
if (!isset($users_smsstops[$id_user]) && $users_settings[$id_user]['smsstop'])
{
$users_smsstops[$id_user] = [];
$smsstops = $internal_smsstop->gets_for_user($id_user);
foreach ($smsstops as $smsstop)
{
$users_smsstops[$id_user][] = $smsstop['number'];
}
}
if (!isset($users_phones[$id_user]))
{
$users_phones[$id_user] = [];
$phones = $internal_phone->gets_for_user($id_user);
foreach ($phones as &$phone)
{
$limits = $internal_phone->get_limits($phone['id']);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $internal_sended->count_since_for_phone_and_user($id_user, $phone['id'], $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
$phone['remaining_volume'] = $remaining_volume;
$users_phones[$id_user][$phone['id']] = $phone;
}
}
if (!isset($users_phone_groups[$id_user]))
{
$users_phone_groups[$id_user] = [];
$phone_groups = $internal_phone_group->gets_for_user($id_user);
foreach ($phone_groups as $phone_group)
{
$phones = $internal_phone_group->get_phones($phone_group['id']);
$phone_group['phones'] = [];
foreach ($phones as $phone)
{
$phone_group['phones'][] = $phone['id'];
}
$users_phone_groups[$id_user][$phone_group['id']] = $phone_group;
}
}
//Add medias to mms
$scheduled['medias'] = [];
if ($scheduled['mms'])
{
$internal_media = new Media($this->bdd);
$scheduled['medias'] = $internal_media->gets_for_scheduled($id_scheduled);
}
$phone_to_use = null;
if ($scheduled['id_phone'])
{
$phone_to_use = $users_phones[$id_user][$scheduled['id_phone']] ?? null;
}
$phone_group_to_use = null;
if ($scheduled['id_phone_group'])
{
$phone_group_to_use = $users_phone_groups[$id_user][$scheduled['id_phone_group']] ?? null;
}
// We turn all contacts, groups and conditional groups into just contacts
$contacts = $this->get_contacts($id_scheduled);
$groups = $this->get_groups($id_scheduled);
foreach ($groups as $group)
{
$contacts_to_add = $internal_group->get_contacts($group['id']);
$contacts = array_merge($contacts, $contacts_to_add);
}
$conditional_groups = $this->get_conditional_groups($id_scheduled);
foreach ($conditional_groups as $conditional_group)
{
$contacts_to_add = $internal_conditional_group->get_contacts_for_condition_and_user($id_user, $conditional_group['condition']);
$contacts = array_merge($contacts, $contacts_to_add);
}
// We turn all numbers and contacts into simple targets with number, data and meta so we can forge all messages from onlye one data source
$targets = [];
$numbers = $this->get_numbers($id_scheduled);
foreach ($numbers as $number)
{
$metas = ['number' => $number['number']];
$targets[] = [
'number' => $number['number'],
'data' => $number['data'],
'metas' => $metas,
];
}
foreach ($contacts as $contact)
{
$metas = $contact;
unset($metas['data'], $metas['id_user']);
$targets[] = [
'number' => $contact['number'],
'data' => $contact['data'],
'metas' => $metas,
];
}
// Pass on all targets to deduplicate destinations, remove number in sms stops, etc.
$used_destinations = [];
foreach ($targets as $key => $target)
{
if (in_array($target['number'], $used_destinations))
{
unset($targets[$key]);
continue;
}
//Remove messages to smsstops numbers if not with tag SMS_STOP
if ($scheduled['tag'] != \models\SmsStop::SMS_STOP_TAG && ($users_smsstops[$id_user] ?? false) && in_array($target['number'], $users_smsstops[$id_user]))
{
unset($targets[$key]);
continue;
}
$used_destinations[] = $target['number'];
}
// Finally, we forge all messages and select phone to use
foreach ($targets as $target)
{
// Forge message if templating enable
$text = $scheduled['text'];
if ((int) ($users_settings[$id_user]['templating'] ?? false)) // Cast to int because it is more reliable than bool on strings
{
$target['data'] = json_decode($target['data'], true);
$data = ['contact' => $target['data'], 'contact_metas' => $target['metas']];
$render = $internal_templating->render($scheduled['text'], $data);
if (!$render['success'])
{
continue;
}
$text = $render['result'];
}
// Ignore empty messages
if ('' === trim($text) && !$scheduled['medias'])
{
continue;
}
// If we must force GSM 7 alphabet
if ((int) ($users_settings[$id_user]['force_gsm_alphabet'] ?? false))
{
$text = Tool::convert_to_gsm0338($text);
}
// If the text contain http links we must replace them
if (ENABLE_URL_SHORTENER && ((int) ($users_settings[$id_user]['shorten_url'] ?? false)))
{
$http_links = Tool::search_http_links($text);
if ($http_links !== false)
{
foreach ($http_links as $http_link)
{
if (!array_key_exists($http_link, $shortlink_cache))
{
$shortlkink = LinkShortener::shorten($http_link);
// If link shortening failed, keep original one
if ($shortlkink === false)
{
continue;
}
$shortlink_cache[$http_link] = $shortlkink;
}
$shortlink = $shortlink_cache[$http_link];
$text = str_replace($http_link, $shortlink, $text);
}
}
}
/*
Choose phone if no phone defined for message
Phones are choosen using type, priority and remaining volume :
1 - If sms is a mms, try to use mms phone if any available. If mms phone available use mms phone, else use default.
2 - In group of phones, keep only phones with remaining volume. If no phones with remaining volume, use all phones instead.
3 - Groupe keeped phones by priority get group with biggest priority.
4 - Get a random phone in this group.
5 - If their is no phone matching, keep phone at null so sender will directly mark it as failed
*/
$random_phone = null;
if (null === $phone_to_use)
{
$phones_subset = $users_phones[$id_user];
if ($phone_group_to_use)
{
$phones_subset = array_filter($phones_subset, function ($phone) use ($phone_group_to_use) {
return in_array($phone['id'], $phone_group_to_use['phones']);
});
}
if ($scheduled['mms'])
{
$mms_only = array_filter($phones_subset, function ($phone) {
return $phone['adapter']::meta_support_mms_sending();
});
$phones_subset = $mms_only ?: $phones_subset;
}
// Keep only available phones
$remaining_volume_phones = array_filter($phones_subset, function ($phone) {
return $phone['status'] == \models\Phone::STATUS_AVAILABLE;
});
$phones_subset = $remaining_volume_phones ?: $phones_subset;
// Keep only phones with remaining volume
if ((int) ($users_settings[$id_user]['phone_limit'] ?? false))
{
$remaining_volume_phones = array_filter($phones_subset, function ($phone) {
return $phone['remaining_volume'] > 0;
});
$phones_subset = $remaining_volume_phones ?: $phones_subset;
}
if ((int) ($users_settings[$id_user]['phone_priority'] ?? false))
{
$max_priority_phones = [];
$max_priority = PHP_INT_MIN;
foreach ($phones_subset as $phone)
{
if ($phone['priority'] < $max_priority)
{
continue;
}
elseif ($phone['priority'] == $max_priority)
{
$max_priority_phones[] = $phone;
}
elseif ($phone['priority'] > $max_priority)
{
$max_priority_phones = [$phone];
$max_priority = $phone['priority'];
}
}
$phones_subset = $max_priority_phones;
}
if ($phones_subset)
{
$random_phone = $phones_subset[array_rand($phones_subset)];
}
}
// This should only happen if the user try to send a message without any phone in his account, then we simply ignore.
if (!$random_phone && !$phone_to_use)
{
continue;
}
$id_phone = $phone_to_use['id'] ?? $random_phone['id'];
$sms_per_scheduled[$id_scheduled][] = [
'id_user' => $id_user,
'id_scheduled' => $id_scheduled,
'id_phone' => $id_phone,
'destination' => $target['number'],
'flash' => $scheduled['flash'],
'mms' => $scheduled['mms'],
'tag' => $scheduled['tag'],
'medias' => $scheduled['medias'],
'text' => $text,
];
// Consume one sms from remaining volume of phone
$users_phones[$id_user][$id_phone]['remaining_volume'] --;
}
}
return $sms_per_scheduled;
}
}

View File

@ -11,8 +11,6 @@
namespace controllers\internals;
use Exception;
class Sended extends StandardController
{
protected $model;
@ -46,14 +44,13 @@ 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, ?string $tag = null, 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, array $medias = [], ?int $originating_scheduled = null, ?string $status = \models\Sended::STATUS_UNKNOWN)
{
$sended = [
'id_user' => $id_user,
@ -65,7 +62,6 @@ use Exception;
'adapter' => $adapter,
'flash' => $flash,
'mms' => $mms,
'tag' => $tag,
'status' => $status,
'originating_scheduled' => $originating_scheduled,
];
@ -183,22 +179,6 @@ use Exception;
return $this->get_model()->get_by_uid_and_adapter_for_user($id_user, $uid, $adapter);
}
/**
* Get number of sended SMS since a date for a phone
*
* @param int $id_user : User id
* @param int $id_phone : Phone id we want the number of sended message for
* @param ?\DateTime $since : Date since which we want sended number. 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, ?\DateTime $before = null, ?string $tag_like = null): int
{
return $this->get_model()->count_since_for_phone_and_user($id_user, $id_phone, $since, $before, $tag_like);
}
/**
* Get number of sended SMS for every date since a date for a specific user.
*
@ -207,9 +187,17 @@ use Exception;
*
* @return array
*/
public function count_by_day_and_status_since_for_user(int $id_user, $date)
public function count_by_day_since_for_user(int $id_user, $date)
{
return $this->get_model()->count_by_day_and_status_since_for_user($id_user, $date);
$counts_by_day = $this->get_model()->count_by_day_since_for_user($id_user, $date);
$return = [];
foreach ($counts_by_day as $count_by_day)
{
$return[$count_by_day['at_ymd']] = $count_by_day['nb'];
}
return $return;
}
/**
@ -234,7 +222,6 @@ 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
@ -245,15 +232,23 @@ 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, ?string $tag = null, 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, array $medias = [], $originating_scheduled = null, string $status = \models\Sended::STATUS_UNKNOWN): array
{
$return = [
'error' => false,
'error_message' => null,
];
$internal_setting = new Setting();
$user_settings = $internal_setting->gets_for_user($id_user);
//If we reached our max quota, do not send the message
$internal_quota = new Quota($this->bdd);
$nb_credits = $internal_quota::compute_credits_for_message($text); //Calculate how much credit the message require
if (!$internal_quota->has_enough_credit($id_user, $nb_credits))
{
$return['error'] = false;
$return['error_message'] = 'Not enough credit to send message.';
return $return;
}
$at = (new \DateTime())->format('Y-m-d H:i:s');
$media_uris = [];
@ -277,71 +272,16 @@ use Exception;
$text .= "\n" . join(' - ', $media_urls);
}
try
{
//If we reached our max quota, do not send the message
$internal_quota = new Quota($this->bdd);
$nb_credits = $internal_quota::compute_credits_for_message($text); //Calculate how much credit the message require
if (!$internal_quota->has_enough_credit($id_user, $nb_credits))
{
throw new Exception('Not enough credit to send message.');
}
$response = $adapter->send($destination, $text, $flash, $mms, $media_uris);
// If this phone status indicate it is not available
$internal_phone = new Phone($this->bdd);
$phone = $internal_phone->get_for_user($id_user, $id_phone);
if (!$phone || $phone['status'] != \models\Phone::STATUS_AVAILABLE)
{
throw new Exception('Invalid phone status : ' . $phone['status']);
}
//If we reached limit for this phone and phone limits are enabled, do not send the message
if ((int) ($user_settings['phone_limit'] ?? false))
{
$limits = $internal_phone->get_limits($id_phone);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $this->count_since_for_phone_and_user($id_user, $id_phone, $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
if ($remaining_volume < 1)
{
throw new Exception('Phone send limit have been reached.');
}
}
$response = $adapter->send($destination, $text, $flash, $mms, $media_uris);
if ($response['error'])
{
throw new Exception($response['error_message']);
}
$uid = $response['uid'];
$status = \models\Sended::STATUS_UNKNOWN;
// If send with success, consume credit
$internal_quota->consume_credit($id_user, $nb_credits);
}
catch (Exception $e)
if ($response['error'])
{
$return['error'] = true;
$return['error_message'] = $e->getMessage();
$return['error_message'] = $response['error_message'];
$status = \models\Sended::STATUS_FAILED;
return $return;
}
finally
{
$uid = $uid ?? uniqid();
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $uid, $adapter->meta_classname(), $flash, $mms, $tag, $medias, $originating_scheduled, $status);
$webhook_body = [
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $originating_scheduled, $status);
$sended = [
'id' => $sended_id,
'at' => $at,
'status' => $status,
@ -354,10 +294,31 @@ use Exception;
];
$internal_webhook = new Webhook($this->bdd);
$internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $webhook_body);
$internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $sended);
return $return;
}
$internal_quota->consume_credit($id_user, $nb_credits);
$sended_id = $this->create($id_user, $id_phone, $at, $text, $destination, $response['uid'] ?? uniqid(), $adapter->meta_classname(), $flash, $mms, $medias, $originating_scheduled, $status);
$sended = [
'id' => $sended_id,
'at' => $at,
'status' => $status,
'text' => $text,
'destination' => $destination,
'origin' => $id_phone,
'mms' => $mms,
'medias' => $medias,
'originating_scheduled' => $originating_scheduled,
];
$internal_webhook = new Webhook($this->bdd);
$internal_webhook->trigger($id_user, \models\Webhook::TYPE_SEND_SMS, $sended);
return $return;
}
/**

View File

@ -35,18 +35,6 @@ namespace controllers\internals;
return $settings_array;
}
/**
* Get a user setting by his name for a user.
*
* @param int $id_user : user id
*
* @return array
*/
public function get_by_name_for_user(int $id_user, string $name)
{
return $this->get_model()->get_by_name_for_user($id_user, $name);
}
/**
* Update a setting by his name and user id.
*

View File

@ -73,8 +73,7 @@ namespace controllers\internals;
*/
public function check_for_stop(string $str)
{
$str = trim(mb_strtolower($str));
return 'stop' == $str || 'stop sms' == $str;
return 'stop' == trim(mb_strtolower($str));
}
/**

View File

@ -11,8 +11,6 @@
namespace controllers\internals;
use BenMorel\GsmCharsetConverter\Converter;
/**
* Some tools frequently used.
* Not a standard controller as it's not linked to a model in any way.
@ -85,22 +83,6 @@ use BenMorel\GsmCharsetConverter\Converter;
return '<a href="' . self::s($url, false, true, false) . '">' . self::s($number_format, false, true, false) . '</a>';
}
/**
* Check for http link in a text
*
* @param string $text : Text to search a link in
*
* @return bool|array : False if no link in the text, or an array of all http links
*/
public static function search_http_links($text)
{
$regex = "#http(s?)://\S+#i";
$matches = [];
$nb_matches = preg_match_all($regex, $text, $matches);
return $nb_matches > 0 ? $matches[0] : false;
}
/**
* Cette fonction fait la correspondance entre un type d'evenement et une icone font awesome.
*
@ -183,24 +165,6 @@ use BenMorel\GsmCharsetConverter\Converter;
return $objectDate && $objectDate->format($format) === $date;
}
/**
* Check if a relative format date (see https://www.php.net/manual/en/datetime.formats.relative.php) is valid.
*
* @param string $date : Relative date
*
* @return bool : True if valid, false else
*/
public static function validate_relative_date($date)
{
try {
$d = new \DateTime($date);
} catch (\Throwable $th) {
return false;
}
return true;
}
/**
* Check if a sting represent a valid PHP period for creating an interval.
*
@ -430,17 +394,4 @@ use BenMorel\GsmCharsetConverter\Converter;
return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
}
/**
* Transform an UTF-8 string into a valid GSM 7 string (GSM 03.38), remplacing invalid chars with best equivalent whenever possible
*
* @param string $text : Input text to convert into gsm string
* @return string : An UTF-8 string with GSM alphabet only
*/
public static function convert_to_gsm0338(string $text): string
{
$converter = new Converter();
return $converter->cleanUpUtf8String($text, true, '?');
}
}

View File

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

View File

@ -49,7 +49,6 @@ namespace controllers\publics;
private $internal_user;
private $internal_phone;
private $internal_phone_group;
private $internal_received;
private $internal_sended;
private $internal_scheduled;
@ -73,7 +72,6 @@ namespace controllers\publics;
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_user = new \controllers\internals\User($bdd);
$this->internal_phone = new \controllers\internals\Phone($bdd);
$this->internal_phone_group = new \controllers\internals\PhoneGroup($bdd);
$this->internal_received = new \controllers\internals\Received($bdd);
$this->internal_sended = new \controllers\internals\Sended($bdd);
$this->internal_scheduled = new \controllers\internals\Scheduled($bdd);
@ -120,14 +118,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', 'phone_group', 'media']
* @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', 'phone_group', 'media'];
$entry_types = ['sended', 'received', 'scheduled', 'contact', 'group', 'conditional_group', 'phone', 'media'];
if (!\in_array($entry_type, $entry_types, true))
{
@ -193,26 +191,6 @@ namespace controllers\publics;
unset($entries[$key]['adapter_data']);
}
}
// Special case for phone group we must add phones because its a join
elseif ('phone_group' === $entry_type)
{
foreach ($entries as $key => $entry)
{
$phones = $this->internal_phone_group->get_phones($entry['id']);
// Hide meta data of phones if needed
foreach ($phones as &$phone)
{
if (!$phone['adapter']::meta_hide_data())
{
continue;
}
unset($phone['adapter_data']);
}
$entries[$key]['phones'] = $phones;
}
}
$return = self::DEFAULT_RETURN;
$return['response'] = $entries;
@ -232,84 +210,14 @@ 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.
*
* @param string $_POST['at'] : Date to send message at format Y-m-d H:i:s
* @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 and id_phone_group null, use a random phone
* @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['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['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
@ -323,10 +231,8 @@ namespace controllers\publics;
$at = $_POST['at'] ?? false;
$text = $_POST['text'] ?? false;
$id_phone = empty($_POST['id_phone']) ? null : $_POST['id_phone'];
$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'] ?? [];
@ -426,7 +332,7 @@ namespace controllers\publics;
$at = (string) $at;
$text = (string) $text;
if ($mms && !(int)($this->user['settings']['mms'] ?? false))
if (($this->user['settings']['mms'] ?? false) && $mms)
{
$return = self::DEFAULT_RETURN;
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
@ -511,16 +417,6 @@ namespace controllers\publics;
return $this->json($return);
}
if ($id_phone && $id_phone_group)
{
$return = self::DEFAULT_RETURN;
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
$return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'id_phone, id_phone_group : You must specify at most one of id_phone or id_phone_group, not both.';
$this->auto_http_code(false);
return $this->json($return);
}
$phone = null;
if ($id_phone)
{
@ -537,22 +433,6 @@ namespace controllers\publics;
return $this->json($return);
}
$phone_group = null;
if ($id_phone_group)
{
$phone_group = $this->internal_phone_group->get_for_user($this->user['id'], $id_phone_group);
}
if ($id_phone_group && !$phone_group)
{
$return = self::DEFAULT_RETURN;
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
$return['message'] = self::ERROR_MESSAGES['INVALID_PARAMETER'] . 'id_phone_group : You must specify an id_phone_group number among thoses of user phone groups.';
$this->auto_http_code(false);
return $this->json($return);
}
if ($mms)
{
foreach ($files_arrays as $file)
@ -575,7 +455,7 @@ namespace controllers\publics;
}
}
$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);
$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;
@ -626,8 +506,6 @@ namespace controllers\publics;
* @param string $_POST['name'] : Phone name
* @param string $_POST['adapter'] : Phone adapter
* @param array $_POST['adapter_data'] : Phone adapter data
* @param int $priority : Priority with which to use phone to send SMS. Default 0.
* @param ?array $_POST['limits'] : Array of limits in number of SMS for a period to be applied to this phone.
*
* @return int : id phone the new phone on success
*/
@ -638,10 +516,6 @@ namespace controllers\publics;
$name = $_POST['name'] ?? false;
$adapter = $_POST['adapter'] ?? false;
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : [];
$priority = $_POST['priority'] ?? 0;
$priority = max(((int) $priority), 0);
$limits = $_POST['limits'] ?? [];
$limits = is_array($limits) ? $limits : [$limits];
if (!$name)
{
@ -661,7 +535,7 @@ namespace controllers\publics;
return $this->json($return);
}
$name_exist = $this->internal_phone->get_by_name_and_user($this->user['id'], $name);
$name_exist = $this->internal_phone->get_by_name($name);
if ($name_exist)
{
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
@ -671,36 +545,6 @@ namespace controllers\publics;
return $this->json($return);
}
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters();
$find_adapter = false;
foreach ($adapters as $metas)
@ -783,7 +627,7 @@ namespace controllers\publics;
return $this->json($return);
}
$phone_id = $this->internal_phone->create($this->user['id'], $name, $adapter, $adapter_data, $priority, $limits);
$phone_id = $this->internal_phone->create($this->user['id'], $name, $adapter, $adapter_data);
if (false === $phone_id)
{
$return['error'] = self::ERROR_CODES['CANNOT_CREATE'];
@ -806,7 +650,6 @@ namespace controllers\publics;
* @param string (optionnal) $_POST['name'] : New phone name
* @param string (optionnal) $_POST['adapter'] : New phone adapter
* @param array (optionnal) $_POST['adapter_data'] : New phone adapter data
* @param int $priority : Priority with which to use phone to send SMS. Default 0.
*
* @return int : id phone the new phone on success
*/
@ -824,16 +667,10 @@ namespace controllers\publics;
return $this->json($return);
}
$limits = $this->internal_phone->get_limits(($phone['id']));
$name = $_POST['name'] ?? $phone['name'];
$priority = $_POST['priority'] ?? $phone['priority'];
$priority = max(((int) $priority), 0);
$adapter = $_POST['adapter'] ?? $phone['adapter'];
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : json_decode($phone['adapter_data'], true);
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : json_decode($phone['adapter_data']);
$adapter_data = is_array($adapter_data) ? $adapter_data : [$adapter_data];
$limits = $_POST['limits'] ?? $limits;
$limits = is_array($limits) ? $limits : [$limits];
if (!$name && !$adapter && !$adapter_data)
@ -846,7 +683,7 @@ namespace controllers\publics;
}
$phone_with_same_name = $this->internal_phone->get_by_name_and_user($this->user['id'], $name);
$phone_with_same_name = $this->internal_phone->get_by_name($name);
if ($phone_with_same_name && $phone_with_same_name['id'] != $phone['id'])
{
$return['error'] = self::ERROR_CODES['INVALID_PARAMETER'];
@ -856,36 +693,6 @@ namespace controllers\publics;
return $this->json($return);
}
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters();
$find_adapter = false;
foreach ($adapters as $metas)
@ -968,7 +775,7 @@ namespace controllers\publics;
return $this->json($return);
}
$success = $this->internal_phone->update_for_user($this->user['id'], $phone['id'], $name, $adapter, $adapter_data_json, $priority, $limits);
$success = $this->internal_phone->update_for_user($this->user['id'], $phone['id'], $name, $adapter, $adapter_data);
if (!$success)
{
$return['error'] = self::ERROR_CODES['CANNOT_UPDATE'];
@ -1010,62 +817,4 @@ namespace controllers\publics;
return $this->json($return);
}
/**
* Trigger re-checking of a phone status
*
* @param int $id : Id of phone to re-check status
*/
public function post_update_phone_status ($id)
{
$return = self::DEFAULT_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);
}
// If user have activated phone limits, check if RaspiSMS phone limit have already been reached
$limit_reached = false;
if ((int) ($this->user['settings']['phone_limit'] ?? false))
{
$limits = $this->internal_phone->get_limits($id);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $this->internal_sended->count_since_for_phone_and_user($this->user['id'], $id, $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
if ($remaining_volume < 1)
{
$limit_reached = true;
}
}
if ($limit_reached)
{
$new_status = \models\Phone::STATUS_LIMIT_REACHED;
}
else
{
//Check status on provider side
$adapter_classname = $phone['adapter'];
$adapter_instance = new $adapter_classname($phone['adapter_data']);
$new_status = $adapter_instance->check_phone_status();
}
$status_update = $this->internal_phone->update_status($id, $new_status);
$return['response'] = $new_status;
$this->auto_http_code(true);
return $this->json($return);
}
}

View File

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

View File

@ -11,8 +11,6 @@
namespace controllers\publics;
use Exception;
/**
* Page des contacts.
*/
@ -347,28 +345,39 @@ use Exception;
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
try
//Try to import file
$invalid_type = false;
switch ($read_file['mime_type'])
{
$result = false;
switch (true)
{
case ($read_file['mime_type'] === 'text/csv' || 'csv' === $read_file['extension']) :
case 'text/csv':
$result = $this->internal_contact->import_csv($id_user, $read_file['content']);
break;
case 'application/json':
$result = $this->internal_contact->import_json($id_user, $read_file['content']);
break;
default:
if ('csv' === $read_file['extension'])
{
$result = $this->internal_contact->import_csv($id_user, $read_file['content']);
break;
case ($read_file['mime_type'] === 'text/json' || 'json' === $read_file['extension']) :
}
elseif ('json' === $read_file['extension'])
{
$result = $this->internal_contact->import_json($id_user, $read_file['content']);
break;
default:
throw new Exception('Le type de fichier n\'est pas valide.');
}
}
else
{
$invalid_type = true;
}
}
catch (\Exception $e)
if ($invalid_type)
{
\FlashMessage\FlashMessage::push('danger', 'Erreur lors de l\'import: ' . $e->getMessage());
\FlashMessage\FlashMessage::push('danger', 'Le type de fichier n\'est pas valide.');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}

View File

@ -87,12 +87,11 @@ namespace controllers\publics;
$stats_start_date_formated = $stats_start_date->format('Y-m-d');
}
$nb_sendeds_by_day = $this->internal_sended->count_by_day_and_status_since_for_user($id_user, $stats_start_date_formated);
$nb_sendeds_by_day = $this->internal_sended->count_by_day_since_for_user($id_user, $stats_start_date_formated);
$nb_receiveds_by_day = $this->internal_received->count_by_day_since_for_user($id_user, $stats_start_date_formated);
//On va traduire ces données pour les afficher en graphique
$array_bar_chart_sended = [];
$array_bar_chart_received = [];
$array_area_chart = [];
$date = clone $stats_start_date;
$one_day = new \DateInterval('P1D');
@ -102,15 +101,12 @@ namespace controllers\publics;
while ($date <= $now)
{
$date_f = $date->format('Y-m-d');
$array_bar_chart_sended[$date_f] = [
$array_area_chart[$date_f] = [
'period' => $date_f,
'sendeds_failed' => 0,
'sendeds_unknown' => 0,
'sendeds_delivered' => 0,
'sendeds' => 0,
'receiveds' => 0,
];
$array_bar_chart_received[$date_f] = ['period' => $date_f, 'receiveds' => 0];
$date->add($one_day);
}
@ -118,16 +114,15 @@ namespace controllers\publics;
$total_receiveds = 0;
//0n remplie le tableau avec les données adaptées
foreach ($nb_sendeds_by_day as $nb_sended)
foreach ($nb_sendeds_by_day as $date => $nb_sended)
{
$array_bar_chart_sended[$nb_sended['at_ymd']]['sendeds_' . $nb_sended['status']] = $nb_sended['nb'];
$array_bar_chart_sended[$nb_sended['at_ymd']]['sendeds_total'] = ($array_bar_chart_sended[$nb_sended['at_ymd']]['sendeds_total'] ?? 0) + $nb_sended['nb'];
$total_sendeds += $nb_sended['nb'];
$array_area_chart[$date]['sendeds'] = $nb_sended;
$total_sendeds += $nb_sended;
}
foreach ($nb_receiveds_by_day as $date => $nb_received)
{
$array_bar_chart_received[$date]['receiveds'] = $nb_received;
$array_area_chart[$date]['receiveds'] = $nb_received;
$total_receiveds += $nb_received;
}
@ -135,8 +130,7 @@ namespace controllers\publics;
$avg_sendeds = round($total_sendeds / $nb_days, 2);
$avg_receiveds = round($total_receiveds / $nb_days, 2);
$array_bar_chart_sended = array_values($array_bar_chart_sended);
$array_bar_chart_received = array_values($array_bar_chart_received);
$array_area_chart = array_values($array_area_chart);
$this->render('dashboard/show', [
'nb_contacts' => $nb_contacts,
@ -151,9 +145,7 @@ namespace controllers\publics;
'sendeds' => $sendeds,
'receiveds' => $receiveds,
'events' => $events,
'data_bar_chart_sended' => json_encode($array_bar_chart_sended),
'data_bar_chart_received' => json_encode($array_bar_chart_received),
'stats_start_date_formated' => $stats_start_date_formated,
'data_area_chart' => json_encode($array_area_chart),
]);
}
}

View File

@ -234,7 +234,6 @@ 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;
@ -316,7 +315,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, $tag, $destinations, [], [], [], $media_ids))
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';

View File

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

View File

@ -18,13 +18,11 @@ class Phone extends \descartes\Controller
{
private $internal_phone;
private $internal_adapter;
private $internal_sended;
public function __construct()
{
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_phone = new \controllers\internals\Phone($bdd);
$this->internal_sended = new \controllers\internals\Sended($bdd);
$this->internal_adapter = new \controllers\internals\Adapter();
\controllers\internals\Tool::verifyconnect();
@ -57,9 +55,6 @@ class Phone extends \descartes\Controller
foreach ($phones as &$phone)
{
$limits = $this->internal_phone->get_limits($phone['id']);
$phone['limits'] = $limits;
$adapter = $adapters[$phone['adapter']] ?? false;
if (!$adapter)
@ -90,11 +85,6 @@ class Phone extends \descartes\Controller
{
$phone['callback_end_call'] = \descartes\Router::url('Callback', 'end_call', ['id_phone' => $phone['id']], ['api_key' => $api_key]);
}
if ($adapter['meta_support_phone_status'])
{
$phone['support_phone_status'] = true;
}
}
header('Content-Type: application/json');
@ -141,11 +131,9 @@ class Phone extends \descartes\Controller
* Create a new phone.
*
* @param $csrf : CSRF token
* @param string $_POST['name'] : Phone name
* @param string $_POST['adapter'] : Phone adapter
* @param ?array $_POST['adapter_data'] : Phone adapter data
* @param ?array $_POST['limits'] : Array of limits in number of SMS for a period to be applied to this phone.
* @param int $_POST['priority'] : Priority with which to use phone to send SMS. Default 0.
* @param string $_POST['name'] : Phone name
* @param string $_POST['adapter'] : Phone adapter
* @param array $_POST['adapter_data'] : Phone adapter data
*/
public function create($csrf)
{
@ -158,12 +146,8 @@ class Phone extends \descartes\Controller
$id_user = $_SESSION['user']['id'];
$name = $_POST['name'] ?? false;
$priority = $_POST['priority'] ?? 0;
$priority = max(((int) $priority), 0);
$adapter = $_POST['adapter'] ?? false;
$adapter_data = !empty($_POST['adapter_data']) ? $_POST['adapter_data'] : [];
$limits = $_POST['limits'] ?? [];
$limits = is_array($limits) ? $limits : [$limits];
if (!$name || !$adapter)
{
@ -172,7 +156,7 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'add'));
}
$name_exist = $this->internal_phone->get_by_name_and_user($id_user, $name);
$name_exist = $this->internal_phone->get_by_name($name);
if ($name_exist)
{
\FlashMessage\FlashMessage::push('danger', 'Ce nom est déjà utilisé pour un autre téléphone.');
@ -180,36 +164,6 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'add'));
}
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters();
$find_adapter = false;
foreach ($adapters as $metas)
@ -291,7 +245,7 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'add'));
}
$success = $this->internal_phone->create($id_user, $name, $adapter, $adapter_data, $priority, $limits);
$success = $this->internal_phone->create($id_user, $name, $adapter, $adapter_data);
if (!$success)
{
\FlashMessage\FlashMessage::push('danger', 'Impossible de créer ce téléphone.');
@ -303,290 +257,4 @@ class Phone extends \descartes\Controller
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
/**
* Return the edit page for phones
*
* @param int... $ids : Phones ids
*/
public function edit()
{
$ids = $_GET['ids'] ?? [];
$id_user = $_SESSION['user']['id'];
$phones = $this->internal_phone->gets_in_for_user($id_user, $ids);
if (!$phones)
{
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
$adapters = $this->internal_adapter->list_adapters();
foreach ($phones as &$phone)
{
$limits = $this->internal_phone->get_limits($phone['id']);
$phone['limits'] = $limits;
}
$this->render('phone/edit', [
'phones' => $phones,
'adapters' => $adapters,
]);
}
/**
* Update multiple phones.
*
* @param $csrf : CSRF token
* @param string $_POST['phones']['id']['name'] : Phone name
* @param string $_POST['phones']['id']['adapter'] : Phone adapter
* @param ?array $_POST['phones']['id']['adapter_data'] : Phone adapter data
* @param ?array $_POST['phones']['id']['limits'] : Array of limits in number of SMS for a period to be applied to this phone.
* @param int $_POST['phones']['id']['priority'] : Priority with which to use phone to send SMS. Default 0.
*/
public function update($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('Phone', 'add'));
}
if (!$_POST['phones'])
{
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
$id_user = $_SESSION['user']['id'];
$nb_update = 0;
foreach ($_POST['phones'] as $id_phone => $phone)
{
$name = $phone['name'] ?? false;
$priority = $phone['priority'] ?? 0;
$priority = max(((int) $priority), 0);
$adapter = $phone['adapter'] ?? false;
$adapter_data = !empty($phone['adapter_data']) ? $phone['adapter_data'] : [];
$limits = $phone['limits'] ?? [];
$limits = is_array($limits) ? $limits : [$limits];
if (!$name || !$adapter)
{
continue;
}
$phone_with_same_name = $this->internal_phone->get_by_name_and_user($id_user, $name);
if ($phone_with_same_name && $phone_with_same_name['id'] != $id_phone)
{
continue;
}
if ($limits)
{
foreach ($limits as $key => $limit)
{
if (!is_array($limit))
{
unset($limits[$key]);
continue;
}
$startpoint = $limit['startpoint'] ?? false;
$volume = $limit['volume'] ?? false;
if (!$startpoint || !$volume)
{
unset($limits[$key]);
continue;
}
$volume = (int) $volume;
$limits[$key]['volume'] = max($volume, 1);
if (!\controllers\internals\Tool::validate_relative_date($startpoint))
{
unset($limits[$key]);
continue;
}
}
}
$adapters = $this->internal_adapter->list_adapters();
$find_adapter = false;
foreach ($adapters as $metas)
{
if ($metas['meta_classname'] === $adapter)
{
$find_adapter = $metas;
break;
}
}
if (!$find_adapter)
{
continue;
}
$current_phone = $this->internal_phone->get_for_user($id_user, $id_phone);
if (!$current_phone)
{
continue;
}
// We can only use an hidden adapter if it was already the adapter we was using
if ($find_adapter['meta_hidden'] && $adapter != $current_phone['adapter'])
{
continue;
}
//If missing required data fields, error
foreach ($find_adapter['meta_data_fields'] as $field)
{
if (false === $field['required'])
{
continue;
}
if (!empty($adapter_data[$field['name']]))
{
continue;
}
continue 2;
}
//If field phone number is invalid
foreach ($find_adapter['meta_data_fields'] as $field)
{
if ('phone_number' !== ($field['type'] ?? false))
{
continue;
}
if (!empty($adapter_data[$field['name']]))
{
$adapter_data[$field['name']] = \controllers\internals\Tool::parse_phone($adapter_data[$field['name']]);
if ($adapter_data[$field['name']])
{
continue;
}
}
continue 2;
}
$adapter_data = json_encode($adapter_data);
//Check adapter is working correctly with thoses names and data
$adapter_classname = $find_adapter['meta_classname'];
$adapter_instance = new $adapter_classname($adapter_data);
$adapter_working = $adapter_instance->test();
if (!$adapter_working)
{
continue;
}
$success = $this->internal_phone->update_for_user($id_user, $id_phone, $name, $adapter, $adapter_data, $priority, $limits);
if (!$success)
{
continue;
}
$nb_update ++;
}
if ($nb_update !== \count($_POST['phones']))
{
\FlashMessage\FlashMessage::push('danger', 'Certains téléphones n\'ont pas pu êtres mis à jour.');
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
\FlashMessage\FlashMessage::push('success', 'Tous les téléphones ont été modifiés avec succès.');
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
/**
* Re-check phone status
* @param array int $_GET['ids'] : ids of phones we want to update status
* @param $csrf : CSRF token
*/
public function update_status ($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
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 user have activated phone limits, check if RaspiSMS phone limit have already been reached
$limit_reached = false;
if ((int) ($_SESSION['user']['settings']['phone_limit'] ?? false))
{
$limits = $this->internal_phone->get_limits($id);
$remaining_volume = PHP_INT_MAX;
foreach ($limits as $limit)
{
$startpoint = new \DateTime($limit['startpoint']);
$consumed = $this->internal_sended->count_since_for_phone_and_user($_SESSION['user']['id'], $id, $startpoint);
$remaining_volume = min(($limit['volume'] - $consumed), $remaining_volume);
}
if ($remaining_volume < 1)
{
$limit_reached = true;
}
}
if ($limit_reached)
{
$new_status = \models\Phone::STATUS_LIMIT_REACHED;
}
else
{
//Check status on provider side
$adapter_classname = $phone['adapter'];
if (!call_user_func([$adapter_classname, 'meta_support_phone_status']))
{
continue;
}
$adapter_instance = new $adapter_classname($phone['adapter_data']);
$new_status = $adapter_instance->check_phone_status();
}
$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.');
return $this->redirect(\descartes\Router::url('Phone', 'list'));
}
/**
* Return a list of phones as a JSON array
*/
public function json_list()
{
header('Content-Type: application/json');
echo json_encode($this->internal_phone->list_for_user($_SESSION['user']['id']));
}
}

View File

@ -1,245 +0,0 @@
<?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\publics;
/**
* Page of phone groups.
*/
class PhoneGroup extends \descartes\Controller
{
private $internal_phone_group;
private $internal_phone;
private $internal_event;
/**
* Call before any other func to check user is connected
*
* @return void;
*/
public function __construct()
{
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_phone_group = new \controllers\internals\PhoneGroup($bdd);
$this->internal_phone = new \controllers\internals\Phone($bdd);
$this->internal_event = new \controllers\internals\Event($bdd);
\controllers\internals\Tool::verifyconnect();
}
/**
* Return all groups as an array for administration.
*/
public function list()
{
$this->render('phone_group/list');
}
/**
* Return groups as json.
*/
public function list_json()
{
$entities = $this->internal_phone_group->list_for_user($_SESSION['user']['id']);
header('Content-Type: application/json');
echo json_encode(['data' => $entities]);
}
/**
* Delete a list of phone groups
*
* @param array int $_GET['ids'] : Ids of phone groups to delete
* @param mixed $csrf
*
* @return boolean;
*/
public function delete($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'list'));
}
$ids = $_GET['ids'] ?? [];
foreach ($ids as $id)
{
$this->internal_phone_group->delete_for_user($_SESSION['user']['id'], $id);
}
return $this->redirect(\descartes\Router::url('PhoneGroup', 'list'));
}
/**
* Return the creation page of a group
*/
public function add()
{
$this->render('phone_group/add');
}
/**
* Return the edition page for phone groups
*
* @param array $_GET['ids'] : Ids of phone groups to edit
*/
public function edit()
{
$ids = $_GET['ids'] ?? [];
$groups = $this->internal_phone_group->gets_in_for_user($_SESSION['user']['id'], $ids);
foreach ($groups as $key => $group)
{
$groups[$key]['phones'] = $this->internal_phone_group->get_phones($group['id']);
}
$this->render('phone_group/edit', [
'phone_groups' => $groups,
]);
}
/**
* Create a new phone group
*
* @param $csrf : CSRF token
* @param string $_POST['name'] : Name of phone group
* @param array $_POST['phones'] : Ids of phones to put in the group
*/
public function create($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'add'));
}
$name = $_POST['name'] ?? false;
$phones_ids = $_POST['phones'] ?? false;
if (!$name || !$phones_ids)
{
\FlashMessage\FlashMessage::push('danger', 'Des champs sont manquants !');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'add'));
}
$id_group = $this->internal_phone_group->create($_SESSION['user']['id'], $name, $phones_ids);
if (!$id_group)
{
\FlashMessage\FlashMessage::push('danger', 'Impossible de créer ce groupe.');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'add'));
}
\FlashMessage\FlashMessage::push('success', 'Le groupe a bien été créé.');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'list'));
}
/**
* Update a list of phone groups
*
* @param $csrf : CSRF token
* @param array $_POST['phone_groups'] : An array of phone groups with group id as keys
*
* @return boolean;
*/
public function update($csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'list'));
}
$groups = $_POST['phone_groups'] ?? [];
$nb_groups_update = 0;
foreach ($groups as $id => $group)
{
foreach ($group['phones_ids'] as $key => $value)
{
$group['phones_ids'][$key] = (int) $value;
}
$nb_groups_update += (int) $this->internal_phone_group->update_for_user($_SESSION['user']['id'], $id, $group['name'], $group['phones_ids']);
}
if ($nb_groups_update !== \count($groups))
{
\FlashMessage\FlashMessage::push('danger', 'Certains groupes n\'ont pas pu êtres mis à jour.');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'list'));
}
\FlashMessage\FlashMessage::push('success', 'Tous les groupes ont été modifiés avec succès.');
return $this->redirect(\descartes\Router::url('PhoneGroup', 'list'));
}
/**
* Return phones of a group as json array
* @param int $id_group = PhoneGroup id
*
* @return json
*/
public function preview (int $id_group)
{
$return = [
'success' => false,
'result' => 'Une erreur inconnue est survenue.',
];
$group = $this->internal_phone_group->get_for_user($_SESSION['user']['id'], $id_group);
if (!$group)
{
$return['result'] = 'Ce groupe n\'existe pas.';
echo json_encode($return);
return false;
}
$phones = $this->internal_phone_group->get_phones($id_group);
if (!$phones)
{
$return['result'] = 'Aucun téléphone dans le groupe.';
echo json_encode($return);
return false;
}
foreach ($phones as &$phone)
{
$phone['adapter_name'] = call_user_func([$phone['adapter'], 'meta_name']);
}
$return['success'] = true;
$return['result'] = $phones;
echo json_encode($return);
return true;
}
/**
* Cette fonction retourne la liste des groups sous forme JSON.
*/
public function json_list()
{
header('Content-Type: application/json');
echo json_encode($this->internal_phone_group->list_for_user($_SESSION['user']['id']));
}
}

View File

@ -18,7 +18,6 @@ namespace controllers\publics;
{
private $internal_scheduled;
private $internal_phone;
private $internal_phone_group;
private $internal_contact;
private $internal_group;
private $internal_conditional_group;
@ -35,7 +34,6 @@ namespace controllers\publics;
$bdd = \descartes\Model::_connect(DATABASE_HOST, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD);
$this->internal_scheduled = new \controllers\internals\Scheduled($bdd);
$this->internal_phone = new \controllers\internals\Phone($bdd);
$this->internal_phone_group = new \controllers\internals\PhoneGroup($bdd);
$this->internal_contact = new \controllers\internals\Contact($bdd);
$this->internal_group = new \controllers\internals\Group($bdd);
$this->internal_conditional_group = new \controllers\internals\ConditionalGroup($bdd);
@ -120,7 +118,6 @@ namespace controllers\publics;
$contacts = $this->internal_contact->gets_for_user($id_user);
$phones = $this->internal_phone->gets_for_user($id_user);
$phone_groups = $this->internal_phone_group->gets_for_user($id_user);
$contact_ids = (isset($_GET['contact_ids']) && \is_array($_GET['contact_ids'])) ? $_GET['contact_ids'] : [];
$group_ids = (isset($_GET['group_ids']) && \is_array($_GET['group_ids'])) ? $_GET['group_ids'] : [];
@ -156,7 +153,6 @@ namespace controllers\publics;
'now' => $now->format('Y-m-d H:i'),
'contacts' => $contacts,
'phones' => $phones,
'phone_groups' => $phone_groups,
'prefilled_contacts' => $prefilled_contacts,
'prefilled_groups' => $prefilled_groups,
'prefilled_conditional_groups' => $prefilled_conditional_groups,
@ -183,7 +179,6 @@ namespace controllers\publics;
$all_contacts = $this->internal_contact->gets_for_user($_SESSION['user']['id']);
$phones = $this->internal_phone->gets_for_user($_SESSION['user']['id']);
$phone_groups = $this->internal_phone_group->gets_for_user($id_user);
$scheduleds = $this->internal_scheduled->gets_in_for_user($_SESSION['user']['id'], $ids);
//Pour chaque message on ajoute les numéros, les contacts & les groups
@ -231,7 +226,6 @@ namespace controllers\publics;
$this->render('scheduled/edit', [
'scheduleds' => $scheduleds,
'phones' => $phones,
'phone_groups' => $phone_groups,
'contacts' => $all_contacts,
]);
}
@ -244,7 +238,7 @@ namespace controllers\publics;
* @param string $_POST['at'] : Date to send message for
* @param string $_POST['text'] : Text of the message
* @param ?bool $_POST['flash'] : Is the message a flash message (by default false)
* @param ?int $_POST['id_phone'] : Id of the phone or phone group to send message from. id will be preceed by phone_ of phonegroup_ depending on type of ressource to use, if null use random phone
* @param ?int $_POST['id_phone'] : Id of the phone to send message from, if null use random phone
* @param ?array $_POST['numbers'] : Numbers to send the message to
* @param ?array $_POST['contacts'] : Numbers to send the message to
* @param ?array $_POST['groups'] : Numbers to send the message to
@ -264,7 +258,6 @@ 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];
@ -440,15 +433,7 @@ namespace controllers\publics;
$mms = (bool) count($media_ids);
// Check if we must send message to a phone or a phone_group based on if id_phone start with 'phone_' or 'phonegroup_'
$id_phone_group = null;
if ($id_phone)
{
$id_phone_group = str_starts_with($id_phone, 'phonegroup_') ? mb_substr($id_phone, mb_strlen('phonegroup_')) : null;
$id_phone = str_starts_with($id_phone, 'phone_') ? mb_substr($id_phone, mb_strlen('phone_')) : null;
}
$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);
$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.');
@ -488,7 +473,6 @@ 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'] ?? [];
@ -666,14 +650,7 @@ namespace controllers\publics;
$mms = (bool) count($media_ids);
$id_phone_group = null;
if ($id_phone)
{
$id_phone_group = str_starts_with($id_phone, 'phonegroup_') ? mb_substr($id_phone, mb_strlen('phonegroup_')) : null;
$id_phone = str_starts_with($id_phone, 'phone_') ? mb_substr($id_phone, mb_strlen('phone_')) : null;
}
$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);
$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;
}

View File

@ -59,9 +59,8 @@ namespace controllers\publics;
0 => 'phone_name',
1 => 'searchable_destination',
2 => 'text',
3 => 'tag',
4 => 'at',
5 => 'status',
3 => 'at',
4 => 'status',
];
$search = $_GET['search']['value'] ?? null;

View File

@ -75,27 +75,12 @@ namespace controllers\publics;
$setting_value = json_encode($setting_value);
}
// If setting dont exists yet, create it, else update
$setting = $this->internal_setting->get_by_name_for_user($_SESSION['user']['id'], $setting_name);
if (!$setting)
$update_setting_result = $this->internal_setting->update_for_user($_SESSION['user']['id'], $setting_name, $setting_value);
if (false === $update_setting_result)
{
$success = $this->internal_setting->create($_SESSION['user']['id'], $setting_name, $setting_value);
if (false === $success)
{
\FlashMessage\FlashMessage::push('danger', 'Impossible de mettre à jour ce réglage.');
\FlashMessage\FlashMessage::push('danger', 'Impossible de mettre à jour ce réglage.');
return $this->redirect(\descartes\Router::url('Setting', 'show'));
}
}
else
{
$update_setting_result = $this->internal_setting->update_for_user($_SESSION['user']['id'], $setting_name, $setting_value);
if (false === $update_setting_result)
{
\FlashMessage\FlashMessage::push('danger', 'Impossible de mettre à jour ce réglage.');
return $this->redirect(\descartes\Router::url('Setting', 'show'));
}
return $this->redirect(\descartes\Router::url('Setting', 'show'));
}
$settings = $this->internal_setting->gets_for_user($_SESSION['user']['id']);

View File

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

View File

@ -53,7 +53,7 @@ class Launcher extends AbstractDaemon
$this->start_mailer_daemon();
$phones = $this->internal_phone->get_all_for_active_users();
$phones = $this->internal_phone->get_all();
$this->start_phones_daemons($phones);
sleep(1);

View File

@ -23,6 +23,7 @@ class Phone extends AbstractDaemon
private $read_delay = 20 / 0.5;
private $read_tick = 0;
private $msg_queue;
private $msg_queue_id;
private $webhook_queue;
private $last_message_at;
private $phone;
@ -37,6 +38,7 @@ class Phone extends AbstractDaemon
public function __construct(array $phone)
{
$this->phone = $phone;
$this->msg_queue_id = (int) (QUEUE_ID_PHONE_PREFIX . $this->phone['id']);
$name = 'RaspiSMS Daemon Phone ' . $this->phone['id'];
$logger = new Logger($name);
@ -85,7 +87,7 @@ class Phone extends AbstractDaemon
//Set last message at to construct time
$this->last_message_at = microtime(true);
$this->msg_queue = msg_get_queue(QUEUE_ID_PHONE);
$this->msg_queue = msg_get_queue($this->msg_queue_id);
//Instanciate adapter
$adapter_class = $this->phone['adapter'];
@ -96,7 +98,11 @@ class Phone extends AbstractDaemon
public function on_stop()
{
$this->logger->info('Stopping Phone daemon with pid ' . getmypid());
//Delete queue on daemon close
$this->logger->info('Closing queue : ' . $this->msg_queue_id);
msg_remove_queue($this->msg_queue);
$this->logger->info('Stopping Phone daemon with pid ' . getmypid());
}
public function handle_other_signals($signal)
@ -119,10 +125,8 @@ class Phone extends AbstractDaemon
$maxsize = 409600;
$message = null;
// Message type is forged from a prefix concat with the phone ID
$message_type = (int) QUEUE_TYPE_SEND_MSG_PREFIX . $this->phone['id'];
$error_code = null;
$success = msg_receive($this->msg_queue, $message_type, $msgtype, $maxsize, $message, true, MSG_IPC_NOWAIT, $error_code); //MSG_IPC_NOWAIT == dont wait if no message found
$success = msg_receive($this->msg_queue, QUEUE_TYPE_SEND_MSG, $msgtype, $maxsize, $message, true, MSG_IPC_NOWAIT, $error_code); //MSG_IPC_NOWAIT == dont wait if no message found
if (!$success && MSG_ENOMSG !== $error_code)
{
@ -144,7 +148,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['tag'], $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['medias'], $message['id_scheduled']);
if ($response['error'])
{
$this->logger->error('Failed send message : ' . json_encode($message) . ' with error : ' . $response['error_message']);

View File

@ -22,9 +22,8 @@ class Sender extends AbstractDaemon
private $internal_phone;
private $internal_scheduled;
private $internal_received;
private $internal_sended;
private $bdd;
private $msg_queue;
private $queues = [];
public function __construct()
{
@ -46,7 +45,6 @@ class Sender extends AbstractDaemon
{
//Create the internal controllers
$this->internal_scheduled = new \controllers\internals\Scheduled($this->bdd);
$this->internal_sended = new \controllers\internals\Sended($this->bdd);
//Get smss and transmit order to send to appropriate phone daemon
$smss_per_scheduled = $this->internal_scheduled->get_smss_to_send();
@ -64,14 +62,15 @@ class Sender extends AbstractDaemon
{
foreach ($smss_per_scheduled as $id_scheduled => $smss)
{
//If queue not already exists
if (!msg_queue_exists(QUEUE_ID_PHONE) || !isset($this->msg_queue))
{
$this->msg_queue = msg_get_queue(QUEUE_ID_PHONE);
}
foreach ($smss as $sms)
{
//If queue not already exists
$queue_id = (int) (QUEUE_ID_PHONE_PREFIX . $sms['id_phone']);
if (!msg_queue_exists($queue_id) || !isset($queues[$queue_id]))
{
$this->queues[$queue_id] = msg_get_queue($queue_id);
}
$msg = [
'id_user' => $sms['id_user'],
'id_scheduled' => $sms['id_scheduled'],
@ -80,14 +79,11 @@ class Sender extends AbstractDaemon
'destination' => $sms['destination'],
'flash' => $sms['flash'],
'mms' => $sms['mms'],
'tag' => $sms['tag'],
'medias' => $sms['medias'] ?? [],
];
// Message type is forged from a prefix concat with the phone ID
$message_type = (int) QUEUE_TYPE_SEND_MSG_PREFIX . $sms['id_phone'];
msg_send($this->msg_queue, $message_type, $msg);
$this->logger->info('Transmit sms send signal to phone ' . $sms['id_phone'] . ' on queue ' . QUEUE_ID_PHONE . ' with message type ' . $message_type . '.');
msg_send($this->queues[$queue_id], QUEUE_TYPE_SEND_MSG, $msg);
$this->logger->info('Transmit sms send signal to phone ' . $sms['id_phone'] . ' on queue ' . $queue_id . '.');
}
$this->logger->info('Scheduled ' . $id_scheduled . ' treated and deleted.');
@ -103,10 +99,6 @@ class Sender extends AbstractDaemon
public function on_stop()
{
//Delete queue on daemon close
$this->logger->info('Closing queue : ' . $this->msg_queue);
msg_remove_queue($this->msg_queue);
$this->logger->info('Stopping Sender with pid ' . getmypid());
}

View File

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

View File

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

View File

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

View File

@ -1,52 +0,0 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
use Phinx\Migration\AbstractMigration;
class CreatePhoneGroup 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()
{
$this->table('phone_group')
->addColumn('id_user', 'integer', ['null' => false])
->addColumn('name', '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'])
->create();
$this->table('phone_group_phone')
->addColumn('id_phone_group', 'integer', ['null' => false])
->addColumn('id_phone', 'integer', ['null' => false])
->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP'])
->addColumn('updated_at', 'timestamp', ['null' => true, 'update' => 'CURRENT_TIMESTAMP'])
->addForeignKey('id_phone_group', 'phone_group', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addForeignKey('id_phone', 'phone', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->create();
}
}

View File

@ -1,39 +0,0 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddIdPhoneGroupToScheduled 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()
{
$this->table('scheduled')
->addColumn('id_phone_group', 'integer', ['null' => true, 'default' => null, 'after' => 'id_phone'])
->addForeignKey('id_phone_group', 'phone_group', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->update();
}
}

View File

@ -1,42 +0,0 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddScheduledTag 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('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();
}
}

View File

@ -1,38 +0,0 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddReachedLimitPhoneStatus 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'], 'default' => 'available']);
$table->save();
}
}

View File

@ -1,24 +0,0 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddIndexToSmsTimestamps extends AbstractMigration
{
/**
* Add indexes on most SMS table timestamp (and possibly other fields) to improve perfs on query using date, like stats, sending limits, etc.
*/
public function change()
{
$table = $this->table('sended');
$table->addIndex('at');
$table->update();
$table = $this->table('received');
$table->addIndex('at');
$table->update();
$table = $this->table('scheduled');
$table->addIndex('at');
$table->update();
}
}

View File

@ -1,24 +0,0 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddIndexOnSendedUid extends AbstractMigration
{
/**
* Modify sended uid and call to be 100 char long, we dont need a 500 char uid and too long a char ss hurting perfs
* Add index on sended uid to make status update more efficient
*/
public function change()
{
$table = $this->table('sended');
$table->changeColumn('uid', 'string', ['limit' => 100]);
$table->addIndex('uid');
$table->update();
$table = $this->table('call');
$table->changeColumn('uid', 'string', ['limit' => 100]);
$table->addIndex('uid');
$table->update();
}
}

View File

@ -91,21 +91,21 @@
/**
* Cette fonction permet de faire un retour sous forme de json
* @param array $datas : Les données à retourner sous forme de json
* @param array $data : Les données à retourner sous forme de json
* @param boolean $secure : Défini si l'affichage doit être sécurisé contre les XSS, par défaut true
* @return ApiController : On retourne l'API controlleur lui meme pour pouvoir chainer
*/
public function json ($datas, $secure = true)
public function json ($data, $secure = true)
{
header('Content-Type: application/json');
if ($secure)
{
echo htmlspecialchars(json_encode($datas), ENT_NOQUOTES);
echo htmlspecialchars(json_encode($data), ENT_NOQUOTES);
}
else
{
echo json_encode($datas);
echo json_encode($data);
}
return $this;

View File

@ -24,7 +24,7 @@
if (!is_readable($template_path))
{
throw new exceptions\DescartesExceptionTemplateNotReadable('Template ' . $template_path . ' is not readable.');
throw new DescartesTemplateNotReadableException('Template ' . $template_path . ' is not readable.');
}
require $template_path;

View File

@ -144,7 +144,7 @@
/**
* Generate IN query params and values
* @param array $values : Values to generate in array from
* @param string $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,11 +214,6 @@
$operator = '>';
break;
case ('%' == $first_char) :
$true_fieldname = mb_substr($fieldname, 1);
$operator = 'LIKE';
break;
case ('=' == $first_char) :
$true_fieldname = mb_substr($fieldname, 1);
$operator = '=';
@ -232,11 +227,8 @@
//Protect against injection in fieldname
$true_fieldname = preg_replace('#[^a-zA-Z0-9_]#', '', $true_fieldname);
// 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];
$query = '`' . $true_fieldname . '` ' . $operator . ' :where_' . $true_fieldname;
$param = ['where_' . $true_fieldname => $value];
return ['QUERY' => $query, 'PARAM' => $param];
}
@ -366,6 +358,7 @@
}
$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)

View File

@ -72,7 +72,8 @@
protected static function clean_url (string $url)
{
$to_remove = parse_url(HTTP_PWD, PHP_URL_PATH);
$url = mb_strcut($url, $to_remove ? mb_strlen($to_remove) : 0);
$url = mb_strcut($url, mb_strlen($to_remove));
$url = parse_url($url, PHP_URL_PATH);
return $url;
@ -251,7 +252,6 @@
$type = $parameter->getType();
$type = $type ?? false;
$type = ($type instanceof \ReflectionNamedType) ? $type->getName() : $type;
if ($type)
{

View File

@ -4,32 +4,25 @@
/*
* Define Descartes env
*/
$http_dir_path = ''; //Path we need to put after servername in url to access app
$https = $_SERVER['HTTPS'] ?? 0;
$http_dir_path = '/raspisms'; //Path we need to put after servername in url to access app
$http_protocol = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://';
$http_server_name = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost';
$http_server_port = isset($_SERVER['SERVER_PORT']) ? ($_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'] : '';
$https = $_SERVER['HTTPS'] ?? false;
// Check for proxy forward
$forwarded_https = ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? $_SERVER['HTTP_FORWARDED_PROTO'] ?? NULL) == 'https';
$forwarded_ssl = ($_SERVER['HTTP_X_FORWARDED_SSL'] ?? NULL) == 'on';
$proxy = $forwarded_https || $forwarded_ssl;
$http_protocol = 'http://';
if ($https)
if ( !isset($_SERVER['SERVER_PORT']) || ($_SERVER['SERVER_PORT'] == 80 && !$https) || ($_SERVER['SERVER_PORT'] == 443 && $https) )
{
$http_protocol = 'https://';
$http_server_port = '';
}
else
{
$http_server_port = ':' . $_SERVER['SERVER_PORT'];
}
$http_server_name = $_SERVER['SERVER_NAME'] ?? 'localhost';
// Check port to only set it if not default port
$port = $_SERVER['SERVER_PORT'] ?? '';
$port = ($port == 80 && !$https) ? '' : $port;
$port = ($port == 443 && $https) ? '' : $port;
$port = $proxy ? '' : $port;
$http_server_port = $port ? ':' . $port : '';
$pwd = substr(__DIR__, 0, strrpos(__DIR__, '/'));
$http_pwd = $http_protocol . $http_server_name . $http_server_port . $http_dir_path;
$env = [
//Global http and file path
@ -38,25 +31,31 @@
'HTTP_SERVER_NAME' => $http_server_name,
'HTTP_SERVER_PORT' => $http_server_port,
'PWD' => $pwd,
'HTTP_PWD' => $http_pwd,
//path of back resources
'PWD_CONTROLLER' => $pwd . '/controllers', //Controllers dir
'PWD_MODEL' => $pwd . '/models', //Models dir
'PWD_TEMPLATES' => $pwd . '/templates', //Templates dir
//path of front resources
'PWD_ASSETS' => $pwd . '/assets', //Assets dir
'PWD_ASSETS' => $pwd . '/assets', //Assets dir
'HTTP_PWD_ASSETS' => $http_pwd . '/assets', //HTTP path of asset dir
//images
'PWD_IMG' => $pwd . '/assets' . '/img',
'HTTP_PWD_IMG' => $http_pwd . '/assets' . '/img',
//css
'PWD_CSS' => $pwd . '/assets' . '/css',
'HTTP_PWD_CSS' => $http_pwd . '/assets' . '/css',
//javascript
'PWD_JS' => $pwd . '/assets' . '/js',
'HTTP_PWD_JS' => $http_pwd . '/assets' . '/js',
//fonts
'PWD_FONT' => $pwd . '/assets' . '/font',
'HTTP_PWD_FONT' => $http_pwd . '/assets' . '/font',
];

View File

@ -21,7 +21,6 @@
$environment = [];
$env = [];
// Load descartes base env
require_once(__DIR__ . '/env.php');
$environment = array_merge($environment, $env);
@ -32,8 +31,11 @@
$environment = array_merge($environment, $env);
}
//Define all Descartes constants
define_array($environment);
### GLOBAL ENV ###
//Load global app env
$environment = [];
$env = [];
if (file_exists(__DIR__ . '/../env.php'))
{
@ -41,30 +43,19 @@
$environment = array_merge($environment, $env);
}
define_array($environment);
### SPECIFIC ENV ###
// Load specific environment env
$environment = [];
$env = [];
if (isset($environment['ENV']) && file_exists(__DIR__ . '/../env.' . $environment['ENV'] . '.php'))
if (defined('ENV') && file_exists(__DIR__ . '/../env.' . ENV . '.php'))
{
require_once(__DIR__ . '/../env.' . $environment['ENV'] . '.php');
require_once(__DIR__ . '/../env.' . ENV . '.php');
$environment = array_merge($environment, $env);
}
### BUILD HTTP PWD CONSTS ###
// We compute http pwd at last minute to allow for simple overriding by user
// by simply defining custom HTTP_* (PROTOCOL, SERVER_NAME, SERVER_PORT, DIR_PATH)
$http_pwd = $environment['HTTP_PROTOCOL'] . $environment['HTTP_SERVER_NAME'] . $environment['HTTP_SERVER_PORT'] . $environment['HTTP_DIR_PATH'];
$env = [
"HTTP_PWD" => $http_pwd,
'HTTP_PWD_ASSETS' => $http_pwd . '/assets', //HTTP path of asset dir
'HTTP_PWD_IMG' => $http_pwd . '/assets' . '/img',
'HTTP_PWD_CSS' => $http_pwd . '/assets' . '/css',
'HTTP_PWD_JS' => $http_pwd . '/assets' . '/js',
'HTTP_PWD_FONT' => $http_pwd . '/assets' . '/font',
];
$environment = array_merge($environment, $env);
define_array($environment);
}

View File

@ -2,31 +2,28 @@
/*
This file define constants and options for the app
*/
$dir_path = '/raspisms';
$http_pwd = $environment['HTTP_PROTOCOL'] . $dir_path . $environment['HTTP_SERVER_PORT'] . $environment['HTTP_DIR_PATH'];
$env = [
'ENV' => '%APP_ENV%', #env name (probably 'dev' or 'prod'), this value is used to get the env.XXX.php.dist matching env file
'SESSION_NAME' => 'raspisms',
'HTTP_DIR_PATH' => $dir_path, // Override default dir path
//RaspiSMS settings
'WEBSITE_TITLE' => 'RaspiSMS',
'WEBSITE_DESCRIPTION' => '',
'WEBSITE_AUTHOR' => 'Raspberry Pi FR',
'PWD_SCRIPTS' => $environment['PWD'] . '/scripts',
'PWD_RECEIVEDS' => $environment['PWD'] . '/receiveds',
'HTTP_PWD_SOUND' => $http_pwd . '/assets' . '/sounds',
'PWD_ADAPTERS' => $environment['PWD'] . '/adapters',
'PWD_DATA' => $environment['PWD'] . '/data',
'HTTP_PWD_DATA' => $http_pwd . '/data',
'PWD_DATA_PUBLIC' => $environment['PWD'] . '/data/public',
'HTTP_PWD_DATA_PUBLIC' => $http_pwd . '/data/public',
'PWD_SCRIPTS' => PWD . '/scripts',
'PWD_RECEIVEDS' => PWD . '/receiveds',
'HTTP_PWD_SOUND' => HTTP_PWD_ASSETS . '/sounds',
'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%',
'ENABLE_COMMAND' => false,
'ENABLE_ACCOUNT_DELETION' => true,
'ENABLE_URL_SHORTENER' => %APP_URL_SHORTENER%,
//E-mail types
'EMAIL_RESET_PASSWORD' => [
@ -56,16 +53,17 @@
],
//Phone messages types
'QUEUE_ID_PHONE' => ftok(__FILE__, 'p'),
'QUEUE_TYPE_SEND_MSG_PREFIX' => 100,
'QUEUE_ID_PHONE_PREFIX' => ftok(__FILE__, 'p'),
'QUEUE_TYPE_SEND_MSG' => 1,
'QUEUE_TYPE_RECEIVE_MSG' => 2,
//Queues ids
'QUEUE_ID_WEBHOOK' => ftok(__FILE__, 'w'),
'QUEUE_TYPE_WEBHOOK' => 100,
'QUEUE_TYPE_WEBHOOK' => 3,
//Queue email
'QUEUE_ID_EMAIL' => ftok(__FILE__, 'e'),
'QUEUE_TYPE_EMAIL' => 100,
'QUEUE_TYPE_EMAIL' => 3,
//User default settings
'USER_DEFAULT_SETTINGS' => [
@ -85,12 +83,6 @@
'alert_quota_limit_reached' => 1,
'alert_quota_limit_close' => 0.9,
'hide_menus' => '',
'force_gsm_alphabet' => 0,
'phone_limit' => 0,
'phone_priority' => 0,
'shorten_url' => 0,
'smsstop_respond' => 1,
'smsstop_response' => 'Demande prise en compte, vous ne recevrez plus de messages.',
],
];

View File

@ -21,11 +21,4 @@
'FROM' => '%APP_MAIL_FROM%',
],
//YOURLS url shortener settings
'URL_SHORTENER' => [
'HOST' => '%APP_URL_SHORTENER_HOST%',
'USER' => '%APP_URL_SHORTENER_USER%',
'PASS' => '%APP_URL_SHORTENER_PASS%',
]
];

View File

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

View File

@ -13,36 +13,6 @@ namespace models;
class Phone extends StandardModel
{
const STATUS_AVAILABLE = 'available';
const STATUS_UNAVAILABLE = 'unavailable';
const STATUS_NO_CREDIT = 'no_credit';
const STATUS_LIMIT_REACHED = 'limit_reached';
/**
* Return all phones that belongs to active users
*
* @return array
*/
public function get_all_for_active_users()
{
$query = '
SELECT phone.*
FROM phone
LEFT JOIN user
ON phone.id_user = user.id
WHERE user.status = :status
';
$params = [
'status' => \models\User::STATUS_ACTIVE,
];
$result = $this->_run_query($query, $params);
return $result;
}
/**
* Return a phone by his name and user.
*
@ -68,48 +38,6 @@ namespace models;
return $this->_select_one('phone', ['name' => $name]);
}
/**
* Return a list of phone limits
*
* @param int $id_phone : Phone id
*
* @return array
*/
public function get_limits(int $id_phone)
{
return $this->_select('phone_limit', ['id_phone' => $id_phone]);
}
/**
* Add a limit for a phone.
*
* @param int $id_phone : Phone id
* @param int $volume : Limit in volume of SMS
* @param string $startpoint : A relative time to use as startpoint for counting volume. See https://www.php.net/manual/en/datetime.formats.relative.php
*
* @return mixed (bool|int) : False on error, new row id else
*/
public function insert_phone_limit(int $id_phone, int $volume, string $startpoint)
{
$success = $this->_insert('phone_limit', ['id_phone' => $id_phone, 'volume' => $volume, 'startpoint' => $startpoint]);
return $success ? $this->_last_id() : false;
}
/**
* Delete limits for a phone
*
* @param array $id_phone : Phone id
*
* @return array
*/
public function delete_phone_limits(int $id_phone)
{
return $this->_delete('phone_limit', ['id_phone' => $id_phone]);
}
/**
* Return table name.
*/

View File

@ -1,123 +0,0 @@
<?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 PhoneGroup extends StandardModel
{
/**
* Return a list of phone groups for a user.
* Add a column nb_phone.
*
* @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 pg.*, COUNT(pgp.id) as nb_phone
FROM `phone_group` as pg
LEFT JOIN phone_group_phone as pgp
ON pg.id = pgp.id_phone_group
WHERE pg.id_user = :id_user
GROUP BY pg.id
';
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);
}
/**
* Return a phone group by his name for a user.
*
* @param int $id_user : User id
* @param string $name : Group name
*
* @return array
*/
public function get_by_name_for_user(int $id_user, string $name)
{
return $this->_select_one($this->get_table_name(), ['id_user' => $id_user, 'name' => $name]);
}
/**
* Delete relations between phone group and phone for a group.
*
* @param int $id_phone_group : Group id
*
* @return int : Number of deleted rows
*/
public function delete_phone_group_phone_relations(int $id_phone_group)
{
return $this->_delete('phone_group_phone', ['id_phone_group' => $id_phone_group]);
}
/**
* Insert a relation between a phone group and a phone.
*
* @param int $id_phone_group : Phone Group id
* @param int $id_phone : Phone id
*
* @return mixed (bool|int) : False on error, new row id else
*/
public function insert_phone_group_phone_relation(int $id_phone_group, int $id_phone)
{
$success = (bool) $this->_insert('phone_group_phone', ['id_phone_group' => $id_phone_group, 'id_phone' => $id_phone]);
return $success ? $this->_last_id() : false;
}
/**
* Get phone groups phones.
*
* @param int $id_phone_group : Phone Group id
*
* @return array : Phones of the group
*/
public function get_phones(int $id_phone_group)
{
$query = '
SELECT *
FROM `phone`
WHERE id IN (SELECT id_phone FROM `phone_group_phone` WHERE id_phone_group = :id_phone_group)
';
$params = ['id_phone_group' => $id_phone_group];
return $this->_run_query($query, $params);
}
/**
* Return table name.
*/
protected function get_table_name(): string
{
return 'phone_group';
}
}

View File

@ -178,42 +178,6 @@ namespace models;
return $this->_select_one('sended', ['id_user' => $id_user, 'uid' => $uid, 'adapter' => $adapter]);
}
/**
* Get number of sended SMS since a date for a phone
*
* @param int $id_user : User id
* @param int $id_phone : Phone id we want the number of sended message for
* @param ?\DateTime $since : Date since which we want sended Number. 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 = null, ?\DateTime $before = null, ?string $tag_like = null) : int
{
$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);
}
/**
* Get number of sended SMS for every date since a date for a specific user.
*
@ -222,14 +186,14 @@ namespace models;
*
* @return array
*/
public function count_by_day_and_status_since_for_user($id_user, $date)
public function count_by_day_since_for_user($id_user, $date)
{
$query = "
SELECT COUNT(id) as nb, status, DATE_FORMAT(at, '%Y-%m-%d') as at_ymd
SELECT COUNT(id) as nb, DATE_FORMAT(at, '%Y-%m-%d') as at_ymd
FROM sended
WHERE at > :date
AND id_user = :id_user
GROUP BY at_ymd, status
GROUP BY at_ymd
";
$params = [

View File

@ -27,18 +27,6 @@ namespace models;
return $this->_update($this->get_table_name(), ['value' => $value], ['id_user' => $id_user, 'name' => $name]);
}
/**
* Get a user setting by his name for a user.
*
* @param int $id_user : user id
*
* @return array
*/
public function get_by_name_for_user(int $id_user, string $name)
{
return $this->_select_one($this->get_table_name(), ['name' => $name, 'id_user' => $id_user]);
}
/**
* Return table name.
*/

View File

@ -13,8 +13,6 @@ namespace models;
class SmsStop extends StandardModel
{
const SMS_STOP_TAG = 'SMS_STOP';
/**
* Return a smsstop by his number and user.
*

View File

@ -76,7 +76,6 @@
'delete' => '/group/delete/{csrf}/',
'edit' => '/group/edit/',
'update' => '/group/update/{csrf}/',
'preview' => '/group/preview/{id_group}/',
'json_list' => '/groups.json/',
],
@ -89,7 +88,6 @@
'edit' => '/conditional_group/edit/',
'update' => '/conditional_group/update/{csrf}/',
'contacts_preview' => '/conditional_group/preview/',
'preview' => '/conditional_group/preview/{id_group}/',
'json_list' => '/conditional_groups.json/',
],
@ -162,22 +160,6 @@
'add' => '/phone/add/',
'create' => '/phone/create/{csrf}/',
'delete' => '/phone/delete/{csrf}/',
'edit' => '/phone/edit/',
'update' => '/phone/update/{csrf}/',
'update_status' => '/phone/update_status/{csrf}/',
'json_list' => '/phones.json/',
],
'PhoneGroup' => [
'list' => '/phone_group/',
'list_json' => '/phone_group/json/',
'add' => '/phone_group/add/',
'create' => '/phone_group/create/{csrf}/',
'delete' => '/phone_group/delete/{csrf}/',
'edit' => '/phone_group/edit/',
'update' => '/phone_group/update/{csrf}/',
'preview' => '/phone_group/preview/{id_group}/',
'json_list' => '/phone_group.json/',
],
'Call' => [
@ -208,7 +190,6 @@
'/api/list/{entry_type}/',
'/api/list/{entry_type}/{page}/',
],
'get_usage' => '/api/usage/',
'post_scheduled' => [
'/api/scheduled/',
],
@ -221,9 +202,6 @@
'post_update_phone' => [
'/api/phone/{id}/',
],
'post_update_phone_status' => [
'/api/phone/{id}/status/',
],
'delete_phone' => [
'/api/phone/{id}/',
],

View File

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

View File

@ -1,160 +1,147 @@
<?php
//Template dashboard
$this->render('incs/head')
//Template dashboard
$this->render('incs/head')
?>
<div id="wrapper">
<?php
$this->render('incs/nav', ['page' => 'dashboard'])
$this->render('incs/nav', ['page' => 'dashboard'])
?>
<div id="page-wrapper">
<div class="container-fluid">
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">
Dashboard <small>Statistiques d'utilisation</small>
</h1>
<ol class="breadcrumb">
<li class="active">
<i class="fa fa-dashboard"></i> Dashboard
</li>
</ol>
</div>
</div>
<!-- /.row -->
<div id="page-wrapper">
<div class="container-fluid">
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">
Dashboard <small>Statistiques d'utilisation</small>
</h1>
<ol class="breadcrumb">
<li class="active">
<i class="fa fa-dashboard"></i> Dashboard
</li>
</ol>
</div>
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-3 col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-user fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_contacts; ?></div>
<div>Contacts</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Contact', 'list') ?>">
<div class="panel-footer">
<span class="pull-left">Voir vos contacts</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-green">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-group fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_groups; ?></div>
<div>Groupes</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Group', 'list') ?>">
<div class="panel-footer">
<span class="pull-left">Voir les groupes</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-yellow">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-calendar fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_scheduleds; ?></div>
<div>SMS programmés</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Scheduled', 'list') ?>">
<div class="panel-footer">
<span class="pull-left">Voir les SMS programmés</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-red">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-eye-slash fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_unreads; ?></div>
<div>SMS non lus</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Received', 'list_unread') ?>">
<div class="panel-footer">
<span class="pull-left">Voir les SMS non lus</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-3 col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-user fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_contacts; ?></div>
<div>Contacts</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Contact', 'list') ?>">
<div class="panel-footer">
<span class="pull-left">Voir vos contacts</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-green">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-group fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_groups; ?></div>
<div>Groupes</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Group', 'list') ?>">
<div class="panel-footer">
<span class="pull-left">Voir les groupes</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-yellow">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-calendar fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_scheduleds; ?></div>
<div>SMS programmés</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Scheduled', 'list') ?>">
<div class="panel-footer">
<span class="pull-left">Voir les SMS programmés</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="panel panel-red">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-eye-slash fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge"><?php echo $nb_unreads; ?></div>
<div>SMS non lus</div>
</div>
</div>
</div>
<a href="<?php echo \descartes\Router::url('Received', 'list_unread') ?>">
<div class="panel-footer">
<span class="pull-left">Voir les SMS non lus</span>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default dashboard-panel-chart">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-area-chart fa-fw"></i> SMS envoyés depuis le <?= $stats_start_date_formated; ?> : </h3>
<span style="color: #5CB85C;">SMS envoyés (moyenne = <?php echo $avg_sendeds; ?> par jour).</span><br/>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default dashboard-panel-chart">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-area-chart fa-fw"></i> Activité de la semaine : </h3>
<span style="color: #5CB85C;">SMS envoyés (moyenne = <?php echo $avg_sendeds; ?> par jour).</span><br/>
<span style="color: #EDAB4D">SMS reçus (moyenne = <?php echo $avg_receiveds; ?> par jour).</span>
<?php if ($quota_unused) { ?>
<br/>
<span style="color: #d9534f">Crédits restants : <?= $quota_unused; ?>.</span>
<?php } ?>
</div>
<div class="panel-body">
<div id="morris-bar-chart-sended"></div>
</div>
</div>
</div>
</div>
</div>
<div class="panel-body">
<div id="morris-area-chart"></div>
</div>
</div>
</div>
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default dashboard-panel-chart">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-area-chart fa-fw"></i> SMS reçus depuis le <?= $stats_start_date_formated; ?> : </h3>
<span style="color: #EDAB4D">SMS reçus (moyenne = <?php echo $avg_receiveds; ?> par jour).</span>
</div>
<div class="panel-body">
<div id="morris-bar-chart-received"></div>
</div>
</div>
</div>
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-upload fa-fw"></i> SMS Envoyés</h3>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-upload fa-fw"></i> SMS Envoyés</h3>
</div>
<div class="panel-body">
<?php if (!$sendeds) { ?>
Vous n'avez envoyé aucun SMS pour l'instant.
@ -181,14 +168,14 @@
<a href="<?php echo \descartes\Router::url('Sended', 'list'); ?>">Voir tous les SMS envoyés <i class="fa fa-arrow-circle-right"></i></a>
</div>
<?php } ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-download fa-fw"></i> SMS Reçus</h3>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-download fa-fw"></i> SMS Reçus</h3>
</div>
<div class="panel-body">
<?php if (!$receiveds) { ?>
Vous n'avez reçu aucun SMS pour l'instant.
@ -217,14 +204,14 @@
<a href="<?php echo \descartes\Router::url('Received', 'list'); ?>">Voir tous les SMS reçus <i class="fa fa-arrow-circle-right"></i></a>
</div>
<?php } ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-clock-o fa-fw"></i> Évènements survenus</h3>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-clock-o fa-fw"></i> Évènements survenus</h3>
</div>
<div class="panel-body">
<?php if (!$events) { ?>
Aucun évènement n'est encore survenu.
@ -241,73 +228,41 @@
<a href="<?php echo \descartes\Router::url('Event', 'list'); ?>">Voirs tous les évènements survenus <i class="fa fa-arrow-circle-right"></i></a>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<!-- /.row -->
</div>
</div>
</div>
</div>
<!-- /.row -->
</div>
<!-- /.container-fluid -->
</div>
<!-- /.container-fluid -->
</div>
<!-- /#page-wrapper -->
</div>
<!-- /#page-wrapper -->
</div>
<script>
jQuery(document).ready(function()
{
Morris.Bar({
element: 'morris-bar-chart-sended',
fillOpacity: 0.4,
data: <?php echo $data_bar_chart_sended;?>,
jQuery(document).ready(function()
{
Morris.Area({
element: 'morris-area-chart',
behaveLikeLine: true,
fillOpacity: 0.4,
data: <?php echo $data_area_chart;?>,
xkey: 'period',
parseTime: false,
ykeys: ['sendeds_failed', 'sendeds_unknown', 'sendeds_delivered'],
labels: ['SMS échoués', 'SMS inconnus', 'SMS délivrés'],
barColors: ['#D9534F', '#337AB7', '#5CB85C'],
goals: [<?php echo $avg_sendeds; ?>,],
goalLineColors: ['#5CB85C'],
goalStrokeWidth: 2,
pointSize: 4,
hideHover: 'auto',
resize: true,
stacked: true,
hoverCallback: function (index, options, content, row) {
ret = '';
for (i = 0; i < options.ykeys.length; i++)
{
ret += options.labels[i];
ret += ' : ';
ret += row[options.ykeys[i]];
ret += ' (';
ret += (row[options.ykeys[i]] / (row.sendeds_total ? row.sendeds_total : 1) * 100).toFixed(2);
ret += '%)';
ret += "<br/>";
}
return ret;
}
});
Morris.Bar({
element: 'morris-bar-chart-received',
fillOpacity: 0.4,
data: <?php echo $data_bar_chart_received;?>,
xkey: 'period',
parseTime: false,
ykeys: ['receiveds'],
labels: ['SMS reçus'],
barColors: ['#EDAB4D'],
goals: [<?php echo $avg_receiveds; ?>],
goalLineColors: ['#EDAB4D'],
goalStrokeWidth: 2,
pointSize: 4,
hideHover: 'auto',
resize: true,
});
});
ykeys: ['sendeds', 'receiveds'],
labels: ['SMS envoyés', 'SMS reçus'],
lineColors: ['#5CB85C', '#EDAB4D'],
goals: [<?php echo $avg_sendeds; ?>, <?php echo $avg_receiveds; ?>],
goalLineColors: ['#5CB85C', '#EDAB4D'],
goalStrokeWidth: 2,
pointSize: 4,
hideHover: 'auto',
resize: true
});
});
</script>
<!-- /#wrapper -->
<?php
$this->render('incs/footer');
$this->render('incs/footer');

Some files were not shown because too many files have changed in this diff Show More