From f1b763f1e9ac97a022b7d9c2cb0bd931a732cfd0 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Tue, 24 Dec 2013 06:05:05 +0200
Subject: [PATCH] Issue #2072995 by claudiu.cristea | yched: Move FAPI
 callbacks for file/image widgets in classes.

---
 core/lib/Drupal/Core/Form/FormBuilder.php          |  10 +-
 core/lib/Drupal/Core/Form/FormBuilderInterface.php |   2 +-
 core/modules/file/file.field.inc                   | 273 --------------------
 .../file/Plugin/Field/FieldWidget/FileWidget.php   | 274 ++++++++++++++++++++-
 core/modules/image/image.field.inc                 | 109 --------
 .../image/Plugin/Field/FieldWidget/ImageWidget.php | 120 ++++++++-
 6 files changed, 391 insertions(+), 397 deletions(-)

diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 99a2d73..cc10ab0 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -1542,7 +1542,7 @@ protected function handleInputElement($form_id, &$element, &$form_state) {
 
     // 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)
@@ -1566,8 +1566,8 @@ protected function handleInputElement($form_id, &$element, &$form_state) {
         // 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;
@@ -1582,8 +1582,8 @@ protected function handleInputElement($form_id, &$element, &$form_state) {
       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 1ccb527..9d68646 100644
--- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php
+++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
@@ -525,7 +525,7 @@ public function executeHandlers($type, &$form, &$form_state);
    * 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 dee5b4e..93ce9d2 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;
 
 /**
@@ -19,278 +18,6 @@ function file_field_info_alter(&$info) {
 }
 
 /**
- * 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.
  *
  * @param $variables
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 948a06d..069bf29 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 @@
 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 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
       $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;
@@ -197,13 +199,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#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'],
@@ -230,7 +231,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       $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'));
       }
     }
 
@@ -257,4 +258,265 @@ public function massageFormValues(array $values, array $form, array &$form_state
     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);
+  }
+
 }
diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc
index 244f389..7cd7e2a 100644
--- a/core/modules/image/image.field.inc
+++ b/core/modules/image/image.field.inc
@@ -15,115 +15,6 @@ function image_field_info_alter(&$info) {
 }
 
 /**
- * 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.
  *
  * @param array $variables
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 c3afdbb..de692f0 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.
@@ -118,14 +119,127 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     $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;
+      }
+    }
+  }
+
+
 }
-- 
1.8.5.2

