Passed
Push — develop ( bb4de3...f6c43e )
by Benjamin
06:52
created

Api::populateTweetFromData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 2
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link      https://dukt.net/twitter/
4
 * @copyright Copyright (c) 2018, Dukt
5
 * @license   https://github.com/dukt/twitter/blob/master/LICENSE.md
6
 */
7
8
namespace dukt\twitter\services;
9
10
use Craft;
11
use craft\helpers\FileHelper;
12
use dukt\twitter\helpers\TwitterHelper;
13
use dukt\twitter\models\Tweet;
14
use dukt\twitter\Plugin;
15
use League\OAuth1\Client\Credentials\TokenCredentials;
16
use yii\base\Component;
17
use GuzzleHttp\Client;
18
use GuzzleHttp\HandlerStack;
19
use GuzzleHttp\Subscriber\Oauth\Oauth1;
20
use DateTime;
21
use GuzzleHttp\Exception\GuzzleException;
22
23
/**
24
 * Api Service
25
 *
26
 * @author Dukt <[email protected]>
27
 * @since  3.0
28
 */
29
class Api extends Component
30
{
31
    // Public Methods
32
    // =========================================================================
33
34
    /**
35
     * Performs a get request on the Twitter API.
36
     *
37
     * @param string     $uri
38
     * @param array|null $query
39
     * @param array|null $headers
40
     * @param array      $options
41
     * @param bool|null  $enableCache
42
     * @param null|int   $cacheExpire
43
     *
44
     * @return array|null
45
     * @throws GuzzleException
46
     */
47
    public function get($uri, array $query = null, array $headers = null, array $options = [], $enableCache = null, $cacheExpire = null)
48
    {
49
        // Add query to the request’s options
50
51
        if ($query) {
52
            $options['query'] = $query;
53
        }
54
55
        // Enable Cache
56
57
        if (null === $enableCache) {
58
            $enableCache = Plugin::getInstance()->getSettings()->enableCache;
59
        }
60
61
62
        // Try to get response from cache
63
64
        if ($enableCache) {
65
            $response = Plugin::getInstance()->getCache()->get([$uri, $headers, $options]);
66
67
            if ($response) {
68
                return $response;
69
            }
70
        }
71
72
73
        // Otherwise request the API
74
75
        $client = $this->getClient();
76
77
        $url = $uri.'.json';
78
79
        if ($headers) {
80
            $options['headers'] = $headers;
81
        }
82
83
        $response = $client->request('GET', $url, $options);
84
85
        $jsonResponse = json_decode($response->getBody(), true);
86
87
        if ($enableCache) {
88
            Plugin::getInstance()->getCache()->set([$uri, $headers, $options], $jsonResponse, $cacheExpire);
0 ignored issues
show
Bug introduced by
It seems like $cacheExpire can also be of type integer; however, parameter $expire of dukt\twitter\services\Cache::set() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

88
            Plugin::getInstance()->getCache()->set([$uri, $headers, $options], $jsonResponse, /** @scrutinizer ignore-type */ $cacheExpire);
Loading history...
89
        }
90
91
        return $jsonResponse;
92
    }
93
94
    /**
95
     * Returns a tweet by its URL or ID.
96
     *
97
     * @param string|int $urlOrId
98
     * @param array|null $query
99
     *
100
     * @return Tweet|null
101
     * @throws GuzzleException
102
     * @throws \yii\base\Exception
103
     */
104
    public function getTweet($urlOrId, array $query = null)
105
    {
106
        $tweetId = TwitterHelper::extractTweetId($urlOrId);
107
108
        if ($tweetId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tweetId of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
109
            return $this->getTweetById($tweetId, $query);
110
        }
111
112
        return null;
113
    }
114
115
    /**
116
     * Returns a user by their ID.
117
     *
118
     * @param int|string $userId
119
     * @param array      $query
120
     *
121
     * @return array|null
122
     * @throws GuzzleException
123
     */
124
    public function getUserById($userId, $query = [])
125
    {
126
        $userId = (int)$userId;
127
128
        $query = array_merge($query, ['user_id' => $userId]);
129
130
        return $this->get('users/show', $query);
131
    }
132
133
    /**
134
     * Populate tweet object from data array.
135
     *
136
     * @param Tweet $tweet
137
     * @param array $data
138
     *
139
     * @return Tweet
140
     */
141
    public function populateTweetFromData(Tweet $tweet, array $data): Tweet
142
    {
143
        if (isset($data['created_at'])) {
144
            $tweet->createdAt = DateTime::createFromFormat('D M d H:i:s O Y', $data['created_at']);
0 ignored issues
show
Documentation Bug introduced by
It seems like DateTime::createFromForm...', $data['created_at']) of type false or DateTime is incompatible with the declared type dukt\twitter\models\The of property $createdAt.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
145
        }
146
147
        $tweet->data = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type array is incompatible with the declared type dukt\twitter\models\Raw of property $data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
148
        $tweet->text = $data['full_text'] ?? null;
149
        $tweet->remoteId = $data['id'] ?? null;
150
        $tweet->remoteUserId = $data['user']['id'] ?? null;
151
        $tweet->username = $data['user']['name'] ?? null;
152
        $tweet->userProfileRemoteImageSecureUrl = $data['user']['profile_image_url_https'] ?? null;
153
        $tweet->userProfileRemoteImageUrl = $data['user']['profile_image_url'] ?? null;
154
        $tweet->userScreenName = $data['user']['screen_name'] ?? null;
155
156
        return $tweet;
157
    }
158
159
    /**
160
     * Saves the original user profile image for a twitter user ID
161
     *
162
     * @param $userId
163
     * @param $remoteImageUrl
164
     *
165
     * @return string|null
166
     * @throws GuzzleException
167
     * @throws \yii\base\Exception
168
     */
169
    public function saveOriginalUserProfileImage($userId, $remoteImageUrl)
170
    {
171
        if (!$userId || !$remoteImageUrl) {
172
            return null;
173
        }
174
175
        $originalFolderPath = Craft::$app->path->getRuntimePath().'/twitter/userimages/'.$userId.'/original/';
176
177
        if (!is_dir($originalFolderPath)) {
178
            FileHelper::createDirectory($originalFolderPath);
179
        }
180
181
        $files = FileHelper::findFiles($originalFolderPath);
182
183
        if (count($files) > 0) {
184
            $imagePath = $files[0];
185
186
            return $imagePath;
187
        }
188
189
        $remoteImageUrl = str_replace('_normal', '', $remoteImageUrl);
190
        $fileName = pathinfo($remoteImageUrl, PATHINFO_BASENAME);
191
        $imagePath = $originalFolderPath.$fileName;
192
193
        $client = new Client();
194
        $response = $client->request('GET', $remoteImageUrl, [
195
            'save_to' => $imagePath
196
        ]);
197
198
        if (!$response->getStatusCode() != 200) {
199
            return null;
200
        }
201
202
        return $imagePath;
203
    }
204
205
    // Private Methods
206
    // =========================================================================
207
208
    /**
209
     * Get the authenticated client
210
     *
211
     * @return Client
212
     */
213
    private function getClient()
214
    {
215
        $options = [
216
            'base_uri' => 'https://api.twitter.com/1.1/'
217
        ];
218
219
        $token = Plugin::getInstance()->getOauth()->getToken();
220
221
        if ($token) {
222
            $stack = $this->getStack($token);
223
224
            $options['auth'] = 'oauth';
225
            $options['handler'] = $stack;
226
        }
227
228
        return new Client($options);
229
    }
230
231
    /**
232
     * Get stack
233
     *
234
     * @param TokenCredentials $token
235
     *
236
     * @return HandlerStack
237
     */
238
    private function getStack(TokenCredentials $token)
239
    {
240
        $stack = HandlerStack::create();
241
242
        $oauthConsumerKey = Plugin::getInstance()->getConsumerKey();
243
        $oauthConsumerSecret = Plugin::getInstance()->getConsumerSecret();
244
245
        $middleware = new Oauth1([
246
            'consumer_key' => $oauthConsumerKey,
247
            'consumer_secret' => $oauthConsumerSecret,
248
            'token' => $token->getIdentifier(),
249
            'token_secret' => $token->getSecret(),
250
            'signature_method' => 'HMAC-SHA1'
251
        ]);
252
253
        $stack->push($middleware);
254
255
        return $stack;
256
    }
257
258
    /**
259
     * Returns a tweet by its ID.
260
     *
261
     * @param            $tweetId
262
     * @param array|null $query
263
     *
264
     * @return Tweet|null
265
     * @throws GuzzleException
266
     * @throws \yii\base\Exception
267
     */
268
    private function getTweetById($tweetId, array $query = null)
269
    {
270
        $tweetId = (int)$tweetId;
271
272
        if(!$query) {
273
            $query = [];
274
        }
275
276
        $query = array_merge($query, [
277
            'id' => $tweetId,
278
            'tweet_mode' => 'extended'
279
        ]);
280
281
        $data = $this->get('statuses/show', $query);
282
283
        if (!$data) {
284
            return null;
285
        }
286
287
        $tweet = new Tweet();
288
        $this->populateTweetFromData($tweet, $data);
289
290
        // generate user profile image
291
        $this->saveOriginalUserProfileImage($tweet->remoteUserId, $tweet->userProfileRemoteImageSecureUrl);
292
293
        return $tweet;
294
    }
295
}