Current File : /home/jvzmxxx/wiki/extensions/Wikibase/client/includes/Api/PageTerms.php
<?php

namespace Wikibase\Client\Api;

use ApiQuery;
use ApiQueryBase;
use ApiResult;
use InvalidArgumentException;
use Title;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\Store\EntityIdLookup;
use Wikibase\TermIndex;
use Wikibase\TermIndexEntry;

/**
 * Provides wikibase terms (labels, descriptions, aliases, etc.) for local pages.
 * For example, if a data item has the label "Washington" and the description "capital
 * city of the US", and has a sitelink to the local page called "Washington DC", calling
 * pageterms with titles=Washington_DC would include that label and description
 * in the response.
 *
 * @since 0.5
 *
 * @license GPL-2.0+
 * @author Daniel Kinzler
 */
class PageTerms extends ApiQueryBase {

	/**
	 * @todo: Use LabelDescriptionLookup for labels/descriptions, so we can apply language fallback.
	 * @var TermIndex
	 */
	private $termIndex;

	/**
	 * @var EntityIdLookup
	 */
	private $idLookup;

	/**
	 * @param TermIndex $termIndex
	 * @param EntityIdLookup $idLookup
	 * @param ApiQuery $query
	 * @param string $moduleName
	 */
	public function __construct(
		TermIndex $termIndex,
		EntityIdLookup $idLookup,
		ApiQuery $query,
		$moduleName
	) {
		parent::__construct( $query, $moduleName, 'wbpt' );
		$this->termIndex = $termIndex;
		$this->idLookup = $idLookup;
	}

	public function execute() {
		$languageCode = $this->getLanguage()->getCode();
		$params = $this->extractRequestParams();

		# Only operate on existing pages
		$titles = $this->getPageSet()->getGoodTitles();
		if ( !count( $titles ) ) {
			# Nothing to do
			return;
		}

		// NOTE: continuation relies on $titles being sorted by page ID.
		ksort( $titles );

		$continue = $params['continue'];

		$pagesToEntityIds = $this->getEntityIdsForTitles( $titles, $continue );
		$entityToPageMap = $this->getEntityToPageMap( $pagesToEntityIds );

		$terms = $this->getTermsOfEntities( $pagesToEntityIds, $params['terms'], array( $languageCode ) );

		$termGroups = $this->groupTermsByPageAndType( $entityToPageMap, $terms );

		$this->addTermsToResult( $pagesToEntityIds, $termGroups );
	}

	/**
	 * @param EntityId[] $pagesToEntityIds
	 *
	 * @return array[]
	 */
	private function splitPageEntityMapByType( array $pagesToEntityIds ) {
		$groups = array();

		foreach ( $pagesToEntityIds as $pageId => $entityId ) {
			$type = $entityId->getEntityType();
			$groups[$type][$pageId] = $entityId;
		}

		return $groups;
	}

	/**
	 * @param EntityId[] $entityIds
	 * @param string[]|null $termTypes
	 * @param string[]|null $languageCodes
	 *
	 * @return TermIndexEntry[]
	 */
	private function getTermsOfEntities( array $entityIds, array $termTypes = null, array $languageCodes = null ) {
		$entityIdGroups = $this->splitPageEntityMapByType( $entityIds );
		$terms = array();

		foreach ( $entityIdGroups as $entityIds ) {
			$terms = array_merge(
				$terms,
				$this->termIndex->getTermsOfEntities( $entityIds, $termTypes, $languageCodes )
			);
		}

		return $terms;
	}

	/**
	 * @param Title[] $titles
	 * @param int|null $continue
	 *
	 * @return array
	 */
	private function getEntityIdsForTitles( array $titles, $continue = 0 ) {
		$entityIds = $this->idLookup->getEntityIds( $titles );

		// Re-sort, so the order of page IDs matches the order in which $titles
		// were given. This is essential for paging to work properly.
		// This also skips all page IDs up to $continue.
		$sortedEntityId = array();
		foreach ( $titles as $pid => $title ) {
			if ( $pid >= $continue && isset( $entityIds[$pid] ) ) {
				$sortedEntityId[$pid] = $entityIds[$pid];
			}
		}

		return $sortedEntityId;
	}

