Passed
Pull Request — master (#54)
by
unknown
13:48
created

Helper::getIndexNameFromUid()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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