Current File : /home/jvzmxxx/wiki/extensions/Wikibase/lib/tests/phpunit/Store/MockTermIndex.php
<?php

namespace Wikibase\Lib\Tests\Store;

use Exception;
use InvalidArgumentException;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\Lib\Store\LabelConflictFinder;
use Wikibase\TermIndex;
use Wikibase\TermIndexEntry;

/**
 * Mock implementation of TermIndex.
 *
 * @note: this uses internal knowledge about which functions of TermIndex are used
 * by PropertyLabelResolver, and how.
 *
 * @todo: make a fully functional mock conforming to the contract of the TermIndex
 * interface and passing tests for that interface. Only then will TermPropertyLabelResolverTest
 * be a true blackbox test.
 *
 * @since 0.4
 *
 * @license GPL-2.0+
 * @author Daniel Kinzler
 */
class MockTermIndex implements TermIndex, LabelConflictFinder {

	/**
	 * @var TermIndexEntry[]
	 */
	protected $terms;

	/**
	 * @param TermIndexEntry[] $terms
	 */
	public function __construct( array $terms ) {
		$this->terms = $terms;
	}

	/**
	 * @see LabelConflictFinder::getLabelConflicts
	 *
	 * @param string $entityType The relevant entity type
	 * @param string[] $labels The label to look for
	 * @param array[]|null $aliases
	 *
	 * @throws InvalidArgumentException
	 * @return EntityId[]
	 */
	public function getLabelConflicts( $entityType, array $labels, array $aliases = null ) {
		if ( !is_string( $entityType ) ) {
			throw new InvalidArgumentException( '$entityType must be a string' );
		}

		if ( empty( $labels ) && empty( $aliases ) ) {
			return array();
		}

		$termTypes = ( $aliases === null )
			? array( TermIndexEntry::TYPE_LABEL )
			: array( TermIndexEntry::TYPE_LABEL, TermIndexEntry::TYPE_ALIAS );

		$termTexts = ( $aliases === null )
			? $labels
			: array_merge( $labels, $aliases );

		$templates = $this->makeTemplateTerms( $termTexts, $termTypes );

		$conflicts = $this->getMatchingTerms(
			$templates,
			$termTypes,
			$entityType
		);

		return $conflicts;
	}

	/**
	 * @see LabelConflictFinder::getLabelWithDescriptionConflicts
	 *
	 * @param string $entityType The relevant entity type
	 * @param string[] $labels The label to look for
	 * @param string[] $descriptions The description to consider, if descriptions are relevant.
	 *
	 * @return EntityId[]
	 */
	public function getLabelWithDescriptionConflicts(
		$entityType,
		array $labels,
		array $descriptions
	) {
		$labels = array_intersect_key( $labels, $descriptions );
		$descriptions = array_intersect_key( $descriptions, $labels );

		if ( empty( $descriptions ) || empty( $labels ) ) {
			return array();
		}

		$labelConflicts = $this->getLabelConflicts(
			$entityType,
			$labels
		);

		if ( empty( $labelConflicts ) ) {
			return array();
		}

		$templates = $this->makeTemplateTerms( $descriptions, array( TermIndexEntry::TYPE_DESCRIPTION ) );

		$descriptionConflicts = $this->getMatchingTerms(
			$templates,
			TermIndexEntry::TYPE_DESCRIPTION,
			$entityType
		);

		$conflicts = $this->intersectConflicts( $labelConflicts, $descriptionConflicts );

		return $conflicts;
	}

	/**
	 * @param array[]|string[] $textsByLanguage A list of texts, or a list of lists of texts (keyed by language on the top level)
	 * @param string[] $types
	 *
	 * @return TermIndexEntry[]
	 */
	private function makeTemplateTerms( array $textsByLanguage, array $types ) {
		$terms = array();

		foreach ( $textsByLanguage as $lang => $texts ) {
			$texts = (array)$texts;

			foreach ( $texts as $text ) {
				foreach ( $types as $type ) {
					$terms[] = new TermIndexEntry( array(
						'termText' => $text,
						'termLanguage' => $lang,
						'termType' => $type,
					) );
				}
			}
		}

		return $terms;
	}

