Version 1
[yaffs-website] / web / core / modules / node / src / NodeGrantDatabaseStorage.php
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 }