gplx/mediawiki/extensions/SemanticMediaWiki/includes/query/SMW_QueryProcessor.php

490 lines
16 KiB
PHP

<?php
use ParamProcessor\Options;
use ParamProcessor\ParamDefinition;
use ParamProcessor\ProcessedParam;
use ParamProcessor\Processor;
use SMW\Parser\RecursiveTextProcessor;
use SMW\Query\Deferred;
use SMW\Query\Exception\ResultFormatNotFoundException;
use SMW\Query\PrintRequest;
use SMW\Query\Processor\DefaultParamDefinition;
use SMW\Query\Processor\ParamListProcessor;
use SMW\Query\QueryContext;
use SMW\Query\QueryResult;
use SMW\Query\ResultFormat;
use SMW\Query\ResultPrinter;
use SMW\Query\ResultPrinterDependency;
use SMW\Query\ResultPrinters\NullResultPrinter;
use SMW\Services\ServicesFactory as ApplicationFactory;
/**
* Static class for accessing functions to generate and execute semantic queries
* and to serialise their results.
* @ingroup SMWQuery
*/
class SMWQueryProcessor implements QueryContext {
/**
* @var RecursiveTextProcessor
*/
private static $recursiveTextProcessor;
/**
* @since 3.0
*
* @param RecursiveTextProcessor|null $recursiveTextProcessor
*/
public static function setRecursiveTextProcessor( ?RecursiveTextProcessor $recursiveTextProcessor = null ) {
self::$recursiveTextProcessor = $recursiveTextProcessor;
}
/**
* Takes an array of unprocessed parameters, processes them using
* Validator, and returns them.
*
* Both input and output arrays are
* param name (string) => param value (mixed)
*
* @since 1.6.2
* The return value changed in SMW 3.1 from Param[] to ProcessedParam[]
*
* @param array $params
* @param PrintRequest[] $printRequests
* @param bool $unknownInvalid
*
* @return ProcessedParam[]
*/
public static function getProcessedParams( array $params, array $printRequests = [], $unknownInvalid = true, $context = null, $showMode = false ) {
// #4261
// The `ProcessedParam` library creates inconsistent results pending the
// input types especially between conversion of string to integer, so to
// ensure that method outputs a consistent behaviour independent of the
// type, we recast offset/limit as string.
//
// We remove the string casts tests would start to fail!
if ( isset( $params['offset'] ) ) {
$params['offset'] = (string)$params['offset'];
}
if ( isset( $params['limit'] ) ) {
$params['limit'] = (string)$params['limit'];
}
$validator = self::getProcessorForParams( $params, $printRequests, $unknownInvalid, $context, $showMode );
$processingResult = $validator->processParameters();
$parameters = [];
foreach ( $processingResult->getParameters() as $parameter ) {
$parameters[$parameter->getName()] = $parameter;
}
// Set limit parameter to its original value.
// This is to allow for special handling of invalid values rather than just setting the parameter to its default.
// $smwgQMaxLimit and $smwgQMaxInlineLimit will be applied at a later point.
if ( isset( $params['limit'] ) && isset( $parameters['limit'] ) ) {
$parameters['limit'] = new ProcessedParam(
'limit',
(int)$params['limit'],
false,
'limit',
(int)$params['limit']
);
}
return $parameters;
}
/**
* Parse a query string given in SMW's query language to create
* an SMWQuery. Parameters are given as key-value-pairs in the
* given array. The parameter $context defines in what context the
* query is used, which affects ceretain general settings.
* An object of type SMWQuery is returned.
*
* The format string is used to specify the output format if already
* known. Otherwise it will be determined from the parameters when
* needed. This parameter is just for optimisation in a common case.
*
* @param string $queryString
* @param array $params These need to be the result of a list fed to getProcessedParams
* @param $context
* @param string $format
* @param array $extraPrintouts
*
* @return SMWQuery
*/
public static function createQuery( $queryString, array $params, $context = self::INLINE_QUERY, $format = '', array $extraPrintouts = [], $contextPage = null ) {
if ( $format === '' || $format === null ) {
$format = $params['format']->getValue();
}
$defaultSort = 'ASC';
if ( $format == 'count' ) {
$queryMode = SMWQuery::MODE_COUNT;
} elseif ( $format == 'debug' ) {
$queryMode = SMWQuery::MODE_DEBUG;
} else {
$printer = self::getResultPrinter( $format, $context );
$queryMode = $printer->getQueryMode( $context );
$defaultSort = $printer->getDefaultSort();
}
// set mode, limit, and offset:
$offset = 0;
$limit = $GLOBALS['smwgQDefaultLimit'];
if ( ( array_key_exists( 'offset', $params ) ) && ( is_int( $params['offset']->getValue() + 0 ) ) ) {
$offset = $params['offset']->getValue();
}
if ( ( array_key_exists( 'limit', $params ) ) && ( is_int( trim( $params['limit']->getValue() ) + 0 ) ) ) {
$limit = $params['limit']->getValue();
// limit < 0: always show further results link only
if ( ( trim( $params['limit']->getValue() ) + 0 ) < 0 ) {
$queryMode = SMWQuery::MODE_NONE;
}
}
// largest possible limit for "count", even inline
if ( $queryMode == SMWQuery::MODE_COUNT ) {
$offset = 0;
$limit = $GLOBALS['smwgQMaxLimit'];
}
$queryCreator = ApplicationFactory::getInstance()->singleton( 'QueryCreator' );
$params = [
'extraPrintouts' => $extraPrintouts,
'queryMode' => $queryMode,
'context' => $context,
'contextPage' => $contextPage,
'offset' => $offset,
'limit' => $limit,
'source' => $params['source']->getValue(),
'mainLabel' => $params['mainlabel']->getValue(),
'sort' => $params['sort']->getValue(),
'order' => $params['order']->getValue(),
'defaultSort' => $defaultSort
];
return $queryCreator->create( $queryString, $params );
}
/**
* Add the subject print request, unless mainlabel is set to "-".
*
* @since 1.7
*
* @param array &$printRequests
* @param array $rawParams
*/
public static function addThisPrintout( array &$printRequests, array $rawParams ) {
if ( $printRequests === null ) {
return;
}
// If THIS is already registered, bail-out!
foreach ( $printRequests as $printRequest ) {
if ( $printRequest->isMode( PrintRequest::PRINT_THIS ) ) {
return;
}
}
$hasMainlabel = array_key_exists( 'mainlabel', $rawParams );
if ( !$hasMainlabel || trim( $rawParams['mainlabel'] ) !== '-' ) {
$printRequest = new PrintRequest(
PrintRequest::PRINT_THIS,
$hasMainlabel ? $rawParams['mainlabel'] : ''
);
// Signal to any post-processing that THIS was added outside of
// the normal processing chain
$printRequest->isDisconnected( true );
array_unshift( $printRequests, $printRequest );
}
}
/**
* Preprocess a query as given by an array of parameters as is
* typically produced by the #ask parser function or by Special:Ask.
* The parsing results in a querystring, an array of additional
* parameters, and an array of additional \SMW\Query\PrintRequest objects,
* which are returned in an array with three components. If
* $showMode is true then the input will be processed as if for #show.
* This uses a slightly different way to get the query, and different
* default labels (empty) for additional print requests.
*
* @param array $rawParams
* @param bool $showMode
* @return array( string, array( string => string ), array( \SMW\Query\PrintRequest ) )
*/
public static function getComponentsFromFunctionParams( array $rawParams, $showMode ) {
/**
* @var ParamListProcessor $paramListProcessor
*/
$paramListProcessor = ApplicationFactory::getInstance()->singleton( 'ParamListProcessor' );
return $paramListProcessor->format(
$paramListProcessor->preprocess( $rawParams, $showMode ),
ParamListProcessor::FORMAT_LEGACY
);
}
/**
* Process and answer a query as given by an array of parameters as is
* typically produced by the #ask parser function. The parameter
* $context defines in what context the query is used, which affects
* certain general settings.
*
* The main task of this function is to preprocess the raw parameters
* to obtain actual parameters, printout requests, and the query string
* for further processing.
*
* @since 1.8
* @param array $rawParams user-provided list of unparsed parameters
* @param int $outputMode SMW_OUTPUT_WIKI, SMW_OUTPUT_HTML, ...
* @param int $context INLINE_QUERY, SPECIAL_PAGE, CONCEPT_DESC
* @param bool $showMode process like #show parser function?
* @return array( SMWQuery, ProcessedParam[] )
*/
public static function getQueryAndParamsFromFunctionParams( array $rawParams, $outputMode, $context, $showMode, $contextPage = null ) {
[ $queryString, $params, $printouts ] = self::getComponentsFromFunctionParams( $rawParams, $showMode );
if ( !$showMode ) {
self::addThisPrintout( $printouts, $params );
}
$params = self::getProcessedParams( $params, $printouts, true, $context, $showMode );
$query = self::createQuery( $queryString, $params, $context, '', $printouts, $contextPage );
// For convenience keep parameters and options to be available for immediate
// processing
if ( $context === self::DEFERRED_QUERY ) {
$query->setOption( Deferred::QUERY_PARAMETERS, implode( '|', $rawParams ) );
$query->setOption( Deferred::SHOW_MODE, $showMode );
$query->setOption( Deferred::CONTROL_ELEMENT, isset( $params['@control'] ) ? $params['@control']->getValue() : '' );
}
return [ $query, $params ];
}
/**
* Create a fully formatted result string from a query and its
* parameters. The method takes care of processing various types of
* query result. Most cases are handled by printers, but counting and
* debugging uses special code.
*
* @param SMWQuery $query
* @param array $params These need to be the result of a list fed to getProcessedParams
* @param int $outputMode
* @param int $context
* @since before 1.7, but public only since 1.8
*
* @return string
*/
public static function getResultFromQuery( SMWQuery $query, array $params, $outputMode, $context ) {
$printer = self::getResultPrinter(
$params['format']->getValue(),
$context
);
if ( $printer instanceof ResultPrinterDependency && $printer->hasMissingDependency() ) {
return $printer->getDependencyError();
}
if ( $printer->isDeferrable() && $context === self::DEFERRED_QUERY && $query->getLimit() > 0 ) {
// Halt processing that is not `DEFERRED_DATA` as it is expected the
// process is picked-up by the `deferred.js` loader that will
// initiate an API request to finalize the query after MW has build
// the page.
if ( $printer->isDeferrable() !== $printer::DEFERRED_DATA ) {
return Deferred::buildHTML( $query );
}
// `DEFERRED_DATA` is interpret as "execute the query with limit=0" (i.e.
// no query execution) but allow the printer to setup the HTML so that
// the data can be loaded after MW has finished the page build including
// the pre-rendered query HTML representation. This mode deferrers the
// actual query execution and data load to after the page build.
//
// Each printer that uses this mode has to handle the required parameters
// and data load accordingly.
$query->querymode = SMWQuery::MODE_INSTANCES;
$query->setOption( 'deferred.limit', $query->getLimit() );
$query->setLimit( 0 );
}
$querySourceFactory = ApplicationFactory::getInstance()->getQuerySourceFactory();
$source = $params['source']->getValue();
if ( $source === '' && $context === self::CURTAILMENT_MODE ) {
$querySource = $querySourceFactory->newSingleEntityQueryLookup();
} else {
$querySource = $querySourceFactory->get( $source );
}
$res = $querySource->getQueryResult( $query );
$start = microtime( true );
if ( $res instanceof QueryResult && $query->getOption( 'calc.result_hash' ) ) {
$query->setOption( 'result_hash', $res->getHash( QueryResult::QUICK_HASH ) );
}
if ( ( $query->querymode == SMWQuery::MODE_INSTANCES ) ||
( $query->querymode == SMWQuery::MODE_NONE ) ) {
$result = $printer->getResult( $res, $params, $outputMode );
$query->setOption( SMWQuery::PROC_PRINT_TIME, microtime( true ) - $start );
return $result;
} else { // result for counting or debugging is just a string or number
if ( $res instanceof QueryResult ) {
$res = $res->getCountValue();
}
if ( is_numeric( $res ) ) {
$res = strval( $res );
}
if ( is_string( $res ) ) {
$result = str_replace( '_', ' ', $params['intro']->getValue() )
. $res
. str_replace( '_', ' ', $params['outro']->getValue() )
. smwfEncodeMessages( $query->getErrors() );
} else {
// When no valid result was obtained, $res will be a QueryResult.
$result = smwfEncodeMessages( $query->getErrors() );
}
$query->setOption( SMWQuery::PROC_PRINT_TIME, microtime( true ) - $start );
return $result;
}
}
/**
* Find suitable SMWResultPrinter for the given format. The context in
* which the query is to be used determines some basic settings of the
* returned printer object. Possible contexts are
* SMWQueryProcessor::SPECIAL_PAGE, SMWQueryProcessor::INLINE_QUERY,
* SMWQueryProcessor::CONCEPT_DESC.
*
* @param string $format
* @param $context
*
* @return ResultPrinter
* @throws ResultFormatNotFoundException
*/
public static function getResultPrinter( $format, $context = self::SPECIAL_PAGE ) {
global $smwgResultFormats;
ResultFormat::resolveFormatAliases( $format );
if ( !array_key_exists( $format, $smwgResultFormats ) ) {
throw new ResultFormatNotFoundException( "There is no result format for '$format'." );
}
$formatClass = $smwgResultFormats[$format];
/**
* @var ResultPrinter $printer
*/
$printer = new $formatClass( $format, ( $context != self::SPECIAL_PAGE ) );
if ( self::$recursiveTextProcessor === null ) {
self::$recursiveTextProcessor = new RecursiveTextProcessor();
}
$printer->setRecursiveTextProcessor(
self::$recursiveTextProcessor
);
return $printer;
}
/**
* Produces a list of default allowed parameters for a result printer. Most
* query printers should override this function.
*
* @since 1.6.2, return element type changed in 1.8
*
* @param int|null $context
* @param ResultPrinter|null $resultPrinter
*
* @return ParamDefinition[]
*/
public static function getParameters( $context = null, $resultPrinter = null ) {
return DefaultParamDefinition::getParamDefinitions( $context, $resultPrinter );
}
/**
* Returns the definitions of all parameters supported by the specified format.
*
* @since 1.8
*
* @param string $format
*
* @return ParamDefinition[]
*/
public static function getFormatParameters( $format ) {
ResultFormat::resolveFormatAliases( $format );
if ( !array_key_exists( $format, $GLOBALS['smwgResultFormats'] ) ) {
return [];
}
$resultPrinter = self::getResultPrinter( $format );
if ( $resultPrinter instanceof NullResultPrinter ) {
return [];
}
return ParamDefinition::getCleanDefinitions(
$resultPrinter->getParamDefinitions( self::getParameters( null, $resultPrinter ) )
);
}
/**
* Takes an array of unprocessed parameters,
* and sets them on a new Validator object,
* which is returned and ready to process the parameters.
*
* @param array $params
* @param PrintRequest[] $printRequests
* @param bool $unknownInvalid
*
* @return Processor
*/
private static function getProcessorForParams( array $params, array $printRequests = [], $unknownInvalid = true, $context = null, $showMode = false ) {
$paramDefinitions = self::getParameters( $context );
/**
* @var ResultFormat $formatParameter
*/
$formatParameter = $paramDefinitions['format'];
$formatParameter->setPrintRequests( $printRequests );
$formatParameter->setShowMode( $showMode );
$processorOptions = new Options();
$processorOptions->setUnknownInvalid( $unknownInvalid );
$processor = Processor::newFromOptions( $processorOptions );
$processor->setParameters( $params, $paramDefinitions );
return $processor;
}
}