Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 5e069e...ee43a7 )
by
unknown
03:56
created

Helper::getIndexNameFromUid()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 48
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 29
c 0
b 0
f 0
nc 5
nop 3
dl 0
loc 48
rs 8.8337
1
<?php
2
3
/**
4
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
5
 *
6
 * This file is part of the Kitodo and TYPO3 projects.
7
 *
8
 * @license GNU General Public License version 3 or later.
9
 * For the full copyright and license information, please read the
10
 * LICENSE.txt file that was distributed with this source code.
11
 */
12
13
namespace Kitodo\Dlf\Common;
14
15
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
16
use TYPO3\CMS\Core\Database\ConnectionPool;
17
use TYPO3\CMS\Core\Http\RequestFactory;
18
use TYPO3\CMS\Core\Log\LogManager;
19
use TYPO3\CMS\Core\Context\Context;
20
use TYPO3\CMS\Core\Localization\LanguageService;
21
use TYPO3\CMS\Core\Messaging\FlashMessageService;
22
use TYPO3\CMS\Core\Utility\ArrayUtility;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Core\Utility\MathUtility;
25
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
26
use TYPO3\CMS\Extbase\Object\ObjectManager;
27
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
28
use TYPO3\CMS\Frontend\Page\PageRepository;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Frontend\Page\PageRepository 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...
29
30
31
/**
32
 * Helper class for the 'dlf' extension
33
 *
34
 * @author Sebastian Meyer <[email protected]>
35
 * @author Henrik Lochmann <[email protected]>
36
 * @package TYPO3
37
 * @subpackage dlf
38
 * @access public
39
 */
