Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.81% covered (warning)
69.81%
37 / 53
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MoreLikeTrait
69.81% covered (warning)
69.81%
37 / 53
20.00% covered (danger)
20.00%
1 / 5
26.91
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
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 collectTitlesFromElastic
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 collectTitlesFromDB
72.73% covered (warning)
72.73%
16 / 22
0.00% covered (danger)
0.00%
0 / 1
9.30
 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 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::onSearchGetNearMatch( $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        $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
74        foreach ( explode( '|', $term ) as $title ) {
75            $title = Title::newFromText( trim( $title ) );
76            while ( true ) {
77                if ( !$title ) {
78                    continue 2;
79                }
80                $titleText = $title->getFullText();
81                if ( isset( $found[$titleText] ) ) {
82                    continue 2;
83                }
84                $found[$titleText] = true;
85                if ( !$title->exists() ) {
86                    continue 2;
87                }
88                if ( !$title->isRedirect() ) {
89                    break;
90                }
91                // If the page was a redirect loop the while( true ) again.
92                $page = $wikiPageFactory->newFromTitle( $title );
93                if ( !$page->exists() ) {
94                    continue 2;
95                }
96                $title = $page->getRedirectTarget();
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 Title[] $titles
112     * @return MoreLikeThis
113     */
114    protected function buildMoreLikeQuery( array $titles ) {
115        sort( $titles, SORT_STRING );
116        $docIds = [];
117        $likeDocs = [];
118        foreach ( $titles as $title ) {
119            $docId = $this->getConfig()->makeId( $title->getArticleID() );
120            $docIds[] = $docId;
121            $likeDocs[] = [ '_id' => $docId ];
122        }
123
124        $moreLikeThisFields = $this->getConfig()->get( 'CirrusSearchMoreLikeThisFields' );
125        sort( $moreLikeThisFields );
126        $query = new MoreLikeThis();
127        $query->setParams( $this->getConfig()->get( 'CirrusSearchMoreLikeThisConfig' ) );
128        $query->setFields( $moreLikeThisFields );
129
130        /** @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal library is mis-annotated */
131        $query->setLike( $likeDocs );
132
133        return $query;
134    }
135
136    /**
137     * @return SearchConfig
138     */
139    abstract public function getConfig(): SearchConfig;
140}