Passed
Pull Request — master (#13)
by Matthew
03:21
created

Fetcher::setUseLatest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Exception;
6
use SilverStripe\Core\Config\Configurable;
7
use SilverStripe\Core\Extensible;
8
use SilverStripe\Core\Injector\Injectable;
9
10
/**
11
 * Class Importer
12
 * @package Dynamic\Salsify\Model
13
 *
14
 * Based off https://github.com/XinV/salsify-php-api/blob/master/lib/Salsify/API.php
15
 */
16
class Fetcher
17
{
18
    use Configurable;
19
    use Extensible;
20
    use Injectable;
21
22
    /**
23
     * @var string
24
     */
25
    const API_BASE_URL = 'https://app.salsify.com/api/';
26
27
    /**
28
     * @var array
29
     */
30
    protected $config;
31
32
    /**
33
     * @var string
34
     */
35
    protected $apiKey;
36
37
    /**
38
     * @var string
39
     */
40
    protected $channelID;
41
42
    /**
43
     * @var string
44
     */
45
    protected $channelRunID;
46
47
    /**
48
     * @var string
49
     */
50
    protected $channelRunDataUrl;
51
52
    /**
53
     * @var bool
54
     */
55
    protected $useLatest = false;
56
57
    /**
58
     * @var int
59
     */
60
    protected $timeout;
61
62
    /**
63
     * Importer constructor.
64
     * @param string|int $channelID
65
     * @param bool $useLatest
66
     */
67
    public function __construct($config)
68
    {
69
        $this->config = $config;
70
71
        if (array_key_exists('channel', $this->config) && $config['channel']) {
72
            $this->setChannelID($config['channel']);
73
        }
74
75
        if (array_key_exists('useLatest', $this->config) && $config['useLatest']) {
76
            $this->setUseLatest((bool)$config['useLatest']);
77
        }
78
79
        $this->setTimeout();
80
    }
81
82
    /**
83
     * @return string
84
     */
85
    public function getApiKey()
86
    {
87
        if (!$this->apiKey) {
88
            $this->setApiKey();
89
        }
90
91
        if (!$this->apiKey) {
92
            throw new Exception('No api key provided');
93
        }
94
        return $this->apiKey;
95
    }
96
97
    /**
98
     * Sets the api key
99
     */
100
    public function setApiKey()
101
    {
102
        $this->apiKey = $this->config()->get('apiKey');
103
        if (array_key_exists('apiKey', $this->config) && $this->config['apiKey']) {
104
            $this->apiKey = $this->config['apiKey'];
105
        }
106
    }
107
108
    /**
109
     * @return int
110
     */
111
    public function getTimeout()
112
    {
113
        if (!$this->timeout) {
114
            $this->setTimeout();
115
        }
116
117
        return $this->timeout;
118
    }
119
120
    /**
121
     * @param int $timeout
122
     */
123
    public function setTimeout($timeout = 0)
124
    {
125
        if (10 <= $timeout) {
126
            $this->timeout = $timeout;
127
            return;
128
        }
129
130
        $this->timeout = $this->config()->get('timeout');
131
        if (array_key_exists('timeout', $this->config) && $this->config['timeout']) {
132
            $this->timeout = $this->config['timeout'];
133
        }
134
    }
135
136
    /**
137
     * @param string|int $channelID
138
     * @return $this
139
     */
140
    public function setChannelID($channelID)
141
    {
142
        $this->channelID = $channelID;
143
        return $this;
144
    }
145
146
    /**
147
     * @param bool useLatest
0 ignored issues
show
Bug introduced by
The type Dynamic\Salsify\Model\useLatest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
148
     * @return $this
149
     */
150
    public function setUseLatest($useLatest)
151
    {
152
        $this->useLatest = $useLatest;
153
        return $this;
154
    }
155
156
    /**
157
     * @return string
158
     */
159
    private function channelUrl()
160
    {
161
        return self::API_BASE_URL . 'channels/' . $this->channelID;
162
    }
163
164
    /**
165
     * @return string
166
     */
167
    private function channelRunsBaseUrl()
168
    {
169
        return $this->channelUrl() . '/runs';
170
    }
171
172
    /**
173
     * @return string
174
     */
175
    private function createChannelRunUrl()
176
    {
177
        return $this->channelRunsBaseUrl();
178
    }
179
180
    /**
181
     * @return string
182
     */
183
    private function channelRunUrl()
184
    {
185
        if ($this->useLatest) {
186
            return $this->channelRunsBaseUrl() . '/latest';
187
        }
188
        return $this->channelRunsBaseUrl() . '/' . $this->channelRunID;
189
    }
190
191
    /**
192
     * @return string
193
     * @throws \Exception
194
     */
195
    private function apiUrlSuffix()
196
    {
197
        return '?format=json&auth_token=' . $this->getApiKey();
198
    }
199
200
    /**
201
     * @param string $url
202
     * @param string $method
203
     * @param string|null $postBody
204
     * @return array|string
205
     *
206
     * @throws \Exception
207
     */
208
    private function salsifyRequest($url, $method = 'GET', $postBody = null)
209
    {
210
        $defaultCurlOptions = array(
211
            CURLOPT_URL => $url . $this->apiUrlSuffix(),
212
            CURLOPT_CUSTOMREQUEST => $method,
213
            CURLOPT_HEADER => false,
214
            CURLOPT_RETURNTRANSFER => true,
215
            CURLOPT_HTTPHEADER => array('Content-Type: application/json'),
216
            // seemed reasonable settings
217
            CURLOPT_TIMEOUT => $this->getTimeout(),
218
            CURLOPT_FRESH_CONNECT => true,
219
            CURLOPT_FORBID_REUSE => true,
220
        );
221
        if ($method === 'POST' && is_array($postBody)) {
0 ignored issues
show
introduced by
The condition is_array($postBody) is always false.
Loading history...
222
            $postBody = json_encode($postBody);
0 ignored issues
show
Unused Code introduced by
The assignment to $postBody is dead and can be removed.
Loading history...
223
        }
224
        $ch = curl_init($url);
225
        curl_setopt_array($ch, $defaultCurlOptions);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt_array() does only seem to accept resource, 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

225
        curl_setopt_array(/** @scrutinizer ignore-type */ $ch, $defaultCurlOptions);
Loading history...
226
        $response = curl_exec($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, 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

226
        $response = curl_exec(/** @scrutinizer ignore-type */ $ch);
Loading history...
227
        $httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
0 ignored issues
show
Unused Code introduced by
The assignment to $httpStatus is dead and can be removed.
Loading history...
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_getinfo() does only seem to accept resource, 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

227
        $httpStatus = curl_getinfo(/** @scrutinizer ignore-type */ $ch, CURLINFO_HTTP_CODE);
Loading history...
228
        curl_close($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, 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

228
        curl_close(/** @scrutinizer ignore-type */ $ch);
Loading history...
229
        if ($response && !empty($response)) {
230
            $response = json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, 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

230
            $response = json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
231
        }
232
        return $response;
233
    }
234
235
    /**
236
     * @return $this
237
     * @throws \Exception
238
     */
239
    public function startExportRun()
240
    {
241
        if (!$this->useLatest) {
242
            $response = $this->salsifyRequest($this->createChannelRunUrl(), 'POST');
243
            $this->channelRunID = $response['id'];
244
        }
245
        return $this;
246
    }
247
248
    /**
249
     * @throws Exception
250
     */
251
    private function checkExportUrl()
252
    {
253
        $exportRun = $this->salsifyRequest($this->channelRunUrl());
254
        $status = $exportRun['status'];
255
        if ($status === 'completed') {
256
            $this->channelRunDataUrl = $exportRun['product_export_url'];
257
        } elseif ($status === 'failed') {
258
            // this would be an internal error in Salsify
259
            throw new Exception('Salsify failed to produce an export.');
260
        }
261
    }
262
263
    /**
264
     * waits until salsify is done preparing the given export, and returns the URL when done.
265
     * Throws an exception if anything funky occurs.
266
     *
267
     * @return $this
268
     * @throws Exception
269
     */
270
    public function waitForExportRunToComplete()
271
    {
272
        $this->channelRunDataUrl = null;
273
        do {
274
            $this->checkExportUrl();
275
            sleep(5);
276
        } while (!$this->channelRunDataUrl);
277
        return $this;
278
    }
279
280
    /**
281
     * @return string
282
     */
283
    public function getExportUrl()
284
    {
285
        return $this->channelRunDataUrl;
286
    }
287
}
288