1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
<?php
namespace MediaWiki\Rest\Handler;
use MediaWiki\Api\IApiMessage;
use MediaWiki\Config\Config;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\Request\WebResponse;
use MediaWiki\Rest\LocalizedHttpException;
use MediaWiki\Rest\Response;
use MediaWiki\Rest\TokenAwareHandlerTrait;
use MediaWiki\Rest\Validator\Validator;
use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Title\TitleFormatter;
use MediaWiki\Title\TitleParser;
use RuntimeException;
use Wikimedia\Message\MessageValue;
/**
* Base class for REST API handlers that perform page edits (main slot only).
*/
abstract class EditHandler extends ActionModuleBasedHandler {
use TokenAwareHandlerTrait;
protected Config $config;
protected IContentHandlerFactory $contentHandlerFactory;
protected TitleParser $titleParser;
protected TitleFormatter $titleFormatter;
protected RevisionLookup $revisionLookup;
public function __construct(
Config $config,
IContentHandlerFactory $contentHandlerFactory,
TitleParser $titleParser,
TitleFormatter $titleFormatter,
RevisionLookup $revisionLookup
) {
$this->config = $config;
$this->contentHandlerFactory = $contentHandlerFactory;
$this->titleParser = $titleParser;
$this->titleFormatter = $titleFormatter;
$this->revisionLookup = $revisionLookup;
}
public function needsWriteAccess() {
return true;
}
/**
* Returns the requested title.
*
* @return string
*/
abstract protected function getTitleParameter();
/**
* @inheritDoc
*/
public function validate( Validator $restValidator ) {
parent::validate( $restValidator );
$this->validateToken( true );
}
/**
* @inheritDoc
*/
protected function mapActionModuleResult( array $data ) {
if ( isset( $data['error'] ) ) {
throw new LocalizedHttpException( new MessageValue( 'apierror-' . $data['error'] ), 400 );
}
if ( !isset( $data['edit'] ) || !$data['edit']['result'] ) {
throw new RuntimeException( 'Bad result structure received from ApiEditPage' );
}
if ( $data['edit']['result'] !== 'Success' ) {
// Probably an edit conflict
// TODO: which code for null edits?
throw new LocalizedHttpException(
new MessageValue( "rest-edit-conflict", [ $data['edit']['result'] ] ),
409
);
}
$title = $this->titleParser->parseTitle( $data['edit']['title'] );
// This seems wasteful. This is the downside of delegating to the action API module:
// if we need additional data in the response, we have to load it.
$revision = $this->revisionLookup->getRevisionById( (int)$data['edit']['newrevid'] );
$content = $revision->getContent( SlotRecord::MAIN );
return [
'id' => $data['edit']['pageid'],
'title' => $this->titleFormatter->getPrefixedText( $title ),
'key' => $this->titleFormatter->getPrefixedDBkey( $title ),
'latest' => [
'id' => $data['edit']['newrevid'],
'timestamp' => $data['edit']['newtimestamp'],
],
'license' => [
'url' => $this->config->get( MainConfigNames::RightsUrl ),
'title' => $this->config->get( MainConfigNames::RightsText )
],
'content_model' => $data['edit']['contentmodel'],
'source' => $content->serialize(),
];
}
/**
* @inheritDoc
*/
protected function throwHttpExceptionForActionModuleError( IApiMessage $msg, $statusCode = 400 ) {
$code = $msg->getApiCode();
if ( $code === 'protectedpage' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 403 );
}
if ( $code === 'badtoken' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 403 );
}
if ( $code === 'missingtitle' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 404 );
}
if ( $code === 'articleexists' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409 );
}
if ( $code === 'editconflict' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409 );
}
if ( $code === 'ratelimited' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 429 );
}
// Fall through to generic handling of the error (status 400).
parent::throwHttpExceptionForActionModuleError( $msg, $statusCode );
}
protected function mapActionModuleResponse(
WebResponse $actionModuleResponse,
array $actionModuleResult,
Response $response
) {
parent::mapActionModuleResponse(
$actionModuleResponse,
$actionModuleResult,
$response
);
if ( $actionModuleResult['edit']['new'] ?? false ) {
$response->setStatus( 201 );
}
}
}
|