Completed
Push — master ( 1a94da...33e4a6 )
by Adrian
02:31
created

Handler   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 96.7%

Importance

Changes 18
Bugs 0 Features 5
Metric Value
c 18
b 0
f 5
dl 0
loc 286
wmc 30
lcom 1
cbo 7
ccs 88
cts 91
cp 0.967
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 16 4
B __construct() 0 29 6
A setOverwrite() 0 6 1
A setPrefix() 0 6 1
A setAutoconfirm() 0 6 1
A setSanitizerCallback() 0 8 2
A addRule() 0 18 2
A validateFile() 0 8 2
A sanitizeFileName() 0 7 2
C processSingleFile() 0 46 9
1
<?php
2
namespace Sirius\Upload;
3
4
use Sirius\Upload\Container\ContainerInterface;
5
use Sirius\Upload\Container\Local as LocalContainer;
6
use Sirius\Upload\Exception\InvalidContainerException;
7
use Sirius\Upload\Util\Arr;
8
use Sirius\Validation\ErrorMessage;
9
use Sirius\Validation\ValueValidator;
10
11
class Handler implements UploadHandlerInterface
12
{
13
    // constants for constructor options
14
    const OPTION_PREFIX = 'prefix';
15
    const OPTION_OVERWRITE = 'overwrite';
16
    const OPTION_AUTOCONFIRM = 'autoconfirm';
17
18
    // constants for validation rules
19
    const RULE_EXTENSION = 'extension';
20
    const RULE_SIZE = 'size';
21
    const RULE_IMAGE = 'image';
22
    const RULE_IMAGE_HEIGHT = 'imageheight';
23
    const RULE_IMAGE_WIDTH = 'imagewidth';
24
    const RULE_IMAGE_RATIO = 'imageratio';
25
26
    /**
27
     * @var ContainerInterface
28
     */
29
    protected $container;
30
31
    /**
32
     * Prefix to be added to the file.
33
     * It can be a subfolder (if it ends with '/', a string to be used as prefix)
34
     * or a callback that returns a string
35
     *
36
     * @var string|callback
37
     */
38
    protected $prefix = '';
39
40
    /**
41
     * When uploading a file that has the same name as a file that is
42
     * already in the container should it overwrite it or use another name
43
     *
44
     * @var boolean
45
     */
46
    protected $overwrite = false;
47
48
    /**
49
     * Whether or not the uploaded files are auto confirmed
50
     *
51
     * @var boolean
52
     */
53
    protected $autoconfirm = false;
54
55
    /**
56
     * @var \Sirius\Validation\ValueValidator
57
     */
58
    protected $validator;
59
    
60
    /**
61
     * @var function|callback
62
     */
63
    protected $sanitizerCallback;
64
65
    /**
66
     * @param $directoryOrContainer
67
     * @param  array                               $options
68
     * @param  ValueValidator                      $validator
69
     * @throws InvalidContainerException
70
     */
71 15
    public function __construct($directoryOrContainer, $options = array(), ValueValidator $validator = null)
72
    {
73 15
        $container = $directoryOrContainer;
74 15
        if (is_string($directoryOrContainer)) {
75 15
            $container = new LocalContainer($directoryOrContainer);
76 15
        }
77 15
        if (!$container instanceof ContainerInterface) {
78 1
            throw new InvalidContainerException('Destination container for uploaded files is not valid');
79
        }
80 15
        $this->container = $container;
81
82
        // create the validator
83 15
        if (!$validator) {
84 15
            $validator = new ValueValidator();
85 15
        }
86 15
        $this->validator = $validator;
87
88
        // set options
89
        $availableOptions = array(
90 15
            static::OPTION_PREFIX => 'setPrefix',
91 15
            static::OPTION_OVERWRITE => 'setOverwrite',
92 15
            static::OPTION_AUTOCONFIRM => 'setAutoconfirm'
93 15
        );
94 15
        foreach ($availableOptions as $key => $method) {
95 15
            if (isset($options[$key])) {
96 15
                $this->{$method}($options[$key]);
97 15
            }
98 15
        }
99 15
    }
100
101
    /**
102
     * Enable/disable upload overwrite
103
     *
104
     * @param  bool                   $overwrite
105
     * @return \Sirius\Upload\Handler
106
     */
107 15
    public function setOverwrite($overwrite)
108
    {
109 15
        $this->overwrite = (bool) $overwrite;
110
111 15
        return $this;
112
    }
113
114
    /**
115
     * File prefix for the upload. Can be
116
     * - a folder (if it ends with /)
117
     * - a string to be used as prefix
118
     * - a function that returns a string
119
     *
120
     * @param  string|callable        $prefix
121
     * @return \Sirius\Upload\Handler
122
     */
123 15
    public function setPrefix($prefix)
124
    {
125 15
        $this->prefix = $prefix;
126
127 15
        return $this;
128
    }
129
130
    /**
131
     * Enable/disable upload autoconfirmation
132
     * Autoconfirmation does not require calling `confirm()`
133
     *
134
     * @param  boolean                $autoconfirm
135
     * @return \Sirius\Upload\Handler
136
     */
137 15
    public function setAutoconfirm($autoconfirm)
138
    {
139 15
        $this->autoconfirm = (bool) $autoconfirm;
140
141 15
        return $this;
142
    }
143
    
144
    /**
145
     * Set the sanitizer function for cleaning up the file names
146
     * 
147
     * @param callable $callback
148
     * @throws \InvalidArgumentException
149
     * @return \Sirius\Upload\Handler
150
     */
151 2
    function setSanitizerCallback($callback)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
152
    {
153 2
        if (!is_callable($callback)) {
154 1
            throw new \InvalidArgumentException('The $callback parameter is not a valid callable entity');
155
        }
156 1
        $this->sanitizerCallback = $callback;
157 1
        return $this;
158
    }
159
160
    /**
161
     * Add validation rule (extension|size|width|height|ratio)
162
     *
163
     * @param  string                 $name
164
     * @param  mixed                  $options
165
     * @param  string                 $errorMessageTemplate
166
     * @param  string                 $label
167
     * @return \Sirius\Upload\Handler
168
     */
169 2
    public function addRule($name, $options = null, $errorMessageTemplate = null, $label = null)
170
    {
171
        $predefinedRules = array(
172 2
            static::RULE_EXTENSION,
173 2
            static::RULE_IMAGE,
174 2
            static::RULE_SIZE,
175 2
            static::RULE_IMAGE_WIDTH,
176 2
            static::RULE_IMAGE_HEIGHT,
177
            static::RULE_IMAGE_RATIO
178 2
        );
179
        // convert to a name that is known by the default RuleFactory
180 2
        if (in_array($name, $predefinedRules)) {
181 2
            $name = 'upload' . $name;
182 2
        }
183 2
        $this->validator->add($name, $options, $errorMessageTemplate, $label);
184
185 2
        return $this;
186
    }
187
188
    /**
189
     * Processes a file upload and returns an upload result file/collection
190
     *
191
     * @param  array                         $files
192
     * @return Result\Collection|Result\File
193
     */
194 12
    public function process($files = array())
195
    {
196 12
        $isSingle = isset($files['name']) && !is_array($files['name']);
197
198 12
        $files = Arr::normalizeFiles($files);
199
200 12
        foreach ($files as $k => $file) {
201 11
            $files[$k] = $this->processSingleFile($file);
202 12
        }
203
204 12
        if ($isSingle) {
205 8
            return new Result\File($files[0], $this->container);
206
        }
207
208 4
        return new Result\Collection($files, $this->container);
209
    }
210
211
    /**
212
     * Processes a single uploaded file
213
     * - sanitize the name
214
     * - validates the file
215
     * - if valid, moves the file to the container
216
     *
217
     * @param  array $file
218
     * @return array
219
     */
220 11
    protected function processSingleFile(array $file)
221
    {
222
        // store it for future reference
223 11
        $file['original_name'] = $file['name'];
224
225
        // sanitize the file name
226 11
        $file['name'] = $this->sanitizeFileName($file['name']);
227
228 11
        $file = $this->validateFile($file);
229
        // if there are messages the file is not valid
230 11
        if (isset($file['messages']) && $file['messages']) {
231 2
            return $file;
232
        }
233
234
        // add the prefix
235 9
        $prefix = '';
236 9
        if (is_callable($this->prefix)) {
237
            $prefix = (string) call_user_func($this->prefix, $file['name']);
238 9
        } elseif (is_string($this->prefix)) {
239 9
            $prefix = (string) $this->prefix;
240 9
        }
241
242
        // if overwrite is not allowed, check if the file is already in the container
243 9
        if (!$this->overwrite) {
244 9
            if ($this->container->has($prefix . $file['name'])) {
245
                // add the timestamp to ensure the file is unique
246
                // method is not bulletproof but it's pretty safe
247 1
                $file['name'] = time() . '_' . $file['name'];
248 1
            }
249 9
        }
250
251
        // attempt to move the uploaded file into the container
252 9
        if (!$this->container->moveUploadedFile($file['tmp_name'], $prefix . $file['name'])) {
253
            $file['name'] = false;
254
255
            return $file;
256
        }
257
258 9
        $file['name'] = $prefix . $file['name'];
259
        // create the lock file if autoconfirm is disabled
260 9
        if (!$this->autoconfirm) {
261 8
            $this->container->save($file['name'] . '.lock', time());
262 8
        }
263
264 9
        return $file;
265
    }
266
267
    /**
268
     * Validates a file according to the rules configured on the handler
269
     *
270
     * @param $file
271
     * @return mixed
272
     */
273 11
    protected function validateFile($file)
274
    {
275 11
        if (!$this->validator->validate($file)) {
276 2
            $file['messages'] = $this->validator->getMessages();
277 2
        }
278
279 11
        return $file;
280
    }
281
282
    /**
283
     * Sanitize the name of the uploaded file by stripping away bad characters
284
     * and replacing "invalid" characters with underscore _
285
     *
286
     * @param  string $name
287
     * @return string
288
     */
289 11
    protected function sanitizeFileName($name)
290
    {
291 11
        if ($this->sanitizerCallback) {
292 1
            return call_user_func($this->sanitizerCallback, $name);
293
        }
294 10
        return preg_replace('/[^A-Za-z0-9\.]+/', '_', $name);
295
    }
296
}
297