diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..e029dfb --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +AddType application/x-httpd-php .html .htm \ No newline at end of file diff --git a/PCSaves.php b/PCSaves.php new file mode 100644 index 0000000..7279184 --- /dev/null +++ b/PCSaves.php @@ -0,0 +1,56 @@ + + + + + FirstAide + + + + +
+
+
+

PC Saves Anonymous Helpline

+
+
+ + + + +
+

+ The PC SAVES Helpline provides anonymous, confidential crisis intervention, support, and information via a call, text, or online chat to Peace Corps Volunteers and Trainees. All options are staffed by trained professionals not affiliated with Peace Corps, available 24/7. No personally identifying information will be collected. +

+ Learn More: pcsaveshelpine.org +
+ + + + +
+
+ + + + + diff --git a/README.md b/README.md index 81867e7..b5bed6c 100644 --- a/README.md +++ b/README.md @@ -1 +1,111 @@ -# PCSA-web +# FirstAide - Web App + +The project repo of FirstAide-Web : [FirstAide-web repo](https://github.com/systers/FirstAide-web) + +FirstAide is a web application being developed for the Peace Corps. Peace Corps aim to provide support to all the volunteers who have been sexually assaulted. The volunteers can get reporting procedures and other relevant information. The initiative is to provide commitment to the volunteers who are victims of sexual assault and also to ensure their safety. + +The project is built using the following technologies: + 1. PHP (Backend) + 2. HTML and CSS (Frontend) + 3. Jquery and Javascript + 4. MySql (Database) + +![working](https://cloud.githubusercontent.com/assets/14356938/15768596/d4ae5218-296f-11e6-9102-0d39e2bcd1da.jpg) + +The welcome page of project + +![recommended pages](https://cloud.githubusercontent.com/assets/14356938/15768497/001cb95e-296f-11e6-8cf4-052ffc24e154.png) + +## Getting Started + +### Installation +Download XAMPP from here [XAMPP download](https://www.apachefriends.org/download.html).You will also need a text editor (Sublime or Notepad++) + +Go to your terminal and execute this command + + $ git clone https://github.com/systers/FirstAide-web + +Or download the ZIP file from above + +###Opening and Running +1. Launch XAMPP by opening XAMPP control panel +2. Start 'Apache' and 'MySQL' +3. Check if XAMPP has started using the url 'http://localhost:8080/' (Here 8080 is the port number, use the one which you have configured) +4. Now extract the ZIP file of the project FirstAide-web which you have downloaded and paste it in the C:\xampp\htdocs (the path where your xampp is present) +5. Open the file which you want to work with using the text editor you have downloaded +6. Make the required changes and save the file +7. Now, view your changes using the url http://localhost:8080/directory_name/file_name.extension Example: 'http://localhost:8080/FirstAide-web/login.html' + +###Documentation + +The documentation can be found here + +1.[project docs](https://github.com/systers/FirstAide-web/tree/master/project-docs) + +2.[docs of GSOC 16](https://github.com/systers/FirstAide-web/tree/master/work-docs) + +###Guide to Naming Convention Used +[Conventions and rules](https://google.github.io/styleguide/htmlcssguide.xml) + +###How to Use CALL and SMS feature +This project makes use of Twilio for sending SMS and making CALLS. Twilio is a third party platform which provides API in PHP for serving the purpose. +SMS and Call had been used in: +Get Help Now module +Group SMS in : +Circle of Trust module + +Read more about Twilio here: [Twilio](https://www.twilio.com/) + +####Steps to enable Twilio in your localhost +1. Get started with SMS and Call by creating an Account on Twilio here : [Twilio Register](https://www.twilio.com/try-twilio) +2. Setting Account SID and Token + + Take the Account SID and Token provided by Twilio here [Your Twilio Credentials](https://www.twilio.com/console) + + Paste them at the correct places in the twilioSMS.php, twilioCall.php and groupsms.php files +3. Setting From Number + Get a phone number from Twilio here: [Twilio phone number](https://www.twilio.com/console/phone-numbers/dashboard). + + Use this as the from number in the files twilioSMS.php, twilioCall.php and groupsms.php +4. Setting To Numbers : + If you are using Twilio Trial Account you can send a SMS or make a Call to a Twilio verified number only. Upgraded accounts may not need to verify a number + + Procedure to verify a number in Twilio + 1. Go to (https://www.twilio.com/console/phone-numbers/dashboard) + 2. Then on the left side vertical menu select Verified Caller IDs + 3. Click the + symbol and verify a number + + After getting the numbers verified it's time to use this numbers to test the feature + While adding the numbers make sure you enter the country code as well else, it won't work. + #####Use in Get Help Now module + 1. Go to javascripts/getHelpNowPhNo.js + 2. Replace the sample numbers to your verified numbers + 3. Make Call or Send SMS from the App + + #####Use in Circle of Trust module + 1. Open the app + 2. Go to Circle of Trust from the menu + 3. Click edit icon + 4. Add your verified numbers there + 5. Submit and go back to Circle of Trust + 6. Click Help Me icon and select a message + 7. An alert pops out saying 'Success' once messages are sent + +References : + +[Tutorial for SMS](https://www.youtube.com/watch?v=jZPeNfLD5Yc) + +[Tutorial for Group SMS](https://www.youtube.com/watch?v=G4oluQf_7S4) + +## Contributing +If you want to contribute to FirstAide-Web, subscribe to our [systers-dev](http://systers.org/mailman/listinfo/systers-dev) mailing list and shoot an introductory email. Pick an open issue from the [issue list](https://github.com/systers/FirstAide-Web/issues), claim it in the comments and wait for approval, fix the issue and send a pull request. +If you find an issue yourself you can report it, a community member will get back to you and approve the issue. Then you can fix it and submit a PR. Please go through our issue list first and make sure the issues you are reporting do not replicate the issues already reported. If you have issues on multiple pages, report them separately. Do not combine them into a single issue. + +### Rules for Contributing +1. Do not fix a new issue and submit a PR without reporting and getting it approved at first. +2. Do not fix an issue assigned to somebody else and submit a PR before the assignee does. +3. Do not report issues which are previously reported by others. (Please check the closed issues too before you report an issue). +4. Do not suggest completely new developments in the issue list. (Please use the mailing list for this kind of suggestions. Use issue list to suggest bugs/features in the already implemented sections.) +5. If an issue is reported by a person, it is automatically assigned to that person +6. Use meaningful commit messages and sqaush all your commits into one +7. Only pick the issues which are labeled as free diff --git a/Services/Twilio.php b/Services/Twilio.php new file mode 100644 index 0000000..18cf7ce --- /dev/null +++ b/Services/Twilio.php @@ -0,0 +1,826 @@ +version = in_array($version, $this->versions) ? $version : end($this->versions); + + if (null === $_http) { + if (!in_array('openssl', get_loaded_extensions())) { + throw new Services_Twilio_HttpException("The OpenSSL extension is required but not currently enabled. For more information, see http://php.net/manual/en/book.openssl.php"); + } + if (in_array('curl', get_loaded_extensions())) { + $_http = new Services_Twilio_TinyHttp( + $this->_getBaseUri(), + array( + "curlopts" => array( + CURLOPT_USERAGENT => self::qualifiedUserAgent(phpversion()), + CURLOPT_HTTPHEADER => array('Accept-Charset: utf-8'), + ), + ) + ); + } else { + $_http = new Services_Twilio_HttpStream( + $this->_getBaseUri(), + array( + "http_options" => array( + "http" => array( + "user_agent" => self::qualifiedUserAgent(phpversion()), + "header" => "Accept-Charset: utf-8\r\n", + ), + "ssl" => array( + 'verify_peer' => true, + 'verify_depth' => 5, + ), + ), + ) + ); + } + } + $_http->authenticate($sid, $token); + $this->http = $_http; + $this->retryAttempts = $retryAttempts; + } + + /** + * Build a query string from query data + * + * :param array $queryData: An associative array of keys and values. The + * values can be a simple type or a list, in which case the list is + * converted to multiple query parameters with the same key. + * :param string $numericPrefix: optional prefix to prepend to numeric keys + * :return: The encoded query string + * :rtype: string + */ + public static function buildQuery($queryData, $numericPrefix = '') { + $query = ''; + // Loop through all of the $query_data + foreach ($queryData as $key => $value) { + // If the key is an int, add the numeric_prefix to the beginning + if (is_int($key)) { + $key = $numericPrefix . $key; + } + + // If the value is an array, we will end up recursing + if (is_array($value)) { + // Loop through the values + foreach ($value as $value2) { + // Add an arg_separator if needed + if ($query !== '') { + $query .= '&'; + } + // Recurse + $query .= self::buildQuery(array($key => $value2), $numericPrefix); + } + } else { + // Add an arg_separator if needed + if ($query !== '') { + $query .= '&'; + } + // Add the key and the urlencoded value (as a string) + $query .= $key . '=' . urlencode((string)$value); + } + } + return $query; + } + + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + $json_path = $full_uri ? $path : "$path.json"; + if (!$full_uri && !empty($params)) { + $query_path = $json_path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $json_path; + } + return $query_path; + } + + /** + * Fully qualified user agent with the current PHP Version. + * + * :return: the user agent + * :rtype: string + */ + public static function qualifiedUserAgent($php_version) { + return self::USER_AGENT . " (php $php_version)"; + } + + /** + * POST to the resource at the specified path. + * + * :param string $path: Path to the resource + * :param array $params: Query string parameters + * + * :return: The object representation of the resource + * :rtype: object + */ + public function createData($path, $params = array(), $full_uri = false) + { + if (!$full_uri) { + $path = "$path.json"; + } + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + $response = $this->http->post( + $path, $headers, self::buildQuery($params, '') + ); + return $this->_processResponse($response); + } + + /** + * DELETE the resource at the specified path. + * + * :param string $path: Path to the resource + * :param array $params: Query string parameters + * + * :return: The object representation of the resource + * :rtype: object + */ + public function deleteData($path, $params = array()) + { + $uri = $this->getRequestUri($path, $params); + return $this->_makeIdempotentRequest(array($this->http, 'delete'), + $uri, $this->retryAttempts); + } + + /** + * Get the retry attempt limit used by the rest client + * + * :return: the number of retry attempts + * :rtype: int + */ + public function getRetryAttempts() { + return $this->retryAttempts; + } + + /** + * Get the api version used by the rest client + * + * :return: the API version in use + * :returntype: string + */ + public function getVersion() { + return $this->version; + } + + /** + * GET the resource at the specified path. + * + * :param string $path: Path to the resource + * :param array $params: Query string parameters + * :param boolean $full_uri: Whether the full URI has been passed as an + * argument + * + * :return: The object representation of the resource + * :rtype: object + */ + public function retrieveData($path, $params = array(), + $full_uri = false + ) + { + $uri = $this->getRequestUri($path, $params, $full_uri); + return $this->_makeIdempotentRequest(array($this->http, 'get'), + $uri, $this->retryAttempts); + } + + /** + * Get the base URI for this client. + * + * :return: base URI + * :rtype: string + */ + protected function _getBaseUri() { + return 'https://api.twilio.com'; + } + + /** + * Helper method for implementing request retry logic + * + * :param array $callable: The function that makes an HTTP request + * :param string $uri: The URI to request + * :param int $retriesLeft: Number of times to retry + * + * :return: The object representation of the resource + * :rtype: object + */ + protected function _makeIdempotentRequest($callable, $uri, $retriesLeft) { + $response = call_user_func_array($callable, array($uri)); + list($status, $headers, $body) = $response; + if ($status >= 500 && $retriesLeft > 0) { + return $this->_makeIdempotentRequest($callable, $uri, $retriesLeft - 1); + } else { + return $this->_processResponse($response); + } + } + + /** + * Convert the JSON encoded resource into a PHP object. + * + * :param array $response: 3-tuple containing status, headers, and body + * + * :return: PHP object decoded from JSON + * :rtype: object + * :throws: A :php:class:`Services_Twilio_RestException` if the Response is + * in the 300-500 range of status codes. + */ + private function _processResponse($response) + { + list($status, $headers, $body) = $response; + if ($status === 204) { + return true; + } + $decoded = json_decode($body); + if ($decoded === null) { + throw new Services_Twilio_RestException( + $status, + 'Could not decode response body as JSON. ' . + 'This likely indicates a 500 server error' + ); + } + if (200 <= $status && $status < 300) { + $this->last_response = $decoded; + return $decoded; + } + throw new Services_Twilio_RestException( + $status, + isset($decoded->message) ? $decoded->message : '', + isset($decoded->code) ? $decoded->code : null, + isset($decoded->more_info) ? $decoded->more_info : null + ); + } +} + +/** + * Create a client to talk to the Twilio Rest API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('2008-08-01', '2010-04-01'); + + public function __construct( + $sid, + $token, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) + { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + + $this->accounts = new Services_Twilio_Rest_Accounts($this, "/{$this->version}/Accounts"); + $this->account = $this->accounts->get($sid); + } +} + +/** + * Create a client to talk to the Twilio TaskRouter API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $workspaceSid: + * Workspace SID to work with + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new TaskRouter_Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class TaskRouter_Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('v1'); + private $accountSid; + + public function __construct( + $sid, + $token, + $workspaceSid, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) + { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + + $this->workspaces = new Services_Twilio_Rest_TaskRouter_Workspaces($this, "/{$this->version}/Workspaces"); + $this->workspace = $this->workspaces->get($workspaceSid); + $this->accountSid = $sid; + } + + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + if (!$full_uri && !empty($params)) { + $query_path = $path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $path; + } + return $query_path; + } + + public static function createWorkspace($sid, $token, $friendlyName, array $params = array(), Services_Twilio_TinyHttp $_http = null) + { + $taskrouterClient = new TaskRouter_Services_Twilio($sid, $token, null, null, $_http); + return $taskrouterClient->workspaces->create($friendlyName, $params); + } + + public function getTaskQueuesStatistics(array $params = array()) + { + return $this->retrieveData("/{$this->version}/Workspaces/{$this->workspace->sid}/TaskQueues/Statistics", $params); + } + + public function getTaskQueueStatistics($taskQueueSid, array $params = array()) + { + return $this->retrieveData("/{$this->version}/Workspaces/{$this->workspace->sid}/TaskQueues/{$taskQueueSid}/Statistics", $params); + } + + public function getWorkersStatistics(array $params = array()) + { + return $this->retrieveData("/{$this->version}/Workspaces/{$this->workspace->sid}/Workers/Statistics", $params); + } + + public function getWorkerStatistics($workerSid, array $params = array()) + { + return $this->retrieveData("/{$this->version}/Workspaces/{$this->workspace->sid}/Workers/{$workerSid}/Statistics", $params); + } + + public function getWorkflowStatistics($workflowSid, array $params = array()) + { + return $this->retrieveData("/{$this->version}/Workspaces/{$this->workspace->sid}/Workflows/{$workflowSid}/Statistics", $params); + } + + public function getWorkspaceStatistics(array $params = array()) + { + return $this->retrieveData("/{$this->version}/Workspaces/{$this->workspace->sid}/Statistics", $params); + } + + protected function _getBaseUri() + { + return 'https://taskrouter.twilio.com'; + } +} + +/** + * Create a client to talk to the Twilio Lookups API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new Lookups_Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class Lookups_Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('v1'); + private $accountSid; + + public function __construct( + $sid, + $token, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) + { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + + $this->accountSid = $sid; + $this->phone_numbers = new Services_Twilio_Rest_Lookups_PhoneNumbers($this, "/{$this->version}/PhoneNumbers"); + } + + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + if (!$full_uri && !empty($params)) { + $query_path = $path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $path; + } + return $query_path; + } + + /** + * Get the base URI for this client. + * + * :return: base URI + * :rtype: string + */ + protected function _getBaseUri() + { + return 'https://lookups.twilio.com'; + } + +} + +/** + * Create a client to talk to the Twilio Pricing API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new Pricing_Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class Pricing_Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('v1'); + + public function __construct( + $sid, + $token, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + + $this->voiceCountries = new Services_Twilio_Rest_Pricing_VoiceCountries( + $this, "/{$this->version}/Voice/Countries" + ); + $this->voiceNumbers = new Services_Twilio_Rest_Pricing_VoiceNumbers( + $this, "/{$this->version}/Voice/Numbers" + ); + $this->phoneNumberCountries = new Services_Twilio_Rest_Pricing_PhoneNumberCountries( + $this, "/{$this->version}/PhoneNumbers/Countries" + ); + $this->messagingCountries = new Services_Twilio_Rest_Pricing_MessagingCountries( + $this, "/{$this->version}/Messaging/Countries" + ); + } + + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + if (!$full_uri && !empty($params)) { + $query_path = $path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $path; + } + return $query_path; + } + + protected function _getBaseUri() { + return 'https://pricing.twilio.com'; + } + +} + +/** + * Create a client to talk to the Twilio Monitor API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new Monitor_Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class Monitor_Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('v1'); + + public function __construct( + $sid, + $token, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) + { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + + $this->events = new Services_Twilio_Rest_Monitor_Events($this, "/{$this->version}/Events"); + $this->alerts = new Services_Twilio_Rest_Monitor_Alerts($this, "/{$this->version}/Alerts"); + } + + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + if (!$full_uri && !empty($params)) { + $query_path = $path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $path; + } + return $query_path; + } + + protected function _getBaseUri() + { + return 'https://monitor.twilio.com'; + } + +} + +/** + * Create a client to talk to the Twilio SIP Trunking API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new Trunking_Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class Trunking_Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('v1'); + + public function __construct( + $sid, + $token, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) + { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + + $this->trunks = new Services_Twilio_Rest_Trunking_Trunks($this, "/{$this->version}/Trunks"); + } + + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + if (!$full_uri && !empty($params)) { + $query_path = $path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $path; + } + return $query_path; + } + + protected function _getBaseUri() + { + return 'https://trunking.twilio.com'; + } + +} + +/** + * Create a client to talk to the Twilio IP Messaging API. + * + * + * :param string $sid: Your Account SID + * :param string $token: Your Auth Token from `your dashboard + * `_ + * :param string $version: API version to use + * :param $_http: A HTTP client for making requests. + * :type $_http: :php:class:`Services_Twilio_TinyHttp` + * :param int $retryAttempts: + * Number of times to retry failed requests. Currently only idempotent + * requests (GET's and DELETE's) are retried. + * + * Here's an example: + * + * .. code-block:: php + * + * require('Services/Twilio.php'); + * $client = new Messaging_Services_Twilio('AC123', '456bef', null, null, 3); + * // Take some action with the client, etc. + */ +class IPMessaging_Services_Twilio extends Base_Services_Twilio +{ + protected $versions = array('v1'); + public function __construct( + $sid, + $token, + $version = null, + Services_Twilio_TinyHttp $_http = null, + $retryAttempts = 1 + ) + { + parent::__construct($sid, $token, $version, $_http, $retryAttempts); + $this->services = new Services_Twilio_Rest_IPMessaging_Services($this, "/{$this->version}/Services"); + $this->credentials = new Services_Twilio_Rest_IPMessaging_Credentials($this, "/{$this->version}/Credentials"); + } + /** + * Construct a URI based on initial path, query params, and paging + * information + * + * We want to use the query params, unless we have a next_page_uri from the + * API. + * + * :param string $path: The request path (may contain query params if it's + * a next_page_uri) + * :param array $params: Query parameters to use with the request + * :param boolean $full_uri: Whether the $path contains the full uri + * + * :return: the URI that should be requested by the library + * :returntype: string + */ + public function getRequestUri($path, $params, $full_uri = false) + { + if (!$full_uri && !empty($params)) { + $query_path = $path . '?' . http_build_query($params, '', '&'); + } else { + $query_path = $path; + } + return $query_path; + } + protected function _getBaseUri() + { + return 'https://ip-messaging.twilio.com'; + } +} diff --git a/Services/Twilio/AccessToken.php b/Services/Twilio/AccessToken.php new file mode 100644 index 0000000..1cc370c --- /dev/null +++ b/Services/Twilio/AccessToken.php @@ -0,0 +1,135 @@ +signingKeySid = $signingKeySid; + $this->accountSid = $accountSid; + $this->secret = $secret; + $this->ttl = $ttl; + + if (!is_null($identity)) { + $this->identity = $identity; + } + + $this->grants = array(); + } + + /** + * Set the identity of this access token + * + * @param string $identity identity of the grant + * + * @return Services_Twilio_AccessToken updated access token + */ + public function setIdentity($identity) + { + $this->identity = $identity; + return $this; + } + + /** + * Returns the identity of the grant + * + * @return string the identity + */ + public function getIdentity() + { + return $this->identity; + } + + /** + * Set the nbf of this access token + * + * @param integer $nbf nbf in epoch seconds of the grant + * + * @return Services_Twilio_AccessToken updated access token + */ + public function setNbf($nbf) + { + $this->nbf = $nbf; + return $this; + } + + /** + * Returns the nbf of the grant + * + * @return integer the nbf in epoch seconds + */ + public function getNbf() + { + return $this->nbf; + } + + /** + * Add a grant to the access token + * + * @param Services_Twilio_Auth_Grant $grant to be added + * + * @return $this the updated access token + */ + public function addGrant(Services_Twilio_Auth_Grant $grant) + { + $this->grants[] = $grant; + return $this; + } + + + public function toJWT($algorithm = 'HS256') + { + $header = array( + 'cty' => 'twilio-fpa;v=1', + 'typ' => 'JWT' + ); + + $now = time(); + + $grants = array(); + if ($this->identity) { + $grants['identity'] = $this->identity; + } + + foreach ($this->grants as $grant) { + $payload = $grant->getPayload(); + if (empty($payload)) { + $payload = json_decode('{}'); + } + + $grants[$grant->getGrantKey()] = $payload; + } + + if (empty($grants)) { + $grants = json_decode('{}'); + } + + $payload = array( + 'jti' => $this->signingKeySid . '-' . $now, + 'iss' => $this->signingKeySid, + 'sub' => $this->accountSid, + 'exp' => $now + $this->ttl, + 'grants' => $grants + ); + + if (!is_null($this->nbf)) { + $payload['nbf'] = $this->nbf; + } + + return JWT::encode($payload, $this->secret, $algorithm, $header); + } + + public function __toString() + { + return $this->toJWT(); + } +} diff --git a/Services/Twilio/Auth/ConversationsGrant.php b/Services/Twilio/Auth/ConversationsGrant.php new file mode 100644 index 0000000..f7e02aa --- /dev/null +++ b/Services/Twilio/Auth/ConversationsGrant.php @@ -0,0 +1,54 @@ +configurationProfileSid; + } + + /** + * @param string $configurationProfileSid the configuration profile sid + * we want to enable for this grant + * + * @return Services_Twilio_Auth_ConversationsGrant updated grant + */ + public function setConfigurationProfileSid($configurationProfileSid) + { + $this->configurationProfileSid = $configurationProfileSid; + return $this; + } + + /** + * Returns the grant type + * + * @return string type of the grant + */ + public function getGrantKey() + { + return "rtc"; + } + + /** + * Returns the grant data + * + * @return array data of the grant + */ + public function getPayload() + { + $payload = array(); + if ($this->configurationProfileSid) { + $payload['configuration_profile_sid'] = $this->configurationProfileSid; + } + + return $payload; + } + +} \ No newline at end of file diff --git a/Services/Twilio/Auth/Grant.php b/Services/Twilio/Auth/Grant.php new file mode 100644 index 0000000..5b4434b --- /dev/null +++ b/Services/Twilio/Auth/Grant.php @@ -0,0 +1,18 @@ +serviceSid; + } + + /** + * Set the service sid of this grant + * + * @param string $serviceSid service sid of the grant + * + * @return Services_Twilio_Auth_IpMessagingGrant updated grant + */ + public function setServiceSid($serviceSid) + { + $this->serviceSid = $serviceSid; + return $this; + } + + /** + * Returns the endpoint id of the grant + * + * @return string the endpoint id + */ + public function getEndpointId() + { + return $this->endpointId; + } + + /** + * Set the endpoint id of the grant + * + * @param string $endpointId endpoint id of the grant + * + * @return Services_Twilio_Auth_IpMessagingGrant updated grant + */ + public function setEndpointId($endpointId) + { + $this->endpointId = $endpointId; + return $this; + } + + /** + * Returns the deployment role sid of the grant + * + * @return string the deployment role sid + */ + public function getDeploymentRoleSid() + { + return $this->deploymentRoleSid; + } + + /** + * Set the role sid of the grant + * + * @param string $deploymentRoleSid role sid of the grant + * + * @return Services_Twilio_Auth_IpMessagingGrant updated grant + */ + public function setDeploymentRoleSid($deploymentRoleSid) + { + $this->deploymentRoleSid = $deploymentRoleSid; + return $this; + } + + /** + * Returns the push credential sid of the grant + * + * @return string the push credential sid + */ + public function getPushCredentialSid() + { + return $this->pushCredentialSid; + } + + /** + * Set the credential sid of the grant + * + * @param string $pushCredentialSid push credential sid of the grant + * + * @return Services_Twilio_Auth_IpMessagingGrant updated grant + */ + public function setPushCredentialSid($pushCredentialSid) + { + $this->pushCredentialSid = $pushCredentialSid; + return $this; + } + + /** + * Returns the grant type + * + * @return string type of the grant + */ + public function getGrantKey() + { + return "ip_messaging"; + } + + /** + * Returns the grant data + * + * @return array data of the grant + */ + public function getPayload() + { + $payload = array(); + if ($this->serviceSid) { + $payload['service_sid'] = $this->serviceSid; + } + if ($this->endpointId) { + $payload['endpoint_id'] = $this->endpointId; + } + if ($this->deploymentRoleSid) { + $payload['deployment_role_sid'] = $this->deploymentRoleSid; + } + if ($this->pushCredentialSid) { + $payload['push_credential_sid'] = $this->pushCredentialSid; + } + + return $payload; + } + +} \ No newline at end of file diff --git a/Services/Twilio/AutoPagingIterator.php b/Services/Twilio/AutoPagingIterator.php new file mode 100644 index 0000000..5aacc49 --- /dev/null +++ b/Services/Twilio/AutoPagingIterator.php @@ -0,0 +1,110 @@ +generator = $generator; + $this->page = $page; + $this->size = $size; + $this->filters = $filters; + $this->next_page_uri = null; + $this->items = array(); + + // Save a backup for rewind() + $this->_args = array( + 'page' => $page, + 'size' => $size, + 'filters' => $filters, + ); + } + + public function current() + { + return current($this->items); + } + + public function key() + { + return key($this->items); + } + + /* + * Return the next item in the list, making another HTTP call to the next + * page of resources if necessary. + */ + public function next() + { + try { + $this->loadIfNecessary(); + return next($this->items); + } + catch (Services_Twilio_RestException $e) { + // 20006 is an out of range paging error, everything else is valid + if ($e->getCode() != 20006) { + throw $e; + } + } + } + + /* + * Restore everything to the way it was before we began paging. This gets + * called at the beginning of any foreach() loop + */ + public function rewind() + { + foreach ($this->_args as $arg => $val) { + $this->$arg = $val; + } + $this->items = array(); + $this->next_page_uri = null; + } + + public function count() + { + throw new BadMethodCallException('Not allowed'); + } + + public function valid() + { + try { + $this->loadIfNecessary(); + return key($this->items) !== null; + } + catch (Services_Twilio_RestException $e) { + // 20006 is an out of range paging error, everything else is valid + if ($e->getCode() != 20006) { + throw $e; + } + } + return false; + } + + /* + * Fill $this->items with a new page from the API, if necessary. + */ + protected function loadIfNecessary() + { + if (// Empty because it's the first time or last page was empty + empty($this->items) + // null key when the items list is iterated over completely + || key($this->items) === null + ) { + $page = call_user_func_array($this->generator, array( + $this->page, + $this->size, + $this->filters, + $this->next_page_uri, + )); + $this->next_page_uri = $page->next_page_uri; + $this->items = $page->getItems(); + $this->page = $this->page + 1; + } + } +} diff --git a/Services/Twilio/Capability.php b/Services/Twilio/Capability.php new file mode 100644 index 0000000..f41956a --- /dev/null +++ b/Services/Twilio/Capability.php @@ -0,0 +1,185 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_Capability +{ + public $accountSid; + public $authToken; + public $scopes; + + /** + * Create a new TwilioCapability with zero permissions. Next steps are to + * grant access to resources by configuring this token through the + * functions allowXXXX. + * + * @param $accountSid the account sid to which this token is granted access + * @param $authToken the secret key used to sign the token. Note, this auth + * token is not visible to the user of the token. + */ + public function __construct($accountSid, $authToken) + { + $this->accountSid = $accountSid; + $this->authToken = $authToken; + $this->scopes = array(); + $this->clientName = false; + } + + /** + * If the user of this token should be allowed to accept incoming + * connections then configure the TwilioCapability through this method and + * specify the client name. + * + * @param $clientName + */ + public function allowClientIncoming($clientName) + { + + // clientName must be a non-zero length alphanumeric string + if (preg_match('/\W/', $clientName)) { + throw new InvalidArgumentException( + 'Only alphanumeric characters allowed in client name.'); + } + + if (strlen($clientName) == 0) { + throw new InvalidArgumentException( + 'Client name must not be a zero length string.'); + } + + $this->clientName = $clientName; + $this->allow('client', 'incoming', + array('clientName' => $clientName)); + } + + /** + * Allow the user of this token to make outgoing connections. + * + * @param $appSid the application to which this token grants access + * @param $appParams signed parameters that the user of this token cannot + * overwrite. + */ + public function allowClientOutgoing($appSid, array $appParams=array()) + { + $this->allow('client', 'outgoing', array( + 'appSid' => $appSid, + 'appParams' => http_build_query($appParams, '', '&'))); + } + + /** + * Allow the user of this token to access their event stream. + * + * @param $filters key/value filters to apply to the event stream + */ + public function allowEventStream(array $filters=array()) + { + $this->allow('stream', 'subscribe', array( + 'path' => '/2010-04-01/Events', + 'params' => http_build_query($filters, '', '&'), + )); + } + + /** + * Generates a new token based on the credentials and permissions that + * previously has been granted to this token. + * + * @param $ttl the expiration time of the token (in seconds). Default + * value is 3600 (1hr) + * @return the newly generated token that is valid for $ttl seconds + */ + public function generateToken($ttl = 3600) + { + $payload = array( + 'scope' => array(), + 'iss' => $this->accountSid, + 'exp' => time() + $ttl, + ); + $scopeStrings = array(); + + foreach ($this->scopes as $scope) { + if ($scope->privilege == "outgoing" && $this->clientName) + $scope->params["clientName"] = $this->clientName; + $scopeStrings[] = $scope->toString(); + } + + $payload['scope'] = implode(' ', $scopeStrings); + return JWT::encode($payload, $this->authToken, 'HS256'); + } + + protected function allow($service, $privilege, $params) { + $this->scopes[] = new ScopeURI($service, $privilege, $params); + } +} + +/** + * Scope URI implementation + * + * Simple way to represent configurable privileges in an OAuth + * friendly way. For our case, they look like this: + * + * scope::? + * + * For example: + * scope:client:incoming?name=jonas + * + * @author Jeff Lindsay + */ +class ScopeURI +{ + public $service; + public $privilege; + public $params; + + public function __construct($service, $privilege, $params = array()) + { + $this->service = $service; + $this->privilege = $privilege; + $this->params = $params; + } + + public function toString() + { + $uri = "scope:{$this->service}:{$this->privilege}"; + if (count($this->params)) { + $uri .= "?".http_build_query($this->params, '', '&'); + } + return $uri; + } + + /** + * Parse a scope URI into a ScopeURI object + * + * @param string $uri The scope URI + * @return ScopeURI The parsed scope uri + */ + public static function parse($uri) + { + if (strpos($uri, 'scope:') !== 0) { + throw new UnexpectedValueException( + 'Not a scope URI according to scheme'); + } + + $parts = explode('?', $uri, 1); + $params = null; + + if (count($parts) > 1) { + parse_str($parts[1], $params); + } + + $parts = explode(':', $parts[0], 2); + + if (count($parts) != 3) { + throw new UnexpectedValueException( + 'Not enough parts for scope URI'); + } + + list($scheme, $service, $privilege) = $parts; + return new ScopeURI($service, $privilege, $params); + } + +} \ No newline at end of file diff --git a/Services/Twilio/HttpException.php b/Services/Twilio/HttpException.php new file mode 100644 index 0000000..b79a357 --- /dev/null +++ b/Services/Twilio/HttpException.php @@ -0,0 +1,3 @@ + array( + "headers" => "", + "timeout" => 60, + "follow_location" => true, + "ignore_errors" => true, + ), + "ssl" => array(), + ); + private $options = array(); + + public function __construct($uri = '', $kwargs = array()) { + $this->uri = $uri; + if (isset($kwargs['debug'])) { + $this->debug = true; + } + if (isset($kwargs['http_options'])) { + $this->options = $kwargs['http_options'] + self::$default_options; + } else { + $this->options = self::$default_options; + } + } + + public function __call($name, $args) { + list($res, $req_headers, $req_body) = $args + array(0, array(), ''); + + if (strpos($res, 'http') === 0) { + $url = $res; + } else { + $url = $this->uri . $res; + } + + $request_options = $this->options; + + if (isset($req_body) && strlen($req_body) > 0) { + $request_options['http']['content'] = $req_body; + } + + foreach($req_headers as $key => $value) { + $request_options['http']['header'] .= sprintf("%s: %s\r\n", $key, $value); + } + + if (isset($this->auth_header)) { + $request_options['http']['header'] .= $this->auth_header; + } + + $request_options['http']['method'] = strtoupper($name); + $request_options['http']['ignore_errors'] = true; + + if ($this->debug) { + error_log(var_export($request_options, true)); + } + $ctx = stream_context_create($request_options); + $result = file_get_contents($url, false, $ctx); + + if (false === $result) { + throw new Services_Twilio_HttpStreamException( + "Unable to connect to service"); + } + + $status_header = array_shift($http_response_header); + if (1 !== preg_match('#HTTP/\d+\.\d+ (\d+)#', $status_header, $matches)) { + throw new Services_Twilio_HttpStreamException( + "Unable to detect the status code in the HTTP result."); + } + + $status_code = intval($matches[1]); + $response_headers = array(); + + foreach($http_response_header as $header) { + list($key, $val) = explode(":", $header); + $response_headers[trim($key)] = trim($val); + } + + return array($status_code, $response_headers, $result); + } + + public function authenticate($user, $pass) { + if (isset($user) && isset($pass)) { + $this->auth_header = sprintf("Authorization: Basic %s", + base64_encode(sprintf("%s:%s", $user, $pass))); + } else { + $this->auth_header = null; + } + } +} diff --git a/Services/Twilio/IPMessagingInstanceResource.php b/Services/Twilio/IPMessagingInstanceResource.php new file mode 100644 index 0000000..7cf83b0 --- /dev/null +++ b/Services/Twilio/IPMessagingInstanceResource.php @@ -0,0 +1,15 @@ +subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } + +} diff --git a/Services/Twilio/IPMessagingListResource.php b/Services/Twilio/IPMessagingListResource.php new file mode 100644 index 0000000..fb265a0 --- /dev/null +++ b/Services/Twilio/IPMessagingListResource.php @@ -0,0 +1,18 @@ +getResourceName(true); + /* + * By default trim the 's' from the end of the list name to get the + * instance name (ex Accounts -> Account). This behavior can be + * overridden by child classes if the rule doesn't work. + */ + if (!isset($this->instance_name)) { + $this->instance_name = "Services_Twilio_Rest_IPMessaging_" . rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } +} diff --git a/Services/Twilio/InstanceResource.php b/Services/Twilio/InstanceResource.php new file mode 100644 index 0000000..0d4932d --- /dev/null +++ b/Services/Twilio/InstanceResource.php @@ -0,0 +1,84 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + * @link http://pear.php.net/package/Services_Twilio + */ + +/** + * Abstraction of an instance resource from the Twilio API. + */ +abstract class Services_Twilio_InstanceResource extends Services_Twilio_Resource { + + /** + * Make a request to the API to update an instance resource + * + * :param mixed $params: An array of updates, or a property name + * :param mixed $value: A value with which to update the resource + * + * :rtype: null + * :throws: a :php:class:`RestException ` if + * the update fails. + */ + public function update($params, $value = null) + { + if (!is_array($params)) { + $params = array($params => $value); + } + $decamelizedParams = $this->client->createData($this->uri, $params); + $this->updateAttributes($decamelizedParams); + } + + /** + * Add all properties from an associative array (the JSON response body) as + * properties on this instance resource, except the URI + * + * :param stdClass $params: An object containing all of the parameters of + * this instance + * :return: Nothing, this is purely side effecting + * :rtype: null + */ + public function updateAttributes($params) { + unset($params->uri); + foreach ($params as $name => $value) { + $this->$name = $value; + } + } + + /** + * Get the value of a property on this resource. + * + * Instead of defining all of the properties of an object directly, we rely + * on the API to tell us which properties an object has. This method will + * query the API to retrieve a property for an object, if it is not already + * set on the object. + * + * If the call is to a subresource, eg ``$client->account->messages``, no + * request is made. + * + * To help with lazy HTTP requests, we don't actually retrieve an object + * from the API unless you really need it. Hence, this function may make API + * requests even if the property you're requesting isn't available on the + * resource. + * + * :param string $key: The property name + * + * :return mixed: Could be anything. + * :throws: a :php:class:`RestException ` if + * the update fails. + */ + public function __get($key) + { + if ($subresource = $this->getSubresources($key)) { + return $subresource; + } + if (!isset($this->$key)) { + $params = $this->client->retrieveData($this->uri); + $this->updateAttributes($params); + } + return $this->$key; + } +} diff --git a/Services/Twilio/JWT.php b/Services/Twilio/JWT.php new file mode 100644 index 0000000..1e3222f --- /dev/null +++ b/Services/Twilio/JWT.php @@ -0,0 +1,164 @@ + + */ +class JWT +{ + /** + * @param string $jwt The JWT + * @param string|null $key The secret key + * @param bool $verify Don't skip verification process + * + * @return object The JWT's payload as a PHP object + */ + public static function decode($jwt, $key = null, $verify = true) + { + $tks = explode('.', $jwt); + if (count($tks) != 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $payloadb64, $cryptob64) = $tks; + if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64))) + ) { + throw new UnexpectedValueException('Invalid segment encoding'); + } + if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($payloadb64)) + ) { + throw new UnexpectedValueException('Invalid segment encoding'); + } + $sig = JWT::urlsafeB64Decode($cryptob64); + if ($verify) { + if (empty($header->alg)) { + throw new DomainException('Empty algorithm'); + } + if ($sig != JWT::sign("$headb64.$payloadb64", $key, $header->alg)) { + throw new UnexpectedValueException('Signature verification failed'); + } + } + return $payload; + } + + /** + * @param object|array $payload PHP object or array + * @param string $key The secret key + * @param string $algo The signing algorithm + * @param array $additionalHeaders Additional keys/values to add to the header + * + * @return string A JWT + */ + public static function encode($payload, $key, $algo = 'HS256', $additionalHeaders = array()) + { + $header = array('typ' => 'JWT', 'alg' => $algo); + $header = $header + $additionalHeaders; + + $segments = array(); + $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header)); + $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload)); + $signing_input = implode('.', $segments); + + $signature = JWT::sign($signing_input, $key, $algo); + $segments[] = JWT::urlsafeB64Encode($signature); + + return implode('.', $segments); + } + + /** + * @param string $msg The message to sign + * @param string $key The secret key + * @param string $method The signing algorithm + * + * @return string An encrypted message + */ + public static function sign($msg, $key, $method = 'HS256') + { + $methods = array( + 'HS256' => 'sha256', + 'HS384' => 'sha384', + 'HS512' => 'sha512', + ); + if (empty($methods[$method])) { + throw new DomainException('Algorithm not supported'); + } + return hash_hmac($methods[$method], $msg, $key, true); + } + + /** + * @param string $input JSON string + * + * @return object Object representation of JSON string + */ + public static function jsonDecode($input) + { + $obj = json_decode($input); + if (function_exists('json_last_error') && $errno = json_last_error()) { + JWT::handleJsonError($errno); + } + else if ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * @param object|array $input A PHP object or array + * + * @return string JSON representation of the PHP object or array + */ + public static function jsonEncode($input) + { + $json = json_encode($input); + if (function_exists('json_last_error') && $errno = json_last_error()) { + JWT::handleJsonError($errno); + } + else if ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + return $json; + } + + /** + * @param string $input A base64 encoded string + * + * @return string A decoded string + */ + public static function urlsafeB64Decode($input) + { + $padlen = 4 - strlen($input) % 4; + $input .= str_repeat('=', $padlen); + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * @param string $input Anything really + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode($input) + { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); + } + + /** + * @param int $errno An error number from json_last_error() + * + * @return void + */ + private static function handleJsonError($errno) + { + $messages = array( + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON' + ); + throw new DomainException(isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } +} \ No newline at end of file diff --git a/Services/Twilio/ListResource.php b/Services/Twilio/ListResource.php new file mode 100644 index 0000000..dad1c0d --- /dev/null +++ b/Services/Twilio/ListResource.php @@ -0,0 +1,182 @@ +`_ and the `Countable + * `_ interfaces. + * + */ +abstract class Services_Twilio_ListResource extends Services_Twilio_Resource + implements IteratorAggregate +{ + + public function __construct($client, $uri) { + $name = $this->getResourceName(true); + /* + * By default trim the 's' from the end of the list name to get the + * instance name (ex Accounts -> Account). This behavior can be + * overridden by child classes if the rule doesn't work. + */ + if (!isset($this->instance_name)) { + $this->instance_name = "Services_Twilio_Rest_" . rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } + + /** + * Gets a resource from this list. + * + * :param string $sid: The resource SID + * :return: The resource + * :rtype: :php:class:`InstanceResource ` + */ + public function get($sid) { + $instance = new $this->instance_name( + $this->client, $this->uri . "/$sid" + ); + // XXX check if this is actually a sid in all cases. + $instance->sid = $sid; + return $instance; + } + + /** + * Construct an :php:class:`InstanceResource + * ` with the specified params. + * + * :param array $params: usually a JSON HTTP response from the API + * :return: An instance with properties + * initialized to the values in the params array. + * :rtype: :php:class:`InstanceResource ` + */ + public function getObjectFromJson($params, $idParam = "sid") + { + if (isset($params->{$idParam})) { + $uri = $this->uri . "/" . $params->{$idParam}; + } else { + $uri = $this->uri; + } + return new $this->instance_name($this->client, $uri, $params); + } + + /** + * Deletes a resource from this list. + * + * :param string $sid: The resource SID + * :rtype: null + */ + public function delete($sid, $params = array()) + { + $this->client->deleteData($this->uri . '/' . $sid, $params); + } + + /** + * Create a resource on the list and then return its representation as an + * InstanceResource. + * + * :param array $params: The parameters with which to create the resource + * + * :return: The created resource + * :rtype: :php:class:`InstanceResource ` + */ + protected function _create($params) + { + $params = $this->client->createData($this->uri, $params); + /* Some methods like verified caller ID don't return sids. */ + if (isset($params->sid)) { + $resource_uri = $this->uri . '/' . $params->sid; + } else { + $resource_uri = $this->uri; + } + return new $this->instance_name($this->client, $resource_uri, $params); + } + + /** + * Returns a page of :php:class:`InstanceResources + * ` from this list. + * + * :param int $page: The start page + * :param int $size: Number of items per page + * :param array $filters: Optional filters + * :param string $deep_paging_uri: if provided, the $page and $size + * parameters will be ignored and this URI will be requested directly. + * + * :return: A page of resources + * :rtype: :php:class:`Services_Twilio_Page` + */ + public function getPage( + $page = 0, $size = 50, $filters = array(), $deep_paging_uri = null + ) { + $list_name = $this->getResourceName(); + if ($deep_paging_uri !== null) { + $page = $this->client->retrieveData($deep_paging_uri, array(), true); + } else { + $page = $this->client->retrieveData($this->uri, array( + 'Page' => $page, + 'PageSize' => $size, + ) + $filters); + } + + /* create a new PHP object for each json obj in the api response. */ + $page->$list_name = array_map( + array($this, 'getObjectFromJson'), + $page->$list_name + ); + if (isset($page->next_page_uri)) { + $next_page_uri = $page->next_page_uri; + } else { + $next_page_uri = null; + } + return new Services_Twilio_Page($page, $list_name, $next_page_uri); + } + + /** + * Returns an iterable list of + * :php:class:`instance resources `. + * + * :param int $page: The start page + * :param int $size: Number of items per page + * :param array $filters: Optional filters. + * The filter array can accept full datetimes when StartTime or DateCreated + * are used. Inequalities should be within the key portion of the array and + * multiple filter parameters can be combined for more specific searches. + * + * .. code-block:: php + * + * array('DateCreated>' => '2011-07-05 08:00:00', 'DateCreated<' => '2011-08-01') + * + * .. code-block:: php + * + * array('StartTime<' => '2011-07-05 08:00:00') + * + * :return: An iterator + * :rtype: :php:class:`Services_Twilio_AutoPagingIterator` + */ + public function getIterator( + $page = 0, $size = 50, $filters = array() + ) { + return new Services_Twilio_AutoPagingIterator( + array($this, 'getPageGenerator'), $page, $size, $filters + ); + } + + /** + * Retrieve a new page of API results, and update iterator parameters. This + * function is called by the paging iterator to retrieve a new page and + * shouldn't be called directly. + */ + public function getPageGenerator( + $page, $size, $filters = array(), $deep_paging_uri = null + ) { + return $this->getPage($page, $size, $filters, $deep_paging_uri); + } +} + diff --git a/Services/Twilio/LookupsInstanceResource.php b/Services/Twilio/LookupsInstanceResource.php new file mode 100644 index 0000000..63685ea --- /dev/null +++ b/Services/Twilio/LookupsInstanceResource.php @@ -0,0 +1,15 @@ +subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } + +} diff --git a/Services/Twilio/LookupsListResource.php b/Services/Twilio/LookupsListResource.php new file mode 100644 index 0000000..856d31b --- /dev/null +++ b/Services/Twilio/LookupsListResource.php @@ -0,0 +1,39 @@ +getResourceName(true); + /* + * By default trim the 's' from the end of the list name to get the + * instance name (ex Accounts -> Account). This behavior can be + * overridden by child classes if the rule doesn't work. + */ + if (!isset($this->instance_name)) { + $this->instance_name = "Services_Twilio_Rest_Lookups_" . rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } + + /** + * Gets a resource from this list. Overridden to add + * filter parameters. + * + * :param string $number: The phone number + * :return: The resource + * :rtype: :php:class:`InstanceResource ` + */ + public function get($number, $filters = array()) { + $number = rawurlencode($number); + $full_path = $this->uri . "/$number"; + if (!empty($filters)) { + $full_path .= '?' . http_build_query($filters, '', '&'); + } + + $instance = new $this->instance_name( + $this->client, $full_path + ); + return $instance; + } +} diff --git a/Services/Twilio/MonitorInstanceResource.php b/Services/Twilio/MonitorInstanceResource.php new file mode 100644 index 0000000..bf4c300 --- /dev/null +++ b/Services/Twilio/MonitorInstanceResource.php @@ -0,0 +1,15 @@ +subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } + +} diff --git a/Services/Twilio/MonitorListResource.php b/Services/Twilio/MonitorListResource.php new file mode 100644 index 0000000..70604e3 --- /dev/null +++ b/Services/Twilio/MonitorListResource.php @@ -0,0 +1,18 @@ +getResourceName(true); + /* + * By default trim the 's' from the end of the list name to get the + * instance name (ex Accounts -> Account). This behavior can be + * overridden by child classes if the rule doesn't work. + */ + if (!isset($this->instance_name)) { + $this->instance_name = "Services_Twilio_Rest_Monitor_" . rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } +} diff --git a/Services/Twilio/NextGenInstanceResource.php b/Services/Twilio/NextGenInstanceResource.php new file mode 100644 index 0000000..dd17dfe --- /dev/null +++ b/Services/Twilio/NextGenInstanceResource.php @@ -0,0 +1,23 @@ +` if + * the update fails. + */ + public function update($params, $value = null) + { + if (!is_array($params)) { + $params = array($params => $value); + } + $decamelizedParams = $this->client->createData($this->uri, $params, true); + $this->updateAttributes($decamelizedParams); + } +} \ No newline at end of file diff --git a/Services/Twilio/NextGenListResource.php b/Services/Twilio/NextGenListResource.php new file mode 100644 index 0000000..8822d1b --- /dev/null +++ b/Services/Twilio/NextGenListResource.php @@ -0,0 +1,60 @@ +client->retrieveData($deep_paging_uri, array(), true); + } else if ($page == 0) { + $page = $this->client->retrieveData($this->uri, array('Page' => $page, 'PageSize' => $size) + $filters); + } else { + return $this->emptyPage(); + } + + $list_name = $page->meta->key; + if (!isset($list_name) || $list_name === '') { + throw new Services_Twilio_HttpException("Couldn't find list key in response"); + } + + $page->$list_name = array_map( + array($this, 'getObjectFromJson'), + $page->$list_name + ); + $page->next_page_uri = $page->meta->next_page_url; + + return new Services_Twilio_Page($page, $list_name, $page->meta->next_page_url); + } + + private function emptyPage() { + $page = new stdClass(); + $page->empty = array(); + return new Services_Twilio_Page($page, 'empty'); + } + + /** + * Create a resource on the list and then return its representation as an + * InstanceResource. + * + * :param array $params: The parameters with which to create the resource + * + * :return: The created resource + * :rtype: :php:class:`InstanceResource ` + */ + protected function _create($params) + { + $params = $this->client->createData($this->uri, $params, true); + /* Some methods like verified caller ID don't return sids. */ + if (isset($params->sid)) { + $resource_uri = $this->uri . '/' . $params->sid; + } else { + $resource_uri = $this->uri; + } + return new $this->instance_name($this->client, $resource_uri, $params); + } + + public function count() { + throw new BadMethodCallException("Counting is not supported by this resource"); + } + +} diff --git a/Services/Twilio/NumberType.php b/Services/Twilio/NumberType.php new file mode 100644 index 0000000..0683d99 --- /dev/null +++ b/Services/Twilio/NumberType.php @@ -0,0 +1,35 @@ +instance_name = 'Services_Twilio_Rest_IncomingPhoneNumber'; + return $camelized ? 'IncomingPhoneNumbers' : 'incoming_phone_numbers'; + } + + /** + * Purchase a new phone number. + * + * Example usage: + * + * .. code-block:: php + * + * $marlosBurner = '+14105551234'; + * $client->account->incoming_phone_numbers->local->purchase($marlosBurner); + * + * :param string $phone_number: The phone number to purchase + * :param array $params: An optional array of parameters to pass along with + * the request (to configure the phone number) + */ + public function purchase($phone_number, array $params = array()) { + $postParams = array( + 'PhoneNumber' => $phone_number + ); + return $this->create($postParams + $params); + } + + public function create(array $params = array()) { + return parent::_create($params); + } + +} diff --git a/Services/Twilio/Page.php b/Services/Twilio/Page.php new file mode 100644 index 0000000..61ddb07 --- /dev/null +++ b/Services/Twilio/Page.php @@ -0,0 +1,68 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + * @link http://pear.php.net/package/Services_Twilio + */ +class Services_Twilio_Page + implements IteratorAggregate +{ + + /** + * The item list. + * + * @var array $items + */ + protected $items; + + /** + * Constructs a page. + * + * @param object $page The page object + * @param string $name The key of the item list + */ + public function __construct($page, $name, $next_page_uri = null) + { + $this->page = $page; + $this->items = $page->{$name}; + $this->next_page_uri = $next_page_uri; + } + + /** + * The item list of the page. + * + * @return array A list of instance resources + */ + public function getItems() + { + return $this->items; + } + + /** + * Magic method to allow retrieving the properties of the wrapped page. + * + * @param string $prop The property name + * + * @return mixed Could be anything + */ + public function __get($prop) + { + return $this->page->$prop; + } + + /** + * Implementation of IteratorAggregate::getIterator(). + * + * @return Traversable + */ + public function getIterator() + { + return $this->getItems(); + } +} + diff --git a/Services/Twilio/PartialApplicationHelper.php b/Services/Twilio/PartialApplicationHelper.php new file mode 100644 index 0000000..639ca51 --- /dev/null +++ b/Services/Twilio/PartialApplicationHelper.php @@ -0,0 +1,41 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + * @link http://pear.php.net/package/Services_Twilio + */ +class Services_Twilio_PartialApplicationHelper +{ + private $callbacks; + + public function __construct() + { + $this->callbacks = array(); + } + + public function set($method, $callback, array $args) + { + if (!is_callable($callback)) { + return FALSE; + } + $this->callbacks[$method] = array($callback, $args); + } + + public function __call($method, $args) + { + if (!isset($this->callbacks[$method])) { + throw new Exception("Method not found: $method"); + } + list($callback, $cb_args) = $this->callbacks[$method]; + return call_user_func_array( + $callback, + array_merge($cb_args, $args) + ); + } +} diff --git a/Services/Twilio/PricingInstanceResource.php b/Services/Twilio/PricingInstanceResource.php new file mode 100644 index 0000000..6ae1e18 --- /dev/null +++ b/Services/Twilio/PricingInstanceResource.php @@ -0,0 +1,14 @@ +subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } +} diff --git a/Services/Twilio/PricingListResource.php b/Services/Twilio/PricingListResource.php new file mode 100644 index 0000000..cc663e4 --- /dev/null +++ b/Services/Twilio/PricingListResource.php @@ -0,0 +1,14 @@ +getResourceName(true); + + if (!isset($this->instance_name)) { + $this->instance_name = 'Services_Twilio_Rest_Pricing_'. rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } + +} diff --git a/Services/Twilio/RequestValidator.php b/Services/Twilio/RequestValidator.php new file mode 100644 index 0000000..8bfa0db --- /dev/null +++ b/Services/Twilio/RequestValidator.php @@ -0,0 +1,67 @@ +AuthToken = $token; + } + + public function computeSignature($url, $data = array()) + { + // sort the array by keys + ksort($data); + + // append them to the data string in order + // with no delimiters + foreach($data as $key => $value) + $url .= "$key$value"; + + // This function calculates the HMAC hash of the data with the key + // passed in + // Note: hash_hmac requires PHP 5 >= 5.1.2 or PECL hash:1.1-1.5 + // Or http://pear.php.net/package/Crypt_HMAC/ + return base64_encode(hash_hmac("sha1", $url, $this->AuthToken, true)); + } + + public function validate($expectedSignature, $url, $data = array()) + { + return self::compare( + $this->computeSignature($url, $data), + $expectedSignature + ); + } + + /** + * Time insensitive compare, function's runtime is governed by the length + * of the first argument, not the difference between the arguments. + * @param $a string First part of the comparison pair + * @param $b string Second part of the comparison pair + * @return bool True if $a == $b, false otherwise. + */ + public static function compare($a, $b) { + $result = true; + + if (strlen($a) != strlen($b)) { + return false; + } + + if (!$a && !$b) { + return true; + } + + $limit = strlen($a); + + for ($i = 0; $i < $limit; ++$i) { + if ($a[$i] != $b[$i]) { + $result = false; + } + } + + return $result; + } + +} diff --git a/Services/Twilio/Resource.php b/Services/Twilio/Resource.php new file mode 100644 index 0000000..e6f1feb --- /dev/null +++ b/Services/Twilio/Resource.php @@ -0,0 +1,134 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + * @link http://pear.php.net/package/Services_Twilio + */ +abstract class Services_Twilio_Resource { + protected $subresources; + + public function __construct($client, $uri, $params = array()) + { + $this->subresources = array(); + $this->client = $client; + + foreach ($params as $name => $param) { + $this->$name = $param; + } + + $this->uri = $uri; + $this->init($client, $uri); + } + + protected function init($client, $uri) + { + // Left empty for derived classes to implement + } + + public function getSubresources($name = null) { + if (isset($name)) { + return isset($this->subresources[$name]) + ? $this->subresources[$name] + : null; + } + return $this->subresources; + } + + protected function setupSubresources() + { + foreach (func_get_args() as $name) { + $constantized = ucfirst(self::camelize($name)); + $type = "Services_Twilio_Rest_" . $constantized; + $this->subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } + + /* + * Get the resource name from the classname + * + * Ex: Services_Twilio_Rest_Accounts -> Accounts + * + * @param boolean $camelized Whether to return camel case or not + */ + public function getResourceName($camelized = false) + { + $name = get_class($this); + $parts = explode('_', $name); + $basename = end($parts); + if ($camelized) { + return $basename; + } else { + return self::decamelize($basename); + } + } + + public static function decamelize($word) + { + $callback = create_function('$matches', + 'return strtolower(strlen("$matches[1]") ? "$matches[1]_$matches[2]" : "$matches[2]");'); + + return preg_replace_callback( + '/(^|[a-z])([A-Z])/', + $callback, + $word + ); + } + + /** + * Return camelized version of a word + * Examples: sms_messages => SMSMessages, calls => Calls, + * incoming_phone_numbers => IncomingPhoneNumbers + * + * @param string $word The word to camelize + * @return string + */ + public static function camelize($word) { + $callback = create_function('$matches', 'return strtoupper("$matches[2]");'); + + return preg_replace_callback('/(^|_)([a-z])/', + $callback, + $word); + } + + /** + * Get the value of a property on this resource. + * + * @param string $key The property name + * @return mixed Could be anything. + */ + public function __get($key) { + if ($subresource = $this->getSubresources($key)) { + return $subresource; + } + return $this->$key; + } + + /** + * Print a JSON representation of this object. Strips the HTTP client + * before returning. + * + * Note, this should mainly be used for debugging, and is not guaranteed + * to correspond 1:1 with the JSON API output. + * + * Note that echoing an object before an HTTP request has been made to + * "fill in" its properties may return an empty object + */ + public function __toString() { + $out = array(); + foreach ($this as $key => $value) { + if ($key !== 'client' && $key !== 'subresources') { + $out[$key] = $value; + } + } + return json_encode($out, true); + } + +} + diff --git a/Services/Twilio/Rest/Account.php b/Services/Twilio/Rest/Account.php new file mode 100644 index 0000000..dc137d3 --- /dev/null +++ b/Services/Twilio/Rest/Account.php @@ -0,0 +1,36 @@ +setupSubresources( + 'applications', + 'available_phone_numbers', + 'outgoing_caller_ids', + 'calls', + 'conferences', + 'incoming_phone_numbers', + 'keys', + 'media', + 'messages', + 'notifications', + 'outgoing_callerids', + 'recordings', + 'sms_messages', + 'short_codes', + 'tokens', + 'transcriptions', + 'connect_apps', + 'authorized_connect_apps', + 'usage_records', + 'usage_triggers', + 'queues', + 'sip', + 'addresses' + ); + + $this->sandbox = new Services_Twilio_Rest_Sandbox( + $client, $uri . '/Sandbox' + ); + } +} diff --git a/Services/Twilio/Rest/Accounts.php b/Services/Twilio/Rest/Accounts.php new file mode 100644 index 0000000..0e7ea0a --- /dev/null +++ b/Services/Twilio/Rest/Accounts.php @@ -0,0 +1,25 @@ +`_ documentation. + */ +class Services_Twilio_Rest_Accounts extends Services_Twilio_ListResource { + + /** + * Create a new subaccount. + * + * :param array $params: An array of parameters describing the new + * subaccount. The ``$params`` array can contain the following keys: + * + * *FriendlyName* + * A description of this account, up to 64 characters long + * + * :returns: The new subaccount + * :rtype: :php:class:`Services_Twilio_Rest_Account` + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/Address.php b/Services/Twilio/Rest/Address.php new file mode 100644 index 0000000..c8a9839 --- /dev/null +++ b/Services/Twilio/Rest/Address.php @@ -0,0 +1,22 @@ +setupSubresources( + 'dependent_phone_numbers' + ); + } + + /** + * Make a request to delete the specified resource. + * + * :rtype: boolean + */ + public function delete() + { + return $this->client->deleteData($this->uri); + } +} diff --git a/Services/Twilio/Rest/Addresses.php b/Services/Twilio/Rest/Addresses.php new file mode 100644 index 0000000..070574e --- /dev/null +++ b/Services/Twilio/Rest/Addresses.php @@ -0,0 +1,22 @@ +instance_name = "Services_Twilio_Rest_Address"; + parent::__construct($client, $uri); + } + + public function create($customerName, $street, $city, $region, $postalCode, $isoCountry, array $params = array()) + { + $params["CustomerName"] = $customerName; + $params["Street"] = $street; + $params["City"] = $city; + $params["Region"] = $region; + $params["PostalCode"] = $postalCode; + $params["IsoCountry"] = $isoCountry; + + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/Application.php b/Services/Twilio/Rest/Application.php new file mode 100644 index 0000000..0db8a05 --- /dev/null +++ b/Services/Twilio/Rest/Application.php @@ -0,0 +1,6 @@ + $name + ) + $params); + } +} diff --git a/Services/Twilio/Rest/AuthorizedConnectApp.php b/Services/Twilio/Rest/AuthorizedConnectApp.php new file mode 100644 index 0000000..0372629 --- /dev/null +++ b/Services/Twilio/Rest/AuthorizedConnectApp.php @@ -0,0 +1,6 @@ +set( + 'getList', + array($this, 'getList'), + array($country, 'Local') + ); + return $curried; + } + public function getTollFree($country) { + $curried = new Services_Twilio_PartialApplicationHelper(); + $curried->set( + 'getList', + array($this, 'getList'), + array($country, 'TollFree') + ); + return $curried; + } + + public function getMobile($country) + { + $curried = new Services_Twilio_PartialApplicationHelper(); + $curried->set( + 'getList', + array($this, 'getList'), + array($country, 'Mobile') + ); + return $curried; + } + + /** + * Get a list of available phone numbers. + * + * @param string $country The 2-digit country code you'd like to search for + * numbers e.g. ('US', 'CA', 'GB') + * @param string $type The type of number ('Local', 'TollFree', or 'Mobile') + * @return object The object representation of the resource + */ + public function getList($country, $type, array $params = array()) + { + return $this->client->retrieveData($this->uri . "/$country/$type", $params); + } + + public function getResourceName($camelized = false) { + // You can't page through the list of available phone numbers. + $this->instance_name = 'Services_Twilio_Rest_AvailablePhoneNumber'; + return $camelized ? 'Countries' : 'countries'; + } +} diff --git a/Services/Twilio/Rest/Call.php b/Services/Twilio/Rest/Call.php new file mode 100644 index 0000000..1a9f696 --- /dev/null +++ b/Services/Twilio/Rest/Call.php @@ -0,0 +1,116 @@ +`_ documentation. + * + * .. php:attr:: sid + * + * A 34 character string that uniquely identifies this resource. + * + * .. php:attr:: parent_call_sid + * + * A 34 character string that uniquely identifies the call that created this leg. + * + * .. php:attr:: date_created + * + * The date that this resource was created, given as GMT in RFC 2822 format. + * + * .. php:attr:: date_updated + * + * The date that this resource was last updated, given as GMT in RFC 2822 format. + * + * .. php:attr:: account_sid + * + * The unique id of the Account responsible for creating this call. + * + * .. php:attr:: to + * + * The phone number that received this call. e.g., +16175551212 (E.164 format) + * + * .. php:attr:: from + * + * The phone number that made this call. e.g., +16175551212 (E.164 format) + * + * .. php:attr:: phone_number_sid + * + * If the call was inbound, this is the Sid of the IncomingPhoneNumber that + * received the call. If the call was outbound, it is the Sid of the + * OutgoingCallerId from which the call was placed. + * + * .. php:attr:: status + * + * A string representing the status of the call. May be `QUEUED`, `RINGING`, + * `IN-PROGRESS`, `COMPLETED`, `FAILED`, `BUSY` or `NO_ANSWER`. + * + * .. php:attr:: start_time + * + * The start time of the call, given as GMT in RFC 2822 format. Empty if the call has not yet been dialed. + * + * .. php:attr:: end_time + * + * The end time of the call, given as GMT in RFC 2822 format. Empty if the call did not complete successfully. + * + * .. php:attr:: duration + * + * The length of the call in seconds. This value is empty for busy, failed, unanswered or ongoing calls. + * + * .. php:attr:: price + * + * The charge for this call in USD. Populated after the call is completed. May not be immediately available. + * + * .. php:attr:: direction + * + * A string describing the direction of the call. inbound for inbound + * calls, outbound-api for calls initiated via the REST API or + * outbound-dial for calls initiated by a verb. + * + * .. php:attr:: answered_by + * + * If this call was initiated with answering machine detection, either human or machine. Empty otherwise. + * + * .. php:attr:: forwarded_from + * + * If this call was an incoming call forwarded from another number, the + * forwarding phone number (depends on carrier supporting forwarding). + * Empty otherwise. + * + * .. php:attr:: caller_name + * + * If this call was an incoming call from a phone number with Caller ID Lookup enabled, the caller's name. Empty otherwise. + */ +class Services_Twilio_Rest_Call extends Services_Twilio_InstanceResource { + + /** + * Hang up the call + */ + public function hangup() { + $this->update('Status', 'completed'); + } + + /** + * Redirect the call to a new URL + * + * :param string $url: the new URL to retrieve call flow from. + */ + public function route($url) { + $this->update('Url', $url); + } + + protected function init($client, $uri) { + $this->setupSubresources( + 'notifications', + 'recordings', + 'feedback' + ); + } + + /** + * Make a request to delete the specified resource. + * + * :rtype: boolean + */ + public function delete() + { + return $this->client->deleteData($this->uri); + } +} diff --git a/Services/Twilio/Rest/Calls.php b/Services/Twilio/Rest/Calls.php new file mode 100644 index 0000000..a140654 --- /dev/null +++ b/Services/Twilio/Rest/Calls.php @@ -0,0 +1,77 @@ +setupSubresources( + 'feedback_summary' + ); + } + + public static function isApplicationSid($value) + { + return strlen($value) == 34 + && !(strpos($value, "AP") === false); + } + + public function create($from, $to, $url, array $params = array()) + { + + $params["To"] = $to; + $params["From"] = $from; + + if (self::isApplicationSid($url)) { + $params["ApplicationSid"] = $url; + } else { + $params["Url"] = $url; + } + + return parent::_create($params); + } + + /** + * Create a feedback for a call. + * + * @param $callSid + * @param $qualityScore + * @param array $issue + * @return Services_Twilio_Rest_Feedback + */ + public function createFeedback($callSid, $qualityScore, array $issue = array()) + { + $params["QualityScore"] = $qualityScore; + $params["Issue"] = $issue; + + $feedbackUri = $this->uri . '/' . $callSid . '/Feedback'; + + $response = $this->client->createData($feedbackUri, $params); + return new Services_Twilio_Rest_Feedback($this->client, $feedbackUri, $response); + } + + /** + * Delete a feedback for a call. + * + * @param $callSid + */ + public function deleteFeedback($callSid) + { + $feedbackUri = $this->uri . '/' . $callSid . '/Feedback'; + $this->client->deleteData($feedbackUri); + } + + /** + * Get a feedback for a call. + * + * @param $callSid + * @return Services_Twilio_Rest_Feedback + */ + public function getFeedback($callSid) + { + $feedbackUri = $this->uri . '/' . $callSid . '/Feedback'; + $response = $this->client->retrieveData($feedbackUri); + return new Services_Twilio_Rest_Feedback($this->client, $feedbackUri, $response); + } +} diff --git a/Services/Twilio/Rest/Conference.php b/Services/Twilio/Rest/Conference.php new file mode 100644 index 0000000..9a36916 --- /dev/null +++ b/Services/Twilio/Rest/Conference.php @@ -0,0 +1,12 @@ +setupSubresources( + 'participants' + ); + } +} diff --git a/Services/Twilio/Rest/Conferences.php b/Services/Twilio/Rest/Conferences.php new file mode 100644 index 0000000..5e92e37 --- /dev/null +++ b/Services/Twilio/Rest/Conferences.php @@ -0,0 +1,6 @@ +setupSubresources( + 'credentials' + ); + } +} + diff --git a/Services/Twilio/Rest/CredentialListMapping.php b/Services/Twilio/Rest/CredentialListMapping.php new file mode 100644 index 0000000..3f9c305 --- /dev/null +++ b/Services/Twilio/Rest/CredentialListMapping.php @@ -0,0 +1,37 @@ +setupSubresources( + 'credentials' + ); + } +} diff --git a/Services/Twilio/Rest/CredentialListMappings.php b/Services/Twilio/Rest/CredentialListMappings.php new file mode 100644 index 0000000..ab34f60 --- /dev/null +++ b/Services/Twilio/Rest/CredentialListMappings.php @@ -0,0 +1,24 @@ +account->sip->domains->get('SDXXX')->credential_list_mappings->create("CLXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + * + * :param string $credential_list_sid: the sid of the CredentialList you're adding to this domain. + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + */ + public function create($credential_list_sid, $params = array()) { + return parent::_create(array( + 'CredentialListSid' => $credential_list_sid, + ) + $params); + } +} + diff --git a/Services/Twilio/Rest/CredentialLists.php b/Services/Twilio/Rest/CredentialLists.php new file mode 100644 index 0000000..e8eb1a6 --- /dev/null +++ b/Services/Twilio/Rest/CredentialLists.php @@ -0,0 +1,24 @@ +account->sip->credential_lists->create("MyFriendlyName"); + * + * :param string $friendly_name: the friendly name of this credential list + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + */ + public function create($friendly_name, $params = array()) { + return parent::_create(array( + 'FriendlyName' => $friendly_name, + ) + $params); + } + +} diff --git a/Services/Twilio/Rest/Credentials.php b/Services/Twilio/Rest/Credentials.php new file mode 100644 index 0000000..129a44d --- /dev/null +++ b/Services/Twilio/Rest/Credentials.php @@ -0,0 +1,28 @@ +account->sip->credential_lists->get('CLXXX')->credentials->create( + * "AwesomeUsername", "SuperSecretPassword", + * ); + * + * :param string $username: the username for the new Credential object + * :param string $password: the password for the new Credential object + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + */ + public function create($username, $password, $params = array()) { + return parent::_create(array( + 'Username' => $username, + 'Password' => $password, + ) + $params); + } + +} diff --git a/Services/Twilio/Rest/DependentPhoneNumber.php b/Services/Twilio/Rest/DependentPhoneNumber.php new file mode 100644 index 0000000..a9402d0 --- /dev/null +++ b/Services/Twilio/Rest/DependentPhoneNumber.php @@ -0,0 +1,6 @@ +setupSubresources( + 'ip_access_control_list_mappings', + 'credential_list_mappings' + ); + } +} diff --git a/Services/Twilio/Rest/Domains.php b/Services/Twilio/Rest/Domains.php new file mode 100644 index 0000000..041c080 --- /dev/null +++ b/Services/Twilio/Rest/Domains.php @@ -0,0 +1,28 @@ +account->sip->domains->create( + * "MyFriendlyName", "MyDomainName" + * ); + * + * :param string $friendly_name: the friendly name of this domain + * :param string $domain_name: the domain name for this domain + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + */ + public function create($friendly_name, $domain_name, $params = array()) { + return parent::_create(array( + 'FriendlyName' => $friendly_name, + 'DomainName' => $domain_name, + ) + $params); + } +} + diff --git a/Services/Twilio/Rest/Feedback.php b/Services/Twilio/Rest/Feedback.php new file mode 100644 index 0000000..ed210f6 --- /dev/null +++ b/Services/Twilio/Rest/Feedback.php @@ -0,0 +1,34 @@ +instance_name = "Services_Twilio_Rest_Feedback"; + return parent::__construct($client, $uri, $params); + } + + /** + * Create feedback for the parent call + */ + public function create(array $params = array()) { + $params = $this->client->createData($this->uri, $params); + return new $this->instance_name($this->client, $this->uri, $params); + } + + /** + * Delete feedback for the parent call + */ + public function delete() { + $this->client->deleteData($this->uri); + } + + /** + * Fetch the feedback for the parent call + */ + public function get() { + return new $this->instance_name( + $this->client, $this->uri + ); + } + +} diff --git a/Services/Twilio/Rest/FeedbackSummary.php b/Services/Twilio/Rest/FeedbackSummary.php new file mode 100644 index 0000000..e6e49e0 --- /dev/null +++ b/Services/Twilio/Rest/FeedbackSummary.php @@ -0,0 +1,33 @@ +instance_name = "Services_Twilio_Rest_FeedbackSummary"; + return parent::__construct($client, $uri, $params); + } + + /** + * Create feedback summary for calls + */ + public function create(array $params = array()) { + $params = $this->client->createData($this->uri, $params); + return new $this->instance_name($this->client, $this->uri, $params); + } + + /** + * Delete a feedback summary + */ + public function delete($sid) { + $this->client->deleteData($this->uri . '/' . $sid); + } + + /** + * Get a feedback summary + */ + public function get($sid) { + return new $this->instance_name( + $this->client, $this->uri . '/' . $sid + ); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Channel.php b/Services/Twilio/Rest/IPMessaging/Channel.php new file mode 100644 index 0000000..bf8d4d0 --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Channel.php @@ -0,0 +1,10 @@ +setupSubresources( + 'members', + 'messages' + ); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Channels.php b/Services/Twilio/Rest/IPMessaging/Channels.php new file mode 100644 index 0000000..12352c0 --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Channels.php @@ -0,0 +1,23 @@ +services->get('SV123')->channels->create(array( + * "FriendlyName" => "TestChannel", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Credential.php b/Services/Twilio/Rest/IPMessaging/Credential.php new file mode 100644 index 0000000..715403e --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Credential.php @@ -0,0 +1,5 @@ +credentials->create(array( + * "FriendlyName" => "TestCredential", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Member.php b/Services/Twilio/Rest/IPMessaging/Member.php new file mode 100644 index 0000000..7a83792 --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Member.php @@ -0,0 +1,5 @@ +channels->get('CH123')->members->create(array( + * "FriendlyName" => "TestMember", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Message.php b/Services/Twilio/Rest/IPMessaging/Message.php new file mode 100644 index 0000000..b65cf6d --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Message.php @@ -0,0 +1,5 @@ +channels->get('CH123')->messages->create(array( + * "Body" => "TestMessage", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Role.php b/Services/Twilio/Rest/IPMessaging/Role.php new file mode 100644 index 0000000..08eba7f --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Role.php @@ -0,0 +1,5 @@ +services->get('SV123')->roles->create(array( + * "FriendlyName" => "TestRole", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Service.php b/Services/Twilio/Rest/IPMessaging/Service.php new file mode 100644 index 0000000..775591c --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Service.php @@ -0,0 +1,11 @@ +setupSubresources( + 'channels', + 'roles', + 'users' + ); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/Services.php b/Services/Twilio/Rest/IPMessaging/Services.php new file mode 100644 index 0000000..c1f30e3 --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/Services.php @@ -0,0 +1,23 @@ +services->create(array( + * "Ttl" => 100, + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IPMessaging/User.php b/Services/Twilio/Rest/IPMessaging/User.php new file mode 100644 index 0000000..c0225b6 --- /dev/null +++ b/Services/Twilio/Rest/IPMessaging/User.php @@ -0,0 +1,5 @@ +credentials->create(array( + * "FriendlyName" => "TestUser", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/IncomingPhoneNumber.php b/Services/Twilio/Rest/IncomingPhoneNumber.php new file mode 100644 index 0000000..37658fa --- /dev/null +++ b/Services/Twilio/Rest/IncomingPhoneNumber.php @@ -0,0 +1,95 @@ +`_ + * documentation. + * + * .. php:attr:: sid + * + * A 34 character string that uniquely idetifies this resource. + * + * .. php:attr:: date_created + * + * The date that this resource was created, given as GMT RFC 2822 format. + * + * .. php:attr:: date_updated + * + * The date that this resource was last updated, given as GMT RFC 2822 format. + * + * .. php:attr:: friendly_name + * + * A human readable descriptive text for this resource, up to 64 + * characters long. By default, the FriendlyName is a nicely formatted + * version of the phone number. + * + * .. php:attr:: account_sid + * + * The unique id of the Account responsible for this phone number. + * + * .. php:attr:: phone_number + * + * The incoming phone number. e.g., +16175551212 (E.164 format) + * + * .. php:attr:: api_version + * + * Calls to this phone number will start a new TwiML session with this + * API version. + * + * .. php:attr:: voice_caller_id_lookup + * + * Look up the caller's caller-ID name from the CNAM database (additional charges apply). Either true or false. + * + * .. php:attr:: voice_url + * + * The URL Twilio will request when this phone number receives a call. + * + * .. php:attr:: voice_method + * + * The HTTP method Twilio will use when requesting the above Url. Either GET or POST. + * + * .. php:attr:: voice_fallback_url + * + * The URL that Twilio will request if an error occurs retrieving or executing the TwiML requested by Url. + * + * .. php:attr:: voice_fallback_method + * + * The HTTP method Twilio will use when requesting the VoiceFallbackUrl. Either GET or POST. + * + * .. php:attr:: status_callback + * + * The URL that Twilio will request to pass status parameters (such as call ended) to your application. + * + * .. php:attr:: status_callback_method + * + * The HTTP method Twilio will use to make requests to the StatusCallback URL. Either GET or POST. + * + * .. php:attr:: sms_url + * + * The URL Twilio will request when receiving an incoming SMS message to this number. + * + * .. php:attr:: sms_method + * + * The HTTP method Twilio will use when making requests to the SmsUrl. Either GET or POST. + * + * .. php:attr:: sms_fallback_url + * + * The URL that Twilio will request if an error occurs retrieving or executing the TwiML from SmsUrl. + * + * .. php:attr:: sms_fallback_method + * + * The HTTP method Twilio will use when requesting the above URL. Either GET or POST. + * + * .. php:attr:: beta + * + * Whether this number is new to Twilio's inventory. + * + * .. php:attr:: uri + * + * The URI for this resource, relative to https://api.twilio.com. + */ +class Services_Twilio_Rest_IncomingPhoneNumber + extends Services_Twilio_InstanceResource +{ +} diff --git a/Services/Twilio/Rest/IncomingPhoneNumbers.php b/Services/Twilio/Rest/IncomingPhoneNumbers.php new file mode 100644 index 0000000..48ce4ca --- /dev/null +++ b/Services/Twilio/Rest/IncomingPhoneNumbers.php @@ -0,0 +1,59 @@ +`_ + * documentation at twilio.com. + */ +class Services_Twilio_Rest_IncomingPhoneNumbers extends Services_Twilio_ListResource { + function init($client, $uri) { + $this->setupSubresources( + 'local', + 'toll_free', + 'mobile' + ); + } + + function create(array $params = array()) { + return parent::_create($params); + } + + function getList($type, array $params = array()) + { + return $this->client->retrieveData($this->uri . "/$type", $params); + } + + /** + * Return a phone number instance from its E.164 representation. If more + * than one number matches the search string, returns the first one. + * + * Example usage: + * + * .. code-block:: php + * + * $number = $client->account->incoming_phone_numbers->getNumber('+14105551234'); + * echo $number->sid; + * + * :param string $number: The number in E.164 format, eg "+684105551234" + * :return: A :php:class:`Services_Twilio_Rest_IncomingPhoneNumber` object, or null + * :raises: a A :php:class:`Services_Twilio_RestException` if the number is + * invalid, not provided in E.164 format or for any other API exception. + */ + public function getNumber($number) { + $page = $this->getPage(0, 1, array( + 'PhoneNumber' => $number + )); + $items = $page->getItems(); + if (is_null($items) || empty($items)) { + return null; + } + return $items[0]; + } +} + +class Services_Twilio_Rest_Local extends Services_Twilio_NumberType { } + +class Services_Twilio_Rest_Mobile extends Services_Twilio_NumberType { } + +class Services_Twilio_Rest_TollFree extends Services_Twilio_NumberType { } diff --git a/Services/Twilio/Rest/IpAccessControlList.php b/Services/Twilio/Rest/IpAccessControlList.php new file mode 100644 index 0000000..5ba83f3 --- /dev/null +++ b/Services/Twilio/Rest/IpAccessControlList.php @@ -0,0 +1,40 @@ +setupSubresources( + 'ip_addresses' + ); + } +} diff --git a/Services/Twilio/Rest/IpAccessControlListMapping.php b/Services/Twilio/Rest/IpAccessControlListMapping.php new file mode 100644 index 0000000..ce5bc5a --- /dev/null +++ b/Services/Twilio/Rest/IpAccessControlListMapping.php @@ -0,0 +1,37 @@ +setupSubresources( + 'ip_addresses' + ); + } +} + diff --git a/Services/Twilio/Rest/IpAccessControlListMappings.php b/Services/Twilio/Rest/IpAccessControlListMappings.php new file mode 100644 index 0000000..f58e1b9 --- /dev/null +++ b/Services/Twilio/Rest/IpAccessControlListMappings.php @@ -0,0 +1,25 @@ +account->sip->domains->get('SDXXX')->ip_access_control_list_mappings->create("ALXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + * + * :param string $ip_access_control_list_sid: the sid of the IpAccessControList + * you're adding to this domain. + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + */ + public function create($ip_access_control_list_sid, $params = array()) { + return parent::_create(array( + 'IpAccessControlListSid' => $ip_access_control_list_sid, + ) + $params); + } +} + diff --git a/Services/Twilio/Rest/IpAccessControlLists.php b/Services/Twilio/Rest/IpAccessControlLists.php new file mode 100644 index 0000000..88b9d14 --- /dev/null +++ b/Services/Twilio/Rest/IpAccessControlLists.php @@ -0,0 +1,27 @@ +account->sip->ip_access_control_lists->create("MyFriendlyName"); + * + * :param string $friendly_name: the friendly name of this ip access control list + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * :return: the created list + * :rtype: :class:`Services_Twilio_Rest_IpAccessControlList` + * + */ + public function create($friendly_name, $params = array()) { + return parent::_create(array( + 'FriendlyName' => $friendly_name, + ) + $params); + } + +} diff --git a/Services/Twilio/Rest/IpAddress.php b/Services/Twilio/Rest/IpAddress.php new file mode 100644 index 0000000..38b716e --- /dev/null +++ b/Services/Twilio/Rest/IpAddress.php @@ -0,0 +1,34 @@ +instance_name = "Services_Twilio_Rest_IpAddress"; + parent::__construct($client, $uri); + } + + /** + * Creates a new IpAddress instance + * + * Example usage: + * + * .. code-block:: php + * + * $client->account->sip->ip_access_control_lists->get('ALXXX')->ip_addresses->create( + * "FriendlyName", "127.0.0.1" + * ); + * + * :param string $friendly_name: the friendly name for the new IpAddress object + * :param string $ip_address: the ip address for the new IpAddress object + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + */ + public function create($friendly_name, $ip_address, $params = array()) { + return parent::_create(array( + 'FriendlyName' => $friendly_name, + 'IpAddress' => $ip_address, + ) + $params); + } +} + diff --git a/Services/Twilio/Rest/Key.php b/Services/Twilio/Rest/Key.php new file mode 100644 index 0000000..4750f5b --- /dev/null +++ b/Services/Twilio/Rest/Key.php @@ -0,0 +1,5 @@ +` objects. + * For the definitive reference, see the `Twilio Media List Documentation + * `_. + */ +class Services_Twilio_Rest_Media extends Services_Twilio_ListResource { + + + // This is overridden because the list key in the Twilio response + // is "media_list", not "media". + public function getResourceName($camelized = false) + { + if ($camelized) { + return "MediaList"; + } else { + return "media_list"; + } + } + + // We manually set the instance name here so that the parent + // constructor doesn't attempt to figure out it. It would do it + // incorrectly because we override getResourceName above. + public function __construct($client, $uri) { + $this->instance_name = "Services_Twilio_Rest_MediaInstance"; + parent::__construct($client, $uri); + } + +} + diff --git a/Services/Twilio/Rest/MediaInstance.php b/Services/Twilio/Rest/MediaInstance.php new file mode 100644 index 0000000..1a152b2 --- /dev/null +++ b/Services/Twilio/Rest/MediaInstance.php @@ -0,0 +1,37 @@ +`_. + * + * .. php:attr:: sid + * + * A 34 character string that identifies this object + * + * .. php:attr:: account_sid + * + * A 34 character string representing the account that sent the message + * + * .. php:attr:: parent_sid + * + * The sid of the message that created this media. + * + * .. php:attr:: date_created + * + * The date the message was created + * + * .. php:attr:: date_updated + * + * The date the message was updated + * + * .. php:attr:: content_type + * + * The content-type of the media. + */ +class Services_Twilio_Rest_MediaInstance extends Services_Twilio_InstanceResource { + public function __construct($client, $uri) { + $uri = str_replace('MediaInstance', 'Media', $uri); + parent::__construct($client, $uri); + } +} + diff --git a/Services/Twilio/Rest/Member.php b/Services/Twilio/Rest/Member.php new file mode 100644 index 0000000..8067cdd --- /dev/null +++ b/Services/Twilio/Rest/Member.php @@ -0,0 +1,22 @@ + $url, + 'Method' => $method, + )); + } +} diff --git a/Services/Twilio/Rest/Members.php b/Services/Twilio/Rest/Members.php new file mode 100644 index 0000000..61e05de --- /dev/null +++ b/Services/Twilio/Rest/Members.php @@ -0,0 +1,28 @@ +instance_name($this->client, $this->uri . '/Front'); + } + + /* Participants are identified by CallSid, not like ME123 */ + public function getObjectFromJson($params, $idParam = 'sid') { + return parent::getObjectFromJson($params, 'call_sid'); + } + + public function getResourceName($camelized = false) + { + // The JSON property name is atypical. + return $camelized ? 'Members' : 'queue_members'; + } +} + diff --git a/Services/Twilio/Rest/Message.php b/Services/Twilio/Rest/Message.php new file mode 100644 index 0000000..2ca32e8 --- /dev/null +++ b/Services/Twilio/Rest/Message.php @@ -0,0 +1,68 @@ +setupSubresources( + 'media' + ); + } + + public function redact() { + $postParams = array('Body' => ''); + self::update($postParams); + } + + /** + * Make a request to delete the specified resource. + * + * :rtype: boolean + */ + public function delete() + { + return $this->client->deleteData($this->uri); + } +} + diff --git a/Services/Twilio/Rest/Messages.php b/Services/Twilio/Rest/Messages.php new file mode 100644 index 0000000..355fce0 --- /dev/null +++ b/Services/Twilio/Rest/Messages.php @@ -0,0 +1,73 @@ +account->messages->create(array( + * "Body" => "foo", + * "From" => "+14105551234", + * "To" => "+14105556789", + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. You may find it easier to use the + * sendMessage helper instead of this function. + * + */ + public function create($params = array()) { + return parent::_create($params); + } + + /** + * Send a message + * + * .. code-block:: php + * + * $client = new Services_Twilio('AC123', '123'); + * $message = $client->account->messages->sendMessage( + * '+14105551234', // From a Twilio number in your account + * '+14105556789', // Text any number + * 'Come at the king, you best not miss.' // Message body (if any) + * array('https://demo.twilio.com/owl.png'), // An array of MediaUrls + * ); + * + * :param string $from: the from number for the message, this must be a + * number you purchased from Twilio + * :param string $to: the message recipient's phone number + * :param $mediaUrls: the URLs of images to send in this MMS + * :type $mediaUrls: null (don't include media), a single URL, or an array + * of URLs to send as media with this message + * :param string $body: the text to include along with this MMS + * :param array $params: Any additional params (callback, etc) you'd like to + * send with this request, these are serialized and sent as POST + * parameters + * + * :return: The created :class:`Services_Twilio_Rest_Message` + * :raises: :class:`Services_Twilio_RestException` + * An exception if the parameters are invalid (for example, the from + * number is not a Twilio number registered to your account, or is + * unable to send MMS) + */ + public function sendMessage($from, $to, $body = null, $mediaUrls = null, + $params = array() + ) { + $postParams = array( + 'From' => $from, + 'To' => $to, + ); + // When the request is made, this will get serialized into MediaUrl=a&MediaUrl=b + if (!is_null($mediaUrls)) { + $postParams['MediaUrl'] = $mediaUrls; + } + if (!is_null($body)) { + $postParams['Body'] = $body; + } + return self::create($postParams + $params); + } +} diff --git a/Services/Twilio/Rest/Monitor/Alert.php b/Services/Twilio/Rest/Monitor/Alert.php new file mode 100644 index 0000000..31bc1b4 --- /dev/null +++ b/Services/Twilio/Rest/Monitor/Alert.php @@ -0,0 +1,5 @@ + $phoneNumber, + ) + $params); + } +} diff --git a/Services/Twilio/Rest/Participant.php b/Services/Twilio/Rest/Participant.php new file mode 100644 index 0000000..b62920b --- /dev/null +++ b/Services/Twilio/Rest/Participant.php @@ -0,0 +1,10 @@ +update('Muted', 'true'); + } +} diff --git a/Services/Twilio/Rest/Participants.php b/Services/Twilio/Rest/Participants.php new file mode 100644 index 0000000..3b0464e --- /dev/null +++ b/Services/Twilio/Rest/Participants.php @@ -0,0 +1,10 @@ +instance_name = "Services_Twilio_Rest_Pricing_MessagingCountry"; + parent::__construct($client, $uri); + } + + public function get($isoCountry) { + $instance = new $this->instance_name($this->client, $this->uri . "/$isoCountry"); + $instance->iso_country = $isoCountry; + return $instance; + } +} diff --git a/Services/Twilio/Rest/Pricing/MessagingCountry.php b/Services/Twilio/Rest/Pricing/MessagingCountry.php new file mode 100644 index 0000000..e39cc4e --- /dev/null +++ b/Services/Twilio/Rest/Pricing/MessagingCountry.php @@ -0,0 +1,5 @@ +instance_name = 'Services_Twilio_Rest_Pricing_PhoneNumberCountry'; + parent::__construct($client, $uri); + } + + public function getResourceName($camelized = false) { + if ($camelized) { + return 'Countries'; + } + return 'countries'; + } + + public function get($isoCountry) { + $instance = new $this->instance_name($this->client, $this->uri . "/$isoCountry"); + $instance->iso_country = $isoCountry; + return $instance; + } +} \ No newline at end of file diff --git a/Services/Twilio/Rest/Pricing/PhoneNumberCountry.php b/Services/Twilio/Rest/Pricing/PhoneNumberCountry.php new file mode 100644 index 0000000..ac840b8 --- /dev/null +++ b/Services/Twilio/Rest/Pricing/PhoneNumberCountry.php @@ -0,0 +1,4 @@ +instance_name = "Services_Twilio_Rest_Pricing_VoiceCountry"; + parent::__construct($client, $uri); + } + + public function get($isoCountry) { + $instance = new $this->instance_name($this->client, $this->uri . "/$isoCountry"); + $instance->iso_country = $isoCountry; + return $instance; + } +} \ No newline at end of file diff --git a/Services/Twilio/Rest/Pricing/VoiceCountry.php b/Services/Twilio/Rest/Pricing/VoiceCountry.php new file mode 100644 index 0000000..5252694 --- /dev/null +++ b/Services/Twilio/Rest/Pricing/VoiceCountry.php @@ -0,0 +1,5 @@ +instance_name($this->client, $this->uri . "/$number"); + $instance->number = $number; + return $instance; + } +} \ No newline at end of file diff --git a/Services/Twilio/Rest/Queue.php b/Services/Twilio/Rest/Queue.php new file mode 100644 index 0000000..fa0f2f7 --- /dev/null +++ b/Services/Twilio/Rest/Queue.php @@ -0,0 +1,10 @@ +setupSubresources('members'); + } +} + diff --git a/Services/Twilio/Rest/Queues.php b/Services/Twilio/Rest/Queues.php new file mode 100644 index 0000000..bc35c83 --- /dev/null +++ b/Services/Twilio/Rest/Queues.php @@ -0,0 +1,19 @@ + $friendly_name, + ) + $params); + } +} + diff --git a/Services/Twilio/Rest/Recording.php b/Services/Twilio/Rest/Recording.php new file mode 100644 index 0000000..a76014c --- /dev/null +++ b/Services/Twilio/Rest/Recording.php @@ -0,0 +1,9 @@ +setupSubresources('transcriptions'); + } +} diff --git a/Services/Twilio/Rest/Recordings.php b/Services/Twilio/Rest/Recordings.php new file mode 100644 index 0000000..47ec0d5 --- /dev/null +++ b/Services/Twilio/Rest/Recordings.php @@ -0,0 +1,6 @@ +setupSubresources( + 'domains', + 'ip_access_control_lists', + 'credential_lists' + ); + } + + public function getResourceName($camelized = false) { + return "SIP"; + } +} diff --git a/Services/Twilio/Rest/SmsMessage.php b/Services/Twilio/Rest/SmsMessage.php new file mode 100644 index 0000000..6bd3f9c --- /dev/null +++ b/Services/Twilio/Rest/SmsMessage.php @@ -0,0 +1,6 @@ + $from, + 'To' => $to, + 'Body' => $body + ) + $params); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/Activities.php b/Services/Twilio/Rest/TaskRouter/Activities.php new file mode 100644 index 0000000..9b1f90f --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/Activities.php @@ -0,0 +1,15 @@ +instance_name = "Services_Twilio_Rest_TaskRouter_Activity"; + parent::__construct($client, $uri); + } + + public function create($friendlyName, $available) { + $params['FriendlyName'] = $friendlyName; + $params['Available'] = $available; + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/Activity.php b/Services/Twilio/Rest/TaskRouter/Activity.php new file mode 100644 index 0000000..08f0633 --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/Activity.php @@ -0,0 +1,5 @@ +client->retrieveData($this->uri, $filters); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/Task.php b/Services/Twilio/Rest/TaskRouter/Task.php new file mode 100644 index 0000000..ace660b --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/Task.php @@ -0,0 +1,9 @@ +setupSubresources('reservations'); + } + +} diff --git a/Services/Twilio/Rest/TaskRouter/TaskQueue.php b/Services/Twilio/Rest/TaskRouter/TaskQueue.php new file mode 100644 index 0000000..2e6fae6 --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/TaskQueue.php @@ -0,0 +1,8 @@ +setupSubresource('statistics'); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/TaskQueueStatistics.php b/Services/Twilio/Rest/TaskRouter/TaskQueueStatistics.php new file mode 100644 index 0000000..73ce98e --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/TaskQueueStatistics.php @@ -0,0 +1,5 @@ +setupSubresource('statistics'); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/TaskQueuesStatistics.php b/Services/Twilio/Rest/TaskRouter/TaskQueuesStatistics.php new file mode 100644 index 0000000..e2b25b5 --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/TaskQueuesStatistics.php @@ -0,0 +1,9 @@ +instance_name = "Services_Twilio_Rest_TaskRouter_TaskQueueStatistics"; + parent::__construct($client, $uri); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/Tasks.php b/Services/Twilio/Rest/TaskRouter/Tasks.php new file mode 100644 index 0000000..d9d193b --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/Tasks.php @@ -0,0 +1,11 @@ +setupSubresource('statistics'); + $this->setupSubresources('reservations'); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/WorkerStatistics.php b/Services/Twilio/Rest/TaskRouter/WorkerStatistics.php new file mode 100644 index 0000000..4c8c26a --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/WorkerStatistics.php @@ -0,0 +1,5 @@ +setupSubresource('statistics'); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/WorkersStatistics.php b/Services/Twilio/Rest/TaskRouter/WorkersStatistics.php new file mode 100644 index 0000000..95455dd --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/WorkersStatistics.php @@ -0,0 +1,5 @@ +setupSubresource('statistics'); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/WorkflowStatistics.php b/Services/Twilio/Rest/TaskRouter/WorkflowStatistics.php new file mode 100644 index 0000000..98b9693 --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/WorkflowStatistics.php @@ -0,0 +1,5 @@ +setupSubresources( + 'activities', + 'events', + 'tasks', + 'task_queues', + 'workers', + 'workflows' + ); + $this->setupSubresource('statistics'); + } +} diff --git a/Services/Twilio/Rest/TaskRouter/WorkspaceStatistics.php b/Services/Twilio/Rest/TaskRouter/WorkspaceStatistics.php new file mode 100644 index 0000000..f5eae06 --- /dev/null +++ b/Services/Twilio/Rest/TaskRouter/WorkspaceStatistics.php @@ -0,0 +1,5 @@ +account->tokens->create(array( + * "Ttl" => 100, + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } + +} diff --git a/Services/Twilio/Rest/Transcription.php b/Services/Twilio/Rest/Transcription.php new file mode 100644 index 0000000..83c139c --- /dev/null +++ b/Services/Twilio/Rest/Transcription.php @@ -0,0 +1,6 @@ +trunks->get('TK123')->credential_lists->create(array( + * "CredentialListSid" => "CL1234xxxxx", + * .... + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/Trunking/IpAccessControlList.php b/Services/Twilio/Rest/Trunking/IpAccessControlList.php new file mode 100644 index 0000000..f66eae6 --- /dev/null +++ b/Services/Twilio/Rest/Trunking/IpAccessControlList.php @@ -0,0 +1,5 @@ +trunks->get('TK123')->ip_access_control_lists->create(array( + * "IpAccessControlListSid" => "AL1234xxxx", + * .... + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/Trunking/OriginationUrl.php b/Services/Twilio/Rest/Trunking/OriginationUrl.php new file mode 100644 index 0000000..0d56c78 --- /dev/null +++ b/Services/Twilio/Rest/Trunking/OriginationUrl.php @@ -0,0 +1,5 @@ +trunks->get('TK123')->origination_urls->create(array( + * "FriendlyName" => "TestUrl", + * .... + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/Trunking/PhoneNumber.php b/Services/Twilio/Rest/Trunking/PhoneNumber.php new file mode 100644 index 0000000..9495f81 --- /dev/null +++ b/Services/Twilio/Rest/Trunking/PhoneNumber.php @@ -0,0 +1,5 @@ +trunks->get('TK123')->phone_numbers->create(array( + * "PhoneNumberSid" => "PN1234xxxx" + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Rest/Trunking/Trunk.php b/Services/Twilio/Rest/Trunking/Trunk.php new file mode 100644 index 0000000..0d93b9e --- /dev/null +++ b/Services/Twilio/Rest/Trunking/Trunk.php @@ -0,0 +1,13 @@ +setupSubresources( + 'credential_lists', + 'ip_access_control_lists', + 'origination_urls', + 'phone_numbers' + ); + } +} diff --git a/Services/Twilio/Rest/Trunking/Trunks.php b/Services/Twilio/Rest/Trunking/Trunks.php new file mode 100644 index 0000000..9c76ef2 --- /dev/null +++ b/Services/Twilio/Rest/Trunking/Trunks.php @@ -0,0 +1,5 @@ +setupSubresources( + 'today', + 'yesterday', + 'all_time', + 'this_month', + 'last_month', + 'daily', + 'monthly', + 'yearly' + ); + } +} + +class Services_Twilio_Rest_Today extends Services_Twilio_TimeRangeResource { } + +class Services_Twilio_Rest_Yesterday extends Services_Twilio_TimeRangeResource { } + +class Services_Twilio_Rest_LastMonth extends Services_Twilio_TimeRangeResource { } + +class Services_Twilio_Rest_ThisMonth extends Services_Twilio_TimeRangeResource { } + +class Services_Twilio_Rest_AllTime extends Services_Twilio_TimeRangeResource { } + +class Services_Twilio_Rest_Daily extends Services_Twilio_UsageResource { } + +class Services_Twilio_Rest_Monthly extends Services_Twilio_UsageResource { } + +class Services_Twilio_Rest_Yearly extends Services_Twilio_UsageResource { } diff --git a/Services/Twilio/Rest/UsageTrigger.php b/Services/Twilio/Rest/UsageTrigger.php new file mode 100644 index 0000000..44c8cf5 --- /dev/null +++ b/Services/Twilio/Rest/UsageTrigger.php @@ -0,0 +1,5 @@ +`_. + * @param string $value Fire the trigger when usage crosses this value. + * @param string $url The URL to request when the trigger fires. + * @param array $params Optional parameters for this trigger. A full list of parameters can be found in the `Usage Trigger documentation `_. + * @return Services_Twilio_Rest_UsageTrigger The created trigger + */ + function create($category, $value, $url, array $params = array()) { + return parent::_create(array( + 'UsageCategory' => $category, + 'TriggerValue' => $value, + 'CallbackUrl' => $url, + ) + $params); + } + +} + diff --git a/Services/Twilio/RestException.php b/Services/Twilio/RestException.php new file mode 100644 index 0000000..c7de16c --- /dev/null +++ b/Services/Twilio/RestException.php @@ -0,0 +1,44 @@ +status = $status; + $this->info = $info; + parent::__construct($message, $code); + } + + /** + * Get the HTTP status code + */ + public function getStatus() { + return $this->status; + } + + /** + * Get a link to more information + */ + public function getInfo() { + return $this->info; + } +} diff --git a/Services/Twilio/SIPListResource.php b/Services/Twilio/SIPListResource.php new file mode 100644 index 0000000..1e63b67 --- /dev/null +++ b/Services/Twilio/SIPListResource.php @@ -0,0 +1,14 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_TaskRouter_Capability extends Services_Twilio_TaskRouter_CapabilityAPI +{ + protected $baseUrl = 'https://taskrouter.twilio.com/v1'; + protected $baseWsUrl = 'https://event-bridge.twilio.com/v1/wschannels'; + protected $version = 'v1'; + + protected $workspaceSid; + protected $channelId; + protected $resourceUrl; + + protected $required = array("required" => true); + protected $optional = array("required" => false); + + public function __construct($accountSid, $authToken, $workspaceSid, $channelId, $resourceUrl = null, $overrideBaseUrl = null, $overrideBaseWSUrl = null) { + parent::__construct($accountSid, $authToken, $this->version, $channelId); + + $this->workspaceSid = $workspaceSid; + $this->channelId = $channelId; + if(isset($overrideBaseUrl)) { + $this->baseUrl = $overrideBaseUrl; + } + if(isset($overrideBaseWSUrl)) { + $this->baseWsUrl = $overrideBaseWSUrl; + } + $this->baseUrl = $this->baseUrl.'/Workspaces/'.$workspaceSid; + + $this->validateJWT(); + + if(!isset($resourceUrl)) { + $this->setupResource(); + } + + //add permissions to GET and POST to the event-bridge channel + $this->allow($this->baseWsUrl."/".$this->accountSid."/".$this->channelId, "GET", null, null); + $this->allow($this->baseWsUrl."/".$this->accountSid."/".$this->channelId, "POST", null, null); + + //add permissions to fetch the instance resource + $this->allow($this->resourceUrl, "GET", null, null); + } + + protected function setupResource() { + if(substr($this->channelId,0,2) == 'WS') { + $this->resourceUrl = $this->baseUrl; + }else if(substr($this->channelId,0,2) == 'WK') { + $this->resourceUrl = $this->baseUrl.'/Workers/'.$this->channelId; + + //add permissions to fetch the list of activities, tasks and worker reservations + $activityUrl = $this->baseUrl.'/Activities'; + $this->allow($activityUrl, "GET", null, null); + + $tasksUrl = $this->baseUrl.'/Tasks/**'; + $this->allow($tasksUrl, "GET", null, null); + + $workerReservationsUrl = $this->resourceUrl.'/Reservations/**'; + $this->allow($workerReservationsUrl, "GET", null, null); + + }else if(substr($this->channelId,0,2) == 'WQ') { + $this->resourceUrl = $this->baseUrl.'/TaskQueues/'.$this->channelId; + } + } + + private function validateJWT() { + if(!isset($this->accountSid) || substr($this->accountSid,0,2) != 'AC') { + throw new Exception("Invalid AccountSid provided: ".$this->accountSid); + } + if(!isset($this->workspaceSid) || substr($this->workspaceSid,0,2) != 'WS') { + throw new Exception("Invalid WorkspaceSid provided: ".$this->workspaceSid); + } + if(!isset($this->channelId)) { + throw new Exception("ChannelId not provided"); + } + $prefix = substr($this->channelId,0,2); + if($prefix != 'WS' && $prefix != 'WK' && $prefix != 'WQ') { + throw new Exception("Invalid ChannelId provided: ".$this->channelId); + } + } + + public function allowFetchSubresources() { + $method = 'GET'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->resourceUrl.'/**', $method, $queryFilter, $postFilter); + } + + public function allowUpdates() { + $method = 'POST'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->resourceUrl, $method, $queryFilter, $postFilter); + } + + public function allowUpdatesSubresources() { + $method = 'POST'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->resourceUrl.'/**', $method, $queryFilter, $postFilter); + } + + public function allowDelete() { + $method = 'DELETE'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->resourceUrl, $method, $queryFilter, $postFilter); + } + + public function allowDeleteSubresources() { + $method = 'DELETE'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->resourceUrl.'/**', $method, $queryFilter, $postFilter); + } + + /** + * @deprecated Please use {Services_Twilio_TaskRouter_Worker_Capability.allowActivityUpdates} instead + */ + public function allowWorkerActivityUpdates() { + $method = 'POST'; + $queryFilter = array(); + $postFilter = array("ActivitySid" => $this->required); + $this->allow($this->resourceUrl, $method, $queryFilter, $postFilter); + } + + /** + * @deprecated Please use {Services_Twilio_TaskRouter_Worker_Capability} instead; added automatically in constructor + */ + public function allowWorkerFetchAttributes() { + $method = 'GET'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->resourceUrl, $method, $queryFilter, $postFilter); + } + + /** + * @deprecated Please use {Services_Twilio_TaskRouter_Worker_Capability.allowReservationUpdates} instead + */ + public function allowTaskReservationUpdates() { + $method = 'POST'; + $queryFilter = array(); + $postFilter = array(); + $reservationsUrl = $this->baseUrl.'/Tasks/**'; + $this->allow($reservationsUrl, $method, $queryFilter, $postFilter); + } + + public function generateToken($ttl = 3600, $extraAttributes = null) { + $taskRouterAttributes = array( + 'account_sid' => $this->accountSid, + 'channel' => $this->channelId, + 'workspace_sid' => $this->workspaceSid + ); + + if(substr($this->channelId,0,2) == 'WK') { + $taskRouterAttributes['worker_sid'] = $this->channelId; + }else if(substr($this->channelId,0,2) == 'WQ') { + $taskRouterAttributes['taskqueue_sid'] = $this->channelId; + } + + return parent::generateToken($ttl, $taskRouterAttributes); + } +} \ No newline at end of file diff --git a/Services/Twilio/TaskRouter/CapabilityAPI.php b/Services/Twilio/TaskRouter/CapabilityAPI.php new file mode 100644 index 0000000..737b40a --- /dev/null +++ b/Services/Twilio/TaskRouter/CapabilityAPI.php @@ -0,0 +1,153 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_TaskRouter_CapabilityAPI +{ + protected $accountSid; + protected $authToken; + private $version; + private $friendlyName; + private $policies; + + public function __construct($accountSid, $authToken, $version, $friendlyName) + { + $this->accountSid = $accountSid; + $this->authToken = $authToken; + $this->version = $version; + $this->friendlyName = $friendlyName; + $this->policies = array(); + } + + public function addPolicyDeconstructed($url, $method, $queryFilter = array(), $postFilter = array(), $allow = true) { + $policy = new Policy($url, $method, $queryFilter, $postFilter, $allow); + array_push($this->policies, $policy); + return $policy; + } + + public function allow($url, $method, $queryFilter = array(), $postFilter = array()) { + $this->addPolicyDeconstructed($url, $method, $queryFilter, $postFilter, true); + } + + public function deny($url, $method, $queryFilter = array(), $postFilter = array()) { + $this->addPolicyDeconstructed($url, $method, $queryFilter, $postFilter, false); + } + + /** + * @deprecated Please use {Services_Twilio_API_Capability.allow, Services_Twilio_API_Capability.disallow} instead + */ + public function addPolicy($policy) { + array_push($this->policies, $policy); + } + + /** + * @deprecated Please use {Services_Twilio_API_Capability.allow, Services_Twilio_API_Capability.disallow} instead + */ + public function generatePolicy($url, $method, $queryFilter = array(), $postFilter = array(), $allow = true) + { + return $this->addPolicyDeconstructed($url, $method, $queryFilter, $postFilter, $allow); + } + + /** + * @deprecated Please use {Services_Twilio_API_Capability.allow, Services_Twilio_API_Capability.disallow} instead + */ + public function generateAndAddPolicy($url, $method, $queryFilter = array(), $postFilter = array(), $allow = true) { + $this->addPolicyDeconstructed($url, $method, $queryFilter, $postFilter, $allow); + } + + /** + * Generates a new token based on the credentials and permissions that + * previously has been granted to this token. + * + * @param $ttl the expiration time of the token (in seconds). Default + * value is 3600 (1hr) + * @param $extraAttributes extra attributes to be tied to the jwt. + * @return the newly generated token that is valid for $ttl seconds + */ + public function generateToken($ttl = 3600, $extraAttributes = null) + { + $payload = array( + 'version' => $this->version, + 'friendly_name' => $this->friendlyName, + 'policies' => array(), + 'iss' => $this->accountSid, + 'exp' => time() + $ttl + ); + if(isset($extraAttributes)) { + foreach ($extraAttributes as $key => $value) { + $payload[$key] = $value; + } + } + + $policyStrings = array(); + + foreach ($this->policies as $policy) { + $policyStrings[] = $policy->toArray(); + } + + $payload['policies'] = $policyStrings; + return JWT::encode($payload, $this->authToken, 'HS256'); + } +} + +/** + * Twilio API Policy constructor + * + * @category Services + * @package Services_Twilio + * @author Justin Witz + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Policy +{ + private $url; + private $method; + private $queryFilter; + private $postFilter; + private $allow; + + public function __construct($url, $method, $queryFilter = array(), $postFilter = array(), $allow = true) + { + $this->url = $url; + $this->method = $method; + $this->queryFilter = $queryFilter; + $this->postFilter = $postFilter; + $this->allow = $allow; + } + + public function addQueryFilter($queryFilter) + { + array_push($this->queryFilter, $queryFilter); + } + + public function addPostFilter($postFilter) + { + array_push($this->postFilter, $postFilter); + } + + public function toArray() { + $policy_array = array('url' => $this->url, 'method' => $this->method, 'allow' => $this->allow); + if (!is_null($this->queryFilter)) { + if (count($this->queryFilter) > 0 ) { + $policy_array['query_filter'] = $this->queryFilter; + } else { + $policy_array['query_filter'] = new stdClass(); + } + } + + if (!is_null($this->postFilter)) { + if (count($this->postFilter) > 0 ) { + $policy_array['post_filter'] = $this->postFilter; + } else { + $policy_array['post_filter'] = new stdClass(); + } + } + + return $policy_array; + } +} diff --git a/Services/Twilio/TaskRouter/TaskQueue/Capability.php b/Services/Twilio/TaskRouter/TaskQueue/Capability.php new file mode 100644 index 0000000..3ad6fe8 --- /dev/null +++ b/Services/Twilio/TaskRouter/TaskQueue/Capability.php @@ -0,0 +1,21 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_TaskRouter_TaskQueue_Capability extends Services_Twilio_TaskRouter_Capability +{ + public function __construct($accountSid, $authToken, $workspaceSid, $taskQueueSid, $overrideBaseUrl = null, $overrideBaseWSUrl = null) + { + parent::__construct($accountSid, $authToken, $workspaceSid, $taskQueueSid, null, $overrideBaseUrl, $overrideBaseWSUrl); + } + + protected function setupResource() { + $this->resourceUrl = $this->baseUrl.'/TaskQueues/'.$this->channelId; + } +} \ No newline at end of file diff --git a/Services/Twilio/TaskRouter/Worker/Capability.php b/Services/Twilio/TaskRouter/Worker/Capability.php new file mode 100644 index 0000000..4ce6c10 --- /dev/null +++ b/Services/Twilio/TaskRouter/Worker/Capability.php @@ -0,0 +1,49 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_TaskRouter_Worker_Capability extends Services_Twilio_TaskRouter_Capability +{ + private $tasksUrl; + private $workerReservationsUrl; + private $activityUrl; + + public function __construct($accountSid, $authToken, $workspaceSid, $workerSid, $overrideBaseUrl = null, $overrideBaseWSUrl = null) + { + parent::__construct($accountSid, $authToken, $workspaceSid, $workerSid, null, $overrideBaseUrl, $overrideBaseWSUrl); + + $this->tasksUrl = $this->baseUrl.'/Tasks/**'; + $this->activityUrl = $this->baseUrl.'/Activities'; + $this->workerReservationsUrl = $this->resourceUrl.'/Reservations/**'; + + //add permissions to fetch the list of activities, tasks, and worker reservations + $this->allow($this->activityUrl, "GET", null, null); + $this->allow($this->tasksUrl, "GET", null, null); + $this->allow($this->workerReservationsUrl, "GET", null, null); + } + + protected function setupResource() { + $this->resourceUrl = $this->baseUrl.'/Workers/'.$this->channelId; + } + + public function allowActivityUpdates() { + $method = 'POST'; + $queryFilter = array(); + $postFilter = array("ActivitySid" => $this->required); + $this->allow($this->resourceUrl, $method, $queryFilter, $postFilter); + } + + public function allowReservationUpdates() { + $method = 'POST'; + $queryFilter = array(); + $postFilter = array(); + $this->allow($this->tasksUrl, $method, $queryFilter, $postFilter); + $this->allow($this->workerReservationsUrl, $method, $queryFilter, $postFilter); + } +} \ No newline at end of file diff --git a/Services/Twilio/TaskRouter/Workspace/Capability.php b/Services/Twilio/TaskRouter/Workspace/Capability.php new file mode 100644 index 0000000..e0a014d --- /dev/null +++ b/Services/Twilio/TaskRouter/Workspace/Capability.php @@ -0,0 +1,21 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_TaskRouter_Workspace_Capability extends Services_Twilio_TaskRouter_Capability +{ + public function __construct($accountSid, $authToken, $workspaceSid, $overrideBaseUrl = null, $overrideBaseWSUrl = null) + { + parent::__construct($accountSid, $authToken, $workspaceSid, $workspaceSid, null, $overrideBaseUrl, $overrideBaseWSUrl); + } + + protected function setupResource() { + $this->resourceUrl = $this->baseUrl; + } +} \ No newline at end of file diff --git a/Services/Twilio/TaskRouterInstanceResource.php b/Services/Twilio/TaskRouterInstanceResource.php new file mode 100644 index 0000000..0c38c5b --- /dev/null +++ b/Services/Twilio/TaskRouterInstanceResource.php @@ -0,0 +1,22 @@ +subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } + + protected function setupSubresource($name) { + $constantized = ucfirst(self::camelize($name)); + $type = get_class($this) . $constantized; + $this->subresources[$name] = new $type( + $this->client, $this->uri . "/". $constantized + ); + } +} diff --git a/Services/Twilio/TaskRouterListResource.php b/Services/Twilio/TaskRouterListResource.php new file mode 100644 index 0000000..156e190 --- /dev/null +++ b/Services/Twilio/TaskRouterListResource.php @@ -0,0 +1,26 @@ +getResourceName(true); + /* + * By default trim the 's' from the end of the list name to get the + * instance name (ex Accounts -> Account). This behavior can be + * overridden by child classes if the rule doesn't work. + */ + if (!isset($this->instance_name)) { + $this->instance_name = "Services_Twilio_Rest_TaskRouter_" . rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } + + protected function setupSubresource($name) { + $constantized = ucfirst(self::camelize($name)); + $type = get_class($this) . $constantized; + $this->subresources[$name] = new $type( + $this->client, $this->uri . "/". $constantized + ); + } +} diff --git a/Services/Twilio/TimeRangeResource.php b/Services/Twilio/TimeRangeResource.php new file mode 100644 index 0000000..ebf1990 --- /dev/null +++ b/Services/Twilio/TimeRangeResource.php @@ -0,0 +1,31 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + * @link http://pear.php.net/package/Services_Twilio + */ +class Services_Twilio_TimeRangeResource extends Services_Twilio_UsageResource { + + /** + * Return a UsageRecord corresponding to the given category. + * + * @param string $category The category of usage to retrieve. For a full + * list of valid categories, please see the documentation at + * http://www.twilio.com/docs/api/rest/usage-records#usage-all-categories + * @return Services_Twilio_Rest_UsageRecord + * @throws Services_Twilio_RestException + */ + public function getCategory($category) { + $page = $this->getPage(0, 1, array( + 'Category' => $category, + )); + $items = $page->getItems(); + if (!is_array($items) || count($items) === 0) { + throw new Services_Twilio_RestException( + 400, "Usage record data is unformattable."); + } + return $items[0]; + } +} diff --git a/Services/Twilio/TinyHttp.php b/Services/Twilio/TinyHttp.php new file mode 100644 index 0000000..b6ccfe2 --- /dev/null +++ b/Services/Twilio/TinyHttp.php @@ -0,0 +1,134 @@ + array( + * CURLOPT_USERAGENT => self::USER_AGENT, + * CURLOPT_HTTPHEADER => array('Accept-Charset: utf-8'), + * CURLOPT_CAINFO => dirname(__FILE__) . '/cacert.pem', + * )) + * ); + */ +class Services_Twilio_TinyHttp { + var $user, $pass, $scheme, $host, $port, $debug, $curlopts; + + public function __construct($uri = '', $kwargs = array()) { + foreach (parse_url($uri) as $name => $value) $this->$name = $value; + $this->debug = isset($kwargs['debug']) ? !!$kwargs['debug'] : NULL; + $this->curlopts = isset($kwargs['curlopts']) ? $kwargs['curlopts'] : array(); + } + + public function __call($name, $args) { + list($res, $req_headers, $req_body) = $args + array(0, array(), ''); + + if (strpos($res, 'http') === 0) { + // We got handed a complete URL, just use it + $url = $res; + } else { + // Build from path and default scheme/host. + $url = "$this->scheme://$this->host$res"; + } + + $opts = $this->curlopts + array( + CURLOPT_URL => $url, + CURLOPT_HEADER => TRUE, + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_INFILESIZE => -1, + CURLOPT_POSTFIELDS => NULL, + CURLOPT_TIMEOUT => 60, + ); + + foreach ($req_headers as $k => $v) $opts[CURLOPT_HTTPHEADER][] = "$k: $v"; + if ($this->port) $opts[CURLOPT_PORT] = $this->port; + if ($this->debug) $opts[CURLINFO_HEADER_OUT] = TRUE; + if ($this->user && $this->pass) $opts[CURLOPT_USERPWD] = "$this->user:$this->pass"; + switch ($name) { + case 'get': + $opts[CURLOPT_HTTPGET] = TRUE; + break; + case 'post': + $opts[CURLOPT_POST] = TRUE; + $opts[CURLOPT_POSTFIELDS] = $req_body; + break; + case 'put': + $opts[CURLOPT_PUT] = TRUE; + if (strlen($req_body)) { + if ($buf = fopen('php://memory', 'w+')) { + fwrite($buf, $req_body); + fseek($buf, 0); + $opts[CURLOPT_INFILE] = $buf; + $opts[CURLOPT_INFILESIZE] = strlen($req_body); + } else throw new Services_Twilio_TinyHttpException('unable to open temporary file'); + } + break; + case 'head': + $opts[CURLOPT_NOBODY] = TRUE; + break; + default: + $opts[CURLOPT_CUSTOMREQUEST] = strtoupper($name); + break; + } + try { + if ($curl = curl_init()) { + if (curl_setopt_array($curl, $opts)) { + if ($response = curl_exec($curl)) { + $parts = explode("\r\n\r\n", $response, 3); + list($head, $body) = ($parts[0] == 'HTTP/1.1 100 Continue') + ? array($parts[1], $parts[2]) + : array($parts[0], $parts[1]); + $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); + if ($this->debug) { + error_log( + curl_getinfo($curl, CURLINFO_HEADER_OUT) . + $req_body + ); + } + $header_lines = explode("\r\n", $head); + array_shift($header_lines); + foreach ($header_lines as $line) { + list($key, $value) = explode(":", $line, 2); + $headers[$key] = trim($value); + } + curl_close($curl); + if (isset($buf) && is_resource($buf)) { + fclose($buf); + } + return array($status, $headers, $body); + } else { + throw new Services_Twilio_TinyHttpException(curl_error($curl)); + } + } else throw new Services_Twilio_TinyHttpException(curl_error($curl)); + } else throw new Services_Twilio_TinyHttpException('unable to initialize cURL'); + } catch (ErrorException $e) { + if (is_resource($curl)) curl_close($curl); + if (isset($buf) && is_resource($buf)) fclose($buf); + throw $e; + } + } + + public function authenticate($user, $pass) { + $this->user = $user; + $this->pass = $pass; + } +} diff --git a/Services/Twilio/TrunkingInstanceResource.php b/Services/Twilio/TrunkingInstanceResource.php new file mode 100644 index 0000000..505fe94 --- /dev/null +++ b/Services/Twilio/TrunkingInstanceResource.php @@ -0,0 +1,15 @@ +subresources[$name] = new $type( + $this->client, $this->uri . "/$constantized" + ); + } + } + +} diff --git a/Services/Twilio/TrunkingListResource.php b/Services/Twilio/TrunkingListResource.php new file mode 100644 index 0000000..dc51ede --- /dev/null +++ b/Services/Twilio/TrunkingListResource.php @@ -0,0 +1,38 @@ +getResourceName(true); + /* + * By default trim the 's' from the end of the list name to get the + * instance name (ex Accounts -> Account). This behavior can be + * overridden by child classes if the rule doesn't work. + */ + if (!isset($this->instance_name)) { + $this->instance_name = "Services_Twilio_Rest_Trunking_" . rtrim($name, 's'); + } + + parent::__construct($client, $uri); + } + + /** + * Create a new Trunk instance + * + * Example usage: + * + * .. code-block:: php + * + * $trunkingClient->trunks->create(array( + * "FriendlyName" => "TestTrunk", + * "DomainName" => "test.pstn.twilio.com" + * )); + * + * :param array $params: a single array of parameters which is serialized and + * sent directly to the Twilio API. + * + */ + public function create($params = array()) { + return parent::_create($params); + } +} diff --git a/Services/Twilio/Twiml.php b/Services/Twilio/Twiml.php new file mode 100644 index 0000000..6a021ca --- /dev/null +++ b/Services/Twilio/Twiml.php @@ -0,0 +1,137 @@ + + * License: http://creativecommons.org/licenses/MIT/ MIT + */ +class Services_Twilio_Twiml { + + protected $element; + + /** + * Constructs a Twiml response. + * + * :param SimpleXmlElement|array $arg: Can be any of + * + * - the element to wrap + * - attributes to add to the element + * - if null, initialize an empty element named 'Response' + */ + public function __construct($arg = null) { + switch (true) { + case $arg instanceof SimpleXmlElement: + $this->element = $arg; + break; + case $arg === null: + $this->element = new SimpleXmlElement(''); + break; + case is_array($arg): + $this->element = new SimpleXmlElement(''); + foreach ($arg as $name => $value) { + $this->element->addAttribute($name, $value); + } + break; + default: + throw new Services_Twilio_TwimlException('Invalid argument'); + } + } + + /** + * Converts method calls into Twiml verbs. + * + * A basic example: + * + * .. code-block:: php + * + * php> print $this->say('hello'); + * hello + * + * An example with attributes: + * + * .. code-block:: php + * + * print $this->say('hello', array('voice' => 'woman')); + * hello + * + * You could even just pass in an attributes array, omitting the noun: + * + * .. code-block:: php + * + * print $this->gather(array('timeout' => '20')); + * + * + * :param string $verb: The Twiml verb. + * :param array $args: + * - (noun string) + * - (noun string, attributes array) + * - (attributes array) + * + * :return: A SimpleXmlElement + * :rtype: SimpleXmlElement + */ + public function __call($verb, array $args) + { + list($noun, $attrs) = $args + array('', array()); + if (is_array($noun)) { + list($attrs, $noun) = array($noun, ''); + } + /* addChild does not escape XML, while addAttribute does. This means if + * you pass unescaped ampersands ("&") to addChild, you will generate + * an error. + * + * Some inexperienced developers will pass in unescaped ampersands, and + * we want to make their code work, by escaping the ampersands for them + * before passing the string to addChild. (with htmlentities) + * + * However other people will know what to do, and their code + * already escapes ampersands before passing them to addChild. We don't + * want to break their existing code by turning their &'s into + * &amp; + * + * We also want to use numeric entities, not named entities so that we + * are fully compatible with XML + * + * The following lines accomplish the desired behavior. + */ + $decoded = html_entity_decode($noun, ENT_COMPAT, 'UTF-8'); + $normalized = htmlspecialchars($decoded, ENT_COMPAT, 'UTF-8', false); + $child = empty($noun) + ? $this->element->addChild(ucfirst($verb)) + : $this->element->addChild(ucfirst($verb), $normalized); + foreach ($attrs as $name => $value) { + /* Note that addAttribute escapes raw ampersands by default, so we + * haven't touched its implementation. So this is the matrix for + * addAttribute: + * + * & turns into & + * & turns into &amp; + */ + if (is_bool($value)) { + $value = ($value === true) ? 'true' : 'false'; + } + $child->addAttribute($name, $value); + } + return new static($child); + } + + /** + * Returns the object as XML. + * + * :return: The response as an XML string + * :rtype: string + */ + public function __toString() + { + $xml = $this->element->asXml(); + return str_replace( + '', + '', $xml); + } +} diff --git a/Services/Twilio/UsageResource.php b/Services/Twilio/UsageResource.php new file mode 100644 index 0000000..b9b929c --- /dev/null +++ b/Services/Twilio/UsageResource.php @@ -0,0 +1,20 @@ + + * @license http://creativecommons.org/licenses/MIT/ MIT + * @link http://pear.php.net/package/Services_Twilio + */ +class Services_Twilio_UsageResource extends Services_Twilio_ListResource { + public function getResourceName($camelized = false) { + $this->instance_name = 'Services_Twilio_Rest_UsageRecord'; + return $camelized ? 'UsageRecords' : 'usage_records'; + } + + public function __construct($client, $uri) { + $uri = preg_replace("#UsageRecords#", "Usage/Records", $uri); + parent::__construct($client, $uri); + } +} + diff --git a/Services/Twilio/WorkflowConfiguration.php b/Services/Twilio/WorkflowConfiguration.php new file mode 100644 index 0000000..ba4da0a --- /dev/null +++ b/Services/Twilio/WorkflowConfiguration.php @@ -0,0 +1,101 @@ +filters = $filters; + $this->default_filter = $default_filter; + } + + public function toJSON() { + return json_encode($this); + } + + public static function parse($json) { + return json_decode($json); + } + + public static function fromJson($json) { + $configJSON = self::parse($json); + $default_filter = $configJSON->task_routing->default_filter; + $filters = array(); + foreach($configJSON->task_routing->filters as $filter) { + // friendly_name and filter_friendly_name should map to same variable + $friendly_name = isset($filter->filter_friendly_name) ? $filter->filter_friendly_name : $filter->friendly_name; + $filter = new WorkflowRule($filter->expression, $filter->targets, $friendly_name); + $filters[] = $filter; + } + return new WorkflowConfiguration($filters, $default_filter); + } + + public function jsonSerialize() { + $json = array(); + $task_routing = array(); + $task_routing["filters"] = $this->filters; + $task_routing["default_filter"] = $this->default_filter; + $json["task_routing"] = $task_routing; + return $json; + } +} + +class WorkflowRule implements JsonSerializable { + public $expression; + public $friendly_name; + public $targets; + + public function __construct($expression, $targets, $friendly_name = null) + { + $this->expression = $expression; + $this->targets = $targets; + $this->friendly_name = $friendly_name; + } + + public function jsonSerialize() { + $json = array(); + $json["expression"] = $this->expression; + $json["targets"] = $this->targets; + if($this->friendly_name != null) { + $json["friendly_name"] = $this->friendly_name; + } + return $json; + } +} + +class WorkflowRuleTarget implements JsonSerializable { + public $queue; + public $expression; + public $priority; + public $timeout; + + public function __construct($queue, $priority = null, $timeout = null, $expression = null) + { + $this->queue = $queue; + $this->priority = $priority; + $this->timeout = $timeout; + $this->expression = $expression; + } + + public function jsonSerialize() { + $json = array(); + $json["queue"] = $this->queue; + if($this->priority != null) { + $json["priority"] = $this->priority; + } + if($this->timeout != null) { + $json["timeout"] = $this->timeout; + } + if($this->expression != null) { + $json["expression"] = $this->expression; + } + return $json; + } +} \ No newline at end of file diff --git a/bystanderIntervention.php b/bystanderIntervention.php new file mode 100644 index 0000000..c17fc24 --- /dev/null +++ b/bystanderIntervention.php @@ -0,0 +1,87 @@ + + + + + FirstAide + + + + +
+
+
+

