tx_dlf_solr::search()   F
last analyzed

Complexity

Conditions 23
Paths 646

Size

Total Lines 186
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 82
nc 646
nop 1
dl 0
loc 186
rs 2.2656
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
4
 *
5
 * This file is part of the Kitodo and TYPO3 projects.
6
 *
7
 * @license GNU General Public License version 3 or later.
8
 * For the full copyright and license information, please read the
9
 * LICENSE.txt file that was distributed with this source code.
10
 */
11
12
/**
13
 * Solr class 'tx_dlf_solr' for the 'dlf' extension.
14
 *
15
 * @author	Sebastian Meyer <[email protected]>
16
 * @author	Henrik Lochmann <[email protected]>
17
 * @package	TYPO3
18
 * @subpackage	tx_dlf
19
 * @access	public
20
 */
21
class tx_dlf_solr {
22
23
    /**
24
     * This holds the core name
25
     *
26
     * @var	string
27
     * @access protected
28
     */
29
    protected $core = '';
30
31
    /**
32
     * This holds the PID for the configuration
33
     *
34
     * @var	integer
35
     * @access protected
36
     */
37
    protected $cPid = 0;
38
39
    /**
40
     * The extension key
41
     *
42
     * @var	string
43
     * @access public
44
     */
45
    public static $extKey = 'dlf';
46
47
    /**
48
     * This holds the max results
49
     *
50
     * @var	integer
51
     * @access protected
52
     */
53
    protected $limit = 50000;
54
55
    /**
56
     * This holds the number of hits for last search
57
     *
58
     * @var	integer
59
     * @access protected
60
     */
61
    protected $numberOfHits = 0;
62
63
    /**
64
     * This holds the additional query parameters
65
     *
66
     * @var	array
67
     * @access protected
68
     */
69
    protected $params = array ();
70
71
    /**
72
     * Is the search instantiated successfully?
73
     *
74
     * @var	boolean
75
     * @access protected
76
     */
77
    protected $ready = FALSE;
78
79
    /**
80
     * This holds the singleton search objects with their core as array key
81
     *
82
     * @var	array(tx_dlf_solr)
83
     * @access protected
84
     */
85
    protected static $registry = array ();
86
87
    /**
88
     * This holds the Solr service object
89
     *
90
     * @var	Apache_Solr_Service
91
     * @access protected
92
     */
93
    protected $service;
94
95
    /**
96
     * Escape all special characters in a query string
97
     *
98
     * @access	public
99
     *
100
     * @param	string		$query: The query string
101
     *
102
     * @return	string		The escaped query string
103
     */
104
    public static function escapeQuery($query) {
105
106
        // Load class.
107
        if (!class_exists('Apache_Solr_Service')) {
108
109
            require_once(\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('EXT:'.self::$extKey.'/lib/SolrPhpClient/Apache/Solr/Service.php'));
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Core\Utility\GeneralUtility was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
110
111
        }
112
113
        // Escape query phrase or term.
114
        if (preg_match('/^".*"$/', $query)) {
115
116
            return '"'.Apache_Solr_Service::escapePhrase(trim($query, '"')).'"';
117
118
        } else {
119
120
            return Apache_Solr_Service::escape($query);
121
122
        }
123
124
    }
125
126
    /**
127
     * Escape all special characters in a query string while retaining valid field queries
128
     *
129
     * @access	public
130
     *
131
     * @param	string		$query: The query string
132
     * @param	integer		$pid: The PID for the field configuration
133
     *
134
     * @return	string		The escaped query string
135
     */
136
    public static function escapeQueryKeepField($query, $pid) {
137
138
        // Is there a field query?
139
        if (preg_match('/^[[:alnum:]]+_[tu][su]i:\(?.*\)?$/', $query)) {
140
141
            // Get all indexed fields.
142
            $fields = array ();
143
144
            $result = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
145
                'tx_dlf_metadata.index_name,tx_dlf_metadata.index_tokenized,tx_dlf_metadata.index_stored',
146
                'tx_dlf_metadata',
147
                'tx_dlf_metadata.index_indexed=1 AND tx_dlf_metadata.pid='.intval($pid).' AND (tx_dlf_metadata.sys_language_uid IN (-1,0) OR tx_dlf_metadata.l18n_parent=0)'.tx_dlf_helper::whereClause('tx_dlf_metadata'),
148
                '',
149
                '',
150
                ''
151
            );
152
153
            if ($GLOBALS['TYPO3_DB']->sql_num_rows($result) > 0) {
154
155
                while ($resArray = $GLOBALS['TYPO3_DB']->sql_fetch_row($result)) {
156
157
                    $fields[] = $resArray[0].'_'.($resArray[1] ? 't' : 'u').($resArray[2] ? 's' : 'u').'i';
158
159
                }
160
161
            }
162
163
            // Check if queried field is valid.
164
            $splitQuery = explode(':', $query, 2);
165
166
            if (in_array($splitQuery[0], $fields)) {
167
168
                $query = $splitQuery[0].':('.self::escapeQuery(trim($splitQuery[1], '()')).')';
169
170
            } else {
171
172
                $query = self::escapeQuery($query);
173
174
            }
175
176
        } elseif (!empty($query) && $query !== '*') {
177
178
            // Don't escape plain asterisk search.
179
            $query = self::escapeQuery($query);
180
181
        }
182
183
        return $query;
184
185
    }