	/**
	 * @param string $label
	 * @param string|null $languageCode
	 * @param string|null $entityType
	 * @param bool $fuzzySearch
	 *
	 * @return EntityId[]
	 */
	public function getEntityIdsForLabel( $label, $languageCode = null, $entityType = null,
		$fuzzySearch = false
	) {
		$entityIds = array();

		foreach ( $this->terms as $term ) {
			if ( $languageCode !== null && $term->getLanguage() !== $languageCode ) {
				continue;
			}

			if ( $entityType !== null && $term->getEntityType() !== $entityType ) {
				continue;
			}

			if ( $term->getType() !== 'label' ) {
				continue;
			}

			if ( !$fuzzySearch ) {
				if ( $term->getText() === $label ) {
					$entityIds[] = $term->getEntityId();
				}
			} else {
				if ( strpos( $term->getText(), $label ) !== false ) {
					$entityIds[] = $term->getEntityId();
				}
			}
		}

		return $entityIds;
	}

	/**
	 * @throws Exception always
	 */
	public function saveTermsOfEntity( EntityDocument $entity ) {
		throw new Exception( 'not implemented by mock class ' );
	}

	/**
	 * @throws Exception always
	 */
	public function deleteTermsOfEntity( EntityId $entityId ) {
		throw new Exception( 'not implemented by mock class ' );
	}

	/**
	 * @param EntityId $entityId
	 * @param string[]|null $termTypes
	 * @param string[]|null $languageCodes
	 *
	 * @return TermIndexEntry[]
	 */
	public function getTermsOfEntity(
		EntityId $entityId,
		array $termTypes = null,
		array $languageCodes = null
	) {
		$matchingTerms = array();

		if ( is_array( $termTypes ) ) {
			$termTypes = array_flip( $termTypes );
		}

		if ( is_array( $languageCodes ) ) {
			$languageCodes = array_flip( $languageCodes );
		}

		foreach ( $this->terms as $term ) {
			if ( ( is_array( $termTypes ) && !isset( $termTypes[$term->getType()] ) )
				|| ( is_array( $languageCodes ) && !isset( $languageCodes[$term->getLanguage()] ) )
				|| !$entityId->equals( $term->getEntityId() )
			) {
				continue;
			}

			$matchingTerms[] = $term;
		}

		return $matchingTerms;
	}

	/**
	 * @see TermIndex::getTermsOfEntities
	 *
	 * @param EntityId[] $entityIds
	 * @param string[]|null $termTypes
	 * @param string[]|null $languageCodes
	 *
	 * @return TermIndexEntry[]
	 */
	public function getTermsOfEntities(
		array $entityIds,
		array $termTypes = null,
		array $languageCodes = null
	) {
		$terms = array();

		foreach ( $entityIds as $id ) {
			$terms = array_merge(
				$terms,
				$this->getTermsOfEntity( $id, $termTypes, $languageCodes )
			);
		}

		return $terms;
	}

	/**
	 * @throws Exception always
	 */
	public function termExists(
		$termValue,
		$termType = null,
		$termLanguage = null,
		$entityType = null
	) {
		throw new Exception( 'not implemented by mock class ' );
	}

	/**
	 * Implemented to fit the need of PropertyLabelResolver.
	 *
	 * @note: The $options parameters is ignored. The language to get is determined by the
	 * language of the first Term in $terms. $The termType and $entityType parameters are used,
	 * but the termType and entityType fields of the Terms in $terms are ignored.
	 *
	 * @param TermIndexEntry[] $terms
	 * @param string|string[]|null $termType
	 * @param string|string[]|null $entityType
	 * @param array $options
	 *
	 * @return TermIndexEntry[]
	 */
	public function getMatchingTerms(
		array $terms,
		$termType = null,
		$entityType = null,
		array $options = array()
	) {
		$matchingTerms = array();

		$termType = $termType === null ? null : (array)$termType;
		$entityType = $entityType === null ? null : (array)$entityType;

		foreach ( $this->terms as $term ) {
			if ( ( $entityType === null || in_array( $term->getEntityType(), $entityType ) )
				&& ( $termType === null || in_array( $term->getType(), $termType ) )
				&& $this->termMatchesTemplates( $term, $terms, $options )
			) {
				$matchingTerms[] = $term;
			}
		}

		$limit = isset( $options['LIMIT'] ) ? $options['LIMIT'] : 0;

		if ( $limit > 0 ) {
			$matchingTerms = array_slice( $matchingTerms, 0, $limit );
		}

		return $matchingTerms;
	}

