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 — master (#647)
by Alexander
04:59 queued 02:33
created

Helper::getCleanString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 11
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
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
        // Turn off libxml's error logging.
209
        $libxmlErrors = libxml_use_internal_errors(true);
210
        // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
211
        $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
212
        // Try to load XML from file.
213
        $xml = simplexml_load_string($content);
214
        // reset entity loader setting
215
        libxml_disable_entity_loader($previousValueOfEntityLoader);
216
        // Reset libxml's error logging.
217
        libxml_use_internal_errors($libxmlErrors);
218
        return $xml;
219
    }
220
221
    /**
222
     * Add a message to the TYPO3 log
223
     *
224
     * @access public
225
     *
226
     * @param string $message: The message to log
227
     * @param int $severity: The severity of the message
228
     *                       0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
229
     *
230
     * @return void
231
     */
232
    //TODO: find better way to handle logger in static class
233
    public static function log($message, $severity = 0)
234
    {
235
        $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__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
     * Load value from user's session.
697
     *
698
     * @access public
699
     *
700
     * @param string $key: Session data key for retrieval
701
     *
702
     * @return mixed Session value for given key or null on failure
703
     */
704
    public static function loadFromSession($key)
705
    {
706
        // Cast to string for security reasons.
707
        $key = (string) $key;
708
        if (!$key) {
709
            self::log('Invalid key "' . $key . '" for session data retrieval', LOG_SEVERITY_WARNING);
710
            return;
711
        }
712
        // Get the session data.
713
        if (\TYPO3_MODE === 'FE') {
714
            return $GLOBALS['TSFE']->fe_user->getKey('ses', $key);
715
        } elseif (\TYPO3_MODE === 'BE') {
716
            return $GLOBALS['BE_USER']->getSessionData($key);
717
        } else {
718
            self::log('Unexpected TYPO3_MODE "' . \TYPO3_MODE . '"', LOG_SEVERITY_ERROR);
719
            return;
720
        }
721
    }
722
723
    /**
724
     * Merges two arrays recursively and actually returns the modified array.
725
     * @see \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule()
726
     *
727
     * @access public
728
     *
729
     * @param array $original: Original array
730
     * @param array $overrule: Overrule array, overruling the original array
731
     * @param bool $addKeys: If set to false, keys that are not found in $original will not be set
732
     * @param bool $includeEmptyValues: If set, values from $overrule will overrule if they are empty
733
     * @param bool $enableUnsetFeature: If set, special value "__UNSET" can be used in the overrule array to unset keys in the original array
734
     *
735
     * @return array Merged array
736
     */
737
    public static function mergeRecursiveWithOverrule(array $original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
738
    {
739
        \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($original, $overrule, $addKeys, $includeEmptyValues, $enableUnsetFeature);
740
        return $original;
741
    }
742
743
    /**
744
     * Process a data and/or command map with TYPO3 core engine as admin.
745
     *
746
     * @access public
747
     *
748
     * @param array $data: Data map
749
     * @param array $cmd: Command map
750
     * @param bool $reverseOrder: Should the data map be reversed?
751
     * @param bool $cmdFirst: Should the command map be processed first?
752
     *
753
     * @return array Array of substituted "NEW..." identifiers and their actual UIDs.
754
     */
755
    public static function processDBasAdmin(array $data = [], array $cmd = [], $reverseOrder = false, $cmdFirst = false)
756
    {
757
        if (
758
            \TYPO3_MODE === 'BE'
759
            && $GLOBALS['BE_USER']->isAdmin()
760
        ) {
761
            // Instantiate TYPO3 core engine.
762
            $dataHandler = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
763
            // We do not use workspaces and have to bypass restrictions in DataHandler.
764
            $dataHandler->bypassWorkspaceRestrictions = true;
765
            // Load data and command arrays.
766
            $dataHandler->start($data, $cmd);
767
            // Process command map first if default order is reversed.
768
            if (
769
                !empty($cmd)
770
                && $cmdFirst
771
            ) {
772
                $dataHandler->process_cmdmap();
773
            }
774
            // Process data map.
775
            if (!empty($data)) {
776
                $dataHandler->reverseOrder = $reverseOrder;
777
                $dataHandler->process_datamap();
778
            }
779
            // Process command map if processing order is not reversed.
780
            if (
781
                !empty($cmd)
782
                && !$cmdFirst
783
            ) {
784
                $dataHandler->process_cmdmap();
785
            }
786
            return $dataHandler->substNEWwithIDs;
787
        } else {
788
            self::log('Current backend user has no admin privileges', LOG_SEVERITY_ERROR);
789
            return [];
790
        }
791
    }
792
793
    /**
794
     * Fetches and renders all available flash messages from the queue.
795
     *
796
     * @access public
797
     *
798
     * @param string $queue: The queue's unique identifier
799
     *
800
     * @return string All flash messages in the queue rendered as HTML.
801
     */
802
    public static function renderFlashMessages($queue = 'kitodo.default.flashMessages')
803
    {
804
        $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
805
        $flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier($queue);
806
        $flashMessages = $flashMessageQueue->getAllMessagesAndFlush();
807
        $content = GeneralUtility::makeInstance(\Kitodo\Dlf\Common\KitodoFlashMessageRenderer::class)
808
            ->render($flashMessages);
809
        return $content;
810
    }
811
812
    /**
813
     * Save given value to user's session.
814
     *
815
     * @access public
816
     *
817
     * @param mixed $value: Value to save
818
     * @param string $key: Session data key for saving
819
     *
820
     * @return bool true on success, false on failure
821
     */
822
    public static function saveToSession($value, $key)
823
    {
824
        // Cast to string for security reasons.
825
        $key = (string) $key;
826
        if (!$key) {
827
            self::log('Invalid key "' . $key . '" for session data saving', LOG_SEVERITY_WARNING);
828
            return false;
829
        }
830
        // Save value in session data.
831
        if (\TYPO3_MODE === 'FE') {
832
            $GLOBALS['TSFE']->fe_user->setKey('ses', $key, $value);
833
            $GLOBALS['TSFE']->fe_user->storeSessionData();
834
            return true;
835
        } elseif (\TYPO3_MODE === 'BE') {
836
            $GLOBALS['BE_USER']->setAndSaveSessionData($key, $value);
837
            return true;
838
        } else {
839
            self::log('Unexpected TYPO3_MODE "' . \TYPO3_MODE . '"', LOG_SEVERITY_ERROR);
840
            return false;
841
        }
842
    }
843
844
    /**
845
     * This translates an internal "index_name"
846
     *
847
     * @access public
848
     *
849
     * @param string $index_name: The internal "index_name" to translate
850
     * @param string $table: Get the translation from this table
851
     * @param string $pid: Get the translation from this page
852
     *
853
     * @return string Localized label for $index_name
854
     */
855
    public static function translate($index_name, $table, $pid)
856
    {
857
        // Load labels into static variable for future use.
858
        static $labels = [];
859
        // Sanitize input.
860
        $pid = max(intval($pid), 0);
861
        if (!$pid) {
862
            self::log('Invalid PID ' . $pid . ' for translation', LOG_SEVERITY_WARNING);
863
            return $index_name;
864
        }
865
        // Check if "index_name" is an UID.
866
        if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($index_name)) {
867
            $index_name = self::getIndexNameFromUid($index_name, $table, $pid);
868
        }
869
        /* $labels already contains the translated content element, but with the index_name of the translated content element itself
870
         * and not with the $index_name of the original that we receive here. So we have to determine the index_name of the
871
         * associated translated content element. E.g. $labels['title0'] != $index_name = title. */
872
873
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
874
            ->getQueryBuilderForTable($table);
875
876
        // First fetch the uid of the received index_name
877
        $result = $queryBuilder
878
            ->select(
879
                $table . '.uid AS uid',
880
                $table . '.l18n_parent AS l18n_parent'
881
            )
882
            ->from($table)
883
            ->where(
884
                $queryBuilder->expr()->eq($table . '.pid', $pid),
885
                $queryBuilder->expr()->eq($table . '.index_name', $queryBuilder->expr()->literal($index_name)),
886
                self::whereExpression($table, true)
887
            )
888
            ->setMaxResults(1)
889
            ->execute();
890
891
        $allResults = $result->fetchAll();
892
893
        if (count($allResults) == 1) {
894
            // Now we use the uid of the l18_parent to fetch the index_name of the translated content element.
895
            $resArray = $allResults[0];
896
897
            $result = $queryBuilder
898
                ->select($table . '.index_name AS index_name')
899
                ->from($table)
900
                ->where(
901
                    $queryBuilder->expr()->eq($table . '.pid', $pid),
902
                    $queryBuilder->expr()->eq($table . '.uid', $resArray['l18n_parent']),
903
                    $queryBuilder->expr()->eq($table . '.sys_language_uid', intval($GLOBALS['TSFE']->sys_language_content)),
904
                    self::whereExpression($table, true)
905
                )
906
                ->setMaxResults(1)
907
                ->execute();
908
909
            $allResults = $result->fetchAll();
910
911
            if (count($allResults) == 1) {
912
                // If there is an translated content element, overwrite the received $index_name.
913
                $index_name = $allResults[0]['index_name'];
914
            }
915
        }
916
917
        // Check if we already got a translation.
918
        if (empty($labels[$table][$pid][$GLOBALS['TSFE']->sys_language_content][$index_name])) {
919
            // Check if this table is allowed for translation.
920
            if (in_array($table, ['tx_dlf_collections', 'tx_dlf_libraries', 'tx_dlf_metadata', 'tx_dlf_structures'])) {
921
                $additionalWhere = $queryBuilder->expr()->in($table . '.sys_language_uid', [-1, 0]);
922
                if ($GLOBALS['TSFE']->sys_language_content > 0) {
923
                    $additionalWhere = $queryBuilder->expr()->andX(
924
                        $queryBuilder->expr()->orX(
925
                            $queryBuilder->expr()->in($table . '.sys_language_uid', [-1, 0]),
926
                            $queryBuilder->expr()->eq($table . '.sys_language_uid', intval($GLOBALS['TSFE']->sys_language_content))
927
                        ),
928
                        $queryBuilder->expr()->eq($table . '.l18n_parent', 0)
929
                    );
930
                }
931
932
                // Get labels from database.
933
                $result = $queryBuilder
934
                    ->select('*')
935
                    ->from($table)
936
                    ->where(
937
                        $queryBuilder->expr()->eq($table . '.pid', $pid),
938
                        $additionalWhere,
939
                        self::whereExpression($table, true)
940
                    )
941
                    ->setMaxResults(10000)
942
                    ->execute();
943
944
                if ($result->rowCount() > 0) {
945
                    while ($resArray = $result->fetch()) {
946
                        // Overlay localized labels if available.
947
                        if ($GLOBALS['TSFE']->sys_language_content > 0) {
948
                            $resArray = $GLOBALS['TSFE']->sys_page->getRecordOverlay($table, $resArray, $GLOBALS['TSFE']->sys_language_content, $GLOBALS['TSFE']->sys_language_contentOL);
949
                        }
950
                        if ($resArray) {
951
                            $labels[$table][$pid][$GLOBALS['TSFE']->sys_language_content][$resArray['index_name']] = $resArray['label'];
952
                        }
953
                    }
954
                } else {
955
                    self::log('No translation with PID ' . $pid . ' available in table "' . $table . '" or translation not accessible', LOG_SEVERITY_NOTICE);
956
                }
957
            } else {
958
                self::log('No translations available for table "' . $table . '"', LOG_SEVERITY_WARNING);
959
            }
960
        }
961
962
        if (!empty($labels[$table][$pid][$GLOBALS['TSFE']->sys_language_content][$index_name])) {
963
            return $labels[$table][$pid][$GLOBALS['TSFE']->sys_language_content][$index_name];
964
        } else {
965
            return $index_name;
966
        }
967
    }
968
969
    /**
970
     * This returns the additional WHERE expression of a table based on its TCA configuration
971
     *
972
     * @access public
973
     *
974
     * @param string $table: Table name as defined in TCA
975
     * @param bool $showHidden: Ignore the hidden flag?
976
     *
977
     * @return string Additional WHERE expression
978
     */
979
    public static function whereExpression($table, $showHidden = false)
980
    {
981
        if (\TYPO3_MODE === 'FE') {
982
            // Should we ignore the record's hidden flag?
983
            $ignoreHide = 0;
984
            if ($showHidden) {
985
                $ignoreHide = 1;
986
            }
987
            $expression = $GLOBALS['TSFE']->sys_page->enableFields($table, $ignoreHide);
988
            if (!empty($expression)) {
989
                return substr($expression, 5);
990
            } else {
991
                return '';
992
            }
993
        } elseif (\TYPO3_MODE === 'BE') {
994
            return GeneralUtility::makeInstance(ConnectionPool::class)
995
                ->getQueryBuilderForTable($table)
996
                ->expr()
997
                ->eq($table . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'], 0);
998
        } else {
999
            self::log('Unexpected TYPO3_MODE "' . \TYPO3_MODE . '"', LOG_SEVERITY_ERROR);
1000
            return '1=-1';
1001
        }
1002
    }
1003
1004
    /**
1005
     * Prevent instantiation by hiding the constructor
1006
     *
1007
     * @access private
1008
     */
1009
    private function __construct()
1010
    {
1011
        // This is a static class, thus no instances should be created.
1012
    }
1013
}
1014