Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
CirrusNearTitleFilterFeature
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
6 / 6
9
100.00% covered (success)
100.00%
1 / 1
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doApply
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 expand
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createQuery
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 getFilterQuery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 doGetFilterQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace GeoData\Search;
4
5use CirrusSearch\Parser\AST\KeywordFeatureNode;
6use CirrusSearch\Query\Builder\QueryBuildingContext;
7use CirrusSearch\Query\FilterQueryFeature;
8use CirrusSearch\Query\SimpleKeywordFeature;
9use CirrusSearch\Search\SearchContext;
10use CirrusSearch\SearchConfig;
11use CirrusSearch\WarningCollector;
12use Elastica\Query\AbstractQuery;
13use Elastica\Query\BoolQuery;
14use Elastica\Query\GeoDistance;
15use Elastica\Query\Nested;
16use Elastica\Query\Term;
17use GeoData\Coord;
18
19/**
20 * Applies geo filtering to the query by providing a Title.
21 *
22 * Limits search results to a geographic area within the geographic area. All values
23 * can be prefixed with a radius in m or km to apply. If not specified this defaults
24 * to 5km.
25 *
26 * Examples:
27 *  neartitle:Shanghai
28 *  neartitle:50km,Seoul
29 */
30class CirrusNearTitleFilterFeature extends SimpleKeywordFeature implements FilterQueryFeature {
31    use CirrusGeoFeature;
32
33    /**
34     * @return string[]
35     */
36    protected function getKeywords() {
37        return [ 'neartitle' ];
38    }
39
40    /** @inheritDoc */
41    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
42        [ $coord, $radius, $excludedPageId ] = $this->parseGeoNearbyTitle( $context, $key, $value );
43        $filter = null;
44        if ( $coord !== null ) {
45            $filter = $this->doGetFilterQuery( $context->getConfig(), $coord, $radius, $excludedPageId );
46        }
47        return [ $filter, false ];
48    }
49
50    /**
51     * @param KeywordFeatureNode $node
52     * @param SearchConfig $config
53     * @param WarningCollector $warningCollector
54     * @return array{?Coord,int,int|string}
55     */
56    public function expand(
57        KeywordFeatureNode $node,
58        SearchConfig $config,
59        WarningCollector $warningCollector
60    ) {
61        return $this->parseGeoNearbyTitle( $warningCollector, $node->getKey(), $node->getValue() );
62    }
63
64    /**
65     * Create a filter for near: and neartitle: queries.
66     *
67     * @param Coord $coord
68     * @param int $radius Search radius in meters
69     * @param string $docIdToExclude Document id to exclude, or "" for no exclusions.
70     * @return AbstractQuery
71     */
72    public static function createQuery( Coord $coord, $radius, $docIdToExclude = '' ) {
73        $query = new BoolQuery();
74        $query->addFilter( new Term( [ 'coordinates.globe' => $coord->globe ] ) );
75        $query->addFilter( new Term( [ 'coordinates.primary' => true ] ) );
76
77        $distanceFilter = new GeoDistance(
78            'coordinates.coord',
79            [ 'lat' => $coord->lat, 'lon' => $coord->lon ],
80            $radius . 'm'
81        );
82        $query->addFilter( $distanceFilter );
83
84        if ( $docIdToExclude !== '' ) {
85            $query->addMustNot( new Term( [ '_id' => $docIdToExclude ] ) );
86        }
87
88        $nested = new Nested();
89        $nested->setPath( 'coordinates' )->setQuery( $query );
90
91        return $nested;
92    }
93
94    /**
95     * @param KeywordFeatureNode $node
96     * @param QueryBuildingContext $context
97     * @return AbstractQuery|null
98     */
99    public function getFilterQuery( KeywordFeatureNode $node, QueryBuildingContext $context ) {
100        [ $coord, $radius, $excludedPageId ] = $context->getKeywordExpandedData( $node );
101        if ( $coord === null ) {
102            return null;
103        }
104        return $this->doGetFilterQuery( $context->getSearchConfig(), $coord, $radius, $excludedPageId );
105    }
106
107    /**
108     * @param SearchConfig $config
109     * @param Coord $coord
110     * @param int $radius
111     * @param int $excludedPageId
112     * @return AbstractQuery
113     */
114    protected function doGetFilterQuery(
115        SearchConfig $config,
116        Coord $coord,
117        $radius,
118        $excludedPageId
119    ) {
120        $excludedDocId = $config->makeId( $excludedPageId );
121        return self::createQuery( $coord, $radius, $excludedDocId );
122    }
123}