MediaWiki  master
ApiDelete.php
Go to the documentation of this file.
1 <?php
29 
36 class ApiDelete extends ApiBase {
37 
39 
41  private $repoGroup;
42 
45 
54  public function __construct(
55  ApiMain $mainModule,
56  $moduleName,
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 
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  return [
312  'action=delete&title=Main%20Page&token=123ABC'
313  => 'apihelp-delete-example-simple',
314  'action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move'
315  => 'apihelp-delete-example-reason',
316  ];
317  }
318 
319  public function getHelpUrls() {
320  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Delete';
321  }
322 }
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.
WatchlistManager $watchlistManager
UserOptionsLookup $userOptionsLookup
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:56
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1458
getErrorFormatter()
Definition: ApiBase.php:640
getResult()
Get the result object.
Definition: ApiBase.php:629
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:765
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1376
checkTitleUserPermissions( $pageIdentity, $actions, array $options=[])
Helper function for permission-denied errors.
Definition: ApiBase.php:1593
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:498
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:1036
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1521
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1303
API module that facilitates deleting pages.
Definition: ApiDelete.php:36
__construct(ApiMain $mainModule, $moduleName, RepoGroup $repoGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup, DeletePageFactory $deletePageFactory)
Definition: ApiDelete.php:54
deleteFile(WikiPage $page, $oldimage, &$reason, bool $suppress, array $tags, bool $deleteTalk)
Definition: ApiDelete.php:214
execute()
Extracts the title and reason from the request parameters and invokes the local delete() function wit...
Definition: ApiDelete.php:81
DeletePageFactory $deletePageFactory
Definition: ApiDelete.php:44
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:319
static canDeleteFile(File $file)
Definition: ApiDelete.php:201
RepoGroup $repoGroup
Definition: ApiDelete.php:41
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:52
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:625
static doDelete(Title $title, LocalFile $file, ?string $oldimage, $reason, $suppress, UserIdentity $user, $tags=[], bool $deleteTalk=false)
Really delete the file.
static isValidOldSpec( $oldimage)
Is the provided oldimage value valid?
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:68
A class containing constants representing the names of configuration variables.
Backend logic for performing a page delete action.
Definition: DeletePage.php:52
Provides access to user options.
Prioritized list of file repositories.
Definition: RepoGroup.php:29
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:43
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
Base representation for an editable wiki page.
Definition: WikiPage.php:62
getAutoDeleteReason(&$hasHistory=false)
Auto-generates a deletion reason.
Definition: WikiPage.php:2979
getTitle()
Get the title object of the article.
Definition: WikiPage.php:303
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