Bystander Intervention

+
+
+ +
+ + + + + + + +
+ Bystander Intervention is a process of safely interrupting a situation in which others may be at risk for becoming the victim of harassment, or sexual or physical violence. +

+ In Peace Corps history, other Volunteers often witnessed the events leading up to Volunteer sexual and physical assaults. There are ways to safely intervene when you see your fellow Trainee or Volunteer in a potentially dangerous situation. +

+ #1 Rule: Your safety is your FIRST priority. Bystander intervention is a tool to be used with Volunteers. If you see a situation between locals that needs intervention, contact your SSM for guidance on how to proceed. +
+

Verbal with Potential Victim:

+
    +
  • Diffuse the situation by starting a new conversation
  • +
  • Say a friend is looking for the individual and leave together to find them
  • +
  • Tell the individual there is a previous engagement with others and they need to leave with you
  • +
  • “I need your advice…” and pull them away from the immediate space
  • +
  • “Do you need help?”
  • +
  • “Let’s walk home together”
  • +
  • “Do you want me to call someone for you?”
  • +
  • “What can I do to help you?”
  • +
  • “Do you want me to talk to so-and-so for you?”
  • +
+
+

Verbal with Potential Offender

+
    +
  • Introduce yourself- let the predator know the individual isn’t alone
  • +
  • Engage with the individual directly by starting a completely different conversation; example- sports, directions
  • +
  • Use humor to diffuse the situation
  • +
  • “How would you feel if someone did that/said that to your sister/mother?”
  • +
  • “I don’t like what you just said.”
  • +
