25
25
use Drupal \Core \Entity \EntityTypeManagerInterface ;
26
26
use Drupal \Core \Entity \Query \QueryBase ;
27
27
use Drupal \Core \Entity \Query \QueryInterface ;
28
+ use Drupal \Core \Extension \ModuleHandlerInterface ;
29
+ use Drupal \Core \Render \RendererInterface ;
30
+ use Drupal \Core \Session \AccountInterface ;
31
+ use Symfony \Component \HttpFoundation \RequestStack ;
28
32
29
33
/**
30
34
* Defines the entity query for Apigee Edge entities.
@@ -53,24 +57,85 @@ class Query extends QueryBase implements QueryInterface {
53
57
*
54
58
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
55
59
*/
56
- protected $ entityTypeManager ;
60
+ protected EntityTypeManagerInterface $ entityTypeManager ;
61
+
62
+ /**
63
+ * The current user.
64
+ *
65
+ * @var \Drupal\Core\Session\AccountInterface
66
+ */
67
+ protected AccountInterface $ currentUser ;
68
+
69
+ /**
70
+ * The request stack.
71
+ *
72
+ * @var \Symfony\Component\HttpFoundation\RequestStack
73
+ */
74
+ protected RequestStack $ requestStack ;
75
+
76
+ /**
77
+ * The renderer.
78
+ *
79
+ * @var \Drupal\Core\Render\RendererInterface
80
+ */
81
+ protected RendererInterface $ renderer ;
82
+
83
+ /**
84
+ * The module handler.
85
+ *
86
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
87
+ */
88
+ protected ModuleHandlerInterface $ moduleHandler ;
57
89
58
90
/**
59
91
* Constructs a Query object.
60
92
*
61
93
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
62
94
* The entity type definition.
63
95
* @param string $conjunction
64
- * - AND: all of the conditions on the query need to match.
96
+ * - AND: all the conditions on the query need to match.
65
97
* - OR: at least one of the conditions on the query need to match.
66
98
* @param array $namespaces
67
99
* List of potential namespaces of the classes belonging to this query.
68
100
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
69
101
* The entity type manager.
102
+ * @param \Drupal\Core\Session\AccountInterface|null $current_user
103
+ * The current user.
104
+ * @param \Symfony\Component\HttpFoundation\RequestStack|null $request_stack
105
+ * The request stack.
106
+ * @param \Drupal\Core\Render\RendererInterface|null $renderer
107
+ * The renderer.
108
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface|null $module_handler
109
+ * The module handler.
70
110
*/
71
- public function __construct (EntityTypeInterface $ entity_type , string $ conjunction , array $ namespaces , EntityTypeManagerInterface $ entity_type_manager ) {
111
+ public function __construct (EntityTypeInterface $ entity_type , string $ conjunction , array $ namespaces , EntityTypeManagerInterface $ entity_type_manager, AccountInterface $ current_user = NULL , RequestStack $ request_stack = NULL , RendererInterface $ renderer = NULL , ModuleHandlerInterface $ module_handler = NULL ) {
72
112
parent ::__construct ($ entity_type , $ conjunction , $ namespaces );
113
+
114
+ if ($ current_user === NULL ) {
115
+ $ current_user = \Drupal::currentUser ();
116
+ @trigger_error ('Calling ' . __METHOD__ . ' without the $current_user is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498 ' , E_USER_DEPRECATED );
117
+ }
118
+
119
+ if ($ request_stack === NULL ) {
120
+ $ request_stack = \Drupal::requestStack ();
121
+ @trigger_error ('Calling ' . __METHOD__ . ' without the $request_stack is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498 ' , E_USER_DEPRECATED );
122
+ }
123
+
124
+ if ($ renderer === NULL ) {
125
+ $ renderer = \Drupal::service ('renderer ' );
126
+ @trigger_error ('Calling ' . __METHOD__ . ' without the $renderer is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498 ' , E_USER_DEPRECATED );
127
+ }
128
+
129
+ if ($ module_handler === NULL ) {
130
+ $ module_handler = \Drupal::moduleHandler ();
131
+ @trigger_error ('Calling ' . __METHOD__ . ' without the $module_handler is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498 ' , E_USER_DEPRECATED );
132
+ }
133
+
73
134
$ this ->entityTypeManager = $ entity_type_manager ;
135
+ $ this ->currentUser = $ current_user ;
136
+ $ this ->requestStack = $ request_stack ;
137
+ $ this ->renderer = $ renderer ;
138
+ $ this ->moduleHandler = $ module_handler ;
74
139
}
75
140
76
141
/**
@@ -86,36 +151,60 @@ public function execute() {
86
151
// result because this function gets called.
87
152
$ all_records = $ this ->getFromStorage ();
88
153
89
- // @todo Proper entity query support that is aligned with the implementation
90
- // in \Drupal\Core\Entity\Query\Sql\Query::prepare() can be only added
91
- // if the following Entity API module issue is solved.
92
- // https://www.drupal.org/project/entity/issues/3332956
93
- // (Having a fix for a similar Group module issue is a nice to have,
94
- // https://www.drupal.org/project/group/issues/3332963.)
154
+ // Be consistent with \Drupal\Core\Entity\Query\Sql\Query::prepare().
155
+ // Add and fire special entity query tags.
156
+ $ this ->addTag ('entity_query ' );
157
+ $ this ->addTag ('entity_query_ ' . $ this ->entityTypeId );
158
+
95
159
if ($ this ->accessCheck ) {
160
+ // We do not just add a tag but ensure that only those Apigee entities
161
+ // are returned that the entity access API grants view access.
162
+ // (Storage level filtering is not available or way too limited.)
163
+ $ this ->addTag ($ this ->entityTypeId . '_access ' );
164
+
96
165
// Read meta-data from query, if provided.
97
- if (! $ account = $ this ->getMetaData ('account ' )) {
98
- // @todo DI dependency.
99
- $ account = \Drupal:: currentUser () ;
166
+ $ account = $ this ->getMetaData ('account ' );
167
+ if ( $ account === NULL ) {
168
+ $ account = $ this -> currentUser ;
100
169
}
170
+
101
171
$ cacheability = CacheableMetadata::createFromRenderArray ([]);
102
- $ all_records = array_filter ($ all_records , static function (EntityInterface $ entity ) use ($ cacheability , $ account ) {
172
+ $ viewable_entity_ids = array_reduce ($ all_records , static function (array $ carry , EntityInterface $ entity ) use ($ cacheability , $ account ) {
103
173
// Bubble up cacheability information even from a revoked access result.
104
174
$ result = $ entity ->access ('view ' , $ account , TRUE );
105
175
$ cacheability ->addCacheableDependency ($ result );
106
- return $ result ->isAllowed ();
107
- });
108
- // @todo DI dependencies.
176
+ if ($ result ->isAllowed ()) {
177
+ $ carry [] = $ entity ->id ();
178
+ }
179
+ return $ carry ;
180
+ }, []);
181
+
182
+ // We deliberately add conditions to the original entity query instead
183
+ // of pre-filtering all records because query conditions are visible
184
+ // in hook_query_TAG_alter() implementations for downstream developers.
185
+ if (empty ($ viewable_entity_ids )) {
186
+ // Add an always false condition. A persisted entity's primary id
187
+ // cannot be null.
188
+ $ this ->condition ->notExists ($ this ->entityType ->getKey ('id ' ));
189
+ }
190
+ else {
191
+ $ this ->condition ->condition ($ this ->entityType ->getKey ('id ' ), $ viewable_entity_ids , 'IN ' );
192
+ }
109
193
/** @var \Symfony\Component\HttpFoundation\Request $request */
110
- $ request = \Drupal::requestStack ()->getCurrentRequest ();
111
- $ renderer = \Drupal::service ('renderer ' );
112
- if ($ request ->isMethodCacheable () && $ renderer ->hasRenderContext ()) {
194
+ $ request = $ this ->requestStack ->getCurrentRequest ();
195
+ if ($ request ->isMethodCacheable () && $ this ->renderer ->hasRenderContext ()) {
113
196
$ build = [];
114
197
$ cacheability ->applyTo ($ build );
115
- $ renderer ->render ($ build );
198
+ $ this -> renderer ->render ($ build );
116
199
}
117
200
}
118
201
202
+ $ hooks = ['query ' ];
203
+ foreach ($ this ->alterTags as $ tag => $ value ) {
204
+ $ hooks [] = 'query_ ' . $ tag ;
205
+ }
206
+ $ this ->moduleHandler ->alter ($ hooks , $ this );
207
+
119
208
$ filter = $ this ->condition ->compile ($ this );
120
209
$ result = array_filter ($ all_records , $ filter );
121
210
@@ -124,7 +213,7 @@ public function execute() {
124
213
}
125
214
126
215
if ($ this ->sort ) {
127
- uasort ($ result , function (EntityInterface $ entity0 , EntityInterface $ entity1 ) : int {
216
+ uasort ($ result , function (EntityInterface $ entity0 , EntityInterface $ entity1 ): int {
128
217
foreach ($ this ->sort as $ sort ) {
129
218
$ value0 = Condition::getProperty ($ entity0 , $ sort ['field ' ]);
130
219
$ value1 = Condition::getProperty ($ entity1 , $ sort ['field ' ]);
@@ -150,7 +239,7 @@ public function execute() {
150
239
$ result = array_slice ($ result , $ this ->range ['start ' ], $ this ->range ['length ' ]);
151
240
}
152
241
153
- return array_map (function (EntityInterface $ entity ) : string {
242
+ return array_map (static function (EntityInterface $ entity ): string {
154
243
return (string ) $ entity ->id ();
155
244
}, $ result );
156
245
}
@@ -219,7 +308,7 @@ protected function getFromStorage(): array {
219
308
else {
220
309
$ ids = [$ id ];
221
310
unset($ filtered_conditions [$ key ]);
222
- // If we found an id field in the query do not look for an another
311
+ // If we found an id field in the query do not look for another
223
312
// because that would not make any sense to query one entity by
224
313
// both id fields. (Where in theory both id field could refer to a
225
314
// different entity.)
0 commit comments