Completed
Pull Request — master (#236)
by Pieter
02:25
created

RawDrupalContext::getRandom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
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\Testwork\Hook\HookDispatcher;
7
8
use Drupal\DrupalDriverManager;
9
10
use Drupal\DrupalExtension\Hook\Scope\AfterLanguageEnableScope;
11
use Drupal\DrupalExtension\Hook\Scope\AfterNodeCreateScope;
12
use Drupal\DrupalExtension\Hook\Scope\AfterTermCreateScope;
13
use Drupal\DrupalExtension\Hook\Scope\AfterUserCreateScope;
14
use Drupal\DrupalExtension\Hook\Scope\BaseEntityScope;
15
use Drupal\DrupalExtension\Hook\Scope\BeforeLanguageEnableScope;
16
use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
17
use Drupal\DrupalExtension\Hook\Scope\BeforeUserCreateScope;
18
use Drupal\DrupalExtension\Hook\Scope\BeforeTermCreateScope;
19
20
21
/**
22
 * Provides the raw functionality for interacting with Drupal.
23
 */
24
class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface {
25
26
  /**
27
   * Drupal driver manager.
28
   *
29
   * @var \Drupal\DrupalDriverManager
30
   */
31
  private $drupal;
32
33
  /**
34
   * Test parameters.
35
   *
36
   * @var array
37
   */
38
  private $drupalParameters;
39
40
  /**
41
   * Event dispatcher object.
42
   *
43
   * @var \Behat\Testwork\Hook\HookDispatcher
44
   */
45
  protected $dispatcher;
46
47
  /**
48
   * Keep track of nodes so they can be cleaned up.
49
   *
50
   * @var array
51
   */
52
  protected $nodes = array();
53
54
  /**
55
   * Current authenticated user.
56
   *
57
   * A value of FALSE denotes an anonymous user.
58
   *
59
   * @var stdClass|bool
60
   */
61
  public $user = FALSE;
62
63
  /**
64
   * Keep track of all users that are created so they can easily be removed.
65
   *
66
   * @var array
67
   */
68
  protected $users = array();
69
70
  /**
71
   * Keep track of all terms that are created so they can easily be removed.
72
   *
73
   * @var array
74
   */
75
  protected $terms = array();
76
77
  /**
78
   * Keep track of any roles that are created so they can easily be removed.
79
   *
80
   * @var array
81
   */
82
  protected $roles = array();
83
84
  /**
85
   * Keep track of any languages that are created so they can easily be removed.
86
   *
87
   * @var array
88
   */
89
  protected $languages = array();
90
91
  /**
92
   * {@inheritDoc}
93
   */
94
  public function setDrupal(DrupalDriverManager $drupal) {
95
    $this->drupal = $drupal;
96
  }
97
98
  /**
99
   * {@inheritDoc}
100
   */
101
  public function getDrupal() {
102
    return $this->drupal;
103
  }
104
105
  /**
106
   * {@inheritDoc}
107
   */
108
  public function setDispatcher(HookDispatcher $dispatcher) {
109
    $this->dispatcher = $dispatcher;
110
  }
111
112
  /**
113
   * Set parameters provided for Drupal.
114
   */
115
  public function setDrupalParameters(array $parameters) {
116
    $this->drupalParameters = $parameters;
117
  }
118
119
  /**
120
   * Returns a specific Drupal parameter.
121
   *
122
   * @param string $name
123
   *   Parameter name.
124
   *
125
   * @return mixed
126
   */
127
  public function getDrupalParameter($name) {
128
    return isset($this->drupalParameters[$name]) ? $this->drupalParameters[$name] : NULL;
129
  }
130
131
  /**
132
   * Returns a specific Drupal text value.
133
   *
134
   * @param string $name
135
   *   Text value name, such as 'log_out', which corresponds to the default 'Log
136
   *   out' link text.
137
   * @throws \Exception
138
   * @return
139
   */
140 View Code Duplication
  public function getDrupalText($name) {
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...
141
    $text = $this->getDrupalParameter('text');
142
    if (!isset($text[$name])) {
143
      throw new \Exception(sprintf('No such Drupal string: %s', $name));
144
    }
145
    return $text[$name];
146
  }
147
148
  /**
149
   * Get active Drupal Driver.
150
   */
151
  public function getDriver($name = NULL) {
152
    return $this->getDrupal()->getDriver($name);
153
  }
154
155
  /**
156
   * Get driver's random generator.
157
   */
158
  public function getRandom() {
159
    return $this->getDriver()->getRandom();
160
  }
161
162
  /**
163
   * Massage node values to match the expectations on different Drupal versions.
164
   *
165
   * @beforeNodeCreate
166
   */
167
  public function alterNodeParameters(BeforeNodeCreateScope $scope) {
168
    $node = $scope->getEntity();
169
170
    // Get the Drupal API version if available. This is not available when
171
    // using e.g. the BlackBoxDriver or DrushDriver.
172
    $api_version = NULL;
173
    $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\DrupalContext, Drupal\DrupalExtension\C...xt\DrupalSubContextBase, Drupal\DrupalExtension\Context\DrushContext, Drupal\DrupalExtension\Context\MessageContext, Drupal\DrupalExtension\Context\RawDrupalContext.

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...
174
    if ($driver instanceof \Drupal\Driver\DrupalDriver) {
175
      $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\DrupalContext, Drupal\DrupalExtension\C...xt\DrupalSubContextBase, Drupal\DrupalExtension\Context\DrushContext, Drupal\DrupalExtension\Context\MessageContext, Drupal\DrupalExtension\Context\RawDrupalContext.

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...
176
    }
177
178
    // On Drupal 8 the timestamps should be in UNIX time.
179
    switch ($api_version) {
180
      case 8:
181
        foreach (array('changed', 'created', 'revision_timestamp') as $field) {
182
          if (!empty($node->$field) && !is_numeric($node->$field)) {
183
            $node->$field = strtotime($node->$field);
184
          }
185
        }
186
      break;
187
    }
188
  }
189
190
  /**
191
   * Remove any created nodes.
192
   *
193
   * @AfterScenario
194
   */
195
  public function cleanNodes() {
196
    // Remove any nodes that were created.
197
    foreach ($this->nodes as $node) {
198
      $this->getDriver()->nodeDelete($node);
199
    }
200
    $this->nodes = array();
201
  }
202
203
  /**
204
   * Remove any created users.
205
   *
206
   * @AfterScenario
207
   */
208
  public function cleanUsers() {
209
    // Remove any users that were created.
210
    if (!empty($this->users)) {
211
      foreach ($this->users as $user) {
212
        $this->getDriver()->userDelete($user);
213
      }
214
      $this->getDriver()->processBatch();
215
      $this->users = array();
216
    }
217
  }
218
219
  /**
220
   * Remove any created terms.
221
   *
222
   * @AfterScenario
223
   */
224
  public function cleanTerms() {
225
    // Remove any terms that were created.
226
    foreach ($this->terms as $term) {
227
      $this->getDriver()->termDelete($term);
228
    }
229
    $this->terms = array();
230
  }
231
232
  /**
233
   * Remove any created roles.
234
   *
235
   * @AfterScenario
236
   */
237
  public function cleanRoles() {
238
    // Remove any roles that were created.
239
    foreach ($this->roles as $rid) {
240
      $this->getDriver()->roleDelete($rid);
241
    }
242
    $this->roles = array();
243
  }
244
245
  /**
246
   * Remove any created languages.
247
   *
248
   * @AfterScenario
249
   */
250
  public function cleanLanguages() {
251
    // Delete any languages that were created.
252
    foreach ($this->languages as $language) {
253
      $this->getDriver()->languageDelete($language);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Driver\DriverInterface as the method languageDelete() does only exist in the following implementations of said interface: Drupal\Driver\DrupalDriver.

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
      unset($this->languages[$language->langcode]);
255
    }
256
  }
257
258
  /**
259
   * Clear static caches.
260
   *
261
   * @AfterScenario @api
262
   */
263
  public function clearStaticCaches() {
264
    $this->getDriver()->clearStaticCaches();
265
  }
266
267
  /**
268
   * Dispatch scope hooks.
269
   *
270
   * @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...
271
   *   The entity scope to dispatch.
272
   * @param stdClass $entity
273
   *   The entity.
274
   */
275
  protected function dispatchHooks($scopeType, \stdClass $entity) {
276
    $fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType;
277
    $scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity);
278
    $callResults = $this->dispatcher->dispatchScopeHooks($scope);
279
280
    // The dispatcher suppresses exceptions, throw them here if there are any.
281
    foreach ($callResults as $result) {
282
      if ($result->hasException()) {
283
        $exception = $result->getException();
284
        throw $exception;
285
      }
286
    }
287
  }
288
289
  /**
290
   * Create a node.
291
   *
292
   * @return object
293
   *   The created node.
294
   */
295 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...
296
    $this->dispatchHooks('BeforeNodeCreateScope', $node);
297
    $this->parseEntityFields('node', $node);
298
    $saved = $this->getDriver()->createNode($node);
299
    $this->dispatchHooks('AfterNodeCreateScope', $saved);
300
    $this->nodes[] = $saved;
301
    return $saved;
302
  }
