]> git.mxchange.org Git - friendica.git/blob - src/Model/Storage/Filesystem.php
e954352bc6103c8ea0aa737c26b50513b05c8a35
[friendica.git] / src / Model / Storage / Filesystem.php
1 <?php
2 /**
3  * @file src/Model/Storage/Filesystem.php
4  * @brief Storage backend system
5  */
6
7 namespace Friendica\Model\Storage;
8
9 use Friendica\Core\Config\IConfiguration;
10 use Friendica\Core\L10n\L10n;
11 use Friendica\Util\Strings;
12 use Psr\Log\LoggerInterface;
13
14 /**
15  * @brief Filesystem based storage backend
16  *
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.
23  */
24 class Filesystem implements IStorage
25 {
26         const NAME = 'Filesystem';
27
28         // Default base folder
29         const DEFAULT_BASE_FOLDER = 'storage';
30
31         /** @var IConfiguration */
32         private $config;
33         /** @var LoggerInterface */
34         private $logger;
35         /** @var L10n */
36         private $l10n;
37
38         /** @var string */
39         private $basePath;
40
41         /**
42          * Filesystem constructor.
43          *
44          * @param IConfiguration  $config
45          * @param LoggerInterface $logger
46          * @param L10n            $l10n
47          */
48         public function __construct(IConfiguration $config, LoggerInterface $logger, L10n $l10n)
49         {
50                 $this->config = $config;
51                 $this->logger = $logger;
52                 $this->l10n   = $l10n;
53
54                 $path           = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
55                 $this->basePath = rtrim($path, '/');
56         }
57
58         /**
59          * @brief Split data ref and return file path
60          *
61          * @param string $reference Data reference
62          *
63          * @return string
64          */
65         private function pathForRef(string $reference)
66         {
67                 $fold1 = substr($reference, 0, 2);
68                 $fold2 = substr($reference, 2, 2);
69                 $file  = substr($reference, 4);
70
71                 return implode('/', [$this->basePath, $fold1, $fold2, $file]);
72         }
73
74
75         /**
76          * @brief Create dirctory tree to store file, with .htaccess and index.html files
77          *
78          * @param string $file Path and filename
79          *
80          * @throws StorageException
81          */
82         private function createFoldersForFile(string $file)
83         {
84                 $path = dirname($file);
85
86                 if (!is_dir($path)) {
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));
90                         }
91                 }
92
93                 while ($path !== $this->basePath) {
94                         if (!is_file($path . '/index.html')) {
95                                 file_put_contents($path . '/index.html', '');
96                         }
97                         chmod($path . '/index.html', 0660);
98                         chmod($path, 0770);
99                         $path = dirname($path);
100                 }
101                 if (!is_file($path . '/index.html')) {
102                         file_put_contents($path . '/index.html', '');
103                         chmod($path . '/index.html', 0660);
104                 }
105         }
106
107         /**
108          * @inheritDoc
109          */
110         public function get(string $reference)
111         {
112                 $file = $this->pathForRef($reference);
113                 if (!is_file($file)) {
114                         return '';
115                 }
116
117                 return file_get_contents($file);
118         }
119
120         /**
121          * @inheritDoc
122          */
123         public function put(string $data, string $reference = '')
124         {
125                 if ($reference === '') {
126                         $reference = Strings::getRandomHex();
127                 }
128                 $file = $this->pathForRef($reference);
129
130                 $this->createFoldersForFile($file);
131
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));
135                 }
136
137                 chmod($file, 0660);
138                 return $reference;
139         }
140
141         /**
142          * @inheritDoc
143          */
144         public function delete(string $reference)
145         {
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)) {
149                         return true;
150                 }
151                 return unlink($file);
152         }
153
154         /**
155          * @inheritDoc
156          */
157         public function getOptions()
158         {
159                 return [
160                         'storagepath' => [
161                                 'input',
162                                 $this->l10n->t('Storage base path'),
163                                 $this->basePath,
164                                 $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
165                         ]
166                 ];
167         }
168
169         /**
170          * @inheritDoc
171          */
172         public function saveOptions(array $data)
173         {
174                 $storagePath = $data['storagepath'] ?? '';
175                 if ($storagePath === '' || !is_dir($storagePath)) {
176                         return [
177                                 'storagepath' => $this->l10n->t('Enter a valid existing folder')
178                         ];
179                 };
180                 $this->config->set('storage', 'filesystem_path', $storagePath);
181                 $this->basePath = $storagePath;
182                 return [];
183         }
184
185         /**
186          * @inheritDoc
187          */
188         public function __toString()
189         {
190                 return self::NAME;
191         }
192 }