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 (#768)
by
unknown
02:46
created

Helper::fromLazy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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