diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 37c913ae9b8..43d15a65bea 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -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 diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php index a7205d1c912..ad6febf8da3 100644 --- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php +++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php @@ -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 diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc index dee5b4ec1fe..93ce9d2b709 100644 --- a/core/modules/file/file.field.inc +++ b/core/modules/file/file.field.inc @@ -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'] = '
'; - $element['#suffix'] = '
'; - - 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. * diff --git a/core/modules/file/lib/Drupal/file/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/lib/Drupal/file/Plugin/Field/FieldWidget/FileWidget.php index a7c8d3028f8..5c976ed5f8f 100644 --- a/core/modules/file/lib/Drupal/file/Plugin/Field/FieldWidget/FileWidget.php +++ b/core/modules/file/lib/Drupal/file/Plugin/Field/FieldWidget/FileWidget.php @@ -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'] = '
'; + $element['#suffix'] = '
'; + + 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); + } + } diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc index 244f3894fbe..7cd7e2aff5d 100644 --- a/core/modules/image/image.field.inc +++ b/core/modules/image/image.field.inc @@ -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. * diff --git a/core/modules/image/lib/Drupal/image/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/lib/Drupal/image/Plugin/Field/FieldWidget/ImageWidget.php index 0ecca824c3b..94528ae86b9 100644 --- a/core/modules/image/lib/Drupal/image/Plugin/Field/FieldWidget/ImageWidget.php +++ b/core/modules/image/lib/Drupal/image/Plugin/Field/FieldWidget/ImageWidget.php @@ -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; + } + } + } + + }