MediaWiki REL1_35
ContentModelChange.php
Go to the documentation of this file.
1<?php
2
9
20
23
25 private $hookRunner;
26
28 private $permManager;
29
31 private $revLookup;
32
34 private $user;
35
37 private $page;
38
40 private $newModel;
41
43 private $tags;
44
46 private $newContent;
47
49 private $latestRevId;
50
52 private $logAction;
53
55 private $msgPrefix;
56
66 public function __construct(
67 IContentHandlerFactory $contentHandlerFactory,
68 HookContainer $hookContainer,
69 PermissionManager $permManager,
70 RevisionLookup $revLookup,
71 User $user,
72 WikiPage $page,
73 string $newModel
74 ) {
75 $this->contentHandlerFactory = $contentHandlerFactory;
76 $this->hookRunner = new HookRunner( $hookContainer );
77 $this->permManager = $permManager;
78 $this->revLookup = $revLookup;
79
80 $this->user = $user;
81 $this->page = $page;
82 $this->newModel = $newModel;
83
84 // SpecialChangeContentModel doesn't support tags
85 // api can specify tags via ::setTags, which also checks if user can add
86 // the tags specified
87 $this->tags = [];
88
89 // Requires createNewContent to be called first
90 $this->logAction = '';
91
92 // Defaults to nothing, for special page
93 $this->msgPrefix = '';
94 }
95
101 public function setMessagePrefix( $msgPrefix ) {
102 $this->msgPrefix = $msgPrefix;
103 }
104
110 public function checkPermissions() {
111 $user = $this->user;
112 $current = $this->page->getTitle();
113 $titleWithNewContentModel = clone $current;
114 $titleWithNewContentModel->setContentModel( $this->newModel );
115
116 $pm = $this->permManager;
117
118 $creationErrors = [];
119 if ( !$current->exists() ) {
120 $creationErrors = $pm->getPermissionErrors( 'create', $user, $current );
121 }
122
123 $errors = wfMergeErrorArrays(
124 // Potentially include creation errors, if applicable
125 $creationErrors,
126 // edit the contentmodel of the page
127 $pm->getPermissionErrors( 'editcontentmodel', $user, $current ),
128 // edit the page under the old content model
129 $pm->getPermissionErrors( 'edit', $user, $current ),
130 // edit the contentmodel under the new content model
131 $pm->getPermissionErrors( 'editcontentmodel', $user, $titleWithNewContentModel ),
132 // edit the page under the new content model
133 $pm->getPermissionErrors( 'edit', $user, $titleWithNewContentModel )
134 );
135
136 return $errors;
137 }
138
145 public function setTags( $tags ) {
146 $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->user );
147 if ( $tagStatus->isOK() ) {
148 $this->tags = $tags;
149 return Status::newGood();
150 } else {
151 return $tagStatus;
152 }
153 }
154
160 private function createNewContent() {
161 $contentHandlerFactory = $this->contentHandlerFactory;
162
163 $title = $this->page->getTitle();
164 $latestRevRecord = $this->revLookup->getRevisionByTitle( $title );
165
166 if ( $latestRevRecord ) {
167 $latestContent = $latestRevRecord->getContent( SlotRecord::MAIN );
168 $latestHandler = $latestContent->getContentHandler();
169 $latestModel = $latestContent->getModel();
170 if ( !$latestHandler->supportsDirectEditing() ) {
171 // Only reachable via api
172 return Status::newFatal(
173 'apierror-changecontentmodel-nodirectediting',
174 ContentHandler::getLocalizedName( $latestModel )
175 );
176 }
177
178 $newModel = $this->newModel;
179 if ( $newModel === $latestModel ) {
180 // Only reachable via api
181 return Status::newFatal( 'apierror-nochanges' );
182 }
184 if ( !$newHandler->canBeUsedOn( $title ) ) {
185 // Only reachable via api
186 return Status::newFatal(
187 'apierror-changecontentmodel-cannotbeused',
188 ContentHandler::getLocalizedName( $newModel ),
189 Message::plaintextParam( $title->getPrefixedText() )
190 );
191 }
192
193 try {
194 $newContent = $newHandler->unserializeContent(
195 $latestContent->serialize()
196 );
197 } catch ( MWException $e ) {
198 // Messages: changecontentmodel-cannot-convert,
199 // apierror-changecontentmodel-cannot-convert
200 return Status::newFatal(
201 $this->msgPrefix . 'changecontentmodel-cannot-convert',
202 Message::plaintextParam( $title->getPrefixedText() ),
203 ContentHandler::getLocalizedName( $newModel )
204 );
205 }
206 $this->latestRevId = $latestRevRecord->getId();
207 $this->logAction = 'change';
208 } else {
209 // Page doesn't exist, create an empty content object
211 ->getContentHandler( $this->newModel )
212 ->makeEmptyContent();
213 $this->latestRevId = false;
214 $this->logAction = 'new';
215 }
216 $this->newContent = $newContent;
217 return Status::newGood();
218 }
219
231 public function doContentModelChange(
232 IContextSource $context,
233 $comment,
234 $bot
235 ) {
236 $status = $this->createNewContent();
237 if ( !$status->isGood() ) {
238 return $status;
239 }
240
241 $page = $this->page;
242 $title = $page->getTitle();
243 $user = $this->user;
244
245 if ( $user->pingLimiter( 'editcontentmodel' ) ) {
246 throw new ThrottledError();
247 }
248
249 // Create log entry
250 $log = new ManualLogEntry( 'contentmodel', $this->logAction );
251 $log->setPerformer( $user );
252 $log->setTarget( $title );
253 $log->setComment( $comment );
254 $log->setParameters( [
255 '4::oldmodel' => $title->getContentModel(),
256 '5::newmodel' => $this->newModel
257 ] );
258 $log->addTags( $this->tags );
259
260 $formatter = LogFormatter::newFromEntry( $log );
261 $formatter->setContext( RequestContext::newExtraneousContext( $title ) );
262 $reason = $formatter->getPlainActionText();
263
264 if ( $comment !== '' ) {
265 $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment;
266 }
267
268 // Run edit filters
269 $derivativeContext = new DerivativeContext( $context );
270 $derivativeContext->setTitle( $title );
271 $derivativeContext->setWikiPage( $page );
272 $status = new Status();
273
274 $newContent = $this->newContent;
275
276 if ( !$this->hookRunner->onEditFilterMergedContent( $derivativeContext, $newContent,
277 $status, $reason, $user, false )
278 ) {
279 if ( $status->isGood() ) {
280 // TODO: extensions should really specify an error message
281 $status->fatal( 'hookaborted' );
282 }
283 return $status;
284 }
285 if ( !$status->isOK() ) {
286 if ( !$status->getErrors() ) {
287 $status->fatal( 'hookaborted' );
288 }
289 return $status;
290 }
291
292 // Make the edit
293 $flags = $this->latestRevId ? EDIT_UPDATE : EDIT_NEW;
294 $flags |= EDIT_INTERNAL;
295 if ( $bot && $this->permManager->userHasRight( $user, 'bot' ) ) {
296 $flags |= EDIT_FORCE_BOT;
297 }
298
299 $status = $page->doEditContent(
301 $reason,
302 $flags,
303 $this->latestRevId,
304 $user,
305 null,
306 $this->tags
307 );
308
309 if ( !$status->isOK() ) {
310 return $status;
311 }
312
313 $logid = $log->insert();
314 $log->publish( $logid );
315
316 $values = [
317 'logid' => $logid
318 ];
319
320 return Status::newGood( $values );
321 }
322
323}
wfMergeErrorArrays(... $args)
Merge arrays in the style of PermissionManager::getPermissionErrors, with duplicate removal e....
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Helper class to change the content model of pages.
User $user
user making the change
createNewContent()
Create the new content.
string[] $tags
tags to add
IContentHandlerFactory $contentHandlerFactory
doContentModelChange(IContextSource $context, $comment, $bot)
Handle change and logging after validatio.
checkPermissions()
Check user can edit and editcontentmodel before and after.
string $msgPrefix
'apierror-' or empty string, for status messages
string $logAction
'new' or 'change'
PermissionManager $permManager
RevisionLookup $revLookup
setMessagePrefix( $msgPrefix)
Set the message prefix.
__construct(IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, PermissionManager $permManager, RevisionLookup $revLookup, User $user, WikiPage $page, string $newModel)
setTags( $tags)
Specify the tags the user wants to add, and check permissions.
int false $latestRevId
latest revision id, or false if creating
An IContextSource implementation which will inherit context from another source but allow individual ...
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
MediaWiki exception.
Class for creating new log entries and inserting them into the database.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Value object representing a content slot associated with a page revision.
static plaintextParam( $plaintext)
Definition Message.php:1130
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Show an error when the user hits a rate limit.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition User.php:1747
Class representing a MediaWiki article and history.
Definition WikiPage.php:51
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null, $tags=[], $undidRevId=0)
Change an existing article or create a new article.
getTitle()
Get the title object of the article.
Definition WikiPage.php:318
const EDIT_FORCE_BOT
Definition Defines.php:146
const EDIT_INTERNAL
Definition Defines.php:149
const EDIT_UPDATE
Definition Defines.php:143
const EDIT_NEW
Definition Defines.php:142
Base interface for content objects.
Definition Content.php:35
Interface for objects which can provide a MediaWiki context on request.
getContentHandler(string $modelID)
Returns a ContentHandler instance for the given $modelID.
Service for looking up page revisions.