diff --git a/core/modules/node/src/Tests/NodeAccessBaseTableTest.php b/core/modules/node/src/Tests/NodeAccessBaseTableTest.php index 91547c4..f0e01dd 100644 --- a/core/modules/node/src/Tests/NodeAccessBaseTableTest.php +++ b/core/modules/node/src/Tests/NodeAccessBaseTableTest.php @@ -99,7 +99,7 @@ function testNodeAccessBasic() { // Array of nids marked private. $private_nodes = []; for ($i = 0; $i < $num_simple_users; $i++) { - $simple_users[$i] = $this->drupalCreateUser(array('access content', 'create article content')); + $simple_users[$i] = $this->drupalCreateUser(array('access content', 'create article content', 'create terms in tags')); } foreach ($simple_users as $this->webUser) { $this->drupalLogin($this->webUser); diff --git a/core/modules/taxonomy/src/Access/TaxonomyOverviewAccessCheck.php b/core/modules/taxonomy/src/Access/TaxonomyOverviewAccessCheck.php new file mode 100644 index 0000000..d529aa5 --- /dev/null +++ b/core/modules/taxonomy/src/Access/TaxonomyOverviewAccessCheck.php @@ -0,0 +1,28 @@ +hasPermission('administer taxonomy') || $account->hasPermission('access taxonomy overview'); + return AccessResult::allowedIf($access); + } + +} diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php index 556e79b..ab98b0b 100644 --- a/core/modules/taxonomy/src/Form/OverviewTerms.php +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php @@ -27,6 +27,13 @@ class OverviewTerms extends FormBase { protected $moduleHandler; /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** * The term storage controller. * * @var \Drupal\taxonomy\TermStorageInterface @@ -43,6 +50,7 @@ class OverviewTerms extends FormBase { */ public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager) { $this->moduleHandler = $module_handler; + $this->entityManager = $entity_manager; $this->storageController = $entity_manager->getStorage('taxonomy_term'); } @@ -204,14 +212,23 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular $destination = $this->getDestinationArray(); $row_position = 0; // Build the actual form. + $access_control_handler = $this->entityManager->getAccessControlHandler('taxonomy_term'); + if ($access_control_handler->createAccess($taxonomy_vocabulary->id())) { + $empty = $this->t('No terms available. Add term.', ['@link' => $this->url('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $taxonomy_vocabulary->id()])]); + } + else { + $empty = $this->t('No terms available.'); + } $form['terms'] = array( '#type' => 'table', '#header' => array($this->t('Name'), $this->t('Weight'), $this->t('Operations')), - '#empty' => $this->t('No terms available. Add term.', array('@link' => $this->url('entity.taxonomy_term.add_form', array('taxonomy_vocabulary' => $taxonomy_vocabulary->id())))), + '#empty' => $empty, '#attributes' => array( 'id' => 'taxonomy', ), ); + $change_weight_access = TRUE; + $operations_access = TRUE; foreach ($current_page as $key => $term) { /** @var $term \Drupal\Core\Entity\EntityInterface */ $form['terms'][$key]['#term'] = $term; @@ -256,28 +273,36 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular ), ); } - $form['terms'][$key]['weight'] = array( - '#type' => 'weight', - '#delta' => $delta, - '#title' => $this->t('Weight for added term'), - '#title_display' => 'invisible', - '#default_value' => $term->getWeight(), - '#attributes' => array( - 'class' => array('term-weight'), - ), - ); - $operations = array( - 'edit' => array( + $edit_access = $term->access('update'); + $change_weight_access &= $edit_access; + + if ($edit_access) { + $form['terms'][$key]['weight'] = [ + '#type' => 'weight', + '#delta' => $delta, + '#title' => $this->t('Weight for added term'), + '#title_display' => 'invisible', + '#default_value' => $term->getWeight(), + '#attributes' => ['class' => ['term-weight']], + '#access' => $edit_access, + ]; + } + $operations = array(); + + if ($edit_access) { + $operations['edit'] = [ 'title' => $this->t('Edit'), 'query' => $destination, 'url' => $term->urlInfo('edit-form'), - ), - 'delete' => array( + ]; + } + if ($term->access('delete')) { + $operations['delete'] = [ 'title' => $this->t('Delete'), 'query' => $destination, 'url' => $term->urlInfo('delete-form'), - ), - ); + ]; + } if ($this->moduleHandler->moduleExists('content_translation') && content_translation_translate_access($term)->isAllowed()) { $operations['translate'] = array( 'title' => $this->t('Translate'), @@ -285,10 +310,14 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular 'url' => $term->urlInfo('drupal:content-translation-overview'), ); } - $form['terms'][$key]['operations'] = array( - '#type' => 'operations', - '#links' => $operations, - ); + + $operations_access &= count($operations) > 0; + if (count($operations)) { + $form['terms'][$key]['operations'] = array( + '#type' => 'operations', + '#links' => $operations, + ); + } $form['terms'][$key]['#attributes']['class'] = array(); if ($parent_fields) { @@ -318,32 +347,40 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular $row_position++; } - if ($parent_fields) { - $form['terms']['#tabledrag'][] = array( - 'action' => 'match', - 'relationship' => 'parent', - 'group' => 'term-parent', - 'subgroup' => 'term-parent', - 'source' => 'term-id', - 'hidden' => FALSE, - ); - $form['terms']['#tabledrag'][] = array( - 'action' => 'depth', - 'relationship' => 'group', - 'group' => 'term-depth', - 'hidden' => FALSE, - ); - $form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy'; - $form['terms']['#attached']['drupalSettings']['taxonomy'] = [ - 'backStep' => $back_step, - 'forwardStep' => $forward_step, + $form['terms']['#header'] = array($this->t('Name')); + if ($change_weight_access) { + $form['terms']['#header'][] = $this->t('Weight'); + if ($parent_fields) { + $form['terms']['#tabledrag'][] = [ + 'action' => 'match', + 'relationship' => 'parent', + 'group' => 'term-parent', + 'subgroup' => 'term-parent', + 'source' => 'term-id', + 'hidden' => FALSE, + ]; + $form['terms']['#tabledrag'][] = [ + 'action' => 'depth', + 'relationship' => 'group', + 'group' => 'term-depth', + 'hidden' => FALSE, + ]; + $form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy'; + $form['terms']['#attached']['drupalSettings']['taxonomy'] = [ + 'backStep' => $back_step, + 'forwardStep' => $forward_step, + ]; + } + $form['terms']['#tabledrag'][] = [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'term-weight', ]; } - $form['terms']['#tabledrag'][] = array( - 'action' => 'order', - 'relationship' => 'sibling', - 'group' => 'term-weight', - ); + + if ($operations_access) { + $form['terms']['#header'][] = $this->t('Operations'); + } if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) { $form['actions'] = array('#type' => 'actions', '#tree' => FALSE); @@ -351,11 +388,13 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular '#type' => 'submit', '#value' => $this->t('Save'), '#button_type' => 'primary', + '#access' => $change_weight_access, ); $form['actions']['reset_alphabetical'] = array( '#type' => 'submit', '#submit' => array('::submitReset'), '#value' => $this->t('Reset to alphabetical'), + '#access' => $change_weight_access, ); } diff --git a/core/modules/taxonomy/src/TaxonomyPermissions.php b/core/modules/taxonomy/src/TaxonomyPermissions.php index 6b7b841..24dc3dc 100644 --- a/core/modules/taxonomy/src/TaxonomyPermissions.php +++ b/core/modules/taxonomy/src/TaxonomyPermissions.php @@ -10,6 +10,7 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\taxonomy\Entity\Vocabulary; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -53,19 +54,30 @@ public static function create(ContainerInterface $container) { */ public function permissions() { $permissions = []; - foreach ($this->entityManager->getStorage('taxonomy_vocabulary')->loadMultiple() as $vocabulary) { - $permissions += [ - 'edit terms in ' . $vocabulary->id() => [ - 'title' => $this->t('Edit terms in %vocabulary', ['%vocabulary' => $vocabulary->label()]), - ], - ]; - $permissions += [ - 'delete terms in ' . $vocabulary->id() => [ - 'title' => $this->t('Delete terms from %vocabulary', ['%vocabulary' => $vocabulary->label()]), - ], - ]; + foreach (Vocabulary::loadMultiple() as $vocabulary) { + $permissions += $this->buildPermissions($vocabulary); } return $permissions; } + /** + * Builds a standard list of taxonomy term permissions for a given vocabulary. + * + * @param \Drupal\taxonomy\Entity\Vocabulary + * The vocabulary. + * + * @return array + * An array of permission names and descriptions. + */ + protected function buildPermissions(Vocabulary $vocabulary) { + $id = $vocabulary->id(); + $args = ['%vocabulary' => $vocabulary->label()]; + + return [ + "create terms in $id" => ['title' => $this->t('%vocabulary: Create terms', $args)], + "delete terms in $id" => ['title' => $this->t('%vocabulary: Delete terms', $args)], + "edit terms in $id" => ['title' => $this->t('%vocabulary: Edit terms', $args)], + ]; + } + } diff --git a/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php b/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php index 00df789..1827cb0 100644 --- a/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php +++ b/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php @@ -15,6 +15,33 @@ class VocabularyPermissionsTest extends TaxonomyTestBase { /** + * Create, edit and delete a vocabulary via the user interface. + */ + function testVocabularyPermissionsVocabulary() { + // VocabularyTest.php already tests for user with "administer taxonomy" permissions. + + // Test as user without proper permissions. + $authenticated_user = $this->drupalCreateUser(array()); + $this->drupalLogin($authenticated_user); + + // Visit the main taxonomy administration page. + $this->drupalGet('admin/structure/taxonomy'); + $this->assertResponse(403, 'Vocabulary overview open failed.'); + + // Test as user with "access taxonomy overview" permissions. + $proper_user = $this->drupalCreateUser(array('access taxonomy overview')); + $this->drupalLogin($proper_user); + + // Visit the main taxonomy administration page. + $this->drupalGet('admin/structure/taxonomy'); + $this->assertResponse(200); + $this->assertText(t('Vocabulary name'), 'Vocabulary overview opened successfully.'); + + // Create a new vocabulary. + $this->assertNoLink(t('Add vocabulary')); + } + + /** * Create, edit and delete a taxonomy term via the user interface. */ function testVocabularyPermissionsTaxonomyTerm() { @@ -57,8 +84,35 @@ function testVocabularyPermissionsTaxonomyTerm() { $this->drupalPostForm(NULL, NULL, t('Delete')); $this->assertRaw(t('Deleted term %name.', array('%name' => $edit['name[0][value]'])), 'Term deleted.'); + // Test as user with "create" permissions. + $user = $this->drupalCreateUser(array("access taxonomy overview", "create terms in {$vocabulary->id()}")); + $this->drupalLogin($user); + + // Visit the main taxonomy administration page. + $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add'); + $this->assertResponse(200); + $this->assertField('edit-name', 'Add taxonomy term form opened successfully.'); + + // Submit the term. + $edit = array(); + $edit['name'] = $this->randomName(); + + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t('Created new term %name.', array('%name' => $edit['name'])), 'Term created successfully.'); + + $terms = taxonomy_term_load_multiple_by_name($edit['name']); + $term = reset($terms); + + // Edit the term. + $this->drupalGet('taxonomy/term/' . $term->id() . '/edit'); + $this->assertResponse(403, 'Edit taxonomy term form open failed.'); + + // Delete the vocabulary. + $this->drupalGet('taxonomy/term/' . $term->id() . '/delete'); + $this->assertResponse(403, 'Delete taxonomy term form open failed.'); + // Test as user with "edit" permissions. - $user = $this->drupalCreateUser(array("edit terms in {$vocabulary->id()}")); + $user = $this->drupalCreateUser(array("access taxonomy overview", "edit terms in {$vocabulary->id()}")); $this->drupalLogin($user); // Visit the main taxonomy administration page. @@ -82,7 +136,7 @@ function testVocabularyPermissionsTaxonomyTerm() { $this->assertResponse(403, 'Delete taxonomy term form open failed.'); // Test as user with "delete" permissions. - $user = $this->drupalCreateUser(array("delete terms in {$vocabulary->id()}")); + $user = $this->drupalCreateUser(array("access taxonomy overview", "delete terms in {$vocabulary->id()}")); $this->drupalLogin($user); // Visit the main taxonomy administration page. diff --git a/core/modules/taxonomy/src/VocabularyListBuilder.php b/core/modules/taxonomy/src/VocabularyListBuilder.php index 88f2594..4fe87c5 100644 --- a/core/modules/taxonomy/src/VocabularyListBuilder.php +++ b/core/modules/taxonomy/src/VocabularyListBuilder.php @@ -9,8 +9,13 @@ use Drupal\Core\Config\Entity\DraggableListBuilder; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines a class to build a listing of taxonomy vocabulary entities. @@ -25,6 +30,51 @@ class VocabularyListBuilder extends DraggableListBuilder { protected $entitiesKey = 'vocabularies'; /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * Constructs a new VocabularyListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager service. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + */ + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, AccountInterface $current_user, EntityManagerInterface $entity_manager) { + parent::__construct($entity_type, $storage); + + $this->current_user = $current_user; + $this->entityManager = $entity_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('current_user'), + $container->get('entity.manager') + ); + } + + /** * {@inheritdoc} */ public function getFormId() { @@ -46,11 +96,14 @@ public function getDefaultOperations(EntityInterface $entity) { 'weight' => 0, 'url' => $entity->urlInfo('overview-form'), ); - $operations['add'] = array( - 'title' => t('Add terms'), - 'weight' => 10, - 'url' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $entity->id()]), - ); + $taxonomy_term_access_control_handler = $this->entityManager->getAccessControlHandler('taxonomy_term'); + if ($taxonomy_term_access_control_handler->createAccess($entity->bundle())) { + $operations['add'] = array( + 'title' => t('Add terms'), + 'weight' => 10, + 'url' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $entity->id()]), + ); + } unset($operations['delete']); return $operations; @@ -61,6 +114,11 @@ public function getDefaultOperations(EntityInterface $entity) { */ public function buildHeader() { $header['label'] = t('Vocabulary name'); + + if ($this->current_user->hasPermission('administer vocabularies')) { + $header['weight'] = t('Weight'); + } + return $header + parent::buildHeader(); } @@ -83,7 +141,14 @@ public function render() { unset($this->weightKey); } $build = parent::render(); - $build['table']['#empty'] = t('No vocabularies available. Add vocabulary.', array('@link' => \Drupal::url('entity.taxonomy_vocabulary.add_form'))); + + if ($this->current_user->hasPermission('administer vocabularies')) { + $build['table']['#empty'] = t('No vocabularies available. Add vocabulary.', array('@link' => \Drupal::url('entity.taxonomy_vocabulary.add_form'))); + } + else { + $build['table']['#empty'] = t('No vocabularies available.'); + } + return $build; } diff --git a/core/modules/taxonomy/taxonomy.permissions.yml b/core/modules/taxonomy/taxonomy.permissions.yml index d485949..bb71e93 100644 --- a/core/modules/taxonomy/taxonomy.permissions.yml +++ b/core/modules/taxonomy/taxonomy.permissions.yml @@ -1,5 +1,9 @@ administer taxonomy: title: 'Administer vocabularies and terms' +access taxonomy overview: + title: 'Access the taxonomy vocabulary overview page' + description: 'Get an overview of all taxonomy vocabularies.' + permission_callbacks: - Drupal\taxonomy\TaxonomyPermissions::permissions diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml index a7a75f3..bd00f33 100644 --- a/core/modules/taxonomy/taxonomy.routing.yml +++ b/core/modules/taxonomy/taxonomy.routing.yml @@ -4,7 +4,7 @@ entity.taxonomy_vocabulary.collection: _entity_list: 'taxonomy_vocabulary' _title: 'Taxonomy' requirements: - _permission: 'administer taxonomy' + _access_taxonomy_overview: 'TRUE' entity.taxonomy_term.add_form: path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add' diff --git a/core/modules/taxonomy/taxonomy.services.yml b/core/modules/taxonomy/taxonomy.services.yml index a8153ed..b521c7f 100644 --- a/core/modules/taxonomy/taxonomy.services.yml +++ b/core/modules/taxonomy/taxonomy.services.yml @@ -4,3 +4,7 @@ services: arguments: ['@entity.manager'] tags: - { name: breadcrumb_builder, priority: 1002 } + access_check.taxonomy.taxonomy_overview: + class: Drupal\taxonomy\Access\TaxonomyOverviewAccessCheck + tags: + - { name: access_check, applies_to: _access_taxonomy_overview } diff --git a/core/modules/views/src/Tests/Wizard/WizardTestBase.php b/core/modules/views/src/Tests/Wizard/WizardTestBase.php index 7d8d9c0..7dd2aca 100644 --- a/core/modules/views/src/Tests/Wizard/WizardTestBase.php +++ b/core/modules/views/src/Tests/Wizard/WizardTestBase.php @@ -15,6 +15,13 @@ abstract class WizardTestBase extends ViewTestBase { /** + * Permissions for this user to test with. + * + * @var array + */ + protected $permissions = array(); + + /** * Modules to enable. * * @var array @@ -24,8 +31,10 @@ protected function setUp() { parent::setUp(); + $this->permissions = array_merge($this->permissions, array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view all revisions')); + // Create and log in a user with administer views permission. - $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view all revisions')); + $views_admin = $this->drupalCreateUser($this->permissions); $this->drupalLogin($views_admin); $this->drupalPlaceBlock('local_actions_block'); }