MediaWiki REL1_34
SpecialChangeContentModel.php
Go to the documentation of this file.
1<?php
2
4
6
7 public function __construct() {
8 parent::__construct( 'ChangeContentModel', 'editcontentmodel' );
9 }
10
11 public function doesWrites() {
12 return true;
13 }
14
18 private $title;
19
25 private $oldRevision;
26
27 protected function setParameter( $par ) {
28 $par = $this->getRequest()->getVal( 'pagetitle', $par );
29 $title = Title::newFromText( $par );
30 if ( $title ) {
31 $this->title = $title;
32 $this->par = $title->getPrefixedText();
33 } else {
34 $this->par = '';
35 }
36 }
37
38 protected function postText() {
39 $text = '';
40 if ( $this->title ) {
41 $contentModelLogPage = new LogPage( 'contentmodel' );
42 $text = Xml::element( 'h2', null, $contentModelLogPage->getName()->text() );
43 $out = '';
44 LogEventsList::showLogExtract( $out, 'contentmodel', $this->title );
45 $text .= $out;
46 }
47 return $text;
48 }
49
50 protected function getDisplayFormat() {
51 return 'ooui';
52 }
53
54 protected function alterForm( HTMLForm $form ) {
55 if ( !$this->title ) {
56 $form->setMethod( 'GET' );
57 }
58
59 $this->addHelpLink( 'Help:ChangeContentModel' );
60
61 // T120576
62 $form->setSubmitTextMsg( 'changecontentmodel-submit' );
63 }
64
65 public function validateTitle( $title ) {
66 if ( !$title ) {
67 // No form input yet
68 return true;
69 }
70
71 // Already validated by HTMLForm, but if not, throw
72 // and exception instead of a fatal
73 $titleObj = Title::newFromTextThrow( $title );
74
75 $this->oldRevision = Revision::newFromTitle( $titleObj ) ?: false;
76
77 if ( $this->oldRevision ) {
78 $oldContent = $this->oldRevision->getContent();
79 if ( !$oldContent->getContentHandler()->supportsDirectEditing() ) {
80 return $this->msg( 'changecontentmodel-nodirectediting' )
81 ->params( ContentHandler::getLocalizedName( $oldContent->getModel() ) )
82 ->escaped();
83 }
84 }
85
86 return true;
87 }
88
89 protected function getFormFields() {
90 $fields = [
91 'pagetitle' => [
92 'type' => 'title',
93 'creatable' => true,
94 'name' => 'pagetitle',
95 'default' => $this->par,
96 'label-message' => 'changecontentmodel-title-label',
97 'validation-callback' => [ $this, 'validateTitle' ],
98 ],
99 ];
100 if ( $this->title ) {
101 $options = $this->getOptionsForTitle( $this->title );
102 if ( empty( $options ) ) {
103 throw new ErrorPageError(
104 'changecontentmodel-emptymodels-title',
105 'changecontentmodel-emptymodels-text',
106 [ $this->title->getPrefixedText() ]
107 );
108 }
109 $fields['pagetitle']['readonly'] = true;
110 $fields += [
111 'currentmodel' => [
112 'type' => 'text',
113 'name' => 'currentcontentmodel',
114 'default' => $this->title->getContentModel(),
115 'label-message' => 'changecontentmodel-current-label',
116 'readonly' => true
117 ],
118 'model' => [
119 'type' => 'select',
120 'name' => 'model',
121 'options' => $options,
122 'label-message' => 'changecontentmodel-model-label'
123 ],
124 'reason' => [
125 'type' => 'text',
126 'name' => 'reason',
127 'validation-callback' => function ( $reason ) {
128 $match = EditPage::matchSummarySpamRegex( $reason );
129 if ( $match ) {
130 return $this->msg( 'spamprotectionmatch', $match )->parse();
131 }
132
133 return true;
134 },
135 'label-message' => 'changecontentmodel-reason-label',
136 ],
137 ];
138 }
139
140 return $fields;
141 }
142
143 private function getOptionsForTitle( Title $title = null ) {
144 $models = ContentHandler::getContentModels();
145 $options = [];
146 foreach ( $models as $model ) {
147 $handler = ContentHandler::getForModelID( $model );
148 if ( !$handler->supportsDirectEditing() ) {
149 continue;
150 }
151 if ( $title ) {
152 if ( $title->getContentModel() === $model ) {
153 continue;
154 }
155 if ( !$handler->canBeUsedOn( $title ) ) {
156 continue;
157 }
158 }
159 $options[ContentHandler::getLocalizedName( $model )] = $model;
160 }
161
162 return $options;
163 }
164
165 public function onSubmit( array $data ) {
166 if ( $data['pagetitle'] === '' ) {
167 // Initial form view of special page, pass
168 return false;
169 }
170
171 // At this point, it has to be a POST request. This is enforced by HTMLForm,
172 // but lets be safe verify that.
173 if ( !$this->getRequest()->wasPosted() ) {
174 throw new RuntimeException( "Form submission was not POSTed" );
175 }
176
177 $this->title = Title::newFromText( $data['pagetitle'] );
178 $titleWithNewContentModel = clone $this->title;
179 $titleWithNewContentModel->setContentModel( $data['model'] );
180 $user = $this->getUser();
181 // Check permissions and make sure the user has permission to:
182 $errors = wfMergeErrorArrays(
183 // edit the contentmodel of the page
184 $this->title->getUserPermissionsErrors( 'editcontentmodel', $user ),
185 // edit the page under the old content model
186 $this->title->getUserPermissionsErrors( 'edit', $user ),
187 // edit the contentmodel under the new content model
188 $titleWithNewContentModel->getUserPermissionsErrors( 'editcontentmodel', $user ),
189 // edit the page under the new content model
190 $titleWithNewContentModel->getUserPermissionsErrors( 'edit', $user )
191 );
192 if ( $errors ) {
193 $out = $this->getOutput();
194 $wikitext = $out->formatPermissionsErrorMessage( $errors );
195 // Hack to get our wikitext parsed
196 return Status::newFatal( new RawMessage( '$1', [ $wikitext ] ) );
197 }
198
199 $page = WikiPage::factory( $this->title );
200 if ( $this->oldRevision === null ) {
201 $this->oldRevision = $page->getRevision() ?: false;
202 }
203 $oldModel = $this->title->getContentModel();
204 if ( $this->oldRevision ) {
205 $oldContent = $this->oldRevision->getContent();
206 try {
207 $newContent = ContentHandler::makeContent(
208 $oldContent->serialize(), $this->title, $data['model']
209 );
210 } catch ( MWException $e ) {
211 return Status::newFatal(
212 $this->msg( 'changecontentmodel-cannot-convert' )
213 ->params(
214 $this->title->getPrefixedText(),
215 ContentHandler::getLocalizedName( $data['model'] )
216 )
217 );
218 }
219 } else {
220 // Page doesn't exist, create an empty content object
221 $newContent = ContentHandler::getForModelID( $data['model'] )->makeEmptyContent();
222 }
223
224 // All other checks have passed, let's check rate limits
225 if ( $user->pingLimiter( 'editcontentmodel' ) ) {
226 throw new ThrottledError();
227 }
228
229 $flags = $this->oldRevision ? EDIT_UPDATE : EDIT_NEW;
230 $flags |= EDIT_INTERNAL;
231 if ( MediaWikiServices::getInstance()
233 ->userHasRight( $user, 'bot' )
234 ) {
235 $flags |= EDIT_FORCE_BOT;
236 }
237
238 $log = new ManualLogEntry( 'contentmodel', $this->oldRevision ? 'change' : 'new' );
239 $log->setPerformer( $user );
240 $log->setTarget( $this->title );
241 $log->setComment( $data['reason'] );
242 $log->setParameters( [
243 '4::oldmodel' => $oldModel,
244 '5::newmodel' => $data['model']
245 ] );
246
247 $formatter = LogFormatter::newFromEntry( $log );
248 $formatter->setContext( RequestContext::newExtraneousContext( $this->title ) );
249 $reason = $formatter->getPlainActionText();
250 if ( $data['reason'] !== '' ) {
251 $reason .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $data['reason'];
252 }
253
254 // Run edit filters
255 $derivativeContext = new DerivativeContext( $this->getContext() );
256 $derivativeContext->setTitle( $this->title );
257 $derivativeContext->setWikiPage( $page );
258 $status = new Status();
259 if ( !Hooks::run( 'EditFilterMergedContent',
260 [ $derivativeContext, $newContent, $status, $reason,
261 $user, false ] )
262 ) {
263 if ( $status->isGood() ) {
264 // TODO: extensions should really specify an error message
265 $status->fatal( 'hookaborted' );
266 }
267 return $status;
268 }
269
270 $status = $page->doEditContent(
271 $newContent,
272 $reason,
273 $flags,
274 $this->oldRevision ? $this->oldRevision->getId() : false,
275 $user
276 );
277 if ( !$status->isOK() ) {
278 return $status;
279 }
280
281 $logid = $log->insert();
282 $log->publish( $logid );
283
284 return $status;
285 }
286
287 public function onSuccess() {
288 $out = $this->getOutput();
289 $out->setPageTitle( $this->msg( 'changecontentmodel-success-title' ) );
290 $out->addWikiMsg( 'changecontentmodel-success-text', $this->title );
291 }
292
301 public function prefixSearchSubpages( $search, $limit, $offset ) {
302 return $this->prefixSearchString( $search, $limit, $offset );
303 }
304
305 protected function getGroupName() {
306 return 'pagetools';
307 }
308}
getPermissionManager()
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
An IContextSource implementation which will inherit context from another source but allow individual ...
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match.
An error page which can definitely be safely rendered using the OutputPage.
Special page which uses an HTMLForm to handle processing.
string null $par
The sub-page of the special page.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:131
setMethod( $method='post')
Set the method used to submit the form.
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Class to simplify the use of log pages.
Definition LogPage.php:33
MediaWiki exception.
Class for creating new log entries and inserting them into the database.
setPerformer(UserIdentity $performer)
Set the user that performed the action being logged.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Variant of the Message class.
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition Revision.php:138
Revision bool null $oldRevision
A Revision object, false if no revision exists, null if not loaded yet.
setParameter( $par)
Maybe do something interesting with the subpage parameter.
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getFormFields()
Get an HTMLForm descriptor array.
postText()
Add post-text to the form.
getDisplayFormat()
Get display format for the form.
doesWrites()
Indicates whether this special page may perform database writes.
onSubmit(array $data)
Process the form on POST submission.
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getRequest()
Get the WebRequest being used for this instance.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
Show an error when the user hits a rate limit.
Represents a title within MediaWiki.
Definition Title.php:42
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1049
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition Title.php:1099
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1818
const EDIT_FORCE_BOT
Definition Defines.php:145
const EDIT_INTERNAL
Definition Defines.php:148
const EDIT_UPDATE
Definition Defines.php:142
const EDIT_NEW
Definition Defines.php:141
return true
Definition router.php:94