40
class Helper
41
{
42
    /**
43
     * The extension key
44
     *
45
     * @var string
46
     * @access public
47
     */
48
    public static $extKey = 'dlf';
49
50
    /**
51
     * This holds the cipher algorithm
52
     * @see openssl_get_cipher_methods() for options
53
     *
54
     * @var string
55
     * @access protected
56
     */
57
    protected static $cipherAlgorithm = 'aes-256-ctr';
58
59
    /**
60
     * This holds the hash algorithm
61
     * @see openssl_get_md_methods() for options
62
     *
63
     * @var string
64
     * @access protected
65
     */
66
    protected static $hashAlgorithm = 'sha256';
67
68
    /**
69
     * The locallang array for flash messages
70
     *
71
     * @var array
72
     * @access protected
73
     */
74
    protected static $messages = [];
75
76
    /**
77
     * Generates a flash message and adds it to a message queue.
78
     *
79
     * @access public
80
     *
81
     * @param string $message: The body of the message
82
     * @param string $title: The title of the message
83
     * @param int $severity: The message's severity
84
     * @param bool $session: Should the message be saved in the user's session?
85
     * @param string $queue: The queue's unique identifier
86
     *
87
     * @return \TYPO3\CMS\Core\Messaging\FlashMessageQueue The queue the message was added to
88
     */
89
    public static function addMessage($message, $title, $severity, $session = false, $queue = 'kitodo.default.flashMessages')
90
    {
91
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
92
        $flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier($queue);
93
        $flashMessage = GeneralUtility::makeInstance(
94
            \TYPO3\CMS\Core\Messaging\FlashMessage::class,
95
            $message,
96
            $title,
97
            $severity,
98
            $session
99
        );
100
        $flashMessageQueue->enqueue($flashMessage);
101
        return $flashMessageQueue;
102
    }
103
104
    /**
105
     * Check if given identifier is a valid identifier of the German National Library
106
     *
107
     * @access public
108
     *
109
     * @param string $id: The identifier to check
110
     * @param string $type: What type is the identifier supposed to be?
111
     *                      Possible values: PPN, IDN, PND, ZDB, SWD, GKD
112
     *
113
     * @return bool Is $id a valid GNL identifier of the given $type?
114
     */
115
    public static function checkIdentifier($id, $type)
116
    {
117
        $digits = substr($id, 0, 8);
118
        $checksum = 0;
119
        for ($i = 0, $j = strlen($digits); $i < $j; $i++) {
120
            $checksum += (9 - $i) * intval(substr($digits, $i, 1));
121
        }
122
        $checksum = (11 - ($checksum % 11)) % 11;
123
        switch (strtoupper($type)) {
124
            case 'PPN':
125
            case 'IDN':
126
            case 'PND':
127
                if ($checksum == 10) {
128
                    $checksum = 'X';
129
                }
130
                if (!preg_match('/\d{8}[\dX]{1}/i', $id)) {
131
                    return false;
132
                } elseif (strtoupper(substr($id, -1, 1)) != $checksum) {
133
                    return false;
134
                }
135
                break;
136
            case 'ZDB':
137
                if ($checksum == 10) {
138
                    $checksum = 'X';
139
                }
140
                if (!preg_match('/\d{8}-[\dX]{1}/i', $id)) {
141
                    return false;
142
                } elseif (strtoupper(substr($id, -1, 1)) != $checksum) {
143
                    return false;
144
                }
145
                break;
146
            case 'SWD':
147
                $checksum = 11 - $checksum;
148
                if (!preg_match('/\d{8}-\d{1}/i', $id)) {
149
                    return false;
150
                } elseif ($checksum == 10) {
151
                    return self::checkIdentifier(($digits + 1) . substr($id, -2, 2), 'SWD');
152
                } elseif (substr($id, -1, 1) != $checksum) {
153
                    return false;
154
                }
155
                break;
156
            case 'GKD':
157
                $checksum = 11 - $checksum;
158
                if ($checksum == 10) {
159
                    $checksum = 'X';
160
                }
161
                if (!preg_match('/\d{8}-[\dX]{1}/i', $id)) {
162
                    return false;
163
                } elseif (strtoupper(substr($id, -1, 1)) != $checksum) {
164
                    return false;
165
                }
166
                break;
167
        }
168
        return true;
169
    }
170
171
    /**
172
     * Decrypt encrypted value with given control hash
173
     *
174
     * @access public
175
     *
176
     * @param string $encrypted: The encrypted value to decrypt
177
     *
178
     * @return mixed The decrypted value or false on error
179
     */
180
    public static function decrypt($encrypted)
181
    {
182
        if (
183
            !in_array(self::$cipherAlgorithm, openssl_get_cipher_methods(true))
184
            || !in_array(self::$hashAlgorithm, openssl_get_md_methods(true))
185
        ) {
186
            self::log('OpenSSL library doesn\'t support cipher and/or hash algorithm', LOG_SEVERITY_ERROR);
187
            return false;
188
        }
189
        if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
190
            self::log('No encryption key set in TYPO3 configuration', LOG_SEVERITY_ERROR);
191
            return false;
192
        }
193
        if (
194
            empty($encrypted)
195
            || strlen($encrypted) < openssl_cipher_iv_length(self::$cipherAlgorithm)
196
        ) {
197
            self::log('Invalid parameters given for decryption', LOG_SEVERITY_ERROR);
198
            return false;
199
        }
200
        // Split initialisation vector and encrypted data.
201
        $binary = base64_decode($encrypted);
202
        $iv = substr($binary, 0, openssl_cipher_iv_length(self::$cipherAlgorithm));
203
        $data = substr($binary, openssl_cipher_iv_length(self::$cipherAlgorithm));
204
        $key = openssl_digest($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'], self::$hashAlgorithm, true);
205
        // Decrypt data.
206
        $decrypted = openssl_decrypt($data, self::$cipherAlgorithm, $key, OPENSSL_RAW_DATA, $iv);
207
        return $decrypted;
208
    }
209
210
    /**
211
     * Try to parse $content into a `SimpleXmlElement`. If $content is not a
212
     * string or does not contain valid XML, `false` is returned.
213
     *
214
     * @access public
215
     *
216
     * @param string $content: content of file to read
217
     *
218
     * @return \SimpleXMLElement|false
219
     */
220
    public static function getXmlFileAsString($content)
221
    {
222
        // Don't make simplexml_load_string throw (when $content is an array
223
        // or object)
224
        if (!is_string($content)) {
225
            return false;
226
        }
227
228
        // Turn off libxml's error logging.
229
        $libxmlErrors = libxml_use_internal_errors(true);
230
        // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
231
        $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
232
        // Try to load XML from file.
233
        $xml = simplexml_load_string($content);
234
        // reset entity loader setting
235
        libxml_disable_entity_loader($previousValueOfEntityLoader);
236
        // Reset libxml's error logging.
237
        libxml_use_internal_errors($libxmlErrors);
238
        return $xml;
239
    }
240
241
    /**
242
     * Add a message to the TYPO3 log
243
     *
244
     * @access public
245
     *
246
     * @param string $message: The message to log
247
     * @param int $severity: The severity of the message
248
     *                       0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
249
     *
250
     * @return void
251
     */
252
    public static function log($message, $severity = 0)
253
    {
254
        $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(get_called_class());
255
256
        switch ($severity) {
257
            case 0:
258
                $logger->info($message);
259
                break;
260
            case 1:
261
                $logger->notice($message);
262
                break;
263
            case 2:
264
                $logger->warning($message);
265
                break;
266
            case 3:
267
                $logger->error($message);
268
                break;
269
            default:
270
                break;
271
        }
272
    }
273
274
    /**
275
     * Digest the given string
276
     *
277
     * @access public
278
     *
279
     * @param string $string: The string to encrypt
280
     *
281
     * @return mixed Hashed string or false on error
282
     */
283
    public static function digest($string)
284
    {
285
        if (!in_array(self::$hashAlgorithm, openssl_get_md_methods(true))) {
286
            self::log('OpenSSL library doesn\'t support hash algorithm', LOG_SEVERITY_ERROR);
287
            return false;
288
        }
289
        // Hash string.
290
        $hashed = openssl_digest($string, self::$hashAlgorithm);
291
        return $hashed;
292
    }
293
294
    /**
295
     * Encrypt the given string
296
     *
297
     * @access public
298
     *
299
     * @param string $string: The string to encrypt
300
     *
301
     * @return mixed Encrypted string or false on error
302
     */
303
    public static function encrypt($string)
304
    {
305
        if (
306
            !in_array(self::$cipherAlgorithm, openssl_get_cipher_methods(true))
307
            || !in_array(self::$hashAlgorithm, openssl_get_md_methods(true))
308
        ) {
309
            self::log('OpenSSL library doesn\'t support cipher and/or hash algorithm', LOG_SEVERITY_ERROR);
310
            return false;
311
        }
312
        if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
313
            self::log('No encryption key set in TYPO3 configuration', LOG_SEVERITY_ERROR);
314
            return false;
315
        }
316
        // Generate random initialization vector.
317
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(self::$cipherAlgorithm));
318
        $key = openssl_digest($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'], self::$hashAlgorithm, true);
319
        // Encrypt data.
320
        $encrypted = openssl_encrypt($string, self::$cipherAlgorithm, $key, OPENSSL_RAW_DATA, $iv);
321
        // Merge initialization vector and encrypted data.
322
        if ($encrypted !== false) {
323
            $encrypted = base64_encode($iv . $encrypted);
324
        }
325
        return $encrypted;
326
    }
