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 — master ( 85f5d2...3e7f1d )
by Alexander
24s queued 19s
created

Helper::getXmlFileAsString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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