From e2055fe0a55cffafe9ba8f9440465c664f14996f Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Mon, 24 Nov 2014 16:21:49 -0500 Subject: [PATCH] API: Allow generators to return data It has long been requested that list=search and list=prefixsearch be able to indicate the search result ordering when used as generators. This change introduces a generic mechanism to allow for generators to specify additional page data. Bug: T16859 Bug: T75623 Change-Id: I115338d2bd890ccc109a79c65f92099c0d41fc2d --- RELEASE-NOTES-1.25 | 7 ++ includes/api/ApiPageSet.php | 95 +++++++++++++++++++++++++++ includes/api/ApiQuery.php | 2 + includes/api/ApiQueryPrefixSearch.php | 5 ++ includes/api/ApiQuerySearch.php | 4 ++ 5 files changed, 113 insertions(+) diff --git a/RELEASE-NOTES-1.25 b/RELEASE-NOTES-1.25 index 2781b380e9..07599516fe 100644 --- a/RELEASE-NOTES-1.25 +++ b/RELEASE-NOTES-1.25 @@ -101,6 +101,11 @@ production. * (bug 66776) format=json results will no longer be corrupted when $wgMangleFlashPolicy is in effect. format=php results will cleanly return an error instead of returning invalid serialized data. +* Generators may now return data for the generated pages when used with + action=query. +* Query page data for generator=search and generator=prefixsearch will now + include an "index" field, which may be used by the client for sorting the + search results. === Action API internal changes in 1.25 === * ApiHelp has been rewritten to support i18n and paginated HTML output. @@ -130,6 +135,8 @@ production. revisions as "good" if the user has the 'deletedhistory' right. New methods ApiPageSet::getLiveRevisionIDs() and ApiPageSet::getDeletedRevisionIDs() are provided to access just the live or just the deleted revids. +* Added ApiPageSet::setGeneratorData() and ApiPageSet::populateGeneratorData() + to allow generators to include data in the action=query result. * The following methods have been deprecated and may be removed in a future release: * ApiBase::getDescription diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index ea85cac550..78c33ed85a 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -71,6 +71,7 @@ class ApiPageSet extends ApiBase { private $mLiveRevIDs = array(); private $mDeletedRevIDs = array(); private $mMissingRevIDs = array(); + private $mGeneratorData = array(); // [ns][dbkey] => data array private $mFakePageId = -1; private $mCacheMode = 'public'; private $mRequestedPageFields = array(); @@ -1173,6 +1174,100 @@ class ApiPageSet extends ApiBase { return $linkBatch; } + /** + * Set data for a title. + * + * This data may be extracted into an ApiResult using + * self::populateGeneratorData. This should generally be limited to + * data that is likely to be particularly useful to end users rather than + * just being a dump of everything returned in non-generator mode. + * + * Redirects here will *not* be followed, even if 'redirects' was + * specified, since in the case of multiple redirects we can't know which + * source's data to use on the target. + * + * @param Title $title + * @param array $data + */ + public function setGeneratorData( Title $title, array $data ) { + $ns = $title->getNamespace(); + $dbkey = $title->getDBkey(); + $this->mGeneratorData[$ns][$dbkey] = $data; + } + + /** + * Populate the generator data for all titles in the result + * + * The page data may be inserted into an ApiResult object or into an + * associative array. The $path parameter specifies the path within the + * ApiResult or array to find the "pages" node. + * + * The "pages" node itself must be an associative array mapping the page ID + * or fake page ID values returned by this pageset (see + * self::getAllTitlesByNamespace() and self::getSpecialTitles()) to + * associative arrays of page data. Each of those subarrays will have the + * data from self::setGeneratorData() merged in. + * + * Data that was set by self::setGeneratorData() for pages not in the + * "pages" node will be ignored. + * + * @param ApiResult|array &$result + * @param array $path + * @return boolean Whether the data fit + */ + public function populateGeneratorData( &$result, array $path = array() ) { + if ( $result instanceof ApiResult ) { + $data = $result->getData(); + } else { + $data = &$result; + } + foreach ( $path as $key ) { + if ( !isset( $data[$key] ) ) { + // Path isn't in $result, so nothing to add, so everything + // "fits" + return true; + } + $data = &$data[$key]; + } + foreach ( $this->mGeneratorData as $ns => $dbkeys ) { + if ( $ns === -1 ) { + $pages = array(); + foreach ( $this->mSpecialTitles as $id => $title ) { + $pages[$title->getDBkey()] = $id; + } + } else { + if ( !isset( $this->mAllPages[$ns] ) ) { + // No known titles in the whole namespace. Skip it. + continue; + } + $pages = $this->mAllPages[$ns]; + } + foreach ( $dbkeys as $dbkey => $genData ) { + if ( !isset( $pages[$dbkey] ) ) { + // Unknown title. Forget it. + continue; + } + $pageId = $pages[$dbkey]; + if ( !isset( $data[$pageId] ) ) { + // $pageId didn't make it into the result. Ignore it. + continue; + } + + if ( $result instanceof ApiResult ) { + $path2 = array_merge( $path, array( $pageId ) ); + foreach ( $genData as $key => $value ) { + if ( !$result->addValue( $path2, $key, $value ) ) { + return false; + } + } + } else { + $data[$pageId] = array_merge( $data[$pageId], $genData ); + } + } + } + return true; + } + /** * Get the database connection (read-only) * @return DatabaseBase diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 5a0491ae7b..bd28408b64 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -437,6 +437,8 @@ class ApiQuery extends ApiBase { } if ( count( $pages ) ) { + $pageSet->populateGeneratorData( $pages ); + if ( $this->mParams['indexpageids'] ) { $pageIDs = array_keys( $pages ); // json treats all map keys as strings - converting to match diff --git a/includes/api/ApiQueryPrefixSearch.php b/includes/api/ApiQueryPrefixSearch.php index 3c90acc821..95f84839a3 100644 --- a/includes/api/ApiQueryPrefixSearch.php +++ b/includes/api/ApiQueryPrefixSearch.php @@ -48,6 +48,11 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase { $titles = $searcher->searchWithVariants( $search, $limit, $namespaces ); if ( $resultPageSet ) { $resultPageSet->populateFromTitles( $titles ); + /** @todo If this module gets an 'offset' parameter, use it here */ + $offset = 1; + foreach ( $titles as $index => $title ) { + $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset ) ); + } } else { $result = $this->getResult(); foreach ( $titles as $title ) { diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index 0f33d07f2e..66ef8db555 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -259,6 +259,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } } else { $resultPageSet->populateFromTitles( $titles ); + $offset = $params['offset'] + 1; + foreach ( $titles as $index => $title ) { + $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset ) ); + } } } -- 2.20.1