Issues (36)

src/AbstractUploadField.php (2 issues)

Labels
1
<?php
2
3
namespace LeKoala\FilePond;
4
5
use LogicException;
6
use SilverStripe\Forms\Form;
7
use SilverStripe\Assets\Image;
8
use SilverStripe\Assets\Folder;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\Forms\FormField;
11
use SilverStripe\Forms\Validator;
0 ignored issues
show
The type SilverStripe\Forms\Validator 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...
12
use SilverStripe\Control\Controller;
13
use SilverStripe\Control\HTTPRequest;
14
use SilverStripe\Control\HTTPResponse;
15
use SilverStripe\Forms\FileHandleField;
16
use SilverStripe\Control\NullHTTPRequest;
17
use SilverStripe\Forms\FileUploadReceiver;
18
use SilverStripe\ORM\FieldType\DBHTMLText;
19
use SilverStripe\AssetAdmin\Forms\UploadField;
0 ignored issues
show
The type SilverStripe\AssetAdmin\Forms\UploadField 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...
20
use SilverStripe\Core\Validation\ValidationResult;
21
use SilverStripe\Model\List\SS_List;
22
23
/**
24
 * An abstract class that serve as a base to implement dedicated uploaders
25
 *
26
 * This follows roughly the same pattern as the UploadField class
27
 * but does not depends on asset admin
28
 *
29
 * Copy pasted functions that were adapted are using NEW: comments on top of
30
 * the lines that are changed/added
31
 */
