MediaWiki master
SpecialChangeContentModel.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
8use LogPage;
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
48 public function __construct(
49 IContentHandlerFactory $contentHandlerFactory,
50 ContentModelChangeFactory $contentModelChangeFactory,
51 SpamChecker $spamChecker,
52 RevisionLookup $revisionLookup,
53 WikiPageFactory $wikiPageFactory,
54 SearchEngineFactory $searchEngineFactory,
55 CollationFactory $collationFactory
56 ) {
57 parent::__construct( 'ChangeContentModel', 'editcontentmodel' );
58
59 $this->contentHandlerFactory = $contentHandlerFactory;
60 $this->contentModelChangeFactory = $contentModelChangeFactory;
61 $this->spamChecker = $spamChecker;
62 $this->revisionLookup = $revisionLookup;
63 $this->wikiPageFactory = $wikiPageFactory;
64 $this->searchEngineFactory = $searchEngineFactory;
65 $this->collationFactory = $collationFactory;
66 }
67
68 public function doesWrites() {
69 return true;
70 }
71
75 private $title;
76
82 private $oldRevision;
83
84 protected function setParameter( $par ) {
85 $par = $this->getRequest()->getVal( 'pagetitle', $par );
86 $title = Title::newFromText( $par );
87 if ( $title ) {
88 $this->title = $title;
89 $this->par = $title->getPrefixedText();
90 } else {
91 $this->par = '';
92 }
93 }
94
95 protected function postHtml() {
96 $text = '';
97 if ( $this->title ) {
98 $contentModelLogPage = new LogPage( 'contentmodel' );
99 $text = Xml::element( 'h2', null, $contentModelLogPage->getName()->text() );
100 $out = '';
101 LogEventsList::showLogExtract( $out, 'contentmodel', $this->title );
102 $text .= $out;
103 }
104 return $text;
105 }
106
107 protected function getDisplayFormat() {
108 return 'ooui';
109 }
110
111 protected function alterForm( HTMLForm $form ) {
112 $this->addHelpLink( 'Help:ChangeContentModel' );
113
114 if ( $this->title ) {
115 $form->setFormIdentifier( 'modelform' );
116 } else {
117 $form->setFormIdentifier( 'titleform' );
118 }
119
120 // T120576
121 $form->setSubmitTextMsg( 'changecontentmodel-submit' );
122
123 if ( $this->title ) {
124 $this->getOutput()->addBacklinkSubtitle( $this->title );
125 }
126 }
127
128 public function validateTitle( $title ) {
129 // Already validated by HTMLForm, but if not, throw
130 // an exception instead of a fatal
131 $titleObj = Title::newFromTextThrow( $title );
132
133 $this->oldRevision = $this->revisionLookup->getRevisionByTitle( $titleObj ) ?: false;
134
135 if ( $this->oldRevision ) {
136 $oldContent = $this->oldRevision->getContent( SlotRecord::MAIN );
137 if ( !$oldContent->getContentHandler()->supportsDirectEditing() ) {
138 return $this->msg( 'changecontentmodel-nodirectediting' )
139 ->params( ContentHandler::getLocalizedName( $oldContent->getModel() ) )
140 ->escaped();
141 }
142 }
143
144 return true;
145 }
146
147 protected function getFormFields() {
148 $fields = [
149 'pagetitle' => [
150 'type' => 'title',
151 'creatable' => true,
152 'name' => 'pagetitle',
153 'default' => $this->par,
154 'label-message' => 'changecontentmodel-title-label',
155 'validation-callback' => [ $this, 'validateTitle' ],
156 ],
157 ];
158 if ( $this->title ) {
159 $options = $this->getOptionsForTitle( $this->title );
160 if ( !$options ) {
161 throw new ErrorPageError(
162 'changecontentmodel-emptymodels-title',
163 'changecontentmodel-emptymodels-text',
164 [ $this->title->getPrefixedText() ]
165 );
166 }
167 $fields['pagetitle']['readonly'] = true;
168 $fields += [
169 'model' => [
170 'type' => 'select',
171 'name' => 'model',
172 'default' => $this->title->getContentModel(),
173 'options' => $options,
174 'label-message' => 'changecontentmodel-model-label'
175 ],
176 'reason' => [
177 'type' => 'text',
178 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
179 'name' => 'reason',
180 'validation-callback' => function ( $reason ) {
181 if ( $reason === null || $reason === '' ) {
182 // Null on form display, or no reason given
183 return true;
184 }
185
186 $match = $this->spamChecker->checkSummary( $reason );
187
188 if ( $match ) {
189 return $this->msg( 'spamprotectionmatch', $match )->parse();
190 }
191
192 return true;
193 },
194 'label-message' => 'changecontentmodel-reason-label',
195 ],
196 ];
197 }
198
199 return $fields;
200 }
201
207 private function getOptionsForTitle( Title $title = null ) {
208 $models = $this->contentHandlerFactory->getContentModels();
209 $options = [];
210 foreach ( $models as $model ) {
211 $handler = $this->contentHandlerFactory->getContentHandler( $model );
212 if ( !$handler->supportsDirectEditing() ) {
213 continue;
214 }
215 if ( $title ) {
216 if ( !$handler->canBeUsedOn( $title ) ) {
217 continue;
218 }
219 }
220 $options[ContentHandler::getLocalizedName( $model )] = $model;
221 }
222
223 // Put the options in the drop-down list in alphabetical order.
224 // Sort by array key, case insensitive.
225 $collation = $this->collationFactory->getCategoryCollation();
226 uksort( $options, static function ( $a, $b ) use ( $collation ) {
227 $a = $collation->getSortKey( $a );
228 $b = $collation->getSortKey( $b );
229 return strcmp( $a, $b );
230 } );
231
232 return $options;
233 }
234
235 public function onSubmit( array $data ) {
236 $this->title = Title::newFromText( $data['pagetitle'] );
237 $page = $this->wikiPageFactory->newFromTitle( $this->title );
238
239 $changer = $this->contentModelChangeFactory->newContentModelChange(
240 $this->getContext()->getAuthority(),
241 $page,
242 $data['model']
243 );
244
245 $permissionStatus = $changer->authorizeChange();
246 if ( !$permissionStatus->isGood() ) {
247 $out = $this->getOutput();
248 $wikitext = $out->formatPermissionStatus( $permissionStatus );
249 // Hack to get our wikitext parsed
250 return Status::newFatal( new RawMessage( '$1', [ $wikitext ] ) );
251 }
252
253 $status = $changer->doContentModelChange(
254 $this->getContext(),
255 $data['reason'],
256 true
257 );
258
259 return $status;
260 }
261
262 public function onSuccess() {
263 $out = $this->getOutput();
264 $out->setPageTitleMsg( $this->msg( 'changecontentmodel-success-title' ) );
265 $out->addWikiMsg( 'changecontentmodel-success-text', $this->title );
266 }
267
276 public function prefixSearchSubpages( $search, $limit, $offset ) {
277 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
278 }
279
280 protected function getGroupName() {
281 return 'pagetools';
282 }
283}
284
286class_alias( SpecialChangeContentModel::class, 'SpecialChangeContentModel' );
A content handler knows how do deal with a specific type of content on a wiki page.
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
An error page which can definitely be safely rendered using the OutputPage.
Class to simplify the use of log pages.
Definition LogPage.php:45
Common factory to construct collation classes.
Handle database storage of comments such as edit summaries and log reasons.
Service to check if text (either content or a summary) qualifies as spam.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:207
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.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition Html.php:216
Variant of the Message class.
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 sub-page of the special page.
prefixSearchString( $search, $limit, $offset, SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
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.
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 this special page may perform database writes.
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:79
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1862
Module of static functions for generating XML.
Definition Xml.php:37
Factory class for SearchEngine.
Service for changing the content model of wiki pages.
Service for looking up page revisions.