aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/hooks.txt15
-rw-r--r--includes/Rest/Entity/SearchResultPageIdentity.php38
-rw-r--r--includes/Rest/Entity/SearchResultPageIdentityValue.php46
-rw-r--r--includes/Rest/Handler/SearchHandler.php163
-rw-r--r--includes/Rest/Hook/SearchResultProvideDescriptionHook.php25
-rw-r--r--includes/Rest/Hook/SearchResultProvideThumbnailHook.php25
-rw-r--r--includes/search/Entity/SearchResultThumbnail.php136
-rw-r--r--tests/api-testing/REST/Search.js8
-rw-r--r--tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php114
9 files changed, 534 insertions, 36 deletions
diff --git a/docs/hooks.txt b/docs/hooks.txt
index 42cb793a7eb3..d745e79a79f1 100644
--- a/docs/hooks.txt
+++ b/docs/hooks.txt
@@ -4057,5 +4057,20 @@ $row: The database row for the revision being dumped. DEPRECATED, use $rev inste
$text: The revision text to be dumped. DEPRECATED, use $rev instead.
$rev: The RevisionRecord that is being dumped to XML
+'SearchResultProvideDescription': Called by REST SearchHandler in order to allow
+extensions to fill the 'description' field in search results. Warning: this
+hook as well as SearchResultPageIdentity interface is being under development and still unstable.
+$pageIdentities: an array (string=>SearchResultPageIdentity) where key is pageId.
+&$descriptions: an output array (string=>string|null) where key is pageId and value is either
+a desciption for given page or null
+
+'SearchResultProvideThumbnail': Called by REST SearchHandler in order to allow
+extensions to fill the 'thumbnail' field in rest search results. Warning: this
+hook as well as SearchResultPageIdentity interface is being under development and still unstable.
+$pageIdentities: an array (string=>SearchResultPageIdentity) where key is pageId.
+&$thumbnails: an output array (string=>SearchResultThumbnail|null) where key is pageId and
+value is either a valid SearchResultThumbnail for given page or null
+
+
More hooks might be available but undocumented, you can execute
"php maintenance/findHooks.php" to find hidden ones.
diff --git a/includes/Rest/Entity/SearchResultPageIdentity.php b/includes/Rest/Entity/SearchResultPageIdentity.php
new file mode 100644
index 000000000000..a811d20d9e4b
--- /dev/null
+++ b/includes/Rest/Entity/SearchResultPageIdentity.php
@@ -0,0 +1,38 @@
+<?php
+namespace MediaWiki\Rest\Entity;
+
+/**
+ * Lightweight interface representing a page identity
+ *
+ * @unstable
+ * @note This interface is temorary solution. It will be replaced by the one from:
+ * https://phabricator.wikimedia.org/T208776
+ */
+interface SearchResultPageIdentity {
+ /**
+ * The numerical page ID.
+ * At the moment it is equivalent to Title::getArticleID()
+ * @see Title::getArticleID()
+ *
+ * @return int
+ */
+ function getId(): int;
+
+ /**
+ * Returns the page's namespace number.
+ * At the moment it is equivalent to Title::getNamespace()
+ * @see Title::getNamespace()
+ *
+ * @return int
+ */
+ function getNamespace(): int;
+
+ /**
+ * Get the page title in DB key form.
+ * At the moment it is equivalent to Title::getDBkey()
+ * @see Title::getDBkey()
+ *
+ * @return string
+ */
+ function getDBkey(): string;
+}
diff --git a/includes/Rest/Entity/SearchResultPageIdentityValue.php b/includes/Rest/Entity/SearchResultPageIdentityValue.php
new file mode 100644
index 000000000000..def4ae5a569d
--- /dev/null
+++ b/includes/Rest/Entity/SearchResultPageIdentityValue.php
@@ -0,0 +1,46 @@
+<?php
+namespace MediaWiki\Rest\Entity;
+
+/**
+ * Lightweight value class representing a page identity
+ *
+ * @unstable
+ * @note This class is temorary solution. It will be replaced by the one from:
+ * https://phabricator.wikimedia.org/T208776
+ */
+class SearchResultPageIdentityValue implements SearchResultPageIdentity {
+
+ /**
+ * @var int
+ */
+ private $id = 0;
+
+ /**
+ * @var string
+ */
+ private $dbKey = '';
+
+ /**
+ * @var int
+ */
+ private $namespace = null;
+
+ public function __construct( int $id, int $namespace, string $dbKey ) {
+ $this->id = $id;
+ $this->namespace = $namespace;
+ $this->dbKey = $dbKey;
+ }
+
+ public function getId(): int {
+ return $this->id;
+ }
+
+ public function getNamespace(): int {
+ return $this->namespace;
+ }
+
+ public function getDBkey(): string {
+ return $this->dbKey;
+ }
+
+}
diff --git a/includes/Rest/Handler/SearchHandler.php b/includes/Rest/Handler/SearchHandler.php
index 7a335ecb5e09..c9b73de823a5 100644
--- a/includes/Rest/Handler/SearchHandler.php
+++ b/includes/Rest/Handler/SearchHandler.php
@@ -3,15 +3,18 @@
namespace MediaWiki\Rest\Handler;
use Config;
+use Hooks;
use InvalidArgumentException;
use ISearchResultSet;
use MediaWiki\Permissions\PermissionManager;
+use MediaWiki\Rest\Entity\SearchResultPageIdentityValue;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\LocalizedHttpException;
use MediaWiki\Rest\RequestInterface;
use MediaWiki\Rest\Response;
use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\Router;
+use MediaWiki\Search\Entity\SearchResultThumbnail;
use RequestContext;
use SearchEngine;
use SearchEngineConfig;
@@ -171,10 +174,10 @@ class SearchHandler extends Handler {
}
/**
- * Execute search and return results.
+ * Execute search and return info about pages for further processing.
*
* @param SearchEngine $searchEngine
- * @return SearchResult[]
+ * @return array[]
* @throws LocalizedHttpException
*/
private function doSearch( $searchEngine ) {
@@ -182,7 +185,7 @@ class SearchHandler extends Handler {
if ( $this->mode == self::COMPLETION_MODE ) {
$completionSearch = $searchEngine->completionSearchWithVariants( $query );
- return $this->buildOutputFromSuggestions( $completionSearch->getSuggestions() );
+ return $this->buildPageInfosFromSuggestions( $completionSearch->getSuggestions() );
} else {
$titleSearch = $searchEngine->searchTitle( $query );
$textSearch = $searchEngine->searchText( $query );
@@ -191,70 +194,143 @@ class SearchHandler extends Handler {
$textSearchResults = $this->getSearchResultsOrThrow( $textSearch );
$mergedResults = array_merge( $titleSearchResults, $textSearchResults );
- return $this->buildOutputFromSearchResults( $mergedResults );
+ return $this->buildPageInfosFromSearchResults( $mergedResults );
}
}
/**
- * Remove duplicate pages and turn results into response json objects
+ * Remove duplicate pages and turn suggestions into array with
+ * information needed for further processing:
+ * pageId => [ $title, $suggestion, null ]
*
* @param SearchSuggestion[] $suggestions
*
- * @return array page objects
+ * @return array[] of pageId => [ $title, $suggestion, null ]
*/
- private function buildOutputFromSuggestions( array $suggestions ) {
- $pages = [];
- $foundPageIds = [];
+ private function buildPageInfosFromSuggestions( array $suggestions ): array {
+ $pageInfos = [];
+
foreach ( $suggestions as $sugg ) {
$title = $sugg->getSuggestedTitle();
if ( $title && $title->exists() ) {
$pageID = $title->getArticleID();
- if ( !isset( $foundPageIds[$pageID] ) &&
+ if ( !isset( $pageInfos[$pageID] ) &&
$this->permissionManager->quickUserCan( 'read', $this->user, $title )
) {
- $page = [
- 'id' => $pageID,
- 'key' => $title->getPrefixedDBkey(),
- 'title' => $title->getPrefixedText(),
- 'excerpt' => $sugg->getText() ?: null,
- ];
- $pages[] = $page;
- $foundPageIds[$pageID] = true;
+ $pageInfos[ $pageID ] = [ $title, $sugg, null ];
}
}
}
- return $pages;
+ return $pageInfos;
}
/**
- * Remove duplicate pages and turn results into response json objects
+ * Remove duplicate pages and turn search results into array with
+ * information needed for further processing:
+ * pageId => [ $title, null, $result ]
*
* @param SearchResult[] $searchResults
*
- * @return array page objects
+ * @return array[] of pageId => [ $title, null, $result ]
*/
- private function buildOutputFromSearchResults( array $searchResults ) {
- $pages = [];
- $foundPageIds = [];
+ private function buildPageInfosFromSearchResults( array $searchResults ): array {
+ $pageInfos = [];
+
foreach ( $searchResults as $result ) {
if ( !$result->isBrokenTitle() && !$result->isMissingRevision() ) {
$title = $result->getTitle();
$pageID = $title->getArticleID();
- if ( !isset( $foundPageIds[$pageID] ) &&
+ if ( !isset( $pageInfos[$pageID] ) &&
$this->permissionManager->quickUserCan( 'read', $this->user, $title )
) {
- $page = [
- 'id' => $pageID,
- 'key' => $title->getPrefixedDBkey(),
- 'title' => $title->getPrefixedText(),
- 'excerpt' => $result->getTextSnippet() ?: null,
- ];
- $pages[] = $page;
- $foundPageIds[$pageID] = true;
+ $pageInfos[$pageID] = [ $title, null, $result ];
}
}
}
- return $pages;
+ return $pageInfos;
+ }
+
+ /**
+ * Turn array of page info into serializable array with common information about the page
+ *
+ * @param array[] $pageInfos
+ *
+ * @return array[] of pageId => [ $title, null, $result ]
+ */
+ private function buildResultFromPageInfos( array $pageInfos ): array {
+ return array_map( function ( $pageInfo ) {
+ list( $title, $sugg, $result ) = $pageInfo;
+ return [
+ 'id' => $title->getArticleID(),
+ 'key' => $title->getPrefixedDBkey(),
+ 'title' => $title->getPrefixedText(),
+ 'excerpt' => ( $sugg ? $sugg->getText() : $result->getTextSnippet() ) ?: null,
+ ];
+ },
+ $pageInfos );
+ }
+
+ /**
+ * Converts SearchResultThumbnail object into serializable array
+ *
+ * @param SearchResultThumbnail|null $thumbnail
+ *
+ * @return array|null
+ */
+ private function serializeThumbnail( ?SearchResultThumbnail $thumbnail ) : ?array {
+ if ( $thumbnail == null ) {
+ return null;
+ }
+
+ return [
+ 'mimetype' => $thumbnail->getMimeType(),
+ 'size' => $thumbnail->getSize(),
+ 'width' => $thumbnail->getWidth(),
+ 'height' => $thumbnail->getHeight(),
+ 'duration' => $thumbnail->getDuration(),
+ 'url' => $thumbnail->getUrl(),
+ ];
+ }
+
+ /**
+ * Turn page info into serializable array with desciption field for the page.
+ *
+ * The information about desciption should be provided by extension by implementing
+ * 'SearchResultProvideDescription' hook. Desciption is set to null if no extensions implement
+ * the hook.
+ * @param array $pageIdentities
+ *
+ * @return array
+ */
+ private function buildDescriptionsFromPageIdentities( array $pageIdentities ) {
+ $descriptions = array_fill_keys( array_keys( $pageIdentities ), null );
+
+ Hooks::run( 'SearchResultProvideDescription', [ $pageIdentities, &$descriptions ] );
+
+ return array_map( function ( $description ) {
+ return [ 'description' => $description ];
+ }, $descriptions );
+ }
+
+ /**
+ * Turn page info into serializable array with thumbnail information for the page.
+ *
+ * The information about thumbnail should be provided by extension by implementing
+ * 'SearchResultProvideThumbnail' hook. Thumbnail is set to null if no extensions implement
+ * the hook.
+ *
+ * @param array $pageIdentities
+ *
+ * @return array
+ */
+ private function buildThumbnailsFromPageIdentities( array $pageIdentities ) {
+ $thumbnails = array_fill_keys( array_keys( $pageIdentities ), null );
+
+ Hooks::run( 'SearchResultProvideThumbnail', [ $pageIdentities, &$thumbnails ] );
+
+ return array_map( function ( $thumbnail ) {
+ return [ 'thumbnail' => $this->serializeThumbnail( $thumbnail ) ];
+ }, $thumbnails );
}
/**
@@ -263,8 +339,23 @@ class SearchHandler extends Handler {
*/
public function execute() {
$searchEngine = $this->createSearchEngine();
- $results = $this->doSearch( $searchEngine );
- $response = $this->getResponseFactory()->createJson( [ 'pages' => $results ] );
+ $pageInfos = $this->doSearch( $searchEngine );
+ $pageIdentities = array_map( function ( $pageInfo ) {
+ list( $title ) = $pageInfo;
+ return new SearchResultPageIdentityValue(
+ $title->getArticleID(),
+ $title->getNamespace(),
+ $title->getDBkey()
+ );
+ }, $pageInfos );
+
+ $result = array_map( "array_merge",
+ $this->buildResultFromPageInfos( $pageInfos ),
+ $this->buildDescriptionsFromPageIdentities( $pageIdentities ),
+ $this->buildThumbnailsFromPageIdentities( $pageIdentities )
+ );
+
+ $response = $this->getResponseFactory()->createJson( [ 'pages' => $result ] );
if ( $this->mode === self::COMPLETION_MODE && $this->completionCacheExpiry ) {
// Type-ahead completion matches should be cached by the client and
diff --git a/includes/Rest/Hook/SearchResultProvideDescriptionHook.php b/includes/Rest/Hook/SearchResultProvideDescriptionHook.php
new file mode 100644
index 000000000000..82832e54077a
--- /dev/null
+++ b/includes/Rest/Hook/SearchResultProvideDescriptionHook.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace MediaWiki\Rest\Hook;
+
+/**
+ * Called by REST SearchHandler in order to allow extensions to fill the 'description'
+ * field in search results. Warning: this hook as well as SearchResultPageIdentity interface
+ * is being under development and still unstable.
+ *
+ * @unstable
+ * @ingroup Hooks
+ */
+interface SearchResultProvideDescriptionHook {
+ /**
+ * This hook is called when generating search results in order to fill the 'description'
+ * field in an extension.
+ *
+ * @since 1.35
+ *
+ * @param array $pageIdentities an array (string=>SearchResultPageIdentity) where key is pageId.
+ * @param array &$descriptions an output array (string=>string|null) where key
+ * is pageId and value is either a desciption for given page or null
+ */
+ public function onSearchResultProvideDescription( array $pageIdentities, &$descriptions );
+}
diff --git a/includes/Rest/Hook/SearchResultProvideThumbnailHook.php b/includes/Rest/Hook/SearchResultProvideThumbnailHook.php
new file mode 100644
index 000000000000..ffa2f182e9a9
--- /dev/null
+++ b/includes/Rest/Hook/SearchResultProvideThumbnailHook.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace MediaWiki\Rest\Hook;
+
+/**
+ * Called by REST SearchHandler in order to allow extensions to fill the 'thumbnail'
+ * field in rest search results. Warning: this hook as well as SearchResultPageIdentity
+ * interface is being under development and still unstable.
+ *
+ * @unstable
+ * @ingroup Hooks
+ */
+interface SearchResultProvideThumbnailHook {
+ /**
+ * This hook is called when generating search results in order to fill the 'thumbnail'
+ * field in an extension.
+ *
+ * @since 1.35
+ *
+ * @param array $pageIdentities an array (string=>SearchResultPageIdentity) where key is pageId.
+ * @param array &$thumbnails an output array (string=>SearchResultThumbnail|null) where key
+ * is pageId and value is either a valid SearchResultThumbnail for given page or null
+ */
+ public function onSearchResultProvideThumbnail( array $pageIdentities, &$thumbnails );
+}
diff --git a/includes/search/Entity/SearchResultThumbnail.php b/includes/search/Entity/SearchResultThumbnail.php
new file mode 100644
index 000000000000..82a30432bd2d
--- /dev/null
+++ b/includes/search/Entity/SearchResultThumbnail.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace MediaWiki\Search\Entity;
+
+/**
+ * Class that stores information about thumbnail, e. g. url, width and height
+ * @newable
+ */
+class SearchResultThumbnail {
+ /**
+ * Internet mime type for the representation, like "image/png" or "audio/mp3"
+ * @var string
+ */
+ private $mimeType;
+
+ /**
+ * Size of the representation in bytes or null if not applicable
+ * @var int|null
+ */
+ private $size;
+
+ /**
+ * Duration of the representation in seconds or null if not applicable
+ * @var int|null
+ */
+ private $duration;
+
+ /**
+ * Full URL to the contents of the file
+ * @var string
+ */
+ private $url;
+
+ /**
+ * Width of the representation in pixels or null if not applicable
+ * @var int|null
+ */
+ private $width;
+
+ /**
+ * Height of the representation in pixels or null if not applicable
+ * @var int|null
+ */
+ private $height;
+
+ /**
+ * String that represent file indentity in storage or null
+ * @var string|null
+ */
+ private $name;
+
+ /**
+ * @param string $mimeType Internet mime type for the representation,
+ * like "image/png" or "audio/mp3"
+ * @param int|null $size Size of the representation in bytes
+ * @param int|null $width Width of the representation in pixels or null if not applicable
+ * @param int|null $height Height of the representation in pixels or null if not applicable
+ * @param int|null $duration Duration of the representation in seconds or
+ * null if not applicable
+ * @param string $url full URL to the contents of the file
+ * @param string|null $name full URL to the contents of the file
+ */
+ public function __construct(
+ string $mimeType,
+ ?int $size,
+ ?int $width,
+ ?int $height,
+ ?int $duration,
+ string $url,
+ ?string $name
+ ) {
+ $this->mimeType = $mimeType;
+ $this->size = $size;
+ $this->width = $width;
+ $this->height = $height;
+ $this->duration = $duration;
+ $this->url = $url;
+ $this->name = $name;
+ }
+
+ /**
+ * Full URL to the contents of the file
+ * @return string
+ */
+ public function getUrl(): string {
+ return $this->url;
+ }
+
+ /**
+ * Width of the representation in pixels or null if not applicable
+ * @return int|null
+ */
+ public function getWidth(): ?int {
+ return $this->width;
+ }
+
+ /**
+ * Height of the representation in pixels or null if not applicable
+ * @return int|null
+ */
+ public function getHeight(): ?int {
+ return $this->height;
+ }
+
+ /**
+ * Internet mime type for the representation, like "image/png" or "audio/mp3"
+ * @return string
+ */
+ public function getMimeType(): string {
+ return $this->mimeType;
+ }
+
+ /**
+ * Size of the representation in bytes or null if not applicable
+ * @return int|null
+ */
+ public function getSize(): ?int {
+ return $this->size;
+ }
+
+ /**
+ * Duration of the representation in seconds or null if not applicable
+ * @return int|null
+ */
+ public function getDuration(): ?int {
+ return $this->duration;
+ }
+
+ /**
+ * String that represent file indentity in storage or null
+ * @return string|null
+ */
+ public function getName(): ?string {
+ return $this->name;
+ }
+}
diff --git a/tests/api-testing/REST/Search.js b/tests/api-testing/REST/Search.js
index 0b798a940ec4..6249e70e1834 100644
--- a/tests/api-testing/REST/Search.js
+++ b/tests/api-testing/REST/Search.js
@@ -34,6 +34,8 @@ describe( 'Search', () => {
assert.nestedProperty( returnPage, 'id' );
assert.nestedProperty( returnPage, 'key' );
assert.nestedProperty( returnPage, 'excerpt' );
+ assert.nestedPropertyVal( returnPage, 'thumbnail', null );
+ assert.nestedPropertyVal( returnPage, 'description', null );
assert.include( returnPage.excerpt, `<span class='searchmatch'>${searchTerm}</span>` );
// full-text search should not have cache-control
@@ -51,6 +53,8 @@ describe( 'Search', () => {
assert.nestedProperty( returnPage, 'id' );
assert.nestedProperty( returnPage, 'key' );
assert.nestedPropertyVal( returnPage, 'excerpt', null );
+ assert.nestedPropertyVal( returnPage, 'thumbnail', null );
+ assert.nestedPropertyVal( returnPage, 'description', null );
} );
it( 'should return a single page when there is a title and text match on the same page', async () => {
const { body } = await client.get( `/search/page?q=${pageWithOwnTitle}` );
@@ -60,6 +64,8 @@ describe( 'Search', () => {
assert.nestedProperty( returnPage, 'id' );
assert.nestedProperty( returnPage, 'key' );
assert.nestedPropertyVal( returnPage, 'title', pageWithOwnTitle );
+ assert.nestedPropertyVal( returnPage, 'thumbnail', null );
+ assert.nestedPropertyVal( returnPage, 'description', null );
} );
it( 'should return two pages when both pages match', async () => {
const { body } = await client.get( `/search/page?q=${searchTerm2}` );
@@ -104,6 +110,8 @@ describe( 'Search', () => {
assert.nestedProperty( returnPage, 'id' );
assert.nestedProperty( returnPage, 'key' );
assert.nestedProperty( returnPage, 'excerpt' );
+ assert.nestedPropertyVal( returnPage, 'thumbnail', null );
+ assert.nestedPropertyVal( returnPage, 'description', null );
// completion search should encourage caching
assert.nestedProperty( headers, 'cache-control' );
diff --git a/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php b/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php
index 865e7b63bda5..d88bd861504f 100644
--- a/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php
+++ b/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php
@@ -3,12 +3,14 @@
namespace MediaWiki\Tests\Rest\Handler;
use HashConfig;
+use InvalidArgumentException;
use Language;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\Handler\SearchHandler;
use MediaWiki\Rest\LocalizedHttpException;
use MediaWiki\Rest\RequestData;
+use MediaWiki\Search\Entity\SearchResultThumbnail;
use MockSearchResultSet;
use PHPUnit\Framework\MockObject\MockObject;
use SearchEngine;
@@ -345,4 +347,116 @@ class SearchHandlerTest extends \MediaWikiUnitTestCase {
$this->assertFalse( $handler->needsWriteAccess() );
}
+ public function testExecute_augmentedFields() {
+ $titleResults = Status::newGood( new MockSearchResultSet( [
+ $this->makeMockSearchResult( 'Foo', 'one' ),
+ $this->makeMockSearchResult( 'FooBar', 'three' ),
+ ] ) );
+ $textResults = Status::newGood( new MockSearchResultSet( [
+ $this->makeMockSearchResult( 'Quux', 'one' ),
+ $this->makeMockSearchResult( 'Xyzzy', 'three' ),
+ ] ) );
+
+ $query = 'foo';
+ $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] );
+
+ $handler = $this->newHandler( $query, $titleResults, $textResults );
+ $data = $this->executeHandlerAndGetBodyData( $handler, $request );
+
+ $this->assertArrayHasKey( 'pages', $data );
+ $this->assertCount( 4, $data['pages'] );
+ $this->assertArrayHasKey( 'thumbnail', $data['pages'][0] );
+ $this->assertNull( $data['pages'][0][ 'thumbnail' ] );
+
+ $this->assertArrayHasKey( 'description', $data['pages'][0] );
+ $this->assertNull( $data['pages'][0][ 'description' ] );
+ }
+
+ public function testExecute_augmentedFieldsDescriptionAndThumbnailProvided() {
+ $titleResults = Status::newGood( new MockSearchResultSet( [
+ $this->makeMockSearchResult( 'Foo', 'one' ),
+ ] ) );
+ $textResults = Status::newGood( new MockSearchResultSet( [
+ $this->makeMockSearchResult( 'Quux', 'one' ),
+ ] ) );
+
+ $query = 'foo';
+ $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] );
+
+ $handler = $this->newHandler( $query, $titleResults, $textResults );
+ $this->setTemporaryHook( 'SearchResultProvideDescription',
+ function ( array $pageIdentities, array &$result ) {
+ foreach ( $pageIdentities as $pageId => $pageIdentity ) {
+ $result[ $pageId ] = 'Description_' . $pageIdentity->getId();
+ }
+ } );
+
+ $this->setTemporaryHook( 'SearchResultProvideThumbnail',
+ function ( array $pageIdentities, array &$result ) {
+ foreach ( $pageIdentities as $pageId => $pageIdentity ) {
+ $result[ $pageId ] = new SearchResultThumbnail(
+ 'image/png',
+ 2250,
+ 100,
+ 125,
+ 500,
+ 'http:/example.org/url_' . $pageIdentity->getId(),
+ null
+ );
+ }
+ } );
+
+ $data = $this->executeHandlerAndGetBodyData( $handler, $request );
+
+ $this->assertArrayHasKey( 'pages', $data );
+ $this->assertCount( 2, $data['pages'] );
+ $this->assertArrayHasKey( 'thumbnail', $data['pages'][0] );
+
+ $this->assertSame( 'http:/example.org/url_1', $data['pages'][0][ 'thumbnail' ]['url'] );
+ $this->assertSame( 125, $data['pages'][0][ 'thumbnail' ]['height'] );
+ $this->assertSame( 100, $data['pages'][0][ 'thumbnail' ]['width'] );
+ $this->assertSame( 'image/png', $data['pages'][0][ 'thumbnail' ]['mimetype'] );
+ $this->assertSame( 2250, $data['pages'][0][ 'thumbnail' ]['size'] );
+ $this->assertSame( 500, $data['pages'][0][ 'thumbnail' ]['duration'] );
+ $this->assertArrayHasKey( 'description', $data['pages'][0] );
+ $this->assertSame( 'Description_1', $data['pages'][0][ 'description' ] );
+ }
+
+ public function testExecute_NullResults() {
+ $query = 'foo';
+ $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] );
+
+ $handler = $this->newHandler( $query, null, null );
+ $data = $this->executeHandlerAndGetBodyData( $handler, $request );
+
+ $this->assertArrayHasKey( 'pages', $data );
+ $this->assertCount( 0, $data['pages'] );
+ }
+
+ public function testInitWrongConfig() {
+ $titleResults = Status::newGood( new MockSearchResultSet( [
+ $this->makeMockSearchResult( 'Foo', 'one' ),
+ $this->makeMockSearchResult( 'FooBar', 'three' ),
+ ] ) );
+ $textResults = Status::newGood( new MockSearchResultSet( [
+ $this->makeMockSearchResult( 'Quux', 'one' ),
+ $this->makeMockSearchResult( 'Xyzzy', 'three' ),
+ ] ) );
+
+ $query = 'foo';
+ $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] );
+
+ $this->expectException( InvalidArgumentException::class );
+
+ $handler = $this->newHandler( $query, $titleResults, $textResults );
+ $data = $this->executeHandlerAndGetBodyData(
+ $handler,
+ $request,
+ [
+ 'mode' => 'SomethingWrong'
+ ] );
+
+ $this->assertArrayHasKey( 'pages', $data );
+ $this->assertCount( 0, $data['pages'] );
+ }
}