	/**
	 * Returns the same as getMatchingTerms simply making sure only one term
	 * is returned per EntityId. This is the first term.
	 * Weighting does not affect the order of return by this method.
	 *
	 * @param TermIndexEntry[] $terms
	 * @param string|string[]|null $termType
	 * @param string|string[]|null $entityType
	 * @param array $options
	 *
	 * @return TermIndexEntry[]
	 */
	public function getTopMatchingTerms(
		array $terms,
		$termType = null,
		$entityType = null,
		array $options = array()
	) {
		$options['orderByWeight'] = true;
		$terms = $this->getMatchingTerms( $terms, $termType, $entityType, $options );
		$previousEntityIdSerializations = array();
		$returnTerms = array();
		foreach ( $terms as $termIndexEntry ) {
			if ( !in_array( $termIndexEntry->getEntityId()->getSerialization(), $previousEntityIdSerializations ) ) {
				$returnTerms[] = $termIndexEntry;
				$previousEntityIdSerializations[] = $termIndexEntry->getEntityId()->getSerialization();
			}
		}
		return $returnTerms;
	}

	/**
	 * @throws Exception always
	 */
	public function clear() {
		$this->terms = array();
	}

	/**
	 * Rekeys a list of Terms based on EntityId and language.
	 *
	 * @param TermIndexEntry[] $conflicts
	 *
	 * @return TermIndexEntry[]
	 */
	private function rekeyConflicts( array $conflicts ) {
		$rekeyed = array();

		foreach ( $conflicts as $term ) {
			$key = $term->getEntityId()->getSerialization();
			$key .= '/' . $term->getLanguage();

			$rekeyed[$key] = $term;
		}

		return $rekeyed;
	}

	/**
	 * Intersects two lists of Terms based on EntityId and language.
	 *
	 * @param TermIndexEntry[] $base
	 * @param TermIndexEntry[] $filter
	 *
	 * @return TermIndexEntry[]
	 */
	private function intersectConflicts( array $base, array $filter ) {
		$base = $this->rekeyConflicts( $base );
		$filter = $this->rekeyConflicts( $filter );

		return array_intersect_key( $base, $filter );
	}

	/**
	 * @param TermIndexEntry $term
	 * @param TermIndexEntry[] $templates
	 * @param array $options
	 *
	 * @return bool
	 */
	private function termMatchesTemplates( TermIndexEntry $term, array $templates, array $options = array() ) {
		foreach ( $templates as $template ) {
			if ( $template->getType() !== null && $template->getType() != $term->getType() ) {
				continue;
			}

			if ( $template->getEntityType() !== null && $template->getEntityType() != $term->getEntityType() ) {
				continue;
			}

			if ( $template->getLanguage() !== null && $template->getLanguage() != $term->getLanguage() ) {
				continue;
			}

			if ( $template->getText() !== null && !$this->textMatches( $template->getText(), $term->getText(), $options ) ) {
				continue;
			}

			if ( $template->getEntityId() !== null && !$template->getEntityId()->equals( $term->getEntityType() ) ) {
				continue;
			}

			return true;
		}

		return false;
	}

	private function textMatches( $find, $text, array $options = array() ) {

		if ( isset( $options[ 'caseSensitive' ] ) && !$options[ 'caseSensitive' ] ) {
			$find = strtolower( $find );
			$text = strtolower( $text );
		}

		if ( isset( $options[ 'prefixSearch' ] ) && $options[ 'prefixSearch' ] ) {
			$text = substr( $text, 0, strlen( $find ) );
		}

		return $find === $text;
	}

}