MediaWiki  master
ApiQueryFilearchive.php
Go to the documentation of this file.
1 <?php
32 
39 
41  private $commentStore;
42 
44  private $commentFormatter;
45 
52  public function __construct(
53  ApiQuery $query,
54  $moduleName,
55  CommentStore $commentStore,
56  CommentFormatter $commentFormatter
57  ) {
58  parent::__construct( $query, $moduleName, 'fa' );
59  $this->commentStore = $commentStore;
60  $this->commentFormatter = $commentFormatter;
61  }
62 
63  public function execute() {
64  $user = $this->getUser();
65  $db = $this->getDB();
66 
67  $params = $this->extractRequestParams();
68 
69  $prop = array_fill_keys( $params['prop'], true );
70  $fld_sha1 = isset( $prop['sha1'] );
71  $fld_timestamp = isset( $prop['timestamp'] );
72  $fld_user = isset( $prop['user'] );
73  $fld_size = isset( $prop['size'] );
74  $fld_dimensions = isset( $prop['dimensions'] );
75  $fld_description = isset( $prop['description'] ) || isset( $prop['parseddescription'] );
76  $fld_parseddescription = isset( $prop['parseddescription'] );
77  $fld_mime = isset( $prop['mime'] );
78  $fld_mediatype = isset( $prop['mediatype'] );
79  $fld_metadata = isset( $prop['metadata'] );
80  $fld_bitdepth = isset( $prop['bitdepth'] );
81  $fld_archivename = isset( $prop['archivename'] );
82 
83  if ( $fld_description && !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
84  $this->dieWithError( 'apierror-cantview-deleted-description', 'permissiondenied' );
85  }
86  if ( $fld_metadata && !$this->getAuthority()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
87  $this->dieWithError( 'apierror-cantview-deleted-metadata', 'permissiondenied' );
88  }
89 
90  $fileQuery = ArchivedFile::getQueryInfo();
91  $this->addTables( $fileQuery['tables'] );
92  $this->addFields( $fileQuery['fields'] );
93  $this->addJoinConds( $fileQuery['joins'] );
94 
95  if ( $params['continue'] !== null ) {
96  $cont = explode( '|', $params['continue'] );
97  $this->dieContinueUsageIf( count( $cont ) != 3 );
98  $op = $params['dir'] == 'descending' ? '<' : '>';
99  $cont_from = $db->addQuotes( $cont[0] );
100  $cont_timestamp = $db->addQuotes( $db->timestamp( $cont[1] ) );
101  $cont_id = (int)$cont[2];
102  $this->dieContinueUsageIf( $cont[2] !== (string)$cont_id );
103  $this->addWhere( "fa_name $op $cont_from OR " .
104  "(fa_name = $cont_from AND " .
105  "(fa_timestamp $op $cont_timestamp OR " .
106  "(fa_timestamp = $cont_timestamp AND " .
107  "fa_id $op= $cont_id )))"
108  );
109  }
110 
111  // Image filters
112  $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
113  $from = ( $params['from'] === null ? null : $this->titlePartToKey( $params['from'], NS_FILE ) );
114  $to = ( $params['to'] === null ? null : $this->titlePartToKey( $params['to'], NS_FILE ) );
115  $this->addWhereRange( 'fa_name', $dir, $from, $to );
116  if ( isset( $params['prefix'] ) ) {
117  $this->addWhere( 'fa_name' . $db->buildLike(
118  $this->titlePartToKey( $params['prefix'], NS_FILE ),
119  $db->anyString() ) );
120  }
121 
122  $sha1Set = isset( $params['sha1'] );
123  $sha1base36Set = isset( $params['sha1base36'] );
124  if ( $sha1Set || $sha1base36Set ) {
125  $sha1 = false;
126  if ( $sha1Set ) {
127  $sha1 = strtolower( $params['sha1'] );
128  if ( !$this->validateSha1Hash( $sha1 ) ) {
129  $this->dieWithError( 'apierror-invalidsha1hash' );
130  }
131  $sha1 = Wikimedia\base_convert( $sha1, 16, 36, 31 );
132  } elseif ( $sha1base36Set ) {
133  $sha1 = strtolower( $params['sha1base36'] );
134  if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
135  $this->dieWithError( 'apierror-invalidsha1base36hash' );
136  }
137  }
138  if ( $sha1 ) {
139  $this->addWhereFld( 'fa_sha1', $sha1 );
140  // Paranoia: avoid brute force searches (T19342)
141  if ( !$this->getAuthority()->isAllowed( 'deletedtext' ) ) {
142  $bitmask = File::DELETED_FILE;
143  } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
145  } else {
146  $bitmask = 0;
147  }
148  if ( $bitmask ) {
149  $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
150  }
151  }
152  }
153 
154  $limit = $params['limit'];
155  $this->addOption( 'LIMIT', $limit + 1 );
156  $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
157  $this->addOption( 'ORDER BY', [
158  'fa_name' . $sort,
159  'fa_timestamp' . $sort,
160  'fa_id' . $sort,
161  ] );
162 
163  $res = $this->select( __METHOD__ );
164 
165  // Format descriptions in a batch
166  $formattedDescriptions = [];
167  $descriptions = [];
168  if ( $fld_parseddescription ) {
169  $commentItems = [];
170  foreach ( $res as $row ) {
171  $desc = $this->commentStore->getComment( 'fa_description', $row )->text;
172  $descriptions[$row->fa_id] = $desc;
173  $commentItems[$row->fa_id] = ( new CommentItem( $desc ) )
174  ->selfLinkTarget( new TitleValue( NS_FILE, $row->fa_name ) );
175  }
176  $formattedDescriptions = $this->commentFormatter->createBatch()
177  ->comments( $commentItems )
178  ->execute();
179  }
180 
181  $count = 0;
182  $result = $this->getResult();
183  foreach ( $res as $row ) {
184  if ( ++$count > $limit ) {
185  // We've reached the one extra which shows that there are
186  // additional pages to be had. Stop here...
188  'continue', "$row->fa_name|$row->fa_timestamp|$row->fa_id"
189  );
190  break;
191  }
192 
193  $canViewFile = RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_FILE, $user );
194 
195  $file = [];
196  $file['id'] = (int)$row->fa_id;
197  $file['name'] = $row->fa_name;
198  $title = Title::makeTitle( NS_FILE, $row->fa_name );
200 
201  if ( $fld_description &&
202  RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_COMMENT, $user )
203  ) {
204  if ( isset( $prop['parseddescription'] ) ) {
205  $file['parseddescription'] = $formattedDescriptions[$row->fa_id];
206  $file['description'] = $descriptions[$row->fa_id];
207  } else {
208  $file['description'] = $this->commentStore->getComment( 'fa_description', $row )->text;
209  }
210  }
211  if ( $fld_user &&
212  RevisionRecord::userCanBitfield( $row->fa_deleted, File::DELETED_USER, $user )
213  ) {
214  $file['userid'] = (int)$row->fa_user;
215  $file['user'] = $row->fa_user_text;
216  }
217  if ( $fld_sha1 && $canViewFile ) {
218  $file['sha1'] = Wikimedia\base_convert( $row->fa_sha1, 36, 16, 40 );
219  }
220  if ( $fld_timestamp ) {
221  $file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
222  }
223  if ( ( $fld_size || $fld_dimensions ) && $canViewFile ) {
224  $file['size'] = $row->fa_size;
225 
226  $pageCount = ArchivedFile::newFromRow( $row )->pageCount();
227  if ( $pageCount !== false ) {
228  $file['pagecount'] = $pageCount;
229  }
230 
231  $file['height'] = $row->fa_height;
232  $file['width'] = $row->fa_width;
233  }
234  if ( $fld_mediatype && $canViewFile ) {
235  $file['mediatype'] = $row->fa_media_type;
236  }
237  if ( $fld_metadata && $canViewFile ) {
238  $metadataArray = ArchivedFile::newFromRow( $row )->getMetadataArray();
239  $file['metadata'] = $row->fa_metadata
240  ? ApiQueryImageInfo::processMetaData( $metadataArray, $result )
241  : null;
242  }
243  if ( $fld_bitdepth && $canViewFile ) {
244  $file['bitdepth'] = $row->fa_bits;
245  }
246  if ( $fld_mime && $canViewFile ) {
247  $file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
248  }
249  if ( $fld_archivename && $row->fa_archive_name !== null ) {
250  $file['archivename'] = $row->fa_archive_name;
251  }
252 
253  if ( $row->fa_deleted & File::DELETED_FILE ) {
254  $file['filehidden'] = true;
255  }
256  if ( $row->fa_deleted & File::DELETED_COMMENT ) {
257  $file['commenthidden'] = true;
258  }
259  if ( $row->fa_deleted & File::DELETED_USER ) {
260  $file['userhidden'] = true;
261  }
262  if ( $row->fa_deleted & File::DELETED_RESTRICTED ) {
263  // This file is deleted for normal admins
264  $file['suppressed'] = true;
265  }
266 
267  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $file );
268  if ( !$fit ) {
270  'continue', "$row->fa_name|$row->fa_timestamp|$row->fa_id"
271  );
272  break;
273  }
274  }
275 
276  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'fa' );
277  }
278 
279  public function getAllowedParams() {
280  return [
281  'from' => null,
282  'to' => null,
283  'prefix' => null,
284  'dir' => [
285  ParamValidator::PARAM_DEFAULT => 'ascending',
286  ParamValidator::PARAM_TYPE => [
287  'ascending',
288  'descending'
289  ]
290  ],
291  'sha1' => null,
292  'sha1base36' => null,
293  'prop' => [
294  ParamValidator::PARAM_DEFAULT => 'timestamp',
295  ParamValidator::PARAM_ISMULTI => true,
296  ParamValidator::PARAM_TYPE => [
297  'sha1',
298  'timestamp',
299  'user',
300  'size',
301  'dimensions',
302  'description',
303  'parseddescription',
304  'mime',
305  'mediatype',
306  'metadata',
307  'bitdepth',
308  'archivename',
309  ],
311  ],
312  'limit' => [
313  ParamValidator::PARAM_DEFAULT => 10,
314  ParamValidator::PARAM_TYPE => 'limit',
315  IntegerDef::PARAM_MIN => 1,
316  IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
317  IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
318  ],
319  'continue' => [
320  ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
321  ],
322  ];
323  }
324 
325  protected function getExamplesMessages() {
326  return [
327  'action=query&list=filearchive'
328  => 'apihelp-query+filearchive-example-simple',
329  ];
330  }
331 
332  public function getHelpUrls() {
333  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Filearchive';
334  }
335 }
const NS_FILE
Definition: Defines.php:70
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition: ApiBase.php:1458
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition: ApiBase.php:1650
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:196
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:221
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
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:163
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:223
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:498
This is a base class for all Query modules.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
validateSha1Base36Hash( $hash)
addWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, and an ORDER BY clause to sort in the right direction.
validateSha1Hash( $hash)
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
getDB()
Get the Query database connection (read-only)
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
titlePartToKey( $titlePart, $namespace=NS_MAIN)
Convert an input title or title prefix into a dbkey.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Query module to enumerate all deleted files.
getExamplesMessages()
Returns usage examples for this module.
__construct(ApiQuery $query, $moduleName, CommentStore $commentStore, CommentFormatter $commentFormatter)
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getHelpUrls()
Return links to more detailed help pages about the module.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
static processMetaData( $metadata, $result)
This is the main query class.
Definition: ApiQuery.php:41
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archivedfile object.
static newFromRow( $row)
Loads a file object from the filearchive table.
Handle database storage of comments such as edit summaries and log reasons.
const DELETED_COMMENT
Definition: File.php:73
const DELETED_RESTRICTED
Definition: File.php:75
const DELETED_FILE
Definition: File.php:72
const DELETED_USER
Definition: File.php:74
This is the main service interface for converting single-line comments from various DB comment fields...
An object to represent one of the inputs to a batch formatting operation.
Definition: CommentItem.php:13
Page revision base class.
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:638
Service for formatting and validating API parameters.
Type definition for integer types.
Definition: IntegerDef.php:23
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42