Completed
Pull Request — master (#15)
by Benjamin
13:30
created

MediaManager::cleanup()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 33
rs 8.439
cc 5
eloc 20
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
                    print 'media deleted';
172
                } catch (IOException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
173
                }
174
            }
175
        }
176
177
        return $expired;
178
    }
179
180
    public function delete(Media $media, $flush = true)
181
    {
182
        $em = $this->entityManager;
183
        $fs = new Filesystem();
184
185
        $file_path = $this->uploadDir . $media->getUri();
186
187
        try {
188
            $file = new File($file_path);
189
            if ($file->isFile() && $file->isWritable()) {
190
                $fs->remove($file_path);
191
            }
192
        } catch (FileNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
193
        }
194
195
        $em->remove($media);
196
        if ($flush) {
197
            $em->flush();
198
        }
199
    }
200
201
    public function getUploadDir($filter = null)
202
    {
203
        if (!empty($filter)) {
204
            return $this->uploadDir . 'filters/' . $filter . '/';
205
        }
206
207
        return $this->uploadDir;
208
    }
209
210
    public function getWebPath(Media $media)
211
    {
212
        $request = $this->container->get('request');
213
        $dir = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . '/';
214
215
        return $dir . $media->getUri();
216
    }
217
218
    public function getAbsolutePath(Media $media, $filter = null)
219
    {
220
        $imgSrc = $this->uploadDir;
221
        if (!empty($filter)) {
222
            return $imgSrc . 'filters/' . $filter . '/' . $media->getUri();
223
        } else {
224
            return $imgSrc . $media->getUri();
225
        }
226
    }
227
228
    public function generateUrl(Media $media, $options)
229
    {
230
        $defaultOptions = [
231
            'public'   => true,
232
            'action'   => 'show',
233
            'filter'   => null,
234
            'absolute' => false,
235
        ];
236
237
        $options = array_merge($defaultOptions, $options);
238
        $params = [];
239
240
        $routeName = 'media_';
241
        if ($options['action'] === 'download') {
242
            $routeName .= 'download_';
243
        } else {
244
            $routeName .= 'show_';
245
        }
246
247
        if ($options['public'] === true) {
248
            $routeName .= 'public';
249
            $params['id'] = $media->getId();
250
            $params['name'] = $media->getName();
251
        } else {
252
            $routeName .= 'private';
253
            $params['secretKey'] = $media->getSecretKey();
254
        }
255
256
        if ($options['filter'] !== null) {
257
            if ($options['public'] === true) {
258
                $routeName .= '_filters';
259
            }
260
            $params['filter'] = $options['filter'];
261
        }
262
263
        $container = $this->container;
264
        $router = $container->get('router');
265
266
        if ($options['absolute']) {
267
            $referenceType = UrlGenerator::ABSOLUTE_URL;
268
        } else {
269
            $referenceType = UrlGenerator::ABSOLUTE_PATH;
270
        }
271
272
        return $router->generate($routeName, $params, $referenceType);
273
    }
274
275
    public function findFromSecret($secret)
276
    {
277
        return $this->entityManager->getRepository('AlpixelMediaBundle:Media')->findOneBySecretKey($secret);
278
    }
279
280
    public function setAllowedMimeTypes(array $type)
281
    {
282
        if ($type !== null) {
283
            $this->allowedMimetypes = $type;
284
        }
285
286
        return $this;
287
    }
288
289
    public function getAllowedMimeTypes()
290
    {
291
        return $this->allowedMimetypes;
292
    }
293
294
    public function convertOctetIn($size, $convert)
295
    {
296
        if ($convert > 0) {
297
            $size = ($size / self::SIZE_OF_KIBIOCTET) * 1;
298
299
            return $this->convertOctetIn($size, $convert - 1);
300
        }
301
302
        return $size;
303
    }
304
}