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
Push — dev-extbase-fluid ( efc5aa...0bcbe0 )
by Alexander
06:04 queued 03:09
created

Helper::isValidHttpUrl()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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