diff -urp feeds.HEAD/feeds.module feeds/feeds.module --- feeds.HEAD/feeds.module 2010-09-18 14:57:33.000000000 +0200 +++ feeds/feeds.module 2010-09-18 15:01:27.000000000 +0200 @@ -175,6 +175,15 @@ function feeds_menu() { 'file' => 'feeds.pages.inc', 'type' => MENU_LOCAL_TASK, ); + $items['import/'. $importer->id . '/mapping'] = array( + 'title' => t('Mapping'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('feeds_import_mapping_form', 1), + 'access callback' => 'feeds_access', + 'access arguments' => array('import', $importer->id), + 'file' => 'feeds.pages.inc', + 'type' => MENU_LOCAL_TASK, + ); } else { $items['node/%node/import'] = array( @@ -197,6 +206,16 @@ function feeds_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => 11, ); + $items['node/%node/mapping'] = array( + 'title' => t('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, + ); } $items += $importer->fetcher->menuItem(); } @@ -226,6 +245,9 @@ function feeds_theme() { 'feeds_upload' => array( 'file' => 'feeds.pages.inc', ), + 'feeds_mapping_form' => array( + 'file' => 'feeds.pages.inc' + ), ); } @@ -370,17 +392,19 @@ function feeds_nodeapi(&$node, $op, $for $source->addConfig($node_feeds); $source->save(); - // Refresh feed if import on create is selected and suppress_import is - // not set. - if ($op == 'insert' && feeds_importer($importer_id)->config['import_on_create'] && !isset($node_feeds['suppress_import'])) { - feeds_batch_set(t('Importing'), 'import', $importer_id, $node->nid); + if ($source->importer->processor->config['mapping_on_import']) { + $_REQUEST['destination'] = 'node/' . $node->nid . '/mapping/' . $source->importer->id; } - // Add source to schedule, make sure importer is scheduled, too. - if ($op == 'insert') { + else { + // Refresh feed if import on create is selected and suppress_import is + // not set. + if ($op == 'insert' && feeds_importer($importer_id)->config['import_on_create'] && !isset($node_feeds['suppress_import'])) { + feeds_batch_set(t('Importing'), 'import', $importer_id, $node->nid); + } + // Add to schedule, make sure importer is scheduled, too. $source->schedule(); $source->importer->schedule(); } - $node_feeds = NULL; break; case 'delete': // Remove attached source. diff -urp feeds.HEAD/feeds.pages.inc feeds/feeds.pages.inc --- feeds.HEAD/feeds.pages.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/feeds.pages.inc 2010-09-18 15:01:27.000000000 +0200 @@ -59,6 +59,12 @@ function feeds_import_form(&$form_state, '#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'; + } + return $form; } @@ -74,20 +80,24 @@ function feeds_import_form_validate($for * Submit handler for feeds_import_form(). */ function feeds_import_form_submit($form, &$form_state) { - // Save source and import. $source = feeds_source($form['#importer_id']); $source->addConfig($form_state['values']['feeds']); $source->save(); - // Refresh feed if import on create is selected. - if ($source->importer->config['import_on_create']) { - feeds_batch_set(t('Importing'), 'import', $form['#importer_id']); + 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']) { + feeds_batch_set(t('Importing'), 'import', $form['#importer_id']); + } + // Add to schedule, make sure importer is scheduled, too. + $source->schedule(); + $source->importer->schedule(); } - - // Add to schedule, make sure importer is scheduled, too. - $source->schedule(); - $source->importer->schedule(); } /** @@ -178,3 +188,147 @@ function theme_feeds_upload($element) { $output .= ''; return theme('form_element', $element, $output); } + +/** + * Declare Feeds mapping on import form + */ +function feeds_import_mapping_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 batch to provide mapping sources + $batch = $source->importer->fetcher->fetch($source); + $source->importer->parser->parse($batch, $source); + + $form = $source->importer->processor->mappingForm($form_state, 'import', $batch); + + // 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']) { + feeds_batch_set(t('Importing'), 'import', $form['#importer_id'], $form['#feed_nid']); + } + + // 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($form) { + // Build the actual mapping table. + $header = array( + t('Source'), + t('Target'), + t('Unique target'), + ' ', + ); + $rows = array(); + if (is_array($form['#mappings'])) { + foreach ($form['#mappings'] as $i => $mapping) { + $rows[] = array( + check_plain($mapping['source']), + check_plain($mapping['target']), + drupal_render($form['unique_flags'][$i]), + drupal_render($form['remove_flags'][$i]), + ); + } + } + if (!count($rows)) { + $rows[] = array( + array( + 'colspan' => 4, + 'data' => t('No mappings defined.'), + ), + ); + } + $rows[] = array( + drupal_render($form['source']), + drupal_render($form['target']), + '', + drupal_render($form['add']), + ); + $output = '
'; + $output .= theme('table', $header, $rows); + + // 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(t('Name'), t('Description')), $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(t('Name'), t('Description')), $rows); + + // Stick tables into collapsible fieldset. + $form['legendset']['legend'] = array( + '#value' => '
'. $legend .'
', + ); + + $output .= drupal_render($form['legendset']); + + $output .= drupal_render($form); + return $output; +} diff -urp feeds.HEAD/feeds_ui/feeds_ui.admin.inc feeds/feeds_ui/feeds_ui.admin.inc --- feeds.HEAD/feeds_ui/feeds_ui.admin.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/feeds_ui/feeds_ui.admin.inc 2010-09-18 15:01:27.000000000 +0200 @@ -36,17 +36,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/story/1 can exist. -

- '); -} - -/** * Build overview of available configurations. */ function feeds_ui_overview_form(&$form_status) { @@ -272,7 +261,6 @@ function feeds_ui_export_form(&$form_sta * Edit feed configuration. */ function feeds_ui_edit_page($importer, $active = 'help', $plugin_key = '') { - // Get plugins and configuration. $plugins = feeds_get_plugins(); $config = $importer->config; @@ -310,8 +298,10 @@ function feeds_ui_edit_page($importer, $ } break; case 'mapping': - $active_container['title'] = t('Mapping for !processor', array('!processor' => $plugins[$config['processor']['plugin_key']]['name'])); - $active_container['body'] = drupal_get_form('feeds_ui_mapping_form', $importer); + if (in_array($plugin_key, array_keys($plugins)) && $plugin = feeds_plugin_instance($plugin_key, $importer->id)) { + $active_container['title'] = t('Mapping for !processor', array('!processor' => $plugins[$config['processor']['plugin_key']]['name'])); + $active_container['body'] = feeds_get_form($plugin, 'mappingForm'); + } break; } @@ -382,8 +372,8 @@ function feeds_ui_edit_page($importer, $ $actions = array(); if (feeds_get_form($importer->processor, 'configForm')) { $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( @@ -473,155 +463,6 @@ function theme_feeds_ui_plugin_form($for } /** - * 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_state, $importer) { - drupal_add_js(drupal_get_path('module', 'feeds_ui') .'/feeds_ui.js'); - - $form = array(); - $form['#importer'] = $importer; - $form['#mappings'] = $mappings = $importer->processor->getMappings(); - $form['help']['#value'] = feeds_ui_mapping_help(); - - // 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']['#value'] = empty($source['name']) ? $k : $source['name']; - $legend['sources'][$k]['description']['#value'] = empty($source['description']) ? '' : $source['description']; - } - } - else { - $legend['sources']['#value'] = 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); - foreach ($targets as $k => $target) { - $legend['targets'][$k]['name']['#value'] = empty($target['name']) ? $k : $target['name']; - $legend['targets'][$k]['description']['#value'] = 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 unique and remove forms to mappings. - $form['unique_flags'] = $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' => 'feeds-ui-trigger-submit'), - ); - } - $form['remove_flags'][$i] = array( - '#type' => 'checkbox', - '#title' => t('Remove'), - '#prefix' => '', - ); - } - } - - if ($source_options) { - $form['source'] = array( - '#type' => 'select', - '#options' => array('' => t('Select a source')) + $source_options, - ); - } - else { - $form['source'] = array( - '#type' => 'textfield', - '#size' => 20, - '#default_value' => t('Name of source field'), - '#attributes' => array('class' => 'hide-text-on-focus'), - ); - } - $form['target'] = array( - '#type' => 'select', - '#options' => array('' => t('Select a target')) + $target_options, - ); - $form['add'] = array( - '#type' => 'submit', - '#value' => t('Add'), - '#submit' => array('feeds_ui_mapping_form_add_submit'), - ); - $form['save'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#attributes' => array('class' => 'feeds-ui-hidden-submit'), - ); - return $form; -} - -/** - * Submit handler for add button on feeds_ui_mapping_form(). - */ -function feeds_ui_mapping_form_add_submit($form, &$form_state) { - $importer = $form['#importer']; - try { - $mappings = $form['#mappings']; - $mappings[] = array( - 'source' => $form_state['values']['source'], - 'target' => $form_state['values']['target'], - 'unique' => FALSE, - ); - $importer->processor->addConfig(array('mappings' => $mappings)); - $importer->processor->save(); - drupal_set_message(t('Mapping has been added.')); - } - catch (Exception $e) { - drupal_set_message($e->getMessage(), 'error'); - } -} - -/** - * Submit handler for save button on feeds_ui_mapping_form(). - */ -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 - // 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['values']['remove_flags'] as $k => $v) { - if ($v) { - unset($mappings[$k]); - // Keep our keys clean. - $mappings = array_values($mappings); - } - } - $processor->addConfig(array('mappings' => $mappings)); - $processor->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. @@ -755,81 +596,3 @@ function theme_feeds_ui_container($conta $output .= '
'; return $output; } - -/** - * Theme function for feeds_ui_mapping_form(). - */ -function theme_feeds_ui_mapping_form($form) { - - // Build the actual mapping table. - $header = array( - t('Source'), - t('Target'), - t('Unique target'), - ' ', - ); - $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']; - $rows[] = array( - check_plain($source), - check_plain($form['target']['#options'][$mapping['target']]), - drupal_render($form['unique_flags'][$i]), - drupal_render($form['remove_flags'][$i]), - ); - } - } - if (!count($rows)) { - $rows[] = array( - array( - 'colspan' => 4, - 'data' => t('No mappings defined.'), - ), - ); - } - $rows[] = array( - drupal_render($form['source']), - drupal_render($form['target']), - '', - drupal_render($form['add']), - ); - $output = '
'; - $output .= theme('table', $header, $rows); - - // 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(t('Name'), t('Description')), $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(t('Name'), t('Description')), $rows); - - // Stick tables into collapsible fieldset. - $form['legendset']['legend'] = array( - '#value' => '
'. $legend .'
', - ); - - $output .= drupal_render($form['legendset']); - - $output .= drupal_render($form); - return $output; -} diff -urp feeds.HEAD/feeds_ui/feeds_ui.module feeds/feeds_ui/feeds_ui.module --- feeds.HEAD/feeds_ui/feeds_ui.module 2010-09-18 14:57:33.000000000 +0200 +++ feeds/feeds_ui/feeds_ui.module 2010-09-18 15:01:27.000000000 +0200 @@ -90,9 +90,6 @@ function feeds_ui_theme() { 'feeds_ui_overview_form' => array( 'file' => 'feeds_ui.admin.inc', ), - 'feeds_ui_mapping_form' => array( - 'file' => 'feeds_ui.admin.inc', - ), 'feeds_ui_edit_page' => array( 'file' => 'feeds_ui.admin.inc', ), diff -urp feeds.HEAD/includes/FeedsBatch.inc feeds/includes/FeedsBatch.inc --- feeds.HEAD/includes/FeedsBatch.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/includes/FeedsBatch.inc 2010-09-18 15:01:27.000000000 +0200 @@ -302,6 +302,22 @@ class FeedsImportBatch extends FeedsBatc public function getItemCount() { return count($this->items); } + + /** + * Get mapping sources after the batch items has been populated + */ + public function getMappingSources() { + $sources = array(); + if ($this->total > 0) { + foreach ($this->items[0] as $k => $v) { + $sources[$k] = array( + 'title' => $k, + 'description' => '', + ); + } + } + return $sources; + } } /** diff -urp feeds.HEAD/plugins/FeedsCSVParser.inc feeds/plugins/FeedsCSVParser.inc --- feeds.HEAD/plugins/FeedsCSVParser.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsCSVParser.inc 2010-09-18 15:01:27.000000000 +0200 @@ -100,7 +100,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']); diff -urp feeds.HEAD/plugins/FeedsDataProcessor.inc feeds/plugins/FeedsDataProcessor.inc --- feeds.HEAD/plugins/FeedsDataProcessor.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsDataProcessor.inc 2010-09-18 15:01:27.000000000 +0200 @@ -235,6 +235,7 @@ class FeedsDataProcessor extends FeedsPr 'update_existing' => FEEDS_SKIP_EXISTING, 'expire' => FEEDS_EXPIRE_NEVER, // Don't expire items by default. 'mappings' => array(), + 'mapping_on_import' => 0, ); } diff -urp feeds.HEAD/plugins/FeedsFeedNodeProcessor.inc feeds/plugins/FeedsFeedNodeProcessor.inc --- feeds.HEAD/plugins/FeedsFeedNodeProcessor.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsFeedNodeProcessor.inc 2010-09-18 15:01:27.000000000 +0200 @@ -105,6 +105,7 @@ class FeedsFeedNodeProcessor extends Fee 'content_type' => '', 'update_existing' => 0, 'mappings' => array(), + 'mapping_on_import' => 0, ); } diff -urp feeds.HEAD/plugins/FeedsNodeProcessor.inc feeds/plugins/FeedsNodeProcessor.inc --- feeds.HEAD/plugins/FeedsNodeProcessor.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsNodeProcessor.inc 2010-09-18 15:01:27.000000000 +0200 @@ -150,6 +150,7 @@ class FeedsNodeProcessor extends FeedsPr 'expire' => FEEDS_EXPIRE_NEVER, 'mappings' => array(), 'author' => 0, + 'mapping_on_import' => 0, ); } @@ -413,7 +414,6 @@ class FeedsNodeProcessor extends FeedsPr * Used for batch deletion. */ function _feeds_node_delete($nid) { - $node = node_load($nid); db_query("DELETE FROM {node} WHERE nid = %d", $node->nid); diff -urp feeds.HEAD/plugins/FeedsProcessor.inc feeds/plugins/FeedsProcessor.inc --- feeds.HEAD/plugins/FeedsProcessor.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsProcessor.inc 2010-09-18 15:02:39.000000000 +0200 @@ -105,7 +105,10 @@ abstract class FeedsProcessor extends Fe $target_item = (object)$target_item; $convert_to_array = TRUE; } - foreach ($this->config['mappings'] as $mapping) { + + $mappings = $this->getMappings(); + + foreach ($mappings as $mapping) { if (isset($targets[$mapping['target']]['real_target'])) { unset($target_item->{$targets[$mapping['target']]['real_target']}); } @@ -165,14 +168,27 @@ abstract class FeedsProcessor extends Fe * Declare default configuration. */ public function configDefaults() { - return array('mappings' => array()); + return array('mappings' => array(), + 'mapping_on_import' => 0, + ); } /** * 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; } /** @@ -226,7 +242,7 @@ abstract class FeedsProcessor extends Fe protected function uniqueTargets(FeedsImportBatch $batch) { $parser = feeds_importer($this->id)->parser; $targets = array(); - foreach ($this->config['mappings'] as $mapping) { + foreach ($this->getMappings() as $mapping) { if ($mapping['unique']) { // Invoke the parser's getSourceElement to retrieve the value for this // mapping's source. @@ -235,4 +251,262 @@ abstract class FeedsProcessor extends Fe } return $targets; } + + /** + * 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 batch 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, $loc_id = 'config', $batch = NULL) { + drupal_add_js(drupal_get_path('module', 'feeds_ui') .'/feeds_ui.js'); + + // Get the FeedsImporter object + $importer = feeds_importer($this->id); + + $form = array(); + if ($loc_id == 'config') { + // 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']['#value'] = feeds_mapping_form_help(); + + // Get mapping sources from parsers or batch and targets from processor, format them + // for output. + // Some parsers do not define mapping sources but let them define on the fly. + if ($loc_id == 'config' || empty($batch)) { + $sources = $importer->parser->getMappingSources(); + } + else { + $sources = $batch->getMappingSources(); + } + + if ($sources) { + $source_options = _feeds_mapping_form_format_options($sources); + foreach ($sources as $k => $source) { + $legend['sources'][$k]['name']['#value'] = empty($source['name']) ? $k : $source['name']; + $legend['sources'][$k]['description']['#value'] = empty($source['description']) ? '' : $source['description']; + } + } + else { + $legend['sources']['#value'] = 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']['#value'] = empty($target['name']) ? $k : $target['name']; + $legend['targets'][$k]['description']['#value'] = 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(); + + // Add unique and remove forms to mappings. + $form['unique_flags'] = $form['remove_flags'] = array( + '#tree' => TRUE, + ); + + if (is_array($mappings)) { + $form['mappings']['#tree'] = TRUE; + + foreach ($mappings as $i => $mapping) { + $mappings[$i]['source'] = isset($source_options) ? $source_options[$mappings[$i]['source']] : $mappings[$i]['source']; + $mappings[$i]['target'] = $target_options[$mappings[$i]['target']]; + $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' => 'feeds-ui-trigger-submit'), + ); + } + $form['remove_flags'][$i] = array( + '#type' => 'checkbox', + '#title' => t('Remove'), + '#prefix' => '', + ); + + // 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, + ); + } + } + + $form['#mappings'] = $mappings; + $form['#targets'] = $targets; + if ($sources) { + $form['source'] = array( + '#type' => 'select', + '#options' => array('' => t('Select a source')) + $source_options, + ); + } + else { + $form['source'] = array( + '#type' => 'textfield', + '#size' => 20, + '#default_value' => t('Name of source field'), + '#attributes' => array('class' => 'hide-text-on-focus'), + ); + } + $form['target'] = array( + '#type' => 'select', + '#options' => array('' => t('Select a target')) + _feeds_mapping_form_format_options($targets), + ); + $form['add'] = array( + '#type' => 'submit', + '#value' => t('Add'), + '#submit' => array('feeds_form_submit'), + ); + } + + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#attributes' => array('class' => 'feeds-ui-hidden-submit'), + '#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 { + // We may set some unique flags to mappings that we remove in the subsequent + // step, that's fine. + if (isset($values['unique_flags'])) { + foreach ($values['unique_flags'] as $k => $v) { + $values['mappings'][$k]['unique'] = $v; + } + } + + if (isset($values['remove_flags'])) { + foreach ($values['remove_flags'] as $k => $v) { + if ($v) { + unset($values['mappings'][$k]); + // Keep or 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.') . + '

