Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — dev-extbase-fluid (#754)
by Alexander
03:00
created

Solr::search()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 58
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 41
c 0
b 0
f 0
dl 0
loc 58
rs 9.264
cc 4
nc 8
nop 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Solr::_getCore() 0 3 1
A Solr::_getLimit() 0 3 1
A Solr::search_raw() 0 22 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Psr\Log\LoggerAwareInterface;
16
use Psr\Log\LoggerAwareTrait;
17
use TYPO3\CMS\Core\Cache\CacheManager;
18
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
19
use TYPO3\CMS\Core\Database\ConnectionPool;
20
use TYPO3\CMS\Core\Log\LogManager;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
use TYPO3\CMS\Core\Utility\MathUtility;
23
24
/**
25
 * Solr class for the 'dlf' extension
26
 *
27
 * @author Sebastian Meyer <[email protected]>
28
 * @author Henrik Lochmann <[email protected]>
29
 * @package TYPO3
30
 * @subpackage dlf
31
 * @access public
32
 * @property-read string|null $core This holds the core name for the current instance
33
 * @property-write int $cPid This holds the PID for the configuration
34
 * @property int $limit This holds the max results
35
 * @property-read int $numberOfHits This holds the number of hits for last search
36
 * @property-write array $params This holds the additional query parameters
37
 * @property-read bool $ready Is the Solr service instantiated successfully?
38
 * @property-read \Solarium\Client $service This holds the Solr service object
39
 */
40
class Solr implements LoggerAwareInterface
41
{
42
    use LoggerAwareTrait;
43
44
    /**
45
     * This holds the Solr configuration
46
     *
47
     * @var array
48
     * @access protected
49
     */
50
    protected $config = [];
51
52
    /**
53
     * This holds the core name
54
     *
55
     * @var string|null
56
     * @access protected
57
     */
58
    protected $core = null;
59
60
    /**
61
     * This holds the PID for the configuration
62
     *
63
     * @var int
64
     * @access protected
65
     */
66
    protected $cPid = 0;
67
68
    /**
69
     * The extension key
70
     *
71
     * @var string
72
     * @access public
73
     */
74
    public static $extKey = 'dlf';
75
76
    /**
77
     * This holds the max results
78
     *
79
     * @var int
80
     * @access protected
81
     */
82
    protected $limit = 50000;
83
84
    /**
85
     * This holds the number of hits for last search
86
     *
87
     * @var int
88
     * @access protected
89
     */
90
    protected $numberOfHits = 0;
91
92
    /**
93
     * This holds the additional query parameters
94
     *
95
     * @var array
96
     * @access protected
97
     */
98
    protected $params = [];
99
100
    /**
101
     * Is the search instantiated successfully?
102
     *
103
     * @var bool
104
     * @access protected
105
     */
106
    protected $ready = false;
107
108
    /**
109
     * This holds the singleton search objects with their core as array key
110
     *
111
     * @var array (\Kitodo\Dlf\Common\Solr)
112
     * @access protected
113
     */
114
    protected static $registry = [];
115
116
    /**
117
     * This holds the Solr service object
118
     *
119
     * @var \Solarium\Client
120
     * @access protected
121
     */
122
    protected $service;
123
124
    /**
125
     * Add a new core to Apache Solr
126
     *
127
     * @access public
128
     *
129
     * @param string $core: The name of the new core. If empty, the next available core name is used.
130
     *
131
     * @return string The name of the new core
132
     */
133
    public static function createCore($core = '')
134
    {
135
        // Get next available core name if none given.
136
        if (empty($core)) {
137
            $core = 'dlfCore' . self::getNextCoreNumber();
138
        }
139
        // Get Solr service instance.
140
        $solr = self::getInstance($core);
141
        // Create new core if core with given name doesn't exist.
142
        if ($solr->ready) {
143
            // Core already exists.
144
            return $core;
145
        } else {
146
            // Core doesn't exist yet.
147
            $solrAdmin = self::getInstance();
148
            if ($solrAdmin->ready) {
149
                $query = $solrAdmin->service->createCoreAdmin();
150
                $action = $query->createCreate();
151
                $action->setConfigSet('dlf');
152
                $action->setCore($core);
153
                $action->setDataDir('data');
154
                $action->setInstanceDir($core);
155
                $query->setAction($action);
156
                try {
157
                    $response = $solrAdmin->service->coreAdmin($query);
158
                    if ($response->getWasSuccessful()) {
159
                        // Core successfully created.
160
                        return $core;
161
                    }
162
                } catch (\Exception $e) {
163
                    // Nothing to do here.
164
                }
165
            } else {
166
                Helper::log('Apache Solr not available', LOG_SEVERITY_ERROR);
167
            }
168
        }
169
        return '';
170
    }
171
172
    /**
173
     * Escape all special characters in a query string
174
     *
175
     * @access public
176
     *
177
     * @param string $query: The query string
178
     *
179
     * @return string The escaped query string
180
     */
181
    public static function escapeQuery($query)
182
    {
183
        $helper = GeneralUtility::makeInstance(\Solarium\Core\Query\Helper::class);
184
        // Escape query phrase or term.
185
        if (preg_match('/^".*"$/', $query)) {
186
            return $helper->escapePhrase(trim($query, '"'));
187
        } else {
188
            // Using a modified escape function here to retain whitespace, '*' and '?' for search truncation.
189
            // @see https://github.com/solariumphp/solarium/blob/5.x/src/Core/Query/Helper.php#L70 for reference
190
            /* return $helper->escapeTerm($query); */
191
            return preg_replace('/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|:|\/|\\\)/', '\\\$1', $query);
192
        }
193
    }
194
195
    /**
196
     * Escape all special characters in a query string while retaining valid field queries
197
     *
198
     * @access public
199
     *
200
     * @param string $query: The query string
201
     * @param int $pid: The PID for the field configuration
202
     *
203
     * @return string The escaped query string
204
     */
205
    public static function escapeQueryKeepField($query, $pid)
206
    {
207
        // Is there a field query?
208
        if (preg_match('/^[[:alnum:]]+_[tu][su]i:\(?.*\)?$/', $query)) {
209
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
210
                ->getQueryBuilderForTable('tx_dlf_metadata');
211
212
            // Get all indexed fields.
213
            $fields = [];
214
            $result = $queryBuilder
215
                ->select(
216
                    'tx_dlf_metadata.index_name AS index_name',
217
                    'tx_dlf_metadata.index_tokenized AS index_tokenized',
218
                    'tx_dlf_metadata.index_stored AS index_stored'
219
                )
220
                ->from('tx_dlf_metadata')
221
                ->where(
222
                    $queryBuilder->expr()->eq('tx_dlf_metadata.index_indexed', 1),
223
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($pid)),
224
                    $queryBuilder->expr()->orX(
225
                        $queryBuilder->expr()->in('tx_dlf_metadata.sys_language_uid', [-1, 0]),
226
                        $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0)
227
                    ),
228
                    Helper::whereExpression('tx_dlf_metadata')
229
                )
230
                ->execute();
231
232
            while ($resArray = $result->fetch()) {
233
                $fields[] = $resArray['index_name'] . '_' . ($resArray['index_tokenized'] ? 't' : 'u') . ($resArray['index_stored'] ? 's' : 'u') . 'i';
234
            }
235
236
            // Check if queried field is valid.
237
            $splitQuery = explode(':', $query, 2);
238
            if (in_array($splitQuery[0], $fields)) {
239
                $query = $splitQuery[0] . ':(' . self::escapeQuery(trim($splitQuery[1], '()')) . ')';
240
            } else {
241
                $query = self::escapeQuery($query);
242
            }
243
        } else {
244
            $query = self::escapeQuery($query);
245
        }
246
        return $query;
247
    }
248
249
    /**
250
     * Get fields for index.
251
     *
252
     * @access public
253
     *
254
     * @return array fields
255
     */
256
    public static function getFields()
257
    {
258
        $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
259
260
        $fields = [];
261
        $fields['id'] = $conf['solrFieldId'];
262
        $fields['uid'] = $conf['solrFieldUid'];
263
        $fields['pid'] = $conf['solrFieldPid'];
264
        $fields['page'] = $conf['solrFieldPage'];
265
        $fields['partof'] = $conf['solrFieldPartof'];
266
        $fields['root'] = $conf['solrFieldRoot'];
267
        $fields['sid'] = $conf['solrFieldSid'];
268
        $fields['toplevel'] = $conf['solrFieldToplevel'];
269
        $fields['type'] = $conf['solrFieldType'];
270
        $fields['title'] = $conf['solrFieldTitle'];
271
        $fields['volume'] = $conf['solrFieldVolume'];
272
        $fields['thumbnail'] = $conf['solrFieldThumbnail'];
273
        $fields['default'] = $conf['solrFieldDefault'];
274
        $fields['timestamp'] = $conf['solrFieldTimestamp'];
275
        $fields['autocomplete'] = $conf['solrFieldAutocomplete'];
276
        $fields['fulltext'] = $conf['solrFieldFulltext'];
277
        $fields['record_id'] = $conf['solrFieldRecordId'];
278
        $fields['purl'] = $conf['solrFieldPurl'];
279
        $fields['urn'] = $conf['solrFieldUrn'];
280
        $fields['location'] = $conf['solrFieldLocation'];
281
        $fields['collection'] = $conf['solrFieldCollection'];
282
        $fields['license'] = $conf['solrFieldLicense'];
283
        $fields['terms'] = $conf['solrFieldTerms'];
284
        $fields['restrictions'] = $conf['solrFieldRestrictions'];
285
        $fields['geom'] = $conf['solrFieldGeom'];
286
287
        return $fields;
288
    }
