wikimedia/mediawiki-extensions-CirrusSearch

View on GitHub
includes/MetaStore/MetaNamespaceStore.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace CirrusSearch\MetaStore;

use Elastica\Document;
use Elastica\Index;
use Elastica\Query\BoolQuery;
use Elastica\Query\Ids;
use Elastica\Query\MatchQuery;
use MediaWiki\Language\Language;
use MediaWiki\WikiMap\WikiMap;

class MetaNamespaceStore implements MetaStore {
    /** @const Value of metastore 'type' field for our documents */
    public const METASTORE_TYPE = 'namespace';

    /** @var Index */
    private $index;

    /** @var string */
    private $wikiId;

    public function __construct( Index $index, $wikiId = null ) {
        $this->index = $index;
        $this->wikiId = $wikiId ?? WikiMap::getCurrentWikiId();
    }

    /**
     * @param string $wikiId Wiki namespace belongs to
     * @param int $nsId Id of the namespace
     * @return string Metastore document id
     */
    public static function docId( $wikiId, $nsId ) {
        return implode( '-', [
            self::METASTORE_TYPE,
            $wikiId, $nsId
        ] );
    }

    /**
     * @return array Custom field mappings for metastore index
     */
    public function buildIndexProperties() {
        return [
            'namespace_name' => [
                'type' => 'text',
                'analyzer' => 'near_match_asciifolding',
                'norms' => false,
                'index_options' => 'docs',
            ],
        ];
    }

    /**
     * Delete and re-index all namespaces for current wiki
     *
     * @param Language $lang Content language of the wiki
     */
    public function reindex( Language $lang ) {
        $documents = $this->buildDocuments( $lang );
        $docIds = [];
        foreach ( $documents as $doc ) {
            $docIds[] = $doc->getId();
        }
        $this->index->deleteByQuery( \Elastica\Query::create(
            $this->queryFilter()->addMustNot( new Ids( $docIds ) ) ) );
        $this->index->addDocuments( $documents );
    }

    /**
     * Find namespaces on the current wiki very similar to $name.
     * Invoking from a user request must be gated on a PoolCounter.
     *
     * @param string $name Namespace to search for
     * @param array $queryOptions Query parameters to send to elasticsearch
     * @return \Elastica\ResultSet
     */
    public function find( $name, array $queryOptions = [] ) {
        $bool = $this->queryFilter();
        $bool->addMust( ( new MatchQuery() )
            ->setField( 'namespace_name', $name ) );
        $query = ( new \Elastica\Query( $bool ) )
            ->setParam( '_source', [ 'namespace_id' ] )
            ->setParam( 'stats', [ 'namespace' ] );

        return $this->index->search( $query, $queryOptions );
    }

    private function queryFilter() {
        return ( new BoolQuery() )
            ->addFilter( new MatchQuery( 'type', self::METASTORE_TYPE ) )
            ->addFilter( new MatchQuery( 'wiki', $this->wikiId ) );
    }

    private function buildDocuments( Language $lang ) {
        $namesByNsId = [];
        foreach ( $lang->getNamespaceIds() as $name => $nsId ) {
            if ( $name ) {
                $namesByNsId[$nsId][] = $name;
            }
        }
        $documents = [];
        foreach ( $namesByNsId as $nsId => $names ) {
            $documents[] = new Document( self::docId( $this->wikiId, $nsId ), [
                'type' => self::METASTORE_TYPE,
                'wiki' => $this->wikiId,
                'namespace_id' => $nsId,
                'namespace_name' => $names,
            ], '_doc' );
        }
        return $documents;
    }
}