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 (#754)
by Alexander
02:42
created

Helper::getMessage()   A

Complexity

Conditions 6
Paths 18

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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