Merge remote-tracking branch 'upstream/develop' into more-q

This commit is contained in:
Michael 2021-10-03 17:40:49 +00:00
commit deb4f8d9ef
173 changed files with 11971 additions and 2238 deletions

View file

@ -21,6 +21,9 @@ Excerp from nitters about page.
Changelog Changelog
--------- ---------
* **Version 2.0**
* Changes the used hook by the addon, so that attached previews of postings get replaced as well.
This means the admins need to reload the addon
* **Version 1.1** * **Version 1.1**
* Initial localization support with DE translation * Initial localization support with DE translation
* Configurable nitter instance address from the admin panel * Configurable nitter instance address from the admin panel

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-08 07:44+0100\n" "POT-Creation-Date: 2021-10-01 16:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: nitter.php:51 #: nitter.php:52
#, php-format #, php-format
msgid "" msgid ""
"Which nitter server shall be used for the replacements in the post bodies? " "Which nitter server shall be used for the replacements in the post bodies? "
@ -25,17 +25,17 @@ msgid ""
"public Nitter servers." "public Nitter servers."
msgstr "" msgstr ""
#: nitter.php:52 #: nitter.php:53
msgid "Nitter server" msgid "Nitter server"
msgstr "" msgstr ""
#: nitter.php:53 #: nitter.php:54
msgid "Save Settings" msgid "Save Settings"
msgstr "" msgstr ""
#: nitter.php:65 #: nitter.php:99
#, php-format #, php-format
msgid "" msgid ""
"Links to Twitter in this posting were replaced by links to the Nitter " "In an attempt to protect your privacy, links to Twitter in this posting were "
"instance at %s" "replaced by links to the Nitter instance at %s"
msgstr "" msgstr ""

View file