+
+

Non-Verbal Tactics for both

+
    +
  • Get in line of vision and catch their eye
  • +
  • Take a group photo to document what the potential offender looks like
  • +
  • Ask/Assess situation with a thumbs up/down
  • +
  • Wave to your friend to indicate you are leaving to get them to come with you
  • +
  • Text or call the Volunteer
  • +
  • Use a distraction to get the predator’s attention
  • +
  • Strike up a conversation with someone nearby to physically get closer
  • +
  • Walk towards the two, alone or with others, and engage in conversation
  • +
  • Make a show of picking up the phone to indicate you are alerting others
  • +
  • Physically pull the individual away
  • +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/circleOfTrust.php b/circleOfTrust.php new file mode 100644 index 0000000..ea685e0 --- /dev/null +++ b/circleOfTrust.php @@ -0,0 +1,75 @@ + + + + + FirstAide + + + + + +
+
+
+

Circle of Trust

+
+

My Trustees

+
+ +
+ + + +
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/commonalitiesOfSexualPredators.php b/commonalitiesOfSexualPredators.php new file mode 100644 index 0000000..286332c --- /dev/null +++ b/commonalitiesOfSexualPredators.php @@ -0,0 +1,108 @@ + + + + + FirstAide + + + + + +
+
+
+

Commonalities Of Sexual Predators

