? 955236-3_error_handling.patch ? PATCHES.txt Index: feeds.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.module,v retrieving revision 1.74.2.11 diff -u -p -r1.74.2.11 feeds.module --- feeds.module 27 Oct 2010 19:53:21 -0000 1.74.2.11 +++ feeds.module 29 Oct 2010 20:50:03 -0000 @@ -738,6 +738,39 @@ function feeds_log($importer_id, $feed_n } /** + * Gets the last caller from a backtrace. + * + * Modeled after _drupal_get_last_caller(). + * + * @param $backtrace + * A standard PHP backtrace. + * @return + * An associative array with keys 'file', 'line' and 'function'. + */ +function feeds_get_last_caller($backtrace) { + // Shift off one to not report Feeds error handler methods. + array_shift($backtrace); + + // The first trace is the call itself. + // It gives us the line and the file of the last call. + $call = $backtrace[0]; + + // The second call give us the function where the call originated. + if (isset($backtrace[1])) { + if (isset($backtrace[1]['class'])) { + $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()'; + } + else { + $call['function'] = $backtrace[1]['function'] . '()'; + } + } + else { + $call['function'] = 'main()'; + } + return $call; +} + +/** * Loads an item info object. * * Example usage: Index: includes/FeedsSource.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsSource.inc,v retrieving revision 1.26.2.8 diff -u -p -r1.26.2.8 FeedsSource.inc --- includes/FeedsSource.inc 26 Oct 2010 03:05:58 -0000 1.26.2.8 +++ includes/FeedsSource.inc 29 Oct 2010 20:50:03 -0000 @@ -328,6 +328,8 @@ class FeedsSource extends FeedsConfigura */ public function import() { $this->acquireLock(); + set_error_handler(array($this, 'importError')); + try { // Fetch. if (empty($this->fetcher_result) || FEEDS_BATCH_COMPLETE == $this->progressParsing()) { @@ -354,6 +356,8 @@ class FeedsSource extends FeedsConfigura unset($this->fetcher_result, $this->state); } $this->save(); + restore_error_handler(); + if (isset($e)) { throw $e; } @@ -376,6 +380,8 @@ class FeedsSource extends FeedsConfigura */ public function clear() { $this->acquireLock(); + set_error_handler(array($this, 'clearError')); + try { $this->importer->fetcher->clear($this); $this->importer->parser->clear($this); @@ -391,6 +397,8 @@ class FeedsSource extends FeedsConfigura unset($this->state); } $this->save(); + restore_error_handler(); + if (isset($e)) { throw $e; } @@ -398,6 +406,57 @@ class FeedsSource extends FeedsConfigura } /** + * Handles errors on import. + */ + public function importError($error_level, $message, $filename, $line, $context) { + if ($error_level & error_reporting()) { + // If this is NOT a recoverable error, attempt to clean up. + // @todo: does this make sense or do we know that if an error was not + // recoverable we can't clean up? + // @todo: it may be safer to delete the state every time when loading it. + if ($this->handleError($error_level, $message, $filename, $line, $context)) { + unset($this->state); + $this->save(); + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } + } + + /** + * Handles errors on clear. + */ + public function clearError($error_level, $message, $filename, $line, $context) { + if ($error_level & error_reporting()) { + if ($this->handleError($error_level, $message, $filename, $line, $context)) { + unset($this->state); + $this->save(); + } + _drupal_error_handler($error_level, $message, $filename, $line, $context); + } + } + + /** + * Logs error and determines whether error is fatal + * + * @return + * TRUE if error is fatal, FALSE otherwise. + */ + protected function handleError($error_level, $message, $filename, $line, $context) { + require_once DRUPAL_ROOT . '/includes/errors.inc'; + $levels = drupal_error_levels(); + list ($severity_message, $severity_level) = $levels[$error_level]; + $caller = feeds_get_last_caller(debug_backtrace()); + $variables = array( + '@message' => $message, + '%function' => $caller['function'], + '%file' => isset($caller['file']) ? $caller['file'] : '', + '%line' => isset($caller['line']) ? $caller['line'] : '', + ); + $this->log('import', "@message
%function
%file
%line", $variables, $severity_level); + return $error_level & array(E_ERROR | E_COMPILE_ERROR | E_COMPILE_ERROR | E_USER_ERROR); + } + + /** * Report progress as float between 0 and 1. 1 = FEEDS_BATCH_COMPLETE. */ public function progressParsing() { Index: mappers/link.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/mappers/link.inc,v retrieving revision 1.5 diff -u -p -r1.5 link.inc --- mappers/link.inc 17 Sep 2010 17:25:19 -0000 1.5 +++ mappers/link.inc 29 Oct 2010 20:50:03 -0000 @@ -1,87 +1,81 @@ $instance) { - $fields = array(); - if (isset($info['fields']) && count($info['fields'])) { - foreach ($info['fields'] as $field_name => $field) { - - if (in_array($field['type'], array('link'))) { - $name = isset($field['widget']['label']) ? $field['widget']['label'] : $field_name; - $targets[$field_name .':url'] = array( - 'name' => t('!field_name (URL)', array('!field_name' => $name)), + $info = field_info_field($name); + $allowed_types = array( + 'link_field', + ); + + if (in_array($info['type'], $allowed_types)) { + if (array_key_exists('url', $info['columns'])) { + $targets[$name . ':url'] = array( + 'name' => $instance['label'] . ' URL', 'callback' => 'link_feeds_set_target', - 'description' => t('The URL for the CCK !name field of the node.', array('!name' => $name)), - 'real_target' => $field_name, + 'description' => t('The @label field of the node.', array('@label' => $instance['label'])), + ); + } + if (array_key_exists('title', $info['columns'])) { + $targets[$name . ':title'] = array( + 'name' => $instance['label'] . ' Title', + 'callback' => 'link_feeds_set_target', + 'description' => t('The @label field of the node.', array('@label' => $instance['label'])), ); - - //Provides a mapping target for the field title if used. - if (in_array($field['title'], array('optional', 'required'))) { - $targets[$field_name .':title'] = array( - 'name' => $name .' (' . t('title').')', - 'callback' => 'link_feeds_set_target', - 'description' => t('The title for the CCK !name field of the node.', array('!name' => $name)), - 'real_target' => $field_name, - ); - } } } } } /** - * Callback for mapping to link field. + * Callback for mapping. Here is where the actual mapping happens. * - * @param $node - * Reference to the node object we are working on. - * @param $target - * The selected link CCK field. - * @param $value - * The value to assign to the CCK field. + * 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. */ -function link_feeds_set_target($node, $target, $value) { - module_load_include('inc', 'link'); - if (!empty($value)) { - static $defaults = array(); - list($field_name, $sub_field) = split(':', $target); - - if (!isset($defaults[$node->type][$field_name])) { - $field = content_fields($field_name, $node->type); - $defaults[$node->type][$field_name]['attributes'] = $field['attributes']; - if (!in_array($field['title'], array('optional', 'required', 'none'))) { - $defaults[$node->type][$field_name]['title'] = $field['title_value']; - } - } - $field_data = isset($node->$field_name) ? $node->$field_name : array(); +function link_feeds_set_target($source, $entity, $target, $value) { + if (empty($value)) { + return; + } - if (!is_array($value)) { - $value = array($value); - } + list($field_name, $sub_field) = preg_split("/:/", $target); - $i = 0; - foreach ($value as $v) { - if ($v instanceof FeedsEnclosure) { - $v = $v->getValue(); - } - if (!isset($field_data[$i])) { - $field_data[$i] = $defaults[$node->type][$field_name]; + // Handle non-multiple value fields. + if (!is_array($value)) { + $value = array($value); + } + + $info = field_info_field($target); + + // Iterate over all values. + $i = 0; + + foreach ($value as $v) { + if (!is_array($v) && !is_object($v)) { + if (strstr($target, 'url')) { + if(isset($entity->{$field_name}['und'][$i]['title'])) { + $field['und'][$i]['title'] = $entity->{$field_name}['und'][$i]['title']; + } + $field['und'][$i]['url'] = $v; } - if ($sub_field != 'url' || (($v = link_cleanup_url($v)) && valid_url($v, true))) { - $field_data[$i][$sub_field] = $v; + elseif (strstr($target, 'title')) { + if(isset($entity->{$field_name}['und'][$i]['url'])) { + $field['und'][$i]['url'] = $entity->{$field_name}['und'][$i]['url']; + } + $field['und'][$i]['title'] = $v; } - $i++; } - - $node->$field_name = $field_data; + if ($info['cardinality'] == 1) { + break; + } + $i++; } + + $entity->{$field_name} = $field; }