32
abstract class AbstractUploadField extends FormField implements FileHandleField
33
{
34
    use FileUploadReceiver;
35
    use ImprovedUploader;
36
37
    /**
38
     * Schema needs to be something else than custom otherwise it fails on ajax load because
39
     * we don't have a proper react component
40
     * @var string
41
     */
42
    protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_HIDDEN;
43
44
    /**
45
     * @var string
46
     */
47
    protected $schemaComponent;
48
49
    /**
50
     * Set if uploading new files is enabled.
51
     * If false, only existing files can be selected
52
     *
53
     * @var bool
54
     */
55
    protected $uploadEnabled = true;
56
57
    /**
58
     * Set if selecting existing files is enabled.
59
     * If false, only new files can be selected.
60
     *
61
     * @var bool
62
     */
63
    protected $attachEnabled = true;
64
65
    /**
66
     * The number of files allowed for this field
67
     *
68
     * @var null|int
69
     */
70
    protected $allowedMaxFileNumber = null;
71
72
    /**
73
     * @var string
74
     */
75
    protected $inputType = 'file';
76
77
    /**
78
     * @var bool|null
79
     */
80
    protected $multiUpload = null;
81
82
    /**
83
     * Create a new file field.
84
     *
85
     * @param string $name The internal field name, passed to forms.
86
     * @param string $title The field label.
87
     * @param SS_List $items Items assigned to this field
88
     */
89
    public function __construct($name, $title = null, ?SS_List $items = null)
90
    {
91
        $this->constructFileUploadReceiver();
92
93
        // NEW : Reset default size to allow our default config to work properly
94
        $this->getUpload()->getValidator()->allowedMaxFileSize = [];
95
96
        // When creating new files, rename on conflict
97
        $this->getUpload()->setReplaceFile(false);
98
99
        parent::__construct($name, $title);
100
        if ($items) {
101
            $this->setItems($items);
102
        }
103
104
        // NEW : Fix null request
105
        if ($this->request instanceof NullHTTPRequest) {
106
            $this->request = Controller::curr()->getRequest();
107
        }
108
    }
109
110
    /**
111
     * @return array<mixed>
112
     */
113
    public function getSchemaDataDefaults()
114
    {
115
        $defaults = parent::getSchemaDataDefaults();
116
117
        /** @var Form|null $form */
118
        $form = $this->form;
119
120
        // NEW : wrap conditionnaly to avoid errors if not linked to a form
121
        if ($form) {
122
            $uploadLink = $this->Link('upload');
123
            $defaults['data']['createFileEndpoint'] = [
124
                'url' => $uploadLink,
125
                'method' => 'post',
126
                'payloadFormat' => 'urlencoded',
127
            ];
128
        }
129
130
        $defaults['data']['maxFilesize'] = $this->getAllowedMaxFileSize() / 1024 / 1024;
131
        $defaults['data']['maxFiles'] = $this->getAllowedMaxFileNumber();
132
        $defaults['data']['multi'] = $this->getIsMultiUpload();
133
        $defaults['data']['parentid'] = $this->getFolderID();
134
        $defaults['data']['canUpload'] = $this->getUploadEnabled();
135
        $defaults['data']['canAttach'] = $this->getAttachEnabled();
136
137
        return $defaults;
138
    }
139
140
141
    /**
142
     * Handles file uploading
143
     *
144
     * @param HTTPRequest $request
145
     * @return HTTPResponse
146
     */
147
    abstract public function upload(HTTPRequest $request);
148
149
    /**
150
     * Get ID of target parent folder
151
     *
152
     * @return int
153
     */
154
    protected function getFolderID()
155
    {
156
        $folderName = $this->getFolderName();
157
        if (!$folderName) {
158
            return 0;
159
        }
160
        $folder = Folder::find_or_make($folderName);
161
        return $folder ? $folder->ID : 0;
162
    }
163
164
    /**
165
     * @return array<mixed>
166
     */
167
    public function getSchemaStateDefaults()
168
    {
169
        $state = parent::getSchemaStateDefaults();
170
        $state['data']['files'] = $this->getItemIDs();
171
        $state['value'] = $this->getValue() ?: ['Files' => []];
172
        return $state;
173
    }
174
175
    /**
176
     * Check if allowed to upload more than one file
177
     *
178
     * @return bool
179
     */
180
    public function getIsMultiUpload()
181
    {
182
        if (isset($this->multiUpload)) {
183
            return $this->multiUpload;
184
        }
185
        // Guess from record
186
        /** @var DataObject|null $record */
187
        $record = $this->getRecord();
188
        $name = $this->getName();
189
190
        // Disabled for has_one components
191
        if ($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
192
            return false;
193
        }
194
        return true;
195
    }
196
197
    /**
198
     * Set upload type to multiple or single
199
     *
200
     * @param bool $bool True for multiple, false for single
201
     * @return $this
202
     */
203
    public function setIsMultiUpload($bool)
204
    {
205
        $this->multiUpload = $bool;
206
        return $this;
207
    }
208
209
    /**
210
     * Gets the number of files allowed for this field
211
     *
212
     * @return null|int
213
     */
214
    public function getAllowedMaxFileNumber()
215
    {
216
        return $this->allowedMaxFileNumber;
217
    }
218
219
    /**
220
     * Returns the max allowed filesize
221
     *
222
     * @return null|int
223
     */
224
    public function getAllowedMaxFileSize()
225
    {
226
        return $this->getValidator()->getLargestAllowedMaxFileSize();
227
    }
228
229
    /**
230
     * @return boolean
231
     */
232
    public function isDefaultMaxFileSize()
233
    {
234
        // This returns null until getAllowedMaxFileSize is called
235
        $current = $this->getValidator()->getLargestAllowedMaxFileSize();
236
        return $current ? false : true;
237
    }
238
239
    /**
240
     * Sets the number of files allowed for this field
241
     * @param int $count
242
     * @return $this
243
     */
244
    public function setAllowedMaxFileNumber($count)
245
    {
246
        $this->allowedMaxFileNumber = $count;
247
248
        return $this;
249
    }
250
251
    /**
252
     * @return array<string,mixed>
253
     */
254
    public function getAttributes()
255
    {
256
        $attributes = array(
257
            'class' => $this->extraClass(),
258
            'type' => 'file',
259
            'multiple' => $this->getIsMultiUpload(),
260
            'id' => $this->ID(),
261
            'data-schema' => json_encode($this->getSchemaData()),
262
            'data-state' => json_encode($this->getSchemaState()),
263
        );
264
265
        $attributes = array_merge($attributes, $this->attributes);
266
267
        $this->extend('updateAttributes', $attributes);
268
269
        return $attributes;
270
    }
271
272
    /**
273
     * @return string
274
     */
275
    public function Type()
276
    {
277
        return 'file';
278
    }
279
280
    public function performReadonlyTransformation()
281
    {
282
        $clone = clone $this;
283
        $clone->setReadonly(true);
284
        return $clone;
285
    }
286
287
    public function performDisabledTransformation()
288
    {
289
        $clone = clone $this;
290
        $clone->setDisabled(true);
291
        return $clone;
292
    }
293
294
    /**
295
     * Checks if the number of files attached adheres to the $allowedMaxFileNumber defined
296
     */
297
    public function validate(): ValidationResult
298
    {
299
        $validator = parent::validate();
300
301
        $maxFiles = $this->getAllowedMaxFileNumber();
302
        $count = count($this->getItems());
303
304
        if ($maxFiles > 1 && $count > $maxFiles) {
305
            $validator->addFieldError($this->getName(), _t(
306
                'FilePondField.ErrorMaxFilesReached',
307
                'You can only upload {count} file.|You can only upload {count} files.',
308
                [
309
                    'count' => $maxFiles,
310
                ]
311
            ));
312
        }
313
314
        return $validator;
315
    }
316
317
    /**
318
     * Check if uploading files is enabled
319
     *
320
     * @return bool
321
     */
322
    public function getUploadEnabled()
323
    {
324
        return $this->uploadEnabled;
325
    }
326
327
    /**
328
     * Set if uploading files is enabled
329
     *
330
     * @param bool $uploadEnabled
331
     * @return $this
332
     */
333
    public function setUploadEnabled($uploadEnabled)
334
    {
335
        $this->uploadEnabled = $uploadEnabled;
336
        return $this;
337
    }
338
339
    /**
340
     * Check if attaching files is enabled
341
     *
342
     * @return bool
343
     */
344
    public function getAttachEnabled()
345
    {
346
        return $this->attachEnabled;
347
    }
348
349
    /**
350
     * Set if attaching files is enabled
351
     *
352
     * @param bool $attachEnabled
353
     * @return AbstractUploadField
354
     */
355
    public function setAttachEnabled($attachEnabled)
356
    {
357
        $this->attachEnabled = $attachEnabled;
358
        return $this;
359
    }
360
361
    /**
362
     * @param array<mixed> $properties
363
     * @return DBHTMLText
364
     */
365
    public function Field($properties = array())
366
    {
367
        /** @var DataObject|null $record */
368
        $record = $this->getRecord();
369
        if ($record) {
370
            $relation = $record->getRelationClass($this->name);
371
372
            // Make sure images do not accept default stuff
373
            if ($relation == Image::class) {
374
                $allowedExtensions = $this->getAllowedExtensions();
375
                if (in_array('zip', $allowedExtensions)) {
376
                    // Only allow processable file types for images by default
377
                    $this->setAllowedExtensions(['jpg', 'jpeg', 'png']);
378
                }
379
            }
380
381
            // Set a default description if none set
382
            if (!$this->description && static::config()->enable_default_description) {
383
                $this->setDefaultDescription($relation, $record, $this->name);
384
            }
385
        }
386
        return parent::Field($properties);
387
    }
388
389
    /**
390
     * Gets the upload folder name
391
     *
392
     * Replaces method from UploadReceiver to provide a more flexible default
393
     *
394
     * @return string
395
     */
396
    public function getFolderName()
397
    {
398
        /** @var bool $hasFolder */
399
        $hasFolder = ($this->folderName !== false);
400
        return $hasFolder ? $this->folderName : $this->getDefaultFolderName();
401
    }
402
403
    /**
404
     * @inheritDoc
405
     */
406
    public function Link($action = null)
407
    {
408
        /** @var Form|null $form */
409
        $form = $this->form;
410
411
        if (!$form) {
412
            throw new LogicException(
413
                'Field must be associated with a form to call Link(). Please use $field->setForm($form);'
414
            );
415
        }
416
        $name = $this->getSafeName();
417
        $link = Controller::join_links($form->FormAction(), 'field/' . $name, $action);
418
        $this->extend('updateLink', $link, $action);
419
        return $link;
420
    }
421
}
422