diff --git a/feeds.api.php b/feeds.api.php index 58e167082b7eedad84bccbd69d7a577a721aa5ca..46cf90201fc56d0f2cba8af16a606b5a7cd9661a 100644 --- a/feeds.api.php +++ b/feeds.api.php @@ -257,7 +257,7 @@ function my_source_get_source(FeedsSource $source, FeedsParserResult $result, $k } /** - * Alters mapping targets for processors. + * Adds mapping targets for processors. * * This hook allows additional target options to be added to the processors * mapping form. @@ -265,18 +265,20 @@ function my_source_get_source(FeedsSource $source, FeedsParserResult $result, $k * If the key in $targets[] does not correspond to the actual key on the node * object ($node->key), real_target MUST be specified. See mappers/link.inc * - * For an example implementation, see mappers/content.inc + * For an example implementation, see mappers/text.inc * - * @param &$targets - * Array containing the targets to be offered to the user. Add to this array - * to expose additional options. Remove from this array to suppress options. - * Remove with caution. - * @param $entity_type + * @param string $entity_type * The entity type of the target, for instance a 'node' entity. - * @param $bundle - * The bundle name for which to alter targets. + * @param string $bundle + * The entity bundle to return targets for. + * + * @return array + * Array containing the targets to be offered to the user. This function must + * return an array, even an empty one. */ -function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { +function hook_feeds_processor_targets($entity_type, $bundle) { + $targets = array(); + if ($entity_type == 'node') { $targets['my_node_field'] = array( 'name' => t('My custom node field'), @@ -285,8 +287,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'), @@ -304,26 +306,56 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { // targets. 'optional_unique' => TRUE, 'unique_callbacks' => array('my_module_mapper_unique'), + + // Preprocess callbacks are called before the actual callback allowing you + // to prepare values on the entity or mapping array. + 'preprocess_callbacks' => array('my_module_preprocess_callback'), ); } + + return $targets; } /** - * Example callback specified in hook_feeds_processor_targets_alter(). + * Alters the target array. * - * @param $source + * This hook allows modifying the target array. + * + * @param array &$targets + * Array containing the targets to be offered to the user. Add to this array + * to expose additional options. + * @param string $entity_type + * The entity type of the target, for instance a 'node' entity. + * @param string $bundle + * The entity bundle to return targets for. + * + * @see hook_feeds_processor_targets() + */ +function hook_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) { + if ($entity_type == 'node' && $bundle == 'article') { + if (isset($targets['nid'])) { + $targets['nid']['unique_callbacks'][] = 'my_module_mapper_unique'; + $targets['nid']['optional_unique'] = TRUE; + } + } +} + +/** + * Example callback specified in hook_feeds_processor_targets(). + * + * @param FeedsSource $source * Field mapper source settings. - * @param $entity + * @param object $entity * An entity object, for instance a node object. - * @param $target + * @param string $target * A string identifying the target on the node. - * @param $values + * @param array $values * The value to populate the target with. - * @param $mapping + * @param array $mapping * Associative array of the mapping settings from the per mapping * configuration form. */ -function my_module_set_target($source, $entity, $target, array $values, $mapping) { +function my_module_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { $entity->{$target}[$entity->language][0]['value'] = reset($values); if (isset($source->importer->processor->config['input_format'])) { $entity->{$target}[$entity->language][0]['format'] = $source->importer->processor->config['input_format']; @@ -331,26 +363,25 @@ function my_module_set_target($source, $entity, $target, array $values, $mapping } /** - * Example of the summary_callback specified in - * hook_feeds_processor_targets_alter(). + * Example of the summary_callback specified in hook_feeds_processor_targets(). * - * @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 <strong>not</strong> active'); } @@ -360,18 +391,17 @@ function my_module_summary_callback($mapping, $target, $form, $form_state) { } /** - * Example of the form_callback specified in - * hook_feeds_processor_targets_alter(). + * Example of the form_callback specified in hook_feeds_processor_targets(). * * 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', @@ -382,8 +412,7 @@ function my_module_form_callback($mapping, $target, $form, $form_state) { } /** - * Example of the unique_callbacks specified in - * hook_feeds_processor_targets_alter(). + * Example of the unique_callbacks specified in hook_feeds_processor_targets(). * * @param FeedsSource $source * The Feed source. @@ -396,10 +425,10 @@ function my_module_form_callback($mapping, $target, $form, $form_state) { * @param array $values * The unique values to be checked. * - * @return int + * @return int|null * The existing entity id, or NULL if no existing entity is found. * - * @see hook_feeds_processor_targets_alter() + * @see hook_feeds_processor_targets() * @see FeedsProcessor::existingEntityId() */ function my_module_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) { @@ -417,6 +446,25 @@ function my_module_mapper_unique(FeedsSource $source, $entity_type, $bundle, $ta } } +/** + * Example of the preprocess_callbacks specified in hook_feeds_processor_targets(). + * + * @param FeedsSource $source + * The Feed source. + * @param object $entity + * The entity being processed. + * @param array $target + * The full target definition. + * @param array &$mapping + * The mapping configuration. + * + * @see hook_feeds_processor_targets() + */ +function my_module_preprocess_callback(FeedsSource $source, $entity, array $target, array &$mapping) { + // Add in default values. + $mapping += array('setting_value' => TRUE); +} + /** * @} */ diff --git a/feeds.feeds.inc b/feeds.feeds.inc new file mode 100644 index 0000000000000000000000000000000000000000..a51a7a6c52fa3ae0c25c584693bb1e697882eaba --- /dev/null +++ b/feeds.feeds.inc @@ -0,0 +1,84 @@ +<?php + +/** + * @file + * Feeds hooks. + */ + +/** + * Implements hook_feeds_processor_targets(). + */ +function feeds_feeds_processor_targets($entity_type, $bundle) { + // Record that we've been called. + // @see _feeds_feeds_processor_targets_alter() + $called = &drupal_static('feeds_feeds_processor_targets', FALSE); + $called = TRUE; + + return array(); +} + +/** + * Implements hook_feeds_processor_targets_alter(). + */ +function feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) { + // This hook gets called last, so that we normalize the whole array. + feeds_normalize_targets($targets); +} + +/** + * Normalizes the target array. + * + * @param array &$targets + * The Feeds target array. + */ +function feeds_normalize_targets(array &$targets) { + static $defaults = array( + 'description' => '', + 'summary_callbacks' => array(), + 'form_callbacks' => array(), + 'preprocess_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) { + // Migrate keys summary_callback and form_callback to the new keys. + if (isset($target['summary_callback'])) { + $target['summary_callbacks'][] = $target['summary_callback']; + } + if (isset($target['form_callback'])) { + $target['form_callbacks'][] = $target['form_callback']; + } + unset($target['summary_callback'], $target['form_callback']); + + static $callback_keys = array( + 'summary_callbacks', + 'form_callbacks', + 'preprocess_callbacks', + 'unique_callbacks', + ); + + // Filter out any incorrect callbacks. 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 be3f52a54e31ec829f8e8dbf578810bad9b106ad..57eaabf8e9fe84031b48245805ae46f1412b931c 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 f53692654391a27583c6c7fdcac39c528da1ecc8..efbc703a163350c3bd63fc032240d147ad983227 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', ); @@ -784,6 +785,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_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc index b174d75d81aee62cbf34690672d7b90b4991f582..f975ffe1864c2f582aff2833bb62ac807a9e14bb 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 += call_user_func($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[] = call_user_func($callback, $mapping, $target, $form, $form_state); } + // Filter out empty summary values. + $summary = implode('<br />', 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 061962d4b96a0fa8818d30f0b3df236886d47b93..d5ea81f8e28048947d6b3277167f2c6f7dcfa9df 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) { list($field_name, $sub_field) = explode(':', $target, 2); $delta = 0; diff --git a/mappers/file.inc b/mappers/file.inc index 22735908185d5c750b023024c577565395eed42c..0c406776faedf30959d33ce74da2975970b19d80 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,14 @@ 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) { // Add default of uri for backwards compatibility. list($field_name, $sub_field) = explode(':', $target . ':uri'); $info = field_info_field($field_name); diff --git a/mappers/link.inc b/mappers/link.inc index 90b5268c44ec44d07c14afc11fc6a81c9567c4ee..e9c363227a05c76c2b5d131b1260976018e1a7ed 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,16 +32,14 @@ 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) { list($field_name, $column) = explode(':', $target); $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array()); diff --git a/mappers/list.inc b/mappers/list.inc index 6a8a16878442c11f9bea22a59ef1806c4cc22160..8c5dc3627f44c367170c3bc819d868fe4afb9cd4 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,12 +42,14 @@ 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()) { +function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values) { $field = isset($entity->$target) ? $entity->$target : array(LANGUAGE_NONE => array()); foreach ($values as $value) { diff --git a/mappers/number.inc b/mappers/number.inc index 2b9c436858c1d7380542fafd5df6700db0931ba3..7f9a0d8e54cb8e7f3594fa155f35b0469e29e060 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,14 +27,14 @@ 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) { // Iterate over all values. $field = isset($entity->$target) ? $entity->$target : array('und' => array()); diff --git a/mappers/path.inc b/mappers/path.inc index cd39fb10b5f076940cf15d31d2d7f388581909b4..1dceeeac48c1b372a264609c82d216b0dc410e48 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 00fdf3ab9a70e9841cbbf0c7d4aa70c6967a0f6d..42f8e88e1254a69d8bb3ac4c2aabbfa2f1850145 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 75f6541c9c0e6c427b4ae24aaf91b2f2c95a8ed3..6fbdec691271337eafb906253a9904af43c0a173 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,14 @@ 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) { // Add in default values. $mapping += array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME, @@ -252,24 +254,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: <strong>@search</strong>', array('@search' => $options[FEEDS_TAXONOMY_SEARCH_TERM_NAME])); @@ -279,12 +265,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 89a03ff1645cf813ccb86690a7b3a57a357646ed..aa9c2e1fb9bab292a3dfeacb2704144131d669a6 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,18 @@ 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) { list($field_name, $column) = explode(':', $target . ':value'); if ($column === 'value' && isset($source->importer->processor->config['input_format'])) { @@ -87,7 +89,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 +111,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 ec23016fbdf86af7bc9cb9a0c7e341d1b473e063..2cfdf3061195f345bfff51fb89908ee019852fe6 100644 --- a/plugins/FeedsNodeProcessor.inc +++ b/plugins/FeedsNodeProcessor.inc @@ -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/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index fe7c541f1e3fce758bdef444dc289de99711e695..0ab04a045d4bdf21a2c56ca5261d6c9c1069dd28 100644 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -585,6 +585,47 @@ abstract class FeedsProcessor extends FeedsPlugin { return db_query("SELECT count(*) FROM {feeds_item} WHERE id = :id AND entity_type = :entity_type AND feed_nid = :feed_nid", array(':id' => $this->id, ':entity_type' => $this->entityType(), ':feed_nid' => $source->feed_nid))->fetchField(); } + /** + * 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. * @@ -603,23 +644,12 @@ 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(); } @@ -628,57 +658,87 @@ abstract class FeedsProcessor extends FeedsPlugin { // 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; + 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. - - If the mapping specifies a callback method, use the callback instead of - setTargetElement(). - */ - self::loadMappers(); + // 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 ($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']); - } + $value = $this->getSourceValue($source, $result, $mapping['source']); - // 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']; + $this->mapToTarget($source, $mapping['target'], $target_item, $value, $mapping); + } - // All target callbacks expect an array. - if (!is_array($value)) { - $value = array($value); - } + 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'])) { + return call_user_func($sources[$source_key]['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(); - $callback($source, $target_item, $mapping['target'], $value, $mapping); + if (isset($targets[$target]['preprocess_callbacks'])) { + foreach ($targets[$target]['preprocess_callbacks'] as $callback) { + call_user_func_array($callback, array($source, $target_item, $target, &$mapping)); } - else { - $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping); + } + + // 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'])) { + + // All target callbacks expect an array. + if (!is_array($value)) { + $value = array($value); } + + call_user_func($targets[$target]['callback'], $source, $target_item, $target, $value, $mapping); } - return $target_item; + else { + $this->setTargetElement($source, $target_item, $target, $value, $mapping); + } } /** @@ -821,6 +881,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(). * @@ -832,6 +908,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 +929,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 +947,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 = call_user_func($callback, $source, $this->entityType(), $this->bundle(), $target, $value)) { // Stop at the first unique ID returned by a callback. break; } diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc index 40b92072a301d0bd216e0bd7eb4f91141554d12a..f69819f30f901792726fb01b8dc17b212c77a981 100644 --- a/plugins/FeedsTermProcessor.inc +++ b/plugins/FeedsTermProcessor.inc @@ -177,21 +177,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 d01db439504cddd6e93686f01383640734e60099..02607f2c90c4181959be6007aa133dd60191ff43 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_config.test b/tests/feeds_mapper_config.test index 58dd11e5eacac31850928a3a12fbb68803f1be78..566bbf96d06263c4f619546ec306af066ca0089c 100644 --- a/tests/feeds_mapper_config.test +++ b/tests/feeds_mapper_config.test @@ -37,6 +37,8 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase { // Click gear to get form. $this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_0'); + $second_callback_value = $this->randomString(); + // Set some settings. $edit = array( 'config[0][settings][checkbox]' => 1, @@ -44,6 +46,7 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase { 'config[0][settings][textarea]' => 'Textarea value: Didery dofffffffffffffffffffffffffffffffffffff', 'config[0][settings][radios]' => 'option1', 'config[0][settings][select]' => 'option4', + 'config[0][settings][second_value]' => $second_callback_value, ); $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_0'); $this->assertText(t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.')); @@ -60,6 +63,7 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase { $this->assertText('Textarea value: Didery dofffffffffffffffffffffffffffffffffffff'); $this->assertText('Radios value: Option 1'); $this->assertText('Select value: Another One'); + $this->assertText(t('Second summary: @value', array('@value' => $second_callback_value))); // Check that settings are in db. $config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id='syndication'")->fetchField()); @@ -70,6 +74,7 @@ class FeedsMapperConfigTestCase extends FeedsMapperTestCase { $this->assertEqual($settings['textarea'], 'Textarea value: Didery dofffffffffffffffffffffffffffffffffffff'); $this->assertEqual($settings['radios'], 'option1'); $this->assertEqual($settings['select'], 'option4'); + $this->assertEqual($settings['second_value'], $second_callback_value); // Check that form validation works. diff --git a/tests/feeds_mapper_hooks.test b/tests/feeds_mapper_hooks.test new file mode 100644 index 0000000000000000000000000000000000000000..5d76043ae77178cb0c7abc1156543cafff59ee67 --- /dev/null +++ b/tests/feeds_mapper_hooks.test @@ -0,0 +1,72 @@ +<?php + +/** + * @file + * Test case for the various callbacks implemented for mappers. + */ + +/** + * Class for testing Feeds field mapper. + */ +class FeedsMapperHookTestCase extends FeedsMapperTestCase { + public static function getInfo() { + return array( + 'name' => '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 4a2eb8ae1a123faaea0b3b22cc7804bb373eb4aa..231235ef16671c71a3a850c9948ed62219bab29e 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 fe25704746f121937e455f54a2bc13afe99d70f7..1ddd2a61ce9f398133894aa0911266b8eec5a193 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', 'feeds_tests_mapper_summary_2'), + 'form_callbacks' => array('feeds_tests_mapper_form', 'feeds_tests_mapper_form_2'), + 'preprocess_callbacks' => array(array(new FeedsTestsPreprocess(), 'callback')), ); $targets['test_unique_target'] = array( @@ -119,7 +130,34 @@ 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( + array('FeedsTestsPreprocess', 'callback'), + // Make sure that invalid callbacks are filtered. + '__feeds_tests_invalid_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, $target, array &$mapping) { + $mapping['required_value'] = TRUE; } /** @@ -127,7 +165,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 +178,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'), @@ -171,10 +213,20 @@ function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) { return drupal_render($list); } +/** + * Provides a second summary callback. + * + * @see my_module_summary_callback() + */ +function feeds_tests_mapper_summary_2(array $mapping, $target, array $form, array $form_state) { + $mapping += array('second_value' => ''); + return t('Second summary: @value', array('@value' => $mapping['second_value'])); +} + /** * 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' => '', @@ -214,10 +266,23 @@ function feeds_tests_mapper_form($mapping, $target, $form, $form_state) { ); } +/** + * Provides a second settings form. + */ +function feeds_tests_mapper_form_2(array $mapping, $target, array $form, array $form_state) { + return array( + 'second_value' => array( + '#type' => 'textfield', + '#title' => t('The second callback value'), + '#default_value' => !empty($mapping['second_value']) ? $mapping['second_value'] : '', + ), + ); +} + /** * 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(); @@ -231,3 +296,19 @@ function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $ return key($result[$entity_type]); } } + +/** + * Helper class to ensure callbacks can be objects. + */ +class FeedsTestsPreprocess { + +/** + * Preprocess callback for test_target. + * + * @see feeds_tests_feeds_processor_targets() + */ + public static function callback(FeedsSource $source, $target_item, $target, array &$mapping) { + $mapping['required_value'] = TRUE; + } + +}