"Fossies" - the Fresh Open Source Software Archive

Member "grav/system/src/Grav/Framework/File/AbstractFile.php" (19 Mar 2020, 9913 Bytes) of package /linux/www/grav-v1.6.23.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "AbstractFile.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 declare(strict_types=1);
    4 
    5 /**
    6  * @package    Grav\Framework\File
    7  *
    8  * @copyright  Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
    9  * @license    MIT License; see LICENSE file for details.
   10  */
   11 
   12 namespace Grav\Framework\File;
   13 
   14 use Grav\Framework\File\Interfaces\FileInterface;
   15 use Grav\Framework\Filesystem\Filesystem;
   16 
   17 class AbstractFile implements FileInterface
   18 {
   19     /** @var Filesystem */
   20     private $filesystem;
   21 
   22     /** @var string */
   23     private $filepath;
   24 
   25     /** @var string|null */
   26     private $filename;
   27 
   28     /** @var string|null */
   29     private $path;
   30 
   31     /** @var string|null */
   32     private $basename;
   33 
   34     /** @var string|null */
   35     private $extension;
   36 
   37     /** @var resource|null */
   38     private $handle;
   39 
   40     /** @var bool */
   41     private $locked = false;
   42 
   43     /**
   44      * @param string $filepath
   45      * @param Filesystem|null $filesystem
   46      */
   47     public function __construct(string $filepath, Filesystem $filesystem = null)
   48     {
   49         $this->filesystem = $filesystem ?? Filesystem::getInstance();
   50         $this->setFilepath($filepath);
   51     }
   52 
   53     /**
   54      * Unlock file when the object gets destroyed.
   55      */
   56     public function __destruct()
   57     {
   58         if ($this->isLocked()) {
   59             $this->unlock();
   60         }
   61     }
   62 
   63     public function __clone()
   64     {
   65         $this->handle = null;
   66         $this->locked = false;
   67     }
   68 
   69     /**
   70      * @return string
   71      */
   72     public function serialize(): string
   73     {
   74         return serialize($this->doSerialize());
   75     }
   76 
   77     /**
   78      * @param string $serialized
   79      */
   80     public function unserialize($serialized): void
   81     {
   82         $this->doUnserialize(unserialize($serialized, ['allowed_classes' => false]));
   83     }
   84 
   85     /**
   86      * {@inheritdoc}
   87      * @see FileInterface::getFilePath()
   88      */
   89     public function getFilePath(): string
   90     {
   91         return $this->filepath;
   92     }
   93 
   94     /**
   95      * {@inheritdoc}
   96      * @see FileInterface::getPath()
   97      */
   98     public function getPath(): string
   99     {
  100         if (null === $this->path) {
  101             $this->setPathInfo();
  102         }
  103 
  104         return $this->path;
  105     }
  106 
  107     /**
  108      * {@inheritdoc}
  109      * @see FileInterface::getFilename()
  110      */
  111     public function getFilename(): string
  112     {
  113         if (null === $this->filename) {
  114             $this->setPathInfo();
  115         }
  116 
  117         return $this->filename;
  118     }
  119 
  120     /**
  121      * {@inheritdoc}
  122      * @see FileInterface::getBasename()
  123      */
  124     public function getBasename(): string
  125     {
  126         if (null === $this->basename) {
  127             $this->setPathInfo();
  128         }
  129 
  130         return $this->basename;
  131     }
  132 
  133     /**
  134      * {@inheritdoc}
  135      * @see FileInterface::getExtension()
  136      */
  137     public function getExtension(bool $withDot = false): string
  138     {
  139         if (null === $this->extension) {
  140             $this->setPathInfo();
  141         }
  142 
  143         return ($withDot ? '.' : '') . $this->extension;
  144     }
  145 
  146     /**
  147      * {@inheritdoc}
  148      * @see FileInterface::exists()
  149      */
  150     public function exists(): bool
  151     {
  152         return is_file($this->filepath);
  153     }
  154 
  155     /**
  156      * {@inheritdoc}
  157      * @see FileInterface::getCreationTime()
  158      */
  159     public function getCreationTime(): int
  160     {
  161         return is_file($this->filepath) ? filectime($this->filepath) : time();
  162     }
  163 
  164     /**
  165      * {@inheritdoc}
  166      * @see FileInterface::getModificationTime()
  167      */
  168     public function getModificationTime(): int
  169     {
  170         return is_file($this->filepath) ? filemtime($this->filepath) : time();
  171     }
  172 
  173     /**
  174      * {@inheritdoc}
  175      * @see FileInterface::lock()
  176      */
  177     public function lock(bool $block = true): bool
  178     {
  179         if (!$this->handle) {
  180             if (!$this->mkdir($this->getPath())) {
  181                 throw new \RuntimeException('Creating directory failed for ' . $this->filepath);
  182             }
  183             $this->handle = @fopen($this->filepath, 'cb+');
  184             if (!$this->handle) {
  185                 $error = error_get_last();
  186 
  187                 throw new \RuntimeException("Opening file for writing failed on error {$error['message']}");
  188             }
  189         }
  190 
  191         $lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
  192 
  193         // Some filesystems do not support file locks, only fail if another process holds the lock.
  194         $this->locked = flock($this->handle, $lock, $wouldblock) || !$wouldblock;
  195 
  196         return $this->locked;
  197     }
  198 
  199     /**
  200      * {@inheritdoc}
  201      * @see FileInterface::unlock()
  202      */
  203     public function unlock(): bool
  204     {
  205         if (!$this->handle) {
  206             return false;
  207         }
  208 
  209         if ($this->locked) {
  210             flock($this->handle, LOCK_UN);
  211             $this->locked = false;
  212         }
  213 
  214         fclose($this->handle);
  215         $this->handle = null;
  216 
  217         return true;
  218     }
  219 
  220     /**
  221      * {@inheritdoc}
  222      * @see FileInterface::isLocked()
  223      */
  224     public function isLocked(): bool
  225     {
  226         return $this->locked;
  227     }
  228 
  229     /**
  230      * {@inheritdoc}
  231      * @see FileInterface::isReadable()
  232      */
  233     public function isReadable(): bool
  234     {
  235         return is_readable($this->filepath) && is_file($this->filepath);
  236     }
  237 
  238     /**
  239      * {@inheritdoc}
  240      * @see FileInterface::isWritable()
  241      */
  242     public function isWritable(): bool
  243     {
  244         if (!file_exists($this->filepath)) {
  245             return $this->isWritablePath($this->getPath());
  246         }
  247 
  248         return is_writable($this->filepath) && is_file($this->filepath);
  249     }
  250 
  251     /**
  252      * {@inheritdoc}
  253      * @see FileInterface::load()
  254      */
  255     public function load()
  256     {
  257         return file_get_contents($this->filepath);
  258     }
  259 
  260     /**
  261      * {@inheritdoc}
  262      * @see FileInterface::save()
  263      */
  264     public function save($data): void
  265     {
  266         $filepath = $this->filepath;
  267         $dir = $this->getPath();
  268 
  269         if (!$this->mkdir($dir)) {
  270             throw new \RuntimeException('Creating directory failed for ' . $filepath);
  271         }
  272 
  273         try {
  274             if ($this->handle) {
  275                 $tmp = true;
  276                 // As we are using non-truncating locking, make sure that the file is empty before writing.
  277                 if (@ftruncate($this->handle, 0) === false || @fwrite($this->handle, $data) === false) {
  278                     // Writing file failed, throw an error.
  279                     $tmp = false;
  280                 }
  281             } else {
  282                 // Create file with a temporary name and rename it to make the save action atomic.
  283                 $tmp = $this->tempname($filepath);
  284                 if (@file_put_contents($tmp, $data) === false) {
  285                     $tmp = false;
  286                 } elseif (@rename($tmp, $filepath) === false) {
  287                     @unlink($tmp);
  288                     $tmp = false;
  289                 }
  290             }
  291         } catch (\Exception $e) {
  292             $tmp = false;
  293         }
  294 
  295         if ($tmp === false) {
  296             throw new \RuntimeException('Failed to save file ' . $filepath);
  297         }
  298 
  299         // Touch the directory as well, thus marking it modified.
  300         @touch($dir);
  301     }
  302 
  303     /**
  304      * {@inheritdoc}
  305      * @see FileInterface::rename()
  306      */
  307     public function rename(string $path): bool
  308     {
  309         if ($this->exists() && !@rename($this->filepath, $path)) {
  310             return false;
  311         }
  312 
  313         $this->setFilepath($path);
  314 
  315         return true;
  316     }
  317 
  318     /**
  319      * {@inheritdoc}
  320      * @see FileInterface::delete()
  321      */
  322     public function delete(): bool
  323     {
  324         return @unlink($this->filepath);
  325     }
  326 
  327     /**
  328      * @param  string  $dir
  329      * @return bool
  330      * @throws \RuntimeException
  331      * @internal
  332      */
  333     protected function mkdir(string $dir): bool
  334     {
  335         // Silence error for open_basedir; should fail in mkdir instead.
  336         if (@is_dir($dir)) {
  337             return true;
  338         }
  339 
  340         $success = @mkdir($dir, 0777, true);
  341 
  342         if (!$success) {
  343             // Take yet another look, make sure that the folder doesn't exist.
  344             clearstatcache(true, $dir);
  345             if (!@is_dir($dir)) {
  346                 return false;
  347             }
  348         }
  349 
  350         return true;
  351     }
  352 
  353     /**
  354      * @return array
  355      */
  356     protected function doSerialize(): array
  357     {
  358         return [
  359             'filepath' => $this->filepath
  360         ];
  361     }
  362 
  363     /**
  364      * @param array $serialized
  365      */
  366     protected function doUnserialize(array $serialized): void
  367     {
  368         $this->setFilepath($serialized['filepath']);
  369     }
  370 
  371     /**
  372      * @param string $filepath
  373      */
  374     protected function setFilepath(string $filepath): void
  375     {
  376         $this->filepath = $filepath;
  377         $this->filename = null;
  378         $this->basename = null;
  379         $this->path = null;
  380         $this->extension = null;
  381     }
  382 
  383     protected function setPathInfo(): void
  384     {
  385         $pathInfo = $this->filesystem->pathinfo($this->filepath);
  386 
  387         $this->filename = $pathInfo['filename'] ?? null;
  388         $this->basename = $pathInfo['basename'] ?? null;
  389         $this->path = $pathInfo['dirname'] ?? null;
  390         $this->extension = $pathInfo['extension'] ?? null;
  391     }
  392 
  393     /**
  394      * @param  string  $dir
  395      * @return bool
  396      * @internal
  397      */
  398     protected function isWritablePath(string $dir): bool
  399     {
  400         if ($dir === '') {
  401             return false;
  402         }
  403 
  404         if (!file_exists($dir)) {
  405             // Recursively look up in the directory tree.
  406             return $this->isWritablePath($this->filesystem->parent($dir));
  407         }
  408 
  409         return is_dir($dir) && is_writable($dir);
  410     }
  411 
  412     /**
  413      * @param string $filename
  414      * @param int $length
  415      * @return string
  416      */
  417     protected function tempname(string $filename, int $length = 5)
  418     {
  419         do {
  420             $test = $filename . substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length);
  421         } while (file_exists($test));
  422 
  423         return $test;
  424     }
  425 }