|
@@ -0,0 +1,771 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+/**
|
|
|
+ * @file
|
|
|
+ * Extend FileField to allow files from multiple sources.
|
|
|
+ */
|
|
|
+
|
|
|
+use Drupal\Core\Form\FormStateInterface;
|
|
|
+use Drupal\Component\Utility\NestedArray;
|
|
|
+use Drupal\Core\Field\WidgetBase;
|
|
|
+use Drupal\Core\Field\WidgetInterface;
|
|
|
+use Drupal\Core\Field\FieldDefinitionInterface;
|
|
|
+use Drupal\Core\Render\Element;
|
|
|
+use Drupal\imce\Imce;
|
|
|
+
|
|
|
+const FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH = 'file_attach';
|
|
|
+const FILEFIELD_SOURCE_ATTACH_RELATIVE = 0;
|
|
|
+const FILEFIELD_SOURCE_ATTACH_ABSOLUTE = 1;
|
|
|
+const FILEFIELD_SOURCE_ATTACH_MODE_MOVE = 'move';
|
|
|
+const FILEFIELD_SOURCE_ATTACH_MODE_COPY = 'copy';
|
|
|
+
|
|
|
+const FILEFIELD_SOURCE_REFERENCE_HINT_TEXT = 'example.png [fid:123]';
|
|
|
+const FILEFIELD_SOURCE_REMOTE_HINT_TEXT = 'http://example.com/files/file.png';
|
|
|
+
|
|
|
+const FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH = '0';
|
|
|
+const FILEFIELD_SOURCE_REFERENCE_MATCH_CONTAINS = '1';
|
|
|
+const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO = '0';
|
|
|
+const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_YES = '1';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Implements hook_element_info_alter().
|
|
|
+ */
|
|
|
+function filefield_sources_element_info_alter(&$type) {
|
|
|
+ if (isset($type['managed_file'])) {
|
|
|
+ $type['managed_file']['#process'][] = 'filefield_sources_field_process';
|
|
|
+ $type['managed_file']['#pre_render'][] = 'filefield_sources_field_pre_render';
|
|
|
+ $type['managed_file']['#element_validate'][] = 'filefield_sources_field_validate';
|
|
|
+ $type['managed_file']['#file_value_callbacks'][] = 'filefield_sources_field_value';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Implements hook_theme().
|
|
|
+ */
|
|
|
+function filefield_sources_theme() {
|
|
|
+ $theme = array();
|
|
|
+
|
|
|
+ $theme['filefield_sources_element'] = array(
|
|
|
+ 'render element' => 'element',
|
|
|
+ 'function' => 'theme_filefield_sources_element',
|
|
|
+ );
|
|
|
+
|
|
|
+ $theme['filefield_sources_list'] = array(
|
|
|
+ 'variables' => array('element' => NULL, 'sources' => NULL),
|
|
|
+ 'function' => 'theme_filefield_sources_list',
|
|
|
+ );
|
|
|
+
|
|
|
+ return $theme;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Implements hook_field_widget_third_party_settings_form().
|
|
|
+ *
|
|
|
+ * Add file field sources settings form to supported field widget forms.
|
|
|
+ *
|
|
|
+ * @see \Drupal\field_ui\FormDisplayOverview
|
|
|
+ */
|
|
|
+function filefield_sources_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
|
|
|
+ $element = array();
|
|
|
+ if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
|
|
|
+ $element = filefield_sources_form($plugin, $form_state);
|
|
|
+ }
|
|
|
+ return $element;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Implements hook_field_widget_settings_summary_alter().
|
|
|
+ *
|
|
|
+ * Add file field sources information to the field widget settings summary.
|
|
|
+ *
|
|
|
+ * @see \Drupal\field_ui\FormDisplayOverview
|
|
|
+ */
|
|
|
+function filefield_sources_field_widget_settings_summary_alter(&$summary, $context) {
|
|
|
+ $plugin = $context['widget'];
|
|
|
+ if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
|
|
|
+ $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
|
|
|
+ $enabled_sources = _filefield_sources_enabled($settings);
|
|
|
+ $summary[] = t('File field sources:') . ' ' . implode(', ', array_keys($enabled_sources));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Implements hook_field_widget_form_alter().
|
|
|
+ *
|
|
|
+ * Add file field sources widget's settings to element.
|
|
|
+ */
|
|
|
+function filefield_sources_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
|
|
|
+ $plugin = $context['widget'];
|
|
|
+ if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
|
|
|
+ $element['#filefield_sources_settings'] = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
|
|
|
+
|
|
|
+ // Bundle is missing in element.
|
|
|
+ $items = $context['items'];
|
|
|
+ $element['#bundle'] = $items->getEntity()->bundle();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Implements hook_filefield_sources_widgets().
|
|
|
+ *
|
|
|
+ * This returns a list of widgets that are compatible with FileField Sources.
|
|
|
+ */
|
|
|
+function filefield_sources_filefield_sources_widgets() {
|
|
|
+ return array('file_generic', 'image_image');
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Configuration form for editing FileField Sources settings for a widget.
|
|
|
+ */
|
|
|
+function filefield_sources_form($plugin, FormStateInterface $form_state) {
|
|
|
+ $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
|
|
|
+
|
|
|
+ // Backward compatibility: auto-enable 'upload'.
|
|
|
+ $enabled = _filefield_sources_enabled($settings);
|
|
|
+
|
|
|
+ $form['filefield_sources'] = array(
|
|
|
+ '#type' => 'details',
|
|
|
+ '#title' => t('File sources'),
|
|
|
+ '#weight' => 20,
|
|
|
+ );
|
|
|
+
|
|
|
+ $sources = filefield_sources_list();
|
|
|
+ $form['filefield_sources']['sources'] = array(
|
|
|
+ '#type' => 'checkboxes',
|
|
|
+ '#title' => t('Enabled sources'),
|
|
|
+ '#options' => $sources,
|
|
|
+ '#default_value' => $enabled,
|
|
|
+ '#description' => t('Select the available locations from which this widget may select files.'),
|
|
|
+ );
|
|
|
+
|
|
|
+ $params = array($plugin);
|
|
|
+ $form['filefield_sources'] = array_merge($form['filefield_sources'], filefield_sources_invoke_all('settings', $params));
|
|
|
+
|
|
|
+ return $form;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * A #process callback to extend the filefield_widget element type.
|
|
|
+ *
|
|
|
+ * Add the central JavaScript and CSS files that allow switching between
|
|
|
+ * different sources. Third-party modules can also add to the list of sources
|
|
|
+ * by implementing hook_filefield_sources_info().
|
|
|
+ */
|
|
|
+function filefield_sources_field_process(&$element, FormStateInterface $form_state, &$complete_form) {
|
|
|
+ static $js_added;
|
|
|
+
|
|
|
+ // Check if we are processing file field sources.
|
|
|
+ if (!isset($element['#filefield_sources_settings'])) {
|
|
|
+ return $element;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Do all processing as needed by each source.
|
|
|
+ $sources = filefield_sources_info();
|
|
|
+ $settings = $element['#filefield_sources_settings'];
|
|
|
+ $enabled_sources = _filefield_sources_enabled($settings);
|
|
|
+
|
|
|
+ $context = array(
|
|
|
+ 'enabled_sources' => &$enabled_sources,
|
|
|
+ 'element' => $element,
|
|
|
+ 'form_state' => $form_state,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Allow other modules to alter the sources.
|
|
|
+ \Drupal::moduleHandler()->alter('filefield_sources_sources', $sources, $context);
|
|
|
+
|
|
|
+ foreach ($sources as $source_name => $source) {
|
|
|
+ if (empty($enabled_sources[$source_name])) {
|
|
|
+ unset($sources[$source_name]);
|
|
|
+ }
|
|
|
+ // Default upload plugin does not have class.
|
|
|
+ elseif (isset($source['class'])) {
|
|
|
+ $callback = array($source['class'], 'process');
|
|
|
+ if (is_callable($callback)) {
|
|
|
+ $element = call_user_func_array($callback, array(
|
|
|
+ &$element,
|
|
|
+ $form_state,
|
|
|
+ &$complete_form,
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $element['#filefield_sources'] = $sources;
|
|
|
+
|
|
|
+ // Exit out if not adding any sources.
|
|
|
+ if (empty($sources)) {
|
|
|
+ return $element;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Hide default 'upload' type?
|
|
|
+ if (!isset($enabled_sources['upload'])) {
|
|
|
+ foreach (array('upload_button', 'upload') as $field) {
|
|
|
+ if (isset($element[$field])) {
|
|
|
+ $element[$field]['#access'] = FALSE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add class to upload button.
|
|
|
+ $element['upload_button']['#attributes']['class'][] = 'upload-button';
|
|
|
+
|
|
|
+ $element['#attached']['library'][] = 'filefield_sources/drupal.filefield_sources';
|
|
|
+
|
|
|
+ // Check the element for hint text that might need to be added.
|
|
|
+ foreach (Element::children($element) as $key) {
|
|
|
+ if (isset($element[$key]['#filefield_sources_hint_text']) && !isset($js_added[$key])) {
|
|
|
+ $type = str_replace('filefield_', '', $key);
|
|
|
+
|
|
|
+ $element['#attached']['drupalSettings']['fileFieldSources'][$type] = array(
|
|
|
+ 'hintText' => $element[$key]['#filefield_sources_hint_text'],
|
|
|
+ );
|
|
|
+
|
|
|
+ $js_added[$key] = TRUE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Adjust the Ajax settings so that on upload and remove of any individual
|
|
|
+ // file, the entire group of file fields is updated together.
|
|
|
+ // Clone of Drupal\file\Plugin\Field\FieldWidget\FileWidget::process().
|
|
|
+ if ($element['#cardinality'] != 1) {
|
|
|
+ $parents = array_slice($element['#array_parents'], 0, -1);
|
|
|
+ $new_options = array(
|
|
|
+ 'query' => array(
|
|
|
+ 'element_parents' => implode('/', $parents),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ $field_element = NestedArray::getValue($complete_form, $parents);
|
|
|
+ $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
|
|
|
+ foreach (Element::children($element) as $key) {
|
|
|
+ foreach (Element::children($element[$key]) as $subkey) {
|
|
|
+ if (isset($element[$key][$subkey]['#ajax'])) {
|
|
|
+ $element[$key][$subkey]['#ajax']['options'] = $new_options;
|
|
|
+ $element[$key][$subkey]['#ajax']['wrapper'] = $new_wrapper;
|
|
|
+ $element[$key][$subkey]['#limit_validation_errors'] = array(
|
|
|
+ array_slice($element['#array_parents'], 0, -2),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($element['#prefix'], $element['#suffix']);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add the list of sources to the element for toggling between sources.
|
|
|
+ if (empty($element['fids']['#value'])) {
|
|
|
+ if (count($enabled_sources) > 1) {
|
|
|
+ $element['filefield_sources_list'] = array(
|
|
|
+ '#theme' => 'filefield_sources_list',
|
|
|
+ '#element' => $element,
|
|
|
+ '#sources' => $sources,
|
|
|
+ '#weight' => -20,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $element;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * A #pre_render function to hide sources if a file is currently uploaded.
|
|
|
+ */
|
|
|
+function filefield_sources_field_pre_render($element) {
|
|
|
+ // If we already have a file, we don't want to show the upload controls.
|
|
|
+ if (!empty($element['#value']['fids'])) {
|
|
|
+ foreach (Element::children($element) as $key) {
|
|
|
+ if (!empty($element[$key]['#filefield_source'])) {
|
|
|
+ $element[$key]['#access'] = FALSE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $element;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * An #element_validate function to run source validations.
|
|
|
+ */
|
|
|
+function filefield_sources_field_validate(&$element, FormStateInterface $form_state, &$complete_form) {
|
|
|
+ // Do all processing as needed by each source.
|
|
|
+ $sources = filefield_sources_info();
|
|
|
+ foreach ($sources as $source) {
|
|
|
+ if (!isset($source['class'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $callback = array($source['class'], 'validate');
|
|
|
+ if (is_callable($callback)) {
|
|
|
+ call_user_func_array($callback, array(
|
|
|
+ $element,
|
|
|
+ $form_state,
|
|
|
+ $complete_form,
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Form submission handler for all FileField Source buttons.
|
|
|
+ *
|
|
|
+ * Clone of \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit(), with
|
|
|
+ * a few changes:
|
|
|
+ * - Submit button is one level down compare to 'Upload' source's submit
|
|
|
+ * button.
|
|
|
+ * - Replace static in static::getWidgetState and static::setWidgetState by
|
|
|
+ * WidgetBase.
|
|
|
+ * - Rebuild the form after all.
|
|
|
+ */
|
|
|
+function filefield_sources_field_submit(&$form, FormStateInterface $form_state) {
|
|
|
+ // During the form rebuild, formElement() will create field item widget
|
|
|
+ // elements using re-indexed deltas, so clear out FormState::$input to
|
|
|
+ // avoid a mismatch between old and new deltas. The rebuilt elements will
|
|
|
+ // have #default_value set appropriately for the current state of the field,
|
|
|
+ // so nothing is lost in doing this.
|
|
|
+ $button = $form_state->getTriggeringElement();
|
|
|
+ $parents = array_slice($button['#parents'], 0, -3);
|
|
|
+ NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
|
|
|
+
|
|
|
+ // Go one level up in the form, to the widgets container.
|
|
|
+ $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
|
|
|
+ $field_name = $element['#field_name'];
|
|
|
+ $parents = $element['#field_parents'];
|
|
|
+
|
|
|
+ $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -3));
|
|
|
+ foreach ($submitted_values as $delta => $submitted_value) {
|
|
|
+ if (empty($submitted_value['fids'])) {
|
|
|
+ unset($submitted_values[$delta]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If there are more files uploaded via the same widget, we have to separate
|
|
|
+ // them, as we display each file in it's own widget.
|
|
|
+ $new_values = array();
|
|
|
+ foreach ($submitted_values as $delta => $submitted_value) {
|
|
|
+ if (is_array($submitted_value['fids'])) {
|
|
|
+ foreach ($submitted_value['fids'] as $fid) {
|
|
|
+ $new_value = $submitted_value;
|
|
|
+ $new_value['fids'] = array($fid);
|
|
|
+ $new_values[] = $new_value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ $new_value = $submitted_value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Re-index deltas after removing empty items.
|
|
|
+ $submitted_values = array_values($new_values);
|
|
|
+
|
|
|
+ // Update form_state values.
|
|
|
+ NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -3), $submitted_values);
|
|
|
+
|
|
|
+ // Update items.
|
|
|
+ $field_state = WidgetBase::getWidgetState($parents, $field_name, $form_state);
|
|
|
+ $field_state['items'] = $submitted_values;
|
|
|
+ WidgetBase::setWidgetState($parents, $field_name, $form_state, $field_state);
|
|
|
+
|
|
|
+ // We need to rebuild the form, so that uploaded file can be displayed.
|
|
|
+ $form_state->setRebuild();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * A #filefield_value_callback to run source value callbacks.
|
|
|
+ */
|
|
|
+function filefield_sources_field_value(&$element, &$input, FormStateInterface $form_state) {
|
|
|
+ // Do all processing as needed by each source.
|
|
|
+ $sources = filefield_sources_info();
|
|
|
+ foreach ($sources as $source) {
|
|
|
+ if (isset($source['class'])) {
|
|
|
+ $callback = array($source['class'], 'value');
|
|
|
+ if (is_callable($callback)) {
|
|
|
+ call_user_func_array($callback, array(&$element, &$input, $form_state));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Call all FileField Source hooks stored in the available include files.
|
|
|
+ */
|
|
|
+function filefield_sources_invoke_all($method, &$params) {
|
|
|
+ $return = array();
|
|
|
+ foreach (\Drupal::service('filefield_sources')->getDefinitions() as $definition) {
|
|
|
+ if (!isset($definition['class'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // Get routes defined by each plugin.
|
|
|
+ $callback = array($definition['class'], $method);
|
|
|
+ if (is_callable($callback)) {
|
|
|
+ $result = call_user_func_array($callback, $params);
|
|
|
+ if (isset($result) && is_array($result)) {
|
|
|
+ $return = array_merge_recursive($return, $result);
|
|
|
+ }
|
|
|
+ elseif (isset($result)) {
|
|
|
+ $return[] = $result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $return;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Load hook_filefield_sources_info() data from all modules.
|
|
|
+ */
|
|
|
+function filefield_sources_info($include_default = TRUE) {
|
|
|
+ $info = \Drupal::service('filefield_sources')->getDefinitions();
|
|
|
+ if (isset($info['imce']) && !Imce::access()) {
|
|
|
+ unset($info['imce']);
|
|
|
+ }
|
|
|
+ if ($include_default) {
|
|
|
+ $info['upload'] = array(
|
|
|
+ 'name' => t('Upload (default)'),
|
|
|
+ 'label' => t('Upload'),
|
|
|
+ 'description' => t('Upload a file from your computer.'),
|
|
|
+ 'weight' => -10,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ uasort($info, '_filefield_sources_sort');
|
|
|
+
|
|
|
+ return $info;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Create a list of FileField Sources by name, suitable for a select list.
|
|
|
+ */
|
|
|
+function filefield_sources_list($include_default = TRUE) {
|
|
|
+ $info = filefield_sources_info($include_default);
|
|
|
+ $list = array();
|
|
|
+
|
|
|
+ foreach ($info as $key => $source) {
|
|
|
+ $list[$key] = $source['name'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $list;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Save a file into the database after validating it.
|
|
|
+ *
|
|
|
+ * This function is identical to the core function file_save_upload() except
|
|
|
+ * that it accepts an input file path instead of an input file source name.
|
|
|
+ *
|
|
|
+ * @see file_save_upload()
|
|
|
+ */
|
|
|
+function filefield_sources_save_file($filepath, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
|
|
|
+ $user = \Drupal::currentUser();
|
|
|
+
|
|
|
+ // Begin building file object.
|
|
|
+ $file = entity_create('file', array(
|
|
|
+ 'uri' => $filepath,
|
|
|
+ 'uid' => $user->id(),
|
|
|
+ 'status' => FILE_EXISTS_RENAME,
|
|
|
+ ));
|
|
|
+ $file->setFilename(trim(basename($filepath), '.'));
|
|
|
+ $file->setMimeType(\Drupal::service('file.mime_type.guesser')->guess($file->getFilename()));
|
|
|
+ $file->setSize(filesize($filepath));
|
|
|
+
|
|
|
+ $extensions = '';
|
|
|
+ if (isset($validators['file_validate_extensions'])) {
|
|
|
+ if (isset($validators['file_validate_extensions'][0])) {
|
|
|
+ // Build the list of non-munged extensions if the caller provided them.
|
|
|
+ $extensions = $validators['file_validate_extensions'][0];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // If 'file_validate_extensions' is set and the list is empty then the
|
|
|
+ // caller wants to allow any extension. In this case we have to remove the
|
|
|
+ // validator or else it will reject all extensions.
|
|
|
+ unset($validators['file_validate_extensions']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // No validator was provided, so add one using the default list.
|
|
|
+ // Build a default non-munged safe list for file_munge_filename().
|
|
|
+ $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
|
|
+ $validators['file_validate_extensions'] = array();
|
|
|
+ $validators['file_validate_extensions'][0] = $extensions;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($extensions)) {
|
|
|
+ // Munge the filename to protect against possible malicious extension hiding
|
|
|
+ // within an unknown file type (ie: filename.html.foo).
|
|
|
+ $file->setFilename(file_munge_filename($file->getFilename(), $extensions));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Rename potentially executable files, to help prevent exploits (i.e. will
|
|
|
+ // rename filename.php.foo and filename.php to filename.php.foo.txt and
|
|
|
+ // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
|
|
|
+ // evaluates to TRUE.
|
|
|
+ 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')) {
|
|
|
+ $file->setMimeType('text/plain');
|
|
|
+ $file->setFileUri($file->getFileUri() . '.txt');
|
|
|
+ $file->setFilename($file->getFilename() . '.txt');
|
|
|
+ // The .txt extension may not be in the allowed list of extensions. We have
|
|
|
+ // to add it here or else the file upload will fail.
|
|
|
+ if (!empty($extensions)) {
|
|
|
+ $validators['file_validate_extensions'][0] .= ' txt';
|
|
|
+ drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename())));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If the destination is not provided, use the temporary directory.
|
|
|
+ if (empty($destination)) {
|
|
|
+ $destination = 'temporary://';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Assert that the destination contains a valid stream.
|
|
|
+ $destination_scheme = file_uri_scheme($destination);
|
|
|
+ if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
|
|
|
+ drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
|
|
|
+ return FALSE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // A URI may already have a trailing slash or look like "public://".
|
|
|
+ if (substr($destination, -1) != '/') {
|
|
|
+ $destination .= '/';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ensure the destination is writable.
|
|
|
+ file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
|
|
|
+
|
|
|
+ // Check if this is actually the same file being "attached" to a file record.
|
|
|
+ // If so, it acts as a file replace, except no file is actually moved.
|
|
|
+ $reuse_file = ($destination . $file->getFilename() === $file->getFileUri());
|
|
|
+ if ($reuse_file) {
|
|
|
+ $replace = FILE_EXISTS_REPLACE;
|
|
|
+ }
|
|
|
+
|
|
|
+ $file->destination = file_destination($destination . $file->getFilename(), $replace);
|
|
|
+ // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
|
|
|
+ // there's an existing file so we need to bail.
|
|
|
+ if ($file->destination === FALSE) {
|
|
|
+ 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');
|
|
|
+ return FALSE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add in our check of the the file name length.
|
|
|
+ $validators['file_validate_name_length'] = array();
|
|
|
+
|
|
|
+ // Call the validation functions specified by this function's caller.
|
|
|
+ $errors = file_validate($file, $validators);
|
|
|
+
|
|
|
+ // Check for errors.
|
|
|
+ if (!empty($errors)) {
|
|
|
+ $message = t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename()));
|
|
|
+ if (count($errors) > 1) {
|
|
|
+ $message .= theme('item_list', array('items' => $errors));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ $message .= ' ' . array_pop($errors);
|
|
|
+ }
|
|
|
+ drupal_set_message($message, 'error');
|
|
|
+ return FALSE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
|
|
|
+ // directory. This overcomes open_basedir restrictions for future file
|
|
|
+ // operations.
|
|
|
+ $file->setFileUri($file->destination);
|
|
|
+ if (!$reuse_file && !file_unmanaged_copy($filepath, $file->getFileUri(), $replace)) {
|
|
|
+ drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
|
|
|
+ \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()));
|
|
|
+ return FALSE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set the permissions on the new file.
|
|
|
+ drupal_chmod($file->getFileUri());
|
|
|
+
|
|
|
+ // If we are replacing an existing file re-use its database record.
|
|
|
+ if ($replace == FILE_EXISTS_REPLACE) {
|
|
|
+ $existing_files = file_load_multiple(array(), array('uri' => $file->getFileUri()));
|
|
|
+ if (count($existing_files)) {
|
|
|
+ $existing = reset($existing_files);
|
|
|
+ $file->setOriginalId($existing->id());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we made it this far it's safe to record this file in the database.
|
|
|
+ $file->save();
|
|
|
+ return $file;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Clean up the file name, munging extensions and transliterating.
|
|
|
+ *
|
|
|
+ * @param string $filepath
|
|
|
+ * A string containing a file name or full path. Only the file name will
|
|
|
+ * actually be modified.
|
|
|
+ *
|
|
|
+ * @return string
|
|
|
+ * A file path with a cleaned-up file name.
|
|
|
+ */
|
|
|
+function filefield_sources_clean_filename($filepath, $extensions) {
|
|
|
+ $filename = basename($filepath);
|
|
|
+
|
|
|
+ if (\Drupal::moduleHandler()->moduleExists('transliteration')) {
|
|
|
+ module_load_include('inc', 'transliteration');
|
|
|
+
|
|
|
+ $langcode = NULL;
|
|
|
+ if (!empty($_POST['language'])) {
|
|
|
+ $languages = language_list();
|
|
|
+ $langcode = isset($languages[$_POST['language']]) ? $_POST['language'] : NULL;
|
|
|
+ }
|
|
|
+ $filename = transliteration_clean_filename($filename, $langcode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Because this transfer mechanism does not use file_save_upload(), we need
|
|
|
+ // to manually munge the filename to prevent dangerous extensions.
|
|
|
+ // See file_save_upload().
|
|
|
+ if (empty($extensions)) {
|
|
|
+ $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
|
|
+ }
|
|
|
+ $filename = file_munge_filename($filename, $extensions);
|
|
|
+ $directory = drupal_dirname($filepath);
|
|
|
+ return ($directory != '.' ? $directory . '/' : '') . $filename;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Theme the display of the source element.
|
|
|
+ */
|
|
|
+function theme_filefield_sources_element($variables) {
|
|
|
+ $element = $variables['element'];
|
|
|
+ $source_id = $element['#source_id'];
|
|
|
+ $method = isset($element['#method']) ? $element['#method'] : 'element';
|
|
|
+ $extra_variables = isset($element['#variables']) ? $element['#variables'] : array();
|
|
|
+
|
|
|
+ $sources = filefield_sources_info();
|
|
|
+ if (isset($sources[$source_id]['class'])) {
|
|
|
+ $callback = array($sources[$source_id]['class'], $method);
|
|
|
+ if (is_callable($callback)) {
|
|
|
+ $variables = array_merge($variables, $extra_variables);
|
|
|
+ return call_user_func_array($callback, array($variables));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Theme the display of the sources list.
|
|
|
+ */
|
|
|
+function theme_filefield_sources_list($variables) {
|
|
|
+ $element = $variables['element'];
|
|
|
+ $sources = $variables['sources'];
|
|
|
+
|
|
|
+ $links = array();
|
|
|
+
|
|
|
+ foreach ($sources as $name => $source) {
|
|
|
+ $links[] = '<a href="#" onclick="return false;" title="' . $source['description'] . '" id="' . $element['#id'] . '-' . $name . '-source" class="filefield-source filefield-source-' . $name . '">' . $source['label'] . '</a>';
|
|
|
+ }
|
|
|
+ return '<div class="filefield-sources-list">' . implode(' | ', $links) . '</div>';
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Validate a file based on the $element['#upload_validators'] property.
|
|
|
+ */
|
|
|
+function filefield_sources_element_validate($element, $file, FormStateInterface $form_state) {
|
|
|
+ $validators = $element['#upload_validators'];
|
|
|
+ $errors = array();
|
|
|
+
|
|
|
+ // Since this frequently is used to reference existing files, check that
|
|
|
+ // they exist first in addition to the normal validations.
|
|
|
+ if (!file_exists($file->getFileUri())) {
|
|
|
+ $errors[] = t('The file does not exist.');
|
|
|
+ }
|
|
|
+ // Call the validation functions.
|
|
|
+ else {
|
|
|
+ foreach ($validators as $function => $args) {
|
|
|
+ // Add the $file variable to the list of arguments and pass it by
|
|
|
+ // reference (required for PHP 5.3 and higher).
|
|
|
+ array_unshift($args, NULL);
|
|
|
+ $args[0] = &$file;
|
|
|
+ $errors = array_merge($errors, call_user_func_array($function, $args));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for validation errors.
|
|
|
+ if (!empty($errors)) {
|
|
|
+ $message = t('The selected file %name could not be referenced.', array('%name' => $file->filename));
|
|
|
+ if (count($errors) > 1) {
|
|
|
+ $message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ $message .= ' ' . array_pop($errors);
|
|
|
+ }
|
|
|
+ $form_state->setError($element, $message);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Generate help text based on the $element['#upload_validators'] property.
|
|
|
+ */
|
|
|
+function filefield_sources_element_validation_help($validators) {
|
|
|
+ $desc = array();
|
|
|
+ foreach ($validators as $callback => $arguments) {
|
|
|
+ $help_func = $callback . '_help';
|
|
|
+ if (function_exists($help_func)) {
|
|
|
+ $desc[] = call_user_func_array($help_func, $arguments);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return empty($desc) ? '' : implode('<br />', $desc);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Custom sort function for ordering sources.
|
|
|
+ */
|
|
|
+function _filefield_sources_sort($a, $b) {
|
|
|
+ $a = (array) $a + array('weight' => 0, 'label' => '');
|
|
|
+ $b = (array) $b + array('weight' => 0, 'label' => '');
|
|
|
+ return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : strnatcasecmp($a['label'], $b['label']));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Helper to return enabled sources for a field.
|
|
|
+ *
|
|
|
+ * This provides backward compatibility for 'upload' type.
|
|
|
+ *
|
|
|
+ * @see http://drupal.org/node/932994
|
|
|
+ */
|
|
|
+function _filefield_sources_enabled($settings) {
|
|
|
+ if (!isset($settings['sources']['upload'])) {
|
|
|
+ $settings['sources']['upload'] = 'upload';
|
|
|
+ }
|
|
|
+
|
|
|
+ $enabled = array_keys(array_filter($settings['sources']));
|
|
|
+ asort($enabled);
|
|
|
+ return array_combine($enabled, $enabled);
|
|
|
+}
|
|
|
+
|
|
|
+// @todo Remove once https://www.drupal.org/node/1808132 is finished.
|
|
|
+if (!function_exists('module_get_weight')) {
|
|
|
+ /**
|
|
|
+ * Gets weight of a particular module.
|
|
|
+ *
|
|
|
+ * @param string $module
|
|
|
+ * The name of the module (without the .module extension).
|
|
|
+ *
|
|
|
+ * @return int
|
|
|
+ * The configured weight of the module.
|
|
|
+ *
|
|
|
+ * @throws InvalidArgumentException
|
|
|
+ * Thrown in case the given module is not installed in the system.
|
|
|
+ */
|
|
|
+ function module_get_weight($module) {
|
|
|
+ $weight = \Drupal::config('core.extension')->get("module.$module");
|
|
|
+ if ($weight !== NULL) {
|
|
|
+ return (int) $weight;
|
|
|
+ }
|
|
|
+ $weight = \Drupal::config('core.extension')->get("disabled.module.$module");
|
|
|
+ if ($weight !== NULL) {
|
|
|
+ return (int) $weight;
|
|
|
+ }
|
|
|
+ throw new InvalidArgumentException(format_string('The module %module is not installed.', array('%module' => $module)));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Check for CURL extension enabled.
|
|
|
+ */
|
|
|
+function filefield_sources_curl_enabled() {
|
|
|
+ return function_exists('curl_version');
|
|
|
+}
|