+
+
+ +
+ + + + + + + +
+

+ While cultural misunderstandings can contribute to increased risk, most sexual assaults are not the result of cross-cultural misinterpretations. They are the result of deliberate planning by the sexual predator. Ultimately, sexual assault is a crime of motive and opportunity. While you can never completely protect yourself from sexual assault, there are some things you can do to help reduce your risk of being assaulted. +

+
+
    +

    Characteristics of assaults:

    +
  • + Sexual predators often plan sexual assaults. +
      +
    • + Sexual assault is not an accident. Sexual predators know what they want to do, even if they have not already identified a specific target. Most of them have a plan in mind for how they will select and control someone, or they will seek out an area where a potential victim might be isolated and unable to get help. When we say planned to some extent it may not mean days or weeks in advance but also planned in the particular + moment. +
    • +
    +
  • +
  • + Sexual predators often watch for vulnerabilities and opportunities. +
      +
    • + Sexual predators look for cues to indicate they can dominate and control a potential victim. They look for signs indicating that someone would be unlikely or unable to resist. For instance, people who are unaware of their surroundings, alone or lost; someone who is intoxicated or in some way incapacitated. +
    • +
    +
  • +
  • + Sexual predators often test the boundaries of potential victims. +
      +
    • + Testing boundaries may involve inappropriate comments, unwanted touching or invading personal space. It is a way of measuring the amount of resistance a potential victim might offer. A person who offers little or no resistance to these advances might be seen as a suitable target. +
    • +
    +
  • +
