diff --git a/feeds.module b/feeds.module
index f0dd48c..0afa6bc 100644
--- a/feeds.module
+++ b/feeds.module
@@ -296,6 +296,15 @@ function feeds_menu() {
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
+ $items['import/%feeds_importer/mapping'] = array(
+ 'title' => 'Mapping',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('feeds_import_mapping_form', 1),
+ 'access callback' => 'feeds_access',
+ 'access arguments' => array('import', 1),
+ 'file' => 'feeds.pages.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
$items['import/%feeds_importer/delete-items'] = array(
'title' => 'Delete items',
'page callback' => 'drupal_get_form',
@@ -352,6 +361,16 @@ function feeds_menu() {
'type' => MENU_LOCAL_TASK,
'weight' => 11,
);
+ $items['node/%node/mapping'] = array(
+ 'title' => 'Mapping',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('feeds_import_mapping_form', NULL, 1),
+ 'access callback' => 'feeds_access',
+ 'access arguments' => array('import', 1),
+ 'file' => 'feeds.pages.inc',
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 12,
+ );
// @todo Eliminate this step and thus eliminate clearing menu cache when
// manipulating importers.
foreach (feeds_importer_load_all() as $importer) {
@@ -403,6 +422,10 @@ function feeds_theme() {
'file' => 'feeds.pages.inc',
'render element' => 'element',
),
+ 'feeds_mapping_form' => array(
+ 'file' => 'feeds.pages.inc',
+ 'render element' => 'form',
+ ),
'feeds_source_status' => array(
'file' => 'feeds.pages.inc',
'variables' => array(
@@ -644,8 +667,12 @@ function feeds_node_insert($node) {
feeds_node_update($node);
if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) {
$source = feeds_source($importer_id, $node->nid);
- // Start import if requested.
- if (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) {
+ // Redirect to mapping page if necessary.
+ if ($source->importer->processor->config['mapping_on_import']) {
+ $_REQUEST['destination'] = 'node/' . $node->nid . '/mapping/' . $importer_id;
+ }
+ // Otherwise start import if requested.
+ elseif (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) {
$source->startImport();
}
// Schedule the source.
@@ -1390,3 +1417,157 @@ function feeds_api_version() {
$version = feeds_ctools_plugin_api('feeds', 'plugins');
return $version['version'];
}
+
+/**
+ * Per mapper configuration form that is a part of feeds_mapping_form().
+ */
+function feeds_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_mapping_form_multistep_submit'),
+ '#ajax' => array(
+ 'callback' => 'feeds_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) {
+ // Build the form.
+ if (isset($target['form_callback'])) {
+ $settings_form = call_user_func($target['form_callback'], $mapping, $target, $form, $form_state);
+ }
+ else {
+ $settings_form = array();
+ }
+
+ // Merge in the optional unique form.
+ $settings_form += feeds_mapping_settings_optional_unique_form($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 {
+ // Build the summary.
+ if (isset($target['summary_callback'])) {
+ $summary = call_user_func($target['summary_callback'], $mapping, $target, $form, $form_state);
+ }
+ else {
+ $summary = '';
+ }
+
+ // Append the optional unique summary.
+ if ($optional_unique_summary = feeds_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state)) {
+ $summary .= ' ' . $optional_unique_summary;
+ }
+
+ if ($summary) {
+ return array(
+ 'summary' => array(
+ '#prefix' => '
',
+ '#markup' => $summary,
+ '#suffix' => '
',
+ ),
+ 'edit_settings' => $base_button + array(
+ '#type' => 'image_button',
+ '#name' => 'mapping_settings_edit_' . $i,
+ '#src' => 'misc/configure.png',
+ '#attributes' => array('alt' => t('Edit')),
+ '#op' => 'edit',
+ ),
+ );
+ }
+ }
+}
+
+/**
+ * AJAX callback that returns the whole feeds_mapping_form().
+ */
+function feeds_mapping_settings_form_callback($form, $form_state) {
+ return $form;
+}
+
+/**
+ * Submit callback for a per mapper configuration form. Switches between edit
+ * and summary mode.
+ */
+function feeds_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;
+}
+
+/**
+ * Per mapping settings summary callback. Shows whether a mapping is used as
+ * unique or not.
+ */
+function feeds_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state) {
+ if (!empty($target['optional_unique'])) {
+ if ($mapping['unique']) {
+ return t('Used as unique.');
+ }
+ 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_mapping_settings_optional_unique_form($mapping, $target, $form, $form_state) {
+ $settings_form = array();
+
+ if (!empty($target['optional_unique'])) {
+ $settings_form['unique'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Unique'),
+ '#default_value' => !empty($mapping['unique']),
+ );
+ }
+
+ return $settings_form;
+}
+
+
diff --git a/feeds.pages.inc b/feeds.pages.inc
index eeeab90..c3b1dee 100644
--- a/feeds.pages.inc
+++ b/feeds.pages.inc
@@ -89,6 +89,12 @@ function feeds_import_form(array $form, array &$form_state, FeedsImporter $impor
'#type' => 'submit',
'#value' => t('Import'),
);
+
+ // If mapping on import is selected, redirect to feeds_import_mapping_form.
+ if ($source->importer->processor->config['mapping_on_import']) {
+ $form['#redirect'] = 'import/' . $importer_id . '/mapping';
+ }
+
$progress = $source->progressImporting();
if ($progress !== FEEDS_BATCH_COMPLETE) {
$form['submit']['#disabled'] = TRUE;
@@ -118,13 +124,20 @@ function feeds_import_form_submit($form, &$form_state) {
$source->save();
}
- // Refresh feed if import on create is selected.
- if ($source->importer->config['import_on_create']) {
- $source->startImport();
+ if ($source->importer->processor->config['mapping_on_import']) {
+ // Handle the redirection to mapping form
+ $form_state['redirect'] = $form['#redirect'];
+ }
+ else {
+ // Refresh feed if import on create is selected.
+ if ($source->importer->config['import_on_create']) {
+ $source->startImport();
+ }
+
+ // Add to schedule, make sure importer is scheduled, too.
+ $source->schedule();
}
- // Add to schedule, make sure importer is scheduled, too.
- $source->schedule();
}
/**
@@ -377,3 +390,163 @@ function theme_feeds_upload($variables) {
return $output;
}
+
+/**
+ * Declare Feeds mapping on import form
+ */
+function feeds_import_mapping_form($form, &$form_state, $importer_id, $node = NULL) {
+ if (empty($node)) {
+ $feed_nid = 0;
+ }
+ else {
+ $importer_id = feeds_get_importer_id($node->type);
+ $feed_nid = empty($node) ? 0 : $node->nid;
+ }
+
+ $source = feeds_source($importer_id, $feed_nid);
+
+ // Fetch and parse the source to enable result to provide mapping sources.
+ $fetcher = $source->importer->fetcher->fetch($source);
+ // Signify to the parser that we're only after the fieldnames for mapping.
+ $result = $source->importer->parser->parse($source, $fetcher);
+ $result->context = 'mapping';
+
+ $form = $source->importer->processor->mappingForm($form_state, $result);
+
+ // Declare the appropriate configurable and forms method for appropriate submit handler
+ $form['#feeds_form_method'] = 'mappingForm';
+ $form['#configurable'] = $source->importer->processor;
+
+ $form['#importer_id'] = $importer_id;
+ $form['#feed_nid'] = $feed_nid;
+
+ $form['import'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import'),
+ '#submit' => array('feeds_import_mapping_form_import_submit'),
+ );
+
+ return $form;
+}
+
+/**
+ * Default submit handler for mapping on import form
+ */
+function feeds_import_mapping_form_submit($form, &$form_state) {
+ $form['#configurable']->addConfig($form_state['values']);
+ $form['#configurable']->save();
+ drupal_set_message(t('Your changes have been saved.'));
+ feeds_cache_clear(FALSE);
+}
+
+/**
+ * Import submit handler for mapping on import form
+ */
+function feeds_import_mapping_form_import_submit($form, &$form_state) {
+ // Handle the redirections
+ if (!$form['#feed_nid']) {
+ $form_state['redirect'] = 'import/' . $form['#importer_id'];
+ }
+ else {
+ $form_state['redirect'] = 'node/' . $form['#feed_nid'];
+ }
+
+ $source = feeds_source($form['#importer_id'], $form['#feed_nid']);
+
+ // Refresh feed if import on create is selected.
+ if ($source->importer->config['import_on_create']) {
+ $source->startImport();
+ }
+
+ // Add to schedule, make sure importer is scheduled, too.
+ $source->schedule();
+ $source->importer->schedule();
+}
+
+/**
+ * Theme function for feeds_mapping_form().
+ */
+function theme_feeds_mapping_form($variables) {
+ $form = $variables['form'];
+
+ // Build the actual mapping table.
+ $header = array(
+ t('Source'),
+ t('Target'),
+ t('Target configuration'),
+ ' ',
+ t('Weight'),
+ );
+ $rows = array();
+ if (is_array($form['#mappings'])) {
+ foreach ($form['#mappings'] as $i => $mapping) {
+ // Some parsers do not define source options.
+ $source = isset($form['source']['#options'][$mapping['source']]) ? $form['source']['#options'][$mapping['source']] : $mapping['source'];
+ $target = isset($form['target']['#options'][$mapping['target']]) ? check_plain($form['target']['#options'][$mapping['target']]) : '' . t('Missing') . '';
+ $rows[] = array(
+ 'data' => array(
+ check_plain($source),
+ $target,
+ drupal_render($form['config'][$i]),
+ drupal_render($form['remove_flags'][$i]),
+ drupal_render($form['mapping_weight'][$i]),
+ ),
+ 'class' => array('draggable', 'tabledrag-leaf'),
+ );
+ }
+ }
+ if (!count($rows)) {
+ $rows[] = array(
+ array(
+ 'colspan' => 5,
+ 'data' => t('No mappings defined.'),
+ ),
+ );
+ }
+ $rows[] = array(
+ drupal_render($form['source']),
+ drupal_render($form['target']),
+ '',
+ drupal_render($form['add']),
+ '',
+ );
+ $output = '' . drupal_render($form['help']) . '
';
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'feeds-mapping-overview')));
+
+ // Build the help table that explains available sources.
+ $legend = '';
+ $rows = array();
+ foreach (element_children($form['legendset']['legend']['sources']) as $k) {
+ $rows[] = array(
+ check_plain(drupal_render($form['legendset']['legend']['sources'][$k]['name'])),
+ check_plain(drupal_render($form['legendset']['legend']['sources'][$k]['description'])),
+ );
+ }
+ if (count($rows)) {
+ $legend .= '' . t('Sources') . '
';
+ $legend .= theme('table', array('header' => array(t('Name'), t('Description')), 'rows' => $rows));
+ }
+
+ // Build the help table that explains available targets.
+ $rows = array();
+ foreach (element_children($form['legendset']['legend']['targets']) as $k) {
+ $rows[] = array(
+ check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['name'])),
+ check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['description'])),
+ );
+ }
+ $legend .= '' . t('Targets') . '
';
+ $legend .= theme('table', array('header' => array(t('Name'), t('Description')), 'rows' => $rows));
+
+ // Stick tables into collapsible fieldset.
+ $form['legendset']['legend'] = array(
+ '#markup' => '' . $legend . '
',
+ );
+
+ $output .= drupal_render($form['legendset']);
+ $output .= drupal_render_children($form);
+
+ drupal_add_tabledrag('feeds-mapping-overview', 'order', 'sibling', 'feeds-mapping-weight');
+ return $output;
+}
+
diff --git a/feeds_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc
index f975ffe..77a8a3b 100644
--- a/feeds_ui/feeds_ui.admin.inc
+++ b/feeds_ui/feeds_ui.admin.inc
@@ -35,17 +35,6 @@ function feeds_ui_edit_help() {
}
/**
- * Help text for mapping.
- */
-function feeds_ui_mapping_help() {
- return t('
-
- Define which elements of a single item of a feed (= Sources) map to which content pieces in Drupal (= Targets). Make sure that at least one definition has a Unique target. A unique target means that a value for a target can only occur once. E. g. only one item with the URL http://example.com/content/1 can exist.
-
- ');
-}
-
-/**
* Build overview of available configurations.
*/
function feeds_ui_overview_form($form, &$form_status) {
@@ -315,9 +304,11 @@ function feeds_ui_edit_page(FeedsImporter $importer, $active = 'help', $plugin_k
break;
case 'mapping':
- $processor_name = isset($plugins[$config['processor']['plugin_key']]['name']) ? $plugins[$config['processor']['plugin_key']]['name'] : $plugins['FeedsMissingPlugin']['name'];
- $active_container['title'] = t('Mapping for @processor', array('@processor' => $processor_name));
- $active_container['body'] = drupal_get_form('feeds_ui_mapping_form', $importer);
+ if (in_array($plugin_key, array_keys($plugins)) && $plugin = feeds_plugin($plugin_key, $importer->id)) {
+ $processor_name = isset($plugins[$config['processor']['plugin_key']]['name']) ? $plugins[$config['processor']['plugin_key']]['name'] : $plugins['FeedsMissingPlugin']['name'];
+ $active_container['title'] = t('Mapping for @processor', array('@processor' => $processor_name));
+ $active_container['body'] = feeds_get_form($plugin, 'mappingForm');
+ }
break;
}
@@ -388,8 +379,8 @@ function feeds_ui_edit_page(FeedsImporter $importer, $active = 'help', $plugin_k
$actions = array();
if ($importer->processor->hasConfigForm()) {
$actions[] = l(t('Settings'), $path . '/settings/' . $config['processor']['plugin_key']);
+ $actions[] = l(t('Mapping'), $path .'/mapping/'. $config['processor']['plugin_key']);
}
- $actions[] = l(t('Mapping'), $path . '/mapping');
$info['title'] = t('Processor');
$info['body'] = array(
array(
@@ -484,403 +475,6 @@ function theme_feeds_ui_plugin_form($variables) {
}
/**
- * Edit mapping.
- *
- * @todo Completely merge this into config form handling. This is just a
- * shared form of configuration, most of the common functionality can live in
- * FeedsProcessor, a flag can tell whether mapping is supported or not.
- */
-function feeds_ui_mapping_form($form, &$form_state, $importer) {
- $form['#importer'] = $importer->id;
- $form['#mappings'] = $mappings = $importer->processor->getMappings();
- $form['help']['#markup'] = feeds_ui_mapping_help();
- $form['#prefix'] = '';
- $form['#suffix'] = '
';
-
- // Show message when target configuration gets changed.
- if (!empty($form_state['mapping_settings'])) {
- $form['#mapping_settings'] = $form_state['mapping_settings'];
- $form['changed'] = array(
- '#theme_wrappers' => array('container'),
- '#attributes' => array('class' => array('messages', 'warning')),
- '#markup' => t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.'),
- );
- }
-
- // Get mapping sources from parsers and targets from processor, format them
- // for output.
- // Some parsers do not define mapping sources but let them define on the fly.
- if ($sources = $importer->parser->getMappingSources()) {
- $source_options = _feeds_ui_format_options($sources);
- foreach ($sources as $k => $source) {
- $legend['sources'][$k]['name']['#markup'] = empty($source['name']) ? $k : $source['name'];
- $legend['sources'][$k]['description']['#markup'] = empty($source['description']) ? '' : $source['description'];
- }
- }
- else {
- $legend['sources']['#markup'] = t('This parser supports free source definitions. Enter the name of the source field in lower case into the Source text field above.');
- }
- $targets = $importer->processor->getMappingTargets();
- $target_options = _feeds_ui_format_options($targets);
- $legend['targets'] = array();
- foreach ($targets as $k => $target) {
- $legend['targets'][$k]['name']['#markup'] = empty($target['name']) ? $k : $target['name'];
- $legend['targets'][$k]['description']['#markup'] = empty($target['description']) ? '' : $target['description'];
- }
-
- // Legend explaining source and target elements.
- $form['legendset'] = array(
- '#type' => 'fieldset',
- '#title' => t('Legend'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- '#tree' => TRUE,
- );
- $form['legendset']['legend'] = $legend;
-
- // Add config forms and remove flags to mappings.
- $form['config'] = $form['remove_flags'] = $form['mapping_weight'] = array(
- '#tree' => TRUE,
- );
- if (is_array($mappings)) {
-
- $delta = count($mappings) + 2;
-
- foreach ($mappings as $i => $mapping) {
- 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'),
- '#prefix' => '',
- '#suffix' => '
',
- );
-
- $form['mapping_weight'][$i] = array(
- '#type' => 'weight',
- '#title' => '',
- '#default_value' => $i,
- '#delta' => $delta,
- '#attributes' => array(
- 'class' => array(
- 'feeds-ui-mapping-weight'
- ),
- ),
- );
- }
- }
-
- if (isset($source_options)) {
- $form['source'] = array(
- '#type' => 'select',
- '#title' => t('Source'),
- '#title_display' => 'invisible',
- '#options' => $source_options,
- '#empty_option' => t('- Select a source -'),
- '#description' => t('An element from the feed.'),
- );
- }
- else {
- $form['source'] = array(
- '#type' => 'textfield',
- '#title' => t('Source'),
- '#title_display' => 'invisible',
- '#size' => 20,
- '#default_value' => '',
- '#description' => t('The name of source field.'),
- );
- }
- $form['target'] = array(
- '#type' => 'select',
- '#title' => t('Target'),
- '#title_display' => 'invisible',
- '#options' => $target_options,
- '#empty_option' => t('- Select a target -'),
- '#description' => t('The field that stores the data.'),
- );
-
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['save'] = array(
- '#type' => 'submit',
- '#value' => t('Save'),
- );
- return $form;
-}
-
-/**
- * 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) {
- $settings_form = array();
-
- foreach ($target['form_callbacks'] as $callback) {
- $settings_form += call_user_func($callback, $mapping, $target, $form, $form_state);
- }
-
- // Merge in the optional unique form.
- $settings_form += feeds_ui_mapping_settings_optional_unique_form($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 {
- // Build the summary.
- $summary = array();
-
- foreach ($target['summary_callbacks'] as $callback) {
- $summary[] = call_user_func($callback, $mapping, $target, $form, $form_state);
- }
-
- // Filter out empty summary values.
- $summary = implode('
', array_filter($summary));
-
- // Append the optional unique summary.
- if ($optional_unique_summary = feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state)) {
- $summary .= ' ' . $optional_unique_summary;
- }
-
- if ($summary) {
- return array(
- 'summary' => array(
- '#prefix' => '',
- '#markup' => $summary,
- '#suffix' => '
',
- ),
- 'edit_settings' => $base_button + array(
- '#type' => 'image_button',
- '#name' => 'mapping_settings_edit_' . $i,
- '#src' => 'misc/configure.png',
- '#attributes' => array('alt' => t('Edit')),
- '#op' => 'edit',
- ),
- );
- }
- }
- return array();
-}
-
-/**
- * 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 handler for feeds_ui_mapping_form().
- */
-function feeds_ui_mapping_form_validate($form, &$form_state) {
- if (!strlen($form_state['values']['source']) xor !strlen($form_state['values']['target'])) {
-
- // Check triggering_element here so we can react differently for ajax
- // submissions.
- switch ($form_state['triggering_element']['#name']) {
-
- // Regular form submission.
- case 'op':
- if (!strlen($form_state['values']['source'])) {
- form_error($form['source'], t('You must select a mapping source.'));
- }
- else {
- form_error($form['target'], t('You must select a mapping target.'));
- }
- break;
-
- // Be more relaxed on ajax submission.
- default:
- form_set_value($form['source'], '', $form_state);
- form_set_value($form['target'], '', $form_state);
- break;
- }
- }
-}
-
-/**
- * Submission handler for feeds_ui_mapping_form().
- */
-function feeds_ui_mapping_form_submit($form, &$form_state) {
- $importer = feeds_importer($form['#importer']);
- $processor = $importer->processor;
-
- $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'];
- 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'])) {
- $remove_flags = array_keys(array_filter($form_state['values']['remove_flags']));
-
- foreach ($remove_flags as $k) {
- unset($mappings[$k]);
- unset($form_state['values']['mapping_weight'][$k]);
- drupal_set_message(t('Mapping has been removed.'), 'status', FALSE);
- }
- }
-
- // Keep our keys clean.
- $mappings = array_values($mappings);
-
- if (!empty($mappings)) {
- array_multisort($form_state['values']['mapping_weight'], $mappings);
- }
-
- $processor->addConfig(array('mappings' => $mappings));
-
- if (strlen($form_state['values']['source']) && strlen($form_state['values']['target'])) {
- try {
- $mappings = $processor->getMappings();
- $mappings[] = array(
- 'source' => $form_state['values']['source'],
- 'target' => $form_state['values']['target'],
- 'unique' => FALSE,
- );
- $processor->addConfig(array('mappings' => $mappings));
- drupal_set_message(t('Mapping has been added.'));
- }
- catch (Exception $e) {
- drupal_set_message($e->getMessage(), 'error');
- }
- }
-
- $importer->save();
- drupal_set_message(t('Your changes have been saved.'));
-}
-
-/**
- * Walk the result of FeedsParser::getMappingSources() or
- * FeedsProcessor::getMappingTargets() and format them into
- * a Form API options array.
- */
-function _feeds_ui_format_options($options) {
- $result = array();
- foreach ($options as $k => $v) {
- if (is_array($v) && !empty($v['name'])) {
- $result[$k] = $v['name'] . ' (' . $k . ')';
- }
- elseif (is_array($v)) {
- $result[$k] = $k;
- }
- else {
- $result[$k] = $v;
- }
- }
- asort($result);
- return $result;
-}
-
-/**
- * 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 unique.');
- }
- 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) {
- $settings_form = array();
-
- if (!empty($target['optional_unique'])) {
- $settings_form['unique'] = array(
- '#type' => 'checkbox',
- '#title' => t('Unique'),
- '#default_value' => !empty($mapping['unique']),
- );
- }
-
- return $settings_form;
-}
-
-/**
* Theme feeds_ui_overview_form().
*/
function theme_feeds_ui_overview_form($variables) {
@@ -1012,102 +606,6 @@ function theme_feeds_ui_container($variables) {
}
/**
- * Theme function for feeds_ui_mapping_form().
- */
-function theme_feeds_ui_mapping_form($variables) {
- $form = $variables['form'];
-
- // Build the actual mapping table.
- $header = array(
- t('Source'),
- t('Target'),
- t('Target configuration'),
- ' ',
- t('Weight'),
- );
- $rows = array();
- if (is_array($form['#mappings'])) {
- foreach ($form['#mappings'] as $i => $mapping) {
- // Some parsers do not define source options.
- $source = isset($form['source']['#options'][$mapping['source']]) ? $form['source']['#options'][$mapping['source']] : $mapping['source'];
- $target = isset($form['target']['#options'][$mapping['target']]) ? check_plain($form['target']['#options'][$mapping['target']]) : '' . t('Missing') . '';
- // Add indicator to target if target configuration changed.
- if (isset($form['#mapping_settings'][$i])) {
- $target .= '*';
- }
- $rows[] = array(
- 'data' => array(
- check_plain($source),
- $target,
- drupal_render($form['config'][$i]),
- drupal_render($form['remove_flags'][$i]),
- drupal_render($form['mapping_weight'][$i]),
- ),
- 'class' => array('draggable', 'tabledrag-leaf'),
- );
- }
- }
- if (!count($rows)) {
- $rows[] = array(
- array(
- 'colspan' => 5,
- 'data' => t('No mappings defined.'),
- ),
- );
- }
- $rows[] = array(
- drupal_render($form['source']),
- drupal_render($form['target']),
- '',
- drupal_render($form['add']),
- '',
- );
- $output = '';
- if (!empty($form['changed'])) {
- // This form element is only available if target configuration changed.
- $output .= drupal_render($form['changed']);
- }
- $output .= '' . drupal_render($form['help']) . '
';
- $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'feeds-ui-mapping-overview')));
-
- // Build the help table that explains available sources.
- $legend = '';
- $rows = array();
- foreach (element_children($form['legendset']['legend']['sources']) as $k) {
- $rows[] = array(
- check_plain(drupal_render($form['legendset']['legend']['sources'][$k]['name'])),
- check_plain(drupal_render($form['legendset']['legend']['sources'][$k]['description'])),
- );
- }
- if (count($rows)) {
- $legend .= '' . t('Sources') . '
';
- $legend .= theme('table', array('header' => array(t('Name'), t('Description')), 'rows' => $rows));
- }
-
- // Build the help table that explains available targets.
- $rows = array();
- foreach (element_children($form['legendset']['legend']['targets']) as $k) {
- $rows[] = array(
- check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['name']) . ' (' . $k . ')'),
- check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['description'])),
- );
- }
- $legend .= '' . t('Targets') . '
';
- $legend .= theme('table', array('header' => array(t('Name'), t('Description')), 'rows' => $rows));
-
- // Stick tables into collapsible fieldset.
- $form['legendset']['legend'] = array(
- '#markup' => '' . $legend . '
',
- );
-
- $output .= drupal_render($form['legendset']);
- $output .= drupal_render_children($form);
-
- drupal_add_tabledrag('feeds-ui-mapping-overview', 'order', 'sibling', 'feeds-ui-mapping-weight');
- return $output;
-}
-
-/**
* Page callback to import a Feeds importer.
*/
function feeds_ui_importer_import($form, &$form_state) {
diff --git a/feeds_ui/feeds_ui.module b/feeds_ui/feeds_ui.module
index 08cc40f..f89a0c1 100644
--- a/feeds_ui/feeds_ui.module
+++ b/feeds_ui/feeds_ui.module
@@ -99,10 +99,6 @@ function feeds_ui_theme() {
'render element' => 'form',
'file' => 'feeds_ui.admin.inc',
),
- 'feeds_ui_mapping_form' => array(
- 'render element' => 'form',
- 'file' => 'feeds_ui.admin.inc',
- ),
'feeds_ui_edit_page' => array(
'variables' => array('info' => NULL, 'active' => NULL),
'file' => 'feeds_ui.admin.inc',
diff --git a/plugins/FeedsCSVParser.inc b/plugins/FeedsCSVParser.inc
index 04d79b6..337156f 100644
--- a/plugins/FeedsCSVParser.inc
+++ b/plugins/FeedsCSVParser.inc
@@ -120,7 +120,7 @@ class FeedsCSVParser extends FeedsParser {
$form = array();
$form['#weight'] = -10;
- $mappings = feeds_importer($this->id)->processor->config['mappings'];
+ $mappings = feeds_importer($this->id)->processor->getMappings();
$sources = $uniques = array();
foreach ($mappings as $mapping) {
$sources[] = check_plain($mapping['source']);
@@ -176,6 +176,7 @@ class FeedsCSVParser extends FeedsParser {
return array(
'delimiter' => ',',
'no_headers' => 0,
+ 'mapping_on_import' => 1,
);
}
diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc
index 2cfdf30..cb47c56 100644
--- a/plugins/FeedsNodeProcessor.inc
+++ b/plugins/FeedsNodeProcessor.inc
@@ -170,6 +170,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
'expire' => FEEDS_EXPIRE_NEVER,
'author' => 0,
'authorize' => TRUE,
+ 'mapping_on_import' => 0,
) + parent::configDefaults();
}
diff --git a/plugins/FeedsParser.inc b/plugins/FeedsParser.inc
index 1e1d329..d14c21a 100644
--- a/plugins/FeedsParser.inc
+++ b/plugins/FeedsParser.inc
@@ -14,6 +14,7 @@ class FeedsParserResult extends FeedsResult {
public $link;
public $items;
public $current_item;
+ public $context;
/**
* Constructor.
@@ -23,6 +24,7 @@ class FeedsParserResult extends FeedsResult {
$this->description = '';
$this->link = '';
$this->items = $items;
+ $this->context = 'import';
}
/**
@@ -46,6 +48,22 @@ class FeedsParserResult extends FeedsResult {
public function currentItem() {
return empty($this->current_item) ? NULL : $this->current_item;
}
+
+ /**
+ * Get mapping sources after the batch items has been populated.
+ */
+ public function getMappingSources() {
+ $sources = array();
+ if (count($this->items) > 0) {
+ foreach ($this->items[0] as $k => $v) {
+ $sources[$k] = array(
+ 'title' => $k,
+ 'description' => '',
+ );
+ }
+ }
+ return $sources;
+ }
}
/**
diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc
index e8b4b49..5e6deb1 100644
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -667,7 +667,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
// Many mappers add to existing fields rather than replacing them. Hence we
// need to clear target elements of each item before mapping in case we are
// mapping on a prepopulated item such as an existing node.
- foreach ($this->config['mappings'] as $mapping) {
+ foreach ($this->getMappings() as $mapping) {
if (isset($targets[$mapping['target']]['real_target'])) {
$target_item->{$targets[$mapping['target']]['real_target']} = NULL;
}
@@ -775,6 +775,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
'input_format' => NULL,
'skip_hash_check' => FALSE,
'bundle' => $bundle,
+ 'mapping_on_import' => 0,
);
}
@@ -851,9 +852,20 @@ abstract class FeedsProcessor extends FeedsPlugin {
/**
* Get mappings.
+ *
+ * Mappings must not be got directly by using $this->config['mappings']
+ * but instead via this function.
*/
public function getMappings() {
- return isset($this->config['mappings']) ? $this->config['mappings'] : array();
+ // If the source is not empty, use the source instead.
+ if (isset($this->source)) {
+ $config = $this->source->getConfigFor($this);
+ $mappings = isset($config['mappings']) ? $config['mappings'] : $this->config['mappings'];
+ }
+ else {
+ $mappings = $this->config['mappings'];
+ }
+ return $mappings;
}
/**
@@ -994,7 +1006,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
protected function uniqueTargets(FeedsSource $source, FeedsParserResult $result) {
$parser = feeds_importer($this->id)->parser;
$targets = array();
- foreach ($this->config['mappings'] as $mapping) {
+ foreach ($this->getMappings() as $mapping) {
if (!empty($mapping['unique'])) {
// Invoke the parser's getSourceElement to retrieve the value for this
// mapping's source.
@@ -1180,6 +1192,265 @@ abstract class FeedsProcessor extends FeedsPlugin {
return $dependencies;
}
+ /**
+ * configDefaults for FeedsSource
+ */
+ function sourceDefaults() {
+ return array ('mappings' => $this->config['mappings']);
+ }
+
+ /**
+ * FeedsProcessor has source config
+ * default: mappings
+ */
+ function hasSourceConfig() {
+ return TRUE;
+ }
+
+ /**
+ * Render the mapping form.
+ * This function should NOT be overwritten.
+ * @param
+ * The location from where this function is called. For now, it will be passed either
+ * 'import' or 'config'
+ * @param
+ * The result object that contains populated items that has been fetched and parsed.
+ * This will only be handled if the first parameter is NOT 'config'
+ */
+ public function mappingForm(&$form_state, $result = NULL) {
+ // Get the FeedsImporter object
+ $importer = feeds_importer($this->id);
+
+ $form = array();
+ if (empty($result)) {
+ // Mapping on import checkbox
+ $form['mapping_on_import'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Define mapping on import'),
+ '#description' => t('If this is checked, the mapping form here will only act as a default mapping. User can further specify the preferred mapping in the import page.'),
+ '#weight' => -100,
+ '#default_value' => $this->config['mapping_on_import'],
+ );
+ }
+
+ $form['#theme'] = 'feeds_mapping_form';
+ $form['help']['#markup'] = feeds_mapping_form_help();
+ $form['#importer'] = $importer->id;
+ $form['#prefix'] = '';
+ $form['#suffix'] = '
';
+
+ // Get mapping sources from parsers or result and targets from processor, format them
+ // for output.
+ // Some parsers do not define mapping sources but let them define on the fly.
+ if (empty($result) || $result->context <> 'mapping') {
+ $sources = $importer->parser->getMappingSources();
+ }
+ else {
+ $sources = $result->getMappingSources();
+ }
+
+ if ($sources) {
+ $source_options = _feeds_mapping_form_format_options($sources);
+ foreach ($sources as $k => $source) {
+ $legend['sources'][$k]['name']['#markup'] = empty($source['name']) ? $k : $source['name'];
+ $legend['sources'][$k]['description']['#markup'] = empty($source['description']) ? '' : $source['description'];
+ }
+ }
+ else {
+ $legend['sources']['#markup'] = t('This parser supports free source definitions. Enter the name of the source field in lower case into the Source text field above.');
+ }
+ $targets = $importer->processor->getMappingTargets();
+ $target_options = _feeds_mapping_form_format_options($targets);
+ foreach ($targets as $k => $target) {
+ $legend['targets'][$k]['name']['#markup'] = empty($target['name']) ? $k : $target['name'];
+ $legend['targets'][$k]['description']['#markup'] = empty($target['description']) ? '' : $target['description'];
+ }
+
+ $form['legendset'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Legend'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#tree' => TRUE,
+ );
+ $form['legendset']['legend'] = $legend;
+
+ $mappings = $this->getMappings();
+ $form['#mappings'] = $mappings;
+
+ // Add config forms and remove flags to mappings.
+ $form['config'] = $form['remove_flags'] = $form['mapping_weight'] = array(
+ '#tree' => TRUE,
+ );
+
+ if (is_array($mappings)) {
+ $form['mappings']['#tree'] = TRUE;
+
+ $delta = count($mappings) + 2;
+
+ foreach ($mappings as $i => $mapping) {
+ if (isset($targets[$mapping['target']])) {
+ $form['config'][$i] = feeds_mapping_settings_form($form, $form_state, $i, $mapping, $targets[$mapping['target']]);
+ }
+
+ $mappings[$i]['source'] = isset($source_options) ? $source_options[$mappings[$i]['source']] : $mappings[$i]['source'];
+ $mappings[$i]['target'] = $target_options[$mappings[$i]['target']];
+
+ $form['remove_flags'][$i] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove'),
+ '#prefix' => '',
+ '#suffix' => '
',
+ );
+
+ $form['mapping_weight'][$i] = array(
+ '#type' => 'weight',
+ '#title' => '',
+ '#default_value' => $i,
+ '#delta' => $delta,
+ '#attributes' => array(
+ 'class' => array(
+ 'feeds-ui-mapping-weight'
+ ),
+ ),
+ );
+
+ // Declare the mappings data in the hidden field, so later
+ // the full mapping data will be submitted.
+ foreach ($mapping as $k => $v) {
+ $form['mappings'][$i][$k] = array (
+ '#type' => 'hidden',
+ '#value' => $v,
+ );
+ }
+ }
+ }
+
+ if (isset($source_options)) {
+ $form['source'] = array(
+ '#type' => 'select',
+ '#title' => t('Source'),
+ '#title_display' => 'invisible',
+ '#options' => $source_options,
+ '#empty_option' => t('- Select a source -'),
+ '#description' => t('An element from the feed.'),
+ );
+ }
+ else {
+ $form['source'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Source'),
+ '#title_display' => 'invisible',
+ '#size' => 20,
+ '#default_value' => '',
+ '#description' => t('The name of source field.'),
+ );
+ }
+ $form['target'] = array(
+ '#type' => 'select',
+ '#title' => t('Target'),
+ '#title_display' => 'invisible',
+ '#options' => $target_options,
+ '#empty_option' => t('- Select a target -'),
+ '#description' => t('The field that stores the data.'),
+ );
+ $form['add'] = array(
+ '#type' => 'submit',
+ '#value' => t('Add'),
+ '#submit' => array('feeds_form_submit'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * Submit handler for FeedsProcessor::mappingForm()
+ */
+ function mappingFormSubmit(&$values) {
+ // If add new mappings submit is invoked.
+ if ($values['op'] == t('Add')) {
+ if (!empty($values['source']) && !empty($values['target'])) {
+ if (method_exists($this, 'addMapping')) {
+ $this->addMapping($values['source'], $values['target']);
+ }
+ $values['mappings'][] = array(
+ 'source' => $values['source'],
+ 'target' => $values['target'],
+ 'unique' => FALSE,
+ );
+ }
+ }
+ // If other submit is invoked.
+ else {
+ if (isset($values['remove_flags'])) {
+ foreach ($values['remove_flags'] as $k => $v) {
+ if ($v) {
+ unset($values['mappings'][$k]);
+ }
+ }
+ // Keep our keys clean.
+ $values['mappings'] = array_values($values['mappings']);
+ }
+ }
+
+ if ($this->config['mapping_on_import'] && !empty($this->source)) {
+ $source_values[get_class($this)] = $values;
+ $this->source->addConfig($source_values);
+ $this->source->save();
+ }
+ else {
+ $this->addConfig($values);
+ $this->save();
+ }
+
+ drupal_set_message(t('Your changes have been saved.'));
+ feeds_cache_clear(FALSE);
+ }
+
+ /**
+ * Validation handler for mappingForm()
+ */
+ function mappingFormValidate(&$values) {
+ }
+}
+
+/**
+ * Walk the result of FeedsParser::getMappingSources() or
+ * FeedsProcessor::getMappingTargets() and format them into
+ * a Form API options array.
+ */
+function _feeds_mapping_form_format_options($options) {
+ $result = array();
+ foreach ($options as $k => $v) {
+ if (is_array($v) && !empty($v['name'])) {
+ $result[$k] = $v['name'];
+ }
+ elseif (is_array($v)) {
+ $result[$k] = $k;
+ }
+ else {
+ $result[$k] = $v;
+ }
+ }
+ return $result;
+}
+
+/**
+ * Help text for mapping.
+ */
+function feeds_mapping_form_help() {
+ return
+ '' .
+ t('Define which elements of a single item of a feed') .
+ '(= ' . t('Sources') . ') ' .
+ t('map to which content pieces in Drupal') .
+ ' (= ' . t('Targets') . '). ' .
+ t('Make sure that at least one definition has a') .
+ ' ' . t('Unique target') . '. ' .
+ t('A unique target means that a value for a target can only occur once. E. g. only one item with the URL ') .
+ 'http://example.com/story/1 ' .
+ t('can exist.') .
+ '
';
}
class FeedsProcessorBundleNotDefined extends Exception {}
diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc
index f69819f..d5dc791 100644
--- a/plugins/FeedsTermProcessor.inc
+++ b/plugins/FeedsTermProcessor.inc
@@ -79,6 +79,7 @@ class FeedsTermProcessor extends FeedsProcessor {
public function configDefaults() {
return array(
'vocabulary' => 0,
+ 'mapping_on_import' => 0,
) + parent::configDefaults();
}
diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc
index 778c72b..661f149 100644
--- a/plugins/FeedsUserProcessor.inc
+++ b/plugins/FeedsUserProcessor.inc
@@ -107,6 +107,7 @@ class FeedsUserProcessor extends FeedsProcessor {
'roles' => array(),
'status' => 1,
'defuse_mail' => FALSE,
+ 'mapping_on_import' => 0,
) + parent::configDefaults();
}
diff --git a/tests/feeds.test b/tests/feeds.test
index b3ce145..8280253 100644
--- a/tests/feeds.test
+++ b/tests/feeds.test
@@ -442,7 +442,9 @@ class FeedsWebTestCase extends DrupalWebTestCase {
*/
public function addMappings($id, array $mappings, $test_mappings = TRUE) {
- $path = "admin/structure/feeds/$id/mapping";
+ $source = feeds_source($id);
+ $config = $source->importer->config;
+ $path = 'admin/structure/feeds/'. $id .'/mapping/' . $config['processor']['plugin_key'];
// Iterate through all mappings and add the mapping via the form.
foreach ($mappings as $i => $mapping) {
diff --git a/tests/feeds_parser_sitemap.test b/tests/feeds_parser_sitemap.test
index 2a16463..7aa9712 100644
--- a/tests/feeds_parser_sitemap.test
+++ b/tests/feeds_parser_sitemap.test
@@ -54,6 +54,7 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
$path = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/';
$nid = $this->createFeedNode('sitemap', $path . 'sitemap-example.xml', 'Testing Sitemap Parser');
$this->assertText('Created 5 nodes');
+ $this->show();
// Assert DB status.
$count = db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'")->fetchField();