Rip general cross-wiki API request helpers out of ApiEchoNotifications

They'll come in useful for other APIs as well

Change-Id: I59e508d8c2d33c3868c13e28e4ac40441bc07d4e
This commit is contained in:
Matthias Mullie 2016-05-26 13:41:25 -07:00
parent 3089594970
commit 6b0081fd78
3 changed files with 190 additions and 93 deletions

View file

@ -4,6 +4,7 @@
global $wgAutoloadClasses;
$wgAutoloadClasses += [
'ApiCrossWikiBase' => __DIR__ . '/includes/api/ApiCrossWikiBase.php',
'ApiEchoMarkRead' => __DIR__ . '/includes/api/ApiEchoMarkRead.php',
'ApiEchoMarkReadTest' => __DIR__ . '/tests/phpunit/api/ApiEchoMarkReadTest.php',
'ApiEchoMarkSeen' => __DIR__ . '/includes/api/ApiEchoMarkSeen.php',

View file

@ -0,0 +1,170 @@
<?php
use MediaWiki\Logger\LoggerFactory;
abstract class ApiCrossWikiBase extends ApiQueryBase {
/**
* @var EchoForeignNotifications
*/
protected $foreignNotifications;
/**
* @param ApiQuery $queryModule
* @param string $moduleName
* @param string $paramPrefix
*/
public function __construct( ApiQuery $queryModule, $moduleName, $paramPrefix = '' ) {
parent::__construct( $queryModule, $moduleName, $paramPrefix );
$this->foreignNotifications = new EchoForeignNotifications( $this->getUser() );
}
/**
* This will turn the current API call (with all of it's params) and execute
* it on all foreign wikis, returning an array of results per wiki.
*
* @return array
* @throws Exception
*/
protected function getFromForeign() {
$reqs = $this->getForeignRequestParams( $this->getForeignWikis() );
return $this->foreignRequests( $reqs );
}
/**
* @return bool
*/
protected function allowCrossWikiNotifications() {
global $wgEchoCrossWikiNotifications;
return $wgEchoCrossWikiNotifications;
}
/**
* @return array Wiki names
*/
protected function getForeignWikis() {
if ( !$this->allowCrossWikiNotifications() ) {
return array();
}
$params = $this->extractRequestParams();
return array_diff( $params['wikis'], array( wfWikiId() ) );
}
/**
* @param User $user
* @return string
*/
protected function getCentralAuthToken( User $user ) {
$context = new RequestContext;
$context->setRequest( new FauxRequest( array( 'action' => 'centralauthtoken' ) ) );
$context->setUser( $user );
$api = new ApiMain( $context );
$api->execute();
return $api->getResult()->getResultData( array( 'centralauthtoken', 'centralauthtoken' ) );
}
/**
* @param array $wikis Wiki names
* @return array
*/
protected function getForeignRequestParams( array $wikis ) {
$apis = $this->foreignNotifications->getApiEndpoints( $wikis );
if ( !$apis ) {
return array();
}
$reqs = array();
foreach ( $apis as $wiki => $api ) {
$reqs[$wiki] = array(
'method' => 'GET',
'url' => $api['url'],
'query' => $this->getForeignQueryParams( $wiki ),
);
}
return $reqs;
}
/**
* @param string $wiki Wiki name
* @return array
*/
protected function getForeignQueryParams( $wiki ) {
// use original request params, to forward them to individual wikis
$params = $this->getRequest()->getValues();
return array(
'centralauthtoken' => $this->getCentralAuthToken( $this->getUser() ),
// once all the results are gathered & merged, they'll be output in the
// user requested format
// but this is going to be an internal request & we don't want those
// results in the format the user requested but in a fixed format that
// we can interpret here
'format' => 'json',
// Only request data from that specific wiki, or they'd all spawn
// cross-wiki api requests...
$this->getModulePrefix() . 'wikis' => $wiki,
) + $params;
}
/**
* @param array $reqs API request params
* @return array
* @throws Exception
*/
protected function foreignRequests( array $reqs ) {
$http = new MultiHttpClient( array() );
$responses = $http->runMulti( $reqs );
$results = array();
foreach ( $responses as $wiki => $response ) {
$statusCode = $response['response']['code'];
if ( $statusCode >= 200 && $statusCode <= 299 ) {
$parsed = json_decode( $response['response']['body'], true );
if ( $parsed ) {
$results[$wiki] = $parsed;
}
}
if ( !isset( $results[$wiki] ) ) {
LoggerFactory::getInstance( 'Echo' )->warning(
'Failed to fetch notifications from {wiki}. Response: {code} {response}',
array(
'wiki' => $wiki,
'code' => $response['response']['code'],
'response' => $response['response']['body'],
)
);
}
}
return $results;
}
/**
* @return array
*/
public function getAllowedParams() {
global $wgConf;
$params = array();
if ( $this->allowCrossWikiNotifications() ) {
$params += array(
// fetch notifications from multiple wikis
'wikis' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => wfWikiId(),
ApiBase::PARAM_TYPE => array_unique( array_merge( $wgConf->wikis, array( wfWikiId() ) ) ),
),
);
}
return $params;
}
}

