Issue #2072995 by claudiu.cristea, yched: Change notice: Move FAPI callbacks for file/image widgets in classes.

8.0.x
webchick 2014-01-24 00:35:36 -08:00
parent 97072d8670
commit edbe2839a4
6 changed files with 391 additions and 397 deletions

View File

@ -1544,7 +1544,7 @@ class FormBuilder implements FormBuilderInterface {
// Set the element's #value property.
if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
$value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
$value_callable = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
if ($process_input) {
// Get the input for the current element. NULL values in the input need
// to be explicitly distinguished from missing input. (see below)
@ -1568,8 +1568,8 @@ class FormBuilder implements FormBuilderInterface {
// If we have input for the current element, assign it to the #value
// property, optionally filtered through $value_callback.
if ($input_exists) {
if (function_exists($value_callback)) {
$element['#value'] = $value_callback($element, $input, $form_state);
if (is_callable($value_callable)) {
$element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
}
if (!isset($element['#value']) && isset($input)) {
$element['#value'] = $input;
@ -1584,8 +1584,8 @@ class FormBuilder implements FormBuilderInterface {
if (!isset($element['#value'])) {
// Call #type_value without a second argument to request default_value
// handling.
if (function_exists($value_callback)) {
$element['#value'] = $value_callback($element, FALSE, $form_state);
if (is_callable($value_callable)) {
$element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
}
// Final catch. If we haven't set a value yet, use the explicit default
// value. Avoid image buttons (which come with garbage value), so we

View File

@ -525,7 +525,7 @@ interface FormBuilderInterface extends FormErrorInterface {
* rendering each element). Each of these three pipelines provides ample
* opportunity for modules to customize what happens. For example, during this
* function's life cycle, the following functions get called for each element:
* - $element['#value_callback']: A function that implements how user input is
* - $element['#value_callback']: A callable that implements how user input is
* mapped to an element's #value property. This defaults to a function named
* 'form_type_TYPE_value' where TYPE is $element['#type'].
* - $element['#process']: An array of functions called after user input has

View File

@ -5,7 +5,6 @@
* Field module functionality for the File module.
*/
use Drupal\Component\Utility\NestedArray;
use Drupal\field\FieldInterface;
/**
@ -18,278 +17,6 @@ function file_field_info_alter(&$info) {
$info['file']['settings']['uri_scheme'] = file_default_scheme();
}
/**
* Render API callback: Retrieves the value for the file_generic field element.
*
* This function is assigned as a #value callback in file_field_widget_form().
*/
function file_field_widget_value($element, $input = FALSE, $form_state) {
if ($input) {
// Checkboxes lose their value when empty.
// If the display field is present make sure its unchecked value is saved.
if (empty($input['display'])) {
$input['display'] = $element['#display_field'] ? 0 : 1;
}
}
// We depend on the managed file element to handle uploads.
$return = file_managed_file_value($element, $input, $form_state);
// Ensure that all the required properties are returned even if empty.
$return += array(
'fids' => array(),
'display' => 1,
'description' => '',
);
return $return;
}
/**
* Validation callback for upload element on file widget. Checks if user has
* uploaded more files than allowed.
*
* This validator is used only when cardinality not set to 1 or unlimited.
*/
function file_field_widget_multiple_count_validate($element, &$form_state, $form) {
$parents = $element['#parents'];
$entity_type = $element['#entity_type'];
$field_name = $element['#field_name'];
$values = NestedArray::getValue($form_state['values'], $parents);
array_pop($parents);
$current = count(element_children(NestedArray::getValue($form, $parents))) - 1;
$field = field_info_field($entity_type, $field_name);
$cardinality = $field->getCardinality();
$uploaded = count($values['fids']);
$count = $uploaded + $current;
if ($count > $cardinality) {
$keep = $uploaded - $count + $cardinality;
$removed_files = array_slice($values['fids'], $keep);
$removed_names = array();
foreach ($removed_files as $fid) {
$file = file_load($fid);
$removed_names[] = $file->getFilename();
}
drupal_set_message(
t(
'Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.',
array(
'%field' => $field_name,
'@max' => $cardinality,
'@count' => $keep,
'%list' => implode(', ', $removed_names),
)
),
'warning'
);
$values['fids'] = array_slice($values['fids'], 0, $keep);
NestedArray::setValue($form_state['values'], $element['#parents'], $values);
}
}
/**
* Render API callback: Processes a file_generic field element.
*
* Expands the file_generic type to include the description and display fields.
*
* This function is assigned as a #process callback in file_field_widget_form().
*/
function file_field_widget_process($element, &$form_state, $form) {
$item = $element['#value'];
$item['fids'] = $element['fids']['#value'];
$element['#theme'] = 'file_widget';
// Add the display field if enabled.
if ($element['#display_field'] && $item['fids']) {
$element['display'] = array(
'#type' => empty($item['fids']) ? 'hidden' : 'checkbox',
'#title' => t('Include file in display'),
'#value' => isset($item['display']) ? $item['display'] : $element['#display_default'],
'#attributes' => array('class' => array('file-display')),
);
}
else {
$element['display'] = array(
'#type' => 'hidden',
'#value' => '1',
);
}
// Add the description field if enabled.
if ($element['#description_field'] && $item['fids']) {
$config = \Drupal::config('file.settings');
$element['description'] = array(
'#type' => $config->get('description.type'),
'#title' => t('Description'),
'#value' => isset($item['description']) ? $item['description'] : '',
'#maxlength' => $config->get('description.length'),
'#description' => t('The description may be used as the label of the link to the file.'),
);
}
// Adjust the Ajax settings so that on upload and remove of any individual
// file, the entire group of file fields is updated together.
if ($element['#cardinality'] != 1) {
$parents = array_slice($element['#array_parents'], 0, -1);
$new_path = 'file/ajax';
$new_options = array(
'query' => array(
'element_parents' => implode('/', $parents),
'form_build_id' => $form['form_build_id']['#value'],
),
);
$field_element = NestedArray::getValue($form, $parents);
$new_wrapper = $field_element['#id'] . '-ajax-wrapper';
foreach (element_children($element) as $key) {
if (isset($element[$key]['#ajax'])) {
$element[$key]['#ajax']['path'] = $new_path;
$element[$key]['#ajax']['options'] = $new_options;
$element[$key]['#ajax']['wrapper'] = $new_wrapper;
}
}
unset($element['#prefix'], $element['#suffix']);
}
// Add another submit handler to the upload and remove buttons, to implement
// functionality needed by the field widget. This submit handler, along with
// the rebuild logic in file_field_widget_form() requires the entire field,
// not just the individual item, to be valid.
foreach (array('upload_button', 'remove_button') as $key) {
$element[$key]['#submit'][] = 'file_field_widget_submit';
$element[$key]['#limit_validation_errors'] = array(array_slice($element['#parents'], 0, -1));
}
return $element;
}
/**
* Render API callback: Processes a group of file_generic field elements.
*
* Adds the weight field to each row so it can be ordered and adds a new Ajax
* wrapper around the entire group so it can be replaced all at once.
*
* This function is assigned as a #process callback in file_field_widget_form().
*/
function file_field_widget_process_multiple($element, &$form_state, $form) {
$element_children = element_children($element, TRUE);
$count = count($element_children);
foreach ($element_children as $delta => $key) {
if ($key != $element['#file_upload_delta']) {
$description = _file_field_get_description_from_element($element[$key]);
$element[$key]['_weight'] = array(
'#type' => 'weight',
'#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
'#title_display' => 'invisible',
'#delta' => $count,
'#default_value' => $delta,
);
}
else {
// The title needs to be assigned to the upload field so that validation
// errors include the correct widget label.
$element[$key]['#title'] = $element['#title'];
$element[$key]['_weight'] = array(
'#type' => 'hidden',
'#default_value' => $delta,
);
}
}
// Add a new wrapper around all the elements for Ajax replacement.
$element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
$element['#suffix'] = '</div>';
return $element;
}
/**
* Retrieves the file description from a field field element.
*
* This helper function is used by file_field_widget_process_multiple().
*
* @param $element
* The element being processed.
*
* @return
* A description of the file suitable for use in the administrative interface.
*/
function _file_field_get_description_from_element($element) {
// Use the actual file description, if it's available.
if (!empty($element['#default_value']['description'])) {
return $element['#default_value']['description'];
}
// Otherwise, fall back to the filename.
if (!empty($element['#default_value']['filename'])) {
return $element['#default_value']['filename'];
}
// This is probably a newly uploaded file; no description is available.
return FALSE;
}
/**
* Form submission handler for upload/remove button of file_field_widget_form().
*
* This runs in addition to and after file_managed_file_submit().
*
* @see file_managed_file_submit()
* @see file_field_widget_form()
* @see file_field_widget_process()
*/
function file_field_widget_submit($form, &$form_state) {
// During the form rebuild, file_field_widget_form() will create field item
// widget elements using re-indexed deltas, so clear out $form_state['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.
$parents = array_slice($form_state['triggering_element']['#parents'], 0, -2);
NestedArray::setValue($form_state['input'], $parents, NULL);
$button = $form_state['triggering_element'];
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_name = $element['#field_name'];
$parents = $element['#field_parents'];
$submitted_values = NestedArray::getValue($form_state['values'], array_slice($button['#parents'], 0, -2));
foreach ($submitted_values as $delta => $submitted_value) {
if (empty($submitted_value['fids'])) {
unset($submitted_values[$delta]);
}
}
// If there are more files uploaded via the same widget, we have to separate
// them, as we display each file in it's own widget.
$new_values = array();
foreach ($submitted_values as $delta => $submitted_value) {
if (is_array($submitted_value['fids'])) {
foreach ($submitted_value['fids'] as $fid) {
$new_value = $submitted_value;
$new_value['fids'] = array($fid);
$new_values[] = $new_value;
}
}
else {
$new_value = $submitted_value;
}
}
// Re-index deltas after removing empty items.
$submitted_values = array_values($new_values);
// Update form_state values.
NestedArray::setValue($form_state['values'], array_slice($button['#parents'], 0, -2), $submitted_values);
// Update items.
$field_state = field_form_get_state($parents, $field_name, $form_state);
$field_state['items'] = $submitted_values;
field_form_set_state($parents, $field_name, $form_state, $field_state);
}
/**
* Returns HTML for an individual file upload widget.
*

View File

@ -10,6 +10,8 @@ namespace Drupal\file\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\field\Field;
/**
* Plugin implementation of the 'file_generic' widget.
@ -145,7 +147,7 @@ class FileWidget extends WidgetBase {
$elements['#type'] = 'details';
$elements['#theme'] = 'file_widget_multiple';
$elements['#theme_wrappers'] = array('details');
$elements['#process'] = array('file_field_widget_process_multiple');
$elements['#process'] = array(array(get_class($this), 'processMultiple'));
$elements['#title'] = $title;
$elements['#description'] = $description;
@ -202,13 +204,12 @@ class FileWidget extends WidgetBase {
'#type' => 'managed_file',
'#upload_location' => $items[$delta]->getUploadLocation(),
'#upload_validators' => $items[$delta]->getUploadValidators(),
'#value_callback' => 'file_field_widget_value',
'#process' => array_merge($element_info['#process'], array('file_field_widget_process')),
'#value_callback' => array(get_class($this), 'value'),
'#process' => array_merge($element_info['#process'], array(array(get_class($this), 'process'))),
'#progress_indicator' => $this->getSetting('progress_indicator'),
// Allows this field to return an array instead of a single value.
'#extended' => TRUE,
// Add properties needed by file_field_widget_value() and
// file_field_widget_process().
// Add properties needed by value() and process() methods.
'#display_field' => (bool) $field_settings['display_field'],
'#display_default' => $field_settings['display_default'],
'#description_field' => $field_settings['description_field'],
@ -235,7 +236,7 @@ class FileWidget extends WidgetBase {
$element['#description'] = drupal_render($file_upload_help);
$element['#multiple'] = $cardinality != 1 ? TRUE : FALSE;
if ($cardinality != 1 && $cardinality != -1) {
$element['#element_validate'] = array('file_field_widget_multiple_count_validate');
$element['#element_validate'] = array(array(get_class($this), 'validateMultipleCount'));
}
}
@ -262,4 +263,265 @@ class FileWidget extends WidgetBase {
return $new_values;
}
/**
* Form API callback. Retrieves the value for the file_generic field element.
*
* This method is assigned as a #value_callback in formElement() method.
*/
public static function value($element, $input = FALSE, $form_state) {
if ($input) {
// Checkboxes lose their value when empty.
// If the display field is present make sure its unchecked value is saved.
if (empty($input['display'])) {
$input['display'] = $element['#display_field'] ? 0 : 1;
}
}
// We depend on the managed file element to handle uploads.
$return = file_managed_file_value($element, $input, $form_state);
// Ensure that all the required properties are returned even if empty.
$return += array(
'fids' => array(),
'display' => 1,
'description' => '',
);
return $return;
}
/**
* Form element validation callback for upload element on file widget. Checks
* if user has uploaded more files than allowed.
*
* This validator is used only when cardinality not set to 1 or unlimited.
*/
public static function validateMultipleCount($element, &$form_state, $form) {
$parents = $element['#parents'];
$values = NestedArray::getValue($form_state['values'], $parents);
array_pop($parents);
$current = count(element_children(NestedArray::getValue($form, $parents))) - 1;
$field = Field::fieldInfo()->getField($element['#entity_type'], $element['#field_name']);
$uploaded = count($values['fids']);
$count = $uploaded + $current;
if ($count > $field->cardinality) {
$keep = $uploaded - $count + $field->cardinality;
$removed_files = array_slice($values['fids'], $keep);
$removed_names = array();
foreach ($removed_files as $fid) {
$file = file_load($fid);
$removed_names[] = $file->getFilename();
}
$args = array('%field' => $field->getFieldName(), '@max' => $field->cardinality, '@count' => $keep, '%list' => implode(', ', $removed_names));
$message = t('Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', $args);
drupal_set_message($message, 'warning');
$values['fids'] = array_slice($values['fids'], 0, $keep);
NestedArray::setValue($form_state['values'], $element['#parents'], $values);
}
}
/**
* Form API callback: Processes a file_generic field element.
*
* Expands the file_generic type to include the description and display
* fields.
*
* This method is assigned as a #process callback in formElement() method.
*/
public static function process($element, &$form_state, $form) {
$item = $element['#value'];
$item['fids'] = $element['fids']['#value'];
$element['#theme'] = 'file_widget';
// Add the display field if enabled.
if ($element['#display_field'] && $item['fids']) {
$element['display'] = array(
'#type' => empty($item['fids']) ? 'hidden' : 'checkbox',
'#title' => t('Include file in display'),
'#value' => isset($item['display']) ? $item['display'] : $element['#display_default'],
'#attributes' => array('class' => array('file-display')),
);
}
else {
$element['display'] = array(
'#type' => 'hidden',
'#value' => '1',
);
}
// Add the description field if enabled.
if ($element['#description_field'] && $item['fids']) {
$config = \Drupal::config('file.settings');
$element['description'] = array(
'#type' => $config->get('description.type'),
'#title' => t('Description'),
'#value' => isset($item['description']) ? $item['description'] : '',
'#maxlength' => $config->get('description.length'),
'#description' => t('The description may be used as the label of the link to the file.'),
);
}
// Adjust the Ajax settings so that on upload and remove of any individual
// file, the entire group of file fields is updated together.
if ($element['#cardinality'] != 1) {
$parents = array_slice($element['#array_parents'], 0, -1);
$new_path = 'file/ajax';
$new_options = array(
'query' => array(
'element_parents' => implode('/', $parents),
'form_build_id' => $form['form_build_id']['#value'],
),
);
$field_element = NestedArray::getValue($form, $parents);
$new_wrapper = $field_element['#id'] . '-ajax-wrapper';
foreach (element_children($element) as $key) {
if (isset($element[$key]['#ajax'])) {
$element[$key]['#ajax']['path'] = $new_path;
$element[$key]['#ajax']['options'] = $new_options;
$element[$key]['#ajax']['wrapper'] = $new_wrapper;
}
}
unset($element['#prefix'], $element['#suffix']);
}
// Add another submit handler to the upload and remove buttons, to implement
// functionality needed by the field widget. This submit handler, along with
// the rebuild logic in file_field_widget_form() requires the entire field,
// not just the individual item, to be valid.
foreach (array('upload_button', 'remove_button') as $key) {
$element[$key]['#submit'][] = array(get_called_class(), 'submit');
$element[$key]['#limit_validation_errors'] = array(array_slice($element['#parents'], 0, -1));
}
return $element;
}
/**
* Form API callback: Processes a group of file_generic field elements.
*
* Adds the weight field to each row so it can be ordered and adds a new Ajax
* wrapper around the entire group so it can be replaced all at once.
*
* This method on is assigned as a #process callback in formMultipleElements()
* method.
*/
public static function processMultiple($element, &$form_state, $form) {
$element_children = element_children($element, TRUE);
$count = count($element_children);
foreach ($element_children as $delta => $key) {
if ($key != $element['#file_upload_delta']) {
$description = static::getDescriptionFromElement($element[$key]);
$element[$key]['_weight'] = array(
'#type' => 'weight',
'#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
'#title_display' => 'invisible',
'#delta' => $count,
'#default_value' => $delta,
);
}
else {
// The title needs to be assigned to the upload field so that validation
// errors include the correct widget label.
$element[$key]['#title'] = $element['#title'];
$element[$key]['_weight'] = array(
'#type' => 'hidden',
'#default_value' => $delta,
);
}
}
// Add a new wrapper around all the elements for Ajax replacement.
$element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
$element['#suffix'] = '</div>';
return $element;
}
/**
* Retrieves the file description from a field field element.
*
* This helper static method is used by processMultiple() method.
*
* @param array $element
* An associative array with the element being processed.
*
* @return array|false
* A description of the file suitable for use in the administrative
* interface.
*/
protected static function getDescriptionFromElement($element) {
// Use the actual file description, if it's available.
if (!empty($element['#default_value']['description'])) {
return $element['#default_value']['description'];
}
// Otherwise, fall back to the filename.
if (!empty($element['#default_value']['filename'])) {
return $element['#default_value']['filename'];
}
// This is probably a newly uploaded file; no description is available.
return FALSE;
}
/**
* Form submission handler for upload/remove button of formElement().
*
* This runs in addition to and after file_managed_file_submit().
*
* @see file_managed_file_submit()
*/
public static function submit($form, &$form_state) {
// During the form rebuild, formElement() will create field item widget
// elements using re-indexed deltas, so clear out $form_state['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.
$parents = array_slice($form_state['triggering_element']['#parents'], 0, -2);
NestedArray::setValue($form_state['input'], $parents, NULL);
$button = $form_state['triggering_element'];
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_name = $element['#field_name'];
$parents = $element['#field_parents'];
$submitted_values = NestedArray::getValue($form_state['values'], array_slice($button['#parents'], 0, -2));
foreach ($submitted_values as $delta => $submitted_value) {
if (empty($submitted_value['fids'])) {
unset($submitted_values[$delta]);
}
}
// If there are more files uploaded via the same widget, we have to separate
// them, as we display each file in it's own widget.
$new_values = array();
foreach ($submitted_values as $delta => $submitted_value) {
if (is_array($submitted_value['fids'])) {
foreach ($submitted_value['fids'] as $fid) {
$new_value = $submitted_value;
$new_value['fids'] = array($fid);
$new_values[] = $new_value;
}
}
else {
$new_value = $submitted_value;
}
}
// Re-index deltas after removing empty items.
$submitted_values = array_values($new_values);
// Update form_state values.
NestedArray::setValue($form_state['values'], array_slice($button['#parents'], 0, -2), $submitted_values);
// Update items.
$field_state = field_form_get_state($parents, $field_name, $form_state);
$field_state['items'] = $submitted_values;
field_form_set_state($parents, $field_name, $form_state, $field_state);
}
}

View File

@ -14,115 +14,6 @@ function image_field_info_alter(&$info) {
$info['image']['settings']['uri_scheme'] = file_default_scheme();
}
/**
* An element #process callback for the image_image field type.
*
* Expands the image_image type to include the alt and title fields.
*/
function image_field_widget_process($element, &$form_state, $form) {
$item = $element['#value'];
$item['fids'] = $element['fids']['#value'];
$element['#theme'] = 'image_widget';
$element['#attached']['css'][] = drupal_get_path('module', 'image') . '/css/image.theme.css';
// Add the image preview.
if (!empty($element['#files']) && $element['#preview_image_style']) {
$file = reset($element['#files']);
$variables = array(
'style_name' => $element['#preview_image_style'],
'uri' => $file->getFileUri(),
);
// Determine image dimensions.
if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
$variables['width'] = $element['#value']['width'];
$variables['height'] = $element['#value']['height'];
}
else {
$image = \Drupal::service('image.factory')->get($file->getFileUri());
if ($image->isSupported()) {
$variables['width'] = $image->getWidth();
$variables['height'] = $image->getHeight();
}
else {
$variables['width'] = $variables['height'] = NULL;
}
}
$element['preview'] = array(
'#theme' => 'image_style',
'#width' => $variables['width'],
'#height' => $variables['height'],
'#style_name' => $variables['style_name'],
'#uri' => $variables['uri'],
);
// Store the dimensions in the form so the file doesn't have to be accessed
// again. This is important for remote files.
$element['width'] = array(
'#type' => 'hidden',
'#value' => $variables['width'],
);
$element['height'] = array(
'#type' => 'hidden',
'#value' => $variables['height'],
);
}
// Add the additional alt and title fields.
$element['alt'] = array(
'#title' => t('Alternate text'),
'#type' => 'textfield',
'#default_value' => isset($item['alt']) ? $item['alt'] : '',
'#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
// @see https://drupal.org/node/465106#alt-text
'#maxlength' => 512,
'#weight' => -2,
'#access' => (bool) $item['fids'] && $element['#alt_field'],
'#element_validate' => $element['#alt_field_required'] == 1 ? array('_image_field_required_fields_validate') : array(),
);
$element['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => isset($item['title']) ? $item['title'] : '',
'#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
'#maxlength' => 1024,
'#weight' => -1,
'#access' => (bool) $item['fids'] && $element['#title_field'],
'#element_validate' => $element['#alt_field_required'] == 1 ? array('_image_field_required_fields_validate') : array(),
);
return $element;
}
/**
* Validate callback for alt and title field, if the user wants them required.
*
* This is separated in a validate function instead of a #required flag to avoid
* being validated on the process callback.
*/
function _image_field_required_fields_validate($element, &$form_state) {
// Only do validation if the function is triggered from other places than
// the image process form.
if (!in_array('file_managed_file_submit', $form_state['triggering_element']['#submit'])) {
// If the image is not there, we do not check for empty values.
$parents = $element['#parents'];
$field = array_pop($parents);
$image_field = NestedArray::getValue($form_state['input'], $parents);
// We check for the array key, so that it can be NULL (like if the user
// submits the form without using the "upload" button).
if (!array_key_exists($field, $image_field)) {
return;
}
// Check if field is left emtpy.
elseif (empty($image_field[$field])) {
form_error($element, $form_state, t('The field !title is required', array('!title' => $element['#title'])));
return;
}
}
}
/**
* Returns HTML for an image field widget.
*

View File

@ -7,8 +7,9 @@
namespace Drupal\image\Plugin\Field\FieldWidget;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
/**
* Plugin implementation of the 'image_image' widget.
@ -119,14 +120,127 @@ class ImageWidget extends FileWidget {
$element['#upload_validators']['file_validate_extensions'][0] = implode(' ', $extensions);
// Add all extra functionality provided by the image widget.
$element['#process'][] = 'image_field_widget_process';
// Add properties needed by image_field_widget_process().
$element['#process'][] = array(get_class($this), 'process');
// Add properties needed by process() method.
$element['#preview_image_style'] = $this->getSetting('preview_image_style');
$element['#title_field'] = $field_settings['title_field'];
$element['#title_field_required'] = $field_settings['title_field_required'];
$element['#alt_field'] = $field_settings['alt_field'];
$element['#alt_field_required'] = $field_settings['alt_field_required'];
return $element;
}
/**
* Form API callback: Processes a image_image field element.
*
* Expands the image_image type to include the alt and title fields.
*
* This method is assigned as a #process callback in formElement() method.
*/
public static function process($element, &$form_state, $form) {
$item = $element['#value'];
$item['fids'] = $element['fids']['#value'];
$element['#theme'] = 'image_widget';
$element['#attached']['css'][] = drupal_get_path('module', 'image') . '/css/image.theme.css';
// Add the image preview.
if (!empty($element['#files']) && $element['#preview_image_style']) {
$file = reset($element['#files']);
$variables = array(
'style_name' => $element['#preview_image_style'],
'uri' => $file->getFileUri(),
);
// Determine image dimensions.
if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
$variables['width'] = $element['#value']['width'];
$variables['height'] = $element['#value']['height'];
}
else {
$image = \Drupal::service('image.factory')->get($file->getFileUri());
if ($image->getExtension()) {
$variables['width'] = $image->getWidth();
$variables['height'] = $image->getHeight();
}
else {
$variables['width'] = $variables['height'] = NULL;
}
}
$element['preview'] = array(
'#theme' => 'image_style',
'#width' => $variables['width'],
'#height' => $variables['height'],
'#style_name' => $variables['style_name'],
'#uri' => $variables['uri'],
);
// Store the dimensions in the form so the file doesn't have to be
// accessed again. This is important for remote files.
$element['width'] = array(
'#type' => 'hidden',
'#value' => $variables['width'],
);
$element['height'] = array(
'#type' => 'hidden',
'#value' => $variables['height'],
);
}
// Add the additional alt and title fields.
$element['alt'] = array(
'#title' => t('Alternate text'),
'#type' => 'textfield',
'#default_value' => isset($item['alt']) ? $item['alt'] : '',
'#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
// @see http://www.gawds.org/show.php?contentid=28
'#maxlength' => 512,
'#weight' => -2,
'#access' => (bool) $item['fids'] && $element['#alt_field'],
'#element_validate' => $element['#alt_field_required'] == 1 ? array(array(get_called_class(), 'validateRequiredFields')) : array(),
);
$element['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => isset($item['title']) ? $item['title'] : '',
'#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
'#maxlength' => 1024,
'#weight' => -1,
'#access' => (bool) $item['fids'] && $element['#title_field'],
'#element_validate' => $element['#title_field_required'] == 1 ? array(array(get_called_class(), 'validateRequiredFields')) : array(),
);
return $element;
}
/**
* Validate callback for alt and title field, if the user wants them required.
*
* This is separated in a validate function instead of a #required flag to
* avoid being validated on the process callback.
*/
public static function validateRequiredFields($element, &$form_state) {
// Only do validation if the function is triggered from other places than
// the image process form.
if (!in_array('file_managed_file_submit', $form_state['triggering_element']['#submit'])) {
// If the image is not there, we do not check for empty values.
$parents = $element['#parents'];
$field = array_pop($parents);
$image_field = NestedArray::getValue($form_state['input'], $parents);
// We check for the array key, so that it can be NULL (like if the user
// submits the form without using the "upload" button).
if (!array_key_exists($field, $image_field)) {
return;
}
// Check if field is left empty.
elseif (empty($image_field[$field])) {
\Drupal::formBuilder()->setError($element, $form_state, t('The field !title is required', array('!title' => $element['#title'])));
return;
}
}
}
}