This task follows up on some discussions on https://gerrit.wikimedia.org/r/c/mediawiki/core/+/681005 about how to avoid conflicts in the integer values for blockable actions added by extensions (see comments on patch).
TL;DR We need a way of assigning integer IDs to blockable actions (as defined by the BlockActionInfo service), in such a way that the IDs for actions added by extensions don't collide.
How partial blocks are stored
Partial blocks against actions will be stored in the existing ipblocks_restrictions table, which has columns for the type of restriction (representing types page, namespace or action) and the value of the restriction (representing the particular page, namespace or action). The table looks like this:
MariaDB [mediawiki]> DESCRIBE ipblocks_restrictions; +-----------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+------------+------+-----+---------+-------+ | ir_ipb_id | int(11) | NO | PRI | NULL | | | ir_type | tinyint(4) | NO | PRI | NULL | | | ir_value | int(11) | NO | PRI | NULL | | +-----------+------------+------+-----+---------+-------+
Actions aren't well-defined
Pages and namespaces have IDs, which are stored in ipblocks_restrictions.ir_value. Actions are very unstructured in MediaWiki. Some actions are respresented by a subclass of Action (recognised by the string returned by Action::getName() - e.g. 'history'); some are defined in the list PermissionManager::coreRights - e.g. 'sendemail'; some are simply strings that are passed around - e.g. 'create'. Actions don't have IDs, and there's no a definitive set of actions defined in one place.
Perhaps the most useful definition of an action, for the sake of partial action blocks, is:
a string that can get passed to PermissionManager::checkUserBlock as the $action argument
We need to define blockable actions
The BlockActionInfo service will define a set of blockable actions. These will be recognised by the string value of the action, but must be stored with an integer ID in ipblocks_restrictions. The BlockActionInfo service will map the string values to their IDs for blockable actions defined in MediaWiki core.
The problem is that extensions need to be able to define blockable actions too. So how do we ensure that their integer IDs don't collide?
Table for mapping actions to IDs
Have a table that maps string actions to IDs. This is proposed in T194529#6210790 and discussed in https://gerrit.wikimedia.org/r/c/mediawiki/core/+/681005/3#message-899556e17feef8a8d0e8f40f8b5e4a7945099f96
As proposed above, the extensions need only register their string actions (which already must not collide), and core could handle assigning IDs. This is similar to what we do with content models and slot roles (using the content_models and slot_roles tables) and there is existing infrastructure to make it easy, specifically the NameTableStore and NameTableStoreFactory classes.
A clear advantage of this method is that IDs will never collide, without extensions needing to do anything special. I think this would clearly be the way to go if we were designing actions from scratch. But I think given the complexity of our Production setup, (multiple wikis, global features, etc) it could add some difficulties. None are insurmountable, but I think they could add maintenance burden.
- Extension action IDs would get out of sync between different wikis, since there would be no enforcement to keep them consistent, and different wikis have different extensions installed. This would make it more difficult to compare blocks on different wikis: we'd need to be careful to join on the remote wiki's action block table whenever looking up a remote block, and moving data between wikis would need to be handled carefully.
- If GlobalBlocking were ever updated to work with partial blocks we'd need to store the action string rather than the ID. (There would be a similar problem for pages of course, but global blocks against pages doesn't make much sense since pages differ across wikis. However, we could implement global blocks against namespaces quite easily, since the IDs are consistent.) It's conceivable that a global block against e.g. 'upload' could be a useful thing to implement someday.
- The usual problem with storing extension data in a core table: if an extension were uninstalled, there could be nonsensical rows left behind in the block actions table unless we deleted them. (There could also be nonsensical blocks left behind, but this is a problem with either solution. It's also an existing problem with namespaces: T280798)
Hard-code the IDs and avoid conflicts via on-wiki documentation
This is how namespace IDs work: https://www.mediawiki.org/wiki/Extension_default_namespaces
Hard-coding is often a less satisfying solution, and having to manage the IDs on a wiki introduces some overhead which would be avoided by using the other solution. It also further entangles the code with the wiki.
The main advantage would be that it avoids the above complexity by keeping IDs consistent across wikis. In any relevant tables it would be clear what the ID meant. Managing the IDs would probably be a bit simpler than for namespaces, since the number of actions that can be partially blocked will likely be smaller.
We could go ahead with the second option while we're just testing with a handful of actions on beta, but we should decide which solution to implement before enabling on production sites.