Coding Drupal Entities - part 3, access callbacks

In the previous post we've taken a look at what menu paths Entity API defines. The access callback function for all menu items is entity_access.

Now let's see how to implement this  access callback function works and define permissions for the entity CRUD operations.

1. entity_access

In order to determine whether a user is allowed to do a certain operation an a custom entity, the default function, that is called is entity_access. This calls the function you've defined in hook_entity_info with the 'access callback' key, e.g.:

// for configuration entities
'access callback' => 'mymodule_entity_type_access',
// ...
// for content entities
'access callback' => 'mymodule_entity_access',

The following arguments are passed to the access callback:

  • $op - the operation being performed, these were defined in the access arguments in hook_menu (create, view, etc.)
  • $entity - the entity to check access for or NULL if there is no such entity (e.g. when creating a new entity)
  • $account - the user account to check for or NULL which defaults to the current user
  • $entity_type - the machine name of the entity type, this is always set

Now let's see how to implement this access callback.

2. Configuration entities

For configuration entites - like node type or profile type - I usually define only one general administrative permission in my hook_permission impementation and use this in my access callback:

/**
 * Implements hook_permission().
 */
function mymodule_permission() {
  $permissions = array(
    'administer myentity_type' =>  array(
      'title' => t('Administer humanmyentitytypes'),
      'description' => t('Create and delete fields on humanmyentities, and set their permissions.'),
    ),
  );
  return $permissions;
}

/**
 * Access callback for configuration entities.
 */
function mymodule_entity_type_access($op, $entity, $account = NULL, $entity_type) {
  // EntityDefaultUIController defines the following $op values:
  // view, create, update, delete
  // We ignore all these and use one general permission.
  return user_access('administer myentity_type', $account);
}

Drupal core works this way, too. There is only one "Administer content types" permissions for nodes and that's it.

3. Content entites

For content entites - like nodes - you'll probably need a much more granular permission system. These are the permissions I usually implement:

  • an admin permission to edit/view/delete every bundle
  • a view permission to view all bundles
  • a create permission for all bundles - since Entity API doesn't pass the bundle name to the access callback, it's only possible to use per bundle create permissions if you override the default implementation, for edit and delete operations you can get the bundle name from the entity object
  • per bundle permissions to create/edit own/edit any/delete own/delete any entities

This is what I add to the $permissions array in the mymodule_permission function above:

    'administer myentities' =>  array(
      'title' => t('Administer myentities'),
      'description' => t('Edit and view all myentities.'),
    ),
    'view myentities' =>  array(
      'title' => t('View myentities'),
      'description' => t('View all myentities.'),
    ),
'create myentities' => array( 'title' => t('Create myentities'), 'description' => t('Create all myentities.'), ),
 ); // Generate per myentity budle permissions. foreach (myentity_get_types() as $type) { $type_name = check_plain($type->type); $permissions += array( // "create any $type_name myentity" => array( // 'title' => t('%type_name: Create new myentity', array('%type_name' => $type->getTranslation('label'))), // ), "edit own $type_name myentity" => array( 'title' => t('%type_name: Edit own myentity', array('%type_name' => $type->getTranslation('label'))), ), "edit any $type_name myentity" => array( 'title' => t('%type_name: Edit any myentity', array('%type_name' => $type->getTranslation('label'))), ), "delete own $type_name myentity" => array( 'title' => t('%type_name: Delete own myentity', array('%type_name' => $type->getTranslation('label'))), ), "delete any $type_name myentity" => array( 'title' => t('%type_name: Delete any myentity', array('%type_name' => $type->getTranslation('label'))), ),

once the permissions are defined, they are going to be used in the access callback. My access callback implementation is pretty similar to the one used by the Profile2 module:

function mymodule_entity_access($op, $myentity = NULL, $account = NULL) {
if (user_access('administer myentities', $account)) {
return TRUE;
}

// @todo: doing this here won't allow modules to override view and create
// access
if ($op == 'view') {
return user_access('view myentities', $account);
}
if ($op == 'create') {
return user_access('create myentities', $account);
}

// Allow modules to grant / deny access.
$access = module_invoke_all('mymodule_entity_access', $op, $myentity, $account);

// Only grant access if at least one module granted access and no one denied
// access.
if (in_array(FALSE, $access, TRUE)) {
return FALSE;
}
elseif (in_array(TRUE, $access, TRUE)) {
return TRUE;
}
return FALSE;
}

The actual access callback is pretty simple, if the user has admin permission, access will be given always. For view and create operations if the user has the appropriate permissions, access will be given.

For everything else a hook will be called and access will be determined based on what is returned from the various implementations. Modules can return either TRUE or FALSE when implementing the hook. If at least one module returns FALSE, the access will be denied, if nothing is returned the access will be denied.

In my example, the hook name is hook_mymodule_entity_access and this is how to implement it:

/**
 * Implements hook_mymodule_entity_access().
 */
function mymodule_mymodule_myentity_access($op, $myentity = NULL, $account = NULL) {
  if (isset($myentity) && ($type_name = $myentity->type)) {
    $account = isset($account) ? $account : $GLOBALS['user'];
    if (user_access("$op any $type_name myentity", $account)) {
      return TRUE;
    }
    if (isset($myentity->uid) && $myentity->uid == $account->uid && user_access("$op own $type_name myentity", $account)) {
      return TRUE;
    }
  }
  // Do not explicitly deny access so others may still grant access.
}

As you can see, this only returns TRUE if the user has the appropriate permissions, otherwise it returns nothing. This allows other modules to implement hook_myentity_access and define custom access conditions.

Services

Drupal theming and sitebuild

  • PSD to Drupal theme
  • Installing and configuring modules (Views, CCK, Rules, etc.)
  • Theming modules' output

Drupal module development

  • Almost every site needs smaller modules to modify HTML output or override certain default functionalities
  • Developing custom modules

From the blog