In recent conversion efforts of Custom Publishing Options and Taxonomy Views Integrator, I had to replicate the permission assignments from Drupal 7 to Drupal 8. hook_permission() has been removed from Drupal 8. Instead, you have to create a mymodule.permissions.yml and define them there, like so (from node.permissions.yml):
administer nodes:
title: 'Administer content'
description: 'Promote, change ownership, edit revisions, and perform other tasks across all content types.'
restrict access: true
access content overview:
title: 'Access the Content overview page'
description: 'Get an overview of all content.'
access content:
title: 'View published content'
view own unpublished content:
title: 'View own unpublished content'
That’s great - but how can you define permissions dynamically that depend on say, config entities in the system?
Permission Callbacks
Turns out we can define permissions_callback in mymodule.permissions.yml, and point it to a controller to return an array of permissions, similar to hook_permission() from Drupal 7. I do that for both modules mentioned above. In Taxonomy Views Integrator’s case, I need a permission set per term and vocabulary, as the module allows added configuration for each:
permission_callbacks:
- Drupal\tvi\TaxonomyViewsIntegratorPermissions::permissions
Drupal will now look to this class and method for permissions. The next part may look a little scary from Drupal 7, but it’s actually pretty straightforward:
/**
* @file
* Contains \Drupal\tvi\TaxonomyViewsIntegratorPermissions.
*/
namespace Drupal\tvi;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TaxonomyViewsIntegratorPermissions implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a TaxonomyViewsIntegratorPermissions instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager'));
}
/**
* Get permissions for Taxonomy Views Integrator.
*
* @return array
* Permissions array.
*/
public function permissions() {
$permissions = [];
foreach ($this->entityManager->getStorage('taxonomy_vocabulary')->loadMultiple() as $vocabulary) {
$permissions += [
'define view for vocabulary ' . $vocabulary->id() => [
'title' => $this->t('Define the view override for the vocabulary %vocabulary', array('%vocabulary' => $vocabulary->label())),
]
];
$permissions += [
'define view for terms in ' . $vocabulary->id() => [
'title' => $this->t('Define the view override for terms in %vocabulary', array('%vocabulary' => $vocabulary->label())),
]
];
}
return $permissions;
}
}
Using dependency injection, our class is constructed with the EntityManager service from the container. This provides access to call for all sorts of information - in this case we are looping all taxonomy_vocabulary entities in the system to create a permission set for them.
With that set, we can now add access checks where we need them, for instance, before altering a taxonomy form, I check the current user has permission:
/**
* Implements hook_form_BASE_FORM_ID_alter().
* @param $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
* @param $form_id
*/
function tvi_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$entity = $form_state->getBuildInfo()['callback_object']->getEntity();
if ($entity->id() !== null && Drupal::currentUser()->hasPermission('define view for vocabulary ' . $entity->id())) {
_tvi_settings_form($form, $form_state, $entity);
}
}
Nice.
Transitioning away from most of the common or favored hooks in Drupal 7 can be bumpy at first, but the more you work with Drupal 8, the more you realize why the changes were necessary to scale up, and have a more modular system.
The same permission check above can be applied to #access property too, for instance, with form options (from Custom Publishing Options):
/**
* Implements hook_form_BASE_FORM_ID_alter().
* Regroup any custom publishing options to be under a grouped tab on the node form.
* @param $form
* @param FormStateInterface $form_state
*/
function custom_pub_form_node_form_alter(&$form, FormStateInterface $form_state) {
$entities = \Drupal::entityTypeManager()->getStorage('custom_publishing_option')->loadMultiple();
$form_keys = Element::children($form);
$user = \Drupal::currentUser();
$custom_publish_options = false;
foreach ($entities as $machine_name => $entity) {
if (in_array($entity->id(), $form_keys)) {
$form[$entity->id()]['#group'] = 'custom_publish_options';
$form[$entity->id()]['#access'] = $user->hasPermission('can set node publish state to ' . $entity->id());
$custom_publish_options = true;
}
}
// show the fieldset if we have options the user can use.
if ($custom_publish_options) {
$form['custom_publish_options'] = array(
'#type' => 'details',
'#title' => t('Custom Publish Options'),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('node-form-custom-publish-options'),
),
'#weight' => 100,
'#optional' => TRUE,
);
}
}
Nothing to it!
The permission callback class in this instance is a fairly common pattern for most use cases. If you use an IDE like PHPStorm, you can create a File Template or Live Template with the permission class above so that creating them in modules is dead simple. Although, I would advise learning how class construction, dependency injection and services work first before you rely on boilerplate code generation.