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.
Completed
Push — dev-extbase-fluid ( bbb213...f435f6 )
by Alexander
04:09 queued 04:05
created

Helper::isPPN()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
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
     * Determine whether or not $url is a valid URL using HTTP or HTTPS scheme.
696
     *
697
     * @param string $url
698
     *
699
     * @return bool
700
     */
701
    public static function isValidHttpUrl($url)
702
    {
703
        if (!GeneralUtility::isValidUrl($url)) {
704
            return false;
705
        }
706
707
        $parsed = parse_url($url);
708
        $scheme = $parsed['scheme'] ?? '';
709
        $schemeNormalized = strtolower($scheme);
710
711
        return $schemeNormalized === 'http' || $schemeNormalized === 'https';
712
    }
713
714
    /**
715
     * Load value from user's session.
716
     *
717
     * @access public
718
     *
719
     * @param string $key: Session data key for retrieval
720
     *
721
     * @return mixed Session value for given key or null on failure
722
     */
723
    public static function loadFromSession($key)
724
    {
725
        // Cast to string for security reasons.
726
        $key = (string) $key;
727
        if (!$key) {
728
            self::log('Invalid key "' . $key . '" for session data retrieval', LOG_SEVERITY_WARNING);
729
            return;
730
        }
731
        // Get the session data.
732
        if (\TYPO3_MODE === 'FE') {
733
            return $GLOBALS['TSFE']->fe_user->getKey('ses', $key);
734
        } elseif (\TYPO3_MODE === 'BE') {
735
            return $GLOBALS['BE_USER']->getSessionData($key);
736
        } else {
737
            self::log('Unexpected TYPO3_MODE "' . \TYPO3_MODE . '"', LOG_SEVERITY_ERROR);
738
            return;
739
        }
740
    }
741
742
    /**
743
     * Merges two arrays recursively and actually returns the modified array.
744
     * @see \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule()
745
     *
746
     * @access public
747
     *
748
     * @param array $original: Original array
749
     * @param array $overrule: Overrule array, overruling the original array
750
     * @param bool $addKeys: If set to false, keys that are not found in $original will not be set
751
     * @param bool $includeEmptyValues: If set, values from $overrule will overrule if they are empty
752
     * @param bool $enableUnsetFeature: If set, special value "__UNSET" can be used in the overrule array to unset keys in the original array
753
     *
754
     * @return array Merged array
755
     */
