123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- <?php
- namespace Drupal\Core\Config;
- use Drupal\Component\FileCache\FileCacheFactory;
- use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
- use Drupal\Core\Serialization\Yaml;
- /**
- * Defines the file storage.
- */
- class FileStorage implements StorageInterface {
- /**
- * The storage collection.
- *
- * @var string
- */
- protected $collection;
- /**
- * The filesystem path for configuration objects.
- *
- * @var string
- */
- protected $directory = '';
- /**
- * The file cache object.
- *
- * @var \Drupal\Component\FileCache\FileCacheInterface
- */
- protected $fileCache;
- /**
- * Constructs a new FileStorage.
- *
- * @param string $directory
- * A directory path to use for reading and writing of configuration files.
- * @param string $collection
- * (optional) The collection to store configuration in. Defaults to the
- * default collection.
- */
- public function __construct($directory, $collection = StorageInterface::DEFAULT_COLLECTION) {
- $this->directory = $directory;
- $this->collection = $collection;
- // Use a NULL File Cache backend by default. This will ensure only the
- // internal statc caching of FileCache is used and thus avoids blowing up
- // the APCu cache.
- $this->fileCache = FileCacheFactory::get('config', ['cache_backend_class' => NULL]);
- }
- /**
- * Returns the path to the configuration file.
- *
- * @return string
- * The path to the configuration file.
- */
- public function getFilePath($name) {
- return $this->getCollectionDirectory() . '/' . $name . '.' . static::getFileExtension();
- }
- /**
- * Returns the file extension used by the file storage for all configuration files.
- *
- * @return string
- * The file extension.
- */
- public static function getFileExtension() {
- return 'yml';
- }
- /**
- * Check if the directory exists and create it if not.
- */
- protected function ensureStorage() {
- $dir = $this->getCollectionDirectory();
- $success = file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
- // Only create .htaccess file in root directory.
- if ($dir == $this->directory) {
- $success = $success && file_save_htaccess($this->directory, TRUE, TRUE);
- }
- if (!$success) {
- throw new StorageException('Failed to create config directory ' . $dir);
- }
- return $this;
- }
- /**
- * {@inheritdoc}
- */
- public function exists($name) {
- return file_exists($this->getFilePath($name));
- }
- /**
- * Implements Drupal\Core\Config\StorageInterface::read().
- *
- * @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
- */
- public function read($name) {
- if (!$this->exists($name)) {
- return FALSE;
- }
- $filepath = $this->getFilePath($name);
- if ($data = $this->fileCache->get($filepath)) {
- return $data;
- }
- $data = file_get_contents($filepath);
- try {
- $data = $this->decode($data);
- }
- catch (InvalidDataTypeException $e) {
- throw new UnsupportedDataTypeConfigException('Invalid data type in config ' . $name . ', found in file' . $filepath . ' : ' . $e->getMessage());
- }
- $this->fileCache->set($filepath, $data);
- return $data;
- }
- /**
- * {@inheritdoc}
- */
- public function readMultiple(array $names) {
- $list = [];
- foreach ($names as $name) {
- if ($data = $this->read($name)) {
- $list[$name] = $data;
- }
- }
- return $list;
- }
- /**
- * {@inheritdoc}
- */
- public function write($name, array $data) {
- try {
- $encoded_data = $this->encode($data);
- }
- catch (InvalidDataTypeException $e) {
- throw new StorageException("Invalid data type in config $name: {$e->getMessage()}");
- }
- $target = $this->getFilePath($name);
- $status = @file_put_contents($target, $encoded_data);
- if ($status === FALSE) {
- // Try to make sure the directory exists and try writing again.
- $this->ensureStorage();
- $status = @file_put_contents($target, $encoded_data);
- }
- if ($status === FALSE) {
- throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name));
- }
- else {
- drupal_chmod($target);
- }
- $this->fileCache->set($target, $data);
- return TRUE;
- }
- /**
- * {@inheritdoc}
- */
- public function delete($name) {
- if (!$this->exists($name)) {
- $dir = $this->getCollectionDirectory();
- if (!file_exists($dir)) {
- throw new StorageException($dir . '/ not found.');
- }
- return FALSE;
- }
- $this->fileCache->delete($this->getFilePath($name));
- return drupal_unlink($this->getFilePath($name));
- }
- /**
- * {@inheritdoc}
- */
- public function rename($name, $new_name) {
- $status = @rename($this->getFilePath($name), $this->getFilePath($new_name));
- if ($status === FALSE) {
- throw new StorageException('Failed to rename configuration file from: ' . $this->getFilePath($name) . ' to: ' . $this->getFilePath($new_name));
- }
- $this->fileCache->delete($this->getFilePath($name));
- $this->fileCache->delete($this->getFilePath($new_name));
- return TRUE;
- }
- /**
- * {@inheritdoc}
- */
- public function encode($data) {
- return Yaml::encode($data);
- }
- /**
- * {@inheritdoc}
- */
- public function decode($raw) {
- $data = Yaml::decode($raw);
- // A simple string is valid YAML for any reason.
- if (!is_array($data)) {
- return FALSE;
- }
- return $data;
- }
- /**
- * {@inheritdoc}
- */
- public function listAll($prefix = '') {
- $dir = $this->getCollectionDirectory();
- if (!is_dir($dir)) {
- return [];
- }
- $extension = '.' . static::getFileExtension();
- // glob() directly calls into libc glob(), which is not aware of PHP stream
- // wrappers. Same for \GlobIterator (which additionally requires an absolute
- // realpath() on Windows).
- // @see https://github.com/mikey179/vfsStream/issues/2
- $files = scandir($dir);
- $names = [];
- $pattern = '/^' . preg_quote($prefix, '/') . '.*' . preg_quote($extension, '/') . '$/';
- foreach ($files as $file) {
- if ($file[0] !== '.' && preg_match($pattern, $file)) {
- $names[] = basename($file, $extension);
- }
- }
- return $names;
- }
- /**
- * {@inheritdoc}
- */
- public function deleteAll($prefix = '') {
- $success = TRUE;
- $files = $this->listAll($prefix);
- foreach ($files as $name) {
- if (!$this->delete($name) && $success) {
- $success = FALSE;
- }
- }
- if ($success && $this->collection != StorageInterface::DEFAULT_COLLECTION) {
- // Remove empty directories.
- if (!(new \FilesystemIterator($this->getCollectionDirectory()))->valid()) {
- drupal_rmdir($this->getCollectionDirectory());
- }
- }
- return $success;
- }
- /**
- * {@inheritdoc}
- */
- public function createCollection($collection) {
- return new static(
- $this->directory,
- $collection
- );
- }
- /**
- * {@inheritdoc}
- */
- public function getCollectionName() {
- return $this->collection;
- }
- /**
- * {@inheritdoc}
- */
- public function getAllCollectionNames() {
- $collections = $this->getAllCollectionNamesHelper($this->directory);
- sort($collections);
- return $collections;
- }
- /**
- * Helper function for getAllCollectionNames().
- *
- * If the file storage has the following subdirectory structure:
- * ./another_collection/one
- * ./another_collection/two
- * ./collection/sub/one
- * ./collection/sub/two
- * this function will return:
- * @code
- * array(
- * 'another_collection.one',
- * 'another_collection.two',
- * 'collection.sub.one',
- * 'collection.sub.two',
- * );
- * @endcode
- *
- * @param string $directory
- * The directory to check for sub directories. This allows this
- * function to be used recursively to discover all the collections in the
- * storage.
- *
- * @return array
- * A list of collection names contained within the provided directory.
- */
- protected function getAllCollectionNamesHelper($directory) {
- $collections = [];
- $pattern = '/\.' . preg_quote($this->getFileExtension(), '/') . '$/';
- foreach (new \DirectoryIterator($directory) as $fileinfo) {
- if ($fileinfo->isDir() && !$fileinfo->isDot()) {
- $collection = $fileinfo->getFilename();
- // Recursively call getAllCollectionNamesHelper() to discover if there
- // are subdirectories. Subdirectories represent a dotted collection
- // name.
- $sub_collections = $this->getAllCollectionNamesHelper($directory . '/' . $collection);
- if (!empty($sub_collections)) {
- // Build up the collection name by concatenating the subdirectory
- // names with the current directory name.
- foreach ($sub_collections as $sub_collection) {
- $collections[] = $collection . '.' . $sub_collection;
- }
- }
- // Check that the collection is valid by searching it for configuration
- // objects. A directory without any configuration objects is not a valid
- // collection.
- // @see \Drupal\Core\Config\FileStorage::listAll()
- foreach (scandir($directory . '/' . $collection) as $file) {
- if ($file[0] !== '.' && preg_match($pattern, $file)) {
- $collections[] = $collection;
- break;
- }
- }
- }
- }
- return $collections;
- }
- /**
- * Gets the directory for the collection.
- *
- * @return string
- * The directory for the collection.
- */
- protected function getCollectionDirectory() {
- if ($this->collection == StorageInterface::DEFAULT_COLLECTION) {
- $dir = $this->directory;
- }
- else {
- $dir = $this->directory . '/' . str_replace('.', '/', $this->collection);
- }
- return $dir;
- }
- }
|