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