Passed
Pull Request — master (#30)
by
unknown
02:44
created

DeepL::listGlossaries()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
namespace BabyMarkt\DeepL;
4
5
use InvalidArgumentException;
6
7
/**
8
 * DeepL API client library
9
 *
10
 * @package BabyMarkt\DeepL
11
 */
12
class DeepL
13
{
14
    const API_URL_SCHEMA = 'https';
15
16
    /**
17
     * API BASE URL
18
     * https://api.deepl.com/v2/[resource]?auth_key=[yourAuthKey]
19
     */
20
    const API_URL_BASE = '%s://%s/v%s/%s?auth_key=%s';
21
22
    /**
23
     * API BASE URL without authentication query parameter
24
     * https://api.deepl.com/v2/[resource]
25
     */
26
    const API_URL_BASE_NO_AUTH = '%s://%s/v%s/%s';
27
28
    /**
29
     * API URL: usage
30
     */
31
    const API_URL_RESOURCE_USAGE = 'usage';
32
33
    /**
34
     * API URL: languages
35
     */
36
    const API_URL_RESOURCE_LANGUAGES = 'languages';
37
38
    /**
39
     * API URL: glossaries
40
     */
41
    const API_URL_RESOURCE_GLOSSARIES = 'glossaries';
42
43
    /**
44
     * DeepL API Version (v2 is default since 2018)
45
     *
46
     * @var integer
47
     */
48
    protected $apiVersion;
49
50
    /**
51
     * DeepL API Auth Key (DeepL Pro access required)
52
     *
53
     * @var string
54
     */
55
    protected $authKey;
56
57
    /**
58
     * cURL resource
59
     *
60
     * @var resource
61
     */
62
    protected $curl;
63
64
    /**
65
     * Hostname of the API (in most cases api.deepl.com)
66
     *
67
     * @var string
68
     */
69
    protected $host;
70
71
    /**
72
     * URL of the proxy used to connect to DeepL (if needed)
73
     *
74
     * @var string|null
75
     */
76
    protected $proxy = null;
77
78
    /**
79
     * Credentials for the proxy used to connect to DeepL (username:password)
80
     *
81
     * @var string|null
82
     */
83
    protected $proxyCredentials = null;
84
85 31
    /**
86
     * Maximum number of seconds the query should take
87 31
     *
88 31
     * @var int|null
89 31
     */
90 31
    protected $timeout = null;
91
92 31
    /**
93 31
     * DeepL constructor
94
     *
95
     * @param string  $authKey
96
     * @param integer $apiVersion
97
     * @param string  $host
98 31
     */
99
    public function __construct($authKey, $apiVersion = 2, $host = 'api.deepl.com')
100 31
    {
101 31
        $this->authKey    = $authKey;
102 31
        $this->apiVersion = $apiVersion;
103 31
        $this->host       = $host;
104
        $this->curl       = curl_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type CurlHandle. However, the property $curl is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
105
106
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
107
    }
108
109
    /**
110
     * DeepL destructor
111
     */
112
    public function __destruct()
113 4
    {
114
        if ($this->curl && is_resource($this->curl)) {
115 4
            curl_close($this->curl);
116 4
        }
117 4
    }
118
119 3
    /**
120
     * Call languages-Endpoint and return Json-response as an Array
121
     *
122
     * @param string $type
123
     *
124
     * @return array
125
     * @throws DeepLException
126
     */
127
    public function languages($type = null)
128
    {
129
        $url       = $this->buildBaseUrl(self::API_URL_RESOURCE_LANGUAGES);
130
        $body      = $this->buildQuery(array('type' => $type));
131
        $languages = $this->request($url, $body);
132
133
        return $languages;
134
    }
135
136
    /**
137
     * Set a proxy to use for querying the DeepL API if needed
138
     *
139
     * @param string $proxy Proxy URL (e.g 'http://proxy-domain.com:3128')
140
     */
141
    public function setProxy($proxy)
142
    {
143
144
        $this->proxy = $proxy;
145
    }
146
147
    /**
148 1
     * Set the proxy credentials
149
     *
150 1
     * @param string $proxyCredentials proxy credentials (using 'username:password' format)
151 1
     */
152
    public function setProxyCredentials($proxyCredentials)
153
    {
154
        $this->proxyCredentials = $proxyCredentials;
155
    }
156
157
    /**
158
     * Set a timeout for queries to the DeepL API
159
     *
160
     * @param int $timeout Timeout in seconds
161
     */
162
    public function setTimeout($timeout)
163
    {
164
        $this->timeout = $timeout;
165
    }
166
167
    /**
168
     * Translate the text string or array from source to destination language
169
     * For detailed info on Parameters see README.md
170
     *
171
     * @param string|string[] $text
172
     * @param string          $sourceLang
173
     * @param string          $targetLang
174 13
     * @param string          $tagHandling
175
     * @param array|null      $ignoreTags
176
     * @param string          $formality
177
     * @param null            $splitSentences
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $splitSentences is correct as it would always require null to be passed?
Loading history...
178
     * @param null            $preserveFormatting
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $preserveFormatting is correct as it would always require null to be passed?
Loading history...
179
     * @param array|null      $nonSplittingTags
180
     * @param null            $outlineDetection
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $outlineDetection is correct as it would always require null to be passed?
Loading history...
181
     * @param array|null      $splittingTags
182
     *
183
     * @return array
184
     * @throws DeepLException
185
     *
186
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
187 13
     */
188 1
    public function translate(
189
        $text,
190
        $sourceLang = 'de',
191 12
        $targetLang = 'en',
192 12
        $tagHandling = null,
193 12
        array $ignoreTags = null,
194 12
        $formality = 'default',
195 12
        $splitSentences = null,
196 12
        $preserveFormatting = null,
197 12
        array $nonSplittingTags = null,
198 12
        $outlineDetection = null,
199 12
        array $splittingTags = null
200 12
    ) {
201 12
        if (is_array($tagHandling)) {
0 ignored issues
show
introduced by
The condition is_array($tagHandling) is always false.
Loading history...
202 12
            throw new InvalidArgumentException('$tagHandling must be of type String in V2 of DeepLLibrary');
203
        }
204 12
        $paramsArray = array(
205 12
            'text'                => $text,
206 12
            'source_lang'         => $sourceLang,
207
            'target_lang'         => $targetLang,
208
            'splitting_tags'      => $splittingTags,
209 12
            'non_splitting_tags'  => $nonSplittingTags,
210
            'ignore_tags'         => $ignoreTags,
211 7
            'tag_handling'        => $tagHandling,
212
            'formality'           => $formality,
213
            'split_sentences'     => $splitSentences,
214
            'preserve_formatting' => $preserveFormatting,
215
            'outline_detection'   => $outlineDetection,
216
        );
217
218
        $paramsArray = $this->removeEmptyParams($paramsArray);
219
        $url         = $this->buildBaseUrl();
220 1
        $body        = $this->buildQuery($paramsArray);
221
222 1
        // request the DeepL API
223 1
        $translationsArray = $this->request($url, $body);
224
225 1
        return $translationsArray['translations'];
226
    }
227
228
    /**
229
     * Calls the usage-Endpoint and return Json-response as an array
230
     *
231
     * @return array
232
     * @throws DeepLException
233
     */
234
    public function usage()
235
    {
236 19
        $url   = $this->buildBaseUrl(self::API_URL_RESOURCE_USAGE);
237
        $usage = $this->request($url);
238 19
239 19
        return $usage;
240 19
    }
241 19
242 19
    /**
243 19
     * Calls the glossary-Endpoint and return Json-response as an array
244 19
     *
245 19
     * @return array
246
     * @throws DeepLException
247 19
     */
248
    public function listGlossaries()
249
    {
250
        return $this->request($this->buildBaseUrl(self::API_URL_RESOURCE_GLOSSARIES), '', 'GET');
251
    }
252
253
    /**
254
     * Creates a glossary, entries must be formatted as [sourceText => entryText] e.g: ['Hallo' => 'Hello']
255 22
     *
256
     * @param string $name
257 22
     * @param array $entries
258 2
     * @param string $sourceLang
259 2
     * @param string $targetLang
260 2
     * @param string $entriesFormat
261 2
     * @return array|null
262 2
     * @throws DeepLException
263 2
     */
264 2
    public function createGlossary(
265
        string $name,
266 22
        array $entries,
267 22
        string $sourceLang = 'de',
268 7
        string $targetLang = 'en',
269 7
        string $entriesFormat = 'tsv'
270 22
    ) {
271
        $formattedEntries = "";
272 22
        foreach ($entries as $source => $target) {
273
            $formattedEntries .= sprintf("%s\t%s\n", $source, $target);
274 22
        }
275 2
276 2
        $paramsArray = [
277
            'name' => $name,
278 22
            'source_lang'    => $sourceLang,
279
            'target_lang'    => $targetLang,
280
            'entries'        => $formattedEntries,
281
            'entries_format' => $entriesFormat
282
        ];
283
284
        $url  = $this->buildBaseUrl(self::API_URL_RESOURCE_GLOSSARIES, false);
285
        $body = $this->buildQuery($paramsArray);
286
287
        return $this->request($url, $body);
288
    }
289
290
    /**
291
     * Deletes a glossary
292
     *
293
     * @param string $glossaryId
294 17
     * @return array|null
295
     * @throws DeepLException
296 17
     */
297 17
    public function deleteGlossary(string $glossaryId)
298 17
    {
299 17
        $url = $this->buildBaseUrl(self::API_URL_RESOURCE_GLOSSARIES, false);
300
        $url .= "/$glossaryId";
301 17
302
        return $this->request($url, '', 'DELETE');
303
    }
304
305 17
    /**
306
     * Gets information about a glossary
307
     *
308
     * @param string $glossaryId
309 17
     * @return array|null
310 1
     * @throws DeepLException
311 1
     */
312
    public function glossaryInformation(string $glossaryId)
313 17
    {
314
        $url  = $this->buildBaseUrl(self::API_URL_RESOURCE_GLOSSARIES, false);
315 17
        $url .= "/$glossaryId";
316 1
317
        return $this->request($url, '', 'GET');
318 16
    }
319 16
320
    /**
321 16
     * Fetch glossary entries and format them as associative array [source => target]
322 3
     *
323
     * @param string $glossaryId
324
     * @return array
325 13
     * @throws DeepLException
326 2
     */
327
    public function glossaryEntries(string $glossaryId)
328
    {
329 11
        $url = $this->buildBaseUrl(self::API_URL_RESOURCE_GLOSSARIES, false);
330
        $url .= "/$glossaryId/entries";
331
332
        $response = $this->request($url, '', 'GET');
333
334
        $entries = [];
335
        if (!empty($response)) {
336
            $allEntries = preg_split('/\n/', $response);
0 ignored issues
show
Bug introduced by
$response of type array is incompatible with the type string expected by parameter $subject of preg_split(). ( Ignorable by Annotation )

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

336
            $allEntries = preg_split('/\n/', /** @scrutinizer ignore-type */ $response);
Loading history...
337 17
            foreach ($allEntries as $entry) {
338
                $sourceAndTarget = preg_split('/\s+/', rtrim($entry));
339
                if (isset($sourceAndTarget[0], $sourceAndTarget[1])) {
340 17
                    $entries[$sourceAndTarget[0]] = $sourceAndTarget[1];
341 17
                }
342 17
            }
343 17
        }
344
345
        return $entries;
346
    }
347 17
348 17
    /**
349 2
     * Creates the Base-Url which all of the 3 API-resources have in common.
350 2
     *
351
     * @param string $resource
352 17
     *
353 3
     * @return string
354 3
     */
355 17
    protected function buildBaseUrl($resource = 'translate', $withAuth = true)
356 17
    {
357
        if ($withAuth) {
358 17
            return sprintf(
359
                self::API_URL_BASE,
360
                self::API_URL_SCHEMA,
361
                $this->host,
362
                $this->apiVersion,
363
                $resource,
364
                $this->authKey
365
            );
366
        }
367
368
        return sprintf(
369
            self::API_URL_BASE_NO_AUTH,
370
            self::API_URL_SCHEMA,
371
            $this->host,
372
            $this->apiVersion,
373
            $resource
374
        );
375
    }
376
377
    /**
378
     * @param array $paramsArray
379
     *
380
     * @return string
381
     */
382
    protected function buildQuery($paramsArray)
383
    {
384
        if (isset($paramsArray['text']) && true === is_array($paramsArray['text'])) {
385
            $text = $paramsArray['text'];
386
            unset($paramsArray['text']);
387
            $textString = '';
388
            foreach ($text as $textElement) {
389
                $textString .= '&text='.rawurlencode($textElement);
390
            }
391
        }
392
393
        foreach ($paramsArray as $key => $value) {
394
            if (true === is_array($value)) {
395
                $paramsArray[$key] = implode(',', $value);
396
            }
397
        }
398
399
        $body = http_build_query($paramsArray, null, '&');
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $numeric_prefix of http_build_query(). ( Ignorable by Annotation )

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

399
        $body = http_build_query($paramsArray, /** @scrutinizer ignore-type */ null, '&');
Loading history...
400
401
        if (isset($textString)) {
402
            $body = $textString.'&'.$body;
403
        }
404
405
        return $body;
406
    }
407
408
    /**
409
     * Make a request to the given URL
410
     *
411
     * @param string $url
412
     * @param string $body
413
     * @param string $method
414
     *
415
     * @return array
416
     *
417
     * @throws DeepLException
418
     */
419
    protected function request($url, $body = '', $method = 'POST')
420
    {
421
        switch ($method) {
422
            case 'DELETE':
423
                curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
424
                break;
425
            case 'POST':
426
                curl_setopt($this->curl, CURLOPT_POST, true);
427
                break;
428
            default:
429
                break;
430
        }
431
432
        curl_setopt($this->curl, CURLOPT_URL, $url);
433
434
        if ($method === 'POST') {
435
            curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
436
        }
437
438
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
439
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, array("Authorization: DeepL-Auth-Key $this->authKey"));
440
441
        if ($this->proxy !== null) {
442
            curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy);
443
        }
444
445
        if ($this->proxyCredentials !== null) {
446
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, $this->proxyCredentials);
447
        }