327
328
    /**
329
     * Get the unqualified name of a class
330
     *
331
     * @access public
332
     *
333
     * @param string $qualifiedClassname: The qualified class name from get_class()
334
     *
335
     * @return string The unqualified class name
336
     */
337
    public static function getUnqualifiedClassName($qualifiedClassname)
338
    {
339
        $nameParts = explode('\\', $qualifiedClassname);
340
        return end($nameParts);
341
    }
342
343
    /**
344
     * Clean up a string to use in an URL.
345
     *
346
     * @access public
347
     *
348
     * @param string $string: The string to clean up
349
     *
350
     * @return string The cleaned up string
351
     */
352
    public static function getCleanString($string)
353
    {
354
        // Convert to lowercase.
355
        $string = strtolower($string);
356
        // Remove non-alphanumeric characters.
357
        $string = preg_replace('/[^a-z\d_\s-]/', '', $string);
358
        // Remove multiple dashes or whitespaces.
359
        $string = preg_replace('/[\s-]+/', ' ', $string);
360
        // Convert whitespaces and underscore to dash.
361
        $string = preg_replace('/[\s_]/', '-', $string);
362
        return $string;
363
    }
364
365
    /**
366
     * Get the registered hook objects for a class
367
     *
368
     * @access public
369
     *
370
     * @param string $scriptRelPath: The path to the class file
371
     *
372
     * @return array Array of hook objects for the class
373
     */
