? 651478-58_mapping_on_import.patch ? feeds-mapping-on-import-651478-48.patch ? libraries/simplepie.inc Index: feeds.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.module,v retrieving revision 1.52 diff -u -p -r1.52 feeds.module --- feeds.module 31 Aug 2010 00:45:54 -0000 1.52 +++ feeds.module 3 Sep 2010 21:33:00 -0000 @@ -65,10 +65,10 @@ function feeds_perm() { */ function feeds_forms() { $forms = array(); - $forms['FeedsImporter_feeds_config_form']['callback'] = 'feeds_config_form'; + $forms['FeedsImporter_feeds_form']['callback'] = 'feeds_form'; $plugins = feeds_get_plugins(); foreach ($plugins as $plugin) { - $forms[$plugin['handler']['class'] .'_feeds_config_form']['callback'] = 'feeds_config_form'; + $forms[$plugin['handler']['class'] .'_feeds_form']['callback'] = 'feeds_form'; } return $forms; } @@ -103,6 +103,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( @@ -125,6 +134,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(); } @@ -154,6 +173,9 @@ function feeds_theme() { 'feeds_upload' => array( 'file' => 'feeds.pages.inc', ), + 'feeds_mapping_form' => array( + 'file' => 'feeds.pages.inc' + ), ); } @@ -298,10 +320,21 @@ 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; + } + 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 import to scheduler. + feeds_scheduler()->add($importer_id, 'import', $node->nid); + // Add expiry to schedule, in case this is the first feed of this + // configuration. + feeds_scheduler()->add($importer_id, 'expire'); + $node_feeds = NULL; } // Add import to scheduler. feeds_scheduler()->add($importer_id, 'import', $node->nid); Index: feeds.pages.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.pages.inc,v retrieving revision 1.20 diff -u -p -r1.20 feeds.pages.inc --- feeds.pages.inc 3 Sep 2010 21:03:48 -0000 1.20 +++ feeds.pages.inc 3 Sep 2010 21:33:01 -0000 @@ -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,25 @@ 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']; } - - // Add importer to schedule. - feeds_scheduler()->add($form['#importer_id'], 'import'); - feeds_scheduler()->add($form['#importer_id'], 'expire'); + 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 importer to schedule. + feeds_scheduler()->add($form['#importer_id'], 'import'); + feeds_scheduler()->add($form['#importer_id'], 'expire'); + } } /** @@ -181,3 +192,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 importer to schedule. + feeds_scheduler()->add($form['#importer_id'], 'import', $form['#feed_nid']); + feeds_scheduler()->add($form['#importer_id'], 'expire', $form['#feed_nid']); +} + +/** + * 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( + $mapping['source'], + $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( + drupal_render($form['legendset']['legend']['sources'][$k]['name']), + 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( + drupal_render($form['legendset']['legend']['targets'][$k]['name']), + 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; +} \ No newline at end of file Index: feeds_ui/feeds_ui.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds_ui/feeds_ui.admin.inc,v retrieving revision 1.34 diff -u -p -r1.34 feeds_ui.admin.inc --- feeds_ui/feeds_ui.admin.inc 10 Jul 2010 23:30:19 -0000 1.34 +++ feeds_ui/feeds_ui.admin.inc 3 Sep 2010 21:33:01 -0000 @@ -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) { @@ -308,18 +297,22 @@ function feeds_ui_edit_page($importer, $ ctools_include('dependent'); if (empty($plugin_key)) { $active_container['title'] = t('Basic settings'); - $active_container['body'] = feeds_get_config_form($importer); + $active_container['body'] = feeds_get_form($importer, 'configForm'); } // feeds_plugin_instance() returns a correct result because feed has been // instantiated previously. elseif (in_array($plugin_key, array_keys($plugins)) && $plugin = feeds_plugin_instance($plugin_key, $importer->id)) { $active_container['title'] = t('Settings for !plugin', array('!plugin' => $plugins[$plugin_key]['name'])); - $active_container['body'] = feeds_get_config_form($plugin); + $active_container['body'] = feeds_get_form($plugin, 'configForm'); } 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; } @@ -354,7 +347,7 @@ function feeds_ui_edit_page($importer, $ // Fetcher. $fetcher = $plugins[$config['fetcher']['plugin_key']]; $actions = array(); - if (feeds_get_config_form($importer->fetcher)) { + if (feeds_get_form($importer->fetcher, 'configForm')) { $actions = array(l(t('Settings'), $path .'/settings/'. $config['fetcher']['plugin_key'])); } $info['title'] = t('Fetcher'); @@ -371,7 +364,7 @@ function feeds_ui_edit_page($importer, $ // Parser. $parser = $plugins[$config['parser']['plugin_key']]; $actions = array(); - if (feeds_get_config_form($importer->parser)) { + if (feeds_get_form($importer->parser, 'configForm')) { $actions = array(l(t('Settings'), $path .'/settings/'. $config['parser']['plugin_key'])); } $info['title'] = t('Parser'); @@ -388,10 +381,10 @@ function feeds_ui_edit_page($importer, $ // Processor. $processor = $plugins[$config['processor']['plugin_key']]; $actions = array(); - if (feeds_get_config_form($importer->processor)) { + 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( @@ -481,153 +474,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['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']; - } - - $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. - $mappings = $importer->processor->getMappings(); - $form['unique_flags'] = $form['remove_flags'] = array( - '#tree' => TRUE, - ); - - if (is_array($mappings)) { - foreach ($mappings as $i => $mapping) { - - $mappings[$i]['source'] = isset($source_options) ? $source_options[$mappings[$i]['source']] : $mappings[$i]['source']; - $mappings[$i]['target'] = isset($target_options[$mappings[$i]['target']]) ? $target_options[$mappings[$i]['target']] : $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' => '', - ); - } - } - - $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_ui_format_options($targets), - ); - $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 { - $importer->processor->addMapping($form_state['values']['source'], $form_state['values']['target']); - $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; - $mappings = $processor->config['mappings']; - // We may set some unique flags to mappings that we remove in the subsequent - // step, that's fine. - if (isset($form_state['values']['unique_flags'])) { - foreach ($form_state['values']['unique_flags'] as $k => $v) { - $processor->setUnique($mappings[$k]['source'], $mappings[$k]['target'], $v); - } - } - - foreach ($form_state['values']['remove_flags'] as $k => $v) { - if ($v) { - $processor->removeMapping($mappings[$k]['source'], $mappings[$k]['target']); - } - } - drupal_set_message(t('Your changes have been saved.')); - $processor->save(); -} - -/** * Walk the result of FeedsParser::getMappingSources() or * FeedsProcessor::getMappingTargets() and format them into * a Form API options array. @@ -760,80 +606,4 @@ 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) { - $rows[] = array( - $mapping['source'], - $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( - drupal_render($form['legendset']['legend']['sources'][$k]['name']), - 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( - drupal_render($form['legendset']['legend']['targets'][$k]['name']), - 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; -} +} \ No newline at end of file Index: includes/FeedsBatch.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsBatch.inc,v retrieving revision 1.14 diff -u -p -r1.14 FeedsBatch.inc --- includes/FeedsBatch.inc 27 Jul 2010 20:27:40 -0000 1.14 +++ includes/FeedsBatch.inc 3 Sep 2010 21:33:01 -0000 @@ -277,6 +277,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; + } } /** Index: includes/FeedsConfigurable.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsConfigurable.inc,v retrieving revision 1.12 diff -u -p -r1.12 FeedsConfigurable.inc --- includes/FeedsConfigurable.inc 28 Feb 2010 17:16:25 -0000 1.12 +++ includes/FeedsConfigurable.inc 3 Sep 2010 21:33:01 -0000 @@ -178,31 +178,43 @@ abstract class FeedsConfigurable { */ public function configFormValidate(&$values) { } + + /** + * Submission handler for configForm(). + * + * @param $values + */ + public function configFormSubmit(&$values) { + $this->addConfig($values); + $this->save(); + drupal_set_message(t('Your changes have been saved.')); + feeds_cache_clear(FALSE); + } } /** * Config form wrapper. Use to render the configuration form of * a FeedsConfigurable object. * - * @param + * @param $configurable * FeedsConfigurable object. - * @param - * The parent object to perform the save on. + * @param $form_method + * The form method that should be rendered. * * @return * Rendered config form, if available. Empty string otherwise. */ -function feeds_get_config_form($configurable) { +function feeds_get_form($configurable, $form_method) { $form_state = array(); - if ($configurable->configForm($form_state)) { - return drupal_get_form(get_class($configurable) .'_feeds_config_form', $configurable); + if (method_exists($configurable, $form_method)) { + return drupal_get_form(get_class($configurable) .'_feeds_form', $configurable, $form_method); } return ''; } /** * Config form callback. Don't call directly, but use - * feeds_get_config_form($configurable) instead. + * feeds_get_form($configurable, 'method') instead. * * @param * FormAPI $form_state. @@ -210,12 +222,15 @@ function feeds_get_config_form($configur * FeedsConfigurable object. * @param * The object to perform the save() operation on. + * @param $form_method + * The $form_method that should be rendered. */ -function feeds_config_form(&$form_state, $configurable) { - $form = $configurable->configForm($form_state); +function feeds_form(&$form_state, $configurable, $form_method) { + $form = $configurable->$form_method($form_state); $form['#configurable'] = $configurable; - $form['#validate'] = array('feeds_config_form_validate'); - $form['#submit'] = array('feeds_config_form_submit'); + $form['#feeds_form_method'] = $form_method; + $form['#validate'] = array('feeds_form_validate'); + $form['#submit'] = array('feeds_form_submit'); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save'), @@ -225,18 +240,21 @@ function feeds_config_form(&$form_state, } /** - * Validation handler for feeds_config_form(). + * Validation handler for feeds_form(). */ -function feeds_config_form_validate($form, &$form_state) { - $form['#configurable']->configFormValidate($form_state['values']); +function feeds_form_validate($form, &$form_state) { + $validate_method = $form['#feeds_form_method'] .'Validate'; + if (method_exists($form['#configurable'], $validate_method)) { + $form['#configurable']->$validate_method($form_state['values']); + } } /** - * Submit handler for feeds_config_form(). + * Submit handler for feeds_form(). */ -function feeds_config_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); -} +function feeds_form_submit($form, &$form_state) { + $submit_method = $form['#feeds_form_method'] .'Submit'; + if (method_exists($form['#configurable'], $submit_method)) { + $form['#configurable']->$submit_method($form_state['values']); + } +} \ No newline at end of file Index: plugins/FeedsCSVParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsCSVParser.inc,v retrieving revision 1.13 diff -u -p -r1.13 FeedsCSVParser.inc --- plugins/FeedsCSVParser.inc 27 Jul 2010 17:55:44 -0000 1.13 +++ plugins/FeedsCSVParser.inc 3 Sep 2010 21:33:01 -0000 @@ -66,7 +66,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[] = $mapping['source']; Index: plugins/FeedsDataProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsDataProcessor.inc,v retrieving revision 1.16 diff -u -p -r1.16 FeedsDataProcessor.inc --- plugins/FeedsDataProcessor.inc 27 Jul 2010 20:27:41 -0000 1.16 +++ plugins/FeedsDataProcessor.inc 3 Sep 2010 21:33:01 -0000 @@ -133,9 +133,6 @@ class FeedsDataProcessor extends FeedsPr throw new Exception(t('Field !field_name already exists as a mapping target. Remove it from mapping if you would like to map another source to it. Remove it from !data_table table if you would like to change its definition.', array('!field_name' => $field_name, '!data_table' => l($this->table()->get('name'), 'admin/content/data')))); } } - - // Let parent populate the mapping configuration. - parent::addMapping($source, $target, $unique); } /** @@ -254,6 +251,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, ); } Index: plugins/FeedsFeedNodeProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsFeedNodeProcessor.inc,v retrieving revision 1.14 diff -u -p -r1.14 FeedsFeedNodeProcessor.inc --- plugins/FeedsFeedNodeProcessor.inc 27 Jul 2010 20:27:41 -0000 1.14 +++ plugins/FeedsFeedNodeProcessor.inc 3 Sep 2010 21:33:01 -0000 @@ -105,6 +105,7 @@ class FeedsFeedNodeProcessor extends Fee 'content_type' => '', 'update_existing' => 0, 'mappings' => array(), + 'mapping_on_import' => 0, ); } Index: plugins/FeedsNodeProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsNodeProcessor.inc,v retrieving revision 1.48 diff -u -p -r1.48 FeedsNodeProcessor.inc --- plugins/FeedsNodeProcessor.inc 3 Sep 2010 21:08:51 -0000 1.48 +++ plugins/FeedsNodeProcessor.inc 3 Sep 2010 21:33:01 -0000 @@ -158,6 +158,7 @@ class FeedsNodeProcessor extends FeedsPr 'expire' => FEEDS_EXPIRE_NEVER, 'mappings' => array(), 'author' => 0, + 'mapping_on_import' => 0, ); } @@ -403,7 +404,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); Index: plugins/FeedsProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsProcessor.inc,v retrieving revision 1.15 diff -u -p -r1.15 FeedsProcessor.inc --- plugins/FeedsProcessor.inc 27 Jul 2010 20:27:41 -0000 1.15 +++ plugins/FeedsProcessor.inc 3 Sep 2010 21:33:01 -0000 @@ -100,7 +100,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']}); } @@ -147,62 +150,27 @@ abstract class FeedsProcessor extends Fe * Declare default configuration. */ public function configDefaults() { - return array('mappings' => array()); - } - - /** - * Add a mapping to existing mappings. - * - * @param $source - * A string that identifies a source element. - * @param $target - * A string that identifies a target element. - * @param $unique - * A boolean that defines whether the target value should be unique. If - * TRUE only one item with a given target value can exist on the local - * system. Compare with existingItemId() and uniqueTargets(). - */ - public function addMapping($source, $target, $unique = FALSE) { - if (!empty($source) && !empty($target)) { - $this->config['mappings'][] = array( - 'source' => $source, - 'target' => $target, - 'unique' => $unique, - ); - } - } - - /** - * Set unique state of a mapping target. - */ - public function setUnique($source, $target, $unique) { - if (!empty($source) && !empty($target)) { - foreach ($this->config['mappings'] as $k => $mapping) { - if ($mapping['source'] == $source && $mapping['target'] == $target) { - $this->config['mappings'][$k]['unique'] = $unique; - } - } - } - } - - /** - * Remove a mapping. - */ - public function removeMapping($source, $target) { - foreach ($this->config['mappings'] as $k => $mapping) { - if ($mapping['source'] == $source && $mapping['target'] == $target) { - unset($this->config['mappings'][$k]); - } - } - // Keep or keys clean. - $this->config['mappings'] = array_values($this->config['mappings']); + 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; } /** @@ -256,7 +224,7 @@ abstract class FeedsProcessor extends Fe protected function uniqueTargets($source_item) { $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. @@ -290,4 +258,263 @@ abstract class FeedsProcessor extends Fe } $loaded = TRUE; } + + /** + * 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'] == '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.') . + '

'; +} \ No newline at end of file Index: plugins/FeedsTermProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsTermProcessor.inc,v retrieving revision 1.12 diff -u -p -r1.12 FeedsTermProcessor.inc --- plugins/FeedsTermProcessor.inc 27 Jul 2010 20:27:41 -0000 1.12 +++ plugins/FeedsTermProcessor.inc 3 Sep 2010 21:33:01 -0000 @@ -130,6 +130,7 @@ class FeedsTermProcessor extends FeedsPr 'vocabulary' => 0, 'update_existing' => FEEDS_SKIP_EXISTING, 'mappings' => array(), + 'mapping_on_import' => 0, ); } Index: plugins/FeedsUserProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsUserProcessor.inc,v retrieving revision 1.18 diff -u -p -r1.18 FeedsUserProcessor.inc --- plugins/FeedsUserProcessor.inc 27 Jul 2010 20:27:41 -0000 1.18 +++ plugins/FeedsUserProcessor.inc 3 Sep 2010 21:33:01 -0000 @@ -115,6 +115,7 @@ class FeedsUserProcessor extends FeedsPr 'update_existing' => FALSE, 'status' => 1, 'mappings' => array(), + 'mapping_on_import' => 0, ); }