1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Drupal\DrupalExtension\Context; |
4
|
|
|
|
5
|
|
|
use Behat\MinkExtension\Context\RawMinkContext; |
6
|
|
|
use Behat\Testwork\Hook\HookDispatcher; |
7
|
|
|
|
8
|
|
|
use Drupal\DrupalDriverManager; |
9
|
|
|
|
10
|
|
|
use Drupal\DrupalExtension\Hook\Scope\AfterLanguageEnableScope; |
11
|
|
|
use Drupal\DrupalExtension\Hook\Scope\AfterNodeCreateScope; |
12
|
|
|
use Drupal\DrupalExtension\Hook\Scope\AfterTermCreateScope; |
13
|
|
|
use Drupal\DrupalExtension\Hook\Scope\AfterUserCreateScope; |
14
|
|
|
use Drupal\DrupalExtension\Hook\Scope\BaseEntityScope; |
15
|
|
|
use Drupal\DrupalExtension\Hook\Scope\BeforeLanguageEnableScope; |
16
|
|
|
use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope; |
17
|
|
|
use Drupal\DrupalExtension\Hook\Scope\BeforeUserCreateScope; |
18
|
|
|
use Drupal\DrupalExtension\Hook\Scope\BeforeTermCreateScope; |
19
|
|
|
|
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Provides the raw functionality for interacting with Drupal. |
23
|
|
|
*/ |
24
|
|
|
class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface { |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Drupal driver manager. |
28
|
|
|
* |
29
|
|
|
* @var \Drupal\DrupalDriverManager |
30
|
|
|
*/ |
31
|
|
|
private $drupal; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Test parameters. |
35
|
|
|
* |
36
|
|
|
* @var array |
37
|
|
|
*/ |
38
|
|
|
private $drupalParameters; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Event dispatcher object. |
42
|
|
|
* |
43
|
|
|
* @var \Behat\Testwork\Hook\HookDispatcher |
44
|
|
|
*/ |
45
|
|
|
protected $dispatcher; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Keep track of nodes so they can be cleaned up. |
49
|
|
|
* |
50
|
|
|
* @var array |
51
|
|
|
*/ |
52
|
|
|
protected $nodes = array(); |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Current authenticated user. |
56
|
|
|
* |
57
|
|
|
* A value of FALSE denotes an anonymous user. |
58
|
|
|
* |
59
|
|
|
* @var stdClass|bool |
60
|
|
|
*/ |
61
|
|
|
public $user = FALSE; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Keep track of all users that are created so they can easily be removed. |
65
|
|
|
* |
66
|
|
|
* @var array |
67
|
|
|
*/ |
68
|
|
|
protected $users = array(); |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Keep track of all terms that are created so they can easily be removed. |
72
|
|
|
* |
73
|
|
|
* @var array |
74
|
|
|
*/ |
75
|
|
|
protected $terms = array(); |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Keep track of any roles that are created so they can easily be removed. |
79
|
|
|
* |
80
|
|
|
* @var array |
81
|
|
|
*/ |
82
|
|
|
protected $roles = array(); |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Keep track of any languages that are created so they can easily be removed. |
86
|
|
|
* |
87
|
|
|
* @var array |
88
|
|
|
*/ |
89
|
|
|
protected $languages = array(); |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* {@inheritDoc} |
93
|
|
|
*/ |
94
|
|
|
public function setDrupal(DrupalDriverManager $drupal) { |
95
|
|
|
$this->drupal = $drupal; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* {@inheritDoc} |
100
|
|
|
*/ |
101
|
|
|
public function getDrupal() { |
102
|
|
|
return $this->drupal; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* {@inheritDoc} |
107
|
|
|
*/ |
108
|
|
|
public function setDispatcher(HookDispatcher $dispatcher) { |
109
|
|
|
$this->dispatcher = $dispatcher; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Set parameters provided for Drupal. |
114
|
|
|
*/ |
115
|
|
|
public function setDrupalParameters(array $parameters) { |
116
|
|
|
$this->drupalParameters = $parameters; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Returns a specific Drupal parameter. |
121
|
|
|
* |
122
|
|
|
* @param string $name |
123
|
|
|
* Parameter name. |
124
|
|
|
* |
125
|
|
|
* @return mixed |
126
|
|
|
*/ |
127
|
|
|
public function getDrupalParameter($name) { |
128
|
|
|
return isset($this->drupalParameters[$name]) ? $this->drupalParameters[$name] : NULL; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Returns a specific Drupal text value. |
133
|
|
|
* |
134
|
|
|
* @param string $name |
135
|
|
|
* Text value name, such as 'log_out', which corresponds to the default 'Log |
136
|
|
|
* out' link text. |
137
|
|
|
* @throws \Exception |
138
|
|
|
* @return |
139
|
|
|
*/ |
140
|
|
View Code Duplication |
public function getDrupalText($name) { |
|
|
|
|
141
|
|
|
$text = $this->getDrupalParameter('text'); |
142
|
|
|
if (!isset($text[$name])) { |
143
|
|
|
throw new \Exception(sprintf('No such Drupal string: %s', $name)); |
144
|
|
|
} |
145
|
|
|
return $text[$name]; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Get active Drupal Driver. |
150
|
|
|
*/ |
151
|
|
|
public function getDriver($name = NULL) { |
152
|
|
|
return $this->getDrupal()->getDriver($name); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Get driver's random generator. |
157
|
|
|
*/ |
158
|
|
|
public function getRandom() { |
159
|
|
|
return $this->getDriver()->getRandom(); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Remove any created nodes. |
164
|
|
|
* |
165
|
|
|
* @AfterScenario |
166
|
|
|
*/ |
167
|
|
|
public function cleanNodes() { |
168
|
|
|
// Remove any nodes that were created. |
169
|
|
|
foreach ($this->nodes as $node) { |
170
|
|
|
$this->getDriver()->nodeDelete($node); |
171
|
|
|
} |
172
|
|
|
$this->nodes = array(); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Remove any created users. |
177
|
|
|
* |
178
|
|
|
* @AfterScenario |
179
|
|
|
*/ |
180
|
|
|
public function cleanUsers() { |
181
|
|
|
// Remove any users that were created. |
182
|
|
|
if (!empty($this->users)) { |
183
|
|
|
foreach ($this->users as $user) { |
184
|
|
|
$this->getDriver()->userDelete($user); |
185
|
|
|
} |
186
|
|
|
$this->getDriver()->processBatch(); |
187
|
|
|
$this->users = array(); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Remove any created terms. |
193
|
|
|
* |
194
|
|
|
* @AfterScenario |
195
|
|
|
*/ |
196
|
|
|
public function cleanTerms() { |
197
|
|
|
// Remove any terms that were created. |
198
|
|
|
foreach ($this->terms as $term) { |
199
|
|
|
$this->getDriver()->termDelete($term); |
200
|
|
|
} |
201
|
|
|
$this->terms = array(); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Remove any created roles. |
206
|
|
|
* |
207
|
|
|
* @AfterScenario |
208
|
|
|
*/ |
209
|
|
|
public function cleanRoles() { |
210
|
|
|
// Remove any roles that were created. |
211
|
|
|
foreach ($this->roles as $rid) { |
212
|
|
|
$this->getDriver()->roleDelete($rid); |
213
|
|
|
} |
214
|
|
|
$this->roles = array(); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Remove any created languages. |
219
|
|
|
* |
220
|
|
|
* @AfterScenario |
221
|
|
|
*/ |
222
|
|
|
public function cleanLanguages() { |
223
|
|
|
// Delete any languages that were created. |
224
|
|
|
foreach ($this->languages as $language) { |
225
|
|
|
$this->getDriver()->languageDelete($language); |
|
|
|
|
226
|
|
|
unset($this->languages[$language->langcode]); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Clear static caches. |
232
|
|
|
* |
233
|
|
|
* @AfterScenario @api |
234
|
|
|
*/ |
235
|
|
|
public function clearStaticCaches() { |
236
|
|
|
$this->getDriver()->clearStaticCaches(); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Dispatch scope hooks. |
241
|
|
|
* |
242
|
|
|
* @param string $scope |
|
|
|
|
243
|
|
|
* The entity scope to dispatch. |
244
|
|
|
* @param stdClass $entity |
245
|
|
|
* The entity. |
246
|
|
|
*/ |
247
|
|
|
protected function dispatchHooks($scopeType, \stdClass $entity) { |
248
|
|
|
$fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType; |
249
|
|
|
$scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity); |
250
|
|
|
$callResults = $this->dispatcher->dispatchScopeHooks($scope); |
251
|
|
|
|
252
|
|
|
// The dispatcher suppresses exceptions, throw them here if there are any. |
253
|
|
|
foreach ($callResults as $result) { |
254
|
|
|
if ($result->hasException()) { |
255
|
|
|
$exception = $result->getException(); |
256
|
|
|
throw $exception; |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Create a node. |
263
|
|
|
* |
264
|
|
|
* @return object |
265
|
|
|
* The created node. |
266
|
|
|
*/ |
267
|
|
View Code Duplication |
public function nodeCreate($node) { |
|
|
|
|
268
|
|
|
$this->dispatchHooks('BeforeNodeCreateScope', $node); |
269
|
|
|
$this->parseEntityFields('node', $node); |
270
|
|
|
$saved = $this->getDriver()->createNode($node); |
271
|
|
|
$this->dispatchHooks('AfterNodeCreateScope', $saved); |
272
|
|
|
$this->nodes[] = $saved; |
273
|
|
|
return $saved; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Parse multi-value fields. Possible formats: |
278
|
|
|
* A, B, C |
279
|
|
|
* A - B, C - D, E - F |
280
|
|
|
* |
281
|
|
|
* @param string $entity_type |
282
|
|
|
* The entity type. |
283
|
|
|
* @param \stdClass $entity |
284
|
|
|
* An object containing the entity properties and fields as properties. |
285
|
|
|
*/ |
286
|
|
|
public function parseEntityFields($entity_type, \stdClass $entity) { |
287
|
|
|
$multicolumn_field = ''; |
288
|
|
|
$multicolumn_fields = array(); |
289
|
|
|
|
290
|
|
|
foreach (clone $entity as $field => $field_value) { |
|
|
|
|
291
|
|
|
// Reset the multicolumn field if the field name does not contain a column. |
292
|
|
|
if (strpos($field, ':') === FALSE) { |
293
|
|
|
$multicolumn_field = ''; |
294
|
|
|
} |
295
|
|
|
// Start tracking a new multicolumn field if the field name contains a ':' |
296
|
|
|
// which is preceded by at least 1 character. |
297
|
|
|
elseif (strpos($field, ':', 1) !== FALSE) { |
298
|
|
|
list($multicolumn_field, $multicolumn_column) = explode(':', $field); |
299
|
|
|
} |
300
|
|
|
// If a field name starts with a ':' but we are not yet tracking a |
301
|
|
|
// multicolumn field we don't know to which field this belongs. |
302
|
|
|
elseif (empty($multicolumn_field)) { |
303
|
|
|
throw new \Exception('Field name missing for ' . $field); |
304
|
|
|
} |
305
|
|
|
// Update the column name if the field name starts with a ':' and we are |
306
|
|
|
// already tracking a multicolumn field. |
307
|
|
|
else { |
308
|
|
|
$multicolumn_column = substr($field, 1); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$is_multicolumn = $multicolumn_field && $multicolumn_column; |
|
|
|
|
312
|
|
|
$field_name = $multicolumn_field ?: $field; |
313
|
|
|
if ($this->getDriver()->isField($entity_type, $field_name)) { |
314
|
|
|
// Split up multiple values in multi-value fields. |
315
|
|
|
$values = array(); |
316
|
|
|
foreach (explode(', ', $field_value) as $key => $value) { |
317
|
|
|
$columns = $value; |
318
|
|
|
// Split up field columns if the ' - ' separator is present. |
319
|
|
|
if (strstr($value, ' - ') !== FALSE) { |
320
|
|
|
$columns = array(); |
321
|
|
|
foreach (explode(' - ', $value) as $column) { |
322
|
|
|
// Check if it is an inline named column. |
323
|
|
|
if (!$is_multicolumn && strpos($column, ': ', 1) !== FALSE) { |
324
|
|
|
list ($key, $column) = explode(': ', $column); |
325
|
|
|
$columns[$key] = $column; |
326
|
|
|
} |
327
|
|
|
else { |
328
|
|
|
$columns[] = $column; |
329
|
|
|
} |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
// Use the column name if we are tracking a multicolumn field. |
333
|
|
|
if ($is_multicolumn) { |
334
|
|
|
$multicolumn_fields[$multicolumn_field][$key][$multicolumn_column] = $columns; |
335
|
|
|
unset($entity->$field); |
336
|
|
|
} |
337
|
|
|
else { |
338
|
|
|
$values[] = $columns; |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
// Replace regular fields inline in the entity after parsing. |
342
|
|
|
if (!$is_multicolumn) { |
343
|
|
|
$entity->$field_name = $values; |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// Add the multicolumn fields to the entity. |
349
|
|
|
foreach ($multicolumn_fields as $field_name => $columns) { |
350
|
|
|
$entity->$field_name = $columns; |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Create a user. |
356
|
|
|
* |
357
|
|
|
* @return object |
358
|
|
|
* The created user. |
359
|
|
|
*/ |
360
|
|
View Code Duplication |
public function userCreate($user) { |
|
|
|
|
361
|
|
|
$this->dispatchHooks('BeforeUserCreateScope', $user); |
362
|
|
|
$this->parseEntityFields('user', $user); |
363
|
|
|
$this->getDriver()->userCreate($user); |
364
|
|
|
$this->dispatchHooks('AfterUserCreateScope', $user); |
365
|
|
|
$this->users[$user->name] = $this->user = $user; |
366
|
|
|
return $user; |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Create a term. |
371
|
|
|
* |
372
|
|
|
* @return object |
373
|
|
|
* The created term. |
374
|
|
|
*/ |
375
|
|
View Code Duplication |
public function termCreate($term) { |
|
|
|
|
376
|
|
|
$this->dispatchHooks('BeforeTermCreateScope', $term); |
377
|
|
|
$this->parseEntityFields('taxonomy_term', $term); |
378
|
|
|
$saved = $this->getDriver()->createTerm($term); |
379
|
|
|
$this->dispatchHooks('AfterTermCreateScope', $saved); |
380
|
|
|
$this->terms[] = $saved; |
381
|
|
|
return $saved; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Creates a language. |
386
|
|
|
* |
387
|
|
|
* @param \stdClass $language |
388
|
|
|
* An object with the following properties: |
389
|
|
|
* - langcode: the langcode of the language to create. |
390
|
|
|
* |
391
|
|
|
* @return object|FALSE |
392
|
|
|
* The created language, or FALSE if the language was already created. |
393
|
|
|
*/ |
394
|
|
|
public function languageCreate(\stdClass $language) { |
395
|
|
|
$this->dispatchHooks('BeforeLanguageCreateScope', $language); |
396
|
|
|
$language = $this->getDriver()->languageCreate($language); |
|
|
|
|
397
|
|
|
if ($language) { |
398
|
|
|
$this->dispatchHooks('AfterLanguageCreateScope', $language); |
399
|
|
|
$this->languages[$language->langcode] = $language; |
400
|
|
|
} |
401
|
|
|
return $language; |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* Log-in the current user. |
406
|
|
|
*/ |
407
|
|
|
public function login() { |
408
|
|
|
// Check if logged in. |
409
|
|
|
if ($this->loggedIn()) { |
410
|
|
|
$this->logout(); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
if (!$this->user) { |
414
|
|
|
throw new \Exception('Tried to login without a user.'); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
$this->getSession()->visit($this->locatePath('/user')); |
418
|
|
|
$element = $this->getSession()->getPage(); |
419
|
|
|
$element->fillField($this->getDrupalText('username_field'), $this->user->name); |
420
|
|
|
$element->fillField($this->getDrupalText('password_field'), $this->user->pass); |
421
|
|
|
$submit = $element->findButton($this->getDrupalText('log_in')); |
422
|
|
|
if (empty($submit)) { |
423
|
|
|
throw new \Exception(sprintf("No submit button at %s", $this->getSession()->getCurrentUrl())); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
// Log in. |
427
|
|
|
$submit->click(); |
428
|
|
|
|
429
|
|
|
if (!$this->loggedIn()) { |
430
|
|
|
throw new \Exception(sprintf("Failed to log in as user '%s' with role '%s'", $this->user->name, $this->user->role)); |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Logs the current user out. |
436
|
|
|
*/ |
437
|
|
|
public function logout() { |
438
|
|
|
$this->getSession()->visit($this->locatePath('/user/logout')); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Determine if the a user is already logged in. |
443
|
|
|
* |
444
|
|
|
* @return boolean |
445
|
|
|
* Returns TRUE if a user is logged in for this session. |
446
|
|
|
*/ |
447
|
|
|
public function loggedIn() { |
448
|
|
|
$session = $this->getSession(); |
449
|
|
|
$session->visit($this->locatePath('/')); |
450
|
|
|
|
451
|
|
|
// If a logout link is found, we are logged in. While not perfect, this is |
452
|
|
|
// how Drupal SimpleTests currently work as well. |
453
|
|
|
$element = $session->getPage(); |
454
|
|
|
return $element->findLink($this->getDrupalText('log_out')); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
/** |
458
|
|
|
* User with a given role is already logged in. |
459
|
|
|
* |
460
|
|
|
* @param string $role |
461
|
|
|
* A single role, or multiple comma-separated roles in a single string. |
462
|
|
|
* |
463
|
|
|
* @return boolean |
464
|
|
|
* Returns TRUE if the current logged in user has this role (or roles). |
465
|
|
|
*/ |
466
|
|
|
public function loggedInWithRole($role) { |
467
|
|
|
return $this->loggedIn() && $this->user && isset($this->user->role) && $this->user->role == $role; |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
} |
471
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.