374
    public static function getHookObjects($scriptRelPath)
375
    {
376
        $hookObjects = [];
377
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::$extKey . '/' . $scriptRelPath]['hookClass'])) {
378
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::$extKey . '/' . $scriptRelPath]['hookClass'] as $classRef) {
379
                $hookObjects[] = GeneralUtility::makeInstance($classRef);
380
            }
381
        }
382
        return $hookObjects;
383
    }
384
385
    /**
386
     * Get the "index_name" for an UID
387
     *
388
     * @access public
389
     *
390
     * @param int $uid: The UID of the record
391
     * @param string $table: Get the "index_name" from this table
392
     * @param int $pid: Get the "index_name" from this page
393
     *
394
     * @return string "index_name" for the given UID
395
     */
396
    public static function getIndexNameFromUid($uid, $table, $pid = -1)
397
    {
398
        // Sanitize input.
399
        $uid = max(intval($uid), 0);
400
        if (
401
            !$uid
402
            // NOTE: Only use tables that don't have too many entries!
403
            || !in_array($table, ['tx_dlf_collections', 'tx_dlf_libraries', 'tx_dlf_metadata', 'tx_dlf_structures', 'tx_dlf_solrcores'])
404
        ) {
405
            self::log('Invalid UID "' . $uid . '" or table "' . $table . '"', LOG_SEVERITY_ERROR);
406
            return '';
407
        }
408
409
        $makeCacheKey = function ($pid, $uid) {
410
            return $pid . '.' . $uid;
411
        };
412
413
        static $cache = [];
414
        if (!isset($cache[$table])) {
415
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
416
                ->getQueryBuilderForTable($table);
417
418
            $result = $queryBuilder
419
                ->select(
420
                    $table . '.index_name AS index_name',
421
                    $table . '.uid AS uid',
422
                    $table . '.pid AS pid',
423
                )
424
                ->from($table)
425
                ->execute();
426
427
            $cache[$table] = [];
428
429
            while ($row = $result->fetchAssociative()) {
430
                $cache[$table][$makeCacheKey($row['pid'], $row['uid'])]
431
                    = $cache[$table][$makeCacheKey(-1, $row['uid'])]
432
                    = $row['index_name'];
433
            }
434
        }
435
436
        $cacheKey = $makeCacheKey($pid, $uid);
437
        $result = $cache[$table][$cacheKey] ?? '';
438
439
        if ($result === '') {
440
            self::log('No "index_name" with UID ' . $uid . ' and PID ' . $pid . ' found in table "' . $table . '"', LOG_SEVERITY_WARNING);
441
        }
442
443
        return $result;
444
    }
445
446
    /**
447
     * Get language name from ISO code
448
     *
449
     * @access public
450
     *
451
     * @param string $code: ISO 639-1 or ISO 639-2/B language code
452
     *
453
     * @return string Localized full name of language or unchanged input
454
     */
455
    public static function getLanguageName($code)
