annotate core/modules/node/src/NodeGrantDatabaseStorage.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 7a779792577d
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\node;
Chris@0 4
Chris@0 5 use Drupal\Core\Access\AccessResult;
Chris@0 6 use Drupal\Core\Database\Connection;
Chris@0 7 use Drupal\Core\Database\Query\SelectInterface;
Chris@0 8 use Drupal\Core\Database\Query\Condition;
Chris@0 9 use Drupal\Core\Extension\ModuleHandlerInterface;
Chris@0 10 use Drupal\Core\Language\LanguageManagerInterface;
Chris@0 11 use Drupal\Core\Session\AccountInterface;
Chris@0 12
Chris@0 13 /**
Chris@0 14 * Defines a storage handler class that handles the node grants system.
Chris@0 15 *
Chris@0 16 * This is used to build node query access.
Chris@0 17 *
Chris@0 18 * @ingroup node_access
Chris@0 19 */
Chris@0 20 class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
Chris@0 21
Chris@0 22 /**
Chris@0 23 * The database connection.
Chris@0 24 *
Chris@0 25 * @var \Drupal\Core\Database\Connection
Chris@0 26 */
Chris@0 27 protected $database;
Chris@0 28
Chris@0 29 /**
Chris@0 30 * The module handler.
Chris@0 31 *
Chris@0 32 * @var \Drupal\Core\Extension\ModuleHandlerInterface
Chris@0 33 */
Chris@0 34 protected $moduleHandler;
Chris@0 35
Chris@0 36 /**
Chris@0 37 * The language manager.
Chris@0 38 *
Chris@0 39 * @var \Drupal\Core\Language\LanguageManagerInterface
Chris@0 40 */
Chris@0 41 protected $languageManager;
Chris@0 42
Chris@0 43 /**
Chris@0 44 * Constructs a NodeGrantDatabaseStorage object.
Chris@0 45 *
Chris@0 46 * @param \Drupal\Core\Database\Connection $database
Chris@0 47 * The database connection.
Chris@0 48 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
Chris@0 49 * The module handler.
Chris@0 50 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
Chris@0 51 * The language manager.
Chris@0 52 */
Chris@0 53 public function __construct(Connection $database, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) {
Chris@0 54 $this->database = $database;
Chris@0 55 $this->moduleHandler = $module_handler;
Chris@0 56 $this->languageManager = $language_manager;
Chris@0 57 }
Chris@0 58
Chris@0 59 /**
Chris@0 60 * {@inheritdoc}
Chris@0 61 */
Chris@0 62 public function access(NodeInterface $node, $operation, AccountInterface $account) {
Chris@0 63 // Grants only support these operations.
Chris@0 64 if (!in_array($operation, ['view', 'update', 'delete'])) {
Chris@0 65 return AccessResult::neutral();
Chris@0 66 }
Chris@0 67
Chris@0 68 // If no module implements the hook or the node does not have an id there is
Chris@0 69 // no point in querying the database for access grants.
Chris@0 70 if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) {
Chris@0 71 // Return the equivalent of the default grant, defined by
Chris@0 72 // self::writeDefault().
Chris@0 73 if ($operation === 'view') {
Chris@0 74 return AccessResult::allowedIf($node->isPublished())->addCacheableDependency($node);
Chris@0 75 }
Chris@0 76 else {
Chris@0 77 return AccessResult::neutral();
Chris@0 78 }
Chris@0 79 }
Chris@0 80
Chris@0 81 // Check the database for potential access grants.
Chris@0 82 $query = $this->database->select('node_access');
Chris@0 83 $query->addExpression('1');
Chris@0 84 // Only interested for granting in the current operation.
Chris@0 85 $query->condition('grant_' . $operation, 1, '>=');
Chris@0 86 // Check for grants for this node and the correct langcode.
Chris@0 87 $nids = $query->andConditionGroup()
Chris@0 88 ->condition('nid', $node->id())
Chris@0 89 ->condition('langcode', $node->language()->getId());
Chris@0 90 // If the node is published, also take the default grant into account. The
Chris@0 91 // default is saved with a node ID of 0.
Chris@0 92 $status = $node->isPublished();
Chris@0 93 if ($status) {
Chris@0 94 $nids = $query->orConditionGroup()
Chris@0 95 ->condition($nids)
Chris@0 96 ->condition('nid', 0);
Chris@0 97 }
Chris@0 98 $query->condition($nids);
Chris@0 99 $query->range(0, 1);
Chris@0 100
Chris@0 101 $grants = static::buildGrantsQueryCondition(node_access_grants($operation, $account));
Chris@0 102
Chris@0 103 if (count($grants) > 0) {
Chris@0 104 $query->condition($grants);
Chris@0 105 }
Chris@0 106
Chris@0 107 // Only the 'view' node grant can currently be cached; the others currently
Chris@0 108 // don't have any cacheability metadata. Hopefully, we can add that in the
Chris@0 109 // future, which would allow this access check result to be cacheable in all
Chris@0 110 // cases. For now, this must remain marked as uncacheable, even when it is
Chris@0 111 // theoretically cacheable, because we don't have the necessary metadata to
Chris@0 112 // know it for a fact.
Chris@0 113 $set_cacheability = function (AccessResult $access_result) use ($operation) {
Chris@0 114 $access_result->addCacheContexts(['user.node_grants:' . $operation]);
Chris@0 115 if ($operation !== 'view') {
Chris@0 116 $access_result->setCacheMaxAge(0);
Chris@0 117 }
Chris@0 118 return $access_result;
Chris@0 119 };
Chris@0 120
Chris@0 121 if ($query->execute()->fetchField()) {
Chris@0 122 return $set_cacheability(AccessResult::allowed());
Chris@0 123 }
Chris@0 124 else {
Chris@0 125 return $set_cacheability(AccessResult::neutral());
Chris@0 126 }
Chris@0 127 }
Chris@0 128
Chris@0 129 /**
Chris@0 130 * {@inheritdoc}
Chris@0 131 */
Chris@0 132 public function checkAll(AccountInterface $account) {
Chris@0 133 $query = $this->database->select('node_access');
Chris@0 134 $query->addExpression('COUNT(*)');
Chris@0 135 $query
Chris@0 136 ->condition('nid', 0)
Chris@0 137 ->condition('grant_view', 1, '>=');
Chris@0 138
Chris@0 139 $grants = static::buildGrantsQueryCondition(node_access_grants('view', $account));
Chris@0 140
Chris@0 141 if (count($grants) > 0) {
Chris@0 142 $query->condition($grants);
Chris@0 143 }
Chris@0 144 return $query->execute()->fetchField();
Chris@0 145 }
Chris@0 146
Chris@0 147 /**
Chris@0 148 * {@inheritdoc}
Chris@0 149 */
Chris@0 150 public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table) {
Chris@0 151 if (!$langcode = $query->getMetaData('langcode')) {
Chris@0 152 $langcode = FALSE;
Chris@0 153 }
Chris@0 154
Chris@0 155 // Find all instances of the base table being joined -- could appear
Chris@0 156 // more than once in the query, and could be aliased. Join each one to
Chris@0 157 // the node_access table.
Chris@0 158 $grants = node_access_grants($op, $account);
Chris@0 159 foreach ($tables as $nalias => $tableinfo) {
Chris@0 160 $table = $tableinfo['table'];
Chris@0 161 if (!($table instanceof SelectInterface) && $table == $base_table) {
Chris@0 162 // Set the subquery.
Chris@0 163 $subquery = $this->database->select('node_access', 'na')
Chris@0 164 ->fields('na', ['nid']);
Chris@0 165
Chris@0 166 // If any grant exists for the specified user, then user has access to the
Chris@0 167 // node for the specified operation.
Chris@0 168 $grant_conditions = static::buildGrantsQueryCondition($grants);
Chris@0 169
Chris@0 170 // Attach conditions to the subquery for nodes.
Chris@0 171 if (count($grant_conditions->conditions())) {
Chris@0 172 $subquery->condition($grant_conditions);
Chris@0 173 }
Chris@0 174 $subquery->condition('na.grant_' . $op, 1, '>=');
Chris@0 175
Chris@0 176 // Add langcode-based filtering if this is a multilingual site.
Chris@0 177 if (\Drupal::languageManager()->isMultilingual()) {
Chris@0 178 // If no specific langcode to check for is given, use the grant entry
Chris@0 179 // which is set as a fallback.
Chris@0 180 // If a specific langcode is given, use the grant entry for it.
Chris@0 181 if ($langcode === FALSE) {
Chris@0 182 $subquery->condition('na.fallback', 1, '=');
Chris@0 183 }
Chris@0 184 else {
Chris@0 185 $subquery->condition('na.langcode', $langcode, '=');
Chris@0 186 }
Chris@0 187 }
Chris@0 188
Chris@0 189 $field = 'nid';
Chris@0 190 // Now handle entities.
Chris@0 191 $subquery->where("$nalias.$field = na.nid");
Chris@0 192
Chris@0 193 $query->exists($subquery);
Chris@0 194 }
Chris@0 195 }
Chris@0 196 }
Chris@0 197
Chris@0 198 /**
Chris@0 199 * {@inheritdoc}
Chris@0 200 */
Chris@0 201 public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
Chris@0 202 if ($delete) {
Chris@0 203 $query = $this->database->delete('node_access')->condition('nid', $node->id());
Chris@0 204 if ($realm) {
Chris@0 205 $query->condition('realm', [$realm, 'all'], 'IN');
Chris@0 206 }
Chris@0 207 $query->execute();
Chris@0 208 }
Chris@0 209 // Only perform work when node_access modules are active.
Chris@0 210 if (!empty($grants) && count($this->moduleHandler->getImplementations('node_grants'))) {
Chris@0 211 $query = $this->database->insert('node_access')->fields(['nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete']);
Chris@0 212 // If we have defined a granted langcode, use it. But if not, add a grant
Chris@0 213 // for every language this node is translated to.
Chris@12 214 $fallback_langcode = $node->getUntranslated()->language()->getId();
Chris@0 215 foreach ($grants as $grant) {
Chris@0 216 if ($realm && $realm != $grant['realm']) {
Chris@0 217 continue;
Chris@0 218 }
Chris@0 219 if (isset($grant['langcode'])) {
Chris@0 220 $grant_languages = [$grant['langcode'] => $this->languageManager->getLanguage($grant['langcode'])];
Chris@0 221 }
Chris@0 222 else {
Chris@0 223 $grant_languages = $node->getTranslationLanguages(TRUE);
Chris@0 224 }
Chris@0 225 foreach ($grant_languages as $grant_langcode => $grant_language) {
Chris@0 226 // Only write grants; denies are implicit.
Chris@0 227 if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
Chris@0 228 $grant['nid'] = $node->id();
Chris@0 229 $grant['langcode'] = $grant_langcode;
Chris@0 230 // The record with the original langcode is used as the fallback.
Chris@12 231 if ($grant['langcode'] == $fallback_langcode) {
Chris@0 232 $grant['fallback'] = 1;
Chris@0 233 }
Chris@0 234 else {
Chris@0 235 $grant['fallback'] = 0;
Chris@0 236 }
Chris@0 237 $query->values($grant);
Chris@0 238 }
Chris@0 239 }
Chris@0 240 }
Chris@0 241 $query->execute();
Chris@0 242 }
Chris@0 243 }
Chris@0 244
Chris@0 245 /**
Chris@0 246 * {@inheritdoc}
Chris@0 247 */
Chris@0 248 public function delete() {
Chris@0 249 $this->database->truncate('node_access')->execute();
Chris@0 250 }
Chris@0 251
Chris@0 252 /**
Chris@0 253 * {@inheritdoc}
Chris@0 254 */
Chris@0 255 public function writeDefault() {
Chris@0 256 $this->database->insert('node_access')
Chris@0 257 ->fields([
Chris@0 258 'nid' => 0,
Chris@0 259 'realm' => 'all',
Chris@0 260 'gid' => 0,
Chris@0 261 'grant_view' => 1,
Chris@0 262 'grant_update' => 0,
Chris@0 263 'grant_delete' => 0,
Chris@0 264 ])
Chris@0 265 ->execute();
Chris@0 266 }
Chris@0 267
Chris@0 268 /**
Chris@0 269 * {@inheritdoc}
Chris@0 270 */
Chris@0 271 public function count() {
Chris@0 272 return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
Chris@0 273 }
Chris@0 274
Chris@0 275 /**
Chris@0 276 * {@inheritdoc}
Chris@0 277 */
Chris@0 278 public function deleteNodeRecords(array $nids) {
Chris@0 279 $this->database->delete('node_access')
Chris@0 280 ->condition('nid', $nids, 'IN')
Chris@0 281 ->execute();
Chris@0 282 }
Chris@0 283
Chris@0 284 /**
Chris@0 285 * Creates a query condition from an array of node access grants.
Chris@0 286 *
Chris@0 287 * @param array $node_access_grants
Chris@0 288 * An array of grants, as returned by node_access_grants().
Chris@0 289 * @return \Drupal\Core\Database\Query\Condition
Chris@0 290 * A condition object to be passed to $query->condition().
Chris@0 291 *
Chris@0 292 * @see node_access_grants()
Chris@0 293 */
Chris@0 294 protected static function buildGrantsQueryCondition(array $node_access_grants) {
Chris@0 295 $grants = new Condition("OR");
Chris@0 296 foreach ($node_access_grants as $realm => $gids) {
Chris@0 297 if (!empty($gids)) {
Chris@0 298 $and = new Condition('AND');
Chris@0 299 $grants->condition($and
Chris@0 300 ->condition('gid', $gids, 'IN')
Chris@0 301 ->condition('realm', $realm)
Chris@0 302 );
Chris@0 303 }
Chris@0 304 }
Chris@0 305
Chris@0 306 return $grants;
Chris@0 307 }
Chris@0 308
Chris@0 309 }