diff --git a/README.txt b/README.txt
index ba64a4d..58304d5 100644
--- a/README.txt
+++ b/README.txt
@@ -17,19 +17,20 @@ Author: Nathan Haug (quicksketch)
Installation
------------
1) Place this module directory in your modules folder (this will usually be
- "sites/all/modules/").
+ "modules/").
2) Enable the module within your Drupal site.
3) Add or configure an existing file or image field. To configure a typical node
- field, visit Admin -> Structure -> Content types and click "manage fields"
- on a type you'd like to modify. Add a new file field or edit an existing one.
+ field, visit Manage -> Structure -> Content types and click
+ "Manage form display" on a type you'd like to modify. Add a new file field or
+ edit an existing one.
While editing the file or image field, you'll have new options available
- under a "File sources" fieldset. You can enable the desired sources for that
+ under a "File sources" details. You can enable the desired sources for that
particular field.
-4) Create a piece of content that uses your file file and try it out.
+4) Create a piece of content that uses your file and try it out.
Support
-------
diff --git a/config/schema/filefield_sources.data_types.schema.yml b/config/schema/filefield_sources.data_types.schema.yml
new file mode 100644
index 0000000..1d0e67d
--- /dev/null
+++ b/config/schema/filefield_sources.data_types.schema.yml
@@ -0,0 +1,5 @@
+# Basic data types for FileField Sources.
+
+filefield_sources_source:
+ type: boolean
+ label: 'Plugin'
diff --git a/config/schema/filefield_sources.schema.yml b/config/schema/filefield_sources.schema.yml
new file mode 100644
index 0000000..a4bb0e7
--- /dev/null
+++ b/config/schema/filefield_sources.schema.yml
@@ -0,0 +1,9 @@
+field.widget.third_party.filefield_sources:
+ type: mapping
+ label: 'filefield_sources entity form display settings'
+ mapping:
+ filefield_sources:
+ type: sequence
+ label: 'filefield_sources settings'
+ sequence:
+ - type: filefield_sources.setting.[%key]
diff --git a/config/schema/filefield_sources.setting.source_attach.schema.yml b/config/schema/filefield_sources.setting.source_attach.schema.yml
new file mode 100644
index 0000000..e94e164
--- /dev/null
+++ b/config/schema/filefield_sources.setting.source_attach.schema.yml
@@ -0,0 +1,13 @@
+filefield_sources.setting.source_attach:
+ type: mapping
+ label: 'File attach settings'
+ mapping:
+ path:
+ type: string
+ label: 'File attach path'
+ absolute:
+ type: integer
+ label: 'File attach location'
+ attach_mode:
+ type: string
+ label: 'Attach method'
\ No newline at end of file
diff --git a/config/schema/filefield_sources.setting.source_reference.schema.yml b/config/schema/filefield_sources.setting.source_reference.schema.yml
new file mode 100644
index 0000000..354679b
--- /dev/null
+++ b/config/schema/filefield_sources.setting.source_reference.schema.yml
@@ -0,0 +1,7 @@
+filefield_sources.setting.source_reference:
+ type: mapping
+ label: 'Autocomplete reference options'
+ mapping:
+ autocomplete:
+ type: string
+ label: 'Match file name'
\ No newline at end of file
diff --git a/config/schema/filefield_sources.setting.sources.schema.yml b/config/schema/filefield_sources.setting.sources.schema.yml
new file mode 100644
index 0000000..cd50a51
--- /dev/null
+++ b/config/schema/filefield_sources.setting.sources.schema.yml
@@ -0,0 +1,5 @@
+filefield_sources.setting.sources:
+ type: sequence
+ label: 'filefield_sources source'
+ sequence:
+ - type: filefield_sources.source.[%key]
diff --git a/config/schema/filefield_sources.source.attach.schema.yml b/config/schema/filefield_sources.source.attach.schema.yml
new file mode 100644
index 0000000..ab7a53f
--- /dev/null
+++ b/config/schema/filefield_sources.source.attach.schema.yml
@@ -0,0 +1,3 @@
+filefield_sources.source.attach:
+ type: filefield_sources_source
+ label: 'File attach'
diff --git a/config/schema/filefield_sources.source.clipboard.schema.yml b/config/schema/filefield_sources.source.clipboard.schema.yml
new file mode 100644
index 0000000..e400679
--- /dev/null
+++ b/config/schema/filefield_sources.source.clipboard.schema.yml
@@ -0,0 +1,3 @@
+filefield_sources.source.clipboard:
+ type: filefield_sources_source
+ label: 'Clipboard'
diff --git a/config/schema/filefield_sources.source.imce.schema.yml b/config/schema/filefield_sources.source.imce.schema.yml
new file mode 100644
index 0000000..ec3b5a3
--- /dev/null
+++ b/config/schema/filefield_sources.source.imce.schema.yml
@@ -0,0 +1,3 @@
+filefield_sources.source.imce:
+ type: filefield_sources_source
+ label: 'File browser'
diff --git a/config/schema/filefield_sources.source.reference.schema.yml b/config/schema/filefield_sources.source.reference.schema.yml
new file mode 100644
index 0000000..4f77b13
--- /dev/null
+++ b/config/schema/filefield_sources.source.reference.schema.yml
@@ -0,0 +1,3 @@
+filefield_sources.source.reference:
+ type: filefield_sources_source
+ label: 'Reference existing'
diff --git a/config/schema/filefield_sources.source.remote.schema.yml b/config/schema/filefield_sources.source.remote.schema.yml
new file mode 100644
index 0000000..a26f8dc
--- /dev/null
+++ b/config/schema/filefield_sources.source.remote.schema.yml
@@ -0,0 +1,3 @@
+filefield_sources.source.remote:
+ type: filefield_sources_source
+ label: 'Remote URL'
diff --git a/config/schema/filefield_sources.source.upload.schema.yml b/config/schema/filefield_sources.source.upload.schema.yml
new file mode 100644
index 0000000..35208c7
--- /dev/null
+++ b/config/schema/filefield_sources.source.upload.schema.yml
@@ -0,0 +1,5 @@
+# Schema for the filefield_sources source plugins.
+
+filefield_sources.source.upload:
+ type: filefield_sources_source
+ label: 'Upload'
diff --git a/css/filefield_sources.css b/css/filefield_sources.css
new file mode 100644
index 0000000..4754fde
--- /dev/null
+++ b/css/filefield_sources.css
@@ -0,0 +1,44 @@
+/* Generic display for all sources. */
+
+div.filefield-source input.form-text,
+div.filefield-source select.form-select {
+ display: inline;
+ width: 20em;
+}
+
+div.filefield-source .form-item {
+ white-space: normal;
+}
+
+div.filefield-source .hint {
+ color: #999;
+}
+
+div.filefield-sources-list a.active {
+ font-weight: bold;
+}
+
+/* Clipboard source. */
+div.filefield-source-clipboard-capture {
+ border: 1px solid #ccc;
+ width: 20em;
+ height: 1.4em;
+ padding: 2px;
+ display: inline-block;
+ vertical-align: top;
+ overflow: hidden;
+}
+div.filefield-source-clipboard-capture img {
+ display: none;
+}
+
+/* Reference source. */
+div.filefield-source-reference-item {
+ font-size: 90%;
+}
+
+/* Remote source. */
+div.filefield-source-remote input.form-text {
+ /* Helps with display consistency since references has a background. */
+ background-image: inherit;
+}
diff --git a/filefield_sources.api.php b/filefield_sources.api.php
index 7f13b58..51142cf 100644
--- a/filefield_sources.api.php
+++ b/filefield_sources.api.php
@@ -1,10 +1,11 @@
t('File attach from Flickr'),
- 'label' => t('Flickr'),
- 'description' => t('Select a file from Flickr.'),
- // This callback function does all the heavy-work of creating a form element
- // to choose a Flickr photo and populate a field. For an example, see
- // filefield_source_remote_process().
- 'process' => 'mymodule_filefield_source_flickr_process',
- // This callback function then takes the value of that field and saves the
- // file locally. For an example, see filefield_source_remote_value().
- 'value' => 'mymodule_filefield_source_flickr_value',
- 'weight' => 3,
- // This optional setting will ensure that your code is included when needed
- // if your value, process, or other callbacks are located in a file other
- // than your .module file.
- 'file' => 'include/mymodule.flickr_source.inc',
- );
- return $sources;
-}
diff --git a/filefield_sources.css b/filefield_sources.css
deleted file mode 100644
index 0d5c8c1..0000000
--- a/filefield_sources.css
+++ /dev/null
@@ -1,45 +0,0 @@
-
-/* Generic display for all sources. */
-
-div.filefield-source input.form-text,
-div.filefield-source select.form-select {
- display: inline;
- width: 20em;
-}
-
-div.filefield-source .form-item {
- white-space: normal;
-}
-
-div.filefield-source .hint {
- color: #999;
-}
-
-div.filefield-sources-list a.active {
- font-weight: bold;
-}
-
-/* Clipboard source. */
-div.filefield-source-clipboard-capture {
- border: 1px solid #CCC;
- width: 20em;
- height: 1.4em;
- padding: 2px;
- display: inline-block;
- vertical-align: top;
- overflow: hidden;
-}
-div.filefield-source-clipboard-capture img {
- display: none;
-}
-
-/* Reference source. */
-div.filefield-source-reference-item {
- font-size: 90%;
-}
-
-/* Remote source. */
-div.filefield-source-remote input.form-text {
- /* Helps with display consistency since references has a background. */
- background-image: inherit;
-}
diff --git a/filefield_sources.info b/filefield_sources.info
deleted file mode 100644
index 5ef07c4..0000000
--- a/filefield_sources.info
+++ /dev/null
@@ -1,5 +0,0 @@
-name = File Field Sources
-description = Extends File fields to allow referencing of existing files, remote files, and server files.
-dependencies[] = file
-package = Fields
-core = 7.x
diff --git a/filefield_sources.info.yml b/filefield_sources.info.yml
new file mode 100644
index 0000000..784e504
--- /dev/null
+++ b/filefield_sources.info.yml
@@ -0,0 +1,7 @@
+name: File Field Sources
+type: module
+description: 'Extends File fields to allow referencing of existing files, remote files, and server files.'
+package: Fields
+core: 8.x
+dependencies:
+ - file
diff --git a/filefield_sources.install b/filefield_sources.install
index 03a19d8..8dfb84c 100644
--- a/filefield_sources.install
+++ b/filefield_sources.install
@@ -6,37 +6,18 @@
*/
/**
- * Implementation of hook_install().
+ * Implements hook_install().
*/
function filefield_sources_install() {
// FileField Sources needs to load after both ImageField and FileField.
- db_query("UPDATE {system} SET weight = 5 WHERE type = 'module' AND name = 'filefield_sources'");
-}
-
-/**
- * Enable FileField Sources on all current fields (as was the previous default).
- */
-function filefield_sources_update_6001() {
- $ret = array();
-
- drupal_load('module', 'content');
- module_load_include('inc', 'content', 'includes/content.crud');
-
- foreach (content_fields() as $field) {
- foreach (node_get_types('types') as $node_type => $type_info) {
- if ($type_field = content_fields($field['field_name'], $node_type)) {
- $type_field['widget']['filefield_sources'] = array(
- 'imce' => 'imce',
- 'reference' => 'reference',
- 'remote' => 'remote',
- );
- content_field_instance_update($type_field);
- }
- }
+ try {
+ $file_weight = module_get_weight('file');
+ $image_weight = module_get_weight('image');
+ $weight = max(array($file_weight, $image_weight));
+ $weight++;
}
-
- // FileField Sources needs to load after both ImageField and FileField.
- $ret[] = update_sql("UPDATE {system} SET weight = 5 WHERE type = 'module' AND name = 'filefield_sources'");
-
- return $ret;
+ catch (Exception $e) {
+ $weight = 5;
+ }
+ module_set_weight('filefield_sources', $weight);
}
diff --git a/filefield_sources.js b/filefield_sources.js
deleted file mode 100644
index fd0d0d4..0000000
--- a/filefield_sources.js
+++ /dev/null
@@ -1,242 +0,0 @@
-(function ($) {
-
-/**
- * Behavior to add source options to configured fields.
- */
-Drupal.behaviors.fileFieldSources = {};
-Drupal.behaviors.fileFieldSources.attach = function(context, settings) {
- $('div.filefield-sources-list:not(.filefield-sources-processed)', context).each(function() {
- $(this).addClass('filefield-sources-processed');
- var $fileFieldElement = $(this).parents('div.form-managed-file:first');
- $(this).find('a').click(function() {
- // Remove the active class.
- $(this).parents('div.filefield-sources-list').find('a.active').removeClass('active');
-
- // Find the unique FileField Source class name.
- var fileFieldSourceClass = this.className.match(/filefield-source-[0-9a-z]+/i)[0];
-
- // The default upload element is a special case.
- if ($(this).is('.filefield-source-upload')) {
- $fileFieldElement.find('div.filefield-sources-list').siblings('input.form-file, input.form-submit').css('display', '');
- $fileFieldElement.find('div.filefield-source').css('display', 'none');
- }
- else {
- $fileFieldElement.find('div.filefield-sources-list').siblings('input.form-file, input.form-submit').css('display', 'none');
- $fileFieldElement.find('div.filefield-source').not('div.' + fileFieldSourceClass).css('display', 'none');
- $fileFieldElement.find('div.' + fileFieldSourceClass).css('display', '');
- }
-
- // Add the active class.
- $(this).addClass('active');
- Drupal.fileFieldSources.updateHintText($fileFieldElement.get(0));
- }).first().triggerHandler('click');
-
- // Clipboard support.
- $fileFieldElement.find('.filefield-source-clipboard-capture')
- .bind('paste', Drupal.fileFieldSources.pasteEvent)
- .bind('focus', Drupal.fileFieldSources.pasteFocus)
- .bind('blur', Drupal.fileFieldSources.pasteBlur);
- });
-
- if (context === document) {
- $('form').submit(function() {
- Drupal.fileFieldSources.removeHintText();
- });
- }
-};
-
-/**
- * Helper functions used by FileField Sources.
- */
-Drupal.fileFieldSources = {
- /**
- * Update the hint text when clicking between source types.
- */
- updateHintText: function(fileFieldElement) {
- // Add default value hint text to text fields.
- $(fileFieldElement).find('div.filefield-source').each(function() {
- var matches = this.className.match(/filefield-source-([a-z]+)/);
- var sourceType = matches[1];
- var defaultText = '';
- var textfield = $(this).find('input.form-text:first').get(0);
- var defaultText = (Drupal.settings.fileFieldSources && Drupal.settings.fileFieldSources[sourceType]) ? Drupal.settings.fileFieldSources[sourceType].hintText : '';
-
- // If the field doesn't exist, just return.
- if (!textfield) {
- return;
- }
-
- // If this field is not shown, remove its value and be done.
- if (!$(this).is(':visible') && textfield.value == defaultText) {
- textfield.value = '';
- return;
- }
-
- // Set a default value:
- if (textfield.value == '') {
- textfield.value = defaultText;
- }
-
- // Set a default class.
- if (textfield.value == defaultText) {
- $(textfield).addClass('hint');
- }
-
- $(textfield).focus(hideHintText);
- $(textfield).blur(showHintText);
-
- function showHintText() {
- if (this.value == '') {
- this.value = defaultText;
- $(this).addClass('hint');
- }
- }
-
- function hideHintText() {
- if (this.value == defaultText) {
- this.value = '';
- $(this).removeClass('hint');
- }
- }
- });
- },
-
- /**
- * Delete all hint text from a form before submit.
- */
- removeHintText: function() {
- $('div.filefield-source input.hint').val('').removeClass('hint');
- },
-
- /**
- * Clean up the default value on focus.
- */
- pasteFocus: function(e) {
- // Set default text.
- if (!this.defaultText) {
- this.defaultText = this.innerHTML;
- this.innerHTML = '';
- }
- // Remove non-text nodes.
- $(this).children().remove();
- },
-
- /**
- * Restore default value on blur.
- */
- pasteBlur: function(e) {
- if (this.defaultText && !this.innerHTML) {
- this.innerHTML = this.defaultText;
- }
- },
-
- pasteEvent: function(e) {
- var clipboardData = null;
- var targetElement = this;
-
- // Chrome.
- if (window.event && window.event.clipboardData && window.event.clipboardData.items) {
- clipboardData = window.event.clipboardData;
- }
- // All browsers in the future (hopefully).
- else if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.items) {
- clipboardData = e.originalEvent.clipboardData;
- }
- // Firefox with content editable pastes as img tag with data href.
- else if ($.browser.mozilla) {
- Drupal.fileFieldSources.waitForPaste(targetElement);
- return true;
- }
- else {
- Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Paste from clipboard not supported in this browser.'));
- return false;
- }
-
- var items = clipboardData.items;
- var types = clipboardData.types;
- var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
-
- // Handle files and image content directly in the clipboard.
- var fileFound = false;
- for (var n = 0; n < items.length; n++) {
- if (items[n] && items[n].kind === 'file') {
- var fileBlob = items[n].getAsFile();
- var fileReader = new FileReader();
- // Define events to fire after the file is read into memory.
- fileReader.onload = function() {
- Drupal.fileFieldSources.pasteSubmit(targetElement, filename, this.result);
- };
- fileReader.onerror = function() {
- Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Error reading file from clipboard.'));
- };
- // Read in the file to fire the above events.
- fileReader.readAsDataURL(fileBlob);
- fileFound = true;
- break;
- }
- // Handle files that a copy/pasted as a file reference.
- //if (types[n] && types[n] === 'Files') {
- // TODO: Figure out how to capture copy/paste of entire files from desktop.
- //}
- }
- if (!fileFound) {
- Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
- }
- return false;
- },
-
- /**
- * For browsers that don't support native clipboardData attributes.
- */
- waitForPaste: function(targetElement) {
- if (targetElement.children && targetElement.children.length > 0) {
- var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
- var tagFound = false;
- $(targetElement).find('img[src^="data:image"]').each(function(n, element) {
- Drupal.fileFieldSources.pasteSubmit(targetElement, filename, element.src);
- tagFound = true;
- });
- $(targetElement).html(filename);
- if (!tagFound) {
- Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
- }
- }
- else {
- setTimeout(function() {
- Drupal.fileFieldSources.waitForPaste(targetElement);
- }, 200);
- }
- },
-
- /**
- * Set an error on the paste field temporarily then clear it.
- */
- pasteError: function(domElement, errorMessage) {
- var $description = $(domElement).parents('.filefield-source-clipboard:first').find('.description');
- if (!$description.data('originalDescription')) {
- $description.data('originalDescription', $description.html())
- }
- $description.html(errorMessage);
- var errorTimeout = setTimeout(function() {
- $description.html($description.data('originalDescription'));
- $(this).unbind('click.pasteError');
- }, 3000);
- $(domElement).bind('click.pasteError', function() {
- clearTimeout(errorTimeout);
- $description.html($description.data('originalDescription'));
- $(this).unbind('click.pasteError');
- });
- },
-
- /**
- * After retreiving a clipboard, post the results to the server.
- */
- pasteSubmit: function(targetElement, filename, contents) {
- var $wrapper = $(targetElement).parents('.filefield-source-clipboard');
- $wrapper.find('.filefield-source-clipboard-filename').val(filename);
- $wrapper.find('.filefield-source-clipboard-contents').val(contents);
- $wrapper.find('input.form-submit').trigger('mousedown');
- }
-};
-
-})(jQuery);
\ No newline at end of file
diff --git a/filefield_sources.libraries.yml b/filefield_sources.libraries.yml
new file mode 100644
index 0000000..7a0c7eb
--- /dev/null
+++ b/filefield_sources.libraries.yml
@@ -0,0 +1,9 @@
+drupal.filefield_sources:
+ version: VERSION
+ js:
+ js/filefield_sources.js: {}
+ css:
+ theme:
+ css/filefield_sources.css: {}
+ dependencies:
+ - file/drupal.file
diff --git a/filefield_sources.module b/filefield_sources.module
index 94dcb97..ad138ad 100644
--- a/filefield_sources.module
+++ b/filefield_sources.module
@@ -5,101 +5,129 @@
* Extend FileField to allow files from multiple sources.
*/
-/**
- * Implements hook_menu().
- */
-function filefield_sources_menu() {
- $params = array();
- return filefield_sources_invoke_all('menu', $params);
-}
+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;
-/**
- * Implements hook_element_info().
- */
-function filefield_sources_element_info() {
- $elements = array();
+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';
- $elements['managed_file']['#process'] = array('filefield_sources_field_process');
- $elements['managed_file']['#pre_render'] = array('filefield_sources_field_pre_render');
- $elements['managed_file']['#element_validate'] = array('filefield_sources_field_validate');
- $elements['managed_file']['#file_value_callbacks'] = array('filefield_sources_field_value');
+const FILEFIELD_SOURCE_REFERENCE_STARTS_WITH_AUTOCOMPLETE_TYPE = 'STARTS_WITH';
+const FILEFIELD_SOURCE_REFERENCE_CONTAINS_AUTOCOMPLETE_TYPE = 'CONTAINS';
- return $elements;
+/**
+ * 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() {
- $params = array();
- $theme = filefield_sources_invoke_all('theme', $params);
+ $theme = array();
+
+ $theme['filefield_sources_element'] = array(
+ 'render element' => 'element',
+ 'function' => 'theme_filefield_sources_element',
+ );
$theme['filefield_sources_list'] = array(
- 'arguments' => array('sources' => NULL),
+ 'variables' => array('element' => NULL, 'sources' => NULL),
+ 'function' => 'theme_filefield_sources_list',
);
return $theme;
}
/**
- * Implements hook_filefield_sources_widgets().
+ * Implements hook_field_widget_third_party_settings_form().
*
- * This returns a list of widgets that are compatible with FileField Sources.
+ * Add file field sources settings form to supported field widget forms.
+ *
+ * @see \Drupal\field_ui\FormDisplayOverview
*/
-function filefield_sources_filefield_sources_widgets() {
- return array('file_generic', 'image_image');
+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_form_FORM_ID_alter().
+ * 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_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
- $instance = $form['#instance'];
- if (in_array($instance['widget']['type'], module_invoke_all('filefield_sources_widgets'))) {
- if (!empty($form['instance']['widget']['settings'])) {
- $form['instance']['widget']['settings'] += filefield_sources_form($instance, $form_state);
- }
- else {
- $form['instance']['widget']['settings'] = filefield_sources_form($instance, $form_state);
- }
+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));
}
}
/**
- * A list of settings needed by FileField Sources module on widgets.
+ * Implements hook_field_widget_form_alter().
+ *
+ * Add file field sources widget's settings to element.
*/
-function filefield_sources_field_widget_info_alter(&$info) {
- $settings = array(
- 'filefield_sources' => array(),
- );
- foreach (module_invoke_all('filefield_sources_widgets') as $widget) {
- $params = array('save', $widget);
- $widget_settings = array_merge($settings, filefield_sources_invoke_all('settings', $params));
- if (isset($info[$widget])) {
- $info[$widget]['settings']['filefield_sources'] = $widget_settings;
- }
+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($instance, &$form_state) {
- $settings = $instance['widget']['settings']['filefield_sources'];
+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' => 'fieldset',
+ '#type' => 'details',
'#title' => t('File sources'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
'#weight' => 20,
);
$sources = filefield_sources_list();
- $form['filefield_sources']['filefield_sources'] = array(
+ $form['filefield_sources']['sources'] = array(
'#type' => 'checkboxes',
'#title' => t('Enabled sources'),
'#options' => $sources,
@@ -107,15 +135,7 @@ function filefield_sources_form($instance, &$form_state) {
'#description' => t('Select the available locations from which this widget may select files.'),
);
- // Make sure all includes are loaded for multistep forms.
- $sources_info = filefield_sources_info(FALSE);
- foreach ($sources_info as $source_name => $source) {
- if (isset($source['file'])) {
- _filefield_sources_form_include($source['module'], $source['file'], $form_state);
- }
- }
-
- $params = array('form', $instance);
+ $params = array($plugin);
$form['filefield_sources'] = array_merge($form['filefield_sources'], filefield_sources_invoke_all('settings', $params));
return $form;
@@ -128,28 +148,32 @@ function filefield_sources_form($instance, &$form_state) {
* 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, &$form_state, $form) {
+function filefield_sources_field_process(&$element, FormStateInterface $form_state, &$complete_form) {
static $js_added;
- // If not a recognized field instance, do not process.
- if (!isset($element['#field_name']) || !($instance = field_widget_instance($element, $form_state)) || !isset($instance['widget']['settings']['filefield_sources']['filefield_sources'])) {
+ // Check if we are processing file field sources.
+ if (!isset($element['#filefield_sources_settings'])) {
return $element;
}
+ $settings = $element['#filefield_sources_settings'];
+ $enabled_sources = _filefield_sources_enabled($settings);
+
// Do all processing as needed by each source.
$sources = filefield_sources_info();
- $enabled_sources = _filefield_sources_enabled($instance['widget']['settings']['filefield_sources']);
foreach ($sources as $source_name => $source) {
if (empty($enabled_sources[$source_name])) {
unset($sources[$source_name]);
}
- else {
- if (isset($source['process'])) {
- $function = $source['process'];
- $element = $function($element, $form_state, $form);
- }
- if (isset($source['file'])) {
- _filefield_sources_form_include($source['module'], $source['file'], $form_state);
+ // 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,
+ ));
}
}
}
@@ -169,46 +193,60 @@ function filefield_sources_field_process($element, &$form_state, $form) {
}
}
- // Add basic JS and CSS.
- $path = drupal_get_path('module', 'filefield_sources');
- $element['#attached']['css'][] = $path . '/filefield_sources.css';
- $element['#attached']['js'][] = $path . '/filefield_sources.js';
+ // 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) {
+ foreach (Element::children($element) as $key) {
if (isset($element[$key]['#filefield_sources_hint_text']) && !isset($js_added[$key])) {
$type = str_replace('filefield_', '', $key);
- drupal_add_js(array('fileFieldSources' => array($type => array('hintText' => $element[$key]['#filefield_sources_hint_text']))), 'setting');
+
+ $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
+ // Adjust the Ajax settings so that on upload and remove of any individual
// file, the entire group of file fields is updated together.
- // Copied directly from file_field_widget_process().
- $field = field_widget_field($element, $form_state);
- if ($field['cardinality'] != 1) {
+ // Duplicate of Drupal\file\Plugin\Field\FieldWidget\FileWidget::process().
+ if ($element['#cardinality'] != 1) {
$parents = array_slice($element['#array_parents'], 0, -1);
- $new_path = 'file/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
- $field_element = drupal_array_get_nested_value($form, $parents);
+ $new_path = 'file/ajax';
+ $new_options = array(
+ 'query' => array(
+ 'element_parents' => implode('/', $parents),
+ 'form_build_id' => $complete_form['form_build_id']['#value'],
+ ),
+ );
+ $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) {
+ foreach (Element::children($element) as $key) {
+ foreach (Element::children($element[$key]) as $subkey) {
if (isset($element[$key][$subkey]['#ajax'])) {
$element[$key][$subkey]['#ajax']['path'] = $new_path;
+ $element[$key][$subkey]['#ajax']['options'] = $new_options;
$element[$key][$subkey]['#ajax']['wrapper'] = $new_wrapper;
- $element[$key][$subkey]['#limit_validation_errors'] = array($parents);
+ $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['fid']['#value'])) {
+ if (empty($element['fids']['#value'])) {
if (count($enabled_sources) > 1) {
$element['filefield_sources_list'] = array(
- '#type' => 'markup',
- '#markup' => theme('filefield_sources_list', array('element' => $element, 'sources' => $sources)),
+ '#theme' => 'filefield_sources_list',
+ '#element' => $element,
+ '#sources' => $sources,
'#weight' => -20,
);
}
@@ -222,8 +260,8 @@ function filefield_sources_field_process($element, &$form_state, $form) {
*/
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']['fid'])) {
- foreach (element_children($element) as $key) {
+ if (!empty($element['#value']['fids'])) {
+ foreach (Element::children($element) as $key) {
if (!empty($element[$key]['#filefield_source'])) {
$element[$key]['#access'] = FALSE;
}
@@ -235,69 +273,101 @@ function filefield_sources_field_pre_render($element) {
/**
* An #element_validate function to run source validations.
*/
-function filefield_sources_field_validate($element, &$form_state, $form) {
+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['validate'])) {
- $function = $source['validate'];
- $function($element, $form_state, $form);
+ 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,
+ ));
}
}
}
/**
* A #submit handler added to all FileField Source buttons.
+ *
+ * Duplicate 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, &$form_state) {
-
- $parents = array_slice($form_state['triggering_element']['#array_parents'], 0, -3);
- $element = drupal_array_get_nested_value($form, $parents);
+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'];
- $langcode = $element['#language'];
+ $parents = $element['#field_parents'];
- // Get exisitng file values.
- // File Field items are stored in the field state after ajax reloads starting
- // from Drupal 7.8. We try to support all releases by merging the items.
- $field_state = field_form_get_state($element['#field_parents'], $field_name, $langcode, $form_state);
- $field_values = drupal_array_get_nested_value($form_state['values'], $parents);
-
- if (isset($field_values) && isset($field_state['items'])) {
- $field_values += $field_state['items'];
+ $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]);
+ }
}
- elseif (isset($field_state['items'])) {
- $field_values = $field_state['items'];
+
+ // 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;
+ }
}
- if (isset($field_values)) {
- // Update sort order according to weight. Note that this is always stored in
- // form state. Sort does not work using regular upload, but that is a core
- // bug.
- usort($field_values, '_field_sort_items_helper');
+ // Re-index deltas after removing empty items.
+ $submitted_values = array_values($new_values);
- // Update form_state values.
- drupal_array_set_nested_value($form_state['values'], $parents, $field_values);
+ // Update form_state values.
+ NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -3), $submitted_values);
- // Update items.
- $field_state['items'] = $field_values;
- field_form_set_state($element['#field_parents'], $field_name, $langcode, $form_state, $field_state);
- }
+ // 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);
- // Clear out input as it will need to be rebuildt.
- drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL);
- $form_state['rebuild'] = TRUE;
+ // 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, &$item, &$form_state) {
+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['value'])) {
- $function = $source['value'];
- $function($element, $item);
+ if (isset($source['class'])) {
+ $callback = array($source['class'], 'value');
+ if (is_callable($callback)) {
+ call_user_func_array($callback, array(&$element, &$input, $form_state));
+ }
}
}
}
@@ -307,10 +377,14 @@ function filefield_sources_field_value($element, &$item, &$form_state) {
*/
function filefield_sources_invoke_all($method, &$params) {
$return = array();
- foreach (filefield_sources_includes() as $source) {
- $function = 'filefield_source_' . $source . '_' . $method;
- if (function_exists($function)) {
- $result = call_user_func_array($function, $params);
+ 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);
}
@@ -326,35 +400,17 @@ function filefield_sources_invoke_all($method, &$params) {
* Load hook_filefield_sources_info() data from all modules.
*/
function filefield_sources_info($include_default = TRUE) {
- // Cache the expensive part.
- $cache = &drupal_static(__FUNCTION__, array());
- if (empty($cache)) {
- $cache['upload'] = array(
+ $info = \Drupal::service('filefield_sources')->getDefinitions();
+ if ($include_default) {
+ $info['upload'] = array(
'name' => t('Upload (default)'),
'label' => t('Upload'),
'description' => t('Upload a file from your computer.'),
'weight' => -10,
);
-
- // Add the providing module name to each source.
- foreach (module_implements('filefield_sources_info') as $module) {
- $function = $module . '_filefield_sources_info';
- $additions = $function();
- foreach ($additions as $source_name => $source_info) {
- $additions[$source_name]['module'] = $module;
- }
- $cache += $additions;
- }
-
- drupal_alter('filefield_sources_info', $cache);
- uasort($cache, '_filefield_sources_sort');
}
- // Remove the upload option from the returned value if needed.
- $info = $cache;
- if (!$include_default) {
- unset($info['upload']);
- }
+ uasort($info, '_filefield_sources_sort');
return $info;
}
@@ -374,81 +430,25 @@ function filefield_sources_list($include_default = TRUE) {
}
/**
- * Implements hook_filefield_sources_info().
- */
-function filefield_sources_filefield_sources_info() {
- $params = array();
- return filefield_sources_invoke_all('info', $params);
-}
-
-/**
- * Load all the potential sources.
- */
-function filefield_sources_includes($include = TRUE, $enabled_only = TRUE) {
- if ($enabled_only) {
- $enabled_includes = variable_get('filefield_sources', filefield_sources_includes(FALSE, FALSE));
- }
-
- $includes = array();
- $directory = drupal_get_path('module', 'filefield_sources') . '/sources';
- foreach (file_scan_directory($directory, '/\.inc$/') as $file) {
- if (!$enabled_only || in_array($file->name, $enabled_includes)) {
- $includes[] = $file->name;
- if ($include) {
- include_once(DRUPAL_ROOT . '/' . $file->uri);
- }
- }
- }
- return $includes;
-}
-
-/**
- * Check the current user's access to a file through hook_file_download().
- *
- * @param $uri
- * A file URI as loaded from the database.
- * @return
- * Boolean TRUE if the user has access, FALSE otherwise.
- *
- * @see file_download()
- * @see hook_file_download().
- */
-function filefield_sources_file_access($uri) {
- $headers = array();
- foreach (module_implements('file_download') as $module) {
- $function = $module . '_file_download';
- $result = $function($uri);
- if ($result == -1) {
- // Throw away the headers received so far.
- $headers = array();
- break;
- }
- if (isset($result) && is_array($result)) {
- $headers = array_merge($headers, $result);
- }
- }
- return !empty($headers);
-}
-
-/**
* 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().
+ * @see file_save_upload()
*/
function filefield_sources_save_file($filepath, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
- global $user;
+ $user = \Drupal::currentUser();
// Begin building file object.
- $file = new stdClass();
- $file->uid = $user->uid;
- $file->status = 0;
- $file->filename = trim(basename($filepath), '.');
- $file->uri = $filepath;
- $file->filemime = file_get_mimetype($file->filename);
- $file->filesize = filesize($filepath);
+ $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'])) {
@@ -474,22 +474,22 @@ function filefield_sources_save_file($filepath, $validators = array(), $destinat
if (!empty($extensions)) {
// Munge the filename to protect against possible malicious extension hiding
// within an unknown file type (ie: filename.html.foo).
- $file->filename = file_munge_filename($file->filename, $extensions);
+ $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 (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
- $file->filemime = 'text/plain';
- $file->uri .= '.txt';
- $file->filename .= '.txt';
+ 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->filename)));
+ drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename())));
}
}
@@ -513,11 +513,11 @@ function filefield_sources_save_file($filepath, $validators = array(), $destinat
// Ensure the destination is writable.
file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
- $file->destination = file_destination($destination . $file->filename, $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->filename, '%directory' => $destination)), 'error');
+ 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;
}
@@ -529,7 +529,7 @@ function filefield_sources_save_file($filepath, $validators = array(), $destinat
// Check for errors.
if (!empty($errors)) {
- $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
+ $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));
}
@@ -543,44 +543,44 @@ function filefield_sources_save_file($filepath, $validators = array(), $destinat
// Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
// directory. This overcomes open_basedir restrictions for future file
// operations.
- $file->uri = $file->destination;
- if (!file_unmanaged_copy($filepath, $file->uri, $replace)) {
+ $file->setFileUri($file->destination);
+ if (!file_unmanaged_copy($filepath, $file->getFileUri(), $replace)) {
drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
- watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri));
+ \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->uri);
+ 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->uri));
+ $existing_files = file_load_multiple(array(), array('uri' => $file->getFileUri()));
if (count($existing_files)) {
$existing = reset($existing_files);
- $file->fid = $existing->fid;
+ $file->setOriginalId($existing->id());
}
}
// If we made it this far it's safe to record this file in the database.
- return file_save($file);
+ $file->save();
+ return $file;
}
/**
* Clean up the file name, munging extensions and transliterating.
*
- * @param $filepath
+ * @param string $filepath
* A string containing a file name or full path. Only the file name will
* actually be modified.
- * @return
+ *
+ * @return string
* A file path with a cleaned-up file name.
*/
function filefield_sources_clean_filename($filepath, $extensions) {
- global $user;
-
$filename = basename($filepath);
- if (module_exists('transliteration')) {
+ if (\Drupal::moduleHandler()->moduleExists('transliteration')) {
module_load_include('inc', 'transliteration');
$langcode = NULL;
@@ -603,6 +603,27 @@ function filefield_sources_clean_filename($filepath, $extensions) {
}
/**
+ * 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) {
@@ -620,13 +641,13 @@ function theme_filefield_sources_list($variables) {
/**
* Validate a file based on the $element['#upload_validators'] property.
*/
-function filefield_sources_element_validate($element, $file) {
+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->uri)) {
+ if (!file_exists($file->getFileUri())) {
$errors[] = t('The file does not exist.');
}
// Call the validation functions.
@@ -649,7 +670,7 @@ function filefield_sources_element_validate($element, $file) {
else {
$message .= ' ' . array_pop($errors);
}
- form_error($element, $message);
+ $form_state->setError($element, $message);
return 0;
}
@@ -671,44 +692,54 @@ function filefield_sources_element_validation_help($validators) {
}
/**
- * Menu access callback; Checks user access to edit a file field.
- */
-function _filefield_sources_field_access($entity_type, $bundle_name, $field_name) {
- $field = field_info_field($field_name);
- return field_access('edit', $field, $entity_type);
-}
-
-/**
* 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' => '');
+ $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']));
}
/**
- * Ensure that a source include file is loaded into $form_state.
- */
-function _filefield_sources_form_include($module, $filepath, &$form_state) {
- $last_dot = strrpos($filepath, '.');
- $path = substr($filepath, 0, $last_dot);
- $extension = substr($filepath, $last_dot + 1);
- form_load_include($form_state, $extension, $module, $path);
-}
-
-/**
- * Helper to return enabled sources for a field
+ * 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['filefield_sources']['upload'])) {
- $settings['filefield_sources']['upload'] = 'upload';
+ if (!isset($settings['sources']['upload'])) {
+ $settings['sources']['upload'] = 'upload';
}
- $enabled = array_keys(array_filter($settings['filefield_sources']));
- return drupal_map_assoc($enabled);
+ $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)));
+ }
}
diff --git a/filefield_sources.routing.yml b/filefield_sources.routing.yml
new file mode 100644
index 0000000..002d37c
--- /dev/null
+++ b/filefield_sources.routing.yml
@@ -0,0 +1,2 @@
+route_callbacks:
+ - '\Drupal\filefield_sources\Routing\FilefieldSourcesRoutes::routes'
diff --git a/filefield_sources.services.yml b/filefield_sources.services.yml
new file mode 100644
index 0000000..289a9fc
--- /dev/null
+++ b/filefield_sources.services.yml
@@ -0,0 +1,10 @@
+services:
+ plugin.manager.filefield_sources:
+ class: Drupal\filefield_sources\FilefieldSourceManager
+ parent: default_plugin_manager
+ filefield_sources:
+ alias: plugin.manager.filefield_sources
+ access_check.filefield_sources.field:
+ class: Drupal\filefield_sources\Access\FieldAccessCheck
+ tags:
+ - { name: access_check, applies_to: _access_filefield_sources_field }
diff --git a/js/filefield_sources.js b/js/filefield_sources.js
new file mode 100644
index 0000000..8d367b1
--- /dev/null
+++ b/js/filefield_sources.js
@@ -0,0 +1,250 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the filefield_sources module.
+ */
+
+(function ($, Drupal) {
+
+"use strict";
+
+// Behavior to add source options to configured fields.
+Drupal.behaviors.fileFieldSources = {};
+Drupal.behaviors.fileFieldSources.attach = function(context, settings) {
+ $('div.filefield-sources-list:not(.filefield-sources-processed)', context).each(function() {
+ $(this).addClass('filefield-sources-processed');
+ var $fileFieldElement = $(this).parents('div.form-managed-file:first');
+ $(this).find('a').click(function() {
+ // Remove the active class.
+ $(this).parents('div.filefield-sources-list').find('a.active').removeClass('active');
+
+ // Find the unique FileField Source class name.
+ var fileFieldSourceClass = this.className.match(/filefield-source-[0-9a-z]+/i)[0];
+
+ // The default upload element is a special case.
+ if ($(this).is('.filefield-source-upload')) {
+ $fileFieldElement.find('div.filefield-sources-list').siblings('input.form-file, input.form-submit').css('display', '');
+ $fileFieldElement.find('div.filefield-source').css('display', 'none');
+ }
+ else {
+ $fileFieldElement.find('div.filefield-sources-list').siblings('input.form-file, input.form-submit').css('display', 'none');
+ $fileFieldElement.find('div.filefield-source').not('div.' + fileFieldSourceClass).css('display', 'none');
+ $fileFieldElement.find('div.' + fileFieldSourceClass).css('display', '');
+ }
+
+ // Add the active class.
+ $(this).addClass('active');
+ Drupal.fileFieldSources.updateHintText($fileFieldElement.get(0));
+ }).first().triggerHandler('click');
+
+ // Clipboard support.
+ $fileFieldElement.find('.filefield-source-clipboard-capture')
+ .bind('paste', Drupal.fileFieldSources.pasteEvent)
+ .bind('focus', Drupal.fileFieldSources.pasteFocus)
+ .bind('blur', Drupal.fileFieldSources.pasteBlur);
+ });
+
+ if (context === document) {
+ $('form').submit(function() {
+ Drupal.fileFieldSources.removeHintText();
+ });
+ }
+};
+
+// Helper functions used by FileField Sources.
+Drupal.fileFieldSources = {
+ /**
+ * Update the hint text when clicking between source types.
+ */
+ updateHintText: function(fileFieldElement) {
+ // Add default value hint text to text fields.
+ $(fileFieldElement).find('div.filefield-source').each(function() {
+ var matches = this.className.match(/filefield-source-([a-z]+)/);
+ var sourceType = matches[1];
+ var textfield = $(this).find('input.form-text:first').get(0);
+ var defaultText = (drupalSettings.fileFieldSources && drupalSettings.fileFieldSources[sourceType]) ? drupalSettings.fileFieldSources[sourceType].hintText : '';
+
+ // If the field doesn't exist, just return.
+ if (!textfield) {
+ return;
+ }
+
+ // If this field is not shown, remove its value and be done.
+ if (!$(this).is(':visible') && textfield.value == defaultText) {
+ textfield.value = '';
+ return;
+ }
+
+ // Set a default value:
+ if (textfield.value == '') {
+ textfield.value = defaultText;
+ }
+
+ // Set a default class.
+ if (textfield.value == defaultText) {
+ $(textfield).addClass('hint');
+ }
+
+ $(textfield).focus(hideHintText);
+ $(textfield).blur(showHintText);
+
+ function showHintText() {
+ if (this.value == '') {
+ this.value = defaultText;
+ $(this).addClass('hint');
+ }
+ }
+
+ function hideHintText() {
+ if (this.value == defaultText) {
+ this.value = '';
+ $(this).removeClass('hint');
+ }
+ }
+ });
+ },
+
+ /**
+ * Delete all hint text from a form before submit.
+ */
+ removeHintText: function() {
+ $('div.filefield-source input.hint').val('').removeClass('hint');
+ },
+
+ /**
+ * Clean up the default value on focus.
+ */
+ pasteFocus: function(e) {
+ // Set default text.
+ if (!this.defaultText) {
+ this.defaultText = this.innerHTML;
+ this.innerHTML = '';
+ }
+ // Remove non-text nodes.
+ $(this).children().remove();
+ },
+
+ /**
+ * Restore default value on blur.
+ */
+ pasteBlur: function(e) {
+ if (this.defaultText && !this.innerHTML) {
+ this.innerHTML = this.defaultText;
+ }
+ },
+
+ pasteEvent: function(e) {
+ var clipboardData = null;
+ var targetElement = this;
+ var userAgent = navigator.userAgent.toLowerCase();
+
+ // Chrome.
+ if (window.event && window.event.clipboardData && window.event.clipboardData.items) {
+ clipboardData = window.event.clipboardData;
+ }
+ // All browsers in the future (hopefully).
+ else if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.items) {
+ clipboardData = e.originalEvent.clipboardData;
+ }
+ // Firefox with content editable pastes as img tag with data href.
+ else if (userAgent.match(/mozilla/) && !userAgent.match(/webkit/)) {
+ Drupal.fileFieldSources.waitForPaste(targetElement);
+ return true;
+ }
+ else {
+ Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Paste from clipboard not supported in this browser.'));
+ return false;
+ }
+
+ var items = clipboardData.items;
+ var types = clipboardData.types;
+ var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
+
+ // Handle files and image content directly in the clipboard.
+ var fileFound = false;
+ for (var n = 0; n < items.length; n++) {
+ if (items[n] && items[n].kind === 'file') {
+ var fileBlob = items[n].getAsFile();
+ var fileReader = new FileReader();
+ // Define events to fire after the file is read into memory.
+ fileReader.onload = function() {
+ Drupal.fileFieldSources.pasteSubmit(targetElement, filename, this.result);
+ };
+ fileReader.onerror = function() {
+ Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Error reading file from clipboard.'));
+ };
+ // Read in the file to fire the above events.
+ fileReader.readAsDataURL(fileBlob);
+ fileFound = true;
+ break;
+ }
+ // Handle files that a copy/pasted as a file reference.
+ /* if (types[n] && types[n] === 'Files') {
+ TODO: Figure out how to capture copy/paste of entire files from desktop.
+ }*/
+ }
+ if (!fileFound) {
+ Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
+ }
+ return false;
+ },
+
+ /**
+ * For browsers that don't support native clipboardData attributes.
+ */
+ waitForPaste: function(targetElement) {
+ if (targetElement.children && targetElement.children.length > 0) {
+ var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
+ var tagFound = false;
+ $(targetElement).find('img[src^="data:image"]').each(function(n, element) {
+ Drupal.fileFieldSources.pasteSubmit(targetElement, filename, element.src);
+ tagFound = true;
+ });
+ $(targetElement).html(filename);
+ if (!tagFound) {
+ Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
+ }
+ }
+ else {
+ setTimeout(function() {
+ Drupal.fileFieldSources.waitForPaste(targetElement);
+ }, 200);
+ }
+ },
+
+ /**
+ * Set an error on the paste field temporarily then clear it.
+ */
+ pasteError: function(domElement, errorMessage) {
+ var $description = $(domElement).parents('.filefield-source-clipboard:first').find('.description');
+ if (!$description.data('originalDescription')) {
+ $description.data('originalDescription', $description.html())
+ }
+ $description.html(errorMessage);
+ var errorTimeout = setTimeout(function() {
+ $description.html($description.data('originalDescription'));
+ $(this).unbind('click.pasteError');
+ }, 3000);
+ $(domElement).bind('click.pasteError', function() {
+ clearTimeout(errorTimeout);
+ $description.html($description.data('originalDescription'));
+ $(this).unbind('click.pasteError');
+ });
+ },
+
+ /**
+ * After retreiving a clipboard, post the results to the server.
+ */
+ pasteSubmit: function(targetElement, filename, contents) {
+ var $wrapper = $(targetElement).parents('.filefield-source-clipboard');
+ $wrapper.find('.filefield-source-clipboard-filename').val(filename);
+ $wrapper.find('.filefield-source-clipboard-contents').val(contents);
+ $wrapper.find('input.form-submit').trigger('mousedown');
+ }
+};
+
+// Override triggerUploadButton method from file.js.
+Drupal.file.triggerUploadButton = function (event) {
+ $(event.target).closest('.form-managed-file').find('.form-submit.upload-button').trigger('mousedown');
+}
+
+})(jQuery, Drupal);
diff --git a/sources/attach.inc b/sources/attach.inc
deleted file mode 100644
index a60f18a..0000000
--- a/sources/attach.inc
+++ /dev/null
@@ -1,303 +0,0 @@
- t('File attach from server directory'),
- 'label' => t('File attach'),
- 'description' => t('Select a file from a directory on the server.'),
- 'process' => 'filefield_source_attach_process',
- 'value' => 'filefield_source_attach_value',
- 'weight' => 3,
- 'file' => 'includes/attach.inc',
- );
- return $source;
-}
-
-/**
- * Implements hook_theme().
- */
-function filefield_source_attach_theme() {
- return array(
- 'filefield_source_attach_element' => array(
- 'render element' => 'element',
- 'file' => 'sources/attach.inc',
- ),
- );
-}
-
-/**
- * Implements hook_filefield_source_settings().
- */
-function filefield_source_attach_settings($op, $instance) {
- $return = array();
-
- if ($op == 'form') {
- $settings = $instance['widget']['settings']['filefield_sources'];
-
- $return['source_attach'] = array(
- '#title' => t('File attach settings'),
- '#type' => 'fieldset',
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- '#description' => t('File attach allows for selecting a file from a directory on the server, commonly used in combination with FTP.') . ' ' . t('This file source will ignore file size checking when used.') . ' ',
- '#element_validate' => array('_filefield_source_attach_file_path_validate'),
- '#weight' => 3,
- );
- $return['source_attach']['path'] = array(
- '#type' => 'textfield',
- '#title' => t('File attach path'),
- '#default_value' => $settings['source_attach']['path'],
- '#size' => 60,
- '#maxlength' => 128,
- '#description' => t('The directory within the File attach location that will contain attachable files.'),
- );
- if (module_exists('token')) {
- $return['source_attach']['tokens'] = array(
- '#theme' => 'token_tree',
- '#token_types' => array('user'),
- );
- }
- $return['source_attach']['absolute'] = array(
- '#type' => 'radios',
- '#title' => t('File attach location'),
- '#options' => array(
- 0 => t('Within the files directory'),
- 1 => t('Absolute server path'),
- ),
- '#default_value' => $settings['source_attach']['absolute'],
- '#description' => t('The File attach path may be with the files directory (%file_directory) or from the root of your server. If an absolute path is used and it does not start with a "/" your path will be relative to your site directory: %realpath.', array('%file_directory' => drupal_realpath(file_default_scheme() . '://'), '%realpath' => realpath('./'))),
- );
- $return['source_attach']['attach_mode'] = array(
- '#type' => 'radios',
- '#title' => t('Attach method'),
- '#options' => array(
- 'move' => t('Move the file directly to the final location'),
- 'copy' => t('Leave a copy of the file in the attach directory'),
- ),
- '#default_value' => isset($settings['source_attach']['attach_mode']) ? $settings['source_attach']['attach_mode'] : 'move',
- );
- }
- elseif ($op == 'save') {
- $return['source_attach']['path'] = 'file_attach';
- $return['source_attach']['absolute'] = 0;
- $return['source_attach']['attach_mode'] = 'move';
- }
-
- return $return;
-}
-
-function _filefield_source_attach_file_path_validate($element, &$form_state) {
- // Only validate if this source is enabled.
- if (!$form_state['values']['instance']['widget']['settings']['filefield_sources']['filefield_sources']['attach']) {
- return;
- }
-
- // Strip slashes from the end of the file path.
- $filepath = rtrim($element['path']['#value'], '\\/');
- form_set_value($element['path'], $filepath, $form_state);
- $filepath = _filefield_source_attach_directory($form_state['values']['instance']);
-
- // Check that the directory exists and is writable.
- if (!file_prepare_directory($filepath, FILE_CREATE_DIRECTORY)) {
- form_error($element['path'], t('Specified file attach path must exist or be writable.'));
- }
-}
-
-/**
- * A #process callback to extend the filefield_widget element type.
- */
-function filefield_source_attach_process($element, &$form_state, $form) {
- $instance = field_widget_instance($element, $form_state);
- $settings = $instance['widget']['settings']['filefield_sources']['source_attach'];
-
- $element['filefield_attach'] = array(
- '#weight' => 100.5,
- '#theme' => 'filefield_source_attach_element',
- '#filefield_source' => TRUE, // Required for proper theming.
- );
-
- $path = _filefield_source_attach_directory($instance);
- $options = _filefield_source_attach_options($path);
-
- // If we have built this element before, append the list of options that we
- // had previously. This allows files to be deleted after copying them and
- // still be considered a valid option during the validation and submit.
- if (!isset($form_state['triggering_element']) && isset($form_state['filefield_sources'][$instance['field_name']]['attach_options'])) {
- $options = $options + $form_state['filefield_sources'][$instance['field_name']]['attach_options'];
- }
- // On initial form build and rebuilds after processing input, save the
- // original list of options so they can be restored in the line above.
- else {
- $form_state['filefield_sources'][$instance['field_name']]['attach_options'] = $options;
- }
-
- $description = t('This method may be used to attach files that exceed the file size limit. Files may be attached from the %directory directory on the server, usually uploaded through FTP.', array('%directory' => realpath($path)));
-
- // Error messages.
- if ($options === FALSE || empty($settings['path'])) {
- $attach_message = t('A file attach directory could not be located.');
- $attach_description = t('Please check your settings for the %field field.', array('%field' => $instance['label']));
- }
- elseif (!count($options)) {
- $attach_message = t('There currently are no files to attach.');
- $attach_description = $description;
- }
-
- if (isset($attach_message)) {
- $element['filefield_attach']['attach_message'] = array(
- '#markup' => $attach_message,
- );
- $element['filefield_attach']['#description'] = $attach_description;
- }
- else {
- $validators = $element['#upload_validators'];
- if (isset($validators['file_validate_size'])) {
- unset($validators['file_validate_size']);
- }
- $description .= ' ' . filefield_sources_element_validation_help($validators);
- $element['filefield_attach']['filename'] = array(
- '#type' => 'select',
- '#options' => $options,
- );
- $element['filefield_attach']['#description'] = $description;
- }
-
- $element['filefield_attach']['attach'] = array(
- '#name' => implode('_', $element['#array_parents']) . '_attach',
- '#type' => 'submit',
- '#value' => t('Attach'),
- '#validate' => array(),
- '#submit' => array('filefield_sources_field_submit'),
- '#limit_validation_errors' => array($element['#parents']),
- '#ajax' => array(
- 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
- 'method' => 'replace',
- 'effect' => 'fade',
- ),
- );
-
- return $element;
-}
-
-function _filefield_source_attach_options($path) {
- if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
- drupal_set_message(t('Specified file attach path must exist or be writable.'), 'error');
- return FALSE;
- }
-
- $options = array();
- $file_attach = file_scan_directory($path, '/.*/', array('key' => 'filename'), 0);
-
- if (count($file_attach)) {
- $options = array('' => t('-- Select file --'));
- foreach ($file_attach as $filename => $fileinfo) {
- $filename = basename($filename);
- $options[$fileinfo->uri] = str_replace($path . '/', '', $fileinfo->uri);
- }
- }
-
- natcasesort($options);
- return $options;
-}
-
-/**
- * A #filefield_value_callback function.
- */
-function filefield_source_attach_value($element, &$item) {
- if (!empty($item['filefield_attach']['filename'])) {
- $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
- $filepath = $item['filefield_attach']['filename'];
-
- // Check that the destination is writable.
- $directory = $element['#upload_location'];
- $mode = variable_get('file_chmod_directory', 0775);
-
- // This first chmod check is for other systems such as S3, which don't work
- // with file_prepare_directory().
- if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
- watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $filepath, '%destination' => drupal_realpath($directory)));
- drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $filepath)), 'error');
- return;
- }
-
- // Clean up the file name extensions and transliterate.
- $original_filepath = $filepath;
- $new_filepath = filefield_sources_clean_filename($filepath, $instance['settings']['file_extensions']);
- rename($filepath, $new_filepath);
- $filepath = $new_filepath;
-
- // Run all the normal validations, minus file size restrictions.
- $validators = $element['#upload_validators'];
- if (isset($validators['file_validate_size'])) {
- unset($validators['file_validate_size']);
- }
-
- // Save the file to the new location.
- if ($file = filefield_sources_save_file($filepath, $validators, $directory)) {
- $item = array_merge($item, (array) $file);
-
- // Delete the original file if "moving" the file instead of copying.
- if ($instance['widget']['settings']['filefield_sources']['source_attach']['attach_mode'] !== 'copy') {
- @unlink($filepath);
- }
- }
-
- // Restore the original file name if the file still exists.
- if (file_exists($filepath) && $filepath != $original_filepath) {
- rename($filepath, $original_filepath);
- }
-
- $item['filefield_attach']['filename'] = '';
- }
-}
-
-/**
- * Theme the output of the autocomplete field.
- */
-function theme_filefield_source_attach_element($variables) {
- $element = $variables['element'];
-
- if (isset($element['attach_message'])) {
- $output = $element['attach_message']['#markup'];
- }
- else {
- $select = '';
- $size = !empty($element['filename']['#size']) ? ' size="' . $element['filename']['#size'] . '"' : '';
- _form_set_class($element['filename'], array('form-select'));
- $multiple = !empty($element['#multiple']);
- $output = ''. form_select_options($element['filename']) .' ';
- }
- $output .= drupal_render($element['attach']);
- $element['#children'] = $output;
- return '
' . theme('form_element', array('element' => $element)) . '
';
-}
-
-function _filefield_source_attach_directory($instance, $account = NULL) {
- $field = field_info_field($instance['field_name']);
- $account = isset($account) ? $account : $GLOBALS['user'];
- $path = $instance['widget']['settings']['filefield_sources']['source_attach']['path'];
- $absolute = !empty($instance['widget']['settings']['filefield_sources']['source_attach']['absolute']);
-
- // Replace user level tokens.
- // Node level tokens require a lot of complexity like temporary storage
- // locations when values don't exist. See the filefield_paths module.
- if (module_exists('token')) {
- $path = token_replace($path, array('user' => $account));
- }
-
- return $absolute ? $path : file_default_scheme() . '://' . $path;
-}
diff --git a/sources/clipboard.inc b/sources/clipboard.inc
deleted file mode 100644
index f6528fc..0000000
--- a/sources/clipboard.inc
+++ /dev/null
@@ -1,187 +0,0 @@
- t('Paste from clipboard (limited browser support )'),
- 'label' => t('Clipboard'),
- 'description' => t('Allow users to paste a file directly from the clipboard.'),
- 'process' => 'filefield_source_clipboard_process',
- 'value' => 'filefield_source_clipboard_value',
- 'weight' => 1,
- 'file' => 'includes/clipboard.inc',
- );
- return $source;
-}
-
-
-/**
- * Implements hook_menu().
- */
-function filefield_source_clipboard_menu() {
- $items = array();
- $items['file/clipboard/%/%/%'] = array(
- 'page callback' => 'filefield_source_clipboard_page',
- 'page arguments' => array(2, 3, 4),
- 'access callback' => '_filefield_sources_field_access',
- 'access arguments' => array(2, 3, 4),
- 'file' => 'sources/clipboard.inc',
- 'type' => MENU_CALLBACK,
- );
- return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function filefield_source_clipboard_theme() {
- return array(
- 'filefield_source_clipboard_element' => array(
- 'render element' => 'element',
- 'file' => 'sources/clipboard.inc',
- ),
- );
-}
-
-/**
- * A #process callback to extend the filefield_widget element type.
- */
-function filefield_source_clipboard_process($element, &$form_state, $form) {
- // If settings are needed later:
- //$instance = field_widget_instance($element, $form_state);
- //$settings = $instance['widget']['settings']['filefield_sources']['source_clipboard'];
-
- $element['filefield_clipboard'] = array(
- '#weight' => 100.5,
- '#theme' => 'filefield_source_clipboard_element',
- '#filefield_source' => TRUE, // Required for proper theming.
- '#filefield_sources_hint_text' => t('Enter filename then paste.'),
- '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
- );
-
- $element['filefield_clipboard']['filename'] = array(
- '#type' => 'hidden',
- '#attributes' => array('class' => array('filefield-source-clipboard-filename')),
- );
- $element['filefield_clipboard']['contents'] = array(
- '#type' => 'hidden',
- '#attributes' => array('class' => array('filefield-source-clipboard-contents')),
- );
- $element['filefield_clipboard']['upload'] = array(
- '#type' => 'submit',
- '#value' => t('Upload'),
- '#ajax' => array(
- 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
- 'effect' => 'fade',
- 'progress' => array(
- 'type' => 'throbber',
- 'message' => t('Transfering file...'),
- ),
- ),
- '#validate' => array(),
- '#submit' => array('filefield_sources_field_submit'),
- '#limit_validation_errors' => array($element['#parents']),
- '#attributes' => array('style' => 'display: none;'),
- );
-
- return $element;
-}
-
-/**
- * A #filefield_value_callback function.
- */
-function filefield_source_clipboard_value(&$element, &$item) {
- if (isset($item['filefield_clipboard']['contents']) && strlen($item['filefield_clipboard']['contents']) > 0) {
- // Check that the destination is writable.
- $temporary_directory = 'temporary://';
- if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
- watchdog('file', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
- drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
- return;
- }
- // Check that the destination is writable.
- $directory = $element['#upload_location'];
- $mode = variable_get('file_chmod_directory', 0775);
-
- // This first chmod check is for other systems such as S3, which don't work
- // with file_prepare_directory().
- if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
- watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
- drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
- return;
- }
-
- // Split the file information in mimetype and base64 encoded binary.
- $base64_data = $item['filefield_clipboard']['contents'];
- $comma_position = strpos($base64_data, ',');
- $semicolon_position = strpos($base64_data, ';');
- $file_contents = base64_decode(substr($base64_data, $comma_position + 1));
- $mimetype = substr($base64_data, 5, $semicolon_position - 5);
-
- include_once('./includes/file.mimetypes.inc');
- $mime_mapping = file_mimetype_mapping();
- $mime_key = array_search($mimetype, $mime_mapping['mimetypes']);
- $extension = array_search($mime_key, $mime_mapping['extensions']);
-
- $filename = trim($item['filefield_clipboard']['filename']);
- $filename = preg_replace('/\.[a-z0-9]{3,4}$/', '', $filename);
- $filename = (empty($filename) ? 'paste_' . REQUEST_TIME : $filename). '.' . $extension;
- $filepath = file_create_filename($filename, $temporary_directory);
-
- $copy_success = FALSE;
- if ($fp = @fopen($filepath, 'w')) {
- fwrite($fp, $file_contents);
- fclose($fp);
- $copy_success = TRUE;
- }
-
- if ($copy_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
- $item = array_merge($item, (array) $file);
- }
-
- // Remove the temporary file generated from paste.
- @unlink($filepath);
- }
-}
-
-/**
- * Handles the uploading of a file through a POST request.
- */
-function filefield_source_clipboard_page($entity_type, $bundle_name, $field_name) {
- global $conf;
-
- // Check access.
- if (!$instance = field_info_instance($entity_type, $field_name, $bundle_name)) {
- return drupal_access_denied();
- }
- $field = field_info_field($field_name);
-
- module_load_include('inc', 'imce', 'inc/imce.page');
- return imce($field['settings']['uri_scheme']);
-}
-
-/**
- * Theme the output of the clipboard field.
- */
-function theme_filefield_source_clipboard_element($variables) {
- $element = $variables['element'];
-
- $capture = 'example_filename.png
';
- $element['#field_suffix'] = drupal_render($element['upload']) . ' ' . t('ctrl + v') . ' ';
- $element['#description'] = t('Enter a file name and paste an image from the clipboard. This feature only works in limited browsers .');
- $element['#children'] = $capture . drupal_render_children($element);
- return '' . theme('form_element', array('element' => $element)) . '
';
-}
\ No newline at end of file
diff --git a/sources/imce.inc b/sources/imce.inc
deleted file mode 100644
index 8634d65..0000000
--- a/sources/imce.inc
+++ /dev/null
@@ -1,319 +0,0 @@
- t('IMCE file browser'),
- 'label' => t('File browser'),
- 'description' => t('Select a file to use from a file browser.'),
- 'process' => 'filefield_source_imce_process',
- 'value' => 'filefield_source_imce_value',
- 'weight' => -1,
- 'file' => 'includes/imce.inc',
- );
- return $source;
-}
-
-/**
- * Implements hook_menu().
- */
-function filefield_source_imce_menu() {
- $items = array();
- $items['file/imce/%/%/%'] = array(
- 'page callback' => 'filefield_source_imce_page',
- 'page arguments' => array(2, 3, 4),
- 'access callback' => '_filefield_sources_field_access',
- 'access arguments' => array(2, 3, 4),
- 'file' => 'sources/imce.inc',
- 'type' => MENU_CALLBACK,
- );
- return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function filefield_source_imce_theme() {
- return array(
- 'filefield_source_imce_element' => array(
- 'render element' => 'element',
- 'file' => 'sources/imce.inc',
- ),
- );
-}
-
-/**
- * Implements hook_filefield_source_settings().
- */
-function filefield_source_imce_settings($op, $instance) {
- $return = array();
-
- if ($op == 'form') {
- $settings = $instance['widget']['settings']['filefield_sources'];
-
- $return['source_imce'] = array(
- '#title' => t('IMCE file browser settings'),
- '#type' => 'fieldset',
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- '#access' => module_exists('imce'),
- );
-
- $return['source_imce']['imce_mode'] = array(
- '#type' => 'radios',
- '#title' => t('File browser mode'),
- '#options' => array(
- 0 => t('Restricted: Users can only browse the field directory. No file operations are allowed.'),
- 1 => t('Full: Browsable directories are defined by IMCE configuration profiles . File operations are allowed.', array('!imce-admin-url' => url('admin/config/media/imce'))),
- ),
- '#default_value' => isset($settings['source_imce']['imce_mode']) ? $settings['source_imce']['imce_mode'] : 0,
- );
- }
- elseif ($op == 'save') {
- $return['source_imce']['imce_mode'] = 0;
- }
-
- return $return;
-
-}
-
-/**
- * A #process callback to extend the filefield_widget element type.
- */
-function filefield_source_imce_process($element, &$form_state, $form) {
- $instance = field_widget_instance($element, $form_state);
-
- $element['filefield_imce'] = array(
- '#weight' => 100.5,
- '#theme' => 'filefield_source_imce_element',
- '#filefield_source' => TRUE, // Required for proper theming.
- '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
- );
-
- $filepath_id = $element['#id'] . '-imce-path';
- $display_id = $element['#id'] . '-imce-display';
- $select_id = $element['#id'] . '-imce-select';
- $element['filefield_imce']['file_path'] = array(
- // IE doesn't support onchange events for hidden fields, so we use a
- // textfield and hide it from display.
- '#type' => 'textfield',
- '#value' => '',
- '#attributes' => array(
- 'id' => $filepath_id,
- 'onblur' => "if (this.value.length > 0) { jQuery('#$select_id').triggerHandler('mousedown'); }",
- 'style' => 'position:absolute; left: -9999em',
- ),
- );
-
- $imce_function = 'window.open(\'' . url('file/imce/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'], array('query' => array('app' => $instance['label'] . '|url@' . $filepath_id))) . '\', \'\', \'width=760,height=560,resizable=1\'); return false;';
- $element['filefield_imce']['display_path'] = array(
- '#type' => 'markup',
- '#markup' => '' . t('No file selected') . ' (' . t('browse') . ' )',
- );
-
- $element['filefield_imce']['select'] = array(
- '#name' => implode('_', $element['#array_parents']) . '_imce_select',
- '#type' => 'submit',
- '#value' => t('Select'),
- '#validate' => array(),
- '#submit' => array('filefield_sources_field_submit'),
- '#limit_validation_errors' => array($element['#parents']),
- '#name' => $element['#name'] . '[filefield_imce][button]',
- '#id' => $select_id,
- '#attributes' => array('style' => 'display: none;'),
- '#ajax' => array(
- 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
- 'method' => 'replace',
- 'effect' => 'fade',
- ),
- );
-
- return $element;
-}
-
-/**
- * A #filefield_value_callback function.
- */
-function filefield_source_imce_value($element, &$item) {
- if (isset($item['filefield_imce']['file_path']) && $item['filefield_imce']['file_path'] != '') {
- $field = field_info_field($element['#field_name']);
-
- $scheme = $field['settings']['uri_scheme'];
- $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
- $file_directory_prefix = $scheme == 'private' ? 'system/files' : $wrapper->getDirectoryPath();
- $uri = preg_replace('/^' . preg_quote(base_path() . $file_directory_prefix . '/', '/') . '/', $scheme . '://', $item['filefield_imce']['file_path']);
-
- // Resolve the file path to an FID.
- $fid = db_select('file_managed', 'f')
- ->condition('uri', rawurldecode($uri))
- ->fields('f', array('fid'))
- ->execute()
- ->fetchField();
- if ($fid) {
- $file = file_load($fid);
- if (filefield_sources_element_validate($element, $file)) {
- $item = array_merge($item, (array) $file);
- }
- }
- else {
- form_error($element, t('The selected file could not be used because the file does not exist in the database.'));
- }
- // No matter what happens, clear the value from the file path field.
- $item['filefield_imce']['file_path'] = '';
- }
-}
-
-/**
- * Theme the output of the autocomplete field.
- */
-function theme_filefield_source_imce_element($variables) {
- $element = $variables['element'];
-
- $output = drupal_render_children($element);;
- return '' . $output . '
';
-}
-
-/**
- * Outputs the IMCE browser for FileField.
- */
-function filefield_source_imce_page($entity_type, $bundle_name, $field_name) {
- global $conf;
-
- // Check access.
- if (!module_exists('imce') || !imce_access() || !$instance = field_info_instance($entity_type, $field_name, $bundle_name)) {
- return drupal_access_denied();
- }
- $field = field_info_field($field_name);
-
- // Full mode
- if (!empty($instance['widget']['settings']['filefield_sources']['source_imce']['imce_mode'])) {
- $conf['imce_custom_scan'] = 'filefield_source_imce_custom_scan_full';
- }
- // Restricted mode
- else {
- $conf['imce_custom_scan'] = 'filefield_source_imce_custom_scan_restricted';
- $conf['imce_custom_field'] = $field + array('_uri' => file_field_widget_uri($field, $instance));
- }
-
- // Disable absolute URLs.
- $conf['imce_settings_absurls'] = 0;
-
- module_load_include('inc', 'imce', 'inc/imce.page');
- return imce($field['settings']['uri_scheme']);
-}
-
-/**
- * Scan and return files, subdirectories, and total size for "full" mode.
- */
-function filefield_source_imce_custom_scan_full($dirname, &$imce) {
- // Get a list of files in the database for this directory.
- $scheme = $imce['scheme'];
- $sql_uri_name = $dirname == '.' ? $scheme . '://' : $scheme . '://' . $dirname . '/';
-
- $result = db_select('file_managed', 'f')
- ->fields('f', array('uri'))
- ->condition('f.uri', $sql_uri_name . '%', 'LIKE')
- ->condition('f.uri', $sql_uri_name . '_%/%', 'NOT LIKE')
- ->execute();
-
- $db_files = array();
- foreach ($result as $row) {
- $db_files[basename($row->uri)] = 1;
- }
-
- // Get the default IMCE directory scan, then filter down to database files.
- $directory = imce_scan_directory($dirname, $imce);
- foreach ($directory['files'] as $filename => $file) {
- if (!isset($db_files[$filename])) {
- unset($directory['files'][$filename]);
- $directory['dirsize'] -= $file['size'];
- }
- }
-
- return $directory;
-}
-
-/**
- * Scan directory and return file list, subdirectories, and total size for Restricted Mode.
- */
-function filefield_source_imce_custom_scan_restricted($dirname, &$imce) {
- $field = $GLOBALS['conf']['imce_custom_field'];
- $root = $imce['scheme'] . '://';
- $field_uri = $field['_uri'];
- $is_root = $field_uri == $root;
-
- // Process IMCE. Make field directory the only accessible one.
- $imce['dir'] = $is_root ? '.' : substr($field_uri, strlen($root));
- $imce['directories'] = array();
- if (!empty($imce['perm'])) {
- filefield_source_imce_disable_perms($imce, array('browse'));
- }
-
- // Create directory info
- $directory = array('dirsize' => 0, 'files' => array(), 'subdirectories' => array(), 'error' => FALSE);
-
- if (isset($field['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) {
- $storage = $field['storage']['details']['sql']['FIELD_LOAD_CURRENT'];
- $table_info = reset($storage);
- $table = key($storage);
- $sql_uri = $field_uri . ($is_root ? '' : '/');
- $query = db_select($table, 'cf');
- $query->innerJoin('file_managed', 'f', 'f.fid = cf.' . $table_info['fid']);
- $result = $query->fields('f')
- ->condition('f.status', 1)
- ->condition('f.uri', $sql_uri . '%', 'LIKE')
- ->condition('f.uri', $sql_uri . '%/%', 'NOT LIKE')
- ->execute();
- foreach ($result as $file) {
- // Get real name
- $name = basename($file->uri);
- // Get dimensions
- $width = $height = 0;
- if ($img = imce_image_info($file->uri)) {
- $width = $img['width'];
- $height = $img['height'];
- }
- $directory['files'][$name] = array(
- 'name' => $name,
- 'size' => $file->filesize,
- 'width' => $width,
- 'height' => $height,
- 'date' => $file->timestamp,
- );
- $directory['dirsize'] += $file->filesize;
- }
- }
-
- return $directory;
- }
-
-/**
- * Disable IMCE profile permissions.
- */
-function filefield_source_imce_disable_perms(&$imce, $exceptions = array()) {
- $disable_all = empty($exceptions);
- foreach ($imce['perm'] as $name => $val) {
- if ($disable_all || !in_array($name, $exceptions)) {
- $imce['perm'][$name] = 0;
- }
- }
- $imce['directories'][$imce['dir']] = array('name' => $imce['dir']) + $imce['perm'];
-}
diff --git a/sources/reference.inc b/sources/reference.inc
deleted file mode 100644
index 8456da0..0000000
--- a/sources/reference.inc
+++ /dev/null
@@ -1,258 +0,0 @@
- t('Autocomplete reference textfield'),
- 'label' => t('Reference existing'),
- 'description' => t('Reuse an existing file by entering its file name.'),
- 'process' => 'filefield_source_reference_process',
- 'value' => 'filefield_source_reference_value',
- 'weight' => 1,
- 'file' => 'includes/reference.inc',
- );
- return $source;
-}
-
-/**
- * Implements hook_menu().
- */
-function filefield_source_reference_menu() {
- $items = array();
-
- $items['file/reference/%/%/%'] = array(
- 'page callback' => 'filefield_source_reference_autocomplete',
- 'page arguments' => array(2, 3, 4),
- 'access callback' => '_filefield_sources_field_access',
- 'access arguments' => array(2, 3, 4),
- 'file' => 'sources/reference.inc',
- 'type' => MENU_CALLBACK,
- );
- return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function filefield_source_reference_theme() {
- return array(
- 'filefield_source_reference_element' => array(
- 'render element' => 'element',
- 'file' => 'sources/reference.inc',
- ),
- 'filefield_source_reference_autocomplete_item' => array(
- 'variables' => array('file' => NULL),
- 'file' => 'sources/reference.inc',
- ),
- );
-}
-
-/**
- * Implements hook_filefield_source_settings().
- */
-function filefield_source_reference_settings($op, $instance) {
- $return = array();
-
- if ($op == 'form') {
- $settings = $instance['widget']['settings']['filefield_sources'];
-
- $return['source_reference'] = array(
- '#title' => t('Autocomplete reference options'),
- '#type' => 'fieldset',
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
-
- $return['source_reference']['autocomplete'] = array(
- '#title' => t('Match file name'),
- '#options' => array(
- '0' => t('Starts with string'),
- '1' => t('Contains string'),
- ),
- '#type' => 'radios',
- '#default_value' => isset($settings['source_reference']['autocomplete']) ? $settings['source_reference']['autocomplete'] : '0',
- );
- }
- elseif ($op == 'save') {
- $return['source_reference']['autocomplete'] = '0';
- }
-
- return $return;
-}
-
-/**
- * A #process callback to extend the filefield_widget element type.
- */
-function filefield_source_reference_process($element, &$form_state, $form) {
-
- $element['filefield_reference'] = array(
- '#weight' => 100.5,
- '#theme' => 'filefield_source_reference_element',
- '#filefield_source' => TRUE, // Required for proper theming.
- '#filefield_sources_hint_text' => FILEFIELD_SOURCE_REFERENCE_HINT_TEXT,
- );
-
- $element['filefield_reference']['autocomplete'] = array(
- '#type' => 'textfield',
- '#autocomplete_path' => 'file/reference/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'],
- '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
- );
-
- $element['filefield_reference']['select'] = array(
- '#name' => implode('_', $element['#array_parents']) . '_autocomplete_select',
- '#type' => 'submit',
- '#value' => t('Select'),
- '#validate' => array(),
- '#submit' => array('filefield_sources_field_submit'),
- '#name' => $element['#name'] . '[filefield_reference][button]',
- '#limit_validation_errors' => array($element['#parents']),
- '#ajax' => array(
- 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
- 'effect' => 'fade',
- ),
- );
-
- return $element;
-}
-
-/**
- * A #filefield_value_callback function.
- */
-function filefield_source_reference_value($element, &$item) {
- if (isset($item['filefield_reference']['autocomplete']) && strlen($item['filefield_reference']['autocomplete']) > 0 && $item['filefield_reference']['autocomplete'] != FILEFIELD_SOURCE_REFERENCE_HINT_TEXT) {
- $matches = array();
- if (preg_match('/\[fid:(\d+)\]/', $item['filefield_reference']['autocomplete'], $matches)) {
- $fid = $matches[1];
- if ($file = file_load($fid)) {
-
- // Remove file size restrictions, since the file already exists on disk.
- if (isset($element['#upload_validators']['file_validate_size'])) {
- unset($element['#upload_validators']['file_validate_size']);
- }
-
- // Check that the user has access to this file through hook_download().
- if (!filefield_sources_file_access($file->uri)) {
- form_error($element, t('You do not have permission to use the selected file.'));
- }
- elseif (filefield_sources_element_validate($element, (object) $file)) {
- $item = array_merge($item, (array) $file);
- }
- }
- else {
- form_error($element, t('The referenced file could not be used because the file does not exist in the database.'));
- }
- }
- // No matter what happens, clear the value from the autocomplete.
- $item['filefield_reference']['autocomplete'] = '';
- }
-}
-
-/**
- * Menu callback; autocomplete.js callback to return a list of files.
- */
-function filefield_source_reference_autocomplete($entity_type, $bundle_name, $field_name, $filename) {
- $field = field_info_instance($entity_type, $field_name, $bundle_name);
-
- $items = array();
- if (!empty($field)) {
- $files = filefield_source_reference_get_files($filename, $field);
- foreach ($files as $fid => $file) {
- $items[$file->filename ." [fid:$fid]"] = theme('filefield_source_reference_autocomplete_item', array('file' => $file));
- }
- }
-
- drupal_json_output($items);
-}
-
-/**
- * Theme the output of a single item in the autocomplete list.
- */
-function theme_filefield_source_reference_autocomplete_item($variables) {
- $file = $variables['file'];
-
- $output = '';
- $output .= '';
- $output .= '' . check_plain($file->filename) . ' (' . format_size($file->filesize) . ') ';
- $output .= '
';
- return $output;
-}
-
-/**
- * Theme the output of the autocomplete field.
- */
-function theme_filefield_source_reference_element($variables) {
- $element = $variables['element'];
-
- $element['autocomplete']['#field_suffix'] = drupal_render($element['select']);
- return '' . drupal_render($element['autocomplete']) . '
';
-}
-
-/**
- * Get all the files used within a particular field (or all fields).
- *
- * @param $file_name
- * The partial name of the file to retrieve.
- * @param $instance
- * Optional. A CCK field array for which to filter returned files.
- */
-function filefield_source_reference_get_files($filename, $instance = NULL) {
- $instances = array();
- if (!isset($instance)) {
- foreach (field_info_fields() as $instance) {
- if ($instance['type'] == 'file' || $instance['type'] == 'image') {
- $instances[] = $instance;
- }
- }
- }
- else {
- $instances = array($instance);
- }
-
- $files = array();
- foreach ($instances as $instance) {
- // Load the field data, which contains the schema information.
- $field = field_info_field($instance['field_name']);
-
- // We don't support fields that are not stored with SQL.
- if (!isset($field['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) {
- continue;
- }
-
- // 1 == contains, 0 == starts with.
- $like = empty($instance['widget']['settings']['filefield_sources']['source_reference']['autocomplete']) ? (db_like($filename) . '%') : ('%' . db_like($filename) . '%');
-
- $table_info = reset($field['storage']['details']['sql']['FIELD_LOAD_CURRENT']);
- $table = key($field['storage']['details']['sql']['FIELD_LOAD_CURRENT']);
- $query = db_select($table, 'cf');
- $query->innerJoin('file_managed', 'f', 'f.fid = cf.' . $table_info['fid']);
- $query->fields('f');
- $query->condition('f.status', 1);
- $query->condition('f.filename', $like, 'LIKE');
- $query->orderBy('f.timestamp', 'DESC');
- $query->groupBy('f.fid');
- $query->range(0, 30);
- $query->addTag('filefield_source_reference_list');
- $result = $query->execute();
-
- foreach ($result as $file) {
- $files[$file->fid] = $file;
- }
- }
-
- return $files;
-}
diff --git a/sources/remote.inc b/sources/remote.inc
deleted file mode 100644
index aa890a8..0000000
--- a/sources/remote.inc
+++ /dev/null
@@ -1,396 +0,0 @@
- t('Remote URL textfield'),
- 'label' => t('Remote URL'),
- 'description' => t('Download a file from a remote server.'),
- 'process' => 'filefield_source_remote_process',
- 'value' => 'filefield_source_remote_value',
- 'file' => 'includes/remote.inc',
- );
- return $source;
-}
-
-/**
- * Implements hook_menu().
- */
-function filefield_source_remote_menu() {
- $items = array();
-
- $items['file/remote/progress/%/%/%/%'] = array(
- 'page callback' => 'filefield_source_remote_progress',
- 'page arguments' => array(3, 4, 5, 6),
- 'access callback' => TRUE,
- 'file' => 'sources/remote.inc',
- 'type' => MENU_CALLBACK,
- );
- return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function filefield_source_remote_theme() {
- return array(
- 'filefield_source_remote_element' => array(
- 'render element' => 'element',
- 'file' => 'sources/remote.inc',
- ),
- );
-}
-
-/**
- * Implements hook_filefield_source_settings().
- */
-function filefield_source_remote_settings($op, $instance) {
- $return = array();
-
- // Add settings to the FileField widget form.
-
- return $return;
-
-}
-
-/**
- * A #process callback to extend the filefield_widget element type.
- */
-function filefield_source_remote_process($element, &$form_state, $form) {
-
- $element['filefield_remote'] = array(
- '#weight' => 100.5,
- '#theme' => 'filefield_source_remote_element',
- '#filefield_source' => TRUE, // Required for proper theming.
- '#filefield_sources_hint_text' => FILEFIELD_SOURCE_REMOTE_HINT_TEXT,
- );
-
- $element['filefield_remote']['url'] = array(
- '#type' => 'textfield',
- '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
- '#maxlength' => NULL,
- );
-
- $element['filefield_remote']['transfer'] = array(
- '#name' => implode('_', $element['#array_parents']) . '_transfer',
- '#type' => 'submit',
- '#value' => t('Transfer'),
- '#validate' => array(),
- '#submit' => array('filefield_sources_field_submit'),
- '#limit_validation_errors' => array($element['#parents']),
- '#ajax' => array(
- 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
- 'effect' => 'fade',
- 'progress' => array(
- 'type' => 'bar',
- 'path' => 'file/remote/progress/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'] . '/' . $element['#delta'],
- 'message' => t('Starting transfer...'),
- ),
- ),
- );
-
- return $element;
-}
-
-/**
- * A #filefield_value_callback function.
- */
-function filefield_source_remote_value($element, &$item) {
- if (isset($item['filefield_remote']['url']) && strlen($item['filefield_remote']['url']) > 0 && valid_url($item['filefield_remote']['url']) && $item['filefield_remote']['url'] != FILEFIELD_SOURCE_REMOTE_HINT_TEXT) {
- $field = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
- $url = $item['filefield_remote']['url'];
-
- // Check that the destination is writable.
- $temporary_directory = 'temporary://';
- if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
- watchdog('file', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
- drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
- return;
- }
-
- // Check that the destination is writable.
- $directory = $element['#upload_location'];
- $mode = variable_get('file_chmod_directory', 0775);
-
- // This first chmod check is for other systems such as S3, which don't work
- // with file_prepare_directory().
- if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
- watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
- drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
- return;
- }
-
- // Check the headers to make sure it exists and is within the allowed size.
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, TRUE);
- curl_setopt($ch, CURLOPT_NOBODY, TRUE);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
- curl_setopt($ch, CURLOPT_HEADERFUNCTION, '_filefield_source_remote_parse_header');
- // Causes a warning if PHP safe mode is on.
- @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
- curl_exec($ch);
- $info = curl_getinfo($ch);
- if ($info['http_code'] != 200) {
- curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
- $file_contents = curl_exec($ch);
- $info = curl_getinfo($ch);
- }
- curl_close($ch);
-
- if ($info['http_code'] != 200) {
- switch ($info['http_code']) {
- case 403:
- form_error($element, t('The remote file could not be transferred because access to the file was denied.'));
- break;
- case 404:
- form_error($element, t('The remote file could not be transferred because it was not found.'));
- break;
- default:
- form_error($element, t('The remote file could not be transferred due to an HTTP error (@code).', array('@code' => $info['http_code'])));
- }
- return;
- }
-
- // Update the $url variable to reflect any redirects.
- $url = $info['url'];
- $url_info = parse_url($url);
-
- // Determine the proper filename by reading the filename given in the
- // Content-Disposition header. If the server fails to send this header,
- // fall back on the basename of the URL.
- //
- // We prefer to use the Content-Disposition header, because we can then
- // use URLs like http://example.com/get_file/23 which would otherwise be
- // rejected because the URL basename lacks an extension.
- $filename = _filefield_source_remote_filename();
- if (empty($filename)) {
- $filename = rawurldecode(basename($url_info['path']));
- }
-
- $pathinfo = pathinfo($filename);
-
- // Create the file extension from the MIME header if all else has failed.
- if (empty($pathinfo['extension']) && $extension = _filefield_source_remote_mime_extension()) {
- $filename = $filename . '.' . $extension;
- $pathinfo = pathinfo($filename);
- }
-
- $filename = filefield_sources_clean_filename($filename, $field['settings']['file_extensions']);
- $filepath = file_create_filename($filename, $temporary_directory);
-
- if (empty($pathinfo['extension'])) {
- form_error($element, t('The remote URL must be a file and have an extension.'));
- return;
- }
-
- // Perform basic extension check on the file before trying to transfer.
- $extensions = $field['settings']['file_extensions'];
- $regex = '/\.('. preg_replace('/[ +]/', '|', preg_quote($extensions)) .')$/i';
- if (!empty($extensions) && !preg_match($regex, $filename)) {
- form_error($element, t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)));
- return;
- }
-
- // Check file size based off of header information.
- if (!empty($element['#upload_validators']['file_validate_size'][0])) {
- $max_size = $element['#upload_validators']['file_validate_size'][0];
- $file_size = $info['download_content_length'];
- if ($file_size > $max_size) {
- form_error($element, t('The remote file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file_size), '%maxsize' => format_size($max_size))));
- return;
- }
- }
-
- // Set progress bar information.
- $options = array(
- 'key' => $element['#entity_type'] . '_' . $element['#bundle'] . '_' . $element['#field_name'] . '_' . $element['#delta'],
- 'filepath' => $filepath,
- );
- filefield_source_remote_set_transfer_options($options);
-
- $transfer_success = FALSE;
- // If we've already downloaded the entire file because the header-retrieval
- // failed, just ave the contents we have.
- if (isset($file_contents)) {
- if ($fp = @fopen($filepath, 'w')) {
- fwrite($fp, $file_contents);
- fclose($fp);
- $transfer_success = TRUE;
- }
- }
- // If we don't have the file contents, download the actual file.
- else {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, FALSE);
- curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'filefield_source_remote_curl_write');
- // Causes a warning if PHP safe mode is on.
- @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
- $transfer_success = curl_exec($ch);
- curl_close($ch);
- }
- if ($transfer_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
- $item = array_merge($item, (array) $file);
- }
-
- // Delete the temporary file.
- @unlink($filepath);
- }
-}
-
-/**
- * Parse cURL header and record the filename specified in Content-Disposition.
- */
-function _filefield_source_remote_parse_header(&$ch, $header) {
- if (preg_match('/Content-Disposition:.*?filename="(.+?)"/', $header, $matches)) {
- // Content-Disposition: attachment; filename="FILE NAME HERE"
- _filefield_source_remote_filename($matches[1]);
- }
- elseif (preg_match('/Content-Disposition:.*?filename=([^; ]+)/', $header, $matches)) {
- // Content-Disposition: attachment; filename=file.ext
- $uri = trim($matches[1]);
- _filefield_source_remote_filename($uri);
- }
- elseif (preg_match('/Content-Type:[ ]*([a-z0-9_\-]+\/[a-z0-9_\-]+)/i', $header, $matches)) {
- $mime_type = $matches[1];
- _filefield_source_remote_mime_extension($mime_type);
- }
-
- // This is required by cURL.
- return strlen($header);
-}
-
-/**
- * Get/set the remote file name in a static variable.
- */
-function _filefield_source_remote_filename($curl_filename = NULL) {
- static $filename = NULL;
- if (isset($curl_filename)) {
- $filename = $curl_filename;
- }
- return $filename;
-}
-
-/**
- * Get/set the remote file extension in a static variable.
- */
-function _filefield_source_remote_mime_extension($curl_mime_type = NULL) {
- static $extension = NULL;
- if (isset($curl_mime_type)) {
- include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
- $curl_mime_type = drupal_strtolower($curl_mime_type);
- $mapping = file_mimetype_mapping();
- // See if this matches a known MIME type.
- $map_id = array_search($curl_mime_type, $mapping['mimetypes']);
- if ($map_id !== FALSE) {
- // If we have a match, get this list of likely extensions. For some reason
- // Drupal lists the "most common" extension last for most file types
- // including php, jpg, and doc.
- if ($extensions = array_keys($mapping['extensions'], $map_id)) {
- $extension = end($extensions);
- }
- }
- }
- return $extension;
-}
-
-/**
- * Menu callback; progress.js callback to return upload progress.
- */
-function filefield_source_remote_progress($entity_type, $bundle_name, $field_name, $delta) {
- $key = $entity_type . '_' . $bundle_name . '_' . $field_name . '_' . $delta;
- $progress = array(
- 'message' => t('Starting transfer...'),
- 'percentage' => -1,
- );
-
- if ($cache = cache_get('filefield_transfer:'. session_id() . ':' . $key)) {
- $current = $cache->data['current'];
- $total = $cache->data['total'];
- $progress['message'] = t('Transferring... (@current of @total)', array('@current' => format_size($current), '@total' => format_size($total)));
- $progress['percentage'] = round(100 * $current / $total);
- }
-
- drupal_json_output($progress);
-}
-
-/**
- * cURL write function to save the file to disk. Also updates progress bar.
- */
-function filefield_source_remote_curl_write(&$ch, $data) {
- $progress_update = 0;
- $options = filefield_source_remote_get_transfer_options();
-
- // Get the current progress and update the progress value.
- // Only update every 64KB to reduce cache_set calls. cURL usually writes
- // in 16KB chunks.
- if (curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) / 65536 > $progress_update) {
- $progress_update++;
- $progress = array(
- 'current' => curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD),
- 'total' => curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD),
- );
- // Set a cache so that we can retrieve this value from the progress bar.
- $cid = 'filefield_transfer:'. session_id() . ':' . $options['key'];
- if ($progress['current'] != $progress['total']) {
- cache_set($cid, $progress, 'cache', time() + 300);
- }
- else {
- cache_clear_all($cid, 'cache');
- }
- }
-
- $data_length = 0;
- if ($fp = @fopen($options['filepath'], 'a')) {
- fwrite($fp, $data);
- fclose($fp);
- $data_length = strlen($data);
- }
-
- return $data_length;
-}
-
-/**
- * Set a transfer key that can be retreived by the progress function.
- */
-function filefield_source_remote_set_transfer_options($options = NULL) {
- static $current = FALSE;
- if (isset($options)) {
- $current = $options;
- }
- return $current;
-}
-
-/**
- * Get a transfer key that can be retrieved by the progress function.
- */
-function filefield_source_remote_get_transfer_options() {
- return filefield_source_remote_set_transfer_options();
-}
-
-/**
- * Theme the output of the autocomplete field.
- */
-function theme_filefield_source_remote_element($variables) {
- $element = $variables['element'];
-
- $element['url']['#field_suffix'] = drupal_render($element['transfer']);
- return '' . drupal_render($element['url']) . '
';
-}
-
diff --git a/src/Access/FieldAccessCheck.php b/src/Access/FieldAccessCheck.php
new file mode 100644
index 0000000..b70f535
--- /dev/null
+++ b/src/Access/FieldAccessCheck.php
@@ -0,0 +1,38 @@
+access('edit', $account, TRUE);
+ }
+
+}
diff --git a/src/Annotation/FilefieldSource.php b/src/Annotation/FilefieldSource.php
new file mode 100644
index 0000000..3a873fe
--- /dev/null
+++ b/src/Annotation/FilefieldSource.php
@@ -0,0 +1,62 @@
+checkDefaultMapping();
+
+ $mime_key = array_search($mimetype, $this->mapping['mimetypes']);
+ $extension = array_search($mime_key, $this->mapping['extensions']);
+
+ return $extension;
+ }
+
+ /**
+ * Convert mime type to most common extension.
+ *
+ * @param string $mimetype
+ * Mime type.
+ *
+ * @return string|bool
+ * Return extension if found, FALSE otherwise.
+ */
+ public function convertMimeTypeToMostCommonExtension($mimetype) {
+ $this->checkDefaultMapping();
+
+ $extension = FALSE;
+ if (isset($mimetype)) {
+ // See if this matches a known MIME type.
+ $mime_key = array_search($mimetype, $this->mapping['mimetypes']);
+ if ($mime_key !== FALSE) {
+ // If we have a match, get this list of likely extensions. For some
+ // reason Drupal lists the "most common" extension last for most file
+ // types including php, jpg, and doc.
+ if ($extensions = array_keys($this->mapping['extensions'], $mime_key)) {
+ $extension = end($extensions);
+ }
+ }
+ }
+ return $extension;
+ }
+
+ /**
+ * Check for default mapping.
+ */
+ private function checkDefaultMapping() {
+ if ($this->mapping === NULL) {
+ $mapping = $this->defaultMapping;
+ // Allow modules to alter the default mapping.
+ $this->moduleHandler->alter('file_mimetype_mapping', $mapping);
+ $this->mapping = $mapping;
+ }
+ }
+
+}
diff --git a/src/FilefieldSourceInterface.php b/src/FilefieldSourceInterface.php
new file mode 100644
index 0000000..8ea45ca
--- /dev/null
+++ b/src/FilefieldSourceInterface.php
@@ -0,0 +1,55 @@
+setCacheBackend($cache_backend, 'filefield_sources');
+
+ parent::__construct('Plugin/FilefieldSource', $namespaces, $module_handler, 'Drupal\filefield_sources\FilefieldSourceInterface', 'Drupal\filefield_sources\Annotation\FilefieldSource');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefinitions() {
+ $definitions = parent::getDefinitions();
+ if (!\Drupal::moduleHandler()->moduleExists('imce') || !imce_access()) {
+ unset($definitions['imce']);
+ }
+ return $definitions;
+ }
+
+}
diff --git a/src/FilefieldSourcesServiceProvider.php b/src/FilefieldSourcesServiceProvider.php
new file mode 100644
index 0000000..8f2a65f
--- /dev/null
+++ b/src/FilefieldSourcesServiceProvider.php
@@ -0,0 +1,26 @@
+getDefinition('file.mime_type.guesser.extension');
+ $definition->setClass('Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser');
+ }
+
+}
diff --git a/src/Plugin/FilefieldSource/Attach.php b/src/Plugin/FilefieldSource/Attach.php
new file mode 100644
index 0000000..be269bf
--- /dev/null
+++ b/src/Plugin/FilefieldSource/Attach.php
@@ -0,0 +1,349 @@
+log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array(
+ '%file' => $filepath,
+ '%destination' => drupal_realpath($directory),
+ ));
+ drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $filepath)), 'error');
+ return;
+ }
+
+ // Clean up the file name extensions and transliterate.
+ $original_filepath = $filepath;
+ $new_filepath = filefield_sources_clean_filename($filepath, $instance->settings['file_extensions']);
+ rename($filepath, $new_filepath);
+ $filepath = $new_filepath;
+
+ // Run all the normal validations, minus file size restrictions.
+ $validators = $element['#upload_validators'];
+ if (isset($validators['file_validate_size'])) {
+ unset($validators['file_validate_size']);
+ }
+
+ // Save the file to the new location.
+ if ($file = filefield_sources_save_file($filepath, $validators, $directory)) {
+ if (!in_array($file->id(), $input['fids'])) {
+ $input['fids'][] = $file->id();
+ }
+
+ // Delete the original file if "moving" the file instead of copying.
+ if ($element['#filefield_sources_settings']['source_attach']['attach_mode'] !== FILEFIELD_SOURCE_ATTACH_MODE_COPY) {
+ @unlink($filepath);
+ }
+ }
+
+ // Restore the original file name if the file still exists.
+ if (file_exists($filepath) && $filepath != $original_filepath) {
+ rename($filepath, $original_filepath);
+ }
+
+ $input['filefield_attach']['filename'] = '';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+ $settings = $element['#filefield_sources_settings']['source_attach'];
+ $field_name = $element['#field_name'];
+ $instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $field_name);
+
+ $element['filefield_attach'] = array(
+ '#weight' => 100.5,
+ '#theme' => 'filefield_sources_element',
+ '#source_id' => 'attach',
+ // Required for proper theming.
+ '#filefield_source' => TRUE,
+ );
+
+ $path = static::getDirectory($settings);
+ $options = static::getAttachOptions($path);
+
+ // If we have built this element before, append the list of options that we
+ // had previously. This allows files to be deleted after copying them and
+ // still be considered a valid option during the validation and submit.
+ $triggering_element = $form_state->getTriggeringElement();
+ $property = array(
+ 'filefield_sources',
+ $field_name,
+ 'attach_options',
+ );
+ if (!isset($triggering_element) && $form_state->has($property)) {
+ $attach_options = $form_state->get($property);
+ $options = $options + $attach_options;
+ }
+ // On initial form build and rebuilds after processing input, save the
+ // original list of options so they can be restored in the line above.
+ else {
+ $form_state->set(array('filefield_sources', $field_name, 'attach_options'), $options);
+ }
+
+ $description = t('This method may be used to attach files that exceed the file size limit. Files may be attached from the %directory directory on the server, usually uploaded through FTP.', array('%directory' => realpath($path)));
+
+ // Error messages.
+ if ($options === FALSE || empty($settings['path'])) {
+ $attach_message = t('A file attach directory could not be located.');
+ $attach_description = t('Please check your settings for the %field field.', array('%field' => $instance->getLabel()));
+ }
+ elseif (!count($options)) {
+ $attach_message = t('There currently are no files to attach.');
+ $attach_description = $description;
+ }
+
+ if (isset($attach_message)) {
+ $element['filefield_attach']['attach_message'] = array(
+ '#markup' => $attach_message,
+ );
+ $element['filefield_attach']['#description'] = $attach_description;
+ }
+ else {
+ $validators = $element['#upload_validators'];
+ if (isset($validators['file_validate_size'])) {
+ unset($validators['file_validate_size']);
+ }
+ $description .= ' ' . filefield_sources_element_validation_help($validators);
+ $element['filefield_attach']['filename'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ );
+ $element['filefield_attach']['#description'] = $description;
+ }
+
+ $ajax_settings = [
+ 'url' => Url::fromRoute('file.ajax_upload'),
+ 'options' => [
+ 'query' => [
+ 'element_parents' => implode('/', $element['#array_parents']),
+ 'form_build_id' => $complete_form['form_build_id']['#value'],
+ ],
+ ],
+ 'wrapper' => $element['#id'] . '-ajax-wrapper',
+ 'effect' => 'fade',
+ ];
+
+ $element['filefield_attach']['attach'] = [
+ '#name' => implode('_', $element['#parents']) . '_attach',
+ '#type' => 'submit',
+ '#value' => t('Attach'),
+ '#validate' => [],
+ '#submit' => ['filefield_sources_field_submit'],
+ '#limit_validation_errors' => [$element['#parents']],
+ '#ajax' => $ajax_settings,
+ ];
+
+ return $element;
+ }
+
+ /**
+ * Theme the output of the attach element.
+ */
+ public static function element($variables) {
+ $element = $variables['element'];
+
+ if (isset($element['attach_message'])) {
+ $output = $element['attach_message']['#markup'];
+ }
+ else {
+ $size = !empty($element['filename']['#size']) ? ' size="' . $element['filename']['#size'] . '"' : '';
+ $element['filename']['#attributes']['class'][] = 'form-select';
+ $multiple = !empty($element['#multiple']);
+ $output = '' . form_select_options($element['filename']) . ' ';
+ }
+ $output .= drupal_render($element['attach']);
+ $element['#children'] = $output;
+ $element['#theme_wrappers'] = array('form_element');
+ return '' . drupal_render($element) . '
';
+ }
+
+ /**
+ * Get directory from settings.
+ *
+ * @param array $settings
+ * Attach source's settings.
+ * @param object $account
+ * User to replace token.
+ *
+ * @return string
+ * Path that contains files to attach.
+ */
+ protected static function getDirectory(array $settings, $account = NULL) {
+ $account = isset($account) ? $account : \Drupal::currentUser();
+ $path = $settings['path'];
+ $absolute = !empty($settings['absolute']);
+
+ // Replace user level tokens.
+ // Node level tokens require a lot of complexity like temporary storage
+ // locations when values don't exist. See the filefield_paths module.
+ if (\Drupal::moduleHandler()->moduleExists('token')) {
+ $path = token_replace($path, array('user' => $account));
+ }
+
+ return $absolute ? $path : file_default_scheme() . '://' . $path;
+ }
+
+ /**
+ * Get attach options.
+ *
+ * @param string $path
+ * Path to scan files.
+ *
+ * @return array
+ * List of options.
+ */
+ protected static function getAttachOptions($path) {
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
+ drupal_set_message(t('Specified file attach path must exist or be writable.'), 'error');
+ return FALSE;
+ }
+
+ $options = array();
+ $file_attach = file_scan_directory($path, '/.*/', array('key' => 'filename'), 0);
+
+ if (count($file_attach)) {
+ $options = array('' => t('-- Select file --'));
+ foreach ($file_attach as $filename => $fileinfo) {
+ $filename = basename($filename);
+ $options[$fileinfo->uri] = str_replace($path . '/', '', $fileinfo->uri);
+ }
+ }
+
+ natcasesort($options);
+ return $options;
+ }
+
+ /**
+ * Implements hook_filefield_source_settings().
+ */
+ public static function settings(WidgetInterface $plugin) {
+ $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
+ 'source_attach' => array(
+ 'path' => FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH,
+ 'absolute' => FILEFIELD_SOURCE_ATTACH_RELATIVE,
+ 'attach_mode' => FILEFIELD_SOURCE_ATTACH_MODE_MOVE,
+ ),
+ ));
+
+ $return['source_attach'] = array(
+ '#title' => t('File attach settings'),
+ '#type' => 'details',
+ '#description' => t('File attach allows for selecting a file from a directory on the server, commonly used in combination with FTP.') . ' ' . t('This file source will ignore file size checking when used.') . ' ',
+ '#element_validate' => array(array(get_called_class(), 'filePathValidate')),
+ '#weight' => 3,
+ );
+ $return['source_attach']['path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('File attach path'),
+ '#default_value' => $settings['source_attach']['path'],
+ '#size' => 60,
+ '#maxlength' => 128,
+ '#description' => t('The directory within the File attach location that will contain attachable files.'),
+ );
+ if (\Drupal::moduleHandler()->moduleExists('token')) {
+ $return['source_attach']['tokens'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => array('user'),
+ );
+ }
+ $return['source_attach']['absolute'] = array(
+ '#type' => 'radios',
+ '#title' => t('File attach location'),
+ '#options' => array(
+ FILEFIELD_SOURCE_ATTACH_RELATIVE => t('Within the files directory'),
+ FILEFIELD_SOURCE_ATTACH_ABSOLUTE => t('Absolute server path'),
+ ),
+ '#default_value' => $settings['source_attach']['absolute'],
+ '#description' => t('The File attach path may be with the files directory (%file_directory) or from the root of your server. If an absolute path is used and it does not start with a "/" your path will be relative to your site directory: %realpath.', array('%file_directory' => drupal_realpath(file_default_scheme() . '://'), '%realpath' => realpath('./'))),
+ );
+ $return['source_attach']['attach_mode'] = array(
+ '#type' => 'radios',
+ '#title' => t('Attach method'),
+ '#options' => array(
+ FILEFIELD_SOURCE_ATTACH_MODE_MOVE => t('Move the file directly to the final location'),
+ FILEFIELD_SOURCE_ATTACH_MODE_COPY => t('Leave a copy of the file in the attach directory'),
+ ),
+ '#default_value' => isset($settings['source_attach']['attach_mode']) ? $settings['source_attach']['attach_mode'] : 'move',
+ );
+
+ return $return;
+ }
+
+ /**
+ * Validate file path.
+ *
+ * @param array $element
+ * Form element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * Form state.
+ * @param array $complete_form
+ * Complete form.
+ */
+ public static function filePathValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
+ $parents = $element['#parents'];
+ array_pop($parents);
+ $input_exists = FALSE;
+
+ // Get input of the whole parent element.
+ $input = NestedArray::getValue($form_state->getValues(), $parents, $input_exists);
+ if ($input_exists) {
+ // Only validate if this source is enabled.
+ if (!$input['sources']['attach']) {
+ return;
+ }
+
+ // Strip slashes from the end of the file path.
+ $filepath = rtrim($element['path']['#value'], '\\/');
+ $form_state->setValueForElement($element['path'], $filepath);
+ $filepath = static::getDirectory($input['source_attach']);
+
+ // Check that the directory exists and is writable.
+ if (!file_prepare_directory($filepath, FILE_CREATE_DIRECTORY)) {
+ $form_state->setError($element['path'], t('Specified file attach path must exist or be writable.'));
+ }
+ }
+ }
+
+}
diff --git a/src/Plugin/FilefieldSource/Clipboard.php b/src/Plugin/FilefieldSource/Clipboard.php
new file mode 100644
index 0000000..17e5813
--- /dev/null
+++ b/src/Plugin/FilefieldSource/Clipboard.php
@@ -0,0 +1,153 @@
+limited browser support)"),
+ * label = @Translation("Clipboard"),
+ * description = @Translation("Allow users to paste a file directly from the clipboard."),
+ * weight = 1
+ * )
+ */
+class Clipboard implements FilefieldSourceInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function value(array &$element, &$input, FormStateInterface $form_state) {
+ if (isset($input['filefield_clipboard']['contents']) && strlen($input['filefield_clipboard']['contents']) > 0) {
+ // Check that the destination is writable.
+ $temporary_directory = 'temporary://';
+ if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
+ \Drupal::logger('filefield_sources')->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
+ drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
+ return;
+ }
+ // Check that the destination is writable.
+ $directory = $element['#upload_location'];
+ $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
+
+ // This first chmod check is for other systems such as S3, which don't
+ // work with file_prepare_directory().
+ if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
+ $url = $input['filefield_clipboard']['filename'];
+ \Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
+ drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
+ return;
+ }
+
+ // Split the file information in mimetype and base64 encoded binary.
+ $base64_data = $input['filefield_clipboard']['contents'];
+ $comma_position = strpos($base64_data, ',');
+ $semicolon_position = strpos($base64_data, ';');
+ $file_contents = base64_decode(substr($base64_data, $comma_position + 1));
+ $mimetype = substr($base64_data, 5, $semicolon_position - 5);
+
+ $extension = \Drupal::service('file.mime_type.guesser.extension')->convertMimeTypeToExtension($mimetype);
+
+ $filename = trim($input['filefield_clipboard']['filename']);
+ $filename = preg_replace('/\.[a-z0-9]{3,4}$/', '', $filename);
+ $filename = (empty($filename) ? 'paste_' . REQUEST_TIME : $filename) . '.' . $extension;
+ $filepath = file_create_filename($filename, $temporary_directory);
+
+ $copy_success = FALSE;
+ if ($fp = @fopen($filepath, 'w')) {
+ fwrite($fp, $file_contents);
+ fclose($fp);
+ $copy_success = TRUE;
+ }
+
+ if ($copy_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
+ if (!in_array($file->id(), $input['fids'])) {
+ $input['fids'][] = $file->id();
+ }
+ }
+
+ // Remove the temporary file generated from paste.
+ @unlink($filepath);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+ $element['filefield_clipboard'] = array(
+ '#weight' => 100.5,
+ '#theme' => 'filefield_sources_element',
+ '#source_id' => 'clipboard',
+ // Required for proper theming.
+ '#filefield_source' => TRUE,
+ '#filefield_sources_hint_text' => t('Enter filename then paste.'),
+ '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+ );
+
+ $element['filefield_clipboard']['capture'] = array(
+ '#type' => 'item',
+ '#markup' => 'example_filename.png
' . t('ctrl + v') . ' ',
+ '#description' => t('Enter a file name and paste an image from the clipboard. This feature only works in limited browsers .'),
+ );
+
+ $element['filefield_clipboard']['filename'] = array(
+ '#type' => 'hidden',
+ '#attributes' => array('class' => array('filefield-source-clipboard-filename')),
+ );
+ $element['filefield_clipboard']['contents'] = array(
+ '#type' => 'hidden',
+ '#attributes' => array('class' => array('filefield-source-clipboard-contents')),
+ );
+
+ $ajax_settings = [
+ 'url' => Url::fromRoute('file.ajax_upload'),
+ 'options' => [
+ 'query' => [
+ 'element_parents' => implode('/', $element['#array_parents']),
+ 'form_build_id' => $complete_form['form_build_id']['#value'],
+ ],
+ ],
+ 'wrapper' => $element['#id'] . '-ajax-wrapper',
+ 'effect' => 'fade',
+ 'progress' => [
+ 'type' => 'throbber',
+ 'message' => t('Transfering file...'),
+ ],
+ ];
+
+ $element['filefield_clipboard']['upload'] = [
+ '#name' => implode('_', $element['#parents']) . '_clipboard_upload_button',
+ '#type' => 'submit',
+ '#value' => t('Upload'),
+ '#attributes' => ['class' => ['js-hide']],
+ '#validate' => [],
+ '#submit' => ['filefield_sources_field_submit'],
+ '#limit_validation_errors' => [$element['#parents']],
+ '#ajax' => $ajax_settings,
+ ];
+
+ return $element;
+ }
+
+ /**
+ * Theme the output of the clipboard element.
+ */
+ public static function element($variables) {
+ $element = $variables['element'];
+
+ return '' . drupal_render_children($element) . '
';
+ }
+
+}
diff --git a/src/Plugin/FilefieldSource/Imce.php b/src/Plugin/FilefieldSource/Imce.php
new file mode 100644
index 0000000..31e71a3
--- /dev/null
+++ b/src/Plugin/FilefieldSource/Imce.php
@@ -0,0 +1,361 @@
+getSettings();
+ $scheme = $field_settings['uri_scheme'];
+
+ $wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
+ $file_directory_prefix = $scheme == 'private' ? 'system/files' : $wrapper->getDirectoryPath();
+ $uri = preg_replace('/^' . preg_quote(base_path() . $file_directory_prefix . '/', '/') . '/', $scheme . '://', $input['filefield_imce']['file_path']);
+
+ // Resolve the file path to an FID.
+ $fid = db_select('file_managed', 'f')
+ ->condition('uri', rawurldecode($uri))
+ ->fields('f', array('fid'))
+ ->execute()
+ ->fetchField();
+ if ($fid) {
+ $file = file_load($fid);
+ if (filefield_sources_element_validate($element, $file)) {
+ if (!in_array($file->id(), $input['fids'])) {
+ $input['fids'][] = $file->id();
+ }
+ }
+ }
+ else {
+ $form_state->setError($element, t('The selected file could not be used because the file does not exist in the database.'));
+ }
+ // No matter what happens, clear the value from the file path field.
+ $input['filefield_imce']['file_path'] = '';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+ $instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
+
+ $element['filefield_imce'] = array(
+ '#weight' => 100.5,
+ '#theme' => 'filefield_sources_element',
+ '#source_id' => 'imce',
+ // Required for proper theming.
+ '#filefield_source' => TRUE,
+ '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+ );
+
+ $filepath_id = $element['#id'] . '-imce-path';
+ $display_id = $element['#id'] . '-imce-display';
+ $select_id = $element['#id'] . '-imce-select';
+ $element['filefield_imce']['file_path'] = array(
+ // IE doesn't support onchange events for hidden fields, so we use a
+ // textfield and hide it from display.
+ '#type' => 'textfield',
+ '#value' => '',
+ '#attributes' => array(
+ 'id' => $filepath_id,
+ 'onblur' => "if (this.value.length > 0) { jQuery('#$select_id').triggerHandler('mousedown'); }",
+ 'style' => 'position:absolute; left: -9999em',
+ ),
+ );
+
+ $imce_function = 'window.open(\'' . \Drupal::url('filefield_sources.imce', array(
+ 'entity_type' => $element['#entity_type'],
+ 'bundle' => $element['#bundle'],
+ 'field_name' => $element['#field_name'],
+ ),
+ array(
+ 'query' => array(
+ 'app' => $instance->getLabel() . '|url@' . $filepath_id,
+ ),
+ )) . '\', \'\', \'width=760,height=560,resizable=1\'); return false;';
+ $element['filefield_imce']['display_path'] = array(
+ '#type' => 'markup',
+ '#markup' => '' . t('No file selected') . ' (' . t('browse') . ' )',
+ );
+
+ $ajax_settings = [
+ 'url' => Url::fromRoute('file.ajax_upload'),
+ 'options' => [
+ 'query' => [
+ 'element_parents' => implode('/', $element['#array_parents']),
+ 'form_build_id' => $complete_form['form_build_id']['#value'],
+ ],
+ ],
+ 'wrapper' => $element['#id'] . '-ajax-wrapper',
+ 'effect' => 'fade',
+ ];
+
+ $element['filefield_imce']['select'] = array(
+ '#name' => implode('_', $element['#parents']) . '_imce_select',
+ '#type' => 'submit',
+ '#value' => t('Select'),
+ '#validate' => [],
+ '#submit' => array('filefield_sources_field_submit'),
+ '#limit_validation_errors' => [$element['#parents']],
+ '#name' => $element['#name'] . '[filefield_imce][button]',
+ '#id' => $select_id,
+ '#attributes' => ['class' => ['js-hide']],
+ '#ajax' => $ajax_settings,
+ );
+
+ return $element;
+ }
+
+ /**
+ * Theme the output of the imce element.
+ */
+ public static function element($variables) {
+ $element = $variables['element'];
+
+ $output = drupal_render_children($element);
+ return '' . $output . '
';
+ }
+
+ /**
+ * Outputs the IMCE browser for FileField.
+ */
+ public static function page($entity_type, $bundle_name, $field_name) {
+ global $conf;
+
+ // Check access.
+ if (!\Drupal::moduleHandler()->moduleExists('imce') || !imce_access() || !$instance = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name)) {
+ throw new AccessDeniedHttpException();
+ }
+ $settings = $instance->getSettings();
+
+ $widget = entity_get_form_display($entity_type, $bundle_name, 'default')->getComponent($field_name);
+ // Full mode.
+ if (!empty($widget['third_party_settings']['filefield_sources']['filefield_sources']['source_imce']['imce_mode'])) {
+ $conf['imce_custom_scan'] = array(get_called_class(), 'customScanFull');
+ }
+ // Restricted mode.
+ else {
+ $conf['imce_custom_scan'] = array(get_called_class(), 'customScanRestricted');
+ $conf['imce_custom_context'] = array(
+ 'field_storage' => entity_load('field_storage_config', $entity_type . '.' . $field_name),
+ 'uri' => static::getUploadLocation($settings),
+ );
+ }
+
+ // Disable absolute URLs.
+ $conf['imce_settings_absurls'] = 0;
+
+ module_load_include('inc', 'imce', 'inc/imce.page');
+ return imce($settings['uri_scheme']);
+ }
+
+ /**
+ * Determines the URI for a file field.
+ *
+ * @param array $data
+ * An array of token objects to pass to token_replace().
+ *
+ * @return string
+ * A file directory URI with tokens replaced.
+ *
+ * @see token_replace()
+ */
+ public static function getUploadLocation($settings, $data = array()) {
+ $destination = trim($settings['file_directory'], '/');
+
+ // Replace tokens.
+ $destination = \Drupal::token()->replace($destination, $data);
+
+ return $settings['uri_scheme'] . '://' . $destination;
+ }
+
+ /**
+ * Scan and return files, subdirectories, and total size for "full" mode.
+ */
+ protected static function customScanFull($dirname, &$imce) {
+ // Get a list of files in the database for this directory.
+ $scheme = $imce['scheme'];
+ $sql_uri_name = $dirname == '.' ? $scheme . '://' : $scheme . '://' . $dirname . '/';
+
+ $result = db_select('file_managed', 'f')
+ ->fields('f', array('uri'))
+ ->condition('f.uri', $sql_uri_name . '%', 'LIKE')
+ ->condition('f.uri', $sql_uri_name . '_%/%', 'NOT LIKE')
+ ->execute();
+
+ $db_files = array();
+ foreach ($result as $row) {
+ $db_files[basename($row->uri)] = 1;
+ }
+
+ // Get the default IMCE directory scan, then filter down to database files.
+ $directory = imce_scan_directory($dirname, $imce);
+ foreach ($directory['files'] as $filename => $file) {
+ if (!isset($db_files[$filename])) {
+ unset($directory['files'][$filename]);
+ $directory['dirsize'] -= $file['size'];
+ }
+ }
+
+ return $directory;
+ }
+
+ /**
+ * Scan directory and return file list, subdirectories, and total size.
+ *
+ * This only work on Restricted Mode.
+ */
+ protected static function customScanRestricted($dirname, &$imce) {
+ $context = $GLOBALS['conf']['imce_custom_context'];
+ $field_storage = $context['field_storage'];
+ $root = $imce['scheme'] . '://';
+ $field_uri = $context['uri'];
+ $is_root = $field_uri == $root;
+
+ // Process IMCE. Make field directory the only accessible one.
+ $imce['dir'] = $is_root ? '.' : substr($field_uri, strlen($root));
+ $imce['directories'] = array();
+ if (!empty($imce['perm'])) {
+ static::disablePerms($imce, array('browse'));
+ }
+
+ // Create directory info.
+ $directory = array(
+ 'dirsize' => 0,
+ 'files' => array(),
+ 'subdirectories' => array(),
+ 'error' => FALSE,
+ );
+
+ if (isset($field_storage['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) {
+ $storage = $field_storage['storage']['details']['sql']['FIELD_LOAD_CURRENT'];
+ $table_info = reset($storage);
+ $table = key($storage);
+ $sql_uri = $field_uri . ($is_root ? '' : '/');
+ $query = db_select($table, 'cf');
+ $query->innerJoin('file_managed', 'f', 'f.fid = cf.' . $table_info['fid']);
+ $result = $query->fields('f')
+ ->condition('f.status', 1)
+ ->condition('f.uri', $sql_uri . '%', 'LIKE')
+ ->condition('f.uri', $sql_uri . '%/%', 'NOT LIKE')
+ ->execute();
+ foreach ($result as $file) {
+ // Get real name.
+ $name = basename($file->uri);
+ // Get dimensions.
+ $width = $height = 0;
+ if ($img = imce_image_info($file->uri)) {
+ $width = $img['width'];
+ $height = $img['height'];
+ }
+ $directory['files'][$name] = array(
+ 'name' => $name,
+ 'size' => $file->filesize,
+ 'width' => $width,
+ 'height' => $height,
+ 'date' => $file->timestamp,
+ );
+ $directory['dirsize'] += $file->filesize;
+ }
+ }
+
+ return $directory;
+ }
+
+ /**
+ * Disable IMCE profile permissions.
+ */
+ protected static function disablePerms(&$imce, $exceptions = array()) {
+ $disable_all = empty($exceptions);
+ foreach ($imce['perm'] as $name => $val) {
+ if ($disable_all || !in_array($name, $exceptions)) {
+ $imce['perm'][$name] = 0;
+ }
+ }
+ $imce['directories'][$imce['dir']] = array('name' => $imce['dir']) + $imce['perm'];
+ }
+
+ /**
+ * Define routes for Imce source.
+ *
+ * @return array
+ * Array of routes.
+ */
+ public static function routes() {
+ $routes = array();
+
+ $routes['filefield_sources.imce'] = new Route(
+ '/file/imce/{entity_type}/{bundle_name}/{field_name}',
+ array(
+ '_controller' => get_called_class() . '::page',
+ ),
+ array(
+ '_access_filefield_sources_field' => 'TRUE',
+ )
+ );
+
+ return $routes;
+ }
+
+ /**
+ * Implements hook_filefield_source_settings().
+ */
+ public static function settings(WidgetInterface $plugin) {
+ $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
+ 'source_imce' => array(
+ 'imce_mode' => 0,
+ ),
+ ));
+
+ $return['source_imce'] = array(
+ '#title' => t('IMCE file browser settings'),
+ '#type' => 'details',
+ '#access' => \Drupal::moduleHandler()->moduleExists('imce'),
+ );
+
+ // $imce_admin_url = \Drupal::url('imce.admin');
+ $imce_admin_url = 'admin/config/media/imce';
+ $return['source_imce']['imce_mode'] = array(
+ '#type' => 'radios',
+ '#title' => t('File browser mode'),
+ '#options' => array(
+ 0 => t('Restricted: Users can only browse the field directory. No file operations are allowed.'),
+ 1 => t('Full: Browsable directories are defined by IMCE configuration profiles . File operations are allowed.', array('!imce-admin-url' => $imce_admin_url)),
+ ),
+ '#default_value' => isset($settings['source_imce']['imce_mode']) ? $settings['source_imce']['imce_mode'] : 0,
+ );
+
+ return $return;
+
+ }
+
+}
diff --git a/src/Plugin/FilefieldSource/Reference.php b/src/Plugin/FilefieldSource/Reference.php
new file mode 100644
index 0000000..166b6a3
--- /dev/null
+++ b/src/Plugin/FilefieldSource/Reference.php
@@ -0,0 +1,214 @@
+ 0 && $input['filefield_reference']['autocomplete'] != FILEFIELD_SOURCE_REFERENCE_HINT_TEXT) {
+ $matches = array();
+ if (preg_match('/\[fid:(\d+)\]/', $input['filefield_reference']['autocomplete'], $matches)) {
+ $fid = $matches[1];
+ if ($file = file_load($fid)) {
+
+ // Remove file size restrictions, since the file already exists on
+ // disk.
+ if (isset($element['#upload_validators']['file_validate_size'])) {
+ unset($element['#upload_validators']['file_validate_size']);
+ }
+
+ // Check that the user has access to this file through
+ // hook_download().
+ if (!$file->access('download')) {
+ $form_state->setError($element, t('You do not have permission to use the selected file.'));
+ }
+ elseif (filefield_sources_element_validate($element, (object) $file, $form_state)) {
+ if (!in_array($file->id(), $input['fids'])) {
+ $input['fids'][] = $file->id();
+ }
+ }
+ }
+ else {
+ $form_state->setError($element, t('The referenced file could not be used because the file does not exist in the database.'));
+ }
+ }
+ // No matter what happens, clear the value from the autocomplete.
+ $input['filefield_reference']['autocomplete'] = '';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+
+ $element['filefield_reference'] = array(
+ '#weight' => 100.5,
+ '#theme' => 'filefield_sources_element',
+ '#source_id' => 'reference',
+ // Required for proper theming.
+ '#filefield_source' => TRUE,
+ '#filefield_sources_hint_text' => FILEFIELD_SOURCE_REFERENCE_HINT_TEXT,
+ );
+
+ $autocomplete_route_parameters = array(
+ 'entity_type' => $element['#entity_type'],
+ 'bundle_name' => $element['#bundle'],
+ 'field_name' => $element['#field_name'],
+ );
+
+ $element['filefield_reference']['autocomplete'] = array(
+ '#type' => 'textfield',
+ '#autocomplete_route_name' => 'filefield_sources.autocomplete',
+ '#autocomplete_route_parameters' => $autocomplete_route_parameters,
+ '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+ );
+
+ $ajax_settings = [
+ 'url' => Url::fromRoute('file.ajax_upload'),
+ 'options' => [
+ 'query' => [
+ 'element_parents' => implode('/', $element['#array_parents']),
+ 'form_build_id' => $complete_form['form_build_id']['#value'],
+ ],
+ ],
+ 'wrapper' => $element['#id'] . '-ajax-wrapper',
+ 'effect' => 'fade',
+ ];
+
+ $element['filefield_reference']['select'] = [
+ '#name' => implode('_', $element['#parents']) . '_autocomplete_select',
+ '#type' => 'submit',
+ '#value' => t('Select'),
+ '#validate' => [],
+ '#submit' => ['filefield_sources_field_submit'],
+ '#limit_validation_errors' => [$element['#parents']],
+ '#ajax' => $ajax_settings,
+ ];
+
+ return $element;
+ }
+
+ /**
+ * Theme the output of the reference element.
+ */
+ public static function element($variables) {
+ $element = $variables['element'];
+
+ $element['autocomplete']['#field_suffix'] = drupal_render($element['select']);
+ return '' . drupal_render($element['autocomplete']) . '
';
+ }
+
+ /**
+ * Menu callback; autocomplete.js callback to return a list of files.
+ */
+ public static function autocomplete(Request $request, $entity_type, $bundle_name, $field_name) {
+ $matches = array();
+ $string = Unicode::strtolower($request->query->get('q'));
+
+ $field_definition = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name);
+ $handler = \Drupal::getContainer()->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_definition);
+
+ if (isset($string)) {
+ // Get an array of matching entities.
+ $widget = entity_get_form_display($entity_type, $bundle_name, 'default')->getComponent($field_name);
+ $autocomplete_type = $widget['third_party_settings']['filefield_sources']['filefield_sources']['source_reference']['autocomplete'];
+ $match_operator = !empty($autocomplete_type) ? $autocomplete_type : FILEFIELD_SOURCE_REFERENCE_CONTAINS_AUTOCOMPLETE_TYPE;
+ $entity_labels = $handler->getReferenceableEntities($string, $match_operator, 10);
+
+ // Loop through the entities and convert them into autocomplete output.
+ foreach ($entity_labels as $values) {
+ foreach ($values as $entity_id => $label) {
+ $key = "$label [fid:$entity_id]";
+ // Strip things like starting/trailing white spaces, line breaks and
+ // tags.
+ $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
+ // Names containing commas or quotes must be wrapped in quotes.
+ $matches[] = array('value' => $key, 'label' => $label);
+ }
+ }
+ }
+
+ return new JsonResponse($matches);
+ }
+
+ /**
+ * Define routes for Reference source.
+ *
+ * @return array
+ * Array of routes.
+ */
+ public static function routes() {
+ $routes = array();
+
+ $routes['filefield_sources.autocomplete'] = new Route(
+ '/file/reference/{entity_type}/{bundle_name}/{field_name}',
+ array(
+ '_controller' => get_called_class() . '::autocomplete',
+ ),
+ array(
+ '_access_filefield_sources_field' => 'TRUE',
+ )
+ );
+
+ return $routes;
+ }
+
+ /**
+ * Implements hook_filefield_source_settings().
+ */
+ public static function settings(WidgetInterface $plugin) {
+ $settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
+ 'source_reference' => array(
+ 'autocomplete' => FILEFIELD_SOURCE_REFERENCE_STARTS_WITH_AUTOCOMPLETE_TYPE,
+ ),
+ ));
+
+ $return['source_reference'] = array(
+ '#title' => t('Autocomplete reference options'),
+ '#type' => 'details',
+ );
+
+ $return['source_reference']['autocomplete'] = array(
+ '#title' => t('Match file name'),
+ '#options' => array(
+ FILEFIELD_SOURCE_REFERENCE_STARTS_WITH_AUTOCOMPLETE_TYPE => t('Starts with'),
+ FILEFIELD_SOURCE_REFERENCE_CONTAINS_AUTOCOMPLETE_TYPE => t('Contains'),
+ ),
+ '#type' => 'radios',
+ '#default_value' => isset($settings['source_reference']['autocomplete']) ? $settings['source_reference']['autocomplete'] : FILEFIELD_SOURCE_REFERENCE_STARTS_WITH_AUTOCOMPLETE_TYPE,
+ );
+
+ return $return;
+ }
+
+}
diff --git a/src/Plugin/FilefieldSource/Remote.php b/src/Plugin/FilefieldSource/Remote.php
new file mode 100644
index 0000000..260e701
--- /dev/null
+++ b/src/Plugin/FilefieldSource/Remote.php
@@ -0,0 +1,396 @@
+ 0 && UrlHelper::isValid($input['filefield_remote']['url']) && $input['filefield_remote']['url'] != FILEFIELD_SOURCE_REMOTE_HINT_TEXT) {
+ $field = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
+ $url = $input['filefield_remote']['url'];
+
+ // Check that the destination is writable.
+ $temporary_directory = 'temporary://';
+ if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
+ \Drupal::logger('filefield_sources')->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
+ drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
+ return;
+ }
+
+ // Check that the destination is writable.
+ $directory = $element['#upload_location'];
+ $mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
+
+ // This first chmod check is for other systems such as S3, which don't
+ // work with file_prepare_directory().
+ if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
+ \Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
+ drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
+ return;
+ }
+
+ // Check the headers to make sure it exists and is within the allowed
+ // size.
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, TRUE);
+ curl_setopt($ch, CURLOPT_NOBODY, TRUE);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(get_called_class(), 'parseHeader'));
+ // Causes a warning if PHP safe mode is on.
+ @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
+ curl_exec($ch);
+ $info = curl_getinfo($ch);
+ if ($info['http_code'] != 200) {
+ curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
+ $file_contents = curl_exec($ch);
+ $info = curl_getinfo($ch);
+ }
+ curl_close($ch);
+
+ if ($info['http_code'] != 200) {
+ switch ($info['http_code']) {
+ case 403:
+ $form_state->setError($element, t('The remote file could not be transferred because access to the file was denied.'));
+ break;
+
+ case 404:
+ $form_state->setError($element, t('The remote file could not be transferred because it was not found.'));
+ break;
+
+ default:
+ $form_state->setError($element, t('The remote file could not be transferred due to an HTTP error (@code).', array('@code' => $info['http_code'])));
+ }
+ return;
+ }
+
+ // Update the $url variable to reflect any redirects.
+ $url = $info['url'];
+ $url_info = parse_url($url);
+
+ // Determine the proper filename by reading the filename given in the
+ // Content-Disposition header. If the server fails to send this header,
+ // fall back on the basename of the URL.
+ //
+ // We prefer to use the Content-Disposition header, because we can then
+ // use URLs like http://example.com/get_file/23 which would otherwise be
+ // rejected because the URL basename lacks an extension.
+ $filename = static::filename();
+ if (empty($filename)) {
+ $filename = rawurldecode(basename($url_info['path']));
+ }
+
+ $pathinfo = pathinfo($filename);
+
+ // Create the file extension from the MIME header if all else has failed.
+ if (empty($pathinfo['extension']) && $extension = static::mimeExtension()) {
+ $filename = $filename . '.' . $extension;
+ $pathinfo = pathinfo($filename);
+ }
+
+ $filename = filefield_sources_clean_filename($filename, $field->settings['file_extensions']);
+ $filepath = file_create_filename($filename, $temporary_directory);
+
+ if (empty($pathinfo['extension'])) {
+ $form_state->setError($element, t('The remote URL must be a file and have an extension.'));
+ return;
+ }
+
+ // Perform basic extension check on the file before trying to transfer.
+ $extensions = $field->settings['file_extensions'];
+ $regex = '/\.(' . preg_replace('/[ +]/', '|', preg_quote($extensions)) . ')$/i';
+ if (!empty($extensions) && !preg_match($regex, $filename)) {
+ $form_state->setError($element, t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)));
+ return;
+ }
+
+ // Check file size based off of header information.
+ if (!empty($element['#upload_validators']['file_validate_size'][0])) {
+ $max_size = $element['#upload_validators']['file_validate_size'][0];
+ $file_size = $info['download_content_length'];
+ if ($file_size > $max_size) {
+ $form_state->setError($element, t('The remote file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file_size), '%maxsize' => format_size($max_size))));
+ return;
+ }
+ }
+
+ // Set progress bar information.
+ $options = array(
+ 'key' => $element['#entity_type'] . '_' . $element['#bundle'] . '_' . $element['#field_name'] . '_' . $element['#delta'],
+ 'filepath' => $filepath,
+ );
+ static::setTransferOptions($options);
+
+ $transfer_success = FALSE;
+ // If we've already downloaded the entire file because the
+ // header-retrieval failed, just ave the contents we have.
+ if (isset($file_contents)) {
+ if ($fp = @fopen($filepath, 'w')) {
+ fwrite($fp, $file_contents);
+ fclose($fp);
+ $transfer_success = TRUE;
+ }
+ }
+ // If we don't have the file contents, download the actual file.
+ else {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, FALSE);
+ curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(get_called_class(), 'curlWrite'));
+ // Causes a warning if PHP safe mode is on.
+ @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
+ $transfer_success = curl_exec($ch);
+ curl_close($ch);
+ }
+ if ($transfer_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
+ if (!in_array($file->id(), $input['fids'])) {
+ $input['fids'][] = $file->id();
+ }
+ }
+
+ // Delete the temporary file.
+ @unlink($filepath);
+ }
+ }
+
+ /**
+ * Set a transfer key that can be retreived by the progress function.
+ */
+ protected static function setTransferOptions($options = NULL) {
+ static $current = FALSE;
+ if (isset($options)) {
+ $current = $options;
+ }
+ return $current;
+ }
+
+ /**
+ * Get a transfer key that can be retrieved by the progress function.
+ */
+ protected static function getTransferOptions() {
+ return static::setTransferOptions();
+ }
+
+ /**
+ * Save the file to disk. Also updates progress bar.
+ */
+ protected static function curlWrite(&$ch, $data) {
+ $progress_update = 0;
+ $options = static::getTransferOptions();
+
+ // Get the current progress and update the progress value.
+ // Only update every 64KB to reduce Drupal::cache()->set() calls.
+ // cURL usually writes in 16KB chunks.
+ if (curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) / 65536 > $progress_update) {
+ $progress_update++;
+ $progress = array(
+ 'current' => curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD),
+ 'total' => curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD),
+ );
+ // Set a cache so that we can retrieve this value from the progress bar.
+ $cid = 'filefield_transfer:' . session_id() . ':' . $options['key'];
+ if ($progress['current'] != $progress['total']) {
+ \Drupal::cache()->set($cid, $progress, time() + 300);
+ }
+ else {
+ \Drupal::cache()->delete($cid);
+ }
+ }
+
+ $data_length = 0;
+ if ($fp = @fopen($options['filepath'], 'a')) {
+ fwrite($fp, $data);
+ fclose($fp);
+ $data_length = strlen($data);
+ }
+
+ return $data_length;
+ }
+
+ /**
+ * Parse cURL header and record the filename specified in Content-Disposition.
+ */
+ protected static function parseHeader(&$ch, $header) {
+ if (preg_match('/Content-Disposition:.*?filename="(.+?)"/', $header, $matches)) {
+ // Content-Disposition: attachment; filename="FILE NAME HERE"
+ static::filename($matches[1]);
+ }
+ elseif (preg_match('/Content-Disposition:.*?filename=([^; ]+)/', $header, $matches)) {
+ // Content-Disposition: attachment; filename=file.ext
+ $uri = trim($matches[1]);
+ static::filename($uri);
+ }
+ elseif (preg_match('/Content-Type:[ ]*([a-z0-9_\-]+\/[a-z0-9_\-]+)/i', $header, $matches)) {
+ $mime_type = $matches[1];
+ static::mimeExtension($mime_type);
+ }
+
+ // This is required by cURL.
+ return strlen($header);
+ }
+
+ /**
+ * Get/set the remote file extension in a static variable.
+ */
+ protected static function mimeExtension($curl_mime_type = NULL) {
+ static $extension = NULL;
+ $mimetype = Unicode::strtolower($curl_mime_type);
+ $result = \Drupal::service('file.mime_type.guesser.extension')->convertMimeTypeToMostCommonExtension($mimetype);
+ if ($result) {
+ $extension = $result;
+ }
+ return $extension;
+ }
+
+ /**
+ * Get/set the remote file name in a static variable.
+ */
+ protected static function filename($curl_filename = NULL) {
+ static $filename = NULL;
+ if (isset($curl_filename)) {
+ $filename = $curl_filename;
+ }
+ return $filename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
+
+ $element['filefield_remote'] = array(
+ '#weight' => 100.5,
+ '#theme' => 'filefield_sources_element',
+ '#source_id' => 'remote',
+ // Required for proper theming.
+ '#filefield_source' => TRUE,
+ '#filefield_sources_hint_text' => FILEFIELD_SOURCE_REMOTE_HINT_TEXT,
+ );
+
+ $element['filefield_remote']['url'] = array(
+ '#type' => 'textfield',
+ '#description' => filefield_sources_element_validation_help($element['#upload_validators']),
+ '#maxlength' => NULL,
+ );
+
+ $ajax_settings = [
+ 'url' => Url::fromRoute('file.ajax_upload'),
+ 'options' => [
+ 'query' => [
+ 'element_parents' => implode('/', $element['#array_parents']),
+ 'form_build_id' => $complete_form['form_build_id']['#value'],
+ ],
+ ],
+ 'wrapper' => $element['#id'] . '-ajax-wrapper',
+ 'effect' => 'fade',
+ 'progress' => [
+ 'type' => 'bar',
+ 'path' => 'file/remote/progress/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'] . '/' . $element['#delta'],
+ 'message' => t('Starting transfer...'),
+ ],
+ ];
+
+ $element['filefield_remote']['transfer'] = [
+ '#name' => implode('_', $element['#parents']) . '_transfer',
+ '#type' => 'submit',
+ '#value' => t('Transfer'),
+ '#validate' => array(),
+ '#submit' => ['filefield_sources_field_submit'],
+ '#limit_validation_errors' => [$element['#parents']],
+ '#ajax' => $ajax_settings,
+ ];
+
+ return $element;
+ }
+
+ /**
+ * Theme the output of the remote element.
+ */
+ public static function element($variables) {
+ $element = $variables['element'];
+
+ $element['url']['#field_suffix'] = drupal_render($element['transfer']);
+ return '' . drupal_render($element['url']) . '
';
+ }
+
+ /**
+ * Menu callback; progress.js callback to return upload progress.
+ */
+ public static function progress($entity_type, $bundle_name, $field_name, $delta) {
+ $key = $entity_type . '_' . $bundle_name . '_' . $field_name . '_' . $delta;
+ $progress = array(
+ 'message' => t('Starting transfer...'),
+ 'percentage' => -1,
+ );
+
+ if ($cache = \Drupal::cache()->get('filefield_transfer:' . session_id() . ':' . $key)) {
+ $current = $cache->data['current'];
+ $total = $cache->data['total'];
+ $progress['message'] = t('Transferring... (@current of @total)', array('@current' => format_size($current), '@total' => format_size($total)));
+ $progress['percentage'] = round(100 * $current / $total);
+ }
+
+ return new JsonResponse($progress);
+ }
+
+ /**
+ * Define routes for Remote source.
+ *
+ * @return array
+ * Array of routes.
+ */
+ public static function routes() {
+ $routes = array();
+
+ $routes['filefield_sources.remote'] = new Route(
+ '/file/remote/progress/{entity_type}/{bundle_name}/{field_name}/{delta}',
+ array(
+ '_controller' => get_called_class() . '::progress',
+ ),
+ array(
+ '_access' => 'TRUE',
+ )
+ );
+
+ return $routes;
+ }
+
+ /**
+ * Implements hook_filefield_source_settings().
+ */
+ public static function settings(WidgetInterface $plugin) {
+ $return = array();
+
+ return $return;
+
+ }
+
+}
diff --git a/src/Routing/FilefieldSourcesRoutes.php b/src/Routing/FilefieldSourcesRoutes.php
new file mode 100644
index 0000000..0530566
--- /dev/null
+++ b/src/Routing/FilefieldSourcesRoutes.php
@@ -0,0 +1,35 @@
+getDefinitions() as $definition) {
+ // Get routes defined by each plugin.
+ $callback = array($definition['class'], 'routes');
+ if (is_callable($callback)) {
+ $routes = array_merge($routes, call_user_func($callback));
+ }
+ }
+
+ return $routes;
+ }
+
+}
diff --git a/src/Tests/AttachSourceTest.php b/src/Tests/AttachSourceTest.php
new file mode 100644
index 0000000..6f3afda
--- /dev/null
+++ b/src/Tests/AttachSourceTest.php
@@ -0,0 +1,149 @@
+xpath('//select[@name=:name]/option[@value=:option]', array(
+ ':name' => $this->fieldName . '[0][filefield_attach][filename]',
+ ':option' => $uri,
+ ));
+ return isset($options[0]);
+ }
+
+ /**
+ * Check to see if can attach file.
+ *
+ * @param object $file
+ * File to attach.
+ */
+ public function assertCanAttachFile($file) {
+ // Ensure option is present.
+ $this->assertTrue($this->isOptionPresent($file->uri), 'File option is present.');
+
+ // Ensure empty message is not present.
+ $this->assertNoText('There currently are no files to attach.', "Empty message is not present.");
+
+ // Attach button is always present.
+ $this->assertFieldByXpath('//input[@type="submit"]', t('Attach'), 'Attach button is present.');
+ }
+
+ /**
+ * Check to see if can attach file.
+ *
+ * @param object $file
+ * File to attach.
+ */
+ public function assertCanNotAttachFile($file) {
+ // Ensure option is not present.
+ $this->assertFalse($this->isOptionPresent($file->uri), 'File option is not present.');
+
+ // Ensure empty message is present.
+ $this->assertText('There currently are no files to attach.', "Empty message is present.");
+
+ // Attach button is always present.
+ $this->assertFieldByXpath('//input[@type="submit"]', t('Attach'), 'Attach button is present.');
+ }
+
+ /**
+ * Tests move file from relative path.
+ *
+ * Default settings: Move file from 'public://file_attach' to 'public://'.
+ */
+ public function testMoveFileFromRelativePath() {
+ // Create test file.
+ $path = file_default_scheme() . '://' . FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH;
+ $file = $this->createTemporaryFile($path);
+ $dest_uri = file_default_scheme() . '://' . $file->filename;
+
+ $this->enableSources(array(
+ 'attach' => TRUE,
+ ));
+
+ $this->assertCanAttachFile($file);
+
+ // Upload a file.
+ $this->uploadFileByAttachSource($file->uri, $file->filename, 0);
+
+ // We can only attach one file on single value field.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Attach'), 'After uploading a file, "Attach" button is no longer displayed.');
+
+ // Ensure file is moved.
+ $this->assertFalse(is_file($file->uri), 'Source file has been removed.');
+ $this->assertTrue(is_file($dest_uri), 'Destination file has been created.');
+
+ $this->removeFile($file->filename, 0);
+
+ $this->assertCanNotAttachFile($file);
+ }
+
+ /**
+ * Calculate custom absolute path.
+ */
+ public function getCustomAttachPath() {
+ $path = drupal_realpath(file_default_scheme() . '://');
+ $path = str_replace(realpath('./'), '', $path);
+ $path = ltrim($path, '/');
+ $path = $path . '/custom_file_attach';
+ return $path;
+ }
+
+ /**
+ * Tests copy file from absolute path.
+ *
+ * Copy file from 'sites/default/files/custom_file_attach' to 'public://'.
+ */
+ public function testCopyFileFromAbsolutePath() {
+ $path = $this->getCustomAttachPath();
+
+ // Create test file.
+ $file = $this->createTemporaryFile($path);
+ $dest_uri = file_default_scheme() . '://' . $file->filename;
+
+ // Change settings.
+ $this->updateFilefieldSourcesSettings('source_attach', 'path', $path);
+ $this->updateFilefieldSourcesSettings('source_attach', 'absolute', FILEFIELD_SOURCE_ATTACH_ABSOLUTE);
+ $this->updateFilefieldSourcesSettings('source_attach', 'attach_mode', FILEFIELD_SOURCE_ATTACH_MODE_COPY);
+
+ $this->enableSources(array(
+ 'attach' => TRUE,
+ ));
+
+ $this->assertCanAttachFile($file);
+
+ // Upload a file.
+ $this->uploadFileByAttachSource($file->uri, $file->filename, 0);
+
+ // We can only attach one file on single value field.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Attach'), 'After uploading a file, "Attach" button is no longer displayed.');
+
+ // Ensure file is copied.
+ $this->assertTrue(is_file($file->uri), 'Source file still exists.');
+ $this->assertTrue(is_file($dest_uri), 'Destination file has been created.');
+
+ $this->removeFile($file->filename, 0);
+
+ $this->assertCanAttachFile($file);
+ }
+
+}
diff --git a/src/Tests/ClipboardSourceTest.php b/src/Tests/ClipboardSourceTest.php
new file mode 100644
index 0000000..215883a
--- /dev/null
+++ b/src/Tests/ClipboardSourceTest.php
@@ -0,0 +1,37 @@
+enableSources(array(
+ 'clipboard' => TRUE,
+ ));
+ $file = $this->createTemporaryFileEntity();
+
+ $this->uploadFileByClipboardSource($file->getFileUri(), $file->getFilename(), 0);
+
+ // We can only upload one file on single value field.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('After uploading a file, "Upload" button is no longer displayed.'));
+
+ $this->removeFile($file->getFilename(), 0);
+
+ // Can upload file again.
+ $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
+ }
+
+}
diff --git a/src/Tests/EmptyValuesTest.php b/src/Tests/EmptyValuesTest.php
new file mode 100644
index 0000000..317cb96
--- /dev/null
+++ b/src/Tests/EmptyValuesTest.php
@@ -0,0 +1,70 @@
+drupalPostForm('admin/structure/types/manage/' . $this->typeName . '/fields/node.' . $this->typeName . '.' . $this->fieldName . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
+
+ $this->enableSources(array(
+ 'upload' => TRUE,
+ 'remote' => TRUE,
+ 'clipboard' => TRUE,
+ 'reference' => TRUE,
+ 'attach' => TRUE,
+ ));
+
+ // Upload a file by 'Remote' source.
+ $this->uploadFileByRemoteSource();
+
+ // Upload a file by 'Reference' source.
+ $this->uploadFileByReferenceSource();
+
+ // Upload a file by 'Clipboard' source.
+ $this->uploadFileByClipboardSource();
+
+ // Upload a file by 'Attach' source.
+ $this->uploadFileByAttachSource();
+
+ // Upload a file by 'Upload' source.
+ $this->uploadFileByUploadSource('', '', 0, TRUE);
+
+ $this->assertUniqueSubmitButtons();
+ }
+
+ /**
+ * Check that there is only one submit button of a source.
+ */
+ protected function assertUniqueSubmitButtons() {
+ $buttons = array(
+ $this->fieldName . '_0_attach' => t('Attach'),
+ $this->fieldName . '_0_clipboard_upload_button' => t('Upload'),
+ $this->fieldName . '_0_autocomplete_select' => t('Select'),
+ $this->fieldName . '_0_transfer' => t('Transfer'),
+ $this->fieldName . '_0_upload_button' => t('Upload'),
+ );
+ foreach ($buttons as $button_name => $button_label) {
+ // Ensure that there is only one button with name.
+ $buttons = $this->xpath('//input[@name="' . $button_name . '" and @value="' . $button_label . '"]');
+ $this->assertEqual(count($buttons), 1, format_string('There is only one button with name %name and label %label', array('%name' => $button_name, '%label' => $button_label)));
+ }
+ }
+
+}
diff --git a/src/Tests/FileFieldSourcesTestBase.php b/src/Tests/FileFieldSourcesTestBase.php
new file mode 100644
index 0000000..abe0eb4
--- /dev/null
+++ b/src/Tests/FileFieldSourcesTestBase.php
@@ -0,0 +1,395 @@
+adminUser = $this->drupalCreateUser(array(
+ 'access content',
+ 'access administration pages',
+ 'administer site configuration',
+ 'administer users',
+ 'administer permissions',
+ 'administer content types',
+ 'administer node fields',
+ 'administer node display',
+ 'administer node form display',
+ 'administer nodes',
+ 'bypass node access',
+ ));
+ $this->drupalLogin($this->adminUser);
+
+ // Create content type.
+ $this->typeName = 'article';
+ $this->drupalCreateContentType(array('type' => $this->typeName, 'name' => 'Article'));
+
+ // Add node.
+ $this->node = $this->drupalCreateNode();
+
+ // Add file field.
+ $this->fieldName = strtolower($this->randomMachineName());
+ $this->createFileField($this->fieldName, 'node', $this->typeName);
+ }
+
+ /**
+ * Enable file field sources.
+ *
+ * @param array $sources
+ * List of sources to enable or disable. e.g
+ * array(
+ * 'upload' => FALSE,
+ * 'remote' => TRUE,
+ * ).
+ */
+ public function enableSources($sources = array()) {
+ $sources += array('upload' => TRUE);
+ $map = array(
+ 'upload' => 'Upload',
+ 'remote' => 'Remote URL',
+ 'clipboard' => 'Clipboard',
+ 'reference' => 'Reference existing',
+ 'attach' => 'File attach',
+ );
+ $sources = array_intersect_key($sources, $map);
+ ksort($sources);
+
+ // Upload source enabled by default.
+ $manage_display = 'admin/structure/types/manage/' . $this->typeName . '/form-display';
+ $this->drupalGet($manage_display);
+ $this->assertText("File field sources: upload", 'The expected summary is displayed.');
+
+ // Click on the widget settings button to open the widget settings form.
+ $this->drupalPostAjaxForm(NULL, array(), $this->fieldName . "_settings_edit");
+
+ // Enable sources.
+ $prefix = 'fields[' . $this->fieldName . '][settings_edit_form][third_party_settings][filefield_sources][filefield_sources][sources]';
+ $edit = array();
+ foreach ($sources as $source => $enabled) {
+ $edit[$prefix . '[' . $source . ']'] = $enabled ? TRUE : FALSE;
+ }
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_plugin_settings_update' => t('Update')));
+ $this->assertText("File field sources: " . implode(', ', array_keys($sources)), 'The expected summary is displayed.');
+
+ // Save the form to save the third party settings.
+ $this->drupalPostForm(NULL, array(), t('Save'));
+
+ $add_node = 'node/add/' . $this->typeName;
+ $this->drupalGet($add_node);
+ if (count($sources) > 1) {
+ // We can swith between sources.
+ foreach ($sources as $source => $enabled) {
+ $label = $map[$source];
+ $this->assertLink($label);
+ }
+ }
+ else {
+ foreach ($map as $source => $label) {
+ $this->assertNoLink($label);
+ }
+ }
+ }
+
+ /**
+ * Create permanent file entity.
+ *
+ * @return object
+ * Permanent file entity.
+ */
+ public function createPermanentFileEntity() {
+ $file = $this->createTemporaryFileEntity();
+ // Only permanent file can be referred.
+ $file->status = FILE_STATUS_PERMANENT;
+ // Author has permission to access file.
+ $file->uid = $this->adminUser->id();
+ $file->save();
+
+ // Permanent file must be used by an entity.
+ \Drupal::service('file.usage')->add($file, 'file', 'node', $this->node->id());
+
+ return $file;
+ }
+
+ /**
+ * Create temporary file entity.
+ *
+ * @return object
+ * Temporary file entity.
+ */
+ public function createTemporaryFileEntity() {
+ $file = $this->createTemporaryFile();
+
+ // Add a filesize property to files as would be read by file_load().
+ $file->filesize = filesize($file->uri);
+
+ return entity_create('file', (array) $file);
+ }
+
+ /**
+ * Create temporary file.
+ *
+ * @return object
+ * Permanent file object.
+ */
+ public function createTemporaryFile($path = '') {
+ $filename = $this->randomMachineName() . '.txt';
+ if (empty($path)) {
+ $path = file_default_scheme() . '://';
+ }
+ $uri = $path . '/' . $filename;
+ $contents = $this->randomString();
+
+ // Change mode so that we can create files.
+ file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+ drupal_chmod($path, FILE_CHMOD_DIRECTORY);
+
+ file_put_contents($uri, $contents);
+ $this->assertTrue(is_file($uri), 'The temporary file has been created.');
+
+ // Change mode so that we can delete created file.
+ drupal_chmod($uri, FILE_CHMOD_FILE);
+
+ // Return object similar to file_scan_directory().
+ $file = new \stdClass();
+ $file->uri = $uri;
+ $file->filename = $filename;
+ $file->name = pathinfo($filename, PATHINFO_FILENAME);
+ return $file;
+ }
+
+ /**
+ * Update file field sources settings.
+ *
+ * @param string $source_key
+ * Wrapper, defined by each source.
+ * @param string $key
+ * Key, defined by each source.
+ * @param mixed $value
+ * Value to set.
+ */
+ public function updateFilefieldSourcesSettings($source_key, $key, $value) {
+ $manage_display = 'admin/structure/types/manage/' . $this->typeName . '/form-display';
+ $this->drupalGet($manage_display);
+
+ // Click on the widget settings button to open the widget settings form.
+ $this->drupalPostAjaxForm(NULL, array(), $this->fieldName . "_settings_edit");
+
+ // Update settings.
+ $name = 'fields[' . $this->fieldName . '][settings_edit_form][third_party_settings][filefield_sources][filefield_sources]' . "[$source_key][$key]";
+ $edit = array($name => $value);
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_plugin_settings_update' => t('Update')));
+
+ // Save the form to save the third party settings.
+ $this->drupalPostForm(NULL, array(), t('Save'));
+ }
+
+ /**
+ * Upload file by 'Attach' source.
+ *
+ * @param string $uri
+ * File uri.
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function uploadFileByAttachSource($uri = '', $filename = '', $delta = 0) {
+ if ($uri) {
+ $edit = array(
+ $this->fieldName . '[' . $delta . '][filefield_attach][filename]' => $uri,
+ );
+ }
+ else {
+ $edit = array();
+ }
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_attach' => t('Attach')));
+
+ if ($filename) {
+ $this->assertFileUploaded($filename, $delta);
+ }
+ else {
+ $this->assertFileNotUploaded($delta);
+ }
+ }
+
+ /**
+ * Upload file by 'Reference' source.
+ *
+ * @param int $fid
+ * File id.
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function uploadFileByReferenceSource($fid = 0, $filename = '', $delta = 0) {
+ $name = $this->fieldName . '[' . $delta . '][filefield_reference][autocomplete]';
+ $value = $fid ? $filename . ' [fid:' . $fid . ']' : '';
+ $edit = array($name => $value);
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_autocomplete_select' => t('Select')));
+
+ if ($filename) {
+ $this->assertFileUploaded($filename, $delta);
+ }
+ else {
+ $this->assertFileNotUploaded($delta);
+ }
+ }
+
+ /**
+ * Upload file by 'Clipboard' source.
+ *
+ * @param string $uri
+ * File uri.
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function uploadFileByClipboardSource($uri = '', $filename = '', $delta = 0) {
+ $prefix = $this->fieldName . '[' . $delta . '][filefield_clipboard]';
+ $file_content = $uri ? 'data:text/plain;base64,' . base64_encode(file_get_contents($uri)) : '';
+ $edit = array(
+ $prefix . '[filename]' => $filename,
+ $prefix . '[contents]' => $file_content,
+ );
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_clipboard_upload_button' => t('Upload')));
+
+ if ($filename) {
+ $this->assertFileUploaded($filename, $delta);
+ }
+ else {
+ $this->assertFileNotUploaded($delta);
+ }
+ }
+
+ /**
+ * Upload file by 'Remote' source.
+ *
+ * @param string $url
+ * File url.
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function uploadFileByRemoteSource($url = '', $filename = '', $delta = 0) {
+ $name = $this->fieldName . '[' . $delta . '][filefield_remote][url]';
+ $edit = array($name => $url);
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_transfer' => t('Transfer')));
+
+ if ($filename) {
+ $this->assertFileUploaded($filename, $delta);
+ }
+ else {
+ $this->assertFileNotUploaded($delta);
+ }
+ }
+
+ /**
+ * Upload file by 'Upload' source.
+ *
+ * @param string $uri
+ * File uri.
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function uploadFileByUploadSource($uri = '', $filename = '', $delta = 0, $multiple = FALSE) {
+ $name = 'files[' . $this->fieldName . '_' . $delta . ']';
+ if ($multiple) {
+ $name .= '[]';
+ }
+ $edit = array(
+ $name => $uri ? drupal_realpath($uri) : '',
+ );
+ $this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_upload_button' => t('Upload')));
+
+ if ($filename) {
+ $this->assertFileUploaded($filename, $delta);
+ }
+ else {
+ $this->assertFileNotUploaded($delta);
+ }
+ }
+
+ /**
+ * Check to see if file is uploaded.
+ *
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function assertFileUploaded($filename, $delta = 0) {
+ $this->assertLink($filename);
+ $this->assertFieldByXPath('//input[@name="' . $this->fieldName . '_' . $delta . '_remove_button"]', t('Remove'), 'After uploading a file, "Remove" button is displayed.');
+ }
+
+ /**
+ * Check to see if file is not uploaded.
+ *
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function assertFileNotUploaded($delta = 0) {
+ $this->assertNoFieldByXPath('//input[@name="' . $this->fieldName . '_' . $delta . '_remove_button"]', t('Remove'), '"Remove" button is not displayed.');
+ }
+
+ /**
+ * Remove uploaded file.
+ *
+ * @param string $filename
+ * File name.
+ * @param int $delta
+ * Delta in multiple values field.
+ */
+ public function removeFile($filename, $delta = 0) {
+ $this->drupalPostAjaxForm(NULL, array(), array($this->fieldName . '_' . $delta . '_remove_button' => t('Remove')));
+
+ // Ensure file is removed.
+ $this->assertFileRemoved($filename);
+ }
+
+ /**
+ * Check to see if file is removed.
+ *
+ * @param string $filename
+ * File name.
+ */
+ public function assertFileRemoved($filename) {
+ $this->assertNoLink($filename);
+ }
+
+}
diff --git a/src/Tests/MultipleValuesTest.php b/src/Tests/MultipleValuesTest.php
new file mode 100644
index 0000000..f388bdf
--- /dev/null
+++ b/src/Tests/MultipleValuesTest.php
@@ -0,0 +1,117 @@
+permanent_file_entity = $this->createPermanentFileEntity();
+ $this->temporary_file_entity_1 = $this->createTemporaryFileEntity();
+ $this->temporary_file_entity_2 = $this->createTemporaryFileEntity();
+
+ $path = file_default_scheme() . '://' . FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH;
+ $this->temporary_file = $this->createTemporaryFile($path);
+
+ // Change allowed number of values.
+ $this->drupalPostForm('admin/structure/types/manage/' . $this->typeName . '/fields/node.' . $this->typeName . '.' . $this->fieldName . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
+
+ $this->enableSources(array(
+ 'upload' => TRUE,
+ 'remote' => TRUE,
+ 'clipboard' => TRUE,
+ 'reference' => TRUE,
+ 'attach' => TRUE,
+ ));
+ }
+
+ /**
+ * Tests uploading then removing files.
+ */
+ public function testUploadThenRemoveFiles() {
+ $this->uploadFiles();
+
+ // Remove all uploaded files.
+ $this->removeFile($this->temporary_file_entity_2->getFilename(), 4);
+ $this->removeFile('INSTALL.txt', 0);
+ $this->removeFile($this->temporary_file_entity_1->getFilename(), 1);
+ $this->removeFile($this->temporary_file->filename, 1);
+ $this->removeFile($this->permanent_file_entity->getFilename(), 0);
+
+ // Ensure all files have been removed.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'All files have been removed.');
+ }
+
+ /**
+ * Tests uploading files and saving node.
+ */
+ public function testUploadFilesThenSaveNode() {
+ $this->uploadFiles();
+
+ $this->drupalPostForm(NULL, array('title[0][value]' => $this->randomMachineName()), t('Save and publish'));
+
+ // Ensure all files are saved to node.
+ $this->assertLink('INSTALL.txt');
+ $this->assertLink($this->permanent_file_entity->getFilename());
+ $this->assertLink($this->temporary_file_entity_1->getFilename());
+ $this->assertLink($this->temporary_file_entity_2->getFilename());
+ $this->assertLink($this->temporary_file->filename);
+ }
+
+ /**
+ * Upload files.
+ *
+ * @return int
+ * Number of files uploaded.
+ */
+ protected function uploadFiles() {
+ $uploaded_files = 0;
+
+ // Ensure no files has been uploaded.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'There are no file have been uploaded.');
+
+ // Upload a file by 'Remote' source.
+ $this->uploadFileByRemoteSource($GLOBALS['base_url'] . '/core/INSTALL.txt', 'INSTALL.txt', $uploaded_files);
+ $uploaded_files++;
+
+ // Upload a file by 'Reference' source.
+ $this->uploadFileByReferenceSource($this->permanent_file_entity->id(), $this->permanent_file_entity->getFilename(), $uploaded_files);
+ $uploaded_files++;
+
+ // Upload a file by 'Clipboard' source.
+ $this->uploadFileByClipboardSource($this->temporary_file_entity_1->getFileUri(), $this->temporary_file_entity_1->getFileName(), $uploaded_files);
+ $uploaded_files++;
+
+ // Upload a file by 'Attach' source.
+ $this->uploadFileByAttachSource($this->temporary_file->uri, $this->temporary_file->filename, $uploaded_files);
+ $uploaded_files++;
+
+ // Upload a file by 'Upload' source.
+ $this->uploadFileByUploadSource($this->temporary_file_entity_2->getFileUri(), $this->temporary_file_entity_2->getFilename(), $uploaded_files, TRUE);
+ $uploaded_files++;
+
+ // Ensure files have been uploaded.
+ $remove_buttons = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]');
+ $this->assertEqual(count($remove_buttons), $uploaded_files, "There are $uploaded_files files have been uploaded.");
+
+ return $uploaded_files;
+ }
+
+}
diff --git a/src/Tests/ReferenceSourceTest.php b/src/Tests/ReferenceSourceTest.php
new file mode 100644
index 0000000..af5eb48
--- /dev/null
+++ b/src/Tests/ReferenceSourceTest.php
@@ -0,0 +1,109 @@
+createPermanentFileEntity();
+
+ $this->enableSources(array(
+ 'reference' => TRUE,
+ ));
+
+ // Upload a file by 'Reference' source.
+ $this->uploadFileByReferenceSource($file->id(), $file->getFilename(), 0);
+
+ // We can only refer one file on single value field.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Select'), t('After uploading a file, "Select" button is no longer displayed.'));
+
+ // Remove uploaded file.
+ $this->removeFile($file->getFileName(), 0);
+
+ // Can select file again.
+ $this->assertFieldByXpath('//input[@type="submit"]', t('Select'), 'After clicking the "Remove" button, the "Select" button is displayed.');
+ }
+
+ /**
+ * Test autocompletion.
+ */
+ public function testAutocompletion() {
+ // Create test file.
+ $file = $this->createPermanentFileEntity();
+ $filename = $file->getFileName();
+ $first_character = substr($filename, 0, 1);
+ $second_character = substr($filename, 1, 1);
+
+ // Switch to 'Starts with' match type.
+ $this->updateFilefieldSourcesSettings('source_reference', 'autocomplete', 'STARTS_WITH');
+
+ // STARTS_WITH: empty results.
+ $query = $this->findCharacterNotInString($first_character);
+ $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+ $this->assertEqual($autocomplete_result, array(), "No files that have name starts with '$query'");
+
+ // STARTS_WITH: not empty results.
+ $query = $first_character;
+ $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+ $this->assertEqual($autocomplete_result[0]['label'], $filename, 'Autocompletion return correct label.');
+ $this->assertEqual($autocomplete_result[0]['value'], $filename . ' [fid:' . $file->id() . ']', 'Autocompletion return correct value.');
+
+ // Switch to 'Contains' match type.
+ $this->updateFilefieldSourcesSettings('source_reference', 'autocomplete', 'CONTAINS');
+
+ // CONTAINS: empty results.
+ $query = $this->findCharacterNotInString($filename);
+ $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+ $this->assertEqual($autocomplete_result, array(), "No files that have name contains '$query'");
+
+ // CONTAINS: not empty results.
+ $query = $second_character;
+ $autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
+ $this->assertEqual($autocomplete_result[0]['label'], $filename, 'Autocompletion return correct label.');
+ $this->assertEqual($autocomplete_result[0]['value'], $filename . ' [fid:' . $file->id() . ']', 'Autocompletion return correct value.');
+ }
+
+ /**
+ * Find the first character that is not in string.
+ *
+ * Only find for lower case character.
+ *
+ * @param string $string
+ * String to check.
+ *
+ * @return string
+ * First character that is not in the string.
+ */
+ protected function findCharacterNotInString($string) {
+ // Only check for lower case string.
+ $string = Unicode::strtolower($string);
+
+ // Lower case characters and numbers generated by
+ // \Drupal\simpletest\TestBase::randomMachineName().
+ $values = array_merge(range(97, 122), range(48, 57));
+ foreach ($values as $value) {
+ $character = chr($value);
+ if (strpos($string, $character) === FALSE) {
+ return $character;
+ }
+ }
+ }
+
+}
diff --git a/src/Tests/RemoteSourceTest.php b/src/Tests/RemoteSourceTest.php
new file mode 100644
index 0000000..2cc5500
--- /dev/null
+++ b/src/Tests/RemoteSourceTest.php
@@ -0,0 +1,38 @@
+enableSources(array(
+ 'remote' => TRUE,
+ ));
+
+ // Upload a file by 'Remote' source.
+ $this->uploadFileByRemoteSource($GLOBALS['base_url'] . '/README.txt', 'README.txt', 0);
+
+ // We can only transfer one file on single value field.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Transfer'), t('After uploading a file, "Transfer" button is no longer displayed.'));
+
+ // Remove uploaded file.
+ $this->removeFile('README.txt', 0);
+
+ // Can transfer file again.
+ $this->assertFieldByXpath('//input[@type="submit"]', t('Transfer'), 'After clicking the "Remove" button, the "Transfer" button is displayed.');
+ }
+
+}
diff --git a/src/Tests/UploadSourceTest.php b/src/Tests/UploadSourceTest.php
new file mode 100644
index 0000000..01afc53
--- /dev/null
+++ b/src/Tests/UploadSourceTest.php
@@ -0,0 +1,62 @@
+enableSources(array(
+ 'upload' => TRUE,
+ ));
+
+ $this->assertUploadSourceWorkProperly();
+ }
+
+ /**
+ * Tests all sources enabled.
+ */
+ public function testAllSourcesEnabled() {
+ $this->enableSources(array(
+ 'upload' => TRUE,
+ 'remote' => TRUE,
+ 'clipboard' => TRUE,
+ 'reference' => TRUE,
+ 'attach' => TRUE,
+ ));
+
+ $this->assertUploadSourceWorkProperly();
+ }
+
+ /**
+ * Tests upload source still working properly.
+ */
+ protected function assertUploadSourceWorkProperly() {
+ $file = $this->createTemporaryFileEntity();
+
+ // Upload a file by 'Upload' source.
+ $this->uploadFileByUploadSource($file->getFileUri(), $file->getFilename(), 0, FALSE);
+
+ // We can only upload one file on single value field.
+ $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('After uploading a file, "Upload" button is no longer displayed.'));
+
+ // Remove uploaded file.
+ $this->removeFile($file->getFilename(), 0);
+
+ // Can upload file again.
+ $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
+ }
+
+}