448
449
        if ($this->timeout !== null) {
450
            curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
451
        }
452
453
        $response = curl_exec($this->curl);
454
455
        if (curl_errno($this->curl)) {
456
            throw new DeepLException('There was a cURL Request Error : ' . curl_error($this->curl));
457
        }
458
        $httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
459
460
        return $this->handleResponse($response, $httpCode);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->handleResp...e($response, $httpCode) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
461
    }
462
463
    /**
464
     * Handles the different kind of response returned from API, array, string or null
465
     *
466
     * @param $response
467
     * @param $httpCode
468
     * @return array|mixed|null
469
     * @throws DeepLException
470
     */
471
    private function handleResponse($response, $httpCode)
472
    {
473
        $responseArray = json_decode($response, true);
474
        if (($httpCode === 200 || $httpCode === 204) && is_null($responseArray)) {
475
            return empty($response) ? null : $response;
476
        }
477
478
        if ($httpCode !== 200 && is_array($responseArray) && array_key_exists('message', $responseArray)) {
479
            throw new DeepLException($responseArray['message'], $httpCode);
480
        }
481
482
        if (!is_array($responseArray)) {
483
            throw new DeepLException('The Response seems to not be valid JSON.', $httpCode);
484
        }
485
486
        return $responseArray;
487
    }
488
489
    /**
490
     * @param array $paramsArray
491
     *
492
     * @return array
493
     */
494
    private function removeEmptyParams($paramsArray)
495
    {
496
        foreach ($paramsArray as $key => $value) {
497
            if (true === empty($value)) {
498
                unset($paramsArray[$key]);
499
            }
500
            // Special Workaround for outline_detection which will be unset above
501
            // DeepL assumes outline_detection=1 if it is not send
502
            // in order to deactivate it, we need to send outline_detection=0 to the api
503
            if ('outline_detection' === $key) {
504
                if (1 === $value) {
505
                    unset($paramsArray[$key]);
506
                }
507
508
                if (0 === $value) {
509
                    $paramsArray[$key] = 0;
510
                }
511
            }
512
        }
513
514
        return $paramsArray;
515
    }
516
}
517