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.
Completed
Push — master ( e8d8b4...e25fb8 )
by Alexander
32s queued 29s
created

Helper   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 960
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 117
eloc 435
c 6
b 0
f 0
dl 0
loc 960
rs 2

24 Methods

Rating   Name   Duplication   Size   Complexity  
A addMessage() 0 13 1
D checkIdentifier() 0 54 20
A decrypt() 0 28 6
A getUnqualifiedClassName() 0 4 1
A getCleanString() 0 11 1
A getHookObjects() 0 9 3
A encrypt() 0 23 5
A getIndexNameFromUid() 0 39 5
A digest() 0 9 2
A log() 0 19 5
A saveToSession() 0 19 4
A whereExpression() 0 22 5
B getMessage() 0 30 8
A isPPN() 0 3 1
A loadFromSession() 0 16 4
A __construct() 0 2 1
A mergeRecursiveWithOverrule() 0 4 1
C translate() 0 111 13
A getURN() 0 57 4
A getUidFromIndexName() 0 38 5
B getLanguageName() 0 32 8
B processDBasAdmin() 0 35 8
A renderFlashMessages() 0 8 1
A getLabelFromUid() 0 39 5

How to fix   Complexity   

Complex Class

Complex classes like Helper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Helper, and based on these observations, apply Extract Interface, too.

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