289
290
    /**
291
     * This is a singleton class, thus instances must be created by this method
292
     *
293
     * @access public
294
     *
295
     * @param mixed $core: Name or UID of the core to load or null to get core admin endpoint
296
     *
297
     * @return \Kitodo\Dlf\Common\Solr Instance of this class
298
     */
299
    public static function getInstance($core = null)
300
    {
301
        // Get core name if UID is given.
302
        if (MathUtility::canBeInterpretedAsInteger($core)) {
303
            $core = Helper::getIndexNameFromUid($core, 'tx_dlf_solrcores');
304
        }
305
        // Check if core is set or null.
306
        if (
307
            empty($core)
308
            && $core !== null
309
        ) {
310
            Helper::log('Invalid core UID or name given for Apache Solr', LOG_SEVERITY_ERROR);
311
        }
312
        if (!empty($core)) {
313
            // Check if there is an instance in the registry already.
314
            if (
315
                is_object(self::$registry[$core])
316
                && self::$registry[$core] instanceof self
317
            ) {
318
                // Return singleton instance if available.
319
                return self::$registry[$core];
320
            }
321
        }
322
        // Create new instance...
323
        $instance = new self($core);
324
        // ...and save it to registry.
325
        if (!empty($instance->core)) {
326
            self::$registry[$instance->core] = $instance;
327
        }
328
        return $instance;
329
    }
330
331
    /**
332
     * Get next unused Solr core number
333
     *
334
     * @access public
335
     *
336
     * @param int $number: Number to start with
337
     *
338
     * @return int First unused core number found
339
     */
340
    public static function getNextCoreNumber($number = 0)
341
    {
342
        $number = max(intval($number), 0);
343
        // Check if core already exists.
344
        $solr = self::getInstance('dlfCore' . $number);
345
        if (!$solr->ready) {
346
            return $number;
347
        } else {
348
            return self::getNextCoreNumber($number + 1);
349
        }
350
    }
351
352
    /**
353
     * Sets the connection information for Solr
354
     *
355
     * @access protected
356
     *
357
     * @return void
358
     */
359
    protected function loadSolrConnectionInfo()
360
    {
361
        if (empty($this->config)) {
362
            $config = [];
363
            // Extract extension configuration.
364
            $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
365
            // Derive Solr scheme
366
            $config['scheme'] = empty($conf['solrHttps']) ? 'http' : 'https';
367
            // Derive Solr host name.
368
            $config['host'] = ($conf['solrHost'] ? $conf['solrHost'] : '127.0.0.1');
369
            // Set username and password.
370
            $config['username'] = $conf['solrUser'];
371
            $config['password'] = $conf['solrPass'];
372
            // Set port if not set.
373
            $config['port'] = MathUtility::forceIntegerInRange($conf['solrPort'], 1, 65535, 8983);
374
            // Trim path of slashes and (re-)add trailing slash if path not empty.
375
            $config['path'] = trim($conf['solrPath'], '/');
376
            if (!empty($config['path'])) {
377
                $config['path'] .= '/';
378
            }
379
            // Add "/solr" API endpoint when using Solarium <5.x
380
                // Todo: Remove when dropping support for Solarium 4.x
381
            if (!\Solarium\Client::checkMinimal('5.0.0')) {
382
                $config['path'] .= 'solr/';
383
            }
384
            // Set connection timeout lower than PHP's max_execution_time.
385
            $max_execution_time = intval(ini_get('max_execution_time')) ? : 30;
386
            $config['timeout'] = MathUtility::forceIntegerInRange($conf['solrTimeout'], 1, $max_execution_time, 10);
387
            $this->config = $config;
388
        }
389
    }
390
391
    /**
392
     * Processes a search request and returns the raw Apache Solr Documents.
393
     *
394
     * @access public
395
     *
396
     * @param array $parameters: Additional search parameters
397
     *
398
     * @return array The Apache Solr Documents that were fetched
399
     */
400
    public function search_raw($parameters = [])
401
    {
402
        // Set additional query parameters.
403
        $parameters['start'] = 0;
404
        $parameters['rows'] = $this->limit;
405
        // Calculate cache identifier.
406
        $cacheIdentifier = Helper::digest($this->core . print_r(array_merge($this->params, $parameters), true));
0 ignored issues
show
Bug introduced by
Are you sure print_r(array_merge($thi...ms, $parameters), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

406
        $cacheIdentifier = Helper::digest($this->core . /** @scrutinizer ignore-type */ print_r(array_merge($this->params, $parameters), true));
Loading history...
407
        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('tx_dlf_solr');
408
        $resultSet = [];
409
        if (($entry = $cache->get($cacheIdentifier)) === false) {
410
            $selectQuery = $this->service->createSelect(array_merge($this->params, $parameters));
411
            $result = $this->service->select($selectQuery);
412
            foreach ($result as $doc) {
413
                $resultSet[] = $doc;
414
            }
415
            // Save value in cache.
416
            $cache->set($cacheIdentifier, $resultSet);
417
        } else {
418
            // Return cache hit.
419
            $resultSet = $entry;
420
        }
421
        return $resultSet;
422
    }
423
424
    /**
425
     * This returns $this->core via __get()
426
     *
427
     * @access protected
428
     *
429
     * @return string|null The core name of the current query endpoint or null if core admin endpoint
430
     */
431
    protected function _getCore()
432
    {
433
        return $this->core;
434
    }
435
436
    /**
437
     * This returns $this->limit via __get()
438
     *
439
     * @access protected
440
     *
441
     * @return int The max number of results
442
     */
443
    protected function _getLimit()
444
    {
445
        return $this->limit;
446
    }
447
448
    /**
449
     * This returns $this->numberOfHits via __get()
450
     *
451
     * @access protected
452
     *
453
     * @return int Total number of hits for last search
454
     */
455
    protected function _getNumberOfHits()
456
    {
457
        return $this->numberOfHits;
458
    }
459
460
    /**
461
     * This returns $this->ready via __get()
462
     *
463
     * @access protected
464
     *
465
     * @return bool Is the search instantiated successfully?
466
     */
467
    protected function _getReady()
468
    {
469
        return $this->ready;
470
    }
471
472
    /**
473
     * This returns $this->service via __get()
474
     *
475
     * @access protected
476
     *
477
     * @return \Solarium\Client Apache Solr service object
478
     */
479
    protected function _getService()
480
    {
481
        return $this->service;
482
    }
483
484
    /**
485
     * This sets $this->cPid via __set()
486
     *
487
     * @access protected
488
     *
489
     * @param int $value: The new PID for the metadata definitions
490
     *
491
     * @return void
492
     */
493
    protected function _setCPid($value)
494
    {
495
        $this->cPid = max(intval($value), 0);
496
    }
497
498
    /**
499
     * This sets $this->limit via __set()
500
     *
501
     * @access protected
502
     *
503
     * @param int $value: The max number of results
504
     *
505
     * @return void
506
     */
507
    protected function _setLimit($value)
508
    {
509
        $this->limit = max(intval($value), 0);
510
    }
511
512
    /**
513
     * This sets $this->params via __set()
514
     *
515
     * @access protected
516
     *
517
     * @param array $value: The query parameters
518
     *
519
     * @return void
520
     */
521
    protected function _setParams(array $value)
522
    {
523
        $this->params = $value;
524
    }
525
526
    /**
527
     * This magic method is called each time an invisible property is referenced from the object
528
     *
529
     * @access public
530
     *
531
     * @param string $var: Name of variable to get
532
     *
533
     * @return mixed Value of $this->$var
534
     */
535
    public function __get($var)
536
    {
537
        $method = '_get' . ucfirst($var);
538
        if (
539
            !property_exists($this, $var)
540
            || !method_exists($this, $method)
541
        ) {
542
            $this->logger->warning('There is no getter function for property "' . $var . '"');
543
            return;
544
        } else {
545
            return $this->$method();
546
        }
547
    }
548
549
    /**
550
     * This magic method is called each time an invisible property is checked for isset() or empty()
551
     *
552
     * @access public
553
     *
554
     * @param string $var: Name of variable to check
555
     *
556
     * @return bool true if variable is set and not empty, false otherwise
557
     */
558
    public function __isset($var)
559
    {
560
        return !empty($this->__get($var));
561
    }
562
563
    /**
564
     * This magic method is called each time an invisible property is referenced from the object
565
     *
566
     * @access public
567
     *
568
     * @param string $var: Name of variable to set
569
     * @param mixed $value: New value of variable
570
     *
571
     * @return void
572
     */
573
    public function __set($var, $value)
574
    {
575
        $method = '_set' . ucfirst($var);
576
        if (
577
            !property_exists($this, $var)
578
            || !method_exists($this, $method)
579
        ) {
580
            $this->logger->warning('There is no setter function for property "' . $var . '"');
581
        } else {
582
            $this->$method($value);
583
        }
584
    }
585
586
    /**
587
     * This is a singleton class, thus the constructor should be private/protected
588
     *
589
     * @access protected
590
     *
591
     * @param string|null $core: The name of the core to use or null for core admin endpoint
592
     *
593
     * @return void
594
     */
595
    protected function __construct($core)
596
    {
597
        // Get Solr connection parameters from configuration.
598
        $this->loadSolrConnectionInfo();
599
        // Configure connection adapter.
600
        $adapter = GeneralUtility::makeInstance(\Solarium\Core\Client\Adapter\Http::class);
601
            // Todo: When updating to TYPO3 >=10.x and Solarium >=6.x
602
            // the timeout must be set with the adapter instead of the
603
            // endpoint (see below).
604
            // $adapter->setTimeout($this->config['timeout']);
605
        // Configure event dispatcher.
606
            // Todo: When updating to TYPO3 >=10.x and Solarium >=6.x
607
            // we have to provide an PSR-14 Event Dispatcher instead of
608
            // "null".
609
            // $eventDispatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Core\EventDispatcher\EventDispatcher::class);
610
        // Configure endpoint.
611
        $config = [
612
            'endpoint' => [
613
                'default' => [
614
                    'scheme' => $this->config['scheme'],
615
                    'host' => $this->config['host'],
616
                    'port' => $this->config['port'],
617
                    'path' => '/' . $this->config['path'],
618
                    'core' => $core,
619
                    'username' => $this->config['username'],
620
                    'password' => $this->config['password'],
621
                    'timeout' => $this->config['timeout'] // Remove when upgrading to Solarium 6.x
622
                ]
623
            ]
624
        ];
625
        // Instantiate Solarium\Client class.
626
        $this->service = GeneralUtility::makeInstance(\Solarium\Client::class, $config);
627
        $this->service->setAdapter($adapter);
628
            // Todo: When updating to TYPO3 >=10.x and Solarium >=6.x
629
            // $adapter and $eventDispatcher are mandatory arguments
630
            // of the \Solarium\Client constructor.
631
            // $this->service = GeneralUtility::makeInstance(\Solarium\Client::class, $adapter, $eventDispatcher, $config);
632
        // Check if connection is established.
633
        $query = $this->service->createCoreAdmin();
634
        $action = $query->createStatus();
635
        if ($core !== null) {
636
            $action->setCore($core);
637
        }
638
        $query->setAction($action);
639
        try {
640
            $response = $this->service->coreAdmin($query);
641
            if ($response->getWasSuccessful()) {
642
                // Solr is reachable, but is the core as well?
643
                if ($core !== null) {
644
                    $result = $response->getStatusResult();
645
                    if (
646
                        $result instanceof \Solarium\QueryType\Server\CoreAdmin\Result\StatusResult
647
                        && $result->getUptime() > 0
648
                    ) {
649
                        // Set core name.
650
                        $this->core = $core;
0 ignored issues
show
Bug introduced by
The property core is declared read-only in Kitodo\Dlf\Common\Solr.
Loading history...
651
                    } else {
652
                        // Core not available.
653
                        return;
654
                    }
655
                }
656
                // Instantiation successful!
657
                $this->ready = true;
658
            }
659
        } catch (\Exception $e) {
660
            // Nothing to do here.
661
        }
662
    }
663
}
664