Completed
Push — master ( af83f4...b4262b )
by Sergey
03:54 queued 01:20
created

Request::getContentTypeHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 14
rs 9.4285
cc 2
eloc 8
nc 2
nop 0
1
<?php
2
3
namespace seregazhuk\PinterestBot\Api;
4
5
use seregazhuk\PinterestBot\Helpers\UrlHelper;
6
use seregazhuk\PinterestBot\Helpers\FileHelper;
7
use seregazhuk\PinterestBot\Helpers\CsrfHelper;
8
use seregazhuk\PinterestBot\Contracts\HttpInterface;
9
use seregazhuk\PinterestBot\Exceptions\AuthException;
10
use seregazhuk\PinterestBot\Contracts\RequestInterface;
11
12
/**
13
 * Class Request.
14
 *
15
 * @property resource $ch
16
 * @property bool     $loggedIn
17
 * @property string   $userAgent
18
 * @property string   $csrfToken
19
 * @property string   $cookieJar
20
 */
21
class Request implements RequestInterface
22
{
23
    const COOKIE_NAME = 'pinterest_cookie';
24
25
    protected $userAgent = 'Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0';
26
    /**
27
     * @var HttpInterface
28
     */
29
    protected $http;
30
    protected $loggedIn;
31
    protected $cookieJar;
32
    protected $options;
33
34
    /**
35
     *
36
     * @var string
37
     */
38
    protected $filePathToUpload;
39
40
    public $csrfToken = '';
41
42
    /**
43
     * Common headers needed for every query.
44
     *
45
     * @var array
46
     */
47
    protected $requestHeaders = [
48
        'Accept: application/json, text/javascript, */*; q=0.01',
49
        'Accept-Language: en-US,en;q=0.5',
50
        'DNT: 1',
51
        'Host: nl.pinterest.com',
52
        'X-Pinterest-AppState: active',
53
        'X-NEW-APP: 1',
54
        'X-APP-VERSION: 04cf8cc',
55
        'X-Requested-With: XMLHttpRequest',
56
    ];
57
58
    /**
59
     * @var string
60
     */
61
    protected $postFileData;
62
63
    /**
64
     * @param HttpInterface $http
65
     */
66
    public function __construct(HttpInterface $http)
67
    {
68
        $this->http = $http;
69
        $this->loggedIn = false;
70
        $this->cookieJar = tempnam(sys_get_temp_dir(), self::COOKIE_NAME);
71
    }
72
73
    /**
74
     * Executes api call for follow or unfollow user.
75
     *
76
     * @param int    $entityId
77
     * @param string $entityName
78
     * @param string $url
79
     *
80
     * @return array
81
     */
82
    public function followMethodCall($entityId, $entityName, $url)
83
    {
84
        $dataJson = [
85
            'options' => [
86
                $entityName => (string)$entityId,
87
            ],
88
            'context' => [],
89
        ];
90
91
        if ($entityName == 'interest_id') {
92
            $dataJson['options']['interest_list'] = 'favorited';
93
        }
94
95
        $post = ['data' => json_encode($dataJson, JSON_FORCE_OBJECT)];
96
        $postString = UrlHelper::buildRequestString($post);
97
98
        return $this->exec($url, $postString);
99
    }
100
101
    public function upload($pathToFile, $url)
102
    {
103
        $this->filePathToUpload = $pathToFile;
104
105
        return $this->exec($url);
106
    }
107
108
    /**
109
     * Executes request to Pinterest API.
110
     *
111
     * @param string $resourceUrl
112
     * @param string $postString
113
     *
114
     * @return array
115
     */
116
    public function exec($resourceUrl, $postString = '')
117
    {
118
        $url = UrlHelper::buildApiUrl($resourceUrl);        
119
        $this->makeHttpOptions($postString);
120
        $res = $this->http->execute($url, $this->options);
121
122
        $this->filePathToUpload = null;
123
        return json_decode($res, true);
124
    }
125
126
    /**
127
     * Adds necessary curl options for query.
128
     *
129
     * @param string $postString POST query string
130
     *
131
     * @return $this
132
     */
133
    protected function makeHttpOptions($postString = '')
134
    {
135
        $this->setDefaultHttpOptions();
136
137
        if ($this->csrfToken == CsrfHelper::DEFAULT_TOKEN) {
138
            $this->options = $this->addDefaultCsrfInfo($this->options);
139
        }
140
141
        if (!empty($postString) || $this->filePathToUpload) {
142
            $this->options[CURLOPT_POST] = true;
143
            $this->options[CURLOPT_POSTFIELDS] = $this->filePathToUpload ? $this->postFileData : $postString;
144
        }
145
146
        return $this;
147
    }
148
149
    /**
150
     * @return array
151
     */
152
    protected function setDefaultHttpOptions()
153
    {
154
        $this->options = [
155
            CURLOPT_USERAGENT      => $this->userAgent,
156
            CURLOPT_RETURNTRANSFER => true,
157
            CURLOPT_SSL_VERIFYPEER => false,
158
            CURLOPT_FOLLOWLOCATION => true,
159
            CURLOPT_ENCODING       => 'gzip,deflate',
160
            CURLOPT_HTTPHEADER     => $this->getDefaultHttpHeaders(),
161
            CURLOPT_REFERER        => UrlHelper::URL_BASE,
162
            CURLOPT_COOKIEFILE     => $this->cookieJar,
163
            CURLOPT_COOKIEJAR      => $this->cookieJar,
164
        ];
165
    }
166
167
    /**
168
     * @param array $options
169
     *
170
     * @return mixed
171
     */
172
    protected function addDefaultCsrfInfo($options)
173
    {
174
        $options[CURLOPT_REFERER] = UrlHelper::URL_BASE;
175
        $options[CURLOPT_HTTPHEADER][] = CsrfHelper::getDefaultCookie();
176
177
        return $options;
178
    }
179
    
180
    /**
181
     * Clear token information.
182
     *
183
     * @return $this
184
     */
185
    public function clearToken()
186
    {
187
        $this->csrfToken = CsrfHelper::DEFAULT_TOKEN;
188
189
        return $this;
190
    }
191
192
    /**
193
     * Mark api as logged.
194
     * @return $this
195
     * @throws AuthException
196
     */
197
    public function login()
198
    {
199
        $this->csrfToken = CsrfHelper::getTokenFromFile($this->cookieJar);
200
        if (empty($this->csrfToken)) {
201
            throw new AuthException('Cannot parse token from cookies.');
202
        }
203
        $this->loggedIn = true;
204
    }
205
206
    public function logout()
207
    {
208
        $this->clearToken();
209
        $this->loggedIn = false;
210
    }
211
212
    /**
213
     * Get log status.
214
     *
215
     * @return bool
216
     */
217
    public function isLoggedIn()
218
    {
219
        return $this->loggedIn;
220
    }
221
222
    /**
223
     * @param $userAgent
224
     * @return $this
225
     */
226
    public function setUserAgent($userAgent)
227
    {
228
        if ($userAgent !== null) {
229
            $this->userAgent = $userAgent;
230
        }
231
232
        return $this;
233
    }
234
235
    /**
236
     * Create request string.
237
     *
238
     * @param array  $data
239
     * @param array  $bookmarks
240
     *
241
     * @return string
242
     */
243
    public static function createQuery(array $data = [], $bookmarks = [])
244
    {
245
        $request = self::createRequestData($data, $bookmarks);
246
247
        return UrlHelper::buildRequestString($request);
248
    }
249
250
    /**
251
     * @param array|object $data
252
     * @param array        $bookmarks
253
     *
254
     * @return array
255
     */
256
    public static function createRequestData(array $data = [], $bookmarks = [])
257
    {
258
        if (empty($data)) {
259
            $data = ['options' => []];
260
        }
261
262
        if (!empty($bookmarks)) {
263
            $data['options']['bookmarks'] = $bookmarks;
264
        }
265
266
        $data['context'] = new \stdClass();
267
268
        return [
269
            'source_url' => '',
270
            'data'       => json_encode($data),
271
        ];
272
    }
273
274
    /**
275
     * @return array
276
     */
277
    protected function getDefaultHttpHeaders()
278
    {
279
        return array_merge(
280
            $this->requestHeaders, $this->getContentTypeHeader(), ['X-CSRFToken: ' . $this->csrfToken]
281
        );
282
    }
283
284
    /**
285
     * If we are uploading file, we should build boundary form data. Otherwise
286
     * it is simple urlencoded form.
287
     *
288
     * @return array
289
     */
290
    protected function getContentTypeHeader()
291
    {
292
        if ($this->filePathToUpload) {
293
            $delimiter = '-------------' . uniqid();
294
            $this->buildFilePostData($delimiter);
295
296
            return [
297
                'Content-Type: multipart/form-data; boundary=' . $delimiter,
298
                'Content-Length: ' . strlen($this->postFileData)
299
            ];
300
        }
301
302
        return ['Content-Type: application/x-www-form-urlencoded; charset=UTF-8;'];
303
    }
304
305
    /**
306
     * @param string $delimiter
307
     * @return $this
308
     */
309
    protected function buildFilePostData($delimiter)
310
    {
311
        $data = "--$delimiter\r\n";
312
        $data .= 'Content-Disposition: form-data; name="img"; filename="' . basename($this->filePathToUpload) . '"' . "\r\n";
313
        $data .= 'Content-Type: ' . FileHelper::getMimeType($this->filePathToUpload) . "\r\n\r\n";
314
        $data .= file_get_contents($this->filePathToUpload) . "\r\n";
315
        $data .= "--$delimiter--\r\n";
316
317
        $this->postFileData = $data;
318
319
        return $this;
320
    }
321
}
322