Passed
Push — 1.11.x ( 03e196...058e18 )
by Yannick
09:50 queued 25s
created

DocumentService::checkDocServiceUrl()   C

Complexity

Conditions 15
Paths 32

Size

Total Lines 65
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 40
c 1
b 0
f 0
dl 0
loc 65
rs 5.9166
cc 15
nc 32
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 *
4
 * (c) Copyright Ascensio System SIA 2023
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the 'License');
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an 'AS IS' BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 *
18
 */
19
20
require_once __DIR__ . '/../../../main/inc/global.inc.php';
21
22
use \Firebase\JWT\JWT;
23
24
class DocumentService {
25
26
    /**
27
     * Plugin object
28
     *
29
     * @var OnlyofficePlugin
30
     * 
31
     */
32
    private $plugin;
33
34
    /**
35
     * New settings for check
36
     *
37
     * @var array
38
     * 
39
     */
40
    private $newSettings;
41
42
    /**
43
     * DocumentService constructor
44
     *
45
     * @param OnlyofficePlugin $plugin - OnlyofficePlugin
46
     *
47
     */
48
    public function __construct($plugin, $newSettings = null) {
49
        $this->plugin = $plugin;
50
        $this->newSettings = $newSettings;
51
    }
52
53
54
    /**
55
     * Request to Document Server with turn off verification
56
     *
57
     * @param string $url - request address
58
     * @param array $method - request method
59
     * @param array $opts - request options
60
     *
61
     * @return string
62
     */
63
    public function request($url, $method = 'GET', $opts = []) {
64
        if (substr($url, 0, strlen('https')) === 'https') {
65
            $opts['verify'] = false;
66
        }
67
        if (!array_key_exists('timeout', $opts)) {
68
            $opts['timeout'] = 60;
69
        }
70
71
        $curl_info = [
72
            CURLOPT_URL => $url,
73
            CURLOPT_RETURNTRANSFER => true,
74
            CURLOPT_ENCODING => '',
75
            CURLOPT_MAXREDIRS => 10,
76
            CURLOPT_TIMEOUT => $opts['timeout'],
77
            CURLOPT_FOLLOWLOCATION => true,
78
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
79
            CURLOPT_CUSTOMREQUEST => $method,
80
            CURLOPT_POSTFIELDS => $opts['body'],
81
            CURLOPT_HTTPHEADER => $opts['headers'],
82
        ];
83
84
        if ($opts == []) {
85
            unset($curl_info[CURLOPT_POSTFIELDS]);
86
        }
87
88
        $curl = curl_init();
89
        curl_setopt_array($curl, $curl_info);
90
        $response = curl_exec($curl);
91
        curl_close($curl);
92
93
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response also could return the type true which is incompatible with the documented return type string.
Loading history...
94
    }
95
96
    /**
97
     * Generate an error code table of convertion
98
     *
99
     * @param int $errorCode - Error code
100
     *
101
     * @throws Exception
102
     */
103
    private function processConvServResponceError($errorCode) {
104
        $errorMessageTemplate = $this->plugin->get_lang('docServiceError');
105
        $errorMessage = '';
106
107
        switch ($errorCode) {
108
            case -20:
109
                $errorMessage = $errorMessageTemplate . ': Error encrypt signature';
110
                break;
111
            case -8:
112
                $errorMessage = $errorMessageTemplate . ': Invalid token';
113
                break;
114
            case -7:
115
                $errorMessage = $errorMessageTemplate . ': Error document request';
116
                break;
117
            case -6:
118
                $errorMessage = $errorMessageTemplate . ': Error while accessing the conversion result database';
119
                break;
120
            case -5:
121
                $errorMessage = $errorMessageTemplate . ': Incorrect password';
122
                break;
123
            case -4:
124
                $errorMessage = $errorMessageTemplate . ': Error while downloading the document file to be converted.';
125
                break;
126
            case -3:
127
                $errorMessage = $errorMessageTemplate . ': Conversion error';
128
                break;
129
            case -2:
130
                $errorMessage = $errorMessageTemplate . ': Timeout conversion error';
131
                break;
132
            case -1:
133
                $errorMessage = $errorMessageTemplate . ': Unknown error';
134
                break;
135
            case 0:
136
                break;
137
            default:
138
                $errorMessage = $errorMessageTemplate . ': ErrorCode = ' . $errorCode;
139
                break;
140
        }
141
142
        throw new \Exception($errorMessage);
143
    }
144
145
    /**
146
     * Generate an error code table of command
147
     *
148
     * @param string $errorCode - Error code
149
     *
150
     * @throws Exception
151
     */
152
    private function processCommandServResponceError($errorCode) {
153
        $errorMessageTemplate = $this->plugin->get_lang('docServiceError');
154
        $errorMessage = '';
155
156
        switch ($errorCode) {
157
            case 6:
158
                $errorMessage = $errorMessageTemplate . ': Invalid token';
159
                break;
160
            case 5:
161
                $errorMessage = $errorMessageTemplate . ': Command not correсt';
162
                break;
163
            case 3:
164
                $errorMessage = $errorMessageTemplate . ': Internal server error';
165
                break;
166
            case 0:
167
                return;
168
            default:
169
                $errorMessage = $errorMessageTemplate . ': ErrorCode = ' . $errorCode;
170
                break;
171
        }
172
173
        throw new \Exception($errorMessage);
174
    }
175
176
    /**
177
     * Create temporary file for convert service testing
178
     *
179
     * @return array
180
     */
181
    private function createTempFile() {
182
        $fileUrl = null;
183
        $fileName = 'convert.docx';
184
        $fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
185
        $baseName = strtolower(pathinfo($fileName, PATHINFO_FILENAME));
186
        $templatePath = TemplateManager::getEmptyTemplate($fileExt);
187
        $folderPath = api_get_path(SYS_PLUGIN_PATH).$this->plugin->getPluginName();
188
        $filePath = $folderPath . '/' . $fileName;
189
190
        if ($fp = @fopen($filePath, 'w')) {
191
            $content = file_get_contents($templatePath);
192
            fputs($fp, $content);
193
            fclose($fp);
194
            chmod($filePath, api_get_permissions_for_new_files());
195
            $fileUrl = api_get_path(WEB_PLUGIN_PATH).$this->plugin->getPluginName().'/'.$fileName;
196
        }
197
198
        return [
199
            'fileUrl' => $fileUrl,
200
            'filePath' => $filePath
201
        ];
202
    }
203
     
204
205
    /**
206
     * Request for conversion to a service
207
     *
208
     * @param string $document_uri - Uri for the document to convert
209
     * @param string $from_extension - Document extension
210
     * @param string $to_extension - Extension to which to convert
211
     * @param string $document_revision_id - Key for caching on service
212
     * @param bool - $is_async - Perform conversions asynchronously
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
213
     * @param string $region - Region
214
     * 
215
     * @throws Exception
216
     *
217
     * @return array
218
     */
219
    public function sendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, $is_async, $region = null) {
220
        if (!empty($this->getValue('document_server_internal'))) {
221
            $documentServerUrl = $this->getValue('document_server_internal');
222
        } else {
223
            $documentServerUrl = $this->getValue('document_server_url');
224
        }
225
226
        if (empty($documentServerUrl)) {
227
            throw new \Exception($this->plugin->get_lang('pluginIsNotConfigured'));
228
        }
229
230
        $urlToConverter = $documentServerUrl . 'ConvertService.ashx';
231
232
        if (empty($document_revision_id)) {
233
            $document_revision_id = $document_uri;
234
        }
235
236
        $document_revision_id = FileUtility::GenerateRevisionId($document_revision_id);
237
238
        if (empty($from_extension)) {
239
            $from_extension = pathinfo($document_uri)['extension'];
240
        } else {
241
            $from_extension = trim($from_extension, '.');
242
        }
243
244
        $data = [
245
            'async' => $is_async,
246
            'url' => $document_uri,
247
            'outputtype' => trim($to_extension, '.'),
248
            'filetype' => $from_extension,
249
            'title' => $document_revision_id . '.' . $from_extension,
250
            'key' => $document_revision_id
251
        ];
252
253
        if (!is_null($region)) {
254
            $data['region'] = $region;
255
        }
256
257
        $opts = [
258
            'timeout' => '120',
259
            'headers' => [
260
                'Content-type' => 'application/json'
261
            ],
262
            'body' => json_encode($data)
263
        ];
264
265
        if (!empty($this->getValue('jwt_secret'))) {
266
            $params = [
267
                'payload' => $data
268
            ];
269
            $token = JWT::encode($params, $this->getValue('jwt_secret'), 'HS256');
270
            $opts['headers'][$this->getValue('jwt_header')] = 'Bearer ' . $token;
271
            $token = JWT::encode($data, $this->getValue('jwt_secret'), 'HS256');
272
            $data['token'] = $token;
273
            $opts['body'] = json_encode($data);
274
        }
275
276
        $response_xml_data = $this->request($urlToConverter, 'POST', $opts);
277
        libxml_use_internal_errors(true);
278
279
        if (!function_exists('simplexml_load_file')) {
280
             throw new \Exception($this->plugin->get_lang('cantReadXml'));
281
        }
282
283
        $response_data = simplexml_load_string($response_xml_data);
284
        
285
        if (!$response_data) {
0 ignored issues
show
introduced by
$response_data is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
286
            $exc = $this->plugin->get_lang('badResponseErrors');
287
            foreach(libxml_get_errors() as $error) {
288
                $exc = $exc . '\t' . $error->message;
289
            }
290
            throw new \Exception ($exc);
291
        }
292
293
        return $response_data;
294
    }
