From 4c29971f1ea9e110252dfbdf77dc6613a0ae3ba6 Mon Sep 17 00:00:00 2001
From: Niklas Fiekas <niklas.fiekas@googlemail.com>
Date: Sat, 13 Aug 2011 05:21:23 +0200
Subject: [PATCH] Issue #860748 by Niklas Fiekas: Per mapper config

Adds graceful degrading, AJAX enabled per mapper configuration forms to
the Feeds UI, saves their values and uses them in the processor.

 - Wrap the feeds_ui_mapping_form in a div for AJAX replacement.

 - Instead of unique flags add setting forms. Unique flags can now be
   set using the settings forms.

 - Each target has a callback for a settings summary (summary_callback)
   and a callback for a settings form (form_callback).
   If the user clicks a configuration button, that button and the
   summary are replaced by the settings form. There is an option to
   update or cancel.

 - Add validation for the source and the target selection, so that a
   selection is required when adding a mapping.

 - Properly save the importers in submit callbacks, so that they can be
   used in multistep forms.

 - Save the per mapper settings in the main submit callback.

 - Add a default summary and settings form callback, that is used for
   selecting unique targets.

 - Adjust theming of the table accordingly.

 - Fix a typo in theme_feeds_ui_mapping_form().

 - Let the FeedsProcessor pass on the per mapper configuration as an
   additional parameter for target callbacks or setTargetElement().
---
 feeds_ui/feeds_ui.admin.inc |  203 ++++++++++++++++++++++++++++++++++++++-----
 plugins/FeedsProcessor.inc  |    4 +-
 2 files changed, 183 insertions(+), 24 deletions(-)

diff --git a/feeds_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc
index 779b070..fc3e845 100644
--- a/feeds_ui/feeds_ui.admin.inc
+++ b/feeds_ui/feeds_ui.admin.inc
@@ -490,6 +490,8 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) {
   $form['#importer'] = $importer;
   $form['#mappings'] = $mappings = $importer->processor->getMappings();
   $form['help']['#markup'] = feeds_ui_mapping_help();
+  $form['#prefix'] = '<div id="feeds-ui-mapping-form-wrapper">';
+  $form['#suffix'] = '</div>';
 
   // Get mapping sources from parsers and targets from processor, format them
   // for output.
@@ -521,23 +523,16 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) {
   );
   $form['legendset']['legend'] = $legend;
 
-  // Add unique and remove forms to mappings.
-  $form['unique_flags'] = $form['remove_flags'] = array(
+  // Add config forms and remove flags to mappings.
+  $form['config'] = $form['remove_flags'] = array(
     '#tree' => TRUE,
   );
   if (is_array($mappings)) {
     foreach ($mappings as $i => $mapping) {
-      $param = array(
-        'processor' => $importer->processor,
-        'mapping' => $mapping,
-      );
-      if (isset($targets[$mapping['target']]['optional_unique']) && $targets[$mapping['target']]['optional_unique'] === TRUE) {
-        $form['unique_flags'][$i] = array(
-          '#type' => 'checkbox',
-          '#default_value' => !empty($mapping['unique']),
-          '#attributes' => array('class' => array('feeds-ui-trigger-submit')),
-        );
+      if (isset($targets[$mapping['target']])) {
+        $form['config'][$i] = feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targets[$mapping['target']]);
       }
+
       $form['remove_flags'][$i] = array(
         '#type' => 'checkbox',
         '#title' => t('Remove'),
@@ -569,6 +564,7 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) {
     '#type' => 'submit',
     '#value' => t('Add'),
     '#submit' => array('feeds_ui_mapping_form_add_submit'),
+    '#validate' => array('feeds_ui_mapping_form_add_validate'),
   );
   $form['save'] = array(
     '#type' => 'submit',
@@ -579,6 +575,126 @@ function feeds_ui_mapping_form($form, &$form_state, $importer) {
 }
 
 /**
+ * Per mapper configuration form that is a part of feeds_ui_mapping_form().
+ */
+function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $target) {
+  $form_state += array(
+    'mapping_settings_edit' => NULL,
+    'mapping_settings' => array(),
+  );
+
+  $base_button = array(
+    '#submit' => array('feeds_ui_mapping_form_multistep_submit'),
+    '#ajax' => array(
+      'callback' => 'feeds_ui_mapping_settings_form_callback',
+      'wrapper' => 'feeds-ui-mapping-form-wrapper',
+      'effect' => 'fade',
+      'progress' => 'none',
+    ),
+    '#i' => $i,
+  );
+
+
+  if (isset($form_state['mapping_settings'][$i])) {
+    $mapping = $form_state['mapping_settings'][$i] + $mapping;
+  }
+
+  if ($form_state['mapping_settings_edit'] === $i) {
+    if (!isset($target['form_callback'])) {
+      $target['form_callback'] = 'feeds_ui_mapping_settings_optional_unique_form';
+    }
+
+    $settings_form = call_user_func($target['form_callback'], $mapping, $target, $form, $form_state);
+
+    return array(
+      '#type' => 'container',
+      'settings' => $settings_form,
+      'save_settings' => $base_button + array(
+        '#type' => 'submit',
+        '#name' => 'mapping_settings_update_' . $i,
+        '#value' => t('Update'),
+        '#op' => 'update',
+      ),
+      'cancel_settings' => $base_button + array(
+        '#type' => 'submit',
+        '#name' => 'mapping_settings_cancel_' . $i,
+        '#value' => t('Cancel'),
+        '#op' => 'cancel',
+      ),
+    );
+  }
+  else {
+    if (!isset($target['summary_callback'])) {
+      $target['summary_callback'] = 'feeds_ui_mapping_settings_optional_unique_summary';
+    }
+
+    $summary = call_user_func($target['summary_callback'], $mapping, $target, $form, $form_state);
+
+    if ($summary) {
+      return array(
+        'summary' => array(
+          '#prefix' => '<div>',
+          '#markup' => $summary,
+          '#suffix' => '</div>',
+        ),
+       'edit_settings' => $base_button + array(
+          '#type' => 'image_button',
+          '#name' => 'mapping_settings_edit_' . $i,
+          '#src' => 'misc/configure.png',
+          '#attributes' => array('alt' => t('Edit')),
+          '#op' => 'edit',
+        ),
+      );
+    }
+  }
+}
+
+/**
+ * Submit callback for a per mapper configuration form. Switches between edit
+ * and summary mode.
+ */
+function feeds_ui_mapping_form_multistep_submit($form, &$form_state) {
+  $trigger = $form_state['triggering_element'];
+
+  switch ($trigger['#op']) {
+    case 'edit':
+      $form_state['mapping_settings_edit'] = $trigger['#i'];
+      break;
+
+    case 'update':
+      $values = $form_state['values']['config'][$trigger['#i']]['settings'];
+      $form_state['mapping_settings'][$trigger['#i']] = $values;
+      unset($form_state['mapping_settings_edit']);
+      break;
+
+    case 'cancel':
+      unset($form_state['mapping_settings_edit']);
+      break;
+  }
+
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * AJAX callback that returns the whole feeds_ui_mapping_form().
+ */
+function feeds_ui_mapping_settings_form_callback($form, $form_state) {
+  return $form;
+}
+
+/**
+ * Validation for source and target selection.
+ */
+function feeds_ui_mapping_form_add_validate($form, &$form_state) {
+  if ($form_state['values']['source'] == '') {
+    form_set_error('source', t('You must select a mapping source.'));
+  }
+  if ($form_state['values']['target'] == '') {
+    form_set_error('target', t('You must select a mapping target.'));
+  }
+}
+
+/**
  * Submit handler for add button on feeds_ui_mapping_form().
  */
 function feeds_ui_mapping_form_add_submit($form, &$form_state) {
@@ -591,7 +707,7 @@ function feeds_ui_mapping_form_add_submit($form, &$form_state) {
       'unique' => FALSE,
     );
     $importer->processor->addConfig(array('mappings' => $mappings));
-    $importer->processor->save();
+    $importer->save();
     drupal_set_message(t('Mapping has been added.'));
   }
   catch (Exception $e) {
@@ -604,13 +720,26 @@ function feeds_ui_mapping_form_add_submit($form, &$form_state) {
  */
 function feeds_ui_mapping_form_submit($form, &$form_state) {
   $processor = $form['#importer']->processor;
-  // We may set some unique flags to mappings that we remove in the subsequent
+
+  $form_state += array(
+    'mapping_settings' => array(),
+    'mapping_settings_edit' => NULL,
+  );
+
+  // If an item is in edit mode, prepare it for saving.
+  if ($form_state['mapping_settings_edit'] !== NULL) {
+    $values = $form_state['values']['config'][$form_state['mapping_settings_edit']]['settings'];
+    $form_state['mapping_settings'][$form_state['mapping_settings_edit']] = $values;
+  }
+
+  // We may set some settings to mappings that we remove in the subsequent
   // step, that's fine.
   $mappings = $form['#mappings'];
-  if (isset($form_state['values']['unique_flags'])) {
-    foreach ($form_state['values']['unique_flags'] as $k => $v) {
-      $mappings[$k]['unique'] = $v;
-    }
+  foreach ($form_state['mapping_settings'] as $k => $v) {
+    $mappings[$k] = array(
+      'source' => $mappings[$k]['source'],
+      'target' => $mappings[$k]['target'],
+    ) + $v;
   }
 
   if (!empty($form_state['values']['remove_flags'])) {
@@ -623,7 +752,7 @@ function feeds_ui_mapping_form_submit($form, &$form_state) {
     $mappings = array_values($mappings);
   }
   $processor->addConfig(array('mappings' => $mappings));
-  $processor->save();
+  $form['#importer']->save();
   drupal_set_message(t('Your changes have been saved.'));
 }
 
@@ -649,6 +778,36 @@ function _feeds_ui_format_options($options) {
 }
 
 /**
+ * Per mapping settings summary callback. Shows whether a mapping is used as
+ * unique or not.
+ */
+function feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state) {
+  if (!empty($target['optional_unique'])) {
+    if ($mapping['unique']) {
+      return t('Used as <strong>unique</strong>');
+    } else {
+      return t('Not used as unique');
+    }
+  }
+}
+
+/**
+ * Per mapping settings form callback. Lets the user choose if a target is as
+ * unique or not.
+ */
+function feeds_ui_mapping_settings_optional_unique_form($mapping, $target, $form, $form_state) {
+  if (!empty($target['optional_unique'])) {
+    return array(
+      'unique' => array(
+        '#type' => 'checkbox',
+        '#title' => t('Unique'),
+        '#default_value' => !empty($mapping['unique']),
+      ),
+    );
+  }
+}
+
+/**
  * Theme feeds_ui_overview_form().
  */
 function theme_feeds_ui_overview_form($variables) {
@@ -783,7 +942,7 @@ function theme_feeds_ui_mapping_form($variables) {
   $header = array(
     t('Source'),
     t('Target'),
-    t('Unique target'),
+    t('Target configuration'),
     '&nbsp;',
   );
   $rows = array();
@@ -795,7 +954,7 @@ function theme_feeds_ui_mapping_form($variables) {
       $rows[] = array(
         check_plain($source),
         $target,
-        drupal_render($form['unique_flags'][$i]),
+        drupal_render($form['config'][$i]),
         drupal_render($form['remove_flags'][$i]),
       );
     }
@@ -814,7 +973,7 @@ function theme_feeds_ui_mapping_form($variables) {
     '',
     drupal_render($form['add']),
   );
-  $output = '<div class="help feeds-admin-ui""' . drupal_render($form['help']) . '</div>';
+  $output = '<div class="help feeds-admin-ui">' . drupal_render($form['help']) . '</div>';
   $output .= theme('table', array('header' => $header, 'rows' => $rows));
 
   // Build the help table that explains available sources.
diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc
index 08ae85f..193bd23 100644
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -395,10 +395,10 @@ abstract class FeedsProcessor extends FeedsPlugin {
           isset($targets[$this->id][$mapping['target']]['callback']) &&
           function_exists($targets[$this->id][$mapping['target']]['callback'])) {
         $callback = $targets[$this->id][$mapping['target']]['callback'];
-        $callback($source, $target_item, $mapping['target'], $value);
+        $callback($source, $target_item, $mapping['target'], $value, $mapping);
       }
       else {
-        $this->setTargetElement($source, $target_item, $mapping['target'], $value);
+        $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping);
       }
     }
     return $target_item;
-- 
1.7.4.4

