MediaWiki master
SpecialChangeContentModel.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
27
32
33 public function __construct(
34 private readonly IContentHandlerFactory $contentHandlerFactory,
35 private readonly ContentModelChangeFactory $contentModelChangeFactory,
36 private readonly SpamChecker $spamChecker,
37 private readonly RevisionLookup $revisionLookup,
38 private readonly WikiPageFactory $wikiPageFactory,
39 private readonly SearchEngineFactory $searchEngineFactory,
40 private readonly CollationFactory $collationFactory,
41 private readonly PermissionManager $permissionManager,
42 ) {
43 parent::__construct( 'ChangeContentModel' );
44 }
45
47 public function doesWrites() {
48 return true;
49 }
50
54 private $title;
55
61 private $oldRevision;
62
63 private string $oldContentModel;
64
65 private bool $titleExisted;
66
67 private string $newContentModel;
68
70 protected function setParameter( $par ) {
71 $par = $this->getRequest()->getVal( 'pagetitle', $par );
72 $title = Title::newFromText( $par );
73 if ( $title ) {
74 $this->title = $title;
75 $this->par = $title->getPrefixedText();
76 } else {
77 $this->par = '';
78 }
79 }
80
82 protected function postHtml() {
83 $text = '';
84 if ( $this->title ) {
85 $contentModelLogPage = new LogPage( 'contentmodel' );
86 $text = Html::element( 'h2', [], $contentModelLogPage->getName()->text() );
87 $out = '';
88 LogEventsList::showLogExtract( $out, 'contentmodel', $this->title );
89 $text .= $out;
90 }
91 return $text;
92 }
93
95 protected function getDisplayFormat() {
96 return 'ooui';
97 }
98
99 protected function alterForm( HTMLForm $form ) {
100 $this->addHelpLink( 'Help:ChangeContentModel' );
101
102 if ( $this->title ) {
103 $form->setFormIdentifier( 'modelform' );
104 if ( $this->title->exists() ) {
105 // T120576
106 $form->setSubmitTextMsg( 'changecontentmodel-submit' );
107 } else {
108 $form->setSubmitTextMsg( 'changecontentmodel-create-submit' );
109 }
110 $this->getOutput()->addBacklinkSubtitle( $this->title );
111 } else {
112 $form->setFormIdentifier( 'titleform' );
113 // T120576
114 $form->setSubmitTextMsg( 'changecontentmodel-submit' );
115 }
116 }
117
119 public function checkPermissions() {
120 $user = $this->getUser();
121 if ( $this->title ) {
122 $perm = $this->title->exists() ? 'editcontentmodel' : 'createwithcontentmodel';
123 $this->permissionManager->throwPermissionErrors( $perm, $user, $this->title );
124 } elseif ( !$this->permissionManager->userHasAnyRight( $user, 'editcontentmodel', 'createwithcontentmodel' ) ) {
125 // The intended use case of this special page is to change the content model of an existing page
126 // nothing stops you from creating a new page with it but that's a hack so display the permission error
127 // for editing an existing page's content model if you can't do either
128 throw new PermissionsError( 'editcontentmodel' );
129 }
130 }
131
136 private function validateTitle( $title ) {
137 // Already validated by HTMLForm, but if not, throw
138 // an exception instead of a fatal
139 $titleObj = Title::newFromTextThrow( $title );
140
141 $this->oldRevision = $this->revisionLookup->getRevisionByTitle( $titleObj ) ?: false;
142
143 if ( $this->oldRevision ) {
144 $oldContent = $this->oldRevision->getContent( SlotRecord::MAIN );
145 if ( !$oldContent->getContentHandler()->supportsDirectEditing() ) {
146 return $this->msg( 'changecontentmodel-nodirectediting' )
147 ->params( ContentHandler::getLocalizedName( $oldContent->getModel() ) )
148 ->escaped();
149 }
150 }
151
152 return true;
153 }
154
156 protected function preHtml() {
157 if ( $this->title ) {
158 // Checking permissions is handled by checkPermissions above
159 if ( $this->title->exists() ) {
160 $msg = $this->msg( 'changecontentmodel-editing', $this->title->getPrefixedText() );
161 } else {
162 $msg = $this->msg( 'changecontentmodel-create', $this->title->getPrefixedText() );
163 }
164 } elseif ( !$this->permissionManager->userHasRight( $this->getUser(), 'editcontentmodel' ) ) {
165 $msg = $this->msg( 'changecontentmodel-create-only' );
166 } else {
167 $msg = $this->msg( 'changecontentmodel-edit' );
168 }
169 return $msg->parseAsBlock();
170 }
171
173 protected function getFormFields() {
174 $fields = [
175 'pagetitle' => [
176 'type' => 'title',
177 'creatable' => true,
178 'name' => 'pagetitle',
179 'default' => $this->par,
180 'label-message' => 'changecontentmodel-title-label',
181 'validation-callback' => $this->validateTitle( ... ),
182 // If you need to enter a non-existing page then don't show autocomplete for existing ones ...
183 'suggestions' => $this->permissionManager->userHasRight( $this->getUser(), 'editcontentmodel' ),
184 ],
185 ];
186 if ( $this->title ) {
187 $options = $this->getOptionsForTitle( $this->title );
188 if ( !$options ) {
189 throw new ErrorPageError(
190 'changecontentmodel-emptymodels-title',
191 'changecontentmodel-emptymodels-text',
192 [ $this->title->getPrefixedText() ]
193 );
194 }
195 $fields['pagetitle']['readonly'] = true;
196 $fields += [
197 'model' => [
198 'type' => 'select',
199 'name' => 'model',
200 'default' => $this->title->getContentModel(),
201 'options' => $options,
202 'label-message' => 'changecontentmodel-model-label'
203 ],
204 'reason' => [
205 'type' => 'text',
206 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
207 'name' => 'reason',
208 'validation-callback' => function ( $reason ) {
209 if ( $reason === null || $reason === '' ) {
210 // Null on form display, or no reason given
211 return true;
212 }
213
214 $match = $this->spamChecker->checkSummary( $reason );
215
216 if ( $match ) {
217 return $this->msg( 'spamprotectionmatch', $match )->parse();
218 }
219
220 return true;
221 },
222 'label-message' => 'changecontentmodel-reason-label',
223 ],
224 ];
225 }
226
227 return $fields;
228 }
229
235 private function getOptionsForTitle( ?Title $title = null ) {
236 $models = $this->contentHandlerFactory->getContentModels();
237 $options = [];
238 foreach ( $models as $model ) {
239 $handler = $this->contentHandlerFactory->getContentHandler( $model );
240 if ( !$handler->supportsDirectEditing() ) {
241 continue;
242 }
243 if ( $title ) {
244 if ( !$handler->canBeUsedOn( $title ) ) {
245 continue;
246 }
247 }
248 $options[ContentHandler::getLocalizedName( $model )] = $model;
249 }
250
251 // Put the options in the drop-down list in alphabetical order.
252 // Sort by array key, case insensitive.
253 $collation = $this->collationFactory->getCategoryCollation();
254 uksort( $options, static function ( $a, $b ) use ( $collation ) {
255 $a = $collation->getSortKey( $a );
256 $b = $collation->getSortKey( $b );
257 return strcmp( $a, $b );
258 } );
259
260 return $options;
261 }
262
264 public function onSubmit( array $data ) {
265 $this->title = Title::newFromText( $data['pagetitle'] );
266 $this->titleExisted = $this->title->exists();
267 $this->oldContentModel = $this->title->getContentModel();
268 $this->newContentModel = $data['model'];
269 $page = $this->wikiPageFactory->newFromTitle( $this->title );
270
271 $changer = $this->contentModelChangeFactory->newContentModelChange(
272 $this->getContext()->getAuthority(),
273 $page,
274 $data['model']
275 );
276
277 $permissionStatus = $changer->authorizeChange();
278 if ( !$permissionStatus->isGood() ) {
279 $out = $this->getOutput();
280 $wikitext = $out->formatPermissionStatus( $permissionStatus );
281 // Hack to get our wikitext parsed
282 return Status::newFatal( new RawMessage( '$1', [ $wikitext ] ) );
283 }
284
285 $status = $changer->doContentModelChange(
286 $this->getContext(),
287 $data['reason'],
288 true
289 );
290
291 return $status;
292 }
293
294 public function onSuccess() {
295 $out = $this->getOutput();
296 if ( $this->titleExisted ) {
297 $out->setPageTitleMsg( $this->msg( 'changecontentmodel-success-title' ) );
298 $out->addWikiMsg( 'changecontentmodel-success-text',
299 $this->title->getPrefixedText(),
300 ContentHandler::getLocalizedName( $this->oldContentModel, $this->getLanguage() ),
301 ContentHandler::getLocalizedName( $this->newContentModel, $this->getLanguage() )
302 );
303 } else {
304 $out->setPageTitleMsg( $this->msg( 'changecontentmodel-create-success-title' ) );
305 $out->addWikiMsg( 'changecontentmodel-create-success-text',
306 $this->title->getPrefixedText(),
307 ContentHandler::getLocalizedName( $this->newContentModel, $this->getLanguage() )
308 );
309 }
310 }
311
320 public function prefixSearchSubpages( $search, $limit, $offset ) {
321 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
322 }
323
325 protected function getGroupName() {
326 return 'pagetools';
327 }
328}
329
331class_alias( SpecialChangeContentModel::class, 'SpecialChangeContentModel' );
Common factory to construct collation classes.
Handle database storage of comments such as edit summaries and log reasons.
Base class for content handling.
Service to check if text (either content or a summary) qualifies as spam.
An error page which can definitely be safely rendered using the OutputPage.
Show an error when a user tries to do something they do not have the necessary permissions for.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:214
setFormIdentifier(string $ident, bool $single=false)
Set an internal identifier for this form.
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
Variant of the Message class.
Class to simplify the use of log pages.
Definition LogPage.php:34
Service for creating WikiPage objects.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Page revision base class.
Value object representing a content slot associated with a page revision.
Factory class for SearchEngine.
Special page which uses an HTMLForm to handle processing.
string null $par
The subpage of the special page.
getUser()
Shortcut to get the User executing this instance.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
prefixSearchString( $search, $limit, $offset, ?SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
getAuthority()
Shortcut to get the Authority executing this instance.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
postHtml()
Add post-HTML to the form.string HTML which will be sent to $form->addPostHtml() 1....
setParameter( $par)
Maybe do something interesting with the subpage parameter.
getDisplayFormat()
Get display format for the form.See HTMLForm documentation for available values.1....
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.to override 1.19 void
onSubmit(array $data)
Process the form on submission.bool|string|array|Status As documented for HTMLForm::trySubmit.
__construct(private readonly IContentHandlerFactory $contentHandlerFactory, private readonly ContentModelChangeFactory $contentModelChangeFactory, private readonly SpamChecker $spamChecker, private readonly RevisionLookup $revisionLookup, private readonly WikiPageFactory $wikiPageFactory, private readonly SearchEngineFactory $searchEngineFactory, private readonly CollationFactory $collationFactory, private readonly PermissionManager $permissionManager,)
getFormFields()
Get an HTMLForm descriptor array.array
preHtml()
Add pre-HTML to the form.string HTML which will be sent to $form->addPreHtml() 1.38
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
doesWrites()
Indicates whether POST requests to this special page require write access to the wiki....
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Represents a title within MediaWiki.
Definition Title.php:69
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1857
Service for changing the content model of wiki pages.
Service for looking up page revisions.
element(SerializerNode $parent, SerializerNode $node, $contents)