From 66697b545fe00fe454930ed0c21025ad315422e5 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Mon, 11 May 2015 11:10:38 +0200 Subject: [PATCH 001/148] PHP 5.3 compat: Revert #107 for this branch --- CHANGELOG | 1 - src/codebird.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4872ad9..edc9d73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,6 @@ codebird-php - changelog 3.0.0-dev (not yet released) - #92, #108 Fix issues with uploading special chars -- #107 Decoding issue for big ints on 32-bit servers + #109 Proxy support - Drop support for internal and old API methods + #111 Set user agent for remote calls diff --git a/src/codebird.php b/src/codebird.php index 21a04ec..0a71fa1 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -918,7 +918,7 @@ protected function _parseBearerReply($result, $httpstatus) break; case CODEBIRD_RETURNFORMAT_JSON: if ($httpstatus === 200) { - $parsed = json_decode($reply, false, 512, JSON_BIGINT_AS_STRING); + $parsed = json_decode($reply); self::setBearerToken($parsed->access_token); } break; @@ -1605,7 +1605,7 @@ protected function _parseApiReply($reply) return new \stdClass; } } - if (! $parsed = json_decode($reply, $need_array, 512, JSON_BIGINT_AS_STRING)) { + if (! $parsed = json_decode($reply, $need_array)) { if ($reply) { if (stripos($reply, '<' . '?xml version="1.0" encoding="UTF-8"?' . '>') === 0) { // we received XML... From 8f4aae1c10562a758885b5177017fcfd8f0b366d Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 14 May 2015 19:51:27 +0200 Subject: [PATCH 002/148] Set version to 2.7.0 --- bower.json | 2 +- src/codebird.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 16cc6d8..4abf678 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codebird-php", - "version": "3.0.0-dev", + "version": "2.7.0", "homepage": "http://www.jublo.net/projects/codebird/php", "authors": [ "Joshua Atkins ", diff --git a/src/codebird.php b/src/codebird.php index 79b057c..a2cd342 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -6,7 +6,7 @@ * A Twitter library in PHP. * * @package codebird - * @version 3.0.0-dev + * @version 2.7.0 * @author Jublo Solutions * @copyright 2010-2015 Jublo Solutions * @license http://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 @@ -104,7 +104,7 @@ class Codebird /** * The current Codebird version */ - protected $_version = '3.0.0-dev'; + protected $_version = '2.7.0'; /** * Auto-detect cURL absence From b3a669debe7776c24fb830318073cc615f139ab2 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 14 May 2015 19:51:34 +0200 Subject: [PATCH 003/148] Set 2.7.0 release date --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b3f0149..0a8d4d8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ codebird-php - changelog ======================== -3.0.0-dev (not yet released) +2.7.0 (2015-05-14) - #92, #108 Fix issues with uploading special chars + #109 Proxy support - Drop support for internal and old API methods From 21bfc4e1bc6b578258c0b8ef978cb1bec3290001 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 22 May 2015 20:51:55 +0200 Subject: [PATCH 004/148] Update README with video upload instructions --- README.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2fbea57..0275b50 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,9 @@ sent with the code above. ### Uploading media to Twitter -Tweet media can be uploaded in a 2-step process. -**First** you send each image to Twitter, like this: +Tweet media can be uploaded in a 2-step process: + +**First** you send each media to Twitter. For **images**, it works like this: ```php // these files to upload. You can also just upload 1 image! @@ -198,6 +199,8 @@ foreach ($media_files as $file) { } ``` +Uploading **videos** requires you to send the data in chunks. See the next section on this. + **Second,** you attach the collected media ids for all images to your call to ```statuses/update```, like this: @@ -217,7 +220,7 @@ print_r($reply); Here is a [sample tweet](https://twitter.com/LarryMcTweet/status/475276535386365952) sent with the code above. -More [documentation for tweeting with media](https://dev.twitter.com/rest/public/uploading-media-multiple-photos) is available on the Twitter Developer site. +More [documentation for uploading media](https://dev.twitter.com/rest/public/uploading-media) is available on the Twitter Developer site. #### Remote files @@ -230,6 +233,78 @@ $reply = $cb->media_upload(array( :warning: *URLs containing Unicode characters should be normalised. A sample normalisation function can be found at http://stackoverflow.com/a/6059053/1816603* +#### Video files + +Uploading videos to Twitter (≤ 15MB, MP4) requires you to send them in chunks. +You need to perform at least 3 calls to obtain your `media_id` for the video: + +1. Send an `INIT` event to get a `media_id` draft. +2. Upload your chunks with `APPEND` events, each one up to 5MB in size. +3. Send a `FINALIZE` event to convert the draft to a ready-to-tweet `media_id`. +4. Post your tweet with video attached. + +Here’s a sample for video uploads: + +```php +$file = 'demo-video.mp4'; +$size_bytes = filesize($file); +$fp = fopen($file, 'r'); + +// INIT the upload + +$reply = $cb->media_upload([ + 'command' => 'INIT', + 'media_type' => 'video/mp4', + 'total_bytes' => $size_bytes +]); + +$media_id = $reply->media_id_string; + +// APPEND data to the upload + +$segment_id = 0; + +while (! feof($fp)) { + $chunk = fread($fp, 1048576); // 1MB per chunk for this sample + + $reply = $cb->media_upload([ + 'command' => 'APPEND', + 'media_id' => $media_id, + 'segment_index' => $segment_id, + 'media' => $chunk + ]); + + $segment_id++; +} + +fclose($fp); + +// FINALIZE the upload + +$reply = $cb->media_upload([ + 'command' => 'FINALIZE', + 'media_id' => $media_id +]); + +var_dump($reply); + +if ($reply->httpstatus < 200 || $reply->httpstatus > 299) { + die(); +} + +// Now use the media_id in a tweet +$reply = $cb->statuses_update([ + 'status' => 'Twitter now accepts video uploads.', + 'media_ids' => $media_id +]); + +``` + +:warning: The Twitter API reproducibly rejected some MP4 videos even though they are valid. It’s currently undocumented which video codecs are supported and which are not. + +:warning: When uploading a video in multiple chunks, you may run into an error `The validation of media ids failed.` even though the `media_id` is correct. This is known. Please check back with this [Twitter community forums thread](https://twittercommunity.com/t/video-uploads-via-rest-api/38177/5). + + ### Requests with app-only auth To send API requests without an access token for a user (app-only auth), From 64195582d02924fdbf208508f5e863bc03c38605 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 22 May 2015 20:52:16 +0200 Subject: [PATCH 005/148] Ignore test videos --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2bc2c81..63cabe4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ RELEASE_MESSAGE* test* *.jpg +*.mp4 \ No newline at end of file From d5bc3a1deb9ee9caa34fcc6ae07bc22b264a8bc6 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 22 May 2015 21:04:55 +0200 Subject: [PATCH 006/148] Provide PHP5.3 compatibility in video sample code --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0275b50..066f435 100644 --- a/README.md +++ b/README.md @@ -252,11 +252,11 @@ $fp = fopen($file, 'r'); // INIT the upload -$reply = $cb->media_upload([ +$reply = $cb->media_upload(array( 'command' => 'INIT', 'media_type' => 'video/mp4', 'total_bytes' => $size_bytes -]); +)); $media_id = $reply->media_id_string; @@ -267,12 +267,12 @@ $segment_id = 0; while (! feof($fp)) { $chunk = fread($fp, 1048576); // 1MB per chunk for this sample - $reply = $cb->media_upload([ + $reply = $cb->media_upload(array( 'command' => 'APPEND', 'media_id' => $media_id, 'segment_index' => $segment_id, 'media' => $chunk - ]); + )); $segment_id++; } @@ -281,10 +281,10 @@ fclose($fp); // FINALIZE the upload -$reply = $cb->media_upload([ +$reply = $cb->media_upload(array( 'command' => 'FINALIZE', 'media_id' => $media_id -]); +)); var_dump($reply); @@ -293,11 +293,10 @@ if ($reply->httpstatus < 200 || $reply->httpstatus > 299) { } // Now use the media_id in a tweet -$reply = $cb->statuses_update([ +$reply = $cb->statuses_update(array( 'status' => 'Twitter now accepts video uploads.', 'media_ids' => $media_id -]); - +)); ``` :warning: The Twitter API reproducibly rejected some MP4 videos even though they are valid. It’s currently undocumented which video codecs are supported and which are not. From 5f726252e14fa00db20c80eace22ff3c116c6ab8 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 22 May 2015 21:09:36 +0200 Subject: [PATCH 007/148] Move Streaming API changelog entry to 3.0.0 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7821139..3125793 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ codebird-php - changelog 3.0.0 (not yet released) - #107 Decoding issue for big ints on 32-bit servers ++ #32 Add support for streaming API 2.7.0 (2015-05-14) - #92, #108 Fix issues with uploading special chars @@ -11,7 +12,6 @@ codebird-php - changelog + #111 Set user agent for remote calls + #106 Add logout method + #86 Return exception for failed cURL requests -+ #32 Add support for streaming API 2.6.1 (2014-12-13) - #90 Allow uploading media with special chars From 2de5cf97d21f50ca7643f3fabb1776f846563925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Kobyli=C5=84ski?= Date: Sun, 24 May 2015 22:56:12 +0200 Subject: [PATCH 008/148] stream api fix --- src/codebird.php | 50 ++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index c7c737c..6ec95d9 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1775,7 +1775,9 @@ protected function _callApiStreaming( stream_set_timeout($ch, 0); // collect headers - $result = stream_get_line($ch, 1048576, "\r\n\r\n"); + do{ + $result = stream_get_line($ch, 1048576, "\r\n\r\n"); + }while(!$result); $headers = explode("\r\n", $result); // find HTTP status @@ -1806,50 +1808,58 @@ protected function _callApiStreaming( $signal_function = function_exists('pcntl_signal_dispatch'); $data = ''; $last_message = time(); - + $chunk_length = 0; + $message_length = 0; + while (!feof($ch)) { // call signal handlers, if any if ($signal_function) { pcntl_signal_dispatch(); } - $cha = [$ch]; $write = $except = null; if (false === ($num_changed_streams = stream_select($cha, $write, $except, 0, 200000))) { break; } elseif ($num_changed_streams === 0) { if (time() - $last_message >= 1) { - // deliver empty message, allow callback to cancel stream + //deliver empty message, allow callback to cancel stream $cancel_stream = $this->_deliverStreamingMessage(null); if ($cancel_stream) { break; } - $last_message = time(); } continue; } - - $chunk_length = fgets($ch, 10); - + $chunk_length_raw = $chunk_length = fgets($ch, 10); if ($chunk_length === '' || !$chunk_length = hexdec($chunk_length)) { continue; } - $chunk = fread($ch, $chunk_length + 4); - $data .= $chunk; + $chunk = ''; + do{ + $chunk .= fread($ch, $chunk_length); + $chunk_length -= strlen($chunk); + } while( $chunk_length > 0); + + if(0 === $message_length){ + $message_length = (int) strstr($chunk, "\r\n", true); + if($message_length){ + $chunk = substr($chunk, strpos( $chunk, "\r\n") + 2); + }else{ + continue; + } - // extract object to parse - list($object_length, $temp) = explode("\r\n", $data, 2); - if ($object_length < 1 - || strlen($temp) < $object_length) { - continue; + $data = $chunk; + }else{ + $data .= $chunk; } - $reply = substr($temp, 0, $object_length); - $data = substr($temp, $object_length + 2); + if(strlen($data) < $message_length){ + continue; + } - $reply = $this->_parseApiReply($reply); + $reply = $this->_parseApiReply($data); switch ($this->_return_format) { case CODEBIRD_RETURNFORMAT_ARRAY: $reply['httpstatus'] = $httpstatus; @@ -1866,6 +1876,8 @@ protected function _callApiStreaming( break; } + $data = ''; + $message_length = 0; $last_message = time(); } @@ -1881,7 +1893,7 @@ protected function _callApiStreaming( */ protected function _deliverStreamingMessage($message) { - return call_user_func($this->_streaming_callback, $message); + return call_user_func($this->_streaming_callback, $message); } /** From cd3e0008948f9e5974333226dee1d32be28fb4ce Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 27 May 2015 19:56:23 +0200 Subject: [PATCH 009/148] Code formatting --- src/codebird.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 6ec95d9..6aecd7d 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1775,9 +1775,9 @@ protected function _callApiStreaming( stream_set_timeout($ch, 0); // collect headers - do{ + do { $result = stream_get_line($ch, 1048576, "\r\n\r\n"); - }while(!$result); + } while(!$result); $headers = explode("\r\n", $result); // find HTTP status @@ -1810,7 +1810,7 @@ protected function _callApiStreaming( $last_message = time(); $chunk_length = 0; $message_length = 0; - + while (!feof($ch)) { // call signal handlers, if any if ($signal_function) { @@ -1822,7 +1822,7 @@ protected function _callApiStreaming( break; } elseif ($num_changed_streams === 0) { if (time() - $last_message >= 1) { - //deliver empty message, allow callback to cancel stream + // deliver empty message, allow callback to cancel stream $cancel_stream = $this->_deliverStreamingMessage(null); if ($cancel_stream) { break; @@ -1837,25 +1837,25 @@ protected function _callApiStreaming( } $chunk = ''; - do{ + do { $chunk .= fread($ch, $chunk_length); $chunk_length -= strlen($chunk); - } while( $chunk_length > 0); - - if(0 === $message_length){ + } while($chunk_length > 0); + + if(0 === $message_length) { $message_length = (int) strstr($chunk, "\r\n", true); - if($message_length){ - $chunk = substr($chunk, strpos( $chunk, "\r\n") + 2); - }else{ + if ($message_length) { + $chunk = substr($chunk, strpos($chunk, "\r\n") + 2); + } else { continue; } $data = $chunk; - }else{ - $data .= $chunk; + } else { + $data .= $chunk; } - if(strlen($data) < $message_length){ + if (strlen($data) < $message_length) { continue; } @@ -1876,9 +1876,9 @@ protected function _callApiStreaming( break; } - $data = ''; + $data = ''; $message_length = 0; - $last_message = time(); + $last_message = time(); } return; From 7b8539bee2077846e53b93b9afcb8fb5afa05d76 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 27 May 2015 20:50:46 +0200 Subject: [PATCH 010/148] Streaming API loop: Remove unused assignments --- src/codebird.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 6aecd7d..35ced09 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1808,7 +1808,6 @@ protected function _callApiStreaming( $signal_function = function_exists('pcntl_signal_dispatch'); $data = ''; $last_message = time(); - $chunk_length = 0; $message_length = 0; while (!feof($ch)) { @@ -1831,7 +1830,7 @@ protected function _callApiStreaming( } continue; } - $chunk_length_raw = $chunk_length = fgets($ch, 10); + $chunk_length = fgets($ch, 10); if ($chunk_length === '' || !$chunk_length = hexdec($chunk_length)) { continue; } From 2099a6b2d3be660b98e9478a1bfe268d28921dc6 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 27 May 2015 20:57:27 +0200 Subject: [PATCH 011/148] Drop cURL workarounds added for PHP 5.3 Fix #117 --- src/codebird.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 35ced09..83fa475 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -26,10 +26,7 @@ 'CURLE_SSL_CACERT' => 60, 'CURLE_SSL_CACERT_BADFILE' => 77, 'CURLE_SSL_CRL_BADFILE' => 82, - 'CURLE_SSL_ISSUER_ERROR' => 83, - // workaround for http://php.net/manual/en/function.curl-setopt.php#107314 - '_CURLOPT_TIMEOUT_MS' => 155, - '_CURLOPT_CONNECTTIMEOUT_MS' => 156 + 'CURLE_SSL_ISSUER_ERROR' => 83 ]; foreach ($constants as $id => $i) { defined($id) or define($id, $i); @@ -1294,8 +1291,8 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // use hardcoded download timeouts for now - curl_setopt($ch, _CURLOPT_TIMEOUT_MS, 5000); - curl_setopt($ch, _CURLOPT_CONNECTTIMEOUT_MS, 2000); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000); $result = curl_exec($ch); if ($result !== false) { $value = $result; @@ -1492,11 +1489,11 @@ protected function _callApiCurl( curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); if (isset($this->_timeout)) { - curl_setopt($ch, _CURLOPT_TIMEOUT_MS, $this->_timeout); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_timeout); } if (isset($this->_connectionTimeout)) { - curl_setopt($ch, _CURLOPT_CONNECTTIMEOUT_MS, $this->_connectionTimeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_connectionTimeout); } $result = curl_exec($ch); From fa4482f29756fd96af8dd515128acd86e1652d5c Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 27 May 2015 20:57:48 +0200 Subject: [PATCH 012/148] Add CHANGELOG entry for #117 --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3125793..81e5ef0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ codebird-php - changelog 3.0.0 (not yet released) - #107 Decoding issue for big ints on 32-bit servers + #32 Add support for streaming API ++ #117 Drop cURL workarounds added for PHP 5.3 2.7.0 (2015-05-14) - #92, #108 Fix issues with uploading special chars From 926fddd97455af0de80a86de915aaee55027b542 Mon Sep 17 00:00:00 2001 From: Joshua Atkins Date: Tue, 7 Jul 2015 16:37:17 +0100 Subject: [PATCH 013/148] Allowed for multiple parameters in templated methods by replacing preg_match with preg_match_all --- src/codebird.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codebird.php b/src/codebird.php index 83fa475..005decf 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -571,7 +571,7 @@ protected function _mapFnToApiMethod($fn, &$apiparams) // replace AA by URL parameters $method_template = $method; $match = []; - if (preg_match('/[A-Z_]{2,}/', $method, $match)) { + if (preg_match_all('/[A-Z_]{2,}/', $method, $match)) { foreach ($match as $param) { $param_l = strtolower($param); $method_template = str_replace($param, ':' . $param_l, $method_template); From bc1117c8d267989e737258b132fce00d05be5763 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 18 Jul 2015 19:44:41 +0200 Subject: [PATCH 014/148] Update cacert.pem --- CHANGELOG | 1 + src/cacert.pem | 1143 ++++++++++++++++++++++++++++-------------------- 2 files changed, 674 insertions(+), 470 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 81e5ef0..50b6ea9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ codebird-php - changelog - #107 Decoding issue for big ints on 32-bit servers + #32 Add support for streaming API + #117 Drop cURL workarounds added for PHP 5.3 ++ Update cacert.pem 2.7.0 (2015-05-14) - #92, #108 Fix issues with uploading special chars diff --git a/src/cacert.pem b/src/cacert.pem index 2c38245..1b24dc6 100644 --- a/src/cacert.pem +++ b/src/cacert.pem @@ -1,75 +1,23 @@ ## -## ca-bundle.crt -- Bundle of CA Root Certificates +## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue Jan 28 09:38:07 2014 +## Certificate data from Mozilla as of: Wed Apr 22 03:12:04 2015 ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: -## http://mxr.mozilla.org/mozilla-release/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1 +## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## +## Conversion done with mk-ca-bundle.pl version 1.25. +## SHA1: ed3c0bbfb7912bcc00cd2033b0cb85c98d10559c +## -GTE CyberTrust Global Root -========================== ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg -Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG -A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz -MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL -Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 -IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u -sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql -HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID -AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW -M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF -NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -Thawte Server CA -================ ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs -dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE -AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j -b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV -BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u -c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG -A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 -ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl -/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 -1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR -MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J -GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ -GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -Thawte Premium Server CA -======================== ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs -dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE -AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl -ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT -AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU -VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 -aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ -cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 -aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh -Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ -qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm -SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf -8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t -UCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- - Equifax Secure CA ================= -----BEGIN CERTIFICATE----- @@ -90,41 +38,6 @@ BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 70+sB3c4 -----END CERTIFICATE----- -Verisign Class 3 Public Primary Certification Authority -======================================================= ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx -FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow -XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz -IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 -f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol -hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA -TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah -WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf -Tqj/ZA1k ------END CERTIFICATE----- - -Verisign Class 3 Public Primary Certification Authority - G2 -============================================================ ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT -MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy -eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz -dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT -MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy -eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz -dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO -FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 -lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB -MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT -1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD -Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 ------END CERTIFICATE----- - GlobalSign Root CA ================== -----BEGIN CERTIFICATE----- @@ -168,63 +81,6 @@ BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -ValiCert Class 1 VA -=================== ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp -b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh -bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy -MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 -d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg -UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 -LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi -GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm -DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG -lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX -icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP -Orf1LXLI ------END CERTIFICATE----- - -ValiCert Class 2 VA -=================== ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp -b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh -bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw -MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 -d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg -UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 -LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC -CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf -ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ -SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV -UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 -W9ViH0Pd ------END CERTIFICATE----- - -RSA Root Certificate 1 -====================== ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp -b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh -bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw -MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 -d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg -UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 -LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td -3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H -BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs -3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF -V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r -on+jjBXu ------END CERTIFICATE----- - Verisign Class 3 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- @@ -273,33 +129,6 @@ RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== -----END CERTIFICATE----- -Entrust.net Secure Server CA -============================ ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV -BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg -cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl -ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG -A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi -eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p -dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ -aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 -gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw -ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw -CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l -dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw -NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow -HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA -BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN -Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 -n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - Entrust.net Premium 2048 Secure Server CA ========================================= -----BEGIN CERTIFICATE----- @@ -345,40 +174,6 @@ Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -Equifax Secure Global eBusiness CA -================================== ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp -bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx -HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds -b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV -PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN -qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn -hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j -BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs -MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN -I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY -NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -Equifax Secure eBusiness CA 1 -============================= ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB -LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE -ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz -IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ -1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a -IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk -MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW -Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF -AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 -lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ -KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - AddTrust Low-Value Services Root ================================ -----BEGIN CERTIFICATE----- @@ -624,59 +419,6 @@ gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- -America Online Root Certification Authority 1 -============================================= ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG -A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg -T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG -v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z -DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh -sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP -8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T -AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z -o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf -GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF -VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft -3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g -Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -America Online Root Certification Authority 2 -============================================= ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG -A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg -T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en -fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 -f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO -qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN -RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 -gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn -6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid -FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 -Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj -B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op -aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY -T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p -+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg -JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy -zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO -ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh -1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf -GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff -Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP -cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= ------END CERTIFICATE----- - Visa eCommerce Root =================== -----BEGIN CERTIFICATE----- @@ -953,30 +695,6 @@ nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- -TDC Internet Root CA -==================== ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE -ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx -NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu -ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j -xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL -znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc -5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 -otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI -AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM -VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM -MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC -AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe -UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G -CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m -gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb -O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU -Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- - UTN DATACorp SGC Root CA ======================== -----BEGIN CERTIFICATE----- @@ -1117,64 +835,6 @@ KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM 8CgHrTwXZoi1/baI -----END CERTIFICATE----- -NetLock Business (Class B) Root -=============================== ------BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT -CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV -BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg -VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD -VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv -bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg -VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB -iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S -o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr -1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV -HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ -RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh -dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 -ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv -c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg -YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz -Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA -bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl -IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 -YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj -cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM -43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR -stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- - -NetLock Express (Class C) Root -============================== ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT -CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV -BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD -KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ -BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 -dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j -ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB -jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z -W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 -euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw -DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN -RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn -YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB -IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i -aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 -ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y -emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k -IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ -UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg -YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 -xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW -gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- - XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- @@ -1318,31 +978,6 @@ CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy +fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS -----END CERTIFICATE----- -Firmaprofesional Root CA -======================== ------BEGIN CERTIFICATE----- -MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMxIjAgBgNVBAcT -GUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1dG9yaWRhZCBkZSBDZXJ0aWZp -Y2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FA -ZmlybWFwcm9mZXNpb25hbC5jb20wHhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTEL -MAkGA1UEBhMCRVMxIjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMT -OUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2 -ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20wggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5uCp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5V -j1H5WuretXDE7aTt/6MNbg9kUDGvASdYrv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJH -lShbz++AbOCQl4oBPB3zhxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf -3H5idPayBQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcLiam8 -NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcbAgMBAAGjgZ8wgZww -KgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lvbmFsLmNvbTASBgNVHRMBAf8ECDAG -AQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQAD -ggEBAEdz/o0nVPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq -u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36mhoEyIwOdyPdf -wUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzflZKG+TQyTmAyX9odtsz/ny4Cm -7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBpQWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YG -VM+h4k0460tQtcsm9MracEpqoeJ5quGnM/b9Sh/22WA= ------END CERTIFICATE----- - Swisscom Root CA 1 ================== -----BEGIN CERTIFICATE----- @@ -1954,40 +1589,6 @@ PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. -====================================== ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT -AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg -LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w -HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ -U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh -IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B -AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN -yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU -2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 -4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP -2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm -8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf -HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa -Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK -5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b -czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g -ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF -BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug -cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf -AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX -EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v -/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 -MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 -3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk -eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f -/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h -RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU -Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== ------END CERTIFICATE----- - TC TrustCenter Class 2 CA II ============================ -----BEGIN CERTIFICATE----- @@ -2015,33 +1616,6 @@ JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk vQ== -----END CERTIFICATE----- -TC TrustCenter Class 3 CA II -============================ ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC -REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy -IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw -MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 -c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE -AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W -yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo -6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ -uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk -2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB -7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 -Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU -cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i -SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE -O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 -yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 -IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal -092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc -5A== ------END CERTIFICATE----- - TC TrustCenter Universal CA I ============================= -----BEGIN CERTIFICATE----- @@ -2635,22 +2209,6 @@ MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- -Verisign Class 3 Public Primary Certification Authority -======================================================= ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx -FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow -XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz -IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 -f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol -hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky -CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX -bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ -D/xwzoiQ ------END CERTIFICATE----- - Microsec e-Szigno Root CA 2009 ============================== -----BEGIN CERTIFICATE----- @@ -2675,28 +2233,6 @@ yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi LXpUq3DDfSJlgnCW -----END CERTIFICATE----- -E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi -=================================================== ------BEGIN CERTIFICATE----- -MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG -EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz -ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 -MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 -cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u -aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY -8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y -jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI -JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk -9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD -AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG -SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d -F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq -D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 -Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq -fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX ------END CERTIFICATE----- - GlobalSign Root CA - R3 ======================= -----BEGIN CERTIFICATE----- @@ -3783,3 +3319,670 @@ i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= -----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +WoSign +====== +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQG +EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNVBAMTIUNlcnRpZmljYXRpb24g +QXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJ +BgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +vcqNrLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1UfcIiePyO +CbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcSccf+Hb0v1naMQFXQoOXXDX +2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2ZjC1vt7tj/id07sBMOby8w7gLJKA84X5 +KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4Mx1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR ++ScPewavVIMYe+HdVHpRaG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ez +EC8wQjchzDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDaruHqk +lWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221KmYo0SLwX3OSACCK2 +8jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvASh0JWzko/amrzgD5LkhLJuYwTKVY +yrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWvHYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0C +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R +8bNLtwYgFP6HEtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJMuYhOZO9sxXq +T2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2eJXLOC62qx1ViC777Y7NhRCOj +y+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VNg64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC +2nz4SNAzqfkHx5Xh9T71XXG68pWpdIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes +5cVAWubXbHssw1abR80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/ +EaEQPkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGcexGATVdVh +mVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+J7x6v+Db9NpSvd4MVHAx +kUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMlOtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGi +kpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWTee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +WoSign China +============ +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQG +EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMMEkNBIOayg+mAmuagueiv +geS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYD +VQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k +8H/rD195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld19AXbbQs5 +uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExfv5RxadmWPgxDT74wwJ85 +dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnkUkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5 +Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+LNVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFy +b7Ao65vh4YOhn0pdr8yb+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc +76DbT52VqyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6KyX2m ++Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0GAbQOXDBGVWCvOGU6 +yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaKJ/kR8slC/k7e3x9cxKSGhxYzoacX +GKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUA +A4ICAQBqinA4WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj/feTZU7n85iY +r83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6jBAyvd0zaziGfjk9DgNyp115 +j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0A +kLppRQjbbpCBhqcqBT/mhDn4t/lXX0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97 +qA4bLJyuQHCH2u2nFoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Y +jj4Du9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10lO1Hm13ZB +ONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Leie2uPAmvylezkolwQOQv +T8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR12KvxAmLBsX5VYc8T1yaw15zLKYs4SgsO +kI26oQ== +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl +OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV +MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF +JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G3 +================================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y +olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t +x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy +EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K +Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur +mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5 +1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp +07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo +FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE +41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu +yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq +KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1 +v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA +8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b +8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r +mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq +1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI +JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV +tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk= +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- \ No newline at end of file From 8285ac41f4314c50f0c322a9ed3f6e3c6b79cc32 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 18 Jul 2015 19:44:48 +0200 Subject: [PATCH 015/148] Drop dead code --- src/codebird.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 83fa475..5129ed9 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -513,9 +513,6 @@ protected function _parseApiParams($params) if (is_array($params[0])) { // given parameters are array $apiparams = $params[0]; - if (! is_array($apiparams)) { - $apiparams = []; - } return $apiparams; } From 91c6bc2582cbbc0d2b36aa213a1ef0d67622e172 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 18 Jul 2015 19:47:05 +0200 Subject: [PATCH 016/148] Add CHANGELOG entry for #121 See PR #122. --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 50b6ea9..1ad3f01 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ codebird-php - changelog + #32 Add support for streaming API + #117 Drop cURL workarounds added for PHP 5.3 + Update cacert.pem ++ #121 Allow for multiple parameters in templated methods + by replacing preg_match with preg_match_all 2.7.0 (2015-05-14) - #92, #108 Fix issues with uploading special chars From 4b5f1f8d58f2a505b1d23f3b4c37b3114a5dd331 Mon Sep 17 00:00:00 2001 From: Eric Klien Date: Sat, 18 Jul 2015 12:27:25 -0700 Subject: [PATCH 017/148] Update codebird.php for redirected files. Find files that have been redirected. --- src/codebird.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/codebird.php b/src/codebird.php index a2cd342..5784a44 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1269,6 +1269,8 @@ protected function _buildMultipart($method, $params) // use hardcoded download timeouts for now curl_setopt($ch, _CURLOPT_TIMEOUT_MS, 5000); curl_setopt($ch, _CURLOPT_CONNECTTIMEOUT_MS, 2000); + // find files that have been redirected + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $result = curl_exec($ch); if ($result !== false) { $value = $result; From 30a76976c20d01db91a55133110e187678c837a4 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 16 Aug 2015 20:32:36 +0200 Subject: [PATCH 018/148] Add Changelog entry for #124 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0a8d4d8..14af63d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ codebird-php - changelog ======================== +2.7.1 (not yet released) ++ #124 Download redirected remote images + 2.7.0 (2015-05-14) - #92, #108 Fix issues with uploading special chars + #109 Proxy support From d78520be2f7c104dd52352bee95c9e43a25c30e9 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 16 Aug 2015 20:39:04 +0200 Subject: [PATCH 019/148] Set version to 2.7.1 --- bower.json | 2 +- src/codebird.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index 4abf678..88726dc 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codebird-php", - "version": "2.7.0", + "version": "2.7.1", "homepage": "http://www.jublo.net/projects/codebird/php", "authors": [ "Joshua Atkins ", diff --git a/src/codebird.php b/src/codebird.php index 5784a44..0b07234 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -6,7 +6,7 @@ * A Twitter library in PHP. * * @package codebird - * @version 2.7.0 + * @version 2.7.1 * @author Jublo Solutions * @copyright 2010-2015 Jublo Solutions * @license http://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 @@ -104,7 +104,7 @@ class Codebird /** * The current Codebird version */ - protected $_version = '2.7.0'; + protected $_version = '2.7.1'; /** * Auto-detect cURL absence @@ -716,7 +716,7 @@ protected function getCurlInitialization($url) protected function getNoCurlInitialization($url, $contextOptions, $hostname = '') { $httpOptions = array(); - + $httpOptions['header'] = array( 'User-Agent: codebird-php ' . $this->getVersion() . ' by Jublo Solutions ' ); From 03045feadf7e66d9edd742ebf419e428b74d7105 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 16 Aug 2015 20:39:11 +0200 Subject: [PATCH 020/148] Set release date for 2.7.1 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 14af63d..09f6cda 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ codebird-php - changelog ======================== -2.7.1 (not yet released) +2.7.1 (2015-08-16) + #124 Download redirected remote images 2.7.0 (2015-05-14) From 6c1e2f20c476fce6f17ce6fc9c9c1507255c6fda Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 29 Aug 2015 16:58:13 +0200 Subject: [PATCH 021/148] Fix regression introduced with #122 Fix #130 --- src/codebird.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index aece0f2..f0b91df 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -570,6 +570,7 @@ protected function _mapFnToApiMethod($fn, &$apiparams) $match = []; if (preg_match_all('/[A-Z_]{2,}/', $method, $match)) { foreach ($match as $param) { + $param = $param[0]; $param_l = strtolower($param); $method_template = str_replace($param, ':' . $param_l, $method_template); if (! isset($apiparams[$param_l])) { @@ -1102,7 +1103,7 @@ protected function _nonce($length = 8) } return substr(md5(microtime(true)), 0, $length); } - + /** * Signature helper * @@ -1317,7 +1318,7 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par $request .= "\r\n\r\n" . $value . "\r\n"; } - + return $request; } @@ -1399,7 +1400,7 @@ protected function _detectStreaming($method) { return $key; } } - + return false; } @@ -1834,7 +1835,7 @@ protected function _callApiStreaming( $chunk = ''; do { $chunk .= fread($ch, $chunk_length); - $chunk_length -= strlen($chunk); + $chunk_length -= strlen($chunk); } while($chunk_length > 0); if(0 === $message_length) { @@ -1878,7 +1879,7 @@ protected function _callApiStreaming( return; } - + /** * Calls streaming callback with received message * @@ -1888,7 +1889,7 @@ protected function _callApiStreaming( */ protected function _deliverStreamingMessage($message) { - return call_user_func($this->_streaming_callback, $message); + return call_user_func($this->_streaming_callback, $message); } /** From 90f8e7d59e1fd3b09706caa773eaff045ec9ae31 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 23 Sep 2015 14:26:53 +0200 Subject: [PATCH 022/148] Fix #135 Invalid HTTP request headers in non-cURL Thanks to @Fearitude for spotting this bug. --- src/codebird.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/codebird.php b/src/codebird.php index 0b07234..cb0991d 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -744,6 +744,9 @@ protected function getNoCurlInitialization($url, $contextOptions, $hostname = '' array('http' => $httpOptions) ); + // concatenate $options['http']['header'] + $options['http']['header'] = implode("\r\n", $options['http']['header']); + // silent the file_get_contents function $content = @file_get_contents($url, false, stream_context_create($options)); From 70f5f7d39ac2239d7c20653054c13e0c0a4e27b9 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 23 Sep 2015 14:27:40 +0200 Subject: [PATCH 023/148] Set version to 2.7.2 --- src/codebird.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index cb0991d..5596e08 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -6,7 +6,7 @@ * A Twitter library in PHP. * * @package codebird - * @version 2.7.1 + * @version 2.7.2 * @author Jublo Solutions * @copyright 2010-2015 Jublo Solutions * @license http://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 @@ -104,7 +104,7 @@ class Codebird /** * The current Codebird version */ - protected $_version = '2.7.1'; + protected $_version = '2.7.2'; /** * Auto-detect cURL absence From ced29520d93eeb6652ebd5a81cf0bf79f9d98597 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 23 Sep 2015 14:27:47 +0200 Subject: [PATCH 024/148] Add CHANGELOG for 2.7.2 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 09f6cda..f0d26a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ codebird-php - changelog ======================== +2.7.2 (2015-09-23) +- #135 Invalid HTTP request headers in non-cURL mode + 2.7.1 (2015-08-16) + #124 Download redirected remote images From ec769779c058d52cf995a6aaae8c8f339212075c Mon Sep 17 00:00:00 2001 From: Eric Klien Date: Sat, 26 Sep 2015 03:31:02 -0700 Subject: [PATCH 025/148] Add support for compressed remote images This enables http://www.inc.com/uploaded_files/image/970x450/Bill-970_23752.jpg to work. --- src/codebird.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/codebird.php b/src/codebird.php index 055f08b..2cf62de 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1296,6 +1296,8 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000); // find files that have been redirected curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + // process compressed images + curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate,sdch'); $result = curl_exec($ch); if ($result !== false) { $value = $result; From 2f370ba0710c6b04196ab51ee0c602a10da1d9ef Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 12:03:29 +0200 Subject: [PATCH 026/148] Add CHANGELOG for #134 --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 5a0122a..7d5ff2c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ codebird-php - changelog + Update cacert.pem + #121 Allow for multiple parameters in templated methods by replacing preg_match with preg_match_all ++ #134 Add support for compressed remote images 2.7.2 (2015-09-23) - #135 Invalid HTTP request headers in non-cURL mode From 88914358b5c7de1c98dd90aa27e209e73f7b2757 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 12:59:10 +0200 Subject: [PATCH 027/148] Allow to change remote media download timeout --- src/codebird.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 2cf62de..0f6ec92 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -132,6 +132,11 @@ class Codebird */ protected $_connectionTimeout = 3000; + /** + * Remote media download timeout + */ + protected $_remoteDownloadTimeout = 5000; + /** * Proxy */ @@ -265,6 +270,18 @@ public function setConnectionTimeout($timeout) $this->_connectionTimeout = (int) $timeout; } + /** + * Sets remote media download timeout in milliseconds + * + * @param int $timeout Remote media timeout in milliseconds + * + * @return void + */ + public function setRemoteDownloadTimeout($timeout) + { + $this->_remoteDownloadTimeout = (int) $timeout; + } + /** * Sets the format for API replies * @@ -1292,8 +1309,8 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // use hardcoded download timeouts for now - curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_remoteDownloadTimeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_remoteDownloadTimeout / 2); // find files that have been redirected curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // process compressed images @@ -1307,7 +1324,7 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par 'http' => [ 'method' => 'GET', 'protocol_version' => '1.1', - 'timeout' => 5000 + 'timeout' => $this->_remote_download_timeout ], 'ssl' => [ 'verify_peer' => false From 4bc86a913627b4c005052056fdd33fa76610140e Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 12:59:17 +0200 Subject: [PATCH 028/148] Add README for #129 --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d4b0fb..0ac3a54 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ along with this program. If not, see . Summary ------- -Use Codebird to connect to the Twitter REST **and Streaming API :sparkles:** from your PHP code. +Use Codebird to connect to the Twitter REST **and Streaming API :sparkles:** from your PHP code. Codebird supports full 3-way OAuth as well as application-only auth. @@ -240,6 +240,13 @@ $reply = $cb->media_upload(array( :warning: *URLs containing Unicode characters should be normalised. A sample normalisation function can be found at http://stackoverflow.com/a/6059053/1816603* +To circumvent download issues when remote servers are slow to respond, +you may customise the remote download timeout, like this: + +```php +$cb->setRemoteDownloadTimeout(10000); // milliseconds +``` + #### Video files Uploading videos to Twitter (≤ 15MB, MP4) requires you to send them in chunks. From e417545e3c7a9f0caa9fde25c944993d43b385ff Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 12:59:22 +0200 Subject: [PATCH 029/148] Add CHANGELOG for #129 --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 7d5ff2c..d11928d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ codebird-php - changelog + #121 Allow for multiple parameters in templated methods by replacing preg_match with preg_match_all + #134 Add support for compressed remote images ++ #129 Allow to change remote media download timeout 2.7.2 (2015-09-23) - #135 Invalid HTTP request headers in non-cURL mode From fbef2e86725aa76ec6af6631c8b1d36bcc23097d Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 13:07:58 +0200 Subject: [PATCH 030/148] Fix bug introduced in 8891435 --- src/codebird.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codebird.php b/src/codebird.php index 0f6ec92..56f8c02 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1324,7 +1324,7 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par 'http' => [ 'method' => 'GET', 'protocol_version' => '1.1', - 'timeout' => $this->_remote_download_timeout + 'timeout' => $this->_remoteDownloadTimeout ], 'ssl' => [ 'verify_peer' => false From 27803440d5cdca49ec059104e7a739982ebdd440 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 21:41:00 +0100 Subject: [PATCH 031/148] Support Collections API Fix #144. --- CHANGELOG | 1 + README.md | 28 ++++++++++++++++++++++++++++ src/codebird.php | 38 ++++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d11928d..523d242 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ codebird-php - changelog by replacing preg_match with preg_match_all + #134 Add support for compressed remote images + #129 Allow to change remote media download timeout ++ #144 Support Collections API 2.7.2 (2015-09-23) - #135 Invalid HTTP request headers in non-cURL mode diff --git a/README.md b/README.md index 0ac3a54..8502a73 100644 --- a/README.md +++ b/README.md @@ -667,3 +667,31 @@ You may also use an authenticated proxy. Use the following call: $cb->setProxy('', ''); $cb->setProxyAuthentication(':'); ``` + +### …access the Collections API? + +Collections are a type of timeline that you control and can be hand curated +and/or programmed using an API. + +Pay close attention to the differences in how collections are presented — +often they will be decomposed, efficient objects with information about users, +Tweets, and timelines grouped, simplified, and stripped of unnecessary repetition. + +Never care about the OAuth signing specialities and the JSON POST body +for POST collections/entries/curate.json. Codebird takes off the work for you +and will always send the correct Content-Type automatically. + +Find out more about the [Collections API](https://dev.twitter.com/rest/collections/about) in the Twitter API docs. + +Here’s a sample for adding a tweet using that API method: + +```php +$reply = $cb->collections_entries_curate([ + "id" => "custom-672852634622144512", + "changes" => [ + ["op" => "add", "tweet_id" => "672727928262828032"] + ] +]); + +var_dump($reply); +``` diff --git a/src/codebird.php b/src/codebird.php index 56f8c02..f464b9f 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -352,6 +352,9 @@ public function getApiMethods() 'application/rate_limit_status', 'blocks/ids', 'blocks/list', + 'collections/entries', + 'collections/list', + 'collections/show', 'direct_messages', 'direct_messages/sent', 'direct_messages/show', @@ -426,6 +429,13 @@ public function getApiMethods() 'account/update_profile_image', 'blocks/create', 'blocks/destroy', + 'collections/create', + 'collections/destroy', + 'collections/entries/add', + 'collections/entries/curate', + 'collections/entries/move', + 'collections/entries/remove', + 'collections/update', 'direct_messages/destroy', 'direct_messages/new', 'favorites/create', @@ -1400,6 +1410,20 @@ protected function _detectMedia($method) { return in_array($method, $medias); } + /** + * Detects if API call should use JSON body + * + * @param string $method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + protected function _detectJsonBody($method) { + $json_bodies = [ + 'collections/entries/curate' + ]; + return in_array($method, $json_bodies); + } + /** * Detects if API call should use streaming endpoint, and if yes, which one * @@ -1662,18 +1686,20 @@ protected function _callApiPreparationsPost( $authorization = $this->_sign($httpmethod, $url, []); } $params = $this->_buildMultipart($method, $params); + $first_newline = strpos($params, "\r\n"); + $multipart_boundary = substr($params, 2, $first_newline - 2); + $request_headers[] = 'Content-Type: multipart/form-data; boundary=' + . $multipart_boundary; + } elseif ($this->_detectJsonBody($method)) { + $authorization = $this->_sign($httpmethod, $url, []); + $params = json_encode($params); + $request_headers[] = 'Content-Type: application/json'; } else { if (! $app_only_auth) { $authorization = $this->_sign($httpmethod, $url, $params); } $params = http_build_query($params); } - if ($multipart) { - $first_newline = strpos($params, "\r\n"); - $multipart_boundary = substr($params, 2, $first_newline - 2); - $request_headers[] = 'Content-Type: multipart/form-data; boundary=' - . $multipart_boundary; - } return [$authorization, $params, $request_headers]; } From d5c79b3f2321c7dc8d425a2a1ebab11f9e2ae4e4 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 13:42:33 +0100 Subject: [PATCH 032/148] Support TON API Fix #145. This would need to be tested whether it really works. --- CHANGELOG | 1 + README.md | 91 ++++++++++++++++- src/codebird.php | 249 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 283 insertions(+), 58 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 523d242..6967394 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ codebird-php - changelog + #134 Add support for compressed remote images + #129 Allow to change remote media download timeout + #144 Support Collections API ++ #145 Support TON API 2.7.2 (2015-09-23) - #135 Invalid HTTP request headers in non-cURL mode diff --git a/README.md b/README.md index 8502a73..722fadb 100644 --- a/README.md +++ b/README.md @@ -687,11 +687,96 @@ Here’s a sample for adding a tweet using that API method: ```php $reply = $cb->collections_entries_curate([ - "id" => "custom-672852634622144512", - "changes" => [ - ["op" => "add", "tweet_id" => "672727928262828032"] + 'id' => 'custom-672852634622144512', + 'changes' => [ + ['op' => 'add', 'tweet_id' => '672727928262828032'] ] ]); var_dump($reply); ``` + +### …access the TON API? + +The [TON (Twitter Object Nest) API](https://dev.twitter.com/rest/ton) allows implementers to upload media and various assets to Twitter. +The TON API supports non-resumable and resumable upload methods based on the size of the file. +For files less than 64MB, non-resumable may be used. For files greater than or equal to 64MB, +resumable must be used. Resumable uploads require chunk sizes of less than 64MB. + +For accessing the TON API, please adapt the following code samples for uploading: + +#### Single-chunk upload + +```php +// single-chunk upload + +$reply = $cb->ton_bucket_BUCKET([ + 'bucket' => 'ta_partner', + 'Content-Type' => 'image/jpeg', + 'media' => $file +]); + +var_dump($reply); + +// use the Location header now... +echo $reply->Location; +``` + +As you see from that sample, Codebird rewrites the special TON API headers into the reply, +so you can easily access them. This also applies to the `X-TON-Min-Chunk-Size` and +`X-Ton-Max-Chunk-Size` for chunked uploads: + +#### Multi-chunk upload + +```php +// multi-chunk upload +$file = 'demo-video.mp4'; +$size_bytes = filesize($file); +$fp = fopen($file, 'r'); + +// INIT the upload + +$reply = $cb->__call( + 'ton/bucket/BUCKET?resumable=true', + [[ // note the double square braces when using __call + 'bucket' => 'ta_partner', + 'Content-Type' => 'video/mp4', + 'X-Ton-Content-Type' => 'video/mp4', + 'X-Ton-Content-Length' => $size_bytes + ]] +); + +$target = $reply->Location; +// something like: '/1.1/ton/bucket/ta_partner/SzFxGfAg_Zj.mp4?resumable=true&resumeId=28401873' +$match = []; + +// match the location parts +preg_match('/ton\/bucket\/.+\/(.+)\?resumable=true&resumeId=(\d+)/', $target, $match); +list ($target, $file, $resumeId) = $match; + +// APPEND data to the upload + +$segment_id = 0; + +while (! feof($fp)) { + $chunk = fread($fp, 1048576); // 1MB per chunk for this sample + + // special way to call Codebird for the upload chunks + $reply = $cb->__call( + 'ton/bucket/BUCKET/FILE?resumable=true&resumeId=RESUMEID', + [[ // note the double square braces when using __call + 'bucket' => 'ta_partner', + 'file' => $file, // you get real filename from INIT, see above + 'Content-Type' => 'image/jpeg', + 'Content-Range' => 'bytes ' + . ($segment_id * 1048576) . '-' . strlen($chunk) . '/' . $size_bytes, + 'resumeId' => $resumeId, + 'media' => $chunk + ]] + ); + + $segment_id++; +} + +fclose($fp); +``` diff --git a/src/codebird.php b/src/codebird.php index f464b9f..1840bd6 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -83,6 +83,10 @@ class Codebird ]; /** + * The TON API endpoint to use + */ + protected static $_endpoint_ton = 'https://ton.twitter.com/1.1/'; + * The API endpoint base to use */ protected static $_endpoint_oauth = 'https://api.twitter.com/'; @@ -467,8 +471,13 @@ public function getApiMethods() 'statuses/retweet/:id', 'statuses/update', 'statuses/update_with_media', // deprecated, use media/upload + 'ton/bucket/:bucket', + 'ton/bucket/:bucket?resumable=true', 'users/lookup', 'users/report_spam' + ], + 'PUT' => [ + 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' ] ]; return $httpmethods; @@ -512,6 +521,7 @@ public function __call($fn, $params) return $this->_callApi( $httpmethod, $method, + $method_template, $apiparams, $multipart, $app_only_auth @@ -596,9 +606,11 @@ protected function _mapFnToApiMethod($fn, &$apiparams) $method_template = $method; $match = []; if (preg_match_all('/[A-Z_]{2,}/', $method, $match)) { - foreach ($match as $param) { - $param = $param[0]; + foreach ($match[0] as $param) { $param_l = strtolower($param); + if ($param_l === 'resumeid') { + $param_l = 'resumeId'; + } $method_template = str_replace($param, ':' . $param_l, $method_template); if (! isset($apiparams[$param_l])) { for ($i = 0; $i < 26; $i++) { @@ -614,10 +626,12 @@ protected function _mapFnToApiMethod($fn, &$apiparams) } } - // replace A-Z by _a-z - for ($i = 0; $i < 26; $i++) { - $method = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method); - $method_template = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method_template); + if (substr($method, 0, 4) !== 'ton/') { + // replace A-Z by _a-z + for ($i = 0; $i < 26; $i++) { + $method = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method); + $method_template = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method_template); + } } return [$method, $method_template]; @@ -1310,40 +1324,9 @@ protected function _getMultipartRequestFromParams($possible_files, $border, $par filter_var($value, FILTER_VALIDATE_URL) && preg_match('/^https?:\/\//', $value) ) { - // try to fetch the file - if ($this->_use_curl) { - $ch = $this->getCurlInitialization($value); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, 0); - // no SSL validation for downloading media - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); - // use hardcoded download timeouts for now - curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_remoteDownloadTimeout); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_remoteDownloadTimeout / 2); - // find files that have been redirected - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - // process compressed images - curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate,sdch'); - $result = curl_exec($ch); - if ($result !== false) { - $value = $result; - } - } else { - $contextOptions = [ - 'http' => [ - 'method' => 'GET', - 'protocol_version' => '1.1', - 'timeout' => $this->_remoteDownloadTimeout - ], - 'ssl' => [ - 'verify_peer' => false - ] - ]; - list($result) = $this->getNoCurlInitialization($value, $contextOptions); - if ($result !== false) { - $value = $result; - } + $data = $this->_fetchRemoteFile($value); + if ($data !== false) { + $value = $data; } } } @@ -1396,6 +1379,84 @@ protected function _buildMultipart($method, $params) return $multipart_request; } + /** + * Detect filenames in upload parameters + * + * @param mixed $input The data or file name to parse + * + * @return null|string + */ + protected function _buildBinaryBody($input) + { + if (// is it a file, a readable one? + @file_exists($input) + && @is_readable($input) + ) { + // try to read the file + $data = @file_get_contents($input); + if ($data !== false && strlen($data) !== 0) { + return $data; + } + } elseif (// is it a remote file? + filter_var($input, FILTER_VALIDATE_URL) + && preg_match('/^https?:\/\//', $input) + ) { + $data = $this->_fetchRemoteFile($input); + if ($data !== false) { + return $data; + } + } + return $input; + } + + /** + * Fetches a remote file + * + * @param string $url The URL to download from + * + * @return mixed The file contents or FALSE + */ + protected function _fetchRemoteFile($url) + { + // try to fetch the file + if ($this->_use_curl) { + $ch = $this->getCurlInitialization($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + // no SSL validation for downloading media + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + // use hardcoded download timeouts for now + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_remoteDownloadTimeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_remoteDownloadTimeout / 2); + // find files that have been redirected + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + // process compressed images + curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate,sdch'); + $result = curl_exec($ch); + if ($result !== false) { + return $result; + } + return false; + } + // no cURL + $contextOptions = [ + 'http' => [ + 'method' => 'GET', + 'protocol_version' => '1.1', + 'timeout' => $this->_remoteDownloadTimeout + ], + 'ssl' => [ + 'verify_peer' => false + ] + ]; + list($result) = $this->getNoCurlInitialization($url, $contextOptions); + if ($result !== false) { + return $result; + } + return false; + } + /** * Detects if API call should use media endpoint * @@ -1424,6 +1485,22 @@ protected function _detectJsonBody($method) { return in_array($method, $json_bodies); } + /** + * Detects if API call should use binary body + * + * @param string $method_template The API method to call + * + * @return bool Whether the method is defined as accepting binary body + */ + protected function _detectBinaryBody($method_template) { + $binary = [ + 'ton/bucket/:bucket', + 'ton/bucket/:bucket?resumable=true', + 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' + ]; + return in_array($method_template, $binary); + } + /** * Detects if API call should use streaming endpoint, and if yes, which one * @@ -1454,17 +1531,20 @@ protected function _detectStreaming($method) { * Builds the complete API endpoint url * * @param string $method The API method to call + * @param string $method_template The API method to call * * @return string The URL to send the request to */ - protected function _getEndpoint($method) + protected function _getEndpoint($method, $method_template) { - if (substr($method, 0, 5) === 'oauth') { + if (substr($method_template, 0, 5) === 'oauth') { $url = self::$_endpoint_oauth . $method; - } elseif ($this->_detectMedia($method)) { + } elseif ($this->_detectMedia($method_template)) { $url = self::$_endpoint_media . $method . '.json'; - } elseif ($variant = $this->_detectStreaming($method)) { + } elseif ($variant = $this->_detectStreaming($method_template)) { $url = self::$_endpoints_streaming[$variant] . $method . '.json'; + } elseif ($variant = $this->_detectBinaryBody($method_template)) { + $url = self::$_endpoint_ton . $method; } else { $url = self::$_endpoint . $method . '.json'; } @@ -1476,6 +1556,7 @@ protected function _getEndpoint($method) * * @param string $httpmethod The HTTP method to use for making the request * @param string $method The API method to call + * @param string $method_template The API method template to call * @param array optional $params The parameters to send along * @param bool optional $multipart Whether to use multipart/form-data * @param bool optional $app_only_auth Whether to use app-only bearer authentication @@ -1483,7 +1564,7 @@ protected function _getEndpoint($method) * @return string The API reply, encoded in the set return_format */ - protected function _callApi($httpmethod, $method, $params = [], $multipart = false, $app_only_auth = false) + protected function _callApi($httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false) { if (! $app_only_auth && $this->_oauth_token === null @@ -1497,9 +1578,9 @@ protected function _callApi($httpmethod, $method, $params = [], $multipart = fal } if ($this->_use_curl) { - return $this->_callApiCurl($httpmethod, $method, $params, $multipart, $app_only_auth); + return $this->_callApiCurl($httpmethod, $method, $method_template, $params, $multipart, $app_only_auth); } - return $this->_callApiNoCurl($httpmethod, $method, $params, $multipart, $app_only_auth); + return $this->_callApiNoCurl($httpmethod, $method, $method_template, $params, $multipart, $app_only_auth); } /** @@ -1507,6 +1588,7 @@ protected function _callApi($httpmethod, $method, $params = [], $multipart = fal * * @param string $httpmethod The HTTP method to use for making the request * @param string $method The API method to call + * @param string $method_template The API method template to call * @param array optional $params The parameters to send along * @param bool optional $multipart Whether to use multipart/form-data * @param bool optional $app_only_auth Whether to use app-only bearer authentication @@ -1515,12 +1597,12 @@ protected function _callApi($httpmethod, $method, $params = [], $multipart = fal */ protected function _callApiCurl( - $httpmethod, $method, $params = [], $multipart = false, $app_only_auth = false + $httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false ) { list ($authorization, $url, $params, $request_headers) = $this->_callApiPreparations( - $httpmethod, $method, $params, $multipart, $app_only_auth + $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth ); $ch = $this->getCurlInitialization($url); @@ -1530,6 +1612,9 @@ protected function _callApiCurl( if ($httpmethod !== 'GET') { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + if ($httpmethod === 'PUT') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + } } curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); @@ -1555,6 +1640,8 @@ protected function _callApiCurl( $httpstatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); list($headers, $reply) = $this->_parseApiHeaders($result); + // TON API & redirects + $reply = $this->_parseApiReplyPrefillHeaders($headers, $reply); $reply = $this->_parseApiReply($reply); $rate = $this->_getRateLimitInfo($headers); @@ -1576,6 +1663,7 @@ protected function _callApiCurl( * * @param string $httpmethod The HTTP method to use for making the request * @param string $method The API method to call + * @param string $method_template The API method template to call * @param array optional $params The parameters to send along * @param bool optional $multipart Whether to use multipart/form-data * @param bool optional $app_only_auth Whether to use app-only bearer authentication @@ -1584,12 +1672,12 @@ protected function _callApiCurl( */ protected function _callApiNoCurl( - $httpmethod, $method, $params = [], $multipart = false, $app_only_auth = false + $httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false ) { list ($authorization, $url, $params, $request_headers) = $this->_callApiPreparations( - $httpmethod, $method, $params, $multipart, $app_only_auth + $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth ); $hostname = parse_url($url, PHP_URL_HOST); @@ -1630,6 +1718,8 @@ protected function _callApiNoCurl( } list($headers, $reply) = $this->_parseApiHeaders($result); + // TON API & redirects + $reply = $this->_parseApiReplyPrefillHeaders($headers, $reply); $reply = $this->_parseApiReply($reply); $rate = $this->_getRateLimitInfo($headers); switch ($this->_return_format) { @@ -1670,6 +1760,7 @@ protected function _callApiPreparationsGet( * @param string $httpmethod The HTTP method to use for making the request * @param string $url The URL to call * @param string $method The API method to call + * @param string $method_template The API method template to call * @param array $params The parameters to send along * @param bool $multipart Whether to use multipart/form-data * @param bool $app_only_auth Whether to use app-only bearer authentication @@ -1677,7 +1768,7 @@ protected function _callApiPreparationsGet( * @return array (string authorization, array params, array request_headers) */ protected function _callApiPreparationsPost( - $httpmethod, $url, $method, $params, $multipart, $app_only_auth + $httpmethod, $url, $method, $method_template, $params, $multipart, $app_only_auth ) { $authorization = null; $request_headers = []; @@ -1694,6 +1785,26 @@ protected function _callApiPreparationsPost( $authorization = $this->_sign($httpmethod, $url, []); $params = json_encode($params); $request_headers[] = 'Content-Type: application/json'; + } elseif ($this->_detectBinaryBody($method_template)) { + // transform parametric headers to real headers + foreach ([ + 'Content-Type', 'X-TON-Content-Type', + 'X-TON-Content-Length', 'Content-Range' + ] as $key) { + if (isset($params[$key])) { + $request_headers[] = $key . ': ' . $params[$key]; + unset($params[$key]); + } + } + $sign_params = []; + parse_str(parse_url($method, PHP_URL_QUERY), $sign_params); + $authorization = $this->_sign($httpmethod, $url, $sign_params); + if (isset($params['media'])) { + $params = $this->_buildBinaryBody($params['media']); + } else { + // resumable upload + $params = []; + } } else { if (! $app_only_auth) { $authorization = $this->_sign($httpmethod, $url, $params); @@ -1727,6 +1838,7 @@ protected function _getBearerAuthorization() * * @param string $httpmethod The HTTP method to use for making the request * @param string $method The API method to call + * @param string $method_template The API method template to call * @param array $params The parameters to send along * @param bool $multipart Whether to use multipart/form-data * @param bool $app_only_auth Whether to use app-only bearer authentication @@ -1734,10 +1846,10 @@ protected function _getBearerAuthorization() * @return array (string authorization, string url, array params, array request_headers) */ protected function _callApiPreparations( - $httpmethod, $method, $params, $multipart, $app_only_auth + $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth ) { - $url = $this->_getEndpoint($method); + $url = $this->_getEndpoint($method, $method_template); $request_headers = []; if ($httpmethod === 'GET') { // GET @@ -1746,7 +1858,7 @@ protected function _callApiPreparations( } else { // POST list ($authorization, $params, $request_headers) = - $this->_callApiPreparationsPost($httpmethod, $url, $method, $params, $multipart, $app_only_auth); + $this->_callApiPreparationsPost($httpmethod, $url, $method, $method_template, $params, $multipart, $app_only_auth); } if ($app_only_auth) { $authorization = $this->_getBearerAuthorization(); @@ -1986,6 +2098,33 @@ protected function _parseApiHeaders($reply) { return [$headers, $reply]; } + /** + * Parses the API headers to return Location and Ton API headers + * + * @param array $headers The headers list + * @param string $reply The actual HTTP body + * + * @return string $reply + */ + protected function _parseApiReplyPrefillHeaders($headers, $reply) + { + if ($reply === '' && (isset($headers['Location']))) { + $reply = [ + 'Location' => $headers['Location'] + ]; + if (isset($headers['X-TON-Min-Chunk-Size'])) { + $reply['X-TON-Min-Chunk-Size'] = $headers['X-TON-Min-Chunk-Size']; + } + if (isset($headers['X-TON-Max-Chunk-Size'])) { + $reply['X-TON-Max-Chunk-Size'] = $headers['X-TON-Max-Chunk-Size']; + } + if (isset($headers['Range'])) { + $reply['Range'] = $headers['Range']; + } + } + return json_encode($reply); + } + /** * Parses the API reply to encode it in the set return_format * From fd3b0fe4f403e8649092f32f6f0abfc8b446c51a Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 16:31:14 +0100 Subject: [PATCH 033/148] Support Ads API Fix #120. --- CHANGELOG | 1 + README.md | 42 +++++ src/codebird.php | 394 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 423 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6967394..94ccd97 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ codebird-php - changelog + #129 Allow to change remote media download timeout + #144 Support Collections API + #145 Support TON API ++ #120 Support Ads API 2.7.2 (2015-09-23) - #135 Invalid HTTP request headers in non-cURL mode diff --git a/README.md b/README.md index 722fadb..09b19a2 100644 --- a/README.md +++ b/README.md @@ -780,3 +780,45 @@ while (! feof($fp)) { fclose($fp); ``` + +### …access the Twitter Ads API? + +The [Twitter Ads API](https://dev.twitter.com/ads/overview) allows partners to +integrate with the Twitter advertising platform in their own advertising solutions. +Selected partners have the ability to create custom tools to manage and execute +Twitter Ad campaigns. + +When accessing the Ads API or Ads Sandbox API, access it by prefixing your call +with `ads_`. Watch out for the usual replacements for in-url parameters, +particularly `:account_id`. + +**Tip:** For accessing the Ads Sandbox API, use the `ads_sandbox_` prefix, +like shown further down. + +Here is an example for calling the Twitter Ads API: + +```php +$reply = $cb->ads_accounts_ACCOUNT_ID_cards_appDownload([ + 'account_id' => '123456789', + 'name' => 'Test', + 'app_country_code' => 'DE' +]); +``` + +#### Multiple-method API calls + +In the Twitter Ads API, there are multiple methods that can be reached by +HTTP `GET`, `POST`, `PUT` and/or `DELETE`. While Codebird does its best to guess +which HTTP verb you’ll want to use, it’s the safest bet to give a hint yourself, +like this: + +```php +$reply = $cb->ads_sandbox_accounts_ACCOUNT_ID_cards_imageConversation_CARD_ID([ + 'httpmethod' => 'DELETE', + 'account_id' => '123456789', + 'card_id' => '2468013579' +]); +``` + +Codebird will remove the `httpmethod` parameter from the parameters list automatically, +and set the corresponding HTTP verb. diff --git a/src/codebird.php b/src/codebird.php index 1840bd6..ccc7eb5 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -87,6 +87,17 @@ class Codebird */ protected static $_endpoint_ton = 'https://ton.twitter.com/1.1/'; + /** + * The Ads API endpoint to use + */ + protected static $_endpoint_ads = 'https://ads-api.twitter.com/0/'; + + /** + * The Ads Sandbox API endpoint to use + */ + protected static $_endpoint_ads_sandbox = 'https://ads-api-sandbox.twitter.com/0/'; + + /** * The API endpoint base to use */ protected static $_endpoint_oauth = 'https://api.twitter.com/'; @@ -343,7 +354,7 @@ public function setStreamingCallback($callback) /** * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method "account/settings"! + * Watch out for multiple-method API methods! * * @return array $apimethods */ @@ -353,6 +364,164 @@ public function getApiMethods() 'GET' => [ 'account/settings', 'account/verify_credentials', + 'ads/accounts', + 'ads/accounts/:account_id', + 'ads/accounts/:account_id/app_event_provider_configurations', + 'ads/accounts/:account_id/app_event_provider_configurations/:id', + 'ads/accounts/:account_id/app_event_tags', + 'ads/accounts/:account_id/app_event_tags/:id', + 'ads/accounts/:account_id/app_lists', + 'ads/accounts/:account_id/authenticated_user_access', + 'ads/accounts/:account_id/campaigns', + 'ads/accounts/:account_id/campaigns/:campaign_id', + 'ads/accounts/:account_id/cards/app_download', + 'ads/accounts/:account_id/cards/app_download/:card_id', + 'ads/accounts/:account_id/cards/image_app_download', + 'ads/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/accounts/:account_id/cards/image_conversation', + 'ads/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/accounts/:account_id/cards/lead_gen', + 'ads/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/accounts/:account_id/cards/video_app_download', + 'ads/accounts/:account_id/cards/video_app_download/:id', + 'ads/accounts/:account_id/cards/video_conversation', + 'ads/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/accounts/:account_id/cards/website', + 'ads/accounts/:account_id/cards/website/:card_id', + 'ads/accounts/:account_id/features', + 'ads/accounts/:account_id/funding_instruments', + 'ads/accounts/:account_id/funding_instruments/:id', + 'ads/accounts/:account_id/line_items', + 'ads/accounts/:account_id/line_items/:line_item_id', + 'ads/accounts/:account_id/promotable_users', + 'ads/accounts/:account_id/promoted_accounts', + 'ads/accounts/:account_id/promoted_tweets', + 'ads/accounts/:account_id/reach_estimate', + 'ads/accounts/:account_id/scoped_timeline', + 'ads/accounts/:account_id/tailored_audience_changes', + 'ads/accounts/:account_id/tailored_audience_changes/:id', + 'ads/accounts/:account_id/tailored_audiences', + 'ads/accounts/:account_id/tailored_audiences/:id', + 'ads/accounts/:account_id/targeting_criteria', + 'ads/accounts/:account_id/targeting_criteria/:id', + 'ads/accounts/:account_id/targeting_suggestions', + 'ads/accounts/:account_id/tweet/preview', + 'ads/accounts/:account_id/tweet/preview/:tweet_id', + 'ads/accounts/:account_id/videos', + 'ads/accounts/:account_id/videos/:id', + 'ads/accounts/:account_id/web_event_tags', + 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/bidding_rules', + 'ads/iab_categories', + 'ads/insights/accounts/:account_id', + 'ads/insights/accounts/:account_id/available_audiences', + 'ads/line_items/placements', + 'ads/sandbox/accounts', + 'ads/sandbox/accounts/:account_id', + 'ads/sandbox/accounts/:account_id/app_event_provider_configurations', + 'ads/sandbox/accounts/:account_id/app_event_provider_configurations/:id', + 'ads/sandbox/accounts/:account_id/app_event_tags', + 'ads/sandbox/accounts/:account_id/app_event_tags/:id', + 'ads/sandbox/accounts/:account_id/app_lists', + 'ads/sandbox/accounts/:account_id/authenticated_user_access', + 'ads/sandbox/accounts/:account_id/campaigns', + 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', + 'ads/sandbox/accounts/:account_id/cards/app_download', + 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_app_download', + 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_conversation', + 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/lead_gen', + 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/sandbox/accounts/:account_id/cards/video_app_download', + 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', + 'ads/sandbox/accounts/:account_id/cards/video_conversation', + 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/website', + 'ads/sandbox/accounts/:account_id/cards/website/:card_id', + 'ads/sandbox/accounts/:account_id/features', + 'ads/sandbox/accounts/:account_id/funding_instruments', + 'ads/sandbox/accounts/:account_id/funding_instruments/:id', + 'ads/sandbox/accounts/:account_id/line_items', + 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', + 'ads/sandbox/accounts/:account_id/promotable_users', + 'ads/sandbox/accounts/:account_id/promoted_accounts', + 'ads/sandbox/accounts/:account_id/promoted_tweets', + 'ads/sandbox/accounts/:account_id/reach_estimate', + 'ads/sandbox/accounts/:account_id/scoped_timeline', + 'ads/sandbox/accounts/:account_id/tailored_audience_changes', + 'ads/sandbox/accounts/:account_id/tailored_audience_changes/:id', + 'ads/sandbox/accounts/:account_id/tailored_audiences', + 'ads/sandbox/accounts/:account_id/tailored_audiences/:id', + 'ads/sandbox/accounts/:account_id/targeting_criteria', + 'ads/sandbox/accounts/:account_id/targeting_criteria/:id', + 'ads/sandbox/accounts/:account_id/targeting_suggestions', + 'ads/sandbox/accounts/:account_id/tweet/preview', + 'ads/sandbox/accounts/:account_id/tweet/preview/:tweet_id', + 'ads/sandbox/accounts/:account_id/videos', + 'ads/sandbox/accounts/:account_id/videos/:id', + 'ads/sandbox/accounts/:account_id/web_event_tags', + 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/sandbox/bidding_rules', + 'ads/sandbox/iab_categories', + 'ads/sandbox/insights/accounts/:account_id', + 'ads/sandbox/insights/accounts/:account_id/available_audiences', + 'ads/sandbox/line_items/placements', + 'ads/sandbox/stats/accounts/:account_id', + 'ads/sandbox/stats/accounts/:account_id/campaigns', + 'ads/sandbox/stats/accounts/:account_id/campaigns/:id', + 'ads/sandbox/stats/accounts/:account_id/funding_instruments', + 'ads/sandbox/stats/accounts/:account_id/funding_instruments/:id', + 'ads/sandbox/stats/accounts/:account_id/line_items', + 'ads/sandbox/stats/accounts/:account_id/line_items/:id', + 'ads/sandbox/stats/accounts/:account_id/promoted_accounts', + 'ads/sandbox/stats/accounts/:account_id/promoted_accounts/:id', + 'ads/sandbox/stats/accounts/:account_id/promoted_tweets', + 'ads/sandbox/stats/accounts/:account_id/promoted_tweets/:id', + 'ads/sandbox/stats/accounts/:account_id/reach/campaigns', + 'ads/sandbox/targeting_criteria/app_store_categories', + 'ads/sandbox/targeting_criteria/behavior_taxonomies', + 'ads/sandbox/targeting_criteria/behaviors', + 'ads/sandbox/targeting_criteria/devices', + 'ads/sandbox/targeting_criteria/events', + 'ads/sandbox/targeting_criteria/interests', + 'ads/sandbox/targeting_criteria/languages', + 'ads/sandbox/targeting_criteria/locations', + 'ads/sandbox/targeting_criteria/network_operators', + 'ads/sandbox/targeting_criteria/platform_versions', + 'ads/sandbox/targeting_criteria/platforms', + 'ads/sandbox/targeting_criteria/tv_channels', + 'ads/sandbox/targeting_criteria/tv_genres', + 'ads/sandbox/targeting_criteria/tv_markets', + 'ads/sandbox/targeting_criteria/tv_shows', + 'ads/stats/accounts/:account_id', + 'ads/stats/accounts/:account_id/campaigns', + 'ads/stats/accounts/:account_id/campaigns/:id', + 'ads/stats/accounts/:account_id/funding_instruments', + 'ads/stats/accounts/:account_id/funding_instruments/:id', + 'ads/stats/accounts/:account_id/line_items', + 'ads/stats/accounts/:account_id/line_items/:id', + 'ads/stats/accounts/:account_id/promoted_accounts', + 'ads/stats/accounts/:account_id/promoted_accounts/:id', + 'ads/stats/accounts/:account_id/promoted_tweets', + 'ads/stats/accounts/:account_id/promoted_tweets/:id', + 'ads/stats/accounts/:account_id/reach/campaigns', + 'ads/targeting_criteria/app_store_categories', + 'ads/targeting_criteria/behavior_taxonomies', + 'ads/targeting_criteria/behaviors', + 'ads/targeting_criteria/devices', + 'ads/targeting_criteria/events', + 'ads/targeting_criteria/interests', + 'ads/targeting_criteria/languages', + 'ads/targeting_criteria/locations', + 'ads/targeting_criteria/network_operators', + 'ads/targeting_criteria/platform_versions', + 'ads/targeting_criteria/platforms', + 'ads/targeting_criteria/tv_channels', + 'ads/targeting_criteria/tv_genres', + 'ads/targeting_criteria/tv_markets', + 'ads/targeting_criteria/tv_shows', 'application/rate_limit_status', 'blocks/ids', 'blocks/list', @@ -424,13 +593,53 @@ public function getApiMethods() ], 'POST' => [ 'account/remove_profile_banner', - 'account/settings__post', + 'account/settings', 'account/update_delivery_device', 'account/update_profile', 'account/update_profile_background_image', 'account/update_profile_banner', 'account/update_profile_colors', 'account/update_profile_image', + 'ads/accounts/:account_id/app_lists', + 'ads/accounts/:account_id/campaigns', + 'ads/accounts/:account_id/cards/app_download', + 'ads/accounts/:account_id/cards/image_app_download', + 'ads/accounts/:account_id/cards/image_conversation', + 'ads/accounts/:account_id/cards/lead_gen', + 'ads/accounts/:account_id/cards/video_app_download', + 'ads/accounts/:account_id/cards/video_conversation', + 'ads/accounts/:account_id/cards/website', + 'ads/accounts/:account_id/line_items', + 'ads/accounts/:account_id/promoted_accounts', + 'ads/accounts/:account_id/promoted_tweets', + 'ads/accounts/:account_id/tailored_audience_changes', + 'ads/accounts/:account_id/tailored_audiences', + 'ads/accounts/:account_id/targeting_criteria', + 'ads/accounts/:account_id/tweet', + 'ads/accounts/:account_id/videos', + 'ads/accounts/:account_id/web_event_tags', + 'ads/batch/accounts/:account_id/campaigns', + 'ads/batch/accounts/:account_id/line_items', + 'ads/sandbox/accounts/:account_id/app_lists', + 'ads/sandbox/accounts/:account_id/campaigns', + 'ads/sandbox/accounts/:account_id/cards/app_download', + 'ads/sandbox/accounts/:account_id/cards/image_app_download', + 'ads/sandbox/accounts/:account_id/cards/image_conversation', + 'ads/sandbox/accounts/:account_id/cards/lead_gen', + 'ads/sandbox/accounts/:account_id/cards/video_app_download', + 'ads/sandbox/accounts/:account_id/cards/video_conversation', + 'ads/sandbox/accounts/:account_id/cards/website', + 'ads/sandbox/accounts/:account_id/line_items', + 'ads/sandbox/accounts/:account_id/promoted_accounts', + 'ads/sandbox/accounts/:account_id/promoted_tweets', + 'ads/sandbox/accounts/:account_id/tailored_audience_changes', + 'ads/sandbox/accounts/:account_id/tailored_audiences', + 'ads/sandbox/accounts/:account_id/targeting_criteria', + 'ads/sandbox/accounts/:account_id/tweet', + 'ads/sandbox/accounts/:account_id/videos', + 'ads/sandbox/accounts/:account_id/web_event_tags', + 'ads/sandbox/batch/accounts/:account_id/campaigns', + 'ads/sandbox/batch/accounts/:account_id/line_items', 'blocks/create', 'blocks/destroy', 'collections/create', @@ -477,7 +686,65 @@ public function getApiMethods() 'users/report_spam' ], 'PUT' => [ + 'ads/accounts/:account_id/campaigns/:campaign_id', + 'ads/accounts/:account_id/cards/app_download/:card_id', + 'ads/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/accounts/:account_id/cards/video_app_download/:id', + 'ads/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/accounts/:account_id/cards/website/:card_id', + 'ads/accounts/:account_id/line_items/:line_item_id', + 'ads/accounts/:account_id/promoted_tweets/:id', + 'ads/accounts/:account_id/tailored_audiences/global_opt_out', + 'ads/accounts/:account_id/targeting_criteria', + 'ads/accounts/:account_id/videos/:id', + 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', + 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', + 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/website/:card_id', + 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', + 'ads/sandbox/accounts/:account_id/promoted_tweets/:id', + 'ads/sandbox/accounts/:account_id/tailored_audiences/global_opt_out', + 'ads/sandbox/accounts/:account_id/targeting_criteria', + 'ads/sandbox/accounts/:account_id/videos/:id', + 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id', 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' + ], + 'DELETE' => [ + 'ads/accounts/:account_id/campaigns/:campaign_id', + 'ads/accounts/:account_id/cards/app_download/:card_id', + 'ads/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/accounts/:account_id/cards/video_app_download/:id', + 'ads/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/accounts/:account_id/cards/website/:card_id', + 'ads/accounts/:account_id/line_items/:line_item_id', + 'ads/accounts/:account_id/promoted_tweets/:id', + 'ads/accounts/:account_id/tailored_audiences/:id', + 'ads/accounts/:account_id/targeting_criteria/:id', + 'ads/accounts/:account_id/videos/:id', + 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', + 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', + 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/website/:card_id', + 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', + 'ads/sandbox/accounts/:account_id/promoted_tweets/:id', + 'ads/sandbox/accounts/:account_id/tailored_audiences/:id', + 'ads/sandbox/accounts/:account_id/targeting_criteria/:id', + 'ads/sandbox/accounts/:account_id/videos/:id', + 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id' ] ]; return $httpmethods; @@ -661,7 +928,11 @@ protected function _mapFnInsertSlashes($fn) */ protected function _mapFnRestoreParamUnderscores($method) { - $url_parameters_with_underscore = ['screen_name', 'place_id']; + $url_parameters_with_underscore = [ + 'screen_name', 'place_id', + 'account_id', 'campaign_id', 'card_id', 'line_item_id', + 'tweet_id', 'web_event_tag_id' + ]; foreach ($url_parameters_with_underscore as $param) { $param = strtoupper($param); $replacement_was = str_replace('_', '/', $param); @@ -1236,21 +1507,112 @@ protected function _sign($httpmethod, $method, $params = [], $append_to_get = fa /** * Detects HTTP method to use for API call * - * @param string $method The API method to call - * @param array $params The parameters to send along + * @param string $method The API method to call + * @param array byref $params The parameters to send along * * @return string The HTTP method that should be used */ - protected function _detectMethod($method, $params) + protected function _detectMethod($method, &$params) { + if (isset($params['httpmethod'])) { + $httpmethod = $params['httpmethod']; + unset($params['httpmethod']); + return $httpmethod; + } + $apimethods = $this->getApiMethods(); + // multi-HTTP method endpoints switch ($method) { - case 'account/settings': - $method = count($params) > 0 ? $method . '__post' : $method; + case 'ads/accounts/:account_id/campaigns': + case 'ads/sandbox/accounts/:account_id/campaigns': + if (isset($params['funding_instrument_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/line_items': + case 'ads/sandbox/accounts/:account_id/line_items': + if (isset($params['campaign_id'])) { + return 'POST'; + } break; + case 'ads/accounts/:account_id/targeting_criteria': + case 'ads/sandbox/accounts/:account_id/targeting_criteria': + if (isset($params['targeting_value'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/app_lists': + case 'ads/accounts/:account_id/campaigns': + case 'ads/accounts/:account_id/cards/app_download': + case 'ads/accounts/:account_id/cards/image_app_download': + case 'ads/accounts/:account_id/cards/image_conversion': + case 'ads/accounts/:account_id/cards/lead_gen': + case 'ads/accounts/:account_id/cards/video_app_download': + case 'ads/accounts/:account_id/cards/video_conversation': + case 'ads/accounts/:account_id/cards/website': + case 'ads/accounts/:account_id/tailored_audiences': + case 'ads/accounts/:account_id/web_event_tags': + case 'ads/sandbox/accounts/:account_id/app_lists': + case 'ads/sandbox/accounts/:account_id/campaigns': + case 'ads/sandbox/accounts/:account_id/cards/app_download': + case 'ads/sandbox/accounts/:account_id/cards/image_app_download': + case 'ads/sandbox/accounts/:account_id/cards/image_conversion': + case 'ads/sandbox/accounts/:account_id/cards/lead_gen': + case 'ads/sandbox/accounts/:account_id/cards/video_app_download': + case 'ads/sandbox/accounts/:account_id/cards/video_conversation': + case 'ads/sandbox/accounts/:account_id/cards/website': + case 'ads/sandbox/accounts/:account_id/tailored_audiences': + case 'ads/sandbox/accounts/:account_id/web_event_tags': + if (isset($params['name'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/promoted_accounts': + case 'ads/sandbox/accounts/:account_id/promoted_accounts': + if (isset($params['user_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/promoted_tweets': + case 'ads/sandbox/accounts/:account_id/promoted_tweets': + if (isset($params['tweet_ids'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/videos': + case 'ads/sandbox/accounts/:account_id/videos': + if (isset($params['video_media_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/tailored_audience_changes': + case 'ads/sandbox/accounts/:account_id/tailored_audience_changes': + if (isset($params['tailored_audience_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/cards/image_conversation/:card_id': + case 'ads/accounts/:account_id/cards/video_conversation/:card_id': + case 'ads/accounts/:account_id/cards/website/:card_id': + case 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id': + case 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id': + case 'ads/sandbox/accounts/:account_id/cards/website/:card_id': + if (isset($params['name'])) { + return 'PUT'; + } + break; + default: + // prefer POST and PUT if parameters are set + if (count($params) > 0) { + if (isset($apimethods['POST'][$method])) { + return 'POST'; + } + if (isset($apimethods['PUT'][$method])) { + return 'PUT'; + } + } } - $apimethods = $this->getApiMethods(); foreach ($apimethods as $httpmethod => $methods) { if (in_array($method, $methods)) { return $httpmethod; @@ -1424,8 +1786,8 @@ protected function _fetchRemoteFile($url) curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); // no SSL validation for downloading media - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // use hardcoded download timeouts for now curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_remoteDownloadTimeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_remoteDownloadTimeout / 2); @@ -1545,6 +1907,10 @@ protected function _getEndpoint($method, $method_template) $url = self::$_endpoints_streaming[$variant] . $method . '.json'; } elseif ($variant = $this->_detectBinaryBody($method_template)) { $url = self::$_endpoint_ton . $method; + } elseif (substr($method_template, 0, 12) === 'ads/sandbox/') { + $url = self::$_endpoint_ads_sandbox . substr($method, 12); + } elseif (substr($method_template, 0, 4) === 'ads/') { + $url = self::$_endpoint_ads . substr($method, 4); } else { $url = self::$_endpoint . $method . '.json'; } @@ -1612,8 +1978,8 @@ protected function _callApiCurl( if ($httpmethod !== 'GET') { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); - if ($httpmethod === 'PUT') { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + if (in_array($httpmethod, ['POST', 'PUT', 'DELETE'])) { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpmethod); } } @@ -1698,7 +2064,7 @@ protected function _callApiNoCurl( 'protocol_version' => '1.1', 'header' => implode("\r\n", $request_headers), 'timeout' => $this->_timeout / 1000, - 'content' => $httpmethod === 'POST' ? $params : null, + 'content' => in_array($httpmethod, ['POST', 'PUT']) ? $params : null, 'ignore_errors' => true ] ]; From 41bd7d1a07c3012c8639bff723283cbe9d443d6c Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 17:04:44 +0100 Subject: [PATCH 034/148] Add WebP support, reindent to 2 spaces --- README.md | 254 +-- src/codebird.php | 4965 +++++++++++++++++++++++----------------------- 2 files changed, 2615 insertions(+), 2604 deletions(-) diff --git a/README.md b/README.md index 09b19a2..91dba5c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ along with this program. If not, see . ### Requirements -- PHP 5.4.0 or higher +- PHP 5.5.0 or higher - OpenSSL extension @@ -54,39 +54,39 @@ Or you authenticate, like this: session_start(); if (! isset($_SESSION['oauth_token'])) { - // get the request token - $reply = $cb->oauth_requestToken([ - 'oauth_callback' => 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] - ]); - - // store the token - $cb->setToken($reply->oauth_token, $reply->oauth_token_secret); - $_SESSION['oauth_token'] = $reply->oauth_token; - $_SESSION['oauth_token_secret'] = $reply->oauth_token_secret; - $_SESSION['oauth_verify'] = true; - - // redirect to auth website - $auth_url = $cb->oauth_authorize(); - header('Location: ' . $auth_url); - die(); + // get the request token + $reply = $cb->oauth_requestToken([ + 'oauth_callback' => 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] + ]); + + // store the token + $cb->setToken($reply->oauth_token, $reply->oauth_token_secret); + $_SESSION['oauth_token'] = $reply->oauth_token; + $_SESSION['oauth_token_secret'] = $reply->oauth_token_secret; + $_SESSION['oauth_verify'] = true; + + // redirect to auth website + $auth_url = $cb->oauth_authorize(); + header('Location: ' . $auth_url); + die(); } elseif (isset($_GET['oauth_verifier']) && isset($_SESSION['oauth_verify'])) { - // verify the token - $cb->setToken($_SESSION['oauth_token'], $_SESSION['oauth_token_secret']); - unset($_SESSION['oauth_verify']); - - // get the access token - $reply = $cb->oauth_accessToken([ - 'oauth_verifier' => $_GET['oauth_verifier'] - ]); - - // store the token (which is different from the request token!) - $_SESSION['oauth_token'] = $reply->oauth_token; - $_SESSION['oauth_token_secret'] = $reply->oauth_token_secret; - - // send to same URL, without oauth GET parameters - header('Location: ' . basename(__FILE__)); - die(); + // verify the token + $cb->setToken($_SESSION['oauth_token'], $_SESSION['oauth_token_secret']); + unset($_SESSION['oauth_verify']); + + // get the access token + $reply = $cb->oauth_accessToken([ + 'oauth_verifier' => $_GET['oauth_verifier'] + ]); + + // store the token (which is different from the request token!) + $_SESSION['oauth_token'] = $reply->oauth_token; + $_SESSION['oauth_token_secret'] = $reply->oauth_token_secret; + + // send to same URL, without oauth GET parameters + header('Location: ' . basename(__FILE__)); + die(); } // assign access token on each page load @@ -159,23 +159,23 @@ because no encoding is needed: ```php $params = [ - 'status' => 'Fish & chips' + 'status' => 'Fish & chips' ]; $reply = $cb->statuses_update($params); ``` ```php $params = [ - 'status' => 'I love London', - 'lat' => 51.5033, - 'long' => 0.1197 + 'status' => 'I love London', + 'lat' => 51.5033, + 'long' => 0.1197 ]; $reply = $cb->statuses_update($params); ``` ```php $params = [ - 'screen_name' => 'jublonet' + 'screen_name' => 'jublonet' ]; $reply = $cb->users_show($params); ``` @@ -184,6 +184,14 @@ sent with the code above. ### Uploading media to Twitter +Twitter will accept the following media types, all of which are supported by Codebird: +- PNG +- JPEG +- BMP +- WebP +- GIF +- Animated GIF + Tweet media can be uploaded in a 2-step process: **First** you send each media to Twitter. For **images**, it works like this: @@ -191,18 +199,18 @@ Tweet media can be uploaded in a 2-step process: ```php // these files to upload. You can also just upload 1 image! $media_files = [ - 'bird1.jpg', 'bird2.jpg', 'bird3.jpg' + 'bird1.jpg', 'bird2.jpg', 'bird3.jpg' ]; // will hold the uploaded IDs $media_ids = []; foreach ($media_files as $file) { - // upload all media files - $reply = $cb->media_upload([ - 'media' => $file - ]); - // and collect their IDs - $media_ids[] = $reply->media_id_string; + // upload all media files + $reply = $cb->media_upload([ + 'media' => $file + ]); + // and collect their IDs + $media_ids[] = $reply->media_id_string; } ``` @@ -217,8 +225,8 @@ $media_ids = implode(',', $media_ids); // send tweet with these medias $reply = $cb->statuses_update([ - 'status' => 'These are some of my relatives.', - 'media_ids' => $media_ids + 'status' => 'These are some of my relatives.', + 'media_ids' => $media_ids ]); print_r($reply); ); @@ -234,7 +242,7 @@ More [documentation for uploading media](https://dev.twitter.com/rest/public/upl Remote files received from `http` and `https` servers are supported, too: ```php $reply = $cb->media_upload(array( - 'media' => 'http://www.bing.com/az/hprichbg/rb/BilbaoGuggenheim_EN-US11232447099_1366x768.jpg' + 'media' => 'http://www.bing.com/az/hprichbg/rb/BilbaoGuggenheim_EN-US11232447099_1366x768.jpg' )); ``` @@ -267,9 +275,9 @@ $fp = fopen($file, 'r'); // INIT the upload $reply = $cb->media_upload([ - 'command' => 'INIT', - 'media_type' => 'video/mp4', - 'total_bytes' => $size_bytes + 'command' => 'INIT', + 'media_type' => 'video/mp4', + 'total_bytes' => $size_bytes ]); $media_id = $reply->media_id_string; @@ -279,16 +287,16 @@ $media_id = $reply->media_id_string; $segment_id = 0; while (! feof($fp)) { - $chunk = fread($fp, 1048576); // 1MB per chunk for this sample + $chunk = fread($fp, 1048576); // 1MB per chunk for this sample - $reply = $cb->media_upload([ - 'command' => 'APPEND', - 'media_id' => $media_id, - 'segment_index' => $segment_id, - 'media' => $chunk - ]); + $reply = $cb->media_upload([ + 'command' => 'APPEND', + 'media_id' => $media_id, + 'segment_index' => $segment_id, + 'media' => $chunk + ]); - $segment_id++; + $segment_id++; } fclose($fp); @@ -296,20 +304,20 @@ fclose($fp); // FINALIZE the upload $reply = $cb->media_upload([ - 'command' => 'FINALIZE', - 'media_id' => $media_id + 'command' => 'FINALIZE', + 'media_id' => $media_id ]); var_dump($reply); if ($reply->httpstatus < 200 || $reply->httpstatus > 299) { - die(); + die(); } // Now use the media_id in a tweet $reply = $cb->statuses_update([ - 'status' => 'Twitter now accepts video uploads.', - 'media_ids' => $media_id + 'status' => 'Twitter now accepts video uploads.', + 'media_ids' => $media_id ]); ``` @@ -338,19 +346,19 @@ map to Codebird function calls. The general rules are: 1. For each slash in a Twitter API method, use an underscore in the Codebird function. - Example: ```statuses/update``` maps to ```Codebird::statuses_update()```. + Example: ```statuses/update``` maps to ```Codebird::statuses_update()```. 2. For each underscore in a Twitter API method, use camelCase in the Codebird function. - Example: ```statuses/home_timeline``` maps to ```Codebird::statuses_homeTimeline()```. + Example: ```statuses/home_timeline``` maps to ```Codebird::statuses_homeTimeline()```. 3. For each parameter template in method, use UPPERCASE in the Codebird function. - Also don’t forget to include the parameter in your parameter list. + Also don’t forget to include the parameter in your parameter list. - Examples: - - ```statuses/show/:id``` maps to ```Codebird::statuses_show_ID('id=12345')```. - - ```users/profile_image/:screen_name``` maps to - `Codebird::users_profileImage_SCREEN_NAME('screen_name=jublonet')`. + Examples: + - ```statuses/show/:id``` maps to ```Codebird::statuses_show_ID('id=12345')```. + - ```users/profile_image/:screen_name``` maps to + `Codebird::users_profileImage_SCREEN_NAME('screen_name=jublonet')`. HTTP methods (GET, POST, DELETE etc.) ------------------------------------- @@ -450,24 +458,24 @@ To consume one of the available Twitter streams, follow these **two steps:** function some_callback($message) { - // gets called for every new streamed message - // gets called with $message = NULL once per second + // gets called for every new streamed message + // gets called with $message = NULL once per second - if ($message !== null) { - print_r($message); - flush(); - } + if ($message !== null) { + print_r($message); + flush(); + } - // return false to continue streaming - // return true to close the stream + // return false to continue streaming + // return true to close the stream - // close streaming after 1 minute for this simple sample - // don't rely on globals in your code! - if (time() - $GLOBALS['time_start'] >= 60) { - return true; - } + // close streaming after 1 minute for this simple sample + // don't rely on globals in your code! + if (time() - $GLOBALS['time_start'] >= 60) { + return true; + } - return false; + return false; } // set the streaming callback in Codebird @@ -525,11 +533,11 @@ Take a look at the returned data as follows: ``` stdClass Object ( - [oauth_token] => 14648265-rPn8EJwfB********************** - [oauth_token_secret] => agvf3L3************************** - [user_id] => 14648265 - [screen_name] => jublonet - [httpstatus] => 200 + [oauth_token] => 14648265-rPn8EJwfB********************** + [oauth_token_secret] => agvf3L3************************** + [user_id] => 14648265 + [screen_name] => jublonet + [httpstatus] => 200 ) ``` @@ -567,9 +575,9 @@ $nextCursor = $result1->next_cursor_str; 3. If ```$nextCursor``` is not 0, use this cursor to request the next result page: ```php - if ($nextCursor > 0) { - $result2 = $cb->followers_list('cursor=' . $nextCursor); - } + if ($nextCursor > 0) { + $result2 = $cb->followers_list('cursor=' . $nextCursor); + } ``` To navigate back instead of forth, use the field ```$resultX->previous_cursor_str``` @@ -587,9 +595,9 @@ Remember that your application needs to be whitelisted to be able to use xAuth. Here’s an example: ```php $reply = $cb->oauth_accessToken([ - 'x_auth_username' => 'username', - 'x_auth_password' => '4h3_p4$$w0rd', - 'x_auth_mode' => 'client_auth' + 'x_auth_username' => 'username', + 'x_auth_password' => '4h3_p4$$w0rd', + 'x_auth_mode' => 'client_auth' ]); ``` @@ -689,7 +697,7 @@ Here’s a sample for adding a tweet using that API method: $reply = $cb->collections_entries_curate([ 'id' => 'custom-672852634622144512', 'changes' => [ - ['op' => 'add', 'tweet_id' => '672727928262828032'] + ['op' => 'add', 'tweet_id' => '672727928262828032'] ] ]); @@ -711,9 +719,9 @@ For accessing the TON API, please adapt the following code samples for uploading // single-chunk upload $reply = $cb->ton_bucket_BUCKET([ - 'bucket' => 'ta_partner', - 'Content-Type' => 'image/jpeg', - 'media' => $file + 'bucket' => 'ta_partner', + 'Content-Type' => 'image/jpeg', + 'media' => $file ]); var_dump($reply); @@ -737,13 +745,13 @@ $fp = fopen($file, 'r'); // INIT the upload $reply = $cb->__call( - 'ton/bucket/BUCKET?resumable=true', - [[ // note the double square braces when using __call - 'bucket' => 'ta_partner', - 'Content-Type' => 'video/mp4', - 'X-Ton-Content-Type' => 'video/mp4', - 'X-Ton-Content-Length' => $size_bytes - ]] + 'ton/bucket/BUCKET?resumable=true', + [[ // note the double square braces when using __call + 'bucket' => 'ta_partner', + 'Content-Type' => 'video/mp4', + 'X-Ton-Content-Type' => 'video/mp4', + 'X-Ton-Content-Length' => $size_bytes + ]] ); $target = $reply->Location; @@ -759,23 +767,23 @@ list ($target, $file, $resumeId) = $match; $segment_id = 0; while (! feof($fp)) { - $chunk = fread($fp, 1048576); // 1MB per chunk for this sample - - // special way to call Codebird for the upload chunks - $reply = $cb->__call( - 'ton/bucket/BUCKET/FILE?resumable=true&resumeId=RESUMEID', - [[ // note the double square braces when using __call - 'bucket' => 'ta_partner', - 'file' => $file, // you get real filename from INIT, see above - 'Content-Type' => 'image/jpeg', - 'Content-Range' => 'bytes ' - . ($segment_id * 1048576) . '-' . strlen($chunk) . '/' . $size_bytes, - 'resumeId' => $resumeId, - 'media' => $chunk - ]] - ); - - $segment_id++; + $chunk = fread($fp, 1048576); // 1MB per chunk for this sample + + // special way to call Codebird for the upload chunks + $reply = $cb->__call( + 'ton/bucket/BUCKET/FILE?resumable=true&resumeId=RESUMEID', + [[ // note the double square braces when using __call + 'bucket' => 'ta_partner', + 'file' => $file, // you get real filename from INIT, see above + 'Content-Type' => 'image/jpeg', + 'Content-Range' => 'bytes ' + . ($segment_id * 1048576) . '-' . strlen($chunk) . '/' . $size_bytes, + 'resumeId' => $resumeId, + 'media' => $chunk + ]] + ); + + $segment_id++; } fclose($fp); diff --git a/src/codebird.php b/src/codebird.php index ccc7eb5..1ebe0f2 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -18,18 +18,18 @@ */ $constants = explode(' ', 'OBJECT ARRAY JSON'); foreach ($constants as $i => $id) { - $id = 'CODEBIRD_RETURNFORMAT_' . $id; - defined($id) or define($id, $i); + $id = 'CODEBIRD_RETURNFORMAT_' . $id; + defined($id) or define($id, $i); } $constants = [ - 'CURLE_SSL_CERTPROBLEM' => 58, - 'CURLE_SSL_CACERT' => 60, - 'CURLE_SSL_CACERT_BADFILE' => 77, - 'CURLE_SSL_CRL_BADFILE' => 82, - 'CURLE_SSL_ISSUER_ERROR' => 83 + 'CURLE_SSL_CERTPROBLEM' => 58, + 'CURLE_SSL_CACERT' => 60, + 'CURLE_SSL_CACERT_BADFILE' => 77, + 'CURLE_SSL_CRL_BADFILE' => 82, + 'CURLE_SSL_ISSUER_ERROR' => 83 ]; foreach ($constants as $id => $i) { - defined($id) or define($id, $i); + defined($id) or define($id, $i); } unset($constants); unset($i); @@ -43,2507 +43,2510 @@ */ class Codebird { - /** - * The current singleton instance - */ - private static $_instance = null; - - /** - * The OAuth consumer key of your registered app - */ - protected static $_oauth_consumer_key = null; - - /** - * The corresponding consumer secret - */ - protected static $_oauth_consumer_secret = null; - - /** - * The app-only bearer token. Used to authorize app-only requests - */ - protected static $_oauth_bearer_token = null; - - /** - * The API endpoint to use - */ - protected static $_endpoint = 'https://api.twitter.com/1.1/'; - - /** - * The media API endpoint to use - */ - protected static $_endpoint_media = 'https://upload.twitter.com/1.1/'; - - /** - * The Streaming API endpoints to use - */ - protected static $_endpoints_streaming = [ - 'public' => 'https://stream.twitter.com/1.1/', - 'user' => 'https://userstream.twitter.com/1.1/', - 'site' => 'https://sitestream.twitter.com/1.1/' + /** + * The current singleton instance + */ + private static $_instance = null; + + /** + * The OAuth consumer key of your registered app + */ + protected static $_oauth_consumer_key = null; + + /** + * The corresponding consumer secret + */ + protected static $_oauth_consumer_secret = null; + + /** + * The app-only bearer token. Used to authorize app-only requests + */ + protected static $_oauth_bearer_token = null; + + /** + * The API endpoint to use + */ + protected static $_endpoint = 'https://api.twitter.com/1.1/'; + + /** + * The media API endpoint to use + */ + protected static $_endpoint_media = 'https://upload.twitter.com/1.1/'; + + /** + * The Streaming API endpoints to use + */ + protected static $_endpoints_streaming = [ + 'public' => 'https://stream.twitter.com/1.1/', + 'user' => 'https://userstream.twitter.com/1.1/', + 'site' => 'https://sitestream.twitter.com/1.1/' + ]; + + /** + * The TON API endpoint to use + */ + protected static $_endpoint_ton = 'https://ton.twitter.com/1.1/'; + + /** + * The Ads API endpoint to use + */ + protected static $_endpoint_ads = 'https://ads-api.twitter.com/0/'; + + /** + * The Ads Sandbox API endpoint to use + */ + protected static $_endpoint_ads_sandbox = 'https://ads-api-sandbox.twitter.com/0/'; + + /** + * The API endpoint base to use + */ + protected static $_endpoint_oauth = 'https://api.twitter.com/'; + + /** + * The Request or access token. Used to sign requests + */ + protected $_oauth_token = null; + + /** + * The corresponding request or access token secret + */ + protected $_oauth_token_secret = null; + + /** + * The format of data to return from API calls + */ + protected $_return_format = CODEBIRD_RETURNFORMAT_OBJECT; + + /** + * The file formats that Twitter accepts as image uploads + */ + protected $_supported_media_files = [ + IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_BMP, + IMAGETYPE_GIF //, IMAGETYPE_WEBP + ]; + + /** + * The callback to call with any new streaming messages + */ + protected $_streaming_callback = null; + + /** + * The current Codebird version + */ + protected $_version = '3.0.0-dev'; + + /** + * Auto-detect cURL absence + */ + protected $_use_curl = true; + + /** + * Request timeout + */ + protected $_timeout = 10000; + + /** + * Connection timeout + */ + protected $_connectionTimeout = 3000; + + /** + * Remote media download timeout + */ + protected $_remoteDownloadTimeout = 5000; + + /** + * Proxy + */ + protected $_proxy = []; + + /** + * + * Class constructor + * + */ + public function __construct() + { + // Pre-define $_use_curl depending on cURL availability + $this->setUseCurl(function_exists('curl_init')); + } + + /** + * Returns singleton class instance + * Always use this method unless you're working with multiple authenticated users at once + * + * @return Codebird The instance + */ + public static function getInstance() + { + if (self::$_instance === null) { + self::$_instance = new self; + } + return self::$_instance; + } + + /** + * Sets the OAuth consumer key and secret (App key) + * + * @param string $key OAuth consumer key + * @param string $secret OAuth consumer secret + * + * @return void + */ + public static function setConsumerKey($key, $secret) + { + self::$_oauth_consumer_key = $key; + self::$_oauth_consumer_secret = $secret; + } + + /** + * Sets the OAuth2 app-only auth bearer token + * + * @param string $token OAuth2 bearer token + * + * @return void + */ + public static function setBearerToken($token) + { + self::$_oauth_bearer_token = $token; + } + + /** + * Gets the current Codebird version + * + * @return string The version number + */ + public function getVersion() + { + return $this->_version; + } + + /** + * Sets the OAuth request or access token and secret (User key) + * + * @param string $token OAuth request or access token + * @param string $secret OAuth request or access token secret + * + * @return void + */ + public function setToken($token, $secret) + { + $this->_oauth_token = $token; + $this->_oauth_token_secret = $secret; + } + + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ + public function logout() + { + $this->_oauth_token = + $this->_oauth_token_secret = null; + + return true; + } + + /** + * Sets if codebird should use cURL + * + * @param bool $use_curl Request uses cURL or not + * + * @return void + */ + public function setUseCurl($use_curl) + { + if ($use_curl && ! function_exists('curl_init')) { + throw new \Exception('To use cURL, the PHP curl extension must be available.'); + } + + $this->_use_curl = (bool) $use_curl; + } + + /** + * Sets request timeout in milliseconds + * + * @param int $timeout Request timeout in milliseconds + * + * @return void + */ + public function setTimeout($timeout) + { + $this->_timeout = (int) $timeout; + } + + /** + * Sets connection timeout in milliseconds + * + * @param int $timeout Connection timeout in milliseconds + * + * @return void + */ + public function setConnectionTimeout($timeout) + { + $this->_connectionTimeout = (int) $timeout; + } + + /** + * Sets remote media download timeout in milliseconds + * + * @param int $timeout Remote media timeout in milliseconds + * + * @return void + */ + public function setRemoteDownloadTimeout($timeout) + { + $this->_remoteDownloadTimeout = (int) $timeout; + } + + /** + * Sets the format for API replies + * + * @param int $return_format One of these: + * CODEBIRD_RETURNFORMAT_OBJECT (default) + * CODEBIRD_RETURNFORMAT_ARRAY + * + * @return void + */ + public function setReturnFormat($return_format) + { + $this->_return_format = $return_format; + } + + /** + * Sets the proxy + * + * @param string $host Proxy host + * @param int $port Proxy port + * + * @return void + */ + public function setProxy($host, $port) + { + $this->_proxy['host'] = $host; + $this->_proxy['port'] = $port; + } + + /** + * Sets the proxy authentication + * + * @param string $authentication Proxy authentication + * + * @return void + */ + public function setProxyAuthentication($authentication) + { + $this->_proxy['authentication'] = $authentication; + } + + /** + * Sets streaming callback + * + * @param callable $callback The streaming callback + * + * @return void + */ + public function setStreamingCallback($callback) + { + if (!is_callable($callback)) { + throw new \Exception('This is not a proper callback.'); + } + $this->_streaming_callback = $callback; + } + + /** + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method API methods! + * + * @return array $apimethods + */ + public function getApiMethods() + { + static $httpmethods = [ + 'GET' => [ + 'account/settings', + 'account/verify_credentials', + 'ads/accounts', + 'ads/accounts/:account_id', + 'ads/accounts/:account_id/app_event_provider_configurations', + 'ads/accounts/:account_id/app_event_provider_configurations/:id', + 'ads/accounts/:account_id/app_event_tags', + 'ads/accounts/:account_id/app_event_tags/:id', + 'ads/accounts/:account_id/app_lists', + 'ads/accounts/:account_id/authenticated_user_access', + 'ads/accounts/:account_id/campaigns', + 'ads/accounts/:account_id/campaigns/:campaign_id', + 'ads/accounts/:account_id/cards/app_download', + 'ads/accounts/:account_id/cards/app_download/:card_id', + 'ads/accounts/:account_id/cards/image_app_download', + 'ads/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/accounts/:account_id/cards/image_conversation', + 'ads/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/accounts/:account_id/cards/lead_gen', + 'ads/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/accounts/:account_id/cards/video_app_download', + 'ads/accounts/:account_id/cards/video_app_download/:id', + 'ads/accounts/:account_id/cards/video_conversation', + 'ads/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/accounts/:account_id/cards/website', + 'ads/accounts/:account_id/cards/website/:card_id', + 'ads/accounts/:account_id/features', + 'ads/accounts/:account_id/funding_instruments', + 'ads/accounts/:account_id/funding_instruments/:id', + 'ads/accounts/:account_id/line_items', + 'ads/accounts/:account_id/line_items/:line_item_id', + 'ads/accounts/:account_id/promotable_users', + 'ads/accounts/:account_id/promoted_accounts', + 'ads/accounts/:account_id/promoted_tweets', + 'ads/accounts/:account_id/reach_estimate', + 'ads/accounts/:account_id/scoped_timeline', + 'ads/accounts/:account_id/tailored_audience_changes', + 'ads/accounts/:account_id/tailored_audience_changes/:id', + 'ads/accounts/:account_id/tailored_audiences', + 'ads/accounts/:account_id/tailored_audiences/:id', + 'ads/accounts/:account_id/targeting_criteria', + 'ads/accounts/:account_id/targeting_criteria/:id', + 'ads/accounts/:account_id/targeting_suggestions', + 'ads/accounts/:account_id/tweet/preview', + 'ads/accounts/:account_id/tweet/preview/:tweet_id', + 'ads/accounts/:account_id/videos', + 'ads/accounts/:account_id/videos/:id', + 'ads/accounts/:account_id/web_event_tags', + 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/bidding_rules', + 'ads/iab_categories', + 'ads/insights/accounts/:account_id', + 'ads/insights/accounts/:account_id/available_audiences', + 'ads/line_items/placements', + 'ads/sandbox/accounts', + 'ads/sandbox/accounts/:account_id', + 'ads/sandbox/accounts/:account_id/app_event_provider_configurations', + 'ads/sandbox/accounts/:account_id/app_event_provider_configurations/:id', + 'ads/sandbox/accounts/:account_id/app_event_tags', + 'ads/sandbox/accounts/:account_id/app_event_tags/:id', + 'ads/sandbox/accounts/:account_id/app_lists', + 'ads/sandbox/accounts/:account_id/authenticated_user_access', + 'ads/sandbox/accounts/:account_id/campaigns', + 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', + 'ads/sandbox/accounts/:account_id/cards/app_download', + 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_app_download', + 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_conversation', + 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/lead_gen', + 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/sandbox/accounts/:account_id/cards/video_app_download', + 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', + 'ads/sandbox/accounts/:account_id/cards/video_conversation', + 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/website', + 'ads/sandbox/accounts/:account_id/cards/website/:card_id', + 'ads/sandbox/accounts/:account_id/features', + 'ads/sandbox/accounts/:account_id/funding_instruments', + 'ads/sandbox/accounts/:account_id/funding_instruments/:id', + 'ads/sandbox/accounts/:account_id/line_items', + 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', + 'ads/sandbox/accounts/:account_id/promotable_users', + 'ads/sandbox/accounts/:account_id/promoted_accounts', + 'ads/sandbox/accounts/:account_id/promoted_tweets', + 'ads/sandbox/accounts/:account_id/reach_estimate', + 'ads/sandbox/accounts/:account_id/scoped_timeline', + 'ads/sandbox/accounts/:account_id/tailored_audience_changes', + 'ads/sandbox/accounts/:account_id/tailored_audience_changes/:id', + 'ads/sandbox/accounts/:account_id/tailored_audiences', + 'ads/sandbox/accounts/:account_id/tailored_audiences/:id', + 'ads/sandbox/accounts/:account_id/targeting_criteria', + 'ads/sandbox/accounts/:account_id/targeting_criteria/:id', + 'ads/sandbox/accounts/:account_id/targeting_suggestions', + 'ads/sandbox/accounts/:account_id/tweet/preview', + 'ads/sandbox/accounts/:account_id/tweet/preview/:tweet_id', + 'ads/sandbox/accounts/:account_id/videos', + 'ads/sandbox/accounts/:account_id/videos/:id', + 'ads/sandbox/accounts/:account_id/web_event_tags', + 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/sandbox/bidding_rules', + 'ads/sandbox/iab_categories', + 'ads/sandbox/insights/accounts/:account_id', + 'ads/sandbox/insights/accounts/:account_id/available_audiences', + 'ads/sandbox/line_items/placements', + 'ads/sandbox/stats/accounts/:account_id', + 'ads/sandbox/stats/accounts/:account_id/campaigns', + 'ads/sandbox/stats/accounts/:account_id/campaigns/:id', + 'ads/sandbox/stats/accounts/:account_id/funding_instruments', + 'ads/sandbox/stats/accounts/:account_id/funding_instruments/:id', + 'ads/sandbox/stats/accounts/:account_id/line_items', + 'ads/sandbox/stats/accounts/:account_id/line_items/:id', + 'ads/sandbox/stats/accounts/:account_id/promoted_accounts', + 'ads/sandbox/stats/accounts/:account_id/promoted_accounts/:id', + 'ads/sandbox/stats/accounts/:account_id/promoted_tweets', + 'ads/sandbox/stats/accounts/:account_id/promoted_tweets/:id', + 'ads/sandbox/stats/accounts/:account_id/reach/campaigns', + 'ads/sandbox/targeting_criteria/app_store_categories', + 'ads/sandbox/targeting_criteria/behavior_taxonomies', + 'ads/sandbox/targeting_criteria/behaviors', + 'ads/sandbox/targeting_criteria/devices', + 'ads/sandbox/targeting_criteria/events', + 'ads/sandbox/targeting_criteria/interests', + 'ads/sandbox/targeting_criteria/languages', + 'ads/sandbox/targeting_criteria/locations', + 'ads/sandbox/targeting_criteria/network_operators', + 'ads/sandbox/targeting_criteria/platform_versions', + 'ads/sandbox/targeting_criteria/platforms', + 'ads/sandbox/targeting_criteria/tv_channels', + 'ads/sandbox/targeting_criteria/tv_genres', + 'ads/sandbox/targeting_criteria/tv_markets', + 'ads/sandbox/targeting_criteria/tv_shows', + 'ads/stats/accounts/:account_id', + 'ads/stats/accounts/:account_id/campaigns', + 'ads/stats/accounts/:account_id/campaigns/:id', + 'ads/stats/accounts/:account_id/funding_instruments', + 'ads/stats/accounts/:account_id/funding_instruments/:id', + 'ads/stats/accounts/:account_id/line_items', + 'ads/stats/accounts/:account_id/line_items/:id', + 'ads/stats/accounts/:account_id/promoted_accounts', + 'ads/stats/accounts/:account_id/promoted_accounts/:id', + 'ads/stats/accounts/:account_id/promoted_tweets', + 'ads/stats/accounts/:account_id/promoted_tweets/:id', + 'ads/stats/accounts/:account_id/reach/campaigns', + 'ads/targeting_criteria/app_store_categories', + 'ads/targeting_criteria/behavior_taxonomies', + 'ads/targeting_criteria/behaviors', + 'ads/targeting_criteria/devices', + 'ads/targeting_criteria/events', + 'ads/targeting_criteria/interests', + 'ads/targeting_criteria/languages', + 'ads/targeting_criteria/locations', + 'ads/targeting_criteria/network_operators', + 'ads/targeting_criteria/platform_versions', + 'ads/targeting_criteria/platforms', + 'ads/targeting_criteria/tv_channels', + 'ads/targeting_criteria/tv_genres', + 'ads/targeting_criteria/tv_markets', + 'ads/targeting_criteria/tv_shows', + 'application/rate_limit_status', + 'blocks/ids', + 'blocks/list', + 'collections/entries', + 'collections/list', + 'collections/show', + 'direct_messages', + 'direct_messages/sent', + 'direct_messages/show', + 'favorites/list', + 'followers/ids', + 'followers/list', + 'friends/ids', + 'friends/list', + 'friendships/incoming', + 'friendships/lookup', + 'friendships/lookup', + 'friendships/no_retweets/ids', + 'friendships/outgoing', + 'friendships/show', + 'geo/id/:place_id', + 'geo/reverse_geocode', + 'geo/search', + 'geo/similar_places', + 'help/configuration', + 'help/languages', + 'help/privacy', + 'help/tos', + 'lists/list', + 'lists/members', + 'lists/members/show', + 'lists/memberships', + 'lists/ownerships', + 'lists/show', + 'lists/statuses', + 'lists/subscribers', + 'lists/subscribers/show', + 'lists/subscriptions', + 'mutes/users/ids', + 'mutes/users/list', + 'oauth/authenticate', + 'oauth/authorize', + 'saved_searches/list', + 'saved_searches/show/:id', + 'search/tweets', + 'site', + 'statuses/firehose', + 'statuses/home_timeline', + 'statuses/mentions_timeline', + 'statuses/oembed', + 'statuses/retweeters/ids', + 'statuses/retweets/:id', + 'statuses/retweets_of_me', + 'statuses/sample', + 'statuses/show/:id', + 'statuses/user_timeline', + 'trends/available', + 'trends/closest', + 'trends/place', + 'user', + 'users/contributees', + 'users/contributors', + 'users/profile_banner', + 'users/search', + 'users/show', + 'users/suggestions', + 'users/suggestions/:slug', + 'users/suggestions/:slug/members' + ], + 'POST' => [ + 'account/remove_profile_banner', + 'account/settings', + 'account/update_delivery_device', + 'account/update_profile', + 'account/update_profile_background_image', + 'account/update_profile_banner', + 'account/update_profile_colors', + 'account/update_profile_image', + 'ads/accounts/:account_id/app_lists', + 'ads/accounts/:account_id/campaigns', + 'ads/accounts/:account_id/cards/app_download', + 'ads/accounts/:account_id/cards/image_app_download', + 'ads/accounts/:account_id/cards/image_conversation', + 'ads/accounts/:account_id/cards/lead_gen', + 'ads/accounts/:account_id/cards/video_app_download', + 'ads/accounts/:account_id/cards/video_conversation', + 'ads/accounts/:account_id/cards/website', + 'ads/accounts/:account_id/line_items', + 'ads/accounts/:account_id/promoted_accounts', + 'ads/accounts/:account_id/promoted_tweets', + 'ads/accounts/:account_id/tailored_audience_changes', + 'ads/accounts/:account_id/tailored_audiences', + 'ads/accounts/:account_id/targeting_criteria', + 'ads/accounts/:account_id/tweet', + 'ads/accounts/:account_id/videos', + 'ads/accounts/:account_id/web_event_tags', + 'ads/batch/accounts/:account_id/campaigns', + 'ads/batch/accounts/:account_id/line_items', + 'ads/sandbox/accounts/:account_id/app_lists', + 'ads/sandbox/accounts/:account_id/campaigns', + 'ads/sandbox/accounts/:account_id/cards/app_download', + 'ads/sandbox/accounts/:account_id/cards/image_app_download', + 'ads/sandbox/accounts/:account_id/cards/image_conversation', + 'ads/sandbox/accounts/:account_id/cards/lead_gen', + 'ads/sandbox/accounts/:account_id/cards/video_app_download', + 'ads/sandbox/accounts/:account_id/cards/video_conversation', + 'ads/sandbox/accounts/:account_id/cards/website', + 'ads/sandbox/accounts/:account_id/line_items', + 'ads/sandbox/accounts/:account_id/promoted_accounts', + 'ads/sandbox/accounts/:account_id/promoted_tweets', + 'ads/sandbox/accounts/:account_id/tailored_audience_changes', + 'ads/sandbox/accounts/:account_id/tailored_audiences', + 'ads/sandbox/accounts/:account_id/targeting_criteria', + 'ads/sandbox/accounts/:account_id/tweet', + 'ads/sandbox/accounts/:account_id/videos', + 'ads/sandbox/accounts/:account_id/web_event_tags', + 'ads/sandbox/batch/accounts/:account_id/campaigns', + 'ads/sandbox/batch/accounts/:account_id/line_items', + 'blocks/create', + 'blocks/destroy', + 'collections/create', + 'collections/destroy', + 'collections/entries/add', + 'collections/entries/curate', + 'collections/entries/move', + 'collections/entries/remove', + 'collections/update', + 'direct_messages/destroy', + 'direct_messages/new', + 'favorites/create', + 'favorites/destroy', + 'friendships/create', + 'friendships/destroy', + 'friendships/update', + 'lists/create', + 'lists/destroy', + 'lists/members/create', + 'lists/members/create_all', + 'lists/members/destroy', + 'lists/members/destroy_all', + 'lists/subscribers/create', + 'lists/subscribers/destroy', + 'lists/update', + 'media/upload', + 'mutes/users/create', + 'mutes/users/destroy', + 'oauth/access_token', + 'oauth/request_token', + 'oauth2/invalidate_token', + 'oauth2/token', + 'saved_searches/create', + 'saved_searches/destroy/:id', + 'statuses/destroy/:id', + 'statuses/filter', + 'statuses/lookup', + 'statuses/retweet/:id', + 'statuses/update', + 'statuses/update_with_media', // deprecated, use media/upload + 'ton/bucket/:bucket', + 'ton/bucket/:bucket?resumable=true', + 'users/lookup', + 'users/report_spam' + ], + 'PUT' => [ + 'ads/accounts/:account_id/campaigns/:campaign_id', + 'ads/accounts/:account_id/cards/app_download/:card_id', + 'ads/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/accounts/:account_id/cards/video_app_download/:id', + 'ads/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/accounts/:account_id/cards/website/:card_id', + 'ads/accounts/:account_id/line_items/:line_item_id', + 'ads/accounts/:account_id/promoted_tweets/:id', + 'ads/accounts/:account_id/tailored_audiences/global_opt_out', + 'ads/accounts/:account_id/targeting_criteria', + 'ads/accounts/:account_id/videos/:id', + 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', + 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', + 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/website/:card_id', + 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', + 'ads/sandbox/accounts/:account_id/promoted_tweets/:id', + 'ads/sandbox/accounts/:account_id/tailored_audiences/global_opt_out', + 'ads/sandbox/accounts/:account_id/targeting_criteria', + 'ads/sandbox/accounts/:account_id/videos/:id', + 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' + ], + 'DELETE' => [ + 'ads/accounts/:account_id/campaigns/:campaign_id', + 'ads/accounts/:account_id/cards/app_download/:card_id', + 'ads/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/accounts/:account_id/cards/video_app_download/:id', + 'ads/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/accounts/:account_id/cards/website/:card_id', + 'ads/accounts/:account_id/line_items/:line_item_id', + 'ads/accounts/:account_id/promoted_tweets/:id', + 'ads/accounts/:account_id/tailored_audiences/:id', + 'ads/accounts/:account_id/targeting_criteria/:id', + 'ads/accounts/:account_id/videos/:id', + 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', + 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', + 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', + 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', + 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', + 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', + 'ads/sandbox/accounts/:account_id/cards/website/:card_id', + 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', + 'ads/sandbox/accounts/:account_id/promoted_tweets/:id', + 'ads/sandbox/accounts/:account_id/tailored_audiences/:id', + 'ads/sandbox/accounts/:account_id/targeting_criteria/:id', + 'ads/sandbox/accounts/:account_id/videos/:id', + 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id' + ] + ]; + return $httpmethods; + } + + /** + * Main API handler working on any requests you issue + * + * @param string $fn The member function you called + * @param array $params The parameters you sent along + * + * @return string The API reply encoded in the set return_format + */ + + public function __call($fn, $params) + { + // parse parameters + $apiparams = $this->_parseApiParams($params); + + // stringify null and boolean parameters + $apiparams = $this->_stringifyNullBoolParams($apiparams); + + $app_only_auth = false; + if (count($params) > 1) { + // convert app_only_auth param to bool + $app_only_auth = !! $params[1]; + } + + // reset token when requesting a new token + // (causes 401 for signature error on subsequent requests) + if ($fn === 'oauth_requestToken') { + $this->setToken(null, null); + } + + // map function name to API method + list($method, $method_template) = $this->_mapFnToApiMethod($fn, $apiparams); + + $httpmethod = $this->_detectMethod($method_template, $apiparams); + $multipart = $this->_detectMultipart($method_template); + + return $this->_callApi( + $httpmethod, + $method, + $method_template, + $apiparams, + $multipart, + $app_only_auth + ); + } + + + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params + * + * @param array|string $params Parameters to parse + * + * @return array $apiparams + */ + protected function _parseApiParams($params) + { + $apiparams = []; + if (count($params) === 0) { + return $apiparams; + } + + if (is_array($params[0])) { + // given parameters are array + $apiparams = $params[0]; + return $apiparams; + } + + // user gave us query-style params + parse_str($params[0], $apiparams); + if (! is_array($apiparams)) { + $apiparams = []; + } + + return $apiparams; + } + + /** + * Replace null and boolean parameters with their string representations + * + * @param array $apiparams Parameter array to replace in + * + * @return array $apiparams + */ + protected function _stringifyNullBoolParams($apiparams) + { + foreach ($apiparams as $key => $value) { + if (! is_scalar($value)) { + // no need to try replacing arrays + continue; + } + if (is_null($value)) { + $apiparams[$key] = 'null'; + } elseif (is_bool($value)) { + $apiparams[$key] = $value ? 'true' : 'false'; + } + } + + return $apiparams; + } + + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + protected function _mapFnToApiMethod($fn, &$apiparams) + { + // replace _ by / + $method = $this->_mapFnInsertSlashes($fn); + + // undo replacement for URL parameters + $method = $this->_mapFnRestoreParamUnderscores($method); + + // replace AA by URL parameters + $method_template = $method; + $match = []; + if (preg_match_all('/[A-Z_]{2,}/', $method, $match)) { + foreach ($match[0] as $param) { + $param_l = strtolower($param); + if ($param_l === 'resumeid') { + $param_l = 'resumeId'; + } + $method_template = str_replace($param, ':' . $param_l, $method_template); + if (! isset($apiparams[$param_l])) { + for ($i = 0; $i < 26; $i++) { + $method_template = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method_template); + } + throw new \Exception( + 'To call the templated method "' . $method_template + . '", specify the parameter value for "' . $param_l . '".' + ); + } + $method = str_replace($param, $apiparams[$param_l], $method); + unset($apiparams[$param_l]); + } + } + + if (substr($method, 0, 4) !== 'ton/') { + // replace A-Z by _a-z + for ($i = 0; $i < 26; $i++) { + $method = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method); + $method_template = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method_template); + } + } + + return [$method, $method_template]; + } + + /** + * API method mapping: Replaces _ with / character + * + * @param string $fn Function called + * + * @return string API method to call + */ + protected function _mapFnInsertSlashes($fn) + { + $path = explode('_', $fn); + $method = implode('/', $path); + + return $method; + } + + /** + * API method mapping: Restore _ character in named parameters + * + * @param string $method API method to call + * + * @return string API method with restored underscores + */ + protected function _mapFnRestoreParamUnderscores($method) + { + $url_parameters_with_underscore = [ + 'screen_name', 'place_id', + 'account_id', 'campaign_id', 'card_id', 'line_item_id', + 'tweet_id', 'web_event_tag_id' + ]; + foreach ($url_parameters_with_underscore as $param) { + $param = strtoupper($param); + $replacement_was = str_replace('_', '/', $param); + $method = str_replace($replacement_was, $param, $method); + } + + return $method; + } + + + /** + * Uncommon API methods + */ + + /** + * Gets the OAuth authenticate URL for the current request token + * + * @param optional bool $force_login Whether to force the user to enter their login data + * @param optional string $screen_name Screen name to repopulate the user name with + * @param optional string $type 'authenticate' or 'authorize', to avoid duplicate code + * + * @return string The OAuth authenticate/authorize URL + */ + public function oauth_authenticate($force_login = NULL, $screen_name = NULL, $type = 'authenticate') + { + if (! in_array($type, ['authenticate', 'authorize'])) { + throw new \Exception('To get the ' . $type . ' URL, use the correct third parameter, or omit it.'); + } + if ($this->_oauth_token === null) { + throw new \Exception('To get the ' . $type . ' URL, the OAuth token must be set.'); + } + $url = self::$_endpoint_oauth . 'oauth/' . $type . '?oauth_token=' . $this->_url($this->_oauth_token); + if ($force_login) { + $url .= "&force_login=1"; + } + if ($screen_name) { + $url .= "&screen_name=" . $screen_name; + } + return $url; + } + + /** + * Gets the OAuth authorize URL for the current request token + * @param optional bool $force_login Whether to force the user to enter their login data + * @param optional string $screen_name Screen name to repopulate the user name with + * + * @return string The OAuth authorize URL + */ + public function oauth_authorize($force_login = NULL, $screen_name = NULL) + { + return $this->oauth_authenticate($force_login, $screen_name, 'authorize'); + } + + /** + * Gets the OAuth bearer token + * + * @return string The OAuth bearer token + */ + + public function oauth2_token() + { + if ($this->_use_curl) { + return $this->_oauth2TokenCurl(); + } + return $this->_oauth2TokenNoCurl(); + } + + /** + * Gets a cURL handle + * @param string $url the URL for the curl initialization + * @return resource handle + */ + protected function getCurlInitialization($url) + { + $ch = curl_init($url); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cacert.pem'); + curl_setopt( + $ch, CURLOPT_USERAGENT, + 'codebird-php/' . $this->getVersion() . ' +https://github.com/jublonet/codebird-php' + ); + + if ($this->hasProxy()) { + curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + curl_setopt($ch, CURLOPT_PROXY, $this->getProxyHost()); + curl_setopt($ch, CURLOPT_PROXYPORT, $this->getProxyPort()); + + if ($this->hasProxyAuthentication()) { + curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->getProxyAuthentication()); + } + } + + return $ch; + } + + /** + * Gets a non cURL initialization + * + * @param string $url the URL for the curl initialization + * @param array $contextOptions the options for the stream context + * @param string $hostname the hostname to verify the SSL FQDN for + * + * @return array the read data + */ + protected function getNoCurlInitialization($url, $contextOptions, $hostname = '') + { + $httpOptions = []; + + $httpOptions['header'] = [ + 'User-Agent: codebird-php/' . $this->getVersion() . ' +https://github.com/jublonet/codebird-php' ]; - /** - * The TON API endpoint to use - */ - protected static $_endpoint_ton = 'https://ton.twitter.com/1.1/'; - - /** - * The Ads API endpoint to use - */ - protected static $_endpoint_ads = 'https://ads-api.twitter.com/0/'; - - /** - * The Ads Sandbox API endpoint to use - */ - protected static $_endpoint_ads_sandbox = 'https://ads-api-sandbox.twitter.com/0/'; - - /** - * The API endpoint base to use - */ - protected static $_endpoint_oauth = 'https://api.twitter.com/'; - - /** - * The Request or access token. Used to sign requests - */ - protected $_oauth_token = null; - - /** - * The corresponding request or access token secret - */ - protected $_oauth_token_secret = null; - - /** - * The format of data to return from API calls - */ - protected $_return_format = CODEBIRD_RETURNFORMAT_OBJECT; - - /** - * The file formats that Twitter accepts as image uploads - */ - protected $_supported_media_files = [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG]; - - /** - * The callback to call with any new streaming messages - */ - protected $_streaming_callback = null; - - /** - * The current Codebird version - */ - protected $_version = '3.0.0-dev'; - - /** - * Auto-detect cURL absence - */ - protected $_use_curl = true; - - /** - * Request timeout - */ - protected $_timeout = 10000; - - /** - * Connection timeout - */ - protected $_connectionTimeout = 3000; - - /** - * Remote media download timeout - */ - protected $_remoteDownloadTimeout = 5000; - - /** - * Proxy - */ - protected $_proxy = []; - - /** - * - * Class constructor - * - */ - public function __construct() - { - // Pre-define $_use_curl depending on cURL availability - $this->setUseCurl(function_exists('curl_init')); - } - - /** - * Returns singleton class instance - * Always use this method unless you're working with multiple authenticated users at once - * - * @return Codebird The instance - */ - public static function getInstance() - { - if (self::$_instance === null) { - self::$_instance = new self; - } - return self::$_instance; - } - - /** - * Sets the OAuth consumer key and secret (App key) - * - * @param string $key OAuth consumer key - * @param string $secret OAuth consumer secret - * - * @return void - */ - public static function setConsumerKey($key, $secret) - { - self::$_oauth_consumer_key = $key; - self::$_oauth_consumer_secret = $secret; - } - - /** - * Sets the OAuth2 app-only auth bearer token - * - * @param string $token OAuth2 bearer token - * - * @return void - */ - public static function setBearerToken($token) - { - self::$_oauth_bearer_token = $token; - } - - /** - * Gets the current Codebird version - * - * @return string The version number - */ - public function getVersion() - { - return $this->_version; - } - - /** - * Sets the OAuth request or access token and secret (User key) - * - * @param string $token OAuth request or access token - * @param string $secret OAuth request or access token secret - * - * @return void - */ - public function setToken($token, $secret) - { - $this->_oauth_token = $token; - $this->_oauth_token_secret = $secret; - } - - /** - * Forgets the OAuth request or access token and secret (User key) - * - * @return bool - */ - public function logout() - { - $this->_oauth_token = - $this->_oauth_token_secret = null; - - return true; - } - - /** - * Sets if codebird should use cURL - * - * @param bool $use_curl Request uses cURL or not - * - * @return void - */ - public function setUseCurl($use_curl) - { - if ($use_curl && ! function_exists('curl_init')) { - throw new \Exception('To use cURL, the PHP curl extension must be available.'); - } - - $this->_use_curl = (bool) $use_curl; - } - - /** - * Sets request timeout in milliseconds - * - * @param int $timeout Request timeout in milliseconds - * - * @return void - */ - public function setTimeout($timeout) - { - $this->_timeout = (int) $timeout; - } - - /** - * Sets connection timeout in milliseconds - * - * @param int $timeout Connection timeout in milliseconds - * - * @return void - */ - public function setConnectionTimeout($timeout) - { - $this->_connectionTimeout = (int) $timeout; - } - - /** - * Sets remote media download timeout in milliseconds - * - * @param int $timeout Remote media timeout in milliseconds - * - * @return void - */ - public function setRemoteDownloadTimeout($timeout) - { - $this->_remoteDownloadTimeout = (int) $timeout; - } - - /** - * Sets the format for API replies - * - * @param int $return_format One of these: - * CODEBIRD_RETURNFORMAT_OBJECT (default) - * CODEBIRD_RETURNFORMAT_ARRAY - * - * @return void - */ - public function setReturnFormat($return_format) - { - $this->_return_format = $return_format; - } - - /** - * Sets the proxy - * - * @param string $host Proxy host - * @param int $port Proxy port - * - * @return void - */ - public function setProxy($host, $port) - { - $this->_proxy['host'] = $host; - $this->_proxy['port'] = $port; - } - - /** - * Sets the proxy authentication - * - * @param string $authentication Proxy authentication - * - * @return void - */ - public function setProxyAuthentication($authentication) - { - $this->_proxy['authentication'] = $authentication; - } - - /** - * Sets streaming callback - * - * @param callable $callback The streaming callback - * - * @return void - */ - public function setStreamingCallback($callback) - { - if (!is_callable($callback)) { - throw new \Exception('This is not a proper callback.'); - } - $this->_streaming_callback = $callback; - } - - /** - * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method API methods! - * - * @return array $apimethods - */ - public function getApiMethods() - { - static $httpmethods = [ - 'GET' => [ - 'account/settings', - 'account/verify_credentials', - 'ads/accounts', - 'ads/accounts/:account_id', - 'ads/accounts/:account_id/app_event_provider_configurations', - 'ads/accounts/:account_id/app_event_provider_configurations/:id', - 'ads/accounts/:account_id/app_event_tags', - 'ads/accounts/:account_id/app_event_tags/:id', - 'ads/accounts/:account_id/app_lists', - 'ads/accounts/:account_id/authenticated_user_access', - 'ads/accounts/:account_id/campaigns', - 'ads/accounts/:account_id/campaigns/:campaign_id', - 'ads/accounts/:account_id/cards/app_download', - 'ads/accounts/:account_id/cards/app_download/:card_id', - 'ads/accounts/:account_id/cards/image_app_download', - 'ads/accounts/:account_id/cards/image_app_download/:card_id', - 'ads/accounts/:account_id/cards/image_conversation', - 'ads/accounts/:account_id/cards/image_conversation/:card_id', - 'ads/accounts/:account_id/cards/lead_gen', - 'ads/accounts/:account_id/cards/lead_gen/:card_id', - 'ads/accounts/:account_id/cards/video_app_download', - 'ads/accounts/:account_id/cards/video_app_download/:id', - 'ads/accounts/:account_id/cards/video_conversation', - 'ads/accounts/:account_id/cards/video_conversation/:card_id', - 'ads/accounts/:account_id/cards/website', - 'ads/accounts/:account_id/cards/website/:card_id', - 'ads/accounts/:account_id/features', - 'ads/accounts/:account_id/funding_instruments', - 'ads/accounts/:account_id/funding_instruments/:id', - 'ads/accounts/:account_id/line_items', - 'ads/accounts/:account_id/line_items/:line_item_id', - 'ads/accounts/:account_id/promotable_users', - 'ads/accounts/:account_id/promoted_accounts', - 'ads/accounts/:account_id/promoted_tweets', - 'ads/accounts/:account_id/reach_estimate', - 'ads/accounts/:account_id/scoped_timeline', - 'ads/accounts/:account_id/tailored_audience_changes', - 'ads/accounts/:account_id/tailored_audience_changes/:id', - 'ads/accounts/:account_id/tailored_audiences', - 'ads/accounts/:account_id/tailored_audiences/:id', - 'ads/accounts/:account_id/targeting_criteria', - 'ads/accounts/:account_id/targeting_criteria/:id', - 'ads/accounts/:account_id/targeting_suggestions', - 'ads/accounts/:account_id/tweet/preview', - 'ads/accounts/:account_id/tweet/preview/:tweet_id', - 'ads/accounts/:account_id/videos', - 'ads/accounts/:account_id/videos/:id', - 'ads/accounts/:account_id/web_event_tags', - 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', - 'ads/bidding_rules', - 'ads/iab_categories', - 'ads/insights/accounts/:account_id', - 'ads/insights/accounts/:account_id/available_audiences', - 'ads/line_items/placements', - 'ads/sandbox/accounts', - 'ads/sandbox/accounts/:account_id', - 'ads/sandbox/accounts/:account_id/app_event_provider_configurations', - 'ads/sandbox/accounts/:account_id/app_event_provider_configurations/:id', - 'ads/sandbox/accounts/:account_id/app_event_tags', - 'ads/sandbox/accounts/:account_id/app_event_tags/:id', - 'ads/sandbox/accounts/:account_id/app_lists', - 'ads/sandbox/accounts/:account_id/authenticated_user_access', - 'ads/sandbox/accounts/:account_id/campaigns', - 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', - 'ads/sandbox/accounts/:account_id/cards/app_download', - 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', - 'ads/sandbox/accounts/:account_id/cards/image_app_download', - 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', - 'ads/sandbox/accounts/:account_id/cards/image_conversation', - 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', - 'ads/sandbox/accounts/:account_id/cards/lead_gen', - 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', - 'ads/sandbox/accounts/:account_id/cards/video_app_download', - 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', - 'ads/sandbox/accounts/:account_id/cards/video_conversation', - 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', - 'ads/sandbox/accounts/:account_id/cards/website', - 'ads/sandbox/accounts/:account_id/cards/website/:card_id', - 'ads/sandbox/accounts/:account_id/features', - 'ads/sandbox/accounts/:account_id/funding_instruments', - 'ads/sandbox/accounts/:account_id/funding_instruments/:id', - 'ads/sandbox/accounts/:account_id/line_items', - 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', - 'ads/sandbox/accounts/:account_id/promotable_users', - 'ads/sandbox/accounts/:account_id/promoted_accounts', - 'ads/sandbox/accounts/:account_id/promoted_tweets', - 'ads/sandbox/accounts/:account_id/reach_estimate', - 'ads/sandbox/accounts/:account_id/scoped_timeline', - 'ads/sandbox/accounts/:account_id/tailored_audience_changes', - 'ads/sandbox/accounts/:account_id/tailored_audience_changes/:id', - 'ads/sandbox/accounts/:account_id/tailored_audiences', - 'ads/sandbox/accounts/:account_id/tailored_audiences/:id', - 'ads/sandbox/accounts/:account_id/targeting_criteria', - 'ads/sandbox/accounts/:account_id/targeting_criteria/:id', - 'ads/sandbox/accounts/:account_id/targeting_suggestions', - 'ads/sandbox/accounts/:account_id/tweet/preview', - 'ads/sandbox/accounts/:account_id/tweet/preview/:tweet_id', - 'ads/sandbox/accounts/:account_id/videos', - 'ads/sandbox/accounts/:account_id/videos/:id', - 'ads/sandbox/accounts/:account_id/web_event_tags', - 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id', - 'ads/sandbox/bidding_rules', - 'ads/sandbox/iab_categories', - 'ads/sandbox/insights/accounts/:account_id', - 'ads/sandbox/insights/accounts/:account_id/available_audiences', - 'ads/sandbox/line_items/placements', - 'ads/sandbox/stats/accounts/:account_id', - 'ads/sandbox/stats/accounts/:account_id/campaigns', - 'ads/sandbox/stats/accounts/:account_id/campaigns/:id', - 'ads/sandbox/stats/accounts/:account_id/funding_instruments', - 'ads/sandbox/stats/accounts/:account_id/funding_instruments/:id', - 'ads/sandbox/stats/accounts/:account_id/line_items', - 'ads/sandbox/stats/accounts/:account_id/line_items/:id', - 'ads/sandbox/stats/accounts/:account_id/promoted_accounts', - 'ads/sandbox/stats/accounts/:account_id/promoted_accounts/:id', - 'ads/sandbox/stats/accounts/:account_id/promoted_tweets', - 'ads/sandbox/stats/accounts/:account_id/promoted_tweets/:id', - 'ads/sandbox/stats/accounts/:account_id/reach/campaigns', - 'ads/sandbox/targeting_criteria/app_store_categories', - 'ads/sandbox/targeting_criteria/behavior_taxonomies', - 'ads/sandbox/targeting_criteria/behaviors', - 'ads/sandbox/targeting_criteria/devices', - 'ads/sandbox/targeting_criteria/events', - 'ads/sandbox/targeting_criteria/interests', - 'ads/sandbox/targeting_criteria/languages', - 'ads/sandbox/targeting_criteria/locations', - 'ads/sandbox/targeting_criteria/network_operators', - 'ads/sandbox/targeting_criteria/platform_versions', - 'ads/sandbox/targeting_criteria/platforms', - 'ads/sandbox/targeting_criteria/tv_channels', - 'ads/sandbox/targeting_criteria/tv_genres', - 'ads/sandbox/targeting_criteria/tv_markets', - 'ads/sandbox/targeting_criteria/tv_shows', - 'ads/stats/accounts/:account_id', - 'ads/stats/accounts/:account_id/campaigns', - 'ads/stats/accounts/:account_id/campaigns/:id', - 'ads/stats/accounts/:account_id/funding_instruments', - 'ads/stats/accounts/:account_id/funding_instruments/:id', - 'ads/stats/accounts/:account_id/line_items', - 'ads/stats/accounts/:account_id/line_items/:id', - 'ads/stats/accounts/:account_id/promoted_accounts', - 'ads/stats/accounts/:account_id/promoted_accounts/:id', - 'ads/stats/accounts/:account_id/promoted_tweets', - 'ads/stats/accounts/:account_id/promoted_tweets/:id', - 'ads/stats/accounts/:account_id/reach/campaigns', - 'ads/targeting_criteria/app_store_categories', - 'ads/targeting_criteria/behavior_taxonomies', - 'ads/targeting_criteria/behaviors', - 'ads/targeting_criteria/devices', - 'ads/targeting_criteria/events', - 'ads/targeting_criteria/interests', - 'ads/targeting_criteria/languages', - 'ads/targeting_criteria/locations', - 'ads/targeting_criteria/network_operators', - 'ads/targeting_criteria/platform_versions', - 'ads/targeting_criteria/platforms', - 'ads/targeting_criteria/tv_channels', - 'ads/targeting_criteria/tv_genres', - 'ads/targeting_criteria/tv_markets', - 'ads/targeting_criteria/tv_shows', - 'application/rate_limit_status', - 'blocks/ids', - 'blocks/list', - 'collections/entries', - 'collections/list', - 'collections/show', - 'direct_messages', - 'direct_messages/sent', - 'direct_messages/show', - 'favorites/list', - 'followers/ids', - 'followers/list', - 'friends/ids', - 'friends/list', - 'friendships/incoming', - 'friendships/lookup', - 'friendships/lookup', - 'friendships/no_retweets/ids', - 'friendships/outgoing', - 'friendships/show', - 'geo/id/:place_id', - 'geo/reverse_geocode', - 'geo/search', - 'geo/similar_places', - 'help/configuration', - 'help/languages', - 'help/privacy', - 'help/tos', - 'lists/list', - 'lists/members', - 'lists/members/show', - 'lists/memberships', - 'lists/ownerships', - 'lists/show', - 'lists/statuses', - 'lists/subscribers', - 'lists/subscribers/show', - 'lists/subscriptions', - 'mutes/users/ids', - 'mutes/users/list', - 'oauth/authenticate', - 'oauth/authorize', - 'saved_searches/list', - 'saved_searches/show/:id', - 'search/tweets', - 'site', - 'statuses/firehose', - 'statuses/home_timeline', - 'statuses/mentions_timeline', - 'statuses/oembed', - 'statuses/retweeters/ids', - 'statuses/retweets/:id', - 'statuses/retweets_of_me', - 'statuses/sample', - 'statuses/show/:id', - 'statuses/user_timeline', - 'trends/available', - 'trends/closest', - 'trends/place', - 'user', - 'users/contributees', - 'users/contributors', - 'users/profile_banner', - 'users/search', - 'users/show', - 'users/suggestions', - 'users/suggestions/:slug', - 'users/suggestions/:slug/members' - ], - 'POST' => [ - 'account/remove_profile_banner', - 'account/settings', - 'account/update_delivery_device', - 'account/update_profile', - 'account/update_profile_background_image', - 'account/update_profile_banner', - 'account/update_profile_colors', - 'account/update_profile_image', - 'ads/accounts/:account_id/app_lists', - 'ads/accounts/:account_id/campaigns', - 'ads/accounts/:account_id/cards/app_download', - 'ads/accounts/:account_id/cards/image_app_download', - 'ads/accounts/:account_id/cards/image_conversation', - 'ads/accounts/:account_id/cards/lead_gen', - 'ads/accounts/:account_id/cards/video_app_download', - 'ads/accounts/:account_id/cards/video_conversation', - 'ads/accounts/:account_id/cards/website', - 'ads/accounts/:account_id/line_items', - 'ads/accounts/:account_id/promoted_accounts', - 'ads/accounts/:account_id/promoted_tweets', - 'ads/accounts/:account_id/tailored_audience_changes', - 'ads/accounts/:account_id/tailored_audiences', - 'ads/accounts/:account_id/targeting_criteria', - 'ads/accounts/:account_id/tweet', - 'ads/accounts/:account_id/videos', - 'ads/accounts/:account_id/web_event_tags', - 'ads/batch/accounts/:account_id/campaigns', - 'ads/batch/accounts/:account_id/line_items', - 'ads/sandbox/accounts/:account_id/app_lists', - 'ads/sandbox/accounts/:account_id/campaigns', - 'ads/sandbox/accounts/:account_id/cards/app_download', - 'ads/sandbox/accounts/:account_id/cards/image_app_download', - 'ads/sandbox/accounts/:account_id/cards/image_conversation', - 'ads/sandbox/accounts/:account_id/cards/lead_gen', - 'ads/sandbox/accounts/:account_id/cards/video_app_download', - 'ads/sandbox/accounts/:account_id/cards/video_conversation', - 'ads/sandbox/accounts/:account_id/cards/website', - 'ads/sandbox/accounts/:account_id/line_items', - 'ads/sandbox/accounts/:account_id/promoted_accounts', - 'ads/sandbox/accounts/:account_id/promoted_tweets', - 'ads/sandbox/accounts/:account_id/tailored_audience_changes', - 'ads/sandbox/accounts/:account_id/tailored_audiences', - 'ads/sandbox/accounts/:account_id/targeting_criteria', - 'ads/sandbox/accounts/:account_id/tweet', - 'ads/sandbox/accounts/:account_id/videos', - 'ads/sandbox/accounts/:account_id/web_event_tags', - 'ads/sandbox/batch/accounts/:account_id/campaigns', - 'ads/sandbox/batch/accounts/:account_id/line_items', - 'blocks/create', - 'blocks/destroy', - 'collections/create', - 'collections/destroy', - 'collections/entries/add', - 'collections/entries/curate', - 'collections/entries/move', - 'collections/entries/remove', - 'collections/update', - 'direct_messages/destroy', - 'direct_messages/new', - 'favorites/create', - 'favorites/destroy', - 'friendships/create', - 'friendships/destroy', - 'friendships/update', - 'lists/create', - 'lists/destroy', - 'lists/members/create', - 'lists/members/create_all', - 'lists/members/destroy', - 'lists/members/destroy_all', - 'lists/subscribers/create', - 'lists/subscribers/destroy', - 'lists/update', - 'media/upload', - 'mutes/users/create', - 'mutes/users/destroy', - 'oauth/access_token', - 'oauth/request_token', - 'oauth2/invalidate_token', - 'oauth2/token', - 'saved_searches/create', - 'saved_searches/destroy/:id', - 'statuses/destroy/:id', - 'statuses/filter', - 'statuses/lookup', - 'statuses/retweet/:id', - 'statuses/update', - 'statuses/update_with_media', // deprecated, use media/upload - 'ton/bucket/:bucket', - 'ton/bucket/:bucket?resumable=true', - 'users/lookup', - 'users/report_spam' - ], - 'PUT' => [ - 'ads/accounts/:account_id/campaigns/:campaign_id', - 'ads/accounts/:account_id/cards/app_download/:card_id', - 'ads/accounts/:account_id/cards/image_app_download/:card_id', - 'ads/accounts/:account_id/cards/image_conversation/:card_id', - 'ads/accounts/:account_id/cards/lead_gen/:card_id', - 'ads/accounts/:account_id/cards/video_app_download/:id', - 'ads/accounts/:account_id/cards/video_conversation/:card_id', - 'ads/accounts/:account_id/cards/website/:card_id', - 'ads/accounts/:account_id/line_items/:line_item_id', - 'ads/accounts/:account_id/promoted_tweets/:id', - 'ads/accounts/:account_id/tailored_audiences/global_opt_out', - 'ads/accounts/:account_id/targeting_criteria', - 'ads/accounts/:account_id/videos/:id', - 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', - 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', - 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', - 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', - 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', - 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', - 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', - 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', - 'ads/sandbox/accounts/:account_id/cards/website/:card_id', - 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', - 'ads/sandbox/accounts/:account_id/promoted_tweets/:id', - 'ads/sandbox/accounts/:account_id/tailored_audiences/global_opt_out', - 'ads/sandbox/accounts/:account_id/targeting_criteria', - 'ads/sandbox/accounts/:account_id/videos/:id', - 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id', - 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' - ], - 'DELETE' => [ - 'ads/accounts/:account_id/campaigns/:campaign_id', - 'ads/accounts/:account_id/cards/app_download/:card_id', - 'ads/accounts/:account_id/cards/image_app_download/:card_id', - 'ads/accounts/:account_id/cards/image_conversation/:card_id', - 'ads/accounts/:account_id/cards/lead_gen/:card_id', - 'ads/accounts/:account_id/cards/video_app_download/:id', - 'ads/accounts/:account_id/cards/video_conversation/:card_id', - 'ads/accounts/:account_id/cards/website/:card_id', - 'ads/accounts/:account_id/line_items/:line_item_id', - 'ads/accounts/:account_id/promoted_tweets/:id', - 'ads/accounts/:account_id/tailored_audiences/:id', - 'ads/accounts/:account_id/targeting_criteria/:id', - 'ads/accounts/:account_id/videos/:id', - 'ads/accounts/:account_id/web_event_tags/:web_event_tag_id', - 'ads/sandbox/accounts/:account_id/campaigns/:campaign_id', - 'ads/sandbox/accounts/:account_id/cards/app_download/:card_id', - 'ads/sandbox/accounts/:account_id/cards/image_app_download/:card_id', - 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id', - 'ads/sandbox/accounts/:account_id/cards/lead_gen/:card_id', - 'ads/sandbox/accounts/:account_id/cards/video_app_download/:id', - 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id', - 'ads/sandbox/accounts/:account_id/cards/website/:card_id', - 'ads/sandbox/accounts/:account_id/line_items/:line_item_id', - 'ads/sandbox/accounts/:account_id/promoted_tweets/:id', - 'ads/sandbox/accounts/:account_id/tailored_audiences/:id', - 'ads/sandbox/accounts/:account_id/targeting_criteria/:id', - 'ads/sandbox/accounts/:account_id/videos/:id', - 'ads/sandbox/accounts/:account_id/web_event_tags/:web_event_tag_id' - ] - ]; - return $httpmethods; - } - - /** - * Main API handler working on any requests you issue - * - * @param string $fn The member function you called - * @param array $params The parameters you sent along - * - * @return string The API reply encoded in the set return_format - */ - - public function __call($fn, $params) - { - // parse parameters - $apiparams = $this->_parseApiParams($params); - - // stringify null and boolean parameters - $apiparams = $this->_stringifyNullBoolParams($apiparams); - - $app_only_auth = false; - if (count($params) > 1) { - // convert app_only_auth param to bool - $app_only_auth = !! $params[1]; - } - - // reset token when requesting a new token - // (causes 401 for signature error on subsequent requests) - if ($fn === 'oauth_requestToken') { - $this->setToken(null, null); - } - - // map function name to API method - list($method, $method_template) = $this->_mapFnToApiMethod($fn, $apiparams); - - $httpmethod = $this->_detectMethod($method_template, $apiparams); - $multipart = $this->_detectMultipart($method_template); - - return $this->_callApi( - $httpmethod, - $method, - $method_template, - $apiparams, - $multipart, - $app_only_auth - ); - } - - - /** - * __call() helpers - */ - - /** - * Parse given params, detect query-style params - * - * @param array|string $params Parameters to parse - * - * @return array $apiparams - */ - protected function _parseApiParams($params) - { - $apiparams = []; - if (count($params) === 0) { - return $apiparams; - } - - if (is_array($params[0])) { - // given parameters are array - $apiparams = $params[0]; - return $apiparams; - } - - // user gave us query-style params - parse_str($params[0], $apiparams); - if (! is_array($apiparams)) { - $apiparams = []; - } - - return $apiparams; - } - - /** - * Replace null and boolean parameters with their string representations - * - * @param array $apiparams Parameter array to replace in - * - * @return array $apiparams - */ - protected function _stringifyNullBoolParams($apiparams) - { - foreach ($apiparams as $key => $value) { - if (! is_scalar($value)) { - // no need to try replacing arrays - continue; - } - if (is_null($value)) { - $apiparams[$key] = 'null'; - } elseif (is_bool($value)) { - $apiparams[$key] = $value ? 'true' : 'false'; - } - } - - return $apiparams; - } - - /** - * Maps called PHP magic method name to Twitter API method - * - * @param string $fn Function called - * @param array $apiparams byref API parameters - * - * @return string[] (string method, string method_template) - */ - protected function _mapFnToApiMethod($fn, &$apiparams) - { - // replace _ by / - $method = $this->_mapFnInsertSlashes($fn); - - // undo replacement for URL parameters - $method = $this->_mapFnRestoreParamUnderscores($method); - - // replace AA by URL parameters - $method_template = $method; - $match = []; - if (preg_match_all('/[A-Z_]{2,}/', $method, $match)) { - foreach ($match[0] as $param) { - $param_l = strtolower($param); - if ($param_l === 'resumeid') { - $param_l = 'resumeId'; - } - $method_template = str_replace($param, ':' . $param_l, $method_template); - if (! isset($apiparams[$param_l])) { - for ($i = 0; $i < 26; $i++) { - $method_template = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method_template); - } - throw new \Exception( - 'To call the templated method "' . $method_template - . '", specify the parameter value for "' . $param_l . '".' - ); - } - $method = str_replace($param, $apiparams[$param_l], $method); - unset($apiparams[$param_l]); - } - } - - if (substr($method, 0, 4) !== 'ton/') { - // replace A-Z by _a-z - for ($i = 0; $i < 26; $i++) { - $method = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method); - $method_template = str_replace(chr(65 + $i), '_' . chr(97 + $i), $method_template); - } - } - - return [$method, $method_template]; - } - - /** - * API method mapping: Replaces _ with / character - * - * @param string $fn Function called - * - * @return string API method to call - */ - protected function _mapFnInsertSlashes($fn) - { - $path = explode('_', $fn); - $method = implode('/', $path); - - return $method; - } - - /** - * API method mapping: Restore _ character in named parameters - * - * @param string $method API method to call - * - * @return string API method with restored underscores - */ - protected function _mapFnRestoreParamUnderscores($method) - { - $url_parameters_with_underscore = [ - 'screen_name', 'place_id', - 'account_id', 'campaign_id', 'card_id', 'line_item_id', - 'tweet_id', 'web_event_tag_id' - ]; - foreach ($url_parameters_with_underscore as $param) { - $param = strtoupper($param); - $replacement_was = str_replace('_', '/', $param); - $method = str_replace($replacement_was, $param, $method); - } - - return $method; - } - - - /** - * Uncommon API methods - */ - - /** - * Gets the OAuth authenticate URL for the current request token - * - * @param optional bool $force_login Whether to force the user to enter their login data - * @param optional string $screen_name Screen name to repopulate the user name with - * @param optional string $type 'authenticate' or 'authorize', to avoid duplicate code - * - * @return string The OAuth authenticate/authorize URL - */ - public function oauth_authenticate($force_login = NULL, $screen_name = NULL, $type = 'authenticate') - { - if (! in_array($type, ['authenticate', 'authorize'])) { - throw new \Exception('To get the ' . $type . ' URL, use the correct third parameter, or omit it.'); - } - if ($this->_oauth_token === null) { - throw new \Exception('To get the ' . $type . ' URL, the OAuth token must be set.'); - } - $url = self::$_endpoint_oauth . 'oauth/' . $type . '?oauth_token=' . $this->_url($this->_oauth_token); - if ($force_login) { - $url .= "&force_login=1"; - } - if ($screen_name) { - $url .= "&screen_name=" . $screen_name; - } - return $url; - } - - /** - * Gets the OAuth authorize URL for the current request token - * @param optional bool $force_login Whether to force the user to enter their login data - * @param optional string $screen_name Screen name to repopulate the user name with - * - * @return string The OAuth authorize URL - */ - public function oauth_authorize($force_login = NULL, $screen_name = NULL) - { - return $this->oauth_authenticate($force_login, $screen_name, 'authorize'); - } - - /** - * Gets the OAuth bearer token - * - * @return string The OAuth bearer token - */ - - public function oauth2_token() - { - if ($this->_use_curl) { - return $this->_oauth2TokenCurl(); - } - return $this->_oauth2TokenNoCurl(); - } - - /** - * Gets a cURL handle - * @param string $url the URL for the curl initialization - * @return resource handle - */ - protected function getCurlInitialization($url) - { - $ch = curl_init($url); - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cacert.pem'); - curl_setopt( - $ch, CURLOPT_USERAGENT, - 'codebird-php/' . $this->getVersion() . ' +https://github.com/jublonet/codebird-php' - ); - - if ($this->hasProxy()) { - curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - curl_setopt($ch, CURLOPT_PROXY, $this->getProxyHost()); - curl_setopt($ch, CURLOPT_PROXYPORT, $this->getProxyPort()); - - if ($this->hasProxyAuthentication()) { - curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->getProxyAuthentication()); - } - } - - return $ch; - } - - /** - * Gets a non cURL initialization - * - * @param string $url the URL for the curl initialization - * @param array $contextOptions the options for the stream context - * @param string $hostname the hostname to verify the SSL FQDN for - * - * @return array the read data - */ - protected function getNoCurlInitialization($url, $contextOptions, $hostname = '') - { - $httpOptions = []; - - $httpOptions['header'] = [ - 'User-Agent: codebird-php/' . $this->getVersion() . ' +https://github.com/jublonet/codebird-php' - ]; - - $httpOptions['ssl'] = [ - 'verify_peer' => true, - 'cafile' => __DIR__ . '/cacert.pem', - 'verify_depth' => 5, - 'peer_name' => $hostname - ]; - - if ($this->hasProxy()) { - $httpOptions['request_fulluri'] = true; - $httpOptions['proxy'] = $this->getProxyHost() . ':' . $this->getProxyPort(); - - if ($this->hasProxyAuthentication()) { - $httpOptions['header'][] = - 'Proxy-Authorization: Basic ' . base64_encode($this->getProxyAuthentication()); - } - } - - // merge the http options with the context options - $options = array_merge_recursive( - $contextOptions, - ['http' => $httpOptions] - ); - - // concatenate $options['http']['header'] - $options['http']['header'] = implode("\r\n", $options['http']['header']); - - // silent the file_get_contents function - $content = @file_get_contents($url, false, stream_context_create($options)); - - $headers = []; - // API is responding - if (isset($http_response_header)) { - $headers = $http_response_header; - } - - return [ - $content, - $headers - ]; - } - - protected function hasProxy() - { - if ($this->getProxyHost() === null) { - return false; - } - - if ($this->getProxyPort() === null) { - return false; - } - - return true; - } - - protected function hasProxyAuthentication() - { - if ($this->getProxyAuthentication() === null) { - return false; - } - - return true; - } - - /** - * Gets the proxy host - * - * @return string The proxy host - */ - protected function getProxyHost() - { - return $this->getProxyData('host'); - } - - /** - * Gets the proxy port - * - * @return string The proxy port - */ - protected function getProxyPort() - { - return $this->getProxyData('port'); - } - - /** - * Gets the proxy authentication - * - * @return string The proxy authentication - */ - protected function getProxyAuthentication() - { - return $this->getProxyData('authentication'); - } - - /** - * @param string $name - */ - private function getProxyData($name) - { - if (empty($this->_proxy[$name])) { - return null; - } - - return $this->_proxy[$name]; - } - - /** - * Gets the OAuth bearer token, using cURL - * - * @return string The OAuth bearer token - */ - - protected function _oauth2TokenCurl() - { - if (self::$_oauth_consumer_key === null) { - throw new \Exception('To obtain a bearer token, the consumer key must be set.'); - } - $post_fields = [ - 'grant_type' => 'client_credentials' - ]; - $url = self::$_endpoint_oauth . 'oauth2/token'; - $ch = $this->getCurlInitialization($url); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields); - - curl_setopt($ch, CURLOPT_USERPWD, self::$_oauth_consumer_key . ':' . self::$_oauth_consumer_secret); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Expect:' - ]); - $result = curl_exec($ch); - - // catch request errors - if ($result === false) { - throw new \Exception('Request error for bearer token: ' . curl_error($ch)); - } - - // certificate validation results - $validation_result = curl_errno($ch); - $this->_validateSslCertificate($validation_result); - - $httpstatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $reply = $this->_parseBearerReply($result, $httpstatus); - return $reply; - } - - /** - * Gets the OAuth bearer token, without cURL - * - * @return string The OAuth bearer token - */ - - protected function _oauth2TokenNoCurl() - { - if (self::$_oauth_consumer_key == null) { - throw new \Exception('To obtain a bearer token, the consumer key must be set.'); - } - - $url = self::$_endpoint_oauth . 'oauth2/token'; - $hostname = parse_url($url, PHP_URL_HOST); - - if ($hostname === false) { - throw new \Exception('Incorrect API endpoint host.'); - } - - $contextOptions = [ - 'http' => [ - 'method' => 'POST', - 'protocol_version' => '1.1', - 'header' => "Accept: */*\r\n" - . 'Authorization: Basic ' - . base64_encode( - self::$_oauth_consumer_key - . ':' - . self::$_oauth_consumer_secret - ), - 'timeout' => $this->_timeout / 1000, - 'content' => 'grant_type=client_credentials', - 'ignore_errors' => true - ] - ]; - list($reply, $headers) = $this->getNoCurlInitialization($url, $contextOptions, $hostname); - $result = ''; - foreach ($headers as $header) { - $result .= $header . "\r\n"; - } - $result .= "\r\n" . $reply; - - // find HTTP status - $httpstatus = '500'; - $match = []; - if (!empty($headers[0]) && preg_match('/HTTP\/\d\.\d (\d{3})/', $headers[0], $match)) { - $httpstatus = $match[1]; - } - - $reply = $this->_parseBearerReply($result, $httpstatus); - return $reply; - } - - - /** - * General helpers to avoid duplicate code - */ - - /** - * Parse oauth2_token reply and set bearer token, if found - * - * @param string $result Raw HTTP response - * @param int $httpstatus HTTP status code - * - * @return string reply - */ - protected function _parseBearerReply($result, $httpstatus) - { - list($headers, $reply) = $this->_parseApiHeaders($result); - $reply = $this->_parseApiReply($reply); - $rate = $this->_getRateLimitInfo($headers); - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - $reply['httpstatus'] = $httpstatus; - $reply['rate'] = $rate; - if ($httpstatus === 200) { - self::setBearerToken($reply['access_token']); - } - break; - case CODEBIRD_RETURNFORMAT_JSON: - if ($httpstatus === 200) { - $parsed = json_decode($reply, false, 512, JSON_BIGINT_AS_STRING); - self::setBearerToken($parsed->access_token); - } - break; - case CODEBIRD_RETURNFORMAT_OBJECT: - $reply->httpstatus = $httpstatus; - $reply->rate = $rate; - if ($httpstatus === 200) { - self::setBearerToken($reply->access_token); - } - break; - } - return $reply; - } - - /** - * Extract rate-limiting data from response headers - * - * @param array $headers The CURL response headers - * - * @return null|array The rate-limiting information - */ - protected function _getRateLimitInfo($headers) - { - if (! isset($headers['x-rate-limit-limit'])) { - return null; - } - return [ - 'limit' => $headers['x-rate-limit-limit'], - 'remaining' => $headers['x-rate-limit-remaining'], - 'reset' => $headers['x-rate-limit-reset'] - ]; - } - - /** - * Check if there were any SSL certificate errors - * - * @param int $validation_result The curl error number - * - * @return void - */ - protected function _validateSslCertificate($validation_result) - { - if (in_array( - $validation_result, - [ - CURLE_SSL_CERTPROBLEM, - CURLE_SSL_CACERT, - CURLE_SSL_CACERT_BADFILE, - CURLE_SSL_CRL_BADFILE, - CURLE_SSL_ISSUER_ERROR - ] - ) - ) { - throw new \Exception( - 'Error ' . $validation_result - . ' while validating the Twitter API certificate.' - ); - } - } - - /** - * Signing helpers - */ - - /** - * URL-encodes the given data - * - * @param mixed $data - * - * @return mixed The encoded data - */ - protected function _url($data) - { - if (is_array($data)) { - return array_map([ - $this, - '_url' - ], $data); - } elseif (is_scalar($data)) { - return str_replace([ - '+', - '!', - '*', - "'", - '(', - ')' - ], [ - ' ', - '%21', - '%2A', - '%27', - '%28', - '%29' - ], rawurlencode($data)); - } else { - return ''; - } - } - - /** - * Gets the base64-encoded SHA1 hash for the given data - * - * @param string $data The data to calculate the hash from - * - * @return string The hash - */ - protected function _sha1($data) - { - if (self::$_oauth_consumer_secret === null) { - throw new \Exception('To generate a hash, the consumer secret must be set.'); - } - if (!function_exists('hash_hmac')) { - throw new \Exception('To generate a hash, the PHP hash extension must be available.'); - } - return base64_encode(hash_hmac( - 'sha1', - $data, - self::$_oauth_consumer_secret - . '&' - . ($this->_oauth_token_secret !== null - ? $this->_oauth_token_secret - : '' - ), - true - )); - } - - /** - * Generates a (hopefully) unique random string - * - * @param int optional $length The length of the string to generate - * - * @return string The random string - */ - protected function _nonce($length = 8) - { - if ($length < 1) { - throw new \Exception('Invalid nonce length.'); - } - return substr(md5(microtime(true)), 0, $length); - } - - /** - * Signature helper - * - * @param string $httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string $method The API method to call - * @param array $base_params The signature base parameters - * - * @return string signature - */ - protected function _getSignature($httpmethod, $method, $base_params) - { - // convert params to string - $base_string = ''; - foreach ($base_params as $key => $value) { - $base_string .= $key . '=' . $value . '&'; - } - - // trim last ampersand - $base_string = substr($base_string, 0, -1); - - // hash it - return $this->_sha1( - $httpmethod . '&' . - $this->_url($method) . '&' . - $this->_url($base_string) - ); - } - - /** - * Generates an OAuth signature - * - * @param string $httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string $method The API method to call - * @param array optional $params The API call parameters, associative - * @param bool optional append_to_get Whether to append the OAuth params to GET - * - * @return string Authorization HTTP header - */ - protected function _sign($httpmethod, $method, $params = [], $append_to_get = false) - { - if (self::$_oauth_consumer_key === null) { - throw new \Exception('To generate a signature, the consumer key must be set.'); - } - $sign_base_params = array_map( - [$this, '_url'], - [ - 'oauth_consumer_key' => self::$_oauth_consumer_key, - 'oauth_version' => '1.0', - 'oauth_timestamp' => time(), - 'oauth_nonce' => $this->_nonce(), - 'oauth_signature_method' => 'HMAC-SHA1' - ] - ); - if ($this->_oauth_token !== null) { - $sign_base_params['oauth_token'] = $this->_url($this->_oauth_token); - } - $oauth_params = $sign_base_params; - - // merge in the non-OAuth params - $sign_base_params = array_merge( - $sign_base_params, - array_map([$this, '_url'], $params) - ); - ksort($sign_base_params); - - $signature = $this->_getSignature($httpmethod, $method, $sign_base_params); - - $params = $append_to_get ? $sign_base_params : $oauth_params; - $params['oauth_signature'] = $signature; - - ksort($params); - if ($append_to_get) { - $authorization = ''; - foreach ($params as $key => $value) { - $authorization .= $key . '="' . $this->_url($value) . '", '; - } - return substr($authorization, 0, -1); - } - $authorization = 'OAuth '; - foreach ($params as $key => $value) { - $authorization .= $key . "=\"" . $this->_url($value) . "\", "; - } - return substr($authorization, 0, -2); - } - - /** - * Detects HTTP method to use for API call - * - * @param string $method The API method to call - * @param array byref $params The parameters to send along - * - * @return string The HTTP method that should be used - */ - protected function _detectMethod($method, &$params) - { - if (isset($params['httpmethod'])) { - $httpmethod = $params['httpmethod']; - unset($params['httpmethod']); - return $httpmethod; - } - $apimethods = $this->getApiMethods(); - - // multi-HTTP method endpoints - switch ($method) { - case 'ads/accounts/:account_id/campaigns': - case 'ads/sandbox/accounts/:account_id/campaigns': - if (isset($params['funding_instrument_id'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/line_items': - case 'ads/sandbox/accounts/:account_id/line_items': - if (isset($params['campaign_id'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/targeting_criteria': - case 'ads/sandbox/accounts/:account_id/targeting_criteria': - if (isset($params['targeting_value'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/app_lists': - case 'ads/accounts/:account_id/campaigns': - case 'ads/accounts/:account_id/cards/app_download': - case 'ads/accounts/:account_id/cards/image_app_download': - case 'ads/accounts/:account_id/cards/image_conversion': - case 'ads/accounts/:account_id/cards/lead_gen': - case 'ads/accounts/:account_id/cards/video_app_download': - case 'ads/accounts/:account_id/cards/video_conversation': - case 'ads/accounts/:account_id/cards/website': - case 'ads/accounts/:account_id/tailored_audiences': - case 'ads/accounts/:account_id/web_event_tags': - case 'ads/sandbox/accounts/:account_id/app_lists': - case 'ads/sandbox/accounts/:account_id/campaigns': - case 'ads/sandbox/accounts/:account_id/cards/app_download': - case 'ads/sandbox/accounts/:account_id/cards/image_app_download': - case 'ads/sandbox/accounts/:account_id/cards/image_conversion': - case 'ads/sandbox/accounts/:account_id/cards/lead_gen': - case 'ads/sandbox/accounts/:account_id/cards/video_app_download': - case 'ads/sandbox/accounts/:account_id/cards/video_conversation': - case 'ads/sandbox/accounts/:account_id/cards/website': - case 'ads/sandbox/accounts/:account_id/tailored_audiences': - case 'ads/sandbox/accounts/:account_id/web_event_tags': - if (isset($params['name'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/promoted_accounts': - case 'ads/sandbox/accounts/:account_id/promoted_accounts': - if (isset($params['user_id'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/promoted_tweets': - case 'ads/sandbox/accounts/:account_id/promoted_tweets': - if (isset($params['tweet_ids'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/videos': - case 'ads/sandbox/accounts/:account_id/videos': - if (isset($params['video_media_id'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/tailored_audience_changes': - case 'ads/sandbox/accounts/:account_id/tailored_audience_changes': - if (isset($params['tailored_audience_id'])) { - return 'POST'; - } - break; - case 'ads/accounts/:account_id/cards/image_conversation/:card_id': - case 'ads/accounts/:account_id/cards/video_conversation/:card_id': - case 'ads/accounts/:account_id/cards/website/:card_id': - case 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id': - case 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id': - case 'ads/sandbox/accounts/:account_id/cards/website/:card_id': - if (isset($params['name'])) { - return 'PUT'; - } - break; - default: - // prefer POST and PUT if parameters are set - if (count($params) > 0) { - if (isset($apimethods['POST'][$method])) { - return 'POST'; - } - if (isset($apimethods['PUT'][$method])) { - return 'PUT'; - } - } - } - - foreach ($apimethods as $httpmethod => $methods) { - if (in_array($method, $methods)) { - return $httpmethod; - } - } - throw new \Exception('Can\'t find HTTP method to use for "' . $method . '".'); - } - - /** - * Detects if API call should use multipart/form-data - * - * @param string $method The API method to call - * - * @return bool Whether the method should be sent as multipart - */ - protected function _detectMultipart($method) - { - $multiparts = [ - // Tweets - 'statuses/update_with_media', - 'media/upload', - - // Users - 'account/update_profile_background_image', - 'account/update_profile_image', - 'account/update_profile_banner' - ]; - return in_array($method, $multiparts); - } - - /** - * Merge multipart string from parameters array - * - * @param array $possible_files List of possible filename parameters - * @param string $border The multipart border - * @param array $params The parameters to send along - * - * @return string request - */ - protected function _getMultipartRequestFromParams($possible_files, $border, $params) - { - $request = ''; - foreach ($params as $key => $value) { - // is it an array? - if (is_array($value)) { - throw new \Exception('Using URL-encoded parameters is not supported for uploading media.'); - } - $request .= - '--' . $border . "\r\n" - . 'Content-Disposition: form-data; name="' . $key . '"'; - - // check for filenames - if (in_array($key, $possible_files)) { - if (// is it a file, a readable one? - @file_exists($value) - && @is_readable($value) - - // is it a valid image? - && $data = @getimagesize($value) - ) { - // is it a supported image format? - if (in_array($data[2], $this->_supported_media_files)) { - // try to read the file - $data = @file_get_contents($value); - if ($data === false || strlen($data) === 0) { - continue; - } - $value = $data; - } - } elseif (// is it a remote file? - filter_var($value, FILTER_VALIDATE_URL) - && preg_match('/^https?:\/\//', $value) - ) { - $data = $this->_fetchRemoteFile($value); - if ($data !== false) { - $value = $data; - } - } - } + $httpOptions['ssl'] = [ + 'verify_peer' => true, + 'cafile' => __DIR__ . '/cacert.pem', + 'verify_depth' => 5, + 'peer_name' => $hostname + ]; - $request .= "\r\n\r\n" . $value . "\r\n"; - } + if ($this->hasProxy()) { + $httpOptions['request_fulluri'] = true; + $httpOptions['proxy'] = $this->getProxyHost() . ':' . $this->getProxyPort(); - return $request; + if ($this->hasProxyAuthentication()) { + $httpOptions['header'][] = + 'Proxy-Authorization: Basic ' . base64_encode($this->getProxyAuthentication()); + } } + // merge the http options with the context options + $options = array_merge_recursive( + $contextOptions, + ['http' => $httpOptions] + ); - /** - * Detect filenames in upload parameters, - * build multipart request from upload params - * - * @param string $method The API method to call - * @param array $params The parameters to send along - * - * @return null|string - */ - protected function _buildMultipart($method, $params) - { - // well, files will only work in multipart methods - if (! $this->_detectMultipart($method)) { - return; - } - - // only check specific parameters - $possible_files = [ - // Tweets - 'statuses/update_with_media' => 'media[]', - 'media/upload' => 'media', - // Accounts - 'account/update_profile_background_image' => 'image', - 'account/update_profile_image' => 'image', - 'account/update_profile_banner' => 'banner' - ]; - // method might have files? - if (! in_array($method, array_keys($possible_files))) { - return; - } - - $possible_files = explode(' ', $possible_files[$method]); + // concatenate $options['http']['header'] + $options['http']['header'] = implode("\r\n", $options['http']['header']); - $multipart_border = '--------------------' . $this->_nonce(); - $multipart_request = - $this->_getMultipartRequestFromParams($possible_files, $multipart_border, $params) - . '--' . $multipart_border . '--'; + // silent the file_get_contents function + $content = @file_get_contents($url, false, stream_context_create($options)); - return $multipart_request; + $headers = []; + // API is responding + if (isset($http_response_header)) { + $headers = $http_response_header; } - /** - * Detect filenames in upload parameters - * - * @param mixed $input The data or file name to parse - * - * @return null|string - */ - protected function _buildBinaryBody($input) - { + return [ + $content, + $headers + ]; + } + + protected function hasProxy() + { + if ($this->getProxyHost() === null) { + return false; + } + + if ($this->getProxyPort() === null) { + return false; + } + + return true; + } + + protected function hasProxyAuthentication() + { + if ($this->getProxyAuthentication() === null) { + return false; + } + + return true; + } + + /** + * Gets the proxy host + * + * @return string The proxy host + */ + protected function getProxyHost() + { + return $this->getProxyData('host'); + } + + /** + * Gets the proxy port + * + * @return string The proxy port + */ + protected function getProxyPort() + { + return $this->getProxyData('port'); + } + + /** + * Gets the proxy authentication + * + * @return string The proxy authentication + */ + protected function getProxyAuthentication() + { + return $this->getProxyData('authentication'); + } + + /** + * @param string $name + */ + private function getProxyData($name) + { + if (empty($this->_proxy[$name])) { + return null; + } + + return $this->_proxy[$name]; + } + + /** + * Gets the OAuth bearer token, using cURL + * + * @return string The OAuth bearer token + */ + + protected function _oauth2TokenCurl() + { + if (self::$_oauth_consumer_key === null) { + throw new \Exception('To obtain a bearer token, the consumer key must be set.'); + } + $post_fields = [ + 'grant_type' => 'client_credentials' + ]; + $url = self::$_endpoint_oauth . 'oauth2/token'; + $ch = $this->getCurlInitialization($url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields); + + curl_setopt($ch, CURLOPT_USERPWD, self::$_oauth_consumer_key . ':' . self::$_oauth_consumer_secret); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Expect:' + ]); + $result = curl_exec($ch); + + // catch request errors + if ($result === false) { + throw new \Exception('Request error for bearer token: ' . curl_error($ch)); + } + + // certificate validation results + $validation_result = curl_errno($ch); + $this->_validateSslCertificate($validation_result); + + $httpstatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $reply = $this->_parseBearerReply($result, $httpstatus); + return $reply; + } + + /** + * Gets the OAuth bearer token, without cURL + * + * @return string The OAuth bearer token + */ + + protected function _oauth2TokenNoCurl() + { + if (self::$_oauth_consumer_key == null) { + throw new \Exception('To obtain a bearer token, the consumer key must be set.'); + } + + $url = self::$_endpoint_oauth . 'oauth2/token'; + $hostname = parse_url($url, PHP_URL_HOST); + + if ($hostname === false) { + throw new \Exception('Incorrect API endpoint host.'); + } + + $contextOptions = [ + 'http' => [ + 'method' => 'POST', + 'protocol_version' => '1.1', + 'header' => "Accept: */*\r\n" + . 'Authorization: Basic ' + . base64_encode( + self::$_oauth_consumer_key + . ':' + . self::$_oauth_consumer_secret + ), + 'timeout' => $this->_timeout / 1000, + 'content' => 'grant_type=client_credentials', + 'ignore_errors' => true + ] + ]; + list($reply, $headers) = $this->getNoCurlInitialization($url, $contextOptions, $hostname); + $result = ''; + foreach ($headers as $header) { + $result .= $header . "\r\n"; + } + $result .= "\r\n" . $reply; + + // find HTTP status + $httpstatus = '500'; + $match = []; + if (!empty($headers[0]) && preg_match('/HTTP\/\d\.\d (\d{3})/', $headers[0], $match)) { + $httpstatus = $match[1]; + } + + $reply = $this->_parseBearerReply($result, $httpstatus); + return $reply; + } + + + /** + * General helpers to avoid duplicate code + */ + + /** + * Parse oauth2_token reply and set bearer token, if found + * + * @param string $result Raw HTTP response + * @param int $httpstatus HTTP status code + * + * @return string reply + */ + protected function _parseBearerReply($result, $httpstatus) + { + list($headers, $reply) = $this->_parseApiHeaders($result); + $reply = $this->_parseApiReply($reply); + $rate = $this->_getRateLimitInfo($headers); + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: + $reply['httpstatus'] = $httpstatus; + $reply['rate'] = $rate; + if ($httpstatus === 200) { + self::setBearerToken($reply['access_token']); + } + break; + case CODEBIRD_RETURNFORMAT_JSON: + if ($httpstatus === 200) { + $parsed = json_decode($reply, false, 512, JSON_BIGINT_AS_STRING); + self::setBearerToken($parsed->access_token); + } + break; + case CODEBIRD_RETURNFORMAT_OBJECT: + $reply->httpstatus = $httpstatus; + $reply->rate = $rate; + if ($httpstatus === 200) { + self::setBearerToken($reply->access_token); + } + break; + } + return $reply; + } + + /** + * Extract rate-limiting data from response headers + * + * @param array $headers The CURL response headers + * + * @return null|array The rate-limiting information + */ + protected function _getRateLimitInfo($headers) + { + if (! isset($headers['x-rate-limit-limit'])) { + return null; + } + return [ + 'limit' => $headers['x-rate-limit-limit'], + 'remaining' => $headers['x-rate-limit-remaining'], + 'reset' => $headers['x-rate-limit-reset'] + ]; + } + + /** + * Check if there were any SSL certificate errors + * + * @param int $validation_result The curl error number + * + * @return void + */ + protected function _validateSslCertificate($validation_result) + { + if (in_array( + $validation_result, + [ + CURLE_SSL_CERTPROBLEM, + CURLE_SSL_CACERT, + CURLE_SSL_CACERT_BADFILE, + CURLE_SSL_CRL_BADFILE, + CURLE_SSL_ISSUER_ERROR + ] + ) + ) { + throw new \Exception( + 'Error ' . $validation_result + . ' while validating the Twitter API certificate.' + ); + } + } + + /** + * Signing helpers + */ + + /** + * URL-encodes the given data + * + * @param mixed $data + * + * @return mixed The encoded data + */ + protected function _url($data) + { + if (is_array($data)) { + return array_map([ + $this, + '_url' + ], $data); + } elseif (is_scalar($data)) { + return str_replace([ + '+', + '!', + '*', + "'", + '(', + ')' + ], [ + ' ', + '%21', + '%2A', + '%27', + '%28', + '%29' + ], rawurlencode($data)); + } else { + return ''; + } + } + + /** + * Gets the base64-encoded SHA1 hash for the given data + * + * @param string $data The data to calculate the hash from + * + * @return string The hash + */ + protected function _sha1($data) + { + if (self::$_oauth_consumer_secret === null) { + throw new \Exception('To generate a hash, the consumer secret must be set.'); + } + if (!function_exists('hash_hmac')) { + throw new \Exception('To generate a hash, the PHP hash extension must be available.'); + } + return base64_encode(hash_hmac( + 'sha1', + $data, + self::$_oauth_consumer_secret + . '&' + . ($this->_oauth_token_secret !== null + ? $this->_oauth_token_secret + : '' + ), + true + )); + } + + /** + * Generates a (hopefully) unique random string + * + * @param int optional $length The length of the string to generate + * + * @return string The random string + */ + protected function _nonce($length = 8) + { + if ($length < 1) { + throw new \Exception('Invalid nonce length.'); + } + return substr(md5(microtime(true)), 0, $length); + } + + /** + * Signature helper + * + * @param string $httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string $method The API method to call + * @param array $base_params The signature base parameters + * + * @return string signature + */ + protected function _getSignature($httpmethod, $method, $base_params) + { + // convert params to string + $base_string = ''; + foreach ($base_params as $key => $value) { + $base_string .= $key . '=' . $value . '&'; + } + + // trim last ampersand + $base_string = substr($base_string, 0, -1); + + // hash it + return $this->_sha1( + $httpmethod . '&' . + $this->_url($method) . '&' . + $this->_url($base_string) + ); + } + + /** + * Generates an OAuth signature + * + * @param string $httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string $method The API method to call + * @param array optional $params The API call parameters, associative + * @param bool optional append_to_get Whether to append the OAuth params to GET + * + * @return string Authorization HTTP header + */ + protected function _sign($httpmethod, $method, $params = [], $append_to_get = false) + { + if (self::$_oauth_consumer_key === null) { + throw new \Exception('To generate a signature, the consumer key must be set.'); + } + $sign_base_params = array_map( + [$this, '_url'], + [ + 'oauth_consumer_key' => self::$_oauth_consumer_key, + 'oauth_version' => '1.0', + 'oauth_timestamp' => time(), + 'oauth_nonce' => $this->_nonce(), + 'oauth_signature_method' => 'HMAC-SHA1' + ] + ); + if ($this->_oauth_token !== null) { + $sign_base_params['oauth_token'] = $this->_url($this->_oauth_token); + } + $oauth_params = $sign_base_params; + + // merge in the non-OAuth params + $sign_base_params = array_merge( + $sign_base_params, + array_map([$this, '_url'], $params) + ); + ksort($sign_base_params); + + $signature = $this->_getSignature($httpmethod, $method, $sign_base_params); + + $params = $append_to_get ? $sign_base_params : $oauth_params; + $params['oauth_signature'] = $signature; + + ksort($params); + if ($append_to_get) { + $authorization = ''; + foreach ($params as $key => $value) { + $authorization .= $key . '="' . $this->_url($value) . '", '; + } + return substr($authorization, 0, -1); + } + $authorization = 'OAuth '; + foreach ($params as $key => $value) { + $authorization .= $key . "=\"" . $this->_url($value) . "\", "; + } + return substr($authorization, 0, -2); + } + + /** + * Detects HTTP method to use for API call + * + * @param string $method The API method to call + * @param array byref $params The parameters to send along + * + * @return string The HTTP method that should be used + */ + protected function _detectMethod($method, &$params) + { + if (isset($params['httpmethod'])) { + $httpmethod = $params['httpmethod']; + unset($params['httpmethod']); + return $httpmethod; + } + $apimethods = $this->getApiMethods(); + + // multi-HTTP method endpoints + switch ($method) { + case 'ads/accounts/:account_id/campaigns': + case 'ads/sandbox/accounts/:account_id/campaigns': + if (isset($params['funding_instrument_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/line_items': + case 'ads/sandbox/accounts/:account_id/line_items': + if (isset($params['campaign_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/targeting_criteria': + case 'ads/sandbox/accounts/:account_id/targeting_criteria': + if (isset($params['targeting_value'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/app_lists': + case 'ads/accounts/:account_id/campaigns': + case 'ads/accounts/:account_id/cards/app_download': + case 'ads/accounts/:account_id/cards/image_app_download': + case 'ads/accounts/:account_id/cards/image_conversion': + case 'ads/accounts/:account_id/cards/lead_gen': + case 'ads/accounts/:account_id/cards/video_app_download': + case 'ads/accounts/:account_id/cards/video_conversation': + case 'ads/accounts/:account_id/cards/website': + case 'ads/accounts/:account_id/tailored_audiences': + case 'ads/accounts/:account_id/web_event_tags': + case 'ads/sandbox/accounts/:account_id/app_lists': + case 'ads/sandbox/accounts/:account_id/campaigns': + case 'ads/sandbox/accounts/:account_id/cards/app_download': + case 'ads/sandbox/accounts/:account_id/cards/image_app_download': + case 'ads/sandbox/accounts/:account_id/cards/image_conversion': + case 'ads/sandbox/accounts/:account_id/cards/lead_gen': + case 'ads/sandbox/accounts/:account_id/cards/video_app_download': + case 'ads/sandbox/accounts/:account_id/cards/video_conversation': + case 'ads/sandbox/accounts/:account_id/cards/website': + case 'ads/sandbox/accounts/:account_id/tailored_audiences': + case 'ads/sandbox/accounts/:account_id/web_event_tags': + if (isset($params['name'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/promoted_accounts': + case 'ads/sandbox/accounts/:account_id/promoted_accounts': + if (isset($params['user_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/promoted_tweets': + case 'ads/sandbox/accounts/:account_id/promoted_tweets': + if (isset($params['tweet_ids'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/videos': + case 'ads/sandbox/accounts/:account_id/videos': + if (isset($params['video_media_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/tailored_audience_changes': + case 'ads/sandbox/accounts/:account_id/tailored_audience_changes': + if (isset($params['tailored_audience_id'])) { + return 'POST'; + } + break; + case 'ads/accounts/:account_id/cards/image_conversation/:card_id': + case 'ads/accounts/:account_id/cards/video_conversation/:card_id': + case 'ads/accounts/:account_id/cards/website/:card_id': + case 'ads/sandbox/accounts/:account_id/cards/image_conversation/:card_id': + case 'ads/sandbox/accounts/:account_id/cards/video_conversation/:card_id': + case 'ads/sandbox/accounts/:account_id/cards/website/:card_id': + if (isset($params['name'])) { + return 'PUT'; + } + break; + default: + // prefer POST and PUT if parameters are set + if (count($params) > 0) { + if (isset($apimethods['POST'][$method])) { + return 'POST'; + } + if (isset($apimethods['PUT'][$method])) { + return 'PUT'; + } + } + } + + foreach ($apimethods as $httpmethod => $methods) { + if (in_array($method, $methods)) { + return $httpmethod; + } + } + throw new \Exception('Can\'t find HTTP method to use for "' . $method . '".'); + } + + /** + * Detects if API call should use multipart/form-data + * + * @param string $method The API method to call + * + * @return bool Whether the method should be sent as multipart + */ + protected function _detectMultipart($method) + { + $multiparts = [ + // Tweets + 'statuses/update_with_media', + 'media/upload', + + // Users + 'account/update_profile_background_image', + 'account/update_profile_image', + 'account/update_profile_banner' + ]; + return in_array($method, $multiparts); + } + + /** + * Merge multipart string from parameters array + * + * @param array $possible_files List of possible filename parameters + * @param string $border The multipart border + * @param array $params The parameters to send along + * + * @return string request + */ + protected function _getMultipartRequestFromParams($possible_files, $border, $params) + { + $request = ''; + foreach ($params as $key => $value) { + // is it an array? + if (is_array($value)) { + throw new \Exception('Using URL-encoded parameters is not supported for uploading media.'); + } + $request .= + '--' . $border . "\r\n" + . 'Content-Disposition: form-data; name="' . $key . '"'; + + // check for filenames + if (in_array($key, $possible_files)) { if (// is it a file, a readable one? - @file_exists($input) - && @is_readable($input) + @file_exists($value) + && @is_readable($value) ) { + // is it a supported image format? + $data = @getimagesize($value); + if ((is_array($data) && in_array($data[2], $this->_supported_media_files)) + || imagecreatefromwebp($data) // A WebP image! :-) —why won’t getimagesize support this? + ) { // try to read the file - $data = @file_get_contents($input); - if ($data !== false && strlen($data) !== 0) { - return $data; + $data = @file_get_contents($value); + if ($data === false || strlen($data) === 0) { + continue; } + $value = $data; + } } elseif (// is it a remote file? - filter_var($input, FILTER_VALIDATE_URL) - && preg_match('/^https?:\/\//', $input) + filter_var($value, FILTER_VALIDATE_URL) + && preg_match('/^https?:\/\//', $value) ) { - $data = $this->_fetchRemoteFile($input); - if ($data !== false) { - return $data; - } - } - return $input; - } - - /** - * Fetches a remote file - * - * @param string $url The URL to download from - * - * @return mixed The file contents or FALSE - */ - protected function _fetchRemoteFile($url) - { - // try to fetch the file - if ($this->_use_curl) { - $ch = $this->getCurlInitialization($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, 0); - // no SSL validation for downloading media - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - // use hardcoded download timeouts for now - curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_remoteDownloadTimeout); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_remoteDownloadTimeout / 2); - // find files that have been redirected - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - // process compressed images - curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate,sdch'); - $result = curl_exec($ch); - if ($result !== false) { - return $result; - } - return false; - } - // no cURL - $contextOptions = [ - 'http' => [ - 'method' => 'GET', - 'protocol_version' => '1.1', - 'timeout' => $this->_remoteDownloadTimeout - ], - 'ssl' => [ - 'verify_peer' => false - ] - ]; - list($result) = $this->getNoCurlInitialization($url, $contextOptions); - if ($result !== false) { - return $result; - } - return false; - } - - /** - * Detects if API call should use media endpoint - * - * @param string $method The API method to call - * - * @return bool Whether the method is defined in media API - */ - protected function _detectMedia($method) { - $medias = [ - 'media/upload' - ]; - return in_array($method, $medias); - } - - /** - * Detects if API call should use JSON body - * - * @param string $method The API method to call - * - * @return bool Whether the method is defined as accepting JSON body - */ - protected function _detectJsonBody($method) { - $json_bodies = [ - 'collections/entries/curate' - ]; - return in_array($method, $json_bodies); - } - - /** - * Detects if API call should use binary body - * - * @param string $method_template The API method to call - * - * @return bool Whether the method is defined as accepting binary body - */ - protected function _detectBinaryBody($method_template) { - $binary = [ - 'ton/bucket/:bucket', - 'ton/bucket/:bucket?resumable=true', - 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' - ]; - return in_array($method_template, $binary); - } - - /** - * Detects if API call should use streaming endpoint, and if yes, which one - * - * @param string $method The API method to call - * - * @return string|false Variant of streaming API to be used - */ - protected function _detectStreaming($method) { - $streamings = [ - 'public' => [ - 'statuses/sample', - 'statuses/filter', - 'statuses/firehose' - ], - 'user' => ['user'], - 'site' => ['site'] - ]; - foreach ($streamings as $key => $values) { - if (in_array($method, $values)) { - return $key; - } - } - - return false; - } - - /** - * Builds the complete API endpoint url - * - * @param string $method The API method to call - * @param string $method_template The API method to call - * - * @return string The URL to send the request to - */ - protected function _getEndpoint($method, $method_template) - { - if (substr($method_template, 0, 5) === 'oauth') { - $url = self::$_endpoint_oauth . $method; - } elseif ($this->_detectMedia($method_template)) { - $url = self::$_endpoint_media . $method . '.json'; - } elseif ($variant = $this->_detectStreaming($method_template)) { - $url = self::$_endpoints_streaming[$variant] . $method . '.json'; - } elseif ($variant = $this->_detectBinaryBody($method_template)) { - $url = self::$_endpoint_ton . $method; - } elseif (substr($method_template, 0, 12) === 'ads/sandbox/') { - $url = self::$_endpoint_ads_sandbox . substr($method, 12); - } elseif (substr($method_template, 0, 4) === 'ads/') { - $url = self::$_endpoint_ads . substr($method, 4); - } else { - $url = self::$_endpoint . $method . '.json'; - } - return $url; - } - - /** - * Calls the API - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $method The API method to call - * @param string $method_template The API method template to call - * @param array optional $params The parameters to send along - * @param bool optional $multipart Whether to use multipart/form-data - * @param bool optional $app_only_auth Whether to use app-only bearer authentication - * - * @return string The API reply, encoded in the set return_format - */ - - protected function _callApi($httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false) - { - if (! $app_only_auth - && $this->_oauth_token === null - && substr($method, 0, 5) !== 'oauth' - ) { - throw new \Exception('To call this API, the OAuth access token must be set.'); - } - // use separate API access for streaming API - if ($this->_detectStreaming($method) !== false) { - return $this->_callApiStreaming($httpmethod, $method, $params, $app_only_auth); - } - - if ($this->_use_curl) { - return $this->_callApiCurl($httpmethod, $method, $method_template, $params, $multipart, $app_only_auth); - } - return $this->_callApiNoCurl($httpmethod, $method, $method_template, $params, $multipart, $app_only_auth); - } - - /** - * Calls the API using cURL - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $method The API method to call - * @param string $method_template The API method template to call - * @param array optional $params The parameters to send along - * @param bool optional $multipart Whether to use multipart/form-data - * @param bool optional $app_only_auth Whether to use app-only bearer authentication - * - * @return string The API reply, encoded in the set return_format - */ - - protected function _callApiCurl( - $httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false - ) - { - list ($authorization, $url, $params, $request_headers) - = $this->_callApiPreparations( - $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth - ); - - $ch = $this->getCurlInitialization($url); - $request_headers[] = 'Authorization: ' . $authorization; - $request_headers[] = 'Expect:'; - - if ($httpmethod !== 'GET') { - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $params); - if (in_array($httpmethod, ['POST', 'PUT', 'DELETE'])) { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpmethod); - } - } - - curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); - - if (isset($this->_timeout)) { - curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_timeout); - } - - if (isset($this->_connectionTimeout)) { - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_connectionTimeout); - } - - $result = curl_exec($ch); - - // catch request errors - if ($result === false) { - throw new \Exception('Request error for API call: ' . curl_error($ch)); - } + $data = $this->_fetchRemoteFile($value); + if ($data !== false) { + $value = $data; + } + } + } + + $request .= "\r\n\r\n" . $value . "\r\n"; + } + + return $request; + } + + + /** + * Detect filenames in upload parameters, + * build multipart request from upload params + * + * @param string $method The API method to call + * @param array $params The parameters to send along + * + * @return null|string + */ + protected function _buildMultipart($method, $params) + { + // well, files will only work in multipart methods + if (! $this->_detectMultipart($method)) { + return; + } + + // only check specific parameters + $possible_files = [ + // Tweets + 'statuses/update_with_media' => 'media[]', + 'media/upload' => 'media', + // Accounts + 'account/update_profile_background_image' => 'image', + 'account/update_profile_image' => 'image', + 'account/update_profile_banner' => 'banner' + ]; + // method might have files? + if (! in_array($method, array_keys($possible_files))) { + return; + } + + $possible_files = explode(' ', $possible_files[$method]); + + $multipart_border = '--------------------' . $this->_nonce(); + $multipart_request = + $this->_getMultipartRequestFromParams($possible_files, $multipart_border, $params) + . '--' . $multipart_border . '--'; + + return $multipart_request; + } + + /** + * Detect filenames in upload parameters + * + * @param mixed $input The data or file name to parse + * + * @return null|string + */ + protected function _buildBinaryBody($input) + { + if (// is it a file, a readable one? + @file_exists($input) + && @is_readable($input) + ) { + // try to read the file + $data = @file_get_contents($input); + if ($data !== false && strlen($data) !== 0) { + return $data; + } + } elseif (// is it a remote file? + filter_var($input, FILTER_VALIDATE_URL) + && preg_match('/^https?:\/\//', $input) + ) { + $data = $this->_fetchRemoteFile($input); + if ($data !== false) { + return $data; + } + } + return $input; + } + + /** + * Fetches a remote file + * + * @param string $url The URL to download from + * + * @return mixed The file contents or FALSE + */ + protected function _fetchRemoteFile($url) + { + // try to fetch the file + if ($this->_use_curl) { + $ch = $this->getCurlInitialization($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + // no SSL validation for downloading media + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + // use hardcoded download timeouts for now + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_remoteDownloadTimeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_remoteDownloadTimeout / 2); + // find files that have been redirected + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + // process compressed images + curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate,sdch'); + $result = curl_exec($ch); + if ($result !== false) { + return $result; + } + return false; + } + // no cURL + $contextOptions = [ + 'http' => [ + 'method' => 'GET', + 'protocol_version' => '1.1', + 'timeout' => $this->_remoteDownloadTimeout + ], + 'ssl' => [ + 'verify_peer' => false + ] + ]; + list($result) = $this->getNoCurlInitialization($url, $contextOptions); + if ($result !== false) { + return $result; + } + return false; + } + + /** + * Detects if API call should use media endpoint + * + * @param string $method The API method to call + * + * @return bool Whether the method is defined in media API + */ + protected function _detectMedia($method) { + $medias = [ + 'media/upload' + ]; + return in_array($method, $medias); + } + + /** + * Detects if API call should use JSON body + * + * @param string $method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + protected function _detectJsonBody($method) { + $json_bodies = [ + 'collections/entries/curate' + ]; + return in_array($method, $json_bodies); + } + + /** + * Detects if API call should use binary body + * + * @param string $method_template The API method to call + * + * @return bool Whether the method is defined as accepting binary body + */ + protected function _detectBinaryBody($method_template) { + $binary = [ + 'ton/bucket/:bucket', + 'ton/bucket/:bucket?resumable=true', + 'ton/bucket/:bucket/:file?resumable=true&resumeId=:resumeId' + ]; + return in_array($method_template, $binary); + } + + /** + * Detects if API call should use streaming endpoint, and if yes, which one + * + * @param string $method The API method to call + * + * @return string|false Variant of streaming API to be used + */ + protected function _detectStreaming($method) { + $streamings = [ + 'public' => [ + 'statuses/sample', + 'statuses/filter', + 'statuses/firehose' + ], + 'user' => ['user'], + 'site' => ['site'] + ]; + foreach ($streamings as $key => $values) { + if (in_array($method, $values)) { + return $key; + } + } + + return false; + } + + /** + * Builds the complete API endpoint url + * + * @param string $method The API method to call + * @param string $method_template The API method to call + * + * @return string The URL to send the request to + */ + protected function _getEndpoint($method, $method_template) + { + if (substr($method_template, 0, 5) === 'oauth') { + $url = self::$_endpoint_oauth . $method; + } elseif ($this->_detectMedia($method_template)) { + $url = self::$_endpoint_media . $method . '.json'; + } elseif ($variant = $this->_detectStreaming($method_template)) { + $url = self::$_endpoints_streaming[$variant] . $method . '.json'; + } elseif ($variant = $this->_detectBinaryBody($method_template)) { + $url = self::$_endpoint_ton . $method; + } elseif (substr($method_template, 0, 12) === 'ads/sandbox/') { + $url = self::$_endpoint_ads_sandbox . substr($method, 12); + } elseif (substr($method_template, 0, 4) === 'ads/') { + $url = self::$_endpoint_ads . substr($method, 4); + } else { + $url = self::$_endpoint . $method . '.json'; + } + return $url; + } + + /** + * Calls the API + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $method The API method to call + * @param string $method_template The API method template to call + * @param array optional $params The parameters to send along + * @param bool optional $multipart Whether to use multipart/form-data + * @param bool optional $app_only_auth Whether to use app-only bearer authentication + * + * @return string The API reply, encoded in the set return_format + */ + + protected function _callApi($httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false) + { + if (! $app_only_auth + && $this->_oauth_token === null + && substr($method, 0, 5) !== 'oauth' + ) { + throw new \Exception('To call this API, the OAuth access token must be set.'); + } + // use separate API access for streaming API + if ($this->_detectStreaming($method) !== false) { + return $this->_callApiStreaming($httpmethod, $method, $params, $app_only_auth); + } + + if ($this->_use_curl) { + return $this->_callApiCurl($httpmethod, $method, $method_template, $params, $multipart, $app_only_auth); + } + return $this->_callApiNoCurl($httpmethod, $method, $method_template, $params, $multipart, $app_only_auth); + } + + /** + * Calls the API using cURL + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $method The API method to call + * @param string $method_template The API method template to call + * @param array optional $params The parameters to send along + * @param bool optional $multipart Whether to use multipart/form-data + * @param bool optional $app_only_auth Whether to use app-only bearer authentication + * + * @return string The API reply, encoded in the set return_format + */ + + protected function _callApiCurl( + $httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false + ) + { + list ($authorization, $url, $params, $request_headers) + = $this->_callApiPreparations( + $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth + ); + + $ch = $this->getCurlInitialization($url); + $request_headers[] = 'Authorization: ' . $authorization; + $request_headers[] = 'Expect:'; + + if ($httpmethod !== 'GET') { + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + if (in_array($httpmethod, ['POST', 'PUT', 'DELETE'])) { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpmethod); + } + } + + curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); + + if (isset($this->_timeout)) { + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_timeout); + } + + if (isset($this->_connectionTimeout)) { + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_connectionTimeout); + } + + $result = curl_exec($ch); + + // catch request errors + if ($result === false) { + throw new \Exception('Request error for API call: ' . curl_error($ch)); + } + + // certificate validation results + $validation_result = curl_errno($ch); + $this->_validateSslCertificate($validation_result); + + $httpstatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + list($headers, $reply) = $this->_parseApiHeaders($result); + // TON API & redirects + $reply = $this->_parseApiReplyPrefillHeaders($headers, $reply); + $reply = $this->_parseApiReply($reply); + $rate = $this->_getRateLimitInfo($headers); + + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: + $reply['httpstatus'] = $httpstatus; + $reply['rate'] = $rate; + break; + case CODEBIRD_RETURNFORMAT_OBJECT: + $reply->httpstatus = $httpstatus; + $reply->rate = $rate; + break; + } + return $reply; + } + + /** + * Calls the API without cURL + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $method The API method to call + * @param string $method_template The API method template to call + * @param array optional $params The parameters to send along + * @param bool optional $multipart Whether to use multipart/form-data + * @param bool optional $app_only_auth Whether to use app-only bearer authentication + * + * @return string The API reply, encoded in the set return_format + */ + + protected function _callApiNoCurl( + $httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false + ) + { + list ($authorization, $url, $params, $request_headers) + = $this->_callApiPreparations( + $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth + ); - // certificate validation results - $validation_result = curl_errno($ch); - $this->_validateSslCertificate($validation_result); - - $httpstatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); - list($headers, $reply) = $this->_parseApiHeaders($result); - // TON API & redirects - $reply = $this->_parseApiReplyPrefillHeaders($headers, $reply); - $reply = $this->_parseApiReply($reply); - $rate = $this->_getRateLimitInfo($headers); - - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - $reply['httpstatus'] = $httpstatus; - $reply['rate'] = $rate; - break; - case CODEBIRD_RETURNFORMAT_OBJECT: - $reply->httpstatus = $httpstatus; - $reply->rate = $rate; - break; - } - return $reply; + $hostname = parse_url($url, PHP_URL_HOST); + if ($hostname === false) { + throw new \Exception('Incorrect API endpoint host.'); } - /** - * Calls the API without cURL - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $method The API method to call - * @param string $method_template The API method template to call - * @param array optional $params The parameters to send along - * @param bool optional $multipart Whether to use multipart/form-data - * @param bool optional $app_only_auth Whether to use app-only bearer authentication - * - * @return string The API reply, encoded in the set return_format - */ - - protected function _callApiNoCurl( - $httpmethod, $method, $method_template, $params = [], $multipart = false, $app_only_auth = false - ) - { - list ($authorization, $url, $params, $request_headers) - = $this->_callApiPreparations( - $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth - ); - - $hostname = parse_url($url, PHP_URL_HOST); - if ($hostname === false) { - throw new \Exception('Incorrect API endpoint host.'); - } - - $request_headers[] = 'Authorization: ' . $authorization; - $request_headers[] = 'Accept: */*'; - $request_headers[] = 'Connection: Close'; - if ($httpmethod !== 'GET' && ! $multipart) { - $request_headers[] = 'Content-Type: application/x-www-form-urlencoded'; - } - - $contextOptions = [ - 'http' => [ - 'method' => $httpmethod, - 'protocol_version' => '1.1', - 'header' => implode("\r\n", $request_headers), - 'timeout' => $this->_timeout / 1000, - 'content' => in_array($httpmethod, ['POST', 'PUT']) ? $params : null, - 'ignore_errors' => true - ] - ]; - - list($reply, $headers) = $this->getNoCurlInitialization($url, $contextOptions, $hostname); - $result = ''; - foreach ($headers as $header) { - $result .= $header . "\r\n"; - } - $result .= "\r\n" . $reply; - - // find HTTP status - $httpstatus = '500'; - $match = []; - if (!empty($headers[0]) && preg_match('/HTTP\/\d\.\d (\d{3})/', $headers[0], $match)) { - $httpstatus = $match[1]; - } - - list($headers, $reply) = $this->_parseApiHeaders($result); - // TON API & redirects - $reply = $this->_parseApiReplyPrefillHeaders($headers, $reply); - $reply = $this->_parseApiReply($reply); - $rate = $this->_getRateLimitInfo($headers); - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - $reply['httpstatus'] = $httpstatus; - $reply['rate'] = $rate; - break; - case CODEBIRD_RETURNFORMAT_OBJECT: - $reply->httpstatus = $httpstatus; - $reply->rate = $rate; - break; - } - return $reply; + $request_headers[] = 'Authorization: ' . $authorization; + $request_headers[] = 'Accept: */*'; + $request_headers[] = 'Connection: Close'; + if ($httpmethod !== 'GET' && ! $multipart) { + $request_headers[] = 'Content-Type: application/x-www-form-urlencoded'; } - /** - * Do preparations to make the API GET call - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $url The URL to call - * @param array $params The parameters to send along - * @param bool $app_only_auth Whether to use app-only bearer authentication - * - * @return string[] (string authorization, string url) - */ - protected function _callApiPreparationsGet( - $httpmethod, $url, $params, $app_only_auth - ) { - return [ - $app_only_auth ? null : $this->_sign($httpmethod, $url, $params), - json_encode($params) === '[]' ? $url : $url . '?' . http_build_query($params) - ]; - } - - /** - * Do preparations to make the API POST call - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $url The URL to call - * @param string $method The API method to call - * @param string $method_template The API method template to call - * @param array $params The parameters to send along - * @param bool $multipart Whether to use multipart/form-data - * @param bool $app_only_auth Whether to use app-only bearer authentication - * - * @return array (string authorization, array params, array request_headers) - */ - protected function _callApiPreparationsPost( - $httpmethod, $url, $method, $method_template, $params, $multipart, $app_only_auth + $contextOptions = [ + 'http' => [ + 'method' => $httpmethod, + 'protocol_version' => '1.1', + 'header' => implode("\r\n", $request_headers), + 'timeout' => $this->_timeout / 1000, + 'content' => in_array($httpmethod, ['POST', 'PUT']) ? $params : null, + 'ignore_errors' => true + ] + ]; + + list($reply, $headers) = $this->getNoCurlInitialization($url, $contextOptions, $hostname); + $result = ''; + foreach ($headers as $header) { + $result .= $header . "\r\n"; + } + $result .= "\r\n" . $reply; + + // find HTTP status + $httpstatus = '500'; + $match = []; + if (!empty($headers[0]) && preg_match('/HTTP\/\d\.\d (\d{3})/', $headers[0], $match)) { + $httpstatus = $match[1]; + } + + list($headers, $reply) = $this->_parseApiHeaders($result); + // TON API & redirects + $reply = $this->_parseApiReplyPrefillHeaders($headers, $reply); + $reply = $this->_parseApiReply($reply); + $rate = $this->_getRateLimitInfo($headers); + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: + $reply['httpstatus'] = $httpstatus; + $reply['rate'] = $rate; + break; + case CODEBIRD_RETURNFORMAT_OBJECT: + $reply->httpstatus = $httpstatus; + $reply->rate = $rate; + break; + } + return $reply; + } + + /** + * Do preparations to make the API GET call + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $url The URL to call + * @param array $params The parameters to send along + * @param bool $app_only_auth Whether to use app-only bearer authentication + * + * @return string[] (string authorization, string url) + */ + protected function _callApiPreparationsGet( + $httpmethod, $url, $params, $app_only_auth + ) { + return [ + $app_only_auth ? null : $this->_sign($httpmethod, $url, $params), + json_encode($params) === '[]' ? $url : $url . '?' . http_build_query($params) + ]; + } + + /** + * Do preparations to make the API POST call + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $url The URL to call + * @param string $method The API method to call + * @param string $method_template The API method template to call + * @param array $params The parameters to send along + * @param bool $multipart Whether to use multipart/form-data + * @param bool $app_only_auth Whether to use app-only bearer authentication + * + * @return array (string authorization, array params, array request_headers) + */ + protected function _callApiPreparationsPost( + $httpmethod, $url, $method, $method_template, $params, $multipart, $app_only_auth + ) { + $authorization = null; + $request_headers = []; + if ($multipart) { + if (! $app_only_auth) { + $authorization = $this->_sign($httpmethod, $url, []); + } + $params = $this->_buildMultipart($method, $params); + $first_newline = strpos($params, "\r\n"); + $multipart_boundary = substr($params, 2, $first_newline - 2); + $request_headers[] = 'Content-Type: multipart/form-data; boundary=' + . $multipart_boundary; + } elseif ($this->_detectJsonBody($method)) { + $authorization = $this->_sign($httpmethod, $url, []); + $params = json_encode($params); + $request_headers[] = 'Content-Type: application/json'; + } elseif ($this->_detectBinaryBody($method_template)) { + // transform parametric headers to real headers + foreach ([ + 'Content-Type', 'X-TON-Content-Type', + 'X-TON-Content-Length', 'Content-Range' + ] as $key) { + if (isset($params[$key])) { + $request_headers[] = $key . ': ' . $params[$key]; + unset($params[$key]); + } + } + $sign_params = []; + parse_str(parse_url($method, PHP_URL_QUERY), $sign_params); + $authorization = $this->_sign($httpmethod, $url, $sign_params); + if (isset($params['media'])) { + $params = $this->_buildBinaryBody($params['media']); + } else { + // resumable upload + $params = []; + } + } else { + if (! $app_only_auth) { + $authorization = $this->_sign($httpmethod, $url, $params); + } + $params = http_build_query($params); + } + return [$authorization, $params, $request_headers]; + } + + /** + * Get Bearer authorization string + * + * @return string authorization + */ + protected function _getBearerAuthorization() + { + if (self::$_oauth_consumer_key === null + && self::$_oauth_bearer_token === null ) { - $authorization = null; - $request_headers = []; - if ($multipart) { - if (! $app_only_auth) { - $authorization = $this->_sign($httpmethod, $url, []); - } - $params = $this->_buildMultipart($method, $params); - $first_newline = strpos($params, "\r\n"); - $multipart_boundary = substr($params, 2, $first_newline - 2); - $request_headers[] = 'Content-Type: multipart/form-data; boundary=' - . $multipart_boundary; - } elseif ($this->_detectJsonBody($method)) { - $authorization = $this->_sign($httpmethod, $url, []); - $params = json_encode($params); - $request_headers[] = 'Content-Type: application/json'; - } elseif ($this->_detectBinaryBody($method_template)) { - // transform parametric headers to real headers - foreach ([ - 'Content-Type', 'X-TON-Content-Type', - 'X-TON-Content-Length', 'Content-Range' - ] as $key) { - if (isset($params[$key])) { - $request_headers[] = $key . ': ' . $params[$key]; - unset($params[$key]); - } - } - $sign_params = []; - parse_str(parse_url($method, PHP_URL_QUERY), $sign_params); - $authorization = $this->_sign($httpmethod, $url, $sign_params); - if (isset($params['media'])) { - $params = $this->_buildBinaryBody($params['media']); - } else { - // resumable upload - $params = []; - } + throw new \Exception('To make an app-only auth API request, consumer key or bearer token must be set.'); + } + // automatically fetch bearer token, if necessary + if (self::$_oauth_bearer_token === null) { + $this->oauth2_token(); + } + return 'Bearer ' . self::$_oauth_bearer_token; + } + + /** + * Do preparations to make the API call + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $method The API method to call + * @param string $method_template The API method template to call + * @param array $params The parameters to send along + * @param bool $multipart Whether to use multipart/form-data + * @param bool $app_only_auth Whether to use app-only bearer authentication + * + * @return array (string authorization, string url, array params, array request_headers) + */ + protected function _callApiPreparations( + $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth + ) + { + $url = $this->_getEndpoint($method, $method_template); + $request_headers = []; + if ($httpmethod === 'GET') { + // GET + list ($authorization, $url) = + $this->_callApiPreparationsGet($httpmethod, $url, $params, $app_only_auth); + } else { + // POST + list ($authorization, $params, $request_headers) = + $this->_callApiPreparationsPost($httpmethod, $url, $method, $method_template, $params, $multipart, $app_only_auth); + } + if ($app_only_auth) { + $authorization = $this->_getBearerAuthorization(); + } + + return [ + $authorization, $url, $params, $request_headers + ]; + } + + /** + * Calls the streaming API + * + * @param string $httpmethod The HTTP method to use for making the request + * @param string $method The API method to call + * @param array optional $params The parameters to send along + * @param bool optional $app_only_auth Whether to use app-only bearer authentication + * + * @return void + */ + + protected function _callApiStreaming( + $httpmethod, $method, $params = [], $app_only_auth = false + ) + { + if ($this->_streaming_callback === null) { + throw new \Exception('Set streaming callback before consuming a stream.'); + } + + $params['delimited'] = 'length'; + + list ($authorization, $url, $params, $request_headers) + = $this->_callApiPreparations( + $httpmethod, $method, $params, false, $app_only_auth + ); + + $hostname = parse_url($url, PHP_URL_HOST); + $path = parse_url($url, PHP_URL_PATH); + $query = parse_url($url, PHP_URL_QUERY); + if ($hostname === false) { + throw new \Exception('Incorrect API endpoint host.'); + } + + $request_headers[] = 'Authorization: ' . $authorization; + $request_headers[] = 'Accept: */*'; + if ($httpmethod !== 'GET') { + $request_headers[] = 'Content-Type: application/x-www-form-urlencoded'; + $request_headers[] = 'Content-Length: ' . strlen($params); + } + + $errno = 0; + $errstr = ''; + $ch = stream_socket_client( + 'ssl://' . $hostname . ':443', + $errno, $errstr, + $this->_connectionTimeout, + STREAM_CLIENT_CONNECT + ); + + // send request + $request = $httpmethod . ' ' + . $path . ($query ? '?' . $query : '') . " HTTP/1.1\r\n" + . 'Host: ' . $hostname . "\r\n" + . implode("\r\n", $request_headers) + . "\r\n\r\n"; + if ($httpmethod !== 'GET') { + $request .= $params; + } + fputs($ch, $request); + stream_set_blocking($ch, 0); + stream_set_timeout($ch, 0); + + // collect headers + do { + $result = stream_get_line($ch, 1048576, "\r\n\r\n"); + } while(!$result); + $headers = explode("\r\n", $result); + + // find HTTP status + $httpstatus = '500'; + $match = []; + if (!empty($headers[0]) && preg_match('/HTTP\/\d\.\d (\d{3})/', $headers[0], $match)) { + $httpstatus = $match[1]; + } + + list($headers,) = $this->_parseApiHeaders($result); + $rate = $this->_getRateLimitInfo($headers); + + if ($httpstatus !== '200') { + $reply = [ + 'httpstatus' => $httpstatus, + 'rate' => $rate + ]; + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: + return $reply; + case CODEBIRD_RETURNFORMAT_OBJECT: + return (object) $reply; + case CODEBIRD_RETURNFORMAT_JSON: + return json_encode($reply); + } + } + + $signal_function = function_exists('pcntl_signal_dispatch'); + $data = ''; + $last_message = time(); + $message_length = 0; + + while (!feof($ch)) { + // call signal handlers, if any + if ($signal_function) { + pcntl_signal_dispatch(); + } + $cha = [$ch]; + $write = $except = null; + if (false === ($num_changed_streams = stream_select($cha, $write, $except, 0, 200000))) { + break; + } elseif ($num_changed_streams === 0) { + if (time() - $last_message >= 1) { + // deliver empty message, allow callback to cancel stream + $cancel_stream = $this->_deliverStreamingMessage(null); + if ($cancel_stream) { + break; + } + $last_message = time(); + } + continue; + } + $chunk_length = fgets($ch, 10); + if ($chunk_length === '' || !$chunk_length = hexdec($chunk_length)) { + continue; + } + + $chunk = ''; + do { + $chunk .= fread($ch, $chunk_length); + $chunk_length -= strlen($chunk); + } while($chunk_length > 0); + + if(0 === $message_length) { + $message_length = (int) strstr($chunk, "\r\n", true); + if ($message_length) { + $chunk = substr($chunk, strpos($chunk, "\r\n") + 2); } else { - if (! $app_only_auth) { - $authorization = $this->_sign($httpmethod, $url, $params); - } - $params = http_build_query($params); - } - return [$authorization, $params, $request_headers]; - } - - /** - * Get Bearer authorization string - * - * @return string authorization - */ - protected function _getBearerAuthorization() - { - if (self::$_oauth_consumer_key === null - && self::$_oauth_bearer_token === null - ) { - throw new \Exception('To make an app-only auth API request, consumer key or bearer token must be set.'); - } - // automatically fetch bearer token, if necessary - if (self::$_oauth_bearer_token === null) { - $this->oauth2_token(); - } - return 'Bearer ' . self::$_oauth_bearer_token; - } - - /** - * Do preparations to make the API call - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $method The API method to call - * @param string $method_template The API method template to call - * @param array $params The parameters to send along - * @param bool $multipart Whether to use multipart/form-data - * @param bool $app_only_auth Whether to use app-only bearer authentication - * - * @return array (string authorization, string url, array params, array request_headers) - */ - protected function _callApiPreparations( - $httpmethod, $method, $method_template, $params, $multipart, $app_only_auth - ) - { - $url = $this->_getEndpoint($method, $method_template); - $request_headers = []; - if ($httpmethod === 'GET') { - // GET - list ($authorization, $url) = - $this->_callApiPreparationsGet($httpmethod, $url, $params, $app_only_auth); + continue; + } + + $data = $chunk; + } else { + $data .= $chunk; + } + + if (strlen($data) < $message_length) { + continue; + } + + $reply = $this->_parseApiReply($data); + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: + $reply['httpstatus'] = $httpstatus; + $reply['rate'] = $rate; + break; + case CODEBIRD_RETURNFORMAT_OBJECT: + $reply->httpstatus = $httpstatus; + $reply->rate = $rate; + break; + } + + $cancel_stream = $this->_deliverStreamingMessage($reply); + if ($cancel_stream) { + break; + } + + $data = ''; + $message_length = 0; + $last_message = time(); + } + + return; + } + + /** + * Calls streaming callback with received message + * + * @param string|array|object message + * + * @return bool Whether to cancel streaming + */ + protected function _deliverStreamingMessage($message) + { + return call_user_func($this->_streaming_callback, $message); + } + + /** + * Parses the API reply to separate headers from the body + * + * @param string $reply The actual raw HTTP request reply + * + * @return array (headers, reply) + */ + protected function _parseApiHeaders($reply) { + // split headers and body + $headers = []; + $reply = explode("\r\n\r\n", $reply, 4); + + // check if using proxy + $proxy_tester = strtolower(substr($reply[0], 0, 35)); + if ($proxy_tester === 'http/1.0 200 connection established' + || $proxy_tester === 'http/1.1 200 connection established' + ) { + array_shift($reply); + } elseif (count($reply) > 2) { + $headers = array_shift($reply); + $reply = [ + $headers, + implode("\r\n", $reply) + ]; + } + + $headers_array = explode("\r\n", $reply[0]); + foreach ($headers_array as $header) { + $header_array = explode(': ', $header, 2); + $key = $header_array[0]; + $value = ''; + if (count($header_array) > 1) { + $value = $header_array[1]; + } + $headers[$key] = $value; + } + + if (count($reply) > 1) { + $reply = $reply[1]; + } else { + $reply = ''; + } + + return [$headers, $reply]; + } + + /** + * Parses the API headers to return Location and Ton API headers + * + * @param array $headers The headers list + * @param string $reply The actual HTTP body + * + * @return string $reply + */ + protected function _parseApiReplyPrefillHeaders($headers, $reply) + { + if ($reply === '' && (isset($headers['Location']))) { + $reply = [ + 'Location' => $headers['Location'] + ]; + if (isset($headers['X-TON-Min-Chunk-Size'])) { + $reply['X-TON-Min-Chunk-Size'] = $headers['X-TON-Min-Chunk-Size']; + } + if (isset($headers['X-TON-Max-Chunk-Size'])) { + $reply['X-TON-Max-Chunk-Size'] = $headers['X-TON-Max-Chunk-Size']; + } + if (isset($headers['Range'])) { + $reply['Range'] = $headers['Range']; + } + } + return json_encode($reply); + } + + /** + * Parses the API reply to encode it in the set return_format + * + * @param string $reply The actual HTTP body, JSON-encoded or URL-encoded + * + * @return array|string|object The parsed reply + */ + protected function _parseApiReply($reply) + { + $need_array = $this->_return_format === CODEBIRD_RETURNFORMAT_ARRAY; + if ($reply === '[]') { + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: + return []; + case CODEBIRD_RETURNFORMAT_JSON: + return '{}'; + case CODEBIRD_RETURNFORMAT_OBJECT: + return new \stdClass; + } + } + if (! $parsed = json_decode($reply, $need_array, 512, JSON_BIGINT_AS_STRING)) { + if ($reply) { + if (stripos($reply, '<' . '?xml version="1.0" encoding="UTF-8"?' . '>') === 0) { + // we received XML... + // since this only happens for errors, + // don't perform a full decoding + preg_match('/(.*)<\/request>/', $reply, $request); + preg_match('/(.*)<\/error>/', $reply, $error); + $parsed['request'] = htmlspecialchars_decode($request[1]); + $parsed['error'] = htmlspecialchars_decode($error[1]); } else { - // POST - list ($authorization, $params, $request_headers) = - $this->_callApiPreparationsPost($httpmethod, $url, $method, $method_template, $params, $multipart, $app_only_auth); - } - if ($app_only_auth) { - $authorization = $this->_getBearerAuthorization(); - } - - return [ - $authorization, $url, $params, $request_headers - ]; - } - - /** - * Calls the streaming API - * - * @param string $httpmethod The HTTP method to use for making the request - * @param string $method The API method to call - * @param array optional $params The parameters to send along - * @param bool optional $app_only_auth Whether to use app-only bearer authentication - * - * @return void - */ - - protected function _callApiStreaming( - $httpmethod, $method, $params = [], $app_only_auth = false - ) - { - if ($this->_streaming_callback === null) { - throw new \Exception('Set streaming callback before consuming a stream.'); - } - - $params['delimited'] = 'length'; - - list ($authorization, $url, $params, $request_headers) - = $this->_callApiPreparations( - $httpmethod, $method, $params, false, $app_only_auth - ); - - $hostname = parse_url($url, PHP_URL_HOST); - $path = parse_url($url, PHP_URL_PATH); - $query = parse_url($url, PHP_URL_QUERY); - if ($hostname === false) { - throw new \Exception('Incorrect API endpoint host.'); - } - - $request_headers[] = 'Authorization: ' . $authorization; - $request_headers[] = 'Accept: */*'; - if ($httpmethod !== 'GET') { - $request_headers[] = 'Content-Type: application/x-www-form-urlencoded'; - $request_headers[] = 'Content-Length: ' . strlen($params); - } - - $errno = 0; - $errstr = ''; - $ch = stream_socket_client( - 'ssl://' . $hostname . ':443', - $errno, $errstr, - $this->_connectionTimeout, - STREAM_CLIENT_CONNECT - ); - - // send request - $request = $httpmethod . ' ' - . $path . ($query ? '?' . $query : '') . " HTTP/1.1\r\n" - . 'Host: ' . $hostname . "\r\n" - . implode("\r\n", $request_headers) - . "\r\n\r\n"; - if ($httpmethod !== 'GET') { - $request .= $params; - } - fputs($ch, $request); - stream_set_blocking($ch, 0); - stream_set_timeout($ch, 0); - - // collect headers - do { - $result = stream_get_line($ch, 1048576, "\r\n\r\n"); - } while(!$result); - $headers = explode("\r\n", $result); - - // find HTTP status - $httpstatus = '500'; - $match = []; - if (!empty($headers[0]) && preg_match('/HTTP\/\d\.\d (\d{3})/', $headers[0], $match)) { - $httpstatus = $match[1]; - } - - list($headers,) = $this->_parseApiHeaders($result); - $rate = $this->_getRateLimitInfo($headers); - - if ($httpstatus !== '200') { - $reply = [ - 'httpstatus' => $httpstatus, - 'rate' => $rate - ]; - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - return $reply; - case CODEBIRD_RETURNFORMAT_OBJECT: - return (object) $reply; - case CODEBIRD_RETURNFORMAT_JSON: - return json_encode($reply); - } - } - - $signal_function = function_exists('pcntl_signal_dispatch'); - $data = ''; - $last_message = time(); - $message_length = 0; - - while (!feof($ch)) { - // call signal handlers, if any - if ($signal_function) { - pcntl_signal_dispatch(); - } - $cha = [$ch]; - $write = $except = null; - if (false === ($num_changed_streams = stream_select($cha, $write, $except, 0, 200000))) { - break; - } elseif ($num_changed_streams === 0) { - if (time() - $last_message >= 1) { - // deliver empty message, allow callback to cancel stream - $cancel_stream = $this->_deliverStreamingMessage(null); - if ($cancel_stream) { - break; - } - $last_message = time(); - } - continue; - } - $chunk_length = fgets($ch, 10); - if ($chunk_length === '' || !$chunk_length = hexdec($chunk_length)) { - continue; - } - - $chunk = ''; - do { - $chunk .= fread($ch, $chunk_length); - $chunk_length -= strlen($chunk); - } while($chunk_length > 0); - - if(0 === $message_length) { - $message_length = (int) strstr($chunk, "\r\n", true); - if ($message_length) { - $chunk = substr($chunk, strpos($chunk, "\r\n") + 2); - } else { - continue; - } - - $data = $chunk; + // assume query format + $reply = explode('&', $reply); + foreach ($reply as $element) { + if (stristr($element, '=')) { + list($key, $value) = explode('=', $element, 2); + $parsed[$key] = $value; } else { - $data .= $chunk; - } - - if (strlen($data) < $message_length) { - continue; - } - - $reply = $this->_parseApiReply($data); - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - $reply['httpstatus'] = $httpstatus; - $reply['rate'] = $rate; - break; - case CODEBIRD_RETURNFORMAT_OBJECT: - $reply->httpstatus = $httpstatus; - $reply->rate = $rate; - break; - } - - $cancel_stream = $this->_deliverStreamingMessage($reply); - if ($cancel_stream) { - break; - } - - $data = ''; - $message_length = 0; - $last_message = time(); - } - - return; - } - - /** - * Calls streaming callback with received message - * - * @param string|array|object message - * - * @return bool Whether to cancel streaming - */ - protected function _deliverStreamingMessage($message) - { - return call_user_func($this->_streaming_callback, $message); - } - - /** - * Parses the API reply to separate headers from the body - * - * @param string $reply The actual raw HTTP request reply - * - * @return array (headers, reply) - */ - protected function _parseApiHeaders($reply) { - // split headers and body - $headers = []; - $reply = explode("\r\n\r\n", $reply, 4); - - // check if using proxy - $proxy_tester = strtolower(substr($reply[0], 0, 35)); - if ($proxy_tester === 'http/1.0 200 connection established' - || $proxy_tester === 'http/1.1 200 connection established' - ) { - array_shift($reply); - } elseif (count($reply) > 2) { - $headers = array_shift($reply); - $reply = [ - $headers, - implode("\r\n", $reply) - ]; - } - - $headers_array = explode("\r\n", $reply[0]); - foreach ($headers_array as $header) { - $header_array = explode(': ', $header, 2); - $key = $header_array[0]; - $value = ''; - if (count($header_array) > 1) { - $value = $header_array[1]; + $parsed['message'] = $element; } - $headers[$key] = $value; - } - - if (count($reply) > 1) { - $reply = $reply[1]; - } else { - $reply = ''; - } - - return [$headers, $reply]; - } - - /** - * Parses the API headers to return Location and Ton API headers - * - * @param array $headers The headers list - * @param string $reply The actual HTTP body - * - * @return string $reply - */ - protected function _parseApiReplyPrefillHeaders($headers, $reply) - { - if ($reply === '' && (isset($headers['Location']))) { - $reply = [ - 'Location' => $headers['Location'] - ]; - if (isset($headers['X-TON-Min-Chunk-Size'])) { - $reply['X-TON-Min-Chunk-Size'] = $headers['X-TON-Min-Chunk-Size']; - } - if (isset($headers['X-TON-Max-Chunk-Size'])) { - $reply['X-TON-Max-Chunk-Size'] = $headers['X-TON-Max-Chunk-Size']; - } - if (isset($headers['Range'])) { - $reply['Range'] = $headers['Range']; - } - } - return json_encode($reply); - } - - /** - * Parses the API reply to encode it in the set return_format - * - * @param string $reply The actual HTTP body, JSON-encoded or URL-encoded - * - * @return array|string|object The parsed reply - */ - protected function _parseApiReply($reply) - { - $need_array = $this->_return_format === CODEBIRD_RETURNFORMAT_ARRAY; - if ($reply === '[]') { - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - return []; - case CODEBIRD_RETURNFORMAT_JSON: - return '{}'; - case CODEBIRD_RETURNFORMAT_OBJECT: - return new \stdClass; - } - } - if (! $parsed = json_decode($reply, $need_array, 512, JSON_BIGINT_AS_STRING)) { - if ($reply) { - if (stripos($reply, '<' . '?xml version="1.0" encoding="UTF-8"?' . '>') === 0) { - // we received XML... - // since this only happens for errors, - // don't perform a full decoding - preg_match('/(.*)<\/request>/', $reply, $request); - preg_match('/(.*)<\/error>/', $reply, $error); - $parsed['request'] = htmlspecialchars_decode($request[1]); - $parsed['error'] = htmlspecialchars_decode($error[1]); - } else { - // assume query format - $reply = explode('&', $reply); - foreach ($reply as $element) { - if (stristr($element, '=')) { - list($key, $value) = explode('=', $element, 2); - $parsed[$key] = $value; - } else { - $parsed['message'] = $element; - } - } - } - } - $reply = json_encode($parsed); - } - switch ($this->_return_format) { - case CODEBIRD_RETURNFORMAT_ARRAY: - return $parsed; - case CODEBIRD_RETURNFORMAT_JSON: - return $reply; - case CODEBIRD_RETURNFORMAT_OBJECT: - return (object) $parsed; + } } + } + $reply = json_encode($parsed); + } + switch ($this->_return_format) { + case CODEBIRD_RETURNFORMAT_ARRAY: return $parsed; + case CODEBIRD_RETURNFORMAT_JSON: + return $reply; + case CODEBIRD_RETURNFORMAT_OBJECT: + return (object) $parsed; } + return $parsed; + } } From 7f2f9e5b6af8b2a71c89c2df19d06df2a7dce26c Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 17:05:20 +0100 Subject: [PATCH 035/148] Add Changelog for WebP support --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 94ccd97..e633ab5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ codebird-php - changelog + #144 Support Collections API + #145 Support TON API + #120 Support Ads API ++ Support WebP media format 2.7.2 (2015-09-23) - #135 Invalid HTTP request headers in non-cURL mode From 4bd9d234d2a9b4c1c09660967837b118a050fdd3 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 17:14:52 +0100 Subject: [PATCH 036/148] Fix issues --- src/codebird.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 1ebe0f2..7ee62a5 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1908,7 +1908,7 @@ protected function _getEndpoint($method, $method_template) $url = self::$_endpoint_media . $method . '.json'; } elseif ($variant = $this->_detectStreaming($method_template)) { $url = self::$_endpoints_streaming[$variant] . $method . '.json'; - } elseif ($variant = $this->_detectBinaryBody($method_template)) { + } elseif ($this->_detectBinaryBody($method_template)) { $url = self::$_endpoint_ton . $method; } elseif (substr($method_template, 0, 12) === 'ads/sandbox/') { $url = self::$_endpoint_ads_sandbox . substr($method, 12); @@ -2167,6 +2167,9 @@ protected function _callApiPreparationsPost( } $sign_params = []; parse_str(parse_url($method, PHP_URL_QUERY), $sign_params); + if ($sign_params === null) { + $sign_params = []; + } $authorization = $this->_sign($httpmethod, $url, $sign_params); if (isset($params['media'])) { $params = $this->_buildBinaryBody($params['media']); @@ -2261,7 +2264,7 @@ protected function _callApiStreaming( list ($authorization, $url, $params, $request_headers) = $this->_callApiPreparations( - $httpmethod, $method, $params, false, $app_only_auth + $httpmethod, $method, $method_template, $params, false, $app_only_auth ); $hostname = parse_url($url, PHP_URL_HOST); From 04e5c544e89df2018df692ef856e7b70dc6e5b7e Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 17:19:25 +0100 Subject: [PATCH 037/148] Fix issue --- src/codebird.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/codebird.php b/src/codebird.php index 7ee62a5..3c4a65d 100644 --- a/src/codebird.php +++ b/src/codebird.php @@ -1943,7 +1943,7 @@ protected function _callApi($httpmethod, $method, $method_template, $params = [] } // use separate API access for streaming API if ($this->_detectStreaming($method) !== false) { - return $this->_callApiStreaming($httpmethod, $method, $params, $app_only_auth); + return $this->_callApiStreaming($httpmethod, $method, $method_template, $params, $app_only_auth); } if ($this->_use_curl) { @@ -2246,6 +2246,7 @@ protected function _callApiPreparations( * * @param string $httpmethod The HTTP method to use for making the request * @param string $method The API method to call + * @param string $method_template The API method template to call * @param array optional $params The parameters to send along * @param bool optional $app_only_auth Whether to use app-only bearer authentication * @@ -2253,7 +2254,7 @@ protected function _callApiPreparations( */ protected function _callApiStreaming( - $httpmethod, $method, $params = [], $app_only_auth = false + $httpmethod, $method, $method_template, $params = [], $app_only_auth = false ) { if ($this->_streaming_callback === null) { From a0fa478f3fa78e768e5f72a5adbec26b23081813 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 5 Dec 2015 17:31:51 +0100 Subject: [PATCH 038/148] Add ApiGen docs --- docs/404.html | 88 + docs/class-Codebird.Codebird.html | 3186 ++++++++++++++++++++++ docs/elementlist.js | 3 + docs/index.html | 92 + docs/namespace-Codebird.html | 99 + docs/resources/collapsed.png | Bin 0 -> 238 bytes docs/resources/combined.js | 1315 +++++++++ docs/resources/footer.png | Bin 0 -> 7948 bytes docs/resources/inherit.png | Bin 0 -> 152 bytes docs/resources/resize.png | Bin 0 -> 216 bytes docs/resources/sort.png | Bin 0 -> 171 bytes docs/resources/style.css | 614 +++++ docs/resources/tree-cleaner.png | Bin 0 -> 126 bytes docs/resources/tree-hasnext.png | Bin 0 -> 128 bytes docs/resources/tree-last.png | Bin 0 -> 172 bytes docs/resources/tree-vertical.png | Bin 0 -> 127 bytes docs/source-class-Codebird.Codebird.html | 2639 ++++++++++++++++++ 17 files changed, 8036 insertions(+) create mode 100644 docs/404.html create mode 100644 docs/class-Codebird.Codebird.html create mode 100644 docs/elementlist.js create mode 100644 docs/index.html create mode 100644 docs/namespace-Codebird.html create mode 100644 docs/resources/collapsed.png create mode 100644 docs/resources/combined.js create mode 100644 docs/resources/footer.png create mode 100644 docs/resources/inherit.png create mode 100644 docs/resources/resize.png create mode 100644 docs/resources/sort.png create mode 100644 docs/resources/style.css create mode 100644 docs/resources/tree-cleaner.png create mode 100644 docs/resources/tree-hasnext.png create mode 100644 docs/resources/tree-last.png create mode 100644 docs/resources/tree-vertical.png create mode 100644 docs/source-class-Codebird.Codebird.html diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..f282ae1 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,88 @@ + + + + + + + Page not found + + + + + + +
+ +
+ +
+ + + + + + diff --git a/docs/class-Codebird.Codebird.html b/docs/class-Codebird.Codebird.html new file mode 100644 index 0000000..b0e0010 --- /dev/null +++ b/docs/class-Codebird.Codebird.html @@ -0,0 +1,3186 @@ + + + + + + Class Codebird\Codebird + + + + + + +
+ +
+ +
+ + + + + + diff --git a/docs/elementlist.js b/docs/elementlist.js new file mode 100644 index 0000000..9558e97 --- /dev/null +++ b/docs/elementlist.js @@ -0,0 +1,3 @@ + +var ApiGen = ApiGen || {}; +ApiGen.elements = [["c","Codebird\\Codebird"]]; diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..4fd7c7e --- /dev/null +++ b/docs/index.html @@ -0,0 +1,92 @@ + + + + + + Overview + + + + + + +
+ +
+ +
+ + + + + + diff --git a/docs/namespace-Codebird.html b/docs/namespace-Codebird.html new file mode 100644 index 0000000..6fd7695 --- /dev/null +++ b/docs/namespace-Codebird.html @@ -0,0 +1,99 @@ + + + + + + Namespace Codebird + + + + + + +
+ +
+ +
+ + + + + + diff --git a/docs/resources/collapsed.png b/docs/resources/collapsed.png new file mode 100644 index 0000000000000000000000000000000000000000..56e7323931a3ca5774e2e85ba622c6282c122f5f GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XCV09yhE&{2`t$$4z4PH~`Tr$57mdKI;Vst0QmG<2><{9 literal 0 HcmV?d00001 diff --git a/docs/resources/combined.js b/docs/resources/combined.js new file mode 100644 index 0000000..2f46220 --- /dev/null +++ b/docs/resources/combined.js @@ -0,0 +1,1315 @@ + +var ApiGen = ApiGen || {}; +ApiGen.config = {"options":{"elementDetailsCollapsed":true,"elementsOrder":"natural"},"name":"ApiGen theme","templatesPath":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src","resources":{"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/resources":"resources"},"templates":{"overview":{"filename":"index.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/overview.latte"},"combined":{"filename":"resources\/combined.js","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/combined.js.latte"},"elementlist":{"filename":"elementlist.js","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/elementlist.js.latte"},"404":{"filename":"404.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/404.latte"},"package":{"filename":"package-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/package.latte"},"namespace":{"filename":"namespace-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/namespace.latte"},"class":{"filename":"class-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/class.latte"},"constant":{"filename":"constant-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/constant.latte"},"function":{"filename":"function-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/function.latte"},"annotationGroup":{"filename":"annotation-group-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/annotation-group.latte"},"source":{"filename":"source-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/source.latte"},"tree":{"filename":"tree.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/tree.latte"},"sitemap":{"filename":"sitemap.xml","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/sitemap.xml.latte"},"opensearch":{"filename":"opensearch.xml","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/opensearch.xml.latte"},"robots":{"filename":"robots.txt","template":"phar:\/\/\/usr\/local\/bin\/apigen\/bin\/..\/vendor\/apigen\/theme-default\/src\/robots.txt.latte"}}}; + + + /*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +*/ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
t
",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("