FileStorage.php 7.59 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\Component\PhpStorage\FileStorage.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 */

namespace Drupal\Component\PhpStorage;

/**
 * Stores the code as regular PHP files.
 */
class FileStorage implements PhpStorageInterface {

  /**
   * The directory where the files should be stored.
   *
   * @var string
   */
  protected $directory;

  /**
   * Constructs this FileStorage object.
   *
25
   * @param array $configuration
26 27
   *   An associative array, containing at least these two keys:
   *   - directory: The directory where the files should be stored.
28 29
   *   - bin: The storage bin. Multiple storage objects can be instantiated with
   *     the same configuration, but for different bins..
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   */
  public function __construct(array $configuration) {
    $this->directory = $configuration['directory'] . '/' . $configuration['bin'];
  }

  /**
   * Implements Drupal\Component\PhpStorage\PhpStorageInterface::exists().
   */
  public function exists($name) {
    return file_exists($this->getFullPath($name));
  }

  /**
   * Implements Drupal\Component\PhpStorage\PhpStorageInterface::load().
   */
  public function load($name) {
    // The FALSE returned on failure is enough for the caller to handle this,
    // we do not want a warning too.
    return (@include_once $this->getFullPath($name)) !== FALSE;
  }

  /**
   * Implements Drupal\Component\PhpStorage\PhpStorageInterface::save().
   */
  public function save($name, $code) {
    $path = $this->getFullPath($name);
56 57 58 59 60 61 62
    $directory = dirname($path);
    if ($this->ensureDirectory($directory)) {
      $htaccess_path =  $directory . '/.htaccess';
      if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) {
        @chmod($htaccess_path, 0444);
      }
    }
63 64 65
    return (bool) file_put_contents($path, $code);
  }

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
  /**
   * Returns the standard .htaccess lines that Drupal writes to file directories.
   *
   * This code is located here so this component can be stand-alone, but it is
   * also called by file_htaccess_lines().
   *
   * @param bool $private
   *   (Optional) Set to FALSE to return the .htaccess lines for an open and
   *   public directory. The default is TRUE, which returns the .htaccess lines
   *   for a private and protected directory.
   *
   * @return string
   *   The desired contents of the .htaccess file.
   */
  public static function htaccessLines($private = TRUE) {
    $lines = <<<EOF
# Turn off all options we don't need.
Options None
Options +FollowSymLinks

# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
  # Override the handler again if we're run later in the evaluation list.
  SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>

# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
  php_flag engine off
</IfModule>
EOF;

    if ($private) {
      $lines = "Deny from all\n\n" . $lines;
    }

    return $lines;
  }

  /**
   * Ensures the directory exists, has the right permissions, and a .htaccess.
   *
   * For compatibility with open_basedir, the requested directory is created
   * using a recursion logic that is based on the relative directory path/tree:
   * It works from the end of the path recursively back towards the root
   * directory, until an existing parent directory is found. From there, the
   * subdirectories are created.
   *
   * @param string $directory
   *   The directory path.
   * @param int $mode
   *   The mode, permissions, the directory should have.
   *
   * @return bool
   *   TRUE if the directory exists or has been created, FALSE otherwise.
   */
  protected function ensureDirectory($directory, $mode = 0777) {
    if ($this->createDirectory($directory, $mode)) {
      $htaccess_path =  $directory . '/.htaccess';
      if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) {
        @chmod($htaccess_path, 0444);
      }
    }
  }

132
  /**
133 134 135 136 137 138 139
   * Ensures the requested directory exists and has the right permissions.
   *
   * For compatibility with open_basedir, the requested directory is created
   * using a recursion logic that is based on the relative directory path/tree:
   * It works from the end of the path recursively back towards the root
   * directory, until an existing parent directory is found. From there, the
   * subdirectories are created.
140 141 142 143 144
   *
   * @param string $directory
   *   The directory path.
   * @param int $mode
   *   The mode, permissions, the directory should have.
145 146 147 148 149
   * @param bool $is_backwards_recursive
   *   Internal use only.
   *
   * @return bool
   *   TRUE if the directory exists or has been created, FALSE otherwise.
150
   */
151
  protected function createDirectory($directory, $mode = 0777, $is_backwards_recursive = FALSE) {
152 153 154 155 156 157 158 159 160 161 162 163 164 165
    // If the directory exists already, there's nothing to do.
    if (is_dir($directory)) {
      return TRUE;
    }
    // Otherwise, try to create the directory and ensure to set its permissions,
    // because mkdir() obeys the umask of the current process.
    if (is_dir($parent = dirname($directory))) {
      // If the parent directory exists, then the backwards recursion must end,
      // regardless of whether the subdirectory could be created.
      if ($status = mkdir($directory)) {
        // Only try to chmod() if the subdirectory could be created.
        $status = chmod($directory, $mode);
      }
      return $is_backwards_recursive ? TRUE : $status;
166
    }
167 168 169 170 171
    // If the parent directory and the requested directory does not exist and
    // could not be created above, walk the requested directory path back up
    // until an existing directory is hit, and from there, recursively create
    // the sub-directories. Only if that recursion succeeds, create the final,
    // originally requested subdirectory.
172
    return $this->createDirectory($parent, $mode, TRUE) && mkdir($directory) && chmod($directory, $mode);
173 174
  }

175 176 177 178 179
  /**
   * Implements Drupal\Component\PhpStorage\PhpStorageInterface::delete().
   */
  public function delete($name) {
    $path = $this->getFullPath($name);
180 181 182 183
    if (file_exists($path)) {
      return $this->unlink($path);
    }
    return FALSE;
184 185 186 187 188 189 190 191
  }

  /**
   * Returns the full path where the file is or should be stored.
   */
  protected function getFullPath($name) {
    return $this->directory . '/' . $name;
  }
192 193 194 195

  /**
   * Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
   */
196
  public function writeable() {
197 198 199 200 201 202
    return TRUE;
  }

  /**
   * Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
   */
203 204
  public function deleteAll() {
    return $this->unlink($this->directory);
205 206 207
  }

  /**
208 209 210 211 212 213 214 215 216 217 218 219
   * Deletes files and/or directories in the specified path.
   *
   * If the specified path is a directory the method will
   * call itself recursively to process the contents. Once the contents have
   * been removed the directory will also be removed.
   *
   * @param string $path
   *   A string containing either a file or directory path.
   *
   * @return boolean
   *   TRUE for success or if path does not exist, FALSE in the event of an
   *   error.
220
   */
221
  protected function unlink($path) {
222
    if (file_exists($path)) {
223
      if (is_dir($path)) {
224
        // Ensure the folder is writable.
225 226 227 228
        @chmod($path, 0777);
        foreach (new \DirectoryIterator($path) as $fileinfo) {
          if (!$fileinfo->isDot()) {
            $this->unlink($fileinfo->getPathName());
229 230 231 232
          }
        }
        return @rmdir($path);
      }
233 234
      // Windows needs the file to be writable.
      @chmod($path, 0700);
235
      return @unlink($path);
236
    }
237 238
    // If there's nothing to delete return TRUE anyway.
    return TRUE;
239
  }
240
}