I wrote the code that needs to be fixed :( Bug: T249183 Change-Id: Ibdf4836962e39b7a0fe316795bd23a630172e31f
301 lines
7.2 KiB
PHP
301 lines
7.2 KiB
PHP
<?php
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
use MediaWiki\Permissions\PermissionManager;
|
|
use MediaWiki\Revision\SlotRecord;
|
|
|
|
/**
|
|
* Helper class to change the content model of pages
|
|
*
|
|
* For creating new pages via the action API,
|
|
* use the edit api and specify the desired content model and format.
|
|
*
|
|
* @since 1.35
|
|
* @author DannyS712
|
|
*/
|
|
class ContentModelChange {
|
|
|
|
/**
|
|
* @var User user making the change
|
|
*/
|
|
private $user;
|
|
|
|
/**
|
|
* @var PermissionManager permission manager to user
|
|
*/
|
|
private $permManager;
|
|
|
|
/**
|
|
* @var WikiPage
|
|
*/
|
|
private $page;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $newModel;
|
|
|
|
/**
|
|
* @var string[] tags to add
|
|
*/
|
|
private $tags;
|
|
|
|
/**
|
|
* @var Content
|
|
*/
|
|
private $newContent;
|
|
|
|
/**
|
|
* @var int|false latest revision id, or false if creating
|
|
*/
|
|
private $latestRevId;
|
|
|
|
/**
|
|
* @var string 'new' or 'change'
|
|
*/
|
|
private $logAction;
|
|
|
|
/**
|
|
* @var string 'apierror-' or empty string, for status messages
|
|
*/
|
|
private $msgPrefix;
|
|
|
|
public function __construct(
|
|
User $user,
|
|
PermissionManager $permManager,
|
|
WikiPage $page,
|
|
$newModel
|
|
) {
|
|
$this->user = $user;
|
|
$this->permManager = $permManager;
|
|
$this->page = $page;
|
|
$this->newModel = $newModel;
|
|
|
|
// SpecialChangeContentModel doesn't support tags
|
|
// api can specify tags via ::setTags, which also checks if user can add
|
|
// the tags specified
|
|
$this->tags = [];
|
|
|
|
// Requires createNewContent to be called first
|
|
$this->logAction = '';
|
|
|
|
// Defaults to nothing, for special page
|
|
$this->msgPrefix = '';
|
|
}
|
|
|
|
/**
|
|
* Set the message prefix
|
|
*
|
|
* @param string $msgPrefix
|
|
*/
|
|
public function setMessagePrefix( $msgPrefix ) {
|
|
$this->msgPrefix = $msgPrefix;
|
|
}
|
|
|
|
/**
|
|
* Check user can edit and editcontentmodel before and after
|
|
*
|
|
* @return array from wfMergeErrorArrays
|
|
*/
|
|
public function checkPermissions() {
|
|
$user = $this->user;
|
|
$current = $this->page->getTitle();
|
|
$titleWithNewContentModel = clone $current;
|
|
$titleWithNewContentModel->setContentModel( $this->newModel );
|
|
|
|
$pm = $this->permManager;
|
|
$errors = wfMergeErrorArrays(
|
|
// edit the contentmodel of the page
|
|
$pm->getPermissionErrors( 'editcontentmodel', $user, $current ),
|
|
// edit the page under the old content model
|
|
$pm->getPermissionErrors( 'edit', $user, $current ),
|
|
// edit the contentmodel under the new content model
|
|
$pm->getPermissionErrors( 'editcontentmodel', $user, $titleWithNewContentModel ),
|
|
// edit the page under the new content model
|
|
$pm->getPermissionErrors( 'edit', $user, $titleWithNewContentModel )
|
|
);
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Specify the tags the user wants to add, and check permissions
|
|
*
|
|
* @param string[] $tags
|
|
* @return Status
|
|
*/
|
|
public function setTags( $tags ) {
|
|
$tagStatus = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->user );
|
|
if ( $tagStatus->isOK() ) {
|
|
$this->tags = $tags;
|
|
return Status::newGood();
|
|
} else {
|
|
return $tagStatus;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create the new content
|
|
*
|
|
* @return Status
|
|
*/
|
|
private function createNewContent() {
|
|
$contentHandlerFactory = MediaWikiServices::getInstance()->getContentHandlerFactory();
|
|
$revLookup = MediaWikiServices::getInstance()->getRevisionLookup();
|
|
|
|
$title = $this->page->getTitle();
|
|
$latestRevRecord = $revLookup->getRevisionByTitle( $title );
|
|
|
|
if ( $latestRevRecord ) {
|
|
$latestContent = $latestRevRecord->getContent( SlotRecord::MAIN );
|
|
$latestHandler = $latestContent->getContentHandler();
|
|
$latestModel = $latestContent->getModel();
|
|
if ( !$latestHandler->supportsDirectEditing() ) {
|
|
// Only reachable via api
|
|
return Status::newFatal(
|
|
'apierror-changecontentmodel-nodirectediting',
|
|
ContentHandler::getLocalizedName( $latestModel )
|
|
);
|
|
}
|
|
|
|
$newModel = $this->newModel;
|
|
if ( $newModel === $latestModel ) {
|
|
// Only reachable via api
|
|
return Status::newFatal( 'apierror-nochanges' );
|
|
}
|
|
$newHandler = $contentHandlerFactory->getContentHandler( $newModel );
|
|
if ( !$newHandler->canBeUsedOn( $title ) ) {
|
|
// Only reachable via api
|
|
return Status::newFatal(
|
|
'apierror-changecontentmodel-cannotbeused',
|
|
ContentHandler::getLocalizedName( $newModel ),
|
|
Message::plaintextParam( $title->getPrefixedText() )
|
|
);
|
|
}
|
|
|
|
try {
|
|
$newContent = $newHandler->unserializeContent(
|
|
$latestContent->serialize()
|
|
);
|
|
} catch ( MWException $e ) {
|
|
// Messages: changecontentmodel-cannot-convert,
|
|
// apierror-changecontentmodel-cannot-convert
|
|
return Status::newFatal(
|
|
$this->msgPrefix . 'changecontentmodel-cannot-convert',
|
|
Message::plaintextParam( $title->getPrefixedText() ),
|
|
ContentHandler::getLocalizedName( $newModel )
|
|
);
|
|
}
|
|
$this->latestRevId = $latestRevRecord->getId();
|
|
$this->logAction = 'change';
|
|
} else {
|
|
// Page doesn't exist, create an empty content object
|
|
$newContent = $contentHandlerFactory
|
|
->getContentHandler( $this->newModel )
|
|
->makeEmptyContent();
|
|
$this->latestRevId = false;
|
|
$this->logAction = 'new';
|
|
}
|
|
$this->newContent = $newContent;
|
|
return Status::newGood();
|
|
}
|
|
|
|
/**
|
|
* Handle change and logging after validatio
|
|
*
|
|
* Can still be intercepted by hooks
|
|
*
|
|
* @param IContextSource $context
|
|
* @param string $comment
|
|
* @param bool $bot Mark as a bot edit if the user can
|
|
* @return Status
|
|
* @throws ThrottledError
|
|
*/
|
|
public function doContentModelChange(
|
|
IContextSource $context,
|
|
$comment,
|
|
$bot
|
|
) {
|
|
$status = $this->createNewContent();
|
|
if ( !$status->isGood() ) {
|
|
return $status;
|
|
}
|
|
|
|
$page = $this->page;
|
|
$title = $page->getTitle();
|
|
$user = $this->user;
|
|
|
|
if ( $user->pingLimiter( 'editcontentmodel' ) ) {
|
|
throw new ThrottledError();
|
|
}
|
|
|
|
// Create log entry
|
|
$log = new ManualLogEntry( 'contentmodel', $this->logAction );
|
|
$log->setPerformer( $user );
|
|
$log->setTarget( $title );
|
|
$log->setComment( $comment );
|
|
$log->setParameters( [
|
|
'4::oldmodel' => $title->getContentModel(),
|
|
'5::newmodel' => $this->newModel
|
|
] );
|
|
$log->addTags( $this->tags );
|
|
|
|
$formatter = LogFormatter::newFromEntry( $log );
|
|
$formatter->setContext( RequestContext::newExtraneousContext( $title ) );
|
|
$reason = $formatter->getPlainActionText();
|
|
|
|
if ( $comment !== '' ) {
|
|
$reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment;
|
|
}
|
|
|
|
// Run edit filters
|
|
$derivativeContext = new DerivativeContext( $context );
|
|
$derivativeContext->setTitle( $title );
|
|
$derivativeContext->setWikiPage( $page );
|
|
$status = new Status();
|
|
|
|
$newContent = $this->newContent;
|
|
|
|
if ( !Hooks::run( 'EditFilterMergedContent',
|
|
[ $derivativeContext, $newContent, $status, $reason,
|
|
$user, false ] )
|
|
) {
|
|
if ( $status->isGood() ) {
|
|
// TODO: extensions should really specify an error message
|
|
$status->fatal( 'hookaborted' );
|
|
}
|
|
return $status;
|
|
}
|
|
|
|
// Make the edit
|
|
$flags = $this->latestRevId ? EDIT_UPDATE : EDIT_NEW;
|
|
$flags |= EDIT_INTERNAL;
|
|
if ( $bot && $this->permManager->userHasRight( $user, 'bot' ) ) {
|
|
$flags |= EDIT_FORCE_BOT;
|
|
}
|
|
|
|
$status = $page->doEditContent(
|
|
$newContent,
|
|
$reason,
|
|
$flags,
|
|
$this->latestRevId,
|
|
$user,
|
|
null,
|
|
$this->tags
|
|
);
|
|
|
|
if ( !$status->isOK() ) {
|
|
return $status;
|
|
}
|
|
|
|
$logid = $log->insert();
|
|
$log->publish( $logid );
|
|
|
|
$values = [
|
|
'logid' => $logid
|
|
];
|
|
|
|
return Status::newGood( $values );
|
|
}
|
|
|
|
}
|