class RulesState
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
- class \RulesState
Expanded class hierarchy of RulesState
File
-
includes/
rules.state.inc, line 14
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 saveable.
*/
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 saveable.
*
* @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
Title Sort descending | Modifiers | Object type | Summary |
---|---|---|---|
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 saveable. |
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 saveable. |
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. |