SaveResourceMiddlewareAbstract::uploadFile()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 10
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 3
crap 12
1
<?php
2
namespace Staticus\Resources\Middlewares;
3
4
use League\Flysystem\FilesystemInterface;
5
use Psr\Http\Message\UploadedFileInterface;
6
use Staticus\Config\ConfigInterface;
7
use Staticus\Exceptions\WrongRequestException;
8
use Staticus\Diactoros\Response\FileUploadedResponse;
9
use Staticus\Resources\Commands\BackupResourceCommand;
10
use Staticus\Resources\Commands\CopyResourceCommand;
11
use Staticus\Resources\Commands\DestroyEqualResourceCommand;
12
use Staticus\Resources\File\ResourceDO;
13
use Staticus\Middlewares\MiddlewareAbstract;
14
use Staticus\Diactoros\Response\FileContentResponse;
15
use Staticus\Resources\Exceptions\SaveResourceErrorException;
16
use Staticus\Exceptions\WrongResponseException;
17
use Staticus\Resources\ResourceDOInterface;
18
use Zend\Diactoros\Response\EmptyResponse;
19
use Psr\Http\Message\ResponseInterface;
20
use Psr\Http\Message\ServerRequestInterface;
21
use Zend\Diactoros\Stream;
22
23
abstract class SaveResourceMiddlewareAbstract extends MiddlewareAbstract
24
{
25
    protected $resourceDO;
26
27
    /**
28
     * @var FilesystemInterface
29
     */
30
    protected $filesystem;
31
32
    /**
33
     * @var ConfigInterface
34
     */
35
    protected $config;
36
37
    public function __construct(
38
        ResourceDOInterface $resourceDO,
39
        FilesystemInterface $filesystem,
40
        ConfigInterface $config
41
    )
42
    {
43
        $this->resourceDO = $resourceDO;
44
        $this->filesystem = $filesystem;
45
        $this->config = $config;
46
    }
47
48
    /**
49
     * @param ServerRequestInterface $request
50
     * @param ResponseInterface $response
51
     * @param callable|null $next
52
     * @return EmptyResponse
53
     * @throws \Exception
54
     */
55
    public function __invoke(
56
        ServerRequestInterface $request,
57
        ResponseInterface $response,
58
        callable $next = null
59
    )
60
    {
61
        parent::__invoke($request, $response, $next);
62
        if (
63
            $response instanceof FileContentResponse
64
            || $response instanceof FileUploadedResponse
65
        ) {
66
            $resourceDO = $this->resourceDO;
67
            $filePath = $resourceDO->getFilePath();
68
            if (empty($filePath)) {
69
                throw new WrongResponseException('Empty file path. File can\'t be saved.');
70
            }
71
            $resourceStream = $response->getResource();
72
            if (is_resource($resourceStream)) {
73
                $this->save($resourceDO, $resourceStream);
74
            } else {
75
                $body = $response->getContent();
76
77
                // If any previous middlewares have not created the resource content, just skip it.
78
                // It is ok in some special cases like image resizing, when generator should not do anything,
79
                // but next middlewares will create resource from original model.
80
                if (null !== $body) {
81
82
                    $this->save($resourceDO, $body);
83
                }
84
            }
85
            $this->response = new EmptyResponse($response->getStatusCode(), [
86
                'Content-Type' => $this->resourceDO->getMimeType(),
87
            ]);
88
        }
89
90
        return $this->next();
91
    }
92
93
    /**
94
     * @param $filePath
95
     * @param $content
96
     */
97
    protected function writeFile($filePath, $content)
98
    {
99
        if (is_resource($content)) {
100
            $result = $this->filesystem->putStream($filePath, $content);
101
        } else {
102
            $result = $this->filesystem->put($filePath, $content);
103
        }
104
        if (!$result) {
105
            throw new SaveResourceErrorException('File cannot be written to the path ' . $filePath);
106
        }
107
    }
108
109
    protected function uploadFile(UploadedFileInterface $content, $mime, $filePath)
110
    {
111
        $uri = $content->getStream()->getMetadata('uri');
112
        if (!$uri) {
113
            throw new SaveResourceErrorException('Unknown error: can\'t get uploaded file uri');
114
        }
115
        $uploadedMime = $this->filesystem->getMimetype($uri);
116
        if ($mime !== $uploadedMime) {
117
            /**
118
             * Try to remove unnecessary file because UploadFile object can be emulated
119
             * @see \Staticus\Middlewares\ActionPostAbstract::download
120
             */
121
            $this->filesystem->delete($uri);
122
            throw new WrongRequestException('Bad request: incorrect mime-type of the uploaded file');
123
        }
124
        $content->moveTo($filePath);
125
    }
126
127
    protected function copyResource(ResourceDOInterface $originResourceDO, ResourceDOInterface $newResourceDO)
128
    {
129
        $command = new CopyResourceCommand($originResourceDO, $newResourceDO, $this->filesystem);
130
131
        return $command();
132
    }
133
134
    /**
135
     * @param $directory
136
     * @throws SaveResourceErrorException
137
     * @see \Staticus\Resources\Middlewares\Image\ImagePostProcessingMiddlewareAbstract::createDirectory
138
     */
139
    protected function createDirectory($directory)
140
    {
141
        if (!$this->filesystem->createDir($directory)) {
142
            throw new SaveResourceErrorException('Can\'t create a directory: ' . $directory);
143
        }
144
    }
145
146
    protected function copyFileToDefaults(ResourceDOInterface $resourceDO)
147
    {
148
        if (
149
            ResourceDO::DEFAULT_VARIANT !== $resourceDO->getVariant()
150
            && $this->config->get('staticus.magic_defaults.variant')
151
        ) {
152
            $defaultDO = clone $resourceDO;
153
            $defaultDO->setVariant();
154
            $defaultDO->setVersion();
155
            $this->copyResource($resourceDO, $defaultDO);
156
        }
157
        if (
158
            ResourceDO::DEFAULT_VERSION !== $resourceDO->getVersion()
159
            && $this->config->get('staticus.magic_defaults.version')
160
        ) {
161
            $defaultDO = clone $resourceDO;
162
            $defaultDO->setVersion();
163
            $this->copyResource($resourceDO, $defaultDO);
164
        }
165
    }
166
167
    /**
168
     * @param ResourceDOInterface $resourceDO
169
     * @param string|resource|Stream $content
170
     * @return ResourceDOInterface
171
     * @throws \RuntimeException if the upload was not successful.
172
     * @throws \InvalidArgumentException if the $path specified is invalid.
173
     * @throws \RuntimeException on any error during the move operation, or on
174
     */
175
    protected function save(ResourceDOInterface $resourceDO, $content)
176
    {
177
        $backupResourceVerDO = null;
178
        $filePath = $resourceDO->getFilePath();
179
        $this->createDirectory(dirname($filePath));
180
        // backups don't needs if this is a 'new creation' command
181
        if ($resourceDO->isRecreate()) {
182
            $backupResourceVerDO = $this->backup($resourceDO);
183
        }
184
        if ($content instanceof UploadedFileInterface) {
185
            $this->uploadFile($content, $resourceDO->getMimeType(), $filePath);
186
        } else {
187
            $this->writeFile($filePath, $content);
188
        }
189
190
        $responseDO = $resourceDO;
191
        if ($backupResourceVerDO instanceof ResourceDOInterface
192
            && $backupResourceVerDO->getVersion() !== ResourceDOInterface::DEFAULT_VERSION) {
193
            // If the newly created file is the same as the previous version, remove backup immediately
194
            $responseDO = $this->destroyEqual($resourceDO, $backupResourceVerDO);
195
        }
196
        if ($responseDO === $resourceDO) {
197
198
            // cleanup postprocessing cache folders
199
            // - if it is a new file creation (remove possible garbage after other operations)
200
            // - or if the basic file is replaced and not equal to the previous version
201
            $this->afterSave($resourceDO);
202
        }
203
        if ($this->config->get('staticus.magic_defaults.allow')) {
204
            $this->copyFileToDefaults($resourceDO);
205
        }
206
        return $resourceDO;
207
    }
208
    abstract protected function afterSave(ResourceDOInterface $resourceDO);
209
210
    protected function backup(ResourceDOInterface $resourceDO)
211
    {
212
        $command = new BackupResourceCommand($resourceDO, $this->filesystem);
213
        $backupResourceVerDO = $command();
214
215
        return $backupResourceVerDO;
216
    }
217
218
    /**
219
     * @param ResourceDOInterface $resourceDO
220
     * @param ResourceDOInterface $backupResourceVerDO
221
     * @return mixed
222
     */
223
    protected function destroyEqual(ResourceDOInterface $resourceDO, ResourceDOInterface $backupResourceVerDO)
224
    {
225
        $command = new DestroyEqualResourceCommand($resourceDO, $backupResourceVerDO, $this->filesystem);
226
        $responseDO = $command();
227
228
        return $responseDO;
229
    }
230
}
231