Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
BoostTemplatesFeature
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
7 / 7
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
 getCrossSearchStrategy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doApply
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 parseBoostTemplates
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 parseValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBoostFunctionBuilder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 buildBoostFunction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace CirrusSearch\Query;
4
5use CirrusSearch\CrossSearchStrategy;
6use CirrusSearch\Parser\AST\KeywordFeatureNode;
7use CirrusSearch\Query\Builder\QueryBuildingContext;
8use CirrusSearch\Search\Rescore\ByKeywordTemplateBoostFunction;
9use CirrusSearch\Search\SearchContext;
10use CirrusSearch\WarningCollector;
11
12/**
13 * Handles the boost-templates keyword in full text search. Allows user
14 * to specify a percentage to increase or decrease a search result by based
15 * on the templates included in the page. Templates can be specified with
16 * spaces or underscores. Multiple templates can be specified. Any value
17 * including a space must be quoted.
18 *
19 * Examples:
20 *  boost-templates:Main_article|250%
21 *  boost-templates:"Featured sound|150%"
22 *  boost-templates:"Main_article|250% List_of_lists|10%"
23 */
24class BoostTemplatesFeature extends SimpleKeywordFeature implements BoostFunctionFeature {
25    /**
26     * @return string[]
27     */
28    protected function getKeywords() {
29        return [ 'boost-templates' ];
30    }
31
32    /**
33     * @param KeywordFeatureNode $node
34     * @return CrossSearchStrategy
35     */
36    public function getCrossSearchStrategy( KeywordFeatureNode $node ) {
37        return CrossSearchStrategy::allWikisStrategy();
38    }
39
40    /**
41     * @param SearchContext $context
42     * @param string $key The keyword
43     * @param string $value The value attached to the keyword with quotes stripped
44     * @param string $quotedValue The original value in the search string, including quotes if used
45     * @param bool $negated Is the search negated? Not used to generate the returned AbstractQuery,
46     *  that will be negated as necessary. Used for any other building/context necessary.
47     * @return array Two element array, first an AbstractQuery or null to apply to the
48     *  query. Second a boolean indicating if the quotedValue should be kept in the search
49     *  string.
50     */
51    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
52        $context->addCustomRescoreComponent(
53            $this->buildBoostFunction( [ 'boost-templates' => self::parseBoostTemplates( $value ) ] )
54        );
55
56        return [ null, false ];
57    }
58
59    /**
60     * Parse boosted templates.  Parse failures silently return no boosted templates.
61     * Matches a template name followed by a | then a positive integer followed by a %.
62     * Multiple templates can be specified separated by a space.
63     *
64     * Examples:
65     *   Featured_article|150%
66     *   List of lists|10% Featured_sound|200%
67     *
68     * @param string $text text representation of boosted templates
69     * @return float[] map of boosted templates (key is the template, value is a float).
70     */
71    public static function parseBoostTemplates( $text ) {
72        $boostTemplates = [];
73        $templateMatches = [];
74        if ( preg_match_all( '/([^|]+)\|(\d+)% ?/', $text, $templateMatches, PREG_SET_ORDER ) ) {
75            foreach ( $templateMatches as $templateMatch ) {
76                // templates field is populated with Title::getPrefixedText
77                // which will replace _ to ' '. We should do the same here.
78                $template = strtr( $templateMatch[ 1 ], '_', ' ' );
79                $boostTemplates[ $template ] = floatval( $templateMatch[ 2 ] ) / 100;
80            }
81        }
82        return $boostTemplates;
83    }
84
85    /**
86     * @param string $key
87     * @param string $value
88     * @param string $quotedValue
89     * @param string $valueDelimiter
90     * @param string $suffix
91     * @param WarningCollector $warningCollector
92     * @return array|false|float[]|null
93     */
94    public function parseValue( $key, $value, $quotedValue, $valueDelimiter, $suffix, WarningCollector $warningCollector ) {
95        return [ 'boost-templates' => self::parseBoostTemplates( $value ) ];
96    }
97
98    /**
99     * @param KeywordFeatureNode $node
100     * @param QueryBuildingContext $context
101     * @return \CirrusSearch\Search\Rescore\BoostFunctionBuilder|null
102     */
103    public function getBoostFunctionBuilder( KeywordFeatureNode $node, QueryBuildingContext $context ) {
104        return $this->buildBoostFunction( $node->getParsedValue() );
105    }
106
107    /**
108     * @param array $parsedValue
109     * @return ByKeywordTemplateBoostFunction
110     */
111    private function buildBoostFunction( array $parsedValue ) {
112        return new ByKeywordTemplateBoostFunction( $parsedValue['boost-templates'] );
113    }
114}