API: Allow generators to return data
authorBrad Jorsch <bjorsch@wikimedia.org>
Mon, 24 Nov 2014 21:21:49 +0000 (16:21 -0500)
committerBrad Jorsch <bjorsch@wikimedia.org>
Wed, 26 Nov 2014 15:41:17 +0000 (10:41 -0500)
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
includes/api/ApiPageSet.php
includes/api/ApiQuery.php
includes/api/ApiQueryPrefixSearch.php
includes/api/ApiQuerySearch.php

index 2781b38..0759951 100644 (file)
@@ -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
index ea85cac..78c33ed 100644 (file)
@@ -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
index 5a0491a..bd28408 100644 (file)
@@ -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
index 3c90acc..95f8483 100644 (file)
@@ -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 ) {
index 0f33d07..66ef8db 100644 (file)
@@ -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 ) );
+                       }
                }
        }