@ -3,39 +3,48 @@
# This file is distributed under the same license as the Friendica nitter addon package. # This file is distributed under the same license as the Friendica nitter addon package.
# #
# #
# Translators:
# Tobias Diekershoff <tobias.diekershoff@gmx.net>, 2021
#
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-08 07:44+0100\n" "POT-Creation-Date: 2021-10-01 16:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2021-05-21 12:58+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Tobias Diekershoff <tobias.diekershoff@gmx.net>, 2021\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: German (https://www.transifex.com/Friendica/teams/12172/de/)\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: nitter.php:51 #: nitter.php:52
#, php-format #, php-format
msgid "" msgid ""
"Which nitter server shall be used for the replacements in the post bodies? " "Which nitter server shall be used for the replacements in the post bodies? "
"Use the URL with servername and protocol. See %s for a list of available " "Use the URL with servername and protocol. See %s for a list of available "
"public Nitter servers." "public Nitter servers."
msgstr "Welcher Nitter server soll für die Ersetzungen verwendet werden? Gib die URL mit Servername und Protokoll an. Eine Liste von öffentlichen Nitter servern findest du unter %s." msgstr ""
"Welcher Nitter server soll für die Ersetzungen verwendet werden? Gib die URL"
" mit Servername und Protokoll an. Eine Liste von öffentlichen Nitter servern"
" findest du unter %s."
#: nitter.php:52 #: nitter.php:53
msgid "Nitter server" msgid "Nitter server"
msgstr "Nitter Server" msgstr "Nitter Server"
#: nitter.php:53 #: nitter.php:54
msgid "Save Settings" msgid "Save Settings"
msgstr "Einstellungen Speichern" msgstr "Einstellungen Speichern"
#: nitter.php:65 #: nitter.php:99
#, php-format #, php-format
msgid "" msgid ""
"Links to Twitter in this posting were replaced by links to the Nitter " "In an attempt to protect your privacy, links to Twitter in this posting were"
"instance at %s" " replaced by links to the Nitter instance at %s"
msgstr "In diesem Beitrag wurden Links nach twitter.com durch die Nitter Instanz auf %s ersetzt." msgstr ""
"Um deine Privatsphäre zu schützen, wurden in diesem Beitrag Links nach "
"twitter.com durch die Nitter Instanz auf %s ersetzt."

View file

@ -1,7 +1,10 @@
<?php <?php
if(! function_exists("string_plural_select_de")) {
function string_plural_select_de($n){
$n = intval($n);
return intval($n != 1);
}}
; ;
$a->strings["Which nitter server shall be used for the replacements in the post bodies? Use the URL with servername and protocol. See %s for a list of available public Nitter servers."] = "Welcher Nitter server soll für die Ersetzungen verwendet werden? Gib die URL mit Servername und Protokoll an. Eine Liste von öffentlichen Nitter servern findest du unter %s.";
$a->strings["Nitter server"] = "Nitter Server"; $a->strings["Nitter server"] = "Nitter Server";
$a->strings["Save Settings"] = "Einstellungen Speichern"; $a->strings["Save Settings"] = "Einstellungen Speichern";
$a->strings["Links to Twitter in this posting were replaced by links to the Nitter instance at %s"] = "In diesem Beitrag wurden Links nach twitter.com durch die Nitter Instanz auf %s ersetzt.";

View file

@ -2,7 +2,7 @@
/* /*
* Name: nitter * Name: nitter
* Description: Replaces links to twitter.com to a nitter server in all displays of postings on a node. * Description: Replaces links to twitter.com to a nitter server in all displays of postings on a node.
* Version: 1.1 * Version: 2.0
* Author: Tobias Diekershoff <tobias@social.diekershoff.de> * Author: Tobias Diekershoff <tobias@social.diekershoff.de>
* *
* Copyright (c) 2020 Tobias Diekershoff * Copyright (c) 2020 Tobias Diekershoff
@ -30,7 +30,7 @@ use Friendica\DI;
function nitter_install() function nitter_install()
{ {
Addon::registerHook ('prepare_body', 'addon/nitter/nitter.php', 'nitter_render'); Addon::registerHook ('prepare_body_final', 'addon/nitter/nitter.php', 'nitter_render');
} }
/* Handle the send data from the admin settings /* Handle the send data from the admin settings
@ -72,6 +72,6 @@ function nitter_render(&$a, &$o)
$replaced = true; $replaced = true;
} }
if ($replaced) { if ($replaced) {
$o['html'] .= '<hr><p>' . DI::l10n()->t('Links to Twitter in this posting were replaced by links to the Nitter instance at %s', $nitter) . '</p>'; $o['html'] .= '<hr><p>' . DI::l10n()->t('In an attempt to protect your privacy, links to Twitter in this posting were replaced by links to the Nitter instance at %s', $nitter) . '</p>';
} }
} }

View file

@ -21,7 +21,7 @@
} }
], ],
"require": { "require": {
"abraham/twitteroauth": "^0.7.4", "abraham/twitteroauth": "2.0.0",
"jublonet/codebird-php": "dev-master" "jublonet/codebird-php": "dev-master"
}, },
"license": "3-clause BSD license", "license": "3-clause BSD license",

101
twitter/composer.lock generated
View file

@ -4,30 +4,33 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "eaeac282537509fd7d0bc4f92f01bc04", "content-hash": "fafd1db0b4f04c4268f5034ce8c7f6ea",
"packages": [ "packages": [
{ {
"name": "abraham/twitteroauth", "name": "abraham/twitteroauth",
"version": "0.7.4", "version": "2.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/abraham/twitteroauth.git", "url": "https://github.com/abraham/twitteroauth.git",
"reference": "c6f9e692552dd037b2324ed0dfa28a4e60875acf" "reference": "96f49e67baec10f5e5cb703d87be16ba01a798a5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/abraham/twitteroauth/zipball/c6f9e692552dd037b2324ed0dfa28a4e60875acf", "url": "https://api.github.com/repos/abraham/twitteroauth/zipball/96f49e67baec10f5e5cb703d87be16ba01a798a5",
"reference": "c6f9e692552dd037b2324ed0dfa28a4e60875acf", "reference": "96f49e67baec10f5e5cb703d87be16ba01a798a5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/ca-bundle": "^1.2",
"ext-curl": "*", "ext-curl": "*",
"php": "^5.6 || ^7.0" "php": "^7.2 || ^7.3 || ^7.4 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpmd/phpmd": "~2.6", "php-vcr/php-vcr": "^1",
"phpunit/phpunit": "~5.7", "php-vcr/phpunit-testlistener-vcr": "dev-php-8",
"squizlabs/php_codesniffer": "~3.0" "phpmd/phpmd": "^2",
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "^3"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -58,7 +61,78 @@
"social", "social",
"twitter" "twitter"
], ],
"time": "2017-06-30T22:02:01+00:00" "time": "2020-12-02T01:27:06+00:00"
},
{
"name": "composer/ca-bundle",
"version": "1.2.10",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "9fdb22c2e97a614657716178093cd1da90a64aa8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/9fdb22c2e97a614657716178093cd1da90a64aa8",
"reference": "9fdb22c2e97a614657716178093cd1da90a64aa8",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0",
"symfony/phpunit-bridge": "^4.2 || ^5",
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2021-06-07T13:58:28+00:00"
}, },
{ {
"name": "composer/installers", "name": "composer/installers",
@ -185,12 +259,12 @@
"version": "dev-master", "version": "dev-master",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/jublonet/codebird-php.git", "url": "https://github.com/jublo/codebird-php.git",
"reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368" "reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/jublonet/codebird-php/zipball/df362d8ad629aad6c4c7dbf36a440e569ec41368", "url": "https://api.github.com/repos/jublo/codebird-php/zipball/df362d8ad629aad6c4c7dbf36a440e569ec41368",
"reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368", "reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368",
"shasum": "" "shasum": ""
}, },
@ -249,5 +323,6 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": [],
"platform-dev": [] "platform-dev": [],
"plugin-api-version": "1.1.0"
} }

View file

@ -104,7 +104,11 @@ function twitter_install()
Hook::register('notifier_normal' , __FILE__, 'twitter_post_hook'); Hook::register('notifier_normal' , __FILE__, 'twitter_post_hook');
Hook::register('jot_networks' , __FILE__, 'twitter_jot_nets'); Hook::register('jot_networks' , __FILE__, 'twitter_jot_nets');
Hook::register('cron' , __FILE__, 'twitter_cron'); Hook::register('cron' , __FILE__, 'twitter_cron');
Hook::register('support_follow' , __FILE__, 'twitter_support_follow');
Hook::register('follow' , __FILE__, 'twitter_follow'); Hook::register('follow' , __FILE__, 'twitter_follow');
Hook::register('unfollow' , __FILE__, 'twitter_unfollow');
Hook::register('block' , __FILE__, 'twitter_block');
Hook::register('unblock' , __FILE__, 'twitter_unblock');
Hook::register('expire' , __FILE__, 'twitter_expire'); Hook::register('expire' , __FILE__, 'twitter_expire');
Hook::register('prepare_body' , __FILE__, 'twitter_prepare_body'); Hook::register('prepare_body' , __FILE__, 'twitter_prepare_body');
Hook::register('check_item_notification', __FILE__, 'twitter_check_item_notification'); Hook::register('check_item_notification', __FILE__, 'twitter_check_item_notification');
@ -134,6 +138,13 @@ function twitter_check_item_notification(App $a, array &$notification_data)
} }
} }
function twitter_support_follow(App $a, array &$data)
{
if ($data['protocol'] == Protocol::TWITTER) {
$data['result'] = true;
}
}
function twitter_follow(App $a, array &$contact) function twitter_follow(App $a, array &$contact)
{ {
Logger::info('Check if contact is twitter contact', ['url' => $contact["url"]]); Logger::info('Check if contact is twitter contact', ['url' => $contact["url"]]);
@ -148,19 +159,7 @@ function twitter_follow(App $a, array &$contact)
$uid = $a->getLoggedInUserId(); $uid = $a->getLoggedInUserId();
$ckey = DI::config()->get('twitter', 'consumerkey'); twitter_api_contact('friendships/create', ['network' => Protocol::TWITTER, 'nick' => $nickname], $uid);
$csecret = DI::config()->get('twitter', 'consumersecret');
$otoken = DI::pConfig()->get($uid, 'twitter', 'oauthtoken');
$osecret = DI::pConfig()->get($uid, 'twitter', 'oauthsecret');
// If the addon is not configured (general or for this user) quit here
if (empty($ckey) || empty($csecret) || empty($otoken) || empty($osecret)) {
$contact = false;
return;
}
$connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
$connection->post('friendships/create', ['screen_name' => $nickname]);
$user = twitter_fetchuser($nickname); $user = twitter_fetchuser($nickname);
@ -173,6 +172,48 @@ function twitter_follow(App $a, array &$contact)
} }
} }
function twitter_unfollow(App $a, array &$hook_data)
{
$hook_data['result'] = twitter_api_contact('friendship/destroy', $hook_data['contact'], $hook_data['uid']);
}
function twitter_block(App $a, array &$hook_data)
{
$hook_data['result'] = twitter_api_contact('blocks/create', $hook_data['contact'], $hook_data['uid']);
}
function twitter_unblock(App $a, array &$hook_data)
{
$hook_data['result'] = twitter_api_contact('blocks/destroy', $hook_data['contact'], $hook_data['uid']);
}
function twitter_api_contact(string $apiPath, array $contact, int $uid): ?bool
{
if ($contact['network'] !== Protocol::TWITTER) {
return null;
}
$ckey = DI::config()->get('twitter', 'consumerkey');
$csecret = DI::config()->get('twitter', 'consumersecret');
$otoken = DI::pConfig()->get($uid, 'twitter', 'oauthtoken');
$osecret = DI::pConfig()->get($uid, 'twitter', 'oauthsecret');
// If the addon is not configured (general or for this user) quit here
if (empty($ckey) || empty($csecret) || empty($otoken) || empty($osecret)) {
return null;
}
try {
$connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
$result = $connection->post($apiPath, ['screen_name' => $contact['nick']]);
Logger::info('[twitter] API call successful', ['apiPath' => $apiPath, 'result' => $result]);
return true;
} catch (Exception $e) {
Logger::notice('[twitter] API call failed', ['apiPath' => $apiPath, 'uid' => $uid, 'url' => $contact['url'], 'exception' => $e]);
return false;
}
}
function twitter_jot_nets(App $a, array &$jotnets_fields) function twitter_jot_nets(App $a, array &$jotnets_fields)
{ {
if (!local_user()) { if (!local_user()) {
@ -481,7 +522,7 @@ function twitter_probe_detect(App $a, array &$hookData)
} }
} }
function twitter_action(App $a, $uid, $pid, $action) function twitter_api_post(string $apiPath, string $pid, int $uid)
{ {
if (empty($pid)) { if (empty($pid)) {
return; return;
@ -496,34 +537,21 @@ function twitter_action(App $a, $uid, $pid, $action)
$post = ['id' => $pid]; $post = ['id' => $pid];
Logger::debug('before action', ['action' => $action, 'pid' => $pid, 'data' => $post]); Logger::debug('before action', ['action' => $apiPath, 'pid' => $pid, 'data' => $post]);
$result = [];
try { try {
switch ($action) { $result = $connection->post($apiPath, $post);
case 'delete': if ($connection->getLastHttpCode() != 200) {
// To-Do: $result = $connection->post('statuses/destroy', $post); Logger::warning('[twitter] API call unsuccessful', ['apiPath' => $apiPath, 'post' => $post, 'result' => $result]);
break;
case 'like':
$result = $connection->post('favorites/create', $post);
if ($connection->getLastHttpCode() != 200) {
Logger::warning('Unable to create favorite', ['result' => $result]);
}
break;
case 'unlike':
$result = $connection->post('favorites/destroy', $post);
if ($connection->getLastHttpCode() != 200) {
Logger::warning('Unable to destroy favorite', ['result' => $result]);
}
break;
default:
Logger::warning('Unhandled action', ['action' => $action]);
} }
} catch (TwitterOAuthException $twitterOAuthException) { } catch (TwitterOAuthException $twitterOAuthException) {
Logger::warning('Unable to communicate with twitter', ['action' => $action, 'data' => $post, 'code' => $twitterOAuthException->getCode(), 'exception' => $twitterOAuthException]); Logger::warning('Unable to communicate with twitter', ['apiPath' => $apiPath, 'data' => $post, 'code' => $twitterOAuthException->getCode(), 'exception' => $twitterOAuthException]);
$result = false;
} }
Logger::info('after action', ['action' => $action, 'result' => $result]); Logger::info('after action', ['action' => $apiPath, 'result' => $result]);
return $result;
} }
function twitter_get_id(string $uri) function twitter_get_id(string $uri)
@ -594,17 +622,19 @@ function twitter_post_hook(App $a, array &$b)
} }
} }
if (($b['verb'] == Activity::POST) && $b['deleted']) { /**
twitter_action($a, $b['uid'], twitter_get_id($thr_parent['uri']), 'delete'); * @TODO This can't work at the moment:
} * - Posts created on Friendica and mirrored to Twitter don't have a Twitter ID
* - Posts created on Twitter and mirrored on Friendica do not trigger the notifier hook this is part of.
*/
//if (($b['verb'] == Activity::POST) && $b['deleted']) {
// twitter_api_post('statuses/destroy', twitter_get_id($thr_parent['uri']), $b['uid']);
//}
if ($b['verb'] == Activity::LIKE) { if ($b['verb'] == Activity::LIKE) {
Logger::info('Like', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]); Logger::info('Like', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]);
if ($b['deleted']) {
twitter_action($a, $b["uid"], twitter_get_id($b["thr-parent"]), "unlike"); twitter_api_post($b['deleted'] ? 'favorite/destroy' : 'favorite/create', twitter_get_id($b["thr-parent"]), $b["uid"]);
} else {
twitter_action($a, $b["uid"], twitter_get_id($b["thr-parent"]), "like");
}
return; return;
} }
@ -612,7 +642,11 @@ function twitter_post_hook(App $a, array &$b)
if ($b['verb'] == Activity::ANNOUNCE) { if ($b['verb'] == Activity::ANNOUNCE) {
Logger::info('Retweet', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]); Logger::info('Retweet', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]);
if ($b['deleted']) { if ($b['deleted']) {
twitter_action($a, $b['uid'], twitter_get_id($thr_parent['extid']), 'delete'); /**
* @TODO This can't work at the moment:
* - Twitter post reshare removal doesn't seem to trigger the notifier hook this is part of
*/
//twitter_api_post('statuses/destroy', twitter_get_id($thr_parent['extid']), $b['uid']);
} else { } else {
twitter_retweet($b["uid"], twitter_get_id($b["thr-parent"])); twitter_retweet($b["uid"], twitter_get_id($b["thr-parent"]));
} }
@ -740,8 +774,7 @@ function twitter_post_hook(App $a, array &$b)
$post['in_reply_to_status_id'] = twitter_get_id($thr_parent['uri']); $post['in_reply_to_status_id'] = twitter_get_id($thr_parent['uri']);
} }
$url = 'statuses/update'; $result = $connection->post('statuses/update', $post);
$result = $connection->post($url, $post);
Logger::info('twitter_post send', ['id' => $b['id'], 'result' => $result]); Logger::info('twitter_post send', ['id' => $b['id'], 'result' => $result]);
if (!empty($result->source)) { if (!empty($result->source)) {
@ -2116,13 +2149,7 @@ function twitter_retweet(int $uid, int $id, int $item_id = 0)
{ {
Logger::info('Retweeting', ['user' => $uid, 'id' => $id]); Logger::info('Retweeting', ['user' => $uid, 'id' => $id]);
$ckey = DI::config()->get('twitter', 'consumerkey'); $result = twitter_api_post('statuses/retweet', $id, $uid);
$csecret = DI::config()->get('twitter', 'consumersecret');
$otoken = DI::pConfig()->get($uid, 'twitter', 'oauthtoken');
$osecret = DI::pConfig()->get($uid, 'twitter', 'oauthsecret');
$connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
$result = $connection->post('statuses/retweet/' . $id);
Logger::info('Retweeted', ['user' => $uid, 'id' => $id, 'result' => $result]); Logger::info('Retweeted', ['user' => $uid, 'id' => $id, 'result' => $result]);

View file

@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: npm
directory: '/'
schedule:
interval: daily
time: '11:00'
open-pull-requests-limit: 10
- package-ecosystem: composer
directory: '/'
schedule:
interval: daily
time: '11:00'
open-pull-requests-limit: 10

View file

@ -0,0 +1,12 @@
name: Lint
on: push
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci
- run: npm run lint

View file

@ -0,0 +1,18 @@
name: Test
on: push
jobs:
run:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0']
name: PHP ${{ matrix.php-versions }}
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer validate --no-interaction --strict
- run: composer install --no-interaction --prefer-dist
- run: npm test

View file

@ -1,4 +1,5 @@
.DS_Store .DS_Store
composer.lock
vendor vendor
env env
*.cache
node_modules

View file

@ -0,0 +1,3 @@
vendor
composer.lock
tests/fixtures

View file

@ -0,0 +1,6 @@
{
"singleQuote": true,
"phpVersion": "7.2",
"trailingCommaPHP": true,
"braceStyle": "psr-2"
}

View file

@ -1,13 +0,0 @@
language: php
dist: trusty
php:
- '5.6'
- '7.0'
- '7.1'
- hhvm
sudo: false
before_script:
- composer self-update
- composer install --prefer-source --no-interaction
script:
- vendor/bin/phpunit

View file

@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include: Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language - Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences - Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism - Gracefully accepting constructive criticism
* Focusing on what is best for the community - Focusing on what is best for the community
* Showing empathy towards other community members - Showing empathy towards other community members
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances - The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks - Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission - Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting - Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities ## Our Responsibilities

View file

@ -0,0 +1,21 @@
# Contributing to TwitterOAuth
## 👏 Thanks!
Thanks for your interest in contributing to TwitterOAuth. We appreciate contributions small and large.
## 🌱 Grow
If you have an idea for something new or would like to improve something. Please [open a quick issue](https://github.com/abraham/twitteroauth/issues/new) explaining the changes and the reasons for them. Everyone's time is important and we don't want you duplicating work someone else might already be working on.
GitHub has [outlined instructions](https://help.github.com/articles/fork-a-repo/) for forking a repo. To work on an update to this repo, you will:
- Fork the repo
- Make the changes
- Submit a pull request
Once the [pull request](https://help.github.com/articles/about-pull-requests/) is reviewed, if the changes are approved they will be merged in to the project.
## 🐛 Bugs
Please [open a new issue](https://github.com/abraham/twitteroauth/issues/new) and details what you are trying to do, what is happening, and what you expect to happen. Err on the side of providing more details.

View file

@ -1,5 +1,6 @@
<span itemprop="name">TwitterOAuth</span> [![Build Status](https://img.shields.io/travis/abraham/twitteroauth.svg)](https://travis-ci.org/abraham/twitteroauth) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/abraham/twitteroauth/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/abraham/twitteroauth/?branch=master) [![Issues Count](https://img.shields.io/github/issues/abraham/twitteroauth.svg)](https://github.com/abraham/twitteroauth/issues) [![Latest Version](https://img.shields.io/packagist/v/abraham/twitteroauth.svg)](https://packagist.org/packages/abraham/twitteroauth) <span itemprop="name">TwitterOAuth</span> [![Build Status](https://github.com/abraham/twitteroauth/workflows/Test/badge.svg)](https://github.com/abraham/twitteroauth/actions) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/abraham/twitteroauth/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/abraham/twitteroauth/?branch=master) [![Issues Count](https://img.shields.io/github/issues/abraham/twitteroauth.svg)](https://github.com/abraham/twitteroauth/issues) [![Latest Version](https://img.shields.io/packagist/v/abraham/twitteroauth.svg)](https://packagist.org/packages/abraham/twitteroauth) [![Downloads this Month](https://img.shields.io/packagist/dm/abraham/twitteroauth.svg)](https://packagist.org/packages/abraham/twitteroauth)
------------
---
<p itemprop="description">The most popular PHP library for Twitter's OAuth REST API.</p> <p itemprop="description">The most popular PHP library for Twitter's OAuth REST API.</p>
@ -7,4 +8,4 @@ See documentation at https://twitteroauth.com.
PHP versions [listed](https://secure.php.net/supported-versions.php) as "active support" or "security fixes only" are supported. PHP versions [listed](https://secure.php.net/supported-versions.php) as "active support" or "security fixes only" are supported.
<img src="https://raw.githubusercontent.com/abraham/twitteroauth-demo/master/images/twitter-logo-blue.png" itemprop="image" alt="Twitter bird" width="200px"> <img src="https://raw.githubusercontent.com/abraham/twitteroauth-com/master/images/twitter-logo-blue.png" itemprop="image" alt="Twitter bird" width="200px">

View file

@ -7,7 +7,6 @@
* @return void * @return void
*/ */
spl_autoload_register(function ($class) { spl_autoload_register(function ($class) {
// project-specific namespace prefix // project-specific namespace prefix
$prefix = 'Abraham\\TwitterOAuth\\'; $prefix = 'Abraham\\TwitterOAuth\\';

View file

@ -1,34 +1,55 @@
{ {
"name": "abraham/twitteroauth", "name": "abraham/twitteroauth",
"type": "library", "type": "library",
"description": "The most popular PHP library for use with the Twitter OAuth REST API.", "description": "The most popular PHP library for use with the Twitter OAuth REST API.",
"keywords": ["twitter", "api", "oauth", "rest", "social", "twitter api", "twitter oauth"], "keywords": [
"license": "MIT", "twitter",
"homepage": "https://twitteroauth.com", "api",
"authors": [ "oauth",
{ "rest",
"name": "Abraham Williams", "social",
"email": "abraham@abrah.am", "twitter api",
"homepage": "https://abrah.am", "twitter oauth"
"role": "Developer" ],
} "license": "MIT",
], "homepage": "https://twitteroauth.com",
"support": { "authors": [
"source": "https://github.com/abraham/twitteroauth", {
"issues": "https://github.com/abraham/twitteroauth/issues" "name": "Abraham Williams",
}, "email": "abraham@abrah.am",
"require": { "homepage": "https://abrah.am",
"php": "^5.6 || ^7.0", "role": "Developer"
"ext-curl": "*"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"squizlabs/php_codesniffer": "~3.0",
"phpmd/phpmd": "~2.6"
},
"autoload": {
"psr-4": {
"Abraham\\TwitterOAuth\\": "src"
}
} }
],
"support": {
"source": "https://github.com/abraham/twitteroauth",
"issues": "https://github.com/abraham/twitteroauth/issues"
},
"repositories": [
{
"type": "git",
"url": "https://github.com/morozov/php-vcr"
},
{
"type": "git",
"url": "https://github.com/abraham/phpunit-testlistener-vcr"
}
],
"require": {
"php": "^7.2 || ^7.3 || ^7.4 || ^8.0",
"ext-curl": "*",
"composer/ca-bundle": "^1.2"
},
"require-dev": {
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "^3",
"phpmd/phpmd": "^2",
"php-vcr/php-vcr": "^1",
"php-vcr/phpunit-testlistener-vcr": "dev-php-8"
},
"autoload": {
"psr-4": {
"Abraham\\TwitterOAuth\\": "src"
}
}
} }

2761
twitter/vendor/abraham/twitteroauth/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

511
twitter/vendor/abraham/twitteroauth/package-lock.json generated vendored Normal file
View file

@ -0,0 +1,511 @@
{
"name": "twitteroauth",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@prettier/plugin-php": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.16.0.tgz",
"integrity": "sha512-HG/FamMUtq4/9hZmeuvwy0BWmOr4m9OWacvLSUmmgUrQd4+TRZW7Nqs2MAJkwSNiBIgAKTt2Vw9PK+33Gxxx8g==",
"dev": true,
"requires": {
"linguist-languages": "^7.5.1",
"mem": "^8.0.0",
"php-parser": "3.0.2"
}
},
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"concurrently": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.3.0.tgz",
"integrity": "sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"date-fns": "^2.0.1",
"lodash": "^4.17.15",
"read-pkg": "^4.0.1",
"rxjs": "^6.5.2",
"spawn-command": "^0.0.2-1",
"supports-color": "^6.1.0",
"tree-kill": "^1.2.2",
"yargs": "^13.3.0"
}
},
"date-fns": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.15.0.tgz",
"integrity": "sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==",
"dev": true
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"dev": true
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
"linguist-languages": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/linguist-languages/-/linguist-languages-7.11.1.tgz",
"integrity": "sha512-+cRUk+1WTbydcdzipXQER2iilX+wMrb1LPkbkGuDP/IcGPJRDmOZH6Olf1iH6sHlHwPnJYiNJH39YsFCVZxvUQ==",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true
},
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
"integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
"dev": true,
"requires": {
"p-defer": "^1.0.0"
}
},
"mem": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-8.0.0.tgz",
"integrity": "sha512-qrcJOe6uD+EW8Wrci1Vdiua/15Xw3n/QnaNXE7varnB6InxSk7nu3/i5jfy3S6kWxr8WYJ6R1o0afMUtvorTsA==",
"dev": true,
"requires": {
"map-age-cleaner": "^0.1.3",
"mimic-fn": "^3.1.0"
}
},
"mimic-fn": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
"dev": true
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
"integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
"dev": true
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"dev": true,
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"php-parser": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/php-parser/-/php-parser-3.0.2.tgz",
"integrity": "sha512-a7y1+odEGsceLDLpu7oNyspZ0pK8FMWJOoim4/yd82AtnEZNLdCLZ67arnOQZ9K0lHJiSp4/7lVUpGELVxE14w==",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"prettier": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
"dev": true
},
"prettier-plugin-package": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-package/-/prettier-plugin-package-1.3.0.tgz",
"integrity": "sha512-KPNHR/Jm2zTevBp1SnjzMnooO1BOQW2bixVbOp8flOJoW+dxdDwEncObfsKZdkjwrv6AIH4oWqm5EO/etDmK9Q==",
"dev": true
},
"read-pkg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
"integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
"dev": true,
"requires": {
"normalize-package-data": "^2.3.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"resolve": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
},
"rxjs": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
"integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"spawn-command": {
"version": "0.0.2-1",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
"integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
"dev": true
},
"spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
"integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
"dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-exceptions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-license-ids": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true
},
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}

View file

@ -0,0 +1,44 @@
{
"name": "twitteroauth",
"version": "0.0.0",
"description": "The most popular PHP library for use with the Twitter OAuth REST API.",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/abraham/twitteroauth.git"
},
"author": "Abraham Williams <abraham@abrah.am>",
"homepage": "https://github.com/abraham/twitteroauth#readme",
"bugs": {
"url": "https://github.com/abraham/twitteroauth/issues"
},
"scripts": {
"fix": "concurrently npm:fix:*",
"fix:phpcbf": "./vendor/bin/phpcbf src tests --standard=PSR12",
"fix:prettier": "prettier . --write",
"lint": "concurrently npm:lint:*",
"lint:phpcs": "./vendor/bin/phpcs src tests --standard=PSR12",
"lint:prettier": "prettier . --check",
"postinstall": "composer install --no-interaction",
"test": "./vendor/bin/phpunit"
},
"keywords": [
"twitter",
"api",
"oauth",
"rest",
"social",
"twitter-api",
"twitter-oauth"
],
"dependencies": {},
"devDependencies": {
"@prettier/plugin-php": "0.16.0",
"concurrently": "^5.3.0",
"prettier": "2.2.1",
"prettier-plugin-package": "1.3.0"
},
"directories": {
"test": "tests"
}
}

View file

@ -1,11 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit <phpunit
colors="true" colors="true"
bootstrap="tests/bootstrap.php"> bootstrap="tests/bootstrap.php"
testdox="true">
<testsuites> <testsuites>
<testsuite name="TwitterOAuth Test Suite"> <testsuite name="TwitterOAuth Test Suite">
<directory>./tests/</directory> <directory>./tests/</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<listeners>
<listener
class="VCR\PHPUnit\TestListener\VCRTestListener"
file="vendor/php-vcr/phpunit-testlistener-vcr/src/VCRTestListener.php" />
</listeners>
</phpunit> </phpunit>

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
/** /**
@ -13,6 +15,11 @@ class Config
protected $timeout = 5; protected $timeout = 5;
/** @var int how long to wait while connecting to the API */ /** @var int how long to wait while connecting to the API */
protected $connectionTimeout = 5; protected $connectionTimeout = 5;
/** @var int How many times we retry request when API is down */
protected $maxRetries = 0;
/** @var int Delay in seconds before we retry the request */
protected $retriesDelay = 1;
/** /**
* Decode JSON Response as associative Array * Decode JSON Response as associative Array
* *
@ -38,32 +45,44 @@ class Config
* @param int $connectionTimeout * @param int $connectionTimeout
* @param int $timeout * @param int $timeout
*/ */
public function setTimeouts($connectionTimeout, $timeout) public function setTimeouts(int $connectionTimeout, int $timeout): void
{ {
$this->connectionTimeout = (int)$connectionTimeout; $this->connectionTimeout = $connectionTimeout;
$this->timeout = (int)$timeout; $this->timeout = $timeout;
}
/**
* Set the number of times to retry on error and how long between each.
*
* @param int $maxRetries
* @param int $retriesDelay
*/
public function setRetries(int $maxRetries, int $retriesDelay): void
{
$this->maxRetries = $maxRetries;
$this->retriesDelay = $retriesDelay;
} }
/** /**
* @param bool $value * @param bool $value
*/ */
public function setDecodeJsonAsArray($value) public function setDecodeJsonAsArray(bool $value): void
{ {
$this->decodeJsonAsArray = (bool)$value; $this->decodeJsonAsArray = $value;
} }
/** /**
* @param string $userAgent * @param string $userAgent
*/ */
public function setUserAgent($userAgent) public function setUserAgent(string $userAgent): void
{ {
$this->userAgent = (string)$userAgent; $this->userAgent = $userAgent;
} }
/** /**
* @param array $proxy * @param array $proxy
*/ */
public function setProxy(array $proxy) public function setProxy(array $proxy): void
{ {
$this->proxy = $proxy; $this->proxy = $proxy;
} }
@ -73,9 +92,9 @@ class Config
* *
* @param boolean $gzipEncoding * @param boolean $gzipEncoding
*/ */
public function setGzipEncoding($gzipEncoding) public function setGzipEncoding(bool $gzipEncoding): void
{ {
$this->gzipEncoding = (bool)$gzipEncoding; $this->gzipEncoding = $gzipEncoding;
} }
/** /**
@ -83,8 +102,8 @@ class Config
* *
* @param int $value * @param int $value
*/ */
public function setChunkSize($value) public function setChunkSize(int $value): void
{ {
$this->chunkSize = (int)$value; $this->chunkSize = $value;
} }
} }

View file

@ -1,8 +1,12 @@
<?php <?php
/** /**
* The MIT License * The MIT License
* Copyright (c) 2007 Andy Smith * Copyright (c) 2007 Andy Smith
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
class Consumer class Consumer
@ -15,12 +19,15 @@ class Consumer
public $callbackUrl; public $callbackUrl;
/** /**
* @param string $key * @param string|null $key
* @param string $secret * @param string|null $secret
* @param null $callbackUrl * @param null $callbackUrl
*/ */
public function __construct($key, $secret, $callbackUrl = null) public function __construct(
{ ?string $key,
?string $secret,
?string $callbackUrl = null
) {
$this->key = $key; $this->key = $key;
$this->secret = $secret; $this->secret = $secret;
$this->callbackUrl = $callbackUrl; $this->callbackUrl = $callbackUrl;

View file

@ -1,8 +1,12 @@
<?php <?php
/** /**
* The MIT License * The MIT License
* Copyright (c) 2007 Andy Smith * Copyright (c) 2007 Andy Smith
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
/** /**
@ -19,17 +23,20 @@ class HmacSha1 extends SignatureMethod
*/ */
public function getName() public function getName()
{ {
return "HMAC-SHA1"; return 'HMAC-SHA1';
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function buildSignature(Request $request, Consumer $consumer, Token $token = null) public function buildSignature(
{ Request $request,
Consumer $consumer,
Token $token = null
): string {
$signatureBase = $request->getSignatureBaseString(); $signatureBase = $request->getSignatureBaseString();
$parts = [$consumer->secret, null !== $token ? $token->secret : ""]; $parts = [$consumer->secret, null !== $token ? $token->secret : ''];
$parts = Util::urlencodeRfc3986($parts); $parts = Util::urlencodeRfc3986($parts);
$key = implode('&', $parts); $key = implode('&', $parts);

View file

@ -1,8 +1,12 @@
<?php <?php
/** /**
* The MIT License * The MIT License
* Copyright (c) 2007 Andy Smith * Copyright (c) 2007 Andy Smith
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
class Request class Request
@ -10,6 +14,7 @@ class Request
protected $parameters; protected $parameters;
protected $httpMethod; protected $httpMethod;
protected $httpUrl; protected $httpUrl;
protected $json;
public static $version = '1.0'; public static $version = '1.0';
/** /**
@ -19,9 +24,15 @@ class Request
* @param string $httpUrl * @param string $httpUrl
* @param array|null $parameters * @param array|null $parameters
*/ */
public function __construct($httpMethod, $httpUrl, array $parameters = []) public function __construct(
{ string $httpMethod,
$parameters = array_merge(Util::parseParameters(parse_url($httpUrl, PHP_URL_QUERY)), $parameters); string $httpUrl,
?array $parameters = []
) {
$parameters = array_merge(
Util::parseParameters(parse_url($httpUrl, PHP_URL_QUERY)),
$parameters
);
$this->parameters = $parameters; $this->parameters = $parameters;
$this->httpMethod = $httpMethod; $this->httpMethod = $httpMethod;
$this->httpUrl = $httpUrl; $this->httpUrl = $httpUrl;
@ -41,21 +52,28 @@ class Request
public static function fromConsumerAndToken( public static function fromConsumerAndToken(
Consumer $consumer, Consumer $consumer,
Token $token = null, Token $token = null,
$httpMethod, string $httpMethod,
$httpUrl, string $httpUrl,
array $parameters = [] array $parameters = [],
$json = false
) { ) {
$defaults = [ $defaults = [
"oauth_version" => Request::$version, 'oauth_version' => Request::$version,
"oauth_nonce" => Request::generateNonce(), 'oauth_nonce' => Request::generateNonce(),
"oauth_timestamp" => time(), 'oauth_timestamp' => time(),
"oauth_consumer_key" => $consumer->key 'oauth_consumer_key' => $consumer->key,
]; ];
if (null !== $token) { if (null !== $token) {
$defaults['oauth_token'] = $token->key; $defaults['oauth_token'] = $token->key;
} }
$parameters = array_merge($defaults, $parameters); // The json payload is not included in the signature on json requests,
// therefore it shouldn't be included in the parameters array.
if ($json) {
$parameters = $defaults;
} else {
$parameters = array_merge($defaults, $parameters);
}
return new Request($httpMethod, $httpUrl, $parameters); return new Request($httpMethod, $httpUrl, $parameters);
} }
@ -64,33 +82,35 @@ class Request
* @param string $name * @param string $name
* @param string $value * @param string $value
*/ */
public function setParameter($name, $value) public function setParameter(string $name, string $value)
{ {
$this->parameters[$name] = $value; $this->parameters[$name] = $value;
} }
/** /**
* @param $name * @param string $name
* *
* @return string|null * @return string|null
*/ */
public function getParameter($name) public function getParameter(string $name): ?string
{ {
return isset($this->parameters[$name]) ? $this->parameters[$name] : null; return isset($this->parameters[$name])
? $this->parameters[$name]
: null;
} }
/** /**
* @return array * @return array
*/ */
public function getParameters() public function getParameters(): array
{ {
return $this->parameters; return $this->parameters;
} }
/** /**
* @param $name * @param string $name
*/ */
public function removeParameter($name) public function removeParameter(string $name): void
{ {
unset($this->parameters[$name]); unset($this->parameters[$name]);
} }
@ -100,7 +120,7 @@ class Request
* *
* @return string * @return string
*/ */
public function getSignableParameters() public function getSignableParameters(): string
{ {
// Grab all parameters // Grab all parameters
$params = $this->parameters; $params = $this->parameters;
@ -123,12 +143,12 @@ class Request
* *
* @return string * @return string
*/ */
public function getSignatureBaseString() public function getSignatureBaseString(): string
{ {
$parts = [ $parts = [
$this->getNormalizedHttpMethod(), $this->getNormalizedHttpMethod(),
$this->getNormalizedHttpUrl(), $this->getNormalizedHttpUrl(),
$this->getSignableParameters() $this->getSignableParameters(),
]; ];
$parts = Util::urlencodeRfc3986($parts); $parts = Util::urlencodeRfc3986($parts);
@ -141,7 +161,7 @@ class Request
* *
* @return string * @return string
*/ */
public function getNormalizedHttpMethod() public function getNormalizedHttpMethod(): string
{ {
return strtoupper($this->httpMethod); return strtoupper($this->httpMethod);
} }
@ -152,7 +172,7 @@ class Request
* *
* @return string * @return string
*/ */
public function getNormalizedHttpUrl() public function getNormalizedHttpUrl(): string
{ {
$parts = parse_url($this->httpUrl); $parts = parse_url($this->httpUrl);
@ -168,7 +188,7 @@ class Request
* *
* @return string * @return string
*/ */
public function toUrl() public function toUrl(): string
{ {
$postData = $this->toPostdata(); $postData = $this->toPostdata();
$out = $this->getNormalizedHttpUrl(); $out = $this->getNormalizedHttpUrl();
@ -183,7 +203,7 @@ class Request
* *
* @return string * @return string
*/ */
public function toPostdata() public function toPostdata(): string
{ {
return Util::buildHttpQuery($this->parameters); return Util::buildHttpQuery($this->parameters);
} }
@ -194,19 +214,25 @@ class Request
* @return string * @return string
* @throws TwitterOAuthException * @throws TwitterOAuthException
*/ */
public function toHeader() public function toHeader(): string
{ {
$first = true; $first = true;
$out = 'Authorization: OAuth'; $out = 'Authorization: OAuth';
foreach ($this->parameters as $k => $v) { foreach ($this->parameters as $k => $v) {
if (substr($k, 0, 5) != "oauth") { if (substr($k, 0, 5) != 'oauth') {
continue; continue;
} }
if (is_array($v)) { if (is_array($v)) {
throw new TwitterOAuthException('Arrays not supported in headers'); throw new TwitterOAuthException(
'Arrays not supported in headers'
);
} }
$out .= ($first) ? ' ' : ', '; $out .= $first ? ' ' : ', ';
$out .= Util::urlencodeRfc3986($k) . '="' . Util::urlencodeRfc3986($v) . '"'; $out .=
Util::urlencodeRfc3986($k) .
'="' .
Util::urlencodeRfc3986($v) .
'"';
$first = false; $first = false;
} }
return $out; return $out;
@ -215,7 +241,7 @@ class Request
/** /**
* @return string * @return string
*/ */
public function __toString() public function __toString(): string
{ {
return $this->toUrl(); return $this->toUrl();
} }
@ -225,11 +251,17 @@ class Request
* @param Consumer $consumer * @param Consumer $consumer
* @param Token $token * @param Token $token
*/ */
public function signRequest(SignatureMethod $signatureMethod, Consumer $consumer, Token $token = null) public function signRequest(
{ SignatureMethod $signatureMethod,
$this->setParameter("oauth_signature_method", $signatureMethod->getName()); Consumer $consumer,
Token $token = null
) {
$this->setParameter(
'oauth_signature_method',
$signatureMethod->getName()
);
$signature = $this->buildSignature($signatureMethod, $consumer, $token); $signature = $this->buildSignature($signatureMethod, $consumer, $token);
$this->setParameter("oauth_signature", $signature); $this->setParameter('oauth_signature', $signature);
} }
/** /**
@ -239,15 +271,18 @@ class Request
* *
* @return string * @return string
*/ */
public function buildSignature(SignatureMethod $signatureMethod, Consumer $consumer, Token $token = null) public function buildSignature(
{ SignatureMethod $signatureMethod,
Consumer $consumer,
Token $token = null
): string {
return $signatureMethod->buildSignature($this, $consumer, $token); return $signatureMethod->buildSignature($this, $consumer, $token);
} }
/** /**
* @return string * @return string
*/ */
public static function generateNonce() public static function generateNonce(): string
{ {
return md5(microtime() . mt_rand()); return md5(microtime() . mt_rand());
} }

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
/** /**
@ -23,7 +25,7 @@ class Response
/** /**
* @param string $apiPath * @param string $apiPath
*/ */
public function setApiPath($apiPath) public function setApiPath(string $apiPath): void
{ {
$this->apiPath = $apiPath; $this->apiPath = $apiPath;
} }
@ -31,7 +33,7 @@ class Response
/** /**
* @return string|null * @return string|null
*/ */
public function getApiPath() public function getApiPath(): ?string
{ {
return $this->apiPath; return $this->apiPath;
} }
@ -55,7 +57,7 @@ class Response
/** /**
* @param int $httpCode * @param int $httpCode
*/ */
public function setHttpCode($httpCode) public function setHttpCode(int $httpCode): void
{ {
$this->httpCode = $httpCode; $this->httpCode = $httpCode;
} }
@ -63,7 +65,7 @@ class Response
/** /**
* @return int * @return int
*/ */
public function getHttpCode() public function getHttpCode(): int
{ {
return $this->httpCode; return $this->httpCode;
} }
@ -71,7 +73,7 @@ class Response
/** /**
* @param array $headers * @param array $headers
*/ */
public function setHeaders(array $headers) public function setHeaders(array $headers): void
{ {
foreach ($headers as $key => $value) { foreach ($headers as $key => $value) {
if (substr($key, 0, 1) == 'x') { if (substr($key, 0, 1) == 'x') {
@ -84,7 +86,7 @@ class Response
/** /**
* @return array * @return array
*/ */
public function getsHeaders() public function getsHeaders(): array
{ {
return $this->headers; return $this->headers;
} }
@ -92,7 +94,7 @@ class Response
/** /**
* @param array $xHeaders * @param array $xHeaders
*/ */
public function setXHeaders(array $xHeaders = []) public function setXHeaders(array $xHeaders = []): void
{ {
$this->xHeaders = $xHeaders; $this->xHeaders = $xHeaders;
} }
@ -100,7 +102,7 @@ class Response
/** /**
* @return array * @return array
*/ */
public function getXHeaders() public function getXHeaders(): array
{ {
return $this->xHeaders; return $this->xHeaders;
} }

View file

@ -1,8 +1,12 @@
<?php <?php
/** /**
* The MIT License * The MIT License
* Copyright (c) 2007 Andy Smith * Copyright (c) 2007 Andy Smith
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
/** /**
@ -30,7 +34,11 @@ abstract class SignatureMethod
* *
* @return string * @return string
*/ */
abstract public function buildSignature(Request $request, Consumer $consumer, Token $token = null); abstract public function buildSignature(
Request $request,
Consumer $consumer,
Token $token = null
);
/** /**
* Verifies that a given signature is correct * Verifies that a given signature is correct
@ -42,8 +50,12 @@ abstract class SignatureMethod
* *
* @return bool * @return bool
*/ */
public function checkSignature(Request $request, Consumer $consumer, Token $token, $signature) public function checkSignature(
{ Request $request,
Consumer $consumer,
Token $token,
string $signature
): bool {
$built = $this->buildSignature($request, $consumer, $token); $built = $this->buildSignature($request, $consumer, $token);
// Check for zero length, although unlikely here // Check for zero length, although unlikely here
@ -58,7 +70,7 @@ abstract class SignatureMethod
// Avoid a timing leak with a (hopefully) time insensitive compare // Avoid a timing leak with a (hopefully) time insensitive compare
$result = 0; $result = 0;
for ($i = 0; $i < strlen($signature); $i++) { for ($i = 0; $i < strlen($signature); $i++) {
$result |= ord($built{$i}) ^ ord($signature{$i}); $result |= ord($built[$i]) ^ ord($signature[$i]);
} }
return $result == 0; return $result == 0;

View file

@ -1,8 +1,12 @@
<?php <?php
/** /**
* The MIT License * The MIT License
* Copyright (c) 2007 Andy Smith * Copyright (c) 2007 Andy Smith
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
class Token class Token
@ -16,7 +20,7 @@ class Token
* @param string $key The OAuth Token * @param string $key The OAuth Token
* @param string $secret The OAuth Token Secret * @param string $secret The OAuth Token Secret
*/ */
public function __construct($key, $secret) public function __construct(?string $key, ?string $secret)
{ {
$this->key = $key; $this->key = $key;
$this->secret = $secret; $this->secret = $secret;
@ -28,9 +32,10 @@ class Token
* *
* @return string * @return string
*/ */
public function __toString() public function __toString(): string
{ {
return sprintf("oauth_token=%s&oauth_token_secret=%s", return sprintf(
'oauth_token=%s&oauth_token_secret=%s',
Util::urlencodeRfc3986($this->key), Util::urlencodeRfc3986($this->key),
Util::urlencodeRfc3986($this->secret) Util::urlencodeRfc3986($this->secret)
); );

View file

@ -1,12 +1,17 @@
<?php <?php
/** /**
* The most popular PHP library for use with the Twitter OAuth REST API. * The most popular PHP library for use with the Twitter OAuth REST API.
* *
* @license MIT * @license MIT
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
use Abraham\TwitterOAuth\Util\JsonDecoder; use Abraham\TwitterOAuth\Util\JsonDecoder;
use Composer\CaBundle\CaBundle;
/** /**
* TwitterOAuth class for interacting with the Twitter API. * TwitterOAuth class for interacting with the Twitter API.
@ -15,9 +20,9 @@ use Abraham\TwitterOAuth\Util\JsonDecoder;
*/ */
class TwitterOAuth extends Config class TwitterOAuth extends Config
{ {
const API_VERSION = '1.1'; private const API_VERSION = '1.1';
const API_HOST = 'https://api.twitter.com'; private const API_HOST = 'https://api.twitter.com';
const UPLOAD_HOST = 'https://upload.twitter.com'; private const UPLOAD_HOST = 'https://upload.twitter.com';
/** @var Response details about the result of the last request */ /** @var Response details about the result of the last request */
private $response; private $response;
@ -29,6 +34,8 @@ class TwitterOAuth extends Config
private $token; private $token;
/** @var HmacSha1 OAuth 1 signature type used by Twitter */ /** @var HmacSha1 OAuth 1 signature type used by Twitter */
private $signatureMethod; private $signatureMethod;
/** @var int Number of attempts we made for the request */
private $attempts = 0;
/** /**
* Constructor * Constructor
@ -38,16 +45,20 @@ class TwitterOAuth extends Config
* @param string|null $oauthToken The Client Token (optional) * @param string|null $oauthToken The Client Token (optional)
* @param string|null $oauthTokenSecret The Client Token Secret (optional) * @param string|null $oauthTokenSecret The Client Token Secret (optional)
*/ */
public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null) public function __construct(
{ string $consumerKey,
string $consumerSecret,
?string $oauthToken = null,
?string $oauthTokenSecret = null
) {
$this->resetLastResponse(); $this->resetLastResponse();
$this->signatureMethod = new HmacSha1(); $this->signatureMethod = new HmacSha1();
$this->consumer = new Consumer($consumerKey, $consumerSecret); $this->consumer = new Consumer($consumerKey, $consumerSecret);
if (!empty($oauthToken) && !empty($oauthTokenSecret)) { if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
$this->token = new Token($oauthToken, $oauthTokenSecret); $this->setOauthToken($oauthToken, $oauthTokenSecret);
} }
if (empty($oauthToken) && !empty($oauthTokenSecret)) { if (empty($oauthToken) && !empty($oauthTokenSecret)) {
$this->bearer = $oauthTokenSecret; $this->setBearer($oauthTokenSecret);
} }
} }
@ -55,15 +66,27 @@ class TwitterOAuth extends Config
* @param string $oauthToken * @param string $oauthToken
* @param string $oauthTokenSecret * @param string $oauthTokenSecret
*/ */
public function setOauthToken($oauthToken, $oauthTokenSecret) public function setOauthToken(
{ string $oauthToken,
string $oauthTokenSecret
): void {
$this->token = new Token($oauthToken, $oauthTokenSecret); $this->token = new Token($oauthToken, $oauthTokenSecret);
$this->bearer = null;
}
/**
* @param string $oauthTokenSecret
*/
public function setBearer(string $oauthTokenSecret): void
{
$this->bearer = $oauthTokenSecret;
$this->token = null;
} }
/** /**
* @return string|null * @return string|null
*/ */
public function getLastApiPath() public function getLastApiPath(): ?string
{ {
return $this->response->getApiPath(); return $this->response->getApiPath();
} }
@ -71,7 +94,7 @@ class TwitterOAuth extends Config
/** /**
* @return int * @return int
*/ */
public function getLastHttpCode() public function getLastHttpCode(): int
{ {
return $this->response->getHttpCode(); return $this->response->getHttpCode();
} }
@ -79,7 +102,7 @@ class TwitterOAuth extends Config
/** /**
* @return array * @return array
*/ */
public function getLastXHeaders() public function getLastXHeaders(): array
{ {
return $this->response->getXHeaders(); return $this->response->getXHeaders();
} }
@ -95,11 +118,29 @@ class TwitterOAuth extends Config
/** /**
* Resets the last response cache. * Resets the last response cache.
*/ */
public function resetLastResponse() public function resetLastResponse(): void
{ {
$this->response = new Response(); $this->response = new Response();
} }
/**
* Resets the attempts number.
*/
private function resetAttemptsNumber(): void
{
$this->attempts = 0;
}
/**
* Delays the retries when they're activated.
*/
private function sleepIfNeeded(): void
{
if ($this->maxRetries && $this->attempts) {
sleep($this->retriesDelay);
}
}
/** /**
* Make URLs for user browser navigation. * Make URLs for user browser navigation.
* *
@ -108,7 +149,7 @@ class TwitterOAuth extends Config
* *
* @return string * @return string
*/ */
public function url($path, array $parameters) public function url(string $path, array $parameters): string
{ {
$this->resetLastResponse(); $this->resetLastResponse();
$this->response->setApiPath($path); $this->response->setApiPath($path);
@ -125,7 +166,7 @@ class TwitterOAuth extends Config
* @return array * @return array
* @throws TwitterOAuthException * @throws TwitterOAuthException
*/ */
public function oauth($path, array $parameters = []) public function oauth(string $path, array $parameters = []): array
{ {
$response = []; $response = [];
$this->resetLastResponse(); $this->resetLastResponse();
@ -151,15 +192,28 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
public function oauth2($path, array $parameters = []) public function oauth2(string $path, array $parameters = [])
{ {
$method = 'POST'; $method = 'POST';
$this->resetLastResponse(); $this->resetLastResponse();
$this->response->setApiPath($path); $this->response->setApiPath($path);
$url = sprintf('%s/%s', self::API_HOST, $path); $url = sprintf('%s/%s', self::API_HOST, $path);
$request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters); $request = Request::fromConsumerAndToken(
$authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer); $this->consumer,
$result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters); $this->token,
$method,
$url,
$parameters
);
$authorization =
'Authorization: Basic ' .
$this->encodeAppAuthorization($this->consumer);
$result = $this->request(
$request->getNormalizedHttpUrl(),
$method,
$authorization,
$parameters
);
$response = JsonDecoder::decode($result, $this->decodeJsonAsArray); $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
$this->response->setBody($response); $this->response->setBody($response);
return $response; return $response;
@ -173,9 +227,9 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
public function get($path, array $parameters = []) public function get(string $path, array $parameters = [])
{ {
return $this->http('GET', self::API_HOST, $path, $parameters); return $this->http('GET', self::API_HOST, $path, $parameters, false);
} }
/** /**
@ -183,12 +237,16 @@ class TwitterOAuth extends Config
* *
* @param string $path * @param string $path
* @param array $parameters * @param array $parameters
* @param bool $json
* *
* @return array|object * @return array|object
*/ */
public function post($path, array $parameters = []) public function post(
{ string $path,
return $this->http('POST', self::API_HOST, $path, $parameters); array $parameters = [],
bool $json = false
) {
return $this->http('POST', self::API_HOST, $path, $parameters, $json);
} }
/** /**
@ -199,9 +257,9 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
public function delete($path, array $parameters = []) public function delete(string $path, array $parameters = [])
{ {
return $this->http('DELETE', self::API_HOST, $path, $parameters); return $this->http('DELETE', self::API_HOST, $path, $parameters, false);
} }
/** /**
@ -212,9 +270,9 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
public function put($path, array $parameters = []) public function put(string $path, array $parameters = [])
{ {
return $this->http('PUT', self::API_HOST, $path, $parameters); return $this->http('PUT', self::API_HOST, $path, $parameters, false);
} }
/** /**
@ -226,8 +284,11 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
public function upload($path, array $parameters = [], $chunked = false) public function upload(
{ string $path,
array $parameters = [],
bool $chunked = false
) {
if ($chunked) { if ($chunked) {
return $this->uploadMediaChunked($path, $parameters); return $this->uploadMediaChunked($path, $parameters);
} else { } else {
@ -235,6 +296,27 @@ class TwitterOAuth extends Config
} }
} }
/**
* Progression of media upload
*
* @param string $media_id
*
* @return array|object
*/
public function mediaStatus(string $media_id)
{
return $this->http(
'GET',
self::UPLOAD_HOST,
'media/upload',
[
'command' => 'STATUS',
'media_id' => $media_id,
],
false
);
}
/** /**
* Private method to upload media (not chunked) to upload.twitter.com. * Private method to upload media (not chunked) to upload.twitter.com.
* *
@ -243,12 +325,24 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
private function uploadMediaNotChunked($path, array $parameters) private function uploadMediaNotChunked(string $path, array $parameters)
{ {
$file = file_get_contents($parameters['media']); if (
$base = base64_encode($file); !is_readable($parameters['media']) ||
$parameters['media'] = $base; ($file = file_get_contents($parameters['media'])) === false
return $this->http('POST', self::UPLOAD_HOST, $path, $parameters); ) {
throw new \InvalidArgumentException(
'You must supply a readable file'
);
}
$parameters['media'] = base64_encode($file);
return $this->http(
'POST',
self::UPLOAD_HOST,
$path,
$parameters,
false
);
} }
/** /**
@ -259,27 +353,46 @@ class TwitterOAuth extends Config
* *
* @return array|object * @return array|object
*/ */
private function uploadMediaChunked($path, array $parameters) private function uploadMediaChunked(string $path, array $parameters)
{ {
$init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters)); $init = $this->http(
'POST',
self::UPLOAD_HOST,
$path,
$this->mediaInitParameters($parameters),
false
);
// Append // Append
$segmentIndex = 0; $segmentIndex = 0;
$media = fopen($parameters['media'], 'rb'); $media = fopen($parameters['media'], 'rb');
while (!feof($media)) while (!feof($media)) {
{ $this->http(
$this->http('POST', self::UPLOAD_HOST, 'media/upload', [ 'POST',
'command' => 'APPEND', self::UPLOAD_HOST,
'media_id' => $init->media_id_string, 'media/upload',
'segment_index' => $segmentIndex++, [
'media_data' => base64_encode(fread($media, $this->chunkSize)) 'command' => 'APPEND',
]); 'media_id' => $init->media_id_string,
'segment_index' => $segmentIndex++,
'media_data' => base64_encode(
fread($media, $this->chunkSize)
),
],
false
);
} }
fclose($media); fclose($media);
// Finalize // Finalize
$finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [ $finalize = $this->http(
'command' => 'FINALIZE', 'POST',
'media_id' => $init->media_id_string self::UPLOAD_HOST,
]); 'media/upload',
[
'command' => 'FINALIZE',
'media_id' => $init->media_id_string,
],
false
);
return $finalize; return $finalize;
} }
@ -291,20 +404,41 @@ class TwitterOAuth extends Config
* *
* @return array * @return array
*/ */
private function mediaInitParameters(array $parameters) private function mediaInitParameters(array $parameters): array
{ {
$return = [ $allowed_keys = [
'command' => 'INIT', 'media_type',
'media_type' => $parameters['media_type'], 'additional_owners',
'total_bytes' => filesize($parameters['media']) 'media_category',
'shared',
]; ];
if (isset($parameters['additional_owners'])) { $base = [
$return['additional_owners'] = $parameters['additional_owners']; 'command' => 'INIT',
'total_bytes' => filesize($parameters['media']),
];
$allowed_parameters = array_intersect_key(
$parameters,
array_flip($allowed_keys)
);
return array_merge($base, $allowed_parameters);
}
/**
* Cleanup any parameters that are known not to work.
*
* @param array $parameters
*
* @return array
*/
private function cleanUpParameters(array $parameters)
{
foreach ($parameters as $key => $value) {
// PHP coerces `true` to `"1"` which some Twitter APIs don't like.
if (is_bool($value)) {
$parameters[$key] = var_export($value, true);
}
} }
if (isset($parameters['media_category'])) { return $parameters;
$return['media_category'] = $parameters['media_category'];
}
return $return;
} }
/** /**
@ -312,39 +446,104 @@ class TwitterOAuth extends Config
* @param string $host * @param string $host
* @param string $path * @param string $path
* @param array $parameters * @param array $parameters
* @param bool $json
* *
* @return array|object * @return array|object
*/ */
private function http($method, $host, $path, array $parameters) private function http(
{ string $method,
string $host,
string $path,
array $parameters,
bool $json
) {
$this->resetLastResponse(); $this->resetLastResponse();
$this->resetAttemptsNumber();
$url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path); $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
$this->response->setApiPath($path); $this->response->setApiPath($path);
$result = $this->oAuthRequest($url, $method, $parameters); if (!$json) {
$response = JsonDecoder::decode($result, $this->decodeJsonAsArray); $parameters = $this->cleanUpParameters($parameters);
$this->response->setBody($response); }
return $this->makeRequests($url, $method, $parameters, $json);
}
/**
*
* Make requests and retry them (if enabled) in case of Twitter's problems.
*
* @param string $method
* @param string $url
* @param string $method
* @param array $parameters
* @param bool $json
*
* @return array|object
*/
private function makeRequests(
string $url,
string $method,
array $parameters,
bool $json
) {
do {
$this->sleepIfNeeded();
$result = $this->oAuthRequest($url, $method, $parameters, $json);
$response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
$this->response->setBody($response);
$this->attempts++;
// Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
} while ($this->requestsAvailable());
return $response; return $response;
} }
/**
* Checks if we have to retry request if API is down.
*
* @return bool
*/
private function requestsAvailable(): bool
{
return $this->maxRetries &&
$this->attempts <= $this->maxRetries &&
$this->getLastHttpCode() >= 500;
}
/** /**
* Format and sign an OAuth / API request * Format and sign an OAuth / API request
* *
* @param string $url * @param string $url
* @param string $method * @param string $method
* @param array $parameters * @param array $parameters
* @param bool $json
* *
* @return string * @return string
* @throws TwitterOAuthException * @throws TwitterOAuthException
*/ */
private function oAuthRequest($url, $method, array $parameters) private function oAuthRequest(
{ string $url,
$request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters); string $method,
array $parameters,
bool $json = false
) {
$request = Request::fromConsumerAndToken(
$this->consumer,
$this->token,
$method,
$url,
$parameters,
$json
);
if (array_key_exists('oauth_callback', $parameters)) { if (array_key_exists('oauth_callback', $parameters)) {
// Twitter doesn't like oauth_callback as a parameter. // Twitter doesn't like oauth_callback as a parameter.
unset($parameters['oauth_callback']); unset($parameters['oauth_callback']);
} }
if ($this->bearer === null) { if ($this->bearer === null) {
$request->signRequest($this->signatureMethod, $this->consumer, $this->token); $request->signRequest(
$this->signatureMethod,
$this->consumer,
$this->token
);
$authorization = $request->toHeader(); $authorization = $request->toHeader();
if (array_key_exists('oauth_verifier', $parameters)) { if (array_key_exists('oauth_verifier', $parameters)) {
// Twitter doesn't always work with oauth in the body and in the header // Twitter doesn't always work with oauth in the body and in the header
@ -354,7 +553,13 @@ class TwitterOAuth extends Config
} else { } else {
$authorization = 'Authorization: Bearer ' . $this->bearer; $authorization = 'Authorization: Bearer ' . $this->bearer;
} }
return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters); return $this->request(
$request->getNormalizedHttpUrl(),
$method,
$authorization,
$parameters,
$json
);
} }
/** /**
@ -362,8 +567,9 @@ class TwitterOAuth extends Config
* *
* @return array * @return array
*/ */
private function curlOptions() private function curlOptions(): array
{ {
$bundlePath = CaBundle::getSystemCaRootBundlePath();
$options = [ $options = [
// CURLOPT_VERBOSE => true, // CURLOPT_VERBOSE => true,
CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout, CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
@ -373,19 +579,17 @@ class TwitterOAuth extends Config
CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => $this->timeout, CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_USERAGENT => $this->userAgent, CURLOPT_USERAGENT => $this->userAgent,
$this->curlCaOpt($bundlePath) => $bundlePath,
]; ];
if ($this->useCAFile()) { if ($this->gzipEncoding) {
$options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
}
if($this->gzipEncoding) {
$options[CURLOPT_ENCODING] = 'gzip'; $options[CURLOPT_ENCODING] = 'gzip';
} }
if (!empty($this->proxy)) { if (!empty($this->proxy)) {
$options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY']; $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
$options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD']; $options[CURLOPT_PROXYUSERPWD] =
$this->proxy['CURLOPT_PROXYUSERPWD'];
$options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT']; $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
$options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC; $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
$options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP; $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
@ -401,22 +605,40 @@ class TwitterOAuth extends Config
* @param string $method * @param string $method
* @param string $authorization * @param string $authorization
* @param array $postfields * @param array $postfields
* @param bool $json
* *
* @return string * @return string
* @throws TwitterOAuthException * @throws TwitterOAuthException
*/ */
private function request($url, $method, $authorization, array $postfields) private function request(
{ string $url,
$options = $this->curlOptions($url, $authorization); string $method,
string $authorization,
array $postfields,
bool $json = false
): string {
$options = $this->curlOptions();
$options[CURLOPT_URL] = $url; $options[CURLOPT_URL] = $url;
$options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:']; $options[CURLOPT_HTTPHEADER] = [
'Accept: application/json',
$authorization,
'Expect:',
];
switch ($method) { switch ($method) {
case 'GET': case 'GET':
break; break;
case 'POST': case 'POST':
$options[CURLOPT_POST] = true; $options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields); if ($json) {
$options[CURLOPT_HTTPHEADER][] =
'Content-type: application/json';
$options[CURLOPT_POSTFIELDS] = json_encode($postfields);
} else {
$options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery(
$postfields
);
}
break; break;
case 'DELETE': case 'DELETE':
$options[CURLOPT_CUSTOMREQUEST] = 'DELETE'; $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
@ -426,21 +648,28 @@ class TwitterOAuth extends Config
break; break;
} }
if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) { if (
in_array($method, ['GET', 'PUT', 'DELETE']) &&
!empty($postfields)
) {
$options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields); $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
} }
$curlHandle = curl_init(); $curlHandle = curl_init();
curl_setopt_array($curlHandle, $options); curl_setopt_array($curlHandle, $options);
$response = curl_exec($curlHandle); $response = curl_exec($curlHandle);
// Throw exceptions on cURL errors. // Throw exceptions on cURL errors.
if (curl_errno($curlHandle) > 0) { if (curl_errno($curlHandle) > 0) {
throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle)); $error = curl_error($curlHandle);
$errorNo = curl_errno($curlHandle);
curl_close($curlHandle);
throw new TwitterOAuthException($error, $errorNo);
} }
$this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE)); $this->response->setHttpCode(
curl_getinfo($curlHandle, CURLINFO_HTTP_CODE)
);
$parts = explode("\r\n\r\n", $response); $parts = explode("\r\n\r\n", $response);
$responseBody = array_pop($parts); $responseBody = array_pop($parts);
$responseHeader = array_pop($parts); $responseHeader = array_pop($parts);
@ -458,12 +687,12 @@ class TwitterOAuth extends Config
* *
* @return array * @return array
*/ */
private function parseHeaders($header) private function parseHeaders(string $header): array
{ {
$headers = []; $headers = [];
foreach (explode("\r\n", $header) as $line) { foreach (explode("\r\n", $header) as $line) {
if (strpos($line, ':') !== false) { if (strpos($line, ':') !== false) {
list ($key, $value) = explode(': ', $line); [$key, $value] = explode(': ', $line);
$key = str_replace('-', '_', strtolower($key)); $key = str_replace('-', '_', strtolower($key));
$headers[$key] = trim($value); $headers[$key] = trim($value);
} }
@ -478,7 +707,7 @@ class TwitterOAuth extends Config
* *
* @return string * @return string
*/ */
private function encodeAppAuthorization(Consumer $consumer) private function encodeAppAuthorization(Consumer $consumer): string
{ {
$key = rawurlencode($consumer->key); $key = rawurlencode($consumer->key);
$secret = rawurlencode($consumer->secret); $secret = rawurlencode($consumer->secret);
@ -486,23 +715,13 @@ class TwitterOAuth extends Config
} }
/** /**
* Is the code running from a Phar module. * Get Curl CA option based on whether the given path is a directory or file.
* *
* @return boolean * @param string $path
* @return int
*/ */
private function pharRunning() private function curlCaOpt(string $path): int
{ {
return class_exists('Phar') && \Phar::running(false) !== ''; return is_dir($path) ? CURLOPT_CAPATH : CURLOPT_CAINFO;
}
/**
* Use included CA file instead of OS provided list.
*
* @return boolean
*/
private function useCAFile()
{
/* Use CACert file when not in a PHAR file. */
return !$this->pharRunning();
} }
} }

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
/** /**

View file

@ -1,24 +1,31 @@
<?php <?php
/** /**
* The MIT License * The MIT License
* Copyright (c) 2007 Andy Smith * Copyright (c) 2007 Andy Smith
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth; namespace Abraham\TwitterOAuth;
class Util class Util
{ {
/** /**
* @param $input * @param mixed $input
* *
* @return array|mixed|string * @return mixed
*/ */
public static function urlencodeRfc3986($input) public static function urlencodeRfc3986($input)
{ {
$output = ''; $output = '';
if (is_array($input)) { if (is_array($input)) {
$output = array_map([__NAMESPACE__ . '\Util', 'urlencodeRfc3986'], $input); $output = array_map(
[__NAMESPACE__ . '\Util', 'urlencodeRfc3986'],
$input
);
} elseif (is_scalar($input)) { } elseif (is_scalar($input)) {
$output = rawurlencode($input); $output = rawurlencode((string) $input);
} }
return $output; return $output;
} }
@ -28,7 +35,7 @@ class Util
* *
* @return string * @return string
*/ */
public static function urldecodeRfc3986($string) public static function urldecodeRfc3986($string): string
{ {
return urldecode($string); return urldecode($string);
} }
@ -42,7 +49,7 @@ class Util
* *
* @return array * @return array
*/ */
public static function parseParameters($input) public static function parseParameters($input): array
{ {
if (!is_string($input)) { if (!is_string($input)) {
return []; return [];
@ -79,7 +86,7 @@ class Util
* *
* @return string * @return string
*/ */
public static function buildHttpQuery(array $params) public static function buildHttpQuery(array $params): string
{ {
if (empty($params)) { if (empty($params)) {
return ''; return '';

View file

@ -15,9 +15,12 @@ class JsonDecoder
* *
* @return array|object * @return array|object
*/ */
public static function decode($string, $asArray) public static function decode(string $string, bool $asArray)
{ {
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { if (
version_compare(PHP_VERSION, '5.4.0', '>=') &&
!(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)
) {
return json_decode($string, $asArray, 512, JSON_BIGINT_AS_STRING); return json_decode($string, $asArray, 512, JSON_BIGINT_AS_STRING);
} }

View file

@ -1,10 +1,13 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth\Tests; namespace Abraham\TwitterOAuth\Tests;
use PHPUnit\Framework\TestCase;
use Abraham\TwitterOAuth\SignatureMethod; use Abraham\TwitterOAuth\SignatureMethod;
abstract class AbstractSignatureMethodTest extends \PHPUnit_Framework_TestCase abstract class AbstractSignatureMethodTest extends TestCase
{ {
protected $name; protected $name;
@ -25,7 +28,10 @@ abstract class AbstractSignatureMethodTest extends \PHPUnit_Framework_TestCase
*/ */
public function testBuildSignature($expected, $request, $consumer, $token) public function testBuildSignature($expected, $request, $consumer, $token)
{ {
$this->assertEquals($expected, $this->getClass()->buildSignature($request, $consumer, $token)); $this->assertEquals(
$expected,
$this->getClass()->buildSignature($request, $consumer, $token)
);
} }
protected function getRequest() protected function getRequest()
@ -35,8 +41,11 @@ abstract class AbstractSignatureMethodTest extends \PHPUnit_Framework_TestCase
->getMock(); ->getMock();
} }
protected function getConsumer($key = null, $secret = null, $callbackUrl = null) protected function getConsumer(
{ $key = null,
$secret = null,
$callbackUrl = null
) {
return $this->getMockBuilder('Abraham\TwitterOAuth\Consumer') return $this->getMockBuilder('Abraham\TwitterOAuth\Consumer')
->setConstructorArgs([$key, $secret, $callbackUrl]) ->setConstructorArgs([$key, $secret, $callbackUrl])
->getMock(); ->getMock();

View file

@ -1,16 +1,23 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth\Tests; namespace Abraham\TwitterOAuth\Tests;
use PHPUnit\Framework\TestCase;
use Abraham\TwitterOAuth\Consumer; use Abraham\TwitterOAuth\Consumer;
class ConsumerTest extends \PHPUnit_Framework_TestCase { class ConsumerTest extends TestCase
{
public function testToString() public function testToString()
{ {
$key = uniqid(); $key = uniqid();
$secret = uniqid(); $secret = uniqid();
$consumer = new Consumer($key, $secret); $consumer = new Consumer($key, $secret);
$this->assertEquals("Consumer[key=$key,secret=$secret]", $consumer->__toString()); $this->assertEquals(
"Consumer[key=$key,secret=$secret]",
$consumer->__toString()
);
} }
} }

View file

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth\Tests; namespace Abraham\TwitterOAuth\Tests;
use Abraham\TwitterOAuth\HmacSha1; use Abraham\TwitterOAuth\HmacSha1;
@ -16,20 +18,30 @@ class HmacSha1Test extends AbstractSignatureMethodTest
public function signatureDataProvider() public function signatureDataProvider()
{ {
return [ return [
['5CoEcoq7XoKFjwYCieQvuzadeUA=', $this->getRequest(), $this->getConsumer(), $this->getToken()], [
'5CoEcoq7XoKFjwYCieQvuzadeUA=',
$this->getRequest(),
$this->getConsumer(),
$this->getToken(),
],
[ [
'EBw0gHngam3BTx8kfPfNNSyKem4=', 'EBw0gHngam3BTx8kfPfNNSyKem4=',
$this->getRequest(), $this->getRequest(),
$this->getConsumer('key', 'secret'), $this->getConsumer('key', 'secret'),
$this->getToken() $this->getToken(),
], ],
[ [
'kDsHFZzws2a5M6cAQjfpdNBo+v8=', 'kDsHFZzws2a5M6cAQjfpdNBo+v8=',
$this->getRequest(), $this->getRequest(),
$this->getConsumer('key', 'secret'), $this->getConsumer('key', 'secret'),
$this->getToken('key', 'secret') $this->getToken('key', 'secret'),
],
[
'EBw0gHngam3BTx8kfPfNNSyKem4=',
$this->getRequest(),
$this->getConsumer('key', 'secret'),
null,
], ],
['EBw0gHngam3BTx8kfPfNNSyKem4=', $this->getRequest(), $this->getConsumer('key', 'secret'), null],
]; ];
} }
} }

View file

@ -1,10 +1,14 @@
<?php <?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth\Tests; namespace Abraham\TwitterOAuth\Tests;
use PHPUnit\Framework\TestCase;
use Abraham\TwitterOAuth\Token; use Abraham\TwitterOAuth\Token;
class TokenTest extends \PHPUnit_Framework_TestCase { class TokenTest extends TestCase
{
/** /**
* @dataProvider tokenProvider * @dataProvider tokenProvider
*/ */
@ -19,8 +23,16 @@ class TokenTest extends \PHPUnit_Framework_TestCase {
{ {
return [ return [
['oauth_token=key&oauth_token_secret=secret', 'key', 'secret'], ['oauth_token=key&oauth_token_secret=secret', 'key', 'secret'],
['oauth_token=key%2Bkey&oauth_token_secret=secret', 'key+key', 'secret'], [
['oauth_token=key~key&oauth_token_secret=secret', 'key~key', 'secret'], 'oauth_token=key%2Bkey&oauth_token_secret=secret',
'key+key',
'secret',
],
[
'oauth_token=key~key&oauth_token_secret=secret',
'key~key',
'secret',
],
]; ];
} }
} }

View file

@ -1,19 +1,30 @@
<?php <?php
/** /**
* WARNING: Running these tests will post and delete through the actual Twitter account. * WARNING: Running tests will post and delete through the actual Twitter account when updating or saving VCR cassettes.
*/ */
declare(strict_types=1);
namespace Abraham\TwitterOAuth\Test; namespace Abraham\TwitterOAuth\Test;
use PHPUnit\Framework\TestCase;
use Abraham\TwitterOAuth\TwitterOAuth; use Abraham\TwitterOAuth\TwitterOAuth;
class TwitterOAuthTest extends \PHPUnit_Framework_TestCase class TwitterOAuthTest extends TestCase
{ {
/** @var TwitterOAuth */ /** @var TwitterOAuth */
protected $twitter; protected $twitter;
protected function setUp() protected function setUp(): void
{ {
$this->twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET); $this->twitter = new TwitterOAuth(
CONSUMER_KEY,
CONSUMER_SECRET,
ACCESS_TOKEN,
ACCESS_TOKEN_SECRET
);
$this->userId = explode('-', ACCESS_TOKEN)[0];
} }
public function testBuildClient() public function testBuildClient()
@ -22,20 +33,30 @@ class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
$this->assertObjectHasAttribute('token', $this->twitter); $this->assertObjectHasAttribute('token', $this->twitter);
} }
/**
* @vcr testSetOauthToken.json
*/
public function testSetOauthToken() public function testSetOauthToken()
{ {
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET); $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$twitter->setOauthToken(ACCESS_TOKEN, ACCESS_TOKEN_SECRET); $twitter->setOauthToken(ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
$this->assertObjectHasAttribute('consumer', $twitter); $this->assertObjectHasAttribute('consumer', $twitter);
$this->assertObjectHasAttribute('token', $twitter); $this->assertObjectHasAttribute('token', $twitter);
$twitter->get('friendships/show', ['target_screen_name' => 'twitterapi']); $twitter->get('friendships/show', [
'target_screen_name' => 'twitterapi',
]);
$this->assertEquals(200, $twitter->getLastHttpCode()); $this->assertEquals(200, $twitter->getLastHttpCode());
} }
/**
* @vcr testOauth2Token.json
*/
public function testOauth2Token() public function testOauth2Token()
{ {
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET); $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$result = $twitter->oauth2('oauth2/token', ['grant_type' => 'client_credentials']); $result = $twitter->oauth2('oauth2/token', [
'grant_type' => 'client_credentials',
]);
$this->assertEquals(200, $twitter->getLastHttpCode()); $this->assertEquals(200, $twitter->getLastHttpCode());
$this->assertObjectHasAttribute('token_type', $result); $this->assertObjectHasAttribute('token_type', $result);
$this->assertObjectHasAttribute('access_token', $result); $this->assertObjectHasAttribute('access_token', $result);
@ -45,40 +66,47 @@ class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
/** /**
* @depends testOauth2Token * @depends testOauth2Token
* @vcr testOauth2BearerToken.json
*/ */
public function testBearerToken($accessToken) public function testOauth2BearerToken($accessToken)
{ {
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, null, $accessToken->access_token); $twitter = new TwitterOAuth(
$result = $twitter->get('statuses/user_timeline', ['screen_name' => 'twitterapi']); CONSUMER_KEY,
if ($twitter->getLastHttpCode() !== 200) { CONSUMER_SECRET,
$this->assertEquals('foo', substr($accessToken->access_token, 0, 75)); null,
$this->assertEquals('foo', print_r($result, true)); $accessToken->access_token
} );
$result = $twitter->get('statuses/user_timeline', [
'screen_name' => 'twitterapi',
]);
$this->assertEquals(200, $twitter->getLastHttpCode()); $this->assertEquals(200, $twitter->getLastHttpCode());
return $accessToken; return $accessToken;
} }
// This causes issues for parallel run tests. /**
// /** * @depends testOauth2BearerToken
// * @depends testBearerToken * @vcr testOauth2TokenInvalidate.json
// */ */
// public function testOauth2TokenInvalidate($accessToken) public function testOauth2TokenInvalidate($accessToken)
// { {
// $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET); $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
// // HACK: access_token is already urlencoded but gets urlencoded again breaking the invalidate request. // HACK: access_token is already urlencoded but gets urlencoded again breaking the invalidate request.
// $result = $twitter->oauth2( $result = $twitter->oauth2('oauth2/invalidate_token', [
// 'oauth2/invalidate_token', 'access_token' => urldecode($accessToken->access_token),
// array('access_token' => urldecode($accessToken->access_token)) ]);
// ); $this->assertEquals(200, $twitter->getLastHttpCode());
// $this->assertEquals(200, $twitter->getLastHttpCode()); $this->assertObjectHasAttribute('access_token', $result);
// $this->assertObjectHasAttribute('access_token', $result); }
// return $result;
// }
/**
* @vcr testOauthRequestToken.json
*/
public function testOauthRequestToken() public function testOauthRequestToken()
{ {
$twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET); $twitter = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$result = $twitter->oauth('oauth/request_token', ['oauth_callback' => OAUTH_CALLBACK]); $result = $twitter->oauth('oauth/request_token', [
'oauth_callback' => OAUTH_CALLBACK,
]);
$this->assertEquals(200, $twitter->getLastHttpCode()); $this->assertEquals(200, $twitter->getLastHttpCode());
$this->assertArrayHasKey('oauth_token', $result); $this->assertArrayHasKey('oauth_token', $result);
$this->assertArrayHasKey('oauth_token_secret', $result); $this->assertArrayHasKey('oauth_token_secret', $result);
@ -88,66 +116,95 @@ class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @expectedException \Abraham\TwitterOAuth\TwitterOAuthException * @vcr testOauthRequestTokenException.json
* @expectedExceptionMessage Could not authenticate you
*/ */
public function testOauthRequestTokenException() public function testOauthRequestTokenException()
{ {
$this->expectException(
\Abraham\TwitterOAuth\TwitterOAuthException::class
);
$this->expectErrorMessage('Could not authenticate you');
$twitter = new TwitterOAuth('CONSUMER_KEY', 'CONSUMER_SECRET'); $twitter = new TwitterOAuth('CONSUMER_KEY', 'CONSUMER_SECRET');
$result = $twitter->oauth('oauth/request_token', ['oauth_callback' => OAUTH_CALLBACK]); $result = $twitter->oauth('oauth/request_token', [
return $result; 'oauth_callback' => OAUTH_CALLBACK,
]);
} }
/** /**
* @expectedException \Abraham\TwitterOAuth\TwitterOAuthException
* @expectedExceptionMessage Invalid oauth_verifier parameter
* @depends testOauthRequestToken * @depends testOauthRequestToken
* @vcr testOauthAccessTokenTokenException.json
*/ */
public function testOauthAccessTokenTokenException(array $requestToken) public function testOauthAccessTokenTokenException(array $requestToken)
{ {
// Can't test this without a browser logging into Twitter so check for the correct error instead. // Can't test this without a browser logging into Twitter so check for the correct error instead.
$this->expectException(
\Abraham\TwitterOAuth\TwitterOAuthException::class
);
$this->expectErrorMessage('Invalid oauth_verifier parameter');
$twitter = new TwitterOAuth( $twitter = new TwitterOAuth(
CONSUMER_KEY, CONSUMER_KEY,
CONSUMER_SECRET, CONSUMER_SECRET,
$requestToken['oauth_token'], $requestToken['oauth_token'],
$requestToken['oauth_token_secret'] $requestToken['oauth_token_secret']
); );
$twitter->oauth("oauth/access_token", ["oauth_verifier" => "fake_oauth_verifier"]); $twitter->oauth('oauth/access_token', [
'oauth_verifier' => 'fake_oauth_verifier',
]);
} }
public function testUrl() public function testUrl()
{ {
$url = $this->twitter->url('oauth/authorize', ['foo' => 'bar', 'baz' => 'qux']); $url = $this->twitter->url('oauth/authorize', [
$this->assertEquals('https://api.twitter.com/oauth/authorize?foo=bar&baz=qux', $url); 'foo' => 'bar',
'baz' => 'qux',
]);
$this->assertEquals(
'https://api.twitter.com/oauth/authorize?foo=bar&baz=qux',
$url
);
} }
/**
* @vcr testGetAccountVerifyCredentials.json
*/
public function testGetAccountVerifyCredentials() public function testGetAccountVerifyCredentials()
{ {
// Include entities boolean added to test parameter value cohearsion $user = $this->twitter->get('account/verify_credentials', [
$this->twitter->get('account/verify_credentials', ["include_entities" => false]); 'include_entities' => false,
'include_email' => true,
]);
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('email', $user);
} }
// BUG: testing is too unreliable for now /**
// public function testSetProxy() * @vcr testSetProxy.json
// { */
// $this->twitter->setProxy(array( public function testSetProxy()
// 'CURLOPT_PROXY' => PROXY, {
// 'CURLOPT_PROXYUSERPWD' => PROXYUSERPWD, $this->twitter->setProxy([
// 'CURLOPT_PROXYPORT' => PROXYPORT, 'CURLOPT_PROXY' => PROXY,
// )); 'CURLOPT_PROXYUSERPWD' => PROXYUSERPWD,
// $this->twitter->setTimeouts(60, 60); 'CURLOPT_PROXYPORT' => PROXYPORT,
// $result = $this->twitter->get('account/verify_credentials'); ]);
// $this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->twitter->setTimeouts(60, 60);
// $this->assertObjectHasAttribute('id', $result); $result = $this->twitter->get('account/verify_credentials');
// } $this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('id', $result);
}
/**
* @vcr testGetStatusesMentionsTimeline.json
*/
public function testGetStatusesMentionsTimeline() public function testGetStatusesMentionsTimeline()
{ {
$this->twitter->get('statuses/mentions_timeline'); $this->twitter->get('statuses/mentions_timeline');
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
} }
/**
* @vcr testGetSearchTweets.json
*/
public function testGetSearchTweets() public function testGetSearchTweets()
{ {
$result = $this->twitter->get('search/tweets', ['q' => 'twitter']); $result = $this->twitter->get('search/tweets', ['q' => 'twitter']);
@ -157,27 +214,32 @@ class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
/** /**
* @depends testGetSearchTweets * @depends testGetSearchTweets
* @vcr testGetSearchTweetsWithMaxId.json
*/ */
public function testGetSearchTweetsWithMaxId($statuses) public function testGetSearchTweetsWithMaxId($statuses)
{ {
$maxId = array_pop($statuses)->id_str; $maxId = array_pop($statuses)->id_str;
$this->twitter->get('search/tweets', ['q' => 'twitter', 'max_id' => $maxId]); $this->twitter->get('search/tweets', [
'q' => 'twitter',
'max_id' => $maxId,
]);
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
} }
/**
* @vcr testPostFavoritesCreate.json
*/
public function testPostFavoritesCreate() public function testPostFavoritesCreate()
{ {
$result = $this->twitter->post('favorites/create', ['id' => '6242973112']); $result = $this->twitter->post('favorites/create', [
if ($this->twitter->getLastHttpCode() == 403) { 'id' => '6242973112',
// Status already favorited ]);
$this->assertEquals(139, $result->errors[0]->code); $this->assertEquals(200, $this->twitter->getLastHttpCode());
} else {
$this->assertEquals(200, $this->twitter->getLastHttpCode());
}
} }
/** /**
* @depends testPostFavoritesCreate * @depends testPostFavoritesCreate
* @vcr testPostFavoritesDestroy.json
*/ */
public function testPostFavoritesDestroy() public function testPostFavoritesDestroy()
{ {
@ -185,49 +247,121 @@ class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
} }
public function testPostStatusesUpdateWithMedia() /**
* @vcr testPostDirectMessagesEventsNew.json
*/
public function testPostDirectMessagesEventsNew()
{ {
$this->twitter->setTimeouts(60, 30); $data = [
// Image source https://www.flickr.com/photos/titrans/8548825587/ 'event' => [
$file_path = __DIR__ . '/kitten.jpg'; 'type' => 'message_create',
$result = $this->twitter->upload('media/upload', ['media' => $file_path]); 'message_create' => [
'target' => [
'recipient_id' => $this->userId,
],
'message_data' => [
'text' => 'Hello World!',
],
],
],
];
$result = $this->twitter->post(
'direct_messages/events/new',
$data,
true
);
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('media_id_string', $result);
$parameters = ['status' => 'Hello World ' . time(), 'media_ids' => $result->media_id_string];
$result = $this->twitter->post('statuses/update', $parameters);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
if ($this->twitter->getLastHttpCode() == 200) {
$result = $this->twitter->post('statuses/destroy/' . $result->id_str);
}
return $result; return $result;
} }
/**
* @depends testPostDirectMessagesEventsNew
* @vcr testDeleteDirectMessagesEventsDestroy.json
*/
public function testDeleteDirectMessagesEventsDestroy($message)
{
$this->twitter->delete('direct_messages/events/destroy', [
'id' => $message->event->id,
]);
$this->assertEquals(204, $this->twitter->getLastHttpCode());
}
/**
* @vcr testPostStatusesUpdateWithMedia.json
*/
public function testPostStatusesUpdateWithMedia()
{
$this->twitter->setTimeouts(60, 60);
// Image source https://www.flickr.com/photos/titrans/8548825587/
$file_path = __DIR__ . '/kitten.jpg';
$result = $this->twitter->upload('media/upload', [
'media' => $file_path,
]);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('media_id_string', $result);
$parameters = [
'status' => 'Hello World ' . MOCK_TIME,
'media_ids' => $result->media_id_string,
];
$result = $this->twitter->post('statuses/update', $parameters);
$this->assertEquals(200, $this->twitter->getLastHttpCode());
$result = $this->twitter->post('statuses/destroy/' . $result->id_str);
return $result;
}
/**
* @vcr testPostStatusUpdateWithInvalidMediaThrowsException.json
*/
public function testPostStatusUpdateWithInvalidMediaThrowsException()
{
$this->expectException(\InvalidArgumentException::class);
$file_path = __DIR__ . '/12345678900987654321.jpg';
$this->assertFalse(\is_readable($file_path));
$result = $this->twitter->upload('media/upload', [
'media' => $file_path,
]);
}
/**
* @vcr testPostStatusesUpdateWithMediaChunked.json
*/
public function testPostStatusesUpdateWithMediaChunked() public function testPostStatusesUpdateWithMediaChunked()
{ {
$this->twitter->setTimeouts(60, 30); $this->twitter->setTimeouts(60, 30);
// Video source http://www.sample-videos.com/ // Video source http://www.sample-videos.com/
$file_path = __DIR__ . '/video.mp4'; $file_path = __DIR__ . '/video.mp4';
$result = $this->twitter->upload('media/upload', ['media' => $file_path, 'media_type' => 'video/mp4'], true); $result = $this->twitter->upload(
'media/upload',
['media' => $file_path, 'media_type' => 'video/mp4'],
true
);
$this->assertEquals(201, $this->twitter->getLastHttpCode()); $this->assertEquals(201, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('media_id_string', $result); $this->assertObjectHasAttribute('media_id_string', $result);
$parameters = ['status' => 'Hello World ' . time(), 'media_ids' => $result->media_id_string]; $parameters = [
'status' => 'Hello World ' . MOCK_TIME,
'media_ids' => $result->media_id_string,
];
$result = $this->twitter->post('statuses/update', $parameters); $result = $this->twitter->post('statuses/update', $parameters);
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
if ($this->twitter->getLastHttpCode() == 200) { $result = $this->twitter->post('statuses/destroy/' . $result->id_str);
$result = $this->twitter->post('statuses/destroy/' . $result->id_str);
}
return $result; return $result;
} }
/**
* @vcr testPostStatusesUpdateUtf8.json
*/
public function testPostStatusesUpdateUtf8() public function testPostStatusesUpdateUtf8()
{ {
$result = $this->twitter->post('statuses/update', ['status' => 'xこんにちは世界 ' . time()]); $result = $this->twitter->post('statuses/update', [
'status' => 'xこんにちは世界 ' . MOCK_TIME,
]);
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
return $result; return $result;
} }
/** /**
* @depends testPostStatusesUpdateUtf8 * @depends testPostStatusesUpdateUtf8
* @vcr testPostStatusesDestroy.json
*/ */
public function testPostStatusesDestroy($status) public function testPostStatusesDestroy($status)
{ {
@ -235,16 +369,23 @@ class TwitterOAuthTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
} }
/**
* @vcr testLastResult.json
*/
public function testLastResult() public function testLastResult()
{ {
$this->twitter->get('search/tweets', ['q' => 'twitter']); $this->twitter->get('search/tweets', ['q' => 'twitter']);
$this->assertEquals('search/tweets', $this->twitter->getLastApiPath()); $this->assertEquals('search/tweets', $this->twitter->getLastApiPath());
$this->assertEquals(200, $this->twitter->getLastHttpCode()); $this->assertEquals(200, $this->twitter->getLastHttpCode());
$this->assertObjectHasAttribute('statuses', $this->twitter->getLastBody()); $this->assertObjectHasAttribute(
'statuses',
$this->twitter->getLastBody()
);
} }
/** /**
* @depends testLastResult * @depends testLastResult
* @vcr testResetLastResponse.json
*/ */
public function testResetLastResponse() public function testResetLastResponse()
{ {

View file

@ -2,9 +2,10 @@
namespace Abraham\TwitterOAuth\Tests; namespace Abraham\TwitterOAuth\Tests;
use PHPUnit\Framework\TestCase;
use Abraham\TwitterOAuth\Util\JsonDecoder; use Abraham\TwitterOAuth\Util\JsonDecoder;
class JsonDecoderTest extends \PHPUnit_Framework_TestCase class JsonDecoderTest extends TestCase
{ {
/** /**
* @dataProvider jsonProvider * @dataProvider jsonProvider
@ -19,7 +20,11 @@ class JsonDecoderTest extends \PHPUnit_Framework_TestCase
return [ return [
['[]', true, []], ['[]', true, []],
['[1,2,3]', true, [1, 2, 3]], ['[1,2,3]', true, [1, 2, 3]],
['[{"id": 556179961825226750}]', true, [['id' => 556179961825226750]]], [
'[{"id": 556179961825226750}]',
true,
[['id' => 556179961825226750]],
],
['[]', false, []], ['[]', false, []],
['[1,2,3]', false, [1, 2, 3]], ['[1,2,3]', false, [1, 2, 3]],
[ [
@ -29,10 +34,9 @@ class JsonDecoderTest extends \PHPUnit_Framework_TestCase
$this->getClass(function ($object) { $this->getClass(function ($object) {
$object->id = 556179961825226750; $object->id = 556179961825226750;
return $object; return $object;
}) }),
] ],
], ],
]; ];
} }

View file

@ -1,12 +1,10 @@
<?php <?php
require __DIR__ . '/../vendor/autoload.php'; declare(strict_types=1);
define('CONSUMER_KEY', getenv('TEST_CONSUMER_KEY')); require __DIR__ . '/../vendor/autoload.php';
define('CONSUMER_SECRET', getenv('TEST_CONSUMER_SECRET')); require 'vars.php';
define('ACCESS_TOKEN', getenv('TEST_ACCESS_TOKEN')); require 'mocks.php';
define('ACCESS_TOKEN_SECRET', getenv('TEST_ACCESS_TOKEN_SECRET'));
define('OAUTH_CALLBACK', getenv('TEST_OAUTH_CALLBACK')); \VCR\VCR::configure()->setStorage('json');
define('PROXY', getenv('TEST_CURLOPT_PROXY')); \VCR\VCR::turnOn();
define('PROXYUSERPWD', getenv('TEST_CURLOPT_PROXYUSERPWD'));
define('PROXYPORT', getenv('TEST_CURLOPT_PROXYPORT'));

View file

@ -0,0 +1,40 @@
[{
"request": {
"method": "DELETE",
"url": "https:\/\/api.twitter.com\/1.1\/direct_messages\/events\/destroy.json?id=1254206523385032714",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"dY4KEaTg5Y6Bv4JlofNCjoArx%2F4%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "204",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-security-policy": "default-src 'self'; connect-src 'self'; font-src 'self' https:\/\/*.twimg.com https:\/\/twitter.com https:\/\/ton.twitter.com data:; frame-src 'self' https:\/\/*.twimg.com https:\/\/twitter.com https:\/\/ton.twitter.com; img-src 'self' https:\/\/*.twimg.com https:\/\/twitter.com https:\/\/ton.twitter.com data:; media-src 'self' https:\/\/*.twimg.com https:\/\/twitter.com https:\/\/ton.twitter.com; object-src 'none'; script-src 'self' https:\/\/*.twimg.com https:\/\/twitter.com https:\/\/ton.twitter.com; style-src 'self' https:\/\/*.twimg.com https:\/\/twitter.com https:\/\/ton.twitter.com; report-uri https:\/\/twitter.com\/i\/csp_report?a=NVQWGYLXFVSG2%3D%3D%3D&ro=false;",
"content-type": "text\/html;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:52 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:52 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_asAbLVWtv6V2+ARemo0VNA==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:52 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786111220677678; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:52 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "204 No Content",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "5cbff6bc4105c0040c4738daac7adcf4",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "37",
"x-transaction": "00f02f6c00e12e76",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
}
}
}]

View file

@ -0,0 +1,46 @@
[{
"request": {
"method": "GET",
"url": "https:\/\/api.twitter.com\/1.1\/account\/verify_credentials.json?include_email=true&include_entities=false",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"k8h8edFh9R2W3DCNJy5Nb07tWo0%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "773",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:11 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:11 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_PPOKPD3f\/ek9QM3+ySQxjw==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:11 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786107181888587; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:11 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "7509696bc24b6d9d09d283b844a3c232",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-rate-limit-limit": "75",
"x-rate-limit-remaining": "73",
"x-rate-limit-reset": "1587861613",
"x-response-time": "46",
"x-transaction": "00cd4f6300163d67",
"x-twitter-response-tags": "BouncerExempt, BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"id\":93915746,\"id_str\":\"93915746\",\"name\":\"OAuth Library Test\",\"screen_name\":\"oauthlibtest\",\"location\":\"\",\"description\":\"\",\"url\":null,\"entities\":{\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":58,\"friends_count\":2,\"listed_count\":6,\"created_at\":\"Tue Dec 01 18:37:44 +0000 2009\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":false,\"statuses_count\":5,\"lang\":null,\"status\":{\"created_at\":\"Tue Dec 01 18:38:07 +0000 2009\",\"id\":6242973112,\"id_str\":\"6242973112\",\"text\":\"Test!\",\"truncated\":false,\"source\":\"\\u003ca href=\\\"http:\\\/\\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":2258,\"favorite_count\":75,\"favorited\":false,\"retweeted\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":true,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\",\"suspended\":false,\"needs_phone_verification\":false,\"email\":\"4braham+oauthlibtest@gmail.com\"}"
}
}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,46 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/oauth2\/token",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "Basic YXdKZk5ENHpGR2FwR09GS2Zkamc6TGZrbU5TUlBJWHdrUWtaVUI5RE5XU3p4NUxJYWl2U2tuVjRyeG5nb2pKYw==",
"Expect": null
},
"body": "grant_type=client_credentials"
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "152",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:09 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:09 GMT",
"ml": "A",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_NF00blSG9GZe8w8KpZvUDA==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:09 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, guest_id=v1%3A158786106988547101; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:09 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-connection-hash": "34e2373c53e7f9e0e80fe6af071dd6b8",
"x-content-type-options": "nosniff",
"x-frame-options": "DENY",
"x-response-time": "20",
"x-transaction": "007d4d19009f7a59",
"x-tsa-request-body-time": "0",
"x-twitter-response-tags": "BouncerCompliant",
"x-ua-compatible": "IE=edge,chrome=1",
"x-xss-protection": "0"
},
"body": "{\"token_type\":\"bearer\",\"access_token\":\"AAAAAAAAAAAAAAAAAAAAAFobAAAAAAAAjPes3FlPiFKh9HaIg%2Fw80waE0s8%3DQqxjhHDgZyjihGIK7olugzbpS0R1Gg8KNhzmer58a6oVbsSGc0\"}"
}
}]

View file

@ -0,0 +1,46 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/oauth2\/invalidate_token",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "Basic YXdKZk5ENHpGR2FwR09GS2Zkamc6TGZrbU5TUlBJWHdrUWtaVUI5RE5XU3p4NUxJYWl2U2tuVjRyeG5nb2pKYw==",
"Expect": null
},
"body": "access_token=AAAAAAAAAAAAAAAAAAAAAFobAAAAAAAAjPes3FlPiFKh9HaIg%2Fw80waE0s8%3DQqxjhHDgZyjihGIK7olugzbpS0R1Gg8KNhzmer58a6oVbsSGc0"
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "135",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:10 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:10 GMT",
"ml": "A",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_8Iv+DqoXk8DVAVDoUVltSA==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:10 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, guest_id=v1%3A158786107054950627; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:10 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-connection-hash": "18b7327592f746230c1c016c344dd14d",
"x-content-type-options": "nosniff",
"x-frame-options": "DENY",
"x-response-time": "19",
"x-transaction": "00c5257f00b7d371",
"x-tsa-request-body-time": "0",
"x-twitter-response-tags": "BouncerCompliant",
"x-ua-compatible": "IE=edge,chrome=1",
"x-xss-protection": "0"
},
"body": "{\"access_token\":\"AAAAAAAAAAAAAAAAAAAAAFobAAAAAAAAjPes3FlPiFKh9HaIg%2Fw80waE0s8%3DQqxjhHDgZyjihGIK7olugzbpS0R1Gg8KNhzmer58a6oVbsSGc0\"}"
}
}]

View file

@ -0,0 +1,45 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/oauth\/access_token",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"CE545gAAAAAAABtaAAABcbPlJBQ\", oauth_verifier=\"fake_oauth_verifier\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"0bcdtKs3nffzbE5abwaVjCI1HPw%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "401",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-encoding": "gzip",
"content-length": "93",
"content-security-policy": "default-src 'none'; connect-src 'self'; font-src https:\/\/abs.twimg.com https:\/\/abs-0.twimg.com data:; frame-src 'self' twitter:; img-src https:\/\/abs.twimg.com https:\/\/*.twimg.com https:\/\/pbs.twimg.com data:; media-src 'none'; object-src 'none'; script-src https:\/\/abs.twimg.com https:\/\/abs-0.twimg.com https:\/\/twitter.com https:\/\/mobile.twitter.com; style-src https:\/\/abs.twimg.com https:\/\/abs-0.twimg.com; report-uri https:\/\/twitter.com\/i\/csp_report?a=NVQWGYLXFVWG6Z3JNY%3D%3D%3D%3D%3D%3D&ro=false;",
"content-type": "text\/html;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:11 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:11 GMT",
"ml": "A",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_n0ZAgT2oLIc0HI23qMIGCA==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:11 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, guest_id=v1%3A158786107147893563; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:11 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "401 Unauthorized",
"strict-transport-security": "max-age=631138519",
"www-authenticate": "OAuth realm=\"https:\/\/api.twitter.com\"",
"x-connection-hash": "90157d4bdfce3a9b90fd408819c767bc",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "41",
"x-transaction": "0080cead006a758d",
"x-twitter-response-tags": "BouncerCompliant",
"x-ua-compatible": "IE=edge,chrome=1",
"x-xss-protection": "0"
},
"body": "Error processing your OAuth request: Invalid oauth_verifier parameter"
}
}]

View file

@ -0,0 +1,44 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/oauth\/request_token",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_callback=\"https%3A%2F%2Ftwitteroauth.com%2Fcallback.php\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"LR7ZVqY%2Fcdisw1w3zssKI6Yjbls%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-encoding": "gzip",
"content-length": "127",
"content-security-policy": "default-src 'none'; connect-src 'self'; font-src https:\/\/abs.twimg.com https:\/\/abs-0.twimg.com data:; frame-src 'self' twitter:; img-src https:\/\/abs.twimg.com https:\/\/*.twimg.com https:\/\/pbs.twimg.com data:; media-src 'none'; object-src 'none'; script-src https:\/\/abs.twimg.com https:\/\/abs-0.twimg.com https:\/\/twitter.com https:\/\/mobile.twitter.com; style-src https:\/\/abs.twimg.com https:\/\/abs-0.twimg.com; report-uri https:\/\/twitter.com\/i\/csp_report?a=NVQWGYLXFVWG6Z3JNY%3D%3D%3D%3D%3D%3D&ro=false;",
"content-type": "text\/html;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:10 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:10 GMT",
"ml": "A",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_mrnWVDThJvkLcAe4hmX0ng==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:10 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, guest_id=v1%3A158786107085601318; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:10 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-connection-hash": "bf00d267c647790cd34d8cd4a28f9895",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "24",
"x-transaction": "0095391f006dd965",
"x-twitter-response-tags": "BouncerCompliant",
"x-ua-compatible": "IE=edge,chrome=1",
"x-xss-protection": "0"
},
"body": "oauth_token=CE545gAAAAAAABtaAAABcbPlJBQ&oauth_token_secret=tTVYBva8AlQu0JxVudzbf9oHXAbIARg5&oauth_callback_confirmed=true"
}
}]

View file

@ -0,0 +1,43 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/oauth\/request_token",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"CONSUMER_KEY\", oauth_callback=\"https%3A%2F%2Ftwitteroauth.com%2Fcallback.php\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"wOUt6ZyVGpWnQhsHNWqcr%2BpOWAw%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "401",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "89",
"content-type": "application\/json; charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:11 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:11 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_Vz8Os736+fzUwkQGIeIKuw==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:11 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, guest_id=v1%3A158786107116335546; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:11 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "401 Unauthorized",
"strict-transport-security": "max-age=631138519",
"www-authenticate": "OAuth realm=\"https:\/\/api.twitter.com\", api_error_code=32",
"x-connection-hash": "d620dbb5b35e124662532c3ef8e89c88",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "6",
"x-transaction": "00bf1248004cdafa",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}"
}
}]

View file

@ -0,0 +1,46 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/1.1\/direct_messages\/events\/new.json",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"3457NqeumGmcalZLF091L9lt7F8%3D\"",
"Expect": null,
"Content-type": "application\/json"
},
"body": "{\"event\":{\"type\":\"message_create\",\"message_create\":{\"target\":{\"recipient_id\":\"93915746\"},\"message_data\":{\"text\":\"Hello World!\"}}}}"
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "206",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:51 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:51 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_Tfqxs0gur2QR4FFIZ3Wq6w==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:51 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786111185015666; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:51 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "bb4f30d1c6406b2cd5d25f20fccfdc1a",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "70",
"x-transaction": "0057fa4c00fb95a1",
"x-tsa-request-body-time": "0",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"event\":{\"type\":\"message_create\",\"id\":\"1254206523385032714\",\"created_timestamp\":\"1587861111862\",\"message_create\":{\"target\":{\"recipient_id\":\"93915746\"},\"sender_id\":\"93915746\",\"message_data\":{\"text\":\"Hello World!\",\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[]}}}}}"
}
}]

View file

@ -0,0 +1,45 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/1.1\/favorites\/create.json",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"EA30eIQPgat0Aw%2F59GyltEiE4Xg%3D\"",
"Expect": null
},
"body": "id=6242973112"
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "755",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:51 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:51 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_Jz87HIDSEIpDevFMBlDD7g==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:51 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786111115490266; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:51 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "7368af4d238e5c36df5379afb1bed3af",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "72",
"x-transaction": "0012beac0086638b",
"x-tsa-request-body-time": "0",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"created_at\":\"Tue Dec 01 18:38:07 +0000 2009\",\"id\":6242973112,\"id_str\":\"6242973112\",\"text\":\"Test!\",\"truncated\":false,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[]},\"source\":\"\\u003ca href=\\\"http:\\\/\\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"user\":{\"id\":93915746,\"id_str\":\"93915746\",\"name\":\"OAuth Library Test\",\"screen_name\":\"oauthlibtest\",\"location\":\"\",\"description\":\"\",\"url\":null,\"entities\":{\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":58,\"friends_count\":2,\"listed_count\":6,\"created_at\":\"Tue Dec 01 18:37:44 +0000 2009\",\"favourites_count\":1,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":false,\"statuses_count\":5,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":true,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"},\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":2258,\"favorite_count\":76,\"favorited\":true,\"retweeted\":false,\"lang\":\"en\"}"
}
}]

View file

@ -0,0 +1,45 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/1.1\/favorites\/destroy.json",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"w3Nti04O5BMi8bySXjmO8%2BW5Pus%3D\"",
"Expect": null
},
"body": "id=6242973112"
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "753",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:51 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:51 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_s1J1pMUNrQO4\/v371oE9AQ==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:51 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786111151392082; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:51 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "a0dc865f09447e41b0d77e9eed981519",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "44",
"x-transaction": "005d9083009bd4c9",
"x-tsa-request-body-time": "0",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"created_at\":\"Tue Dec 01 18:38:07 +0000 2009\",\"id\":6242973112,\"id_str\":\"6242973112\",\"text\":\"Test!\",\"truncated\":false,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[]},\"source\":\"\\u003ca href=\\\"http:\\\/\\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"user\":{\"id\":93915746,\"id_str\":\"93915746\",\"name\":\"OAuth Library Test\",\"screen_name\":\"oauthlibtest\",\"location\":\"\",\"description\":\"\",\"url\":null,\"entities\":{\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":58,\"friends_count\":2,\"listed_count\":6,\"created_at\":\"Tue Dec 01 18:37:44 +0000 2009\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":false,\"statuses_count\":5,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":true,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"},\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":2258,\"favorite_count\":75,\"favorited\":false,\"retweeted\":false,\"lang\":\"en\"}"
}
}]

View file

@ -0,0 +1,43 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/1.1\/statuses\/destroy\/1254206657548226561.json",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"kyOKi3x9Ar3foSG5%2BN9XzBbnIOw%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "804",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:32:24 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:32:24 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_juPKvfSeQeQoZAVeLglnhA==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:32:24 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786114418847477; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:32:24 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "f4375157b19d6cd139b9917a6d76d0b0",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "198",
"x-transaction": "00f3e731001ccb87",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"created_at\":\"Sun Apr 26 00:32:23 +0000 2020\",\"id\":1254206657548226561,\"id_str\":\"1254206657548226561\",\"text\":\"x\\u3053\\u3093\\u306b\\u3061\\u306f\\u4e16\\u754c 1587861062\",\"truncated\":false,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[]},\"source\":\"\\u003ca href=\\\"https:\\\/\\\/twitteroauth.com\\\" rel=\\\"nofollow\\\"\\u003eTwitterOAuth dev\\u003c\\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"user\":{\"id\":93915746,\"id_str\":\"93915746\",\"name\":\"OAuth Library Test\",\"screen_name\":\"oauthlibtest\",\"location\":\"\",\"description\":\"\",\"url\":null,\"entities\":{\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":58,\"friends_count\":2,\"listed_count\":6,\"created_at\":\"Tue Dec 01 18:37:44 +0000 2009\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":false,\"statuses_count\":6,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":true,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"},\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":0,\"favorite_count\":0,\"favorited\":false,\"retweeted\":false,\"lang\":\"ja\"}"
}
}]

View file

@ -0,0 +1,45 @@
[{
"request": {
"method": "POST",
"url": "https:\/\/api.twitter.com\/1.1\/statuses\/update.json",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"zIzkM9jxroYElpL1fPTyYnYE%2Bys%3D\"",
"Expect": null
},
"body": "status=x%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E4%B8%96%E7%95%8C%201587861062"
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "804",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:32:23 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:32:23 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_8nFfK\/V8KyJDl1aminWCQw==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:32:23 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786114384224672; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:32:23 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "54c0be65e0c80b57d5b7c895e58061c8",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-response-time": "55",
"x-transaction": "00eb7dbc0057ef33",
"x-tsa-request-body-time": "0",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"created_at\":\"Sun Apr 26 00:32:23 +0000 2020\",\"id\":1254206657548226561,\"id_str\":\"1254206657548226561\",\"text\":\"x\\u3053\\u3093\\u306b\\u3061\\u306f\\u4e16\\u754c 1587861062\",\"truncated\":false,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[]},\"source\":\"\\u003ca href=\\\"https:\\\/\\\/twitteroauth.com\\\" rel=\\\"nofollow\\\"\\u003eTwitterOAuth dev\\u003c\\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"user\":{\"id\":93915746,\"id_str\":\"93915746\",\"name\":\"OAuth Library Test\",\"screen_name\":\"oauthlibtest\",\"location\":\"\",\"description\":\"\",\"url\":null,\"entities\":{\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":58,\"friends_count\":2,\"listed_count\":6,\"created_at\":\"Tue Dec 01 18:37:44 +0000 2009\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":false,\"statuses_count\":6,\"lang\":null,\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":true,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\"},\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":0,\"favorite_count\":0,\"favorited\":false,\"retweeted\":false,\"lang\":\"ja\"}"
}
}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,46 @@
[{
"request": {
"method": "GET",
"url": "https:\/\/api.twitter.com\/1.1\/friendships\/show.json?target_screen_name=twitterapi",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"TzPCDLbvxIAlxBqg5Fpf4JZpFJo%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "2",
"code": "200",
"message": ""
},
"headers": {
"cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
"content-disposition": "attachment; filename=json.json",
"content-encoding": "gzip",
"content-length": "246",
"content-type": "application\/json;charset=utf-8",
"date": "Sun, 26 Apr 2020 00:31:09 GMT",
"expires": "Tue, 31 Mar 1981 05:00:00 GMT",
"last-modified": "Sun, 26 Apr 2020 00:31:09 GMT",
"pragma": "no-cache",
"server": "tsa_b",
"set-cookie": "personalization_id=\"v1_1Yr9ogG1fxy1wdDOY63jAw==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:09 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None, lang=en; Path=\/, guest_id=v1%3A158786106956820884; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:09 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None",
"status": "200 OK",
"strict-transport-security": "max-age=631138519",
"x-access-level": "read-write-directmessages",
"x-connection-hash": "e8b1e309982b5c1d1adedc814fa2e6c3",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-rate-limit-limit": "180",
"x-rate-limit-remaining": "178",
"x-rate-limit-reset": "1587861610",
"x-response-time": "20",
"x-transaction": "0075ffd2008ff583",
"x-twitter-response-tags": "BouncerCompliant",
"x-xss-protection": "0"
},
"body": "{\"relationship\":{\"source\":{\"id\":93915746,\"id_str\":\"93915746\",\"screen_name\":\"oauthlibtest\",\"following\":false,\"followed_by\":false,\"live_following\":false,\"following_received\":false,\"following_requested\":false,\"notifications_enabled\":false,\"can_dm\":false,\"blocking\":false,\"blocked_by\":false,\"muting\":false,\"want_retweets\":false,\"all_replies\":false,\"marked_spam\":false},\"target\":{\"id\":6253282,\"id_str\":\"6253282\",\"screen_name\":\"TwitterAPI\",\"following\":false,\"followed_by\":false,\"following_received\":false,\"following_requested\":false}}}"
}
}]

View file

@ -0,0 +1,20 @@
[{
"request": {
"method": "GET",
"url": "https:\/\/api.twitter.com\/1.1\/account\/verify_credentials.json",
"headers": {
"Host": "api.twitter.com",
"Accept": "application\/json",
"Authorization": "OAuth oauth_version=\"1.0\", oauth_nonce=\"2b67ebbeace76543f356ba8bbd59abde\", oauth_timestamp=\"1587861062\", oauth_consumer_key=\"awJfND4zFGapGOFKfdjg\", oauth_token=\"93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"ZN1bM0df5EPRy1d7oYcoZfj3Mpw%3D\"",
"Expect": null
}
},
"response": {
"status": {
"http_version": "1.1",
"code": "200",
"message": "OK"
},
"body": "HTTP\/2 200 \r\ncache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0\r\ncontent-disposition: attachment; filename=json.json\r\ncontent-encoding: gzip\r\ncontent-length: 778\r\ncontent-type: application\/json;charset=utf-8\r\ndate: Sun, 26 Apr 2020 00:31:49 GMT\r\nexpires: Tue, 31 Mar 1981 05:00:00 GMT\r\nlast-modified: Sun, 26 Apr 2020 00:31:49 GMT\r\npragma: no-cache\r\nserver: tsa_a\r\nset-cookie: personalization_id=\"v1_hI7rl+lJjoy5n7HgyNo9LQ==\"; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:49 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None\r\nset-cookie: lang=en; Path=\/\r\nset-cookie: guest_id=v1%3A158786110929931313; Max-Age=63072000; Expires=Tue, 26 Apr 2022 00:31:49 GMT; Path=\/; Domain=.twitter.com; Secure; SameSite=None\r\nstatus: 200 OK\r\nstrict-transport-security: max-age=631138519\r\nx-access-level: read-write-directmessages\r\nx-connection-hash: 18e8b1b5df2ee964aebf8f85055ada9c\r\nx-content-type-options: nosniff\r\nx-frame-options: SAMEORIGIN\r\nx-rate-limit-limit: 75\r\nx-rate-limit-remaining: 72\r\nx-rate-limit-reset: 1587861178\r\nx-response-time: 34\r\nx-transaction: 00716cfd00f6fd8d\r\nx-twitter-response-tags: BouncerExempt\r\nx-twitter-response-tags: BouncerCompliant\r\nx-xss-protection: 0\r\n\r\n{\"id\":93915746,\"id_str\":\"93915746\",\"name\":\"OAuth Library Test\",\"screen_name\":\"oauthlibtest\",\"location\":\"\",\"description\":\"\",\"url\":null,\"entities\":{\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":58,\"friends_count\":2,\"listed_count\":6,\"created_at\":\"Tue Dec 01 18:37:44 +0000 2009\",\"favourites_count\":0,\"utc_offset\":null,\"time_zone\":null,\"geo_enabled\":true,\"verified\":false,\"statuses_count\":1,\"lang\":null,\"status\":{\"created_at\":\"Tue Dec 01 18:38:07 +0000 2009\",\"id\":6242973112,\"id_str\":\"6242973112\",\"text\":\"Test!\",\"truncated\":false,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[]},\"source\":\"\\u003ca href=\\\"http:\\\/\\\/twitter.com\\\" rel=\\\"nofollow\\\"\\u003eTwitter Web Client\\u003c\\\/a\\u003e\",\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"is_quote_status\":false,\"retweet_count\":2258,\"favorite_count\":74,\"favorited\":false,\"retweeted\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/images\\\/themes\\\/theme1\\\/bg.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_image_url_https\":\"https:\\\/\\\/abs.twimg.com\\\/sticky\\\/default_profile_images\\\/default_profile_normal.png\",\"profile_link_color\":\"1DA1F2\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":true,\"default_profile_image\":true,\"following\":false,\"follow_request_sent\":false,\"notifications\":false,\"translator_type\":\"none\",\"suspended\":false,\"needs_phone_verification\":false}"
}
}]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Abraham\TwitterOAuth;
// Mock time and random values for consistent tests with VCR
function time()
{
return MOCK_TIME;
}
function microtime()
{
return 'FAKE_MICROTIME';
}
function mt_rand()
{
return 123456789;
}

View file

@ -1,13 +0,0 @@
# WARNING: Running the tests will perform live actions as the Twitter account.
# Set all values, move to `env`, run `source tests/env` and `phpunit` to start testing.
# To run the tests you must register Twitter application at https://app.twitter.com/.
export TEST_CONSUMER_KEY=
export TEST_CONSUMER_SECRET=
export TEST_ACCESS_TOKEN=
export TEST_ACCESS_TOKEN_SECRET=
export TEST_OAUTH_CALLBACK=
# You can find proxies for testing at http://proxylist.hidemyass.com/.
export TEST_CURLOPT_PROXY=
export TEST_CURLOPT_PROXYUSERPWD=
export TEST_CURLOPT_PROXYPORT=

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
// These keys have been revoked and are only valid for teh VCR cassettes.
// To request new VCR cassettes please open an issue: https://github.com/abraham/twitteroauth/issues
// To update VCR cassettes
// 1. Delete all `tests/fixtures/*` files
// `rm tests/fixtures/*`
// 2. Set application and suer authorization credentials below
// 3. Set MOCK_TIME to current unix time
// 4. Run PHPUnit tests
// 5. Reset application credentials on Twitter dashboard
// 6. Commit new cassettes and revoked credentials
// TwitterOAuth dev
define('CONSUMER_KEY', 'awJfND4zFGapGOFKfdjg');
define('CONSUMER_SECRET', 'LfkmNSRPIXwkQkZUB9DNWSzx5LIaivSknV4rxngojJc');
define('OAUTH_CALLBACK', 'https://twitteroauth.com/callback.php');
// oauthlibtest
define('ACCESS_TOKEN', '93915746-KjE3c27dCt8awONxuUAaJ00yishXXwcH5CdLBnO1x');
define('ACCESS_TOKEN_SECRET', 'vurSbgJw6nHvv7xBfqKnBLWEQekOi59KFkXDLiY3Vqn3u');
// Timestamp the VCR cassettes were last updated
define('MOCK_TIME', 1587861062);
// https://free-proxy-list.net/
define('PROXY', '12.218.209.130');
define('PROXYUSERPWD', '');
define('PROXYPORT', '53281');

View file

@ -60,7 +60,7 @@ class ClassLoader
public function getPrefixes() public function getPrefixes()
{ {
if (!empty($this->prefixesPsr0)) { if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0); return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
} }
return array(); return array();
@ -279,7 +279,7 @@ class ClassLoader
*/ */
public function setApcuPrefix($apcuPrefix) public function setApcuPrefix($apcuPrefix)
{ {
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
} }
/** /**

View file

@ -7,5 +7,6 @@ $baseDir = dirname($vendorDir);
return array( return array(
'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'), 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
'Abraham\\TwitterOAuth\\' => array($vendorDir . '/abraham/twitteroauth/src'), 'Abraham\\TwitterOAuth\\' => array($vendorDir . '/abraham/twitteroauth/src'),
); );

View file

@ -13,6 +13,9 @@ class ComposerAutoloaderInitTwitterAddon
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View file

@ -10,6 +10,7 @@ class ComposerStaticInitTwitterAddon
'C' => 'C' =>
array ( array (
'Composer\\Installers\\' => 20, 'Composer\\Installers\\' => 20,
'Composer\\CaBundle\\' => 18,
), ),
'A' => 'A' =>
array ( array (
@ -22,6 +23,10 @@ class ComposerStaticInitTwitterAddon
array ( array (
0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers', 0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers',
), ),
'Composer\\CaBundle\\' =>
array (
0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
),
'Abraham\\TwitterOAuth\\' => 'Abraham\\TwitterOAuth\\' =>
array ( array (
0 => __DIR__ . '/..' . '/abraham/twitteroauth/src', 0 => __DIR__ . '/..' . '/abraham/twitteroauth/src',

View file

@ -0,0 +1,19 @@
Copyright (C) 2016 Composer
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,85 @@
composer/ca-bundle
==================
Small utility library that lets you find a path to the system CA bundle,
and includes a fallback to the Mozilla CA bundle.
Originally written as part of [composer/composer](https://github.com/composer/composer),
now extracted and made available as a stand-alone library.
Installation
------------
Install the latest version with:
```bash
$ composer require composer/ca-bundle
```
Requirements
------------
* PHP 5.3.2 is required but using the latest version of PHP is highly recommended.
Basic usage
-----------
### `Composer\CaBundle\CaBundle`
- `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback
- `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file
- `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use
- `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse()
- `CaBundle::reset()`: Resets the static caches
#### To use with curl
```php
$curl = curl_init("https://example.org/");
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile);
} else {
curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile);
}
$result = curl_exec($curl);
```
#### To use with php streams
```php
$opts = array(
'http' => array(
'method' => "GET"
)
);
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
$opts['ssl']['capath'] = $caPathOrFile;
} else {
$opts['ssl']['cafile'] = $caPathOrFile;
}
$context = stream_context_create($opts);
$result = file_get_contents('https://example.com', false, $context);
```
#### To use with Guzzle
```php
$client = new \GuzzleHttp\Client([
\GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath()
]);
```
License
-------
composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details.

View file

@ -0,0 +1,54 @@
{
"name": "composer/ca-bundle",
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"type": "library",
"license": "MIT",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues"
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.2 || ^5",
"phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Composer\\CaBundle\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"scripts": {
"test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
"phpstan": "vendor/bin/phpstan analyse"
}
}

View file

@ -0,0 +1,353 @@
<?php
/*
* This file is part of composer/ca-bundle.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\CaBundle;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;
/**
* @author Chris Smith <chris@cs278.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class CaBundle
{
/** @var string|null */
private static $caPath;
/** @var array<string, bool> */
private static $caFileValidity = array();
/** @var bool|null */
private static $useOpensslParse;
/**
* Returns the system CA bundle path, or a path to the bundled one
*
* This method was adapted from Sslurp.
* https://github.com/EvanDotPro/Sslurp
*
* (c) Evan Coury <me@evancoury.com>
*
* For the full copyright and license information, please see below:
*
* Copyright (c) 2013, Evan Coury
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
* @return string path to a CA bundle file or directory
*/
public static function getSystemCaRootBundlePath(LoggerInterface $logger = null)
{
if (self::$caPath !== null) {
return self::$caPath;
}
$caBundlePaths = array();
// If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');
// If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');
$caBundlePaths[] = ini_get('openssl.cafile');
$caBundlePaths[] = ini_get('openssl.capath');
$otherLocations = array(
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
'/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
'/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
'/usr/ssl/certs/ca-bundle.crt', // Cygwin
'/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
'/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
'/etc/ssl/cert.pem', // OpenBSD
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
'/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
'/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
);
foreach($otherLocations as $location) {
$otherLocations[] = dirname($location);
}
$caBundlePaths = array_merge($caBundlePaths, $otherLocations);
foreach ($caBundlePaths as $caBundle) {
if ($caBundle && self::caFileUsable($caBundle, $logger)) {
return self::$caPath = $caBundle;
}
if ($caBundle && self::caDirUsable($caBundle)) {
return self::$caPath = $caBundle;
}
}
return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
}
/**
* Returns the path to the bundled CA file
*
* In case you don't want to trust the user or the system, you can use this directly
*
* @return string path to a CA bundle file
*/
public static function getBundledCaBundlePath()
{
$caBundleFile = __DIR__.'/../res/cacert.pem';
// cURL does not understand 'phar://' paths
// see https://github.com/composer/ca-bundle/issues/10
if (0 === strpos($caBundleFile, 'phar://')) {
$tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
if (false === $tempCaBundleFile) {
throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
}
file_put_contents(
$tempCaBundleFile,
file_get_contents($caBundleFile)
);
register_shutdown_function(function() use ($tempCaBundleFile) {
@unlink($tempCaBundleFile);
});
$caBundleFile = $tempCaBundleFile;
}
return $caBundleFile;
}
/**
* Validates a CA file using opensl_x509_parse only if it is safe to use
*
* @param string $filename
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
*
* @return bool
*/
public static function validateCaFile($filename, LoggerInterface $logger = null)
{
static $warned = false;
if (isset(self::$caFileValidity[$filename])) {
return self::$caFileValidity[$filename];
}
$contents = file_get_contents($filename);
// assume the CA is valid if php is vulnerable to
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
if (!static::isOpensslParseSafe()) {
if (!$warned && $logger) {
$logger->warning(sprintf(
'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.',
PHP_VERSION
));
$warned = true;
}
$isValid = !empty($contents);
} elseif (is_string($contents) && strlen($contents) > 0) {
$contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
if (null === $contents) {
// regex extraction failed
$isValid = false;
} else {
$isValid = (bool) openssl_x509_parse($contents);
}
} else {
$isValid = false;
}
if ($logger) {
$logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
}
return self::$caFileValidity[$filename] = $isValid;
}
/**
* Test if it is safe to use the PHP function openssl_x509_parse().
*
* This checks if OpenSSL extensions is vulnerable to remote code execution
* via the exploit documented as CVE-2013-6420.
*
* @return bool
*/
public static function isOpensslParseSafe()
{
if (null !== self::$useOpensslParse) {
return self::$useOpensslParse;
}
if (PHP_VERSION_ID >= 50600) {
return self::$useOpensslParse = true;
}
// Vulnerable:
// PHP 5.3.0 - PHP 5.3.27
// PHP 5.4.0 - PHP 5.4.22
// PHP 5.5.0 - PHP 5.5.6
if (
(PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328)
|| (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423)
|| PHP_VERSION_ID >= 50507
) {
// This version of PHP has the fix for CVE-2013-6420 applied.
return self::$useOpensslParse = true;
}
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
// Windows is probably insecure in this case.
return self::$useOpensslParse = false;
}
$compareDistroVersionPrefix = function ($prefix, $fixedVersion) {
$regex = '{^'.preg_quote($prefix).'([0-9]+)$}';
if (preg_match($regex, PHP_VERSION, $m)) {
return ((int) $m[1]) >= $fixedVersion;
}
return false;
};
// Hard coded list of PHP distributions with the fix backported.
if (
$compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze)
|| $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy)
|| $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise)
) {
return self::$useOpensslParse = true;
}
// Symfony Process component is missing so we assume it is unsafe at this point
if (!class_exists('Symfony\Component\Process\PhpProcess')) {
return self::$useOpensslParse = false;
}
// This is where things get crazy, because distros backport security
// fixes the chances are on NIX systems the fix has been applied but
// it's not possible to verify that from the PHP version.
//
// To verify exec a new PHP process and run the issue testcase with
// known safe input that replicates the bug.
// Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
// changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
$cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
$script = <<<'EOT'
error_reporting(-1);
$info = openssl_x509_parse(base64_decode('%s'));
var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);
EOT;
$script = '<'."?php\n".sprintf($script, $cert);
try {
$process = new PhpProcess($script);
$process->mustRun();
} catch (\Exception $e) {
// In the case of any exceptions just accept it is not possible to
// determine the safety of openssl_x509_parse and bail out.
return self::$useOpensslParse = false;
}
$output = preg_split('{\r?\n}', trim($process->getOutput()));
$errorOutput = trim($process->getErrorOutput());
if (
is_array($output)
&& count($output) === 3
&& $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
&& $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
&& $output[2] === 'int(-1)'
&& preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput)
) {
// This PHP has the fix backported probably by a distro security team.
return self::$useOpensslParse = true;
}
return self::$useOpensslParse = false;
}
/**
* Resets the static caches
* @return void
*/
public static function reset()
{
self::$caFileValidity = array();
self::$caPath = null;
self::$useOpensslParse = null;
}
/**
* @param string $name
* @return string|false
*/
private static function getEnvVariable($name)
{
if (isset($_SERVER[$name])) {
return (string) $_SERVER[$name];
}
if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
return (string) $value;
}
return false;
}
/**
* @param string|false $certFile
* @return bool
*/
private static function caFileUsable($certFile, LoggerInterface $logger = null)
{
return $certFile && @is_file($certFile) && @is_readable($certFile) && static::validateCaFile($certFile, $logger);
}
/**
* @param string|false $certDir
* @return bool
*/
private static function caDirUsable($certDir)
{
return $certDir && @is_dir($certDir) && @is_readable($certDir) && glob($certDir . '/*');
}
}

View file

@ -1,29 +1,32 @@
[ [
{ {
"name": "abraham/twitteroauth", "name": "abraham/twitteroauth",
"version": "0.7.4", "version": "2.0.0",
"version_normalized": "0.7.4.0", "version_normalized": "2.0.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/abraham/twitteroauth.git", "url": "https://github.com/abraham/twitteroauth.git",
"reference": "c6f9e692552dd037b2324ed0dfa28a4e60875acf" "reference": "96f49e67baec10f5e5cb703d87be16ba01a798a5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/abraham/twitteroauth/zipball/c6f9e692552dd037b2324ed0dfa28a4e60875acf", "url": "https://api.github.com/repos/abraham/twitteroauth/zipball/96f49e67baec10f5e5cb703d87be16ba01a798a5",
"reference": "c6f9e692552dd037b2324ed0dfa28a4e60875acf", "reference": "96f49e67baec10f5e5cb703d87be16ba01a798a5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/ca-bundle": "^1.2",
"ext-curl": "*", "ext-curl": "*",
"php": "^5.6 || ^7.0" "php": "^7.2 || ^7.3 || ^7.4 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpmd/phpmd": "~2.6", "php-vcr/php-vcr": "^1",
"phpunit/phpunit": "~5.7", "php-vcr/phpunit-testlistener-vcr": "dev-php-8",
"squizlabs/php_codesniffer": "~3.0" "phpmd/phpmd": "^2",
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "^3"
}, },
"time": "2017-06-30T22:02:01+00:00", "time": "2020-12-02T01:27:06+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -55,6 +58,79 @@
"twitter" "twitter"
] ]
}, },
{
"name": "composer/ca-bundle",
"version": "1.2.10",
"version_normalized": "1.2.10.0",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "9fdb22c2e97a614657716178093cd1da90a64aa8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/9fdb22c2e97a614657716178093cd1da90a64aa8",
"reference": "9fdb22c2e97a614657716178093cd1da90a64aa8",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0",
"symfony/phpunit-bridge": "^4.2 || ^5",
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
},
"time": "2021-06-07T13:58:28+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
]
},
{ {
"name": "composer/installers", "name": "composer/installers",
"version": "v1.6.0", "version": "v1.6.0",
@ -183,12 +259,12 @@
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/jublonet/codebird-php.git", "url": "https://github.com/jublo/codebird-php.git",
"reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368" "reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/jublonet/codebird-php/zipball/df362d8ad629aad6c4c7dbf36a440e569ec41368", "url": "https://api.github.com/repos/jublo/codebird-php/zipball/df362d8ad629aad6c4c7dbf36a440e569ec41368",
"reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368", "reference": "df362d8ad629aad6c4c7dbf36a440e569ec41368",
"shasum": "" "shasum": ""
}, },

View file

@ -0,0 +1,19 @@
Copyright (c) 2012 Kyle Robinson Young
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,105 @@
{
"name": "composer/installers",
"type": "composer-plugin",
"license": "MIT",
"description": "A multi-framework Composer library installer",
"keywords": [
"installer",
"Aimeos",
"AGL",
"AnnotateCms",
"Attogram",
"Bitrix",
"CakePHP",
"Chef",
"Cockpit",
"CodeIgniter",
"concrete5",
"Craft",
"Croogo",
"DokuWiki",
"Dolibarr",
"Drupal",
"Elgg",
"Eliasis",
"ExpressionEngine",
"eZ Platform",
"FuelPHP",
"Grav",
"Hurad",
"ImageCMS",
"iTop",
"Joomla",
"Kanboard",
"Kohana",
"Lan Management System",
"Laravel",
"Lavalite",
"Lithium",
"Magento",
"majima",
"Mako",
"Mautic",
"Maya",
"MODX",
"MODX Evo",
"MediaWiki",
"OXID",
"osclass",
"MODULEWork",
"Moodle",
"Piwik",
"pxcms",
"phpBB",
"Plentymarkets",
"PPI",
"Puppet",
"Porto",
"RadPHP",
"ReIndex",
"Roundcube",
"shopware",
"SilverStripe",
"SMF",
"SyDES",
"symfony",
"Thelia",
"TYPO3",
"WolfCMS",
"WordPress",
"YAWIK",
"Zend",
"Zikula"
],
"homepage": "https://composer.github.io/installers/",
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"autoload": {
"psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
},
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"replace": {
"shama/baton": "*",
"roundcube/plugin-installer": "*"
},
"require": {
"composer-plugin-api": "^1.0"
},
"require-dev": {
"composer/composer": "1.0.*@dev",
"phpunit/phpunit": "^4.8.36"
},
"scripts": {
"test": "phpunit"
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Composer\Installers;
class AglInstaller extends BaseInstaller
{
protected $locations = array(
'module' => 'More/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars($vars)
{
$vars['name'] = preg_replace_callback('/(?:^|_|-)(.?)/', function ($matches) {
return strtoupper($matches[1]);
}, $vars['name']);
return $vars;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Composer\Installers;
class AimeosInstaller extends BaseInstaller
{
protected $locations = array(
'extension' => 'ext/{$name}/',
);
}

View file

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class AnnotateCmsInstaller extends BaseInstaller
{
protected $locations = array(
'module' => 'addons/modules/{$name}/',
'component' => 'addons/components/{$name}/',
'service' => 'addons/services/{$name}/',
);
}

View file

@ -0,0 +1,49 @@
<?php
namespace Composer\Installers;
class AsgardInstaller extends BaseInstaller
{
protected $locations = array(
'module' => 'Modules/{$name}/',
'theme' => 'Themes/{$name}/'
);
/**
* Format package name.
*
* For package type asgard-module, cut off a trailing '-plugin' if present.
*
* For package type asgard-theme, cut off a trailing '-theme' if present.
*
*/
public function inflectPackageVars($vars)
{
if ($vars['type'] === 'asgard-module') {
return $this->inflectPluginVars($vars);
}
if ($vars['type'] === 'asgard-theme') {
return $this->inflectThemeVars($vars);
}
return $vars;
}
protected function inflectPluginVars($vars)
{
$vars['name'] = preg_replace('/-module$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
protected function inflectThemeVars($vars)
{
$vars['name'] = preg_replace('/-theme$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Composer\Installers;
class AttogramInstaller extends BaseInstaller
{
protected $locations = array(
'module' => 'modules/{$name}/',
);
}

View file

@ -0,0 +1,136 @@
<?php
namespace Composer\Installers;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Package\PackageInterface;
abstract class BaseInstaller
{
protected $locations = array();
protected $composer;
protected $package;
protected $io;
/**
* Initializes base installer.
*
* @param PackageInterface $package
* @param Composer $composer
* @param IOInterface $io
*/
public function __construct(PackageInterface $package = null, Composer $composer = null, IOInterface $io = null)
{
$this->composer = $composer;
$this->package = $package;
$this->io = $io;
}
/**
* Return the install path based on package type.
*
* @param PackageInterface $package
* @param string $frameworkType
* @return string
*/
public function getInstallPath(PackageInterface $package, $frameworkType = '')
{
$type = $this->package->getType();
$prettyName = $this->package->getPrettyName();
if (strpos($prettyName, '/') !== false) {
list($vendor, $name) = explode('/', $prettyName);
} else {
$vendor = '';
$name = $prettyName;
}
$availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type'));
$extra = $package->getExtra();
if (!empty($extra['installer-name'])) {
$availableVars['name'] = $extra['installer-name'];
}
if ($this->composer->getPackage()) {
$extra = $this->composer->getPackage()->getExtra();
if (!empty($extra['installer-paths'])) {
$customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor);
if ($customPath !== false) {
return $this->templatePath($customPath, $availableVars);
}
}
}
$packageType = substr($type, strlen($frameworkType) + 1);
$locations = $this->getLocations();
if (!isset($locations[$packageType])) {
throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type));
}
return $this->templatePath($locations[$packageType], $availableVars);
}
/**
* For an installer to override to modify the vars per installer.
*
* @param array $vars
* @return array
*/
public function inflectPackageVars($vars)
{
return $vars;
}
/**
* Gets the installer's locations
*
* @return array
*/
public function getLocations()
{
return $this->locations;
}
/**
* Replace vars in a path
*
* @param string $path
* @param array $vars
* @return string
*/
protected function templatePath($path, array $vars = array())
{
if (strpos($path, '{') !== false) {
extract($vars);
preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches);
if (!empty($matches[1])) {
foreach ($matches[1] as $var) {
$path = str_replace('{$' . $var . '}', $$var, $path);
}
}
}
return $path;
}
/**
* Search through a passed paths array for a custom install path.
*
* @param array $paths
* @param string $name
* @param string $type
* @param string $vendor = NULL
* @return string
*/
protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL)
{
foreach ($paths as $path => $names) {
if (in_array($name, $names) || in_array('type:' . $type, $names) || in_array('vendor:' . $vendor, $names)) {
return $path;
}
}
return false;
}
}

View file

@ -0,0 +1,126 @@
<?php
namespace Composer\Installers;
use Composer\Util\Filesystem;
/**
* Installer for Bitrix Framework. Supported types of extensions:
* - `bitrix-d7-module` copy the module to directory `bitrix/modules/<vendor>.<name>`.
* - `bitrix-d7-component` copy the component to directory `bitrix/components/<vendor>/<name>`.
* - `bitrix-d7-template` copy the template to directory `bitrix/templates/<vendor>_<name>`.
*
* You can set custom path to directory with Bitrix kernel in `composer.json`:
*
* ```json
* {
* "extra": {
* "bitrix-dir": "s1/bitrix"
* }
* }
* ```
*
* @author Nik Samokhvalov <nik@samokhvalov.info>
* @author Denis Kulichkin <onexhovia@gmail.com>
*/
class BitrixInstaller extends BaseInstaller
{
protected $locations = array(
'module' => '{$bitrix_dir}/modules/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
'component' => '{$bitrix_dir}/components/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
'theme' => '{$bitrix_dir}/templates/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
'd7-module' => '{$bitrix_dir}/modules/{$vendor}.{$name}/',
'd7-component' => '{$bitrix_dir}/components/{$vendor}/{$name}/',
'd7-template' => '{$bitrix_dir}/templates/{$vendor}_{$name}/',
);
/**
* @var array Storage for informations about duplicates at all the time of installation packages.
*/
private static $checkedDuplicates = array();
/**
* {@inheritdoc}
*/
public function inflectPackageVars($vars)
{
if ($this->composer->getPackage()) {
$extra = $this->composer->getPackage()->getExtra();
if (isset($extra['bitrix-dir'])) {
$vars['bitrix_dir'] = $extra['bitrix-dir'];
}
}
if (!isset($vars['bitrix_dir'])) {
$vars['bitrix_dir'] = 'bitrix';
}
return parent::inflectPackageVars($vars);
}
/**
* {@inheritdoc}
*/
protected function templatePath($path, array $vars = array())
{
$templatePath = parent::templatePath($path, $vars);
$this->checkDuplicates($templatePath, $vars);
return $templatePath;
}
/**
* Duplicates search packages.
*
* @param string $path
* @param array $vars
*/
protected function checkDuplicates($path, array $vars = array())
{
$packageType = substr($vars['type'], strlen('bitrix') + 1);
$localDir = explode('/', $vars['bitrix_dir']);
array_pop($localDir);
$localDir[] = 'local';
$localDir = implode('/', $localDir);
$oldPath = str_replace(
array('{$bitrix_dir}', '{$name}'),
array($localDir, $vars['name']),
$this->locations[$packageType]
);
if (in_array($oldPath, static::$checkedDuplicates)) {
return;
}
if ($oldPath !== $path && file_exists($oldPath) && $this->io && $this->io->isInteractive()) {
$this->io->writeError(' <error>Duplication of packages:</error>');
$this->io->writeError(' <info>Package ' . $oldPath . ' will be called instead package ' . $path . '</info>');
while (true) {
switch ($this->io->ask(' <info>Delete ' . $oldPath . ' [y,n,?]?</info> ', '?')) {
case 'y':
$fs = new Filesystem();
$fs->removeDirectory($oldPath);
break 2;
case 'n':
break 2;
case '?':
default:
$this->io->writeError(array(
' y - delete package ' . $oldPath . ' and to continue with the installation',
' n - don\'t delete and to continue with the installation',
));
$this->io->writeError(' ? - print help');
break;
}
}
}
static::$checkedDuplicates[] = $oldPath;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Composer\Installers;
class BonefishInstaller extends BaseInstaller
{
protected $locations = array(
'package' => 'Packages/{$vendor}/{$name}/'
);
}

View file

@ -0,0 +1,82 @@
<?php
namespace Composer\Installers;
use Composer\DependencyResolver\Pool;
class CakePHPInstaller extends BaseInstaller
{
protected $locations = array(
'plugin' => 'Plugin/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars($vars)
{
if ($this->matchesCakeVersion('>=', '3.0.0')) {
return $vars;
}
$nameParts = explode('/', $vars['name']);
foreach ($nameParts as &$value) {
$value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value));
$value = str_replace(array('-', '_'), ' ', $value);
$value = str_replace(' ', '', ucwords($value));
}
$vars['name'] = implode('/', $nameParts);
return $vars;
}
/**
* Change the default plugin location when cakephp >= 3.0
*/
public function getLocations()
{
if ($this->matchesCakeVersion('>=', '3.0.0')) {
$this->locations['plugin'] = $this->composer->getConfig()->get('vendor-dir') . '/{$vendor}/{$name}/';
}
return $this->locations;
}
/**
* Check if CakePHP version matches against a version
*
* @param string $matcher
* @param string $version
* @return bool
*/
protected function matchesCakeVersion($matcher, $version)
{
if (class_exists('Composer\Semver\Constraint\MultiConstraint')) {
$multiClass = 'Composer\Semver\Constraint\MultiConstraint';
$constraintClass = 'Composer\Semver\Constraint\Constraint';
} else {
$multiClass = 'Composer\Package\LinkConstraint\MultiConstraint';
$constraintClass = 'Composer\Package\LinkConstraint\VersionConstraint';
}
$repositoryManager = $this->composer->getRepositoryManager();
if ($repositoryManager) {
$repos = $repositoryManager->getLocalRepository();
if (!$repos) {
return false;
}
$cake3 = new $multiClass(array(
new $constraintClass($matcher, $version),
new $constraintClass('!=', '9999999-dev'),
));
$pool = new Pool('dev');
$pool->addRepository($repos);
$packages = $pool->whatProvides('cakephp/cakephp');
foreach ($packages as $package) {
$installed = new $constraintClass('=', $package->getVersion());
if ($cake3->matches($installed)) {
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class ChefInstaller extends BaseInstaller
{
protected $locations = array(
'cookbook' => 'Chef/{$vendor}/{$name}/',
'role' => 'Chef/roles/{$name}/',
);
}

View file

@ -0,0 +1,9 @@
<?php
namespace Composer\Installers;
class CiviCrmInstaller extends BaseInstaller
{
protected $locations = array(
'ext' => 'ext/{$name}/'
);
}

View file

@ -0,0 +1,10 @@
<?php
namespace Composer\Installers;
class ClanCatsFrameworkInstaller extends BaseInstaller
{
protected $locations = array(
'ship' => 'CCF/orbit/{$name}/',
'theme' => 'CCF/app/themes/{$name}/',
);
}

View file

@ -0,0 +1,34 @@
<?php
namespace Composer\Installers;
class CockpitInstaller extends BaseInstaller
{
protected $locations = array(
'module' => 'cockpit/modules/addons/{$name}/',
);
/**
* Format module name.
*
* Strip `module-` prefix from package name.
*
* @param array @vars
*
* @return array
*/
public function inflectPackageVars($vars)
{
if ($vars['type'] == 'cockpit-module') {
return $this->inflectModuleVars($vars);
}
return $vars;
}
public function inflectModuleVars($vars)
{
$vars['name'] = ucfirst(preg_replace('/cockpit-/i', '', $vars['name']));
return $vars;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class CodeIgniterInstaller extends BaseInstaller
{
protected $locations = array(
'library' => 'application/libraries/{$name}/',
'third-party' => 'application/third_party/{$name}/',
'module' => 'application/modules/{$name}/',
);
}

View file

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class Concrete5Installer extends BaseInstaller
{
protected $locations = array(
'core' => 'concrete/',
'block' => 'application/blocks/{$name}/',
'package' => 'packages/{$name}/',
'theme' => 'application/themes/{$name}/',
'update' => 'updates/{$name}/',
);
}

View file

@ -0,0 +1,35 @@
<?php
namespace Composer\Installers;
/**
* Installer for Craft Plugins
*/
class CraftInstaller extends BaseInstaller
{
const NAME_PREFIX = 'craft';
const NAME_SUFFIX = 'plugin';
protected $locations = array(
'plugin' => 'craft/plugins/{$name}/',
);
/**
* Strip `craft-` prefix and/or `-plugin` suffix from package names
*
* @param array $vars
*
* @return array
*/
final public function inflectPackageVars($vars)
{
return $this->inflectPluginVars($vars);
}
private function inflectPluginVars($vars)
{
$vars['name'] = preg_replace('/-' . self::NAME_SUFFIX . '$/i', '', $vars['name']);
$vars['name'] = preg_replace('/^' . self::NAME_PREFIX . '-/i', '', $vars['name']);
return $vars;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Composer\Installers;
class CroogoInstaller extends BaseInstaller
{
protected $locations = array(
'plugin' => 'Plugin/{$name}/',
'theme' => 'View/Themed/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars($vars)
{
$vars['name'] = strtolower(str_replace(array('-', '_'), ' ', $vars['name']));
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Composer\Installers;
class DecibelInstaller extends BaseInstaller
{
/** @var array */
protected $locations = array(
'app' => 'app/{$name}/',
);
}

View file

@ -0,0 +1,50 @@
<?php
namespace Composer\Installers;
class DokuWikiInstaller extends BaseInstaller
{
protected $locations = array(
'plugin' => 'lib/plugins/{$name}/',
'template' => 'lib/tpl/{$name}/',
);
/**
* Format package name.
*
* For package type dokuwiki-plugin, cut off a trailing '-plugin',
* or leading dokuwiki_ if present.
*
* For package type dokuwiki-template, cut off a trailing '-template' if present.
*
*/
public function inflectPackageVars($vars)
{
if ($vars['type'] === 'dokuwiki-plugin') {
return $this->inflectPluginVars($vars);
}
if ($vars['type'] === 'dokuwiki-template') {
return $this->inflectTemplateVars($vars);
}
return $vars;
}
protected function inflectPluginVars($vars)
{
$vars['name'] = preg_replace('/-plugin$/', '', $vars['name']);
$vars['name'] = preg_replace('/^dokuwiki_?-?/', '', $vars['name']);
return $vars;
}
protected function inflectTemplateVars($vars)
{
$vars['name'] = preg_replace('/-template$/', '', $vars['name']);
$vars['name'] = preg_replace('/^dokuwiki_?-?/', '', $vars['name']);
return $vars;
}
}

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