756
    public static function mergeRecursiveWithOverrule(array $original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
757
    {
758
        \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($original, $overrule, $addKeys, $includeEmptyValues, $enableUnsetFeature);
759
        return $original;
760
    }
761
762
    /**
763
     * Fetches and renders all available flash messages from the queue.
764
     *
765
     * @access public
766
     *
767
     * @param string $queue: The queue's unique identifier
768
     *
769
     * @return string All flash messages in the queue rendered as HTML.
770
     */
771
    public static function renderFlashMessages($queue = 'kitodo.default.flashMessages')
772
    {
773
        $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
774
        $flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier($queue);
775
        $flashMessages = $flashMessageQueue->getAllMessagesAndFlush();
776
        $content = GeneralUtility::makeInstance(\Kitodo\Dlf\Common\KitodoFlashMessageRenderer::class)
777
            ->render($flashMessages);
778
        return $content;
779
    }
780
781
    /**
782
     * Save given value to user's session.
783
     *
784
     * @access public
785
     *
786
     * @param mixed $value: Value to save
787
     * @param string $key: Session data key for saving
788
     *
789
     * @return bool true on success, false on failure
790
     */
791
    public static function saveToSession($value, $key)
792
    {
793
        // Cast to string for security reasons.
794
        $key = (string) $key;
795
        if (!$key) {
796
            self::log('Invalid key "' . $key . '" for session data saving', LOG_SEVERITY_WARNING);
797
            return false;
798
        }
799
        // Save value in session data.
800
        if (\TYPO3_MODE === 'FE') {
801
            $GLOBALS['TSFE']->fe_user->setKey('ses', $key, $value);
802
            $GLOBALS['TSFE']->fe_user->storeSessionData();
803
            return true;
804
        } elseif (\TYPO3_MODE === 'BE') {
805
            $GLOBALS['BE_USER']->setAndSaveSessionData($key, $value);
806
            return true;
807
        } else {
808
            self::log('Unexpected TYPO3_MODE "' . \TYPO3_MODE . '"', LOG_SEVERITY_ERROR);
809
            return false;
810
        }
811
    }
812
813
    /**
814
     * This translates an internal "index_name"
815
     *
816
     * @access public
817
     *
818
     * @param string $index_name: The internal "index_name" to translate
819
     * @param string $table: Get the translation from this table
820
     * @param string $pid: Get the translation from this page
821
     *
822
     * @return string Localized label for $index_name
823
     */
824
    public static function translate($index_name, $table, $pid)
825
    {
826
        // Load labels into static variable for future use.
827
        static $labels = [];
828
        // Sanitize input.
829
        $pid = max(intval($pid), 0);
830
        if (!$pid) {
831
            self::log('Invalid PID ' . $pid . ' for translation', LOG_SEVERITY_WARNING);
832
            return $index_name;
833
        }
834
        /** @var \TYPO3\CMS\Frontend\Page\PageRepository $pageRepository */
835
        $pageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
836
837
        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
838
839
        // Check if "index_name" is an UID.
840
        if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($index_name)) {
841
            $index_name = self::getIndexNameFromUid($index_name, $table, $pid);
842
        }
843
        /* $labels already contains the translated content element, but with the index_name of the translated content element itself
844
         * and not with the $index_name of the original that we receive here. So we have to determine the index_name of the
845
         * associated translated content element. E.g. $labels['title0'] != $index_name = title. */
846
847
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
848
            ->getQueryBuilderForTable($table);
849
850
        // First fetch the uid of the received index_name
851
        $result = $queryBuilder
852
            ->select(
853
                $table . '.uid AS uid',
854
                $table . '.l18n_parent AS l18n_parent'
855
            )
856
            ->from($table)
857
            ->where(
858
                $queryBuilder->expr()->eq($table . '.pid', $pid),
859
                $queryBuilder->expr()->eq($table . '.index_name', $queryBuilder->expr()->literal($index_name)),
860
                self::whereExpression($table, true)
861
            )
862
            ->setMaxResults(1)
863
            ->execute();
864
865
        $allResults = $result->fetchAll();
866
867
        if (count($allResults) == 1) {
868
            // Now we use the uid of the l18_parent to fetch the index_name of the translated content element.
869
            $resArray = $allResults[0];
870
871
            $result = $queryBuilder
872
                ->select($table . '.index_name AS index_name')
873
                ->from($table)
874
                ->where(
875
                    $queryBuilder->expr()->eq($table . '.pid', $pid),
876
                    $queryBuilder->expr()->eq($table . '.uid', $resArray['l18n_parent']),
877
                    $queryBuilder->expr()->eq($table . '.sys_language_uid', intval($languageAspect->getContentId())),
878
                    self::whereExpression($table, true)
879
                )
880
                ->setMaxResults(1)
881
                ->execute();
882
883
            $allResults = $result->fetchAll();
884
885
            if (count($allResults) == 1) {
886
                // If there is an translated content element, overwrite the received $index_name.
887
                $index_name = $allResults[0]['index_name'];
888
            }
889
        }
890
891
        // Check if we already got a translation.
892
        if (empty($labels[$table][$pid][$languageAspect->getContentId()][$index_name])) {
893
            // Check if this table is allowed for translation.
894
            if (in_array($table, ['tx_dlf_collections', 'tx_dlf_libraries', 'tx_dlf_metadata', 'tx_dlf_structures'])) {
895
                $additionalWhere = $queryBuilder->expr()->in($table . '.sys_language_uid', [-1, 0]);
896
                if ($languageAspect->getContentId() > 0) {
897
                    $additionalWhere = $queryBuilder->expr()->andX(
898
                        $queryBuilder->expr()->orX(
899
                            $queryBuilder->expr()->in($table . '.sys_language_uid', [-1, 0]),
900
                            $queryBuilder->expr()->eq($table . '.sys_language_uid', intval($languageAspect->getContentId()))
901
                        ),
902
                        $queryBuilder->expr()->eq($table . '.l18n_parent', 0)
903
                    );
904
                }
905
906
                // Get labels from database.
907
                $result = $queryBuilder
908
                    ->select('*')
909
                    ->from($table)
910
                    ->where(
911
                        $queryBuilder->expr()->eq($table . '.pid', $pid),
912
                        $additionalWhere,
913
                        self::whereExpression($table, true)
914
                    )
915
                    ->setMaxResults(10000)
916
                    ->execute();
917
918
                if ($result->rowCount() > 0) {
919
                    while ($resArray = $result->fetch()) {
920
                        // Overlay localized labels if available.
921
                        if ($languageAspect->getContentId() > 0) {
922
                            $resArray = $pageRepository->getRecordOverlay($table, $resArray, $languageAspect->getContentId(), $languageAspect->getLegacyOverlayType());
923
                        }
924
                        if ($resArray) {
925
                            $labels[$table][$pid][$languageAspect->getContentId()][$resArray['index_name']] = $resArray['label'];
926
                        }
927
                    }
928
                } else {
929
                    self::log('No translation with PID ' . $pid . ' available in table "' . $table . '" or translation not accessible', LOG_SEVERITY_NOTICE);
930
                }
931
            } else {
932
                self::log('No translations available for table "' . $table . '"', LOG_SEVERITY_WARNING);
933
            }
934
        }
935
936
        if (!empty($labels[$table][$pid][$languageAspect->getContentId()][$index_name])) {
937
            return $labels[$table][$pid][$languageAspect->getContentId()][$index_name];
938
        } else {
939
            return $index_name;
940
        }
941
    }
