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