MediaWiki  master
ApiDelete.php
Go to the documentation of this file.
1 <?php
32 
39 class ApiDelete extends ApiBase {
40 
42 
43  private RepoGroup $repoGroup;
44  private DeletePageFactory $deletePageFactory;
45 
54  public function __construct(
55  ApiMain $mainModule,
56  $moduleName,
57  RepoGroup $repoGroup,
58  WatchlistManager $watchlistManager,
59  UserOptionsLookup $userOptionsLookup,
60  DeletePageFactory $deletePageFactory
61  ) {
62  parent::__construct( $mainModule, $moduleName );
63  $this->repoGroup = $repoGroup;
64  $this->deletePageFactory = $deletePageFactory;
65 
66  // Variables needed in ApiWatchlistTrait trait
67  $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
68  $this->watchlistMaxDuration =
69  $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
70  $this->watchlistManager = $watchlistManager;
71  $this->userOptionsLookup = $userOptionsLookup;
72  }
73 
81  public function execute() {
83 
84  $params = $this->extractRequestParams();
85 
86  $pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
87  $titleObj = $pageObj->getTitle();
88  $this->getErrorFormatter()->setContextTitle( $titleObj );
89  if ( !$pageObj->exists() &&
90  // @phan-suppress-next-line PhanUndeclaredMethod
91  !( $titleObj->getNamespace() === NS_FILE && self::canDeleteFile( $pageObj->getFile() ) )
92  ) {
93  $this->dieWithError( 'apierror-missingtitle' );
94  }
95 
96  $reason = $params['reason'];
97  $user = $this->getUser();
98 
99  $tags = $params['tags'] ?: [];
100 
101  if ( $titleObj->getNamespace() === NS_FILE ) {
102  $status = $this->deleteFile(
103  $pageObj,
104  $params['oldimage'],
105  $reason,
106  false,
107  $tags,
108  $params['deletetalk']
109  );
110  // TODO What kind of non-fatal errors should we expect here?
111  $wasScheduled = $status->isOK() && $status->getValue() === false;
112  } else {
113  $status = $this->delete( $pageObj, $reason, $tags, $params['deletetalk'] );
114  $wasScheduled = $status->isGood() && $status->getValue() === false;
115  }
116 
117  if ( !$status->isOK() ) {
118  $this->dieStatus( $status );
119  }
120 
121  if ( $wasScheduled ) {
122  $this->addWarning( [ 'delete-scheduled', $titleObj->getPrefixedText() ] );
123  }
124 
125  // Deprecated parameters
126  if ( $params['watch'] ) {
127  $watch = 'watch';
128  } elseif ( $params['unwatch'] ) {
129  $watch = 'unwatch';
130  } else {
131  $watch = $params['watchlist'];
132  }
133 
134  $watchlistExpiry = $this->getExpiryFromParams( $params );
135  $this->setWatch( $watch, $titleObj, $user, 'watchdeletion', $watchlistExpiry );
136 
137  $r = [
138  'title' => $titleObj->getPrefixedText(),
139  'reason' => $reason,
140  ];
141 
142  // TODO: We could expose additional information (scheduled and log ID) about the status of the talk page
143  // deletion.
144  if ( $wasScheduled ) {
145  $r['scheduled'] = true;
146  } else {
147  // Scheduled deletions don't currently have a log entry available at this point
148  $r['logid'] = $status->value;
149  }
150  $this->getResult()->addValue( null, $this->getModuleName(), $r );
151  }
152 
165  private function delete( WikiPage $page, &$reason, array $tags, bool $deleteTalk ): StatusValue {
166  $title = $page->getTitle();
167 
168  // Auto-generate a summary, if necessary
169  if ( $reason === null ) {
170  $reason = $page->getAutoDeleteReason();
171  if ( $reason === false ) {
172  // Should be reachable only if the page has no revisions
173  return Status::newFatal( 'cannotdelete', $title->getPrefixedText() ); // @codeCoverageIgnore
174  }
175  }
176 
177  $deletePage = $this->deletePageFactory->newDeletePage( $page, $this->getAuthority() );
178  if ( $deleteTalk ) {
179  $checkStatus = $deletePage->canProbablyDeleteAssociatedTalk();
180  if ( !$checkStatus->isGood() ) {
181  foreach ( $checkStatus->getErrors() as $error ) {
182  $this->addWarning( $error );
183  }
184  } else {
185  $deletePage->setDeleteAssociatedTalk( true );
186  }
187  }
188  $deletionStatus = $deletePage->setTags( $tags )->deleteIfAllowed( $reason );
189  if ( $deletionStatus->isGood() ) {
190  $deletionStatus->value = $deletePage->deletionsWereScheduled()[DeletePage::PAGE_BASE]
191  ? false
192  : $deletePage->getSuccessfulDeletionsIDs()[DeletePage::PAGE_BASE];
193  }
194  return $deletionStatus;
195  }
196 
201  protected static function canDeleteFile( File $file ) {
202  return $file->exists() && $file->isLocal() && !$file->getRedirected();
203  }
204 
214  private function deleteFile(
215  WikiPage $page,
216  $oldimage,
217  &$reason,
218  bool $suppress,
219  array $tags,
220  bool $deleteTalk
221  ) {
222  $title = $page->getTitle();
223 
224  // @phan-suppress-next-line PhanUndeclaredMethod There's no right typehint for it
225  $file = $page->getFile();
226  if ( !self::canDeleteFile( $file ) ) {
227  return $this->delete( $page, $reason, $tags, $deleteTalk );
228  }
229 
230  // Check that the user is allowed to carry out the deletion
231  $this->checkTitleUserPermissions( $page->getTitle(), 'delete' );
232  if ( $tags ) {
233  // If change tagging was requested, check that the user is allowed to tag,
234  // and the tags are valid
235  $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getAuthority() );
236  if ( !$tagStatus->isOK() ) {
237  $this->dieStatus( $tagStatus );
238  }
239  }
240 
241  if ( $oldimage ) {
242  if ( !FileDeleteForm::isValidOldSpec( $oldimage ) ) {
243  return Status::newFatal( 'invalidoldimage' );
244  }
245  $oldfile = $this->repoGroup->getLocalRepo()->newFromArchiveName( $title, $oldimage );
246  if ( !$oldfile->exists() || !$oldfile->isLocal() || $oldfile->getRedirected() ) {
247  return Status::newFatal( 'nodeleteablefile' );
248  }
249  }
250 
251  if ( $reason === null ) { // Log and RC don't like null reasons
252  $reason = '';
253  }
254 
255  return FileDeleteForm::doDelete(
256  $title,
257  $file,
258  $oldimage,
259  $reason,
260  $suppress,
261  $this->getUser(),
262  $tags,
263  $deleteTalk
264  );
265  }
266 
267  public function mustBePosted() {
268  return true;
269  }
270 
271  public function isWriteMode() {
272  return true;
273  }
274 
275  public function getAllowedParams() {
276  $params = [
277  'title' => null,
278  'pageid' => [
279  ParamValidator::PARAM_TYPE => 'integer'
280  ],
281  'reason' => null,
282  'tags' => [
283  ParamValidator::PARAM_TYPE => 'tags',
284  ParamValidator::PARAM_ISMULTI => true,
285  ],
286  'deletetalk' => false,
287  'watch' => [
288  ParamValidator::PARAM_DEFAULT => false,
289  ParamValidator::PARAM_DEPRECATED => true,
290  ],
291  ];
292 
293  // Params appear in the docs in the order they are defined,
294  // which is why this is here and not at the bottom.
295  $params += $this->getWatchlistParams();
296 
297  return $params + [
298  'unwatch' => [
299  ParamValidator::PARAM_DEFAULT => false,
300  ParamValidator::PARAM_DEPRECATED => true,
301  ],
302  'oldimage' => null,
303  ];
304  }
305 
306  public function needsToken() {
307  return 'csrf';
308  }
309 
310  protected function getExamplesMessages() {
311  $title = Title::newMainPage()->getPrefixedText();
312  $mp = rawurlencode( $title );
313 
314  return [
315  "action=delete&title={$mp}&token=123ABC"
316  => 'apihelp-delete-example-simple',
317  "action=delete&title={$mp}&token=123ABC&reason=Preparing%20for%20move"
318  => 'apihelp-delete-example-reason',
319  ];
320  }
321 
322  public function getHelpUrls() {
323  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Delete';
324  }
325 }
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
setWatch(string $watch, Title $title, User $user, ?string $userOption=null, ?string $expiry=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
getWatchlistParams(array $watchOptions=[])
Get additional allow params specific to watchlisting.
const NS_FILE
Definition: Defines.php:70
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:63
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition: ApiBase.php:1516
getErrorFormatter()
Definition: ApiBase.php:679
getResult()
Get the result object.
Definition: ApiBase.php:668
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:808
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1434
checkTitleUserPermissions( $pageIdentity, $actions, array $options=[])
Helper function for permission-denied errors.
Definition: ApiBase.php:1660
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:529
getTitleOrPageId( $params, $load=false)
Attempts to load a WikiPage object from a title or pageid parameter, if possible.
Definition: ApiBase.php:1081
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1571
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1364
API module that facilitates deleting pages.
Definition: ApiDelete.php:39
__construct(ApiMain $mainModule, $moduleName, RepoGroup $repoGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup, DeletePageFactory $deletePageFactory)
Definition: ApiDelete.php:54
execute()
Extracts the title and reason from the request parameters and invokes the local delete() function wit...
Definition: ApiDelete.php:81
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiDelete.php:306
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiDelete.php:310
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiDelete.php:275
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiDelete.php:322
static canDeleteFile(File $file)
Definition: ApiDelete.php:201
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiDelete.php:271
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiDelete.php:267
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:64
static canAddTagsAccompanyingChange(array $tags, Authority $performer=null, $checkBlock=true)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:396
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:70
A class containing constants representing the names of configuration variables.
Backend logic for performing a page delete action.
Definition: DeletePage.php:51
File deletion user interface.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
Represents a title within MediaWiki.
Definition: Title.php:76
Provides access to user options.
Prioritized list of file repositories.
Definition: RepoGroup.php:30
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:46
Base representation for an editable wiki page.
Definition: WikiPage.php:77
getAutoDeleteReason(&$hasHistory=false)
Auto-generates a deletion reason.
Definition: WikiPage.php:2941
getTitle()
Get the title object of the article.
Definition: WikiPage.php:271
Service for formatting and validating API parameters.
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Service for page delete actions.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42