Passed
Push — master ( 89c1f2...c9e638 )
by Gilles
05:19 queued 12s
created

BaseCachedFile::setCdnBaseUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/*************************************************************************************/
3
/*      This file is part of the Thelia package.                                     */
4
/*                                                                                   */
5
/*      Copyright (c) OpenStudio                                                     */
6
/*      email : [email protected]                                                       */
7
/*      web : http://www.thelia.net                                                  */
8
/*                                                                                   */
9
/*      For the full copyright and license information, please view the LICENSE.txt  */
10
/*      file that was distributed with this source code.                             */
11
/*************************************************************************************/
12
namespace Thelia\Action;
13
14
use Propel\Runtime\Propel;
15
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16
use Thelia\Core\Event\CachedFileEvent;
17
use Thelia\Core\Event\File\FileCreateOrUpdateEvent;
18
use Thelia\Core\Event\File\FileDeleteEvent;
19
use Thelia\Core\Event\File\FileToggleVisibilityEvent;
20
use Thelia\Core\Event\UpdateFilePositionEvent;
21
use Thelia\Exception\FileException;
22
use Thelia\Files\FileManager;
23
use Thelia\Model\ConfigQuery;
24
use Thelia\Model\Map\ProductImageTableMap;
0 ignored issues
show
Bug introduced by
The type Thelia\Model\Map\ProductImageTableMap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use Thelia\Tools\URL;
26
27
/**
28
 *
29
 * Cached file management actions. This class handles file caching in the web space
30
 *
31
 * Basically, files are stored outside the web space (by default in local/media/<dirname>),
32
 * and cached in the web space (by default in web/local/<dirname>).
33
 *
34
 * In the file cache directory, a subdirectory for files categories (eg. product, category, folder, etc.) is
35
 * automatically created, and the cached file is created here. Plugin may use their own subdirectory as required.
36
 *
37
 * A copy (or symbolic link, by default) of the original file is created in the cache.
38
 *
39
 * @package Thelia\Action
40
 * @author Franck Allimant <[email protected]>
41
 *
42
 */