View file

@ -1,13 +1,6 @@
<?php
use MediaWiki\Logger\LoggerFactory;
class ApiEchoNotifications extends ApiQueryBase {
/**
* @var EchoForeignNotifications
*/
protected $foreignNotifications;
class ApiEchoNotifications extends ApiCrossWikiBase {
/**
* @var bool
*/
@ -18,7 +11,6 @@ class ApiEchoNotifications extends ApiQueryBase {
}
public function execute() {
global $wgEchoCrossWikiNotifications;
// To avoid API warning, register the parameter used to bust browser cache
$this->getMain()->getVal( '_' );
@ -42,24 +34,22 @@ class ApiEchoNotifications extends ApiQueryBase {
);
}
if ( $wgEchoCrossWikiNotifications ) {
$this->foreignNotifications = new EchoForeignNotifications( $this->getUser() );
if ( $this->allowCrossWikiNotifications() ) {
$this->crossWikiSummary = $params['crosswikisummary'];
$wikis = $params['wikis'];
} else {
$wikis = array( wfWikiId() );
}
$results = array();
if ( in_array( wfWikiId(), $wikis ) ) {
if ( !$this->allowCrossWikiNotifications() || in_array( wfWikiId(), $params['wikis'] ) ) {
$results[wfWikiId()] = $this->getLocalNotifications( $params );
}
$foreignWikis = array_diff( $wikis, array( wfWikiId() ) );
if ( !empty( $foreignWikis ) ) {
// get original request params, to forward them to individual wikis
$requestParams = $this->getRequest()->getValues();
$results += $this->getForeignNotifications( $foreignWikis, $requestParams );
if ( $this->getForeignWikis() ) {
$foreignResults = $this->getFromForeign();
foreach ( $foreignResults as $wiki => $result ) {
if ( isset( $result['query']['notifications'] ) ) {
$results[$wiki] = $result['query']['notifications'];
}
}
}
// after getting local & foreign results, merge them all together
@ -333,74 +323,16 @@ class ApiEchoNotifications extends ApiQueryBase {
}
/**
* @param User $user
* @return string
*/
protected function getCentralAuthToken( User $user ) {
$context = new RequestContext;
$context->setRequest( new FauxRequest( array( 'action' => 'centralauthtoken' ) ) );
$context->setUser( $user );
$api = new ApiMain( $context );
$api->execute();
return $api->getResult()->getResultData( array( 'centralauthtoken', 'centralauthtoken' ) );
}
/**
* @param array $wikis
* @param array $params
* @param string $wiki Wiki name
* @return array
*/
protected function getForeignNotifications( array $wikis, array $params ) {
$apis = $this->foreignNotifications->getApiEndpoints( $wikis );
if ( !$apis ) {
return array();
}
protected function getForeignQueryParams( $wiki ) {
$params = parent::getForeignQueryParams( $wiki );
// Don't request cross-wiki notifications
// don't request cross-wiki notification summaries
unset( $params['notcrosswikisummary'] );
$params['format'] = 'json';
$reqs = array();
foreach ( $apis as $wiki => $api ) {
$reqs[$wiki] = array(
'method' => 'GET',
'url' => $api['url'],
// Only request data from that specific wiki, or they'd all spawn
// cross-wiki api requests...
'query' => array_merge( $params, array(
'notwikis' => $wiki,
'centralauthtoken' => $this->getCentralAuthToken( $this->getUser() )
) ),
);
}
$http = new MultiHttpClient( array() );
$responses = $http->runMulti( $reqs );
$results = array();
foreach ( $responses as $wiki => $response ) {
$statusCode = $response['response']['code'];
if ( $statusCode >= 200 && $statusCode <= 299 ) {
$parsed = json_decode( $response['response']['body'], true );
if ( $parsed && isset( $parsed['query']['notifications'] ) ) {
$results[$wiki] = $parsed['query']['notifications'];
}
}
if ( !isset( $results[$wiki] ) ) {
LoggerFactory::getInstance( 'Echo' )->warning(
"Failed to fetch notifications from {wiki}. Response: {code} {response}",
array(
'wiki' => $wiki,
'code' => $response['response']['code'],
'response' => $response['response']['body'],
)
);
}
}
return $results;
return $params;
}
/**
@ -495,10 +427,10 @@ class ApiEchoNotifications extends ApiQueryBase {
}
public function getAllowedParams() {
global $wgConf, $wgEchoCrossWikiNotifications;
$sections = EchoAttributeManager::$sections;
$params = array(
$params = parent::getAllowedParams();
$params += array(
'filter' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'read|!read',
@ -557,14 +489,8 @@ class ApiEchoNotifications extends ApiQueryBase {
);
}
if ( $wgEchoCrossWikiNotifications ) {
if ( $this->allowCrossWikiNotifications() ) {
$params += array(
// fetch notifications from multiple wikis
'wikis' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => wfWikiId(),
ApiBase::PARAM_TYPE => array_unique( array_merge( $wgConf->wikis, array( wfWikiId() ) ) ),
),
// create "x notifications from y wikis" notification bundle &
// include unread counts from other wikis in prop=count results
'crosswikisummary' => array(