186
187
    /**
188
     * This is a singleton class, thus instances must be created by this method
189
     *
190
     * @access	public
191
     *
192
     * @param	mixed		$core: Name or UID of the core to load
193
     *
194
     * @return	tx_dlf_solr		Instance of this class
195
     */
196
    public static function getInstance($core) {
197
198
        // Save parameter for logging purposes.
199
        $_core = $core;
200
201
        // Get core name if UID is given.
202
        if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($core)) {
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Core\Utility\MathUtility was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
203
204
            $core = tx_dlf_helper::getIndexName($core, 'tx_dlf_solrcores');
205
206
        }
207
208
        // Check if core is set.
209
        if (empty($core)) {
210
211
            if (TYPO3_DLOG) {
0 ignored issues
show
Bug introduced by
The constant TYPO3_DLOG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
212
213
                \TYPO3\CMS\Core\Utility\GeneralUtility::devLog('[tx_dlf_solr->getInstance('.$_core.')] Invalid core name "'.$core.'" for Apache Solr', self::$extKey, SYSLOG_SEVERITY_ERROR);
0 ignored issues
show
Bug introduced by
The constant SYSLOG_SEVERITY_ERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
214
215
            }
216
217
            return;
218
219
        }
220
221
        // Check if there is an instance in the registry already.
222
        if (is_object(self::$registry[$core]) && self::$registry[$core] instanceof self) {
223
224
            // Return singleton instance if available.
225
            return self::$registry[$core];
226
227
        }
228
229
        // Create new instance...
230
        $instance = new self($core);
231
232
        // ...and save it to registry.
233
        if ($instance->ready) {
234
235
            self::$registry[$core] = $instance;
236
237
            // Return new instance.
238
            return $instance;
239
240
        } else {
241
242
            if (TYPO3_DLOG) {
243
244
                \TYPO3\CMS\Core\Utility\GeneralUtility::devLog('[tx_dlf_solr->getInstance('.$_core.')] Could not connect to Apache Solr server', self::$extKey, SYSLOG_SEVERITY_ERROR);
245
246
            }
247
248
            return;
249
250
        }
251
252
    }
253
254
    /**
255
     * Returns the connection information a specific Solr core
256
     *
257
     * @access	public
258
     *
259
     * @param	string		$core: Name of the core to load
260
     *
261
     * @return	string		The connection parameters for a specific Solr core
262
     */
263
    public static function getSolrConnectionInfo($core = '') {
264
265
        $solrInfo = array ();
266
267
        // Extract extension configuration.
268
        $conf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::$extKey]);
269
270
        // Derive Solr host name.
271
        $solrInfo['host'] = ($conf['solrHost'] ? $conf['solrHost'] : '127.0.0.1');
272
273
        // Prepend username and password to hostname.
274
        if ($conf['solrUser'] && $conf['solrPass']) {
275
276
            $solrInfo['host'] = $conf['solrUser'].':'.$conf['solrPass'].'@'.$solrInfo['host'];
277
278
        }
279
280
        // Set port if not set.
281
        $solrInfo['port'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['solrPort'], 1, 65535, 8180);
282
283
        // Append core name to path.
284
        $solrInfo['path'] = trim($conf['solrPath'], '/').'/'.$core;
285
286
        return $solrInfo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $solrInfo returns the type array<mixed,mixed|string> which is incompatible with the documented return type string.
Loading history...
287
288
    }
