diff --git a/core/modules/update/src/Form/UpdateManagerUpdate.php b/core/modules/update/src/Form/UpdateManagerUpdate.php index fb5385c..220aead 100644 --- a/core/modules/update/src/Form/UpdateManagerUpdate.php +++ b/core/modules/update/src/Form/UpdateManagerUpdate.php @@ -199,42 +199,46 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Drupal core needs to be upgraded manually. $needs_manual = $project['project_type'] == 'core'; + // If the recommended release for a contributed project is not compatible + // with the currently installed version of core, list that project in a + // separate table. To determine if the releases is compatible, we inspect + // the 'core_compatible' key from the release info array. If it's not + // defined, it means we can't determine compatibility requirements (or + // we're looking at core), so we assume it is compatible. + $compatible = $recommended_release['core_compatible'] ?? TRUE; + if ($needs_manual) { - // There are no checkboxes in the 'Manual updates' table so it will be - // rendered by '#theme' => 'table', not '#theme' => 'tableselect'. Since - // the data formats are incompatible, we convert now to the format - // expected by '#theme' => 'table'. - unset($entry['#weight']); - $attributes = $entry['#attributes']; - unset($entry['#attributes']); - $entry = [ - 'data' => $entry, - ] + $attributes; + $this->removeCheckboxFromRow($entry); + $projects['manual'][$name] = $entry; + } + elseif (!$compatible) { + $this->removeCheckboxFromRow($entry); + // If the release has a core_compatibility_message, inject it. + if (!empty($recommended_release['core_compatibility_message'])) { + $entry['data']['recommended_version']['data']['#template'] .= '
{{ core_compatibility_message }}
'; + $entry['data']['recommended_version']['data']['#context']['core_compatibility_message'] = $recommended_release['core_compatibility_message']; + } + $projects['not-compatible'][$name] = $entry; } else { $form['project_downloads'][$name] = [ '#type' => 'value', '#value' => $recommended_release['download_link'], ]; - } - - // Based on what kind of project this is, save the entry into the - // appropriate subarray. - switch ($project['project_type']) { - case 'core': - // Core needs manual updates at this time. - $projects['manual'][$name] = $entry; - break; - case 'module': - case 'theme': - $projects['enabled'][$name] = $entry; - break; - - case 'module-disabled': - case 'theme-disabled': - $projects['disabled'][$name] = $entry; - break; + // Based on what kind of project this is, save the entry into the + // appropriate subarray. + switch ($project['project_type']) { + case 'module': + case 'theme': + $projects['enabled'][$name] = $entry; + break; + + case 'module-disabled': + case 'theme-disabled': + $projects['disabled'][$name] = $entry; + break; + } } } @@ -297,10 +301,43 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; } + if (!empty($projects['not-compatible'])) { + $form['not_compatible'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $projects['not-compatible'], + '#prefix' => '

' . $this->t('Not compatible') . '

', + '#weight' => 150, + ]; + } + return $form; } /** + * Prepares a row entry for use in a regular table, not a 'tableselect'. + * + * There are no checkboxes in the 'Manual updates' or 'Not compatible' tables, + * so they will be rendered by '#theme' => 'table', not 'tableselect'. Since + * the data formats are incompatible, this method converts to the format + * expected by '#theme' => 'table'. Generally, rows end up in the main tables + * that have a checkbox to allow the site admin to select which missing + * updates to install. This method is only used for the special case tables + * that have no such checkbox. + * + * @param array[] $row + * The render array for a table row. + */ + protected function removeCheckboxFromRow(array &$row) { + unset($row['#weight']); + $attributes = $row['#attributes']; + unset($row['#attributes']); + $row = [ + 'data' => $row, + ] + $attributes; + } + + /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { diff --git a/core/modules/update/tests/modules/update_test/bbb_update_test.1_1.xml b/core/modules/update/tests/modules/update_test/bbb_update_test.1_1.xml new file mode 100644 index 0000000..b06f173 --- /dev/null +++ b/core/modules/update/tests/modules/update_test/bbb_update_test.1_1.xml @@ -0,0 +1,39 @@ + + +BBB Update test +bbb_update_test +Drupal +8.x-1. +published +http://example.com/project/bbb_update_test + + ProjectsModules + + + + bbb_update_test 8.x-1.1 + 8.x-1.1 + 8.x-1.1 + published + http://example.com/bbb_update_test-8-x-1-1-release + http://example.com/bbb_update_test-8.x-1.1.tar.gz + 1250444521 + + Release typeBug fixes + + + + bbb_update_test 8.x-1.0 + 8.x-1.0 + 8.x-1.0 + published + http://example.com/bbb_update_test-7-x-1-0-release + http://example.com/bbb_update_test-8.x-1.0.tar.gz + 1250424521 + + Release typeNew features + Release typeBug fixes + + + + diff --git a/core/modules/update/tests/modules/update_test/bbb_update_test.1_2.xml b/core/modules/update/tests/modules/update_test/bbb_update_test.1_2.xml new file mode 100644 index 0000000..a8aa845 --- /dev/null +++ b/core/modules/update/tests/modules/update_test/bbb_update_test.1_2.xml @@ -0,0 +1,52 @@ + + +BBB Update test +bbb_update_test +Drupal +8.x-1. +published +http://example.com/project/bbb_update_test + + ProjectsModules + + + + bbb_update_test 8.x-1.2 + 8.x-1.2 + 8.x-1.2 + ^8.1.0 + published + http://example.com/bbb_update_test-8-x-1-2-release + http://example.com/bbb_update_test-8.x-1.2.tar.gz + 1250445521 + + Release typeBug fixes + + + + bbb_update_test 8.x-1.1 + 8.x-1.1 + 8.x-1.1 + published + http://example.com/bbb_update_test-8-x-1-1-release + http://example.com/bbb_update_test-8.x-1.1.tar.gz + 1250444521 + + Release typeBug fixes + + + + bbb_update_test 8.x-1.0 + 8.x-1.0 + 8.x-1.0 + published + http://example.com/bbb_update_test-7-x-1-0-release + http://example.com/bbb_update_test-8.x-1.0.tar.gz + 1250424521 + + Release typeNew features + Release typeBug fixes + + + + diff --git a/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php b/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php new file mode 100644 index 0000000..6d9cc03 --- /dev/null +++ b/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php @@ -0,0 +1,230 @@ +drupalCreateUser([ + 'administer modules', + 'administer software updates', + 'administer site configuration', + ]); + $this->drupalLogin($admin_user); + + // The installed state of the system is the same for all test cases. What + // varies for each test scenario is which release history fixture we fetch, + // which in turn changes the expected state of the UpdateManagerUpdateForm. + $system_info = [ + '#all' => [ + 'version' => '8.0.0', + ], + 'aaa_update_test' => [ + 'project' => 'aaa_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ], + 'bbb_update_test' => [ + 'project' => 'bbb_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ], + ]; + $this->config('update_test.settings')->set('system_info', $system_info)->save(); + } + + /** + * Provides data for test scenarios involving incompatible updates. + * + * These test cases rely on the following fixtures containing the following + * releases: + * - aaa_update_test.8.x-1.2.xml + * - 8.x-1.2 Compatible with 8.0.0 core. + * - aaa_update_test.cre_compatibility.8.x-1.2_8.x-2.2.xml + * - 8.x-1.2 Requires 8.1.0 and above. + * - bbb_update_test.1_0.xml + * - 8.x-1.0 is the only available release. + * - bbb_update_test.1_1.xml + * - 8.x-1.1 is available and compatible with everything. + * - bbb_update_test.1_2.xml + * - 8.x-1.1 is available and compatible with everything. + * - 8.x-1.2 is available and requires Drupal 8.1.0 and above. + * + * @todo In https://www.drupal.org/project/drupal/issues/3112962: + * Change the 'core_fixture' values here to use: + * - '1.1' instead of '1.1-core_compatibility'. + * - '1.1-alpha1' instead of '1.1-alpha1-core_compatibility'. + * Delete the files: + * - core/modules/update/tests/modules/update_test/drupal.1.1-alpha1-core_compatibility.xml + * - core/modules/update/tests/modules/update_test/drupal.1.1-core_compatibility.xml + * + * @return array[] + * Test data. + */ + public function incompatibleUpdatesTableProvider() { + return [ + 'only one compatible' => [ + 'core_fixture' => '1.1-core_compatibility', + 'a_fixture' => '8.x-1.2', + // Use a fixture with only a 8.x-1.0 release so BBB is up to date. + 'b_fixture' => '1_0', + 'compatible' => [ + 'AAA' => '8.x-1.2', + ], + 'incompatible' => [], + ], + 'only one incompatible' => [ + 'core_fixture' => '1.1-core_compatibility', + 'a_fixture' => 'core_compatibility.8.x-1.2_8.x-2.2', + // Use a fixture with only a 8.x-1.0 release so BBB is up to date. + 'b_fixture' => '1_0', + 'compatible' => [], + 'incompatible' => [ + 'AAA' => [ + 'recommended' => '8.x-1.2', + 'range' => '8.1.0 to 8.1.1', + ], + ], + ], + 'two compatible, no incompatible' => [ + 'core_fixture' => '1.1-core_compatibility', + 'a_fixture' => '8.x-1.2', + 'b_fixture' => '1_1', + 'compatible' => [ + 'AAA' => '8.x-1.2', + 'BBB' => '8.x-1.1', + ], + 'incompatible' => [], + ], + 'two incompatible, no compatible' => [ + 'core_fixture' => '1.1-core_compatibility', + 'a_fixture' => 'core_compatibility.8.x-1.2_8.x-2.2', + 'b_fixture' => '1_2', + 'compatible' => [], + 'incompatible' => [ + 'AAA' => [ + 'recommended' => '8.x-1.2', + 'range' => '8.1.0 to 8.1.1', + ], + 'BBB' => [ + 'recommended' => '8.x-1.2', + 'range' => '8.1.0 to 8.1.1', + ], + ], + ], + 'one compatible, one incompatible' => [ + 'core_fixture' => '1.1-core_compatibility', + 'a_fixture' => 'core_compatibility.8.x-1.2_8.x-2.2', + 'b_fixture' => '1_1', + 'compatible' => [ + 'BBB' => '8.x-1.1', + ], + 'incompatible' => [ + 'AAA' => [ + 'recommended' => '8.x-1.2', + 'range' => '8.1.0 to 8.1.1', + ], + ], + ], + ]; + } + + /** + * Tests the Update form for a single test scenario of incompatible updates. + * + * @param string $core_fixture + * The fixture file to use for Drupal core. + * @param string $a_fixture + * The fixture file to use for aaa_update_test module. + * @param string $b_fixture + * The fixture file to use for bbb_update_test module. + * @param string[] $compatible + * Compatible recommended updates (if any). Keys module identifier ('AAA' or + * 'BBB') and values are the expected recommended release. + * @param string[][] $incompatible + * Incompatible recommended updates (if any). Keys module identifier ('AAA' + * or 'BBB') and values are subarrays with the following keys: + * - 'recommended': The recommended version. + * - 'range': The versions of Drupal core required for that version. + * + * @dataProvider incompatibleUpdatesTableProvider + */ + public function testIncompatibleUpdatesTable($core_fixture, $a_fixture, $b_fixture, array $compatible, array $incompatible) { + + $assert_session = $this->assertSession(); + $compatible_table_locator = '[data-drupal-selector="edit-projects"]'; + $incompatible_table_locator = '[data-drupal-selector="edit-not-compatible"]'; + + $this->refreshUpdateStatus(['drupal' => $core_fixture, 'aaa_update_test' => $a_fixture, 'bbb_update_test' => $b_fixture]); + $this->drupalGet('admin/reports/updates/update'); + + if ($compatible) { + // Verify the number of rows in the table. + $assert_session->elementsCount('css', "$compatible_table_locator tbody tr", count($compatible)); + // We never want to see a compatibly range in the compatible table. + $assert_session->elementTextNotContains('css', $compatible_table_locator, 'Requires Drupal core'); + $i = 1; + foreach ($compatible as $module => $version) { + $compatible_row = "$compatible_table_locator tbody tr:nth-of-type($i)"; + // First is the checkbox, so start with td #2. + $assert_session->elementTextContains('css', "$compatible_row td:nth-of-type(2)", "$module Update test"); + // Both contrib modules use 8.x-1.0 as the currently-installed version. + $assert_session->elementTextContains('css', "$compatible_row td:nth-of-type(3)", '8.x-1.0'); + $assert_session->elementTextContains('css', "$compatible_row td:nth-of-type(4)", $version); + $i++; + } + } + else { + // Verify there is no compatible updates table. + $assert_session->elementNotExists('css', $compatible_table_locator); + } + + if ($incompatible) { + // Verify the number of rows in the table. + $assert_session->elementsCount('css', "$incompatible_table_locator tbody tr", count($incompatible)); + $i = 1; + foreach ($incompatible as $module => $data) { + $incompatible_row = "$incompatible_table_locator tbody tr:nth-of-type($i)"; + $assert_session->elementTextContains('css', "$incompatible_row td:nth-of-type(1)", "$module Update test"); + // Both contrib modules use 8.x-1.0 as the currently-installed version. + $assert_session->elementTextContains('css', "$incompatible_row td:nth-of-type(2)", '8.x-1.0'); + $assert_session->elementTextContains('css', "$incompatible_row td:nth-of-type(3)", $data['recommended']); + $assert_session->elementTextContains('css', "$incompatible_row td:nth-of-type(3)", 'Requires Drupal core: ' . $data['range']); + $i++; + } + } + else { + // Verify there is no incompatible updates table. + $assert_session->elementNotExists('css', $incompatible_table_locator); + } + } + +}