'MediaWiki\\Widget\\DateTimeInputWidget' => __DIR__ . '/includes/widget/DateTimeInputWidget.php',
'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php',
'MediaWiki\\Widget\\SearchInputWidget' => __DIR__ . '/includes/widget/SearchInputWidget.php',
+ 'MediaWiki\\Widget\\Search\\BasicSearchResultSetWidget' => __DIR__ . '/includes/widget/search/BasicSearchResultSetWidget.php',
'MediaWiki\\Widget\\Search\\FullSearchResultWidget' => __DIR__ . '/includes/widget/search/FullSearchResultWidget.php',
+ 'MediaWiki\\Widget\\Search\\InterwikiSearchResultSetWidget' => __DIR__ . '/includes/widget/search/InterwikiSearchResultSetWidget.php',
'MediaWiki\\Widget\\Search\\SearchFormWidget' => __DIR__ . '/includes/widget/search/SearchFormWidget.php',
'MediaWiki\\Widget\\Search\\SearchResultWidget' => __DIR__ . '/includes/widget/search/SearchResultWidget.php',
'MediaWiki\\Widget\\Search\\SimpleSearchResultWidget' => __DIR__ . '/includes/widget/search/SimpleSearchResultWidget.php',
*/
use MediaWiki\MediaWikiServices;
+use MediaWiki\Widget\Search\BasicSearchResultSetWidget;
+use MediaWiki\Widget\Search\InterwikiSearchResultSetWidget;
+use MediaWiki\Widget\Search\FullSearchResultWidget;
+use MediaWiki\Widget\Search\SimpleSearchResultWidget;
/**
* implements Special:Search - Run text & title search and display the output
*/
protected $runSuggestion = true;
- /**
- * Names of the wikis, in format: Interwiki prefix -> caption
- * @var array
- */
- protected $customCaptions;
-
/**
* Search engine configurations.
* @var SearchEngineConfig
if ( $textMatches ) {
$textMatchesNum = $textMatches->numRows();
$numTextMatches = $textMatches->getTotalHits();
+ if ( $textMatchesNum > 0 ) {
+ $search->augmentSearchResults( $textMatches );
+ }
}
$num = $titleMatchesNum + $textMatchesNum;
$totalRes = $numTitleMatches + $numTextMatches;
// Show the create link ahead
$this->showCreateLink( $title, $num, $titleMatches, $textMatches );
+ Hooks::run( 'SpecialSearchResults', [ $term, $titleMatches, $textMatches ] );
+
// If we have no results and have not already displayed an error message
if ( $num === 0 && !$hasErrors ) {
$out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
] );
}
- Hooks::run( 'SpecialSearchResults', [ $term, $titleMatches, $textMatches ] );
+ // Although $num might be 0 there can still be secondary or inline
+ // results to display.
+ $linkRenderer = $this->getLinkRenderer();
+ $mainResultWidget = new FullSearchResultWidget( $this, $linkRenderer );
+ $sidebarResultWidget = new SimpleSearchResultWidget( $this, $linkRenderer );
+ $sidebarResultsWidget = new InterwikiSearchResultSetWidget(
+ $this,
+ $sidebarResultWidget,
+ $linkRenderer,
+ MediaWikiServices::getInstance()->getInterwikiLookup()
+ );
+ $widget = new BasicSearchResultSetWidget( $this, $mainResultWidget, $sidebarResultsWidget );
+
+ $out->addHTML( $widget->render(
+ $term, $this->offset, $titleMatches, $textMatches
+ ) );
- $out->parserOptions()->setEditSection( false );
if ( $titleMatches ) {
- if ( $numTitleMatches > 0 ) {
- $out->wrapWikiMsg( "==$1==\n", 'titlematches' );
- $out->addHTML( $this->showMatches( $titleMatches ) );
- }
$titleMatches->free();
}
- if ( $textMatches ) {
- // output appropriate heading
- if ( $numTextMatches > 0 && $numTitleMatches > 0 ) {
- $out->addHTML( '<div class="mw-search-visualclear"></div>' );
- // if no title matches the heading is redundant
- $out->wrapWikiMsg( "==$1==\n", 'textmatches' );
- }
-
- // show results
- if ( $numTextMatches > 0 ) {
- $search->augmentSearchResults( $textMatches );
- $out->addHTML( $this->showMatches( $textMatches ) );
- }
-
- // show secondary interwiki results if any
- if ( $textMatches->hasInterwikiResults( SearchResultSet::SECONDARY_RESULTS ) ) {
- $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(
- SearchResultSet::SECONDARY_RESULTS ), $term ) );
- }
- }
-
- if ( $hasOtherResults ) {
- foreach ( $textMatches->getInterwikiResults( SearchResultSet::INLINE_RESULTS )
- as $interwiki => $interwikiResult ) {
- if ( $interwikiResult instanceof Status || $interwikiResult->numRows() == 0 ) {
- // ignore bad interwikis for now
- continue;
- }
- // TODO: wiki header
- $out->addHTML( $this->showMatches( $interwikiResult, $interwiki ) );
- }
- }
-
if ( $textMatches ) {
$textMatches->free();
}
Hooks::run( 'SpecialSearchResultsAppend', [ $this, $out, $term ] );
}
- /**
- * Produce wiki header for interwiki results
- * @param string $interwiki Interwiki name
- * @param SearchResultSet $interwikiResult The result set
- * @return string
- */
- protected function interwikiHeader( $interwiki, $interwikiResult ) {
- // TODO: we need to figure out how to name wikis correctly
- $wikiMsg = $this->msg( 'search-interwiki-results-' . $interwiki )->parse();
- return "<p class=\"mw-search-interwiki-header mw-search-visualclear\">\n$wikiMsg</p>";
- }
-
/**
* Generates HTML shown to the user when we have a suggestion about a query
* that might give more results than their current query.
return false;
}
- /**
- * Show whole set of results
- *
- * @param SearchResultSet $matches
- * @param string $interwiki Interwiki name
- *
- * @return string
- */
- protected function showMatches( $matches, $interwiki = null ) {
- global $wgContLang;
-
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $out = '';
- $result = $matches->next();
- $pos = $this->offset;
-
- if ( $result && $interwiki ) {
- $out .= $this->interwikiHeader( $interwiki, $matches );
- }
-
- $out .= "<ul class='mw-search-results'>\n";
- $widget = new \MediaWiki\Widget\Search\FullSearchResultWidget(
- $this,
- $this->getLinkRenderer()
- );
- while ( $result ) {
- $out .= $widget->render( $result, $terms, $pos++ );
- $result = $matches->next();
- }
- $out .= "</ul>\n";
-
- // convert the whole thing to desired language variant
- $out = $wgContLang->convert( $out );
-
- return $out;
- }
-
- /**
- * Extract custom captions from search-interwiki-custom message
- */
- protected function getCustomCaptions() {
- if ( is_null( $this->customCaptions ) ) {
- $this->customCaptions = [];
- // format per line <iwprefix>:<caption>
- $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() );
- foreach ( $customLines as $line ) {
- $parts = explode( ":", $line, 2 );
- if ( count( $parts ) == 2 ) { // validate line
- $this->customCaptions[$parts[0]] = $parts[1];
- }
- }
- }
- }
-
- /**
- * Show results from other wikis
- *
- * @param SearchResultSet|array $matches
- * @param string $terms
- *
- * @return string
- */
- protected function showInterwiki( $matches, $terms ) {
- global $wgContLang;
-
- // work out custom project captions
- $this->getCustomCaptions();
-
- if ( !is_array( $matches ) ) {
- $matches = [ $matches ];
- }
-
- $iwResults = [];
- foreach ( $matches as $set ) {
- $result = $set->next();
- while ( $result ) {
- if ( !$result->isBrokenTitle() ) {
- $iwResults[$result->getTitle()->getInterwiki()][] = $result;
- }
- $result = $set->next();
- }
- }
-
- $out = '';
- $widget = new MediaWiki\Widget\Search\SimpleSearchResultWidget(
- $this,
- $this->getLinkRenderer()
- );
- foreach ( $iwResults as $iwPrefix => $results ) {
- $out .= $this->iwHeaderHtml( $iwPrefix, $terms );
- $out .= "<ul class='mw-search-iwresults'>";
- foreach ( $results as $result ) {
- // This makes the bold asumption interwiki results are never paginated.
- // That's currently true, but could change at some point?
- $out .= $widget->render( $result, $terms, 0 );
- }
- $out .= "</ul>";
- }
-
- $out =
- "<div id='mw-search-interwiki'>" .
- "<div id='mw-search-interwiki-caption'>" .
- $this->msg( 'search-interwiki-caption' )->escaped() .
- "</div>" .
- $out .
- "</div>";
-
- // convert the whole thing to desired language variant
- return $wgContLang->convert( $out );
- }
-
- /**
- * @param string $iwPrefix The interwiki prefix to render a header for
- * @param string $terms The user-provided search terms
- */
- protected function iwHeaderHtml( $iwPrefix, $terms ) {
- if ( isset( $this->customCaptions[$iwPrefix] ) ) {
- $caption = $this->customCaptions[$iwPrefix];
- } else {
- $iwLookup = MediaWiki\MediaWikiServices::getInstance()->getInterwikiLookup();
- $interwiki = $iwLookup->fetch( $iwPrefix );
- $parsed = wfParseUrl( wfExpandUrl( $interwiki ? $interwiki->getURL() : '/' ) );
- $caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text();
- }
- $searchLink = Linker::linkKnown(
- Title::newFromText( "$iwPrefix:Special:Search" ),
- $this->msg( 'search-interwiki-more' )->text(),
- [],
- [
- 'search' => $terms,
- 'fulltext' => 1,
- ]
- );
- return
- "<div class='mw-search-interwiki-project'>" .
- "<span class='mw-search-interwiki-more'>{$searchLink}</span>" .
- $caption .
- "</div>";
- }
-
/**
* @return array
*/
--- /dev/null
+<?php
+
+namespace MediaWiki\Widget\Search;
+
+use Message;
+use SearchResultSet;
+use SpecialSearch;
+use Status;
+
+/**
+ * Renders the search result area. Handles Title and Full-Text search results,
+ * along with inline and sidebar secondary (interwiki) results.
+ */
+class BasicSearchResultSetWidget {
+ /** @var SpecialSearch */
+ protected $specialPage;
+ /** @var SearchResultWidget */
+ protected $resultWidget;
+ /** @var InterwikiSearchResultSetWidget */
+ protected $sidebarWidget;
+
+ public function __construct(
+ SpecialSearch $specialPage,
+ SearchResultWidget $resultWidget,
+ InterwikiSearchResultSetWidget $sidebarWidget
+ ) {
+ $this->specialPage = $specialPage;
+ $this->resultWidget = $resultWidget;
+ $this->sidebarWidget = $sidebarWidget;
+ }
+
+ /**
+ * @param string $term The search term to highlight
+ * @param int $offset The offset of the first result in the result set
+ * @param SearchResultSet|null $titleResultSet Results of searching only page titles
+ * @param SearchResultSet|null $textResultSet Results of general full text search.
+ * @return string HTML
+ */
+ public function render(
+ $term,
+ $offset,
+ SearchResultSet $titleResultSet = null,
+ SearchResultSet $textResultSet = null
+ ) {
+ global $wgContLang;
+
+ $hasTitle = $titleResultSet ? $titleResultSet->numRows() > 0 : false;
+ $hasText = $textResultSet ? $textResultSet->numRows() > 0 : false;
+ $hasSecondary = $textResultSet
+ ? $textResultSet->hasInterwikiResults( SearchResultSet::SECONDARY_RESULTS )
+ : false;
+ $hasSecondaryInline = $textResultSet
+ ? $textResultSet->hasInterwikiResults( SearchResultSet::INLINE_RESULTS )
+ : false;
+
+ if ( !$hasTitle && !$hasText && !$hasSecondary && !$hasSecondaryInline ) {
+ return '';
+ }
+
+ $out = '';
+ if ( $hasTitle ) {
+ $out .= $this->header( $this->specialPage->msg( 'titlematches' ) )
+ . $this->renderResultSet( $titleResultSet, $offset );
+ }
+
+ if ( $hasText ) {
+ if ( $hasTitle ) {
+ $out .= "<div class='mw-search-visualclear'></div>" .
+ $this->header( $this->specialPage->msg( 'textmatches' ) );
+ }
+ $out .= $this->renderResultSet( $textResultSet, $offset );
+ }
+
+ if ( $hasSecondaryInline ) {
+ $iwResults = $textResultSet->getInterwikiResults( SearchResultSet::INLINE_RESULTS );
+ foreach ( $iwResults as $interwiki => $results ) {
+ if ( $results instanceof Status || $results->numRows() === 0 ) {
+ // ignore bad interwikis for now
+ continue;
+ }
+ $out .=
+ "<p class='mw-search-interwiki-header mw-search-visualclear'>" .
+ $this->specialPage->msg( "search-interwiki-results-{$interwiki}" )->parse() .
+ "</p>";
+ $out .= $this->renderResultSet( $results, $offset );
+ }
+ }
+
+ if ( $hasSecondary ) {
+ $out .= $this->sidebarWidget->render(
+ $term,
+ $textResultSet->getInterwikiResults( SearchResultSet::SECONDARY_RESULTS )
+ );
+ }
+
+ // Convert the whole thing to desired language variant
+ // TODO: Move this up to Special:Search?
+ return $wgContLang->convert( $out );
+ }
+
+ /**
+ * Generate a headline for a section of the search results. In prior
+ * implementations this was rendering wikitext of '==$1==', but seems
+ * a waste to call the full parser to generate this tiny bit of html
+ *
+ * @param Message $msg i18n message to use as header
+ * @return string HTML
+ */
+ protected function header( Message $msg ) {
+ return
+ "<h2>" .
+ "<span class='mw-headline'>" . $msg->escaped() . "</span>" .
+ "</h2>";
+ }
+
+ /**
+ * @param SearchResultSet $resultSet The search results to render
+ * @param int $offset Offset of the first result in $resultSet
+ * @return string HTML
+ */
+ protected function renderResultSet( SearchResultSet $resultSet, $offset ) {
+ global $wgContLang;
+
+ $terms = $wgContLang->convertForSearchResult( $resultSet->termMatches() );
+
+ $hits = [];
+ $result = $resultSet->next();
+ while ( $result ) {
+ $hits[] .= $this->resultWidget->render( $result, $terms, $offset++ );
+ $result = $resultSet->next();
+ }
+
+ return "<ul class='mw-search-results'>" . implode( '', $hits ) . "</ul>";
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Widget\Search;
+
+use MediaWiki\Interwiki\InterwikiLookup;
+use MediaWiki\Linker\LinkRenderer;
+use SearchResultSet;
+use SpecialSearch;
+use Title;
+
+/**
+ * Renders one or more SearchResultSets into a sidebar grouped by
+ * interwiki prefix. Includes a per-wiki header indicating where
+ * the results are from.
+ */
+class InterwikiSearchResultSetWidget {
+ /** @var SpecialSearch */
+ protected $specialSearch;
+ /** @var SearchResultWidget */
+ protected $resultWidget;
+ /** @var string[]|null */
+ protected $customCaptions;
+ /** @var LinkRenderer */
+ protected $linkRenderer;
+ /** @var InterwikiLookup */
+ protected $iwLookup;
+
+ public function __construct(
+ SpecialSearch $specialSearch,
+ SearchResultWidget $resultWidget,
+ LinkRenderer $linkRenderer,
+ InterwikiLookup $iwLookup
+ ) {
+ $this->specialSearch = $specialSearch;
+ $this->resultWidget = $resultWidget;
+ $this->linkRenderer = $linkRenderer;
+ $this->iwLookup = $iwLookup;
+ }
+
+ /**
+ * @param string $term User provided search term
+ * @param SearchResultSet|SearchResultSet[] $resultSets List of interwiki
+ * results to render.
+ * @return string HTML
+ */
+ public function render( $term, $resultSets ) {
+ if ( !is_array( $resultSets ) ) {
+ $resultSets = [ $resultSets ];
+ }
+
+ $this->loadCustomCaptions();
+
+ $iwResults = [];
+ foreach ( $resultSets as $resultSet ) {
+ $result = $resultSet->next();
+ while ( $result ) {
+ if ( !$result->isBrokenTitle() ) {
+ $iwResults[$result->getTitle()->getInterwiki()][] = $result;
+ }
+ $result = $resultSet->next();
+ }
+ }
+
+ $out = '';
+ foreach ( $iwResults as $iwPrefix => $results ) {
+ $out .= $this->headerHtml( $iwPrefix, $term );
+ $out .= "<ul class='mw-search-iwresults'>";
+ // TODO: Assumes interwiki results are never paginated
+ $position = 0;
+ foreach ( $results as $result ) {
+ $out .= $this->resultWidget->render( $result, $term, $position++ );
+ }
+ $out .= "</ul>";
+ }
+
+ return
+ "<div id='mw-search-interwiki'>" .
+ "<div id='mw-search-interwiki-caption'>" .
+ $this->specialSearch->msg( 'search-interwiki-caption' )->text() .
+ '</div>' .
+ $out .
+ "</div>";
+ }
+
+ /**
+ * Generates an appropriate HTML header for the given interwiki prefix
+ *
+ * @param string $iwPrefix Interwiki prefix of wiki to show header for
+ * @param string $term User provided search term
+ * @return string HTML
+ */
+ protected function headerHtml( $iwPrefix, $term ) {
+ if ( isset( $this->customCaptions[$iwPrefix] ) ) {
+ $caption = $this->customCaptions[$iwPrefix];
+ } else {
+ $interwiki = $this->iwLookup->fetch( $iwPrefix );
+ $parsed = wfParseUrl( wfExpandUrl( $interwiki ? $interwiki->getURL() : '/' ) );
+ $caption = $this->specialSearch->msg( 'search-interwiki-default', $parsed['host'] )->text();
+ }
+ $searchLink = $this->linkRenderer->makeLink(
+ Title::newFromText( "$iwPrefix:Special:Search" ),
+ $this->specialSearch->msg( 'search-interwiki-more' )->text(),
+ [],
+ [
+ 'search' => $term,
+ 'fulltext' => 1,
+ ]
+ );
+ return
+ "<div class='mw-search-interwiki-project'>" .
+ "<span class='mw-search-interwiki-more'>{$searchLink}</span>" .
+ $caption .
+ "</div>";
+ }
+
+ protected function loadCustomCaptions() {
+ if ( $this->customCaptions !== null ) {
+ return;
+ }
+
+ $this->customCaptions = [];
+ $customLines = explode( "\n", $this->specialSearch->msg( 'search-interwiki-custom' )->text() );
+ foreach ( $customLines as $line ) {
+ $parts = explode( ':', $line, 2 );
+ if ( count( $parts ) === 2 ) {
+ $this->customCaptions[$parts[0]] = $parts[1];
+ }
+ }
+ }
+}
/**
* @param SearchResult $result The result to render
* @param string $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
- * @param int $position The result position, including offset
+ * @param int $position The zero indexed result position, including offset
* @return string HTML
*/
public function render( SearchResult $result, $terms, $position );