PermissionHandler.php 8.04 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\user;

5
use Drupal\Core\Discovery\YamlDiscovery;
6
use Drupal\Core\Controller\ControllerResolverInterface;
7 8 9 10 11
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;

/**
12
 * Provides the available permissions based on yml files.
13
 *
14 15 16 17 18
 * To define permissions you can use a $module.permissions.yml file. This file
 * defines machine names, human-readable names, restrict access (if required for
 * security warning), and optionally descriptions for each permission type. The
 * machine names are the canonical way to refer to permissions for access
 * checking.
19
 *
20 21 22 23 24 25
 * If your module needs to define dynamic permissions you can use the
 * permission_callbacks key to declare a callable that will return an array of
 * permissions, keyed by machine name. Each item in the array can contain the
 * same keys as an entry in $module.permissions.yml.
 *
 * Here is an example from the core filter module (comments have been added):
26
 * @code
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
 * # The key is the permission machine name, and is required.
 * administer filters:
 *   # (required) Human readable name of the permission used in the UI.
 *   title: 'Administer text formats and filters'
 *   # (optional) Additional description fo the permission used in the UI.
 *   description: 'Define how text is handled by combining filters into text formats.'
 *   # (optional) Boolean, when set to true a warning about site security will
 *   # be displayed on the Permissions page. Defaults to false.
 *   restrict access: false
 *
 * # An array of callables used to generate dynamic permissions.
 * permission_callbacks:
 *   # Each item in the array should return an associative array with one or
 *   # more permissions following the same keys as the permission defined above.
 *   - Drupal\filter\FilterPermissions::permissions
42
 * @endcode
43 44 45 46
 *
 * @see filter.permissions.yml
 * @see \Drupal\filter\FilterPermissions
 * @see user_api
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
 */
class PermissionHandler implements PermissionHandlerInterface {

  use StringTranslationTrait;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The YAML discovery class to find all .permissions.yml files.
   *
62
   * @var \Drupal\Core\Discovery\YamlDiscovery
63 64 65
   */
  protected $yamlDiscovery;

66 67 68 69 70 71 72
  /**
   * The controller resolver.
   *
   * @var \Drupal\Core\Controller\ControllerResolverInterface
   */
  protected $controllerResolver;

73 74 75 76 77 78 79
  /**
   * Constructs a new PermissionHandler.
   *
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   The string translation.
80 81
   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
   *   The controller resolver.
82
   */
83
  public function __construct(ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, ControllerResolverInterface $controller_resolver) {
84 85 86 87
    // @todo It would be nice if you could pull all module directories from the
    //   container.
    $this->moduleHandler = $module_handler;
    $this->stringTranslation = $string_translation;
88
    $this->controllerResolver = $controller_resolver;
89 90 91 92 93
  }

  /**
   * Gets the YAML discovery.
   *
94
   * @return \Drupal\Core\Discovery\YamlDiscovery
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
   *   The YAML discovery.
   */
  protected function getYamlDiscovery() {
    if (!isset($this->yamlDiscovery)) {
      $this->yamlDiscovery = new YamlDiscovery('permissions', $this->moduleHandler->getModuleDirectories());
    }
    return $this->yamlDiscovery;
  }

  /**
   * {@inheritdoc}
   */
  public function getPermissions() {
    $all_permissions = $this->buildPermissionsYaml();

110
    return $this->sortPermissions($all_permissions);
111 112
  }

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
  /**
   * {@inheritdoc}
   */
  public function moduleProvidesPermissions($module_name) {
    // @TODO Static cache this information, see
    // https://www.drupal.org/node/2339487
    $permissions = $this->getPermissions();

    foreach ($permissions as $permission) {
      if ($permission['provider'] == $module_name) {
        return TRUE;
      }
    }
    return FALSE;
  }

129 130 131 132 133 134 135 136 137 138
  /**
   * Builds all permissions provided by .permissions.yml files.
   *
   * @return array[]
   *   Each return permission is an array with the following keys:
   *   - title: The title of the permission.
   *   - description: The description of the permission, defaults to NULL.
   *   - provider: The provider of the permission.
   */
  protected function buildPermissionsYaml() {
139 140
    $all_permissions = [];
    $all_callback_permissions = [];
141

142
    foreach ($this->getYamlDiscovery()->findAll() as $provider => $permissions) {
143 144 145 146 147 148 149 150 151 152 153
      // The top-level 'permissions_callback' is a list of methods in controller
      // syntax, see \Drupal\Core\Controller\ControllerResolver. These methods
      // should return an array of permissions in the same structure.
      if (isset($permissions['permission_callbacks'])) {
        foreach ($permissions['permission_callbacks'] as $permission_callback) {
          $callback = $this->controllerResolver->getControllerFromDefinition($permission_callback);
          if ($callback_permissions = call_user_func($callback)) {
            // Add any callback permissions to the array of permissions. Any
            // defaults can then get processed below.
            foreach ($callback_permissions as $name => $callback_permission) {
              if (!is_array($callback_permission)) {
154
                $callback_permission = [
155
                  'title' => $callback_permission,
156
                ];
157 158
              }

159
              $callback_permission += [
160
                'description' => NULL,
161
                'provider' => $provider,
162
              ];
163 164 165 166 167 168 169 170 171

              $all_callback_permissions[$name] = $callback_permission;
            }
          }
        }

        unset($permissions['permission_callbacks']);
      }

172 173
      foreach ($permissions as &$permission) {
        if (!is_array($permission)) {
174
          $permission = [
175
            'title' => $permission,
176
          ];
177 178 179
        }
        $permission['title'] = $this->t($permission['title']);
        $permission['description'] = isset($permission['description']) ? $this->t($permission['description']) : NULL;
180
        $permission['provider'] = !empty($permission['provider']) ? $permission['provider'] : $provider;
181
      }
182

183 184
      $all_permissions += $permissions;
    }
185 186

    return $all_permissions + $all_callback_permissions;
187 188 189
  }

  /**
190
   * Sorts the given permissions by provider name and title.
191 192 193 194 195 196 197 198 199 200
   *
   * @param array $all_permissions
   *   The permissions to be sorted.
   *
   * @return array[]
   *   Each return permission is an array with the following keys:
   *   - title: The title of the permission.
   *   - description: The description of the permission, defaults to NULL.
   *   - provider: The provider of the permission.
   */
201
  protected function sortPermissions(array $all_permissions = []) {
202 203 204 205 206
    // Get a list of all the modules providing permissions and sort by
    // display name.
    $modules = $this->getModuleNames();

    uasort($all_permissions, function (array $permission_a, array $permission_b) use ($modules) {
207 208 209 210 211 212
      if ($modules[$permission_a['provider']] == $modules[$permission_b['provider']]) {
        return $permission_a['title'] > $permission_b['title'];
      }
      else {
        return $modules[$permission_a['provider']] > $modules[$permission_b['provider']];
      }
213 214 215 216 217 218 219 220 221 222 223
    });
    return $all_permissions;
  }

  /**
   * Returns all module names.
   *
   * @return string[]
   *   Returns the human readable names of all modules keyed by machine name.
   */
  protected function getModuleNames() {
224
    $modules = [];
225
    foreach (array_keys($this->moduleHandler->getModuleList()) as $module) {
226
      $modules[$module] = $this->moduleHandler->getName($module);
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    }
    asort($modules);
    return $modules;
  }

  /**
   * Wraps system_rebuild_module_data()
   *
   * @return \Drupal\Core\Extension\Extension[]
   */
  protected function systemRebuildModuleData() {
    return system_rebuild_module_data();
  }

}