Completed
Pull Request — master (#339)
by Pieter
02:03
created

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