Passed
Pull Request — master (#87)
by Alexander
02:55
created

Helper::getXmlFileAsString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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