comparison core/modules/node/src/NodeGrantDatabaseStorage.php @ 0:4c8ae668cc8c

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