Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
62.50% covered (warning)
62.50%
5 / 8
CRAP
96.05% covered (success)
96.05%
73 / 76
FullTextCirrusSearchResultBuilder
0.00% covered (danger)
0.00%
0 / 1
62.50% covered (warning)
62.50%
5 / 8
33
96.05% covered (success)
96.05%
73 / 76
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 newBuilder
0.00% covered (danger)
0.00%
0 / 1
2.06
75.00% covered (warning)
75.00%
3 / 4
 build
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
20 / 20
 getTitleHelper
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 doTitleSnippet
100.00% covered (success)
100.00%
1 / 1
11
100.00% covered (success)
100.00%
20 / 20
 doMainSnippet
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
12 / 12
 doHeadings
0.00% covered (danger)
0.00%
0 / 1
4.03
87.50% covered (warning)
87.50%
7 / 8
 doCategory
0.00% covered (danger)
0.00%
0 / 1
4.05
85.71% covered (warning)
85.71%
6 / 7
<?php
namespace CirrusSearch\Search;
use CirrusSearch\Search\Fetch\HighlightedField;
use CirrusSearch\Search\Fetch\HighlightingTrait;
use MWTimestamp;
use Title;
class FullTextCirrusSearchResultBuilder {
    use HighlightingTrait;
    /** @var CirrusSearchResultBuilder|null */
    private $builder;
    /** @var TitleHelper */
    private $titleHelper;
    /** @var HighlightedField[][] indexed per target and ordered by priority */
    private $highligtedFields;
    /** @var string[] */
    private $extraFields;
    /**
     * @param TitleHelper $titleHelper
     * @param HighlightedField[][] $hlFieldsPerTarget list of highlighted field indexed per target and sorted by priority
     * @param string[] $extraFields list of extra fields to extract from the source doc
     */
    public function __construct( TitleHelper $titleHelper, array $hlFieldsPerTarget, array $extraFields = [] ) {
        $this->titleHelper = $titleHelper;
        $this->highligtedFields = $hlFieldsPerTarget;
        $this->extraFields = $extraFields;
    }
    /**
     * @param Title $title
     * @param string $docId
     * @return CirrusSearchResultBuilder
     */
    private function newBuilder( Title $title, $docId ): CirrusSearchResultBuilder {
        if ( $this->builder === null ) {
            $this->builder = new CirrusSearchResultBuilder( $title, $docId );
        } else {
            $this->builder->reset( $title, $docId );
        }
        return $this->builder;
    }
    /**
     * @param \Elastica\Result $result
     * @return CirrusSearchResult
     */
    public function build( \Elastica\Result $result ): CirrusSearchResult {
        $title = $this->getTitleHelper()->makeTitle( $result );
        $fields = $result->getFields();
        $builder = $this->newBuilder( $title, $result->getId() )
            ->wordCount( $fields['text.word_count'][0] ?? 0 )
            ->byteSize( $result->text_bytes ?? 0 )
            ->timestamp( new MWTimestamp( $result->timestamp ) )
            ->score( $result->getScore() )
            ->explanation( $result->getExplanation() );
        if ( isset( $result->namespace_text ) ) {
            $builder->interwikiNamespaceText( $result->namespace_text );
        }
        $highlights = $result->getHighlights();
        $this->doTitleSnippet( $title, $result, $highlights );
        $this->doMainSnippet( $highlights );
        $this->doHeadings( $title, $highlights );
        $this->doCategory( $highlights );
        $source = $result->getData();
        foreach ( $this->extraFields as $field ) {
            if ( isset( $source[$field] ) ) {
                $builder->addExtraField( $field, $source[$field] );
            }
        }
        return $builder->build();
    }
    /**
     * @return TitleHelper
     */
    protected function getTitleHelper(): TitleHelper {
        return $this->titleHelper;
    }
    private function doTitleSnippet( Title $title, \Elastica\Result $result, array $highlights ) {
        $matched = false;
        foreach ( $this->highligtedFields[ArrayCirrusSearchResult::TITLE_SNIPPET] as $hlField ) {
            if ( isset( $highlights[$hlField->getFieldName()] ) ) {
                $nstext = $title->getNamespace() === 0 ? '' :
                    $this->titleHelper->getNamespaceText( $title ) . ':';
                $this->builder->titleSnippet( $nstext . $this->escapeHighlightedText( $highlights[ $hlField->getFieldName() ][ 0 ] ) );
                $matched = true;
                break;
            }
        }
        if ( !$matched && $title->isExternal() ) {
            // Interwiki searches are weird. They won't have title highlights by design, but
            // if we don't return a title snippet we'll get weird display results.
            $this->builder->titleSnippet( $title->getText() );
        }
        if ( !$matched && isset( $this->highligtedFields[ArrayCirrusSearchResult::REDIRECT_SNIPPET] ) ) {
            foreach ( $this->highligtedFields[ArrayCirrusSearchResult::REDIRECT_SNIPPET] as $hlField ) {
                // Make sure to find the redirect title before escaping because escaping breaks it....
                if ( !isset( $highlights[$hlField->getFieldName()][0] ) ) {
                    continue;
                }
                $redirTitle = $this->findRedirectTitle( $result, $highlights[$hlField->getFieldName()][0] );
                if ( $redirTitle !== null ) {
                    $this->builder->redirectTitle( $redirTitle )
                        ->redirectSnippet( $this->escapeHighlightedText( $highlights[$hlField->getFieldName()][0] ) );
                    break;
                }
            }
        }
    }
    private function doMainSnippet( $highlights ) {
        $hasTextSnippet = false;
        foreach ( $this->highligtedFields[ArrayCirrusSearchResult::TEXT_SNIPPET] as $hlField ) {
            if ( isset( $highlights[$hlField->getFieldName()][0] ) ) {
                $snippet = $highlights[$hlField->getFieldName()][0];
                if ( $this->containsMatches( $snippet ) ) {
                    $this->builder->textSnippet( $this->escapeHighlightedText( $snippet ) )->
                        fileMatch( $hlField->getFieldName() === 'file_text' );
                    $hasTextSnippet = true;
                    break;
                }
            }
        }
        // Hardcode the fallback to the "text" highlight, it generally contains the beginning of the
        // text content if nothing has matched.
        if ( !$hasTextSnippet && isset( $highlights['text'][0] ) ) {
            $this->builder->textSnippet( $this->escapeHighlightedText( $highlights['text'][0] ) );
        }
    }
    private function doHeadings( Title $title, $highlights ) {
        if ( !isset( $this->highligtedFields[ArrayCirrusSearchResult::SECTION_SNIPPET] ) ) {
            return;
        }
        foreach ( $this->highligtedFields[ArrayCirrusSearchResult::SECTION_SNIPPET] as $hlField ) {
            if ( isset( $highlights[$hlField->getFieldName()] ) ) {
                $this->builder->sectionSnippet( $this->escapeHighlightedText( $highlights[$hlField->getFieldName()][0] ) )
                    ->sectionTitle( $this->findSectionTitle( $highlights[$hlField->getFieldName()][0], $title ) );
                break;
            }
        }
    }
    private function doCategory( $highlights ) {
        if ( !isset( $this->highligtedFields[ArrayCirrusSearchResult::CATEGORY_SNIPPET] ) ) {
            return;
        }
        foreach ( $this->highligtedFields[ArrayCirrusSearchResult::CATEGORY_SNIPPET] as $hlField ) {
            if ( isset( $highlights[$hlField->getFieldName()] ) ) {
                $this->builder->categorySnippet( $this->escapeHighlightedText( $highlights[$hlField->getFieldName()][0] ) );
            }
            break;
        }
    }
}