Completed
Pull Request — master (#300)
by
unknown
02:11
created

RawDrupalContext::getRandom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
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
9
use Drupal\DrupalDriverManager;
10
use Drupal\DrupalUserManagerInterface;
11
12
use Drupal\DrupalExtension\Hook\Scope\AfterLanguageEnableScope;
13
use Drupal\DrupalExtension\Hook\Scope\AfterNodeCreateScope;
14
use Drupal\DrupalExtension\Hook\Scope\AfterTermCreateScope;
15
use Drupal\DrupalExtension\Hook\Scope\AfterUserCreateScope;
16
use Drupal\DrupalExtension\Hook\Scope\AfterEntityCreateScope;
17
use Drupal\DrupalExtension\Hook\Scope\BaseEntityScope;
18
use Drupal\DrupalExtension\Hook\Scope\BeforeLanguageEnableScope;
19
use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
20
use Drupal\DrupalExtension\Hook\Scope\BeforeUserCreateScope;
21
use Drupal\DrupalExtension\Hook\Scope\BeforeTermCreateScope;
22
use Drupal\DrupalExtension\Hook\Scope\BeforeEntityCreateScope;
23
24
/**
25
 * Provides the raw functionality for interacting with Drupal.
26
 */
27
class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface {
28
29
  /**
30
   * Drupal driver manager.
31
   *
32
   * @var \Drupal\DrupalDriverManager
33
   */
34
  private $drupal;
35
36
  /**
37
   * Test parameters.
38
   *
39
   * @var array
40
   */
41
  private $drupalParameters;
42
43
  /**
44
   * Event dispatcher object.
45
   *
46
   * @var \Behat\Testwork\Hook\HookDispatcher
47
   */
48
  protected $dispatcher;
49
50
  /**
51
   * Drupal user manager.
52
   *
53
   * @var \Drupal\DrupalUserManagerInterface
54
   */
55
  protected $userManager;
56
57
  /**
58
   * Keep track of nodes so they can be cleaned up.
59
   *
60
   * @var array
61
   */
62
  protected $nodes = array();
63
64
  /**
65
   * Keep track of all terms that are created so they can easily be removed.
66
   *
67
   * @var array
68
   */
69
  protected $terms = array();
70
71
  /**
72
   * Keep track of any roles that are created so they can easily be removed.
73
   *
74
   * @var array
75
   */
76
  protected $roles = array();
77
78
  /**
79
   * Keep track of any languages that are created so they can easily be removed.
80
   *
81
   * @var array
82
   */
83
  protected $languages = array();
84
85
  /**
86
   * Keep track of any entities that are created so they can easily be removed.
87
   *
88
   * @var array
89
   */
90
  protected $entities = array();
91
92
  /**
93
   * {@inheritDoc}
94
   */
95
  public function setDrupal(DrupalDriverManager $drupal) {
96
    $this->drupal = $drupal;
97
  }
98
99
  /**
100
   * {@inheritDoc}
101
   */
102
  public function getDrupal() {
103
    return $this->drupal;
104
  }
105
106
  /**
107
   * {@inheritDoc}
108
   */
109
  public function setUserManager(DrupalUserManagerInterface $userManager) {
110
    $this->userManager = $userManager;
111
  }
112
113
  /**
114
   * {@inheritdoc}
115
   */
116
  public function getUserManager() {
117
    return $this->userManager;
118
  }
119
120
  /**
121
   * Magic setter.
122
   */
123
  public function __set($name, $value) {
124
    switch ($name) {
125
      case 'user':
126
        trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->setCurrentUser() instead.', E_USER_DEPRECATED);
127
        // Set the user on the user manager service, so it is shared between all
128
        // contexts.
129
        $this->getUserManager()->setCurrentUser($value);
130
        break;
131
132
      case 'users':
133
        trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->addUser() instead.', E_USER_DEPRECATED);
134
        // Set the user on the user manager service, so it is shared between all
135
        // contexts.
136
        if (empty($value)) {
137
          $this->getUserManager()->clearUsers();
138
        }
139
        else {
140
          foreach ($value as $user) {
141
            $this->getUserManager()->addUser($user);
142
          }
143
        }
144
        break;
145
    }
146
  }
147
148
  /**
149
   * Magic getter.
150
   */
151
  public function __get($name) {
152
    switch ($name) {
153
      case 'user':
154
        trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->getCurrentUser() instead.', E_USER_DEPRECATED);
155
        // Returns the current user from the user manager service. This is shared
156
        // between all contexts.
157
        return $this->getUserManager()->getCurrentUser();
158
159
      case 'users':
160
        trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->getUsers() instead.', E_USER_DEPRECATED);
161
        // Returns the current user from the user manager service. This is shared
162
        // between all contexts.
163
        return $this->getUserManager()->getUsers();
164
    }
165
  }
166
167
  /**
168
   * {@inheritdoc}
169
   */
170
  public function setDispatcher(HookDispatcher $dispatcher) {
171
    $this->dispatcher = $dispatcher;
172
  }
173
174
  /**
175
   * Set parameters provided for Drupal.
176
   */
177
  public function setDrupalParameters(array $parameters) {
178
    $this->drupalParameters = $parameters;
179
  }
180
181
  /**
182
   * Returns a specific Drupal parameter.
183
   *
184
   * @param string $name
185
   *   Parameter name.
186
   *
187
   * @return mixed
188
   */
189
  public function getDrupalParameter($name) {
190
    return isset($this->drupalParameters[$name]) ? $this->drupalParameters[$name] : NULL;
191
  }
192
193
  /**
194
   * Returns a specific Drupal text value.
195
   *
196
   * @param string $name
197
   *   Text value name, such as 'log_out', which corresponds to the default 'Log
198
   *   out' link text.
199
   * @throws \Exception
200
   * @return
201
   */
202 View Code Duplication
  public function getDrupalText($name) {
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...
203
    $text = $this->getDrupalParameter('text');
204
    if (!isset($text[$name])) {
205
      throw new \Exception(sprintf('No such Drupal string: %s', $name));
206
    }
207
    return $text[$name];
208
  }
209
210
  /**
211
   * Returns a specific css selector.
212
   *
213
   * @param $name
214
   *   string CSS selector name
215
   */
216 View Code Duplication
  public function getDrupalSelector($name) {
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...
217
    $text = $this->getDrupalParameter('selectors');
218
    if (!isset($text[$name])) {
219
      throw new \Exception(sprintf('No such selector configured: %s', $name));
220
    }
221
    return $text[$name];
222
  }
223
224
  /**
225
   * Get active Drupal Driver.
226
   *
227
   * @return \Drupal\Driver\DrupalDriver
228
   */
229
  public function getDriver($name = NULL) {
230
    return $this->getDrupal()->getDriver($name);
231
  }
232
233
  /**
234
   * Get driver's random generator.
235
   */
236
  public function getRandom() {
237
    return $this->getDriver()->getRandom();
238
  }
239
240
  /**
241
   * Massage node values to match the expectations on different Drupal versions.
242
   *
243
   * @beforeNodeCreate
244
   */
245
  public function alterNodeParameters(BeforeNodeCreateScope $scope) {
246
    $node = $scope->getEntity();
247
248
    // Get the Drupal API version if available. This is not available when
249
    // using e.g. the BlackBoxDriver or DrushDriver.
250
    $api_version = NULL;
251
    $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...
252
    if ($driver instanceof \Drupal\Driver\DrupalDriver) {
253
      $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...
254
    }
255
256
    // On Drupal 8 the timestamps should be in UNIX time.
257
    switch ($api_version) {
258
      case 8:
259
        foreach (array('changed', 'created', 'revision_timestamp') as $field) {
260
          if (!empty($node->$field) && !is_numeric($node->$field)) {
261
            $node->$field = strtotime($node->$field);
262
          }
263
        }
264
      break;
265
    }
266
  }
267
268
  /**
269
   * Remove any created nodes.
270
   *
271
   * @AfterScenario
272
   */
273
  public function cleanNodes() {
274
    // Remove any nodes that were created.
275
    foreach ($this->nodes as $node) {
276
      $this->getDriver()->nodeDelete($node);
277
    }
278
    $this->nodes = array();
279
  }
280
281
  /**
282
   * Remove any created users.
283
   *
284
   * @AfterScenario
285
   */
286
  public function cleanUsers() {
287
    // Remove any users that were created.
288
    if ($this->userManager->hasUsers()) {
289
      foreach ($this->userManager->getUsers() as $user) {
290
        $this->getDriver()->userDelete($user);
291
      }
292
      $this->getDriver()->processBatch();
293
      $this->userManager->clearUsers();
294
      if ($this->loggedIn()) {
295
        $this->logout();
296
      }
297
    }
298
  }
299
300
  /**
301
   * Remove any created terms.
302
   *
303
   * @AfterScenario
304
   */
305
  public function cleanTerms() {
306
    // Remove any terms that were created.
307
    foreach ($this->terms as $term) {
308
      $this->getDriver()->termDelete($term);
309
    }
310
    $this->terms = array();
311
  }
312
313
  /**
314
   * Remove any created roles.
315
   *
316
   * @AfterScenario
317
   */
318
  public function cleanRoles() {
319
    // Remove any roles that were created.
320
    foreach ($this->roles as $rid) {
321
      $this->getDriver()->roleDelete($rid);
322
    }
323
    $this->roles = array();
324
  }
325
326
  /**
327
   * Remove any created languages.
328
   *
329
   * @AfterScenario
330
   */
331
  public function cleanLanguages() {
332
    // Delete any languages that were created.
333
    foreach ($this->languages as $language) {
334
      $this->getDriver()->languageDelete($language);
335
      unset($this->languages[$language->langcode]);
336
    }
337
  }
338
339
  /**
340
   * Clear static caches.
341
   *
342
   * @AfterScenario @api
343
   */
344
  public function clearStaticCaches() {
345
    $this->getDriver()->clearStaticCaches();
346
  }
347
348
  /**
349
   * Dispatch scope hooks.
350
   *
351
   * @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...
352
   *   The entity scope to dispatch.
353
   * @param \stdClass $entity
354
   *   The entity.
355
   */
356
  protected function dispatchHooks($scopeType, \stdClass $entity) {
357
    $fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType;
358
    $scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity);
359
    $callResults = $this->dispatcher->dispatchScopeHooks($scope);
360
361
    // The dispatcher suppresses exceptions, throw them here if there are any.
362
    foreach ($callResults as $result) {
363
      if ($result->hasException()) {
364
        $exception = $result->getException();
365
        throw $exception;
366
      }
367
    }
368
  }
369
370
  /**
371
   * Create a node.
372
   *
373
   * @return object
374
   *   The created node.
375
   */
376 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...
377
    $this->dispatchHooks('BeforeNodeCreateScope', $node);
378
    $this->parseEntityFields('node', $node);
379
    $saved = $this->getDriver()->createNode($node);
380
    $this->dispatchHooks('AfterNodeCreateScope', $saved);
381
    $this->nodes[] = $saved;
382
    return $saved;
383
  }
384
385
  /**
386
   * Parse multi-value fields. Possible formats:
387
   *    A, B, C
388
   *    A - B, C - D, E - F
389
   *
390
   * @param string $entity_type
391
   *   The entity type.
392
   * @param \stdClass $entity
393
   *   An object containing the entity properties and fields as properties.
394
   */
395
  public function parseEntityFields($entity_type, \stdClass $entity) {
396
    $multicolumn_field = '';
397
    $multicolumn_fields = array();
398
399
    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...
400
      // Reset the multicolumn field if the field name does not contain a column.
401
      if (strpos($field, ':') === FALSE) {
402
        $multicolumn_field = '';
403
      }
404
      // Start tracking a new multicolumn field if the field name contains a ':'
405
      // which is preceded by at least 1 character.
406
      elseif (strpos($field, ':', 1) !== FALSE) {
407
        list($multicolumn_field, $multicolumn_column) = explode(':', $field);
408
      }
409
      // If a field name starts with a ':' but we are not yet tracking a
410
      // multicolumn field we don't know to which field this belongs.
411
      elseif (empty($multicolumn_field)) {
412
        throw new \Exception('Field name missing for ' . $field);
413
      }
414
      // Update the column name if the field name starts with a ':' and we are
415
      // already tracking a multicolumn field.
416
      else {
417
        $multicolumn_column = substr($field, 1);
418
      }
419
420
      $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...
421
      $field_name = $multicolumn_field ?: $field;
422
      if ($this->getDriver()->isField($entity_type, $field_name)) {
423
        // Split up multiple values in multi-value fields.
424
        $values = array();
425
        foreach (explode(', ', $field_value) as $key => $value) {
426
          $columns = $value;
427
          // Split up field columns if the ' - ' separator is present.
428
          if (strstr($value, ' - ') !== FALSE) {
429
            $columns = array();
430
            foreach (explode(' - ', $value) as $column) {
431
              // Check if it is an inline named column.
432
              if (!$is_multicolumn && strpos($column, ': ', 1) !== FALSE) {
433
                list ($key, $column) = explode(': ', $column);
434
                $columns[$key] = $column;
435
              }
436
              else {
437
                $columns[] = $column;
438
              }
439
            }
440
          }
441
          // Use the column name if we are tracking a multicolumn field.
442
          if ($is_multicolumn) {
443
            $multicolumn_fields[$multicolumn_field][$key][$multicolumn_column] = $columns;
444
            unset($entity->$field);
445
          }
446
          else {
447
            $values[] = $columns;
448
          }
449
        }
450
        // Replace regular fields inline in the entity after parsing.
451
        if (!$is_multicolumn) {
452
          $entity->$field_name = $values;
453
        }
454
      }
455
    }
456
457
    // Add the multicolumn fields to the entity.
458
    foreach ($multicolumn_fields as $field_name => $columns) {
459
      $entity->$field_name = $columns;
460
    }
461
  }
462
463
  /**
464
   * Create a user.
465
   *
466
   * @return object
467
   *   The created user.
468
   */
469
  public function userCreate($user) {
470
    $this->dispatchHooks('BeforeUserCreateScope', $user);
471
    $this->parseEntityFields('user', $user);
472
    $this->getDriver()->userCreate($user);
473
    $this->dispatchHooks('AfterUserCreateScope', $user);
474
    $this->userManager->addUser($user);
475
    return $user;
476
  }
477
478
  /**
479
   * Create a term.
480
   *
481
   * @return object
482
   *   The created term.
483
   */
484 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...
485
    $this->dispatchHooks('BeforeTermCreateScope', $term);
486
    $this->parseEntityFields('taxonomy_term', $term);
487
    $saved = $this->getDriver()->createTerm($term);
488
    $this->dispatchHooks('AfterTermCreateScope', $saved);
489
    $this->terms[] = $saved;
490
    return $saved;
491
  }
492
493
  /**
494
   * Creates a language.
495
   *
496
   * @param \stdClass $language
497
   *   An object with the following properties:
498
   *   - langcode: the langcode of the language to create.
499
   *
500
   * @return object|FALSE
501
   *   The created language, or FALSE if the language was already created.
502
   */
503
  public function languageCreate(\stdClass $language) {
504
    $this->dispatchHooks('BeforeLanguageCreateScope', $language);
505
    $language = $this->getDriver()->languageCreate($language);
506
    if ($language) {
507
      $this->dispatchHooks('AfterLanguageCreateScope', $language);
508
      $this->languages[$language->langcode] = $language;
509
    }
510
    return $language;
511
  }
512
513
  /**
514
   * Log-in the given user.
515
   *
516
   * @param \stdClass $user
517
   *   The user to log in.
518
   */
519
  public function login(\stdClass $user) {
520
    $manager = $this->getUserManager();
521
522
    // Check if logged in.
523
    if ($this->loggedIn()) {
524
      $this->logout();
525
    }
526
527
    $this->getSession()->visit($this->locatePath('/user'));
528
    $element = $this->getSession()->getPage();
529
    $element->fillField($this->getDrupalText('username_field'), $user->name);
530
    $element->fillField($this->getDrupalText('password_field'), $user->pass);
531
    $submit = $element->findButton($this->getDrupalText('log_in'));
532
    if (empty($submit)) {
533
      throw new \Exception(sprintf("No submit button at %s", $this->getSession()->getCurrentUrl()));
534
    }
535
536
    // Log in.
537
    $submit->click();
538
539
    if (!$this->loggedIn()) {
540
      if (isset($user->role)) {
541
        throw new \Exception(sprintf("Unable to determine if logged in because 'log_out' link cannot be found for user '%s' with role '%s'", $user->name, $user->role));
542
      }
543
      else {
544
        throw new \Exception(sprintf("Unable to determine if logged in because 'log_out' link cannot be found for user '%s'", $user->name));
545
      }
546
    }
547
548
    $manager->setCurrentUser($user);
549
  }
550
551
  /**
552
   * Logs the current user out.
553
   */
554
  public function logout() {
555
    $this->getSession()->visit($this->locatePath('/user/logout'));
556
    $this->getUserManager()->setCurrentUser(FALSE);
557
  }
558
559
  /**
560
   * Determine if the a user is already logged in.
561
   *
562
   * @return boolean
563
   *   Returns TRUE if a user is logged in for this session.
564
   */
565
  public function loggedIn() {
566
    $session = $this->getSession();
567
    $page = $session->getPage();
568
569
    // Look for a css selector to determine if a user is logged in.
570
    // Default is the logged-in class on the body tag.
571
    // Which should work with almost any theme.
572
    try {
573
      if ($page->has('css', $this->getDrupalSelector('logged_in_selector'))) {
574
        return TRUE;
575
      }
576
    } catch (DriverException $e) {
577
      // This test may fail if the driver did not load any site yet.
578
    }
579
580
    // Some themes do not add that class to the body, so lets check if the
581
    // login form is displayed on /user/login.
582
    $session->visit($this->locatePath('/user/login'));
583
    if (!$page->has('css', $this->getDrupalSelector('login_form_selector'))) {
584
      return TRUE;
585
    }
586
587
    $session->visit($this->locatePath('/'));
588
589
    // As a last resort, if a logout link is found, we are logged in. While not
590
    // perfect, this is how Drupal SimpleTests currently work as well.
591
    if ($page->findLink($this->getDrupalText('log_out'))) {
592
      return TRUE;
593
    }
594
595
    // The user appears to be anonymous. Clear the current user from the user
596
    // manager so this reflects the actual situation.
597
    $this->getUserManager()->setCurrentUser(FALSE);
598
    return FALSE;
599
  }
600
601
  /**
602
   * User with a given role is already logged in.
603
   *
604
   * @param string $role
605
   *   A single role, or multiple comma-separated roles in a single string.
606
   *
607
   * @return boolean
608
   *   Returns TRUE if the current logged in user has this role (or roles).
609
   */
610
  public function loggedInWithRole($role) {
611
    return $this->loggedIn() && $this->getUserManager()->currentUserHasRole($role);
612
  }
613
614
  /**
615
   * Create an entity.
616
   *
617
   * @return object
618
   *   The created entity.
619
   */
620
  public function entityCreate($entity_type, $entity) {
621
	if (empty($entity_type)) {
622
	        throw new \Exception("Steps must specify an entity type in order to create an entity.");
623
	}
624
    // We want hooks & cleanup to know the entity type
625
    $entity->step_entity_type = $entity_type;
626
	
627
    $this->dispatchHooks('BeforeEntityCreateScope', $entity);
628
    $this->parseEntityFields($entity_type, $entity);
629
    $saved = $this->getDriver()->createEntity($entity_type, $entity);
0 ignored issues
show
Bug introduced by
The method createEntity() does not seem to exist on object<Drupal\Driver\DrupalDriver>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
630
    $saved->step_entity_type = $entity_type;
631
    $this->dispatchHooks('AfterEntityCreateScope', $saved);
632
    $this->entities[] = $saved;
633
    return $saved;
634
  }
635
636
  /**
637
   * Remove any content entities created by entityCreate(),
638
   * but not those created by nodeCreate(), termCreate() or userCreate().
639
   *
640
   * @AfterScenario
641
   */
642
  public function cleanEntities() {
643
    foreach ($this->entities as $entity) {
644
      $this->getDriver()->entityDelete($entity->step_entity_type, $entity);
0 ignored issues
show
Bug introduced by
The method entityDelete() does not seem to exist on object<Drupal\Driver\DrupalDriver>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
645
    }
646
    $this->entities = array();
647
  }
648
649
}
650