43
abstract class BaseCachedFile extends BaseAction
44
{
45
    /**
46
     * @var FileManager
47
     */
48
    protected $fileManager;
49
50
    /** @var null|string */
51
    protected $cdnBaseUrl;
52
53
    public function __construct(FileManager $fileManager)
54
    {
55
        $this->fileManager = $fileManager;
56
57
        $this->cdnBaseUrl = ConfigQuery::read('cdn.documents-base-url', null);
58
    }
59
60
    /**
61
     * @return string root of the file cache directory in web space
62
     */
63
    abstract protected function getCacheDirFromWebRoot();
64
65
    /**
66
     * @param string $url the fully qualified CDN URL that will be used to create doucments URL.
67
     */
68
    public function setCdnBaseUrl($url)
69
    {
70
        $this->cdnBaseUrl = $url;
71
    }
72
73
    /**
74
     * Clear the file cache. Is a subdirectory is specified, only this directory is cleared.
75
     * If no directory is specified, the whole cache is cleared.
76
     * Only files are deleted, directories will remain.
77
     *
78
     * @param CachedFileEvent $event
79
     */
80
    public function clearCache(CachedFileEvent $event)
81
    {
82
        $path = $this->getCachePath($event->getCacheSubdirectory(), false);
83
84
        $this->clearDirectory($path);
85
    }
86
87
    /**
88
     * Recursively clears the specified directory.
89
     *
90
     * @param string $path the directory path
91
     */
92
    protected function clearDirectory($path)
93
    {
94
        $iterator = new \DirectoryIterator($path);
95
96
        /** @var \DirectoryIterator $fileinfo */
97
        foreach ($iterator as $fileinfo) {
98
            if ($fileinfo->isDot()) {
99
                continue;
100
            }
101
102
            if ($fileinfo->isFile() || $fileinfo->isLink()) {
103
                @unlink($fileinfo->getPathname());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

103
                /** @scrutinizer ignore-unhandled */ @unlink($fileinfo->getPathname());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
104
            } elseif ($fileinfo->isDir()) {
105
                $this->clearDirectory($fileinfo->getPathname());
106
            }
107
        }
108
    }
109
110
    /**
111
     * Return the absolute URL to the cached file
112
     *
113
     * @param  string $subdir the subdirectory related to cache base
114
     * @param  string $safe_filename the safe filename, as returned by getCacheFilePath()
115
     * @return string the absolute URL to the cached file
116
     */
117
    protected function getCacheFileURL($subdir, $safe_filename)
118
    {
119
        $path = $this->getCachePathFromWebRoot($subdir);
120
121
        return URL::getInstance()->absoluteUrl(sprintf("%s/%s", $path, $safe_filename), null, URL::PATH_TO_FILE, $this->cdnBaseUrl);
122
    }
123
124
    /**
125
     * Return the full path of the cached file
126
     *
127
     * @param  string $subdir the subdirectory related to cache base
128
     * @param  string $filename the filename
129
     * @param  boolean $forceOriginalFile if true, the original file path in the cache dir is returned.
130
     * @param  string $hashed_options a hash of transformation options, or null if no transformations have been applied
131
     * @return string  the cache directory path relative to Web Root
132
     */
133
    protected function getCacheFilePath($subdir, $filename, $forceOriginalFile = false, $hashed_options = null)
134
    {
135
        $path = $this->getCachePath($subdir);
136
137
        $safe_filename = preg_replace("[^:alnum:\-\._]", "-", strtolower(basename($filename)));
138
139
        // Keep original safe name if no tranformations are applied
140
        if ($forceOriginalFile || $hashed_options == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $hashed_options of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
141
            return sprintf("%s/%s", $path, $safe_filename);
142
        } else {
143
            return sprintf("%s/%s-%s", $path, $hashed_options, $safe_filename);
144
        }
145
    }
146
147
    /**
148
     * Return the cache directory path relative to Web Root
149
     *
150
     * @param  string $subdir the subdirectory related to cache base, or null to get the cache directory only.
151
     * @return string the cache directory path relative to Web Root
152
     */
153
    protected function getCachePathFromWebRoot($subdir = null)
154
    {
155
        $cache_dir_from_web_root = $this->getCacheDirFromWebRoot();
156
157
        if ($subdir != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $subdir of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
158
            $safe_subdir = basename($subdir);
159
160
            $path = sprintf("%s/%s", $cache_dir_from_web_root, $safe_subdir);
161
        } else {
162
            $path = $cache_dir_from_web_root;
163
        }
164
165
        // Check if path is valid, e.g. in the cache dir
166
        return $path;
167
    }
168
169
    /**
170
     * Return the absolute cache directory path
171
     *
172
     * @param string $subdir the subdirectory related to cache base, or null to get the cache base directory.
173
     * @param bool $create_if_not_exists create the directory if it is not found
174
     *
175
     * @throws \RuntimeException         if cache directory cannot be created
176
     * @throws \InvalidArgumentException ii path is invalid, e.g. not in the cache dir
177
     *
178
     * @return string the absolute cache directory path
179
     */
180
    protected function getCachePath($subdir = null, $create_if_not_exists = true)
181
    {
182
        $cache_base = $this->getCachePathFromWebRoot($subdir);
183
184
        $web_root = rtrim(THELIA_WEB_DIR, '/');
185
186
        $path = sprintf("%s/%s", $web_root, $cache_base);
187
188
        // Create directory (recursively) if it does not exists.
189
        if ($create_if_not_exists && !is_dir($path)) {
190
            if (!@mkdir($path, 0777, true)) {
191
                throw new \RuntimeException(sprintf("Failed to create %s file in cache directory", $path));
192
            }
193
        }
194
195
        // Check if path is valid, e.g. in the cache dir
196
        $cache_base = realpath(sprintf("%s/%s", $web_root, $this->getCachePathFromWebRoot()));
197
198
        if (strpos(realpath($path), $cache_base) !== 0) {
199
            throw new \InvalidArgumentException(sprintf("Invalid cache path %s, with subdirectory %s", $path, $subdir));
200
        }
201
202
        return $path;
203
    }
204
205
    /**
206
     * Take care of saving a file in the database and file storage
207
     *
208
     * @param FileCreateOrUpdateEvent $event Image event
209
     *
210
     * @throws \Thelia\Exception\FileException|\Exception
211
     *
212
     */
213
    public function saveFile(FileCreateOrUpdateEvent $event)
214
    {
215
        $model = $event->getModel();
216
        $model->setFile(sprintf("tmp/%s", $event->getUploadedFile()->getFilename()));
217
        $con = Propel::getWriteConnection(ProductImageTableMap::DATABASE_NAME);
218
        $con->beginTransaction();
219
220
        try {
221
            $nbModifiedLines = $model->save($con);
0 ignored issues
show
Unused Code introduced by
The call to Thelia\Files\FileModelInterface::save() has too many arguments starting with $con. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
            /** @scrutinizer ignore-call */ 
222
            $nbModifiedLines = $model->save($con);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
222
            $event->setModel($model);
223
224
            if (!$nbModifiedLines) {
225
                throw new FileException(
226
                    sprintf(
227
                        'File "%s" (type %s) with parent id %s failed to be saved',
228
                        $event->getParentName(),
229
                        get_class($model),
230
                        $event->getParentId()
231
                    )
232
                );
233
            }
234
235
            $newUploadedFile = $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
236
237
            $event->setUploadedFile($newUploadedFile);
238
            $con->commit();
239
        } catch (\Exception $e) {
240
            $con->rollBack();
241
242
            throw $e;
243
        }
244
    }
245
246
    /**
247
     * Take care of updating file in the database and file storage
248
     *
249
     * @param FileCreateOrUpdateEvent $event Image event
250
     *
251
     * @throws \Thelia\Exception\FileException
252
     */
253
    public function updateFile(FileCreateOrUpdateEvent $event)
254
    {
255
        // Copy and save file
256
        if ($event->getUploadedFile()) {
257
            // Remove old picture file from file storage
258
            $url = $event->getModel()->getUploadDir() . '/' . $event->getOldModel()->getFile();
259
            unlink(str_replace('..', '', $url));
260
261
            $newUploadedFile = $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
262
            $event->setUploadedFile($newUploadedFile);
263
        }
264
265
        // Update image modifications
266
        $event->getModel()->save();
267
268
        $event->setModel($event->getModel());
269
    }
270
271
    /**
272
     * Deleting file in the database and in storage
273
     *
274
     * @param FileDeleteEvent $event Image event
275
     */
276
    public function deleteFile(FileDeleteEvent $event)
277
    {
278
        $this->fileManager->deleteFile($event->getFileToDelete());
279
    }
280
281
    public function updatePosition(UpdateFilePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
0 ignored issues
show
Unused Code introduced by
The parameter $eventName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

281
    public function updatePosition(UpdateFilePositionEvent $event, /** @scrutinizer ignore-unused */ $eventName, EventDispatcherInterface $dispatcher)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
282
    {
283
        $this->genericUpdatePosition($event->getQuery(), $event, $dispatcher);
284
    }
285
286
    public function toggleVisibility(FileToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
0 ignored issues
show
Unused Code introduced by
The parameter $eventName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

286
    public function toggleVisibility(FileToggleVisibilityEvent $event, /** @scrutinizer ignore-unused */ $eventName, EventDispatcherInterface $dispatcher)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
287
    {
288
        $this->genericToggleVisibility($event->getQuery(), $event, $dispatcher);
289
    }
290
}
291