303
304
  /**
305
   * Parse multi-value fields. Possible formats:
306
   *    A, B, C
307
   *    A - B, C - D, E - F
308
   *
309
   * @param string $entity_type
310
   *   The entity type.
311
   * @param \stdClass $entity
312
   *   An object containing the entity properties and fields as properties.
313
   */
314
  public function parseEntityFields($entity_type, \stdClass $entity) {
315
    $multicolumn_field = '';
316
    $multicolumn_fields = array();
317
318
    foreach ($entity as $field => $field_value) {
0 ignored issues
show
Bug introduced by
The expression $entity of type object<stdClass> is not traversable.
Loading history...
319
      // Reset the multicolumn field if the field name does not contain a column.
320
      if (strpos($field, ':') === FALSE) {
321
        $multicolumn_field = '';
322
      }
323
      // Start tracking a new multicolumn field if the field name contains a ':'
324
      // which is preceded by at least 1 character.
325
      elseif (strpos($field, ':', 1) !== FALSE) {
326
        list($multicolumn_field, $multicolumn_column) = explode(':', $field);
327
      }
328
      // If a field name starts with a ':' but we are not yet tracking a
329
      // multicolumn field we don't know to which field this belongs.
330
      elseif (empty($multicolumn_field)) {
331
        throw new \Exception('Field name missing for ' . $field);
332
      }
333
      // Update the column name if the field name starts with a ':' and we are
334
      // already tracking a multicolumn field.
335
      else {
336
        $multicolumn_column = substr($field, 1);
337
      }
338
339
      $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...
340
      $field_name = $multicolumn_field ?: $field;
341
      if ($this->getDriver()->isField($entity_type, $field_name)) {
342
        // Split up multiple values in multi-value fields.
343
        $values = array();
344
        foreach (explode(', ', $field_value) as $key => $value) {
345
          $columns = $value;
346
          // Split up field columns if the ' - ' separator is present.
347
          if (strstr($value, ' - ') !== FALSE) {
348
            $columns = array();
349
            foreach (explode(' - ', $value) as $column) {
350
              // Check if it is an inline named column.
351
              if (!$is_multicolumn && strpos($column, ': ', 1) !== FALSE) {
352
                list ($key, $column) = explode(': ', $column);
353
                $columns[$key] = $column;
354
              }
355
              else {
356
                $columns[] = $column;
357
              }
358
            }
359
          }
360
          // Use the column name if we are tracking a multicolumn field.
361
          if ($is_multicolumn) {
362
            $multicolumn_fields[$multicolumn_field][$key][$multicolumn_column] = $columns;
363
            unset($entity->$field);
364
          }
365
          else {
366
            $values[] = $columns;
367
          }
368
        }
369
        // Replace regular fields inline in the entity after parsing.
370
        if (!$is_multicolumn) {
371
          $entity->$field_name = $values;
372
        }
373
      }
374
    }
375
376
    // Add the multicolumn fields to the entity.
377
    foreach ($multicolumn_fields as $field_name => $columns) {
378
      $entity->$field_name = $columns;
379
    }
380
  }
