Passed
Push — master ( f7ab31...f45a11 )
by Matthew
03:09 queued 01:32
created

Fetcher::getExportUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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
 * @mixin Configurable
18
 * @mixin Extensible
19
 * @mixin Injectable
20
 */
21
class Fetcher extends Service
22
{
23
    /**
24
     * @var string
25
     */
26
    const API_BASE_URL = 'https://app.salsify.com/api/';
27
28
    /**
29
     * @var string
30
     */
31
    protected $channelRunID;
32
33
    /**
34
     * @var string
35
     */
36
    protected $channelRunDataUrl;
37
38
    /**
39
     * Importer constructor.
40
     * @param string $importerKey
41
     * @throws \Exception
42
     */
43
    public function __construct($importerKey)
44
    {
45
        parent::__construct($importerKey);
46
47
        if (!$this->config()->get('apiKey')) {
48
            throw new Exception('An API key needs to be provided');
49
        }
50
51
        if (!$this->config()->get('channel')) {
52
            throw new Exception('A fetcher needs a channel');
53
        }
54
    }
55
56
    /**
57
     * @return string
58
     */
59
    private function channelUrl()
60
    {
61
        $channel = $this->config()->get('channel');
62
        if ($organization = $this->config()->get('organizationID')) {
63
            return "orgs/{$organization}/channels/{$channel}";
64
        }
65
        return "channels/{$channel}";
66
    }
67
68
    /**
69
     * @return string
70
     */
71
    private function channelRunsBaseUrl()
72
    {
73
        return $this->channelUrl() . '/runs';
74
    }
75
76
    /**
77
     * @return string
78
     */
79
    private function createChannelRunUrl()
80
    {
81
        return $this->channelRunsBaseUrl();
82
    }
83
84
    /**
85
     * @return string
86
     */
87
    private function channelRunUrl()
88
    {
89
        if ($this->config()->get('useLatest')) {
90
            return $this->channelRunsBaseUrl() . '/latest';
91
        }
92
        return $this->channelRunsBaseUrl() . '/' . $this->channelRunID;
93
    }
94
95
    /**
96
     * @param string $url
97
     * @param string $method
98
     * @param string|null $postBody
99
     * @return array|string
100
     *
101
     * @throws \Exception
102
     */
103
    private function salsifyRequest($url, $method = 'GET', $postBody = null)
104
    {
105
        $client = new Client([
106
            'base_uri' => self::API_BASE_URL,
107
            'timeout' => $this->config()->get('timeout'),
108
            'http_errors' => false,
109
            'verify' => true,
110
            'headers' => [
111
                'Authorization' => 'Bearer ' . $this->config()->get('apiKey'),
112
                'Content-Type' => 'application/json',
113
            ],
114
        ]);
115
116
        if ($method === 'POST' && is_array($postBody)) {
0 ignored issues
show
introduced by
The condition is_array($postBody) is always false.
Loading history...
117
            $response = $client->request($method, $url, [
118
                'json' => $postBody,
119
            ]);
120
        } else {
121
            $response = $client->request($method, $url);
122
        }
123
124
125
        if ($response->getStatusCode() === 404) {
126
            throw new Exception("Endpoint wasn't found. Are you sure the channel and organization are correct?");
127
        }
128
129
        if ($response->getBody() && !empty($response->getBody())) {
130
            $response = json_decode($response->getBody(), true);
131
132
            // throw exceptions for salsify errors
133
            if (array_key_exists('errors', $response)) {
134
                foreach ($response['errors'] as $error) {
135
                    throw new Exception($error);
136
                }
137
            }
138
        }
139
140
        return $response;
141
    }
142
143
    /**
144
     * @return $this
145
     * @throws \Exception
146
     */
147
    public function startExportRun()
148
    {
149
        if ($this->config()->get('useLatest') !== true) {
150
            $response = $this->salsifyRequest($this->createChannelRunUrl(), 'POST');
151
            $this->channelRunID = $response['id'];
152
        }
153
        return $this;
154
    }
155
156
    /**
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
     */
178
    public function waitForExportRunToComplete()
179
    {
180
        $this->channelRunDataUrl = null;
181
        do {
182
            $this->checkExportUrl();
183
            sleep(5);
184
        } while (!$this->channelRunDataUrl);
185
        return $this;
186
    }
187
188
    /**
189
     * @return string
190
     */
191
    public function getExportUrl()
192
    {
193
        return $this->channelRunDataUrl;
194
    }
195
}
196