295
296
    /**
297
     * Request health status
298
     *
299
     * @throws Exception
300
     * 
301
     * @return bool
302
     */
303
    public function healthcheckRequest() {
304
        if (!empty($this->getValue('document_server_internal'))) {
305
            $documentServerUrl = $this->getValue('document_server_internal');
306
        } else {
307
            $documentServerUrl = $this->getValue('document_server_url');
308
        }
309
310
        if (empty($documentServerUrl)) {
311
            throw new \Exception($this->plugin->get_lang('appIsNotConfigured'));
312
        }
313
314
        $urlHealthcheck = $documentServerUrl . 'healthcheck';
315
        $response = $this->request($urlHealthcheck);
316
        return $response === 'true';
317
    }
318
319
    /**
320
     * The method is to convert the file to the required format and return the result url
321
     *
322
     * @param string $document_uri - Uri for the document to convert
323
     * @param string $from_extension - Document extension
324
     * @param string $to_extension - Extension to which to convert
325
     * @param string $document_revision_id - Key for caching on service
326
     * @param string $region - Region
327
     *
328
     * @return string
329
     */
330
    public function getConvertedUri($document_uri, $from_extension, $to_extension, $document_revision_id, $region = null) {
331
        $responceFromConvertService = $this->sendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, false, $region);
332
        $errorElement = $responceFromConvertService->Error;
333
        if ($errorElement->count() > 0) {
334
            $this->processConvServResponceError($errorElement . '');
335
        }
336
337
        $isEndConvert = $responceFromConvertService->EndConvert;
338
339
        if ($isEndConvert !== null && strtolower($isEndConvert) === 'true') {
340
            return $responceFromConvertService->FileUrl;
341
        }
342
343
        return '';
344
    }
345
346
    /**
347
     * Send command
348
     *
349
     * @param string $method - type of command
350
     *
351
     * @return array
352
     */
353
    public function commandRequest($method) {
354
        //$documentServerUrl = $this->plugin->getDocumentServerInternalUrl();
355
        if (!empty($this->getValue('document_server_internal'))) {
356
            $documentServerUrl = $this->getValue('document_server_internal');
357
        } else {
358
            $documentServerUrl = $this->getValue('document_server_url');
359
        }
360
        
361
362
        if (empty($documentServerUrl)) {
363
            throw new \Exception($this->plugin->get_lang('cantReadXml'));
364
        }
365
366
        $urlCommand = $documentServerUrl . 'coauthoring/CommandService.ashx';
367
        $data = [
368
            'c' => $method
369
        ];
370
        $opts = [
371
            'headers' => [
372
                'Content-type' => 'application/json'
373
            ],
374
            'body' => json_encode($data)
375
        ];
376
377
        if (!empty($this->getValue('jwt_secret'))) {
378
            $params = [
379
                'payload' => $data
380
            ];
381
            $token = JWT::encode($params, $this->getValue('jwt_secret'), 'HS256');
382
            $opts['headers'][$this->getValue('jwt_header')] = 'Bearer ' . $token;
383
384
            $token = JWT::encode($data, $this->getValue('jwt_secret'), 'HS256');
385
            $data['token'] = $token;
386
            $opts['body'] = json_encode($data);
387
        }
388
389
        $response = $this->request($urlCommand, 'POST', $opts);
390
        $data = json_decode($response);
391
        $this->processCommandServResponceError($data->error);
392
393
        return $data;
394
    }
