Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — dev-extbase-fluid (#757)
by Alexander
05:02 queued 02:23
created

Helper::polyfillExtbaseClassesForTYPO3v9()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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