3 namespace Drupal\file\Plugin\Field\FieldType;
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;
16 * Plugin implementation of the 'file' field type.
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" = {}}
29 class FileItem extends EntityReferenceItem {
34 public static function defaultStorageSettings() {
36 'target_type' => 'file',
37 'display_field' => FALSE,
38 'display_default' => FALSE,
39 'uri_scheme' => file_default_scheme(),
40 ] + parent::defaultStorageSettings();
46 public static function defaultFieldSettings() {
48 'file_extensions' => 'txt',
49 'file_directory' => '[date:custom:Y]-[date:custom:m]',
51 'description_field' => 0,
52 ] + parent::defaultFieldSettings();
58 public static function schema(FieldStorageDefinitionInterface $field_definition) {
62 'description' => 'The ID of the file entity.',
67 'description' => 'Flag to control whether this file should be displayed when viewing content.',
74 'description' => 'A description of the file.',
79 'target_id' => ['target_id'],
83 'table' => 'file_managed',
84 'columns' => ['target_id' => 'fid'],
93 public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
94 $properties = parent::propertyDefinitions($field_definition);
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'));
100 $properties['description'] = DataDefinition::create('string')
101 ->setLabel(t('Description'));
109 public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
112 $element['#attached']['library'][] = 'file/drupal.file';
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.'),
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.'),
127 ':input[name="settings[display_field]"]' => ['checked' => TRUE],
132 $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
133 $element['uri_scheme'] = [
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,
148 public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
150 $settings = $this->getSettings();
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']],
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']],
171 // By making this field required, we prevent a potential security issue
172 // that would allow files of any type to be uploaded.
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())]),
182 '#element_validate' => [[get_class($this), 'validateMaxFilesize']],
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.'),
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
204 * This function is assigned as an #element_validate callback in
205 * fieldSettingsForm().
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);
216 * This function is assigned as an #element_validate callback in
217 * fieldSettingsForm().
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().
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.'));
232 $form_state->setValueForElement($element, $extensions);
240 * Ensures that a size has been entered and that it can be parsed by
241 * \Drupal\Component\Utility\Bytes::toInt().
243 * This function is assigned as an #element_validate callback in
244 * fieldSettingsForm().
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']]));
253 * Determines the URI for a file field.
256 * An array of token objects to pass to Token::replace().
259 * An unsanitized file directory URI with tokens replaced. The result of
260 * the token replacement is then converted to plain text and returned.
262 * @see \Drupal\Core\Utility\Token::replace()
264 public function getUploadLocation($data = []) {
265 return static::doGetUploadLocation($this->getSettings(), $data);
269 * Determines the URI for a file field.
271 * @param array $settings
272 * The array of field settings.
274 * An array of token objects to pass to Token::replace().
277 * An unsanitized file directory URI with tokens replaced. The result of
278 * the token replacement is then converted to plain text and returned.
280 * @see \Drupal\Core\Utility\Token::replace()
282 protected static function doGetUploadLocation(array $settings, $data = []) {
283 $destination = trim($settings['file_directory'], '/');
285 // Replace tokens. As the tokens might contain HTML we convert it to plain
287 $destination = PlainTextOutput::renderFromHtml(\Drupal::token()->replace($destination, $data));
288 return $settings['uri_scheme'] . '://' . $destination;
292 * Retrieves the upload validators for a file field.
295 * An array suitable for passing to file_save_upload() or the file field
296 * element's '#upload_validators' property.
298 public function getUploadValidators() {
300 $settings = $this->getSettings();
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']));
308 // There is always a file size limit due to the PHP server limit.
309 $validators['file_validate_size'] = [$max_filesize];
311 // Add the extension check if necessary.
312 if (!empty($settings['file_extensions'])) {
313 $validators['file_validate_extensions'] = [$settings['file_extensions']];
322 public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
323 $random = new Random();
324 $settings = $field_definition->getSettings();
326 // Prepare destination.
327 $dirname = static::doGetUploadLocation($settings);
328 file_prepare_directory($dirname, FILE_CREATE_DIRECTORY);
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);
335 'target_id' => $file->id(),
336 'display' => (int)$settings['display_default'],
337 'description' => $random->sentences(10),
343 * Determines whether an item should be displayed when rendering the field.
346 * TRUE if the item should be displayed, FALSE if not.
348 public function isDisplayed() {
349 if ($this->getSetting('display_field')) {
350 return (bool) $this->display;
358 public static function getPreconfiguredOptions() {