MediaWiki  master
ContentModelChange.php
Go to the documentation of this file.
1 <?php
2 
12 
23 
26 
28  private $hookRunner;
29 
31  private $revLookup;
32 
34  private $userFactory;
35 
37  private $performer;
38 
40  private $page;
41 
43  private $newModel;
44 
46  private $tags;
47 
49  private $newContent;
50 
52  private $latestRevId;
53 
55  private $logAction;
56 
58  private $msgPrefix;
59 
69  public function __construct(
71  HookContainer $hookContainer,
76  string $newModel
77  ) {
78  $this->contentHandlerFactory = $contentHandlerFactory;
79  $this->hookRunner = new HookRunner( $hookContainer );
80  $this->revLookup = $revLookup;
81  $this->userFactory = $userFactory;
82 
83  $this->performer = $performer;
84  $this->page = $page;
85  $this->newModel = $newModel;
86 
87  // SpecialChangeContentModel doesn't support tags
88  // api can specify tags via ::setTags, which also checks if user can add
89  // the tags specified
90  $this->tags = [];
91 
92  // Requires createNewContent to be called first
93  $this->logAction = '';
94 
95  // Defaults to nothing, for special page
96  $this->msgPrefix = '';
97  }
98 
102  public function setMessagePrefix( $msgPrefix ) {
103  $this->msgPrefix = $msgPrefix;
104  }
105 
110  private function authorizeInternal( callable $authorizer ): PermissionStatus {
111  $current = $this->page->getTitle();
112  $titleWithNewContentModel = clone $current;
113  $titleWithNewContentModel->setContentModel( $this->newModel );
114 
115  $status = PermissionStatus::newEmpty();
116  $authorizer( 'editcontentmodel', $current, $status );
117  $authorizer( 'edit', $current, $status );
118  $authorizer( 'editcontentmodel', $titleWithNewContentModel, $status );
119  $authorizer( 'edit', $titleWithNewContentModel, $status );
120  return $status;
121  }
122 
132  public function probablyCanChange(): PermissionStatus {
133  return $this->authorizeInternal(
134  function ( string $action, PageIdentity $target, PermissionStatus $status ) {
135  return $this->performer->probablyCan( $action, $target, $status );
136  }
137  );
138  }
139 
149  public function authorizeChange(): PermissionStatus {
150  return $this->authorizeInternal(
151  function ( string $action, PageIdentity $target, PermissionStatus $status ) {
152  return $this->performer->authorizeWrite( $action, $target, $status );
153  }
154  );
155  }
156 
163  public function checkPermissions() {
164  wfDeprecated( __METHOD__, '1.36' );
165  $status = $this->authorizeInternal(
166  function ( string $action, PageIdentity $target, PermissionStatus $status ) {
167  return $this->performer->definitelyCan( $action, $target, $status );
168  } );
169  return $status->toLegacyErrorArray();
170  }
171 
178  public function setTags( $tags ) {
179  $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->performer );
180  if ( $tagStatus->isOK() ) {
181  $this->tags = $tags;
182  return Status::newGood();
183  } else {
184  return $tagStatus;
185  }
186  }
187 
191  private function createNewContent() {
193 
194  $title = $this->page->getTitle();
195  $latestRevRecord = $this->revLookup->getRevisionByTitle( $title );
196 
197  if ( $latestRevRecord ) {
198  $latestContent = $latestRevRecord->getContent( SlotRecord::MAIN );
199  $latestHandler = $latestContent->getContentHandler();
200  $latestModel = $latestContent->getModel();
201  if ( !$latestHandler->supportsDirectEditing() ) {
202  // Only reachable via api
203  return Status::newFatal(
204  'apierror-changecontentmodel-nodirectediting',
205  ContentHandler::getLocalizedName( $latestModel )
206  );
207  }
208 
210  if ( $newModel === $latestModel ) {
211  // Only reachable via api
212  return Status::newFatal( 'apierror-nochanges' );
213  }
215  if ( !$newHandler->canBeUsedOn( $title ) ) {
216  // Only reachable via api
217  return Status::newFatal(
218  'apierror-changecontentmodel-cannotbeused',
220  Message::plaintextParam( $title->getPrefixedText() )
221  );
222  }
223 
224  try {
225  $newContent = $newHandler->unserializeContent(
226  $latestContent->serialize()
227  );
228  } catch ( MWException $e ) {
229  // Messages: changecontentmodel-cannot-convert,
230  // apierror-changecontentmodel-cannot-convert
231  return Status::newFatal(
232  $this->msgPrefix . 'changecontentmodel-cannot-convert',
233  Message::plaintextParam( $title->getPrefixedText() ),
235  );
236  }
237  $this->latestRevId = $latestRevRecord->getId();
238  $this->logAction = 'change';
239  } else {
240  // Page doesn't exist, create an empty content object
242  ->getContentHandler( $this->newModel )
243  ->makeEmptyContent();
244  $this->latestRevId = false;
245  $this->logAction = 'new';
246  }
247  $this->newContent = $newContent;
248  return Status::newGood();
249  }
250 
262  public function doContentModelChange(
263  IContextSource $context,
264  $comment,
265  $bot
266  ) {
267  $status = $this->createNewContent();
268  if ( !$status->isGood() ) {
269  return $status;
270  }
271 
272  $page = $this->page;
273  $title = $page->getTitle();
274  $user = $this->userFactory->newFromAuthority( $this->performer );
275 
276  // TODO: fold into authorizeChange
277  if ( $user->pingLimiter( 'editcontentmodel' ) ) {
278  throw new ThrottledError();
279  }
280 
281  // Create log entry
282  $log = new ManualLogEntry( 'contentmodel', $this->logAction );
283  $log->setPerformer( $this->performer->getUser() );
284  $log->setTarget( $title );
285  $log->setComment( $comment );
286  $log->setParameters( [
287  '4::oldmodel' => $title->getContentModel(),
288  '5::newmodel' => $this->newModel
289  ] );
290  $log->addTags( $this->tags );
291 
292  $formatter = LogFormatter::newFromEntry( $log );
293  $formatter->setContext( RequestContext::newExtraneousContext( $title ) );
294  $reason = $formatter->getPlainActionText();
295 
296  if ( $comment !== '' ) {
297  $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment;
298  }
299 
300  // Run edit filters
301  $derivativeContext = new DerivativeContext( $context );
302  $derivativeContext->setTitle( $title );
303  $derivativeContext->setWikiPage( $page );
304  $status = new Status();
305 
307 
308  if ( !$this->hookRunner->onEditFilterMergedContent( $derivativeContext, $newContent,
309  $status, $reason, $user, false )
310  ) {
311  if ( $status->isGood() ) {
312  // TODO: extensions should really specify an error message
313  $status->fatal( 'hookaborted' );
314  }
315  return $status;
316  }
317 
318  // Make the edit
319  $flags = $this->latestRevId ? EDIT_UPDATE : EDIT_NEW;
320  $flags |= EDIT_INTERNAL;
321  if ( $bot && $this->performer->isAllowed( 'bot' ) ) {
322  $flags |= EDIT_FORCE_BOT;
323  }
324 
325  $status = $page->doUserEditContent(
326  $newContent,
327  $this->performer,
328  $reason,
329  $flags,
330  $this->latestRevId,
331  $this->tags
332  );
333 
334  if ( !$status->isOK() ) {
335  return $status;
336  }
337 
338  $logid = $log->insert();
339  $log->publish( $logid );
340 
341  $values = [
342  'logid' => $logid
343  ];
344 
345  return Status::newGood( $values );
346  }
347 
348 }
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
WikiPage\doUserEditContent
doUserEditContent(Content $content, Authority $performer, $summary, $flags=0, $originalRevId=false, $tags=[], $undidRevId=0)
Change an existing article or create a new article.
Definition: WikiPage.php:1961
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
ContentModelChange\$newModel
string $newModel
Definition: ContentModelChange.php:43
ContentModelChange\probablyCanChange
probablyCanChange()
Check whether $performer can execute the move.
Definition: ContentModelChange.php:132
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:60
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1182
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:658
MediaWiki\Permissions\PermissionStatus\toLegacyErrorArray
toLegacyErrorArray()
Returns this permission status in legacy error array format.
Definition: PermissionStatus.php:78
ContentModelChange\createNewContent
createNewContent()
Definition: ContentModelChange.php:191
ContentModelChange\setTags
setTags( $tags)
Specify the tags the user wants to add, and check permissions.
Definition: ContentModelChange.php:178
ContentModelChange\__construct
__construct(IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, RevisionLookup $revLookup, UserFactory $userFactory, Authority $performer, WikiPage $page, string $newModel)
Definition: ContentModelChange.php:69
MediaWiki\Revision\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:38
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
ContentModelChange\$hookRunner
HookRunner $hookRunner
Definition: ContentModelChange.php:28
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:33
MWException
MediaWiki exception.
Definition: MWException.php:29
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:996
ContentModelChange\setMessagePrefix
setMessagePrefix( $msgPrefix)
Definition: ContentModelChange.php:102
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:317
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:125
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:28
$title
$title
Definition: testCompression.php:38
ContentModelChange\authorizeInternal
authorizeInternal(callable $authorizer)
Definition: ContentModelChange.php:110
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
ChangeTags\canAddTagsAccompanyingChange
static canAddTagsAccompanyingChange(array $tags, Authority $performer=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:625
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:306
ContentModelChange\$page
WikiPage $page
Definition: ContentModelChange.php:40
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Message\plaintextParam
static plaintextParam( $plaintext)
Definition: Message.php:1202
ContentModelChange\$msgPrefix
string $msgPrefix
'apierror-' or empty string, for status messages
Definition: ContentModelChange.php:58
ContentModelChange\$latestRevId
int false $latestRevId
latest revision id, or false if creating
Definition: ContentModelChange.php:52
ContentModelChange\doContentModelChange
doContentModelChange(IContextSource $context, $comment, $bot)
Handle change and logging after validation.
Definition: ContentModelChange.php:262
ContentModelChange\authorizeChange
authorizeChange()
Authorize the move by $performer.
Definition: ContentModelChange.php:149
ContentModelChange\$newContent
Content $newContent
Definition: ContentModelChange.php:49
ContentModelChange\$userFactory
UserFactory $userFactory
Definition: ContentModelChange.php:34
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:126
Content
Base interface for content objects.
Definition: Content.php:35
MediaWiki\Content\IContentHandlerFactory\getContentHandler
getContentHandler(string $modelID)
Returns a ContentHandler instance for the given $modelID.
ContentModelChange\checkPermissions
checkPermissions()
Check user can edit and editcontentmodel before and after.
Definition: ContentModelChange.php:163
ContentModelChange\$revLookup
RevisionLookup $revLookup
Definition: ContentModelChange.php:31
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:44
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
ContentModelChange\$logAction
string $logAction
'new' or 'change'
Definition: ContentModelChange.php:55
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:555
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:129
ContentModelChange\$tags
string[] $tags
tags to add
Definition: ContentModelChange.php:46
MediaWiki\User\UserFactory
Creates User objects.
Definition: UserFactory.php:41
ContentModelChange\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: ContentModelChange.php:25
ContentModelChange
Helper class to change the content model of pages.
Definition: ContentModelChange.php:22
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
ContentModelChange\$performer
Authority $performer
making the change
Definition: ContentModelChange.php:37
LogFormatter\newFromEntry
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Definition: LogFormatter.php:54
EDIT_INTERNAL
const EDIT_INTERNAL
Definition: Defines.php:132