Robert Vogel has uploaded a new change for review. https://gerrit.wikimedia.org/r/219143
Change subject: [WIP] Setting up new extension ...................................................................... [WIP] Setting up new extension This is the first change. The extension does not work yet Change-Id: I204ad7bce1d7ee8c03d2cdfda87f0d9fec69225a --- A .gitignore A BSExtendedSearch.php A composer.json A includes/api/BSApiQueryExtendedSearch.php 4 files changed, 244 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/BSExtendedSearch refs/changes/43/219143/1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/BSExtendedSearch.php b/BSExtendedSearch.php new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/BSExtendedSearch.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0b6af31 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "solarium/solarium": "dev-master" + } +} diff --git a/includes/api/BSApiQueryExtendedSearch.php b/includes/api/BSApiQueryExtendedSearch.php new file mode 100644 index 0000000..0a94d33 --- /dev/null +++ b/includes/api/BSApiQueryExtendedSearch.php @@ -0,0 +1,238 @@ +<?php + +/** + * TODO: Implement as subclass of BSApiExtJSStoreBase and map ExtJS Store + * filters to Solr filterQuery-feature + */ +class BSApiQueryExtendedSearch extends ApiBase { + protected $root = 'results'; + protected $totalProperty = 'total'; + + /** + * @var Solarium\QueryType\Select\Query\Query + */ + protected $query = null; + + /** + * @var \Solarium\QueryType\Select\Query\Component\EdisMax + */ + protected $edismax = null; + + /** + * @var Solarium\QueryType\Select\Query\Component\Highlighting\Highlighting + */ + protected $hl = null; + + public function execute() { + global $bsgExtendedSearchDefaultEDisMaxQueryFields; + $aSearchParams = $this->getParameter( 'searchParams' ); + if( !isset( $aSearchParams['qf'] ) ) { + $aSearchParams['qf'] = $bsgExtendedSearchDefaultEDisMaxQueryFields; //Array + } + + wfRunHooks( 'BSApiQueryExtendedSearchBeforeExecute', array( $this, &$aSearchParams ) ); + + $client = $this->makeClient(); + $this->query = $this->makeQuery( $client ); + $this->edismax = $this->query->getEDisMax(); + $this->edismax->setQueryFields( $this->makeQueryFields( $aSearchParams ) ); + $this->makeFilterQueries( $this->query, $aSearchParams ); + + $this->query + ->setQuery( $this->getParameter('query') ) // '*:*' is not supported by eDisMax + ->setStart( $this->getParameter('start') ) + ->setRows( $this->getParameter('limit') ); + + $this->hl = $this->query->getHighlighting(); + //TODO: use setOptions to allow more extensability + $this->hl->setFields('title, text'); + $this->hl->setSnippets(3); + $this->hl->setSimplePrefix('<span class="bs-extendedsearch-highlight">'); + $this->hl->setSimplePostfix('</span>'); + + $resultset = $client->execute($this->query); + + $aData = array(); + foreach( $resultset as $document ) { + $aData[] = $this->makeResultRow( $document, $resultset ); + } + + $result = $this->getResult(); + $result->setIndexedTagName( $aData, $this->root ); + $result->addValue( null, $this->root, $aData ); + $result->addValue( null, 'facetts', array() ); //TODO: Implement for facetted quieries! + $result->addValue( null, $this->totalProperty, $resultset->getNumFound() ); + } + + public function getAllowedParams() { + return array( + 'searchParams' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => false, + ApiBase::PARAM_DFLT => '{}' + ), + 'query' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true + ), + 'start' => array( + ApiBase::PARAM_TYPE => 'integer', + ApiBase::PARAM_REQUIRED => false, + ApiBase::PARAM_DFLT => 0 + ), + 'limit' => array( + ApiBase::PARAM_TYPE => 'integer', + ApiBase::PARAM_REQUIRED => false, + ApiBase::PARAM_DFLT => 5 + ), + 'format' => array( + ApiBase::PARAM_DFLT => 'json', + ApiBase::PARAM_REQUIRED => true, + ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' ) + ) + ); + } + + public function getParamDescription() { + return array( + 'searchParams' => 'JSON string with parameters for SOLR. E.g filter querys "fq" in form "fieldname":value(s)', + 'query' => 'The query term for the search engine', + 'start' => 'The offset to start the result list from', + 'limit' => 'The number of items to return', + 'format' => 'The format of the output (only JSON or formatted JSON)' + ); + } + + protected function getParameterFromSettings($paramName, $paramSettings, $parseLimit) { + $value = parent::getParameterFromSettings($paramName, $paramSettings, $parseLimit); + //Unfortunately there is no way to register custom types for parameters + if( in_array( $paramName, array( 'searchParams' ) ) ) { + $value = FormatJson::decode( $value, true ); + if( empty($value) ) { + return array(); + } + } + return $value; + } + + /** + * + * @param \Solarium\QueryType\Select\Result\Document $document + * @param \Solarium\Core\Query\Result\ResultInterface $resultset + * @return array + */ + public function makeResultRow( $document, $resultset ) { + $oFormatter = $this->makeFormatter( $document->overall_type ); + return $oFormatter->format( $document, $resultset, $this->getFinalParams() ); + } + + /** + * + * @param Solarium\Client $client + * @return Solarium\QueryType\Select\Query\Query + */ + protected function makeQuery( $client ) { + return $client->createQuery( Solarium\Client::QUERY_SELECT ); + } + + protected function makeClient() { + $sUrl = BsConfig::get( 'MW::ExtendedSearch::SolrServiceUrl' ); + $aUrl = wfParseUrl( $sUrl.'/bluespice' ); + + return new Solarium\Client( array( + 'endpoint' => array( + 'localhost' => $aUrl + ) + ) ); + } + + /** + * + * @param Solarium\QueryType\Select\Query\Query $query + */ + protected function makeFilterQueries( $query, $aSearchParams ) { + //Implicit filter: wiki ID for shared indices! + $query->createFilterQuery('wiki')->setQuery( 'wiki:'.BsConfig::get( 'MW::ExtendedSearch::CustomerID' ) ); + + //Implicit filter: readable namespaces! + $aReadableNamespaces = $this->getReadableNamespaces(); //TODO: Use BSF method when available! + $aFilterQueries = array(); + if( isset($aSearchParams['fq']) ) { + $aFilterQueries = $aSearchParams['fq']; + } + if( isset( $aFilterQueries['namespace'] ) ) { + $aFilterQueries['namespace'] = array_intersect( $aReadableNamespaces, $aFilterQueries['namespace'] ); + } + else { + $aFilterQueries['namespace'] = $aReadableNamespaces; + } + + foreach( $aFilterQueries as $sFieldname => $mValue ) { + if( is_array( $mValue ) ) { + $sFilterQuery = $sFieldname.':('.implode( ' ', $mValue ).')'; + $query->createFilterQuery( $sFieldname )->setQuery( $sFilterQuery ); + } + else { + $sFilterQuery = $sFieldname.':'.$mValue; + } + } + } + + protected function getReadableNamespaces() { + $aNamespaces = $this->getLanguage()->getNamespaceIds(); + $aReadableNS = array(); + foreach( $aNamespaces as $iNsId ) { + $oTitle = Title::makeTitle( $iNsId, 'X' ); + if( $oTitle->userCan( 'read' ) ) { + $aReadableNS[] = $iNsId; + } + } + return $aReadableNS; + } + + /** + * + * @global array $bsgExtendedSearchAPIFormatters + * @param string $sOverallType + * @return BSExtendedSearchApiFormatterBase + * @throws InvalidArgumentException + */ + public function makeFormatter( $sOverallType ) { + global $bsgExtendedSearchAPIFormatters; + if( !isset( $bsgExtendedSearchAPIFormatters[$sOverallType] ) ) { + //TODO: Or shall we fall back to 'BSExtendedSearchApiFormatterBase'? + throw new InvalidArgumentException( 'No formatter for overall_type "'.$sOverallType.'" found!' ); + } + + return new $bsgExtendedSearchAPIFormatters[$sOverallType](); + } + + /** + * + * @param array $aSearchParams + * @return string + */ + public function makeQueryFields( $aSearchParams ) { + $sQueryFields = ''; + if( !isset( $aSearchParams['qf'] ) ) { + return ''; + } + if( is_string( $aSearchParams['qf'] ) ) { + return $aSearchParams['qf']; + } + + $aFieldBoosts = array(); + foreach( $aSearchParams['qf'] as $sFieldName => $iBoost ) { + $sFieldBoost = $sFieldName; + if(is_numeric($iBoost)) { + $sFieldBoost .= '^'.$iBoost; + } + + $aFieldBoosts[] = $sFieldBoost; + } + $sQueryFields = implode( ' ', $aFieldBoosts ); + + return $sQueryFields; + } + +} \ No newline at end of file -- To view, visit https://gerrit.wikimedia.org/r/219143 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I204ad7bce1d7ee8c03d2cdfda87f0d9fec69225a Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/BSExtendedSearch Gerrit-Branch: master Gerrit-Owner: Robert Vogel <vo...@hallowelt.biz> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits