<?php /** * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; under version 2 * of the License (non-upgradable). * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright (c) 2015 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT); * */ namespace oat\oatbox\filesystem; use oat\oatbox\service\ConfigurableService; use League\Flysystem\AdapterInterface; use common_exception_Error; use Zend\ServiceManager\ServiceLocatorAwareInterface; use \League\Flysystem\Filesystem as FlyFileSystem; use League\Flysystem\FilesystemInterface; /** * A service to reference and retrieve filesystems */ class FileSystemService extends ConfigurableService { const SERVICE_ID = 'generis/filesystem'; const OPTION_FILE_PATH = 'filesPath'; const OPTION_ADAPTERS = 'adapters'; const OPTION_DIRECTORIES = 'dirs'; const FLYSYSTEM_ADAPTER_NS = '\\League\\Flysystem\\Adapter\\'; const FLYSYSTEM_LOCAL_ADAPTER = 'Local'; private $filesystems = []; /** * * @param $id * @return \oat\oatbox\filesystem\Directory */ public function getDirectory($id) { return $this->propagate(new Directory($id, '')); } /** * Returns the directory config * @return array */ protected function getDirectories() { return $this->hasOption(self::OPTION_DIRECTORIES) ? $this->getOption(self::OPTION_DIRECTORIES) : []; } /** * Add a directory reference * @param string $id * @param string $adapterId */ protected function addDir($id, $adapterId) { $dirs = $this->getDirectories(); $dirs[$id] = $adapterId; $this->setOption(self::OPTION_DIRECTORIES, $dirs); } /** * Returns whenever or not a FS exists * @param string $id * @return boolean */ public function hasDirectory($id) { $adapterConfig = $this->getOption(self::OPTION_ADAPTERS); $dirConfig = $this->getOption(self::OPTION_DIRECTORIES); return isset($adapterConfig[$id]) || isset($dirConfig[$id]); } /** * Get FileSystem by ID * * Retrieve an existing FileSystem by ID. * * @param string $id * @return FilesystemInterface * @throws \common_exception_Error * @throws \common_exception_NotFound */ public function getFileSystem($id) { if (!isset($this->filesystems[$id])) { $config = $this->getAdapterConfig($id); $adapter = $this->getFlysystemAdapter($config['adapter']); $this->filesystems[$id] = new FileSystem($id, new FlyFileSystem($adapter), $config['path']); } return $this->filesystems[$id]; } /** * Creates a filesystem using the default implementation (Local) * Override this function to create your files elsewhere by default * * @param string $id * @param string $subPath * @return FilesystemInterface */ public function createFileSystem($id, $subPath = null) { $this->addDir($id, 'default'); return $this->getFileSystem($id); } /** * Create a new local file system * * @deprecated never rely on a directory being local, use addDir instead * @param string $id * @return FilesystemInterface */ public function createLocalFileSystem($id) { $path = $this->getOption(self::OPTION_FILE_PATH) . \helpers_File::sanitizeInjectively($id); $this->registerLocalFileSystem($id, $path); return $this->getFileSystem($id); } /** * Registers a local file system, used for transition * * @deprecated never rely on a directory being local, use addDir instead * @param string $id * @param string $path * @return boolean */ public function registerLocalFileSystem($id, $path) { $adapters = $this->hasOption(self::OPTION_ADAPTERS) ? $this->getOption(self::OPTION_ADAPTERS) : []; $adapters[$id] = [ 'class' => self::FLYSYSTEM_LOCAL_ADAPTER, 'options' => ['root' => $path] ]; $this->setOption(self::OPTION_ADAPTERS, $adapters); return true; } /** * Remove a filesystem adapter * * @param string $id * @return boolean */ public function unregisterFileSystem($id) { if (isset($this->filesystems[$id])) { unset($this->filesystems[$id]); } $adapters = $this->getOption(self::OPTION_ADAPTERS); if (isset($adapters[$id])) { unset($adapters[$id]); $this->setOption(self::OPTION_ADAPTERS, $adapters); return true; } elseif ($this->hasDirectory($id)) { $directories = $this->getOption(self::OPTION_DIRECTORIES); unset($directories[$id]); $this->setOption(self::OPTION_DIRECTORIES, $directories); return true; } else { return false; } } /** * Get file adapter by file * * @param File $file * @return AdapterInterface * @throws \common_exception_NotFound * @throws common_exception_Error */ public function getFileAdapterByFile(File $file) { $config = $this->getAdapterConfig($file->getFileSystemId()); return $this->getFlysystemAdapter($config['adapter']); } /** * Returns the configuration for an adapter * @param string $id * @return string[] */ protected function getAdapterConfig($id) { $dirs = $this->getDirectories(); if (!isset($dirs[$id])) { $config = [ 'adapter' => $id, 'path' => '' ]; } elseif (is_array($dirs[$id])) { $config = $dirs[$id]; } else { $config = [ 'adapter' => $dirs[$id], 'path' => $id ]; } return $config; } /** * inspired by burzum/storage-factory * * @param string $id * @throws \common_exception_NotFound if adapter doesn't exist * @throws \common_exception_Error if adapter is not valid * @return AdapterInterface */ protected function getFlysystemAdapter($id) { $fsConfig = $this->getOption(self::OPTION_ADAPTERS); if (!isset($fsConfig[$id])) { throw new \common_exception_NotFound('Undefined filesystem "' . $id . '"'); } $adapterConfig = $fsConfig[$id]; // alias? while (is_string($adapterConfig)) { $adapterConfig = $fsConfig[$adapterConfig]; } $class = $adapterConfig['class']; $options = isset($adapterConfig['options']) ? $adapterConfig['options'] : []; if (!class_exists($class)) { if (class_exists(self::FLYSYSTEM_ADAPTER_NS . $class)) { $class = self::FLYSYSTEM_ADAPTER_NS . $class; } elseif (class_exists(self::FLYSYSTEM_ADAPTER_NS . $class . '\\' . $class . 'Adapter')) { $class = self::FLYSYSTEM_ADAPTER_NS . $class . '\\' . $class . 'Adapter'; } else { throw new common_exception_Error('Unknown Flysystem adapter "' . $class . '"'); } } if (!is_subclass_of($class, 'League\Flysystem\AdapterInterface')) { throw new common_exception_Error('"' . $class . '" is not a flysystem adapter'); } $adapter = (new \ReflectionClass($class))->newInstanceArgs($options); if ($adapter instanceof ServiceLocatorAwareInterface) { $adapter->setServiceLocator($this->getServiceLocator()); } return $adapter; } }