@Reedy pointed out some code smell with the patrol feature in T257005.
The implementation of the patrol feature is distributed across a variety of core classes:
- The update API is in RecentChange (doMarkPatrolled, reallyMarkPatrolled), but it only uses public methods of RecentChange so it could be moved elsewhere.
- The User methods raised in T257005 could similarly be moved.
- There is ChangesList::isUnpatrolled(), a public static method called from 6 different classes, apparently just put in ChangesList for lack of a better place.
- RevisionStore::getRcIdIfUnpatrolled() has a todo "This is overly specific, so move or kill this method"
- PatrolLog is just a holder for a public static method with more patrol-related logic. It's a helper for RecentChange::doMarkPatrolled() which is also called by the PageTriage extension.
- RollbackPage has a todo comment asking for its 5 lines of patrol-related code to be moved to EditPage::attemptSave(). Same for WikiPage::doUserEditContent().
- IRCColourfulRCFeedFormatter and MachineReadableRCFeedFormatter need the patrol-related config globals to help them interpret a RecentChange object. A patrol class could help them with that.
I think changes list special pages are mostly OK, except that they call things that will be moved. The aim is to get rid of direct access to the patrol-related config variables, and to reduce the size of massive classes like User and RecentChange by giving patrol code its own place to live.
Other UI code:
- The patrol footer UI is in Article::showPatrolFooter(), and it uses a cache, necessitating a public static method Article::purgePatrolFooterCache() to invalidate that cache when a file is uploaded. It could use the checkKeys option to WANObjectCache::getWithSetCallback(). A backend method in a patrol class would purge the check key, implicitly invalidating the patrol footer cache, so that the file upload code would not have to know about the UI.
- DifferenceEngine builds links to MarkpatrolledAction in markPatrolledLink().
- PatrolLogFormatter is pretty conventional and could probably stay where it is.
Proposed interface summary:
<?php namespace MediaWiki; use MediaWiki\Config\ServiceOptions; use MediaWiki\Permissions\Authority; use MediaWiki\User\UserIdentity; use RecentChange; class Patrol { public function __construct( ServiceOptions $options ) {} // Storage public function markPatrolled( RecentChange $change, Authority $performer, $auto = false, $tags = null ) {} public function reallyMarkPatrolled( RecentChange $rc ) {} public function recordLogEvent( $rc, $auto, UserIdentity $user, $tags = null ) {} // formerly PatrolLog::record() // Retrieval public function isChangeUnpatrolled( RecentChange $rc, Authority $user ) {} public function isRowUnpatrolled( $rcRow, Authority $user ) {} public function getRcIdIfUnpatrolled( RecentChange $rc ) {} public function isChangePatrollable( RecentChange $rc ) {} // for IRCColourfulRCFeedFormatter // Config public function isRCPatrolEnabled() {} public function isNewPagesPatrolEnabled() {} public function isFilePatrolEnabled() {} // Authorization public function isRCPatrolAllowed( Authority $authority ) { return $this->isRCPatrolEnabled() && $authority->isAllowedAny( 'patrol', 'patrolmarks' ); } public function isNewPagesPatrolAllowed( Authority $authority ) {} public function isFilePatrolAllowed( Authority $authority ) {} }
Maybe you could argue for splitting this up into more classes. Maybe the storage methods in particular should be split off to avoid the need to inject a LoadBalancer for the rest of it.
I used "NewPages" in the proposed methods above instead of NP. I can see why the author used NP, but RC is a common abbreviation whereas NP is not.