on the wiki farm with a different domain, MediaWiki will instead alter the redirect
URL to include a ?cpPosTime parameter that triggers the database synchronization when
the URL is followed by the client. The same-domain case uses a new cpPosTime cookie.
+* Added new hooks, 'ApiQueryBaseBeforeQuery', 'ApiQueryBaseAfterQuery', and
+ 'ApiQueryBaseProcessRow', to make it easier for extensions to add 'prop' and
+ 'show' parameters to existing API query modules.
=== External library changes in 1.28 ===
* ApiResult::setParsedLimit() was removed (deprecated since 1.25)
* ApiResult::setRawMode() was removed (deprecated since 1.25)
* ApiResult::size() was removed (deprecated since 1.25)
+* Added new hooks, 'ApiQueryBaseBeforeQuery', 'ApiQueryBaseAfterQuery', and
+ 'ApiQueryBaseProcessRow', to make it easier for extensions to add 'prop' and
+ 'show' parameters to existing API query modules. A query module can enable
+ these hooks by passing an array for $hookData to ApiQueryBase::select() and
+ by calling ApiQueryBase->processRow() before adding a row's data to the
+ result.
=== Languages updated in 1.28 ===
action=query submodule. Use this to extend core API modules.
&$module: Module object
+'ApiQueryBaseAfterQuery': Called for (some) API query modules after the
+database query has returned. An API query module wanting to use this hook
+should see the ApiQueryBase::select() and ApiQueryBase::processRow()
+documentation.
+$module: ApiQueryBase module in question
+$result: ResultWrapper|bool returned from the IDatabase::select()
+&$hookData: array that was passed to the 'ApiQueryBaseBeforeQuery' hook and
+ will be passed to the 'ApiQueryBaseProcessRow' hook, intended for inter-hook
+ communication.
+
+'ApiQueryBaseBeforeQuery': Called for (some) API query modules before a
+database query is made. WARNING: It would be very easy to misuse this hook and
+break the module! Any joins added *must* join on a unique key of the target
+table unless you really know what you're doing. An API query module wanting to
+use this hook should see the ApiQueryBase::select() and
+ApiQueryBase::processRow() documentation.
+$module: ApiQueryBase module in question
+&$tables: array of tables to be queried
+&$fields: array of columns to select
+&$conds: array of WHERE conditionals for query
+&$query_options: array of options for the database request
+&$join_conds: join conditions for the tables
+&$hookData: array that will be passed to the 'ApiQueryBaseAfterQuery' and
+ 'ApiQueryBaseProcessRow' hooks, intended for inter-hook communication.
+
+'ApiQueryBaseProcessRow': Called for (some) API query modules as each row of
+the database result is processed. Return false to stop processing the result
+set. An API query module wanting to use this hook should see the
+ApiQueryBase::select() and ApiQueryBase::processRow() documentation.
+$module: ApiQueryBase module in question
+$row: stdClass Database result row
+&$data: array to be included in the ApiResult.
+&$hookData: array that was be passed to the 'ApiQueryBaseBeforeQuery' and
+ 'ApiQueryBaseAfterQuery' hooks, intended for inter-hook communication.
+
'APIQueryGeneratorAfterExecute': After calling the executeGenerator() method of
an action=query submodule. Use this to extend core API modules.
&$module: Module object
$orderby[] = "rev_id $sort";
$this->addOption( 'ORDER BY', $orderby );
- $res = $this->select( __METHOD__ );
+ $hookData = [];
+ $res = $this->select( __METHOD__, [], $hookData );
$pageMap = []; // Maps rev_page to array index
$count = 0;
$nextIndex = 0;
];
ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
ApiQueryBase::addTitleInfo( $a, $title );
- $fit = $result->addValue( [ 'query', $this->getModuleName() ], $index, $a );
+ $fit = $this->processRow( $row, $a['revisions'][0], $hookData ) &&
+ $result->addValue( [ 'query', $this->getModuleName() ], $index, $a );
} else {
$index = $pageMap[$row->rev_page];
- $fit = $result->addValue(
- [ 'query', $this->getModuleName(), $index, 'revisions' ],
- null, $rev );
+ $fit = $this->processRow( $row, $rev, $hookData ) &&
+ $result->addValue( [ 'query', $this->getModuleName(), $index, 'revisions' ], null, $rev );
}
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', "$row->rev_timestamp|$row->rev_id" );
* 'options' => ...,
* 'join_conds' => ...
* ]
+ * @param array|null &$hookData If set, the ApiQueryBaseBeforeQuery and
+ * ApiQueryBaseAfterQuery hooks will be called, and the
+ * ApiQueryBaseProcessRow hook will be expected.
* @return ResultWrapper
*/
- protected function select( $method, $extraQuery = [] ) {
+ protected function select( $method, $extraQuery = [], array &$hookData = null ) {
$tables = array_merge(
$this->tables,
isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : []
);
+ if ( $hookData !== null ) {
+ Hooks::run( 'ApiQueryBaseBeforeQuery',
+ [ $this, &$tables, &$fields, &$where, &$options, &$join_conds, &$hookData ]
+ );
+ }
+
$res = $this->getDB()->select( $tables, $fields, $where, $method, $options, $join_conds );
+ if ( $hookData !== null ) {
+ Hooks::run( 'ApiQueryBaseAfterQuery', [ $this, $res, &$hookData ] );
+ }
+
return $res;
}
+ /**
+ * Call the ApiQueryBaseProcessRow hook
+ *
+ * Generally, a module that passed $hookData to self::select() will call
+ * this just before calling ApiResult::addValue(), and treat a false return
+ * here in the same way it treats a false return from addValue().
+ *
+ * @since 1.28
+ * @param object $row Database row
+ * @param array &$data Data to be added to the result
+ * @param array &$hookData Hook data from ApiQueryBase::select()
+ * @return bool Return false if row processing should end with continuation
+ */
+ protected function processRow( $row, array &$data, array &$hookData ) {
+ return Hooks::run( 'ApiQueryBaseProcessRow', [ $this, $row, &$data, &$hookData ] );
+ }
+
/**
* @param string $query
* @param string $protocol
$this->mGeneratorPageSet = $generatorPageSet;
}
+ /**
+ * Indicate whether the module is in generator mode
+ * @since 1.28
+ * @return bool
+ */
+ public function isInGeneratorMode() {
+ return $this->mGeneratorPageSet !== null;
+ }
+
/**
* Get the PageSet object to work on.
* If this module is generator, the pageSet object is different from other module's
$this->token = $params['token'];
$this->addOption( 'LIMIT', $params['limit'] + 1 );
+ $hookData = [];
$count = 0;
/* Perform the actual query. */
- $res = $this->select( __METHOD__ );
+ $res = $this->select( __METHOD__, [], $hookData );
$revids = [];
$titles = [];
$vals = $this->extractRowInfo( $row );
/* Add that row's data to our final output. */
- $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
+ $fit = $this->processRow( $row, $vals, $hookData ) &&
+ $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
break;
$count = 0;
$generated = [];
- $res = $this->select( __METHOD__ );
+ $hookData = [];
+ $res = $this->select( __METHOD__, [], $hookData );
foreach ( $res as $row ) {
if ( ++$count > $this->limit ) {
}
}
- $fit = $this->addPageSubItem( $row->rev_page, $rev, 'rev' );
+ $fit = $this->processRow( $row, $rev, $hookData ) &&
+ $this->addPageSubItem( $row->rev_page, $rev, 'rev' );
if ( !$fit ) {
if ( $enumRevMode ) {
$this->setContinueEnumParameter( 'continue',
$this->prepareQuery();
+ $hookData = [];
// Do the actual query.
- $res = $this->select( __METHOD__ );
+ $res = $this->select( __METHOD__, [], $hookData );
if ( $this->fld_sizediff ) {
$revIds = [];
}
$vals = $this->extractRowInfo( $row );
- $fit = $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
+ $fit = $this->processRow( $row, $vals, $hookData ) &&
+ $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
break;