diff --git a/feeds.api.php b/feeds.api.php index 58e1670..a23bd2f 100644 --- a/feeds.api.php +++ b/feeds.api.php @@ -285,8 +285,8 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { // Specify both summary_callback and form_callback to add a per mapping // configuration form. - 'summary_callback' => 'my_module_summary_callback', - 'form_callback' => 'my_module_form_callback', + 'summary_callbacks' => array('my_module_summary_callback'), + 'form_callbacks' => array('my_module_form_callback'), ); $targets['my_node_field2'] = array( 'name' => t('My Second custom node field'), @@ -334,23 +334,23 @@ function my_module_set_target($source, $entity, $target, array $values, $mapping * Example of the summary_callback specified in * hook_feeds_processor_targets_alter(). * - * @param $mapping + * @param array $mapping * Associative array of the mapping settings. - * @param $target + * @param string $target * Array of target settings, as defined by the processor or * hook_feeds_processor_targets_alter(). - * @param $form + * @param array $form * The whole mapping form. - * @param $form_state + * @param array $form_state * The form state of the mapping form. * - * @return + * @return string * Returns, as a string that may contain HTML, the summary to display while * the full form isn't visible. * If the return value is empty, no summary and no option to view the form * will be displayed. */ -function my_module_summary_callback($mapping, $target, $form, $form_state) { +function my_module_summary_callback(array $mapping, $target, array $form, array $form_state) { if (empty($mapping['my_setting'])) { return t('My setting not active'); } @@ -365,13 +365,13 @@ function my_module_summary_callback($mapping, $target, $form, $form_state) { * * The arguments are the same that my_module_summary_callback() gets. * - * @return + * @return array * The per mapping configuration form. Once the form is saved, $mapping will * be populated with the form values. * * @see my_module_summary_callback() */ -function my_module_form_callback($mapping, $target, $form, $form_state) { +function my_module_form_callback(array $mapping, $target, array $form, array $form_state) { return array( 'my_setting' => array( '#type' => 'checkbox', diff --git a/feeds.feeds.inc b/feeds.feeds.inc new file mode 100644 index 0000000..fec42f8 --- /dev/null +++ b/feeds.feeds.inc @@ -0,0 +1,87 @@ + '', + 'summary_callbacks' => array(), + 'form_callbacks' => array(), + 'preprocess_callbacks' => array(), + 'postprocess_callbacks' => array(), + 'unique_callbacks' => array(), + ); + + foreach (array_keys($targets) as $target) { + $targets[$target] += $defaults; + + // Filter out any uncallable keys. + _feeds_filter_callback_arrays($targets[$target]); + } +} + +/** + * Filters the callbacks of a single target array. + * + * @param array &$target + * The target arary. + */ +function _feeds_filter_callback_arrays(array &$target) { + static $normalize = array('summary_callback', 'form_callback'); + + // Migrate keys summary_callback and form_callback to the new keys. + foreach ($normalize as $key) { + if (!empty($target[$key])) { + $target[$key . 's'][] = $target[$key]; + } + + unset($target[$key]); + } + + static $callback_keys = array( + 'summary_callbacks', + 'form_callbacks', + 'preprocess_callbacks', + 'unique_callbacks', + ); + + // Filter out any incorect callbaks. Do it here so it only has to be done + // once. + foreach ($callback_keys as $callback_key) { + $target[$callback_key] = array_filter($target[$callback_key], 'is_callable'); + } + + // This makes checking in FeedsProcessor::mapToTarget() simpler. + if (empty($target['callback']) || !is_callable($target['callback'])) { + unset($target['callback']); + } +} diff --git a/feeds.info b/feeds.info index be3f52a..57eaabf 100644 --- a/feeds.info +++ b/feeds.info @@ -36,6 +36,7 @@ files[] = tests/feeds_mapper_date.test files[] = tests/feeds_mapper_date_multiple.test files[] = tests/feeds_mapper_field.test files[] = tests/feeds_mapper_file.test +files[] = tests/feeds_mapper_hooks.test files[] = tests/feeds_mapper_link.test files[] = tests/feeds_mapper_path.test files[] = tests/feeds_mapper_profile.test diff --git a/feeds.module b/feeds.module index f536926..efbc703 100644 --- a/feeds.module +++ b/feeds.module @@ -35,6 +35,7 @@ function feeds_hook_info() { 'feeds_after_save', 'feeds_after_import', 'feeds_after_clear', + 'feeds_processor_targets', 'feeds_processor_targets_alter', 'feeds_parser_sources_alter', ); @@ -785,6 +786,36 @@ function feeds_system_info_alter(array &$info, $file, $type) { } /** + * Implements hook_module_implements_alter(). + */ +function feeds_module_implements_alter(array &$implementations, $hook) { + if ($hook === 'feeds_processor_targets_alter') { + // We need two implementations of this hook, so we add one that gets + // called first, and move the normal one to last. + $implementations = array('_feeds' => FALSE) + $implementations; + + // Move normal implementation to last. + $group = $implementations['feeds']; + unset($implementations['feeds']); + $implementations['feeds'] = $group; + } +} + +/** + * Implements hook_feeds_processor_targets_alter(). + * + * @see feeds_feeds_processor_targets() + * @see feeds_feeds_processor_targets_alter() + */ +function _feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) { + // If hook_feeds_processor_targets() hasn't been called, for instance, by + // older processors, invoke it ourself. + if (!drupal_static('feeds_feeds_processor_targets', FALSE)) { + $targets += module_invoke_all('feeds_processor_targets', $entity_type, $bundle); + } +} + +/** * @} */ diff --git a/feeds.pages.inc b/feeds.pages.inc index eeeab90..99a601e 100644 --- a/feeds.pages.inc +++ b/feeds.pages.inc @@ -232,7 +232,9 @@ function feeds_unlock_tab_form($form, &$form_state, FeedsImporter $importer = NU '#value' => feeds_source_status($source), ); $form = confirm_form($form, t('Unlock this importer?'), $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update'); - if ($source->progressImporting() == FEEDS_BATCH_COMPLETE && $source->progressClearing() == FEEDS_BATCH_COMPLETE) { + $locked = !lock_may_be_available("feeds_source_{$source->id}_{$source->feed_nid}"); + + if (!$locked) { $form['source_locked'] = array( '#type' => 'markup', '#title' => t('Not Locked'), diff --git a/feeds_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc index b174d75..6a794b8 100644 --- a/feeds_ui/feeds_ui.admin.inc +++ b/feeds_ui/feeds_ui.admin.inc @@ -634,12 +634,10 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe } 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(); + $settings_form = array(); + + foreach ($target['form_callbacks'] as $callback) { + $settings_form += $callback($mapping, $target, $form, $form_state); } // Merge in the optional unique form. @@ -664,13 +662,15 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe } else { // Build the summary. - if (isset($target['summary_callback'])) { - $summary = call_user_func($target['summary_callback'], $mapping, $target, $form, $form_state); - } - else { - $summary = ''; + $summary = array(); + + foreach ($target['summary_callbacks'] as $callback) { + $summary[] = $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; diff --git a/mappers/date.inc b/mappers/date.inc index 061962d..c75f125 100644 --- a/mappers/date.inc +++ b/mappers/date.inc @@ -6,13 +6,13 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsNodeProcessor::getMappingTargets(). + * Implements hook_feeds_processor_targets(). * * @todo Only provides "end date" target if field allows it. */ -function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function date_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { $info = field_info_field($name); if (in_array($info['type'], array('date', 'datestamp', 'datetime'))) { @@ -30,12 +30,14 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam ); } } + + return $targets; } /** - * Callback for setting target values. + * Callback for setting date values. */ -function date_feeds_set_target($source, $entity, $target, array $values) { +function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { list($field_name, $sub_field) = explode(':', $target, 2); $delta = 0; @@ -54,7 +56,7 @@ function date_feeds_set_target($source, $entity, $target, array $values) { } } - $value->buildDateField($entity, $field_name, $delta); + $value->buildDateField($entity, $field_name, $delta, $mapping['language']); $delta++; } } diff --git a/mappers/entity_translation.inc b/mappers/entity_translation.inc new file mode 100644 index 0000000..3d09bff --- /dev/null +++ b/mappers/entity_translation.inc @@ -0,0 +1,84 @@ +feeds_item->entity_type; + + if (!$handler = entity_translation_get_handler($entity_type, $entity)) { + return; + } + + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + $languages_seen = array(); + + $entity_info = entity_get_info($entity_type); + $translation_key = isset($entity_info['entity keys']['translations']) ? $entity_info['entity keys']['translations'] : FALSE; + + if (!$translation_key) { + return; + } + + foreach (field_info_instances($entity_type, $bundle) as $instance) { + $field_name = $instance['field_name']; + + // No values in this field, skip it. + if (empty($entity->$field_name)) { + continue; + } + + // Not translatable. + if (($info = field_info_field($field_name)) && !$info['translatable']) { + continue; + } + + // Init the translation handler. + if (empty($entity->$translation_key->data)) { + $handler->initTranslations(); + } + + // Avoid invalid user configuration. Entity translation does this when + // loading the translation overview page. + if (count($entity->$field_name) === 1 && key($entity->$field_name) === LANGUAGE_NONE) { + $entity->{$field_name}[$handler->getLanguage()] = $entity->{$field_name}[LANGUAGE_NONE]; + unset($entity->{$field_name}[LANGUAGE_NONE]); + } + + // Look at all languages in this field. + foreach ($entity->$field_name as $language => $v) { + if (isset($languages_seen[$language]) || $language === LANGUAGE_NONE) { + continue; + } + + $languages_seen[$language] = TRUE; + + if ($language === $handler->getLanguage()) { + continue; + } + + $translation = array( + 'translate' => 0, + 'status' => 1, + 'language' => $language, + 'source' => $handler->getLanguage(), + ); + + $handler->setTranslation($translation, $entity); + } + } + + // Loop through every language for the site, and remove translations for the + // ones that don't have any values. + foreach (language_list() as $language) { + if (!isset($languages_seen[$language->language])) { + $handler->removeTranslation($language->language); + } + } +} diff --git a/mappers/file.inc b/mappers/file.inc index 2273590..e1ee412 100644 --- a/mappers/file.inc +++ b/mappers/file.inc @@ -7,11 +7,11 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsNodeProcessor::getMappingTargets() + * Implements hook_feeds_processor_targets(). */ -function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function file_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { $info = field_info_field($name); @@ -47,16 +47,16 @@ function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam } } } + + return $targets; } /** - * Callback for mapping. Here is where the actual mapping happens. - * - * When the callback is invoked, $target contains the name of the field the - * user has decided to map to and $value contains the value of the feed item - * element the user has picked as a source. + * Callback for mapping file fields. */ -function file_feeds_set_target($source, $entity, $target, array $values) { +function file_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + // Add default of uri for backwards compatibility. list($field_name, $sub_field) = explode(':', $target . ':uri'); $info = field_info_field($field_name); @@ -92,31 +92,31 @@ function file_feeds_set_target($source, $entity, $target, array $values) { } // Populate entity. - $field = isset($entity->$field_name) ? $entity->$field_name : array(LANGUAGE_NONE => array()); + $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array()); $delta = 0; foreach ($values as $v) { if ($info['cardinality'] == $delta) { break; } - if (!isset($field[LANGUAGE_NONE][$delta])) { - $field[LANGUAGE_NONE][$delta] = array(); + if (!isset($field[$language][$delta])) { + $field[$language][$delta] = array(); } switch ($sub_field) { case 'alt': case 'title': case 'description': - $field[LANGUAGE_NONE][$delta][$sub_field] = $v; + $field[$language][$delta][$sub_field] = $v; break; case 'uri': if ($v) { try { $file = $v->getFile($destination); - $field[LANGUAGE_NONE][$delta] += (array) $file; + $field[$language][$delta] += (array) $file; // @todo: Figure out how to properly populate this field. - $field[LANGUAGE_NONE][$delta]['display'] = 1; + $field[$language][$delta]['display'] = 1; } catch (Exception $e) { watchdog_exception('Feeds', $e, nl2br(check_plain($e))); diff --git a/mappers/link.inc b/mappers/link.inc index 90b5268..1dc19dc 100644 --- a/mappers/link.inc +++ b/mappers/link.inc @@ -6,11 +6,11 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsProcessor::getMappingTargets() + * Implements hook_feeds_processor_targets(). */ -function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function link_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { $info = field_info_field($name); if ($info['type'] == 'link_field') { @@ -32,19 +32,19 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam } } } + + return $targets; } /** - * Callback for mapping. Here is where the actual mapping happens. - * - * When the callback is invoked, $target contains the name of the field the - * user has decided to map to and $value contains the value of the feed item - * element the user has picked as a source. + * Callback for mapping link fields. */ -function link_feeds_set_target($source, $entity, $target, array $values) { +function link_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + list($field_name, $column) = explode(':', $target); - $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array()); + $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array()); $delta = 0; foreach ($values as $value) { @@ -53,7 +53,7 @@ function link_feeds_set_target($source, $entity, $target, array $values) { } if (is_scalar($value)) { - $field['und'][$delta][$column] = (string) $value; + $field[$language][$delta][$column] = (string) $value; } $delta++; } diff --git a/mappers/list.inc b/mappers/list.inc index 6a8a168..6a30829 100644 --- a/mappers/list.inc +++ b/mappers/list.inc @@ -6,11 +6,11 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsProcessor::getMappingTargets() + * Implements hook_feeds_processor_targets(). */ -function list_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function list_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { $info = field_info_field($name); @@ -42,13 +42,17 @@ function list_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam break; } } + + return $targets; } /** * Callback for setting list_boolean fields. */ -function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values, array $mapping = array()) { - $field = isset($entity->$target) ? $entity->$target : array(LANGUAGE_NONE => array()); +function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + + $field = isset($entity->$target) ? $entity->$target : array($language => array()); foreach ($values as $value) { @@ -56,7 +60,7 @@ function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, ar $value = $value->getValue(); } - $field[LANGUAGE_NONE][] = array('value' => (int) (bool) $value); + $field[$language][] = array('value' => (int) (bool) $value); } $entity->$target = $field; diff --git a/mappers/locale.inc b/mappers/locale.inc new file mode 100644 index 0000000..4d18dcc --- /dev/null +++ b/mappers/locale.inc @@ -0,0 +1,90 @@ +processor->entityType(); + $translatable = _locale_feeds_target_is_translatable($entity_type, $mapping['target']); + + $mapping += array('field_language' => LANGUAGE_NONE); + + // This is an invalid configuration that can come from disabling + // entity_translation. + $error = $mapping['field_language'] !== LANGUAGE_NONE && !$translatable; + + // Nothing to see here. + if (!$error && !$translatable) { + return; + } + + if ($error) { + return t('Language: @error', array('@error' => t('Error'))); + } + + $language_options = array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name'); + + return t('Language: %lang', array('%lang' => $language_options[$mapping['field_language']])); +} + +function locale_feeds_form_callback(array $mapping, array $target, array $form, array $form_state) { + $form = array(); + + $entity_type = $form_state['build_info']['args'][0]->processor->entityType(); + + $translatable = _locale_feeds_target_is_translatable($entity_type, $mapping['target']); + $mapping += array('field_language' => LANGUAGE_NONE); + + // This is an invalid configuration that can come from disabling + // entity_translation. + $error = $mapping['field_language'] !== LANGUAGE_NONE && !$translatable; + + // Nothing to see here. + if (!$error && !$translatable) { + return $form; + } + + $language_options = array(LANGUAGE_NONE => t('Language neutral')); + + if (!$error) { + $language_options += locale_language_list('name'); + } + + $form['field_language'] = array( + '#type' => 'select', + '#title' => t('Language'), + '#options' => $language_options, + '#default_value' => $mapping['field_language'], + ); + + return $form; +} + +function _locale_feeds_target_is_translatable($entity_type, $target) { + list($field_name) = explode(':', $target, 2); + + $info = field_info_field($field_name); + + return !empty($info) && field_is_translatable($entity_type, $info); +} diff --git a/mappers/number.inc b/mappers/number.inc index 2b9c436..406b4f8 100644 --- a/mappers/number.inc +++ b/mappers/number.inc @@ -6,11 +6,11 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsProcessor::getMappingTargets() + * Implements hook_feeds_processor_targets(). */ -function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function number_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + $numeric_types = array( 'number_integer', 'number_decimal', @@ -27,16 +27,18 @@ function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_n ); } } + + return $targets; } /** - * Callback for mapping numerics. - * - * Ensure that $value is a numeric to avoid database errors. + * Callback for mapping number fields. */ -function number_feeds_set_target($source, $entity, $target, array $values) { +function number_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + // Iterate over all values. - $field = isset($entity->$target) ? $entity->$target : array('und' => array()); + $field = isset($entity->$target) ? $entity->$target : array($language => array()); foreach ($values as $value) { @@ -45,7 +47,7 @@ function number_feeds_set_target($source, $entity, $target, array $values) { } if (is_numeric($value)) { - $field['und'][] = array('value' => $value); + $field[$language][] = array('value' => $value); } } diff --git a/mappers/path.inc b/mappers/path.inc index cd39fb1..1dceeea 100644 --- a/mappers/path.inc +++ b/mappers/path.inc @@ -6,11 +6,11 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsNodeProcessor::getMappingTargets(). + * Implements hook_feeds_processor_targets(). */ -function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function path_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + switch ($entity_type) { case 'node': case 'taxonomy_term': @@ -19,21 +19,19 @@ function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'name' => t('Path alias'), 'description' => t('URL path alias of the node.'), 'callback' => 'path_feeds_set_target', - 'summary_callback' => 'path_feeds_summary_callback', - 'form_callback' => 'path_feeds_form_callback', + 'summary_callbacks' => array('path_feeds_summary_callback'), + 'form_callbacks' => array('path_feeds_form_callback'), ); break; } + + return $targets; } /** - * Callback for mapping. Here is where the actual mapping happens. - * - * When the callback is invoked, $target contains the name of the field the - * user has decided to map to and $value contains the value of the feed item - * element the user has picked as a source. + * Callback for mapping path aliases. */ -function path_feeds_set_target($source, $entity, $target, array $values, $mapping) { +function path_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { $alias = FALSE; // Path alias cannot be multi-valued, so use the first non-empty value. foreach ($values as $value) { @@ -68,24 +66,8 @@ function path_feeds_set_target($source, $entity, $target, array $values, $mappin /** * Mapping configuration summary for path.module. - * - * @param $mapping - * Associative array of the mapping settings. - * @param $target - * Array of target settings, as defined by the processor or - * hook_feeds_processor_targets_alter(). - * @param $form - * The whole mapping form. - * @param $form_state - * The form state of the mapping form. - * - * @return - * Returns, as a string that may contain HTML, the summary to display while - * the full form isn't visible. - * If the return value is empty, no summary and no option to view the form - * will be displayed. */ -function path_feeds_summary_callback($mapping, $target, $form, $form_state) { +function path_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) { if (!module_exists('pathauto')) { return; } @@ -101,12 +83,8 @@ function path_feeds_summary_callback($mapping, $target, $form, $form_state) { /** * Settings form callback. - * - * @return - * The per mapping configuration form. Once the form is saved, $mapping will - * be populated with the form values. */ -function path_feeds_form_callback($mapping, $target, $form, $form_state) { +function path_feeds_form_callback(array $mapping, $target, array $form, array $form_state) { return array( 'pathauto_override' => array( '#type' => 'checkbox', diff --git a/mappers/profile.inc b/mappers/profile.inc index 00fdf3a..42f8e88 100644 --- a/mappers/profile.inc +++ b/mappers/profile.inc @@ -2,16 +2,17 @@ /** * @file - * On behalf implementation of Feeds mapping API for user profiles. + * On behalf implementation of Feeds mapping API for profile.module. */ /** - * Implements hook_feeds_processor_target_alter(). + * Implements hook_feeds_processor_targets(). */ -function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function profile_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); if ($entity_type != 'user') { - return; + return $targets; } $categories = profile_user_categories(); @@ -25,11 +26,13 @@ function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_ ); } } + + return $targets; } /** * Set the user profile target after import. */ -function profile_feeds_set_target($source, $entity, $target, array $values, $mapping) { +function profile_feeds_set_target(FeedsSource $source, $entity, $target, array $values) { $entity->$target = reset($values); } diff --git a/mappers/taxonomy.inc b/mappers/taxonomy.inc index 75f6541..bd7fed4 100644 --- a/mappers/taxonomy.inc +++ b/mappers/taxonomy.inc @@ -23,7 +23,7 @@ define('FEEDS_TAXONOMY_SEARCH_TERM_GUID', 2); /** * Implements hook_feeds_parser_sources_alter(). */ -function taxonomy_feeds_parser_sources_alter(&$sources, $content_type) { +function taxonomy_feeds_parser_sources_alter(array &$sources, $content_type) { if (!empty($content_type)) { foreach (taxonomy_get_vocabularies($content_type) as $vocabulary) { $sources['parent:taxonomy:' . $vocabulary->machine_name] = array( @@ -55,9 +55,11 @@ function taxonomy_feeds_get_source(FeedsSource $source, FeedsParserResult $resul } /** - * Implements hook_feeds_processor_targets_alter(). + * Implements hook_feeds_processor_targets(). */ -function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function taxonomy_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { $info = field_info_field($name); if ($info['type'] == 'taxonomy_term_reference') { @@ -65,8 +67,8 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle 'name' => check_plain($instance['label']), 'callback' => 'taxonomy_feeds_set_target', 'description' => t('The @label field of the entity.', array('@label' => $instance['label'])), - 'summary_callback' => 'taxonomy_feeds_summary_callback', - 'form_callback' => 'taxonomy_feeds_form_callback', + 'summary_callbacks' => array('taxonomy_feeds_summary_callback'), + 'form_callbacks' => array('taxonomy_feeds_form_callback'), ); } } @@ -75,14 +77,16 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle $targets['tid']['description'] = t('The tid of the taxonomy term. NOTE: use this feature with care, node ids are usually assigned by Drupal.'); unset($targets['vocabulary']); } + + return $targets; } /** - * Callback for mapping. Here is where the actual mapping happens. - * - * @todo Do not create new terms for non-autotag fields. + * Callback for mapping taxonomy terms. */ -function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $mapping = array()) { +function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array $terms, array $mapping) { + $language = $mapping['language']; + // Add in default values. $mapping += array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME, @@ -116,10 +120,10 @@ function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $map ->range(0, 1); - $field = isset($entity->$target) ? $entity->$target : array('und' => array()); + $field = isset($entity->$target) ? $entity->$target : array($language => array()); // Allow for multiple mappings to the same target. - $delta = count($field['und']); + $delta = count($field[$language]); // Iterate over all values. foreach ($terms as $term) { @@ -179,7 +183,7 @@ function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $map } if ($tid && isset($cache['allowed_values'][$target][$tid])) { - $field['und'][] = array('tid' => $tid); + $field[$language][] = array('tid' => $tid); $delta++; } } @@ -252,24 +256,8 @@ function taxonomy_feeds_term_lookup_term_by_guid($guid) { /** * Mapping configuration summary for taxonomy.module. - * - * @param array $mapping - * Associative array of the mapping settings. - * @param array $target - * Array of target settings, as defined by the processor or - * hook_feeds_processor_targets_alter(). - * @param array $form - * The whole mapping form. - * @param array $form_state - * The form state of the mapping form. - * - * @return string - * Returns, as a string that may contain HTML, the summary to display while - * the full form isn't visible. - * If the return value is empty, no summary and no option to view the form - * will be displayed. */ -function taxonomy_feeds_summary_callback($mapping, $target, $form, $form_state) { +function taxonomy_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) { $options = _taxonomy_feeds_form_callback_options(); if (empty($mapping['term_search'])) { return t('Search taxonomy terms by: @search', array('@search' => $options[FEEDS_TAXONOMY_SEARCH_TERM_NAME])); @@ -279,12 +267,8 @@ function taxonomy_feeds_summary_callback($mapping, $target, $form, $form_state) /** * Settings form callback. - * - * @return array - * The per mapping configuration form. Once the form is saved, $mapping will - * be populated with the form values. */ -function taxonomy_feeds_form_callback($mapping, $target, $form, $form_state) { +function taxonomy_feeds_form_callback(array $mapping, $target, array $form, array $form_state) { return array( 'term_search' => array( '#type' => 'select', diff --git a/mappers/text.inc b/mappers/text.inc index 89a03ff..1cf76bf 100644 --- a/mappers/text.inc +++ b/mappers/text.inc @@ -6,11 +6,11 @@ */ /** - * Implements hook_feeds_processor_targets_alter(). - * - * @see FeedsProcessor::getMappingTargets() + * Implements hook_feeds_processor_targets(). */ -function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function text_feeds_processor_targets($entity_type, $bundle_name) { + $targets = array(); + $text_types = array( 'text', 'text_long', @@ -37,16 +37,20 @@ function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam } if (!empty($instance['settings']['text_processing'])) { - $targets[$name]['summary_callback'] = 'text_feeds_summary_callback'; - $targets[$name]['form_callback'] = 'text_feeds_form_callback'; + $targets[$name]['summary_callbacks'] = array('text_feeds_summary_callback'); + $targets[$name]['form_callbacks'] = array('text_feeds_form_callback'); } } + + return $targets; } /** * Callback for mapping text fields. */ -function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping = array()) { +function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + $language = $mapping['language']; + list($field_name, $column) = explode(':', $target . ':value'); if ($column === 'value' && isset($source->importer->processor->config['input_format'])) { @@ -57,7 +61,7 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val ); } - $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array()); + $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array()); // Iterate over all values. $delta = 0; @@ -69,10 +73,10 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val if (is_scalar($value) && strlen($value)) { - $field['und'][$delta][$column] = (string) $value; + $field[$language][$delta][$column] = (string) $value; if (isset($mapping['format'])) { - $field['und'][$delta]['format'] = $mapping['format']; + $field[$language][$delta]['format'] = $mapping['format']; } } @@ -87,7 +91,7 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val * * Displays which text format will be used for the text field target. * - * @see text_feeds_processor_targets_alter() + * @see text_feeds_processor_targets() * @see text_feeds_form_callback() */ function text_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) { @@ -109,7 +113,7 @@ function text_feeds_summary_callback(array $mapping, $target, array $form, array * * Allows to select a text format for the text field target. * - * @see text_feeds_processor_targets_alter() + * @see text_feeds_processor_targets() * @see text_feeds_summary_callback() */ function text_feeds_form_callback(array $mapping, $target, array $form, array $form_state) { diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc index ec23016..d04d546 100644 --- a/plugins/FeedsNodeProcessor.inc +++ b/plugins/FeedsNodeProcessor.inc @@ -40,7 +40,7 @@ class FeedsNodeProcessor extends FeedsProcessor { $node->type = $this->bundle(); $node->changed = REQUEST_TIME; $node->created = REQUEST_TIME; - $node->language = LANGUAGE_NONE; + $node->language = $this->config['language']; $node->is_new = TRUE; node_object_prepare($node); // Populate properties that are set by node_object_prepare(). @@ -339,11 +339,7 @@ class FeedsNodeProcessor extends FeedsProcessor { ); } - // Let other modules expose mapping targets. - self::loadMappers(); - $entity_type = $this->entityType(); - $bundle = $this->bundle(); - drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle); + $this->getHookTargets($targets); return $targets; } diff --git a/plugins/FeedsParser.inc b/plugins/FeedsParser.inc index c864f29..a8434bb 100644 --- a/plugins/FeedsParser.inc +++ b/plugins/FeedsParser.inc @@ -465,13 +465,13 @@ class FeedsDateTimeElement extends FeedsElement { * Helper method for buildDateField(). Build a FeedsDateTimeElement object * from a standard formatted node. */ - protected static function readDateField($entity, $field_name, $delta = 0) { + protected static function readDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) { $ret = new FeedsDateTimeElement(); - if (isset($entity->{$field_name}['und'][$delta]['date']) && $entity->{$field_name}['und'][$delta]['date'] instanceof FeedsDateTime) { - $ret->start = $entity->{$field_name}['und'][$delta]['date']; + if (isset($entity->{$field_name}[$language][$delta]['date']) && $entity->{$field_name}[$language][$delta]['date'] instanceof FeedsDateTime) { + $ret->start = $entity->{$field_name}[$language][$delta]['date']; } - if (isset($entity->{$field_name}['und'][$delta]['date2']) && $entity->{$field_name}['und'][$delta]['date2'] instanceof FeedsDateTime) { - $ret->end = $entity->{$field_name}['und'][$delta]['date2']; + if (isset($entity->{$field_name}[$language][$delta]['date2']) && $entity->{$field_name}[$language][$delta]['date2'] instanceof FeedsDateTime) { + $ret->end = $entity->{$field_name}[$language][$delta]['date2']; } return $ret; } @@ -486,10 +486,10 @@ class FeedsDateTimeElement extends FeedsElement { * @param int $delta * The delta in the field. */ - public function buildDateField($entity, $field_name, $delta = 0) { + public function buildDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) { $info = field_info_field($field_name); - $oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta); + $oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta, $language); // Merge with any preexisting objects on the field; we take precedence. $oldfield = $this->merge($oldfield); $use_start = $oldfield->start; @@ -522,27 +522,27 @@ class FeedsDateTimeElement extends FeedsElement { $db_tz = new DateTimeZone($db_tz); if (!isset($entity->{$field_name})) { - $entity->{$field_name} = array('und' => array()); + $entity->{$field_name} = array($language => array()); } if ($use_start) { - $entity->{$field_name}['und'][$delta]['timezone'] = $use_start->getTimezone()->getName(); - $entity->{$field_name}['und'][$delta]['offset'] = $use_start->getOffset(); + $entity->{$field_name}[$language][$delta]['timezone'] = $use_start->getTimezone()->getName(); + $entity->{$field_name}[$language][$delta]['offset'] = $use_start->getOffset(); $use_start->setTimezone($db_tz); - $entity->{$field_name}['und'][$delta]['date'] = $use_start; + $entity->{$field_name}[$language][$delta]['date'] = $use_start; /** * @todo the date_type_format line could be simplified based upon a patch * DO issue #259308 could affect this, follow up on at some point. * Without this, all granularity info is lost. * $use_start->format(date_type_format($field['type'], $use_start->granularity)); */ - $entity->{$field_name}['und'][$delta]['value'] = $use_start->format(date_type_format($info['type'])); + $entity->{$field_name}[$language][$delta]['value'] = $use_start->format(date_type_format($info['type'])); } if ($use_end) { // Don't ever use end to set timezone (for now) - $entity->{$field_name}['und'][$delta]['offset2'] = $use_end->getOffset(); + $entity->{$field_name}[$language][$delta]['offset2'] = $use_end->getOffset(); $use_end->setTimezone($db_tz); - $entity->{$field_name}['und'][$delta]['date2'] = $use_end; - $entity->{$field_name}['und'][$delta]['value2'] = $use_end->format(date_type_format($info['type'])); + $entity->{$field_name}[$language][$delta]['date2'] = $use_end; + $entity->{$field_name}[$language][$delta]['value2'] = $use_end->format(date_type_format($info['type'])); } } } diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index fe7c541..8f14447 100644 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -586,6 +586,47 @@ abstract class FeedsProcessor extends FeedsPlugin { } /** + * Returns a statically cached version of the target mappings. + * + * @return array + * The targets for this importer. + */ + protected function getCachedTargets() { + $targets = &drupal_static('FeedsProcessor::getCachedTargets', array()); + + if (!isset($targets[$this->id])) { + $targets[$this->id] = $this->getMappingTargets(); + } + + return $targets[$this->id]; + } + + /** + * Returns a statically cached version of the source mappings. + * + * @return array + * The sources for this importer. + */ + protected function getCachedSources() { + $sources = &drupal_static('FeedsProcessor::getCachedSources', array()); + + if (!isset($sources[$this->id])) { + + $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources(); + + if (is_array($sources[$this->id])) { + foreach ($sources[$this->id] as $source_key => $source) { + if (empty($source['callback']) || !is_callable($source['callback'])) { + unset($sources[$this->id][$source_key]['callback']); + } + } + } + } + + return $sources[$this->id]; + } + + /** * Execute mapping on an item. * * This method encapsulates the central mapping functionality. When an item is @@ -603,82 +644,128 @@ abstract class FeedsProcessor extends FeedsPlugin { * @ingroup mappingapi * * @see hook_feeds_parser_sources_alter() - * @see hook_feeds_data_processor_targets_alter() - * @see hook_feeds_node_processor_targets_alter() - * @see hook_feeds_term_processor_targets_alter() - * @see hook_feeds_user_processor_targets_alter() + * @see hook_feeds_processor_targets() + * @see hook_feeds_processor_targets_alter() */ protected function map(FeedsSource $source, FeedsParserResult $result, $target_item = NULL) { + $targets = $this->getCachedTargets(); - // Static cache $targets as getMappingTargets() may be an expensive method. - static $sources; - if (!isset($sources[$this->id])) { - $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources(); - } - static $targets; - if (!isset($targets[$this->id])) { - $targets[$this->id] = $this->getMappingTargets(); - } - $parser = feeds_importer($this->id)->parser; if (empty($target_item)) { - $target_item = array(); + $target_item = new stdClass(); } + $mappings = $this->config['mappings']; // 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) { - if (isset($targets[$this->id][$mapping['target']]['real_target'])) { - $target_item->{$targets[$this->id][$mapping['target']]['real_target']} = NULL; + foreach ($mappings as $mapping) { + if (isset($targets[$mapping['target']]['real_target'])) { + $target_item->{$targets[$mapping['target']]['real_target']} = NULL; } else { $target_item->{$mapping['target']} = NULL; } } - /* - This is where the actual mapping happens: For every mapping we envoke - the parser's getSourceElement() method to retrieve the value of the source - element and pass it to the processor's setTargetElement() to stick it - on the right place of the target item. + // Invoke preprocess callbacks on targets. + $this->invokeProcessCallbacks('pre', $source, $target_item, $targets, $mappings); - If the mapping specifies a callback method, use the callback instead of - setTargetElement(). - */ - self::loadMappers(); - foreach ($this->config['mappings'] as $mapping) { - // Retrieve source element's value from parser. - if (isset($sources[$this->id][$mapping['source']]) && - is_array($sources[$this->id][$mapping['source']]) && - isset($sources[$this->id][$mapping['source']]['callback']) && - function_exists($sources[$this->id][$mapping['source']]['callback'])) { - $callback = $sources[$this->id][$mapping['source']]['callback']; - $value = $callback($source, $result, $mapping['source']); - } - else { - $value = $parser->getSourceElement($source, $result, $mapping['source']); + // This is where the actual mapping happens: For every mapping we invoke + // the parser's getSourceElement() method to retrieve the value of the + // source element and pass it to the processor's setTargetElement() to stick + // it on the right place of the target item. + foreach ($mappings as $mapping) { + + // Ensure there's always a language set. + if (empty($mapping['language'])) { + $mapping['language'] = LANGUAGE_NONE; } - // Map the source element's value to the target. - if (isset($targets[$this->id][$mapping['target']]) && - is_array($targets[$this->id][$mapping['target']]) && - isset($targets[$this->id][$mapping['target']]['callback']) && - function_exists($targets[$this->id][$mapping['target']]['callback'])) { - $callback = $targets[$this->id][$mapping['target']]['callback']; + $value = $this->getSourceValue($source, $result, $mapping['source']); - // All target callbacks expect an array. - if (!is_array($value)) { - $value = array($value); - } + $this->mapToTarget($source, $mapping['target'], $target_item, $value, $mapping); + } + + // Invoke postprocess callbacks on targets. + $this->invokeProcessCallbacks('post', $source, $target_item, $targets, $mappings); + + return $target_item; + } + + protected function invokeProcessCallbacks($key, FeedsSource $source, $target_item, array $targets, array &$mappings) { + $key .= 'process_callbacks'; + + foreach (array_keys($mappings) as $delta) { + $target = $mappings[$delta]['target']; - $callback($source, $target_item, $mapping['target'], $value, $mapping); + if (!isset($targets[$target][$key])) { + continue; } - else { - $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping); + + foreach ($targets[$target][$key] as $callback) { + $callback($source, $target_item, $targets[$target], $mappings[$delta]); } } + } - return $target_item; + /** + * Returns the values from the parser, or callback. + * + * @param FeedsSource $source + * The feed source. + * @param FeedsParserResult $result + * The parser result. + * @param string $source_key + * The current key being processed. + * + * @return mixed + * A value, or a list of values. + */ + protected function getSourceValue(FeedsSource $source, FeedsParserResult $result, $source_key) { + $sources = $this->getCachedSources(); + + if (isset($sources[$source_key]['callback'])) { + $callback = $sources[$source_key]['callback']; + + return $callback($source, $result, $source_key); + } + + return feeds_importer($this->id)->parser->getSourceElement($source, $result, $source_key); + } + + /** + * Maps values onto the target item. + * + * @param FeedsSource $source + * The feed source. + * @param mixed &$target_item + * The target item to apply values into. + * @param mixed $value + * A value, or a list of values. + * @param array $mapping + * The mapping configuration. + */ + protected function mapToTarget(FeedsSource $source, $target, &$target_item, $value, array $mapping) { + $targets = $this->getCachedTargets(); + + // Map the source element's value to the target. + // If the mapping specifies a callback method, use the callback instead of + // setTargetElement(). + if (isset($targets[$target]['callback'])) { + + $callback = $targets[$target]['callback']; + + // All target callbacks expect an array. + if (!is_array($value)) { + $value = array($value); + } + + $callback($source, $target_item, $target, $value, $mapping); + } + + else { + $this->setTargetElement($source, $target_item, $target, $value, $mapping); + } } /** @@ -705,6 +792,7 @@ abstract class FeedsProcessor extends FeedsPlugin { 'input_format' => NULL, 'skip_hash_check' => FALSE, 'bundle' => $bundle, + 'language' => LANGUAGE_NONE, ); } @@ -731,6 +819,16 @@ abstract class FeedsProcessor extends FeedsPlugin { ); } + if (module_exists('locale') && !empty($info['entity keys']['language'])) { + $form['language'] = array( + '#type' => 'select', + '#options' => array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name'), + '#title' => t('Language'), + '#required' => TRUE, + '#default_value' => $this->config['language'], + ); + } + $tokens = array('@entities' => strtolower($info['label plural'])); $form['update_existing'] = array( @@ -822,6 +920,22 @@ abstract class FeedsProcessor extends FeedsPlugin { } /** + * Allows other modules to expose targets. + * + * @param array &$targets + * The existing target array. + */ + protected function getHookTargets(array &$targets) { + self::loadMappers(); + + $entity_type = $this->entityType(); + $bundle = $this->bundle(); + $targets += module_invoke_all('feeds_processor_targets', $entity_type, $bundle); + + drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle); + } + + /** * Set a concrete target element. Invoked from FeedsProcessor::map(). * * @ingroup mappingapi @@ -832,6 +946,7 @@ abstract class FeedsProcessor extends FeedsPlugin { case 'guid': $target_item->feeds_item->$target_element = $value; break; + default: $target_item->$target_element = $value; break; @@ -852,10 +967,7 @@ abstract class FeedsProcessor extends FeedsPlugin { * The serial id of an entity if found, 0 otherwise. */ protected function existingEntityId(FeedsSource $source, FeedsParserResult $result) { - $targets = &drupal_static('FeedsProcessor::existingEntityId', array()); - if (!isset($targets[$this->id])) { - $targets[$this->id] = $this->getMappingTargets(); - } + $targets = $this->getCachedTargets(); $entity_id = 0; @@ -873,13 +985,13 @@ abstract class FeedsProcessor extends FeedsPlugin { ->fetchField(); } - if (!$entity_id && !empty($targets[$this->id][$target]['unique_callbacks'])) { + if (!$entity_id && !empty($targets[$target]['unique_callbacks'])) { if (!is_array($value)) { $value = array($value); } - foreach ($targets[$this->id][$target]['unique_callbacks'] as $callback) { - if (is_callable($callback) && $entity_id = call_user_func_array($callback, array($source, $this->entityType(), $this->bundle(), $target, $value))) { + foreach ($targets[$target]['unique_callbacks'] as $callback) { + if ($entity_id = $callback($source, $this->entityType(), $this->bundle(), $target, $value)) { // Stop at the first unique ID returned by a callback. break; } @@ -1010,7 +1122,7 @@ abstract class FeedsProcessor extends FeedsPlugin { $message .= '

Original item

'; $message .= '
' . drupal_var_export($item). '
'; $message .= '

Entity

'; - $message .= '
' . drupal_var_export($entity) . '
'; + // $message .= '
' . drupal_var_export($entity) . '
'; return $message; } diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc index 40b9207..ca3dd3d 100644 --- a/plugins/FeedsTermProcessor.inc +++ b/plugins/FeedsTermProcessor.inc @@ -38,10 +38,24 @@ class FeedsTermProcessor extends FeedsProcessor { } /** + * {@inheritdoc} + */ + protected function entityLoad(FeedsSource $source, $entity_id) { + if ($term = parent::entityLoad($source, $entity_id)) { + $vocabulary = $this->vocabulary(); + $term->vid = $vocabulary->vid; + $term->vocabulary_machine_name = $vocabulary->machine_name; + return $term; + } + + return FALSE; + } + + /** * Validates a term. */ protected function entityValidate($term) { - if (drupal_strlen($term->name) == 0) { + if (!strlen($term->name)) { throw new FeedsValidationException(t('Term name missing.')); } } @@ -177,21 +191,13 @@ class FeedsTermProcessor extends FeedsProcessor { 'description' => array( 'name' => t('Term description'), 'description' => t('Description of the taxonomy term.'), - 'summary_callback' => 'text_feeds_summary_callback', - 'form_callback' => 'text_feeds_form_callback', + 'summary_callbacks' => array('text_feeds_summary_callback'), + 'form_callbacks' => array('text_feeds_form_callback'), ), ); - // Let implementers of hook_feeds_term_processor_targets() add their targets. - try { - self::loadMappers(); - $entity_type = $this->entityType(); - $bundle = $this->bundle(); - drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle); - } - catch (Exception $e) { - // Do nothing. - } + $this->getHookTargets($targets); + return $targets; } diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc index d01db43..02607f2 100644 --- a/plugins/FeedsUserProcessor.inc +++ b/plugins/FeedsUserProcessor.inc @@ -193,11 +193,7 @@ class FeedsUserProcessor extends FeedsProcessor { ); } - // Let other modules expose mapping targets. - self::loadMappers(); - $entity_type = $this->entityType(); - $bundle = $this->bundle(); - drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle); + $this->getHookTargets($targets); return $targets; } diff --git a/tests/feeds_mapper_hooks.test b/tests/feeds_mapper_hooks.test new file mode 100644 index 0000000..5d76043 --- /dev/null +++ b/tests/feeds_mapper_hooks.test @@ -0,0 +1,72 @@ + 'Mapper: Hooks and callbacks', + 'description' => 'Test case for the various callbacks implemented for mappers.', + 'group' => 'Feeds', + ); + } + + /** + * Basic test loading a double entry CSV file. + */ + public function test() { + + // Create and configure importer. + $this->createImporterConfiguration(); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + 1 => array( + 'source' => 'description', + 'target' => 'test_target', + ), + )); + + // Checks that alter hooks are invoked. + $this->assertText(t('The target description was altered.')); + + // Inherently tests preprocess callbacks. + // @see feeds_tests_mapper_set_target() + $nid = $this->createFeedNode(); + $this->drupalGet('node/2/edit'); + $body_value = $this->xpath('//*[@name = "body[und][0][value]"]'); + $value = unserialize((string) $body_value[0]); + $this->assertTrue(!empty($value)); + + // Tests old-style target keys. + $this->addMappings('syndication', array( + 2 => array( + 'source' => 'url', + 'target' => 'test_target_compat', + ), + )); + + // Click gear to get form. + $this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_2'); + + // Set some settings. + $edit = array( + 'config[2][settings][checkbox]' => 1, + 'config[2][settings][textfield]' => 'Some text', + 'config[2][settings][textarea]' => 'Textarea value: Didery dofffffffffffffffffffffffffffffffffffff', + 'config[2][settings][radios]' => 'option1', + 'config[2][settings][select]' => 'option4', + ); + $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_2'); + $this->assertText(t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.')); + } + +} diff --git a/tests/feeds_mapper_taxonomy.test b/tests/feeds_mapper_taxonomy.test index 4a2eb8a..231235e 100644 --- a/tests/feeds_mapper_taxonomy.test +++ b/tests/feeds_mapper_taxonomy.test @@ -236,11 +236,13 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase { 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_ID, ); - taxonomy_feeds_set_target(NULL, $entity, $target, $terms, $mapping); + $source = FeedsSource::instance('tmp', 0); + + taxonomy_feeds_set_target($source, $entity, $target, $terms, $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); // Test a second mapping with a bogus term id. - taxonomy_feeds_set_target(NULL, $entity, $target, array(1234), $mapping); + taxonomy_feeds_set_target($source, $entity, $target, array(1234), $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); } @@ -284,14 +286,16 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase { 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_GUID, ); - taxonomy_feeds_set_target(NULL, $entity, $target, $guids, $mapping); + $source = FeedsSource::instance('tmp', 0); + + taxonomy_feeds_set_target($source, $entity, $target, $guids, $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); foreach ($entity->field_tags[LANGUAGE_NONE] as $delta => $values) { $this->assertEqual($tids[$delta], $values['tid'], 'Correct term id foud.'); } // Test a second mapping with a bogus term id. - taxonomy_feeds_set_target(NULL, $entity, $target, array(1234), $mapping); + taxonomy_feeds_set_target($source, $entity, $target, array(1234), $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); foreach ($entity->field_tags[LANGUAGE_NONE] as $delta => $values) { $this->assertEqual($tids[$delta], $values['tid'], 'Correct term id foud.'); diff --git a/tests/feeds_tests.module b/tests/feeds_tests.module index fe25704..d6aec41 100644 --- a/tests/feeds_tests.module +++ b/tests/feeds_tests.module @@ -102,15 +102,26 @@ function feeds_tests_files_remote() { } /** - * Implements hook_feeds_processor_targets_alter(). + * Implements hook_feeds_processor_targets(). */ -function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { +function feeds_tests_feeds_processor_targets($entity_type, $bundle) { + $targets = array(); + + // Tests that old keys still work. + $targets['test_target_compat'] = array( + 'name' => t('Old style target'), + 'callback' => 'feeds_tests_mapper_set_target', + 'summary_callback' => 'feeds_tests_mapper_summary', + 'form_callback' => 'feeds_tests_mapper_form', + ); + $targets['test_target'] = array( 'name' => t('Test Target'), 'description' => t('This is a test target.'), 'callback' => 'feeds_tests_mapper_set_target', - 'summary_callback' => 'feeds_tests_mapper_summary', - 'form_callback' => 'feeds_tests_mapper_form', + 'summary_callbacks' => array('feeds_tests_mapper_summary'), + 'form_callbacks' => array('feeds_tests_mapper_form'), + 'preprocess_callbacks' => array('feeds_tests_preprocess_callback'), ); $targets['test_unique_target'] = array( @@ -119,7 +130,30 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun 'callback' => 'feeds_tests_mapper_set_target', 'optional_unique' => TRUE, 'unique_callbacks' => array('feeds_tests_mapper_unique'), + 'preprocess_callbacks' => array('feeds_tests_preprocess_callback'), ); + + return $targets; +} + +/** + * Implements hook_feeds_processor_targets_alter(). + */ +function feeds_tests_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) { + if (!isset($targets['test_target'])) { + return; + } + + $targets['test_target']['description'] = t('The target description was altered.'); +} + +/** + * Preprocess callback for test_target. + * + * @see feeds_tests_feeds_processor_targets() + */ +function feeds_tests_preprocess_callback(FeedsSource $source, $target_item, array $target, array &$mapping) { + $mapping['required_value'] = TRUE; } /** @@ -127,7 +161,11 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun * * @see my_module_set_target() */ -function feeds_tests_mapper_set_target($source, $entity, $target, $value, $mapping) { +function feeds_tests_mapper_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + if (empty($mapping['required_value'])) { + trigger_error('The required value was not set.', E_USER_ERROR); + } + $entity->body['und'][0]['value'] = serialize($mapping); } @@ -136,7 +174,7 @@ function feeds_tests_mapper_set_target($source, $entity, $target, $value, $mappi * * @see my_module_summary_callback() */ -function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) { +function feeds_tests_mapper_summary(array $mapping, $target, array $form, array $form_state) { $options = array( 'option1' => t('Option 1'), 'option2' => t('Another Option'), @@ -174,7 +212,7 @@ function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) { /** * Provides the form with mapper settings. */ -function feeds_tests_mapper_form($mapping, $target, $form, $form_state) { +function feeds_tests_mapper_form(array $mapping, $target, array $form, array $form_state) { $mapping += array( 'checkbox' => FALSE, 'textfield' => '', @@ -217,7 +255,7 @@ function feeds_tests_mapper_form($mapping, $target, $form, $form_state) { /** * Callback for unique_callbacks for test_target mapper. * - * @see feeds_tests_feeds_processor_targets_alter() + * @see feeds_tests_feeds_processor_targets() */ function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) { $query = new EntityFieldQuery();