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 — dev-extbase-fluid (#746)
by Alexander
03:26
created

Helper::mergeRecursiveWithOverrule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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