3 * @file src/Model/Storage/Filesystem.php
4 * Storage backend system
7 namespace Friendica\Model\Storage;
9 use Friendica\Core\Config\IConfiguration;
10 use Friendica\Core\L10n\L10n;
11 use Friendica\Util\Strings;
12 use Psr\Log\LoggerInterface;
15 * Filesystem based storage backend
17 * This class manage data on filesystem.
18 * Base folder for storage is set in storage.filesystem_path.
19 * Best would be for storage folder to be outside webserver folder, we are using a
20 * folder relative to code tree root as default to ease things for users in shared hostings.
21 * Each new resource gets a value as reference and is saved in a
22 * folder tree stucture created from that value.
24 class Filesystem extends AbstractStorage
26 const NAME = 'Filesystem';
28 // Default base folder
29 const DEFAULT_BASE_FOLDER = 'storage';
31 /** @var IConfiguration */
38 * Filesystem constructor.
40 * @param IConfiguration $config
41 * @param LoggerInterface $logger
44 public function __construct(IConfiguration $config, LoggerInterface $logger, L10n $l10n)
46 parent::__construct($l10n, $logger);
48 $this->config = $config;
50 $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
51 $this->basePath = rtrim($path, '/');
55 * Split data ref and return file path
57 * @param string $reference Data reference
61 private function pathForRef(string $reference)
63 $fold1 = substr($reference, 0, 2);
64 $fold2 = substr($reference, 2, 2);
65 $file = substr($reference, 4);
67 return implode('/', [$this->basePath, $fold1, $fold2, $file]);
72 * Create dirctory tree to store file, with .htaccess and index.html files
74 * @param string $file Path and filename
76 * @throws StorageException
78 private function createFoldersForFile(string $file)
80 $path = dirname($file);
83 if (!mkdir($path, 0770, true)) {
84 $this->logger->warning('Failed to create dir.', ['path' => $path]);
85 throw new StorageException($this->l10n->t('Filesystem storage failed to create "%s". Check you write permissions.', $path));
89 while ($path !== $this->basePath) {
90 if (!is_file($path . '/index.html')) {
91 file_put_contents($path . '/index.html', '');
93 chmod($path . '/index.html', 0660);
95 $path = dirname($path);
97 if (!is_file($path . '/index.html')) {
98 file_put_contents($path . '/index.html', '');
99 chmod($path . '/index.html', 0660);
106 public function get(string $reference)
108 $file = $this->pathForRef($reference);
109 if (!is_file($file)) {
113 return file_get_contents($file);
119 public function put(string $data, string $reference = '')
121 if ($reference === '') {
122 $reference = Strings::getRandomHex();
124 $file = $this->pathForRef($reference);
126 $this->createFoldersForFile($file);
128 $result = file_put_contents($file, $data);
130 // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
131 if ($result === false) {
132 $this->logger->warning('Failed to write data.', ['file' => $file]);
133 throw new StorageException($this->l10n->t('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
143 public function delete(string $reference)
145 $file = $this->pathForRef($reference);
146 // return true if file doesn't exists. we want to delete it: success with zero work!
147 if (!is_file($file)) {
150 return unlink($file);
156 public function getOptions()
161 $this->l10n->t('Storage base path'),
163 $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
171 public function saveOptions(array $data)
173 $storagePath = $data['storagepath'] ?? '';
174 if ($storagePath === '' || !is_dir($storagePath)) {
176 'storagepath' => $this->l10n->t('Enter a valid existing folder')
179 $this->config->set('storage', 'filesystem_path', $storagePath);
180 $this->basePath = $storagePath;
187 public static function getName()