289
290
    /**
291
     * Returns the request URL for a specific Solr core
292
     *
293
     * @access	public
294
     *
295
     * @param	string		$core: Name of the core to load
296
     *
297
     * @return	string		The request URL for a specific Solr core
298
     */
299
    public static function getSolrUrl($core = '') {
300
301
        // Get Solr connection information.
302
        $solrInfo = self::getSolrConnectionInfo($core);
303
304
        // Return entire request URL.
305
        return 'http://'.$solrInfo['host'].':'.$solrInfo['port'].'/'.$solrInfo['path'];
306
307
    }
308
309
    /**
310
     * Get next unused Solr core number
311
     *
312
     * @access	public
313
     *
314
     * @param	integer		$start: Number to start with
315
     *
316
     * @return	integer		First unused core number found
317
     */
318
    public static function solrGetCoreNumber($start = 0) {
319
320
        $start = max(intval($start), 0);
321
322
        // Check if core already exists.
323
        if (self::getInstance('dlfCore'.$start) === NULL) {
324
325
            return $start;
326
327
        } else {
328
329
            return self::solrGetCoreNumber($start + 1);
330
331
        }
332
333
    }
334
335
    /**
336
     * Processes a search request.
337
     *
338
     * @access	public
339
     *
340
     * @param	string		$query: The search query
341
     *
342
     * @return	tx_dlf_list		The result list
343
     */
344
    public function search($query = '') {
345
346
        // Perform search.
347
        $results = $this->service->search((string) $query, 0, $this->limit, $this->params);
348
349
        $this->numberOfHits = count($results->response->docs);
350
351
        $toplevel = array ();
352
353
        $checks = array ();
354
355
        // Get metadata configuration.
356
        if ($this->numberOfHits > 0) {
357
358
            $result = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
359
                'tx_dlf_metadata.index_name AS index_name',
360
                'tx_dlf_metadata',
361
                'tx_dlf_metadata.is_sortable=1 AND tx_dlf_metadata.pid='.intval($this->cPid).tx_dlf_helper::whereClause('tx_dlf_metadata'),
362
                '',
363
                '',
364
                ''
365
            );
366
367
            $sorting = array ();
368
369
            while ($resArray = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) {
370
371
                $sorting[$resArray['index_name']] = $resArray['index_name'].'_sorting';
372
373
            }
374
375
        }
376
377
        // Keep track of relevance.
378
        $i = 0;
379
380
        // Process results.
381
        foreach ($results->response->docs as $doc) {
382
383
            // Split toplevel documents from subparts.
384
            if ($doc->toplevel == 1) {
385
386
                // Prepare document's metadata for sorting.
387
                $docSorting = array ();
388
389
                foreach ($sorting as $index_name => $solr_name) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sorting does not seem to be defined for all execution paths leading up to this point.
Loading history...
390
391
                    if (!empty($doc->$solr_name)) {
392
393
                        $docSorting[$index_name] = (is_array($doc->$solr_name) ? $doc->$solr_name[0] : $doc->$solr_name);
394
395
                    }
396
397
                }
398
399
                // Preserve relevance ranking.
400
                if (!empty($toplevel[$doc->uid]['s']['relevance'])) {
401
402
                    $docSorting['relevance'] = $toplevel[$doc->uid]['s']['relevance'];
403
404
                }
405
406
                $toplevel[$doc->uid] = array (
407
                    'u' => $doc->uid,
408
                    'h' => '',
409
                    's' => $docSorting,
410
                    'p' => (!empty($toplevel[$doc->uid]['p']) ? $toplevel[$doc->uid]['p'] : array ())
411
                );
412
413
            } else {
414
415
                $toplevel[$doc->uid]['p'][] = array (
416
                    'u' => $doc->id,
417
                    'h' => (!empty($results->highlighting->{$doc->id}->fulltext) ? $results->highlighting->{$doc->id}->fulltext[0] : '')
418
                );
419
420
                if (!in_array($doc->uid, $checks)) {
421
422
                    $checks[] = $doc->uid;
423
424
                }
425
426
            }
427
428
            // Add relevance to sorting values.
429
            if (empty($toplevel[$doc->uid]['s']['relevance'])) {
430
431
                $toplevel[$doc->uid]['s']['relevance'] = str_pad($i, 6, '0', STR_PAD_LEFT);
432
433
            }
434
435
            $i++;
436
437
        }
438
439
        // Check if the toplevel documents have metadata.