381
382
  /**
383
   * Create a user.
384
   *
385
   * @return object
386
   *   The created user.
387
   */
388 View Code Duplication
  public function userCreate($user) {
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...
389
    $this->dispatchHooks('BeforeUserCreateScope', $user);
390
    $this->parseEntityFields('user', $user);
391
    $this->getDriver()->userCreate($user);
392
    $this->dispatchHooks('AfterUserCreateScope', $user);
393
    $this->users[$user->name] = $this->user = $user;
394
    return $user;
395
  }
396
397
  /**
398
   * Create a term.
399
   *
400
   * @return object
401
   *   The created term.
402
   */
403 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...
404
    $this->dispatchHooks('BeforeTermCreateScope', $term);
405
    $this->parseEntityFields('taxonomy_term', $term);
406
    $saved = $this->getDriver()->createTerm($term);
407
    $this->dispatchHooks('AfterTermCreateScope', $saved);
408
    $this->terms[] = $saved;
409
    return $saved;
410
  }
411
412
  /**
413
   * Creates a language.
414
   *
415
   * @param \stdClass $language
416
   *   An object with the following properties:
417
   *   - langcode: the langcode of the language to create.
418
   *
419
   * @return object|FALSE
420
   *   The created language, or FALSE if the language was already created.
421
   */
