Fetcher::channelUrl()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 2
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 10
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Exception;
6
use GuzzleHttp\Client;
7
use SilverStripe\Core\Config\Configurable;
8
use SilverStripe\Core\Extensible;
9
use SilverStripe\Core\Injector\Injectable;
10
11
/**
12
 * Class Importer
13
 * @package Dynamic\Salsify\Model
14
 *
15
 * Based off https://github.com/XinV/salsify-php-api/blob/master/lib/Salsify/API.php
16
 */
17
class Fetcher extends Service
18
{
19
20
    /**
21
     * @var string
22
     */
23
    const API_BASE_URL = 'https://app.salsify.com/api/';
24
25
    /**
26
     * @var string
27
     */
28
    protected $channelRunID;
29
30
    /**
31
     * @var string
32
     */
33
    protected $channelRunDataUrl;
34
35
    /**
36
     * Importer constructor.
37
     * @param string $importerKey
38
     * @param bool $noChannel
39
     * @throws \Exception
40
     */
41
    public function __construct($importerKey, $noChannel = false)
42
    {
43
        parent::__construct($importerKey);
0 ignored issues
show
Bug introduced by
$importerKey of type string is incompatible with the type Dynamic\Salsify\Model\stirng expected by parameter $importerKey of Dynamic\Salsify\Model\Service::__construct(). ( Ignorable by Annotation )

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

43
        parent::__construct(/** @scrutinizer ignore-type */ $importerKey);
Loading history...
44
45
        if (!$this->config()->get('apiKey')) {
46
            throw new Exception('An API key needs to be provided');
47
        }
48
49
        if ($noChannel === false && !$this->config()->get('channel')) {
50
            throw new Exception('A fetcher needs a channel');
51
        }
52
    }
53
54
    /**
55
     * @return string
56
     */
57
    private function channelUrl()
58
    {
59
        $channel = $this->config()->get('channel');
60
        if ($organization = $this->config()->get('organizationID')) {
61
            return "orgs/{$organization}/channels/{$channel}";
62
        }
63
        return "channels/{$channel}";
64
    }
65
66
    /**
67
     * @return string
68
     */
69
    private function channelRunsBaseUrl()
70
    {
71
        return $this->channelUrl() . '/runs';
72
    }
73
74
    /**
75
     * @return string
76
     */
77
    private function createChannelRunUrl()
78
    {
79
        return $this->channelRunsBaseUrl();
80
    }
81
82
    /**
83
     * @return string
84
     */
85
    private function channelRunUrl()
86
    {
87
        if ($this->config()->get('useLatest')) {
88
            return $this->channelRunsBaseUrl() . '/latest';
89
        }
90
        return $this->channelRunsBaseUrl() . '/' . $this->channelRunID;
91
    }
92
93
    /**
94
     * @param string $url
95
     * @param string $method
96
     * @param string|null $postBody
97
     * @return array|string
98
     *
99
     * @throws \Exception
100
     * @throws \GuzzleHttp\Exception\GuzzleException
101
     */
102
    private function salsifyRequest($url, $method = 'GET', $postBody = null)
103
    {
104
        $client = new Client([
105
            'base_uri' => self::API_BASE_URL,
106
            'timeout' => $this->config()->get('timeout'),
107
            'http_errors' => false,
108
            'verify' => true,
109
            'headers' => [
110
                'Authorization' => 'Bearer ' . $this->config()->get('apiKey'),
111
                'Content-Type' => 'application/json',
112
            ],
113
        ]);
114
115
        if ($method === 'POST' && is_array($postBody)) {
0 ignored issues
show
introduced by
The condition is_array($postBody) is always false.
Loading history...
116
            $response = $client->request($method, $url, [
117
                'json' => $postBody,
118
            ]);
119
        } else {
120
            $response = $client->request($method, $url);
121
        }
122
123
124
        if ($response->getStatusCode() === 404) {
125
            throw new Exception("Endpoint wasn't found. Are you sure the channel and organization are correct?");
126
        }
127
128
        if ($response->getBody() && !empty($response->getBody())) {
129
            $response = json_decode($response->getBody(), true);
130
131
            // throw exceptions for salsify errors
132
            if (array_key_exists('errors', $response)) {
133
                foreach ($this->yieldSingle($response['errors']) as $error) {
134
                    throw new Exception($error);
135
                }
136
            }
137
        }
138
139
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response also could return the type Psr\Http\Message\ResponseInterface which is incompatible with the documented return type array|string.
Loading history...
140
    }
141
142
    /**
143
     * @return $this
144
     * @throws \GuzzleHttp\Exception\GuzzleException
145
     */
146
    public function startExportRun()
147
    {
148
        if ($this->config()->get('useLatest') !== true) {
149
            $response = $this->salsifyRequest($this->createChannelRunUrl(), 'POST');
150
            $this->channelRunID = $response['id'];
151
        }
152
        return $this;
153
    }
154
155
    /**
156
     * @throws \GuzzleHttp\Exception\GuzzleException
157
     * @throws \Exception
158
     */
159
    private function checkExportUrl()
160
    {
161
        $exportRun = $this->salsifyRequest($this->channelRunUrl());
162
        $status = $exportRun['status'];
163
        if ($status === 'completed') {
164
            $this->channelRunDataUrl = $exportRun['product_export_url'];
165
        } elseif ($status === 'failed') {
166
            // this would be an internal error in Salsify
167
            throw new Exception('Salsify failed to produce an export.');
168
        }
169
    }
170
171
    /**
172
     * waits until salsify is done preparing the given export, and returns the URL when done.
173
     * Throws an exception if anything funky occurs.
174
     *
175
     * @return $this
176
     * @throws \Exception
177
     * @throws \GuzzleHttp\Exception\GuzzleException
178
     */
179
    public function waitForExportRunToComplete()
180
    {
181
        $this->channelRunDataUrl = null;
182
        do {
183
            $this->checkExportUrl();
184
            sleep(5);
185
        } while (!$this->channelRunDataUrl);
186
        return $this;
187
    }
188
189
    /**
190
     * @return string
191
     */
192
    public function getExportUrl()
193
    {
194
        return $this->channelRunDataUrl;
195
    }
196
}
197