Completed
Pull Request — master (#15)
by Benjamin
02:36
created

MediaManager::cleanup()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
c 6
b 1
f 0
dl 0
loc 32
rs 8.439
cc 5
eloc 19
nc 8
nop 0
1
<?php
2
3
namespace Alpixel\Bundle\MediaBundle\Services;
4
5
use Alpixel\Bundle\MediaBundle\Entity\Media;
6
use Alpixel\Bundle\MediaBundle\Exception\InvalidMimeTypeException;
7
use Cocur\Slugify\Slugify;
8
use Doctrine\ORM\EntityManager;
9
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
10
use Symfony\Component\Filesystem\Exception\IOException;
11
use Symfony\Component\Filesystem\Filesystem;
12
use Symfony\Component\Finder\Finder;
13
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
14
use Symfony\Component\HttpFoundation\File\Exception\UploadException;
15
use Symfony\Component\HttpFoundation\File\File;
16
use Symfony\Component\HttpFoundation\File\UploadedFile;
17
use Symfony\Component\Routing\Generator\UrlGenerator;
18
19
class MediaManager
20
{
21
    protected $entityManager;
22
    protected $uploadDir;
23
    protected $allowedMimetypes;
24
25
    use ContainerAwareTrait;
26
27
    const SIZE_OF_KIBIOCTET = 1024;
28
    const OCTET_IN_KO = 1;
29
    const OCTET_IN_MO = 2;
30
    const OCTET_IN_GO = 3;
31
    const OCTET_IN_TO = 4;
32
    const OCTET_IN_PO = 5;
33
34
    public function __construct(EntityManager $entityManager, $uploadDir, $allowedMimetypes)
35
    {
36
        $this->entityManager = $entityManager;
37
        if (substr($uploadDir, -1) !== '/') {
38
            $uploadDir = $uploadDir . '/';
39
        }
40
        $this->uploadDir = $uploadDir;
41
        $this->allowedMimetypes = $allowedMimetypes;
42
    }
43
44
    /**
45
     * $current_uri String actual uri of the file
46
     * $dest_folder String future uri of the file starting from web/upload folder
47
     * $lifetime DateTime lifetime of the file. If time goes over this limit, the file will be deleted.
48
     **/
49
    public function upload(File $file, $dest_folder = '', \DateTime $lifetime = null)
50
    {
51
        if ($file instanceof UploadedFile) {
52
            if ($file->getError() !== null && $file->getError() !== 0) {
53
                throw new UploadException($file->getErrorMessage());
54
            }
55
        }
56
57
        //preparing dir name
58
        $dest_folder = date('Ymd') . '/' . date('G') . '/' . $dest_folder;
59
60
        //checking mimetypes
61
        $mimeTypePassed = false;
62
        foreach ($this->allowedMimetypes as $mimeType) {
63
            if (preg_match('@' . $mimeType . '@', $file->getMimeType())) {
64
                $mimeTypePassed = true;
65
            }
66
        }
67
68
        if (!$mimeTypePassed) {
69
            throw new InvalidMimeTypeException('Only following filetypes are allowed : ' . implode(', ', $this->allowedMimetypes));
70
        }
71
72
        $fs = new Filesystem();
73
        if (!$fs->exists($this->uploadDir . $dest_folder)) {
74
            $fs->mkdir($this->uploadDir . $dest_folder);
75
        }
76
77
        $em = $this->entityManager;
78
        $media = new Media();
79
        $media->setMime($file->getMimeType());
80
81
        // If there's one, we try to generate a new name
82
        $extension = $file->getExtension();
83
84
        // Sanitizing the filename
85
        $slugify = new Slugify();
86
        if ($file instanceof UploadedFile) {
87
            if (empty($extension)) {
88
                $extension = $file->getClientOriginalExtension();
89
                if (empty($extension)) {
90
                    $extension = $file->guessClientExtension();
91
                }
92
            }
93
            $filename = $slugify->slugify(basename($file->getClientOriginalName(), $extension)) . '.' . $extension;
94
        } else {
95
            if (empty($extension)) {
96
                $extension = $file->guessClientExtension();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\HttpFoundation\File\File as the method guessClientExtension() does only exist in the following sub-classes of Symfony\Component\HttpFoundation\File\File: Symfony\Component\HttpFoundation\File\UploadedFile. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
97
            }
98
            $filename = $slugify->slugify(basename($file->getFilename(), $extension)) . '.' . $extension;
99
        }
100
101
        // A media can have a lifetime and will be deleted with the cleanup function
102
        if (!empty($lifetime)) {
103
            $media->setLifetime($lifetime);
104
        }
105
106
        // Checking for a media with the same name
107
        $mediaExists = $this->entityManager->getRepository('AlpixelMediaBundle:Media')->findOneByUri($dest_folder . $filename);
108
        $mediaExists = (count($mediaExists) > 0);
109
        if ($mediaExists === false) {
110
            $mediaExists = $fs->exists($this->uploadDir . $dest_folder . $filename);
111
        }
112
113
        if ($mediaExists === true) {
114
            $filename = basename($filename, '.' . $extension);
115
            $i = 1;
116
            do {
117
                $media->setName($filename . '-' . $i++ . '.' . $extension);
118
                $media->setUri($dest_folder . $media->getName());
119
                $mediaExists = $this->entityManager->getRepository('AlpixelMediaBundle:Media')->findOneByUri($media->getUri());
120
                $mediaExists = (count($mediaExists) > 0);
121
                if ($mediaExists === false) {
122
                    $mediaExists = $fs->exists($this->uploadDir . $dest_folder . $filename);
123
                }
124
            } while ($mediaExists === true);
125
        } else {
126
            $media->setName($filename);
127
            $media->setUri($dest_folder . $media->getName());
128
        }
129
130
        $file->move($this->uploadDir . $dest_folder, $media->getName());
131
        chmod($this->uploadDir . $dest_folder . $media->getName(), 0664);
132
133
        // Getting the salt defined in parameters.yml
134
        $secret = $this->container->getParameter('secret');
135
        $media->setSecretKey(hash('sha256', $secret . $media->getName() . $media->getUri()));
136
137
        $em->persist($media);
138
        $em->flush();
139
140
        return $media;
141
    }
142
143
    /**
144
     * @return int
145
     */
146
    public function cleanup()
147
    {
148
        $expired = 0;
149
150
        $mediaRepo = $this->entityManager->getRepository('AlpixelMediaBundle:Media');
151
152
        //Cleanup expired files
153
        $medias = $mediaRepo->findExpiredMedias();
154
        $expired += count($medias);
155
        foreach ($medias as $media) {
156
            $this->delete($media);
157
        }
158
159
        //Cleanup files without any database record
160
        $fs = new Filesystem();
161
        $finder = new Finder();
162
        $files = $finder->in($this->uploadDir)->exclude('filters')->files();
163
164
        foreach ($files as $file) {
165
            $path = $file->getRelativePathname();
166
            $media = $mediaRepo->findOneByUri($path);
167
            if ($media === null) {
168
                try {
169
                    $fs->remove($file);
170
                    $expired++;
171
                } catch (IOException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
172
                }
173
            }
174
        }
175
176
        return $expired;
177
    }
178
179
    public function delete(Media $media, $flush = true)
180
    {
181
        $em = $this->entityManager;
182
        $fs = new Filesystem();
183
184
        $file_path = $this->uploadDir . $media->getUri();
185
186
        try {
187
            $file = new File($file_path);
188
            if ($file->isFile() && $file->isWritable()) {
189
                $fs->remove($file_path);
190
            }
191
        } catch (FileNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
192
        }
193
194
        $em->remove($media);
195
        if ($flush) {
196
            $em->flush();
197
        }
198
    }
199
200
    public function getUploadDir($filter = null)
201
    {
202
        if (!empty($filter)) {
203
            return $this->uploadDir . 'filters/' . $filter . '/';
204
        }
205
206
        return $this->uploadDir;
207
    }
208
209
    public function getWebPath(Media $media)
210
    {
211
        $request = $this->container->get('request');
212
        $dir = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . '/';
213
214
        return $dir . $media->getUri();
215
    }
216
217
    public function getAbsolutePath(Media $media, $filter = null)
218
    {
219
        $imgSrc = $this->uploadDir;
220
        if (!empty($filter)) {
221
            return $imgSrc . 'filters/' . $filter . '/' . $media->getUri();
222
        } else {
223
            return $imgSrc . $media->getUri();
224
        }
225
    }
226
227
    public function generateUrl(Media $media, $options)
228
    {
229
        $defaultOptions = [
230
            'public'   => true,
231
            'action'   => 'show',
232
            'filter'   => null,
233
            'absolute' => false,
234
        ];
235
236
        $options = array_merge($defaultOptions, $options);
237
        $params = [];
238
239
        $routeName = 'media_';
240
        if ($options['action'] === 'download') {
241
            $routeName .= 'download_';
242
        } else {
243
            $routeName .= 'show_';
244
        }
245
246
        if ($options['public'] === true) {
247
            $routeName .= 'public';
248
            $params['id'] = $media->getId();
249
            $params['name'] = $media->getName();
250
        } else {
251
            $routeName .= 'private';
252
            $params['secretKey'] = $media->getSecretKey();
253
        }
254
255
        if ($options['filter'] !== null) {
256
            if ($options['public'] === true) {
257
                $routeName .= '_filters';
258
            }
259
            $params['filter'] = $options['filter'];
260
        }
261
262
        $container = $this->container;
263
        $router = $container->get('router');
264
265
        if ($options['absolute']) {
266
            $referenceType = UrlGenerator::ABSOLUTE_URL;
267
        } else {
268
            $referenceType = UrlGenerator::ABSOLUTE_PATH;
269
        }
270
271
        return $router->generate($routeName, $params, $referenceType);
272
    }
273
274
    public function findFromSecret($secret)
275
    {
276
        return $this->entityManager->getRepository('AlpixelMediaBundle:Media')->findOneBySecretKey($secret);
277
    }
278
279
    public function setAllowedMimeTypes(array $type)
280
    {
281
        if ($type !== null) {
282
            $this->allowedMimetypes = $type;
283
        }
284
285
        return $this;
286
    }
287
288
    public function getAllowedMimeTypes()
289
    {
290
        return $this->allowedMimetypes;
291
    }
292
293
    public function convertOctetIn($size, $convert)
294
    {
295
        if ($convert > 0) {
296
            $size = ($size / self::SIZE_OF_KIBIOCTET) * 1;
297
298
            return $this->convertOctetIn($size, $convert - 1);
299
        }
300
301
        return $size;
302
    }
303
}
304