Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.79% covered (warning)
76.79%
43 / 56
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MoreLikeTrait
76.79% covered (warning)
76.79%
43 / 56
20.00% covered (danger)
20.00%
1 / 5
20.62
0.00% covered (danger)
0.00%
0 / 1
 doExpand
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 collectTitles
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 collectTitlesFromElastic
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 collectTitlesFromDB
85.71% covered (warning)
85.71%
18 / 21
0.00% covered (danger)
0.00%
0 / 1
7.14
 buildMoreLikeQuery
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
 getConfig
n/a
0 / 0
n/a
0 / 0
0
1<?php
2
3namespace CirrusSearch\Query;
4
5use CirrusSearch\CirrusConfigNames;
6use CirrusSearch\Connection;
7use CirrusSearch\Hooks;
8use CirrusSearch\SearchConfig;
9use CirrusSearch\WarningCollector;
10use Elastica\Query\MoreLikeThis;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Page\PageIdentity;
13use MediaWiki\Title\Title;
14
15trait MoreLikeTrait {
16    /**
17     * @param string $key
18     * @param string $term
19     * @param WarningCollector $warningCollector
20     * @return PageIdentity[]
21     */
22    protected function doExpand( $key, $term, WarningCollector $warningCollector ) {
23        // If no fields have been set we return no results. This can happen if
24        // the user override this setting with field names that are not allowed
25        // in $this->getConfig()->get( 'CirrusSearchMoreLikeThisAllowedFields' )
26        // (see Hooks.php)
27        if ( !$this->getConfig()->get( CirrusConfigNames::MoreLikeThisFields ) ) {
28            $warningCollector->addWarning( "cirrussearch-mlt-not-configured", $key );
29            return [];
30        }
31        $titles = $this->collectTitles( $term );
32        if ( $titles === [] ) {
33            $warningCollector->addWarning( "cirrussearch-mlt-feature-no-valid-titles", $key );
34        }
35        return $titles;
36    }
37
38    /**
39     * @param string $term
40     * @return PageIdentity[]
41     */
42    private function collectTitles( $term ) {
43        if ( $this->getConfig()->getElement( CirrusConfigNames::DevelOptions,
44            'morelike_collect_titles_from_elastic' )
45        ) {
46            return $this->collectTitlesFromElastic( $term );
47        } else {
48            return $this->collectTitlesFromDB( $term );
49        }
50    }
51
52    /**
53     * Use for devel purpose only
54     * @param string $terms
55     * @return PageIdentity[]
56     */
57    private function collectTitlesFromElastic( $terms ) {
58        $titles = [];
59        foreach ( explode( '|', $terms ) as $term ) {
60            $title = null;
61            Hooks::handleSearchGetNearMatch( $term, $title );
62            if ( $title != null ) {
63                $titles[] = $title;
64            }
65        }
66        return $titles;
67    }
68
69    /**
70     * @param string $term
71     * @return PageIdentity[]
72     */
73    private function collectTitlesFromDB( $term ) {
74        $titles = [];
75        $found = [];
76        $titleFactory = MediaWikiServices::getInstance()->getTitleFactory();
77        $redirectLookup = MediaWikiServices::getInstance()->getRedirectLookup();
78        foreach ( explode( '|', $term ) as $title ) {
79            $title = $titleFactory->newFromText( trim( $title ) );
80            while ( true ) {
81                if ( !$title ) {
82                    continue 2;
83                }
84                $titleText = $title->getFullText();
85                if ( isset( $found[$titleText] ) ) {
86                    continue 2;
87                }
88                $found[$titleText] = true;
89                if ( !$title->exists() ) {
90                    continue 2;
91                }
92                $redirectTarget = $redirectLookup->getRedirectTarget( $title );
93                if ( !$redirectTarget ) {
94                    break;
95                }
96                $title = Title::castFromLinkTarget( $redirectTarget );
97            }
98            $titles[] = $title;
99        }
100
101        return $titles;
102    }
103
104    /**
105     * Builds a more like this query for the specified titles. Take care that
106     * this outputs a stable result, regardless of order of configuration
107     * parameters and input titles. The result of this is hashed to generate an
108     * application side cache key. If the result is unstable we will see a
109     * reduced hit rate, and waste cache storage space.
110     *
111     * @param PageIdentity[] $titles
112     * @return MoreLikeThis
113     */
114    protected function buildMoreLikeQuery( array $titles ) {
115        sort( $titles, SORT_STRING );
116        $likeDocs = [];
117        // We pull a connection object to access index names, ideally we should not require this
118        // since we actually don't make any connection but these methods have been available there
119        // for historical reasons.
120        $connection = new Connection( $this->getConfig() );
121        $indexBaseName = $this->getConfig()->get( CirrusConfigNames::IndexBaseName );
122        foreach ( $titles as $title ) {
123            $docId = $this->getConfig()->makeId( $title->getId() );
124
125            $likeDocs[] = [
126                '_id' => $docId,
127                '_index' => $connection->getIndexName( $indexBaseName, $connection->getIndexSuffixForNamespace( $title->getNamespace() ) )
128            ];
129        }
130
131        $moreLikeThisFields = $this->getConfig()->get( CirrusConfigNames::MoreLikeThisFields );
132        sort( $moreLikeThisFields );
133        $query = new MoreLikeThis();
134        $query->setParams( $this->getConfig()->get( CirrusConfigNames::MoreLikeThisConfig ) );
135        $query->setFields( $moreLikeThisFields );
136
137        /** @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal library is mis-annotated */
138        $query->setLike( $likeDocs );
139
140        return $query;
141    }
142
143    /**
144     * @return SearchConfig
145     */
146    abstract public function getConfig(): SearchConfig;
147}