	/**
	 * @param EntityId[] $entityIds
	 *
	 * @return int[]
	 */
	private function getEntityToPageMap( array $entityIds ) {
		$entityIdsStrings = array_map(
			function( EntityId $id ) {
				return $id->getSerialization();
			},
			$entityIds
		);

		return array_flip( $entityIdsStrings );
	}

	/**
	 * @param int[] $entityToPageMap
	 * @param TermIndexEntry[] $terms
	 *
	 * @return array[] An associative array, mapping pageId + entity type to a list of strings.
	 */
	private function groupTermsByPageAndType( array $entityToPageMap, array $terms ) {
		$termsPerPage = array();

		foreach ( $terms as $term ) {
			// Since we construct $terms and $entityToPageMap from the same set of page IDs,
			// the entry $entityToPageMap[$key] should really always be set.
			$type = $term->getType();
			$key = $term->getEntityId()->getSerialization();
			$pageId = $entityToPageMap[$key];
			$text = $term->getText();

			if ( $text !== null ) {
				// For each page ID, record a list of terms for each term type.
				$termsPerPage[$pageId][$type][] = $text;
			} else {
				// $text should never be null, but let's be vigilant.
				wfWarn( __METHOD__ . ': Encountered null text in TermIndexEntry object!' );
			}
		}

		return $termsPerPage;
	}

	/**
	 * @param EntityId[] $pagesToEntityIds
	 * @param array[] $termGroups
	 */
	private function addTermsToResult( array $pagesToEntityIds, array $termGroups ) {
		$result = $this->getResult();

		foreach ( $pagesToEntityIds as $currentPage => $entityId ) {
			if ( !isset( $termGroups[$currentPage] ) ) {
				// No entity for page, or no terms for entity.
				continue;
			}

			$group = $termGroups[$currentPage];

			if ( !$this->addTermsForPage( $result, $currentPage, $group ) ) {
				break;
			}
		}
	}

	/**
	 * Add page term to an ApiResult, adding a continue
	 * parameter if it doesn't fit.
	 *
	 * @param ApiResult $result
	 * @param int $pageId
	 * @param array[] $termsByType
	 *
	 * @throws InvalidArgumentException
	 * @return bool True if it fits in the result
	 */
	private function addTermsForPage( ApiResult $result, $pageId, array $termsByType ) {
		ApiResult::setIndexedTagNameRecursive( $termsByType, 'term' );

		$fit = $result->addValue( array( 'query', 'pages', $pageId ), 'terms', $termsByType );

		if ( !$fit ) {
			$this->setContinueEnumParameter( 'continue', $pageId );
		}

		return $fit;
	}

	/**
	 * @see ApiBase::getAllowedParams
	 */
	protected function getAllowedParams() {
		return array(
			'continue' => array(
				self::PARAM_HELP_MSG => 'api-help-param-continue',
				self::PARAM_TYPE => 'integer',
			),
			'terms' => array(
				// XXX: Ought to get this list from Wikibase\TermIndexEntry, its setType() also hardcodes it.
				self::PARAM_TYPE => array(
					TermIndexEntry::TYPE_ALIAS,
					TermIndexEntry::TYPE_DESCRIPTION,
					TermIndexEntry::TYPE_LABEL
				),
				self::PARAM_ISMULTI => true,
				self::PARAM_HELP_MSG => 'apihelp-query+pageterms-param-terms',
			),
		);
	}

	/**
	 * @see ApiBase::getExamplesMessages
	 */
	protected function getExamplesMessages() {
		if ( defined( 'WB_VERSION' ) ) {
			return array(
				'action=query&prop=pageterms&titles=Q84'
					=> 'apihelp-query+pageterms-example-item',
			);
		} else {
			return array(
				'action=query&prop=pageterms&titles=London'
					=> 'apihelp-query+pageterms-example-simple',
				'action=query&prop=pageterms&titles=London&wbptterms=label|alias&uselang=en'
					=> 'apihelp-query+pageterms-example-label-en',
			);
		}
	}

}