7373c4397fe03919eeccbc8a4da6329116fffc1f
[yaffs-website] / web / core / modules / file / src / Plugin / Field / FieldType / FileItem.php
1 <?php
2
3 namespace Drupal\file\Plugin\Field\FieldType;
4
5 use Drupal\Component\Utility\Bytes;
6 use Drupal\Component\Render\PlainTextOutput;
7 use Drupal\Component\Utility\Random;
8 use Drupal\Core\Field\FieldDefinitionInterface;
9 use Drupal\Core\Field\FieldStorageDefinitionInterface;
10 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
13 use Drupal\Core\TypedData\DataDefinition;
14
15 /**
16  * Plugin implementation of the 'file' field type.
17  *
18  * @FieldType(
19  *   id = "file",
20  *   label = @Translation("File"),
21  *   description = @Translation("This field stores the ID of a file as an integer value."),
22  *   category = @Translation("Reference"),
23  *   default_widget = "file_generic",
24  *   default_formatter = "file_default",
25  *   list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
26  *   constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
27  * )
28  */
29 class FileItem extends EntityReferenceItem {
30
31   /**
32    * {@inheritdoc}
33    */
34   public static function defaultStorageSettings() {
35     return [
36       'target_type' => 'file',
37       'display_field' => FALSE,
38       'display_default' => FALSE,
39       'uri_scheme' => file_default_scheme(),
40     ] + parent::defaultStorageSettings();
41   }
42
43   /**
44    * {@inheritdoc}
45    */
46   public static function defaultFieldSettings() {
47     return [
48       'file_extensions' => 'txt',
49       'file_directory' => '[date:custom:Y]-[date:custom:m]',
50       'max_filesize' => '',
51       'description_field' => 0,
52     ] + parent::defaultFieldSettings();
53   }
54
55   /**
56    * {@inheritdoc}
57    */
58   public static function schema(FieldStorageDefinitionInterface $field_definition) {
59     return [
60       'columns' => [
61         'target_id' => [
62           'description' => 'The ID of the file entity.',
63           'type' => 'int',
64           'unsigned' => TRUE,
65         ],
66         'display' => [
67           'description' => 'Flag to control whether this file should be displayed when viewing content.',
68           'type' => 'int',
69           'size' => 'tiny',
70           'unsigned' => TRUE,
71           'default' => 1,
72         ],
73         'description' => [
74           'description' => 'A description of the file.',
75           'type' => 'text',
76         ],
77       ],
78       'indexes' => [
79         'target_id' => ['target_id'],
80       ],
81       'foreign keys' => [
82         'target_id' => [
83           'table' => 'file_managed',
84           'columns' => ['target_id' => 'fid'],
85         ],
86       ],
87     ];
88   }
89
90   /**
91    * {@inheritdoc}
92    */
93   public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
94     $properties = parent::propertyDefinitions($field_definition);
95
96     $properties['display'] = DataDefinition::create('boolean')
97       ->setLabel(t('Display'))
98       ->setDescription(t('Flag to control whether this file should be displayed when viewing content'));
99
100     $properties['description'] = DataDefinition::create('string')
101       ->setLabel(t('Description'));
102
103     return $properties;
104   }
105
106   /**
107    * {@inheritdoc}
108    */
109   public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
110     $element = [];
111
112     $element['#attached']['library'][] = 'file/drupal.file';
113
114     $element['display_field'] = [
115       '#type' => 'checkbox',
116       '#title' => t('Enable <em>Display</em> field'),
117       '#default_value' => $this->getSetting('display_field'),
118       '#description' => t('The display option allows users to choose if a file should be shown when viewing the content.'),
119     ];
120     $element['display_default'] = [
121       '#type' => 'checkbox',
122       '#title' => t('Files displayed by default'),
123       '#default_value' => $this->getSetting('display_default'),
124       '#description' => t('This setting only has an effect if the display option is enabled.'),
125       '#states' => [
126         'visible' => [
127           ':input[name="settings[display_field]"]' => ['checked' => TRUE],
128         ],
129       ],
130     ];
131
132     $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
133     $element['uri_scheme'] = [
134       '#type' => 'radios',
135       '#title' => t('Upload destination'),
136       '#options' => $scheme_options,
137       '#default_value' => $this->getSetting('uri_scheme'),
138       '#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
139       '#disabled' => $has_data,
140     ];
141
142     return $element;
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
149     $element = [];
150     $settings = $this->getSettings();
151
152     $element['file_directory'] = [
153       '#type' => 'textfield',
154       '#title' => t('File directory'),
155       '#default_value' => $settings['file_directory'],
156       '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.'),
157       '#element_validate' => [[get_class($this), 'validateDirectory']],
158       '#weight' => 3,
159     ];
160
161     // Make the extension list a little more human-friendly by comma-separation.
162     $extensions = str_replace(' ', ', ', $settings['file_extensions']);
163     $element['file_extensions'] = [
164       '#type' => 'textfield',
165       '#title' => t('Allowed file extensions'),
166       '#default_value' => $extensions,
167       '#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
168       '#element_validate' => [[get_class($this), 'validateExtensions']],
169       '#weight' => 1,
170       '#maxlength' => 256,
171       // By making this field required, we prevent a potential security issue
172       // that would allow files of any type to be uploaded.
173       '#required' => TRUE,
174     ];
175
176     $element['max_filesize'] = [
177       '#type' => 'textfield',
178       '#title' => t('Maximum upload size'),
179       '#default_value' => $settings['max_filesize'],
180       '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', ['%limit' => format_size(file_upload_max_size())]),
181       '#size' => 10,
182       '#element_validate' => [[get_class($this), 'validateMaxFilesize']],
183       '#weight' => 5,
184     ];
185
186     $element['description_field'] = [
187       '#type' => 'checkbox',
188       '#title' => t('Enable <em>Description</em> field'),
189       '#default_value' => isset($settings['description_field']) ? $settings['description_field'] : '',
190       '#description' => t('The description field allows users to enter a description about the uploaded file.'),
191       '#weight' => 11,
192     ];
193
194     return $element;
195   }
196
197   /**
198    * Form API callback
199    *
200    * Removes slashes from the beginning and end of the destination value and
201    * ensures that the file directory path is not included at the beginning of the
202    * value.
203    *
204    * This function is assigned as an #element_validate callback in
205    * fieldSettingsForm().
206    */
207   public static function validateDirectory($element, FormStateInterface $form_state) {
208     // Strip slashes from the beginning and end of $element['file_directory'].
209     $value = trim($element['#value'], '\\/');
210     $form_state->setValueForElement($element, $value);
211   }
212
213   /**
214    * Form API callback.
215    *
216    * This function is assigned as an #element_validate callback in
217    * fieldSettingsForm().
218    *
219    * This doubles as a convenience clean-up function and a validation routine.
220    * Commas are allowed by the end-user, but ultimately the value will be stored
221    * as a space-separated list for compatibility with file_validate_extensions().
222    */
223   public static function validateExtensions($element, FormStateInterface $form_state) {
224     if (!empty($element['#value'])) {
225       $extensions = preg_replace('/([, ]+\.?)/', ' ', trim(strtolower($element['#value'])));
226       $extensions = array_filter(explode(' ', $extensions));
227       $extensions = implode(' ', array_unique($extensions));
228       if (!preg_match('/^([a-z0-9]+([.][a-z0-9])* ?)+$/', $extensions)) {
229         $form_state->setError($element, t('The list of allowed extensions is not valid, be sure to exclude leading dots and to separate extensions with a comma or space.'));
230       }
231       else {
232         $form_state->setValueForElement($element, $extensions);
233       }
234     }
235   }
236
237   /**
238    * Form API callback.
239    *
240    * Ensures that a size has been entered and that it can be parsed by
241    * \Drupal\Component\Utility\Bytes::toInt().
242    *
243    * This function is assigned as an #element_validate callback in
244    * fieldSettingsForm().
245    */
246   public static function validateMaxFilesize($element, FormStateInterface $form_state) {
247     if (!empty($element['#value']) && !is_numeric(Bytes::toInt($element['#value']))) {
248       $form_state->setError($element, t('The "@name" option must contain a valid value. You may either leave the text field empty or enter a string like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes).', ['@name' => $element['title']]));
249     }
250   }
251
252   /**
253    * Determines the URI for a file field.
254    *
255    * @param array $data
256    *   An array of token objects to pass to Token::replace().
257    *
258    * @return string
259    *   An unsanitized file directory URI with tokens replaced. The result of
260    *   the token replacement is then converted to plain text and returned.
261    *
262    * @see \Drupal\Core\Utility\Token::replace()
263    */
264   public function getUploadLocation($data = []) {
265     return static::doGetUploadLocation($this->getSettings(), $data);
266   }
267
268   /**
269    * Determines the URI for a file field.
270    *
271    * @param array $settings
272    *   The array of field settings.
273    * @param array $data
274    *   An array of token objects to pass to Token::replace().
275    *
276    * @return string
277    *   An unsanitized file directory URI with tokens replaced. The result of
278    *   the token replacement is then converted to plain text and returned.
279    *
280    * @see \Drupal\Core\Utility\Token::replace()
281    */
282   protected static function doGetUploadLocation(array $settings, $data = []) {
283     $destination = trim($settings['file_directory'], '/');
284
285     // Replace tokens. As the tokens might contain HTML we convert it to plain
286     // text.
287     $destination = PlainTextOutput::renderFromHtml(\Drupal::token()->replace($destination, $data));
288     return $settings['uri_scheme'] . '://' . $destination;
289   }
290
291   /**
292    * Retrieves the upload validators for a file field.
293    *
294    * @return array
295    *   An array suitable for passing to file_save_upload() or the file field
296    *   element's '#upload_validators' property.
297    */
298   public function getUploadValidators() {
299     $validators = [];
300     $settings = $this->getSettings();
301
302     // Cap the upload size according to the PHP limit.
303     $max_filesize = Bytes::toInt(file_upload_max_size());
304     if (!empty($settings['max_filesize'])) {
305       $max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize']));
306     }
307
308     // There is always a file size limit due to the PHP server limit.
309     $validators['file_validate_size'] = [$max_filesize];
310
311     // Add the extension check if necessary.
312     if (!empty($settings['file_extensions'])) {
313       $validators['file_validate_extensions'] = [$settings['file_extensions']];
314     }
315
316     return $validators;
317   }
318
319   /**
320    * {@inheritdoc}
321    */
322   public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
323     $random = new Random();
324     $settings = $field_definition->getSettings();
325
326     // Prepare destination.
327     $dirname = static::doGetUploadLocation($settings);
328     file_prepare_directory($dirname, FILE_CREATE_DIRECTORY);
329
330     // Generate a file entity.
331     $destination = $dirname . '/' . $random->name(10, TRUE) . '.txt';
332     $data = $random->paragraphs(3);
333     $file = file_save_data($data, $destination, FILE_EXISTS_ERROR);
334     $values = [
335       'target_id' => $file->id(),
336       'display' => (int)$settings['display_default'],
337       'description' => $random->sentences(10),
338     ];
339     return $values;
340   }
341
342   /**
343    * Determines whether an item should be displayed when rendering the field.
344    *
345    * @return bool
346    *   TRUE if the item should be displayed, FALSE if not.
347    */
348   public function isDisplayed() {
349     if ($this->getSetting('display_field')) {
350       return (bool) $this->display;
351     }
352     return TRUE;
353   }
354
355   /**
356    * {@inheritdoc}
357    */
358   public static function getPreconfiguredOptions() {
359     return [];
360   }
361
362 }