440
        foreach ($checks as $check) {
441
442
            if (empty($toplevel[$check]['u'])) {
443
444
                // Get information for toplevel document.
445
                $result = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
446
                    'tx_dlf_documents.uid AS uid,tx_dlf_documents.metadata_sorting AS metadata_sorting',
447
                    'tx_dlf_documents',
448
                    'tx_dlf_documents.uid='.intval($check).tx_dlf_helper::whereClause('tx_dlf_documents'),
449
                    '',
450
                    '',
451
                    '1'
452
                );
453
454
                // Process results.
455
                if ($GLOBALS['TYPO3_DB']->sql_num_rows($result)) {
456
457
                    $resArray = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result);
458
459
                    // Prepare document's metadata for sorting.
460
                    $sorting = unserialize($resArray['metadata_sorting']);
461
462
                    if (!empty($sorting['type']) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($sorting['type'])) {
463
464
                        $sorting['type'] = tx_dlf_helper::getIndexName($sorting['type'], 'tx_dlf_structures', $this->cPid);
465
466
                    }
467
468
                    if (!empty($sorting['owner']) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($sorting['owner'])) {
469
470
                        $sorting['owner'] = tx_dlf_helper::getIndexName($sorting['owner'], 'tx_dlf_libraries', $this->cPid);
471
472
                    }
473
474
                    if (!empty($sorting['collection']) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($sorting['collection'])) {
475
476
                        $sorting['collection'] = tx_dlf_helper::getIndexName($sorting['collection'], 'tx_dlf_collections', $this->cPid);
477
478
                    }
479
480
                    // Preserve relevance ranking.
481
                    if (!empty($toplevel[$check]['s']['relevance'])) {
482
483
                        $sorting['relevance'] = $toplevel[$check]['s']['relevance'];
484
485
                    }
486
487
                    $toplevel[$check] = array (
488
                        'u' => $resArray['uid'],
489
                        'h' => '',
490
                        's' => $sorting,
491
                        'p' => $toplevel[$check]['p']
492
                    );
493
494
                } else {
495
496
                    // Clear entry if there is no (accessible) toplevel document.
497
                    unset ($toplevel[$check]);
498
499
                }
500
501
            }
502
503
        }
504
505
        // Save list of documents.
506
        $list = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('tx_dlf_list');
507
508
        $list->reset();
509
510
        $list->add(array_values($toplevel));
511
512
        // Set metadata for search.
513
        $list->metadata = array (
514
            'label' => '',
515
            'description' => '',
516
            'options' => array (
517
                'source' => 'search',
518
                'engine' => 'solr',
519
                'select' => $query,
520
                'userid' => 0,
521
                'params' => $this->params,
522
                'core' => $this->core,
523
                'pid' => $this->cPid,
524
                'order' => 'relevance',
525
                'order.asc' => TRUE,
526
            )
527
        );
528
529
        return $list;
530
531
    }
532
533
    /**
534
     * Processes a search request and returns the raw Apache Solr Documents.
535
     *
536
     * @access	public
537
     *
538
     * @param	string		$query: The search query
539
     *
540
     * @return	array       The Apache Solr Documents that were fetched
541
     */
542
    public function search_raw($query = '', $parameters = array ()) {
543
        $solr_response = $this->service->search((string) $query, 0, $this->limit, array_merge($this->params, $parameters));
544
545
        $searchresult = array ();
546
547
        foreach ($solr_response->response->docs as $doc) {
548
            $searchresult[] = $doc;
549
        }
550
551
        return $searchresult;
552
    }
553
554
    /**
555
     * This returns $this->limit via __get()
556
     *
557
     * @access	protected
558
     *
559
     * @return	integer		The max number of results
560
     */
561
    protected function _getLimit() {
562
563
        return $this->limit;
564
565
    }
566
567
    /**
568
     * This returns $this->numberOfHits via __get()
569
     *
570
     * @access	protected
571
     *
572
     * @return	integer		Total number of hits for last search
573
     */
574
    protected function _getNumberOfHits() {
575
576
        return $this->numberOfHits;
577
578
    }
579
580
    /**
581
     * This returns $this->ready via __get()
582
     *
583
     * @access	protected
584
     *
585
     * @return	boolean		Is the search instantiated successfully?
586
     */
587
    protected function _getReady() {
588
589
        return $this->ready;
590
591
    }
592
593
    /**
594
     * This returns $this->service via __get()
595
     *
596
     * @access	protected
597
     *
598
     * @return	Apache_Solr_Service		Apache Solr service object
599
     */
