diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index 81eb1a36e59e015b7952e32176055e79190ff486..346cd4e28df58333a4ed2602447266d77eaa377d 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -186,7 +186,7 @@ Filter module - Daniel F. Kudwien 'sun' Forum module -- ? +- Lee Rowlands 'larowlan' Help module - ? diff --git a/includes/common.inc b/includes/common.inc index 1ef681f121a9afdbed0b40a206cd72792bb6a08e..f54f29a7c39faf62f1f1ece5e0c601f4009d2952 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -5041,7 +5041,15 @@ function drupal_cron_run() { drupal_register_shutdown_function('drupal_cron_cleanup'); // Iterate through the modules calling their cron handlers (if any): - module_invoke_all('cron'); + foreach (module_implements('cron') as $module) { + // Do not let an exception thrown by one module disturb another. + try { + module_invoke($module, 'cron'); + } + catch (Exception $e) { + watchdog_exception('cron', $e); + } + } // Record cron time variable_set('cron_last', REQUEST_TIME); diff --git a/includes/file.inc b/includes/file.inc index 30fd44577515d942334a22a170369ec3984aa535..6e2e5cb2828c9f4622b9253feed8a296b463050d 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1539,7 +1539,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, // directory. This overcomes open_basedir restrictions for future file // operations. $file->uri = $file->destination; - if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) { + if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) { form_set_error($source, t('File upload error. Could not move uploaded file.')); watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri)); return FALSE; @@ -1566,6 +1566,42 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, return FALSE; } +/** + * Moves an uploaded file to a new location. + * + * PHP's move_uploaded_file() does not properly support streams if safe_mode + * or open_basedir are enabled, so this function fills that gap. + * + * Compatibility: normal paths and stream wrappers. + * @see http://drupal.org/node/515192 + * + * @param $filename + * The filename of the uploaded file. + * @param $uri + * A string containing the destination URI of the file. + * + * @return + * TRUE on success, or FALSE on failure. + * + * @see move_uploaded_file() + * @ingroup php_wrappers + */ +function drupal_move_uploaded_file($filename, $uri) { + $result = @move_uploaded_file($filename, $uri); + // PHP's move_uploaded_file() does not properly support streams if safe_mode + // or open_basedir are enabled so if the move failed, try finding a real path + // and retry the move operation. + if (!$result) { + if ($realpath = drupal_realpath($uri)) { + $result = move_uploaded_file($filename, $realpath); + } + else { + $result = move_uploaded_file($filename, $uri); + } + } + + return $result; +} /** * Check that a file meets the criteria specified by the validators. diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 68784d13a75edb61fcb33dbb134fec170e834bc9..88f9231ac8ce711cee6e21b17a52c2859e40a0ed 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -675,10 +675,6 @@ function hook_field_is_empty($item, $field) { * Widget hooks are typically called by the Field Attach API during the * creation of the field form structure with field_attach_form(). * - * @see hook_field_widget_info_alter() - * @see hook_field_widget_form() - * @see hook_field_widget_error() - * * @return * An array describing the widget types implemented by the module. * The keys are widget type names. To avoid name clashes, widget type @@ -704,6 +700,12 @@ function hook_field_is_empty($item, $field) { * - FIELD_BEHAVIOR_DEFAULT: (default) If the widget accepts default * values. * - FIELD_BEHAVIOR_NONE: if the widget does not support default values. + * + * @see hook_field_widget_info_alter() + * @see hook_field_widget_form() + * @see hook_field_widget_form_alter() + * @see hook_field_widget_WIDGET_TYPE_form_alter() + * @see hook_field_widget_error() */ function hook_field_widget_info() { return array( @@ -783,8 +785,8 @@ function hook_field_widget_info_alter(&$info) { * properties from $field and $instance and set them as ad-hoc * $element['#custom'] properties, for later use by its element callbacks. * - * @see field_widget_field() - * @see field_widget_instance() + * Other modules may alter the form element provided by this function using + * hook_field_widget_form_alter(). * * @param $form * The form structure where widgets are being attached to. This might be a @@ -826,6 +828,11 @@ function hook_field_widget_info_alter(&$info) { * * @return * The form elements for a single widget for this field. + * + * @see field_widget_field() + * @see field_widget_instance() + * @see hook_field_widget_form_alter() + * @see hook_field_widget_WIDGET_TYPE_form_alter() */ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $element += array( @@ -835,6 +842,69 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco return $element; } +/** + * Alter forms for field widgets provided by other modules. + * + * @param $element + * The field widget form element as constructed by hook_field_widget_form(). + * @param $form_state + * An associative array containing the current state of the form. + * @param $context + * An associative array containing the following key-value pairs, matching the + * arguments received by hook_field_widget_form(): + * - "form": The form structure where widgets are being attached to. This + * might be a full form structure, or a sub-element of a larger form. + * - "field": The field structure. + * - "instance": The field instance structure. + * - "langcode": The language associated with $items. + * - "items": Array of default values for this field. + * - "delta": The order of this item in the array of subelements (0, 1, 2, + * etc). + * + * @see hook_field_widget_form() + * @see hook_field_widget_WIDGET_TYPE_form_alter + */ +function hook_field_widget_form_alter(&$element, &$form_state, $context) { + // Add a css class to widget form elements for all fields of type mytype. + if ($context['field']['type'] == 'mytype') { + // Be sure not to overwrite existing attributes. + $element['#attributes']['class'][] = 'myclass'; + } +} + +/** + * Alter widget forms for a specific widget provided by another module. + * + * Modules can implement hook_field_widget_WIDGET_TYPE_form_alter() to modify a + * specific widget form, rather than using hook_field_widget_form_alter() and + * checking the widget type. + * + * @param $element + * The field widget form element as constructed by hook_field_widget_form(). + * @param $form_state + * An associative array containing the current state of the form. + * @param $context + * An associative array containing the following key-value pairs, matching the + * arguments received by hook_field_widget_form(): + * - "form": The form structure where widgets are being attached to. This + * might be a full form structure, or a sub-element of a larger form. + * - "field": The field structure. + * - "instance": The field instance structure. + * - "langcode": The language associated with $items. + * - "items": Array of default values for this field. + * - "delta": The order of this item in the array of subelements (0, 1, 2, + * etc). + * + * @see hook_field_widget_form() + * @see hook_field_widget_form_alter() + */ +function hook_field_widget_WIDGET_TYPE_form_alter(&$element, &$form_state, $context) { + // Code here will only act on widgets of type WIDGET_TYPE. For example, + // hook_field_widget_mymodule_autocomplete_form_alter() will only act on + // widgets of type 'mymodule_autocomplete'. + $element['#autocomplete_path'] = 'mymodule/autocomplete_path'; +} + /** * Flag a field-level validation error. * diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index 640ea75ec08ff6dc774d0c3dabe541a041602c14..79fc14a20e87c4324e6d6193984f2e395ff9c802 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -76,6 +76,17 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode, '#delta' => $delta, ); if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) { + // Allow modules to alter the field widget form element. + $context = array( + 'form' => $form, + 'field' => $field, + 'instance' => $instance, + 'langcode' => $langcode, + 'items' => $items, + 'delta' => $delta, + ); + drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context); + // If we're processing a specific delta value for a field where the // field module handles multiples, set the delta in the result. // For fields that handle their own processing, we can't make @@ -193,6 +204,18 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form, '#weight' => 100, ); } + + // Allow modules to alter the field widget form element. + $context = array( + 'form' => $form, + 'field' => $field, + 'instance' => $instance, + 'langcode' => $langcode, + 'items' => $items, + 'delta' => $delta, + ); + drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context); + $field_elements[$delta] = $element; } } diff --git a/modules/field/field.module b/modules/field/field.module index 6432ce06062c7db490fdd8ba402686cf3af20dd8..30166bdeecfd4049aa73ffef37abd234dc410cf8 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -305,13 +305,6 @@ class FieldException extends Exception {} */ class FieldUpdateForbiddenException extends FieldException {} -/** - * Implements hook_flush_caches(). - */ -function field_flush_caches() { - return array('cache_field'); -} - /** * Implements hook_help(). */ @@ -370,6 +363,7 @@ function field_theme() { * Purges some deleted Field API data, if any exists. */ function field_cron() { + field_sync_field_status(); $limit = variable_get('field_purge_batch_size', 10); field_purge_batch($limit); } @@ -386,45 +380,84 @@ function field_modules_uninstalled($modules) { } /** - * Implements hook_modules_enabled(). + * Implements hook_system_info_alter(). + * + * Goes through a list of all modules that provide a field type, and makes them + * required if there are any active fields of that type. */ -function field_modules_enabled($modules) { - foreach ($modules as $module) { - field_associate_fields($module); +function field_system_info_alter(&$info, $file, $type) { + if ($type == 'module' && module_hook($file->name, 'field_info')) { + $fields = field_read_fields(array('module' => $file->name), array('include_deleted' => TRUE)); + if ($fields) { + $info['required'] = TRUE; + + // Provide an explanation message (only mention pending deletions if there + // remains no actual, non-deleted fields) + $non_deleted = FALSE; + foreach ($fields as $field) { + if (empty($field['deleted'])) { + $non_deleted = TRUE; + break; + } + } + if ($non_deleted) { + if (module_exists('field_ui')) { + $explanation = t('Field type(s) in use - see !link', array('!link' => l(t('Field list'), 'admin/reports/fields'))); + } + else { + $explanation = t('Fields type(s) in use'); + } + } + else { + $explanation = t('Fields pending deletion'); + } + $info['explanation'] = $explanation; + } } - field_cache_clear(); } /** - * Implements hook_modules_disabled(). + * Implements hook_flush_caches(). + */ +function field_flush_caches() { + field_sync_field_status(); + return array('cache_field'); +} + +/** + * Refreshes the 'active' and 'storage_active' columns for fields. */ -function field_modules_disabled($modules) { - // Track fields whose field type is being disabled. +function field_sync_field_status() { + // Refresh the 'active' and 'storage_active' columns according to the current + // set of enabled modules. + $all_modules = system_rebuild_module_data(); + $modules = array(); + foreach ($all_modules as $module_name => $module) { + if ($module->status) { + $modules[] = $module_name; + field_associate_fields($module_name); + } + } db_update('field_config') ->fields(array('active' => 0)) - ->condition('module', $modules, 'IN') + ->condition('module', $modules, 'NOT IN') ->execute(); - - // Track fields whose storage backend is being disabled. db_update('field_config') ->fields(array('storage_active' => 0)) - ->condition('storage_module', $modules, 'IN') + ->condition('storage_module', $modules, 'NOT IN') ->execute(); - - field_cache_clear(); } /** * Allows a module to update the database for fields and columns it controls. * - * @param string $module + * @param $module * The name of the module to update on. */ function field_associate_fields($module) { // Associate field types. - $field_types =(array) module_invoke($module, 'field_info'); + $field_types = (array) module_invoke($module, 'field_info'); foreach ($field_types as $name => $field_info) { - watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module)); db_update('field_config') ->fields(array('module' => $module, 'active' => 1)) ->condition('type', $name) @@ -433,7 +466,6 @@ function field_associate_fields($module) { // Associate storage backends. $storage_types = (array) module_invoke($module, 'field_storage_info'); foreach ($storage_types as $name => $storage_info) { - watchdog('field', 'Updating field storage %type with module %module.', array('%type' => $name, '%module' => $module)); db_update('field_config') ->fields(array('storage_module' => $module, 'storage_active' => 1)) ->condition('storage_type', $name) diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index ebb1c9f9194f5ed3e14afc60a118b2ce1e62ce9c..669fc37cf44104ca07fe06a2ae6e9a5fa34f9ac2 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -2356,6 +2356,7 @@ class FieldCrudTestCase extends FieldTestCase { $this->assertTrue($field_definition <= $field, t('The field was properly read.')); module_disable($modules, FALSE); + drupal_flush_all_caches(); $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE)); $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.')); @@ -2368,6 +2369,7 @@ class FieldCrudTestCase extends FieldTestCase { $module = array_shift($modules); module_enable(array($module), FALSE); + drupal_flush_all_caches(); } // Check that the field is active again after all modules have been diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index 9cd5ee15da7de54f2589c6698ce0ccce30a634aa..3f60085d8747d65fe15f66698af603acffeb797a 100644 --- a/modules/field_ui/field_ui.admin.inc +++ b/modules/field_ui/field_ui.admin.inc @@ -12,17 +12,27 @@ function field_ui_fields_list() { $instances = field_info_instances(); $field_types = field_info_field_types(); $bundles = field_info_bundles(); + + $modules = system_rebuild_module_data(); + $header = array(t('Field name'), t('Field type'), t('Used in')); $rows = array(); foreach ($instances as $entity_type => $type_bundles) { foreach ($type_bundles as $bundle => $bundle_instances) { foreach ($bundle_instances as $field_name => $instance) { $field = field_info_field($field_name); + + // Initialize the row if we encounter the field for the first time. + if (!isset($rows[$field_name])) { + $rows[$field_name]['class'] = $field['locked'] ? array('menu-disabled') : array(''); + $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + $module_name = $field_types[$field['type']]['module']; + $rows[$field_name]['data'][1] = $field_types[$field['type']]['label'] . ' ' . t('(module: !module)', array('!module' => $modules[$module_name]->info['name'])); + } + + // Add the current instance. $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); - $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; - $rows[$field_name]['data'][1] = $field_types[$field['type']]['label']; $rows[$field_name]['data'][2][] = $admin_path ? l($bundles[$entity_type][$bundle]['label'], $admin_path . '/fields') : $bundles[$entity_type][$bundle]['label']; - $rows[$field_name]['class'] = $field['locked'] ? array('menu-disabled') : array(''); } } } @@ -1717,6 +1727,14 @@ function field_ui_field_delete_form_submit($form, &$form_state) { $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); $form_state['redirect'] = field_ui_get_destinations(array($admin_path . '/fields')); + + // Fields are purged on cron. However field module prevents disabling modules + // when field types they provided are used in a field until it is fully + // purged. In the case that a field has minimal or no content, a single call + // to field_purge_batch() will remove it from the system. Call this with a + // low batch limit to avoid administrators having to wait for cron runs when + // removing instances that meet this criteria. + field_purge_batch(10); } /** diff --git a/modules/forum/forum-list.tpl.php b/modules/forum/forum-list.tpl.php index bc0935f888234303ebc034fa2dc6ba88ee52b5c0..257cea947d944a61b381070f7c52166407eb0665 100644 --- a/modules/forum/forum-list.tpl.php +++ b/modules/forum/forum-list.tpl.php @@ -15,6 +15,8 @@ * FALSE if the forum can contain only topics. * - $forum->depth: How deep the forum is in the current hierarchy. * - $forum->zebra: 'even' or 'odd' string used for row class. + * - $forum->icon_class: 'default' or 'new' string used for forum icon class. + * - $forum->icon_title: Text alternative for the forum icon. * - $forum->name: The name of the forum. * - $forum->link: The URL to link to this forum. * - $forum->description: The description of this forum. @@ -48,6 +50,9 @@ * left-margin for indenting. */ ?> ', $forum->depth); ?> +
+ icon_title; ?> +
description): ?>
description; ?>
diff --git a/modules/forum/forum-rtl.css b/modules/forum/forum-rtl.css index d31c9e7f372effc98e105a292391979a28ce300e..81dd4d39606a7a2dbc443f0fa484f4835804c18c 100644 --- a/modules/forum/forum-rtl.css +++ b/modules/forum/forum-rtl.css @@ -1,8 +1,7 @@ -#forum tr td.forum { - padding-left: 0.5em; - padding-right: 25px; - background-position: 98% 2px; +#forum td.forum .icon { + float: right; + margin: 0 0 0 9px; } .forum-topic-navigation { padding: 1em 3em 0 0; diff --git a/modules/forum/forum.css b/modules/forum/forum.css index 3f3ed98481ce779676bbf7dc6a7d44964afc7fed..4a67c8bcdab26b8e7209491389684ca911389cc4 100644 --- a/modules/forum/forum.css +++ b/modules/forum/forum.css @@ -11,15 +11,19 @@ #forum td.pager { white-space: nowrap; } -#forum tr td.forum { - padding-left: 25px; /* LTR */ - background-position: 2px 2px; /* LTR */ - background-image: url(../../misc/forum-default.png); + +#forum td.forum .icon { + background-image: url(../../misc/forum-icons.png); background-repeat: no-repeat; + float: left; /* LTR */ + height: 24px; + margin: 0 9px 0 0; /* LTR */ + width: 24px; } -#forum tr.new-topics td.forum { - background-image: url(../../misc/forum-new.png); +#forum td.forum .forum-status-new { + background-position: -24px 0; } + #forum div.indent { margin-left: 20px; } diff --git a/modules/forum/forum.install b/modules/forum/forum.install index b5817ab2992f53f80be7fd6ba3d8d90cc86eef22..2eebd7fba3ee2087bc06f8e152c39910222d1068 100644 --- a/modules/forum/forum.install +++ b/modules/forum/forum.install @@ -113,6 +113,11 @@ function forum_uninstall() { variable_del('forum_block_num_active'); variable_del('forum_block_num_new'); variable_del('node_options_forum'); + + field_delete_field('taxonomy_forums'); + // Purge field data now to allow taxonomy module to be uninstalled + // if this is the only field remaining. + field_purge_batch(10); } /** diff --git a/modules/forum/forum.module b/modules/forum/forum.module index f2ac5acc51eaff5e27ff2df308957078019a0161..5bb43925bc55a9f10bb2f989053f9cb00d4b63fd 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -1064,11 +1064,15 @@ function template_preprocess_forum_list(&$variables) { $variables['forums'][$id]->new_url = ''; $variables['forums'][$id]->new_topics = 0; $variables['forums'][$id]->old_topics = $forum->num_topics; + $variables['forums'][$id]->icon_class = 'default'; + $variables['forums'][$id]->icon_title = t('No new posts'); if ($user->uid) { $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid); if ($variables['forums'][$id]->new_topics) { $variables['forums'][$id]->new_text = format_plural($variables['forums'][$id]->new_topics, '1 new', '@count new'); $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new')); + $variables['forums'][$id]->icon_class = 'new'; + $variables['forums'][$id]->icon_title = t('New posts'); } $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics; } diff --git a/modules/forum/forum.test b/modules/forum/forum.test index 1dc45c6fdf61b4d13b81acd5b4ec41afdb268532..c7c3d9c1b8c2e2327273874a1ed0d55f671afe34 100644 --- a/modules/forum/forum.test +++ b/modules/forum/forum.test @@ -31,6 +31,7 @@ class ForumTestCase extends DrupalWebTestCase { // Create users. $this->admin_user = $this->drupalCreateUser(array( 'access administration pages', + 'administer modules', 'administer blocks', 'administer forums', 'administer menu', @@ -51,6 +52,30 @@ class ForumTestCase extends DrupalWebTestCase { $this->web_user = $this->drupalCreateUser(array()); } + /** + * Tests disabling and re-enabling forum. + */ + function testEnableForumField() { + $this->drupalLogin($this->admin_user); + + // Disable the forum module. + $edit = array(); + $edit['modules[Core][forum][enable]'] = FALSE; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.')); + module_list(TRUE); + $this->assertFalse(module_exists('forum'), t('Forum module is not enabled.')); + + // Attempt to re-enable the forum module and ensure it does not try to + // recreate the taxonomy_forums field. + $edit = array(); + $edit['modules[Core][forum][enable]'] = 'forum'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.')); + module_list(TRUE); + $this->assertTrue(module_exists('forum'), t('Forum module is enabled.')); + } + /** * Login users, create forum nodes, and test forum functionality through the admin and user interfaces. */ diff --git a/modules/poll/poll.module b/modules/poll/poll.module index 01c6bcc801ecdb115b5c5849a77c8a5acab5a9f0..2737c2bd8dde405730571b4fa4d3ec96dea43559 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -594,6 +594,10 @@ function poll_update($node) { ->condition('nid', $node->nid) ->condition('chid', $key) ->execute(); + db_delete('poll_choice') + ->condition('nid', $node->nid) + ->condition('chid', $choice['chid']) + ->execute(); } } } diff --git a/modules/poll/poll.test b/modules/poll/poll.test index d6c4f4005ba5d4c0485458d50647a9439c7672da..d7648a6bad29335f094e2b63528fea191aea8f90 100644 --- a/modules/poll/poll.test +++ b/modules/poll/poll.test @@ -746,3 +746,39 @@ class PollExpirationTestCase extends PollTestCase { $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll has expired.')); } } + +class PollDeleteChoiceTestCase extends PollTestCase { + public static function getInfo() { + return array( + 'name' => 'Poll choice deletion', + 'description' => 'Test the poll choice deletion logic.', + 'group' => 'Poll', + ); + } + + function setUp() { + parent::setUp('poll'); + } + + function testChoiceRemoval() { + // Set up a poll with three choices. + $title = $this->randomName(); + $choices = array('First choice', 'Second choice', 'Third choice'); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->assertTrue($poll_nid, t('Poll for choice deletion logic test created.')); + + // Edit the poll, and try to delete first poll choice. + $this->drupalGet("node/$poll_nid/edit"); + $edit['choice[chid:1][chtext]'] = ''; + $this->drupalPost(NULL, $edit, t('Save')); + + // Click on the poll title to go to node page. + $this->drupalGet('poll'); + $this->clickLink($title); + + // Check the first poll choice is deleted, while the others remain. + $this->assertNoText('First choice', t('First choice removed.')); + $this->assertText('Second choice', t('Second choice remains.')); + $this->assertText('Third choice', t('Third choice remains.')); + } +} diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module index 9b6178804c9087b7d5144e45a23d6f7d48fc0d87..c400eaed11423b857b50fed0c31160f7c17e416c 100644 --- a/modules/simpletest/tests/common_test.module +++ b/modules/simpletest/tests/common_test.module @@ -225,3 +225,16 @@ function common_test_js_and_css_querystring() { drupal_add_css('/' . drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2'); return ''; } + +/** + * Implements hook_cron(). + * + * System module should handle if a module does not catch an exception and keep + * cron going. + * + * @see common_test_cron_helper() + * + */ +function common_test_cron() { + throw new Exception(t('Uncaught exception')); +} diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info new file mode 100644 index 0000000000000000000000000000000000000000..ce1a6326fb330068c919cb1e614f028be4aab01a --- /dev/null +++ b/modules/simpletest/tests/common_test_cron_helper.info @@ -0,0 +1,6 @@ +name = "Common Test Cron Helper" +description = "Helper module for CronRunTestCase::testCronExceptions()." +package = Testing +version = VERSION +core = 7.x +hidden = TRUE diff --git a/modules/simpletest/tests/common_test_cron_helper.module b/modules/simpletest/tests/common_test_cron_helper.module new file mode 100644 index 0000000000000000000000000000000000000000..94a2b2c43821ae8e8f41ff6a3a6a81fbc984868e --- /dev/null +++ b/modules/simpletest/tests/common_test_cron_helper.module @@ -0,0 +1,17 @@ +status; if (!empty($module->info['required'] )) { $extra['disabled'] = TRUE; - $extra['required_by'][] = $distribution_name; + $extra['required_by'][] = $distribution_name . (!empty($module->info['explanation']) ? ' ('. $module->info['explanation'] .')' : ''); } // If this module requires other modules, add them to the array. @@ -3131,4 +3131,4 @@ function system_action_delete_orphans_post($orphaned) { function system_actions_remove_orphans() { actions_synchronize(TRUE); drupal_goto('admin/config/system/actions/manage'); -} \ No newline at end of file +} diff --git a/modules/system/system.test b/modules/system/system.test index 9944619ec45b5fcd80f2baa4ec40239de4ea5023..1ce8e6b7e0ed106cc7582a32d8f4e14f64f5357b 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -445,39 +445,6 @@ class ModuleDependencyTestCase extends ModuleTestCase { } - /** - * Tests re-enabling forum with taxonomy disabled. - */ - function testEnableForumTaxonomyFieldDependency() { - // Enable the forum module. - $edit = array(); - $edit['modules[Core][forum][enable]'] = 'forum'; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertModules(array('forum'), TRUE); - - // Disable the forum module. - $edit = array(); - $edit['modules[Core][forum][enable]'] = FALSE; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertModules(array('forum'), FALSE); - - // Disable the taxonomy module. - $edit = array(); - $edit['modules[Core][taxonomy][enable]'] = FALSE; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertModules(array('taxonomy'), FALSE); - - // Attempt to re-enable the forum module with taxonomy disabled and ensure - // forum does not try to recreate the taxonomy_forums field. - $edit = array(); - $edit['modules[Core][forum][enable]'] = 'forum'; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertText(t('Some required modules must be enabled'), t('Dependency required.')); - $this->drupalPost(NULL, NULL, t('Continue')); - $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.')); - $this->assertModules(array('taxonomy', 'forum'), TRUE); - } - /** * Tests that module dependencies are enabled in the correct order via the * UI. Dependencies should be enabled before their dependents. @@ -516,18 +483,18 @@ class ModuleDependencyTestCase extends ModuleTestCase { $this->drupalPost('admin/modules', $edit, t('Save configuration')); $this->assertModules(array('forum'), TRUE); - // Disable forum and taxonomy. Both should now be installed but disabled. + // Disable forum and comment. Both should now be installed but disabled. $edit = array('modules[Core][forum][enable]' => FALSE); $this->drupalPost('admin/modules', $edit, t('Save configuration')); $this->assertModules(array('forum'), FALSE); - $edit = array('modules[Core][taxonomy][enable]' => FALSE); + $edit = array('modules[Core][comment][enable]' => FALSE); $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertModules(array('taxonomy'), FALSE); + $this->assertModules(array('comment'), FALSE); // Check that the taxonomy module cannot be uninstalled. $this->drupalGet('admin/modules/uninstall'); - $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="uninstall[taxonomy]"]'); - $this->assert(count($checkbox) == 1, t('Checkbox for uninstalling the taxonomy module is disabled.')); + $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="uninstall[comment]"]'); + $this->assert(count($checkbox) == 1, t('Checkbox for uninstalling the comment module is disabled.')); // Uninstall the forum module, and check that taxonomy now can also be // uninstalled. @@ -535,7 +502,7 @@ class ModuleDependencyTestCase extends ModuleTestCase { $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); $this->drupalPost(NULL, NULL, t('Uninstall')); $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.')); - $edit = array('uninstall[taxonomy]' => 'taxonomy'); + $edit = array('uninstall[comment]' => 'comment'); $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); $this->drupalPost(NULL, NULL, t('Uninstall')); $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.')); @@ -728,6 +695,10 @@ class CronRunTestCase extends DrupalWebTestCase { ); } + function setUp() { + parent::setUp(array('common_test', 'common_test_cron_helper')); + } + /** * Test cron runs. */ @@ -832,6 +803,19 @@ class CronRunTestCase extends DrupalWebTestCase { $this->assertTrue(file_exists($perm_old->uri), t('Old permanent file was correctly ignored.')); $this->assertTrue(file_exists($perm_new->uri), t('New permanent file was correctly ignored.')); } + + /** + * Make sure exceptions thrown on hook_cron() don't affect other modules. + */ + function testCronExceptions() { + variable_del('common_test_cron'); + // The common_test module throws an exception. If it isn't caught, the tests + // won't finish successfully. + // The common_test_cron_helper module sets the 'common_test_cron' variable. + $this->cronRun(); + $result = variable_get('common_test_cron'); + $this->assertEqual($result, 'success', t('Cron correctly handles exceptions thrown during hook_cron() invocations.')); + } } class AdminMetaTagTestCase extends DrupalWebTestCase { diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index aa7cc2e44a68943f66c446aa3835274301f65ffa..9a89b9c98ff63924d7d592c7408efe70cdb756d4 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -360,7 +360,6 @@ class TaxonomyVocabularyUnitTest extends TaxonomyWebTestCase { $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4); $this->field = field_create_field($this->field); - $this->field_id = $this->field['id']; $this->instance = array( 'field_name' => $this->field_name, 'entity_type' => 'taxonomy_term', @@ -370,6 +369,7 @@ class TaxonomyVocabularyUnitTest extends TaxonomyWebTestCase { field_create_instance($this->instance); module_disable(array('taxonomy')); + drupal_flush_all_caches(); require_once DRUPAL_ROOT . '/includes/install.inc'; drupal_uninstall_modules(array('taxonomy')); module_enable(array('taxonomy')); diff --git a/modules/update/update.test b/modules/update/update.test index e72f4c5402a71a3e1c9de1b6d79cba4a222674e9..4fb8630d76c9964eb3124605f31c99b1590cc90c 100644 --- a/modules/update/update.test +++ b/modules/update/update.test @@ -441,6 +441,11 @@ class UpdateTestContribCase extends UpdateTestHelper { 'hidden' => FALSE, ), ); + // When there are contributed modules in the site's file system, the + // total number of attempts made in the test may exceed the default value + // of update_max_fetch_attempts. Therefore this variable is set very high + // to avoid test failures in those cases. + variable_set('update_max_fetch_attempts', 99999); variable_set('update_test_system_info', $system_info); $xml_mapping = array( 'drupal' => '0', diff --git a/themes/bartik/css/style.css b/themes/bartik/css/style.css index 614d32a1ed25d19335ad4809a49a86af8d48f6b0..4fb8210255b9a40940de73ab4fa7bba7418df9b5 100644 --- a/themes/bartik/css/style.css +++ b/themes/bartik/css/style.css @@ -1431,9 +1431,6 @@ div.password-suggestions { div.vertical-tabs .vertical-tabs-panes fieldset.vertical-tabs-pane { padding: 1em; } -#forum tr td.forum { - padding-left: 35px; -} #forum .name { font-size: 1.083em; }