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 — 3.3.x (#766)
by
unknown
15:58 queued 12:33
created

Helper::checkIdentifier()   D

Complexity

Conditions 20
Paths 70

Size

Total Lines 54
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 43
c 0
b 0
f 0
dl 0
loc 54
rs 4.1666
cc 20
nc 70
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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