• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

webeweb / edm-bundle / 9269479192

28 May 2024 12:40PM UTC coverage: 98.042% (-0.2%) from 98.267%
9269479192

push

github

webeweb
Update build workflow configuration:
- restore Symfony 5.0
- restore Symfony 5.1

701 of 715 relevant lines covered (98.04%)

9.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.19
/src/Provider/Storage/FilesystemStorageProvider.php
1
<?php
2

3
/*
4
 * This file is part of the edm-bundle package.
5
 *
6
 * (c) 2017 WEBEWEB
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types = 1);
13

14
namespace WBW\Bundle\EDMBundle\Provider\Storage;
15

16
use DateTime;
17
use Psr\Log\LoggerInterface;
18
use ReflectionClass;
19
use ReflectionException;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpFoundation\StreamedResponse;
23
use Throwable;
24
use WBW\Bundle\EDMBundle\Entity\Document;
25
use WBW\Bundle\EDMBundle\Helper\DocumentHelper;
26
use WBW\Bundle\EDMBundle\Model\DocumentInterface;
27
use WBW\Bundle\EDMBundle\Provider\StorageProviderInterface;
28
use WBW\Library\Common\Logger\LoggerTrait;
29
use WBW\Library\Common\Traits\Strings\StringDirectoryTrait;
30
use ZipArchive;
31

32
/**
33
 * Filesystem storage provider.
34
 *
35
 * @author webeweb <https://github.com/webeweb>
36
 * @package WBW\Bundle\EDMBundle\Provider\Storage
37
 */
