2018-08-16 04:55:55 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Block restriction interface.
|
|
|
|
|
*
|
|
|
|
|
* 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 MediaWiki\Block;
|
|
|
|
|
|
Introduce infrastructure for partial blocks for actions
This adds a new type of block restriction for actions, which extends
AbstractRestriction. Like page and namespace restrictions, action
restrictions are stored in the ipblocks_restrictions table.
Blockable actions are defined in a BlockActionInfo service, with a
method for getting all the blockable actions, getAllBlockActions.
Action blocks are checked for in PermissionManager::checkUserBlock
using DatabaseBlock::appliesToRight. To make this work, this patch
also removes the 'edit' case from AbstractBlock::appliesToRight,
which always returned true. This was incorrect, as blocks do not
always apply to edit, so cases that called appliesToRight('edit')
were fixed before this commit. appliesToRight('edit') now returns
null (i.e. unsure), which is correct because it is not possible to
determine whether a block applies to editing a particular page
without knowing what that page is, and appliesToRight doesn't know
that page.
There are some flags on sitewide blocks that predate partial blocks,
which block particular actions: 'createaccount' and 'sendemail'.
These are still handled in AbstractBlock::appliesToRight, and are
still checked for separately in the peripheral components.
The feature flag $wgEnablePartialActionBlocks must set to true to
enable partial action blocks.
Bug: T279556
Bug: T6995
Change-Id: I17962bb7c4247a12c722e7bc6bcaf8c36efd8600
2021-04-26 23:07:17 +00:00
|
|
|
use MediaWiki\Block\Restriction\ActionRestriction;
|
2018-10-30 18:19:22 +00:00
|
|
|
use MediaWiki\Block\Restriction\NamespaceRestriction;
|
2018-08-16 04:55:55 +00:00
|
|
|
use MediaWiki\Block\Restriction\PageRestriction;
|
|
|
|
|
use MediaWiki\Block\Restriction\Restriction;
|
2021-09-28 17:21:37 +00:00
|
|
|
use MediaWiki\DAO\WikiAwareEntity;
|
2019-06-27 20:42:54 +00:00
|
|
|
use stdClass;
|
2023-05-05 13:13:06 +00:00
|
|
|
use Wikimedia\Rdbms\IConnectionProvider;
|
2020-01-10 00:00:51 +00:00
|
|
|
use Wikimedia\Rdbms\IResultWrapper;
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2019-04-11 19:54:10 +00:00
|
|
|
class BlockRestrictionStore {
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2024-07-30 21:52:32 +00:00
|
|
|
private IConnectionProvider $dbProvider;
|
2019-04-11 19:54:10 +00:00
|
|
|
|
2021-09-28 17:21:37 +00:00
|
|
|
/**
|
|
|
|
|
* @var string|false
|
|
|
|
|
*/
|
|
|
|
|
private $wikiId;
|
|
|
|
|
|
|
|
|
|
public function __construct(
|
2023-05-05 13:13:06 +00:00
|
|
|
IConnectionProvider $dbProvider,
|
2024-07-30 21:52:32 +00:00
|
|
|
/* string|false */ $wikiId = WikiAwareEntity::LOCAL
|
2021-09-28 17:21:37 +00:00
|
|
|
) {
|
2023-05-05 13:13:06 +00:00
|
|
|
$this->dbProvider = $dbProvider;
|
2021-09-28 17:21:37 +00:00
|
|
|
$this->wikiId = $wikiId;
|
2019-04-11 19:54:10 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-16 04:55:55 +00:00
|
|
|
/**
|
2023-09-01 00:15:03 +00:00
|
|
|
* Retrieve the restrictions from the database by block ID.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2023-06-14 21:31:41 +00:00
|
|
|
* @param int|int[] $blockId
|
2018-08-16 04:55:55 +00:00
|
|
|
* @return Restriction[]
|
|
|
|
|
*/
|
2022-03-22 18:30:21 +00:00
|
|
|
public function loadByBlockId( $blockId ) {
|
2019-01-11 15:49:44 +00:00
|
|
|
if ( $blockId === null || $blockId === [] ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 13:13:06 +00:00
|
|
|
$result = $this->dbProvider->getReplicaDatabase( $this->wikiId )
|
|
|
|
|
->newSelectQueryBuilder()
|
|
|
|
|
->select( [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ] )
|
|
|
|
|
->from( 'ipblocks_restrictions' )
|
|
|
|
|
->leftJoin( 'page', null, [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] )
|
|
|
|
|
->where( [ 'ir_ipb_id' => $blockId ] )
|
|
|
|
|
->caller( __METHOD__ )->fetchResultSet();
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2019-04-11 19:54:10 +00:00
|
|
|
return $this->resultToRestrictions( $result );
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-09-01 00:15:03 +00:00
|
|
|
* Insert the restrictions into the database.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2018-08-16 04:55:55 +00:00
|
|
|
* @param Restriction[] $restrictions
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function insert( array $restrictions ) {
|
2019-03-22 23:10:41 +00:00
|
|
|
if ( !$restrictions ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rows = [];
|
|
|
|
|
foreach ( $restrictions as $restriction ) {
|
|
|
|
|
$rows[] = $restriction->toRow();
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 13:13:06 +00:00
|
|
|
$dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2023-08-03 20:05:22 +00:00
|
|
|
$dbw->newInsertQueryBuilder()
|
In query builders, use insertInto() and deleteFrom() instead of insert() and delete()
The design principle for SelectQueryBuilder was to make the chained
builder calls look as much like SQL as possible, so that developers
could leverage their knowledge of SQL to understand what the query
builder is doing.
That's why SelectQueryBuilder::select() takes a list of fields, and by
the same principle, it makes sense for UpdateQueryBuilder::update() to
take a table. However with "insert" and "delete", the SQL designers
chose to add prepositions "into" and "from", and I think it makes sense
to follow that here.
In terms of natural language, we update a table, but we don't delete a
table, or insert a table. We delete rows from a table, or insert rows
into a table. The table is not the object of the verb.
So, add insertInto() as an alias for insert(), and add deleteFrom() as
an alias for delete(). Use the new methods in MW core callers where
PHPStorm knows the type.
Change-Id: Idb327a54a57a0fb2288ea067472c1e9727016000
2023-09-08 00:06:59 +00:00
|
|
|
->insertInto( 'ipblocks_restrictions' )
|
2023-08-03 20:05:22 +00:00
|
|
|
->ignore()
|
|
|
|
|
->rows( $rows )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2019-03-22 21:12:09 +00:00
|
|
|
|
|
|
|
|
return true;
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-09-01 00:15:03 +00:00
|
|
|
* Update the list of restrictions. This method does not allow removing all
|
2018-08-16 04:55:55 +00:00
|
|
|
* of the restrictions. To do that, use ::deleteByBlockId().
|
|
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2018-08-16 04:55:55 +00:00
|
|
|
* @param Restriction[] $restrictions
|
2023-09-01 00:15:03 +00:00
|
|
|
* @return bool Whether all operations were successful
|
2018-08-16 04:55:55 +00:00
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function update( array $restrictions ) {
|
2023-05-05 13:13:06 +00:00
|
|
|
$dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
|
|
|
|
$dbw->startAtomic( __METHOD__ );
|
|
|
|
|
|
2023-09-01 00:15:03 +00:00
|
|
|
// Organize the restrictions by block ID.
|
2019-04-11 19:54:10 +00:00
|
|
|
$restrictionList = $this->restrictionsByBlockId( $restrictions );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2023-09-01 00:15:03 +00:00
|
|
|
// Load the existing restrictions and organize by block ID. Any block IDs
|
2018-08-16 04:55:55 +00:00
|
|
|
// that were passed into this function will be used to load all of the
|
|
|
|
|
// existing restrictions. This list might be the same, or may be completely
|
|
|
|
|
// different.
|
|
|
|
|
$existingList = [];
|
|
|
|
|
$blockIds = array_keys( $restrictionList );
|
2023-09-08 21:18:11 +00:00
|
|
|
if ( $blockIds ) {
|
2023-05-05 13:13:06 +00:00
|
|
|
$result = $dbw->newSelectQueryBuilder()
|
|
|
|
|
->select( [ 'ir_ipb_id', 'ir_type', 'ir_value' ] )
|
|
|
|
|
->forUpdate()
|
|
|
|
|
->from( 'ipblocks_restrictions' )
|
|
|
|
|
->where( [ 'ir_ipb_id' => $blockIds ] )
|
|
|
|
|
->caller( __METHOD__ )->fetchResultSet();
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2019-04-11 19:54:10 +00:00
|
|
|
$existingList = $this->restrictionsByBlockId(
|
|
|
|
|
$this->resultToRestrictions( $result )
|
2018-08-16 04:55:55 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = true;
|
2023-09-01 00:15:03 +00:00
|
|
|
// Perform the actions on a per block-ID basis.
|
2018-08-16 04:55:55 +00:00
|
|
|
foreach ( $restrictionList as $blockId => $blockRestrictions ) {
|
|
|
|
|
// Insert all of the restrictions first, ignoring ones that already exist.
|
2019-04-11 19:54:10 +00:00
|
|
|
$success = $this->insert( $blockRestrictions );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
|
|
|
|
$result = $success && $result;
|
|
|
|
|
|
2019-04-11 19:54:10 +00:00
|
|
|
$restrictionsToRemove = $this->restrictionsToRemove(
|
2018-08-16 04:55:55 +00:00
|
|
|
$existingList[$blockId] ?? [],
|
|
|
|
|
$restrictions
|
|
|
|
|
);
|
|
|
|
|
|
2023-09-08 21:18:11 +00:00
|
|
|
if ( !$restrictionsToRemove ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 19:54:10 +00:00
|
|
|
$success = $this->delete( $restrictionsToRemove );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
|
|
|
|
$result = $success && $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$dbw->endAtomic( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-09-01 00:15:03 +00:00
|
|
|
* Updates the list of restrictions by parent ID.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2018-08-16 04:55:55 +00:00
|
|
|
* @param int $parentBlockId
|
|
|
|
|
* @param Restriction[] $restrictions
|
2023-09-01 00:15:03 +00:00
|
|
|
* @return bool Whether all updates were successful
|
2018-08-16 04:55:55 +00:00
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function updateByParentBlockId( $parentBlockId, array $restrictions ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
$parentBlockId = (int)$parentBlockId;
|
|
|
|
|
|
2023-05-05 13:13:06 +00:00
|
|
|
$db = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
2024-04-09 03:26:02 +00:00
|
|
|
$blockIds = $db->newSelectQueryBuilder()
|
|
|
|
|
->select( 'bl_id' )
|
|
|
|
|
->forUpdate()
|
|
|
|
|
->from( 'block' )
|
|
|
|
|
->where( [ 'bl_parent_block_id' => $parentBlockId ] )
|
|
|
|
|
->caller( __METHOD__ )->fetchFieldValues();
|
2023-06-20 17:34:58 +00:00
|
|
|
if ( !$blockIds ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If removing all of the restrictions, then just delete them all.
|
2023-09-08 21:18:11 +00:00
|
|
|
if ( !$restrictions ) {
|
2023-06-20 17:34:58 +00:00
|
|
|
$blockIds = array_map( 'intval', $blockIds );
|
|
|
|
|
return $this->deleteByBlockId( $blockIds );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$db->startAtomic( __METHOD__ );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
|
|
|
|
$result = true;
|
|
|
|
|
foreach ( $blockIds as $id ) {
|
2019-04-11 19:54:10 +00:00
|
|
|
$success = $this->update( $this->setBlockId( $id, $restrictions ) );
|
2018-08-16 04:55:55 +00:00
|
|
|
$result = $success && $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$db->endAtomic( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete the restrictions.
|
|
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2019-06-27 20:42:54 +00:00
|
|
|
* @param Restriction[] $restrictions
|
2018-08-16 04:55:55 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function delete( array $restrictions ) {
|
2023-05-05 13:13:06 +00:00
|
|
|
$dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
|
2018-08-16 04:55:55 +00:00
|
|
|
foreach ( $restrictions as $restriction ) {
|
2023-06-14 21:43:46 +00:00
|
|
|
$dbw->newDeleteQueryBuilder()
|
In query builders, use insertInto() and deleteFrom() instead of insert() and delete()
The design principle for SelectQueryBuilder was to make the chained
builder calls look as much like SQL as possible, so that developers
could leverage their knowledge of SQL to understand what the query
builder is doing.
That's why SelectQueryBuilder::select() takes a list of fields, and by
the same principle, it makes sense for UpdateQueryBuilder::update() to
take a table. However with "insert" and "delete", the SQL designers
chose to add prepositions "into" and "from", and I think it makes sense
to follow that here.
In terms of natural language, we update a table, but we don't delete a
table, or insert a table. We delete rows from a table, or insert rows
into a table. The table is not the object of the verb.
So, add insertInto() as an alias for insert(), and add deleteFrom() as
an alias for delete(). Use the new methods in MW core callers where
PHPStorm knows the type.
Change-Id: Idb327a54a57a0fb2288ea067472c1e9727016000
2023-09-08 00:06:59 +00:00
|
|
|
->deleteFrom( 'ipblocks_restrictions' )
|
2018-08-16 04:55:55 +00:00
|
|
|
// The restriction row is made up of a compound primary key. Therefore,
|
|
|
|
|
// the row and the delete conditions are the same.
|
2023-05-05 13:13:06 +00:00
|
|
|
->where( $restriction->toRow() )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-14 21:43:46 +00:00
|
|
|
return true;
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-05-13 14:18:07 +00:00
|
|
|
* Delete the restrictions by block ID.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2023-06-14 21:31:41 +00:00
|
|
|
* @param int|int[] $blockId
|
2018-08-16 04:55:55 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function deleteByBlockId( $blockId ) {
|
2023-06-14 21:43:46 +00:00
|
|
|
$this->dbProvider->getPrimaryDatabase( $this->wikiId )
|
2023-05-05 13:13:06 +00:00
|
|
|
->newDeleteQueryBuilder()
|
In query builders, use insertInto() and deleteFrom() instead of insert() and delete()
The design principle for SelectQueryBuilder was to make the chained
builder calls look as much like SQL as possible, so that developers
could leverage their knowledge of SQL to understand what the query
builder is doing.
That's why SelectQueryBuilder::select() takes a list of fields, and by
the same principle, it makes sense for UpdateQueryBuilder::update() to
take a table. However with "insert" and "delete", the SQL designers
chose to add prepositions "into" and "from", and I think it makes sense
to follow that here.
In terms of natural language, we update a table, but we don't delete a
table, or insert a table. We delete rows from a table, or insert rows
into a table. The table is not the object of the verb.
So, add insertInto() as an alias for insert(), and add deleteFrom() as
an alias for delete(). Use the new methods in MW core callers where
PHPStorm knows the type.
Change-Id: Idb327a54a57a0fb2288ea067472c1e9727016000
2023-09-08 00:06:59 +00:00
|
|
|
->deleteFrom( 'ipblocks_restrictions' )
|
2023-05-05 13:13:06 +00:00
|
|
|
->where( [ 'ir_ipb_id' => $blockId ] )
|
|
|
|
|
->caller( __METHOD__ )->execute();
|
2023-06-14 21:43:46 +00:00
|
|
|
return true;
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-09-01 00:15:03 +00:00
|
|
|
* Check if two arrays of Restrictions are effectively equal. This is a loose
|
2018-08-16 04:55:55 +00:00
|
|
|
* equality check as the restrictions do not have to contain the same block
|
2023-09-01 00:15:03 +00:00
|
|
|
* IDs.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2018-08-16 04:55:55 +00:00
|
|
|
* @param Restriction[] $a
|
|
|
|
|
* @param Restriction[] $b
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function equals( array $a, array $b ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
$aCount = count( $a );
|
|
|
|
|
$bCount = count( $b );
|
|
|
|
|
|
|
|
|
|
// If the count is different, then they are obviously a different set.
|
|
|
|
|
if ( $aCount !== $bCount ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If both sets contain no items, then they are the same set.
|
|
|
|
|
if ( $aCount === 0 && $bCount === 0 ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Blocks cleanup
* Make BlockManager internal methods private, since nothing calls them
anymore.
* In AbstractBlock and DatabaseBlock, remove deprecated public
properties mExpiry, mHideName, mTimestamp, mAuto and mParentBlockId.
* In BlockRestrictionStore, remove all the "instanceof Restriction"
checks. If someone passes in something that's not a Restriction, we
should throw, not ignore it, because we don't know the caller's
intention. Add a type declaration to $hasher in equals() so that it
will throw.
* Remove the "m" prefix from all private and protected properties.
AbstractBlock is not stable to override so this is not a stable
interface break.
* In BlockRestrictionStore::restrictionsToRemove(), use an O(N)
algorithm.
* In BlockRestrictionStore::rowToRestriction(), use a switch instead of
a type map, so that the calls are statically analyzable.
* In BlockUser::__construct(), fix the initialisation order issue by
inlining the relevant logic.
* Rename variable $actionRestriction.
* In Special:Block, fix call to deprecated method getTargetAndType(),
and hard deprecate it. @deprecated has the effect of deprecating a
method for both internal and external callers, there's no such thing
as an external-only deprecation. So it's necessary to rename it if you
want to keep it as a private method.
Bug: T345683
Change-Id: If4a4a18d7b5fec825417de81302266119c215fd3
2023-09-18 01:32:32 +00:00
|
|
|
$hasher = static function ( Restriction $r ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
return $r->getHash();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$aHashes = array_map( $hasher, $a );
|
|
|
|
|
$bHashes = array_map( $hasher, $b );
|
|
|
|
|
|
|
|
|
|
sort( $aHashes );
|
|
|
|
|
sort( $bHashes );
|
|
|
|
|
|
|
|
|
|
return $aHashes === $bHashes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the blockId on a set of restrictions and return a new set.
|
|
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2018-08-16 04:55:55 +00:00
|
|
|
* @param int $blockId
|
|
|
|
|
* @param Restriction[] $restrictions
|
|
|
|
|
* @return Restriction[]
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
public function setBlockId( $blockId, array $restrictions ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
$blockRestrictions = [];
|
|
|
|
|
|
|
|
|
|
foreach ( $restrictions as $restriction ) {
|
|
|
|
|
// Clone the restriction so any references to the current restriction are
|
|
|
|
|
// not suddenly changed to a different blockId.
|
|
|
|
|
$restriction = clone $restriction;
|
|
|
|
|
$restriction->setBlockId( $blockId );
|
|
|
|
|
|
|
|
|
|
$blockRestrictions[] = $restriction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $blockRestrictions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the restrictions that should be removed, which are existing
|
|
|
|
|
* restrictions that are not in the new list of restrictions.
|
|
|
|
|
*
|
|
|
|
|
* @param Restriction[] $existing
|
|
|
|
|
* @param Restriction[] $new
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
private function restrictionsToRemove( array $existing, array $new ) {
|
Blocks cleanup
* Make BlockManager internal methods private, since nothing calls them
anymore.
* In AbstractBlock and DatabaseBlock, remove deprecated public
properties mExpiry, mHideName, mTimestamp, mAuto and mParentBlockId.
* In BlockRestrictionStore, remove all the "instanceof Restriction"
checks. If someone passes in something that's not a Restriction, we
should throw, not ignore it, because we don't know the caller's
intention. Add a type declaration to $hasher in equals() so that it
will throw.
* Remove the "m" prefix from all private and protected properties.
AbstractBlock is not stable to override so this is not a stable
interface break.
* In BlockRestrictionStore::restrictionsToRemove(), use an O(N)
algorithm.
* In BlockRestrictionStore::rowToRestriction(), use a switch instead of
a type map, so that the calls are statically analyzable.
* In BlockUser::__construct(), fix the initialisation order issue by
inlining the relevant logic.
* Rename variable $actionRestriction.
* In Special:Block, fix call to deprecated method getTargetAndType(),
and hard deprecate it. @deprecated has the effect of deprecating a
method for both internal and external callers, there's no such thing
as an external-only deprecation. So it's necessary to rename it if you
want to keep it as a private method.
Bug: T345683
Change-Id: If4a4a18d7b5fec825417de81302266119c215fd3
2023-09-18 01:32:32 +00:00
|
|
|
$restrictionsByHash = [];
|
|
|
|
|
foreach ( $existing as $restriction ) {
|
|
|
|
|
$restrictionsByHash[$restriction->getHash()] = $restriction;
|
|
|
|
|
}
|
|
|
|
|
foreach ( $new as $restriction ) {
|
|
|
|
|
unset( $restrictionsByHash[$restriction->getHash()] );
|
|
|
|
|
}
|
|
|
|
|
return array_values( $restrictionsByHash );
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts an array of restrictions to an associative array of restrictions
|
2023-09-01 00:15:03 +00:00
|
|
|
* where the keys are the block IDs.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
|
|
|
|
* @param Restriction[] $restrictions
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
private function restrictionsByBlockId( array $restrictions ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
$blockRestrictions = [];
|
|
|
|
|
|
|
|
|
|
foreach ( $restrictions as $restriction ) {
|
|
|
|
|
$blockRestrictions[$restriction->getBlockId()][] = $restriction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $blockRestrictions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-09-01 00:15:03 +00:00
|
|
|
* Convert a result wrapper to an array of restrictions.
|
2018-08-16 04:55:55 +00:00
|
|
|
*
|
|
|
|
|
* @param IResultWrapper $result
|
|
|
|
|
* @return Restriction[]
|
|
|
|
|
*/
|
2019-04-11 19:54:10 +00:00
|
|
|
private function resultToRestrictions( IResultWrapper $result ) {
|
2018-08-16 04:55:55 +00:00
|
|
|
$restrictions = [];
|
|
|
|
|
foreach ( $result as $row ) {
|
2019-04-11 19:54:10 +00:00
|
|
|
$restriction = $this->rowToRestriction( $row );
|
2018-08-16 04:55:55 +00:00
|
|
|
|
|
|
|
|
if ( !$restriction ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$restrictions[] = $restriction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $restrictions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert a result row from the database into a restriction object.
|
|
|
|
|
*
|
2019-06-27 20:42:54 +00:00
|
|
|
* @param stdClass $row
|
2018-08-16 04:55:55 +00:00
|
|
|
* @return Restriction|null
|
|
|
|
|
*/
|
2019-06-27 20:42:54 +00:00
|
|
|
private function rowToRestriction( stdClass $row ) {
|
Blocks cleanup
* Make BlockManager internal methods private, since nothing calls them
anymore.
* In AbstractBlock and DatabaseBlock, remove deprecated public
properties mExpiry, mHideName, mTimestamp, mAuto and mParentBlockId.
* In BlockRestrictionStore, remove all the "instanceof Restriction"
checks. If someone passes in something that's not a Restriction, we
should throw, not ignore it, because we don't know the caller's
intention. Add a type declaration to $hasher in equals() so that it
will throw.
* Remove the "m" prefix from all private and protected properties.
AbstractBlock is not stable to override so this is not a stable
interface break.
* In BlockRestrictionStore::restrictionsToRemove(), use an O(N)
algorithm.
* In BlockRestrictionStore::rowToRestriction(), use a switch instead of
a type map, so that the calls are statically analyzable.
* In BlockUser::__construct(), fix the initialisation order issue by
inlining the relevant logic.
* Rename variable $actionRestriction.
* In Special:Block, fix call to deprecated method getTargetAndType(),
and hard deprecate it. @deprecated has the effect of deprecating a
method for both internal and external callers, there's no such thing
as an external-only deprecation. So it's necessary to rename it if you
want to keep it as a private method.
Bug: T345683
Change-Id: If4a4a18d7b5fec825417de81302266119c215fd3
2023-09-18 01:32:32 +00:00
|
|
|
switch ( (int)$row->ir_type ) {
|
|
|
|
|
case PageRestriction::TYPE_ID:
|
|
|
|
|
return PageRestriction::newFromRow( $row );
|
|
|
|
|
case NamespaceRestriction::TYPE_ID:
|
|
|
|
|
return NamespaceRestriction::newFromRow( $row );
|
|
|
|
|
case ActionRestriction::TYPE_ID:
|
|
|
|
|
return ActionRestriction::newFromRow( $row );
|
|
|
|
|
default:
|
|
|
|
|
return null;
|
2018-08-16 04:55:55 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|