+
+

Both in Peace Corps and worldwide, the majority of sexual assault have these similarities:

+
    +
  • Predators know the victim
  • +
  • Occur when the victim is isolated.
  • +
  • Occur at night.
  • +
+
+

Tactics used by sexual predators

+
    +
  • + Attempt to isolate their potential victim. They may target someone who is already alone. For example, walking alone, or they may try to get their target alone by offering a ride in their car. +
  • +
  • + Persuasion and confidence.This is the “Smooth Talker” who puts you at ease. They make you feel comfortable and relaxed so you are not aware of their true intent. They may try to persuade you to do something you feel uncomfortable about. They might promise that they won’t try anything with you or reassure you by saying “You can trust me.” +
  • + Pressure and guilt.Sexual predators may try to coerce you by pressuring you to go farther in a relationship than you are ready or willing to go. They may try to make you feel guilty if you do not give in to their advances. They might say “You are offending me culturally” or something similar to make Volunteers feel guilty. +
  • + Threats and intimidation. Sometimes the sexual predator threatens to physically harm the Volunteer or someone they care about. They might also threaten to blackmail the Volunteer unless they comply. +
  • +
  • + Force and violence.Force and Violence involves a direct physical attack to overpower a Volunteer. It is what we frequently see on TV and in movies…like when the assailant jumps out of the bushes with a knife and attacks an unsuspecting jogger. +
  • +
  • + Drugs, including alcohol + Description to be provided.... +
  • +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/copingWithUnwantedAttentionStrategies.php b/copingWithUnwantedAttentionStrategies.php new file mode 100644 index 0000000..1a84163 --- /dev/null +++ b/copingWithUnwantedAttentionStrategies.php @@ -0,0 +1,77 @@ + + + + + FirstAide + + + + +
+
+
+

Coping With Unwanted Attention Strategies

+
+
+ + + + + + +
+ All PCVs will deal with some type of unwanted attention at some point in their service. This occurs across all cultures. The reaction someone has in response is highly personal and depends on the type of unwanted attention. Reactions may range from a slight feeling of discomfort to anger to fearing for one’s safety. You do not have to “be nice” if you feel unsafe or even uncomfortable. Peace Corps staff are here to help and have been trained on how to help PCVs cope. It’s important to report unwanted attention that is severe in nature so you can get the help and support you deserve. +

+ There’s a difference between unwanted attention and stalking. Stalking and cyber-stalking are defined as repeated threatening behavior from a single person that causes a Volunteer to fear for his or her safety or suffer substantial emotional distress. Unwanted physical contact, such as grabbing or touching, is considered assault, not unwanted attention. +
+
    +
  • + Walk purposefully.Always walk with confidence and purpose. +
  • +
  • + Look assertive. Hold your head high, shoulders back and present yourself as a professional demanding respect. +
  • +
  • + Nod (to acknowledge) and keep on walking. Simply recognizing the person can help ward off unwanted attention. Many times an inappropriate comment is an effort to get attention.
  • +
  • + Dress appropriately. Keep in mind what is culturally appropriate. +
  • +
  • + Greetings.Sometimes a polite “Good Morning” can thwart a potential unwanted comment, but at other times it can escalate the situation. If this strategy does not work, try a different one. +
  • +
  • + Pretend that you heard something else. “I agree, it HAS been really great weather recently. Have a nice day, bye!” +
  • +
  • + Humor. Use humor to lighten the moment and solicit another response. For example, if you are told that you would make a good lover, reply that your spouse is sure to agree! Keep walking. This may not work with a persistent individual, so please keep trying different strategies when needed. +
  • +
  • + Be polite but firm. It is quite normal to stand your ground. “I am offended by your comment; please do not address me in that manner.” +
  • +
  • + Maintain your composure. Try to remain calm even if you feel upset. The converse is also true; try not to show hostility as this may provoke a confrontation. It is best to remove yourself from a situation if you feel that you are losing control. +
  • +
  • + Never say “next time.” Make no promises for another time, because you can be sure that the next time they see you, they will remind you of that promise. +
  • +