600
    protected function _getService() {
601
602
        return $this->service;
603
604
    }
605
606
    /**
607
     * This sets $this->cPid via __set()
608
     *
609
     * @access	protected
610
     *
611
     * @param	integer		$value: The new PID for the metadata definitions
612
     *
613
     * @return	void
614
     */
615
    protected function _setCPid($value) {
616
617
        $this->cPid = max(intval($value), 0);
618
619
    }
620
621
    /**
622
     * This sets $this->limit via __set()
623
     *
624
     * @access	protected
625
     *
626
     * @param	integer		$value: The max number of results
627
     *
628
     * @return	void
629
     */
630
    protected function _setLimit($value) {
631
632
        $this->limit = max(intval($value), 0);
633
634
    }
635
636
    /**
637
     * This sets $this->params via __set()
638
     *
639
     * @access	protected
640
     *
641
     * @param	array		$value: The query parameters
642
     *
643
     * @return	void
644
     */
645
    protected function _setParams(array $value) {
646
647
        $this->params = $value;
648
649
    }
650
651
    /**
652
     * This magic method is called each time an invisible property is referenced from the object
653
     *
654
     * @access	public
655
     *
656
     * @param	string		$var: Name of variable to get
657
     *
658
     * @return	mixed		Value of $this->$var
659
     */
660
    public function __get($var) {
661
662
        $method = '_get'.ucfirst($var);
663
664
        if (!property_exists($this, $var) || !method_exists($this, $method)) {
665
666
            if (TYPO3_DLOG) {
0 ignored issues
show
Bug introduced by
The constant TYPO3_DLOG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
667
668
                \TYPO3\CMS\Core\Utility\GeneralUtility::devLog('[tx_dlf_solr->__get('.$var.')] There is no getter function for property "'.$var.'"', self::$extKey, SYSLOG_SEVERITY_WARNING);
0 ignored issues
show
Bug introduced by
The constant SYSLOG_SEVERITY_WARNING was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
669
670
            }
671
672
            return;
673
674
        } else {
675
676
            return $this->$method();
677
678
        }
679
680
    }
681
682
    /**
683
     * This magic method is called each time an invisible property is referenced from the object
684
     *
685
     * @access	public
686
     *
687
     * @param	string		$var: Name of variable to set
688
     * @param	mixed		$value: New value of variable
689
     *
690
     * @return	void
691
     */
692
    public function __set($var, $value) {
693
694
        $method = '_set'.ucfirst($var);
695
696
        if (!property_exists($this, $var) || !method_exists($this, $method)) {
697
698
            if (TYPO3_DLOG) {
0 ignored issues
show
Bug introduced by
The constant TYPO3_DLOG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
699
700
                \TYPO3\CMS\Core\Utility\GeneralUtility::devLog('[tx_dlf_solr->__set('.$var.', [data])] There is no setter function for property "'.$var.'"', self::$extKey, SYSLOG_SEVERITY_WARNING, $value);
0 ignored issues
show
Bug introduced by
The constant SYSLOG_SEVERITY_WARNING was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
701
702
            }
703
704
        } else {
705
706
            $this->$method($value);
707
708
        }
709
710
    }
711
712
    /**
713
     * This is a singleton class, thus the constructor should be private/protected
714
     *
715
     * @access	protected
716
     *
717
     * @param	string		$core: The name of the core to use
718
     *
719
     * @return	void
720
     */
721
    protected function __construct($core) {
722
723
        // Load class.
724
        if (!class_exists('Apache_Solr_Service')) {
725
726
            require_once(\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('EXT:'.self::$extKey.'/lib/SolrPhpClient/Apache/Solr/Service.php'));
727
728
        }
729
730
        $solrInfo = self::getSolrConnectionInfo($core);
731
732
        // Instantiate Apache_Solr_Service class.
733
        $this->service = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('Apache_Solr_Service', $solrInfo['host'], $solrInfo['port'], $solrInfo['path']);
734
735
        // Check if connection is established.
736
        if ($this->service->ping() !== FALSE) {
737
738
            // Do not collapse single value arrays.
739
            $this->service->setCollapseSingleValueArrays = FALSE;
740
741
            // Set core name.
742
            $this->core = $core;
743
744
            // Instantiation successful!
745
            $this->ready = TRUE;
746
747
        }
748
749
    }
750
751
}
752