Completed
Push — master ( 05bb5a...5289b8 )
by Rafał
34:32 queued 04:15
created

MediaManager::guessExtension()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 0
cp 0
rs 9.9332
c 0
b 0
f 0
cc 3
nc 2
nop 1
crap 12
1
<?php
2
3
/*
4
 * This file is part of the Superdesk Web Publisher Content Bundle.
5
 *
6
 * Copyright 2016 Sourcefabric z.ú. and contributors.
7
 *
8
 * For the full copyright and license information, please see the
9
 * AUTHORS and LICENSE files distributed with this source code.
10
 *
11
 * @copyright 2016 Sourcefabric z.ú
12
 * @license http://www.superdesk.org/license
13
 */
14
15
namespace SWP\Bundle\ContentBundle\Manager;
16
17
use GuzzleHttp\Client;
18
use GuzzleHttp\Handler\CurlHandler;
19
use GuzzleHttp\HandlerStack;
20
use GuzzleHttp\Middleware;
21
use Hoa\Mime\Mime;
22
use Psr\Log\LoggerInterface;
23
use SWP\Bundle\ContentBundle\Doctrine\ArticleMediaRepositoryInterface;
24
use SWP\Bundle\ContentBundle\Factory\FileFactoryInterface;
25
use SWP\Bundle\ContentBundle\Model\ArticleMedia;
26
use SWP\Bundle\ContentBundle\Model\FileInterface;
27
use Symfony\Component\HttpFoundation\File\UploadedFile;
28
use League\Flysystem\Filesystem;
29
use Symfony\Component\Routing\RouterInterface;
30
use GuzzleHttp\Exception\ConnectException;
31
use GuzzleHttp\Exception\RequestException;
32
use GuzzleHttp\Psr7\Request;
33
use GuzzleHttp\Psr7\Response;
34
35
class MediaManager implements MediaManagerInterface
36
{
37
    /**
38
     * @var Filesystem
39
     */
40
    protected $filesystem;
41
42
    /**
43
     * @var RouterInterface
44
     */
45
    protected $router;
46
47
    /**
48
     * @var ArticleMediaRepositoryInterface
49
     */
50
    protected $mediaRepository;
51
52
    /**
53
     * @var FileFactoryInterface
54
     */
55 12
    protected $fileFactory;
56
57
    /**
58
     * @var bool
59
     */
60
    private $logger;
61 12
62 12
    /**
63 12
     * @var string
64 12
     */
65 12
    private $retryDownloads;
66
67
    public function __construct(
68
        ArticleMediaRepositoryInterface $mediaRepository,
69
        Filesystem $filesystem,
70
        RouterInterface $router,
71
        FileFactoryInterface $fileFactory,
72
        LoggerInterface $logger,
73
        bool $retryDownloads
74
    ) {
75
        $this->mediaRepository = $mediaRepository;
76
        $this->filesystem = $filesystem;
77
        $this->router = $router;
78
        $this->fileFactory = $fileFactory;
79
        $this->logger = $logger;
0 ignored issues
show
Documentation Bug introduced by
It seems like $logger of type object<Psr\Log\LoggerInterface> is incompatible with the declared type boolean of property $logger.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
80
        $this->retryDownloads = $retryDownloads;
0 ignored issues
show
Documentation Bug introduced by
The property $retryDownloads was declared of type string, but $retryDownloads is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function handleUploadedFile(UploadedFile $uploadedFile, $mediaId)
87
    {
88
        $mediaId = ArticleMedia::handleMediaId($mediaId);
89
        $asset = $this->createMediaAsset($uploadedFile, $mediaId);
90
        $this->saveFile($uploadedFile, $mediaId);
91
        $this->mediaRepository->persist($asset);
92
93
        return $asset;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function getFile(FileInterface $media)
100
    {
101
        return $this->filesystem->read($this->getMediaBasePath().'/'.$media->getAssetId().'.'.$media->getFileExtension());
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function saveFile(UploadedFile $uploadedFile, $fileName)
108
    {
109
        $extension = $this->guessExtension($uploadedFile);
110
        $filePath = $this->getMediaBasePath().'/'.$fileName.'.'.$extension;
111
112
        if ($this->filesystem->has($filePath)) {
113
            return true;
114
        }
115
116
        $stream = fopen($uploadedFile->getRealPath(), 'r+');
117
        $result = $this->filesystem->writeStream($filePath, $stream);
118
        fclose($stream);
119
120
        return $result;
121
    }
122
123
    public function downloadFile(string $url, string $mediaId, string $mimeType = null): UploadedFile
124
    {
125
        $pathParts = \pathinfo($url);
126
        if (null === $mimeType) {
127
            $mimeType = Mime::getMimeFromExtension($pathParts['extension']);
128
        }
129
130
        $handlerStack = HandlerStack::create(new CurlHandler());
131
        $handlerStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
132
        $client = new Client(array('handler' => $handlerStack));
133
        $tempLocation = \rtrim(\sys_get_temp_dir(), '/').DIRECTORY_SEPARATOR.\sha1($mediaId.date('his'));
134
        $client->request('GET', $url, ['sink' => $tempLocation]);
135
136
        return new UploadedFile($tempLocation, $mediaId, $mimeType, \strlen($tempLocation), null, true);
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function getMediaPublicUrl(FileInterface $media)
143
    {
144
        return $this->getMediaUri($media, RouterInterface::ABSOLUTE_URL);
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function getMediaUri(FileInterface $media, $type = RouterInterface::ABSOLUTE_PATH)
151
    {
152
        return $this->router->generate('swp_media_get', [
153
            'mediaId' => $media->getAssetId(),
154
            'extension' => $media->getFileExtension(),
155
        ], $type);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function createMediaAsset(UploadedFile $uploadedFile, string $assetId): FileInterface
162
    {
163
        $extension = $this->guessExtension($uploadedFile);
164
165
        return $this->fileFactory->createWith($assetId, $extension);
166
    }
167
168
    /**
169
     * @return string
170
     */
171
    protected function getMediaBasePath(): string
172
    {
173
        $pathElements = ['swp', 'media'];
174
175
        return implode('/', $pathElements);
176
    }
177
178
    private function guessExtension(UploadedFile $uploadedFile): string
179
    {
180
        $extension = $uploadedFile->guessExtension();
181
182
        if ('mpga' === $extension && 'mp3' === $uploadedFile->getExtension()) {
183
            $extension = 'mp3';
184
        }
185
186
        return $extension;
187
    }
188
189
    public function retryDecider()
190
    {
191
        return function (
192
            $retries,
193
            Request $request,
194
            Response $response = null,
195
            RequestException $exception = null
196
        ): bool {
197
            $retry = false;
198
            if (!$this->retryDownloads) {
199
                $this->logger->error(\sprintf('Retries are disabled'));
0 ignored issues
show
Bug introduced by
The method error cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
200
201
                return false;
202
            }
203
204
            if ($retries >= 4) {
205
                $this->logger->error(\sprintf('Maximum number of retires reached'));
0 ignored issues
show
Bug introduced by
The method error cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
206
207
                return false;
208
            }
209
210
            // Retry connection exceptions
211
            if ($exception instanceof ConnectException) {
212
                $retry = true;
213
            }
214
215
            if ($response) {
216
                // Retry on server errors
217
                if ($response->getStatusCode() >= 400) {
218
                    $retry = true;
219
                }
220
            }
221
222
            if (true === $retry) {
223
                $this->logger->info(\sprintf('Retry downloading %s', $request->getUri()));
0 ignored issues
show
Bug introduced by
The method info cannot be called on $this->logger (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
224
            }
225
226
            return $retry;
227
        };
228
    }
229
230
    public function retryDelay()
231
    {
232
        return function ($numberOfRetries): int {
233
            return 1000 * $numberOfRetries;
234
        };
235
    }
236
}
237