+
+
+
+ + + + \ No newline at end of file diff --git a/css files/AvenirNextLTPro-Regular.otf b/css files/AvenirNextLTPro-Regular.otf new file mode 100755 index 0000000..263ae4c Binary files /dev/null and b/css files/AvenirNextLTPro-Regular.otf differ diff --git a/css files/circle-of-trust.css b/css files/circle-of-trust.css new file mode 100644 index 0000000..007f504 --- /dev/null +++ b/css files/circle-of-trust.css @@ -0,0 +1,183 @@ +/* Created by Akanksha + Desc: Style for Circle of Trust + Used in following php files + editComrades + circleOfTrust +*/ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body { + background-color: #05d197; + margin: 0; +} +#bw-arrow { + margin-left: 0px; + margin-top: 200px; + position: absolute; +} +/* The Close Button in popup*/ +.close { + color: #ffffff; + float: right; + font-size: 28px; + font-weight: bold; +} + +.close:hover,.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; +} +/*Icons style present in circleOfTrust.php*/ +.icons-container .icons-row { + margin: 50px; +} + +.icons-container .icons-row img { + cursor: pointer; + margin:0 50px; +} + +#ic-edit{ + margin: 0 700px 0 0px; +} + +.inputs input { + background-color: #ffffff; + border: 3px solid #2db58f; + color: #2db58f; + font-size: 15px; + height: 100%; + margin: 10px; + padding: 10px; + width: 500px; +} + +.line { + color: white; + width: 80%; +} + +/*popup css*/ +.popup { + background-color: #2db58f; + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + display: none; /* Hidden by default */ + left: 0; + height: 100%; + overflow: auto; + padding-top: 100px; /* Location of the box */ + position: fixed; + top: 0; + width: 100%; + z-index: 1; /*On top of screen*/ +} +/*Style for popup when Help me clicked*/ +.popup-button { /*buttons in contact popup*/ + background-color: #2db58f; + border-width: 3px; + color: #ffffff; + cursor: pointer; + font-size: 20px; + font-weight: bold; + height: 70px; + margin-top: 20px; + padding: 20px; + text-align:center; + width: 100%; +} + +.popup-button:hover { + background-color: #02845f; +} + +.popup-content { + background-color: #2db58f; + border: 3px solid #ffffff; + margin: auto; + padding: 20px; + width: 30%; +} + +.small-button {/*submit button in editComrades.php*/ + background-color: #2db58f; + border-width: 3px; + border-color: #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + height: 50px; + margin-top: 20px; + text-align: center; + width: 30%; +} + +.small-button:hover { + background-color: #02845f; +} + +#table { + border-collapse:separate; + border-spacing:50px 50px; + height: 200px; + width: 100%; +} + +.text { + color: #ffffff; + font-weight: bold; +} + +.window { + height: 100%; + margin-left: 25%; + padding: 1px 16px; +} + +::-webkit-input-placeholder { /* WebKit, Blink, Edge */ + color: #2db58f; + font-size: 15px; +} + +:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #2db58f; + font-size: 15px; + opacity: 1; +} + +::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #2db58f; + font-size: 15px; + opacity: 1; +} + +:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: #2db58f; + font-size: 15px; +} + +/*custom scrollbar for the app*/ +::-webkit-scrollbar { + background-color: #f5f5f5; + width: 12px; +} + +::-webkit-scrollbar-track { + background-color: #f5f5f5; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #bdbdbd; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); +} + diff --git a/css files/gethelpnow-style.css b/css files/gethelpnow-style.css new file mode 100644 index 0000000..4ad1424 --- /dev/null +++ b/css files/gethelpnow-style.css @@ -0,0 +1,229 @@ +/* Created by Akanksha + Desc: Style for Get Help Now module and it's submodules + Use in the following php files + getHelpNow + getHelpNow2 + PCSaves + OfficeOfVictimAdvocacy + OfficeOfCivilRightsAndDiversity + OfficeOfInspectorGeneral + twilioSMS + twilioCall + */ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body { + background-color: #05d197; + margin: 0; +} + +.block { + background-color: #2db58f; + border: 3px solid #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + height: 100%; + margin-top: 30px; + padding: 20px; + width: 400px; +} + +#btn-table { + border-collapse: separate; + border-spacing: 50px 50px; + width: 100%; + height: 200px; +} + +.button { /*The big green buttons*/ + background-color: #2db58f; + border-width: 3px; + border-color: #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + font-weight: bold; + height: 150px; + margin-top: 30px; + text-align: center; + width: 100%; +} + +.button:hover { + background-color: #02845f; +} + +#bw-arrow { + margin-top: 250px; + position: absolute; +} + +/* The Close Button in popup */ +.close { + color: #ffffff; + float: right; + font-size: 28px; + font-weight: bold; +} + +.close:hover,.close:focus { + color: #000; + cursor: pointer; + text-decoration: none; +} + +#fw-arrow { + float: right; + display: inline-block; + margin-top: 0px; +} + +#input { + background-color: #2db58f; + border: 3px solid #ffffff; + color: #ffffff; + font-size: 20px; + height: 100%; + margin-top: 30px; + padding: 20px; + width: 400px; +} + +.line { + color: white; + width: 50%; +} + +#location{ + background-color: #2db58f; + border: 1px solid #ffffff; + color: #ffffff; +} + +/*popup css*/ +.popup { + background-color: #2db58f; + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + display: none; /* Hidden by default */ + left: 0; + height: 100%; + overflow: auto; + padding-top: 100px; /* Location of the box */ + position: fixed; + top: 0; + width: 100%; + z-index: 1; /*On top of screen*/ +} + +.popup-button { /*buttons in contact popup*/ + background-color: #2db58f; + border-width: 3px; + color: #ffffff; + cursor: pointer; + font-size: 20px; + height: 70px; + margin-top: 20px; + padding: 20px; + text-align:center; + width: 100%; +} + +.popup-button:hover { + background-color: #02845f; +} + +.popup-content { + background-color: #2db58f; + border: 3px solid #ffffff; + margin: auto; + padding: 20px; + width: 30%; +} + +.small-button {/*used in sub-modules present at getHelpNow2.php*/ + background-color: #2db58f; + border-width: 3px; + border-color: #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + height: 50px; + margin-top: 20px; + text-align: center; + width: 30%; +} + +.small-button:hover { + background-color: #02845f; +} + +#SMS-body { + background-color: #2db58f; + border: 3px solid #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + height: 200px; + margin-top: 30px; + padding: 20px; + width: 400px; +} + +.text { + color: #ffffff; + font-weight: bold; +} + +.window { + height: 100%; + margin-left: 25%; + padding: 1px 16px; +} + +::-webkit-input-placeholder { /* WebKit, Blink, Edge */ + color: #d3d3d3; + font-size: 15px; +} + +:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #d3d3d3; + font-size: 15px; + opacity: 1; +} + +::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #d3d3d3; + font-size: 15px; + opacity: 1; +} + +:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: #d3d3d3; + font-size: 15px; +} + +/*custom scrollbar for the app*/ +::-webkit-scrollbar { + background-color: #f5f5f5; + width: 12px; +} + +::-webkit-scrollbar-track { + background-color: #f5f5f5; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #bdbdbd; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); +} diff --git a/css files/index-style.css b/css files/index-style.css new file mode 100644 index 0000000..8307ae0 --- /dev/null +++ b/css files/index-style.css @@ -0,0 +1,46 @@ +/* Created by Akanksha + Desc: CSS for index page +*/ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body { + background-color: #05d197; + height: 240px; + left: 50%; + margin-left: -400px; + margin-top: -120px; + position: absolute; + top: 50%; + width: 800px; +} + +.title { + color: #ffffff; + font-size: 100px; + font-weight: bold; +} + +/*custom scrollbar for the app*/ +::-webkit-scrollbar { + background-color: #f5f5f5; + width: 12px; +} + +::-webkit-scrollbar-track { + border-radius: 10px; + background-color: #f5f5f5; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #bdbdbd; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); +} \ No newline at end of file diff --git a/css files/loginAndRegistration.css b/css files/loginAndRegistration.css new file mode 100644 index 0000000..7c621c6 --- /dev/null +++ b/css files/loginAndRegistration.css @@ -0,0 +1,147 @@ +/* DOCUMENT INFORMATION + - Created by : Akanksha + - Files : login.php, registration.php +*/ + +/* ===GLOBAL=== */ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body { + background-color: #05d197; +} + +/* ===HYPERLINKS=== */ + +a:link { + color: #ffffff; +} + +a:visited { + color: #ffffff; +} + +a:hover { + color: blue; +} + +/* ===BUTTONS=== */ + +.button { + background-color: #2db58f; + border: 3px #ffffff; + color: #ffffff; + font-size: 20px; + font-weight: bold; + height: 60px; + margin-top: 30px; + text-align: center; + width: 25%; +} + +.button:hover { + background-color: #02845f !important; +} + +/* Div style to show form in the center */ + +.div { + margin-top: 100px; +} + +.div-reg { + margin-top: 30px; +} + +/* ===INPUTS=== */ + +.input-box { + background-color: transparent; + border-bottom: solid #eeeeee 1px; + border-left: none; + border-right: none; + border-top: none; + color: #eeeeee; + outline: none; + padding: 3px 10px; +} + +.input-box:focus { + box-shadow: 0px 2px; +} + +/* ===HORIZONTAL LINE=== */ + +#line { + border-width: 2px; + color: #ffffff; + width: 50%; +} + +/* ===TABLE=== */ + +.tables { + border-collapse: separate; + border-spacing: 0 45px; +} + +/* ===TEXT=== */ + +.text { + color: #ffffff; + font-weight: bold; +} + +/* ===PLACEHOLDER=== */ + +/* WebKit, Blink, Edge */ +::-webkit-input-placeholder { + color: #d3d3d3; + font-size: 15px; +} + +/* Mozilla Firefox 4 to 18 */ +:-moz-placeholder { + color: #d3d3d3; + font-size: 15px; + opacity: 1; +} + +/* Mozilla Firefox 19+ */ +::-moz-placeholder { + color: #d3d3d3; + font-size: 15px; + opacity: 1; +} + +/* Internet Explorer 10-11 */ +:-ms-input-placeholder { + color: #d3d3d3; + font-size: 15px; +} + +/* ===SCROLLBAR=== */ + +/* WebKit, Blink, Edge */ +::-webkit-scrollbar { + background-color: #f5f5f5; + width: 12px; +} + +::-webkit-scrollbar-track { + border-radius: 10px; + background-color: #f5f5f5; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #bdbdbd; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); +} diff --git a/css files/menu-css.css b/css files/menu-css.css new file mode 100644 index 0000000..482add3 --- /dev/null +++ b/css files/menu-css.css @@ -0,0 +1,147 @@ +/* Created by Akanksha + Desc: CSS for the vertical accordian menu +*/ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +#accordian { + background: #ffd590; + color: white; + /*Some cool shadow and glow effect*/ + box-shadow: + 0 5px 15px 1px rgba(0, 0, 0, 0.6), + 0 0 200px 1px rgba(255, 255, 255, 0.5); + position: fixed; + height: 100% +} + +#accordian h2 a { + background: #ffd590; + background: linear-gradient(#ffd590, #ffd590); + color: #ffffff; + cursor: pointer; + font-size: 25px; + font-weight: bold; + line-height: 34px; +} + +#accordian h2 img { + float:left; + height: 30px; + margin-bottom:10px; + margin-left:45px; + margin-top:5px; + padding: 9px; + width: 30px; +} + +#accordian h3 { + background: #ffd590; + background: linear-gradient(#ffd590, #ffd590); + color: #ffffff; + cursor: pointer; + font-size: 15px; + font-weight: bold; + line-height: 34px; + padding: 0 10px; +} + +#accordian h3 a { + color: #ffffff; +} + +#accordian h3 a:hover { + background: #ffd590; + border-left: 5px solid orange; +} + +#accordian h3:hover { + text-shadow: 0 0 1px rgba(255, 255, 255, 0.7); +} + +#accordian li { + list-style-type: none; +} + +#accordian li.active .ul-menu { + display: block; +} + +#accordian .ul-menu .ul-menu li a { + color: white; + display: block; + font-size: 15px; + line-height: 27px; + padding: 0 15px; + text-decoration: none; + /*transition for smooth hover animation*/ + transition: all 0.15s; +} + +#accordian .ul-menu .ul-menu li a:hover { + background: #ffd590; + border-left: 5px solid orange; +} + +/*hide the non active LIs by default*/ +#accordian .ul-menu .ul-menu { + display: none; +} +/*CSS for list*/ +li a { + display: block; + color: #000; + padding: 8px 0 8px 16px; + text-decoration: none; +} + +li a.active { + background-color: #4caf50; + color: white; +} + +li a:hover:not(.active) { + background-color: #555; + color: white; +} + +li.last { + background-color: red; + float: none; + overflow: hidden; +} + +#line { + color: white; +} +/*CSS for ul of menu*/ +.ul-menu { + background-color: #fbc469; + height: 100%; + list-style-type: none; + margin: 0; + overflow: auto;/*enables scrolling if menu too long*/ + padding: 0; + width: 250px; +} + +.ul-menu .ul-menu li a { + color: white; + display: block; + font-size: 11px; + line-height: 27px; + padding: 0 15px; + text-decoration: none; + /*transition for smooth hover animation*/ + transition: all 0.15s; +} + +.ul-menu .ul-menu { + display: none; +} diff --git a/css files/progress-bar.css b/css files/progress-bar.css new file mode 100644 index 0000000..2580a8a --- /dev/null +++ b/css files/progress-bar.css @@ -0,0 +1,59 @@ +/* Created by Akanksha + CSS for progress bar +*/ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +#bar { + background-color: #fbc469; + height: 100%; + position: absolute; + width: 1%; +} + +body { + background-color: #05d197; + margin: 0; +} + +#line { + color: white; + width: 50%; +} + +#progress { + background-color: #ddd; + height: 30px; + margin-top: 200px; + position: relative; + width: 80%; +} + +.text { + color: #ffffff; + font-weight: bold; +} + +/*custom scrollbar for the app*/ +::-webkit-scrollbar { + background-color: #f5f5f5; + width: 12px; +} + +::-webkit-scrollbar-track { + background-color: #f5f5f5; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #bdbdbd; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); +} diff --git a/css files/safety-tools.css b/css files/safety-tools.css new file mode 100644 index 0000000..1f58296 --- /dev/null +++ b/css files/safety-tools.css @@ -0,0 +1,193 @@ +/* Created by Akanksha + Desc: CSS for all pages of safety tools + RADAR + Personal security startegies + Bystander Intervention + Coping with unwanted attention strategies + Commonalities of sexual predators + Safety tools basics + Safety plan worksheet +*/ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body { + background-color: #05d197; + margin: 0; +} + +.block { + background-color: #2db58f; + border: 3px solid #ffffff; + color: #ffffff; + cursor: grab; + font-size: 20px; + height: 300px; + padding: 20px; + width: 500px; +} + +#btn-table { + border-collapse:separate; + border-spacing:50px 50px; + height: 200px; + width: 100%; +} + +.button { /*The big green buttons*/ + background-color: #2db58f; + border-color: #ffffff; + border-width: 3px; + color: #ffffff; + cursor: pointer; + font-size: 20px; + font-weight: bold; + height: 150px; + margin-top: 30px; + text-align: center; + width: 100%; +} + +.button:hover { + background-color: #02845f; +} + +#bw-arrow {/*css for backward arrow*/ + margin-left: 0px; + margin-top: 250px; + position: absolute; +} + +.content { + background-color:#2db58f; + padding: 5px 10px; +} + +.dragscroll {/*for horizontally scrolled content*/ + cursor: grab; + cursor : -o-grab; + cursor : -moz-grab; + cursor : -webkit-grab; + margin-top: 5%; + overflow: auto; + overflow-x:hidden; +} + +#fw-arrow {/*the forward arrow*/ + margin-left: 1020px; + margin-top: 250px; + position: absolute; +} + +.greaterthan5-blocks-content {/*used for pages which have multiple blocks (>5)*/ + border-collapse:separate; + border-spacing:50px 50px; + width: 270%; +} + +.heading {/*used only in safety plan worksheet*/ + background-color: #05d197 ; + color: #ffffff; + cursor: pointer; + margin: 10px; + padding: 30px 20px; + position: relative; +} + +.line { + color: white; + width: 50%; +} + +p { + padding: 5px 0; +} + +.text { + color: #ffffff; +} + +.threeorfour-blocks-content {/*used for pages which have 3 or 4 blocks only*/ + border-collapse:separate; + border-spacing:50px 50px; + width: 150%; +} + +.two-blocks-content {/*used for pages which have 2 blocks only*/ + border-collapse:separate; + border-spacing:50px 50px; + height: 500px; + overflow:scroll; + table-layout:fixed; +} +/*ul CSS*/ +.ul-safetytools { + background-color: #2db58f; + color: #ffffff; + font-size: 20px; + width: 90%; + height: 80%; + overflow:scroll; + overflow-x: hidden; +} + +.ul-safetytools li{ + margin-top: 10px; +} + +.window { + height: 100%; + margin-left: 20%; + padding: 1px 16px; +} +.wrap { + margin: 0; + padding: 0; +} + +::-webkit-input-placeholder { /* WebKit, Blink, Edge */ + color: #d3d3d3; + font-size: 15px; +} + +:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #d3d3d3; + font-size: 15px; + opacity: 1; +} + +::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #d3d3d3; + font-size: 15px; + opacity: 1; +} + +:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: #d3d3d3; + font-size: 15px; +} + +/*custom scrollbar for the app*/ +::-webkit-scrollbar { + background-color: #f5f5f5; + width: 12px; +} + +::-webkit-scrollbar-track { + background-color: #f5f5f5; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #bdbdbd; + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); +} + diff --git a/css files/sweetalert.css b/css files/sweetalert.css new file mode 100644 index 0000000..19f7d65 --- /dev/null +++ b/css files/sweetalert.css @@ -0,0 +1,941 @@ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body.stop-scrolling { + height: 100%; + overflow: hidden; } + +.sweet-overlay { + background-color: black; + /* IE8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + /* IE8 */ + background-color: rgba(0, 0, 0, 0.4); + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: none; + z-index: 10000; } + +.sweet-alert { + background-color: white; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + width: 478px; + padding: 17px; + border-radius: 5px; + text-align: center; + position: fixed; + left: 50%; + top: 50%; + margin-left: -256px; + margin-top: -200px; + overflow: hidden; + display: none; + z-index: 99999; } + @media all and (max-width: 540px) { + .sweet-alert { + width: auto; + margin-left: 0; + margin-right: 0; + left: 15px; + right: 15px; } } + .sweet-alert h2 { + color: #575757; + font-size: 30px; + text-align: center; + font-weight: 600; + text-transform: none; + position: relative; + margin: 25px 0; + padding: 0; + line-height: 40px; + display: block; } + .sweet-alert p { + color: #797979; + font-size: 16px; + text-align: center; + font-weight: 300; + position: relative; + text-align: inherit; + float: none; + margin: 0; + padding: 0; + line-height: normal; } + .sweet-alert fieldset { + border: none; + position: relative; } + .sweet-alert .sa-error-container { + background-color: #f1f1f1; + margin-left: -17px; + margin-right: -17px; + overflow: hidden; + padding: 0 10px; + max-height: 0; + webkit-transition: padding 0.15s, max-height 0.15s; + transition: padding 0.15s, max-height 0.15s; } + .sweet-alert .sa-error-container.show { + padding: 10px 0; + max-height: 100px; + webkit-transition: padding 0.2s, max-height 0.2s; + transition: padding 0.25s, max-height 0.25s; } + .sweet-alert .sa-error-container .icon { + display: inline-block; + width: 24px; + height: 24px; + border-radius: 50%; + background-color: #ea7d7d; + color: white; + line-height: 24px; + text-align: center; + margin-right: 3px; } + .sweet-alert .sa-error-container p { + display: inline-block; } + .sweet-alert .sa-input-error { + position: absolute; + top: 29px; + right: 26px; + width: 20px; + height: 20px; + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; + -webkit-transition: all 0.1s; + transition: all 0.1s; } + .sweet-alert .sa-input-error::before, .sweet-alert .sa-input-error::after { + content: ""; + width: 20px; + height: 6px; + background-color: #f06e57; + border-radius: 3px; + position: absolute; + top: 50%; + margin-top: -4px; + left: 50%; + margin-left: -9px; } + .sweet-alert .sa-input-error::before { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); } + .sweet-alert .sa-input-error::after { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); } + .sweet-alert .sa-input-error.show { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); } + .sweet-alert input { + width: 100%; + box-sizing: border-box; + border-radius: 3px; + border: 1px solid #d7d7d7; + height: 43px; + margin-top: 10px; + margin-bottom: 17px; + font-size: 18px; + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.06); + padding: 0 12px; + display: none; + -webkit-transition: all 0.3s; + transition: all 0.3s; } + .sweet-alert input:focus { + outline: none; + box-shadow: 0px 0px 3px #c4e6f5; + border: 1px solid #b4dbed; } + .sweet-alert input:focus::-moz-placeholder { + transition: opacity 0.3s 0.03s ease; + opacity: 0.5; } + .sweet-alert input:focus:-ms-input-placeholder { + transition: opacity 0.3s 0.03s ease; + opacity: 0.5; } + .sweet-alert input:focus::-webkit-input-placeholder { + transition: opacity 0.3s 0.03s ease; + opacity: 0.5; } + .sweet-alert input::-moz-placeholder { + color: #bdbdbd; } + .sweet-alert input:-ms-input-placeholder { + color: #bdbdbd; } + .sweet-alert input::-webkit-input-placeholder { + color: #bdbdbd; } + .sweet-alert.show-input input { + display: block; } + .sweet-alert .sa-confirm-button-container { + display: inline-block; + position: relative; } + .sweet-alert .la-ball-fall { + position: absolute; + left: 50%; + top: 50%; + margin-left: -27px; + margin-top: 4px; + opacity: 0; + visibility: hidden; } + .sweet-alert button { + background-color: #05d197; + color: white; + border: none; + box-shadow: none; + font-size: 17px; + font-weight: 500; + -webkit-border-radius: 4px; + border-radius: 5px; + padding: 10px 32px; + margin: 26px 5px 0 5px; + cursor: pointer; } + .sweet-alert button:focus { + outline: none; + box-shadow: 0 0 2px rgba(128, 179, 235, 0.5), inset 0 0 0 1px rgba(0, 0, 0, 0.05); } + .sweet-alert button:hover { + background-color: #7ecff4; } + .sweet-alert button:active { + background-color: #5dc2f1; } + .sweet-alert button.cancel { + background-color: #C1C1C1; } + .sweet-alert button.cancel:hover { + background-color: #b9b9b9; } + .sweet-alert button.cancel:active { + background-color: #a8a8a8; } + .sweet-alert button.cancel:focus { + box-shadow: rgba(197, 205, 211, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.0470588) 0px 0px 0px 1px inset !important; } + .sweet-alert button[disabled] { + opacity: .6; + cursor: default; } + .sweet-alert button.confirm[disabled] { + color: transparent; } + .sweet-alert button.confirm[disabled] ~ .la-ball-fall { + opacity: 1; + visibility: visible; + transition-delay: 0s; } + .sweet-alert button::-moz-focus-inner { + border: 0; } + .sweet-alert[data-has-cancel-button=false] button { + box-shadow: none !important; } + .sweet-alert[data-has-confirm-button=false][data-has-cancel-button=false] { + padding-bottom: 40px; } + .sweet-alert .sa-icon { + width: 80px; + height: 80px; + border: 4px solid gray; + -webkit-border-radius: 40px; + border-radius: 40px; + border-radius: 50%; + margin: 20px auto; + padding: 0; + position: relative; + box-sizing: content-box; } + .sweet-alert .sa-icon.sa-error { + border-color: #F27474; } + .sweet-alert .sa-icon.sa-error .sa-x-mark { + position: relative; + display: block; } + .sweet-alert .sa-icon.sa-error .sa-line { + position: absolute; + height: 5px; + width: 47px; + background-color: #F27474; + display: block; + top: 37px; + border-radius: 2px; } + .sweet-alert .sa-icon.sa-error .sa-line.sa-left { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + left: 17px; } + .sweet-alert .sa-icon.sa-error .sa-line.sa-right { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + right: 16px; } + .sweet-alert .sa-icon.sa-warning { + border-color: #F8BB86; } + .sweet-alert .sa-icon.sa-warning .sa-body { + position: absolute; + width: 5px; + height: 47px; + left: 50%; + top: 10px; + -webkit-border-radius: 2px; + border-radius: 2px; + margin-left: -2px; + background-color: #F8BB86; } + .sweet-alert .sa-icon.sa-warning .sa-dot { + position: absolute; + width: 7px; + height: 7px; + -webkit-border-radius: 50%; + border-radius: 50%; + margin-left: -3px; + left: 50%; + bottom: 10px; + background-color: #F8BB86; } + .sweet-alert .sa-icon.sa-info { + border-color: #C9DAE1; } + .sweet-alert .sa-icon.sa-info::before { + content: ""; + position: absolute; + width: 5px; + height: 29px; + left: 50%; + bottom: 17px; + border-radius: 2px; + margin-left: -2px; + background-color: #C9DAE1; } + .sweet-alert .sa-icon.sa-info::after { + content: ""; + position: absolute; + width: 7px; + height: 7px; + border-radius: 50%; + margin-left: -3px; + top: 19px; + background-color: #C9DAE1; } + .sweet-alert .sa-icon.sa-success { + border-color: #A5DC86; } + .sweet-alert .sa-icon.sa-success::before, .sweet-alert .sa-icon.sa-success::after { + content: ''; + -webkit-border-radius: 40px; + border-radius: 40px; + border-radius: 50%; + position: absolute; + width: 60px; + height: 120px; + background: white; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); } + .sweet-alert .sa-icon.sa-success::before { + -webkit-border-radius: 120px 0 0 120px; + border-radius: 120px 0 0 120px; + top: -7px; + left: -33px; + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + -webkit-transform-origin: 60px 60px; + transform-origin: 60px 60px; } + .sweet-alert .sa-icon.sa-success::after { + -webkit-border-radius: 0 120px 120px 0; + border-radius: 0 120px 120px 0; + top: -11px; + left: 30px; + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + -webkit-transform-origin: 0px 60px; + transform-origin: 0px 60px; } + .sweet-alert .sa-icon.sa-success .sa-placeholder { + width: 80px; + height: 80px; + border: 4px solid rgba(165, 220, 134, 0.2); + -webkit-border-radius: 40px; + border-radius: 40px; + border-radius: 50%; + box-sizing: content-box; + position: absolute; + left: -4px; + top: -4px; + z-index: 2; } + .sweet-alert .sa-icon.sa-success .sa-fix { + width: 5px; + height: 90px; + background-color: white; + position: absolute; + left: 28px; + top: 8px; + z-index: 1; + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); } + .sweet-alert .sa-icon.sa-success .sa-line { + height: 5px; + background-color: #A5DC86; + display: block; + border-radius: 2px; + position: absolute; + z-index: 2; } + .sweet-alert .sa-icon.sa-success .sa-line.sa-tip { + width: 25px; + left: 14px; + top: 46px; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); } + .sweet-alert .sa-icon.sa-success .sa-line.sa-long { + width: 47px; + right: 8px; + top: 38px; + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); } + .sweet-alert .sa-icon.sa-custom { + background-size: contain; + border-radius: 0; + border: none; + background-position: center center; + background-repeat: no-repeat; } + +/* + * Animations + */ +@-webkit-keyframes showSweetAlert { + 0% { + transform: scale(0.7); + -webkit-transform: scale(0.7); } + 45% { + transform: scale(1.05); + -webkit-transform: scale(1.05); } + 80% { + transform: scale(0.95); + -webkit-transform: scale(0.95); } + 100% { + transform: scale(1); + -webkit-transform: scale(1); } } + +@keyframes showSweetAlert { + 0% { + transform: scale(0.7); + -webkit-transform: scale(0.7); } + 45% { + transform: scale(1.05); + -webkit-transform: scale(1.05); } + 80% { + transform: scale(0.95); + -webkit-transform: scale(0.95); } + 100% { + transform: scale(1); + -webkit-transform: scale(1); } } + +@-webkit-keyframes hideSweetAlert { + 0% { + transform: scale(1); + -webkit-transform: scale(1); } + 100% { + transform: scale(0.5); + -webkit-transform: scale(0.5); } } + +@keyframes hideSweetAlert { + 0% { + transform: scale(1); + -webkit-transform: scale(1); } + 100% { + transform: scale(0.5); + -webkit-transform: scale(0.5); } } + +@-webkit-keyframes slideFromTop { + 0% { + top: 0%; } + 100% { + top: 50%; } } + +@keyframes slideFromTop { + 0% { + top: 0%; } + 100% { + top: 50%; } } + +@-webkit-keyframes slideToTop { + 0% { + top: 50%; } + 100% { + top: 0%; } } + +@keyframes slideToTop { + 0% { + top: 50%; } + 100% { + top: 0%; } } + +@-webkit-keyframes slideFromBottom { + 0% { + top: 70%; } + 100% { + top: 50%; } } + +@keyframes slideFromBottom { + 0% { + top: 70%; } + 100% { + top: 50%; } } + +@-webkit-keyframes slideToBottom { + 0% { + top: 50%; } + 100% { + top: 70%; } } + +@keyframes slideToBottom { + 0% { + top: 50%; } + 100% { + top: 70%; } } + +.showSweetAlert[data-animation=pop] { + -webkit-animation: showSweetAlert 0.3s; + animation: showSweetAlert 0.3s; } + +.showSweetAlert[data-animation=none] { + -webkit-animation: none; + animation: none; } + +.showSweetAlert[data-animation=slide-from-top] { + -webkit-animation: slideFromTop 0.3s; + animation: slideFromTop 0.3s; } + +.showSweetAlert[data-animation=slide-from-bottom] { + -webkit-animation: slideFromBottom 0.3s; + animation: slideFromBottom 0.3s; } + +.hideSweetAlert[data-animation=pop] { + -webkit-animation: hideSweetAlert 0.2s; + animation: hideSweetAlert 0.2s; } + +.hideSweetAlert[data-animation=none] { + -webkit-animation: none; + animation: none; } + +.hideSweetAlert[data-animation=slide-from-top] { + -webkit-animation: slideToTop 0.4s; + animation: slideToTop 0.4s; } + +.hideSweetAlert[data-animation=slide-from-bottom] { + -webkit-animation: slideToBottom 0.3s; + animation: slideToBottom 0.3s; } + +@-webkit-keyframes animateSuccessTip { + 0% { + width: 0; + left: 1px; + top: 19px; } + 54% { + width: 0; + left: 1px; + top: 19px; } + 70% { + width: 50px; + left: -8px; + top: 37px; } + 84% { + width: 17px; + left: 21px; + top: 48px; } + 100% { + width: 25px; + left: 14px; + top: 45px; } } + +@keyframes animateSuccessTip { + 0% { + width: 0; + left: 1px; + top: 19px; } + 54% { + width: 0; + left: 1px; + top: 19px; } + 70% { + width: 50px; + left: -8px; + top: 37px; } + 84% { + width: 17px; + left: 21px; + top: 48px; } + 100% { + width: 25px; + left: 14px; + top: 45px; } } + +@-webkit-keyframes animateSuccessLong { + 0% { + width: 0; + right: 46px; + top: 54px; } + 65% { + width: 0; + right: 46px; + top: 54px; } + 84% { + width: 55px; + right: 0px; + top: 35px; } + 100% { + width: 47px; + right: 8px; + top: 38px; } } + +@keyframes animateSuccessLong { + 0% { + width: 0; + right: 46px; + top: 54px; } + 65% { + width: 0; + right: 46px; + top: 54px; } + 84% { + width: 55px; + right: 0px; + top: 35px; } + 100% { + width: 47px; + right: 8px; + top: 38px; } } + +@-webkit-keyframes rotatePlaceholder { + 0% { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); } + 5% { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); } + 12% { + transform: rotate(-405deg); + -webkit-transform: rotate(-405deg); } + 100% { + transform: rotate(-405deg); + -webkit-transform: rotate(-405deg); } } + +@keyframes rotatePlaceholder { + 0% { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); } + 5% { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); } + 12% { + transform: rotate(-405deg); + -webkit-transform: rotate(-405deg); } + 100% { + transform: rotate(-405deg); + -webkit-transform: rotate(-405deg); } } + +.animateSuccessTip { + -webkit-animation: animateSuccessTip 0.75s; + animation: animateSuccessTip 0.75s; } + +.animateSuccessLong { + -webkit-animation: animateSuccessLong 0.75s; + animation: animateSuccessLong 0.75s; } + +.sa-icon.sa-success.animate::after { + -webkit-animation: rotatePlaceholder 4.25s ease-in; + animation: rotatePlaceholder 4.25s ease-in; } + +@-webkit-keyframes animateErrorIcon { + 0% { + transform: rotateX(100deg); + -webkit-transform: rotateX(100deg); + opacity: 0; } + 100% { + transform: rotateX(0deg); + -webkit-transform: rotateX(0deg); + opacity: 1; } } + +@keyframes animateErrorIcon { + 0% { + transform: rotateX(100deg); + -webkit-transform: rotateX(100deg); + opacity: 0; } + 100% { + transform: rotateX(0deg); + -webkit-transform: rotateX(0deg); + opacity: 1; } } + +.animateErrorIcon { + -webkit-animation: animateErrorIcon 0.5s; + animation: animateErrorIcon 0.5s; } + +@-webkit-keyframes animateXMark { + 0% { + transform: scale(0.4); + -webkit-transform: scale(0.4); + margin-top: 26px; + opacity: 0; } + 50% { + transform: scale(0.4); + -webkit-transform: scale(0.4); + margin-top: 26px; + opacity: 0; } + 80% { + transform: scale(1.15); + -webkit-transform: scale(1.15); + margin-top: -6px; } + 100% { + transform: scale(1); + -webkit-transform: scale(1); + margin-top: 0; + opacity: 1; } } + +@keyframes animateXMark { + 0% { + transform: scale(0.4); + -webkit-transform: scale(0.4); + margin-top: 26px; + opacity: 0; } + 50% { + transform: scale(0.4); + -webkit-transform: scale(0.4); + margin-top: 26px; + opacity: 0; } + 80% { + transform: scale(1.15); + -webkit-transform: scale(1.15); + margin-top: -6px; } + 100% { + transform: scale(1); + -webkit-transform: scale(1); + margin-top: 0; + opacity: 1; } } + +.animateXMark { + -webkit-animation: animateXMark 0.5s; + animation: animateXMark 0.5s; } + +@-webkit-keyframes pulseWarning { + 0% { + border-color: #F8D486; } + 100% { + border-color: #F8BB86; } } + +@keyframes pulseWarning { + 0% { + border-color: #F8D486; } + 100% { + border-color: #F8BB86; } } + +.pulseWarning { + -webkit-animation: pulseWarning 0.75s infinite alternate; + animation: pulseWarning 0.75s infinite alternate; } + +@-webkit-keyframes pulseWarningIns { + 0% { + background-color: #F8D486; } + 100% { + background-color: #F8BB86; } } + +@keyframes pulseWarningIns { + 0% { + background-color: #F8D486; } + 100% { + background-color: #F8BB86; } } + +.pulseWarningIns { + -webkit-animation: pulseWarningIns 0.75s infinite alternate; + animation: pulseWarningIns 0.75s infinite alternate; } + +@-webkit-keyframes rotate-loading { + 0% { + transform: rotate(0deg); } + 100% { + transform: rotate(360deg); } } + +@keyframes rotate-loading { + 0% { + transform: rotate(0deg); } + 100% { + transform: rotate(360deg); } } + +/* Internet Explorer 9 has some special quirks that are fixed here */ +/* The icons are not animated. */ +/* This file is automatically merged into sweet-alert.min.js through Gulp */ +/* Error icon */ +.sweet-alert .sa-icon.sa-error .sa-line.sa-left { + -ms-transform: rotate(45deg) \9; } + +.sweet-alert .sa-icon.sa-error .sa-line.sa-right { + -ms-transform: rotate(-45deg) \9; } + +/* Success icon */ +.sweet-alert .sa-icon.sa-success { + border-color: transparent\9; } + +.sweet-alert .sa-icon.sa-success .sa-line.sa-tip { + -ms-transform: rotate(45deg) \9; } + +.sweet-alert .sa-icon.sa-success .sa-line.sa-long { + -ms-transform: rotate(-45deg) \9; } + +/*! + * Load Awesome v1.1.0 (http://github.danielcardoso.net/load-awesome/) + * Copyright 2015 Daniel Cardoso <@DanielCardoso> + * Licensed under MIT + */ +.la-ball-fall, +.la-ball-fall > div { + position: relative; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.la-ball-fall { + display: block; + font-size: 0; + color: #fff; } + +.la-ball-fall.la-dark { + color: #333; } + +.la-ball-fall > div { + display: inline-block; + float: none; + background-color: currentColor; + border: 0 solid currentColor; } + +.la-ball-fall { + width: 54px; + height: 18px; } + +.la-ball-fall > div { + width: 10px; + height: 10px; + margin: 4px; + border-radius: 100%; + opacity: 0; + -webkit-animation: ball-fall 1s ease-in-out infinite; + -moz-animation: ball-fall 1s ease-in-out infinite; + -o-animation: ball-fall 1s ease-in-out infinite; + animation: ball-fall 1s ease-in-out infinite; } + +.la-ball-fall > div:nth-child(1) { + -webkit-animation-delay: -200ms; + -moz-animation-delay: -200ms; + -o-animation-delay: -200ms; + animation-delay: -200ms; } + +.la-ball-fall > div:nth-child(2) { + -webkit-animation-delay: -100ms; + -moz-animation-delay: -100ms; + -o-animation-delay: -100ms; + animation-delay: -100ms; } + +.la-ball-fall > div:nth-child(3) { + -webkit-animation-delay: 0ms; + -moz-animation-delay: 0ms; + -o-animation-delay: 0ms; + animation-delay: 0ms; } + +.la-ball-fall.la-sm { + width: 26px; + height: 8px; } + +.la-ball-fall.la-sm > div { + width: 4px; + height: 4px; + margin: 2px; } + +.la-ball-fall.la-2x { + width: 108px; + height: 36px; } + +.la-ball-fall.la-2x > div { + width: 20px; + height: 20px; + margin: 8px; } + +.la-ball-fall.la-3x { + width: 162px; + height: 54px; } + +.la-ball-fall.la-3x > div { + width: 30px; + height: 30px; + margin: 12px; } + +/* + * Animation + */ +@-webkit-keyframes ball-fall { + 0% { + opacity: 0; + -webkit-transform: translateY(-145%); + transform: translateY(-145%); } + 10% { + opacity: .5; } + 20% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); } + 80% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); } + 90% { + opacity: .5; } + 100% { + opacity: 0; + -webkit-transform: translateY(145%); + transform: translateY(145%); } } + +@-moz-keyframes ball-fall { + 0% { + opacity: 0; + -moz-transform: translateY(-145%); + transform: translateY(-145%); } + 10% { + opacity: .5; } + 20% { + opacity: 1; + -moz-transform: translateY(0); + transform: translateY(0); } + 80% { + opacity: 1; + -moz-transform: translateY(0); + transform: translateY(0); } + 90% { + opacity: .5; } + 100% { + opacity: 0; + -moz-transform: translateY(145%); + transform: translateY(145%); } } + +@-o-keyframes ball-fall { + 0% { + opacity: 0; + -o-transform: translateY(-145%); + transform: translateY(-145%); } + 10% { + opacity: .5; } + 20% { + opacity: 1; + -o-transform: translateY(0); + transform: translateY(0); } + 80% { + opacity: 1; + -o-transform: translateY(0); + transform: translateY(0); } + 90% { + opacity: .5; } + 100% { + opacity: 0; + -o-transform: translateY(145%); + transform: translateY(145%); } } + +@keyframes ball-fall { + 0% { + opacity: 0; + -webkit-transform: translateY(-145%); + -moz-transform: translateY(-145%); + -o-transform: translateY(-145%); + transform: translateY(-145%); } + 10% { + opacity: .5; } + 20% { + opacity: 1; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + transform: translateY(0); } + 80% { + opacity: 1; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + transform: translateY(0); } + 90% { + opacity: .5; } + 100% { + opacity: 0; + -webkit-transform: translateY(145%); + -moz-transform: translateY(145%); + -o-transform: translateY(145%); + transform: translateY(145%); } } diff --git a/css files/welcome-style.css b/css files/welcome-style.css new file mode 100644 index 0000000..11f0bad --- /dev/null +++ b/css files/welcome-style.css @@ -0,0 +1,95 @@ +/*Created by Akanksha + Desc: CSS for welcome.php +*/ +@font-face { + font-family: "AvenirNext"; + src: url("AvenirNextLTPro-Regular.otf"); +} + +* { + font-family: "AvenirNext"; +} + +body { + background-color: #05d197; + margin: 0; +} + +.button { /*The green buttons css*/ + background-color: #2db58f; + border-width: 3px; + border-color: #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + font-weight: bold; + height: 150px; + margin-top: 30px; + text-align: center; + width: 100%; +} + +.button:hover { + background-color: #02845f; +} + +.button-org {/*Orange buttons css*/ + background-color: #ffd590; + border-width: 3px; + border-color: #ffffff; + color: #ffffff; + cursor: pointer; + font-size: 20px; + font-weight: bold; + height: 150px; + margin-top: 30px; + text-align: center; + width: 100%; +} + +.button-org:hover { + background-color: #ffffff; + color: #ffd590; +} + +.line { + color: white; + width: 50%; +} + +#buttons-table {/*table containing buttons*/ + border-collapse:separate; + border-spacing:50px 50px; + width: 100%; + height: 200px; +} + +.text { + color: #ffffff; + font-weight: bold; +} + +.window { + height: 100%; + margin-left: 25%; + padding: 1px 16px; +} + +/*custom scrollbar for the app*/ +::-webkit-scrollbar { + width: 12px; + background-color: #f5f5f5; +} + +::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + border-radius: 10px; + background-color: #f5f5f5; +} + +::-webkit-scrollbar-thumb { + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); + background-color: #bdbdbd; +} + diff --git a/database/pcsa_web.sql b/database/pcsa_web.sql new file mode 100644 index 0000000..e0bc08c --- /dev/null +++ b/database/pcsa_web.sql @@ -0,0 +1,99 @@ +-- phpMyAdmin SQL Dump +-- version 4.5.1 +-- http://www.phpmyadmin.net +-- +-- Host: 127.0.0.1 +-- Generation Time: Aug 09, 2016 at 02:44 PM +-- Server version: 10.1.10-MariaDB +-- PHP Version: 5.6.19 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `pcsa_web` +-- + +DELIMITER $$ +-- +-- Procedures +-- +CREATE DEFINER=`root`@`localhost` PROCEDURE `addcomrade` (IN `id` INT(1), IN `mail` VARCHAR(100)) NO SQL +INSERT INTO comrade(comradeid,email)VALUES(id,mail)$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `dupemail` (IN `mail` VARCHAR(100)) SELECT *from user where email = mail$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `getcomradenum` (IN `inemail` VARCHAR(100), IN `incomradeid` INT(1)) select phonenumber from comrade where email = inemail and comradeid = incomradeid$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `login` (IN `pass` VARCHAR(100), IN `mail` VARCHAR(100)) SELECT * from user where password = pass AND email = mail$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `registration` (IN `mail` VARCHAR(100), IN `uname` VARCHAR(100), IN `pass` VARCHAR(100), IN `country` VARCHAR(100)) INSERT INTO user VALUES(mail,uname,pass,country)$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `updatecomrade` (IN `id` INT(1), IN `mail` VARCHAR(100), IN `phno` VARCHAR(100)) NO SQL +UPDATE comrade SET phonenumber = phno where email = mail and comradeid = id$$ + +DELIMITER ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `comrade` +-- + +CREATE TABLE `comrade` ( + `comradeid` int(1) NOT NULL, + `email` varchar(100) NOT NULL, + `phonenumber` varchar(100) DEFAULT NULL, + `comrade_email` varchar(100) CHARACTER SET latin1 DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `user` +-- + +CREATE TABLE `user` ( + `email` varchar(100) NOT NULL, + `username` varchar(100) CHARACTER SET latin1 NOT NULL, + `password` varchar(100) CHARACTER SET latin1 NOT NULL, + `host_country` varchar(100) CHARACTER SET latin1 NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `comrade` +-- +ALTER TABLE `comrade` + ADD PRIMARY KEY (`comradeid`,`email`), + ADD UNIQUE KEY `comrade_email` (`comrade_email`), + ADD KEY `email` (`email`); + +-- +-- Indexes for table `user` +-- +ALTER TABLE `user` + ADD PRIMARY KEY (`email`); + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `comrade` +-- +ALTER TABLE `comrade` + ADD CONSTRAINT `comrade_ibfk_1` FOREIGN KEY (`email`) REFERENCES `user` (`email`); + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/dbconnect.php b/dbconnect.php new file mode 100644 index 0000000..8442bf6 --- /dev/null +++ b/dbconnect.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/editComrades.php b/editComrades.php new file mode 100644 index 0000000..4cde153 --- /dev/null +++ b/editComrades.php @@ -0,0 +1,130 @@ + + + + + FirstAide + + + + +
+ + + +
+
+
+

Circle of Trust

+
+

My Trustees

+

Add the Comrade Numbers here

+
+ +
+ + + +
+ + 0) + { + echo ""; + } + else if(count(array_unique($formphnos))salert('Duplicate Phone Number','Please enter again!','error');"; + } + else + { + for($i=1;$i<=6;$i++) + { + $id = 'comrade'.$i; + $phno = $_POST[$id]; + + if(empty($phno)) + { + $query = mysqli_query($connection,"CALL updatecomrade($i,'$useremail',NULL)"); + } + else + { + $query = mysqli_query($connection,"CALL updatecomrade($i,'$useremail','$phno')"); + } + $connection -> next_result(); + } + + //Messages to inform state of execution + $nochange = 0; + $empty = 0; + + for($i=1;$i<=6;$i++) + { + $id = 'comrade'.$i; + $newphno = $_POST[$id]; + + if($newphno==$dbphnos[$i]) + $nochange++; + if($newphno==NULL) + $empty++; + } + //Possible messages after execution + if($empty==6) + echo ""; + else if($nochange==6) + echo ""; + else + echo ""; + + } + + } + mysqli_close($connection); + include('loadComradeNumbers.php'); //load phonenumbers again after updation +?> + +
+ + + + + + +
+ +
+
+ + + + diff --git a/getHelpNow.php b/getHelpNow.php new file mode 100644 index 0000000..bb05fbf --- /dev/null +++ b/getHelpNow.php @@ -0,0 +1,107 @@ + + + + + FirstAide + + + + +
+
+
+

Get Help Now

+
+ + + + + + + + +
Change Location: + +

This information is for Syria (current post)

+
+ + + + + + + + + + + +
+ + + +
+ + + + + +
+ + + + + + + + + + +
+
+ + + + + + + + + diff --git a/getHelpNow2.php b/getHelpNow2.php new file mode 100644 index 0000000..3a1a151 --- /dev/null +++ b/getHelpNow2.php @@ -0,0 +1,60 @@ + + + + + FirstAide + + + + +
+
+ +
+

Get Help Now

+
+
+ +
+ + + +
+ +
+ + + + + + + + + +
+ + + +
+ + + +
+
+
+
+ + \ No newline at end of file diff --git a/groupsms.php b/groupsms.php new file mode 100644 index 0000000..f072cfd --- /dev/null +++ b/groupsms.php @@ -0,0 +1,66 @@ + array( + CURLOPT_SSL_VERIFYPEER => false + ))); + + //instantiate a new Twilio Rest Client + $client = new Services_Twilio($AccountSid, $AuthToken, '2010-04-01', $http); + + //Loop over all comrades. $number is a phone number above, and + // $name is the name next to it + try { + $sent = 0; + foreach ($toNos as $number) { + + $sms = $client->account->messages->sendMessage( + // Change the 'From' number below to be a valid Twilio number + // that you've purchased, or the (deprecated) Sandbox number + "", + // the number we are sending to - Any phone number + $number, + // the sms body + $msg + ); + + $sent = $sent + 1; + } + echo $sent;//count of messages successfull is sent to circleOfTrustMessage.js + } + catch(\Services_Twilio_RestException $e){ + $error = $e->getMessage(); + echo $error; + } + + /*do not close php using ?> here*/ + + \ No newline at end of file diff --git a/images/PClogoWhite.png b/images/PClogoWhite.png new file mode 100644 index 0000000..0686c08 Binary files /dev/null and b/images/PClogoWhite.png differ diff --git a/images/bw-arrow.png b/images/bw-arrow.png new file mode 100644 index 0000000..2d91e90 Binary files /dev/null and b/images/bw-arrow.png differ diff --git a/images/close-button.png b/images/close-button.png new file mode 100644 index 0000000..76e7f26 Binary files /dev/null and b/images/close-button.png differ diff --git a/images/drag_hand.png b/images/drag_hand.png new file mode 100644 index 0000000..8e3f08d Binary files /dev/null and b/images/drag_hand.png differ diff --git a/images/fw-arrow.png b/images/fw-arrow.png new file mode 100644 index 0000000..7ef8c41 Binary files /dev/null and b/images/fw-arrow.png differ diff --git a/images/ic_comrade.png b/images/ic_comrade.png new file mode 100644 index 0000000..e087958 Binary files /dev/null and b/images/ic_comrade.png differ diff --git a/images/ic_edit_button.png b/images/ic_edit_button.png new file mode 100644 index 0000000..c87777f Binary files /dev/null and b/images/ic_edit_button.png differ diff --git a/images/ic_help_me.png b/images/ic_help_me.png new file mode 100644 index 0000000..366c060 Binary files /dev/null and b/images/ic_help_me.png differ diff --git a/images/mail.png b/images/mail.png new file mode 100644 index 0000000..e30392b Binary files /dev/null and b/images/mail.png differ diff --git a/images/phone.png b/images/phone.png new file mode 100644 index 0000000..ba0716f Binary files /dev/null and b/images/phone.png differ diff --git a/images/secure.png b/images/secure.png new file mode 100644 index 0000000..7598567 Binary files /dev/null and b/images/secure.png differ diff --git a/images/settings.png b/images/settings.png new file mode 100644 index 0000000..d9ae232 Binary files /dev/null and b/images/settings.png differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..be6260e --- /dev/null +++ b/index.php @@ -0,0 +1,18 @@ + + + + + FirstAide + + + +
+ +

First Aide

+
+ + + \ No newline at end of file diff --git a/javascripts/PCMOpopup.js b/javascripts/PCMOpopup.js new file mode 100644 index 0000000..80108cf --- /dev/null +++ b/javascripts/PCMOpopup.js @@ -0,0 +1,24 @@ +/*Created by Akanksha + Desc: Popup js for ContactPCMO +*/ +function openPCMO() +{ + var popup = document.getElementById('popup-PCMO'); + var span = document.getElementById("close-PCMO"); + + //open popup on click of button + popup.style.display = "block"; + + // When the user clicks on (x), close the modal + span.onclick = function() { + popup.style.display = "none"; + } + + // When the user clicks anywhere outside of the modal, close it + window.onclick = function(event) { + if (event.target == popup) { + popup.style.display = "none"; + } + } + +} \ No newline at end of file diff --git a/javascripts/SARLpopup.js b/javascripts/SARLpopup.js new file mode 100644 index 0000000..a991c98 --- /dev/null +++ b/javascripts/SARLpopup.js @@ -0,0 +1,24 @@ +/*Created by Akanksha + Desc: Popup js for ContactSARL +*/ +function openSARL() +{ + var popup = document.getElementById('popup-SARL'); + var span = document.getElementById("close-SARL"); + + //open popup on click of button + popup.style.display = "block"; + + // When the user clicks on (x), close the popup + span.onclick = function() { + popup.style.display = "none"; + } + + // When the user clicks anywhere outside of the pupo, close it + window.onclick = function(event) { + if (event.target == popup) { + popup.style.display = "none"; + } + } + +} \ No newline at end of file diff --git a/javascripts/SSMpopup.js b/javascripts/SSMpopup.js new file mode 100644 index 0000000..2da3a44 --- /dev/null +++ b/javascripts/SSMpopup.js @@ -0,0 +1,24 @@ +/*Created by Akanksha + Desc: Popup js for ContactSSM +*/ +function openSSM() +{ + var popup = document.getElementById('popup-SSM'); + var span = document.getElementById("close-SSM"); + + //open popup on click of button + popup.style.display = "block"; + + // When the user clicks on (x), close the popup + span.onclick = function() { + popup.style.display = "none"; + } + + // When the user clicks anywhere outside of the pupo, close it + window.onclick = function(event) { + if (event.target == popup) { + popup.style.display = "none"; + } + } + +} \ No newline at end of file diff --git a/javascripts/changeloc.js b/javascripts/changeloc.js new file mode 100644 index 0000000..773be67 --- /dev/null +++ b/javascripts/changeloc.js @@ -0,0 +1,11 @@ +/*Created by Akanksha + Desc: Sets the messages to location selected in the dropdown + of getHelpNow.php + */ +function changeloc() + { + var location = document.getElementById("location").value; + //setting the selected location on screen + document.getElementById("loc").innerHTML = + "This information is for " +location+"(current post)"; +} \ No newline at end of file diff --git a/javascripts/circleOfTrustMessage.js b/javascripts/circleOfTrustMessage.js new file mode 100644 index 0000000..e45b7b3 --- /dev/null +++ b/javascripts/circleOfTrustMessage.js @@ -0,0 +1,56 @@ +$(document).ready(function() { + + $("#msg1").click(function(event){ + $.post( + "groupsms.php", + { msg: "Come and get me.I need help getting home safely.Call ASAP to get my Location.Message sent through First Aide's Circle of Trust" }, + function(data) { + + if (data>=1) + salert('Success','Message has been sent to '+data+' comrades','success'); + else if(data==0) + salert('Error','No comrades registered'+data,'error'); + else + salert('Error',data,'error'); + closePopup(); + + } + ); + + }); + + $("#msg2").click(function(event){ + $.post( + "groupsms.php", + { msg: "Call and pretend you need me.I need an interruption.Message sent through First Aide's Circle of Trust" }, + function(data) { + if (data>=1) + salert('Success','Message has been sent to '+data+' comrades','success'); + else if(data==0) + salert('Error','No comrades registered'+data,'error'); + else + salert('Error',data,'error'); + closePopup(); + } + ); + + }); + + $("#msg3").click(function(event){ + $.post( + "groupsms.php", + { msg: "I need to talk.Message sent through First Aide's Circle of Trust" }, + function(data) { + if (data>=1) + salert('Success','Message has been sent to '+data+' comrades','success'); + else if(data==0) + salert('Error','No comrades registered'+data,'error'); + else + salert('Error',data,'error'); + closePopup(); + } + ); + + }); + + }); \ No newline at end of file diff --git a/javascripts/closePopup.js b/javascripts/closePopup.js new file mode 100644 index 0000000..d68404e --- /dev/null +++ b/javascripts/closePopup.js @@ -0,0 +1,7 @@ +/*Created by Akanksha + Desc: Used to close a popup when called + */ +function closePopup(){ + var popup = document.getElementById('popup-cnt'); + popup.style.display = "none"; + } \ No newline at end of file diff --git a/javascripts/dragscroll.js b/javascripts/dragscroll.js new file mode 100644 index 0000000..6c55a2e --- /dev/null +++ b/javascripts/dragscroll.js @@ -0,0 +1,86 @@ +/** + * @fileoverview dragscroll - scroll area by dragging + * @version 0.0.6 + * + * @license MIT, see http://github.com/asvd/intence + * @copyright 2015 asvd + */ + + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['exports'], factory); + } else if (typeof exports !== 'undefined') { + factory(exports); + } else { + factory((root.dragscroll = {})); + } +}(this, function (exports) { + var _window = window; + var _document = document; + var mousemove = 'mousemove'; + var mouseup = 'mouseup'; + var mousedown = 'mousedown'; + var EventListener = 'EventListener'; + var addEventListener = 'add'+EventListener; + var removeEventListener = 'remove'+EventListener; + + var dragged = []; + var reset = function(i, el) { + for (i = 0; i < dragged.length;) { + el = dragged[i++]; + el = el.container || el; + el[removeEventListener](mousedown, el.md, 0); + _window[removeEventListener](mouseup, el.mu, 0); + _window[removeEventListener](mousemove, el.mm, 0); + } + + // cloning into array since HTMLCollection is updated dynamically + dragged = [].slice.call(_document.getElementsByClassName('dragscroll')); + for (i = 0; i < dragged.length;) { + (function(el, lastClientX, lastClientY, pushed, scroller, cont){ + (cont = el.container || el)[addEventListener]( + mousedown, + cont.md = function(e) { + if (!el.hasAttribute('nochilddrag') || + _document.elementFromPoint( + e.pageX, e.pageY + ) == cont + ) { + pushed = 1; + lastClientX = e.clientX; + lastClientY = e.clientY; + + e.preventDefault(); + } + }, 0 + ); + + _window[addEventListener]( + mouseup, cont.mu = function() {pushed = 0;}, 0 + ); + + _window[addEventListener]( + mousemove, + cont.mm = function(e) { + if (pushed) { + (scroller = el.scroller||el).scrollLeft -= + (- lastClientX + (lastClientX=e.clientX)); + scroller.scrollTop -= + (- lastClientY + (lastClientY=e.clientY)); + } + }, 0 + ); + })(dragged[i++]); + } + } + + + if (_document.readyState == 'complete') { + reset(); + } else { + _window[addEventListener]('load', reset, 0); + } + + exports.reset = reset; +})); diff --git a/javascripts/gethelpnowPhNo.js b/javascripts/gethelpnowPhNo.js new file mode 100644 index 0000000..72ae6c7 --- /dev/null +++ b/javascripts/gethelpnowPhNo.js @@ -0,0 +1,53 @@ +/*Created by Akanksha + Desc: Sends correct number to twilio-call or twilio-sms + based on the location and organization selected + */ +function setnum(id) +{ + var phonenum = "0"; + var location = document.getElementById("location").value; + /*set var phonenum to the correct value depending on which button PCMO or SSM or SARL + invoked this function*/ + switch(id) + { + //set the correct phone numbers here (after given from Peace Corps), these are sample + case "PCMO-msg" : + case "PCMO-call": + { + if(location=="Syria") //set var phonenum according to location selected + phonenum = "4444"; + else if(location =="Uganda") + phonenum = "1111"; + else if(location == "Tunisia") + phonenum = "7777"; + break; + } + case "SSM-msg" : + case "SSM-call": + { + if(location=="Syria") + phonenum = "5555"; + else if(location =="Uganda") + phonenum = "2222"; + else if(location == "Tunisia") + phonenum = "8888"; + break; + } + case "SARL-msg" : + case "SARL-call": + { + if(location=="Syria") + phonenum = "6666"; + else if(location =="Uganda") + phonenum = "3333"; + else if(location == "Tunisia") + phonenum = "9999"; + break; + } + + } + if(id.indexOf("msg")>-1)//check if id contains "msg" string + send_sms(phonenum); //send phone number to twilio-sms.js + else + make_call(phonenum);//send phone number to twilio-call.js +} \ No newline at end of file diff --git a/javascripts/jquery-1.12.4.min.js b/javascripts/jquery-1.12.4.min.js new file mode 100644 index 0000000..e836475 --- /dev/null +++ b/javascripts/jquery-1.12.4.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; +}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("