Security update for permissions_by_term
[yaffs-website] / vendor / drupal / drupal-extension / src / Drupal / DrupalExtension / Context / RawDrupalContext.php
diff --git a/vendor/drupal/drupal-extension/src/Drupal/DrupalExtension/Context/RawDrupalContext.php b/vendor/drupal/drupal-extension/src/Drupal/DrupalExtension/Context/RawDrupalContext.php
new file mode 100644 (file)
index 0000000..6ca74c9
--- /dev/null
@@ -0,0 +1,557 @@
+<?php
+
+namespace Drupal\DrupalExtension\Context;
+
+use Behat\MinkExtension\Context\RawMinkContext;
+use Behat\Mink\Exception\DriverException;
+use Behat\Testwork\Hook\HookDispatcher;
+
+use Drupal\DrupalDriverManager;
+
+use Drupal\DrupalExtension\Hook\Scope\AfterLanguageEnableScope;
+use Drupal\DrupalExtension\Hook\Scope\AfterNodeCreateScope;
+use Drupal\DrupalExtension\Hook\Scope\AfterTermCreateScope;
+use Drupal\DrupalExtension\Hook\Scope\AfterUserCreateScope;
+use Drupal\DrupalExtension\Hook\Scope\BaseEntityScope;
+use Drupal\DrupalExtension\Hook\Scope\BeforeLanguageEnableScope;
+use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
+use Drupal\DrupalExtension\Hook\Scope\BeforeUserCreateScope;
+use Drupal\DrupalExtension\Hook\Scope\BeforeTermCreateScope;
+
+
+/**
+ * Provides the raw functionality for interacting with Drupal.
+ */
+class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface {
+
+  /**
+   * Drupal driver manager.
+   *
+   * @var \Drupal\DrupalDriverManager
+   */
+  private $drupal;
+
+  /**
+   * Test parameters.
+   *
+   * @var array
+   */
+  private $drupalParameters;
+
+  /**
+   * Event dispatcher object.
+   *
+   * @var \Behat\Testwork\Hook\HookDispatcher
+   */
+  protected $dispatcher;
+
+  /**
+   * Keep track of nodes so they can be cleaned up.
+   *
+   * @var array
+   */
+  protected $nodes = array();
+
+  /**
+   * Current authenticated user.
+   *
+   * A value of FALSE denotes an anonymous user.
+   *
+   * @var \stdClass|bool
+   */
+  public $user = FALSE;
+
+  /**
+   * Keep track of all users that are created so they can easily be removed.
+   *
+   * @var array
+   */
+  protected $users = array();
+
+  /**
+   * Keep track of all terms that are created so they can easily be removed.
+   *
+   * @var array
+   */
+  protected $terms = array();
+
+  /**
+   * Keep track of any roles that are created so they can easily be removed.
+   *
+   * @var array
+   */
+  protected $roles = array();
+
+  /**
+   * Keep track of any languages that are created so they can easily be removed.
+   *
+   * @var array
+   */
+  protected $languages = array();
+
+  /**
+   * {@inheritDoc}
+   */
+  public function setDrupal(DrupalDriverManager $drupal) {
+    $this->drupal = $drupal;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function getDrupal() {
+    return $this->drupal;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function setDispatcher(HookDispatcher $dispatcher) {
+    $this->dispatcher = $dispatcher;
+  }
+
+  /**
+   * Set parameters provided for Drupal.
+   */
+  public function setDrupalParameters(array $parameters) {
+    $this->drupalParameters = $parameters;
+  }
+
+  /**
+   * Returns a specific Drupal parameter.
+   *
+   * @param string $name
+   *   Parameter name.
+   *
+   * @return mixed
+   */
+  public function getDrupalParameter($name) {
+    return isset($this->drupalParameters[$name]) ? $this->drupalParameters[$name] : NULL;
+  }
+
+  /**
+   * Returns a specific Drupal text value.
+   *
+   * @param string $name
+   *   Text value name, such as 'log_out', which corresponds to the default 'Log
+   *   out' link text.
+   * @throws \Exception
+   * @return
+   */
+  public function getDrupalText($name) {
+    $text = $this->getDrupalParameter('text');
+    if (!isset($text[$name])) {
+      throw new \Exception(sprintf('No such Drupal string: %s', $name));
+    }
+    return $text[$name];
+  }
+
+  /**
+   * Returns a specific css selector.
+   *
+   * @param $name
+   *   string CSS selector name
+   */
+  public function getDrupalSelector($name) {
+    $text = $this->getDrupalParameter('selectors');
+    if (!isset($text[$name])) {
+      throw new \Exception(sprintf('No such selector configured: %s', $name));
+    }
+    return $text[$name];
+  }
+
+  /**
+   * Get active Drupal Driver.
+   *
+   * @return \Drupal\Driver\DrupalDriver
+   */
+  public function getDriver($name = NULL) {
+    return $this->getDrupal()->getDriver($name);
+  }
+
+  /**
+   * Get driver's random generator.
+   */
+  public function getRandom() {
+    return $this->getDriver()->getRandom();
+  }
+
+  /**
+   * Massage node values to match the expectations on different Drupal versions.
+   *
+   * @beforeNodeCreate
+   */
+  public static function alterNodeParameters(BeforeNodeCreateScope $scope) {
+    $node = $scope->getEntity();
+
+    // Get the Drupal API version if available. This is not available when
+    // using e.g. the BlackBoxDriver or DrushDriver.
+    $api_version = NULL;
+    $driver = $scope->getContext()->getDrupal()->getDriver();
+    if ($driver instanceof \Drupal\Driver\DrupalDriver) {
+      $api_version = $scope->getContext()->getDrupal()->getDriver()->version;
+    }
+
+    // On Drupal 8 the timestamps should be in UNIX time.
+    switch ($api_version) {
+      case 8:
+        foreach (array('changed', 'created', 'revision_timestamp') as $field) {
+          if (!empty($node->$field) && !is_numeric($node->$field)) {
+            $node->$field = strtotime($node->$field);
+          }
+        }
+      break;
+    }
+  }
+
+  /**
+   * Remove any created nodes.
+   *
+   * @AfterScenario
+   */
+  public function cleanNodes() {
+    // Remove any nodes that were created.
+    foreach ($this->nodes as $node) {
+      $this->getDriver()->nodeDelete($node);
+    }
+    $this->nodes = array();
+  }
+
+  /**
+   * Remove any created users.
+   *
+   * @AfterScenario
+   */
+  public function cleanUsers() {
+    // Remove any users that were created.
+    if (!empty($this->users)) {
+      foreach ($this->users as $user) {
+        $this->getDriver()->userDelete($user);
+      }
+      $this->getDriver()->processBatch();
+      $this->users = array();
+      $this->user = FALSE;
+      if ($this->loggedIn()) {
+        $this->logout();
+      }
+    }
+  }
+
+  /**
+   * Remove any created terms.
+   *
+   * @AfterScenario
+   */
+  public function cleanTerms() {
+    // Remove any terms that were created.
+    foreach ($this->terms as $term) {
+      $this->getDriver()->termDelete($term);
+    }
+    $this->terms = array();
+  }
+
+  /**
+   * Remove any created roles.
+   *
+   * @AfterScenario
+   */
+  public function cleanRoles() {
+    // Remove any roles that were created.
+    foreach ($this->roles as $rid) {
+      $this->getDriver()->roleDelete($rid);
+    }
+    $this->roles = array();
+  }
+
+  /**
+   * Remove any created languages.
+   *
+   * @AfterScenario
+   */
+  public function cleanLanguages() {
+    // Delete any languages that were created.
+    foreach ($this->languages as $language) {
+      $this->getDriver()->languageDelete($language);
+      unset($this->languages[$language->langcode]);
+    }
+  }
+
+  /**
+   * Clear static caches.
+   *
+   * @AfterScenario @api
+   */
+  public function clearStaticCaches() {
+    $this->getDriver()->clearStaticCaches();
+  }
+
+  /**
+   * Dispatch scope hooks.
+   *
+   * @param string $scope
+   *   The entity scope to dispatch.
+   * @param \stdClass $entity
+   *   The entity.
+   */
+  protected function dispatchHooks($scopeType, \stdClass $entity) {
+    $fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType;
+    $scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity);
+    $callResults = $this->dispatcher->dispatchScopeHooks($scope);
+
+    // The dispatcher suppresses exceptions, throw them here if there are any.
+    foreach ($callResults as $result) {
+      if ($result->hasException()) {
+        $exception = $result->getException();
+        throw $exception;
+      }
+    }
+  }
+
+  /**
+   * Create a node.
+   *
+   * @return object
+   *   The created node.
+   */
+  public function nodeCreate($node) {
+    $this->dispatchHooks('BeforeNodeCreateScope', $node);
+    $this->parseEntityFields('node', $node);
+    $saved = $this->getDriver()->createNode($node);
+    $this->dispatchHooks('AfterNodeCreateScope', $saved);
+    $this->nodes[] = $saved;
+    return $saved;
+  }
+
+  /**
+   * Parse multi-value fields. Possible formats:
+   *    A, B, C
+   *    A - B, C - D, E - F
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param \stdClass $entity
+   *   An object containing the entity properties and fields as properties.
+   */
+  public function parseEntityFields($entity_type, \stdClass $entity) {
+    $multicolumn_field = '';
+    $multicolumn_fields = array();
+
+    foreach (clone $entity as $field => $field_value) {
+      // Reset the multicolumn field if the field name does not contain a column.
+      if (strpos($field, ':') === FALSE) {
+        $multicolumn_field = '';
+      }
+      // Start tracking a new multicolumn field if the field name contains a ':'
+      // which is preceded by at least 1 character.
+      elseif (strpos($field, ':', 1) !== FALSE) {
+        list($multicolumn_field, $multicolumn_column) = explode(':', $field);
+      }
+      // If a field name starts with a ':' but we are not yet tracking a
+      // multicolumn field we don't know to which field this belongs.
+      elseif (empty($multicolumn_field)) {
+        throw new \Exception('Field name missing for ' . $field);
+      }
+      // Update the column name if the field name starts with a ':' and we are
+      // already tracking a multicolumn field.
+      else {
+        $multicolumn_column = substr($field, 1);
+      }
+
+      $is_multicolumn = $multicolumn_field && $multicolumn_column;
+      $field_name = $multicolumn_field ?: $field;
+      if ($this->getDriver()->isField($entity_type, $field_name)) {
+        // Split up multiple values in multi-value fields.
+        $values = array();
+        foreach (explode(', ', $field_value) as $key => $value) {
+          $columns = $value;
+          // Split up field columns if the ' - ' separator is present.
+          if (strstr($value, ' - ') !== FALSE) {
+            $columns = array();
+            foreach (explode(' - ', $value) as $column) {
+              // Check if it is an inline named column.
+              if (!$is_multicolumn && strpos($column, ': ', 1) !== FALSE) {
+                list ($key, $column) = explode(': ', $column);
+                $columns[$key] = $column;
+              }
+              else {
+                $columns[] = $column;
+              }
+            }
+          }
+          // Use the column name if we are tracking a multicolumn field.
+          if ($is_multicolumn) {
+            $multicolumn_fields[$multicolumn_field][$key][$multicolumn_column] = $columns;
+            unset($entity->$field);
+          }
+          else {
+            $values[] = $columns;
+          }
+        }
+        // Replace regular fields inline in the entity after parsing.
+        if (!$is_multicolumn) {
+          $entity->$field_name = $values;
+          // Don't specify any value if the step author has left it blank.
+          if ($field_value === '') {
+            unset($entity->$field_name);
+          }
+        }
+      }
+    }
+
+    // Add the multicolumn fields to the entity.
+    foreach ($multicolumn_fields as $field_name => $columns) {
+      // Don't specify any value if the step author has left it blank.
+      if (count(array_filter($columns, function ($var) {
+        return ($var !== '');
+      })) > 0) {
+        $entity->$field_name = $columns;
+      }
+    }
+  }
+
+  /**
+   * Create a user.
+   *
+   * @return object
+   *   The created user.
+   */
+  public function userCreate($user) {
+    $this->dispatchHooks('BeforeUserCreateScope', $user);
+    $this->parseEntityFields('user', $user);
+    $this->getDriver()->userCreate($user);
+    $this->dispatchHooks('AfterUserCreateScope', $user);
+    $this->users[$user->name] = $this->user = $user;
+    return $user;
+  }
+
+  /**
+   * Create a term.
+   *
+   * @return object
+   *   The created term.
+   */
+  public function termCreate($term) {
+    $this->dispatchHooks('BeforeTermCreateScope', $term);
+    $this->parseEntityFields('taxonomy_term', $term);
+    $saved = $this->getDriver()->createTerm($term);
+    $this->dispatchHooks('AfterTermCreateScope', $saved);
+    $this->terms[] = $saved;
+    return $saved;
+  }
+
+  /**
+   * Creates a language.
+   *
+   * @param \stdClass $language
+   *   An object with the following properties:
+   *   - langcode: the langcode of the language to create.
+   *
+   * @return object|FALSE
+   *   The created language, or FALSE if the language was already created.
+   */
+  public function languageCreate(\stdClass $language) {
+    $this->dispatchHooks('BeforeLanguageCreateScope', $language);
+    $language = $this->getDriver()->languageCreate($language);
+    if ($language) {
+      $this->dispatchHooks('AfterLanguageCreateScope', $language);
+      $this->languages[$language->langcode] = $language;
+    }
+    return $language;
+  }
+
+  /**
+   * Log-in the current user.
+   */
+  public function login() {
+    // Check if logged in.
+    if ($this->loggedIn()) {
+      $this->logout();
+    }
+
+    if (!$this->user) {
+      throw new \Exception('Tried to login without a user.');
+    }
+
+    $this->getSession()->visit($this->locatePath('/user'));
+    $element = $this->getSession()->getPage();
+    $element->fillField($this->getDrupalText('username_field'), $this->user->name);
+    $element->fillField($this->getDrupalText('password_field'), $this->user->pass);
+    $submit = $element->findButton($this->getDrupalText('log_in'));
+    if (empty($submit)) {
+      throw new \Exception(sprintf("No submit button at %s", $this->getSession()->getCurrentUrl()));
+    }
+
+    // Log in.
+    $submit->click();
+
+    if (!$this->loggedIn()) {
+      if (isset($this->user->role)) {
+        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));
+      }
+      else {
+        throw new \Exception(sprintf("Unable to determine if logged in because 'log_out' link cannot be found for user '%s'", $this->user->name));
+      }
+    }
+  }
+
+  /**
+   * Logs the current user out.
+   */
+  public function logout() {
+    $this->getSession()->visit($this->locatePath('/user/logout'));
+  }
+
+  /**
+   * Determine if the a user is already logged in.
+   *
+   * @return boolean
+   *   Returns TRUE if a user is logged in for this session.
+   */
+  public function loggedIn() {
+    $session = $this->getSession();
+
+    // If the session has not been started yet, or no page has yet been loaded,
+    // then this is a brand new test session and the user is not logged in.
+    if (!$session->isStarted() || !$page = $session->getPage()) {
+      return FALSE;
+    }
+
+    // Look for a css selector to determine if a user is logged in.
+    // Default is the logged-in class on the body tag.
+    // Which should work with almost any theme.
+    try {
+      if ($page->has('css', $this->getDrupalSelector('logged_in_selector'))) {
+        return TRUE;
+      }
+    } catch (DriverException $e) {
+      // This test may fail if the driver did not load any site yet.
+    }
+
+    // Some themes do not add that class to the body, so lets check if the
+    // login form is displayed on /user/login.
+    $session->visit($this->locatePath('/user/login'));
+    if (!$page->has('css', $this->getDrupalSelector('login_form_selector'))) {
+      return TRUE;
+    }
+
+    $session->visit($this->locatePath('/'));
+
+    // As a last resort, if a logout link is found, we are logged in. While not
+    // perfect, this is how Drupal SimpleTests currently work as well.
+    return $page->findLink($this->getDrupalText('log_out'));
+  }
+
+  /**
+   * User with a given role is already logged in.
+   *
+   * @param string $role
+   *   A single role, or multiple comma-separated roles in a single string.
+   *
+   * @return boolean
+   *   Returns TRUE if the current logged in user has this role (or roles).
+   */
+  public function loggedInWithRole($role) {
+    return $this->loggedIn() && $this->user && isset($this->user->role) && $this->user->role == $role;
+  }
+
+}