395
396
    /**
397
     * Checking document service location
398
     *
399
     * @return array
400
     */
401
    public function checkDocServiceUrl() {
402
        $version = null;
403
        try {
404
            if (preg_match('/^https:\/\//i', api_get_path(WEB_PATH))
405
                && preg_match('/^http:\/\//i', $this->getValue('document_server_url'))) {
406
                throw new \Exception($this->plugin->get_lang('mixedContent'));
407
            }
408
        } catch (\Exception $e) {
409
            return [$e->getMessage(), $version];
410
        }
411
412
        try {
413
            $healthcheckResponse = $this->healthcheckRequest();
414
415
            if (!$healthcheckResponse) {
416
                throw new \Exception($this->plugin->get_lang('badHealthcheckStatus'));
417
            }
418
        } catch (\Exception $e) {
419
            return [$e->getMessage(), $version];
420
        }
421
422
        try {
423
            $commandResponse = $this->commandRequest('version');
424
425
            if (empty($commandResponse)) {
426
                throw new \Exception($this->plugin->get_lang('errorOccuredDocService'));
427
            }
428
429
            $version = $commandResponse->version;
430
            $versionF = floatval($version);
431
432
            if ($versionF > 0.0 && $versionF <= 6.0) {
433
                throw new \Exception($this->plugin->get_lang('notSupportedVersion'));
434
            }
435
        } catch (\Exception $e) {
436
            return [$e->getMessage(), $version];
437
        }
438
439
        $convertedFileUri = null;
440
441
        try {
442
            $emptyFile = $this->createTempFile();
443
444
            if ($emptyFile['fileUrl'] !== null) {
445
                if (!empty($this->getValue('storage_url'))) {
446
                    $emptyFile['fileUrl'] = str_replace(api_get_path(WEB_PATH), $this->getValue('storage_url'), $emptyFile['fileUrl']);
447
                   }
448
                $convertedFileUri = $this->getConvertedUri($emptyFile['fileUrl'], 'docx', 'docx', 'check_' . rand());
449
            }
450
            
451
            unlink($emptyFile['filePath']);
452
        } catch (\Exception $e) {
453
            if (isset($emptyFile['filePath'])) {
454
                unlink($emptyFile['filePath']);
455
            }
456
            return [$e->getMessage(), $version];
457
        }
458
459
        try {
460
            $this->request($convertedFileUri);
461
        } catch (\Exception $e) {
462
            return [$e->getMessage(), $version];
463
        }
464
465
        return ['', $version];
466
    }
467
468
    /**
469
     * Get setting value (from data base or submited form)
470
     *
471
     * @return string
472
     */
473
    private function getValue($value) {
474
        $result = null;
475
476
        if (!isset($this->newSettings)) {
477
            switch ($value) {
478
                case 'document_server_url':
479
                    $result = $this->plugin->getDocumentServerUrl();
480
                    break;
481
                case 'jwt_secret':
482
                    $result = $this->plugin->getDocumentServerSecret();
483
                    break;
484
                case 'jwt_header':
485
                    $result = $this->plugin->getJwtHeader();
486
                    break;
487
                case 'document_server_internal':
488
                    $result = $this->plugin->getDocumentServerInternalUrl();
489
                    break;
490
                case 'storage_url':
491
                    $result = $this->plugin->getStorageUrl();
492
                    break;
493
                default:
494
            }
495
        } else {
496
            $result = isset($this->newSettings[$value]) ? (string)$this->newSettings[$value] : null;
497
            if ($value !== 'jwt_secret' && $value !== 'jwt_header') {
498
                if ($result !== null && $result !== "/") {
499
                    $result = rtrim($result, "/");
500
                    if (strlen($result) > 0) {
501
                        $result = $result . "/";
502
                    }
503
                }
504
            } else if ($value === 'jwt_header' && empty($this->newSettings[$value])) {
505
                $result = 'Authorization';
506
            }
507
        }
508
        return $result;
509
    }
510
511
}