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