456
    {
457
        // Analyze code and set appropriate ISO table.
458
        $isoCode = strtolower(trim($code));
459
        if (preg_match('/^[a-z]{3}$/', $isoCode)) {
460
            $file = 'EXT:dlf/Resources/Private/Data/iso-639-2b.xlf';
461
        } elseif (preg_match('/^[a-z]{2}$/', $isoCode)) {
462
            $file = 'EXT:dlf/Resources/Private/Data/iso-639-1.xlf';
463
        } else {
464
            // No ISO code, return unchanged.
465
            return $code;
466
        }
467
        $lang = LocalizationUtility::translate('LLL:' . $file . ':' . $code);
468
        if (!empty($lang)) {
469
            return $lang;
470
        } else {
471
            self::log('Language code "' . $code . '" not found in ISO-639 table', LOG_SEVERITY_NOTICE);
472
            return $code;
473
        }
474
    }
475
476
    /**
477
     * Get all document structures as array
478
     *
479
     * @param int $pid: Get the "index_name" from this page only
480
     *
481
     * @return array
482
     */
483
    public static function getDocumentStructures($pid = -1)
484
    {
485
        // TODO: Against redundancy with getIndexNameFromUid
486
487
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
488
        $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_dlf_structures');
489
490
        $where = '';
491
        // Should we check for a specific PID, too?
492
        if ($pid !== -1) {
493
            $pid = max(intval($pid), 0);
494
            $where = $queryBuilder->expr()->eq('tx_dlf_structures.pid', $pid);
495
        }
496
497
        // Fetch document info for UIDs in $documentSet from DB
498
        $kitodoStructures = $queryBuilder
499
            ->select(
500
                'tx_dlf_structures.uid AS uid',
501
                'tx_dlf_structures.index_name AS indexName'
502
            )
503
            ->from('tx_dlf_structures')
504
            ->where($where)
505
            ->execute();
506
507
        $allStructures = $kitodoStructures->fetchAllAssociative();
508
509
        // make lookup-table indexName -> uid
510
        $allStructures = array_column($allStructures, 'indexName', 'uid');
511
512
        return $allStructures;
513
    }
514
515
    /**
516
     * Get the URN of an object
517
     * @see http://www.persistent-identifier.de/?link=316
518
     *
519
     * @access public
520
     *
521
     * @param string $base: The namespace and base URN
522
     * @param string $id: The object's identifier
523
     *
524
     * @return string Uniform Resource Name as string
525
     */
526
    public static function getURN($base, $id)
527
    {
528
        $concordance = [
529
            '0' => 1,
530
            '1' => 2,
531
            '2' => 3,
532
            '3' => 4,
533
            '4' => 5,
534
            '5' => 6,
535
            '6' => 7,
536
            '7' => 8,
537
            '8' => 9,
538
            '9' => 41,
539
            'a' => 18,
540
            'b' => 14,
541
            'c' => 19,
542
            'd' => 15,
543
            'e' => 16,
544
            'f' => 21,
545
            'g' => 22,
546
            'h' => 23,
547
            'i' => 24,
548
            'j' => 25,
549
            'k' => 42,
550
            'l' => 26,
551
            'm' => 27,
552
            'n' => 13,
553
            'o' => 28,
554
            'p' => 29,
555
            'q' => 31,
556
            'r' => 12,
557
            's' => 32,
558
            't' => 33,
559
            'u' => 11,
560
            'v' => 34,
561
            'w' => 35,
562
            'x' => 36,
563
            'y' => 37,
564
            'z' => 38,
565
            '-' => 39,
566
            ':' => 17,
567
        ];
568
        $urn = strtolower($base . $id);
569
        if (preg_match('/[^a-z\d:-]/', $urn)) {
570
            self::log('Invalid chars in given parameters', LOG_SEVERITY_WARNING);
571
            return '';
572
        }
573
        $digits = '';
574
        for ($i = 0, $j = strlen($urn); $i < $j; $i++) {
575
            $digits .= $concordance[substr($urn, $i, 1)];
576
        }
577
        $checksum = 0;
578
        for ($i = 0, $j = strlen($digits); $i < $j; $i++) {
579
            $checksum += ($i + 1) * intval(substr($digits, $i, 1));
580
        }
581
        $checksum = substr(intval($checksum / intval(substr($digits, -1, 1))), -1, 1);
582
        return $base . $id . $checksum;
583
    }