38
class FilesystemStorageProvider implements StorageProviderInterface {
39

40
    use LoggerTrait;
41
    use StringDirectoryTrait {
42
        setDirectory as protected;
43
    }
44

45
    /**
46
     * Service name.
47
     *
48
     * @var string
49
     */
50
    const SERVICE_NAME = "wbw.edm.provider.storage.filesystem";
51

52
    /**
53
     * Constructor.
54
     *
55
     * @param LoggerInterface $logger The logger.
56
     * @param string $directory The directory.
57
     */
58
    public function __construct(LoggerInterface $logger, string $directory) {
59
        $logger->debug(sprintf('Filesystem storage provider use this directory "%s"', $directory));
38✔
60
        $this->setDirectory($directory);
38✔
61
        $this->setLogger($logger);
38✔
62
    }
63

64
    /**
65
     * {@inheritDoc}
66
     */
67
    public function deleteDirectory(DocumentInterface $directory): void {
68

69
        DocumentHelper::isDirectory($directory);
6✔
70

71
        $pathname = $this->getAbsolutePath($directory);
4✔
72
        $context  = [
4✔
73
            "_provider" => get_class($this),
4✔
74
        ];
4✔
75

76
        $this->logInfo(sprintf('Filesystem storage provider tries to delete the directory "%s"', $pathname), $context);
4✔
77

78
        $filesystem = new Filesystem();
4✔
79
        $filesystem->remove($pathname);
4✔
80
    }
81

82
    /**
83
     * {@inheritDoc}
84
     */
85
    public function deleteDocument(DocumentInterface $document): void {
86

87
        DocumentHelper::isDocument($document);
4✔
88

89
        $pathname = $this->getAbsolutePath($document);
2✔
90
        $context  = [
2✔
91
            "_provider" => get_class($this),
2✔
92
        ];
2✔
93

94
        $this->logInfo(sprintf('Filesystem storage provider tries to delete the document "%s"', $pathname), $context);
2✔
95

96
        $filesystem = new Filesystem();
2✔
97
        $filesystem->remove($pathname);
2✔
98
    }
99

100
    /**
101
     * {@inheritDoc}
102
     */
103
    public function downloadDirectory(DocumentInterface $directory): Response {
104
        return $this->newStreamedResponse($directory);
4✔
105
    }
106

107
    /**
108
     * {@inheritDoc}
109
     */
110
    public function downloadDocument(DocumentInterface $document): Response {
111
        return $this->newStreamedResponse($document);
2✔
112
    }
113

114
    /**
115
     * Get an absolute path.
116
     *
117
     * @param DocumentInterface $document The document.
118
     * @param bool $rename Rename ?
119
     * @return string Returns the absolute path.
120
     */
121
    protected function getAbsolutePath(DocumentInterface $document, bool $rename = false): string {
122

123
        $path = [
22✔
124
            $this->getDirectory(),
22✔
125
        ];
22✔
126

127
        foreach (DocumentHelper::getPaths($document, $rename) as $current) {
22✔
128
            $path[] = $current->getId();
22✔
129
        }
130

131
        return implode(DIRECTORY_SEPARATOR, $path);
22✔
132
    }
133

134
    /**
135
     * Log an info.
136
     *
137
     * @param string $message The message.
138
     * @param mixed[] $context The context.
139
     * @return StorageProviderInterface Returns this filesystem storage provider.
140
     */
141
    protected function logInfo(string $message, array $context = []): StorageProviderInterface {
142
        $this->getLogger()->info($message, $context);
20✔
143
        return $this;
20✔
144
    }
145

146
    /**
147
     * {@inheritDoc}
148
     */
149
    public function moveDocument(DocumentInterface $document): void {
150

151
        $src = $this->getAbsolutePath($document, true);
4✔
152
        $dst = $this->getAbsolutePath($document);
4✔
153

154
        $context = [
4✔
155
            "_provider" => get_class($this),
4✔
156
        ];
4✔
157

158
        $this->logInfo(sprintf('Filesystem storage provider tries to move "%s" into "%s"', $src, $dst), $context);
4✔
159

160
        $filesystem = new Filesystem();
4✔
161
        $filesystem->rename($src, $dst);
4✔
162
    }
163

164
    /**
165
     * {@inheritDoc}
166
     */
167
    public function newDirectory(DocumentInterface $directory): void {
168

169
        DocumentHelper::isDirectory($directory);
6✔
170

171
        $pathname = $this->getAbsolutePath($directory);
4✔
172
        $context  = [
4✔
173
            "_provider" => get_class($this),
4✔
174
        ];
4✔
175

176
        $this->logInfo(sprintf('Filesystem storage provider tries to create the directory "%s"', $pathname), $context);
4✔
177

178
        $filesystem = new Filesystem();
4✔
179
        $filesystem->mkdir($pathname);
4✔
180
    }
181

182
    /**
183
     * Create a new streamed response.
184
     *
185
     * @param DocumentInterface $document The document.
186
     * @return StreamedResponse Returns the streamed response.
187
     * @throws Throwable Throws an exception if an error occurs.
188
     */
189
    protected function newStreamedResponse(DocumentInterface $document): Response {
190

191
        $myself = $this;
6✔
192

193
        /** @var callable $callback */
194
        $callback = function() use ($document, $myself) {
6✔
195

196
            if (true === $document->isDocument()) {
2✔
197
                $myself->streamDocument($document);
×
198
                return;
×
199
            }
200

201
            $archive = $myself->zipDirectory($document);
2✔
202
            $myself->streamDocument($archive);
2✔
203
        };
6✔
204

205
        $timestamp = (new DateTime())->format("Y.m.d-H.i");
6✔
206
        $filename  = str_replace(" ", "_", DocumentHelper::getFilename($document));
6✔
207
        $extension = true === $document->isDirectory() ? ".zip" : "";
6✔
208
        $mimeType  = true === $document->isDirectory() ? "application/zip" : $document->getMimeType();
6✔
209

210
        $response = new StreamedResponse();
6✔
211
        $response->headers->set("Content-Disposition", "attachement; filename=\"{$timestamp}_$filename$extension\"");
6✔
212
        $response->headers->set("Content-Type", $mimeType);
6✔
213
        $response->setCallback($callback);
6✔
214
        $response->setStatusCode(200);
6✔
215

216
        return $response;
6✔
217
    }
218

219
    /**
220
     * Create a ZIP document.
221
     *
222
     * @param DocumentInterface $document The document.
223
     * @return DocumentInterface Returns the ZIP document.
224
     * @throws ReflectionException Throws a Reflection exception if an error occurs.
225
     */
226
    protected function newZipDocument(DocumentInterface $document): DocumentInterface {
227

228
        $id = (new DateTime())->format("YmdHisu");
2✔
229

230
        $model = new Document();
2✔
231
        $model->setExtension("zip");
2✔
232
        $model->setMimeType("application/zip");
2✔
233
        $model->setName($document->getName() . "-" . $id);
2✔
234
        $model->setType(DocumentInterface::TYPE_DOCUMENT);
2✔
235

236
        // Use reflection to set the private id attribute.
237
        $idProperty = (new ReflectionClass($model))->getProperty("id");
2✔
238
        $idProperty->setAccessible(true);
2✔
239
        $idProperty->setValue($model, intval($id));
2✔
240

241
        return $model;
2✔
242
    }
243

244
    /**
245
     * Stream a document.
246
     *
247
     * @param DocumentInterface $document The document.
248
     * @return void
249
     */
250
    protected function streamDocument(DocumentInterface $document): void {
251

252
        $filename = $this->getAbsolutePath($document);
2✔
253

254
        $input  = fopen($filename, "r");
2✔
255
        $output = fopen("php://output", "w+");
2✔
256

257
        while (false === feof($input)) {
2✔
258
            stream_copy_to_stream($input, $output, 4096);
2✔
259
        }
260

261
        fclose($input);
2✔
262
        fclose($output);
2✔
263

264
        if (1 === preg_match("/\.download$/", $filename)) {
2✔
265
            unlink($filename);
×
266
        }
267
    }
268

269
    /**
270
     * {@inheritDoc}
271
     */
272
    public function uploadDocument(DocumentInterface $document): void {
273

274
        DocumentHelper::isDocument($document);
8✔
275

276
        $src = $document->getUploadedFile()->getRealPath();
6✔
277
        $dst = $this->getAbsolutePath($document);
6✔
278

279
        $context = [
6✔
280
            "_provider" => get_class($this),
6✔
281
        ];
6✔
282

283
        $this->logInfo(sprintf('Filesystem storage provider tries to copy the uploaded document "%s" into "%s"', $src, $dst), $context);
6✔
284

285
        $filesystem = new Filesystem();
6✔
286
        $filesystem->copy($src, $dst);
6✔
287
    }
288

289
    /**
290
     * Zip a directory.
291
     *
292
     * @param DocumentInterface $directory The directory.
293
     * @return DocumentInterface Returns the zipped directory in case success.
294
     * @throws ReflectionException Throws a Reflection exception if an error occurs.
295
     */
296
    protected function zipDirectory(DocumentInterface $directory): DocumentInterface {
297

298
        $archive = $this->newZipDocument($directory);
2✔
299

300
        $src = $this->getAbsolutePath($directory);
2✔
301
        $dst = $this->getAbsolutePath($archive);
2✔
302

303
        $zip = new ZipArchive();
2✔
304
        $zip->open($dst, ZipArchive::CREATE);
2✔
305

306
        foreach ($directory->getChildren() as $current) {
2✔
307

308
            $pattern = implode("", ["/^", preg_quote("$src/", "/"), "/"]);
2✔
309
            $zipPath = preg_replace($pattern, "", DocumentHelper::getPathname($current));
2✔
310

311
            if (true === $current->isDirectory()) {
2✔
312
                $zip->addEmptyDir($zipPath);
2✔
313
            }
314
            if (true === $current->isDocument()) {
2✔
315
                $zip->addFromString($zipPath, file_get_contents($this->getAbsolutePath($current)));
×
316
            }
317
        }
318

319
        $zip->close();
2✔
320

321
        return $archive;
2✔
322
    }
323
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc