Completed
Pull Request — master (#300)
by
unknown
01:31
created

RawDrupalContext::getAuthenticationManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Drupal\DrupalExtension\Context;
4
5
use Behat\MinkExtension\Context\RawMinkContext;
6
use Behat\Mink\Exception\DriverException;
7
use Behat\Testwork\Hook\HookDispatcher;
8
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
9
10
use Drupal\DrupalDriverManager;
11
use Drupal\DrupalExtension\DrupalParametersTrait;
12
use Drupal\DrupalExtension\Manager\DrupalAuthenticationManagerInterface;
13
use Drupal\DrupalExtension\Manager\DrupalUserManagerInterface;
14
15
use Drupal\DrupalExtension\Hook\Scope\AfterLanguageEnableScope;
16
use Drupal\DrupalExtension\Hook\Scope\AfterNodeCreateScope;
17
use Drupal\DrupalExtension\Hook\Scope\AfterTermCreateScope;
18
use Drupal\DrupalExtension\Hook\Scope\AfterUserCreateScope;
19
use Drupal\DrupalExtension\Hook\Scope\AfterEntityCreateScope;
20
use Drupal\DrupalExtension\Hook\Scope\BaseEntityScope;
21
use Drupal\DrupalExtension\Hook\Scope\BeforeLanguageEnableScope;
22
use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
23
use Drupal\DrupalExtension\Hook\Scope\BeforeUserCreateScope;
24
use Drupal\DrupalExtension\Hook\Scope\BeforeTermCreateScope;
25
use Drupal\DrupalExtension\Manager\FastLogoutInterface;
26
use Drupal\DrupalExtension\Hook\Scope\BeforeEntityCreateScope;
27
28
/**
29
 * Provides the raw functionality for interacting with Drupal.
30
 */
31
class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface
32
{
33
34
    use DrupalParametersTrait;
35
36
  /**
37
   * Drupal driver manager.
38
   *
39
   * @var \Drupal\DrupalDriverManager
40
   */
41
    private $drupal;
42
43
  /**
44
   * Event dispatcher object.
45
   *
46
   * @var \Behat\Testwork\Hook\HookDispatcher
47
   */
48
    protected $dispatcher;
49
50
  /**
51
   * Drupal authentication manager.
52
   *
53
   * @var \Drupal\DrupalExtension\Manager\DrupalAuthenticationManagerInterface
54
   */
55
    protected $authenticationManager;
56
57
  /**
58
   * Drupal user manager.
59
   *
60
   * @var \Drupal\DrupalExtension\Manager\DrupalUserManagerInterface
61
   */
62
    protected $userManager;
63
64
  /**
65
   * Keep track of nodes so they can be cleaned up.
66
   *
67
   * @var array
68
   */
69
    protected $nodes = array();
70
71
  /**
72
   * Keep track of all terms that are created so they can easily be removed.
73
   *
74
   * @var array
75
   */
76
    protected $terms = array();
77
78
  /**
79
   * Keep track of any roles that are created so they can easily be removed.
80
   *
81
   * @var array
82
   */
83
    protected $roles = array();
84
85
  /**
86
   * Keep track of any languages that are created so they can easily be removed.
87
   *
88
   * @var array
89
   */
90
    protected $languages = array();
91
92
  /**
93
   * Keep track of any entities that are created so they can easily be removed.
94
   *
95
   * @var array
96
   */
97
  protected $entities = array();
98
99
  /**
100
   * {@inheritDoc}
101
   */
102
    public function setDrupal(DrupalDriverManager $drupal)
103
    {
104
        $this->drupal = $drupal;
105
    }
106
107
  /**
108
   * {@inheritDoc}
109
   */
110
    public function getDrupal()
111
    {
112
        return $this->drupal;
113
    }
114
115
  /**
116
   * {@inheritDoc}
117
   */
118
    public function setUserManager(DrupalUserManagerInterface $userManager)
119
    {
120
        $this->userManager = $userManager;
121
    }
122
123
  /**
124
   * {@inheritdoc}
125
   */
126
    public function getUserManager()
127
    {
128
        return $this->userManager;
129
    }
130
131
  /**
132
   * {@inheritdoc}
133
   */
134
    public function setAuthenticationManager(DrupalAuthenticationManagerInterface $authenticationManager)
135
    {
136
        $this->authenticationManager = $authenticationManager;
137
    }
138
139
  /**
140
   * {@inheritdoc}
141
   */
142
    public function getAuthenticationManager()
143
    {
144
        return $this->authenticationManager;
145
    }
146
147
  /**
148
   * Magic setter.
149
   */
150
    public function __set($name, $value)
151
    {
152
        switch ($name) {
153
            case 'user':
154
                trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->setCurrentUser() instead.', E_USER_DEPRECATED);
155
                // Set the user on the user manager service, so it is shared between all
156
                // contexts.
157
                $this->getUserManager()->setCurrentUser($value);
158
                break;
159
160
            case 'users':
161
                trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->addUser() instead.', E_USER_DEPRECATED);
162
                // Set the user on the user manager service, so it is shared between all
163
                // contexts.
164
                if (empty($value)) {
165
                    $this->getUserManager()->clearUsers();
166
                } else {
167
                    foreach ($value as $user) {
168
                        $this->getUserManager()->addUser($user);
169
                    }
170
                }
171
                break;
172
        }
173
    }
174
175
  /**
176
   * Magic getter.
177
   */
178
    public function __get($name)
179
    {
180
        switch ($name) {
181
            case 'user':
182
                trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->getCurrentUser() instead.', E_USER_DEPRECATED);
183
                // Returns the current user from the user manager service. This is shared
184
                // between all contexts.
185
                return $this->getUserManager()->getCurrentUser();
186
187
            case 'users':
188
                trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->getUsers() instead.', E_USER_DEPRECATED);
189
                // Returns the current user from the user manager service. This is shared
190
                // between all contexts.
191
                return $this->getUserManager()->getUsers();
192
        }
193
    }
194
195
  /**
196
   * {@inheritdoc}
197
   */
198
    public function setDispatcher(HookDispatcher $dispatcher)
199
    {
200
        $this->dispatcher = $dispatcher;
201
    }
202
203
  /**
204
   * Get active Drupal Driver.
205
   *
206
   * @return \Drupal\Driver\DrupalDriver
207
   */
208
    public function getDriver($name = null)
209
    {
210
        return $this->getDrupal()->getDriver($name);
211
    }
212
213
  /**
214
   * Get driver's random generator.
215
   */
216
    public function getRandom()
217
    {
218
        return $this->getDriver()->getRandom();
219
    }
220
221
  /**
222
   * Massage node values to match the expectations on different Drupal versions.
223
   *
224
   * @beforeNodeCreate
225
   */
226
    public static function alterNodeParameters(BeforeNodeCreateScope $scope)
227
    {
228
        $node = $scope->getEntity();
229
230
        // Get the Drupal API version if available. This is not available when
231
        // using e.g. the BlackBoxDriver or DrushDriver.
232
        $api_version = null;
233
        $driver = $scope->getContext()->getDrupal()->getDriver();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\Context as the method getDrupal() does only exist in the following implementations of said interface: Drupal\DrupalExtension\Context\ConfigContext, Drupal\DrupalExtension\Context\DrupalContext, Drupal\DrupalExtension\C...xt\DrupalSubContextBase, Drupal\DrupalExtension\Context\DrushContext, Drupal\DrupalExtension\Context\EntityContext, Drupal\DrupalExtension\Context\MessageContext, Drupal\DrupalExtension\Context\RawDrupalContext, FeatureContext, FooFoo.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
234
        if ($driver instanceof \Drupal\Driver\DrupalDriver) {
235
            $api_version = $scope->getContext()->getDrupal()->getDriver()->version;
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\Context as the method getDrupal() does only exist in the following implementations of said interface: Drupal\DrupalExtension\Context\ConfigContext, Drupal\DrupalExtension\Context\DrupalContext, Drupal\DrupalExtension\C...xt\DrupalSubContextBase, Drupal\DrupalExtension\Context\DrushContext, Drupal\DrupalExtension\Context\EntityContext, Drupal\DrupalExtension\Context\MessageContext, Drupal\DrupalExtension\Context\RawDrupalContext, FeatureContext, FooFoo.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
236
        }
237
238
        // On Drupal 8 the timestamps should be in UNIX time.
239
        switch ($api_version) {
240
            case 8:
241
                foreach (array('changed', 'created', 'revision_timestamp') as $field) {
242
                    if (!empty($node->$field) && !is_numeric($node->$field)) {
243
                        $node->$field = strtotime($node->$field);
244
                    }
245
                }
246
                break;
247
        }
248
    }
249
250
  /**
251
   * Remove any created nodes.
252
   *
253
   * @AfterScenario
254
   */
255
    public function cleanNodes()
256
    {
257
        // Remove any nodes that were created.
258
        foreach ($this->nodes as $node) {
259
            $this->getDriver()->nodeDelete($node);
260
        }
261
        $this->nodes = array();
262
    }
263
264
  /**
265
   * Remove any created users.
266
   *
267
   * @AfterScenario
268
   */
269
    public function cleanUsers()
270
    {
271
        // Remove any users that were created.
272
        if ($this->userManager->hasUsers()) {
273
            foreach ($this->userManager->getUsers() as $user) {
274
                $this->getDriver()->userDelete($user);
275
            }
276
            $this->getDriver()->processBatch();
277
            $this->userManager->clearUsers();
278
            // If the authentication manager supports logout, no need to check if the user is logged in.
279
            if ($this->getAuthenticationManager() instanceof FastLogoutInterface) {
280
                $this->logout(true);
281
            } elseif ($this->loggedIn()) {
282
                $this->logout();
283
            }
284
        }
285
    }
286
287
  /**
288
   * Remove any created terms.
289
   *
290
   * @AfterScenario
291
   */
292
    public function cleanTerms()
293
    {
294
        // Remove any terms that were created.
295
        foreach ($this->terms as $term) {
296
            $this->getDriver()->termDelete($term);
297
        }
298
        $this->terms = array();
299
    }
300
301
  /**
302
   * Remove any created roles.
303
   *
304
   * @AfterScenario
305
   */
306
    public function cleanRoles()
307
    {
308
        // Remove any roles that were created.
309
        foreach ($this->roles as $rid) {
310
            $this->getDriver()->roleDelete($rid);
311
        }
312
        $this->roles = array();
313
    }
314
315
  /**
316
   * Remove any created languages.
317
   *
318
   * @AfterScenario
319
   */
320
    public function cleanLanguages()
321
    {
322
        // Delete any languages that were created.
323
        foreach ($this->languages as $language) {
324
            $this->getDriver()->languageDelete($language);
325
            unset($this->languages[$language->langcode]);
326
        }
327
    }
328
329
  /**
330
   * Clear static caches.
331
   *
332
   * @AfterScenario @api
333
   */
334
    public function clearStaticCaches()
335
    {
336
        $this->getDriver()->clearStaticCaches();
337
    }
338
339
  /**
340
   * Dispatch scope hooks.
341
   *
342
   * @param string $scope
0 ignored issues
show
Bug introduced by
There is no parameter named $scope. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
343
   *   The entity scope to dispatch.
344
   * @param \stdClass $entity
345
   *   The entity.
346
   */
347
    protected function dispatchHooks($scopeType, \stdClass $entity)
348
    {
349
        $fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType;
350
        $scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity);
351
        $callResults = $this->dispatcher->dispatchScopeHooks($scope);
352
353
        // The dispatcher suppresses exceptions, throw them here if there are any.
354
        foreach ($callResults as $result) {
355
            if ($result->hasException()) {
356
                $exception = $result->getException();
357
                throw $exception;
358
            }
359
        }
360
    }
361
362
  /**
363
   * Create a node.
364
   *
365
   * @return object
366
   *   The created node.
367
   */
368 View Code Duplication
    public function nodeCreate($node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
369
    {
370
        $this->dispatchHooks('BeforeNodeCreateScope', $node);
371
        $this->parseEntityFields('node', $node);
372
        $saved = $this->getDriver()->createNode($node);
373
        $this->dispatchHooks('AfterNodeCreateScope', $saved);
374
        $this->nodes[] = $saved;
375
        return $saved;
376
    }
377
378
  /**
379
   * Parses the field values and turns them into the format expected by Drupal.
380
   *
381
   * Multiple values in a single field must be separated by commas. Wrap the
382
   * field value in double quotes in case it should contain a comma.
383
   *
384
   * Compound field properties are identified using a ':' operator, either in
385
   * the column heading or in the cell. If multiple properties are present in a
386
   * single cell, they must be separated using ' - ', and values should not
387
   * contain ':' or ' - '.
388
   *
389
   * Possible formats for the values:
390
   *   A
391
   *   A, B, "a value, containing a comma"
392
   *   A - B
393
   *   x: A - y: B
394
   *   A - B, C - D, "E - F"
395
   *   x: A - y: B,  x: C - y: D,  "x: E - y: F"
396
   *
397
   * See field_handlers.feature for examples of usage.
398
   *
399
   * @param string $entity_type
400
   *   The entity type.
401
   * @param \stdClass $entity
402
   *   An object containing the entity properties and fields as properties.
403
   *
404
   * @throws \Exception
405
   *   Thrown when a field name is invalid.
406
   */
407
    public function parseEntityFields($entity_type, \stdClass $entity)
408
    {
409
        $multicolumn_field = '';
410
        $multicolumn_fields = array();
411
412
        foreach (clone $entity as $field => $field_value) {
0 ignored issues
show
Bug introduced by
The expression clone $entity of type object<stdClass> is not traversable.
Loading history...
413
            // Reset the multicolumn field if the field name does not contain a column.
414
            if (strpos($field, ':') === false) {
415
                $multicolumn_field = '';
416
            } // Start tracking a new multicolumn field if the field name contains a ':'
417
            // which is preceded by at least 1 character.
418
            elseif (strpos($field, ':', 1) !== false) {
419
                list($multicolumn_field, $multicolumn_column) = explode(':', $field);
420
            } // If a field name starts with a ':' but we are not yet tracking a
421
            // multicolumn field we don't know to which field this belongs.
422
            elseif (empty($multicolumn_field)) {
423
                throw new \Exception('Field name missing for ' . $field);
424
            } // Update the column name if the field name starts with a ':' and we are
425
            // already tracking a multicolumn field.
426
            else {
427
                $multicolumn_column = substr($field, 1);
428
            }
429
430
            $is_multicolumn = $multicolumn_field && $multicolumn_column;
0 ignored issues
show
Bug introduced by
The variable $multicolumn_column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
431
            $field_name = $multicolumn_field ?: $field;
432
            if ($this->getDriver()->isField($entity_type, $field_name)) {
433
                // Split up multiple values in multi-value fields.
434
                $values = array();
435
                foreach (str_getcsv($field_value) as $key => $value) {
436
                    $value = trim($value);
437
                    $columns = $value;
438
                    // Split up field columns if the ' - ' separator is present.
439
                    if (strstr($value, ' - ') !== false) {
440
                        $columns = array();
441
                        foreach (explode(' - ', $value) as $column) {
442
                            // Check if it is an inline named column.
443
                            if (!$is_multicolumn && strpos($column, ': ', 1) !== false) {
444
                                list ($key, $column) = explode(': ', $column);
445
                                $columns[$key] = $column;
446
                            } else {
447
                                $columns[] = $column;
448
                            }
449
                        }
450
                    }
451
                    // Use the column name if we are tracking a multicolumn field.
452
                    if ($is_multicolumn) {
453
                        $multicolumn_fields[$multicolumn_field][$key][$multicolumn_column] = $columns;
454
                        unset($entity->$field);
455
                    } else {
456
                        $values[] = $columns;
457
                    }
458
                }
459
                // Replace regular fields inline in the entity after parsing.
460
                if (!$is_multicolumn) {
461
                    $entity->$field_name = $values;
462
                    // Don't specify any value if the step author has left it blank.
463
                    if ($field_value === '') {
464
                        unset($entity->$field_name);
465
                    }
466
                }
467
            }
468
        }
469
470
        // Add the multicolumn fields to the entity.
471
        foreach ($multicolumn_fields as $field_name => $columns) {
472
            // Don't specify any value if the step author has left it blank.
473
            if (count(array_filter($columns, function ($var) {
474
                return ($var !== '');
475
            })) > 0) {
476
                $entity->$field_name = $columns;
477
            }
478
        }
479
    }
480
481
  /**
482
   * Create a user.
483
   *
484
   * @return object
485
   *   The created user.
486
   */
487
    public function userCreate($user)
488
    {
489
        $this->dispatchHooks('BeforeUserCreateScope', $user);
490
        $this->parseEntityFields('user', $user);
491
        $this->getDriver()->userCreate($user);
492
        $this->dispatchHooks('AfterUserCreateScope', $user);
493
        $this->userManager->addUser($user);
494
        return $user;
495
    }
496
497
  /**
498
   * Create a term.
499
   *
500
   * @return object
501
   *   The created term.
502
   */
503 View Code Duplication
    public function termCreate($term)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
504
    {
505
        $this->dispatchHooks('BeforeTermCreateScope', $term);
506
        $this->parseEntityFields('taxonomy_term', $term);
507
        $saved = $this->getDriver()->createTerm($term);
508
        $this->dispatchHooks('AfterTermCreateScope', $saved);
509
        $this->terms[] = $saved;
510
        return $saved;
511
    }
512
513
  /**
514
   * Creates a language.
515
   *
516
   * @param \stdClass $language
517
   *   An object with the following properties:
518
   *   - langcode: the langcode of the language to create.
519
   *
520
   * @return object|FALSE
521
   *   The created language, or FALSE if the language was already created.
522
   */
523
    public function languageCreate(\stdClass $language)
524
    {
525
        $this->dispatchHooks('BeforeLanguageCreateScope', $language);
526
        $language = $this->getDriver()->languageCreate($language);
527
        if ($language) {
528
            $this->dispatchHooks('AfterLanguageCreateScope', $language);
529
            $this->languages[$language->langcode] = $language;
530
        }
531
        return $language;
532
    }
533
534
  /**
535
   * Log-in the given user.
536
   *
537
   * @param \stdClass $user
538
   *   The user to log in.
539
   */
540
    public function login(\stdClass $user)
541
    {
542
        $this->getAuthenticationManager()->logIn($user);
543
    }
544
545
  /**
546
   * Logs the current user out.
547
   *
548
   * @param bool $fast
549
   *   Utilize direct logout by session if available.
550
   */
551
    public function logout($fast = false)
552
    {
553
        if ($fast && $this->getAuthenticationManager() instanceof FastLogoutInterface) {
554
            $this->getAuthenticationManager()->fastLogout();
555
        } else {
556
            $this->getAuthenticationManager()->logOut();
557
        }
558
    }
559
560
  /**
561
   * Determine if the a user is already logged in.
562
   *
563
   * @return boolean
564
   *   Returns TRUE if a user is logged in for this session.
565
   */
566
    public function loggedIn()
567
    {
568
        return $this->getAuthenticationManager()->loggedIn();
569
    }
570
571
  /**
572
   * User with a given role is already logged in.
573
   *
574
   * @param string $role
575
   *   A single role, or multiple comma-separated roles in a single string.
576
   *
577
   * @return boolean
578
   *   Returns TRUE if the current logged in user has this role (or roles).
579
   */
580
    public function loggedInWithRole($role)
581
    {
582
        return $this->loggedIn() && $this->getUserManager()->currentUserHasRole($role);
583
    }
584
585
  /**
586
   * Create an entity.
587
   *
588
   * @return object
589
   *   The created entity.
590
   */
591
  public function entityCreate($entity_type, $entity) {
592
	if (empty($entity_type)) {
593
	        throw new \Exception("Steps must specify an entity type in order to create an entity.");
594
	}
595
    // We want hooks & cleanup to know the entity type
596
    $entity->step_entity_type = $entity_type;
597
	
598
    $this->dispatchHooks('BeforeEntityCreateScope', $entity);
599
    $this->parseEntityFields($entity_type, $entity);
600
    $saved = $this->getDriver()->createEntity($entity_type, $entity);
601
    $saved->step_entity_type = $entity_type;
602
    $this->dispatchHooks('AfterEntityCreateScope', $saved);
603
    $this->entities[] = $saved;
604
    return $saved;
605
  }
606
607
  /**
608
   * Remove any content entities created by entityCreate(),
609
   * but not those created by nodeCreate(), termCreate() or userCreate().
610
   *
611
   * @AfterScenario
612
   */
613
  public function cleanEntities() {
614
    foreach ($this->entities as $entity) {
615
      $this->getDriver()->entityDelete($entity->step_entity_type, $entity);
616
    }
617
    $this->entities = array();
618
  }
619
620
  /**
621
   * Returns the Behat context that corresponds with the given class name.
622
   *
623
   * This is inspired by InitializedContextEnvironment::getContext() but also
624
   * returns subclasses of the given class name. This allows us to retrieve for
625
   * example DrupalContext even if it is overridden in a project.
626
   *
627
   * @param string $class
628
   *   A fully namespaced class name.
629
   *
630
   * @return \Behat\Behat\Context\Context|false
631
   *   The requested context, or FALSE if the context is not registered.
632
   *
633
   * @throws \Exception
634
   *   Thrown when the environment is not yet initialized, meaning that contexts
635
   *   cannot yet be retrieved.
636
   */
637
    protected function getContext($class)
638
    {
639
        /** @var InitializedContextEnvironment $environment */
640
        $environment = $this->drupal->getEnvironment();
641
        // Throw an exception if the environment is not yet initialized. To make
642
        // sure state doesn't leak between test scenarios, the environment is
643
        // reinitialized at the start of every scenario. If this code is executed
644
        // before a test scenario starts (e.g. in a `@BeforeScenario` hook) then the
645
        // contexts cannot yet be retrieved.
646
        if (!$environment instanceof InitializedContextEnvironment) {
647
            throw new \Exception('Cannot retrieve contexts when the environment is not yet initialized.');
648
        }
649
        foreach ($environment->getContexts() as $context) {
650
            if ($context instanceof $class) {
651
                return $context;
652
            }
653
        }
654
655
        return false;
656
    }
657
}
658