584
585
    /**
586
     * Check if given ID is a valid Pica Production Number (PPN)
587
     *
588
     * @access public
589
     *
590
     * @param string $id: The identifier to check
591
     *
592
     * @return bool Is $id a valid PPN?
593
     */
594
    public static function isPPN($id)
595
    {
596
        return self::checkIdentifier($id, 'PPN');
597
    }
598
599
    /**
600
     * Determine whether or not $url is a valid URL using HTTP or HTTPS scheme.
601
     *
602
     * @param string $url
603
     *
604
     * @return bool
605
     */
606
    public static function isValidHttpUrl($url)
607
    {
608
        if (!GeneralUtility::isValidUrl($url)) {
609
            return false;
610
        }
611
612
        $parsed = parse_url($url);
613
        $scheme = $parsed['scheme'] ?? '';
614
        $schemeNormalized = strtolower($scheme);
615
616
        return $schemeNormalized === 'http' || $schemeNormalized === 'https';
617
    }
618
619
    /**
620
     * Merges two arrays recursively and actually returns the modified array.
621
     * @see ArrayUtility::mergeRecursiveWithOverrule()
622
     *
623
     * @access public
624
     *
625
     * @param array $original: Original array
626
     * @param array $overrule: Overrule array, overruling the original array
627
     * @param bool $addKeys: If set to false, keys that are not found in $original will not be set
628
     * @param bool $includeEmptyValues: If set, values from $overrule will overrule if they are empty
629
     * @param bool $enableUnsetFeature: If set, special value "__UNSET" can be used in the overrule array to unset keys in the original array
630
     *
631
     * @return array Merged array
632
     */
