Security update for permissions_by_term
[yaffs-website] / web / modules / contrib / filefield_sources / filefield_sources.module
1 <?php
2
3 /**
4  * @file
5  * Extend FileField to allow files from multiple sources.
6  */
7
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\Component\Utility\NestedArray;
10 use Drupal\Core\Field\WidgetBase;
11 use Drupal\Core\Field\WidgetInterface;
12 use Drupal\Core\Field\FieldDefinitionInterface;
13 use Drupal\Core\Render\Element;
14 use Drupal\imce\Imce;
15
16 const FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH = 'file_attach';
17 const FILEFIELD_SOURCE_ATTACH_RELATIVE = 0;
18 const FILEFIELD_SOURCE_ATTACH_ABSOLUTE = 1;
19 const FILEFIELD_SOURCE_ATTACH_MODE_MOVE = 'move';
20 const FILEFIELD_SOURCE_ATTACH_MODE_COPY = 'copy';
21
22 const FILEFIELD_SOURCE_REFERENCE_HINT_TEXT = 'example.png [fid:123]';
23 const FILEFIELD_SOURCE_REMOTE_HINT_TEXT = 'http://example.com/files/file.png';
24
25 const FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH = '0';
26 const FILEFIELD_SOURCE_REFERENCE_MATCH_CONTAINS = '1';
27 const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO = '0';
28 const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_YES = '1';
29
30 /**
31  * Implements hook_element_info_alter().
32  */
33 function filefield_sources_element_info_alter(&$type) {
34   if (isset($type['managed_file'])) {
35     $type['managed_file']['#process'][] = 'filefield_sources_field_process';
36     $type['managed_file']['#pre_render'][] = 'filefield_sources_field_pre_render';
37     $type['managed_file']['#element_validate'][] = 'filefield_sources_field_validate';
38     $type['managed_file']['#file_value_callbacks'][] = 'filefield_sources_field_value';
39   }
40 }
41
42 /**
43  * Implements hook_theme().
44  */
45 function filefield_sources_theme() {
46   $theme = array();
47
48   $theme['filefield_sources_element'] = array(
49     'render element' => 'element',
50     'function' => 'theme_filefield_sources_element',
51   );
52
53   $theme['filefield_sources_list'] = array(
54     'variables' => array('element' => NULL, 'sources' => NULL),
55     'function' => 'theme_filefield_sources_list',
56   );
57
58   return $theme;
59 }
60
61 /**
62  * Implements hook_field_widget_third_party_settings_form().
63  *
64  * Add file field sources settings form to supported field widget forms.
65  *
66  * @see \Drupal\field_ui\FormDisplayOverview
67  */
68 function filefield_sources_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
69   $element = array();
70   if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
71     $element = filefield_sources_form($plugin, $form_state);
72   }
73   return $element;
74 }
75
76 /**
77  * Implements hook_field_widget_settings_summary_alter().
78  *
79  * Add file field sources information to the field widget settings summary.
80  *
81  * @see \Drupal\field_ui\FormDisplayOverview
82  */
83 function filefield_sources_field_widget_settings_summary_alter(&$summary, $context) {
84   $plugin = $context['widget'];
85   if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
86     $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
87     $enabled_sources = _filefield_sources_enabled($settings);
88     $summary[] = t('File field sources:') . ' ' . implode(', ', array_keys($enabled_sources));
89   }
90 }
91
92 /**
93  * Implements hook_field_widget_form_alter().
94  *
95  * Add file field sources widget's settings to element.
96  */
97 function filefield_sources_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
98   $plugin = $context['widget'];
99   if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
100     $element['#filefield_sources_settings'] = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
101
102     // Bundle is missing in element.
103     $items = $context['items'];
104     $element['#bundle'] = $items->getEntity()->bundle();
105   }
106 }
107
108 /**
109  * Implements hook_filefield_sources_widgets().
110  *
111  * This returns a list of widgets that are compatible with FileField Sources.
112  */
113 function filefield_sources_filefield_sources_widgets() {
114   return array('file_generic', 'image_image');
115 }
116
117 /**
118  * Configuration form for editing FileField Sources settings for a widget.
119  */
120 function filefield_sources_form($plugin, FormStateInterface $form_state) {
121   $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
122
123   // Backward compatibility: auto-enable 'upload'.
124   $enabled = _filefield_sources_enabled($settings);
125
126   $form['filefield_sources'] = array(
127     '#type' => 'details',
128     '#title' => t('File sources'),
129     '#weight' => 20,
130   );
131
132   $sources = filefield_sources_list();
133   $form['filefield_sources']['sources'] = array(
134     '#type' => 'checkboxes',
135     '#title' => t('Enabled sources'),
136     '#options' => $sources,
137     '#default_value' => $enabled,
138     '#description' => t('Select the available locations from which this widget may select files.'),
139   );
140
141   $params = array($plugin);
142   $form['filefield_sources'] = array_merge($form['filefield_sources'], filefield_sources_invoke_all('settings', $params));
143
144   return $form;
145 }
146
147 /**
148  * A #process callback to extend the filefield_widget element type.
149  *
150  * Add the central JavaScript and CSS files that allow switching between
151  * different sources. Third-party modules can also add to the list of sources
152  * by implementing hook_filefield_sources_info().
153  */
154 function filefield_sources_field_process(&$element, FormStateInterface $form_state, &$complete_form) {
155   static $js_added;
156
157   // Check if we are processing file field sources.
158   if (!isset($element['#filefield_sources_settings'])) {
159     return $element;
160   }
161
162   // Do all processing as needed by each source.
163   $sources = filefield_sources_info();
164   $settings = $element['#filefield_sources_settings'];
165   $enabled_sources = _filefield_sources_enabled($settings);
166
167   $context = array(
168     'enabled_sources' => &$enabled_sources,
169     'element'         => $element,
170     'form_state'      => $form_state,
171   );
172
173   // Allow other modules to alter the sources.
174   \Drupal::moduleHandler()->alter('filefield_sources_sources', $sources, $context);
175
176   foreach ($sources as $source_name => $source) {
177     if (empty($enabled_sources[$source_name])) {
178       unset($sources[$source_name]);
179     }
180     // Default upload plugin does not have class.
181     elseif (isset($source['class'])) {
182       $callback = array($source['class'], 'process');
183       if (is_callable($callback)) {
184         $element = call_user_func_array($callback, array(
185           &$element,
186           $form_state,
187           &$complete_form,
188         ));
189       }
190     }
191   }
192   $element['#filefield_sources'] = $sources;
193
194   // Exit out if not adding any sources.
195   if (empty($sources)) {
196     return $element;
197   }
198
199   // Hide default 'upload' type?
200   if (!isset($enabled_sources['upload'])) {
201     foreach (array('upload_button', 'upload') as $field) {
202       if (isset($element[$field])) {
203         $element[$field]['#access'] = FALSE;
204       }
205     }
206   }
207
208   // Add class to upload button.
209   $element['upload_button']['#attributes']['class'][] = 'upload-button';
210
211   $element['#attached']['library'][] = 'filefield_sources/drupal.filefield_sources';
212
213   // Check the element for hint text that might need to be added.
214   foreach (Element::children($element) as $key) {
215     if (isset($element[$key]['#filefield_sources_hint_text']) && !isset($js_added[$key])) {
216       $type = str_replace('filefield_', '', $key);
217
218       $element['#attached']['drupalSettings']['fileFieldSources'][$type] = array(
219         'hintText' => $element[$key]['#filefield_sources_hint_text'],
220       );
221
222       $js_added[$key] = TRUE;
223     }
224   }
225
226   // Adjust the Ajax settings so that on upload and remove of any individual
227   // file, the entire group of file fields is updated together.
228   // Clone of Drupal\file\Plugin\Field\FieldWidget\FileWidget::process().
229   if ($element['#cardinality'] != 1) {
230     $parents = array_slice($element['#array_parents'], 0, -1);
231     $new_options = array(
232       'query' => array(
233         'element_parents' => implode('/', $parents),
234       ),
235     );
236     $field_element = NestedArray::getValue($complete_form, $parents);
237     $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
238     foreach (Element::children($element) as $key) {
239       foreach (Element::children($element[$key]) as $subkey) {
240         if (isset($element[$key][$subkey]['#ajax'])) {
241           $element[$key][$subkey]['#ajax']['options'] = $new_options;
242           $element[$key][$subkey]['#ajax']['wrapper'] = $new_wrapper;
243           $element[$key][$subkey]['#limit_validation_errors'] = array(
244             array_slice($element['#array_parents'], 0, -2),
245           );
246         }
247       }
248     }
249     unset($element['#prefix'], $element['#suffix']);
250   }
251
252   // Add the list of sources to the element for toggling between sources.
253   if (empty($element['fids']['#value'])) {
254     if (count($enabled_sources) > 1) {
255       $element['filefield_sources_list'] = array(
256         '#theme' => 'filefield_sources_list',
257         '#element' => $element,
258         '#sources' => $sources,
259         '#weight' => -20,
260       );
261     }
262   }
263
264   return $element;
265 }
266
267 /**
268  * A #pre_render function to hide sources if a file is currently uploaded.
269  */
270 function filefield_sources_field_pre_render($element) {
271   // If we already have a file, we don't want to show the upload controls.
272   if (!empty($element['#value']['fids'])) {
273     foreach (Element::children($element) as $key) {
274       if (!empty($element[$key]['#filefield_source'])) {
275         $element[$key]['#access'] = FALSE;
276       }
277     }
278   }
279   return $element;
280 }
281
282 /**
283  * An #element_validate function to run source validations.
284  */
285 function filefield_sources_field_validate(&$element, FormStateInterface $form_state, &$complete_form) {
286   // Do all processing as needed by each source.
287   $sources = filefield_sources_info();
288   foreach ($sources as $source) {
289     if (!isset($source['class'])) {
290       continue;
291     }
292
293     $callback = array($source['class'], 'validate');
294     if (is_callable($callback)) {
295       call_user_func_array($callback, array(
296         $element,
297         $form_state,
298         $complete_form,
299       ));
300     }
301   }
302 }
303
304 /**
305  * Form submission handler for all FileField Source buttons.
306  *
307  * Clone of \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit(), with
308  * a few changes:
309  *   - Submit button is one level down compare to 'Upload' source's submit
310  *     button.
311  *   - Replace static in static::getWidgetState and static::setWidgetState by
312  *     WidgetBase.
313  *   - Rebuild the form after all.
314  */
315 function filefield_sources_field_submit(&$form, FormStateInterface $form_state) {
316   // During the form rebuild, formElement() will create field item widget
317   // elements using re-indexed deltas, so clear out FormState::$input to
318   // avoid a mismatch between old and new deltas. The rebuilt elements will
319   // have #default_value set appropriately for the current state of the field,
320   // so nothing is lost in doing this.
321   $button = $form_state->getTriggeringElement();
322   $parents = array_slice($button['#parents'], 0, -3);
323   NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
324
325   // Go one level up in the form, to the widgets container.
326   $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
327   $field_name = $element['#field_name'];
328   $parents = $element['#field_parents'];
329
330   $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -3));
331   foreach ($submitted_values as $delta => $submitted_value) {
332     if (empty($submitted_value['fids'])) {
333       unset($submitted_values[$delta]);
334     }
335   }
336
337   // If there are more files uploaded via the same widget, we have to separate
338   // them, as we display each file in it's own widget.
339   $new_values = array();
340   foreach ($submitted_values as $delta => $submitted_value) {
341     if (is_array($submitted_value['fids'])) {
342       foreach ($submitted_value['fids'] as $fid) {
343         $new_value = $submitted_value;
344         $new_value['fids'] = array($fid);
345         $new_values[] = $new_value;
346       }
347     }
348     else {
349       $new_value = $submitted_value;
350     }
351   }
352
353   // Re-index deltas after removing empty items.
354   $submitted_values = array_values($new_values);
355
356   // Update form_state values.
357   NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -3), $submitted_values);
358
359   // Update items.
360   $field_state = WidgetBase::getWidgetState($parents, $field_name, $form_state);
361   $field_state['items'] = $submitted_values;
362   WidgetBase::setWidgetState($parents, $field_name, $form_state, $field_state);
363
364   // We need to rebuild the form, so that uploaded file can be displayed.
365   $form_state->setRebuild();
366 }
367
368 /**
369  * A #filefield_value_callback to run source value callbacks.
370  */
371 function filefield_sources_field_value(&$element, &$input, FormStateInterface $form_state) {
372   // Do all processing as needed by each source.
373   $sources = filefield_sources_info();
374   foreach ($sources as $source) {
375     if (isset($source['class'])) {
376       $callback = array($source['class'], 'value');
377       if (is_callable($callback)) {
378         call_user_func_array($callback, array(&$element, &$input, $form_state));
379       }
380     }
381   }
382 }
383
384 /**
385  * Call all FileField Source hooks stored in the available include files.
386  */
387 function filefield_sources_invoke_all($method, &$params) {
388   $return = array();
389   foreach (\Drupal::service('filefield_sources')->getDefinitions() as $definition) {
390     if (!isset($definition['class'])) {
391       continue;
392     }
393     // Get routes defined by each plugin.
394     $callback = array($definition['class'], $method);
395     if (is_callable($callback)) {
396       $result = call_user_func_array($callback, $params);
397       if (isset($result) && is_array($result)) {
398         $return = array_merge_recursive($return, $result);
399       }
400       elseif (isset($result)) {
401         $return[] = $result;
402       }
403     }
404   }
405   return $return;
406 }
407
408 /**
409  * Load hook_filefield_sources_info() data from all modules.
410  */
411 function filefield_sources_info($include_default = TRUE) {
412   $info = \Drupal::service('filefield_sources')->getDefinitions();
413   if (isset($info['imce']) && !Imce::access()) {
414     unset($info['imce']);
415   }
416   if ($include_default) {
417     $info['upload'] = array(
418       'name' => t('Upload (default)'),
419       'label' => t('Upload'),
420       'description' => t('Upload a file from your computer.'),
421       'weight' => -10,
422     );
423   }
424
425   uasort($info, '_filefield_sources_sort');
426
427   return $info;
428 }
429
430 /**
431  * Create a list of FileField Sources by name, suitable for a select list.
432  */
433 function filefield_sources_list($include_default = TRUE) {
434   $info = filefield_sources_info($include_default);
435   $list = array();
436
437   foreach ($info as $key => $source) {
438     $list[$key] = $source['name'];
439   }
440
441   return $list;
442 }
443
444 /**
445  * Save a file into the database after validating it.
446  *
447  * This function is identical to the core function file_save_upload() except
448  * that it accepts an input file path instead of an input file source name.
449  *
450  * @see file_save_upload()
451  */
452 function filefield_sources_save_file($filepath, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
453   $user = \Drupal::currentUser();
454
455   // Begin building file object.
456   $file = entity_create('file', array(
457     'uri' => $filepath,
458     'uid' => $user->id(),
459     'status' => FILE_EXISTS_RENAME,
460   ));
461   $file->setFilename(trim(basename($filepath), '.'));
462   $file->setMimeType(\Drupal::service('file.mime_type.guesser')->guess($file->getFilename()));
463   $file->setSize(filesize($filepath));
464
465   $extensions = '';
466   if (isset($validators['file_validate_extensions'])) {
467     if (isset($validators['file_validate_extensions'][0])) {
468       // Build the list of non-munged extensions if the caller provided them.
469       $extensions = $validators['file_validate_extensions'][0];
470     }
471     else {
472       // If 'file_validate_extensions' is set and the list is empty then the
473       // caller wants to allow any extension. In this case we have to remove the
474       // validator or else it will reject all extensions.
475       unset($validators['file_validate_extensions']);
476     }
477   }
478   else {
479     // No validator was provided, so add one using the default list.
480     // Build a default non-munged safe list for file_munge_filename().
481     $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
482     $validators['file_validate_extensions'] = array();
483     $validators['file_validate_extensions'][0] = $extensions;
484   }
485
486   if (!empty($extensions)) {
487     // Munge the filename to protect against possible malicious extension hiding
488     // within an unknown file type (ie: filename.html.foo).
489     $file->setFilename(file_munge_filename($file->getFilename(), $extensions));
490   }
491
492   // Rename potentially executable files, to help prevent exploits (i.e. will
493   // rename filename.php.foo and filename.php to filename.php.foo.txt and
494   // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
495   // evaluates to TRUE.
496   if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
497     $file->setMimeType('text/plain');
498     $file->setFileUri($file->getFileUri() . '.txt');
499     $file->setFilename($file->getFilename() . '.txt');
500     // The .txt extension may not be in the allowed list of extensions. We have
501     // to add it here or else the file upload will fail.
502     if (!empty($extensions)) {
503       $validators['file_validate_extensions'][0] .= ' txt';
504       drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename())));
505     }
506   }
507
508   // If the destination is not provided, use the temporary directory.
509   if (empty($destination)) {
510     $destination = 'temporary://';
511   }
512
513   // Assert that the destination contains a valid stream.
514   $destination_scheme = file_uri_scheme($destination);
515   if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
516     drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
517     return FALSE;
518   }
519
520   // A URI may already have a trailing slash or look like "public://".
521   if (substr($destination, -1) != '/') {
522     $destination .= '/';
523   }
524
525   // Ensure the destination is writable.
526   file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
527
528   // Check if this is actually the same file being "attached" to a file record.
529   // If so, it acts as a file replace, except no file is actually moved.
530   $reuse_file = ($destination . $file->getFilename() === $file->getFileUri());
531   if ($reuse_file) {
532     $replace = FILE_EXISTS_REPLACE;
533   }
534
535   $file->destination = file_destination($destination . $file->getFilename(), $replace);
536   // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
537   // there's an existing file so we need to bail.
538   if ($file->destination === FALSE) {
539     drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $file->getFilename(), '%directory' => $destination)), 'error');
540     return FALSE;
541   }
542
543   // Add in our check of the the file name length.
544   $validators['file_validate_name_length'] = array();
545
546   // Call the validation functions specified by this function's caller.
547   $errors = file_validate($file, $validators);
548
549   // Check for errors.
550   if (!empty($errors)) {
551     $message = t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename()));
552     if (count($errors) > 1) {
553       $message .= theme('item_list', array('items' => $errors));
554     }
555     else {
556       $message .= ' ' . array_pop($errors);
557     }
558     drupal_set_message($message, 'error');
559     return FALSE;
560   }
561
562   // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
563   // directory. This overcomes open_basedir restrictions for future file
564   // operations.
565   $file->setFileUri($file->destination);
566   if (!$reuse_file && !file_unmanaged_copy($filepath, $file->getFileUri(), $replace)) {
567     drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
568     \Drupal::logger('filefield_sources')->log(E_NOTICE, 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->getFilename(), '%destination' => $file->getFileUri()));
569     return FALSE;
570   }
571
572   // Set the permissions on the new file.
573   drupal_chmod($file->getFileUri());
574
575   // If we are replacing an existing file re-use its database record.
576   if ($replace == FILE_EXISTS_REPLACE) {
577     $existing_files = file_load_multiple(array(), array('uri' => $file->getFileUri()));
578     if (count($existing_files)) {
579       $existing = reset($existing_files);
580       $file->setOriginalId($existing->id());
581     }
582   }
583
584   // If we made it this far it's safe to record this file in the database.
585   $file->save();
586   return $file;
587 }
588
589 /**
590  * Clean up the file name, munging extensions and transliterating.
591  *
592  * @param string $filepath
593  *   A string containing a file name or full path. Only the file name will
594  *   actually be modified.
595  *
596  * @return string
597  *   A file path with a cleaned-up file name.
598  */
599 function filefield_sources_clean_filename($filepath, $extensions) {
600   $filename = basename($filepath);
601
602   if (\Drupal::moduleHandler()->moduleExists('transliteration')) {
603     module_load_include('inc', 'transliteration');
604
605     $langcode = NULL;
606     if (!empty($_POST['language'])) {
607       $languages = language_list();
608       $langcode = isset($languages[$_POST['language']]) ? $_POST['language'] : NULL;
609     }
610     $filename = transliteration_clean_filename($filename, $langcode);
611   }
612
613   // Because this transfer mechanism does not use file_save_upload(), we need
614   // to manually munge the filename to prevent dangerous extensions.
615   // See file_save_upload().
616   if (empty($extensions)) {
617     $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
618   }
619   $filename = file_munge_filename($filename, $extensions);
620   $directory = drupal_dirname($filepath);
621   return ($directory != '.' ? $directory . '/' : '') . $filename;
622 }
623
624 /**
625  * Theme the display of the source element.
626  */
627 function theme_filefield_sources_element($variables) {
628   $element = $variables['element'];
629   $source_id = $element['#source_id'];
630   $method = isset($element['#method']) ? $element['#method'] : 'element';
631   $extra_variables = isset($element['#variables']) ? $element['#variables'] : array();
632
633   $sources = filefield_sources_info();
634   if (isset($sources[$source_id]['class'])) {
635     $callback = array($sources[$source_id]['class'], $method);
636     if (is_callable($callback)) {
637       $variables = array_merge($variables, $extra_variables);
638       return call_user_func_array($callback, array($variables));
639     }
640   }
641
642   return '';
643 }
644
645 /**
646  * Theme the display of the sources list.
647  */
648 function theme_filefield_sources_list($variables) {
649   $element = $variables['element'];
650   $sources = $variables['sources'];
651
652   $links = array();
653
654   foreach ($sources as $name => $source) {
655     $links[] = '<a href="#" onclick="return false;" title="' . $source['description'] . '" id="' . $element['#id'] . '-' . $name . '-source" class="filefield-source filefield-source-' . $name . '">' . $source['label'] . '</a>';
656   }
657   return '<div class="filefield-sources-list">' . implode(' | ', $links) . '</div>';
658 }
659
660 /**
661  * Validate a file based on the $element['#upload_validators'] property.
662  */
663 function filefield_sources_element_validate($element, $file, FormStateInterface $form_state) {
664   $validators = $element['#upload_validators'];
665   $errors = array();
666
667   // Since this frequently is used to reference existing files, check that
668   // they exist first in addition to the normal validations.
669   if (!file_exists($file->getFileUri())) {
670     $errors[] = t('The file does not exist.');
671   }
672   // Call the validation functions.
673   else {
674     foreach ($validators as $function => $args) {
675       // Add the $file variable to the list of arguments and pass it by
676       // reference (required for PHP 5.3 and higher).
677       array_unshift($args, NULL);
678       $args[0] = &$file;
679       $errors = array_merge($errors, call_user_func_array($function, $args));
680     }
681   }
682
683   // Check for validation errors.
684   if (!empty($errors)) {
685     $message = t('The selected file %name could not be referenced.', array('%name' => $file->filename));
686     if (count($errors) > 1) {
687       $message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
688     }
689     else {
690       $message .= ' ' . array_pop($errors);
691     }
692     $form_state->setError($element, $message);
693     return 0;
694   }
695
696   return 1;
697 }
698
699 /**
700  * Generate help text based on the $element['#upload_validators'] property.
701  */
702 function filefield_sources_element_validation_help($validators) {
703   $desc = array();
704   foreach ($validators as $callback => $arguments) {
705     $help_func = $callback . '_help';
706     if (function_exists($help_func)) {
707       $desc[] = call_user_func_array($help_func, $arguments);
708     }
709   }
710   return empty($desc) ? '' : implode('<br />', $desc);
711 }
712
713 /**
714  * Custom sort function for ordering sources.
715  */
716 function _filefield_sources_sort($a, $b) {
717   $a = (array) $a + array('weight' => 0, 'label' => '');
718   $b = (array) $b + array('weight' => 0, 'label' => '');
719   return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : strnatcasecmp($a['label'], $b['label']));
720 }
721
722 /**
723  * Helper to return enabled sources for a field.
724  *
725  * This provides backward compatibility for 'upload' type.
726  *
727  * @see http://drupal.org/node/932994
728  */
729 function _filefield_sources_enabled($settings) {
730   if (!isset($settings['sources']['upload'])) {
731     $settings['sources']['upload'] = 'upload';
732   }
733
734   $enabled = array_keys(array_filter($settings['sources']));
735   asort($enabled);
736   return array_combine($enabled, $enabled);
737 }
738
739 // @todo Remove once https://www.drupal.org/node/1808132 is finished.
740 if (!function_exists('module_get_weight')) {
741   /**
742    * Gets weight of a particular module.
743    *
744    * @param string $module
745    *   The name of the module (without the .module extension).
746    *
747    * @return int
748    *   The configured weight of the module.
749    *
750    * @throws InvalidArgumentException
751    *   Thrown in case the given module is not installed in the system.
752    */
753   function module_get_weight($module) {
754     $weight = \Drupal::config('core.extension')->get("module.$module");
755     if ($weight !== NULL) {
756       return (int) $weight;
757     }
758     $weight = \Drupal::config('core.extension')->get("disabled.module.$module");
759     if ($weight !== NULL) {
760       return (int) $weight;
761     }
762     throw new InvalidArgumentException(format_string('The module %module is not installed.', array('%module' => $module)));
763   }
764 }
765
766 /**
767  * Check for CURL extension enabled.
768  */
769 function filefield_sources_curl_enabled() {
770   return function_exists('curl_version');
771 }