diff --git a/feeds_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc index b9fe2a6..65b1615 100644 --- a/feeds_ui/feeds_ui.admin.inc +++ b/feeds_ui/feeds_ui.admin.inc @@ -401,6 +401,15 @@ function feeds_ui_edit_page(FeedsImporter $importer, $active = 'help', $plugin_k $info['actions'] = array(l(t('Change'), $path . '/processor')); $config_info[] = $info; + // Validate configuration. + $errors = $importer->validateConfig(); + if (count($errors)) { + $message = t('There are some issues with the importer configuration: !errors', array( + '!errors' => theme('item_list', array('items' => $errors)), + )); + drupal_set_message($message, 'warning'); + } + return theme('feeds_ui_edit_page', array( 'info' => $config_info, 'active' => $active_container, @@ -1207,12 +1216,21 @@ function feeds_ui_importer_import_validate($form, &$form_state) { } if (!$form_state['values']['bypass_validation']) { + $errors = array(); + + $importer = feeds_importer($feeds_importer->id); + $importer->setConfig($feeds_importer->config); foreach (array('fetcher', 'parser', 'processor') as $type) { $plugin = feeds_plugin($feeds_importer->config[$type]['plugin_key'], $feeds_importer->id); - if (get_class($plugin) == 'FeedsMissingPlugin') { - form_error($form['importer'], t('The plugin %plugin is unavailable.', array('%plugin' => $feeds_importer->config[$type]['plugin_key']))); + if (!($plugin instanceof FeedsMissingPlugin)) { + $importer->setPlugin($feeds_importer->config[$type]['plugin_key']); + $importer->$type->setConfig($feeds_importer->config[$type]['config']); } } + $errors = array_merge($errors, $importer->validateConfig()); + if (!empty($errors)) { + form_error($form['importer'], theme('item_list', array('items' => $errors))); + } } $form_state['importer'] = $feeds_importer; diff --git a/feeds_ui/feeds_ui.test b/feeds_ui/feeds_ui.test index 7378b91..14694a4 100644 --- a/feeds_ui/feeds_ui.test +++ b/feeds_ui/feeds_ui.test @@ -18,7 +18,7 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase { } public function setUp() { - parent::setUp(array('php'), array('use PHP for settings')); + parent::setUp(array('php', 'locale'), array('use PHP for settings', 'administer languages')); } /** @@ -116,6 +116,85 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase { // @todo Refreshing/deleting feed items. Needs to live in feeds.test } + /** + * Tests if the user is warned when an invalid plugin is used. + */ + public function testInvalidPlugin() { + // Create an importer. + $this->createImporterConfiguration('Test feed', 'test_feed'); + + // Add invalid fetcher plugin. + $invalid_plugin = $this->randomName(); + $importer = feeds_importer('test_feed'); + $importer->addConfig(array( + 'fetcher' => array( + 'plugin_key' => $invalid_plugin, + 'config' => array(), + ), + )); + $importer->save(); + + // Assert error message on importer page. + $this->drupalGet('admin/structure/feeds/test_feed'); + $this->assertText(format_string('The plugin @invalid_plugin is unavailable.', array( + '@invalid_plugin' => $invalid_plugin, + ))); + } + + /** + * Tests if the user is warned when an invalid bundle is selected. + */ + public function testInvalidBundle() { + // Create an importer. + $this->createImporterConfiguration('Test feed', 'test_feed'); + + // Set invalid bundle. + $invalid_bundle = drupal_strtolower($this->randomName()); + $importer = feeds_importer('test_feed'); + $importer->processor->addConfig(array( + 'bundle' => $invalid_bundle, + )); + $importer->save(); + + // Assert error message on processor settings page. + $this->drupalGet('admin/structure/feeds/test_feed/settings/FeedsNodeProcessor'); + $this->assertText(format_string('Invalid value @invalid_bundle for config option Bundle.', array( + '@invalid_bundle' => $invalid_bundle, + ))); + + // But the option should still be selected. + $this->assertFieldByName('bundle', $invalid_bundle); + } + + /** + * Tests if the user is warned when an invalid language is selected. + */ + public function testInvalidLanguage() { + // Add the Dutch language. + $edit = array( + 'langcode' => 'nl', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Create an importer. + $this->createImporterConfiguration('Test feed', 'test_feed'); + // Change processor's language to Dutch. + $this->setSettings('test_feed', 'FeedsNodeProcessor', array('language' => 'nl')); + + // Now remove the Dutch language. + $path = 'admin/config/regional/language/delete/nl'; + $this->drupalPost($path, array(), t('Delete')); + + // Assert error message on processor settings page. + $this->drupalGet('admin/structure/feeds/test_feed/settings/FeedsNodeProcessor'); + $this->assertText(format_string('Invalid value @invalid_lang for config option Language.', array( + '@invalid_lang' => 'nl', + ))); + + // But the option should still be selected. + $this->assertFieldByName('language', 'nl'); + } + public function testImporterImport() { $name = $this->randomString(); $id = drupal_strtolower($this->randomName()); @@ -157,4 +236,71 @@ class FeedsUIUserInterfaceTestCase extends FeedsWebTestCase { $this->assertTrue($config['skip_hash_check']); } + /** + * Tests if the user is warned when importing an importer with invalid configuration. + */ + public function testInvalidConfigurationWhenImportingImporter() { + $name = $this->randomString(); + $id = drupal_strtolower($this->randomName()); + $this->createImporterConfiguration($name, $id); + $this->setPlugin($id, 'FeedsCSVParser'); + $this->setPlugin($id, 'FeedsFileFetcher'); + + $this->drupalGet('admin/structure/feeds/' . $id . '/export'); + + $export = $this->xpath('//textarea[1]/text()'); + $export = (string) $export[0]; + + // Add in some invalid configuration in the export. + $invalid_plugin = $this->randomName(); + $invalid_bundle = strtolower($this->randomName()); + $invalid_language = 'de'; + $export = str_replace('FeedsFileFetcher', $invalid_plugin, $export); + $export = str_replace("'bundle' => 'article'", "'bundle' => '" . $invalid_bundle . "'", $export); + $export = str_replace("'language' => 'und'", "'language' => '" . $invalid_language . "'", $export); + + // Delete this importer. + $this->drupalPost('admin/structure/feeds/' . $id . '/delete', array(), t('Delete')); + + // Try to import. + $edit = array( + 'importer' => $export, + ); + $this->drupalPost('admin/structure/feeds/import', $edit, t('Import')); + + // Assert that the importer was not imported. + $this->assertNoText("Successfully imported the $id feeds importer."); + $this->assertFalse(feeds_importer_load($id), 'The importer was not created.'); + + // Assert error messages. + $this->assertText(format_string('The plugin @invalid_plugin is unavailable.', array( + '@invalid_plugin' => $invalid_plugin, + ))); + $this->assertText(format_string('Invalid value @invalid_bundle for config option Bundle.', array( + '@invalid_bundle' => $invalid_bundle, + ))); + $this->assertText(format_string('Invalid value @invalid_lang for config option Language.', array( + '@invalid_lang' => $invalid_language, + ))); + + // Try if the importer is imported when ignoring validation. + $edit['bypass_validation'] = 1; + $this->drupalPost(NULL, $edit, t('Import')); + + // Assert that the importer has been imported now. + drupal_static_reset(); + $this->assertTrue(feeds_importer_load($id) instanceof FeedsImporter, 'The importer was created.'); + + // But the warnings should still be displayed. + $this->drupalGet('admin/structure/feeds/' . $id); + $this->assertText(format_string('The plugin @invalid_plugin is unavailable.', array( + '@invalid_plugin' => $invalid_plugin, + ))); + $this->assertText(format_string('Invalid value @invalid_bundle for config option Bundle.', array( + '@invalid_bundle' => $invalid_bundle, + ))); + $this->assertText(format_string('Invalid value @invalid_lang for config option Language.', array( + '@invalid_lang' => $invalid_language, + ))); + } } diff --git a/includes/FeedsConfigurable.inc b/includes/FeedsConfigurable.inc index 93ad2eb..6fb9f94 100644 --- a/includes/FeedsConfigurable.inc +++ b/includes/FeedsConfigurable.inc @@ -182,6 +182,16 @@ abstract class FeedsConfigurable { } /** + * Validates the configuration. + * + * @return array + * A list of errors. + */ + public function validateConfig() { + return array(); + } + + /** * Returns whether or not the configurable has a config form. * * @return bool diff --git a/includes/FeedsImporter.inc b/includes/FeedsImporter.inc index cad7695..0edf669 100644 --- a/includes/FeedsImporter.inc +++ b/includes/FeedsImporter.inc @@ -177,6 +177,38 @@ class FeedsImporter extends FeedsConfigurable { } /** + * Validates the configuration. + */ + public function validateConfig() { + $errors = parent::validateConfig(); + + // Validate errors of each plugin as well. + $plugin_types = array( + 'fetcher' => t('Fetcher'), + 'parser' => t('Parser'), + 'processor' => t('Processor'), + ); + foreach ($plugin_types as $type => $plugin_label) { + // Check if plugin exists. + $plugin = feeds_plugin($this->config[$type]['plugin_key'], $this->id); + if ($plugin instanceof FeedsMissingPlugin) { + $errors[] = t('The plugin %plugin is unavailable.', array('%plugin' => $this->config[$type]['plugin_key'])); + continue; + } + + $plugin_errors = $this->$type->validateConfig(); + foreach ($plugin_errors as $key => $error) { + $errors[] = t('@plugin: !error', array( + '@plugin' => $plugin_label, + '!error' => $error, + )); + } + } + + return $errors; + } + + /** * Return defaults for feed configuration. */ public function configDefaults() { diff --git a/plugins/FeedsPlugin.inc b/plugins/FeedsPlugin.inc index 65115f8..6b44881 100644 --- a/plugins/FeedsPlugin.inc +++ b/plugins/FeedsPlugin.inc @@ -210,6 +210,12 @@ abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInter public static function child($plugin_key, $parent_plugin) { ctools_include('plugins'); $plugins = ctools_get_plugins('feeds', 'plugins'); + + if (!isset($plugins[$plugin_key])) { + // Plugin is not available. + return FALSE; + } + $info = $plugins[$plugin_key]; if (empty($info['handler']['parent'])) { diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index 7898f24..687cffe 100644 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -913,6 +913,42 @@ abstract class FeedsProcessor extends FeedsPlugin { } /** + * Validates the configuration. + * + * @return array + * A list of errors. + */ + public function validateConfig() { + $errors = parent::validateConfig(); + $info = $this->entityInfo(); + $config = $this->getConfig(); + + // Check configured bundle. + if (isset($config['bundle'])) { + $bundles = $this->bundleOptions(); + if (!in_array($config['bundle'], array_keys($bundles))) { + $errors[] = t('Invalid value %value for config option %key.', array( + '%value' => $config['bundle'], + '%key' => !empty($info['bundle name']) ? $info['bundle name'] : t('Bundle'), + )); + } + } + + // Check configured language. + if (module_exists('locale') && !empty($info['entity keys']['language']) && isset($config['language'])) { + $languages = array(LANGUAGE_NONE) + array_keys(locale_language_list('name')); + if (!in_array($config['language'], $languages)) { + $errors[] = t('Invalid value %value for config option %key.', array( + '%value' => $config['language'], + '%key' => t('Language'), + )); + } + } + + return $errors; + } + + /** * Overrides parent::configForm(). */ public function configForm(&$form_state) { @@ -920,13 +956,21 @@ abstract class FeedsProcessor extends FeedsPlugin { $form = array(); if (!empty($info['entity keys']['bundle'])) { + $default_bundle = $this->bundle(); $form['bundle'] = array( '#type' => 'select', '#options' => $this->bundleOptions(), '#title' => !empty($info['bundle name']) ? $info['bundle name'] : t('Bundle'), '#required' => TRUE, - '#default_value' => $this->bundle(), + '#default_value' => $default_bundle, ); + + // Add default value as one of the options if not yet available. + if (!isset($form['bundle']['#options'][$default_bundle])) { + $form['bundle']['#options'][$default_bundle] = t('Unknown bundle: @bundle', array( + '@bundle' => $default_bundle, + )); + } } else { $form['bundle'] = array( @@ -943,6 +987,13 @@ abstract class FeedsProcessor extends FeedsPlugin { '#required' => TRUE, '#default_value' => $this->config['language'], ); + + // Add default value as one of the options if not yet available. + if (!isset($form['language']['#options'][$this->config['language']])) { + $form['language']['#options'][$this->config['language']] = t('Unknown language: @language', array( + '@language' => $this->config['language'], + )); + } } $tokens = array('@entities' => strtolower($info['label plural']));