Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
DeleteTranslatableBundleSpecialPage.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\PageTranslation;
5
6use ErrorPageError;
7use HTMLForm;
11use MediaWiki\Permissions\PermissionManager;
12use MediaWiki\SpecialPage\UnlistedSpecialPage;
13use MediaWiki\Title\Title;
14use OutputPage;
15use PermissionsError;
16use ReadOnlyError;
17use WebRequest;
18use Xml;
19
26class DeleteTranslatableBundleSpecialPage extends UnlistedSpecialPage {
27 // Basic form parameters both as text and as titles
28 private string $text;
29 private ?Title $title;
30 // Other form parameters
32 private string $reason;
34 private bool $doSubpages = false;
36 private ?string $code;
37 private PermissionManager $permissionManager;
38 private TranslatableBundleDeleter $bundleDeleter;
39 private TranslatableBundleFactory $bundleFactory;
40 private string $entityType;
41 private const PAGE_TITLE_MSG = [
42 'messagebundle' => 'pt-deletepage-mb-title',
43 'translatablepage' => 'pt-deletepage-tp-title',
44 'translationpage' => 'pt-deletepage-lang-title'
45 ];
46 private const WRAPPER_LEGEND_MSG = [
47 'messagebundle' => 'pt-deletepage-mb-legend',
48 'translatablepage' => 'pt-deletepage-tp-title',
49 'translationpage' => 'pt-deletepage-tp-legend'
50 ];
51 private const LOG_PAGE = [
52 'messagebundle' => 'Special:Log/messagebundle',
53 'translatablepage' => 'Special:Log/pagetranslation',
54 'translationpage' => 'Special:Log/pagetranslation'
55 ];
56
57 public function __construct(
58 PermissionManager $permissionManager,
59 TranslatableBundleDeleter $bundleDeleter,
60 TranslatableBundleFactory $bundleFactory
61 ) {
62 parent::__construct( 'PageTranslationDeletePage', 'pagetranslation' );
63 $this->permissionManager = $permissionManager;
64 $this->bundleFactory = $bundleFactory;
65 $this->bundleDeleter = $bundleDeleter;
66 }
67
68 public function doesWrites() {
69 return true;
70 }
71
72 public function execute( $par ) {
73 $this->addHelpLink( 'Help:Deletion_and_undeletion' );
74
75 $request = $this->getRequest();
76
77 $par = (string)$par;
78
79 // Yes, the use of getVal() and getText() is wanted, see bug T22365
80 $this->text = $request->getVal( 'wpTitle', $par );
81 $this->title = Title::newFromText( $this->text );
82 $this->reason = $this->getDeleteReason( $request );
83 $this->doSubpages = $request->getBool( 'subpages' );
84
85 if ( !$this->doBasicChecks() ) {
86 return;
87 }
88
89 $out = $this->getOutput();
90
91 // Real stuff starts here
92 $entityType = $this->identifyEntityType();
93 if ( !$entityType ) {
94 throw new ErrorPageError( 'pt-deletepage-invalid-title', 'pt-deletepage-invalid-text' );
95 }
96 $this->entityType = $entityType;
97
98 if ( $this->isTranslation() ) {
99 [ , $this->code ] = Utilities::figureMessage( $this->title->getText() );
100 } else {
101 $this->code = null;
102 }
103
104 $out->setPageTitleMsg(
105 $this->msg( self::PAGE_TITLE_MSG[ $this->entityType ], $this->title->getPrefixedText() )
106 );
107
108 if ( !$this->getUser()->isAllowed( 'pagetranslation' ) ) {
109 throw new PermissionsError( 'pagetranslation' );
110 }
111
112 // Is there really no better way to do this?
113 $subactionText = $request->getText( 'subaction' );
114 switch ( $subactionText ) {
115 case $this->msg( 'pt-deletepage-action-check' )->text():
116 $subaction = 'check';
117 break;
118 case $this->msg( 'pt-deletepage-action-perform' )->text():
119 $subaction = 'perform';
120 break;
121 case $this->msg( 'pt-deletepage-action-other' )->text():
122 $subaction = '';
123 break;
124 default:
125 $subaction = '';
126 }
127
128 if ( $subaction === 'check' && $this->checkToken() && $request->wasPosted() ) {
129 $this->showConfirmation();
130 } elseif ( $subaction === 'perform' && $this->checkToken() && $request->wasPosted() ) {
131 $this->performAction();
132 } else {
133 $this->showForm();
134 }
135 }
136
142 private function doBasicChecks(): bool {
143 // Check rights
144 if ( !$this->userCanExecute( $this->getUser() ) ) {
145 $this->displayRestrictionError();
146 }
147
148 if ( $this->title === null ) {
149 throw new ErrorPageError( 'notargettitle', 'notargettext' );
150 }
151
152 if ( !$this->title->exists() ) {
153 throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
154 }
155
156 $permissionErrors = $this->permissionManager->getPermissionErrors(
157 'delete', $this->getUser(), $this->title
158 );
159 if ( count( $permissionErrors ) ) {
160 throw new PermissionsError( 'delete', $permissionErrors );
161 }
162
163 # Check for database lock
164 $this->checkReadOnly();
165
166 // Let the caller know it's safe to continue
167 return true;
168 }
169
174 private function checkToken(): bool {
175 return $this->getContext()->getCsrfTokenSet()->matchTokenField( 'wpEditToken' );
176 }
177
179 private function showForm(): void {
180 $out = $this->getOutput();
181 $out->addBacklinkSubtitle( $this->title );
182 $out->addWikiMsg( 'pt-deletepage-intro', self::LOG_PAGE[ $this->entityType ] );
183
184 $formDescriptor = $this->getCommonFormFields();
185
186 HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
187 ->setAction( $this->getPageTitle( $this->text )->getLocalURL() )
188 ->setSubmitName( 'subaction' )
189 ->setSubmitTextMsg( 'pt-deletepage-action-check' )
190 ->setWrapperLegendMsg( 'pt-deletepage-any-legend' )
191 ->prepareForm()
192 ->displayForm( false );
193 }
194
199 private function showConfirmation(): void {
200 $out = $this->getOutput();
201 $count = 0;
202 $subpageCount = 0;
203
204 $out->addBacklinkSubtitle( $this->title );
205 $out->addWikiMsg( 'pt-deletepage-intro', self::LOG_PAGE[ $this->entityType ] );
206
207 $subpages = $this->bundleDeleter->getPagesForDeletion( $this->title, $this->code, $this->isTranslation() );
208
209 $out->wrapWikiMsg( '== $1 ==', 'pt-deletepage-list-pages' );
210
211 if ( !$this->isTranslation() ) {
212 $count++;
213 $out->addWikiTextAsInterface(
214 $this->getChangeLine( $this->title )
215 );
216 }
217
218 $out->wrapWikiMsg( '=== $1 ===', 'pt-deletepage-list-translation' );
219 $lines = [];
220 foreach ( $subpages[ 'translationPages' ] as $old ) {
221 $count++;
222 $lines[] = $this->getChangeLine( $old );
223 }
224 $this->listPages( $out, $lines );
225
226 $out->wrapWikiMsg( '=== $1 ===', 'pt-deletepage-list-section' );
227
228 $lines = [];
229 foreach ( $subpages[ 'translationUnitPages' ] as $old ) {
230 $count++;
231 $lines[] = $this->getChangeLine( $old );
232 }
233 $this->listPages( $out, $lines );
234
235 if ( Utilities::allowsSubpages( $this->title ) ) {
236 $out->wrapWikiMsg( '=== $1 ===', 'pt-deletepage-list-other' );
237 $subpages = $subpages[ 'normalSubpages' ];
238 $lines = [];
239 foreach ( $subpages as $old ) {
240 $subpageCount++;
241 $lines[] = $this->getChangeLine( $old );
242 }
243 $this->listPages( $out, $lines );
244 }
245
246 $totalPageCount = $count + $subpageCount;
247
248 $out->addWikiTextAsInterface( "----\n" );
249 $out->addWikiMsg(
250 'pt-deletepage-list-count',
251 $this->getLanguage()->formatNum( $totalPageCount ),
252 $this->getLanguage()->formatNum( $subpageCount )
253 );
254
255 $formDescriptor = $this->getCommonFormFields();
256 $formDescriptor['subpages'] = [
257 'type' => 'check',
258 'name' => 'subpages',
259 'id' => 'mw-subpages',
260 'label' => $this->msg( 'pt-deletepage-subpages' )->text(),
261 'default' => $this->doSubpages,
262 ];
263
264 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
265 $htmlForm
266 ->setWrapperLegendMsg(
267 $this->msg( self::WRAPPER_LEGEND_MSG[ $this->entityType ], $this->title->getPrefixedText() )
268 )
269 ->setAction( $this->getPageTitle( $this->text )->getLocalURL() )
270 ->setSubmitTextMsg( 'pt-deletepage-action-perform' )
271 ->setSubmitName( 'subaction' )
272 ->setSubmitDestructive()
273 ->addButton( [
274 'name' => 'subaction',
275 'value' => $this->msg( 'pt-deletepage-action-other' )->text(),
276 ] )
277 ->prepareForm()
278 ->displayForm( false );
279 }
280
282 private function getChangeLine( Title $title ): string {
283 return '* ' . $title->getPrefixedText();
284 }
285
286 private function performAction(): void {
287 $this->bundleDeleter->deleteAsynchronously(
288 $this->title,
289 $this->isTranslation(),
290 $this->getUser(),
291 $this->bundleDeleter->getPagesForDeletion( $this->title, $this->code, $this->isTranslation() ),
292 $this->doSubpages,
293 $this->reason
294 );
295
296 $this->getOutput()->addWikiMsg( 'pt-deletepage-started', self::LOG_PAGE[ $this->entityType ] );
297 }
298
299 private function getCommonFormFields(): array {
300 $dropdownOptions = $this->msg( 'deletereason-dropdown' )->inContentLanguage()->text();
301
302 $options = Xml::listDropdownOptions(
303 $dropdownOptions,
304 [
305 'other' => $this->msg( 'pt-deletepage-reason-other' )->inContentLanguage()->text()
306 ]
307 );
308
309 return [
310 'wpTitle' => [
311 'type' => 'text',
312 'name' => 'wpTitle',
313 'label-message' => 'pt-deletepage-current',
314 'size' => 30,
315 'default' => $this->title->getPrefixedText(),
316 'readonly' => true,
317 ],
318 'wpDeleteReasonList' => [
319 'type' => 'select',
320 'name' => 'wpDeleteReasonList',
321 'label-message' => 'pt-deletepage-reason',
322 'options' => $options,
323 ],
324 'wpReason' => [
325 'type' => 'text',
326 'name' => 'wpReason',
327 'label-message' => 'pt-deletepage-reason-details',
328 'default' => $this->reason,
329 ]
330 ];
331 }
332
333 private function listPages( OutputPage $out, array $lines ): void {
334 if ( $lines ) {
335 $out->addWikiTextAsInterface( implode( "\n", $lines ) );
336 } else {
337 $out->addWikiMsg( 'pt-deletepage-list-no-pages' );
338 }
339 }
340
341 private function getDeleteReason( WebRequest $request ): string {
342 $dropdownSelection = $request->getText( 'wpDeleteReasonList', 'other' );
343 $reasonInput = $request->getText( 'wpReason' );
344
345 if ( $dropdownSelection === 'other' ) {
346 return $reasonInput;
347 } elseif ( $reasonInput !== '' ) {
348 // Entry from drop down menu + additional comment
349 $separator = $this->msg( 'colon-separator' )->inContentLanguage()->text();
350 return "$dropdownSelection$separator$reasonInput";
351 } else {
352 return $dropdownSelection;
353 }
354 }
355
357 private function identifyEntityType(): ?string {
358 $bundle = $this->bundleFactory->getBundle( $this->title );
359 if ( $bundle ) {
360 if ( $bundle instanceof MessageBundle ) {
361 return 'messagebundle';
362 } else {
363 return 'translatablepage';
364 }
365 } elseif ( TranslatablePage::isTranslationPage( $this->title ) ) {
366 return 'translationpage';
367 }
368
369 return null;
370 }
371
372 private function isTranslation(): bool {
373 return $this->entityType === 'translationpage';
374 }
375}
Create instances of various classes based on the type of TranslatableBundle.
figureMessage()
Recommended to use getCode and getKey instead.
Special page which enables deleting translations of translatable bundles and translation pages.
Contains the core logic to delete translatable bundles or translation pages.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31