Completed
Pull Request — master (#396)
by
unknown
01:25
created

RawDrupalContext::getContext()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
666
      throw new \Exception('Cannot retrieve contexts when the environment is not yet initialized.');
667
    }
668
    foreach ($environment->getContexts() as $context) {
669
      if ($context instanceof $class) {
670
        return $context;
671
      }
672
    }
673
674
    return FALSE;
675
  }
676
677
}
678