633
    public static function mergeRecursiveWithOverrule(array $original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
634
    {
635
        ArrayUtility::mergeRecursiveWithOverrule($original, $overrule, $addKeys, $includeEmptyValues, $enableUnsetFeature);
636
        return $original;
637
    }
638
639
    /**
640
     * Fetches and renders all available flash messages from the queue.
641
     *
642
     * @access public
643
     *
644
     * @param string $queue: The queue's unique identifier
645
     *
646
     * @return string All flash messages in the queue rendered as HTML.
647
     */
648
    public static function renderFlashMessages($queue = 'kitodo.default.flashMessages')
649
    {
650
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
651
        $flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier($queue);
652
        $flashMessages = $flashMessageQueue->getAllMessagesAndFlush();
653
        $content = GeneralUtility::makeInstance(\Kitodo\Dlf\Common\KitodoFlashMessageRenderer::class)
654
            ->render($flashMessages);
655
        return $content;
656
    }
657
658
    /**
659
     * This translates an internal "index_name"
660
     *
661
     * @access public
662
     *
663
     * @param string $index_name: The internal "index_name" to translate
664
     * @param string $table: Get the translation from this table
665
     * @param string $pid: Get the translation from this page
666
     *
667
     * @return string Localized label for $index_name
668
     */
669
    public static function translate($index_name, $table, $pid)
670
    {
671
        // Load labels into static variable for future use.
672
        static $labels = [];
673
        // Sanitize input.
674
        $pid = max(intval($pid), 0);
675
        if (!$pid) {
676
            self::log('Invalid PID ' . $pid . ' for translation', LOG_SEVERITY_WARNING);
677
            return $index_name;
678
        }
679
        /** @var PageRepository $pageRepository */
680
        $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
681
682
        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
683
        $languageContentId = $languageAspect->getContentId();
684
685
        // Check if "index_name" is an UID.
686
        if (MathUtility::canBeInterpretedAsInteger($index_name)) {
687
            $index_name = self::getIndexNameFromUid($index_name, $table, $pid);
688
        }
689
        /* $labels already contains the translated content element, but with the index_name of the translated content element itself
690
         * and not with the $index_name of the original that we receive here. So we have to determine the index_name of the
691
         * associated translated content element. E.g. $labels['title0'] != $index_name = title. */
692
693
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
694
            ->getQueryBuilderForTable($table);
695
696
        // First fetch the uid of the received index_name
697
        $result = $queryBuilder
698
            ->select(
699
                $table . '.uid AS uid',
700
                $table . '.l18n_parent AS l18n_parent'
701
            )
702
            ->from($table)
703
            ->where(
704
                $queryBuilder->expr()->eq($table . '.pid', $pid),
705
                $queryBuilder->expr()->eq($table . '.index_name', $queryBuilder->expr()->literal($index_name)),
706
                self::whereExpression($table, true)
707
            )
708
            ->setMaxResults(1)
709
            ->execute();
710
711
        $row = $result->fetchAssociative();
712
713
        if ($row) {
714
            // Now we use the uid of the l18_parent to fetch the index_name of the translated content element.
715
            $result = $queryBuilder
716
                ->select($table . '.index_name AS index_name')
717
                ->from($table)
718
                ->where(
719
                    $queryBuilder->expr()->eq($table . '.pid', $pid),
720
                    $queryBuilder->expr()->eq($table . '.uid', $row['l18n_parent']),
721
                    $queryBuilder->expr()->eq($table . '.sys_language_uid', intval($languageContentId)),
722
                    self::whereExpression($table, true)
723
                )
724
                ->setMaxResults(1)
725
                ->execute();
726
727
            $row = $result->fetchAssociative();
728
729
            if ($row) {
730
                // If there is an translated content element, overwrite the received $index_name.
731
                $index_name = $row['index_name'];
732
            }
733
        }
734
735
        // Check if we already got a translation.
736
        if (empty($labels[$table][$pid][$languageContentId][$index_name])) {
737
            // Check if this table is allowed for translation.
738
            if (in_array($table, ['tx_dlf_collections', 'tx_dlf_libraries', 'tx_dlf_metadata', 'tx_dlf_structures'])) {
739
                $additionalWhere = $queryBuilder->expr()->in($table . '.sys_language_uid', [-1, 0]);
740
                if ($languageContentId > 0) {
741
                    $additionalWhere = $queryBuilder->expr()->andX(
742
                        $queryBuilder->expr()->orX(
743
                            $queryBuilder->expr()->in($table . '.sys_language_uid', [-1, 0]),
744
                            $queryBuilder->expr()->eq($table . '.sys_language_uid', intval($languageContentId))
745
                        ),
746
                        $queryBuilder->expr()->eq($table . '.l18n_parent', 0)
747
                    );
748
                }
749
750
                // Get labels from database.
751
                $result = $queryBuilder
752
                    ->select('*')
753
                    ->from($table)
754
                    ->where(
755
                        $queryBuilder->expr()->eq($table . '.pid', $pid),
756
                        $additionalWhere,
757
                        self::whereExpression($table, true)
758
                    )
759
                    ->setMaxResults(10000)
760
                    ->execute();
761
762
                if ($result->rowCount() > 0) {
763
                    while ($resArray = $result->fetchAssociative()) {
764
                        // Overlay localized labels if available.
765
                        if ($languageContentId > 0) {
766
                            $resArray = $pageRepository->getRecordOverlay($table, $resArray, $languageContentId, $languageAspect->getLegacyOverlayType());
767
                        }
768
                        if ($resArray) {
769
                            $labels[$table][$pid][$languageContentId][$resArray['index_name']] = $resArray['label'];
770
                        }
771
                    }
772
                } else {
773
                    self::log('No translation with PID ' . $pid . ' available in table "' . $table . '" or translation not accessible', LOG_SEVERITY_NOTICE);
774
                }
775
            } else {
776
                self::log('No translations available for table "' . $table . '"', LOG_SEVERITY_WARNING);
777
            }
778
        }
779
780
        if (!empty($labels[$table][$pid][$languageContentId][$index_name])) {
781
            return $labels[$table][$pid][$languageContentId][$index_name];
782
        } else {
783
            return $index_name;
784
        }
785
    }
786
787
    /**
788
     * This returns the additional WHERE expression of a table based on its TCA configuration
789
     *
790
     * @access public
791
     *
792
     * @param string $table: Table name as defined in TCA
793
     * @param bool $showHidden: Ignore the hidden flag?
794
     *
795
     * @return string Additional WHERE expression
796
     */
797
    public static function whereExpression($table, $showHidden = false)
798
    {
799
        // TODO: Check with applicationType; TYPO3_MODE is removed in v12
800
        if (\TYPO3_MODE === 'FE') {
801
            // Should we ignore the record's hidden flag?
802
            $ignoreHide = 0;
803
            if ($showHidden) {
804
                $ignoreHide = 1;
805
            }
806
            /** @var PageRepository $pageRepository */
807
            $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
808
809
            $expression = $pageRepository->enableFields($table, $ignoreHide);
810
            if (!empty($expression)) {
811
                return substr($expression, 5);
812
            } else {
813
                return '';
814
            }
815
            // TODO: Check with applicationType; TYPO3_MODE is removed in v12
816
        } elseif (\TYPO3_MODE === 'BE') {
817
            return GeneralUtility::makeInstance(ConnectionPool::class)
818
                ->getQueryBuilderForTable($table)
819
                ->expr()
820
                ->eq($table . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'], 0);
821
        } else {
822
            self::log('Unexpected TYPO3_MODE', LOG_SEVERITY_ERROR);
823
            return '1=-1';
824
        }
825
    }
826
827
    /**
828
     * Prevent instantiation by hiding the constructor
829
     *
830
     * @access private
831
     */
832
    private function __construct()
833
    {
834
        // This is a static class, thus no instances should be created.
835
    }
836
837
    /**
838
     * Returns the LanguageService
839
     *
840
     * @return LanguageService
841
     */
842
    public static function getLanguageService(): LanguageService
843
    {
844
        return $GLOBALS['LANG'];
845
    }
846
847
    /**
848
     * Replacement for the TYPO3 GeneralUtility::getUrl().
849
     *
850
     * This method respects the User Agent settings from extConf
851
     *
852
     * @access public
853
     *
854
     * @param string $url
855
     *
856
     * @return string|bool
857
     */
858
    public static function getUrl(string $url)
859
    {
860
        if (!Helper::isValidHttpUrl($url)) {
861
            return false;
862
        }
863
864
        // Get extension configuration.
865
        $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('dlf');
866
867
        /** @var RequestFactory $requestFactory */
868
        $requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
869
        $configuration = [
870
            'timeout' => 30,
871
            'headers' => [
872
                'User-Agent' => $extConf['useragent'] ?? 'Kitodo.Presentation Proxy',
873
            ],
874
        ];
875
        try {
876
            $response = $requestFactory->request($url, 'GET', $configuration);
877
        } catch (\Exception $e) {
878
            self::log('Could not fetch data from URL "' . $url . '". Error: ' . $e->getMessage() . '.', LOG_SEVERITY_WARNING);
879
            return false;
880
        }
881
        $content  = $response->getBody()->getContents();
882
883
        return $content;
884
    }
885
886
    /**
887
     * Check if given value is a valid XML ID.
888
     * @see https://www.w3.org/TR/xmlschema-2/#ID
889
     *
890
     * @access public
891
     *
892
     * @param mixed $id: The ID value to check
893
     *
894
     * @return bool: TRUE if $id is valid XML ID, FALSE otherwise
895
     */
896
    public static function isValidXmlId($id): bool
897
    {
898
        return preg_match('/^[_a-z][_a-z0-9-.]*$/i', $id) === 1;
899
    }
900
}
901