MediaWiki master
PatrolManager.php
Go to the documentation of this file.
1<?php
8
21
27
28 public const PRC_UNPATROLLED = 0;
29 public const PRC_PATROLLED = 1;
30 public const PRC_AUTOPATROLLED = 2;
31
35 public const CONSTRUCTOR_OPTIONS = [
39 ];
40
41 private IConnectionProvider $connectionProvider;
42 private UserFactory $userFactory;
43 private HookContainer $hookContainer;
44 private RevertedTagUpdateManager $revertedTagUpdateManager;
45
46 private bool $useRCPatrol;
47 private bool $useNPPatrol;
48 private bool $useFilePatrol;
49
50 public function __construct(
51 ServiceOptions $options,
52 IConnectionProvider $connectionProvider,
53 UserFactory $userFactory,
54 HookContainer $hookContainer,
55 RevertedTagUpdateManager $revertedTagUpdateManager
56 ) {
57 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
58
59 $this->connectionProvider = $connectionProvider;
60 $this->userFactory = $userFactory;
61 $this->hookContainer = $hookContainer;
62 $this->revertedTagUpdateManager = $revertedTagUpdateManager;
63
64 $this->useRCPatrol = $options->get( MainConfigNames::UseRCPatrol );
65 $this->useNPPatrol = $options->get( MainConfigNames::UseNPPatrol );
66 $this->useFilePatrol = $options->get( MainConfigNames::UseFilePatrol );
67 }
68
81 public function markPatrolled(
82 RecentChange $recentChange,
83 Authority $performer,
84 $tags = null
86 // Fix up $tags so that the MarkPatrolled hook below always gets an array
87 if ( $tags === null ) {
88 $tags = [];
89 } elseif ( is_string( $tags ) ) {
90 $tags = [ $tags ];
91 }
92
93 // If recentchanges patrol is disabled, only new pages or new file versions
94 // can be patrolled, provided the appropriate config variable is set
95 if ( !$this->useRCPatrol &&
96 ( !$this->useNPPatrol || $recentChange->getAttribute( 'rc_source' ) != RecentChange::SRC_NEW ) &&
97 ( !$this->useFilePatrol || !( $recentChange->getAttribute( 'rc_source' ) == RecentChange::SRC_LOG &&
98 $recentChange->getAttribute( 'rc_log_type' ) == 'upload' ) )
99 ) {
100 return PermissionStatus::newFatal( 'rcpatroldisabled' );
101 }
102
103 // Users without the 'autopatrol' right can't patrol their own revisions
104 if ( $performer->getUser()->equals( $recentChange->getPerformerIdentity() )
105 && !$performer->isAllowed( 'autopatrol' )
106 ) {
107 return PermissionStatus::newFatal( 'markedaspatrollederror-noautopatrol' );
108 }
109
110 $status = PermissionStatus::newEmpty();
111 $performer->authorizeWrite( 'patrol', $recentChange->getTitle(), $status );
112 if ( !$status->isGood() ) {
113 return $status;
114 }
115
116 $user = $this->userFactory->newFromAuthority( $performer );
117 $hookRunner = new HookRunner( $this->hookContainer );
118
119 if ( !$hookRunner->onMarkPatrolled( $recentChange->getAttribute( 'rc_id' ), $user, false, false, $tags ) ) {
120 return PermissionStatus::newFatal( 'hookaborted' );
121 }
122
123 // If the change was patrolled already, do nothing
124 if ( $recentChange->getAttribute( 'rc_patrolled' ) ) {
125 return $status;
126 }
127
128 // Attempt to set the 'patrolled' flag in RC database
129 $affectedRowCount = $this->reallyMarkPatrolled( $recentChange );
130
131 if ( $affectedRowCount === 0 ) {
132 // Query succeeded but no rows change, e.g. another request
133 // patrolled the same change just before us.
134 // Avoid duplicate log entry (T196182).
135 return $status;
136 }
137
138 // Log this patrol event
139 $logId = $this->createPatrolLog( $recentChange, $user, $tags );
140
141 $hookRunner->onMarkPatrolledComplete( $recentChange->getAttribute( 'rc_id' ), $user, false, false );
142 $hookRunner->onMarkPatrolledAudit( $recentChange, $user, $logId );
143
144 return $status;
145 }
146
154 public function reallyMarkPatrolled( RecentChange $recentChange ): int {
155 $dbw = $this->connectionProvider->getPrimaryDatabase();
156 $dbw->newUpdateQueryBuilder()
157 ->update( 'recentchanges' )
158 ->set( [ 'rc_patrolled' => self::PRC_PATROLLED ] )
159 ->where( [
160 'rc_id' => $recentChange->getAttribute( 'rc_id' ),
161 'rc_patrolled' => self::PRC_UNPATROLLED,
162 ] )
163 ->caller( __METHOD__ )->execute();
164
165 $affectedRowCount = $dbw->affectedRows();
166
167 // The change was patrolled already, do nothing
168 if ( $affectedRowCount === 0 ) {
169 return 0;
170 }
171
172 // Invalidate the page cache after the page has been patrolled
173 // to make sure that the Patrol link isn't visible any longer!
174 $recentChange->getTitle()->invalidateCache();
175
176 // Enqueue a reverted tag update (in case the edit was a revert)
177 $revisionId = $recentChange->getAttribute( 'rc_this_oldid' );
178 if ( $revisionId ) {
179 $this->revertedTagUpdateManager->approveRevertedTagForRevision( $revisionId );
180 }
181
182 return $affectedRowCount;
183 }
184
195 public function createPatrolLog(
196 RecentChange $recentChange,
197 UserIdentity $user,
198 $tags = null
199 ): int {
200 $entry = new ManualLogEntry( 'patrol', 'patrol' );
201
202 $page = $recentChange->getPage() ?? PageReferenceValue::localReference( NS_SPECIAL, 'Badtitle' );
203 $entry->setTarget( $page );
204 $entry->setParameters( [
205 '4::curid' => $recentChange->getAttribute( 'rc_this_oldid' ),
206 '5::previd' => $recentChange->getAttribute( 'rc_last_oldid' ),
207 '6::auto' => 0,
208 ] );
209 $entry->setPerformer( $user );
210 $entry->addTags( $tags );
211
212 $logId = $entry->insert();
213 $entry->publish( $logId, 'udp' );
214
215 return $logId;
216 }
217}
const NS_SPECIAL
Definition Defines.php:40
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
const UseRCPatrol
Name constant for the UseRCPatrol setting, for use with Config::get()
const UseNPPatrol
Name constant for the UseNPPatrol setting, for use with Config::get()
const UseFilePatrol
Name constant for the UseFilePatrol setting, for use with Config::get()
Immutable value object representing a page reference.
A StatusValue for permission errors.
markPatrolled(RecentChange $recentChange, Authority $performer, $tags=null)
Mark this RecentChange as patrolled.
createPatrolLog(RecentChange $recentChange, UserIdentity $user, $tags=null)
Create a logging entry for a change being patrolled.
reallyMarkPatrolled(RecentChange $recentChange)
Mark this RecentChange patrolled, without error checking.
__construct(ServiceOptions $options, IConnectionProvider $connectionProvider, UserFactory $userFactory, HookContainer $hookContainer, RevertedTagUpdateManager $revertedTagUpdateManager)
Utility class for creating and reading rows in the recentchanges table.
getPerformerIdentity()
Get the UserIdentity of the client that performed this change.
getAttribute( $name)
Get an attribute value.
Class for managing delayed RevertedTagUpdateJob waiting for user approval.
Create User objects.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
Interface for objects representing user identity.
Provide primary and replica IDatabase connections.