jenkins-bot has submitted this change and it was merged. Change subject: lstprop=owner and a few bug fixes ......................................................................
lstprop=owner and a few bug fixes * Added lstprop=owner to get owner only when needed * Added $wgGatherAllowPublicWatchlist global * Optimized lists query's owner retrieval * Fixed obscure bug with multiple watchlists showing in a list * Fixed continuation bug when several IDs given * Fixed format=xml counting bug (thx Anomie) * Fixed slow filesort in several mysql queries * ApiEditList::updateInfo() was called with 3 params instead of 2 * More tests Change-Id: Id18e7c92110dae5e690e7a6cc5d386c639b3de32 (cherry picked from commit 23af469379423565e7a939f01c7275be1971dab5) --- M Gather.php M includes/api/ApiEditList.php M includes/api/ApiQueryLists.php M includes/specials/SpecialGatherLists.php M tests/phpunit/api/GatherTests.php 5 files changed, 339 insertions(+), 167 deletions(-) Approvals: Phuedx: Looks good to me, approved jenkins-bot: Verified diff --git a/Gather.php b/Gather.php index dca7fa2..3803be9 100644 --- a/Gather.php +++ b/Gather.php @@ -97,5 +97,10 @@ $wgGroupPermissions['*']['gather-hidelist'] = false; $wgGroupPermissions['sysop']['gather-hidelist'] = true; +/** + * If true, user's watchlist can be made public + */ +$wgGatherAllowPublicWatchlist = false; + // ResourceLoader modules require_once __DIR__ . "/resources/Resources.php"; diff --git a/includes/api/ApiEditList.php b/includes/api/ApiEditList.php index 0f7f17b..8fbd89b 100644 --- a/includes/api/ApiEditList.php +++ b/includes/api/ApiEditList.php @@ -99,10 +99,7 @@ // ACTION: delete list (items + list itself) $dbw->delete( 'gather_list_item', array( 'gli_gl_id' => $listId ), __METHOD__ ); $dbw->delete( 'gather_list', array( 'gl_id' => $listId ), __METHOD__ ); - $this->getResult()->addValue( null, $this->getModuleName(), array( - 'status' => 'deleted', - 'id' => $listId, - ) ); + $this->setResultStatus( $listId, 'deleted' ); break; case 'hidelist': case 'showlist': @@ -177,21 +174,20 @@ } if ( $isWatchlist ) { + global $wgGatherAllowPublicWatchlist; if ( $label !== null ) { $this->dieUsage( "{$p}label cannot be set for a watchlist", 'invalidparammix' ); } elseif ( $mode === 'deletelist' ) { $this->dieUsage( "Watchlist may not be deleted", 'invalidparammix' ); - } elseif ( $params['perm'] === 'public' ) { + } elseif ( !$wgGatherAllowPublicWatchlist ) { // Per team discussion, introducing artificial limitation for now // until we establish that making watchlist public would cause no harm. - // This check can be deleted at any time since all other API code supports it. - $this->dieUsage( 'Making watchlist public is not supported for security reasons', - 'publicwatchlist' ); - } elseif ( $isNoUpdatesMode ) { - // Per team discussion, introducing artificial limitation for now - // until we establish that making watchlist public would cause no harm. - // This check can be deleted at any time since all other API code supports it. - $this->dieUsage( "{$p}mode=$mode is not allowed for watchlist", 'watchlist' ); + if ( $isNoUpdatesMode ) { + $this->dieUsage( "{$p}mode=$mode is not allowed for watchlist", 'watchlist' ); + } elseif ( $params['perm'] === 'public' ) { + $this->dieUsage( 'Making watchlist public is not supported for security reasons', + 'publicwatchlist' ); + } } } if ( $isNew ) { @@ -393,8 +389,8 @@ */ private function createRow( DatabaseBase $dbw, User $user, array $params, &$isWatchlist ) { $label = $isWatchlist ? '' : $params['label']; - $info = $this->updateInfo( new stdClass(), $params, $isWatchlist ); - $createRow = !$isWatchlist || $info; + $info = $this->updateInfo( new stdClass(), $params ); + $createRow = !$isWatchlist || $info || $params['perm'] === 'public'; if ( $createRow ) { $id = $dbw->nextSequenceValue( 'gather_list_gl_id_seq' ); @@ -425,16 +421,10 @@ $this->dieDebug( "List was not found", 'badid' ); } else { // Watchlist, no changes - $this->getResult()->addValue( null, $this->getModuleName(), array( - 'status' => 'nochanges', - 'id' => 0, - ) ); + $this->setResultStatus( 0, 'nochanges' ); } } else { - $this->getResult()->addValue( null, $this->getModuleName(), array( - 'status' => 'created', - 'id' => $id, - ) ); + $this->setResultStatus( $id, 'created' ); } return $id; } @@ -449,7 +439,7 @@ private function updateListDb( DatabaseBase $dbw, array $params, $row ) { $update = array(); $info = self::parseListInfo( $row->gl_info, $row->gl_id, true ); - $info = $this->updateInfo( $info, $params, $row->gl_label === '' ); + $info = $this->updateInfo( $info, $params ); if ( $info ) { $update['gl_info'] = $info; } @@ -495,8 +485,7 @@ $titles->append( new ArrayIterator( $pageSet->getGoodTitles() ) ); $titles->append( new ArrayIterator( $pageSet->getMissingTitles() ) ); - $mode = $params['mode']; - $isRemoving = $mode === 'remove'; + $isRemoving = $params['mode'] === 'remove'; if ( $isWatchlist ) { // Legacy watchlist - watch/unwatch foreach ( $titles as $title ) { @@ -675,9 +664,17 @@ } else { $status = 'nochanges'; } + $this->setResultStatus( $row->gl_id, $status ); + } + + /** + * @param int $id + * @param string $status + */ + private function setResultStatus( $id, $status ) { $this->getResult()->addValue( null, $this->getModuleName(), array( 'status' => $status, - 'id' => $row->gl_id, + 'id' => $id, ) ); } } diff --git a/includes/api/ApiQueryLists.php b/includes/api/ApiQueryLists.php index cb18e16..a087c2e 100644 --- a/includes/api/ApiQueryLists.php +++ b/includes/api/ApiQueryLists.php @@ -56,6 +56,8 @@ $fld_public = in_array( 'public', $params['prop'] ); $fld_image = in_array( 'image', $params['prop'] ); $fld_updated = in_array( 'updated', $params['prop'] ); + $fld_owner = in_array( 'owner', $params['prop'] ); + $fld_count = in_array( 'count', $params['prop'] ); // Watchlist, having the label set to '', should always appear first // If it doesn't, make sure to insert a fake one in the result @@ -77,9 +79,8 @@ } $injectWatchlist = false; $findWatchlist = false; - $owner = false; + $owner = null; $showPrivate = false; - $userId = 0; } else { if ( $ids ) { $findWatchlist = array_search( 0, $ids ); @@ -96,7 +97,6 @@ /** @var User $owner */ list( $owner, $showPrivate ) = $this->calcPermissions( $params, $ids ); - $userId = $owner ? $owner->getId() : $this->getUser()->getId(); if ( $showPrivate !== true ) { $injectWatchlist = false; } @@ -105,13 +105,28 @@ $db = $this->getDB(); $this->addTables( 'gather_list' ); $this->addFields( 'gl_id' ); - $this->addFieldsIf( 'gl_label', $fld_label || !$mode ); + if ( $fld_label || !$mode ) { + $this->addFields( 'gl_label' ); + } else { + $this->addFields( array( 'isWl' => "gl_label=''" ) ); + } $this->addFieldsIf( 'gl_updated', $fld_updated || $mode ); - $this->addFieldsIf( 'gl_user', $mode ); $this->addFieldsIf( 'gl_perm', $fld_public ); + + if ( $fld_owner && !$owner ) { + // Join user table to get user name + // else - we already know the user to return, no need to join tables + $this->addTables( 'user' ); + $this->addJoinConds( array( 'user' => array( 'INNER JOIN', 'user_id=gl_user' ) ) ); + $this->addFields( 'user_name' ); + } if ( $owner ) { $this->addWhereFld( 'gl_user', $owner->getId() ); + $singleUser = true; + } else { + $owner = $this->getUser(); + $singleUser = false; } if ( $ids || $findWatchlist ) { @@ -120,17 +135,22 @@ $cond['gl_id'] = $ids; } if ( $findWatchlist ) { - $cond['gl_label'] = ''; + if ( $singleUser ) { + $cond['gl_label'] = ''; + } else { + $cond[] = + $db->makeList( array( 'gl_label' => '', 'gl_user' => $owner->getId() ), + LIST_AND ); + if ( !$ids ) { + $singleUser = true; + } + } } $this->addWhere( $db->makeList( $cond, LIST_OR ) ); } if ( $mode === 'allhidden' ) { $this->addWhereFld( 'gl_perm', ApiEditList::PERM_HIDDEN ); - if ( !$this->getUser()->isAllowed( 'gather-hidelist' ) ) { - // Show only the lists for the current user - $this->addWhereFld( 'gl_user', $this->getUser()->getId() ); - } } elseif ( $showPrivate !== true ) { $cond = array( 'gl_perm' => ApiEditList::PERM_PUBLIC ); if ( $showPrivate === null ) { @@ -140,18 +160,30 @@ } if ( $continue ) { - if ( $mode ) { - $cont = explode( '|', $params['continue'] ); - $this->dieContinueUsageIf( count( $cont ) != 2 ); - $cont_upd = $db->addQuotes( $cont[0] ); - $cont_id = intval( $cont[1] ); - $this->dieContinueUsageIf( strval( $cont_id ) !== $cont[1] ); - $cont_id = $db->addQuotes( $cont_id ); - $this->addWhere( "gl_updated < $cont_upd OR " . - "(gl_updated = $cont_upd AND gl_id <= $cont_id)" ); + if ( $singleUser ) { + // Single value continue + $contLabel = $db->addQuotes( $params['continue'] ); + $this->addWhere( "gl_label >= $contLabel" ); } else { - $cont = $db->addQuotes( $continue ); - $this->addWhere( "gl_label >= $cont" ); + if ( $mode ) { + $cont = explode( '|', $params['continue'] ); + $this->dieContinueUsageIf( count( $cont ) != 2 ); + $contUpd = $db->addQuotes( $cont[0] ); + $contId = intval( $cont[1] ); + $this->dieContinueUsageIf( strval( $contId ) !== $cont[1] ); + $contId = $db->addQuotes( $contId ); + $this->addWhere( "gl_updated < $contUpd OR " . + "(gl_updated = $contUpd AND gl_id <= $contId)" ); + } else { + $cont = explode( '|', $params['continue'], 2 ); // label may contain '|' + $this->dieContinueUsageIf( count( $cont ) != 2 ); + $contUser = intval( $cont[0] ); + $this->dieContinueUsageIf( strval( $contUser ) !== $cont[0] ); + $contUser = $db->addQuotes( $contUser ); + $contLabel = $db->addQuotes( $cont[1] ); + $this->addWhere( "gl_user > $contUser OR " . + "(gl_user = $contUser AND gl_label >= $contLabel)" ); + } } } if ( $mode ) { @@ -161,8 +193,14 @@ $setContinue = function( $row ) use ( $self ) { $self->setContinueEnumParameter( 'continue', "{$row->gl_updated}|{$row->gl_id}" ); }; - } else { + } elseif ( !$singleUser ) { + $this->addFields( 'gl_user' ); $this->addOption( 'ORDER BY', 'gl_user, gl_label' ); + $setContinue = function( $row ) use ( $self ) { + $self->setContinueEnumParameter( 'continue', "{$row->gl_user}|{$row->gl_label}" ); + }; + } else { + $this->addOption( 'ORDER BY', 'gl_label' ); $setContinue = function( $row ) use ( $self ) { $self->setContinueEnumParameter( 'continue', $row->gl_label ); }; @@ -183,10 +221,8 @@ $subsql = $db->selectSQLText( 'gather_list_item', 'gli_gl_id', $cond, __METHOD__ ); $subsql = "($subsql)"; $this->addFields( array( 'isIn' => $subsql ) ); - } else { - // Avoid subquery because there would be no results - searching for watchlist - $this->addFields( array( 'isIn' => 'NULL' ) ); } + // else - avoid subquery because there would be no results - searching for watchlist } $useInfo = $fld_description || $fld_image; @@ -200,15 +236,14 @@ // This closure will process one row, even if that row is fake watchlist $processRow = function( $row ) use ( $self, &$count, $mode, $limit, $useInfo, $title, - $fld_label, $fld_description, $fld_public, $fld_image, $fld_updated, $path, $userId, - $setContinue + $fld_label, $fld_description, $fld_public, $fld_image, $fld_updated, $fld_owner, + $path, $owner, $setContinue ) { if ( $row === null ) { // Fake watchlist row $row = (object) array( 'gl_id' => 0, 'gl_label' => '', - 'gl_user' => false, 'gl_perm' => ApiEditList::PERM_PRIVATE, 'gl_updated' => '', 'gl_info' => '', @@ -225,7 +260,7 @@ return false; } - $isWatchlist = $row->gl_label === ''; + $isWatchlist = property_exists( $row, 'isWl' ) ? $row->isWl : $row->gl_label === ''; $data = array( 'id' => $row->gl_id ); if ( $isWatchlist ) { @@ -235,14 +270,15 @@ // TODO: check if this is the right wfMessage to show $data['label'] = !$isWatchlist ? $row->gl_label : wfMessage( 'watchlist' )->plain(); } - if ( $mode ) { - $data['owner'] = User::newFromId( $row->gl_user )->getName(); + if ( $fld_owner ) { + $data['owner'] = property_exists( $row, 'user_name' ) ? + $row->user_name : $owner->getName(); } if ( $title ) { if ( $isWatchlist ) { - $data['title'] = $self->isTitleInWatchlist( $userId, $title ); + $data['title'] = $self->isTitleInWatchlist( $owner, $title ); } else { - $data['title'] = $row->isIn !== null; + $data['title'] = isset( $row->isIn ); } } if ( $fld_public ) { @@ -297,7 +333,7 @@ foreach ( $this->select( __METHOD__ ) as $row ) { if ( $injectWatchlist ) { - if ( $row->gl_label !== '' ) { + if ( property_exists( $row, 'isWl' ) ? !$row->isWl : $row->gl_label !== '' ) { // The very first DB row already has a label, so inject a fake if ( !$processRow( null ) ) { break; @@ -317,7 +353,9 @@ $this->getResult()->setIndexedTagName_internal( $path, 'c' ); - $this->updateCounts( $params, $userId ); + if ( $fld_count ) { + $this->updateCounts( $owner ); + } } public function getCacheMode( $params ) { @@ -342,6 +380,7 @@ 'image', 'count', 'updated', + 'owner', ) ), 'ids' => array( @@ -417,7 +456,7 @@ // ids given - will need to validate each id to belong to the current user for privacy $owner = false; if ( !$this->getUser()->isLoggedIn() ) { - $showPrivate = false; + $showPrivate = false; // Anonymous user - optimize, none of the results are private } else { $showPrivate = null; // check each id against current user's ID } @@ -434,13 +473,9 @@ /** * Update result lists with their page counts - * @param $params - * @param int $userId + * @param User $wlOwner In case there is a watchlist, this is the user it belongs to */ - private function updateCounts( $params, $userId ) { - if ( !in_array( 'count', $params['prop'] ) ) { - return; - } + private function updateCounts( User $wlOwner ) { $data = $this->getResult()->getData(); if ( !isset( $data['query'] ) || !isset( $data['query'][$this->getModuleName()] ) ) { return; @@ -449,13 +484,13 @@ $ids = array(); $wlListId = false; - $wlUserId = false; foreach ( $data as $page ) { - if ( $page['id'] === 0 || isset( $page['watchlist'] ) ) { - $wlListId = $page['id']; - $wlUserId = $userId; - } else { - $ids[] = $page['id']; + if ( is_array( $page ) ) { + if ( $page['id'] === 0 || isset( $page['watchlist'] ) ) { + $wlListId = $page['id']; + } else { + $ids[] = $page['id']; + } } } @@ -465,7 +500,7 @@ $db = $this->getQuery()->getNamedDB( 'watchlist', DB_SLAVE, 'watchlist' ); // Must divide in two because of duplicate talk pages (same as the special page) $counts[$wlListId] = intval( floor( - $db->selectRowCount( 'watchlist', '*', array( 'wl_user' => $wlUserId ), + $db->selectRowCount( 'watchlist', '*', array( 'wl_user' => $wlOwner->getId() ), __METHOD__ ) / 2 ) ); } if ( count( $ids ) > 0 ) { @@ -482,8 +517,10 @@ } foreach ( $data as &$page ) { - $id = $page['id']; - $page['count'] = isset( $counts[$id] ) ? $counts[$id] : 0; + if ( is_array( $page ) ) { + $id = $page['id']; + $page['count'] = isset( $counts[$id] ) ? $counts[$id] : 0; + } } // Replace result with the results with counts $this->getResult()->addValue( 'query', $this->getModuleName(), $data, @@ -491,15 +528,15 @@ } /** - * @param int $userId + * @param User $user * @param Title $title * @return bool * @throws \DBUnexpectedError */ - private function isTitleInWatchlist( $userId, $title ) { + private function isTitleInWatchlist( User $user, Title $title ) { $db = $this->getQuery()->getNamedDB( 'watchlist', DB_SLAVE, 'watchlist' ); return (bool)$db->selectField( 'watchlist', '1', array( - 'wl_user' => $userId, + 'wl_user' => $user->getId(), 'wl_namespace' => $title->getNamespace(), 'wl_title' => $title->getDBkey(), ), __METHOD__ ); diff --git a/includes/specials/SpecialGatherLists.php b/includes/specials/SpecialGatherLists.php index 555ce11..032156b 100644 --- a/includes/specials/SpecialGatherLists.php +++ b/includes/specials/SpecialGatherLists.php @@ -56,7 +56,7 @@ 'list' => 'lists', 'lstmode' => $subPage === 'hidden' ? 'allhidden' : 'allpublic', // FIXME: Need owner to link to collection - 'lstprop' => 'label|description|image|count|updated', + 'lstprop' => 'label|description|image|count|updated|owner', // TODO: Pagination 'continue' => '', ) ) ); diff --git a/tests/phpunit/api/GatherTests.php b/tests/phpunit/api/GatherTests.php index fd9cb1a..c81f9ef 100644 --- a/tests/phpunit/api/GatherTests.php +++ b/tests/phpunit/api/GatherTests.php @@ -65,17 +65,89 @@ 'GatherWlOnly2', 'GatherE1', 'GatherE2', + 'GatherE3', ) as $name ) { self::$wlUsers[$name] = new TestUser( $name ); } } self::$users = array_merge( self::$users, self::$wlUsers ); + global $wgGatherAllowPublicWatchlist; + $wgGatherAllowPublicWatchlist = false; + } + + protected function tearDown() { + global $wgGatherAllowPublicWatchlist; + $wgGatherAllowPublicWatchlist = false; + $this->db->delete( 'gather_list_item', '*' ); + $this->db->delete( 'gather_list', '*' ); + parent::tearDown(); + } + + + public function testMultiUser() { + $usrA = User::newFromId( 0 ); // Anonymous user + $n = 'GatherE1'; + $n2 = 'GatherE2'; + $n3 = 'GatherE3'; + $nS = 'sysop'; + $usr = self::$users[$n]->getUser(); + $usr2 = self::$users[$n2]->getUser(); + $usr3 = self::$users[$n3]->getUser(); + $usrS = self::$users[$nS]->getUser(); + $token = $this->getToken( $usr ); + $token2 = $this->getToken( $usr2 ); + $token3 = $this->getToken( $usr3 ); + $tokenS = $this->getToken( $usrS ); + $tokenA = $this->getToken( $usrA ); + + global $wgGatherAllowPublicWatchlist; + $wgGatherAllowPublicWatchlist = true; + + $res = $this->editList( 'mu-a1', $usr, $token, '"id":0,"perm":"public"' ); + $idWl = $this->getVal( 'mu-a1', '"id"', $res ); + $this->assertNotEquals( 0, $idWl, 'mu-a1' ); + + $res = $this->editList( 'mu-a2', $usr2, $token2, '"id":0,"perm":"public"' ); + $id2Wl = $this->getVal( 'mu-a2', '"id"', $res ); + $this->assertNotEquals( 0, $id2Wl, 'mu-a1' ); + + $res = $this->editList( 'mu-a3', $usr3, $token3, '"id":0,"description":"aa"' ); + $id3Wl = $this->getVal( 'mu-a3', '"id"', $res ); + $this->assertNotEquals( 0, $id3Wl, 'mu-a1' ); + + $pS = array( 'watchlist' => true, 'label' => 'Watchlist' ); + $p1 = (object) array_merge( array( 'id' => $idWl ), $pS ); + $p2 = (object) array_merge( array( 'id' => $id2Wl ), $pS ); + $p3 = (object) array_merge( array( 'id' => $id3Wl ), $pS ); + $pS = (object) array_merge( array( 'id' => 0 ), $pS ); + + $this->assertLists( 'mu-b1', $usr, 0, $p1 ); + $this->assertLists( 'mu-b2', $usr2, 0, $p2 ); + $this->assertLists( 'mu-b2', $usr3, 0, $p3 ); + $this->assertLists( 'mu-b3', $usrS, 0, $pS ); + $this->assertLists( 'mu-b4', $usr3, + '"lstids":"' . $idWl . '|' . $id2Wl . '"', array( $p1, $p2 ) ); + $this->assertLists( 'mu-b5', $usr2, + '"lstids":"' . $idWl . '|' . 0 . '"', array( $p1, $p2 ) ); + $this->assertLists( 'mu-b6', $usr3, + '"lstids":"' . $idWl . '|' . 0 . '"', array( $p1, $p3 ) ); + $this->assertLists( 'mu-b7', $usrA, + '"lstids":"' . $idWl . '|' . 0 . '"', array( $p1 ) ); + $this->assertLists( 'mu-b8', $usr3, '"lstmode":"allpublic"', array( $p2, $p1 ) ); + $this->assertLists( 'mu-b9', $usrS, '"lstmode":"allhidden"', null ); } public function testListEditAndPages() { $n = 'GatherE1'; $n2 = 'GatherE2'; + $n3 = 'GatherE3'; $nS = 'sysop'; + $usr = self::$users[$n]->getUser(); + $usr2 = self::$users[$n2]->getUser(); + $usr3 = self::$users[$n3]->getUser(); + $usrS = self::$users[$nS]->getUser(); + $usrA = User::newFromId( 0 ); // Anonymous user + $pageW = array( 'ns' => 0, 'title' => 'Gather-ListW' ); $pageTW = array( 'ns' => 1, 'title' => 'Talk:Gather-ListW' ); $pageWA = array( 'ns' => 0, 'title' => 'Gather-ListWA' ); @@ -89,18 +161,19 @@ $usr = self::$users[$n]->getUser(); $usr2 = self::$users[$n2]->getUser(); + $usr3 = self::$users[$n3]->getUser(); $usrS = self::$users[$nS]->getUser(); $usrA = User::newFromId( 0 ); // Anonymous user $token = $this->getToken( $usr ); $token2 = $this->getToken( $usr2 ); + $token3 = $this->getToken( $usr3 ); $tokenS = $this->getToken( $usrS ); $tokenA = $this->getToken( $usrA ); // Make sure there are no watchlists yet for these users (starting from clean slate) foreach ( array( $usr, $usr2 ) as $user ) { - $res = $this->getLists( 'ed-a0', $user, '{}' ); - $this->assertListsEquals( 'ed-a0', $res, + $this->assertLists( 'ed-a0', $user, '{}', '[{"id":0, "watchlist":true, "label":"Watchlist"}]' ); $this->assertPages( 'ed-a1', $user, null, array(), array() ); } @@ -139,15 +212,22 @@ $this->badUseEdit( 'ed-ba16', $usrS, $tokenS, $idWL . ', "mode": "hidelist"' ); $this->badUseEdit( 'ed-ba17', $usrS, $tokenS, $idWL . ', "mode": "showlist"' ); - $expListsW = array( 'id' => 0, 'watchlist' => true, 'label' => 'Watchlist' ); - $expListsW2 = array_merge( $expListsW, array( + $this->assertLists( 'ed-bb1', $usr, '"lstmode":"allpublic"', null ); + $this->assertLists( 'ed-bb2', $usrA, '"lstmode":"allpublic"', null ); + $this->assertLists( 'ed-bb3', $usrS, '"lstmode":"allpublic"', null ); + $this->badUseLists( 'ed-bb4', $usr, '"lstmode":"allhidden"' ); + $this->badUseLists( 'ed-bb5', $usrA, '"lstmode":"allhidden"' ); + $this->assertLists( 'ed-bb6', $usrS, '"lstmode":"allhidden"', null ); + + $expListsW = (object) array( 'id' => 0, 'watchlist' => true, 'label' => 'Watchlist' ); + $expListsW2 = (object) array_merge( (array) $expListsW, array( 'public' => false, 'perm' => 'private', 'description' => '', 'image' => false, 'count' => 0, ) ); - $this->assertOneList( 'ed-bb1', $usr, 0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-bb1', $usr, 0, $expListsW, $expListsW2 ); // // Add one page to the non-created watchlist @@ -157,29 +237,29 @@ $this->getVal( 'ed-c1', '"pages", 0, "title"', $res, 'Gather-ListW' ); $this->getVal( 'ed-c1', '"pages", 0, "added"', $res, '' ); - $expListsW2['count'] = 1; + $expListsW2->count = 1; $expPagesW = array( $pageW, $pageTW ); $this->assertPages( 'ed-c2', $usr, null, $expPagesW ); $this->assertPages( 'ed-c3', $usr, 0, $expPagesW ); - $this->assertOneList( 'ed-c4', $usr, 0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-c4', $usr, 0, $expListsW, $expListsW2 ); // // Create Watchlist row $res = $this->editList( 'ed-d1', $usr, $token, '"id":0, "description": "x"' ); $id0 = $this->getVal( 'ed-d1', '"id"', $res ); $idWL = '"id":' . $id0; - $expListsW['id'] = $id0; - $expListsW2['id'] = $id0; - $expListsW2['description'] = 'x'; + $expListsW->id = $id0; + $expListsW2->id = $id0; + $expListsW2->description = 'x'; $this->assertPages( 'ed-d2', $usr, 0, $expPagesW ); $this->assertPages( 'ed-d3', $usr, $id0, $expPagesW ); - $this->assertOneList( 'ed-d4', $usr, 0, $expListsW, $expListsW2 ); - $this->assertOneList( 'ed-d5', $usr, $id0, $expListsW, $expListsW2 ); - $this->assertOneList( 'ed-d6', $usrA, $id0, null ); - $this->assertOneList( 'ed-d7', $usr2, $id0, null ); - $this->assertOneList( 'ed-d7a', $usrS, $id0, null ); + $this->assertLists( 'ed-d4', $usr, 0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-d5', $usr, $id0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-d6', $usrA, $id0, null ); + $this->assertLists( 'ed-d7', $usr2, $id0, null ); + $this->assertLists( 'ed-d7a', $usrS, $id0, null ); $this->badUsePage( 'ed-d8', $usrA, '"lspid": ' . $id0 ); $this->badUsePage( 'ed-d9', $usr2, '"lspid": ' . $id0 ); $this->badUsePage( 'ed-d10', $usrS, '"lspid": ' . $id0 ); @@ -204,6 +284,13 @@ $this->badUseEdit( 'ed-da16', $usrS, $tokenS, $idWL . ', "mode": "hidelist"' ); $this->badUseEdit( 'ed-da17', $usrS, $tokenS, $idWL . ', "mode": "showlist"' ); + $this->assertLists( 'ed-da21', $usr, '"lstmode":"allpublic"', null ); + $this->assertLists( 'ed-da22', $usrA, '"lstmode":"allpublic"', null ); + $this->assertLists( 'ed-da23', $usrS, '"lstmode":"allpublic"', null ); + $this->badUseLists( 'ed-da24', $usr, '"lstmode":"allhidden"' ); + $this->badUseLists( 'ed-da25', $usrA, '"lstmode":"allhidden"' ); + $this->assertLists( 'ed-da26', $usrS, '"lstmode":"allhidden"', null ); + // // Add Gather-ListWA to the created watchlist $res = $this->editList( 'ed-e1', $usr, $token, $idWL . ', "titles": "Gather-ListWA"' ); @@ -212,14 +299,14 @@ $this->getVal( 'ed-e1', '"pages", 0, "title"', $res, 'Gather-ListWA' ); $this->getVal( 'ed-e1', '"pages", 0, "added"', $res, '' ); - $expListsW2['count'] = 2; + $expListsW2->count = 2; $expPagesW = array( $pageW, $pageWA, $pageTW, $pageTWA ); $this->assertPages( 'ed-e2', $usr, null, $expPagesW ); $this->assertPages( 'ed-e3', $usr, 0, $expPagesW ); $this->assertPages( 'ed-e4', $usr, $id0, $expPagesW ); - $this->assertOneList( 'ed-e5', $usr, 0, $expListsW, $expListsW2 ); - $this->assertOneList( 'ed-e6', $usr, $id0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-e5', $usr, 0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-e6', $usr, $id0, $expListsW, $expListsW2 ); // // Add Gather-ListWAB to the created watchlist with ID=0 and description change @@ -230,15 +317,15 @@ $this->getVal( 'ed-f1', '"pages", 0, "title"', $res, 'Gather-ListWAB' ); $this->getVal( 'ed-f1', '"pages", 0, "added"', $res, '' ); - $expListsW2['count'] = 3; - $expListsW2['description'] = 'y'; + $expListsW2->count = 3; + $expListsW2->description = 'y'; $expPagesW = array( $pageW, $pageWA, $pageWAB, $pageTW, $pageTWA, $pageTWAB ); $this->assertPages( 'ed-f2', $usr, null, $expPagesW ); $this->assertPages( 'ed-f3', $usr, 0, $expPagesW ); $this->assertPages( 'ed-f4', $usr, $id0, $expPagesW ); - $this->assertOneList( 'ed-f5', $usr, 0, $expListsW, $expListsW2 ); - $this->assertOneList( 'ed-f6', $usr, $id0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-f5', $usr, 0, $expListsW, $expListsW2 ); + $this->assertLists( 'ed-f6', $usr, $id0, $expListsW, $expListsW2 ); // // Create new list A @@ -246,8 +333,8 @@ $this->getVal( 'ed-i1', '"status"', $res, 'created' ); $idA = $this->getVal( 'ed-i1', '"id"', $res ); $idAs = '"id":' . $idA; - $expListsA = array( 'id' => $idA, 'label' => 'A' ); - $expListsA2 = array_merge( $expListsA, array( + $expListsA = (object) array( 'id' => $idA, 'label' => 'A' ); + $expListsA2 = (object) array_merge( (array) $expListsA, array( 'public' => false, 'perm' => 'private', 'description' => '', @@ -257,10 +344,10 @@ $expPagesA = array(); $this->assertPages( 'ed-i2', $usr, $idA, $expPagesA ); - $this->assertOneList( 'ed-i3', $usr, $idA, $expListsA, $expListsA2 ); - $this->assertOneList( 'ed-i4', $usrA, $idA, null ); - $this->assertOneList( 'ed-i5', $usr2, $idA, null ); - $this->assertOneList( 'ed-i6', $usrS, $idA, null ); + $this->assertLists( 'ed-i3', $usr, $idA, $expListsA, $expListsA2 ); + $this->assertLists( 'ed-i4', $usrA, $idA, null ); + $this->assertLists( 'ed-i5', $usr2, $idA, null ); + $this->assertLists( 'ed-i6', $usrS, $idA, null ); $this->badUsePage( 'ed-ia1', $usrA, '"lspid": ' . $idA ); $this->badUsePage( 'ed-ia2', $usr2, '"lspid": ' . $idA ); @@ -281,19 +368,26 @@ $this->badUseEdit( 'ed-ia16', $usrS, $tokenS, $idAs . ', "mode": "hidelist"' ); $this->badUseEdit( 'ed-ia17', $usrS, $tokenS, $idAs . ', "mode": "showlist"' ); + $this->assertLists( 'ed-ia21', $usr, '"lstmode":"allpublic"', null ); + $this->assertLists( 'ed-ia22', $usrA, '"lstmode":"allpublic"', null ); + $this->assertLists( 'ed-ia23', $usrS, '"lstmode":"allpublic"', null ); + $this->badUseLists( 'ed-ia24', $usr, '"lstmode":"allhidden"' ); + $this->badUseLists( 'ed-ia25', $usrA, '"lstmode":"allhidden"' ); + $this->assertLists( 'ed-ia26', $usrS, '"lstmode":"allhidden"', null ); + // // Rename list A to 'a' $res = $this->editList( 'ed-j1', $usr, $token, $idAs . ', "label": "a"' ); $this->getVal( 'ed-j1', '"status"', $res, 'updated' ); $this->getVal( 'ed-j1', '"id"', $res, $idA ); - $expListsA['label'] = 'a'; - $expListsA2['label'] = 'a'; + $expListsA->label = 'a'; + $expListsA2->label = 'a'; $this->assertPages( 'ed-j2', $usr, $idA, $expPagesA ); - $this->assertOneList( 'ed-j3', $usr, $idA, $expListsA, $expListsA2 ); - $this->assertOneList( 'ed-j4', $usrA, $id0, null ); - $this->assertOneList( 'ed-j5', $usr2, $id0, null ); - $this->assertOneList( 'ed-j6', $usrS, $id0, null ); + $this->assertLists( 'ed-j3', $usr, $idA, $expListsA, $expListsA2 ); + $this->assertLists( 'ed-j4', $usrA, $id0, null ); + $this->assertLists( 'ed-j5', $usr2, $id0, null ); + $this->assertLists( 'ed-j6', $usrS, $id0, null ); $this->badUsePage( 'ed-ja1', $usrA, '"lspid": ' . $idA ); $this->badUsePage( 'ed-ja2', $usr2, '"lspid": ' . $idA ); @@ -314,17 +408,17 @@ $res = $this->editList( 'ed-k1', $usr, $token, '"label": "a", "perm": "public"' ); $this->getVal( 'ed-k1', '"status"', $res, 'updated' ); $this->getVal( 'ed-k1', '"id"', $res, $idA ); - $expListsA2['public'] = true; - $expListsA2['perm'] = 'public'; + $expListsA2->public = true; + $expListsA2->perm = 'public'; $this->assertPages( 'ed-k2', $usr, $idA, $expPagesA ); $this->assertPages( 'ed-k2a', $usr2, $idA, $expPagesA ); $this->assertPages( 'ed-k2b', $usrA, $idA, $expPagesA ); $this->assertPages( 'ed-k2c', $usrS, $idA, $expPagesA ); - $this->assertOneList( 'ed-k3', $usr, $idA, $expListsA, $expListsA2 ); - $this->assertOneList( 'ed-k4', $usrA, $idA, $expListsA, $expListsA2 ); - $this->assertOneList( 'ed-k5', $usr2, $idA, $expListsA, $expListsA2 ); - $this->assertOneList( 'ed-k6', $usrS, $idA, $expListsA, $expListsA2 ); + $this->assertLists( 'ed-k3', $usr, $idA, $expListsA, $expListsA2 ); + $this->assertLists( 'ed-k4', $usrA, $idA, $expListsA, $expListsA2 ); + $this->assertLists( 'ed-k5', $usr2, $idA, $expListsA, $expListsA2 ); + $this->assertLists( 'ed-k6', $usrS, $idA, $expListsA, $expListsA2 ); $this->badUseEdit( 'ed-ka1', $usr, false, $idAs . ', "description": "x"' ); $this->badUseEdit( 'ed-ka2', $usrA, false, $idAs . ', "description": "x"' ); @@ -347,6 +441,13 @@ $this->badUseEdit( 'ed-ka18', $usr2, $token2, $idAs . ', "mode": "hidelist"' ); $this->badUseEdit( 'ed-ka19', $usr2, $token2, $idAs . ', "mode": "showlist"' ); + $this->assertLists( 'ed-ka21', $usr, '"lstmode":"allpublic"', $expListsA, $expListsA2 ); + $this->assertLists( 'ed-ka22', $usrA, '"lstmode":"allpublic"', $expListsA, $expListsA2 ); + $this->assertLists( 'ed-ka23', $usrS, '"lstmode":"allpublic"', $expListsA, $expListsA2 ); + $this->badUseLists( 'ed-ka24', $usr, '"lstmode":"allhidden"' ); + $this->badUseLists( 'ed-ka25', $usrA, '"lstmode":"allhidden"' ); + $this->assertLists( 'ed-ka26', $usrS, '"lstmode":"allhidden"', null ); + // // Delete list A $res = $this->editList( 'ed-l1', $usr, $token, $idAs . ', "mode": "deletelist"' ); @@ -357,10 +458,10 @@ $this->badUsePage( 'ed-l3', $usr2, '"lspid": ' . $idA ); $this->badUsePage( 'ed-l3a', $usrS, '"lspid": ' . $idA ); $this->badUsePage( 'ed-l4', $usrA, '"lspid": ' . $idA ); - $this->assertOneList( 'ed-l5', $usr, $idA, null ); - $this->assertOneList( 'ed-l6', $usrA, $idA, null ); - $this->assertOneList( 'ed-l7', $usr2, $idA, null ); - $this->assertOneList( 'ed-l7a', $usrS, $idA, null ); + $this->assertLists( 'ed-l5', $usr, $idA, null ); + $this->assertLists( 'ed-l6', $usrA, $idA, null ); + $this->assertLists( 'ed-l7', $usr2, $idA, null ); + $this->assertLists( 'ed-l7a', $usrS, $idA, null ); $this->badUseEdit( 'ed-l8', $usr, $token, $idAs . ', "titles": "ABC"' ); $this->badUseEdit( 'ed-l9', $usr, $token, $idAs . ', "label": "x"' ); @@ -375,8 +476,9 @@ $this->getVal( 'ed-n1', '"status"', $res, 'created' ); $idB = $this->getVal( 'ed-n1', '"id"', $res ); $idBs = '"id":' . $idB; - $expListsB = array( 'id' => $idB, 'label' => 'B' ); - $expListsB2 = array_merge( $expListsB, array( + + $expListsB = (object) array( 'id' => $idB, 'label' => 'B' ); + $expListsB2 = (object) array_merge( (array) $expListsB, array( 'public' => true, 'perm' => 'public', 'description' => '', @@ -390,10 +492,10 @@ $this->assertPages( 'ed-n3', $usr2, $idB, $expPagesB ); $this->assertPages( 'ed-n3a', $usrS, $idB, $expPagesB ); $this->assertPages( 'ed-n4', $usrA, $idB, $expPagesB ); - $this->assertOneList( 'ed-n5', $usr, $idB, $expListsB, $expListsB2 ); - $this->assertOneList( 'ed-n6', $usrA, $idB, $expListsB, $expListsB2 ); - $this->assertOneList( 'ed-n7', $usr2, $idB, $expListsB, $expListsB2 ); - $this->assertOneList( 'ed-n8', $usrS, $idB, $expListsB, $expListsB2 ); + $this->assertLists( 'ed-n5', $usr, $idB, $expListsB, $expListsB2 ); + $this->assertLists( 'ed-n6', $usrA, $idB, $expListsB, $expListsB2 ); + $this->assertLists( 'ed-n7', $usr2, $idB, $expListsB, $expListsB2 ); + $this->assertLists( 'ed-n8', $usrS, $idB, $expListsB, $expListsB2 ); $this->badUseEdit( 'ed-na1', $usr, $token, '"label": "B", "mode": "hidelist"' ); $this->badUseEdit( 'ed-na2', $usr, $token, '"label": "B", "mode": "showlist"' ); $this->badUseEdit( 'ed-na3', $usrA, $tokenA, '"label": "B", "mode": "hidelist"' ); @@ -420,14 +522,16 @@ $res = $this->editList( 'ed-o2', $usrS, $tokenS, $idBs . ', "mode":"hidelist"' ); $this->getVal( 'ed-o2', '"status"', $res, 'updated' ); $this->getVal( 'ed-o2', '"id"', $res, $idB ); - $expListsB2['public'] = false; - $expListsB2['perm'] = 'hidden'; + $expListsB2->public = false; + $expListsB2->perm = 'hidden'; $this->assertPages( 'ed-o3', $usr, $idB, $expPagesB ); - $this->assertOneList( 'ed-o4', $usr, $idB, $expListsB, $expListsB2 ); - $this->assertOneList( 'ed-o5', $usrA, $idB, null ); - $this->assertOneList( 'ed-o6', $usr2, $idB, null ); - $this->assertOneList( 'ed-o7', $usrS, $idB, null ); + $this->assertLists( 'ed-o4', $usr, $idB, $expListsB, $expListsB2 ); + $this->assertLists( 'ed-o4a', $usr, '{}', array( $expListsW, $expListsB ), + array( $expListsW2, $expListsB2 ) ); + $this->assertLists( 'ed-o5', $usrA, $idB, null ); + $this->assertLists( 'ed-o6', $usr2, $idB, null ); + $this->assertLists( 'ed-o7', $usrS, $idB, null ); $this->badUsePage( 'ed-oa1', $usrA, '"lspid": ' . $idB ); $this->badUsePage( 'ed-oa2', $usr2, '"lspid": ' . $idB ); @@ -453,11 +557,11 @@ $res = $this->editList( 'ed-p1', $usrS, $tokenS, $idBs . ', "mode":"showlist"' ); $this->getVal( 'ed-p1', '"status"', $res, 'updated' ); $this->getVal( 'ed-p1', '"id"', $res, $idB ); - $expListsB2['public'] = true; - $expListsB2['perm'] = 'public'; + $expListsB2->public = true; + $expListsB2->perm = 'public'; $this->assertPages( 'ed-p2', $usr, $idB, $expPagesB ); - $this->assertOneList( 'ed-p3', $usr, $idB, $expListsB, $expListsB2 ); + $this->assertLists( 'ed-p3', $usr, $idB, $expListsB, $expListsB2 ); } public function testMultipleLists() { @@ -567,15 +671,13 @@ // // Use owner param // user: get all with owner=user - $res = $this->getLists( "$p i0", $u, '"lstowner":' . $n ); - $this->assertListsEquals( "$p i0", $res, + $this->assertLists( "$p i0", $u, '"lstowner":' . $n, '[{"id":' . $wlId . ', "watchlist":true,"label":"Watchlist"}, ' . '{"id":' . $idA . ', "label":"A"},' . '{"id":' . $idB . ', "label":"B"}]' ); // user: get by idA with owner=user - $res = $this->getLists( "$p i0a", $u, - '"lstowner": ' . $n . ', "lstids": ' . $idA ); - $this->assertListsEquals( "$p i0a", $res, '[{"id":' . $idA . ', "label":"A"}]' ); + $this->assertLists( "$p i0a", $u, '"lstowner": ' . $n . ', "lstids": ' . $idA, + '[{"id":' . $idA . ', "label":"A"}]' ); // anon: get all with owner=user $res = $this->getLists( "$p i1", $a, '"lstowner":' . $n ); @@ -600,6 +702,10 @@ // user2: get by idB with owner=user $res = $this->getLists( "$p i5", $u2, '"lstowner": ' . $n . ', "lstids": ' . $idB ); $this->assertListNoId( "$p i5", $res, '[{"label":"B"}]' ); + + $res = $this->editList( "$p j1", $u, $token, '"id":' . $idB . ', "mode":"deletelist"' ); + $this->getVal( "$p j1", '"status"', $res, 'deleted' ); + $this->getVal( "$p j1", '"id"', $res, $idB ); } public function testWatchlistOnly() { @@ -688,20 +794,13 @@ $wlOnly = array( array( 'id' => $id, 'watchlist' => true, 'label' => 'Watchlist' ) ); - $res = $this->getLists( 'nc-f2', $u, '{}' ); - $this->assertListsEquals( 'nc-f2', $res, $wlOnly ); - - $res = $this->getLists( 'nc-f3', $u, '"lstids": 0' ); - $this->assertListsEquals( 'nc-f3', $res, $wlOnly ); - - $res = $this->getLists( 'nc-f4', $u, '"lstprop": "label|description|public|count"' ); - $this->assertListsEquals( 'nc-f4', $res, + $this->assertLists( 'nc-f2', $u, '{}', $wlOnly ); + $this->assertLists( 'nc-f3', $u, '"lstids": 0', $wlOnly ); + $this->assertLists( 'nc-f4', $u, '"lstprop": "label|description|public|count"', '[{"id":' . $id . ',"watchlist":true,"count":1,"label":"Watchlist","description":"aa",' . '"public":false,"perm":"private"}]' ); - - $res = $this->getLists( 'nc-f5', $u, '"lsttitle": "Gather-ListW"' ); - $this->assertListsEquals( 'nc-f5', $res, '[{"id":' . $id . + $this->assertLists( 'nc-f5', $u, '"lsttitle": "Gather-ListW"', '[{"id":' . $id . ',"watchlist":true,"label":"Watchlist","title":true}]' ); // @@ -820,7 +919,6 @@ } } - private function toApiParams( $message, $default, $token, $params ) { $params = $this->toArr( $message, $params, true ); if ( !isset( $params['action'] ) ) { @@ -888,9 +986,12 @@ /** * Debugging function to track the sate of the table during test development - * @param string $table + * @param bool|string $table */ - private function dumpTable( $table ) { + private function dumpTable( $table = false ) { + if ( !$table ) { + $table = 'gather_list'; + } echo "\nTable dump $table:\n"; foreach ( $this->db->select( $table, '*' ) as $row ) { echo $this->toStr( $row ) . "\n"; @@ -898,24 +999,56 @@ echo "\nEnd of the table dump $table\n"; } - private function assertOneList( $message, $u, $id, $expected, $expectedProp = null ) { - $params = '"lstids":' . $id; + /** + * @param $message + * @param User $u + * @param $params + * @param null|array|object $expected if null, expect empty, if object, expect one page, + * if array, expect several pages + * @param null|array|object $expectedProp + */ + private function assertLists( $message, $u, $params, $expected, $expectedProp = null ) { + if ( is_integer( $params ) ) { + $params = '"lstids":' . $params; + } + $params = $this->toArr( $message, $params, true ); + if ( $expected ) { + if ( is_object( $expected ) ) { + $expected = array( (array)$expected ); + } elseif ( is_string( $expected ) ) { + $expected = $this->toArr( $message, $expected ); + } elseif( is_array( $expected ) ) { + $expected = array_map( function ( $v ) { + return (array)$v; + }, $expected ); + } + } + if ( $expectedProp ) { + if ( is_object( $expectedProp ) ) { + $expectedProp = array( (array)$expectedProp ); + } elseif ( is_string( $expectedProp ) ) { + $expectedProp = $this->toArr( $message, $expectedProp ); + } elseif( is_array( $expectedProp ) ) { + $expectedProp = array_map( function ( $v ) { + return (array)$v; + }, $expectedProp ); + } + } $res = $this->getLists( $message, $u, $params ); $lst = $this->getVal( $message, '"query", "lists"', $res ); + if ( $expected === null ) { $this->assertCount( 0, $lst, $message ); } else { - $this->assertCount( 1, $lst, $message ); - $this->assertEquals( $expected, $lst[0], $message ); + $this->assertEquals( $expected, $lst, $message ); } if ( $expectedProp ) { - $params .= ', "lstprop":"label|description|public|image|count"'; + $params['lstprop'] = 'label|description|public|image|count'; $message .= '-p'; $res = $this->getLists( $message, $u, $params ); $lst = $this->getVal( $message, '"query", "lists"', $res ); - $this->assertCount( 1, $lst, $message ); - $this->assertEquals( $expectedProp, $lst[0], $message ); + $this->assertEquals( $expectedProp, $lst, $message ); } } -- To view, visit https://gerrit.wikimedia.org/r/201118 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Id18e7c92110dae5e690e7a6cc5d386c639b3de32 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Gather Gerrit-Branch: wmf/1.25wmf22 Gerrit-Owner: Jdlrobson <jrob...@wikimedia.org> Gerrit-Reviewer: Phuedx <g...@samsmith.io> Gerrit-Reviewer: Yurik <yu...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits