diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 92037d98d6..51472cf8c5 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -7,14 +7,11 @@ use Drupal\comment\CommentInterface; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; -use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\taxonomy\TermInterface; use Drupal\taxonomy\VocabularyInterface; use Drupal\user\Entity\User; @@ -352,22 +349,6 @@ function forum_form_node_form_alter(&$form, FormStateInterface $form_state, $for } /** - * Implements hook_ENTITY_TYPE_access() for taxonomy_term. - */ -function forum_taxonomy_term_access(TermInterface $term, $operation, AccountInterface $account) { - // Allow update access to terms in the forum vocabulary with the - // administer forums permission. - $forum_vid = \Drupal::config('forum.settings')->get('vocabulary'); - if ($operation === 'update' && $term->bundle() === $forum_vid) { - return AccessResult::allowedIfHasPermission($account, 'administer forums'); - } - - // No opinion. - return AccessResult::neutral(); -} - - -/** * Implements hook_preprocess_HOOK() for block templates. */ function forum_preprocess_block(&$variables) { diff --git a/core/modules/forum/src/Form/Overview.php b/core/modules/forum/src/Form/Overview.php index 8c374b5842..c581802998 100644 --- a/core/modules/forum/src/Form/Overview.php +++ b/core/modules/forum/src/Form/Overview.php @@ -56,29 +56,37 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Build base taxonomy term overview. $form = parent::buildForm($form, $form_state, $vocabulary); + // Ensure there is always an operations column. + if (count($form['terms']['#header']) === 1) { + $form['terms']['#header'][] = $this->t('Operations'); + } + foreach (Element::children($form['terms']) as $key) { if (isset($form['terms'][$key]['#term'])) { /** @var \Drupal\taxonomy\TermInterface $term */ $term = $form['terms'][$key]['#term']; $form['terms'][$key]['term']['#url'] = Url::fromRoute('forum.page', ['taxonomy_term' => $term->id()]); - if ($term->access('delete')) { - unset($form['terms'][$key]['operations']['#links']['delete']); - } - if ($term->access('update')) { - $route_parameters = $form['terms'][$key]['operations']['#links']['edit']['url']->getRouteParameters(); - if (!empty($term->forum_container->value)) { - $form['terms'][$key]['operations']['#links']['edit']['title'] = $this->t('edit container'); - $form['terms'][$key]['operations']['#links']['edit']['url'] = Url::fromRoute('entity.taxonomy_term.forum_edit_container_form', $route_parameters); - } - else { - $form['terms'][$key]['operations']['#links']['edit']['title'] = $this->t('edit forum'); - $form['terms'][$key]['operations']['#links']['edit']['url'] = Url::fromRoute('entity.taxonomy_term.forum_edit_form', $route_parameters); - } - // We don't want the redirect from the link so we can redirect the - // delete action. - unset($form['terms'][$key]['operations']['#links']['edit']['query']['destination']); + if (!empty($term->forum_container->value)) { + $title = $this->t('edit container'); + $url = Url::fromRoute('entity.taxonomy_term.forum_edit_container_form', ['taxonomy_term' => $term->id()]); + } + else { + $title = $this->t('edit forum'); + $url = Url::fromRoute('entity.taxonomy_term.forum_edit_form', ['taxonomy_term' => $term->id()]); } + + // Re-create the operations column and add only the edit link. + $form['terms'][$key]['operations'] = [ + '#type' => 'operations', + '#links' => [ + 'edit' => [ + 'title' => $title, + 'url' => $url, + ] + ], + ]; + } } diff --git a/core/modules/forum/tests/src/Functional/ForumIndexTest.php b/core/modules/forum/tests/src/Functional/ForumIndexTest.php index 4b048e2647..e3d904d368 100644 --- a/core/modules/forum/tests/src/Functional/ForumIndexTest.php +++ b/core/modules/forum/tests/src/Functional/ForumIndexTest.php @@ -57,7 +57,7 @@ public function testForumIndexStatus() { 'parent[0]' => $tid, ]; $this->drupalPostForm('admin/structure/forum/add/forum', $edit, t('Save')); - $this->assertSession()->linkExists(t('edit forum'), 0, 'edit forum link is not accessible to user with administer forums permission'); + $this->assertSession()->linkExists(t('edit forum')); $tid_child = $tid + 1; diff --git a/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php b/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php index ea0dde664f..5b99ccf68e 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php @@ -94,7 +94,7 @@ public function testNodeAccessBasic() { // Array of nids marked private. $private_nodes = []; for ($i = 0; $i < $num_simple_users; $i++) { - $simple_users[$i] = $this->drupalCreateUser(['access content', 'create article content', 'create terms in tags']); + $simple_users[$i] = $this->drupalCreateUser(['access content', 'create article content']); } foreach ($simple_users as $this->webUser) { $this->drupalLogin($this->webUser); diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php index a986c683c8..2ac0f1f5ed 100644 --- a/core/modules/taxonomy/src/Form/OverviewTerms.php +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php @@ -229,6 +229,10 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular 'id' => 'taxonomy', ], ]; + + // Only allow access to changing weights if the user has update access for + // all terms, show operations if the user is allowed to see any operation + // for at least one term. $change_weight_access = TRUE; $operations_access = FALSE; foreach ($current_page as $key => $term) { diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index eb887274a7..c165855c28 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -75,25 +75,26 @@ function taxonomy_help($route_name, RouteMatchInterface $route_match) { case 'entity.taxonomy_vocabulary.overview_form': $vocabulary = $route_match->getParameter('taxonomy_vocabulary'); - if (!\Drupal::currentUser()->hasPermission('administer taxonomy')) { - // There is no drag-and-drop handles. + if (\Drupal::currentUser()->hasPermission('administer taxonomy') || \Drupal::currentUser()->hasPermission('edit terms in ' . $vocabulary->id())) { switch ($vocabulary->getHierarchy()) { - case TAXONOMY_HIERARCHY_DISABLED: + case VocabularyInterface::HIERARCHY_DISABLED: + return '

' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '

'; + case VocabularyInterface::HIERARCHY_SINGLE: + return '

' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '

'; + case VocabularyInterface::HIERARCHY_MULTIPLE: + return '

' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '

'; + } + } + else { + switch ($vocabulary->getHierarchy()) { + case VocabularyInterface::HIERARCHY_DISABLED: return '

' . t('%capital_name contains the following terms.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '

'; - case TAXONOMY_HIERARCHY_SINGLE: + case VocabularyInterface::HIERARCHY_SINGLE: return '

' . t('%capital_name contains terms grouped under parent terms', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '

'; - case TAXONOMY_HIERARCHY_MULTIPLE: + case VocabularyInterface::HIERARCHY_MULTIPLE: return '

' . t('%capital_name contains terms with multiple parents.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '

'; } } - switch ($vocabulary->getHierarchy()) { - case VocabularyInterface::HIERARCHY_DISABLED: - return '

' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '

'; - case VocabularyInterface::HIERARCHY_SINGLE: - return '

' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '

'; - case VocabularyInterface::HIERARCHY_MULTIPLE: - return '

' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '

'; - } } } diff --git a/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php b/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php index 1cb7dc9a2f..5f0d545edd 100644 --- a/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php +++ b/core/modules/taxonomy/tests/src/Functional/VocabularyPermissionsTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\taxonomy\Functional; +use Drupal\Component\Utility\Unicode; + /** * Tests the taxonomy vocabulary permissions. * @@ -9,10 +11,19 @@ */ class VocabularyPermissionsTest extends TaxonomyTestBase { + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['help']; + protected function setUp() { parent::setUp(); $this->drupalPlaceBlock('page_title_block'); + $this->drupalPlaceBlock('local_actions_block'); + $this->drupalPlaceBlock('help_block'); } /** @@ -26,9 +37,11 @@ public function testVocabularyPermissionsVocabulary() { $authenticated_user = $this->drupalCreateUser([]); $this->drupalLogin($authenticated_user); + $assert_session = $this->assertSession(); + // Visit the main taxonomy administration page. $this->drupalGet('admin/structure/taxonomy'); - $this->assertResponse(403, 'Vocabulary overview open failed.'); + $assert_session->statusCodeEquals(403); // Test as user with "access taxonomy overview" permissions. $proper_user = $this->drupalCreateUser(['access taxonomy overview']); @@ -36,11 +49,162 @@ public function testVocabularyPermissionsVocabulary() { // Visit the main taxonomy administration page. $this->drupalGet('admin/structure/taxonomy'); - $this->assertResponse(200); - $this->assertText(t('Vocabulary name'), 'Vocabulary overview opened successfully.'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('Vocabulary name'); + $assert_session->linkNotExists('Add vocabulary'); + } - // Create a new vocabulary. - $this->assertNoLink(t('Add vocabulary')); + /** + * Test the vocabulary overview permission. + */ + function testTaxonomyVocabularyOverviewPermissions() { + // Create two vocabularies, one with two terms, the other without any term. + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary1 , $vocabulary2 */ + $vocabulary1 = $this->createVocabulary(); + $vocabulary2 = $this->createVocabulary(); + $vocabulary1_id = $vocabulary1->id(); + $vocabulary2_id = $vocabulary2->id(); + $this->createTerm($vocabulary1); + $this->createTerm($vocabulary1); + + // Assert expected help texts on first vocabulary. + $edit_help_text = t('You can reorganize the terms in @capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', ['@capital_name' => Unicode::ucfirst($vocabulary1->label())]); + $no_edit_help_text = t('@capital_name contains the following terms.', ['@capital_name' => Unicode::ucfirst($vocabulary1->label())]); + + $assert_session = $this->assertSession(); + + // Logged in as admin user with 'administer taxonomy' permission. + $admin_user = $this->drupalCreateUser(['administer taxonomy']); + $this->drupalLogin($admin_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->linkExists('Edit'); + $assert_session->linkExists('Delete'); + $assert_session->linkExists('Add term'); + $assert_session->buttonExists('Save'); + $assert_session->pageTextContains($edit_help_text); + + // Visit vocabulary overview without terms. 'Add term' should be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('No terms available'); + $assert_session->linkExists('Add term'); + + // Login as a user without any of the required permissions. + $no_permission_user = $this->drupalCreateUser(); + $this->drupalLogin($no_permission_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(403); + + // Visit vocabulary overview without terms. 'Add term' should not be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(403); + + // Log in as a user with only the overview permission, neither edit nor + // delete operations must be available and no Save button. + $overview_only_user = $this->drupalCreateUser(['access taxonomy overview']); + $this->drupalLogin($overview_only_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->linkNotExists('Edit'); + $assert_session->linkNotExists('Delete'); + $assert_session->buttonNotExists('Save'); + $assert_session->linkNotExists('Add term'); + $assert_session->pageTextContains($no_edit_help_text); + + // Visit vocabulary overview without terms. 'Add term' should not be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('No terms available'); + $assert_session->linkNotExists('Add term'); + + // Login as a user with permission to edit terms, only edit link should be + // visible. + $edit_user = $this->createUser([ + 'access taxonomy overview', + 'edit terms in ' . $vocabulary1_id, + 'edit terms in ' . $vocabulary2_id, + ]); + $this->drupalLogin($edit_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->linkExists('Edit'); + $assert_session->linkNotExists('Delete'); + $assert_session->buttonExists('Save'); + $assert_session->linkNotExists('Add term'); + $assert_session->pageTextContains($edit_help_text); + + // Visit vocabulary overview without terms. 'Add term' should not be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('No terms available'); + $assert_session->linkNotExists('Add term'); + + // Login as a user with permission only to delete terms. + $edit_delete_user = $this->createUser([ + 'access taxonomy overview', + 'delete terms in ' . $vocabulary1_id, + 'delete terms in ' . $vocabulary2_id, + ]); + $this->drupalLogin($edit_delete_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->linkNotExists('Edit'); + $assert_session->linkExists('Delete'); + $assert_session->linkNotExists('Add term'); + $assert_session->buttonNotExists('Save'); + $assert_session->pageTextContains($no_edit_help_text); + + // Visit vocabulary overview without terms. 'Add term' should not be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('No terms available'); + $assert_session->linkNotExists('Add term'); + + // Login as a user with permission to edit and delete terms. + $edit_delete_user = $this->createUser([ + 'access taxonomy overview', + 'edit terms in ' . $vocabulary1_id, + 'delete terms in ' . $vocabulary1_id, + 'edit terms in ' . $vocabulary2_id, + 'delete terms in ' . $vocabulary2_id, + ]); + $this->drupalLogin($edit_delete_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->linkExists('Edit'); + $assert_session->linkExists('Delete'); + $assert_session->linkNotExists('Add term'); + $assert_session->buttonExists('Save'); + $assert_session->pageTextContains($edit_help_text); + + // Visit vocabulary overview without terms. 'Add term' should not be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('No terms available'); + $assert_session->linkNotExists('Add term'); + + // Login as a user with permission to create new terms, only add new term + // link should be visible. + $edit_user = $this->createUser([ + 'access taxonomy overview', + 'create terms in ' . $vocabulary1_id, + 'create terms in ' . $vocabulary2_id, + ]); + $this->drupalLogin($edit_user); + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->linkNotExists('Edit'); + $assert_session->linkNotExists('Delete'); + $assert_session->linkExists('Add term'); + $assert_session->buttonNotExists('Save'); + $assert_session->pageTextContains($no_edit_help_text); + + // Visit vocabulary overview without terms. 'Add term' should not be shown. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview'); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('No terms available'); + $assert_session->linkExists('Add term'); } /** @@ -91,34 +255,34 @@ public function testVocabularyPermissionsTaxonomyTerm() { $this->assertRaw(t('Deleted term %name.', ['%name' => $edit['name[0][value]']]), 'Term deleted.'); // Test as user with "create" permissions. - $user = $this->drupalCreateUser(["access taxonomy overview", "create terms in {$vocabulary->id()}"]); + $user = $this->drupalCreateUser(["create terms in {$vocabulary->id()}"]); $this->drupalLogin($user); - // Visit the main taxonomy administration page. + $assert_session = $this->assertSession(); + + // Create a new term. $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add'); - $this->assertResponse(200); - $this->assertFieldByName('name[0][value]', NULL, 'Add taxonomy term form opened successfully.'); + $assert_session->statusCodeEquals(200); + $assert_session->fieldExists('name[0][value]'); // Submit the term. $edit = []; $edit['name[0][value]'] = $this->randomMachineName(); $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertText(t('Created new term @name.', ['@name' => $edit['name[0][value]']]), 'Term created successfully.'); + $assert_session->pageTextContains(t('Created new term @name.', ['@name' => $edit['name[0][value]']])); $terms = taxonomy_term_load_multiple_by_name($edit['name[0][value]']); $term = reset($terms); - // Edit the term. + // Ensure that edit and delete access is denied. $this->drupalGet('taxonomy/term/' . $term->id() . '/edit'); - $this->assertResponse(403, 'Edit taxonomy term form open failed.'); - - // Delete the vocabulary. + $assert_session->statusCodeEquals(403); $this->drupalGet('taxonomy/term/' . $term->id() . '/delete'); - $this->assertResponse(403, 'Delete taxonomy term form open failed.'); + $assert_session->statusCodeEquals(403); // Test as user with "edit" permissions. - $user = $this->drupalCreateUser(["access taxonomy overview", "edit terms in {$vocabulary->id()}"]); + $user = $this->drupalCreateUser(["edit terms in {$vocabulary->id()}"]); $this->drupalLogin($user); // Visit the main taxonomy administration page. @@ -146,7 +310,7 @@ public function testVocabularyPermissionsTaxonomyTerm() { $this->assertResponse(403, 'Delete taxonomy term form open failed.'); // Test as user with "delete" permissions. - $user = $this->drupalCreateUser(["access taxonomy overview", "delete terms in {$vocabulary->id()}"]); + $user = $this->drupalCreateUser(["delete terms in {$vocabulary->id()}"]); $this->drupalLogin($user); // Visit the main taxonomy administration page.