Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
DefaultSortSuggestionsBuilder
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 5
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getRequiredFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 build
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 addInputToFST
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isRelevantDefaultSort
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3namespace CirrusSearch\BuildDocument\Completion;
4
5use Transliterator;
6
7/**
8 * Extra builder that appends the defaultsort value to suggest and suggest-stop
9 * inputs on title suggestions
10 */
11class DefaultSortSuggestionsBuilder implements ExtraSuggestionsBuilder {
12    private const FIELD = 'defaultsort';
13    private Transliterator $utr30;
14
15    public function __construct() {
16        $utr30 = Transliterator::createFromRules( file_get_contents( __DIR__ . '/../../../data/utr30.txt' ) );
17        if ( $utr30 === null ) {
18            throw new \RuntimeException( "Failed to construct transliterator" );
19        }
20        $this->utr30 = $utr30;
21    }
22
23    /**
24     * @inheritDoc
25     */
26    public function getRequiredFields() {
27        return [ self::FIELD ];
28    }
29
30    /**
31     * @param mixed[] $inputDoc
32     * @param string $suggestType (title or redirect)
33     * @param int $score
34     * @param \Elastica\Document $suggestDoc suggestion type (title or redirect)
35     * @param int $targetNamespace
36     */
37    public function build( array $inputDoc, $suggestType, $score, \Elastica\Document $suggestDoc, $targetNamespace ) {
38        if ( $targetNamespace != $inputDoc['namespace'] ) {
39            // This is a cross namespace redirect, we don't
40            // add defaultsort for this one.
41            return;
42        }
43        if ( $suggestType === SuggestBuilder::TITLE_SUGGESTION && isset( $inputDoc[ self::FIELD ] ) ) {
44            $value = $inputDoc[self::FIELD];
45            if ( is_string( $value ) && $this->isRelevantDefaultSort( $inputDoc["title"], $value ) ) {
46                $this->addInputToFST( $value, 'suggest', $suggestDoc );
47                $this->addInputToFST( $value, 'suggest-stop', $suggestDoc );
48            }
49        }
50    }
51
52    /**
53     * @param string $input the new input
54     * @param string $fstField field name
55     * @param \Elastica\Document $suggestDoc
56     */
57    private function addInputToFST( $input, $fstField, $suggestDoc ) {
58        if ( $suggestDoc->has( $fstField ) ) {
59            $entryDef = $suggestDoc->get( $fstField );
60            $entryDef['input'][] = $input;
61            $suggestDoc->set( $fstField, $entryDef );
62        }
63    }
64
65    /**
66     * Verify that default is relevant to the title.
67     * We inspect a common pattern for default which is using a comma as a separator:
68     *  John Doe (Person) => Doe, John
69     * @param string $title
70     * @param string $defaultSort
71     * @return bool
72     */
73    private function isRelevantDefaultSort( string $title, string $defaultSort ): bool {
74        $split = explode( ', ', $defaultSort, 2 );
75        if ( count( $split ) !== 2 ) {
76            return false;
77        }
78        $normalizedTitle = $this->utr30->transliterate( $title );
79        if ( $normalizedTitle === false ) {
80            return false;
81        }
82        $normalizedDefaultSort = $this->utr30->transliterate( "{$split[1]} {$split[0]}" );
83        if ( $normalizedDefaultSort === false ) {
84            return false;
85        }
86        $normalizedDefaultSort = preg_replace( '/\s+/', ' ', $normalizedDefaultSort );
87        if ( $normalizedDefaultSort === null ) {
88            return false;
89        }
90        $normalizedDefaultSort = rtrim( $normalizedDefaultSort );
91        $normalizedTitle = preg_replace( '/\s+/', ' ', $normalizedTitle );
92        if ( $normalizedTitle === null ) {
93            return false;
94        }
95        $normalizedTitle = rtrim( $normalizedTitle );
96        return str_starts_with( $normalizedTitle, $normalizedDefaultSort );
97    }
98}