MediaWiki  1.30.0
FSFileBackend.php
Go to the documentation of this file.
1 <?php
23 use Wikimedia\Timestamp\ConvertibleTimestamp;
24 
43  protected $basePath;
44 
46  protected $containerPaths = [];
47 
49  protected $fileMode;
51  protected $dirMode;
52 
54  protected $fileOwner;
55 
57  protected $isWindows;
59  protected $currentUser;
60 
62  protected $hadWarningErrors = [];
63 
74  public function __construct( array $config ) {
75  parent::__construct( $config );
76 
77  $this->isWindows = ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
78  // Remove any possible trailing slash from directories
79  if ( isset( $config['basePath'] ) ) {
80  $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
81  } else {
82  $this->basePath = null; // none; containers must have explicit paths
83  }
84 
85  if ( isset( $config['containerPaths'] ) ) {
86  $this->containerPaths = (array)$config['containerPaths'];
87  foreach ( $this->containerPaths as &$path ) {
88  $path = rtrim( $path, '/' ); // remove trailing slash
89  }
90  }
91 
92  $this->fileMode = isset( $config['fileMode'] ) ? $config['fileMode'] : 0644;
93  $this->dirMode = isset( $config['directoryMode'] ) ? $config['directoryMode'] : 0777;
94  if ( isset( $config['fileOwner'] ) && function_exists( 'posix_getuid' ) ) {
95  $this->fileOwner = $config['fileOwner'];
96  // cache this, assuming it doesn't change
97  $this->currentUser = posix_getpwuid( posix_getuid() )['name'];
98  }
99  }
100 
101  public function getFeatures() {
102  return !$this->isWindows ? FileBackend::ATTR_UNICODE_PATHS : 0;
103  }
104 
105  protected function resolveContainerPath( $container, $relStoragePath ) {
106  // Check that container has a root directory
107  if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
108  // Check for sane relative paths (assume the base paths are OK)
109  if ( $this->isLegalRelPath( $relStoragePath ) ) {
110  return $relStoragePath;
111  }
112  }
113 
114  return null;
115  }
116 
123  protected function isLegalRelPath( $path ) {
124  // Check for file names longer than 255 chars
125  if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
126  return false;
127  }
128  if ( $this->isWindows ) { // NTFS
129  return !preg_match( '![:*?"<>|]!', $path );
130  } else {
131  return true;
132  }
133  }
134 
143  protected function containerFSRoot( $shortCont, $fullCont ) {
144  if ( isset( $this->containerPaths[$shortCont] ) ) {
145  return $this->containerPaths[$shortCont];
146  } elseif ( isset( $this->basePath ) ) {
147  return "{$this->basePath}/{$fullCont}";
148  }
149 
150  return null; // no container base path defined
151  }
152 
159  protected function resolveToFSPath( $storagePath ) {
160  list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
161  if ( $relPath === null ) {
162  return null; // invalid
163  }
164  list( , $shortCont, ) = FileBackend::splitStoragePath( $storagePath );
165  $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
166  if ( $relPath != '' ) {
167  $fsPath .= "/{$relPath}";
168  }
169 
170  return $fsPath;
171  }
172 
173  public function isPathUsableInternal( $storagePath ) {
174  $fsPath = $this->resolveToFSPath( $storagePath );
175  if ( $fsPath === null ) {
176  return false; // invalid
177  }
178  $parentDir = dirname( $fsPath );
179 
180  if ( file_exists( $fsPath ) ) {
181  $ok = is_file( $fsPath ) && is_writable( $fsPath );
182  } else {
183  $ok = is_dir( $parentDir ) && is_writable( $parentDir );
184  }
185 
186  if ( $this->fileOwner !== null && $this->currentUser !== $this->fileOwner ) {
187  $ok = false;
188  trigger_error( __METHOD__ . ": PHP process owner is not '{$this->fileOwner}'." );
189  }
190 
191  return $ok;
192  }
193 
194  protected function doCreateInternal( array $params ) {
195  $status = $this->newStatus();
196 
197  $dest = $this->resolveToFSPath( $params['dst'] );
198  if ( $dest === null ) {
199  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
200 
201  return $status;
202  }
203 
204  if ( !empty( $params['async'] ) ) { // deferred
205  $tempFile = TempFSFile::factory( 'create_', 'tmp', $this->tmpDirectory );
206  if ( !$tempFile ) {
207  $status->fatal( 'backend-fail-create', $params['dst'] );
208 
209  return $status;
210  }
211  $this->trapWarnings();
212  $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
213  $this->untrapWarnings();
214  if ( $bytes === false ) {
215  $status->fatal( 'backend-fail-create', $params['dst'] );
216 
217  return $status;
218  }
219  $cmd = implode( ' ', [
220  $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
221  escapeshellarg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
222  escapeshellarg( $this->cleanPathSlashes( $dest ) )
223  ] );
224  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
225  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
226  $status->fatal( 'backend-fail-create', $params['dst'] );
227  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
228  }
229  };
230  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
231  $tempFile->bind( $status->value );
232  } else { // immediate write
233  $this->trapWarnings();
234  $bytes = file_put_contents( $dest, $params['content'] );
235  $this->untrapWarnings();
236  if ( $bytes === false ) {
237  $status->fatal( 'backend-fail-create', $params['dst'] );
238 
239  return $status;
240  }
241  $this->chmod( $dest );
242  }
243 
244  return $status;
245  }
246 
247  protected function doStoreInternal( array $params ) {
248  $status = $this->newStatus();
249 
250  $dest = $this->resolveToFSPath( $params['dst'] );
251  if ( $dest === null ) {
252  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
253 
254  return $status;
255  }
256 
257  if ( !empty( $params['async'] ) ) { // deferred
258  $cmd = implode( ' ', [
259  $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
260  escapeshellarg( $this->cleanPathSlashes( $params['src'] ) ),
261  escapeshellarg( $this->cleanPathSlashes( $dest ) )
262  ] );
263  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
264  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
265  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
266  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
267  }
268  };
269  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
270  } else { // immediate write
271  $this->trapWarnings();
272  $ok = copy( $params['src'], $dest );
273  $this->untrapWarnings();
274  // In some cases (at least over NFS), copy() returns true when it fails
275  if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
276  if ( $ok ) { // PHP bug
277  unlink( $dest ); // remove broken file
278  trigger_error( __METHOD__ . ": copy() failed but returned true." );
279  }
280  $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
281 
282  return $status;
283  }
284  $this->chmod( $dest );
285  }
286 
287  return $status;
288  }
289 
290  protected function doCopyInternal( array $params ) {
291  $status = $this->newStatus();
292 
293  $source = $this->resolveToFSPath( $params['src'] );
294  if ( $source === null ) {
295  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
296 
297  return $status;
298  }
299 
300  $dest = $this->resolveToFSPath( $params['dst'] );
301  if ( $dest === null ) {
302  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
303 
304  return $status;
305  }
306 
307  if ( !is_file( $source ) ) {
308  if ( empty( $params['ignoreMissingSource'] ) ) {
309  $status->fatal( 'backend-fail-copy', $params['src'] );
310  }
311 
312  return $status; // do nothing; either OK or bad status
313  }
314 
315  if ( !empty( $params['async'] ) ) { // deferred
316  $cmd = implode( ' ', [
317  $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
318  escapeshellarg( $this->cleanPathSlashes( $source ) ),
319  escapeshellarg( $this->cleanPathSlashes( $dest ) )
320  ] );
321  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
322  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
323  $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
324  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
325  }
326  };
327  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd, $dest );
328  } else { // immediate write
329  $this->trapWarnings();
330  $ok = ( $source === $dest ) ? true : copy( $source, $dest );
331  $this->untrapWarnings();
332  // In some cases (at least over NFS), copy() returns true when it fails
333  if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
334  if ( $ok ) { // PHP bug
335  $this->trapWarnings();
336  unlink( $dest ); // remove broken file
337  $this->untrapWarnings();
338  trigger_error( __METHOD__ . ": copy() failed but returned true." );
339  }
340  $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
341 
342  return $status;
343  }
344  $this->chmod( $dest );
345  }
346 
347  return $status;
348  }
349 
350  protected function doMoveInternal( array $params ) {
351  $status = $this->newStatus();
352 
353  $source = $this->resolveToFSPath( $params['src'] );
354  if ( $source === null ) {
355  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
356 
357  return $status;
358  }
359 
360  $dest = $this->resolveToFSPath( $params['dst'] );
361  if ( $dest === null ) {
362  $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
363 
364  return $status;
365  }
366 
367  if ( !is_file( $source ) ) {
368  if ( empty( $params['ignoreMissingSource'] ) ) {
369  $status->fatal( 'backend-fail-move', $params['src'] );
370  }
371 
372  return $status; // do nothing; either OK or bad status
373  }
374 
375  if ( !empty( $params['async'] ) ) { // deferred
376  $cmd = implode( ' ', [
377  $this->isWindows ? 'MOVE /Y' : 'mv', // (overwrite)
378  escapeshellarg( $this->cleanPathSlashes( $source ) ),
379  escapeshellarg( $this->cleanPathSlashes( $dest ) )
380  ] );
381  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
382  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
383  $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
384  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
385  }
386  };
387  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
388  } else { // immediate write
389  $this->trapWarnings();
390  $ok = ( $source === $dest ) ? true : rename( $source, $dest );
391  $this->untrapWarnings();
392  clearstatcache(); // file no longer at source
393  if ( !$ok ) {
394  $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
395 
396  return $status;
397  }
398  }
399 
400  return $status;
401  }
402 
403  protected function doDeleteInternal( array $params ) {
404  $status = $this->newStatus();
405 
406  $source = $this->resolveToFSPath( $params['src'] );
407  if ( $source === null ) {
408  $status->fatal( 'backend-fail-invalidpath', $params['src'] );
409 
410  return $status;
411  }
412 
413  if ( !is_file( $source ) ) {
414  if ( empty( $params['ignoreMissingSource'] ) ) {
415  $status->fatal( 'backend-fail-delete', $params['src'] );
416  }
417 
418  return $status; // do nothing; either OK or bad status
419  }
420 
421  if ( !empty( $params['async'] ) ) { // deferred
422  $cmd = implode( ' ', [
423  $this->isWindows ? 'DEL' : 'unlink',
424  escapeshellarg( $this->cleanPathSlashes( $source ) )
425  ] );
426  $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
427  if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
428  $status->fatal( 'backend-fail-delete', $params['src'] );
429  trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
430  }
431  };
432  $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
433  } else { // immediate write
434  $this->trapWarnings();
435  $ok = unlink( $source );
436  $this->untrapWarnings();
437  if ( !$ok ) {
438  $status->fatal( 'backend-fail-delete', $params['src'] );
439 
440  return $status;
441  }
442  }
443 
444  return $status;
445  }
446 
453  protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
454  $status = $this->newStatus();
455  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
456  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
457  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
458  $existed = is_dir( $dir ); // already there?
459  // Create the directory and its parents as needed...
460  $this->trapWarnings();
461  if ( !$existed && !mkdir( $dir, $this->dirMode, true ) && !is_dir( $dir ) ) {
462  $this->logger->error( __METHOD__ . ": cannot create directory $dir" );
463  $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
464  } elseif ( !is_writable( $dir ) ) {
465  $this->logger->error( __METHOD__ . ": directory $dir is read-only" );
466  $status->fatal( 'directoryreadonlyerror', $params['dir'] );
467  } elseif ( !is_readable( $dir ) ) {
468  $this->logger->error( __METHOD__ . ": directory $dir is not readable" );
469  $status->fatal( 'directorynotreadableerror', $params['dir'] );
470  }
471  $this->untrapWarnings();
472  // Respect any 'noAccess' or 'noListing' flags...
473  if ( is_dir( $dir ) && !$existed ) {
474  $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
475  }
476 
477  return $status;
478  }
479 
480  protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
481  $status = $this->newStatus();
482  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
483  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
484  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
485  // Seed new directories with a blank index.html, to prevent crawling...
486  if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
487  $this->trapWarnings();
488  $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
489  $this->untrapWarnings();
490  if ( $bytes === false ) {
491  $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
492  }
493  }
494  // Add a .htaccess file to the root of the container...
495  if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
496  $this->trapWarnings();
497  $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
498  $this->untrapWarnings();
499  if ( $bytes === false ) {
500  $storeDir = "mwstore://{$this->name}/{$shortCont}";
501  $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
502  }
503  }
504 
505  return $status;
506  }
507 
508  protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
509  $status = $this->newStatus();
510  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
511  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
512  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
513  // Unseed new directories with a blank index.html, to allow crawling...
514  if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
515  $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
516  $this->trapWarnings();
517  if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
518  $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
519  }
520  $this->untrapWarnings();
521  }
522  // Remove the .htaccess file from the root of the container...
523  if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
524  $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
525  $this->trapWarnings();
526  if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
527  $storeDir = "mwstore://{$this->name}/{$shortCont}";
528  $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
529  }
530  $this->untrapWarnings();
531  }
532 
533  return $status;
534  }
535 
536  protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
537  $status = $this->newStatus();
538  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
539  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
540  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
541  $this->trapWarnings();
542  if ( is_dir( $dir ) ) {
543  rmdir( $dir ); // remove directory if empty
544  }
545  $this->untrapWarnings();
546 
547  return $status;
548  }
549 
550  protected function doGetFileStat( array $params ) {
551  $source = $this->resolveToFSPath( $params['src'] );
552  if ( $source === null ) {
553  return false; // invalid storage path
554  }
555 
556  $this->trapWarnings(); // don't trust 'false' if there were errors
557  $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
558  $hadError = $this->untrapWarnings();
559 
560  if ( $stat ) {
561  $ct = new ConvertibleTimestamp( $stat['mtime'] );
562 
563  return [
564  'mtime' => $ct->getTimestamp( TS_MW ),
565  'size' => $stat['size']
566  ];
567  } elseif ( !$hadError ) {
568  return false; // file does not exist
569  } else {
570  return null; // failure
571  }
572  }
573 
574  protected function doClearCache( array $paths = null ) {
575  clearstatcache(); // clear the PHP file stat cache
576  }
577 
578  protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
579  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
580  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
581  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
582 
583  $this->trapWarnings(); // don't trust 'false' if there were errors
584  $exists = is_dir( $dir );
585  $hadError = $this->untrapWarnings();
586 
587  return $hadError ? null : $exists;
588  }
589 
597  public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
598  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
599  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
600  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
601  $exists = is_dir( $dir );
602  if ( !$exists ) {
603  $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
604 
605  return []; // nothing under this dir
606  } elseif ( !is_readable( $dir ) ) {
607  $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
608 
609  return null; // bad permissions?
610  }
611 
612  return new FSFileBackendDirList( $dir, $params );
613  }
614 
622  public function getFileListInternal( $fullCont, $dirRel, array $params ) {
623  list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
624  $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
625  $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
626  $exists = is_dir( $dir );
627  if ( !$exists ) {
628  $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
629 
630  return []; // nothing under this dir
631  } elseif ( !is_readable( $dir ) ) {
632  $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
633 
634  return null; // bad permissions?
635  }
636 
637  return new FSFileBackendFileList( $dir, $params );
638  }
639 
640  protected function doGetLocalReferenceMulti( array $params ) {
641  $fsFiles = []; // (path => FSFile)
642 
643  foreach ( $params['srcs'] as $src ) {
644  $source = $this->resolveToFSPath( $src );
645  if ( $source === null || !is_file( $source ) ) {
646  $fsFiles[$src] = null; // invalid path or file does not exist
647  } else {
648  $fsFiles[$src] = new FSFile( $source );
649  }
650  }
651 
652  return $fsFiles;
653  }
654 
655  protected function doGetLocalCopyMulti( array $params ) {
656  $tmpFiles = []; // (path => TempFSFile)
657 
658  foreach ( $params['srcs'] as $src ) {
659  $source = $this->resolveToFSPath( $src );
660  if ( $source === null ) {
661  $tmpFiles[$src] = null; // invalid path
662  } else {
663  // Create a new temporary file with the same extension...
665  $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
666  if ( !$tmpFile ) {
667  $tmpFiles[$src] = null;
668  } else {
669  $tmpPath = $tmpFile->getPath();
670  // Copy the source file over the temp file
671  $this->trapWarnings();
672  $ok = copy( $source, $tmpPath );
673  $this->untrapWarnings();
674  if ( !$ok ) {
675  $tmpFiles[$src] = null;
676  } else {
677  $this->chmod( $tmpPath );
678  $tmpFiles[$src] = $tmpFile;
679  }
680  }
681  }
682  }
683 
684  return $tmpFiles;
685  }
686 
687  protected function directoriesAreVirtual() {
688  return false;
689  }
690 
696  protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
697  $statuses = [];
698 
699  $pipes = [];
700  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
701  $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
702  }
703 
704  $errs = [];
705  foreach ( $pipes as $index => $pipe ) {
706  // Result will be empty on success in *NIX. On Windows,
707  // it may be something like " 1 file(s) [copied|moved].".
708  $errs[$index] = stream_get_contents( $pipe );
709  fclose( $pipe );
710  }
711 
712  foreach ( $fileOpHandles as $index => $fileOpHandle ) {
713  $status = $this->newStatus();
714  $function = $fileOpHandle->call;
715  $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
716  $statuses[$index] = $status;
717  if ( $status->isOK() && $fileOpHandle->chmodPath ) {
718  $this->chmod( $fileOpHandle->chmodPath );
719  }
720  }
721 
722  clearstatcache(); // files changed
723  return $statuses;
724  }
725 
732  protected function chmod( $path ) {
733  $this->trapWarnings();
734  $ok = chmod( $path, $this->fileMode );
735  $this->untrapWarnings();
736 
737  return $ok;
738  }
739 
745  protected function indexHtmlPrivate() {
746  return '';
747  }
748 
754  protected function htaccessPrivate() {
755  return "Deny from all\n";
756  }
757 
764  protected function cleanPathSlashes( $path ) {
765  return $this->isWindows ? strtr( $path, '/', '\\' ) : $path;
766  }
767 
771  protected function trapWarnings() {
772  $this->hadWarningErrors[] = false; // push to stack
773  set_error_handler( [ $this, 'handleWarning' ], E_WARNING );
774  }
775 
781  protected function untrapWarnings() {
782  restore_error_handler(); // restore previous handler
783  return array_pop( $this->hadWarningErrors ); // pop from stack
784  }
785 
792  public function handleWarning( $errno, $errstr ) {
793  $this->logger->error( $errstr ); // more detailed error logging
794  $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
795 
796  return true; // suppress from PHP handler
797  }
798 }
799 
804  public $cmd; // string; shell command
805  public $chmodPath; // string; file to chmod
806 
814  public function __construct(
816  ) {
817  $this->backend = $backend;
818  $this->params = $params;
819  $this->call = $call;
820  $this->cmd = $cmd;
821  $this->chmodPath = $chmodPath;
822  }
823 }
824 
832 abstract class FSFileBackendList implements Iterator {
834  protected $iter;
835 
837  protected $suffixStart;
838 
840  protected $pos = 0;
841 
843  protected $params = [];
844 
849  public function __construct( $dir, array $params ) {
850  $path = realpath( $dir ); // normalize
851  if ( $path === false ) {
852  $path = $dir;
853  }
854  $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
855  $this->params = $params;
856 
857  try {
858  $this->iter = $this->initIterator( $path );
859  } catch ( UnexpectedValueException $e ) {
860  $this->iter = null; // bad permissions? deleted?
861  }
862  }
863 
870  protected function initIterator( $dir ) {
871  if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
872  # Get an iterator that will get direct sub-nodes
873  return new DirectoryIterator( $dir );
874  } else { // recursive
875  # Get an iterator that will return leaf nodes (non-directories)
876  # RecursiveDirectoryIterator extends FilesystemIterator.
877  # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
878  $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
879 
880  return new RecursiveIteratorIterator(
881  new RecursiveDirectoryIterator( $dir, $flags ),
882  RecursiveIteratorIterator::CHILD_FIRST // include dirs
883  );
884  }
885  }
886 
891  public function key() {
892  return $this->pos;
893  }
894 
899  public function current() {
900  return $this->getRelPath( $this->iter->current()->getPathname() );
901  }
902 
907  public function next() {
908  try {
909  $this->iter->next();
910  $this->filterViaNext();
911  } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
912  throw new FileBackendError( "File iterator gave UnexpectedValueException." );
913  }
914  ++$this->pos;
915  }
916 
921  public function rewind() {
922  $this->pos = 0;
923  try {
924  $this->iter->rewind();
925  $this->filterViaNext();
926  } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
927  throw new FileBackendError( "File iterator gave UnexpectedValueException." );
928  }
929  }
930 
935  public function valid() {
936  return $this->iter && $this->iter->valid();
937  }
938 
942  protected function filterViaNext() {
943  }
944 
952  protected function getRelPath( $dir ) {
953  $path = realpath( $dir );
954  if ( $path === false ) {
955  $path = $dir;
956  }
957 
958  return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
959  }
960 }
961 
963  protected function filterViaNext() {
964  while ( $this->iter->valid() ) {
965  if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
966  $this->iter->next(); // skip non-directories and dot files
967  } else {
968  break;
969  }
970  }
971  }
972 }
973 
975  protected function filterViaNext() {
976  while ( $this->iter->valid() ) {
977  if ( !$this->iter->current()->isFile() ) {
978  $this->iter->next(); // skip non-files and dot files
979  } else {
980  break;
981  }
982  }
983  }
984 }
FileBackend\splitStoragePath
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
Definition: FileBackend.php:1447
FSFileBackendList\initIterator
initIterator( $dir)
Return an appropriate iterator object to wrap.
Definition: FSFileBackend.php:870
FSFileBackend\__construct
__construct(array $config)
Definition: FSFileBackend.php:74
FSFileBackend\doCleanInternal
doCleanInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:536
FSFileOpHandle
Definition: FSFileBackend.php:803
FSFileBackendFileList\filterViaNext
filterViaNext()
Filter out items by advancing to the next ones.
Definition: FSFileBackend.php:975
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:42
FSFileBackend\doStoreInternal
doStoreInternal(array $params)
Definition: FSFileBackend.php:247
FSFileBackend\doClearCache
doClearCache(array $paths=null)
Clears any additional stat caches for storage paths.
Definition: FSFileBackend.php:574
FSFileBackend\doPublishInternal
doPublishInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:508
FSFileBackend\$currentUser
string $currentUser
OS username running this script.
Definition: FSFileBackend.php:59
captcha-old.count
count
Definition: captcha-old.py:249
FSFileBackend\doPrepareInternal
doPrepareInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:453
FSFileBackendList\current
current()
Definition: FSFileBackend.php:899
FileBackendError
File backend exception for checked exceptions (e.g.
Definition: FileBackendError.php:8
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1245
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
FileBackend\extensionFromPath
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
Definition: FileBackend.php:1506
FSFileBackend\$dirMode
int $dirMode
File permission mode.
Definition: FSFileBackend.php:51
$params
$params
Definition: styleTest.css.php:40
FSFileBackend\$hadWarningErrors
array $hadWarningErrors
Definition: FSFileBackend.php:62
FSFileBackend\htaccessPrivate
htaccessPrivate()
Return the text of a .htaccess file to make a directory private.
Definition: FSFileBackend.php:754
FSFileBackend\doGetFileStat
doGetFileStat(array $params)
Definition: FSFileBackend.php:550
FSFileBackend\directoriesAreVirtual
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
Definition: FSFileBackend.php:687
FSFileBackend\$fileOwner
string $fileOwner
Required OS username to own files.
Definition: FSFileBackend.php:54
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
FSFileBackend\doGetLocalReferenceMulti
doGetLocalReferenceMulti(array $params)
Definition: FSFileBackend.php:640
FSFileBackend\$basePath
string $basePath
Directory holding the container directories.
Definition: FSFileBackend.php:43
FSFileBackendDirList\filterViaNext
filterViaNext()
Filter out items by advancing to the next ones.
Definition: FSFileBackend.php:963
FSFileBackend\doDirectoryExists
doDirectoryExists( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:578
FSFileBackend\getDirectoryListInternal
getDirectoryListInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:597
FSFileBackend\getFeatures
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
Definition: FSFileBackend.php:101
FSFileBackendFileList
Definition: FSFileBackend.php:974
FSFileBackendList\getRelPath
getRelPath( $dir)
Return only the relative path and normalize slashes to FileBackend-style.
Definition: FSFileBackend.php:952
FSFileBackend\containerFSRoot
containerFSRoot( $shortCont, $fullCont)
Given the short (unresolved) and full (resolved) name of a container, return the file system path of ...
Definition: FSFileBackend.php:143
FileBackendStoreOpHandle\$backend
FileBackendStore $backend
Definition: FileBackendStore.php:1860
FSFileBackendDirList
Definition: FSFileBackend.php:962
FileBackend\ATTR_UNICODE_PATHS
const ATTR_UNICODE_PATHS
Definition: FileBackend.php:130
FSFileBackend\doSecureInternal
doSecureInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:480
FSFileBackendList
Wrapper around RecursiveDirectoryIterator/DirectoryIterator that catches exception or does any custom...
Definition: FSFileBackend.php:832
FSFileBackend\doExecuteOpHandlesInternal
doExecuteOpHandlesInternal(array $fileOpHandles)
Definition: FSFileBackend.php:696
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
$dir
$dir
Definition: Autoload.php:8
FSFileBackend\$containerPaths
array $containerPaths
Map of container names to root paths for custom container paths.
Definition: FSFileBackend.php:46
FSFileBackend\resolveToFSPath
resolveToFSPath( $storagePath)
Get the absolute file system path for a storage path.
Definition: FSFileBackend.php:159
FSFileBackendList\$iter
Iterator $iter
Definition: FSFileBackend.php:834
TempFSFile\factory
static factory( $prefix, $extension='', $tmpDirectory=null)
Make a new temporary file on the file system.
Definition: TempFSFile.php:55
FSFileBackend\doDeleteInternal
doDeleteInternal(array $params)
Definition: FSFileBackend.php:403
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2141
FSFileBackend\$fileMode
int $fileMode
File permission mode.
Definition: FSFileBackend.php:49
FSFileBackendList\valid
valid()
Definition: FSFileBackend.php:935
FSFileBackend\resolveContainerPath
resolveContainerPath( $container, $relStoragePath)
Resolve a relative storage path, checking if it's allowed by the backend.
Definition: FSFileBackend.php:105
FSFileBackendList\$pos
int $pos
Definition: FSFileBackend.php:840
FileBackendStore\resolveStoragePathReal
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
Definition: FileBackendStore.php:1469
FileBackendStore
Base class for all backends using particular storage medium.
Definition: FileBackendStore.php:38
FileBackendStoreOpHandle
FileBackendStore helper class for performing asynchronous file operations.
Definition: FileBackendStore.php:1856
FSFile
Class representing a non-directory file on the file system.
Definition: FSFile.php:29
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:781
FSFileBackend\$isWindows
bool $isWindows
Definition: FSFileBackend.php:57
FSFileBackendList\next
next()
Definition: FSFileBackend.php:907
FSFileBackend
Class for a file system (FS) based file backend.
Definition: FSFileBackend.php:41
FSFileBackendList\key
key()
Definition: FSFileBackend.php:891
FSFileBackend\indexHtmlPrivate
indexHtmlPrivate()
Return the text of an index.html file to hide directory listings.
Definition: FSFileBackend.php:745
$ext
$ext
Definition: NoLocalSettings.php:25
FSFileBackend\cleanPathSlashes
cleanPathSlashes( $path)
Clean up directory separators for the given OS.
Definition: FSFileBackend.php:764
$path
$path
Definition: NoLocalSettings.php:26
FSFileOpHandle\__construct
__construct(FSFileBackend $backend, array $params, $call, $cmd, $chmodPath=null)
Definition: FSFileBackend.php:814
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
FSFileBackend\doCreateInternal
doCreateInternal(array $params)
Definition: FSFileBackend.php:194
FSFileBackend\chmod
chmod( $path)
Chmod a file, suppressing the warnings.
Definition: FSFileBackend.php:732
FSFileBackendList\$suffixStart
int $suffixStart
Definition: FSFileBackend.php:837
FileBackendStoreOpHandle\$params
array $params
Definition: FileBackendStore.php:1858
$source
$source
Definition: mwdoc-filter.php:46
FSFileBackend\isLegalRelPath
isLegalRelPath( $path)
Sanity check a relative file system path for validity.
Definition: FSFileBackend.php:123
FSFileBackendList\rewind
rewind()
Definition: FSFileBackend.php:921
FSFileBackend\isPathUsableInternal
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path.
Definition: FSFileBackend.php:173
FileBackend\copy
copy(array $params, array $opts=[])
Performs a single copy operation.
Definition: FileBackend.php:502
FSFileBackend\handleWarning
handleWarning( $errno, $errstr)
Definition: FSFileBackend.php:792
FSFileBackend\untrapWarnings
untrapWarnings()
Stop listening for E_WARNING errors and return true if any happened.
Definition: FSFileBackend.php:781
FSFileBackend\doGetLocalCopyMulti
doGetLocalCopyMulti(array $params)
Definition: FSFileBackend.php:655
FSFileOpHandle\$cmd
$cmd
Definition: FSFileBackend.php:804
FSFileBackend\doCopyInternal
doCopyInternal(array $params)
Definition: FSFileBackend.php:290
FSFileBackendList\$params
array $params
Definition: FSFileBackend.php:843
FSFileBackend\getFileListInternal
getFileListInternal( $fullCont, $dirRel, array $params)
Definition: FSFileBackend.php:622
FSFileBackend\trapWarnings
trapWarnings()
Listen for E_WARNING errors and track whether any happen.
Definition: FSFileBackend.php:771
FileBackendStoreOpHandle\$call
$call
Definition: FileBackendStore.php:1864
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
FileBackend\newStatus
newStatus()
Yields the result of the status wrapper callback on either:
Definition: FileBackend.php:1597
array
the array() calling protocol came about after MediaWiki 1.4rc1.
FSFileBackendList\__construct
__construct( $dir, array $params)
Definition: FSFileBackend.php:849
FSFileBackend\doMoveInternal
doMoveInternal(array $params)
Definition: FSFileBackend.php:350
FSFileOpHandle\$chmodPath
$chmodPath
Definition: FSFileBackend.php:805
FSFileBackendList\filterViaNext
filterViaNext()
Filter out items by advancing to the next ones.
Definition: FSFileBackend.php:942