MediaWiki master
SpecialChangeContentModel.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
25
30
31 private IContentHandlerFactory $contentHandlerFactory;
32 private ContentModelChangeFactory $contentModelChangeFactory;
33 private SpamChecker $spamChecker;
34 private RevisionLookup $revisionLookup;
35 private WikiPageFactory $wikiPageFactory;
36 private SearchEngineFactory $searchEngineFactory;
37 private CollationFactory $collationFactory;
38
39 public function __construct(
40 IContentHandlerFactory $contentHandlerFactory,
41 ContentModelChangeFactory $contentModelChangeFactory,
42 SpamChecker $spamChecker,
43 RevisionLookup $revisionLookup,
44 WikiPageFactory $wikiPageFactory,
45 SearchEngineFactory $searchEngineFactory,
46 CollationFactory $collationFactory
47 ) {
48 parent::__construct( 'ChangeContentModel', 'editcontentmodel' );
49
50 $this->contentHandlerFactory = $contentHandlerFactory;
51 $this->contentModelChangeFactory = $contentModelChangeFactory;
52 $this->spamChecker = $spamChecker;
53 $this->revisionLookup = $revisionLookup;
54 $this->wikiPageFactory = $wikiPageFactory;
55 $this->searchEngineFactory = $searchEngineFactory;
56 $this->collationFactory = $collationFactory;
57 }
58
59 public function doesWrites() {
60 return true;
61 }
62
66 private $title;
67
73 private $oldRevision;
74
75 protected function setParameter( $par ) {
76 $par = $this->getRequest()->getVal( 'pagetitle', $par );
77 $title = Title::newFromText( $par );
78 if ( $title ) {
79 $this->title = $title;
80 $this->par = $title->getPrefixedText();
81 } else {
82 $this->par = '';
83 }
84 }
85
86 protected function postHtml() {
87 $text = '';
88 if ( $this->title ) {
89 $contentModelLogPage = new LogPage( 'contentmodel' );
90 $text = Html::element( 'h2', [], $contentModelLogPage->getName()->text() );
91 $out = '';
92 LogEventsList::showLogExtract( $out, 'contentmodel', $this->title );
93 $text .= $out;
94 }
95 return $text;
96 }
97
98 protected function getDisplayFormat() {
99 return 'ooui';
100 }
101
102 protected function alterForm( HTMLForm $form ) {
103 $this->addHelpLink( 'Help:ChangeContentModel' );
104
105 if ( $this->title ) {
106 $form->setFormIdentifier( 'modelform' );
107 } else {
108 $form->setFormIdentifier( 'titleform' );
109 }
110
111 // T120576
112 $form->setSubmitTextMsg( 'changecontentmodel-submit' );
113
114 if ( $this->title ) {
115 $this->getOutput()->addBacklinkSubtitle( $this->title );
116 }
117 }
118
119 public function validateTitle( $title ) {
120 // Already validated by HTMLForm, but if not, throw
121 // an exception instead of a fatal
122 $titleObj = Title::newFromTextThrow( $title );
123
124 $this->oldRevision = $this->revisionLookup->getRevisionByTitle( $titleObj ) ?: false;
125
126 if ( $this->oldRevision ) {
127 $oldContent = $this->oldRevision->getContent( SlotRecord::MAIN );
128 if ( !$oldContent->getContentHandler()->supportsDirectEditing() ) {
129 return $this->msg( 'changecontentmodel-nodirectediting' )
130 ->params( ContentHandler::getLocalizedName( $oldContent->getModel() ) )
131 ->escaped();
132 }
133 }
134
135 return true;
136 }
137
138 protected function getFormFields() {
139 $fields = [
140 'pagetitle' => [
141 'type' => 'title',
142 'creatable' => true,
143 'name' => 'pagetitle',
144 'default' => $this->par,
145 'label-message' => 'changecontentmodel-title-label',
146 'validation-callback' => [ $this, 'validateTitle' ],
147 ],
148 ];
149 if ( $this->title ) {
150 $options = $this->getOptionsForTitle( $this->title );
151 if ( !$options ) {
152 throw new ErrorPageError(
153 'changecontentmodel-emptymodels-title',
154 'changecontentmodel-emptymodels-text',
155 [ $this->title->getPrefixedText() ]
156 );
157 }
158 $fields['pagetitle']['readonly'] = true;
159 $fields += [
160 'model' => [
161 'type' => 'select',
162 'name' => 'model',
163 'default' => $this->title->getContentModel(),
164 'options' => $options,
165 'label-message' => 'changecontentmodel-model-label'
166 ],
167 'reason' => [
168 'type' => 'text',
169 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
170 'name' => 'reason',
171 'validation-callback' => function ( $reason ) {
172 if ( $reason === null || $reason === '' ) {
173 // Null on form display, or no reason given
174 return true;
175 }
176
177 $match = $this->spamChecker->checkSummary( $reason );
178
179 if ( $match ) {
180 return $this->msg( 'spamprotectionmatch', $match )->parse();
181 }
182
183 return true;
184 },
185 'label-message' => 'changecontentmodel-reason-label',
186 ],
187 ];
188 }
189
190 return $fields;
191 }
192
198 private function getOptionsForTitle( ?Title $title = null ) {
199 $models = $this->contentHandlerFactory->getContentModels();
200 $options = [];
201 foreach ( $models as $model ) {
202 $handler = $this->contentHandlerFactory->getContentHandler( $model );
203 if ( !$handler->supportsDirectEditing() ) {
204 continue;
205 }
206 if ( $title ) {
207 if ( !$handler->canBeUsedOn( $title ) ) {
208 continue;
209 }
210 }
211 $options[ContentHandler::getLocalizedName( $model )] = $model;
212 }
213
214 // Put the options in the drop-down list in alphabetical order.
215 // Sort by array key, case insensitive.
216 $collation = $this->collationFactory->getCategoryCollation();
217 uksort( $options, static function ( $a, $b ) use ( $collation ) {
218 $a = $collation->getSortKey( $a );
219 $b = $collation->getSortKey( $b );
220 return strcmp( $a, $b );
221 } );
222
223 return $options;
224 }
225
226 public function onSubmit( array $data ) {
227 $this->title = Title::newFromText( $data['pagetitle'] );
228 $page = $this->wikiPageFactory->newFromTitle( $this->title );
229
230 $changer = $this->contentModelChangeFactory->newContentModelChange(
231 $this->getContext()->getAuthority(),
232 $page,
233 $data['model']
234 );
235
236 $permissionStatus = $changer->authorizeChange();
237 if ( !$permissionStatus->isGood() ) {
238 $out = $this->getOutput();
239 $wikitext = $out->formatPermissionStatus( $permissionStatus );
240 // Hack to get our wikitext parsed
241 return Status::newFatal( new RawMessage( '$1', [ $wikitext ] ) );
242 }
243
244 $status = $changer->doContentModelChange(
245 $this->getContext(),
246 $data['reason'],
247 true
248 );
249
250 return $status;
251 }
252
253 public function onSuccess() {
254 $out = $this->getOutput();
255 $out->setPageTitleMsg( $this->msg( 'changecontentmodel-success-title' ) );
256 $out->addWikiMsg( 'changecontentmodel-success-text', $this->title );
257 }
258
267 public function prefixSearchSubpages( $search, $limit, $offset ) {
268 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
269 }
270
271 protected function getGroupName() {
272 return 'pagetools';
273 }
274}
275
277class_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.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:209
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:57
Variant of the Message class.
Class to simplify the use of log pages.
Definition LogPage.php:50
Service for creating WikiPage objects.
Page revision base class.
Value object representing a content slot associated with a page revision.
Special page which uses an HTMLForm to handle processing.
string null $par
The subpage of the special page.
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.
__construct(IContentHandlerFactory $contentHandlerFactory, ContentModelChangeFactory $contentModelChangeFactory, SpamChecker $spamChecker, RevisionLookup $revisionLookup, WikiPageFactory $wikiPageFactory, SearchEngineFactory $searchEngineFactory, CollationFactory $collationFactory)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
setParameter( $par)
Maybe do something interesting with the subpage parameter.
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
onSubmit(array $data)
Process the form on submission.
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:54
Represents a title within MediaWiki.
Definition Title.php:78
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1855
Factory class for SearchEngine.
Service for changing the content model of wiki pages.
Service for looking up page revisions.
element(SerializerNode $parent, SerializerNode $node, $contents)