├── .gitignore
├── README.md
├── vendor
├── dg
│ └── twitter-php
│ │ ├── .gitignore
│ │ ├── .github
│ │ └── funding.yml
│ │ ├── src
│ │ ├── twitter.class.php
│ │ ├── Twitter.php
│ │ └── OAuth.php
│ │ ├── examples
│ │ ├── send.php
│ │ ├── custom-request.php
│ │ ├── search.php
│ │ └── load.php
│ │ ├── composer.json
│ │ ├── license.md
│ │ └── readme.md
├── composer
│ ├── autoload_psr4.php
│ ├── autoload_namespaces.php
│ ├── LICENSE
│ ├── autoload_classmap.php
│ ├── autoload_static.php
│ ├── installed.json
│ ├── autoload_real.php
│ └── ClassLoader.php
└── autoload.php
├── composer.json
├── lib
└── zwitscher.php
├── boot.php
├── package.yml
├── LICENSE
└── composer.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zwitscher
2 | 🐣 twittern mit REDAXO
3 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "dg/twitter-php": "^4.1"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/lib/zwitscher.php:
--------------------------------------------------------------------------------
1 | getPath('vendor/'.'autoload.php');
4 |
--------------------------------------------------------------------------------
/vendor/composer/autoload_psr4.php:
--------------------------------------------------------------------------------
1 | send('I am fine'); // you can add $imagePath or array of image paths as second argument
12 |
13 | } catch (DG\Twitter\TwitterException $e) {
14 | echo 'Error: ' . $e->getMessage();
15 | }
16 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dg/twitter-php",
3 | "description": "Small and easy Twitter library for PHP",
4 | "keywords": ["twitter", "oauth"],
5 | "homepage": "https://github.com/dg/twitter-php",
6 | "license": ["BSD-3-Clause"],
7 | "authors": [
8 | {
9 | "name": "David Grudl",
10 | "homepage": "https://davidgrudl.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=7.1",
15 | "ext-curl": "*",
16 | "ext-json": "*"
17 | },
18 | "autoload": {
19 | "classmap": ["src/"]
20 | },
21 | "extra": {
22 | "branch-alias": {
23 | "dev-master": "4.0-dev"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/examples/custom-request.php:
--------------------------------------------------------------------------------
1 | request('statuses/retweets_of_me', 'GET');
12 |
13 | ?>
14 |
15 |
16 |
Twitter retweets of me
17 |
18 |
27 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/examples/search.php:
--------------------------------------------------------------------------------
1 | search('#nette');
11 | // or use hashmap: $results = $twitter->search(['q' => '#nette', 'geocode' => '50.088224,15.975611,20km']);
12 |
13 | ?>
14 |
15 |
16 | Twitter search demo
17 |
18 |
27 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/examples/load.php:
--------------------------------------------------------------------------------
1 | load(Twitter::ME_AND_FRIENDS);
15 |
16 | ?>
17 |
18 |
19 | Twitter timeline demo
20 |
21 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Friends Of REDAXO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vendor/composer/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) Nils Adermann, Jordi Boggiano
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/vendor/composer/autoload_classmap.php:
--------------------------------------------------------------------------------
1 | $vendorDir . '/dg/twitter-php/src/Twitter.php',
10 | 'DG\\Twitter\\OAuth\\Consumer' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
11 | 'DG\\Twitter\\OAuth\\Exception' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
12 | 'DG\\Twitter\\OAuth\\Request' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
13 | 'DG\\Twitter\\OAuth\\SignatureMethod' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
14 | 'DG\\Twitter\\OAuth\\SignatureMethod_HMAC_SHA1' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
15 | 'DG\\Twitter\\OAuth\\SignatureMethod_PLAINTEXT' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
16 | 'DG\\Twitter\\OAuth\\SignatureMethod_RSA_SHA1' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
17 | 'DG\\Twitter\\OAuth\\Token' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
18 | 'DG\\Twitter\\OAuth\\Util' => $vendorDir . '/dg/twitter-php/src/OAuth.php',
19 | 'DG\\Twitter\\Twitter' => $vendorDir . '/dg/twitter-php/src/Twitter.php',
20 | );
21 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/license.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008, Copyright (c) 2008 David Grudl
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of David Grudl nor the names of its
15 | contributors may be used to endorse or promote products derived from this
16 | software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/vendor/composer/autoload_static.php:
--------------------------------------------------------------------------------
1 | __DIR__ . '/..' . '/dg/twitter-php/src/Twitter.php',
11 | 'DG\\Twitter\\OAuth\\Consumer' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
12 | 'DG\\Twitter\\OAuth\\Exception' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
13 | 'DG\\Twitter\\OAuth\\Request' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
14 | 'DG\\Twitter\\OAuth\\SignatureMethod' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
15 | 'DG\\Twitter\\OAuth\\SignatureMethod_HMAC_SHA1' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
16 | 'DG\\Twitter\\OAuth\\SignatureMethod_PLAINTEXT' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
17 | 'DG\\Twitter\\OAuth\\SignatureMethod_RSA_SHA1' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
18 | 'DG\\Twitter\\OAuth\\Token' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
19 | 'DG\\Twitter\\OAuth\\Util' => __DIR__ . '/..' . '/dg/twitter-php/src/OAuth.php',
20 | 'DG\\Twitter\\Twitter' => __DIR__ . '/..' . '/dg/twitter-php/src/Twitter.php',
21 | );
22 |
23 | public static function getInitializer(ClassLoader $loader)
24 | {
25 | return \Closure::bind(function () use ($loader) {
26 | $loader->classMap = ComposerStaticInit3dc24853ab255ce2977620ec7964f167::$classMap;
27 |
28 | }, null, ClassLoader::class);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/vendor/composer/installed.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "dg/twitter-php",
4 | "version": "v4.1.2",
5 | "version_normalized": "4.1.2.0",
6 | "source": {
7 | "type": "git",
8 | "url": "https://github.com/dg/twitter-php.git",
9 | "reference": "450baa2fd7b5e815c5f050b019c4486d1b1b01da"
10 | },
11 | "dist": {
12 | "type": "zip",
13 | "url": "https://api.github.com/repos/dg/twitter-php/zipball/450baa2fd7b5e815c5f050b019c4486d1b1b01da",
14 | "reference": "450baa2fd7b5e815c5f050b019c4486d1b1b01da",
15 | "shasum": ""
16 | },
17 | "require": {
18 | "ext-curl": "*",
19 | "ext-json": "*",
20 | "php": ">=7.1"
21 | },
22 | "time": "2020-05-11T22:48:42+00:00",
23 | "type": "library",
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "4.0-dev"
27 | }
28 | },
29 | "installation-source": "dist",
30 | "autoload": {
31 | "classmap": [
32 | "src/"
33 | ]
34 | },
35 | "notification-url": "https://packagist.org/downloads/",
36 | "license": [
37 | "BSD-3-Clause"
38 | ],
39 | "authors": [
40 | {
41 | "name": "David Grudl",
42 | "homepage": "https://davidgrudl.com"
43 | }
44 | ],
45 | "description": "Small and easy Twitter library for PHP",
46 | "homepage": "https://github.com/dg/twitter-php",
47 | "keywords": [
48 | "oauth",
49 | "twitter"
50 | ]
51 | }
52 | ]
53 |
--------------------------------------------------------------------------------
/vendor/composer/autoload_real.php:
--------------------------------------------------------------------------------
1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27 | if ($useStaticLoader) {
28 | require_once __DIR__ . '/autoload_static.php';
29 |
30 | call_user_func(\Composer\Autoload\ComposerStaticInit3dc24853ab255ce2977620ec7964f167::getInitializer($loader));
31 | } else {
32 | $map = require __DIR__ . '/autoload_namespaces.php';
33 | foreach ($map as $namespace => $path) {
34 | $loader->set($namespace, $path);
35 | }
36 |
37 | $map = require __DIR__ . '/autoload_psr4.php';
38 | foreach ($map as $namespace => $path) {
39 | $loader->setPsr4($namespace, $path);
40 | }
41 |
42 | $classMap = require __DIR__ . '/autoload_classmap.php';
43 | if ($classMap) {
44 | $loader->addClassMap($classMap);
45 | }
46 | }
47 |
48 | $loader->register(true);
49 |
50 | return $loader;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "5127c09c1a6129cb30a86e759da370f5",
8 | "packages": [
9 | {
10 | "name": "dg/twitter-php",
11 | "version": "v4.1.2",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/dg/twitter-php.git",
15 | "reference": "450baa2fd7b5e815c5f050b019c4486d1b1b01da"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/dg/twitter-php/zipball/450baa2fd7b5e815c5f050b019c4486d1b1b01da",
20 | "reference": "450baa2fd7b5e815c5f050b019c4486d1b1b01da",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-curl": "*",
25 | "ext-json": "*",
26 | "php": ">=7.1"
27 | },
28 | "type": "library",
29 | "extra": {
30 | "branch-alias": {
31 | "dev-master": "4.0-dev"
32 | }
33 | },
34 | "autoload": {
35 | "classmap": [
36 | "src/"
37 | ]
38 | },
39 | "notification-url": "https://packagist.org/downloads/",
40 | "license": [
41 | "BSD-3-Clause"
42 | ],
43 | "authors": [
44 | {
45 | "name": "David Grudl",
46 | "homepage": "https://davidgrudl.com"
47 | }
48 | ],
49 | "description": "Small and easy Twitter library for PHP",
50 | "homepage": "https://github.com/dg/twitter-php",
51 | "keywords": [
52 | "oauth",
53 | "twitter"
54 | ],
55 | "time": "2020-05-11T22:48:42+00:00"
56 | }
57 | ],
58 | "packages-dev": [],
59 | "aliases": [],
60 | "minimum-stability": "stable",
61 | "stability-flags": [],
62 | "prefer-stable": false,
63 | "prefer-lowest": false,
64 | "platform": [],
65 | "platform-dev": []
66 | }
67 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/readme.md:
--------------------------------------------------------------------------------
1 | [Twitter for PHP](https://phpfashion.com/twitter-for-php) [](https://nette.org/make-donation?to=twitter-php)
2 | ================================
3 |
4 | [](https://packagist.org/packages/dg/twitter-php)
5 |
6 | Twitter for PHP is a very small and easy-to-use library for sending
7 | messages to Twitter and receiving status updates.
8 |
9 | If you like this, **[please make a donation now](https://nette.org/make-donation?to=twitter-php)**. Thank you!
10 |
11 | It requires PHP 5.4 or newer with CURL extension and is licensed under the New BSD License.
12 | You can obtain the latest version from our [GitHub repository](https://github.com/dg/twitter-php)
13 | or install it via Composer:
14 |
15 | composer require dg/twitter-php
16 |
17 |
18 | Usage
19 | -----
20 | Sign in to the https://twitter.com and register an application from the https://apps.twitter.com page. Remember
21 | to never reveal your consumer secrets. Click on My Access Token link from the sidebar and retrieve your own access
22 | token. Now you have consumer key, consumer secret, access token and access token secret.
23 |
24 | Create object using application and request/access keys
25 |
26 | ```php
27 | use DG\Twitter\Twitter;
28 |
29 | $twitter = new Twitter($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
30 | ```
31 |
32 | The send() method updates your status. The message must be encoded in UTF-8:
33 |
34 | ```php
35 | $twitter->send('I am fine today.');
36 | ```
37 |
38 | The load() method returns the 20 most recent status updates
39 | posted by you:
40 |
41 | ```php
42 | $statuses = $twitter->load(Twitter::ME);
43 | ```
44 |
45 | or posted by you and your friends:
46 |
47 | ```php
48 | $statuses = $twitter->load(Twitter::ME_AND_FRIENDS);
49 | ```
50 | or most recent mentions for you:
51 |
52 | ```php
53 | $statuses = $twitter->load(Twitter::REPLIES);
54 | ```
55 | Extracting the information from the channel is easy:
56 |
57 | ```php
58 | foreach ($statuses as $status) {
59 | echo "message: ", Twitter::clickable($status);
60 | echo "posted at " , $status->created_at;
61 | echo "posted by " , $status->user->name;
62 | }
63 | ```
64 |
65 | The static method `Twitter::clickable()` makes links, mentions and hash tags in status clickable.
66 |
67 | The authenticate() method tests if user credentials are valid:
68 |
69 | ```php
70 | if (!$twitter->authenticate()) {
71 | die('Invalid name or password');
72 | }
73 | ```
74 |
75 | The search() method provides searching in twitter statuses:
76 |
77 | ```php
78 | $results = $twitter->search('#nette');
79 | ```
80 |
81 | The returned result is a again array of statuses.
82 |
83 |
84 | Error handling
85 | --------------
86 |
87 | All methods throw a `DG\Twitter\Exception` on error:
88 |
89 | ```php
90 | try {
91 | $statuses = $twitter->load(Twitter::ME);
92 | } catch (DG\Twitter\Exception $e) {
93 | echo "Error: ", $e->getMessage();
94 | }
95 | ```
96 |
97 | Additional features
98 | -------------------
99 |
100 | The `authenticate()` method tests if user credentials are valid:
101 |
102 | ```php
103 | if (!$twitter->authenticate()) {
104 | die('Invalid name or password');
105 | }
106 | ```
107 |
108 | Other commands
109 | --------------
110 |
111 | You can use all commands defined by [Twitter API 1.1](https://dev.twitter.com/rest/public).
112 | For example [GET statuses/retweets_of_me](https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me)
113 | returns the array of most recent tweets authored by the authenticating user:
114 |
115 | ```php
116 | $statuses = $twitter->request('statuses/retweets_of_me', 'GET', ['count' => 20]);
117 | ```
118 |
119 | Changelog
120 | ---------
121 | v4.1 (11/2019)
122 | - added Delete Method (#68)
123 | - token is optional throughout + supply get() method
124 |
125 | v4.0 (2/2019)
126 | - requires PHP 7.1 and uses its advantages like typehints, strict types etc.
127 | - class Twitter is now DG\Twitter\Twitter
128 | - class TwitterException is now DG\Twitter\Exception
129 |
130 | v3.8 (2/2019)
131 | - Twitter::sendDirectMessage() uses new API
132 | - Twitter::clickable: added support for $status->full_text (#60)
133 |
134 | v3.7 (3/2018)
135 | - minimal required PHP version changed to 5.4
136 | - Twitter::send() added $options
137 | - Twitter::clickable() now works only with statuses and entites
138 | - fixed coding style
139 |
140 | v3.6 (8/2016)
141 | - added loadUserFollowersList() and sendDirectMessage()
142 | - Twitter::send() allows to upload multiple images
143 | - changed http:// to https://
144 |
145 | v3.5 (12/2014)
146 | - allows to send message starting with @ and upload file at the same time in PHP >= 5.5
147 |
148 | v3.4 (11/2014)
149 | - cache expiration can be specified as string
150 | - fixed some bugs
151 |
152 | v3.3 (3/2014)
153 | - Twitter::send($status, $image) can upload image
154 | - added Twitter::follow()
155 |
156 | v3.2 (1/2014)
157 | - Twitter API uses SSL OAuth
158 | - Twitter::clickable() supports media
159 | - added Twitter::loadUserInfoById() and loadUserFollowers()
160 | - fixed Twitter::destroy()
161 |
162 | v3.1 (3/2013)
163 | - Twitter::load() - added third argument $data
164 | - Twitter::clickable() uses entities; pass as parameter status object, not just text
165 | - added Twitter::$httpOptions for custom cURL configuration
166 |
167 | v3.0 (12/2012)
168 | - updated to Twitter API 1.1. Some stuff deprecated by Twitter was removed:
169 | - removed RSS, ATOM and XML support
170 | - removed Twitter::ALL
171 | - Twitter::load() - removed third argument $page
172 | - Twitter::search() requires authentication and returns different structure
173 | - removed shortening URL using http://is.gd
174 | - changed order of Twitter::request() arguments to $resource, $method, $data
175 |
176 | v2.0 (8/2012)
177 | - added support for OAuth authentication protocol
178 | - added Twitter::clickable() which makes links, @usernames and #hashtags clickable
179 | - installable via `composer require dg/twitter-php`
180 |
181 | v1.0 (7/2008)
182 | - initial release
183 |
184 |
185 | -----
186 | (c) David Grudl, 2008, 2016 (https://davidgrudl.com)
187 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/src/Twitter.php:
--------------------------------------------------------------------------------
1 | 20,
42 | CURLOPT_SSL_VERIFYPEER => 0,
43 | CURLOPT_USERAGENT => 'Twitter for PHP',
44 | ];
45 |
46 | /** @var OAuth\Consumer */
47 | private $consumer;
48 |
49 | /** @var OAuth\Token */
50 | private $token;
51 |
52 |
53 | /**
54 | * Creates object using consumer and access keys.
55 | * @throws Exception when CURL extension is not loaded
56 | */
57 | public function __construct(string $consumerKey, string $consumerSecret, string $accessToken = null, string $accessTokenSecret = null)
58 | {
59 | if (!extension_loaded('curl')) {
60 | throw new Exception('PHP extension CURL is not loaded.');
61 | }
62 |
63 | $this->consumer = new OAuth\Consumer($consumerKey, $consumerSecret);
64 | if ($accessToken && $accessTokenSecret) {
65 | $this->token = new OAuth\Token($accessToken, $accessTokenSecret);
66 | }
67 | }
68 |
69 |
70 | /**
71 | * Tests if user credentials are valid.
72 | * @throws Exception
73 | */
74 | public function authenticate(): bool
75 | {
76 | try {
77 | $res = $this->request('account/verify_credentials', 'GET');
78 | return !empty($res->id);
79 |
80 | } catch (Exception $e) {
81 | if ($e->getCode() === 401) {
82 | return false;
83 | }
84 | throw $e;
85 | }
86 | }
87 |
88 |
89 | /**
90 | * Sends message to the Twitter.
91 | * https://dev.twitter.com/rest/reference/post/statuses/update
92 | * @param string|array $mediaPath path to local media file to be uploaded
93 | * @throws Exception
94 | */
95 | public function send(string $message, $mediaPath = null, array $options = []): stdClass
96 | {
97 | $mediaIds = [];
98 | foreach ((array) $mediaPath as $item) {
99 | $res = $this->request(
100 | 'https://upload.twitter.com/1.1/media/upload.json',
101 | 'POST',
102 | [],
103 | ['media' => $item]
104 | );
105 | $mediaIds[] = $res->media_id_string;
106 | }
107 | return $this->request(
108 | 'statuses/update',
109 | 'POST',
110 | $options + ['status' => $message, 'media_ids' => implode(',', $mediaIds) ?: null]
111 | );
112 | }
113 |
114 |
115 | /**
116 | * Sends a direct message to the specified user.
117 | * https://dev.twitter.com/rest/reference/post/direct_messages/new
118 | * @throws Exception
119 | */
120 | public function sendDirectMessage(string $username, string $message): stdClass
121 | {
122 | return $this->request(
123 | 'direct_messages/events/new',
124 | 'JSONPOST',
125 | ['event' => [
126 | 'type' => 'message_create',
127 | 'message_create' => [
128 | 'target' => ['recipient_id' => $this->loadUserInfo($username)->id_str],
129 | 'message_data' => ['text' => $message],
130 | ],
131 | ]]
132 | );
133 | }
134 |
135 |
136 | /**
137 | * Follows a user on Twitter.
138 | * https://dev.twitter.com/rest/reference/post/friendships/create
139 | * @throws Exception
140 | */
141 | public function follow(string $username): stdClass
142 | {
143 | return $this->request('friendships/create', 'POST', ['screen_name' => $username]);
144 | }
145 |
146 |
147 | /**
148 | * Returns the most recent statuses.
149 | * https://dev.twitter.com/rest/reference/get/statuses/user_timeline
150 | * @param int $flags timeline (ME | ME_AND_FRIENDS | REPLIES) and optional (RETWEETS)
151 | * @return stdClass[]
152 | * @throws Exception
153 | */
154 | public function load(int $flags = self::ME, int $count = 20, array $data = null): array
155 | {
156 | static $timelines = [
157 | self::ME => 'user_timeline',
158 | self::ME_AND_FRIENDS => 'home_timeline',
159 | self::REPLIES => 'mentions_timeline',
160 | ];
161 | if (!isset($timelines[$flags & 3])) {
162 | throw new \InvalidArgumentException;
163 | }
164 |
165 | return $this->cachedRequest('statuses/' . $timelines[$flags & 3], (array) $data + [
166 | 'count' => $count,
167 | 'include_rts' => $flags & self::RETWEETS ? 1 : 0,
168 | ]);
169 | }
170 |
171 |
172 | /**
173 | * Returns information of a given user.
174 | * https://dev.twitter.com/rest/reference/get/users/show
175 | * @throws Exception
176 | */
177 | public function loadUserInfo(string $username): stdClass
178 | {
179 | return $this->cachedRequest('users/show', ['screen_name' => $username]);
180 | }
181 |
182 |
183 | /**
184 | * Returns information of a given user by id.
185 | * https://dev.twitter.com/rest/reference/get/users/show
186 | * @throws Exception
187 | */
188 | public function loadUserInfoById(string $id): stdClass
189 | {
190 | return $this->cachedRequest('users/show', ['user_id' => $id]);
191 | }
192 |
193 |
194 | /**
195 | * Returns IDs of followers of a given user.
196 | * https://dev.twitter.com/rest/reference/get/followers/ids
197 | * @throws Exception
198 | */
199 | public function loadUserFollowers(string $username, int $count = 5000, int $cursor = -1, $cacheExpiry = null): stdClass
200 | {
201 | return $this->cachedRequest('followers/ids', [
202 | 'screen_name' => $username,
203 | 'count' => $count,
204 | 'cursor' => $cursor,
205 | ], $cacheExpiry);
206 | }
207 |
208 |
209 | /**
210 | * Returns list of followers of a given user.
211 | * https://dev.twitter.com/rest/reference/get/followers/list
212 | * @throws Exception
213 | */
214 | public function loadUserFollowersList(string $username, int $count = 200, int $cursor = -1, $cacheExpiry = null): stdClass
215 | {
216 | return $this->cachedRequest('followers/list', [
217 | 'screen_name' => $username,
218 | 'count' => $count,
219 | 'cursor' => $cursor,
220 | ], $cacheExpiry);
221 | }
222 |
223 |
224 | /**
225 | * Destroys status.
226 | * @param int|string $id status to be destroyed
227 | * @throws Exception
228 | */
229 | public function destroy($id)
230 | {
231 | $res = $this->request("statuses/destroy/$id", 'POST');
232 | return $res->id ?: false;
233 | }
234 |
235 |
236 | /**
237 | * Retrieves a single status.
238 | * @param int|string $id status to be retrieved
239 | * @throws Exception
240 | */
241 | public function get($id)
242 | {
243 | $res = $this->request("statuses/show/$id", 'GET');
244 | return $res;
245 | }
246 |
247 |
248 | /**
249 | * Returns tweets that match a specified query.
250 | * https://dev.twitter.com/rest/reference/get/search/tweets
251 | * @param string|array
252 | * @throws Exception
253 | * @return stdClass|stdClass[]
254 | */
255 | public function search($query, bool $full = false)
256 | {
257 | $res = $this->request('search/tweets', 'GET', is_array($query) ? $query : ['q' => $query]);
258 | return $full ? $res : $res->statuses;
259 | }
260 |
261 |
262 | /**
263 | * Retrieves the top 50 trending topics for a specific WOEID.
264 | * @param int|string $WOEID Where On Earth IDentifier
265 | */
266 | public function getTrends(int $WOEID): array
267 | {
268 | return $this->request("trends/place.json?id=$WOEID", 'GET');
269 | }
270 |
271 |
272 | /**
273 | * Process HTTP request.
274 | * @param string $method GET|POST|JSONPOST|DELETE
275 | * @return mixed
276 | * @throws Exception
277 | */
278 | public function request(string $resource, string $method, array $data = [], array $files = [])
279 | {
280 | if (!strpos($resource, '://')) {
281 | if (!strpos($resource, '.')) {
282 | $resource .= '.json';
283 | }
284 | $resource = self::API_URL . $resource;
285 | }
286 |
287 | foreach ($data as $key => $val) {
288 | if ($val === null) {
289 | unset($data[$key]);
290 | }
291 | }
292 |
293 | foreach ($files as $key => $file) {
294 | if (!is_file($file)) {
295 | throw new Exception("Cannot read the file $file. Check if file exists on disk and check its permissions.");
296 | }
297 | $data[$key] = new \CURLFile($file);
298 | }
299 |
300 | $headers = ['Expect:'];
301 |
302 | if ($method === 'JSONPOST') {
303 | $method = 'POST';
304 | $data = json_encode($data);
305 | $headers[] = 'Content-Type: application/json';
306 |
307 | } elseif (($method === 'GET' || $method === 'DELETE') && $data) {
308 | $resource .= '?' . http_build_query($data, '', '&');
309 | }
310 |
311 | $request = OAuth\Request::from_consumer_and_token($this->consumer, $this->token, $method, $resource);
312 | $request->sign_request(new OAuth\SignatureMethod_HMAC_SHA1, $this->consumer, $this->token);
313 | $headers[] = $request->to_header();
314 |
315 | $options = [
316 | CURLOPT_URL => $resource,
317 | CURLOPT_HEADER => false,
318 | CURLOPT_RETURNTRANSFER => true,
319 | CURLOPT_HTTPHEADER => $headers,
320 | ] + $this->httpOptions;
321 |
322 | if ($method === 'POST') {
323 | $options += [
324 | CURLOPT_POST => true,
325 | CURLOPT_POSTFIELDS => $data,
326 | CURLOPT_SAFE_UPLOAD => true,
327 | ];
328 | } elseif ($method === 'DELETE') {
329 | $options += [
330 | CURLOPT_CUSTOMREQUEST => 'DELETE',
331 | ];
332 | }
333 |
334 | $curl = curl_init();
335 | curl_setopt_array($curl, $options);
336 | $result = curl_exec($curl);
337 | if (curl_errno($curl)) {
338 | throw new Exception('Server error: ' . curl_error($curl));
339 | }
340 |
341 | if (strpos(curl_getinfo($curl, CURLINFO_CONTENT_TYPE), 'application/json') !== false) {
342 | $payload = @json_decode($result, false, 128, JSON_BIGINT_AS_STRING); // intentionally @
343 | if ($payload === false) {
344 | throw new Exception('Invalid server response');
345 | }
346 | }
347 |
348 | $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
349 | if ($code >= 400) {
350 | throw new Exception(isset($payload->errors[0]->message)
351 | ? $payload->errors[0]->message
352 | : "Server error #$code with answer $result",
353 | $code
354 | );
355 | } elseif ($code === 204) {
356 | $payload = true;
357 | }
358 |
359 | return $payload;
360 | }
361 |
362 |
363 | /**
364 | * Cached HTTP request.
365 | * @return stdClass|stdClass[]
366 | */
367 | public function cachedRequest(string $resource, array $data = [], $cacheExpire = null)
368 | {
369 | if (!self::$cacheDir) {
370 | return $this->request($resource, 'GET', $data);
371 | }
372 | if ($cacheExpire === null) {
373 | $cacheExpire = self::$cacheExpire;
374 | }
375 |
376 | $cacheFile = self::$cacheDir
377 | . '/twitter.'
378 | . md5($resource . json_encode($data) . serialize([$this->consumer, $this->token]))
379 | . '.json';
380 |
381 | $cache = @json_decode((string) @file_get_contents($cacheFile)); // intentionally @
382 | $expiration = is_string($cacheExpire) ? strtotime($cacheExpire) - time() : $cacheExpire;
383 | if ($cache && @filemtime($cacheFile) + $expiration > time()) { // intentionally @
384 | return $cache;
385 | }
386 |
387 | try {
388 | $payload = $this->request($resource, 'GET', $data);
389 | file_put_contents($cacheFile, json_encode($payload));
390 | return $payload;
391 |
392 | } catch (Exception $e) {
393 | if ($cache) {
394 | return $cache;
395 | }
396 | throw $e;
397 | }
398 | }
399 |
400 |
401 | /**
402 | * Makes twitter links, @usernames and #hashtags clickable.
403 | */
404 | public static function clickable(stdClass $status): string
405 | {
406 | $all = [];
407 | foreach ($status->entities->hashtags as $item) {
408 | $all[$item->indices[0]] = ["https://twitter.com/search?q=%23$item->text", "#$item->text", $item->indices[1]];
409 | }
410 | foreach ($status->entities->urls as $item) {
411 | if (!isset($item->expanded_url)) {
412 | $all[$item->indices[0]] = [$item->url, $item->url, $item->indices[1]];
413 | } else {
414 | $all[$item->indices[0]] = [$item->expanded_url, $item->display_url, $item->indices[1]];
415 | }
416 | }
417 | foreach ($status->entities->user_mentions as $item) {
418 | $all[$item->indices[0]] = ["https://twitter.com/$item->screen_name", "@$item->screen_name", $item->indices[1]];
419 | }
420 | if (isset($status->entities->media)) {
421 | foreach ($status->entities->media as $item) {
422 | $all[$item->indices[0]] = [$item->url, $item->display_url, $item->indices[1]];
423 | }
424 | }
425 |
426 | krsort($all);
427 | $s = isset($status->full_text) ? $status->full_text : $status->text;
428 | foreach ($all as $pos => $item) {
429 | $s = iconv_substr($s, 0, $pos, 'UTF-8')
430 | . '' . htmlspecialchars($item[1]) . ''
431 | . iconv_substr($s, $item[2], iconv_strlen($s, 'UTF-8'), 'UTF-8');
432 | }
433 | return $s;
434 | }
435 | }
436 |
437 |
438 |
439 | /**
440 | * An exception generated by Twitter.
441 | */
442 | class Exception extends \Exception
443 | {
444 | }
445 |
--------------------------------------------------------------------------------
/vendor/composer/ClassLoader.php:
--------------------------------------------------------------------------------
1 |
7 | * Jordi Boggiano
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | namespace Composer\Autoload;
14 |
15 | /**
16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17 | *
18 | * $loader = new \Composer\Autoload\ClassLoader();
19 | *
20 | * // register classes with namespaces
21 | * $loader->add('Symfony\Component', __DIR__.'/component');
22 | * $loader->add('Symfony', __DIR__.'/framework');
23 | *
24 | * // activate the autoloader
25 | * $loader->register();
26 | *
27 | * // to enable searching the include path (eg. for PEAR packages)
28 | * $loader->setUseIncludePath(true);
29 | *
30 | * In this example, if you try to use a class in the Symfony\Component
31 | * namespace or one of its children (Symfony\Component\Console for instance),
32 | * the autoloader will first look for the class under the component/
33 | * directory, and it will then fallback to the framework/ directory if not
34 | * found before giving up.
35 | *
36 | * This class is loosely based on the Symfony UniversalClassLoader.
37 | *
38 | * @author Fabien Potencier
39 | * @author Jordi Boggiano
40 | * @see http://www.php-fig.org/psr/psr-0/
41 | * @see http://www.php-fig.org/psr/psr-4/
42 | */
43 | class ClassLoader
44 | {
45 | // PSR-4
46 | private $prefixLengthsPsr4 = array();
47 | private $prefixDirsPsr4 = array();
48 | private $fallbackDirsPsr4 = array();
49 |
50 | // PSR-0
51 | private $prefixesPsr0 = array();
52 | private $fallbackDirsPsr0 = array();
53 |
54 | private $useIncludePath = false;
55 | private $classMap = array();
56 | private $classMapAuthoritative = false;
57 | private $missingClasses = array();
58 | private $apcuPrefix;
59 |
60 | public function getPrefixes()
61 | {
62 | if (!empty($this->prefixesPsr0)) {
63 | return call_user_func_array('array_merge', $this->prefixesPsr0);
64 | }
65 |
66 | return array();
67 | }
68 |
69 | public function getPrefixesPsr4()
70 | {
71 | return $this->prefixDirsPsr4;
72 | }
73 |
74 | public function getFallbackDirs()
75 | {
76 | return $this->fallbackDirsPsr0;
77 | }
78 |
79 | public function getFallbackDirsPsr4()
80 | {
81 | return $this->fallbackDirsPsr4;
82 | }
83 |
84 | public function getClassMap()
85 | {
86 | return $this->classMap;
87 | }
88 |
89 | /**
90 | * @param array $classMap Class to filename map
91 | */
92 | public function addClassMap(array $classMap)
93 | {
94 | if ($this->classMap) {
95 | $this->classMap = array_merge($this->classMap, $classMap);
96 | } else {
97 | $this->classMap = $classMap;
98 | }
99 | }
100 |
101 | /**
102 | * Registers a set of PSR-0 directories for a given prefix, either
103 | * appending or prepending to the ones previously set for this prefix.
104 | *
105 | * @param string $prefix The prefix
106 | * @param array|string $paths The PSR-0 root directories
107 | * @param bool $prepend Whether to prepend the directories
108 | */
109 | public function add($prefix, $paths, $prepend = false)
110 | {
111 | if (!$prefix) {
112 | if ($prepend) {
113 | $this->fallbackDirsPsr0 = array_merge(
114 | (array) $paths,
115 | $this->fallbackDirsPsr0
116 | );
117 | } else {
118 | $this->fallbackDirsPsr0 = array_merge(
119 | $this->fallbackDirsPsr0,
120 | (array) $paths
121 | );
122 | }
123 |
124 | return;
125 | }
126 |
127 | $first = $prefix[0];
128 | if (!isset($this->prefixesPsr0[$first][$prefix])) {
129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130 |
131 | return;
132 | }
133 | if ($prepend) {
134 | $this->prefixesPsr0[$first][$prefix] = array_merge(
135 | (array) $paths,
136 | $this->prefixesPsr0[$first][$prefix]
137 | );
138 | } else {
139 | $this->prefixesPsr0[$first][$prefix] = array_merge(
140 | $this->prefixesPsr0[$first][$prefix],
141 | (array) $paths
142 | );
143 | }
144 | }
145 |
146 | /**
147 | * Registers a set of PSR-4 directories for a given namespace, either
148 | * appending or prepending to the ones previously set for this namespace.
149 | *
150 | * @param string $prefix The prefix/namespace, with trailing '\\'
151 | * @param array|string $paths The PSR-4 base directories
152 | * @param bool $prepend Whether to prepend the directories
153 | *
154 | * @throws \InvalidArgumentException
155 | */
156 | public function addPsr4($prefix, $paths, $prepend = false)
157 | {
158 | if (!$prefix) {
159 | // Register directories for the root namespace.
160 | if ($prepend) {
161 | $this->fallbackDirsPsr4 = array_merge(
162 | (array) $paths,
163 | $this->fallbackDirsPsr4
164 | );
165 | } else {
166 | $this->fallbackDirsPsr4 = array_merge(
167 | $this->fallbackDirsPsr4,
168 | (array) $paths
169 | );
170 | }
171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172 | // Register directories for a new namespace.
173 | $length = strlen($prefix);
174 | if ('\\' !== $prefix[$length - 1]) {
175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176 | }
177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178 | $this->prefixDirsPsr4[$prefix] = (array) $paths;
179 | } elseif ($prepend) {
180 | // Prepend directories for an already registered namespace.
181 | $this->prefixDirsPsr4[$prefix] = array_merge(
182 | (array) $paths,
183 | $this->prefixDirsPsr4[$prefix]
184 | );
185 | } else {
186 | // Append directories for an already registered namespace.
187 | $this->prefixDirsPsr4[$prefix] = array_merge(
188 | $this->prefixDirsPsr4[$prefix],
189 | (array) $paths
190 | );
191 | }
192 | }
193 |
194 | /**
195 | * Registers a set of PSR-0 directories for a given prefix,
196 | * replacing any others previously set for this prefix.
197 | *
198 | * @param string $prefix The prefix
199 | * @param array|string $paths The PSR-0 base directories
200 | */
201 | public function set($prefix, $paths)
202 | {
203 | if (!$prefix) {
204 | $this->fallbackDirsPsr0 = (array) $paths;
205 | } else {
206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207 | }
208 | }
209 |
210 | /**
211 | * Registers a set of PSR-4 directories for a given namespace,
212 | * replacing any others previously set for this namespace.
213 | *
214 | * @param string $prefix The prefix/namespace, with trailing '\\'
215 | * @param array|string $paths The PSR-4 base directories
216 | *
217 | * @throws \InvalidArgumentException
218 | */
219 | public function setPsr4($prefix, $paths)
220 | {
221 | if (!$prefix) {
222 | $this->fallbackDirsPsr4 = (array) $paths;
223 | } else {
224 | $length = strlen($prefix);
225 | if ('\\' !== $prefix[$length - 1]) {
226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227 | }
228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229 | $this->prefixDirsPsr4[$prefix] = (array) $paths;
230 | }
231 | }
232 |
233 | /**
234 | * Turns on searching the include path for class files.
235 | *
236 | * @param bool $useIncludePath
237 | */
238 | public function setUseIncludePath($useIncludePath)
239 | {
240 | $this->useIncludePath = $useIncludePath;
241 | }
242 |
243 | /**
244 | * Can be used to check if the autoloader uses the include path to check
245 | * for classes.
246 | *
247 | * @return bool
248 | */
249 | public function getUseIncludePath()
250 | {
251 | return $this->useIncludePath;
252 | }
253 |
254 | /**
255 | * Turns off searching the prefix and fallback directories for classes
256 | * that have not been registered with the class map.
257 | *
258 | * @param bool $classMapAuthoritative
259 | */
260 | public function setClassMapAuthoritative($classMapAuthoritative)
261 | {
262 | $this->classMapAuthoritative = $classMapAuthoritative;
263 | }
264 |
265 | /**
266 | * Should class lookup fail if not found in the current class map?
267 | *
268 | * @return bool
269 | */
270 | public function isClassMapAuthoritative()
271 | {
272 | return $this->classMapAuthoritative;
273 | }
274 |
275 | /**
276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277 | *
278 | * @param string|null $apcuPrefix
279 | */
280 | public function setApcuPrefix($apcuPrefix)
281 | {
282 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
283 | }
284 |
285 | /**
286 | * The APCu prefix in use, or null if APCu caching is not enabled.
287 | *
288 | * @return string|null
289 | */
290 | public function getApcuPrefix()
291 | {
292 | return $this->apcuPrefix;
293 | }
294 |
295 | /**
296 | * Registers this instance as an autoloader.
297 | *
298 | * @param bool $prepend Whether to prepend the autoloader or not
299 | */
300 | public function register($prepend = false)
301 | {
302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303 | }
304 |
305 | /**
306 | * Unregisters this instance as an autoloader.
307 | */
308 | public function unregister()
309 | {
310 | spl_autoload_unregister(array($this, 'loadClass'));
311 | }
312 |
313 | /**
314 | * Loads the given class or interface.
315 | *
316 | * @param string $class The name of the class
317 | * @return bool|null True if loaded, null otherwise
318 | */
319 | public function loadClass($class)
320 | {
321 | if ($file = $this->findFile($class)) {
322 | includeFile($file);
323 |
324 | return true;
325 | }
326 | }
327 |
328 | /**
329 | * Finds the path to the file where the class is defined.
330 | *
331 | * @param string $class The name of the class
332 | *
333 | * @return string|false The path if found, false otherwise
334 | */
335 | public function findFile($class)
336 | {
337 | // class map lookup
338 | if (isset($this->classMap[$class])) {
339 | return $this->classMap[$class];
340 | }
341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342 | return false;
343 | }
344 | if (null !== $this->apcuPrefix) {
345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346 | if ($hit) {
347 | return $file;
348 | }
349 | }
350 |
351 | $file = $this->findFileWithExtension($class, '.php');
352 |
353 | // Search for Hack files if we are running on HHVM
354 | if (false === $file && defined('HHVM_VERSION')) {
355 | $file = $this->findFileWithExtension($class, '.hh');
356 | }
357 |
358 | if (null !== $this->apcuPrefix) {
359 | apcu_add($this->apcuPrefix.$class, $file);
360 | }
361 |
362 | if (false === $file) {
363 | // Remember that this class does not exist.
364 | $this->missingClasses[$class] = true;
365 | }
366 |
367 | return $file;
368 | }
369 |
370 | private function findFileWithExtension($class, $ext)
371 | {
372 | // PSR-4 lookup
373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374 |
375 | $first = $class[0];
376 | if (isset($this->prefixLengthsPsr4[$first])) {
377 | $subPath = $class;
378 | while (false !== $lastPos = strrpos($subPath, '\\')) {
379 | $subPath = substr($subPath, 0, $lastPos);
380 | $search = $subPath . '\\';
381 | if (isset($this->prefixDirsPsr4[$search])) {
382 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
383 | foreach ($this->prefixDirsPsr4[$search] as $dir) {
384 | if (file_exists($file = $dir . $pathEnd)) {
385 | return $file;
386 | }
387 | }
388 | }
389 | }
390 | }
391 |
392 | // PSR-4 fallback dirs
393 | foreach ($this->fallbackDirsPsr4 as $dir) {
394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395 | return $file;
396 | }
397 | }
398 |
399 | // PSR-0 lookup
400 | if (false !== $pos = strrpos($class, '\\')) {
401 | // namespaced class name
402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404 | } else {
405 | // PEAR-like class name
406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407 | }
408 |
409 | if (isset($this->prefixesPsr0[$first])) {
410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411 | if (0 === strpos($class, $prefix)) {
412 | foreach ($dirs as $dir) {
413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414 | return $file;
415 | }
416 | }
417 | }
418 | }
419 | }
420 |
421 | // PSR-0 fallback dirs
422 | foreach ($this->fallbackDirsPsr0 as $dir) {
423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424 | return $file;
425 | }
426 | }
427 |
428 | // PSR-0 include paths.
429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430 | return $file;
431 | }
432 |
433 | return false;
434 | }
435 | }
436 |
437 | /**
438 | * Scope isolated include.
439 | *
440 | * Prevents access to $this/self from included files.
441 | */
442 | function includeFile($file)
443 | {
444 | include $file;
445 | }
446 |
--------------------------------------------------------------------------------
/vendor/dg/twitter-php/src/OAuth.php:
--------------------------------------------------------------------------------
1 | key = $key;
49 | $this->secret = $secret;
50 | }
51 |
52 |
53 | public function __toString(): string
54 | {
55 | return "OAuthConsumer[key=$this->key,secret=$this->secret]";
56 | }
57 | }
58 |
59 |
60 | class Token
61 | {
62 | // access tokens and request tokens
63 | public $key;
64 | public $secret;
65 |
66 |
67 | /**
68 | * key = the token
69 | * secret = the token secret
70 | */
71 | public function __construct(string $key, string $secret)
72 | {
73 | $this->key = $key;
74 | $this->secret = $secret;
75 | }
76 |
77 |
78 | /**
79 | * generates the basic string serialization of a token that a server
80 | * would respond to request_token and access_token calls with
81 | */
82 | public function to_string(): string
83 | {
84 | return 'oauth_token=' .
85 | Util::urlencode_rfc3986($this->key) .
86 | '&oauth_token_secret=' .
87 | Util::urlencode_rfc3986($this->secret);
88 | }
89 |
90 |
91 | public function __toString(): string
92 | {
93 | return $this->to_string();
94 | }
95 | }
96 |
97 |
98 | /**
99 | * A class for implementing a Signature Method
100 | * See section 9 ("Signing Requests") in the spec
101 | */
102 | abstract class SignatureMethod
103 | {
104 | /**
105 | * Needs to return the name of the Signature Method (ie HMAC-SHA1)
106 | */
107 | abstract public function get_name(): string;
108 |
109 |
110 | /**
111 | * Build up the signature
112 | * NOTE: The output of this function MUST NOT be urlencoded.
113 | * the encoding is handled in OAuthRequest when the final
114 | * request is serialized
115 | */
116 | abstract public function build_signature(Request $request, Consumer $consumer, ?Token $token): string;
117 |
118 |
119 | /**
120 | * Verifies that a given signature is correct
121 | */
122 | public function check_signature(Request $request, Consumer $consumer, Token $token, string $signature): bool
123 | {
124 | $built = $this->build_signature($request, $consumer, $token);
125 | return $built == $signature;
126 | }
127 | }
128 |
129 |
130 | /**
131 | * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
132 | * where the Signature Base String is the text and the key is the concatenated values (each first
133 | * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
134 | * character (ASCII code 38) even if empty.
135 | * - Chapter 9.2 ("HMAC-SHA1")
136 | */
137 | class SignatureMethod_HMAC_SHA1 extends SignatureMethod
138 | {
139 | public function get_name(): string
140 | {
141 | return 'HMAC-SHA1';
142 | }
143 |
144 |
145 | public function build_signature(Request $request, Consumer $consumer, ?Token $token): string
146 | {
147 | $base_string = $request->get_signature_base_string();
148 | $request->base_string = $base_string;
149 |
150 | $key_parts = [
151 | $consumer->secret,
152 | $token ? $token->secret : '',
153 | ];
154 |
155 | $key_parts = Util::urlencode_rfc3986($key_parts);
156 | $key = implode('&', $key_parts);
157 |
158 | return base64_encode(hash_hmac('sha1', $base_string, $key, true));
159 | }
160 | }
161 |
162 |
163 | /**
164 | * The PLAINTEXT method does not provide any security protection and SHOULD only be used
165 | * over a secure channel such as HTTPS. It does not use the Signature Base String.
166 | * - Chapter 9.4 ("PLAINTEXT")
167 | */
168 | class SignatureMethod_PLAINTEXT extends SignatureMethod
169 | {
170 | public function get_name(): string
171 | {
172 | return 'PLAINTEXT';
173 | }
174 |
175 |
176 | /**
177 | * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
178 | * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
179 | * empty. The result MUST be encoded again.
180 | * - Chapter 9.4.1 ("Generating Signatures")
181 | *
182 | * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
183 | * OAuthRequest handles this!
184 | */
185 | public function build_signature(Request $request, Consumer $consumer, ?Token $token): string
186 | {
187 | $key_parts = [
188 | $consumer->secret,
189 | $token ? $token->secret : '',
190 | ];
191 |
192 | $key_parts = Util::urlencode_rfc3986($key_parts);
193 | $key = implode('&', $key_parts);
194 | $request->base_string = $key;
195 |
196 | return $key;
197 | }
198 | }
199 |
200 |
201 | /**
202 | * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
203 | * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
204 | * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
205 | * verified way to the Service Provider, in a manner which is beyond the scope of this
206 | * specification.
207 | * - Chapter 9.3 ("RSA-SHA1")
208 | */
209 | abstract class SignatureMethod_RSA_SHA1 extends SignatureMethod
210 | {
211 | public function get_name(): string
212 | {
213 | return 'RSA-SHA1';
214 | }
215 |
216 |
217 | /**
218 | * Up to the SP to implement this lookup of keys. Possible ideas are:
219 | * (1) do a lookup in a table of trusted certs keyed off of consumer
220 | * (2) fetch via http using a url provided by the requester
221 | * (3) some sort of specific discovery code based on request
222 | *
223 | * Either way should return a string representation of the certificate
224 | */
225 | abstract protected function fetch_public_cert(&$request);
226 |
227 |
228 | /**
229 | * Up to the SP to implement this lookup of keys. Possible ideas are:
230 | * (1) do a lookup in a table of trusted certs keyed off of consumer
231 | *
232 | * Either way should return a string representation of the certificate
233 | */
234 | abstract protected function fetch_private_cert(&$request);
235 |
236 |
237 | public function build_signature(Request $request, Consumer $consumer, ?Token $token): string
238 | {
239 | $base_string = $request->get_signature_base_string();
240 | $request->base_string = $base_string;
241 |
242 | // Fetch the private key cert based on the request
243 | $cert = $this->fetch_private_cert($request);
244 |
245 | // Pull the private key ID from the certificate
246 | $privatekeyid = openssl_get_privatekey($cert);
247 |
248 | // Sign using the key
249 | $ok = openssl_sign($base_string, $signature, $privatekeyid);
250 |
251 | // Release the key resource
252 | openssl_free_key($privatekeyid);
253 |
254 | return base64_encode($signature);
255 | }
256 |
257 |
258 | public function check_signature(Request $request, Consumer $consumer, Token $token, string $signature): bool
259 | {
260 | $decoded_sig = base64_decode($signature, true);
261 |
262 | $base_string = $request->get_signature_base_string();
263 |
264 | // Fetch the public key cert based on the request
265 | $cert = $this->fetch_public_cert($request);
266 |
267 | // Pull the public key ID from the certificate
268 | $publickeyid = openssl_get_publickey($cert);
269 |
270 | // Check the computed signature against the one passed in the query
271 | $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
272 |
273 | // Release the key resource
274 | openssl_free_key($publickeyid);
275 |
276 | return $ok == 1;
277 | }
278 | }
279 |
280 |
281 | class Request
282 | {
283 | // for debug purposes
284 | public $base_string;
285 | public static $version = '1.0';
286 | public static $POST_INPUT = 'php://input';
287 | protected $parameters;
288 | protected $http_method;
289 | protected $http_url;
290 |
291 |
292 | public function __construct(string $http_method, string $http_url, array $parameters = null)
293 | {
294 | $parameters = $parameters ?: [];
295 | $parameters = array_merge(Util::parse_parameters((string) parse_url($http_url, PHP_URL_QUERY)), $parameters);
296 | $this->parameters = $parameters;
297 | $this->http_method = $http_method;
298 | $this->http_url = $http_url;
299 | }
300 |
301 |
302 | /**
303 | * attempt to build up a request from what was passed to the server
304 | */
305 | public static function from_request(string $http_method = null, string $http_url = null, array $parameters = null): self
306 | {
307 | $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on')
308 | ? 'http'
309 | : 'https';
310 | $http_url = ($http_url) ? $http_url : $scheme .
311 | '://' . $_SERVER['HTTP_HOST'] .
312 | ':' .
313 | $_SERVER['SERVER_PORT'] .
314 | $_SERVER['REQUEST_URI'];
315 | $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
316 |
317 | // We weren't handed any parameters, so let's find the ones relevant to
318 | // this request.
319 | // If you run XML-RPC or similar you should use this to provide your own
320 | // parsed parameter-list
321 | if (!$parameters) {
322 | // Find request headers
323 | $request_headers = Util::get_headers();
324 |
325 | // Parse the query-string to find GET parameters
326 | $parameters = Util::parse_parameters($_SERVER['QUERY_STRING']);
327 |
328 | // It's a POST request of the proper content-type, so parse POST
329 | // parameters and add those overriding any duplicates from GET
330 | if ($http_method == 'POST'
331 | && isset($request_headers['Content-Type'])
332 | && strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')
333 | ) {
334 | $post_data = Util::parse_parameters(
335 | file_get_contents(self::$POST_INPUT)
336 | );
337 | $parameters = array_merge($parameters, $post_data);
338 | }
339 |
340 | // We have a Authorization-header with OAuth data. Parse the header
341 | // and add those overriding any duplicates from GET or POST
342 | if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
343 | $header_parameters = Util::split_header(
344 | $request_headers['Authorization']
345 | );
346 | $parameters = array_merge($parameters, $header_parameters);
347 | }
348 | }
349 |
350 | return new self($http_method, $http_url, $parameters);
351 | }
352 |
353 |
354 | /**
355 | * pretty much a helper function to set up the request
356 | */
357 | public static function from_consumer_and_token(Consumer $consumer, ?Token $token, string $http_method, string $http_url, array $parameters = null): self
358 | {
359 | $parameters = $parameters ?: [];
360 | $defaults = [
361 | 'oauth_version' => self::$version,
362 | 'oauth_nonce' => self::generate_nonce(),
363 | 'oauth_timestamp' => self::generate_timestamp(),
364 | 'oauth_consumer_key' => $consumer->key,
365 | ];
366 | if ($token) {
367 | $defaults['oauth_token'] = $token->key;
368 | }
369 |
370 | $parameters = array_merge($defaults, $parameters);
371 |
372 | return new self($http_method, $http_url, $parameters);
373 | }
374 |
375 |
376 | public function set_parameter(string $name, $value, bool $allow_duplicates = true): void
377 | {
378 | if ($allow_duplicates && isset($this->parameters[$name])) {
379 | // We have already added parameter(s) with this name, so add to the list
380 | if (is_scalar($this->parameters[$name])) {
381 | // This is the first duplicate, so transform scalar (string)
382 | // into an array so we can add the duplicates
383 | $this->parameters[$name] = [$this->parameters[$name]];
384 | }
385 |
386 | $this->parameters[$name][] = $value;
387 | } else {
388 | $this->parameters[$name] = $value;
389 | }
390 | }
391 |
392 |
393 | public function get_parameter(string $name)
394 | {
395 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
396 | }
397 |
398 |
399 | public function get_parameters(): array
400 | {
401 | return $this->parameters;
402 | }
403 |
404 |
405 | public function unset_parameter(string $name): void
406 | {
407 | unset($this->parameters[$name]);
408 | }
409 |
410 |
411 | /**
412 | * The request parameters, sorted and concatenated into a normalized string.
413 | */
414 | public function get_signable_parameters(): string
415 | {
416 | // Grab all parameters
417 | $params = $this->parameters;
418 |
419 | // Remove oauth_signature if present
420 | // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
421 | if (isset($params['oauth_signature'])) {
422 | unset($params['oauth_signature']);
423 | }
424 |
425 | return Util::build_http_query($params);
426 | }
427 |
428 |
429 | /**
430 | * Returns the base string of this request
431 | *
432 | * The base string defined as the method, the url
433 | * and the parameters (normalized), each urlencoded
434 | * and the concated with &.
435 | */
436 | public function get_signature_base_string(): string
437 | {
438 | $parts = [
439 | $this->get_normalized_http_method(),
440 | $this->get_normalized_http_url(),
441 | $this->get_signable_parameters(),
442 | ];
443 |
444 | $parts = Util::urlencode_rfc3986($parts);
445 |
446 | return implode('&', $parts);
447 | }
448 |
449 |
450 | /**
451 | * just uppercases the http method
452 | */
453 | public function get_normalized_http_method(): string
454 | {
455 | return strtoupper($this->http_method);
456 | }
457 |
458 |
459 | /**
460 | * parses the url and rebuilds it to be
461 | * scheme://host/path
462 | */
463 | public function get_normalized_http_url(): string
464 | {
465 | $parts = parse_url($this->http_url);
466 |
467 | $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
468 | $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
469 | $host = (isset($parts['host'])) ? $parts['host'] : '';
470 | $path = (isset($parts['path'])) ? $parts['path'] : '';
471 |
472 | if (($scheme == 'https' && $port != '443')
473 | || ($scheme == 'http' && $port != '80')) {
474 | $host = "$host:$port";
475 | }
476 | return "$scheme://$host$path";
477 | }
478 |
479 |
480 | /**
481 | * builds a url usable for a GET request
482 | */
483 | public function to_url(): string
484 | {
485 | $post_data = $this->to_postdata();
486 | $out = $this->get_normalized_http_url();
487 | if ($post_data) {
488 | $out .= '?' . $post_data;
489 | }
490 | return $out;
491 | }
492 |
493 |
494 | /**
495 | * builds the data one would send in a POST request
496 | */
497 | public function to_postdata(): string
498 | {
499 | return Util::build_http_query($this->parameters);
500 | }
501 |
502 |
503 | /**
504 | * builds the Authorization: header
505 | */
506 | public function to_header(string $realm = null): string
507 | {
508 | $first = true;
509 | if ($realm) {
510 | $out = 'Authorization: OAuth realm="' . Util::urlencode_rfc3986($realm) . '"';
511 | $first = false;
512 | } else {
513 | $out = 'Authorization: OAuth';
514 | }
515 |
516 | $total = [];
517 | foreach ($this->parameters as $k => $v) {
518 | if (substr($k, 0, 5) != 'oauth') {
519 | continue;
520 | }
521 | if (is_array($v)) {
522 | throw new Exception('Arrays not supported in headers');
523 | }
524 | $out .= $first ? ' ' : ',';
525 | $out .= Util::urlencode_rfc3986($k) . '="' . Util::urlencode_rfc3986($v) . '"';
526 | $first = false;
527 | }
528 | return $out;
529 | }
530 |
531 |
532 | public function __toString(): string
533 | {
534 | return $this->to_url();
535 | }
536 |
537 |
538 | public function sign_request(SignatureMethod $signature_method, Consumer $consumer, ?Token $token)
539 | {
540 | $this->set_parameter(
541 | 'oauth_signature_method',
542 | $signature_method->get_name(),
543 | false
544 | );
545 | $signature = $this->build_signature($signature_method, $consumer, $token);
546 | $this->set_parameter('oauth_signature', $signature, false);
547 | }
548 |
549 |
550 | public function build_signature(SignatureMethod $signature_method, Consumer $consumer, ?Token $token)
551 | {
552 | $signature = $signature_method->build_signature($this, $consumer, $token);
553 | return $signature;
554 | }
555 |
556 |
557 | /**
558 | * util function: current timestamp
559 | */
560 | private static function generate_timestamp(): int
561 | {
562 | return time();
563 | }
564 |
565 |
566 | /**
567 | * util function: current nonce
568 | */
569 | private static function generate_nonce(): string
570 | {
571 | $mt = microtime();
572 | $rand = mt_rand();
573 |
574 | return md5($mt . $rand); // md5s look nicer than numbers
575 | }
576 | }
577 |
578 |
579 | class Util
580 | {
581 | public static function urlencode_rfc3986($input)
582 | {
583 | if (is_array($input)) {
584 | return array_map([__CLASS__, 'urlencode_rfc3986'], $input);
585 | } elseif (is_scalar($input)) {
586 | return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode((string) $input)));
587 | } else {
588 | return '';
589 | }
590 | }
591 |
592 |
593 | /**
594 | * This decode function isn't taking into consideration the above
595 | * modifications to the encoding process. However, this method doesn't
596 | * seem to be used anywhere so leaving it as is.
597 | */
598 | public static function urldecode_rfc3986(string $string): string
599 | {
600 | return urldecode($string);
601 | }
602 |
603 |
604 | /**
605 | * Utility function for turning the Authorization: header into
606 | * parameters, has to do some unescaping
607 | * Can filter out any non-oauth parameters if needed (default behaviour)
608 | */
609 | public static function split_header(string $header, bool $only_allow_oauth_parameters = true): array
610 | {
611 | $params = [];
612 | if (preg_match_all('/(' . ($only_allow_oauth_parameters ? 'oauth_' : '') . '[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
613 | foreach ($matches[1] as $i => $h) {
614 | $params[$h] = self::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
615 | }
616 | if (isset($params['realm'])) {
617 | unset($params['realm']);
618 | }
619 | }
620 | return $params;
621 | }
622 |
623 |
624 | /**
625 | * helper to try to sort out headers for people who aren't running apache
626 | */
627 | public static function get_headers(): array
628 | {
629 | if (function_exists('apache_request_headers')) {
630 | // we need this to get the actual Authorization: header
631 | // because apache tends to tell us it doesn't exist
632 | $headers = apache_request_headers();
633 |
634 | // sanitize the output of apache_request_headers because
635 | // we always want the keys to be Cased-Like-This and arh()
636 | // returns the headers in the same case as they are in the
637 | // request
638 | $out = [];
639 | foreach ($headers as $key => $value) {
640 | $key = str_replace(
641 | ' ',
642 | '-',
643 | ucwords(strtolower(str_replace('-', ' ', $key)))
644 | );
645 | $out[$key] = $value;
646 | }
647 | } else {
648 | // otherwise we don't have apache and are just going to have to hope
649 | // that $_SERVER actually contains what we need
650 | $out = [];
651 | if (isset($_SERVER['CONTENT_TYPE'])) {
652 | $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
653 | }
654 | if (isset($_ENV['CONTENT_TYPE'])) {
655 | $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
656 | }
657 |
658 | foreach ($_SERVER as $key => $value) {
659 | if (substr($key, 0, 5) == 'HTTP_') {
660 | // this is chaos, basically it is just there to capitalize the first
661 | // letter of every word that is not an initial HTTP and strip HTTP
662 | // code from przemek
663 | $key = str_replace(
664 | ' ',
665 | '-',
666 | ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))
667 | );
668 | $out[$key] = $value;
669 | }
670 | }
671 | }
672 | return $out;
673 | }
674 |
675 |
676 | /**
677 | * This function takes a input like a=b&a=c&d=e and returns the parsed parameters like this
678 | * ['a' => array('b','c'), 'd' => 'e']
679 | */
680 | public static function parse_parameters(string $input): array
681 | {
682 | if (!isset($input) || !$input) {
683 | return [];
684 | }
685 |
686 | $pairs = explode('&', $input);
687 |
688 | $parsed_parameters = [];
689 | foreach ($pairs as $pair) {
690 | $split = explode('=', $pair, 2);
691 | $parameter = self::urldecode_rfc3986($split[0]);
692 | $value = isset($split[1]) ? self::urldecode_rfc3986($split[1]) : '';
693 |
694 | if (isset($parsed_parameters[$parameter])) {
695 | // We have already recieved parameter(s) with this name, so add to the list
696 | // of parameters with this name
697 |
698 | if (is_scalar($parsed_parameters[$parameter])) {
699 | // This is the first duplicate, so transform scalar (string) into an array
700 | // so we can add the duplicates
701 | $parsed_parameters[$parameter] = [$parsed_parameters[$parameter]];
702 | }
703 |
704 | $parsed_parameters[$parameter][] = $value;
705 | } else {
706 | $parsed_parameters[$parameter] = $value;
707 | }
708 | }
709 | return $parsed_parameters;
710 | }
711 |
712 |
713 | public static function build_http_query(array $params): string
714 | {
715 | if (!$params) {
716 | return '';
717 | }
718 |
719 | // Urlencode both keys and values
720 | $keys = self::urlencode_rfc3986(array_keys($params));
721 | $values = self::urlencode_rfc3986(array_values($params));
722 | $params = array_combine($keys, $values);
723 |
724 | // Parameters are sorted by name, using lexicographical byte value ordering.
725 | // Ref: Spec: 9.1.1 (1)
726 | uksort($params, 'strcmp');
727 |
728 | $pairs = [];
729 | foreach ($params as $parameter => $value) {
730 | if (is_array($value)) {
731 | // If two or more parameters share the same name, they are sorted by their value
732 | // Ref: Spec: 9.1.1 (1)
733 | // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
734 | sort($value, SORT_STRING);
735 | foreach ($value as $duplicate_value) {
736 | $pairs[] = $parameter . '=' . $duplicate_value;
737 | }
738 | } else {
739 | $pairs[] = $parameter . '=' . $value;
740 | }
741 | }
742 | // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
743 | // Each name-value pair is separated by an '&' character (ASCII code 38)
744 | return implode('&', $pairs);
745 | }
746 | }
747 |
--------------------------------------------------------------------------------