Yurik has uploaded a new change for review. https://gerrit.wikimedia.org/r/203003
Change subject: Implements prop=listmembership ...................................................................... Implements prop=listmembership Code-complete alpha code, might need more debugging, my vagrat broke Bug: T95516 Change-Id: I90b6df0c7a9d69460921e9410284c312b3d09d7b --- M Gather.php A includes/api/ApiQueryListMembership.php M includes/api/ApiQueryListPages.php A includes/api/ApiShared.php 4 files changed, 260 insertions(+), 62 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Gather refs/changes/03/203003/1 diff --git a/Gather.php b/Gather.php index b506820..37b7cbc 100644 --- a/Gather.php +++ b/Gather.php @@ -63,8 +63,10 @@ 'Gather\SpecialGatherLists' => 'specials/SpecialGatherLists', 'Gather\SpecialGatherEditFeed' => 'specials/SpecialGatherEditFeed', + 'Gather\api\ApiShared' => 'api/ApiShared', 'Gather\api\ApiEditList' => 'api/ApiEditList', 'Gather\api\ApiQueryLists' => 'api/ApiQueryLists', + 'Gather\api\ApiQueryListMembership' => 'api/ApiQueryListMembership', 'Gather\api\ApiQueryListPages' => 'api/ApiQueryListPages', ); @@ -95,6 +97,7 @@ // Api $wgAPIModules['editlist'] = 'Gather\api\ApiEditList'; $wgAPIListModules['lists'] = 'Gather\api\ApiQueryLists'; +$wgAPIPropModules['listmembership'] = 'Gather\api\ApiQueryListMembership'; $wgAPIListModules['listpages'] = 'Gather\api\ApiQueryListPages'; // Configuration diff --git a/includes/api/ApiQueryListMembership.php b/includes/api/ApiQueryListMembership.php new file mode 100644 index 0000000..780278f --- /dev/null +++ b/includes/api/ApiQueryListMembership.php @@ -0,0 +1,136 @@ +<?php +/** + * + * Copyright © 2015 Yuri Astrakhan "<Firstname><Lastname>@gmail.com", + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace Gather\api; + +use ApiQueryBase; +use ApiQuery; +use LinkBatch; +use ApiBase; + +/** + * A query prop module to show if pages belong to a specific list + * + * @ingroup API + */ +class ApiQueryListMembership extends ApiQueryBase { + + public function __construct( ApiQuery $query, $moduleName ) { + parent::__construct( $query, $moduleName, 'lsm' ); + } + + public function execute() { + $titles = $this->getPageSet()->getGoodAndMissingTitles(); + $titleLookup = $this->getPageSet()->getGoodAndMissingTitlesByNamespace(); + + if ( !count( $titles ) ) { + # Nothing to do + return; + } + + $params = $this->extractRequestParams(); + + $db = $this->getDB(); + + $userId = ApiShared::checkListAccess( $this->getDB(), $this, $params ); + + if ( $userId !== 0 ) { + // This is a watchlist + $pfx = 'wl'; + $this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' ); + $db = $this->getDB(); // TODO: selectNamedDB should return this value + $this->addTables( 'watchlist' ); + $this->addWhereFld( 'wl_user', $userId ); + } else { + // This is a gather list + $pfx = 'gli'; + $this->addTables( 'gather_list_item' ); + $this->addWhereFld( 'gli_gl_id', $params['id'] ); + } + + $this->addFields( array( 'ns' => "{$pfx}_namespace", 'title' => "{$pfx}_title" ) ); + + $lb = new LinkBatch( $titles ); + $this->addWhere( $lb->constructSet( $pfx, $db ) ); + + if ( $params['continue'] !== null ) { + $cont = explode( '|', $params['continue'] ); + $this->dieContinueUsageIf( count( $cont ) != 2 ); + $contNs = intval( $cont[0] ); + $this->dieContinueUsageIf( strval( $contNs ) !== $cont[0] ); + $contTitle = $db->addQuotes( $cont[1] ); + $this->addWhere( "{$pfx}_namespace > $contNs OR " . "({$pfx}_namespace = $contNs AND " . + "{$pfx}_title >= $contTitle)" ); + } + + // Don't ORDER BY namespace if it's constant in the WHERE clause + if ( count( $titleLookup ) === 1 ) { + $this->addOption( 'ORDER BY', "{$pfx}_title" ); + } else { + $this->addOption( 'ORDER BY', array( "{$pfx}_namespace", "{$pfx}_title" ) ); + } + + // NOTE: We never set listmembership=false because we don't really know which ones are not in the database. + // If we ran out of memory halfway and need to continue, next time we will skip those already done, + // so even though DB contains rows, we skipped them and gotten the next batch. + // In other words, the pages that at the end of this module do not have "listmembership"=true might + // still be true, but they were reported in the previous API call. + + $result = $this->getResult(); + foreach ( $this->select( __METHOD__ ) as $row ) { + $ns = intval( $row->ns ); + if ( !isset( $titleLookup[$ns][$row->title] ) ) { + wfDebug( __METHOD__ . " Unexpected DB row {$row->ns}:{$row->title}\n" ); + continue; + } + $fit = $result->addValue( array( 'query', 'pages', $titleLookup[$ns][$row->title] ), 'listmembership', true ); + if ( !$fit ) { + $this->setContinueEnumParameter( 'continue', $row->ns . '|' . $row->title ); + break; + } + } + } + + public function getCacheMode( $params ) { + return 'anon-public-user-private'; + } + + public function getAllowedParams() { + return array_merge( ApiShared::getListAccessParams(), array( + 'continue' => array( + ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', + ), + ) ); + } + + protected function getExamplesMessages() { + return array( + 'action=query&prop=listmembership&titles=Page&lsmid=0' + => 'apihelp-query+listmembership', + ); + } + + public function getHelpUrls() { + return '//www.mediawiki.org/wiki/Extension:Gather'; + } +} diff --git a/includes/api/ApiQueryListPages.php b/includes/api/ApiQueryListPages.php index 767c031..1c91f1a 100644 --- a/includes/api/ApiQueryListPages.php +++ b/includes/api/ApiQueryListPages.php @@ -61,58 +61,15 @@ private function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); - $p = $this->getModulePrefix(); - - $useOwner = $params['owner'] !== null; - if ( $useOwner !== ( $params['token'] !== null ) ) { - $this->dieUsage( "Both {$p}owner and {$p}token must be given or missing", - 'invalidparammix' ); - } - $isGenerator = $resultPageSet !== null; - if ( !$params['id'] ) { - // If id is not given (or equals to 0), permissions the same as watchlistraw access - $user = $this->getWatchlistUser( $params ); - $titles = $this->queryLegacyWatchlist( $params, $isGenerator, $user->getId() ); + $userId = ApiShared::checkListAccess( $this->getDB(), $this, $params ); + + if ( $userId !== 0 ) { + // This is a watchlist + $titles = $this->queryLegacyWatchlist( $params, $isGenerator, $userId ); } else { - // Id was given, this could be public or private list, legacy watchlist or regular - // Allow access to any public list/watchlist, and to private with proper owner/self - $db = $this->getDB(); - $listRow = ApiEditList::normalizeRow( $db->selectRow( 'gather_list', - array( 'gl_label', 'gl_user', 'gl_perm' ), - array( 'gl_id' => $params['id'] ), __METHOD__ ) ); - if ( $listRow === false ) { - $this->dieUsage( "List does not exist", 'badid' ); - } - if ( $useOwner ) { - // Caller supplied token: treat them as trusted, someone who could see even private - // At the same time, owner param must match list's owner - // TODO: if we allow non-matching owner, we could treat it as public-only, - // but that might be unexpected behavior - $user = $this->getWatchlistUser( $params ); - if ( $listRow->gl_user !== $user->getId() ) { - $this->dieUsage( 'The owner supplied does not match the list\'s owner', - 'permissiondenied' ); - } - $showPrivate = true; - } else { - $user = $this->getUser(); - $showPrivate = $user->isLoggedIn() && $listRow->gl_user === $user->getId() - && $user->isAllowed( 'viewmywatchlist' ); - } - - // Check if this is a public list (if required) - if ( !$showPrivate && $listRow->gl_perm !== ApiEditList::PERM_PUBLIC ) { - $this->dieUsage( "You have no rights to see this list", 'badid' ); - } - - if ( $listRow->gl_label === '' ) { - // This is actually a watchlist, and it is either public or belongs to current user - $titles = $this->queryLegacyWatchlist( $params, $isGenerator, $listRow->gl_user ); - } else { - $titles = $this->queryListItems( $params, $isGenerator ); - } + $titles = $this->queryListItems( $params, $isGenerator ); } if ( !$isGenerator ) { @@ -242,14 +199,9 @@ } public function getAllowedParams() { - return array( + return array_merge( ApiShared::getListAccessParams(), array( 'continue' => array( ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', - ), - 'id' => array( - ApiBase::PARAM_DFLT => 0, - ApiBase::PARAM_TYPE => 'integer', - ApiBase::PARAM_MIN => 0, ), 'namespace' => array( ApiBase::PARAM_ISMULTI => true, @@ -262,12 +214,6 @@ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2, ), - 'owner' => array( - ApiBase::PARAM_TYPE => 'user', - ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ), 'dir' => array( ApiBase::PARAM_DFLT => 'ascending', ApiBase::PARAM_TYPE => array( @@ -276,7 +222,7 @@ ), ApiBase::PARAM_HELP_MSG => 'api-help-param-direction', ), - ); + ) ); } protected function getExamplesMessages() { diff --git a/includes/api/ApiShared.php b/includes/api/ApiShared.php new file mode 100644 index 0000000..02b74bb --- /dev/null +++ b/includes/api/ApiShared.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © 2015 Yuri Astrakhan "<Firstname><Lastname>@gmail.com", + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace Gather\api; + +use ApiBase; +use DatabaseBase; + +/** + * Shared code for List's API + * + * @ingroup API + */ +class ApiShared { + + /** + * Get parameters used to identify a list with ownership + * @return array + */ + public static function getListAccessParams() { + return array( + 'id' => array( + ApiBase::PARAM_DFLT => 0, + ApiBase::PARAM_TYPE => 'integer', + ApiBase::PARAM_MIN => 0, + ), + 'owner' => array( + ApiBase::PARAM_TYPE => 'user', + ), + 'token' => array( + ApiBase::PARAM_TYPE => 'string', + ), + ); + } + + /** + * Ensure that current params allow access to one list + * @param DatabaseBase $db + * @param ApiBase $context + * @param array $params module parameters + * @return int userId for watchlist, or 0 for a regular list + * @throws \UsageException In case access is not allowed + */ + public static function checkListAccess( DatabaseBase $db, ApiBase $context, array $params ) { + $useOwner = $params['owner'] !== null; + if ( $useOwner !== ( $params['token'] !== null ) ) { + $p = $context->getModulePrefix(); + $context->dieUsage( "Both {$p}owner and {$p}token must be given or missing", + 'invalidparammix' ); + } + + if ( !$params['id'] ) { + // If id is not given (or equals to 0), permissions the same as watchlistraw access + return $context->getWatchlistUser( $params )->getId(); + } + + // Id was given, this could be public or private list, legacy watchlist or regular + // Allow access to any public list/watchlist, and to private with proper owner/self + $listRow = $db->selectRow( 'gather_list', + array( 'gl_label', 'gl_user', 'gl_perm' ), + array( 'gl_id' => $params['id'] ), + __METHOD__ ); + if ( $listRow === false ) { + $context->dieUsage( "List does not exist", 'badid' ); + } + + $listRow = ApiEditList::normalizeRow( $listRow ); + if ( $useOwner ) { + // Caller supplied token: treat them as trusted, someone who could see even private + // At the same time, owner param must match list's owner + // TODO: if we allow non-matching owner, we could treat it as public-only, + // but that might be unexpected behavior + $user = $context->getWatchlistUser( $params ); + if ( $listRow->gl_user !== $user->getId() ) { + $context->dieUsage( 'The owner supplied does not match the list\'s owner', + 'permissiondenied' ); + } + $showPrivate = true; + } else { + $user = $context->getUser(); + $showPrivate = + $user->isLoggedIn() && $listRow->gl_user === $user->getId() && + $user->isAllowed( 'viewmywatchlist' ); + } + + // Check if this is a public list (if required) + if ( !$showPrivate && $listRow->gl_perm !== ApiEditList::PERM_PUBLIC ) { + $context->dieUsage( "You have no rights to see this list", 'badid' ); + } + + // If true, this is actually a watchlist, and it is either public or belongs to current user + return $listRow->gl_label === '' ? $listRow->gl_user : 0; + } +} -- To view, visit https://gerrit.wikimedia.org/r/203003 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I90b6df0c7a9d69460921e9410284c312b3d09d7b Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Gather Gerrit-Branch: master Gerrit-Owner: Yurik <yu...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits