You are here

class RulesState in Rules 7.2

The rules evaluation state.

A rule element may clone the state, so any added variables are only visible for elements in the current PHP-variable-scope.

Hierarchy

Expanded class hierarchy of RulesState

File

includes/rules.state.inc, line 14
Contains the state and data related stuff.

View source
class RulesState {

  /**
   * Globally keeps the ids of rules blocked due to recursion prevention.
   *
   * @var array
   */
  protected static $blocked = array();

  /**
   * The known variables.
   *
   * @var array
   */
  public $variables = array();

  /**
   * Holds info about the variables.
   *
   * @var array
   */
  protected $info = array();

  /**
   * Keeps wrappers to be saved later on.
   */
  protected $save;

  /**
   * Holds the arguments while an element is executed.
   *
   * May be used by the element to easily access the wrapped arguments.
   */
  public $currentArguments;

  /**
   * Variable for saving currently blocked configs for serialization.
   */
  protected $currentlyBlocked;

  /**
   * Constructs a RulesState object.
   */
  public function __construct() {

    // Use an object in order to ensure any cloned states reference the same
    // save information.
    $this->save = new ArrayObject();
    $this
      ->addVariable('site', FALSE, self::defaultVariables('site'));
  }

  /**
   * Adds the given variable to the given execution state.
   */
  public function addVariable($name, $data, $info) {
    $this->info[$name] = $info + array(
      'skip save' => FALSE,
      'type' => 'unknown',
      'handler' => FALSE,
    );
    if (empty($this->info[$name]['handler'])) {
      $this->variables[$name] = rules_wrap_data($data, $this->info[$name]);
    }
  }

  /**
   * Runs post-evaluation tasks, such as saving variables.
   */
  public function cleanUp() {

    // Make changes permanent.
    foreach ($this->save
      ->getArrayCopy() as $selector => $wrapper) {
      $this
        ->saveNow($selector);
    }
    unset($this->currentArguments);
  }

  /**
   * Block a rules configuration from execution.
   */
  public function block($rules_config) {
    if (empty($rules_config->recursion) && $rules_config->id) {
      self::$blocked[$rules_config->id] = TRUE;
    }
  }

  /**
   * Unblock a rules configuration from execution.
   */
  public function unblock($rules_config) {
    if (empty($rules_config->recursion) && $rules_config->id) {
      unset(self::$blocked[$rules_config->id]);
    }
  }

  /**
   * Returns whether a rules configuration should be blocked from execution.
   */
  public function isBlocked($rule_config) {
    return !empty($rule_config->id) && isset(self::$blocked[$rule_config->id]);
  }

  /**
   * Get the info about the state variables or a single variable.
   */
  public function varInfo($name = NULL) {
    if (isset($name)) {
      return isset($this->info[$name]) ? $this->info[$name] : FALSE;
    }
    return $this->info;
  }

  /**
   * Returns whether the given wrapper is savable.
   */
  public function isSavable($wrapper) {
    return $wrapper instanceof EntityDrupalWrapper && entity_type_supports($wrapper
      ->type(), 'save') || $wrapper instanceof RulesDataWrapperSavableInterface;
  }

  /**
   * Returns whether the variable with the given name is an entity.
   */
  public function isEntity($name) {
    $entity_info = entity_get_info();
    return isset($this->info[$name]['type']) && isset($entity_info[$this->info[$name]['type']]);
  }

  /**
   * Gets a variable.
   *
   * If necessary, the specified handler is invoked to fetch the variable.
   *
   * @param string $name
   *   The name of the variable to return.
   *
   * @return
   *   The variable or a EntityMetadataWrapper containing the variable.
   *
   * @throws RulesEvaluationException
   *   Throws a RulesEvaluationException in case we have info about the
   *   requested variable, but it is not defined.
   */
  public function &get($name) {
    if (!array_key_exists($name, $this->variables)) {

      // If there is handler to load the variable, do it now.
      if (!empty($this->info[$name]['handler'])) {
        $data = call_user_func($this->info[$name]['handler'], rules_unwrap_data($this->variables), $name, $this->info[$name]);
        $this->variables[$name] = rules_wrap_data($data, $this->info[$name]);
        $this->info[$name]['handler'] = FALSE;
        if (!isset($data)) {
          throw new RulesEvaluationException('Unable to load variable %name, aborting.', array(
            '%name' => $name,
          ), NULL, RulesLog::INFO);
        }
      }
      else {
        throw new RulesEvaluationException('Unable to get variable %name, it is not defined.', array(
          '%name' => $name,
        ), NULL, RulesLog::ERROR);
      }
    }
    return $this->variables[$name];
  }

  /**
   * Apply permanent changes provided the wrapper's data type is savable.
   *
   * @param $selector
   *   The data selector of the wrapper to save or just a variable name.
   * @param $wrapper
   * @param bool $immediate
   *   Pass FALSE to postpone saving to later on. Else it's immediately saved.
   */
  public function saveChanges($selector, $wrapper, $immediate = FALSE) {
    $info = $wrapper
      ->info();
    if (empty($info['skip save']) && $this
      ->isSavable($wrapper)) {
      $this
        ->save($selector, $wrapper, $immediate);
    }
    elseif (empty($info['skip save']) && isset($info['parent']) && !$wrapper instanceof EntityDrupalWrapper) {

      // Cut of the last part of the selector.
      $selector = implode(':', explode(':', $selector, -1));
      $this
        ->saveChanges($selector, $info['parent'], $immediate);
    }
    return $this;
  }

  /**
   * Remembers to save the wrapper on cleanup or does it now.
   */
  protected function save($selector, EntityMetadataWrapper $wrapper, $immediate) {

    // Convert variable names and selectors to both use underscores.
    $selector = strtr($selector, '-', '_');
    if (isset($this->save[$selector])) {
      if ($this->save[$selector][0]
        ->getIdentifier() == $wrapper
        ->getIdentifier()) {

        // The entity is already remembered. So do a combined save.
        $this->save[$selector][1] += self::$blocked;
      }
      else {

        // The wrapper is already in there, but wraps another entity. So first
        // save the old one, then care about the new one.
        $this
          ->saveNow($selector);
      }
    }
    if (!isset($this->save[$selector])) {

      // In case of immediate saving don't clone the wrapper, so saving a new
      // entity immediately makes the identifier available afterwards.
      $this->save[$selector] = array(
        $immediate ? $wrapper : clone $wrapper,
        self::$blocked,
      );
    }
    if ($immediate) {
      $this
        ->saveNow($selector);
    }
  }

  /**
   * Saves the wrapper for the given selector.
   */
  protected function saveNow($selector) {

    // Add the set of blocked elements for the recursion prevention.
    $previously_blocked = self::$blocked;
    self::$blocked += $this->save[$selector][1];

    // Actually save!
    $wrapper = $this->save[$selector][0];
    $entity = $wrapper
      ->value();

    // When operating in hook_entity_insert() $entity->is_new might be still
    // set. In that case remove the flag to avoid causing another insert instead
    // of an update.
    if (!empty($entity->is_new) && $wrapper
      ->getIdentifier()) {
      $entity->is_new = FALSE;
    }
    rules_log('Saved %selector of type %type.', array(
      '%selector' => $selector,
      '%type' => $wrapper
        ->type(),
    ));
    $wrapper
      ->save();

    // Restore the state's set of blocked elements.
    self::$blocked = $previously_blocked;
    unset($this->save[$selector]);
  }

  /**
   * Merges info from the given state into the existing state.
   *
   * Merges the info about to-be-saved variables from the given state into the
   * existing state. Therefore we can aggregate saves from invoked components.
   * Merged-in saves are removed from the given state, but not-mergeable saves
   * remain there.
   *
   * @param $state
   *   The state for which to merge the to be saved variables in.
   * @param $component
   *   The component which has been invoked, thus needs to be blocked for the
   *   merged in saves.
   * @param $settings
   *   The settings of the element that invoked the component. Contains
   *   information about variable/selector mappings between the states.
   */
  public function mergeSaveVariables(RulesState $state, RulesPlugin $component, $settings) {

    // For any saves that we take over, also block the component.
    $this
      ->block($component);
    foreach ($state->save
      ->getArrayCopy() as $selector => $data) {
      $parts = explode(':', $selector, 2);

      // Adapt the selector to fit for the parent state and move the wrapper.
      if (isset($settings[$parts[0] . ':select'])) {
        $parts[0] = $settings[$parts[0] . ':select'];
        $this
          ->save(implode(':', $parts), $data[0], FALSE);
        unset($state->save[$selector]);
      }
    }
    $this
      ->unblock($component);
  }

  /**
   * Returns an entity metadata wrapper as specified in the selector.
   *
   * @param string $selector
   *   The selector string, e.g. "node:author:mail".
   * @param string $langcode
   *   (optional) The language code used to get the argument value if the
   *   argument value should be translated. Defaults to LANGUAGE_NONE.
   *
   * @return EntityMetadataWrapper
   *   The wrapper for the given selector.
   *
   * @throws RulesEvaluationException
   *   Throws a RulesEvaluationException in case the selector cannot be applied.
   */
  public function applyDataSelector($selector, $langcode = LANGUAGE_NONE) {
    $parts = explode(':', str_replace('-', '_', $selector), 2);
    $wrapper = $this
      ->get($parts[0]);
    if (count($parts) == 1) {
      return $wrapper;
    }
    elseif (!$wrapper instanceof EntityMetadataWrapper) {
      throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not wrapped correctly.', array(
        '%selector' => $selector,
      ));
    }
    try {
      foreach (explode(':', $parts[1]) as $name) {
        if ($wrapper instanceof EntityListWrapper || $wrapper instanceof EntityStructureWrapper) {

          // Make sure we are using the right language. Wrappers might be cached
          // and have previous langcodes set, so always set the right language.
          if ($wrapper instanceof EntityStructureWrapper) {
            $wrapper
              ->language($langcode);
          }
          $wrapper = $wrapper
            ->get($name);
        }
        else {
          throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not a list or a structure: %wrapper.', array(
            '%selector' => $selector,
            '%wrapper' => $wrapper,
          ));
        }
      }
    } catch (EntityMetadataWrapperException $e) {

      // In case of an exception, re-throw it.
      throw new RulesEvaluationException('Unable to apply data selector %selector: %error', array(
        '%selector' => $selector,
        '%error' => $e
          ->getMessage(),
      ));
    }
    return $wrapper;
  }

  /**
   * Magic method. Only serialize variables and their info.
   *
   * Additionally we remember currently blocked configs, so we can restore them
   * upon deserialization using restoreBlocks().
   */
  public function __sleep() {
    $this->currentlyBlocked = self::$blocked;
    return array(
      'info',
      'variables',
      'currentlyBlocked',
    );
  }

  /**
   * Magic method. Unserialize variables and their info.
   */
  public function __wakeup() {
    $this->save = new ArrayObject();
  }

  /**
   * Restores the before-serialization blocked configurations.
   *
   * Warning: This overwrites any possible currently blocked configs. Thus
   * do not invoke this method if there might be evaluations active.
   */
  public function restoreBlocks() {
    self::$blocked = $this->currentlyBlocked;
  }

  /**
   * Defines always-available variables.
   *
   * @param $key
   *   (optional)
   */
  public static function defaultVariables($key = NULL) {

    // Add a variable for accessing site-wide data properties.
    $vars['site'] = array(
      'type' => 'site',
      'label' => t('Site information'),
      'description' => t("Site-wide settings and other global information."),
      // Add the property info via a callback making use of the cached info.
      'property info alter' => array(
        'RulesData',
        'addSiteMetadata',
      ),
      'property info' => array(),
      'optional' => TRUE,
    );
    return isset($key) ? $vars[$key] : $vars;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
RulesState::$blocked protected static property Globally keeps the ids of rules blocked due to recursion prevention.
RulesState::$currentArguments public property Holds the arguments while an element is executed.
RulesState::$currentlyBlocked protected property Variable for saving currently blocked configs for serialization.
RulesState::$info protected property Holds info about the variables.
RulesState::$save protected property Keeps wrappers to be saved later on.
RulesState::$variables public property The known variables.
RulesState::addVariable public function Adds the given variable to the given execution state.
RulesState::applyDataSelector public function Returns an entity metadata wrapper as specified in the selector.
RulesState::block public function Block a rules configuration from execution.
RulesState::cleanUp public function Runs post-evaluation tasks, such as saving variables.
RulesState::defaultVariables public static function Defines always-available variables.
RulesState::get public function Gets a variable.
RulesState::isBlocked public function Returns whether a rules configuration should be blocked from execution.
RulesState::isEntity public function Returns whether the variable with the given name is an entity.
RulesState::isSavable public function Returns whether the given wrapper is savable.
RulesState::mergeSaveVariables public function Merges info from the given state into the existing state.
RulesState::restoreBlocks public function Restores the before-serialization blocked configurations.
RulesState::save protected function Remembers to save the wrapper on cleanup or does it now.
RulesState::saveChanges public function Apply permanent changes provided the wrapper's data type is savable.
RulesState::saveNow protected function Saves the wrapper for the given selector.
RulesState::unblock public function Unblock a rules configuration from execution.
RulesState::varInfo public function Get the info about the state variables or a single variable.
RulesState::__construct public function Constructs a RulesState object.
RulesState::__sleep public function Magic method. Only serialize variables and their info.
RulesState::__wakeup public function Magic method. Unserialize variables and their info.