422
  public function languageCreate(\stdClass $language) {
423
    $this->dispatchHooks('BeforeLanguageCreateScope', $language);
424
    $language = $this->getDriver()->languageCreate($language);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Driver\DriverInterface as the method languageCreate() does only exist in the following implementations of said interface: Drupal\Driver\DrupalDriver.

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...
425
    if ($language) {
426
      $this->dispatchHooks('AfterLanguageCreateScope', $language);
427
      $this->languages[$language->langcode] = $language;
428
    }
429
    return $language;
430
  }
431
432
  /**
433
   * Log-in the current user.
434
   */
435
  public function login() {
436
    // Check if logged in.
437
    if ($this->loggedIn()) {
438
      $this->logout();
439
    }
440
441
    if (!$this->user) {
442
      throw new \Exception('Tried to login without a user.');
443
    }
444
445
    $this->getSession()->visit($this->locatePath('/user'));
446
    $element = $this->getSession()->getPage();
447
    $element->fillField($this->getDrupalText('username_field'), $this->user->name);
448
    $element->fillField($this->getDrupalText('password_field'), $this->user->pass);
449
    $submit = $element->findButton($this->getDrupalText('log_in'));
450
    if (empty($submit)) {
451
      throw new \Exception(sprintf("No submit button at %s", $this->getSession()->getCurrentUrl()));
452
    }
453
454
    // Log in.
455
    $submit->click();
456
457
    if (!$this->loggedIn()) {
458
      if (isset($this->user->role)) {
459
        throw new \Exception(sprintf("Unable to determine if logged in because 'log_out' link cannot be found for user '%s' with role '%s'", $this->user->name, $this->user->role));
460
      }
461
      else {
462
        throw new \Exception(sprintf("Unable to determine if logged in because 'log_out' link cannot be found for user '%s'", $this->user->name));
463
      }
464
    }
465
  }
466
467
  /**
468
   * Logs the current user out.
469
   */
470
  public function logout() {
471
    $this->getSession()->visit($this->locatePath('/user/logout'));
472
  }
473
474
  /**
475
   * Determine if the a user is already logged in.
476
   *
477
   * @return boolean
478
   *   Returns TRUE if a user is logged in for this session.
479
   */
480
  public function loggedIn() {
481
    $session = $this->getSession();
482
    $session->visit($this->locatePath('/'));
483
484
    // If a logout link is found, we are logged in. While not perfect, this is
485
    // how Drupal SimpleTests currently work as well.
486
    $element = $session->getPage();
487
    return $element->findLink($this->getDrupalText('log_out'));
488
  }
489
490
  /**
491
   * User with a given role is already logged in.
492
   *
493
   * @param string $role
494
   *   A single role, or multiple comma-separated roles in a single string.
495
   *
496
   * @return boolean
497
   *   Returns TRUE if the current logged in user has this role (or roles).
498
   */
499
  public function loggedInWithRole($role) {
500
    return $this->loggedIn() && $this->user && isset($this->user->role) && $this->user->role == $role;
501
  }
502
503
}
504