Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileSystemService
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 13
1190
0.00% covered (danger)
0.00%
0 / 1
 getDirectory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDirectories
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 addDir
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 hasDirectory
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getFileSystem
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 createFileSystem
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 createLocalFileSystem
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 registerLocalFileSystem
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 unregisterFileSystem
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 getFileAdapterByFile
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getAdapterConfig
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 getFlysystemAdapter
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
90
 handleFlysystemUpgrade
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2015 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 *
20 */
21
22namespace oat\oatbox\filesystem;
23
24use League\Flysystem\Local\LocalFilesystemAdapter;
25use oat\oatbox\service\ConfigurableService;
26use League\Flysystem\FilesystemAdapter;
27use common_exception_Error;
28use Zend\ServiceManager\ServiceLocatorAwareInterface;
29use League\Flysystem\Filesystem as FlyFileSystem;
30use League\Flysystem\FilesystemOperator;
31
32/**
33* A service to reference and retrieve filesystems
34*/
35class FileSystemService extends ConfigurableService
36{
37    public const SERVICE_ID = 'generis/filesystem';
38
39    public const OPTION_FILE_PATH = 'filesPath';
40
41    public const OPTION_ADAPTERS = 'adapters';
42
43    public const OPTION_DIRECTORIES = 'dirs';
44
45    private const FLYSYSTEM_NS = '\\League\\Flysystem\\';
46
47    private $filesystems = [];
48
49    /**
50     *
51     * @param $id
52     * @return \oat\oatbox\filesystem\Directory
53     */
54    public function getDirectory($id)
55    {
56        return $this->propagate(new Directory($id, ''));
57    }
58
59    /**
60     * Returns the directory config
61     * @return array
62     */
63    protected function getDirectories()
64    {
65        return $this->hasOption(self::OPTION_DIRECTORIES)
66            ? $this->getOption(self::OPTION_DIRECTORIES)
67            : [];
68    }
69
70    /**
71     * Add a directory reference
72     * @param string $id
73     * @param string $adapterId
74     */
75    protected function addDir($id, $adapterId)
76    {
77        $dirs = $this->getDirectories();
78        $dirs[$id] = $adapterId;
79        $this->setOption(self::OPTION_DIRECTORIES, $dirs);
80    }
81
82    /**
83     * Returns whenever or not a FS exists
84     * @param string $id
85     * @return boolean
86     */
87    public function hasDirectory($id)
88    {
89        $adapterConfig = $this->getOption(self::OPTION_ADAPTERS);
90        $dirConfig = $this->getOption(self::OPTION_DIRECTORIES);
91        return isset($adapterConfig[$id]) || isset($dirConfig[$id]);
92    }
93
94    /**
95     * Get FileSystem by ID
96     *
97     * Retrieve an existing FileSystem by ID.
98     *
99     * @param string $id
100     * @return FileSystem
101     * @throws \common_exception_Error
102     * @throws \common_exception_NotFound
103     */
104    public function getFileSystem($id)
105    {
106        if (!isset($this->filesystems[$id])) {
107            $config = $this->getAdapterConfig($id);
108            $adapter = $this->getFlysystemAdapter($config['adapter']);
109            $this->filesystems[$id] = new FileSystem($id, new FlyFileSystem($adapter), $config['path']);
110        }
111        return $this->filesystems[$id];
112    }
113
114    /**
115     * Creates a filesystem using the default implementation (Local)
116     * Override this function to create your files elsewhere by default
117     *
118     * @param string $id
119     * @param string $subPath
120     * @return FileSystem
121     */
122    public function createFileSystem($id, $subPath = null)
123    {
124        $this->addDir($id, 'default');
125        return $this->getFileSystem($id);
126    }
127
128    /**
129     * Create a new local file system
130     *
131     * @deprecated never rely on a directory being local, use addDir instead
132     * @param string $id
133     * @return FilesystemOperator
134     */
135    public function createLocalFileSystem($id)
136    {
137        $path = $this->getOption(self::OPTION_FILE_PATH) . \helpers_File::sanitizeInjectively($id);
138        $this->registerLocalFileSystem($id, $path);
139        return $this->getFileSystem($id);
140    }
141
142    /**
143     * Registers a local file system, used for transition
144     *
145     * @deprecated never rely on a directory being local, use addDir instead
146     * @param string $id
147     * @param string $path
148     * @return boolean
149     */
150    public function registerLocalFileSystem($id, $path)
151    {
152        $adapters = $this->hasOption(self::OPTION_ADAPTERS) ? $this->getOption(self::OPTION_ADAPTERS) : [];
153        $adapters[$id] = [
154            'class' => LocalFilesystemAdapter::class,
155            'options' => ['location' => $path]
156        ];
157        $this->setOption(self::OPTION_ADAPTERS, $adapters);
158        return true;
159    }
160
161    /**
162     * Remove a filesystem adapter
163     *
164     * @param string $id
165     * @return boolean
166     */
167    public function unregisterFileSystem($id)
168    {
169        if (isset($this->filesystems[$id])) {
170            unset($this->filesystems[$id]);
171        }
172        $adapters = $this->getOption(self::OPTION_ADAPTERS);
173        if (isset($adapters[$id])) {
174            unset($adapters[$id]);
175            $this->setOption(self::OPTION_ADAPTERS, $adapters);
176            return true;
177        } elseif ($this->hasDirectory($id)) {
178            $directories = $this->getOption(self::OPTION_DIRECTORIES);
179            unset($directories[$id]);
180            $this->setOption(self::OPTION_DIRECTORIES, $directories);
181            return true;
182        } else {
183            return false;
184        }
185    }
186
187    /**
188     * Get file adapter by file
189     *
190     * @param File $file
191     * @return FilesystemAdapter
192     * @throws \common_exception_NotFound
193     * @throws common_exception_Error
194     */
195    public function getFileAdapterByFile(File $file)
196    {
197        $config = $this->getAdapterConfig($file->getFileSystemId());
198        return $this->getFlysystemAdapter($config['adapter']);
199    }
200
201    /**
202     * Returns the configuration for an adapter
203     * @param string $id
204     * @return string[]
205     */
206    protected function getAdapterConfig($id)
207    {
208        $dirs = $this->getDirectories();
209        if (!isset($dirs[$id])) {
210            $config = [
211                'adapter' => $id,
212                'path' => ''
213            ];
214        } elseif (is_array($dirs[$id])) {
215            $config = $dirs[$id];
216        } else {
217            $config = [
218                'adapter' => $dirs[$id],
219                'path' => $id
220            ];
221        }
222        return $config;
223    }
224
225    /**
226     * inspired by burzum/storage-factory
227     *
228     * @param string $id
229     * @throws \common_exception_NotFound if adapter doesn't exist
230     * @throws \common_exception_Error if adapter is not valid
231     * @return FilesystemAdapter
232     */
233    protected function getFlysystemAdapter($id)
234    {
235        $fsConfig = $this->getOption(self::OPTION_ADAPTERS);
236        if (!isset($fsConfig[$id])) {
237            throw new \common_exception_NotFound('Undefined filesystem "' . $id . '"');
238        }
239        $adapterConfig = $fsConfig[$id];
240        // alias?
241        while (is_string($adapterConfig)) {
242            $adapterConfig = $fsConfig[$adapterConfig];
243        }
244        $adapterConfig = $this->handleFlysystemUpgrade($adapterConfig);
245        $class = $adapterConfig['class'];
246        $options = isset($adapterConfig['options']) ? $adapterConfig['options'] : [];
247
248        if (!class_exists($class)) {
249            if (class_exists(self::FLYSYSTEM_NS . $class)) {
250                $class = self::FLYSYSTEM_NS . $class;
251            } elseif (class_exists(self::FLYSYSTEM_NS . $class . '\\' . $class . 'FilesystemAdapter')) {
252                $class = self::FLYSYSTEM_NS . $class . '\\' . $class . 'FilesystemAdapter';
253            } else {
254                throw new common_exception_Error('Unknown Flysystem adapter "' . $class . '"');
255            }
256        }
257
258        if (!is_subclass_of($class, 'League\Flysystem\FilesystemAdapter')) {
259            throw new common_exception_Error('"' . $class . '" is not a flysystem adapter');
260        }
261        $adapter = (new \ReflectionClass($class))->newInstanceArgs($options);
262        if ($adapter instanceof ServiceLocatorAwareInterface) {
263            $adapter->setServiceLocator($this->getServiceLocator());
264        }
265        return $adapter;
266    }
267
268    private function handleFlysystemUpgrade(array $adapterConfig): array
269    {
270        if (
271            $adapterConfig['class'] === 'Local'
272            || $adapterConfig['class'] === 'League\\Flysystem\\Local\\LocalFilesystemAdapter'
273        ) {
274            if (!empty($adapterConfig['options']['root'])) {
275                $adapterConfig['options']['location'] = $adapterConfig['options']['root'];
276                unset($adapterConfig['options']['root']);
277            }
278        } elseif ($adapterConfig['class'] === 'League\\Flysystem\\Memory\\MemoryAdapter') {
279            $adapterConfig['class'] = 'League\\Flysystem\\InMemory\\InMemoryFilesystemAdapter';
280        }
281
282        return $adapterConfig;
283    }
284}