'; } diff -urp feeds.HEAD/plugins/FeedsTermProcessor.inc feeds/plugins/FeedsTermProcessor.inc --- feeds.HEAD/plugins/FeedsTermProcessor.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsTermProcessor.inc 2010-09-18 15:01:27.000000000 +0200 @@ -138,6 +138,7 @@ class FeedsTermProcessor extends FeedsPr 'vocabulary' => 0, 'update_existing' => FEEDS_SKIP_EXISTING, 'mappings' => array(), + 'mapping_on_import' => 0, ); } diff -urp feeds.HEAD/plugins/FeedsUserProcessor.inc feeds/plugins/FeedsUserProcessor.inc --- feeds.HEAD/plugins/FeedsUserProcessor.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/plugins/FeedsUserProcessor.inc 2010-09-18 15:01:27.000000000 +0200 @@ -114,6 +114,7 @@ class FeedsUserProcessor extends FeedsPr 'update_existing' => FALSE, 'status' => 1, 'mappings' => array(), + 'mapping_on_import' => 0, ); } diff -urp feeds.HEAD/tests/feeds.test.inc feeds/tests/feeds.test.inc --- feeds.HEAD/tests/feeds.test.inc 2010-09-18 14:57:33.000000000 +0200 +++ feeds/tests/feeds.test.inc 2010-09-18 15:01:27.000000000 +0200 @@ -283,7 +283,10 @@ class FeedsWebTestCase extends DrupalWeb */ public function addMappings($id, $mappings) { - $path = 'admin/build/feeds/edit/'. $id .'/mapping'; + $source = feeds_source($id); + $config = $source->importer->config; + + $path = 'admin/build/feeds/edit/'. $id .'/mapping/' . $config['processor']['plugin_key']; // Iterate through all mappings and add the via the form. foreach ($mappings as $i => $mapping) { @@ -293,7 +296,6 @@ class FeedsWebTestCase extends DrupalWeb $unique = !empty($mapping['unique']); unset($mapping['unique']); $this->drupalPost($path, $mapping, t('Add')); - // If unique was set, set the last mapping's unique flag. if ($unique) { $edit = array( diff -urp feeds.HEAD/tests/feeds_parser_sitemap.test feeds/tests/feeds_parser_sitemap.test --- feeds.HEAD/tests/feeds_parser_sitemap.test 2010-09-18 14:57:33.000000000 +0200 +++ feeds/tests/feeds_parser_sitemap.test 2010-09-18 15:01:27.000000000 +0200 @@ -81,6 +81,7 @@ class FeedsSitemapParserTestCase extends $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 Story nodes.'); + $this->show(); // Assert DB status. $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));