Security update for Core, with self-updated composer
[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       $fallback_langcode = $node->getUntranslated()->language()->getId();
215       foreach ($grants as $grant) {
216         if ($realm && $realm != $grant['realm']) {
217           continue;
218         }
219         if (isset($grant['langcode'])) {
220           $grant_languages = [$grant['langcode'] => $this->languageManager->getLanguage($grant['langcode'])];
221         }
222         else {
223           $grant_languages = $node->getTranslationLanguages(TRUE);
224         }
225         foreach ($grant_languages as $grant_langcode => $grant_language) {
226           // Only write grants; denies are implicit.
227           if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
228             $grant['nid'] = $node->id();
229             $grant['langcode'] = $grant_langcode;
230             // The record with the original langcode is used as the fallback.
231             if ($grant['langcode'] == $fallback_langcode) {
232               $grant['fallback'] = 1;
233             }
234             else {
235               $grant['fallback'] = 0;
236             }
237             $query->values($grant);
238           }
239         }
240       }
241       $query->execute();
242     }
243   }
244
245   /**
246    * {@inheritdoc}
247    */
248   public function delete() {
249     $this->database->truncate('node_access')->execute();
250   }
251
252   /**
253    * {@inheritdoc}
254    */
255   public function writeDefault() {
256     $this->database->insert('node_access')
257       ->fields([
258           'nid' => 0,
259           'realm' => 'all',
260           'gid' => 0,
261           'grant_view' => 1,
262           'grant_update' => 0,
263           'grant_delete' => 0,
264         ])
265       ->execute();
266   }
267
268   /**
269    * {@inheritdoc}
270    */
271   public function count() {
272     return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
273   }
274
275   /**
276    * {@inheritdoc}
277    */
278   public function deleteNodeRecords(array $nids) {
279     $this->database->delete('node_access')
280       ->condition('nid', $nids, 'IN')
281       ->execute();
282   }
283
284   /**
285    * Creates a query condition from an array of node access grants.
286    *
287    * @param array $node_access_grants
288    *   An array of grants, as returned by node_access_grants().
289    * @return \Drupal\Core\Database\Query\Condition
290    *   A condition object to be passed to $query->condition().
291    *
292    * @see node_access_grants()
293    */
294   protected static function buildGrantsQueryCondition(array $node_access_grants) {
295     $grants = new Condition("OR");
296     foreach ($node_access_grants as $realm => $gids) {
297       if (!empty($gids)) {
298         $and = new Condition('AND');
299         $grants->condition($and
300           ->condition('gid', $gids, 'IN')
301           ->condition('realm', $realm)
302         );
303       }
304     }
305
306     return $grants;
307   }
308
309 }