3 * @file src/Model/Storage/Filesystem.php
4 * @brief 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 * @brief 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 implements IStorage
26 const NAME = 'Filesystem';
28 // Default base folder
29 const DEFAULT_BASE_FOLDER = 'storage';
31 /** @var IConfiguration */
33 /** @var LoggerInterface */
42 * Filesystem constructor.
44 * @param IConfiguration $config
45 * @param LoggerInterface $logger
48 public function __construct(IConfiguration $config, LoggerInterface $logger, L10n $l10n)
50 $this->config = $config;
51 $this->logger = $logger;
54 $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
55 $this->basePath = rtrim($path, '/');
59 * @brief Split data ref and return file path
61 * @param string $reference Data reference
65 private function pathForRef(string $reference)
67 $fold1 = substr($reference, 0, 2);
68 $fold2 = substr($reference, 2, 2);
69 $file = substr($reference, 4);
71 return implode('/', [$this->basePath, $fold1, $fold2, $file]);
76 * @brief Create dirctory tree to store file, with .htaccess and index.html files
78 * @param string $file Path and filename
80 * @throws StorageException
82 private function createFoldersForFile(string $file)
84 $path = dirname($file);
87 if (!mkdir($path, 0770, true)) {
88 $this->logger->warning('Failed to create dir.', ['path' => $path]);
89 throw new StorageException($this->l10n->t('Filesystem storage failed to create "%s". Check you write permissions.', $path));
93 while ($path !== $this->basePath) {
94 if (!is_file($path . '/index.html')) {
95 file_put_contents($path . '/index.html', '');
97 chmod($path . '/index.html', 0660);
99 $path = dirname($path);
101 if (!is_file($path . '/index.html')) {
102 file_put_contents($path . '/index.html', '');
103 chmod($path . '/index.html', 0660);
110 public function get(string $reference)
112 $file = $this->pathForRef($reference);
113 if (!is_file($file)) {
117 return file_get_contents($file);
123 public function put(string $data, string $reference = '')
125 if ($reference === '') {
126 $reference = Strings::getRandomHex();
128 $file = $this->pathForRef($reference);
130 $this->createFoldersForFile($file);
132 if ((file_exists($file) && !is_writable($file)) || !file_put_contents($file, $data)) {
133 $this->logger->warning('Failed to write data.', ['file' => $file]);
134 throw new StorageException($this->l10n->t('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
144 public function delete(string $reference)
146 $file = $this->pathForRef($reference);
147 // return true if file doesn't exists. we want to delete it: success with zero work!
148 if (!is_file($file)) {
151 return unlink($file);
157 public function getOptions()
162 $this->l10n->t('Storage base path'),
164 $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
172 public function saveOptions(array $data)
174 $storagePath = $data['storagepath'] ?? '';
175 if ($storagePath === '' || !is_dir($storagePath)) {
177 'storagepath' => $this->l10n->t('Enter a valid existing folder')
180 $this->config->set('storage', 'filesystem_path', $storagePath);
181 $this->basePath = $storagePath;
188 public function __toString()