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 — master ( a50da4...3e7fda )
by
unknown
08:59
created

Helper::isPPN()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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