942
943
    /**
944
     * This returns the additional WHERE expression of a table based on its TCA configuration
945
     *
946
     * @access public
947
     *
948
     * @param string $table: Table name as defined in TCA
949
     * @param bool $showHidden: Ignore the hidden flag?
950
     *
951
     * @return string Additional WHERE expression
952
     */
953
    public static function whereExpression($table, $showHidden = false)
954
    {
955
        if (\TYPO3_MODE === 'FE') {
956
            // Should we ignore the record's hidden flag?
957
            $ignoreHide = 0;
958
            if ($showHidden) {
959
                $ignoreHide = 1;
960
            }
961
            /** @var \TYPO3\CMS\Frontend\Page\PageRepository $pageRepository */
962
            $pageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
963
964
            $expression = $pageRepository->enableFields($table, $ignoreHide);
965
            if (!empty($expression)) {
966
                return substr($expression, 5);
967
            } else {
968
                return '';
969
            }
970
        } elseif (\TYPO3_MODE === 'BE') {
971
            return GeneralUtility::makeInstance(ConnectionPool::class)
972
                ->getQueryBuilderForTable($table)
973
                ->expr()
974
                ->eq($table . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'], 0);
975
        } else {
976
            self::log('Unexpected TYPO3_MODE "' . \TYPO3_MODE . '"', LOG_SEVERITY_ERROR);
977
            return '1=-1';
978
        }
979
    }
980
981
    /**
982
     * Prevent instantiation by hiding the constructor
983
     *
984
     * @access private
985
     */
986
    private function __construct()
987
    {
988
        // This is a static class, thus no instances should be created.
989
    }
990
991
    /**
992
     * Returns the LanguageService
993
     *
994
     * @return LanguageService
995
     */
996
    public static function getLanguageService(): LanguageService
997
    {
998
        return $GLOBALS['LANG'];
999
    }
1000
1001
    /**
1002
     * Make classname configuration from `Classes.php` available in contexts
1003
     * where it normally isn't, and where the classical way via TypoScript won't
1004
     * work either.
1005
     *
1006
     * This transforms the structure used in `Classes.php` to that used in
1007
     * `ext_typoscript_setup.txt`. See commit 5e6110fb for a similar approach.
1008
     *
1009
     * @deprecated Remove once we drop support for TYPO3v9
1010
     *
1011
     * @access public
1012
     */
1013
    public static function polyfillExtbaseClassesForTYPO3v9()
1014
    {
1015
        $classes = require __DIR__ . '/../../Configuration/Extbase/Persistence/Classes.php';
1016
1017
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
1018
        $configurationManager = $objectManager->get(ConfigurationManager::class);
1019
        $frameworkConfiguration = $configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1020
1021
        $extbaseClassmap = &$frameworkConfiguration['persistence']['classes'];
1022
        if ($extbaseClassmap === null) {
1023
            $extbaseClassmap = [];
1024
        }
1025
1026
        foreach ($classes as $className => $classConfig) {
1027
            $extbaseClass = &$extbaseClassmap[$className];
1028
            if ($extbaseClass === null) {
1029
                $extbaseClass = [];
1030
            }
1031
            if (!isset($extbaseClass['mapping'])) {
1032
                $extbaseClass['mapping'] = [];
1033
            }
1034
            $extbaseClass['mapping']['tableName'] = $classConfig['tableName'];
1035
        }
1036
1037
        $configurationManager->setConfiguration($frameworkConfiguration);
1038
    }
1039
}
1040