Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Filesystem.php

Zuletzt modifiziert: 09.10.2024, 12:54 - Dateigröße: 23.38 KiB


001  <?php
002   
003  /*
004   * This file is part of the Symfony package.
005   *
006   * (c) Fabien Potencier <fabien@symfony.com>
007   *
008   * For the full copyright and license information, please view the LICENSE
009   * file that was distributed with this source code.
010   */
011   
012  namespace Symfony\Component\Filesystem;
013   
014  use Symfony\Component\Filesystem\Exception\IOException;
015  use Symfony\Component\Filesystem\Exception\FileNotFoundException;
016   
017  /**
018   * Provides basic utility to manipulate the file system.
019   *
020   * @author Fabien Potencier <fabien@symfony.com>
021   */
022  class Filesystem
023  {
024      /**
025       * Copies a file.
026       *
027       * If the target file is older than the origin file, it's always overwritten.
028       * If the target file is newer, it is overwritten only when the
029       * $overwriteNewerFiles option is set to true.
030       *
031       * @param string $originFile          The original filename
032       * @param string $targetFile          The target filename
033       * @param bool   $overwriteNewerFiles If true, target files newer than origin files are overwritten
034       *
035       * @throws FileNotFoundException When originFile doesn't exist
036       * @throws IOException           When copy fails
037       */
038      public function copy($originFile, $targetFile, $overwriteNewerFiles = false)
039      {
040          if (stream_is_local($originFile) && !is_file($originFile)) {
041              throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
042          }
043   
044          $this->mkdir(dirname($targetFile));
045   
046          $doCopy = true;
047          if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) {
048              $doCopy = filemtime($originFile) > filemtime($targetFile);
049          }
050   
051          if ($doCopy) {
052              // https://bugs.php.net/bug.php?id=64634
053              if (false === $source = @fopen($originFile, 'r')) {
054                  throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
055              }
056   
057              // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
058              if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) {
059                  throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
060              }
061   
062              $bytesCopied = stream_copy_to_stream($source, $target);
063              fclose($source);
064              fclose($target);
065              unset($source, $target);
066   
067              if (!is_file($targetFile)) {
068                  throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
069              }
070   
071              // Like `cp`, preserve executable permission bits
072              @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
073   
074              if (stream_is_local($originFile) && $bytesCopied !== ($bytesOrigin = filesize($originFile))) {
075                  throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
076              }
077          }
078      }
079   
080      /**
081       * Creates a directory recursively.
082       *
083       * @param string|array|\Traversable $dirs The directory path
084       * @param int                       $mode The directory mode
085       *
086       * @throws IOException On any directory creation failure
087       */
088      public function mkdir($dirs, $mode = 0777)
089      {
090          foreach ($this->toIterator($dirs) as $dir) {
091              if (is_dir($dir)) {
092                  continue;
093              }
094   
095              if (true !== @mkdir($dir, $mode, true)) {
096                  $error = error_get_last();
097                  if (!is_dir($dir)) {
098                      // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
099                      if ($error) {
100                          throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir);
101                      }
102                      throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
103                  }
104              }
105          }
106      }
107   
108      /**
109       * Checks the existence of files or directories.
110       *
111       * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check
112       *
113       * @return bool true if the file exists, false otherwise
114       */
115      public function exists($files)
116      {
117          foreach ($this->toIterator($files) as $file) {
118              if ('\\' === DIRECTORY_SEPARATOR && strlen($file) > 258) {
119                  throw new IOException('Could not check if file exist because path length exceeds 258 characters.', 0, null, $file);
120              }
121   
122              if (!file_exists($file)) {
123                  return false;
124              }
125          }
126   
127          return true;
128      }
129   
130      /**
131       * Sets access and modification time of file.
132       *
133       * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create
134       * @param int                       $time  The touch time as a Unix timestamp
135       * @param int                       $atime The access time as a Unix timestamp
136       *
137       * @throws IOException When touch fails
138       */
139      public function touch($files, $time = null, $atime = null)
140      {
141          foreach ($this->toIterator($files) as $file) {
142              $touch = $time ? @touch($file, $time, $atime) : @touch($file);
143              if (true !== $touch) {
144                  throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
145              }
146          }
147      }
148   
149      /**
150       * Removes files or directories.
151       *
152       * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
153       *
154       * @throws IOException When removal fails
155       */
156      public function remove($files)
157      {
158          if ($files instanceof \Traversable) {
159              $files = iterator_to_array($files, false);
160          } elseif (!is_array($files)) {
161              $files = array($files);
162          }
163          $files = array_reverse($files);
164          foreach ($files as $file) {
165              if (is_link($file)) {
166                  // See https://bugs.php.net/52176
167                  if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) {
168                      $error = error_get_last();
169                      throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message']));
170                  }
171              } elseif (is_dir($file)) {
172                  $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
173   
174                  if (!@rmdir($file) && file_exists($file)) {
175                      $error = error_get_last();
176                      throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message']));
177                  }
178              } elseif (!@unlink($file) && file_exists($file)) {
179                  $error = error_get_last();
180                  throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message']));
181              }
182          }
183      }
184   
185      /**
186       * Change mode for an array of files or directories.
187       *
188       * @param string|array|\Traversable $files     A filename, an array of files, or a \Traversable instance to change mode
189       * @param int                       $mode      The new mode (octal)
190       * @param int                       $umask     The mode mask (octal)
191       * @param bool                      $recursive Whether change the mod recursively or not
192       *
193       * @throws IOException When the change fail
194       */
195      public function chmod($files, $mode, $umask = 0000, $recursive = false)
196      {
197          foreach ($this->toIterator($files) as $file) {
198              if (true !== @chmod($file, $mode & ~$umask)) {
199                  throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
200              }
201              if ($recursive && is_dir($file) && !is_link($file)) {
202                  $this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
203              }
204          }
205      }
206   
207      /**
208       * Change the owner of an array of files or directories.
209       *
210       * @param string|array|\Traversable $files     A filename, an array of files, or a \Traversable instance to change owner
211       * @param string                    $user      The new owner user name
212       * @param bool                      $recursive Whether change the owner recursively or not
213       *
214       * @throws IOException When the change fail
215       */
216      public function chown($files, $user, $recursive = false)
217      {
218          foreach ($this->toIterator($files) as $file) {
219              if ($recursive && is_dir($file) && !is_link($file)) {
220                  $this->chown(new \FilesystemIterator($file), $user, true);
221              }
222              if (is_link($file) && function_exists('lchown')) {
223                  if (true !== @lchown($file, $user)) {
224                      throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
225                  }
226              } else {
227                  if (true !== @chown($file, $user)) {
228                      throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
229                  }
230              }
231          }
232      }
233   
234      /**
235       * Change the group of an array of files or directories.
236       *
237       * @param string|array|\Traversable $files     A filename, an array of files, or a \Traversable instance to change group
238       * @param string                    $group     The group name
239       * @param bool                      $recursive Whether change the group recursively or not
240       *
241       * @throws IOException When the change fail
242       */
243      public function chgrp($files, $group, $recursive = false)
244      {
245          foreach ($this->toIterator($files) as $file) {
246              if ($recursive && is_dir($file) && !is_link($file)) {
247                  $this->chgrp(new \FilesystemIterator($file), $group, true);
248              }
249              if (is_link($file) && function_exists('lchgrp')) {
250                  if (true !== @lchgrp($file, $group) || (defined('HHVM_VERSION') && !posix_getgrnam($group))) {
251                      throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
252                  }
253              } else {
254                  if (true !== @chgrp($file, $group)) {
255                      throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
256                  }
257              }
258          }
259      }
260   
261      /**
262       * Renames a file or a directory.
263       *
264       * @param string $origin    The origin filename or directory
265       * @param string $target    The new filename or directory
266       * @param bool   $overwrite Whether to overwrite the target if it already exists
267       *
268       * @throws IOException When target file or directory already exists
269       * @throws IOException When origin cannot be renamed
270       */
271      public function rename($origin, $target, $overwrite = false)
272      {
273          // we check that target does not exist
274          if (!$overwrite && $this->isReadable($target)) {
275              throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
276          }
277   
278          if (true !== @rename($origin, $target)) {
279              throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
280          }
281      }
282   
283      /**
284       * Tells whether a file exists and is readable.
285       *
286       * @param string $filename Path to the file
287       *
288       * @return bool
289       *
290       * @throws IOException When windows path is longer than 258 characters
291       */
292      private function isReadable($filename)
293      {
294          if ('\\' === DIRECTORY_SEPARATOR && strlen($filename) > 258) {
295              throw new IOException('Could not check if file is readable because path length exceeds 258 characters.', 0, null, $filename);
296          }
297   
298          return is_readable($filename);
299      }
300   
301      /**
302       * Creates a symbolic link or copy a directory.
303       *
304       * @param string $originDir     The origin directory path
305       * @param string $targetDir     The symbolic link name
306       * @param bool   $copyOnWindows Whether to copy files if on Windows
307       *
308       * @throws IOException When symlink fails
309       */
310      public function symlink($originDir, $targetDir, $copyOnWindows = false)
311      {
312          if ('\\' === DIRECTORY_SEPARATOR) {
313              $originDir = strtr($originDir, '/', '\\');
314              $targetDir = strtr($targetDir, '/', '\\');
315   
316              if ($copyOnWindows) {
317                  $this->mirror($originDir, $targetDir);
318   
319                  return;
320              }
321          }
322   
323          $this->mkdir(dirname($targetDir));
324   
325          $ok = false;
326          if (is_link($targetDir)) {
327              if (readlink($targetDir) != $originDir) {
328                  $this->remove($targetDir);
329              } else {
330                  $ok = true;
331              }
332          }
333   
334          if (!$ok && true !== @symlink($originDir, $targetDir)) {
335              $report = error_get_last();
336              if (is_array($report)) {
337                  if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) {
338                      throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir);
339                  }
340              }
341              throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir);
342          }
343      }
344   
345      /**
346       * Given an existing path, convert it to a path relative to a given starting path.
347       *
348       * @param string $endPath   Absolute path of target
349       * @param string $startPath Absolute path where traversal begins
350       *
351       * @return string Path of target relative to starting path
352       */
353      public function makePathRelative($endPath, $startPath)
354      {
355          // Normalize separators on Windows
356          if ('\\' === DIRECTORY_SEPARATOR) {
357              $endPath = str_replace('\\', '/', $endPath);
358              $startPath = str_replace('\\', '/', $startPath);
359          }
360   
361          // Split the paths into arrays
362          $startPathArr = explode('/', trim($startPath, '/'));
363          $endPathArr = explode('/', trim($endPath, '/'));
364   
365          // Find for which directory the common path stops
366          $index = 0;
367          while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
368              ++$index;
369          }
370   
371          // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
372          if (count($startPathArr) === 1 && $startPathArr[0] === '') {
373              $depth = 0;
374          } else {
375              $depth = count($startPathArr) - $index;
376          }
377   
378          // When we need to traverse from the start, and we are starting from a root path, don't add '../'
379          if ('/' === $startPath[0] && 0 === $index && 0 === $depth) {
380              $traverser = '';
381          } else {
382              // Repeated "../" for each level need to reach the common path
383              $traverser = str_repeat('../', $depth);
384          }
385   
386          $endPathRemainder = implode('/', array_slice($endPathArr, $index));
387   
388          // Construct $endPath from traversing to the common path, then to the remaining $endPath
389          $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
390   
391          return '' === $relativePath ? './' : $relativePath;
392      }
393   
394      /**
395       * Mirrors a directory to another.
396       *
397       * @param string       $originDir The origin directory
398       * @param string       $targetDir The target directory
399       * @param \Traversable $iterator  A Traversable instance
400       * @param array        $options   An array of boolean options
401       *                                Valid options are:
402       *                                - $options['override'] Whether to override an existing file on copy or not (see copy())
403       *                                - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
404       *                                - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
405       *
406       * @throws IOException When file type is unknown
407       */
408      public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array())
409      {
410          $targetDir = rtrim($targetDir, '/\\');
411          $originDir = rtrim($originDir, '/\\');
412   
413          // Iterate in destination folder to remove obsolete entries
414          if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
415              $deleteIterator = $iterator;
416              if (null === $deleteIterator) {
417                  $flags = \FilesystemIterator::SKIP_DOTS;
418                  $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
419              }
420              foreach ($deleteIterator as $file) {
421                  $origin = str_replace($targetDir, $originDir, $file->getPathname());
422                  if (!$this->exists($origin)) {
423                      $this->remove($file);
424                  }
425              }
426          }
427   
428          $copyOnWindows = false;
429          if (isset($options['copy_on_windows'])) {
430              $copyOnWindows = $options['copy_on_windows'];
431          }
432   
433          if (null === $iterator) {
434              $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
435              $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
436          }
437   
438          if ($this->exists($originDir)) {
439              $this->mkdir($targetDir);
440          }
441   
442          foreach ($iterator as $file) {
443              $target = str_replace($originDir, $targetDir, $file->getPathname());
444   
445              if ($copyOnWindows) {
446                  if (is_file($file)) {
447                      $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
448                  } elseif (is_dir($file)) {
449                      $this->mkdir($target);
450                  } else {
451                      throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
452                  }
453              } else {
454                  if (is_link($file)) {
455                      $this->symlink($file->getLinkTarget(), $target);
456                  } elseif (is_dir($file)) {
457                      $this->mkdir($target);
458                  } elseif (is_file($file)) {
459                      $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
460                  } else {
461                      throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
462                  }
463              }
464          }
465      }
466   
467      /**
468       * Returns whether the file path is an absolute path.
469       *
470       * @param string $file A file path
471       *
472       * @return bool
473       */
474      public function isAbsolutePath($file)
475      {
476          return strspn($file, '/\\', 0, 1)
477              || (strlen($file) > 3 && ctype_alpha($file[0])
478                  && substr($file, 1, 1) === ':'
479                  && strspn($file, '/\\', 2, 1)
480              )
481              || null !== parse_url($file, PHP_URL_SCHEME)
482          ;
483      }
484   
485      /**
486       * Creates a temporary file with support for custom stream wrappers.
487       *
488       * @param string $dir    The directory where the temporary filename will be created
489       * @param string $prefix The prefix of the generated temporary filename
490       *                       Note: Windows uses only the first three characters of prefix
491       *
492       * @return string The new temporary filename (with path), or throw an exception on failure
493       */
494      public function tempnam($dir, $prefix)
495      {
496          list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir);
497   
498          // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
499          if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
500              $tmpFile = @tempnam($hierarchy, $prefix);
501   
502              // If tempnam failed or no scheme return the filename otherwise prepend the scheme
503              if (false !== $tmpFile) {
504                  if (null !== $scheme && 'gs' !== $scheme) {
505                      return $scheme.'://'.$tmpFile;
506                  }
507   
508                  return $tmpFile;
509              }
510   
511              throw new IOException('A temporary file could not be created.');
512          }
513   
514          // Loop until we create a valid temp file or have reached 10 attempts
515          for ($i = 0; $i < 10; ++$i) {
516              // Create a unique filename
517              $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true);
518   
519              // Use fopen instead of file_exists as some streams do not support stat
520              // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
521              $handle = @fopen($tmpFile, 'x+');
522   
523              // If unsuccessful restart the loop
524              if (false === $handle) {
525                  continue;
526              }
527   
528              // Close the file if it was successfully opened
529              @fclose($handle);
530   
531              return $tmpFile;
532          }
533   
534          throw new IOException('A temporary file could not be created.');
535      }
536   
537      /**
538       * Atomically dumps content into a file.
539       *
540       * @param string   $filename The file to be written to
541       * @param string   $content  The data to write into the file
542       * @param null|int $mode     The file mode (octal). If null, file permissions are not modified
543       *                           Deprecated since version 2.3.12, to be removed in 3.0.
544       *
545       * @throws IOException If the file cannot be written to.
546       */
547      public function dumpFile($filename, $content, $mode = 0666)
548      {
549          $dir = dirname($filename);
550   
551          if (!is_dir($dir)) {
552              $this->mkdir($dir);
553          } elseif (!is_writable($dir)) {
554              throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
555          }
556   
557          $tmpFile = $this->tempnam($dir, basename($filename));
558   
559          if (false === @file_put_contents($tmpFile, $content)) {
560              throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
561          }
562   
563          if (null !== $mode) {
564              if (func_num_args() > 2) {
565                  @trigger_error('Support for modifying file permissions is deprecated since version 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED);
566              }
567   
568              $this->chmod($tmpFile, $mode);
569          }
570          $this->rename($tmpFile, $filename, true);
571      }
572   
573      /**
574       * @param mixed $files
575       *
576       * @return \Traversable
577       */
578      private function toIterator($files)
579      {
580          if (!$files instanceof \Traversable) {
581              $files = new \ArrayObject(is_array($files) ? $files : array($files));
582          }
583   
584          return $files;
585      }
586   
587      /**
588       * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)).
589       *
590       * @param string $filename The filename to be parsed
591       *
592       * @return array The filename scheme and hierarchical part
593       */
594      private function getSchemeAndHierarchy($filename)
595      {
596          $components = explode('://', $filename, 2);
597   
598          return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
599      }
600  }
601