first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -0,0 +1,311 @@
<?php
/**
* @file Extendable Object Faces API. Provided by the faces module.
*/
if (!interface_exists('FacesExtenderInterface', FALSE)) {
/**
* Interface for extenders.
*/
interface FacesExtenderInterface {
/**
* Constructs an instance of the extender.
*/
function __construct(FacesExtendable $object);
/**
* Returns the extended object.
*/
public function getExtendable();
}
/**
* The Exception thrown by the FacesExtendable.
*/
class FacesExtendableException extends ErrorException {}
}
if (!class_exists('FacesExtender', FALSE)) {
/**
* A common base class for FacesExtenders. Extenders may access protected
* methods and properties of the extendable using the property() and call()
* methods.
*/
abstract class FacesExtender implements FacesExtenderInterface {
/**
* @var FacesExtendable
*/
protected $object;
function __construct(FacesExtendable $object) {
$this->object = $object;
}
/**
* Returns the extended object.
*/
public function getExtendable() {
return $this->object;
}
/**
* Makes protected properties of the extendable accessible.
*/
protected function &property($name) {
$var =& $this->object->property($name);
return $var;
}
/**
* Invokes any method on the extended object. May be used to invoke
* protected methods.
*
* @param $name
* The method name.
* @param $arguments
* An array of arguments to pass to the method.
*/
protected function call($name, array $args = array()) {
return $this->object->call($name, $args);
}
}
}
if (!class_exists('FacesExtendable', FALSE)) {
/**
* An extendable base class.
*/
abstract class FacesExtendable {
protected $facesMethods = array();
protected $faces = array();
protected $facesIncludes = array();
protected $facesClassInstances = array();
static protected $facesIncluded = array();
/**
* Wraps calls to module_load_include() to prevent multiple inclusions.
*
* @see module_load_include()
*/
protected static function load_include($args) {
$args += array('type' => 'inc', 'module' => '', 'name' => NULL);
$key = implode(':', $args);
if (!isset(self::$facesIncluded[$key])) {
self::$facesIncluded[$key] = TRUE;
module_load_include($args['type'], $args['module'], $args['name']);
}
}
/**
* Magic method: Invoke the dynamically implemented methods.
*/
function __call($name, $arguments = array()) {
if (isset($this->facesMethods[$name])) {
$method = $this->facesMethods[$name];
// Include code, if necessary.
if (isset($this->facesIncludes[$name])) {
self::load_include($this->facesIncludes[$name]);
$this->facesIncludes[$name] = NULL;
}
if (isset($method[0])) {
// We always pass the object reference and the name of the method.
$arguments[] = $this;
$arguments[] = $name;
return call_user_func_array($method[0], $arguments);
}
// Call the method on the extender object, but don't use extender()
// for performance reasons.
if (!isset($this->facesClassInstances[$method[1]])) {
$this->facesClassInstances[$method[1]] = new $method[1]($this);
}
return call_user_func_array(array($this->facesClassInstances[$method[1]], $name), $arguments);
}
$class = check_plain(get_class($this));
throw new FacesExtendableException("There is no method $name for this instance of the class $class.");
}
/**
* Returns the extender object for the given class. May be used to
* explicitly invoke a specific extender, e.g. a function overriding a
* method may use that to explicitly invoke the original extender.
*/
public function extender($class) {
if (!isset($this->facesClassInstances[$class])) {
$this->facesClassInstances[$class] = new $class($this);
}
return $this->facesClassInstances[$class];
}
/**
* Returns whether the object can face as the given interface, thus it
* returns TRUE if this oject has been extended by an appropriate
* implementation.
*
* @param $interface
* Optional. A interface to test for. If it's omitted, all interfaces that
* the object can be faced as are returned.
* @return
* Whether the object can face as the interface or an array of interface
* names.
*/
public function facesAs($interface = NULL) {
if (!isset($interface)) {
return array_values($this->faces);
}
return in_array($interface, $this->faces) || $this instanceof $interface;
}
/**
* Extend the object by a class to implement the given interfaces.
*
* @param $interface
* The interface name or an array of interface names.
* @param $class
* The extender class, which has to implement the FacesExtenderInterface.
* @param $include
* An optional array describing the file to include before invoking the
* class. The array entries known are 'type', 'module', and 'name'
* matching the parameters of module_load_include(). Only 'module' is
* required as 'type' defaults to 'inc' and 'name' to NULL.
*/
public function extendByClass($interface, $className, array $includes = array()) {
$parents = class_implements($className);
if (!in_array('FacesExtenderInterface', $parents)) {
throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the FacesExtenderInterface.");
}
$interfaces = is_array($interface) ? $interface : array($interface);
foreach ($interfaces as $interface) {
if (!in_array($interface, $parents)) {
throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the interface " . check_plain($interface) . ".");
}
$this->faces[$interface] = $interface;
$this->faces += class_implements($interface);
$face_methods = get_class_methods($interface);
$this->addIncludes($face_methods, $includes);
foreach ($face_methods as $method) {
$this->facesMethods[$method] = array(1 => $className);
}
}
}
/**
* Extend the object by the given functions to implement the given
* interface. There has to be an implementation function for each method of
* the interface.
*
* @param $interface
* The interface name or FALSE to extend the object without a given
* interface.
* @param $methods
* An array, where the keys are methods of the given interface and the
* values the callback functions to use.
* @param $includes
* An optional array to describe files to include before invoking the
* callbacks. You may pass a single array describing one include for all
* callbacks or an array of arrays, keyed by the method names. Look at the
* extendByClass() $include parameter for more details about how to
* describe a single file.
*/
public function extend($interface, array $callbacks = array(), array $includes = array()) {
$face_methods = $interface ? get_class_methods($interface) : array_keys($callbacks);
if ($interface) {
if (array_diff($face_methods, array_keys($callbacks))) {
throw new FacesExtendableException("Missing methods for implementing the interface " . check_plain($interface) . ".");
}
$this->faces[$interface] = $interface;
$this->faces += class_implements($interface);
}
$this->addIncludes($face_methods, $includes);
foreach ($face_methods as $method) {
$this->facesMethods[$method] = array(0 => $callbacks[$method]);
}
}
/**
* Override the implementation of an extended method.
*
* @param $methods
* An array of methods of the interface, that should be overriden, where
* the keys are methods to override and the values the callback functions
* to use.
* @param $includes
* An optional array to describe files to include before invoking the
* callbacks. You may pass a single array describing one include for all
* callbacks or an array of arrays, keyed by the method names. Look at the
* extendByClass() $include parameter for more details about how to
* describe a single file.
*/
public function override(array $callbacks = array(), array $includes = array()) {
if (array_diff_key($callbacks, $this->facesMethods)) {
throw new FacesExtendableException("A not implemented method is to be overridden.");
}
$this->addIncludes(array_keys($callbacks), $includes);
foreach ($callbacks as $method => $callback) {
$this->facesMethods[$method] = array(0 => $callback);
}
}
/**
* Adds in include files for the given methods while removing any old files.
* If a single include file is described, it's added for all methods.
*/
protected function addIncludes($methods, $includes) {
$includes = isset($includes['module']) && is_string($includes['module']) ? array_fill_keys($methods, $includes) : $includes;
$this->facesIncludes = $includes + array_diff_key($this->facesIncludes, array_flip($methods));
}
/**
* Only serialize what is really necessary.
*/
public function __sleep() {
return array('facesMethods', 'faces', 'facesIncludes');
}
/**
* Destroys all references to created instances so that PHP's garbage
* collection can do its work. This is needed as PHP's gc has troubles with
* circular references until PHP < 5.3.
*/
public function destroy() {
// Avoid circular references.
$this->facesClassInstances = array();
}
/**
* Makes protected properties accessible.
*/
public function &property($name) {
if (property_exists($this, $name)) {
return $this->$name;
}
}
/**
* Invokes any method.
*
* This also allows to pass arguments by reference, so it may be used to
* pass arguments by reference to dynamically extended methods.
*
* @param $name
* The method name.
* @param $arguments
* An array of arguments to pass to the method.
*/
public function call($name, array $args = array()) {
if (method_exists($this, $name)) {
return call_user_func_array(array($this, $name), $args);
}
return $this->__call($name, $args);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,766 @@
<?php
/**
* @file Contains plugin info and implementations not needed for rule evaluation.
*/
/**
* Implements a rules action.
*/
class RulesAction extends RulesAbstractPlugin implements RulesActionInterface {
protected $itemName = 'action';
/**
* Execute the callback and update/save data as specified by the action.
*/
protected function executeCallback(array $args, RulesState $state = NULL) {
rules_log('Evaluating the action %name.', array('%name' => $this->elementName), RulesLog::INFO, $this);
$return = $this->__call('execute', empty($this->info['named parameter']) ? $args : array($args));
// Get the (partially) wrapped arguments.
$args = $state->currentArguments;
if (is_array($return)) {
foreach ($return as $name => $data) {
// Add provided variables.
if (isset($this->info['provides'][$name])) {
$var_name = isset($this->settings[$name . ':var']) ? $this->settings[$name . ':var'] : $name;
if (!$state->varInfo($var_name)) {
$state->addVariable($var_name, $data, $this->info['provides'][$name]);
rules_log('Added the provided variable %name of type %type', array('%name' => $var_name, '%type' => $this->info['provides'][$name]['type']), RulesLog::INFO, $this);
if (!empty($this->info['provides'][$name]['save']) && $state->variables[$var_name] instanceof EntityMetadataWrapper) {
$state->saveChanges($var_name, $state->variables[$var_name]);
}
}
}
// Support updating variables by returning the values.
elseif (!isset($this->info['provides'][$name])) {
// Update the data value using the wrapper.
if (isset($args[$name]) && $args[$name] instanceof EntityMetadataWrapper) {
try {
$args[$name]->set($data);
}
catch (EntityMetadataWrapperException $e) {
throw new RulesEvaluationException('Unable to update the argument for parameter %name: %error', array('%name' => $name, '%error' => $e->getMessage()), $this);
}
}
elseif (array_key_exists($name, $args)) {
// Map back to the source variable name and update it.
$var_name = !empty($this->settings[$name . ':select']) ? str_replace('-', '_', $this->settings[$name . ':select']) : $name;
$state->variables[$var_name] = $data;
}
}
}
}
// Save parameters as defined in the parameter info.
if ($return !== FALSE) {
foreach ($this->info['parameter'] as $name => $info) {
if (!empty($info['save']) && $args[$name] instanceof EntityMetadataWrapper) {
if (isset($this->settings[$name . ':select'])) {
$state->saveChanges($this->settings[$name . ':select'], $args[$name]);
}
else {
// Wrapper has been configured via direct input, so just save.
rules_log('Saved argument of type %type for parameter %name.', array('%name' => $name, '%type' => $args[$name]->type()));
$args[$name]->save();
}
}
}
}
}
}
/**
* Implements a rules condition.
*/
class RulesCondition extends RulesAbstractPlugin implements RulesConditionInterface {
protected $itemName = 'condition';
protected $negate = FALSE;
public function providesVariables() {
return array();
}
public function negate($negate = TRUE) {
$this->negate = (bool) $negate;
return $this;
}
public function isNegated() {
return $this->negate;
}
protected function executeCallback(array $args, RulesState $state = NULL) {
$return = (bool) $this->__call('execute', empty($this->info['named parameter']) ? $args : array($args));
rules_log('The condition %name evaluated to %bool', array('%name' => $this->elementName, '%bool' => $return ? 'TRUE' : 'FALSE'), RulesLog::INFO, $this);
return $this->negate ? !$return : $return;
}
public function __sleep() {
return parent::__sleep() + array('negate' => 'negate');
}
/**
* Just return the boolean result.
*/
protected function returnVariables(RulesState $state, $result = NULL) {
return $result;
}
protected function exportToArray() {
$not = $this->negate ? 'NOT ' : '';
$export = $this->exportSettings();
// Abbreviate the export making "USING" implicit.
return array($not . $this->elementName => isset($export['USING']) ? $export['USING'] : array());
}
public function import(array $export) {
$this->elementName = rules_array_key($export);
if (strpos($this->elementName, 'NOT ') === 0) {
$this->elementName = substr($this->elementName, 4);
$this->negate = TRUE;
}
// After setting the element name, setup the element again so the right
// element info is loaded.
$this->setUp();
// Re-add 'USING' which has been removed for abbreviation.
$this->importSettings(array('USING' => reset($export)));
}
public function label() {
$label = parent::label();
return $this->negate ? t('NOT @condition', array('@condition' => $label)) : $label;
}
}
/**
* An actual rule.
* Note: A rule also implements the RulesActionInterface (inherited).
*/
class Rule extends RulesActionContainer {
protected $conditions = NULL;
protected $itemName = 'rule';
public $label = 'unlabeled';
public function __construct($variables = array(), $providesVars = array()) {
parent::__construct($variables, $providesVars);
// Initialize the conditions container.
if (!isset($this->conditions)) {
$this->conditions = rules_and();
// Don't use setParent() to avoid having it added to the children.
$this->conditions->parent = $this;
}
}
/**
* Get an iterator over all contained conditions. Note that this iterator also
* implements the ArrayAcces interface.
*
* @return RulesRecursiveElementIterator
*/
public function conditions() {
return $this->conditions->getIterator();
}
/**
* Returns the "And" condition container, which contains all conditions of
* this rule.
*
* @return RulesAnd
*/
public function conditionContainer() {
return $this->conditions;
}
public function __sleep() {
return parent::__sleep() + drupal_map_assoc(array('conditions', 'label'));
}
/**
* Get an iterator over all contained actions. Note that this iterator also
* implements the ArrayAcces interface.
*
* @return RulesRecursiveElementIterator
*/
public function actions() {
return parent::getIterator();
}
/**
* Add a condition. Pass either an instance of the RulesConditionInterface
* or the arguments as needed by rules_condition().
*
* @return Rule
* Returns $this to support chained usage.
*/
public function condition($name, $settings = array()) {
$this->conditions->condition($name, $settings);
return $this;
}
public function sortChildren($deep = FALSE) {
$this->conditions->sortChildren($deep);
parent::sortChildren($deep);
}
public function evaluate(RulesState $state) {
rules_log('Evaluating conditions of rule %label.', array('%label' => $this->label), RulesLog::INFO, $this);
if ($this->conditions->evaluate($state)) {
rules_log('Rule %label fires.', array('%label' => $this->label), RulesLog::INFO, $this, TRUE);
parent::evaluate($state);
rules_log('Rule %label has fired.', array('%label' => $this->label), RulesLog::INFO, $this, FALSE);
}
}
/**
* Fires the rule, i.e. evaluates the rule without checking its conditions.
*
* @see RulesPlugin::evaluate()
*/
public function fire(RulesState $state) {
rules_log('Firing rule %label.', array('%label' => $this->label), RulesLog::INFO, $this);
parent::evaluate($state);
}
public function integrityCheck() {
parent::integrityCheck();
$this->conditions->integrityCheck();
return $this;
}
public function access() {
return (!isset($this->conditions) || $this->conditions->access()) && parent::access();
}
public function dependencies() {
return array_keys(array_flip($this->conditions->dependencies()) + array_flip(parent::dependencies()));
}
public function destroy() {
$this->conditions->destroy();
parent::destroy();
}
/**
* @return RulesRecursiveElementIterator
*/
public function getIterator() {
$array = array_merge(array($this->conditions), $this->children);
return new RulesRecursiveElementIterator($array);
}
protected function stateVariables($element = NULL) {
// Don't add in provided action variables for the conditions.
if (isset($element) && $element === $this->conditions) {
return $this->availableVariables();
}
$vars = parent::stateVariables($element);
// Take variable info assertions of conditions into account.
if ($assertions = $this->conditions->variableInfoAssertions()) {
$vars = RulesData::addMetadataAssertions($vars, $assertions);
}
return $vars;
}
protected function exportFlat() {
return $this->isRoot();
}
protected function exportToArray() {
$export = parent::exportToArray();
if (!$this->isRoot()) {
$export[strtoupper($this->plugin())]['LABEL'] = $this->label;
}
return $export;
}
protected function exportChildren($key = NULL) {
$export = array();
if ($this->conditions->children) {
$export = $this->conditions->exportChildren('IF');
}
return $export + parent::exportChildren('DO');
}
public function import(array $export) {
if (!$this->isRoot() && isset($export[strtoupper($this->plugin())]['LABEL'])) {
$this->label = $export[strtoupper($this->plugin())]['LABEL'];
}
parent::import($export);
}
protected function importChildren($export, $key = NULL) {
if (!empty($export['IF'])) {
$this->conditions->importChildren($export, 'IF');
}
parent::importChildren($export, 'DO');
}
public function __clone() {
parent::__clone();
$this->conditions = clone $this->conditions;
$this->conditions->parent = $this;
}
/**
* Rules may not provided any variable info assertions, as Rules are only
* conditionally executed.
*/
protected function variableInfoAssertions() {
return array();
}
/**
* Overridden to ensure the whole Rule is deleted at once.
*/
public function delete($keep_children = FALSE) {
parent::delete($keep_children);
}
/**
* Overriden to expose the variables of all actions for embedded rules.
*/
public function providesVariables() {
$provides = parent::providesVariables();
if (!$this->isRoot()) {
foreach ($this->actions() as $action) {
$provides += $action->providesVariables();
}
}
return $provides;
}
public function resetInternalCache() {
parent::resetInternalCache();
$this->conditions->resetInternalCache();
}
}
/**
* Represents rules getting triggered by events.
*/
class RulesReactionRule extends Rule implements RulesTriggerableInterface {
protected $itemName = 'reaction rule';
protected $events = array();
/**
* Returns the array of events associated with that Rule.
*/
public function &events() {
return $this->events;
}
/**
* Removes an event from the rule configuration.
*
* @param $event
* The name of the event to remove.
* @return RulesReactionRule
*/
public function removeEvent($event) {
if (($id = array_search($event, $this->events)) !== FALSE) {
unset($this->events[$id]);
}
return $this;
}
/**
* @return RulesReactionRule
*/
public function event($event) {
$this->events[] = $event;
return $this;
}
/**
* Reaction rules can't add variables to the parent scope, so clone $state.
*/
public function evaluate(RulesState $state) {
// Implement recursion prevention for reaction rules.
if ($state->isBlocked($this)) {
return rules_log('Not evaluating @plugin %label to prevent recursion.', array('%label' => $this->label(), '@plugin' => $this->plugin()), RulesLog::INFO, $this);
}
$state->block($this);
$copy = clone $state;
parent::evaluate($copy);
$state->unblock($this);
}
public function access() {
$event_info = rules_fetch_data('event_info');
foreach ($this->events as $event) {
if (!empty($event_info[$event]['access callback']) && !call_user_func($event_info[$event]['access callback'], 'event', $event)) {
return FALSE;
}
}
return parent::access();
}
public function dependencies() {
$modules = array_flip(parent::dependencies());
$event_info = rules_fetch_data('event_info');
foreach ($this->events as $event) {
if (isset($event_info[$event]['module'])) {
$modules[$event_info[$event]['module']] = TRUE;
}
}
return array_keys($modules);
}
public function providesVariables() {
return array();
}
public function parameterInfo($optional = FALSE) {
// If executed directly, all variables as defined by the event need to
// be passed.
return rules_filter_array($this->availableVariables(), 'handler', FALSE);
}
public function availableVariables() {
if (!isset($this->availableVariables)) {
if (isset($this->parent)) {
// Return the event variables provided by the event set, once cached.
$this->availableVariables = $this->parent->stateVariables();
}
else {
// The intersection of the variables provided by the events are
// available.
$event_info = rules_fetch_data('event_info');
$events = array_intersect($this->events, array_keys($event_info));
foreach ($events as $event) {
$event_info[$event] += array('variables' => array());
if (isset($this->availableVariables)) {
$this->availableVariables = array_intersect_key($this->availableVariables, $event_info[$event]['variables']);
}
else {
$this->availableVariables = $event_info[$event]['variables'];
}
}
$this->availableVariables = isset($this->availableVariables) ? RulesState::defaultVariables() + $this->availableVariables : RulesState::defaultVariables();
}
}
return $this->availableVariables;
}
public function __sleep() {
return parent::__sleep() + drupal_map_assoc(array('events'));
}
protected function exportChildren($key = 'ON') {
$export[$key] = array_values($this->events);
return $export + parent::exportChildren();
}
protected function importChildren($export, $key = 'ON') {
$this->events = $export[$key];
parent::importChildren($export);
}
}
/**
* A logical AND.
*/
class RulesAnd extends RulesConditionContainer {
protected $itemName = 'and';
public function evaluate(RulesState $state) {
foreach ($this->children as $condition) {
if (!$condition->evaluate($state)) {
rules_log('AND evaluated to FALSE.');
return $this->negate;
}
}
rules_log('AND evaluated to TRUE.');
return !$this->negate;
}
public function label() {
return !empty($this->label) ? $this->label : ($this->negate ? t('NOT AND') : t('AND'));
}
}
/**
* A logical OR.
*/
class RulesOr extends RulesConditionContainer {
protected $itemName = 'or';
public function evaluate(RulesState $state) {
foreach ($this->children as $condition) {
if ($condition->evaluate($state)) {
rules_log('OR evaluated to TRUE.');
return !$this->negate;
}
}
rules_log('OR evaluated to FALSE.');
return $this->negate;
}
public function label() {
return !empty($this->label) ? $this->label : ($this->negate ? t('NOT OR') : t('OR'));
}
/**
* Overridden to exclude all variable assertions as in an OR we cannot assert
* the children are successfully evaluated.
*/
protected function stateVariables($element = NULL) {
$vars = $this->availableVariables();
if (isset($element)) {
// Add in variables provided by siblings executed before the element.
foreach ($this->children as $child) {
if ($child === $element) {
break;
}
$vars += $child->providesVariables();
}
}
return $vars;
}
}
/**
* A loop element.
*/
class RulesLoop extends RulesActionContainer {
protected $itemName = 'loop';
protected $listItemInfo;
public function __construct($settings = array(), $variables = NULL) {
$this->setUp();
$this->settings = (array) $settings + array(
'item:var' => 'list_item',
'item:label' => t('Current list item'),
);
if (!empty($variables)) {
$this->info['variables'] = $variables;
}
}
public function pluginParameterInfo() {
$info['list'] = array(
'type' => 'list',
'restriction' => 'selector',
'label' => t('List'),
'description' => t('The list to loop over. The loop will step through each item in the list, allowing further actions on them. See <a href="@url"> the online handbook</a> for more information on how to use loops.',
array('@url' => rules_external_help('loops'))),
);
return $info;
}
public function integrityCheck() {
parent::integrityCheck();
$this->checkVarName($this->settings['item:var']);
}
public function listItemInfo() {
if (!isset($this->listItemInfo)) {
if ($info = $this->getArgumentInfo('list')) {
// Pass through the variable info keys like property info.
$this->listItemInfo = array_intersect_key($info, array_flip(array('type', 'property info', 'bundle')));
$this->listItemInfo['type'] = isset($info['type']) ? entity_property_list_extract_type($info['type']) : 'unknown';
}
else {
$this->listItemInfo = array('type' => 'unknown');
}
$this->listItemInfo['label'] = $this->settings['item:label'];
}
return $this->listItemInfo;
}
public function evaluate(RulesState $state) {
try {
$param_info = $this->pluginParameterInfo();
$list = $this->getArgument('list', $param_info['list'], $state);
$item_var_info = $this->listItemInfo();
$item_var_name = $this->settings['item:var'];
if (isset($this->settings['list:select'])) {
rules_log('Looping over the list items of %selector', array('%selector' => $this->settings['list:select']), RulesLog::INFO, $this);
}
// Loop over the list and evaluate the children for each list item.
foreach ($list as $key => $item) {
// Use a separate state so variables are available in the loop only.
$state2 = clone $state;
$state2->addVariable($item_var_name, $list[$key], $item_var_info);
parent::evaluate($state2);
// Update variables from parent scope.
foreach ($state->variables as $var_key => &$var_value) {
if (array_key_exists($var_key, $state2->variables)) {
$var_value = $state2->variables[$var_key];
}
}
}
}
catch (RulesEvaluationException $e) {
rules_log($e->msg, $e->args, $e->severity);
rules_log('Unable to evaluate %name.', array('%name' => $this->getPluginName()), RulesLog::WARN, $this);
}
}
protected function stateVariables($element = NULL) {
return array($this->settings['item:var'] => $this->listItemInfo()) + parent::stateVariables($element);
}
public function label() {
return !empty($this->label) ? $this->label : t('Loop');
}
protected function exportChildren($key = 'DO') {
return parent::exportChildren($key);
}
protected function importChildren($export, $key = 'DO') {
parent::importChildren($export, $key);
}
protected function exportSettings() {
$export = parent::exportSettings();
$export['ITEM'][$this->settings['item:var']] = $this->settings['item:label'];
return $export;
}
protected function importSettings($export) {
parent::importSettings($export);
if (isset($export['ITEM'])) {
$this->settings['item:var'] = rules_array_key($export['ITEM']);
$this->settings['item:label'] = reset($export['ITEM']);
}
}
}
/**
* An action set component.
*/
class RulesActionSet extends RulesActionContainer {
protected $itemName = 'action set';
}
/**
* A set of rules to execute upon defined variables.
*/
class RulesRuleSet extends RulesActionContainer {
protected $itemName = 'rule set';
/**
* @return RulesRuleSet
*/
public function rule($rule) {
return $this->action($rule);
}
protected function exportChildren($key = 'RULES') {
return parent::exportChildren($key);
}
protected function importChildren($export, $key = 'RULES') {
parent::importChildren($export, $key);
}
}
/**
* This class is used for caching the rules to be evaluated per event.
*/
class RulesEventSet extends RulesRuleSet {
protected $itemName = 'event set';
// Event sets may recurse as we block recursions on rule-level.
public $recursion = TRUE;
public function __construct($info = array()) {
$this->setup();
$this->info = $info;
}
public function executeByArgs($args = array()) {
rules_log('Reacting on event %label.', array('%label' => $this->info['label']), RulesLog::INFO, NULL, TRUE);
$state = $this->setUpState($args);
module_invoke_all('rules_config_execute', $this);
$this->evaluate($state);
$state->cleanUp($this);
rules_log('Finished reacting on event %label.', array('%label' => $this->info['label']), RulesLog::INFO, NULL, FALSE);
}
/**
* Cache event-sets per event to allow efficient usage via rules_invoke_event().
*
* @see rules_get_cache()
* @see rules_invoke_event()
*/
public static function rebuildEventCache() {
// Set up the per-event cache.
$events = rules_fetch_data('event_info');
$sets = array();
// Add all rules associated with this event to an EventSet for caching.
$rules = rules_config_load_multiple(FALSE, array('plugin' => 'reaction rule', 'active' => TRUE));
foreach ($rules as $name => $rule) {
foreach ($rule->events() as $event) {
// Skip not defined events.
if (empty($events[$event])) {
continue;
}
// Create an event set if not yet done.
if (!isset($sets[$event])) {
$event_info = $events[$event] + array(
'variables' => isset($events[$event]['arguments']) ? $events[$event]['arguments'] : array(),
);
$sets[$event] = new RulesEventSet($event_info);
$sets[$event]->name = $event;
}
// If a rule is marked as dirty, check if this still applies.
if ($rule->dirty) {
rules_config_update_dirty_flag($rule);
}
if (!$rule->dirty) {
// Clone the rule to avoid modules getting the changed version from
// the static cache.
$sets[$event]->rule(clone $rule);
}
}
}
// Create cache items for all created sets.
foreach ($sets as $event => $set) {
$set->sortChildren();
$set->optimize();
// Allow modules to alter the cached event set.
drupal_alter('rules_event_set', $event, $set);
rules_set_cache('event_' . $event, $set);
}
// Cache a list of empty sets so we can use it to speed up later calls.
// See rules_get_event_set().
$empty_events = array_keys(array_diff_key($events, $sets));
variable_set('rules_empty_sets', array_flip($empty_events));
}
protected function stateVariables($element = NULL) {
return $this->availableVariables();
}
/**
* Do not save since this class is for caching purposes only.
*
* @see RulesPlugin::save()
*/
public function save($name = NULL, $module = 'rules') {
return FALSE;
}
}

View File

@@ -0,0 +1,364 @@
<?php
/**
* @file Contains classes for data processing.
*
* Data processors can be used to process element arguments on evaluation time,
* e.g. to apply input evaluators or to apply simple calculations to number
* arguments.
*/
/**
* Common base class for Rules data processors.
*/
abstract class RulesDataProcessor {
/**
* The processors' setting value.
*/
protected $setting = NULL;
/**
* Allows chaining processors. If set, the next processor to invoke.
*/
protected $processor = NULL;
/**
* Constructor.
*/
protected function __construct($setting, $param_info, $var_info = array(), $processor = NULL) {
$this->setting = $setting;
$this->processor = $processor;
}
/**
* Return $this or skip this processor by returning the next processor.
*/
protected function getPreparedValue() {
return isset($this->setting) && array_filter($this->setting) ? $this : $this->processor;
}
/**
* Returns whether the current user has permission to edit this chain of data
* processors.
*/
public function editAccess() {
return $this->access() && (!isset($this->processor) || $this->processor->editAccess());
}
/**
* Prepares the processor for parameters.
*
* It turns the settings into a suiting processor object, which gets invoked
* on evaluation time.
*
* @param $setting
* The processor settings which are to be prepared.
* @param $param_info
* The info about the parameter to prepare the processor for.
* @param $var_info
* An array of info about the available variables.
*/
public static function prepareSetting(&$setting, $param_info, $var_info = array()) {
$processor = NULL;
foreach (self::processors($param_info, FALSE) as $name => $info) {
if (!empty($setting[$name])) {
$object = new $info['class']($setting[$name], $param_info, $var_info, $processor);
$processor = $object->getPreparedValue();
}
}
$setting = $processor;
}
/**
* Attaches the form of applicable data processors.
*/
public static function attachForm(&$form, $settings, $param_info, $var_info, $access_check = TRUE) {
// If $settings is already prepared get the settings from the processors.
if ($settings instanceof RulesDataProcessor) {
$settings = $settings->getChainSettings();
}
foreach (self::processors($param_info, $access_check) as $name => $info) {
$settings += array($name => array());
$form[$name] = call_user_func(array($info['class'], 'form'), $settings[$name], $var_info);
$form[$name]['#weight'] = $info['weight'];
}
}
/**
* Returns defined data processors applicable for the given parameter.
* Optionally also access to the processors is checked.
*
* @param $param_info
* If given, only processors valid for this parameter are returned.
*/
public static function processors($param_info = NULL, $access_check = TRUE, $hook = 'data_processor_info') {
static $items = array();
if (!isset($items[$hook]['all'])) {
$items[$hook]['all'] = rules_fetch_data($hook);
uasort($items[$hook]['all'], array(__CLASS__, '_item_sort'));
}
// Data processing isn't supported for multiple types.
if (isset($param_info) && is_array($param_info['type'])) {
return array();
}
// Filter the items by type.
if (isset($param_info['type']) && !isset($items[$hook][$param_info['type']])) {
$items[$hook][$param_info['type']] = array();
foreach ($items[$hook]['all'] as $name => $info) {
// Check whether the parameter type matches the supported types.
$info += array('type' => 'text');
if (RulesData::typesMatch($param_info, $info, FALSE)) {
$items[$hook][$param_info['type']][$name] = $info;
}
}
}
// Apply the access check.
$return = isset($param_info['type']) ? $items[$hook][$param_info['type']] : $items[$hook]['all'];
if ($access_check) {
foreach ($return as $base => $info) {
if (!call_user_func(array($info['class'], 'access'))) {
unset($return[$base]);
}
}
}
return $return;
}
public static function _item_sort($a, $b) {
return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : 0);
}
/**
* Gets the settings array for this and all contained chained processors.
*/
public function getChainSettings() {
foreach ($this->unchain() as $name => $processor) {
$settings[$name] = $processor->getSetting();
}
return isset($settings) ? $settings : array();
}
/**
* Returns an array of modules which we depend on.
*/
public function dependencies() {
$used_processor_info = array_intersect_key($this->processors(), $this->unchain());
$modules = array();
foreach ($used_processor_info as $name => $info) {
$modules[] = $info['module'];
}
return array_filter($modules);
}
/**
* @return
* An array of processors keyed by processor name.
*/
protected function unchain() {
$processor = $this;
while ($processor instanceof RulesDataProcessor) {
$processors[get_class($processor)] = $processor;
$processor = $processor->processor;
}
// Note: Don't use the static context to call processors() here as we need a
// late binding to invoke the input evaluators version, if needed.
$return = array();
foreach ($this->processors() as $name => $info) {
if (isset($processors[$info['class']])) {
$return[$name] = $processors[$info['class']];
}
}
return $return;
}
/**
* Gets the settings of this processor.
*/
public function getSetting() {
return $this->setting;
}
/**
* Processes the value. If $this->processor is set, invoke this processor
* first so chaining multiple processors is working.
*
* @param $value
* The value to process.
* @param $info
* Info about the parameter for which we process the value.
* @param $state RulesState
* The rules evaluation state.
* @param $element RulesPlugin
* The element for which we process the value.
* @return
* The processed value.
*/
abstract public function process($value, $info, RulesState $state, RulesPlugin $element);
/**
* Return whether the current user has permission to use the processor.
*/
public static function access() {
return TRUE;
}
/**
* Defines the processor form element.
*
* @param $settings
* The settings of the processor.
* @param $var_info
* An array of info about the available variables.
*
* @return
* A form element structure.
*/
protected static function form($settings, $var_info) {
return array();
}
}
/**
* A base processor for use as input evaluators. Input evaluators are not listed
* in hook_rules_data_processor_info(). Instead they use
* hook_rules_evaluator_info() and get attached to input forms.
*/
abstract class RulesDataInputEvaluator extends RulesDataProcessor {
/**
* Overridden to invoke prepare().
*/
protected function __construct($setting, $param_info, $var_info = array(), $processor = NULL) {
$this->setting = TRUE;
$this->processor = $processor;
$this->prepare($setting, $var_info, $param_info);
}
/**
* Overridden to generate evaluator $options and invoke evaluate().
*/
public function process($value, $info, RulesState $state, RulesPlugin $element, $options = NULL) {
$options = isset($options) ? $options : $this->getEvaluatorOptions($info, $state, $element);
$value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element, $options) : $value;
return $this->evaluate($value, $options, $state);
}
/**
* Generates the evaluator $options.
*/
protected function getEvaluatorOptions($info, $state, $element) {
$cache = rules_get_cache();
$languages = language_list();
$info += array(
'cleaning callback' => isset($cache['data info'][$info['type']]['cleaning callback']) ? $cache['data info'][$info['type']]['cleaning callback'] : FALSE,
'sanitize' => FALSE,
);
$options = array_filter(array(
'language' => $info['#langcode'] != LANGUAGE_NONE && isset($languages[$info['#langcode']]) ? $languages[$info['#langcode']] : NULL,
'callback' => $info['cleaning callback'],
'sanitize' => $info['sanitize'],
));
return $options;
}
/**
* Overriden to prepare input evaluator processors. The setting is expected
* to be the input value to be evaluated later on and is replaced by the
* suiting processor.
*/
public static function prepareSetting(&$setting, $param_info, $var_info = array()) {
$processor = NULL;
foreach (self::evaluators($param_info, FALSE) as $name => $info) {
$object = new $info['class']($setting, $param_info, $var_info, $processor);
$processor = $object->getPreparedValue();
}
$setting = $processor;
}
protected function getPreparedValue() {
return isset($this->setting) ? $this : $this->processor;
}
/**
* Overriden to just attach the help() of evaluators.
*/
public static function attachForm(&$form, $settings, $param_info, $var_info, $access_check = TRUE) {
foreach (self::evaluators($param_info, $access_check) as $name => $info) {
$form['help'][$name] = call_user_func(array($info['class'], 'help'), $var_info, $param_info);
$form['help'][$name]['#weight'] = $info['weight'];
}
}
/**
* Returns all input evaluators that can be applied to the parameters needed
* type.
*/
public static function evaluators($param_info = NULL, $access_check = TRUE) {
return parent::processors($param_info, $access_check, 'evaluator_info');
}
/**
* Overridden to default to our hook, thus being equivalent to
* self::evaluators().
*/
public static function processors($param_info = NULL, $access_check = TRUE, $hook = 'evaluator_info') {
return parent::processors($param_info, $access_check, $hook);
}
/**
* Prepares the evalution, e.g. to determine whether the input evaluator has
* been used. If this evaluator should be skipped just unset $this->setting.
*
* @param $text
* The text to evaluate later on.
* @param $variables
* An array of info about available variables.
* @param $param_info
* (optional) An array of information about the handled parameter value.
* For backward compatibility, this parameter is not required.
*/
abstract public function prepare($text, $variables);
/**
* Apply the input evaluator.
*
* @param $text
* The text to evaluate.
* @param $options
* A keyed array of settings and flags to control the processing.
* Supported options are:
* - language: A language object to be used when processing.
* - callback: A callback function that will be used to post-process
* replacements that might be incorporated, so they can be cleaned in a
* certain way.
* - sanitize: A boolean flag indicating whether incorporated replacements
* should be sanitized.
* @param RulesState
* The rules evaluation state.
*
* @return
* The evaluated text.
*/
abstract public function evaluate($text, $options, RulesState $state);
/**
* Provide some usage help for the evaluator.
*
* @param $variables
* An array of info about available variables.
* @param $param_info
* (optional) An array of information about the handled parameter value.
* For backward compatibility, this parameter is not required.
*
* @return
* A renderable array.
*/
public static function help($variables) {
return array();
}
}

View File

@@ -0,0 +1,770 @@
<?php
/**
* @file Contains the state and data related stuff.
*/
/**
* 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.
*/
class RulesState {
/**
* Globally keeps the ids of rules blocked due to recursion prevention.
*/
static protected $blocked = array();
/**
* The known variables.
*/
public $variables = array();
/**
* Holds info about the variables.
*/
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;
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 $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 $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);
}
// No entity, so try saving the parent.
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 the info about to be saved variables form the given state into the
* existing state. Therefor we can aggregate saves from invoked components.
* Merged in saves are removed from the given state, but not mergable 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 $selector
* The selector string, e.g. "node:author:mail".
* @param $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 usign 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');
}
public function __wakeup() {
$this->save = new ArrayObject();
}
/**
* Restore 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.
*/
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;
}
}
/**
* A class holding static methods related to data.
*/
class RulesData {
/**
* Returns whether the type match. They match if type1 is compatible to type2.
*
* @param $var_info
* The name of the type to check for whether it is compatible to type2.
* @param $param_info
* The type expression to check for.
* @param $ancestors
* Whether sub-type relationships for checking type compatibility should be
* taken into account. Defaults to TRUE.
*
* @return
* Whether the types match.
*/
public static function typesMatch($var_info, $param_info, $ancestors = TRUE) {
$var_type = $var_info['type'];
$param_type = $param_info['type'];
if ($param_type == '*' || $param_type == 'unknown') {
return TRUE;
}
if ($var_type == $param_type) {
// Make sure the bundle matches, if specified by the parameter.
return !isset($param_info['bundles']) || isset($var_info['bundle']) && in_array($var_info['bundle'], $param_info['bundles']);
}
// Parameters may specify multiple types using an array.
$valid_types = is_array($param_type) ? $param_type : array($param_type);
if (in_array($var_type, $valid_types)) {
return TRUE;
}
// Check for sub-type relationships.
if ($ancestors && !isset($param_info['bundles'])) {
$cache = &rules_get_cache();
self::typeCalcAncestors($cache, $var_type);
// If one of the types is an ancestor return TRUE.
return (bool)array_intersect_key($cache['data_info'][$var_type]['ancestors'], array_flip($valid_types));
}
return FALSE;
}
protected static function typeCalcAncestors(&$cache, $type) {
if (!isset($cache['data_info'][$type]['ancestors'])) {
$cache['data_info'][$type]['ancestors'] = array();
if (isset($cache['data_info'][$type]['parent']) && $parent = $cache['data_info'][$type]['parent']) {
$cache['data_info'][$type]['ancestors'][$parent] = TRUE;
self::typeCalcAncestors($cache, $parent);
// Add all parent ancestors to our own ancestors.
$cache['data_info'][$type]['ancestors'] += $cache['data_info'][$parent]['ancestors'];
}
// For special lists like list<node> add in "list" as valid parent.
if (entity_property_list_extract_type($type)) {
$cache['data_info'][$type]['ancestors']['list'] = TRUE;
}
}
}
/**
* Returns matching data variables or properties for the given info and the to
* be configured parameter.
*
* @param $source
* Either an array of info about available variables or a entity metadata
* wrapper.
* @param $param_info
* The information array about the to be configured parameter.
* @param $prefix
* An optional prefix for the data selectors.
* @param $recursions
* The number of recursions used to go down the tree. Defaults to 2.
* @param $suggestions
* Whether possibilities to recurse are suggested as soon as the deepest
* level of recursions is reached. Defaults to TRUE.
*
* @return
* An array of info about matching variables or properties that match, keyed
* with the data selector.
*/
public static function matchingDataSelector($source, $param_info, $prefix = '', $recursions = 2, $suggestions = TRUE) {
// If an array of info is given, get entity metadata wrappers first.
$data = NULL;
if (is_array($source)) {
foreach ($source as $name => $info) {
$source[$name] = rules_wrap_data($data, $info, TRUE);
}
}
$matches = array();
foreach ($source as $name => $wrapper) {
$info = $wrapper->info();
$name = str_replace('_', '-', $name);
if (self::typesMatch($info, $param_info)) {
$matches[$prefix . $name] = $info;
if (!is_array($source) && $source instanceof EntityListWrapper) {
// Add some more possible list items.
for ($i = 1; $i < 4; $i++) {
$matches[$prefix . $i] = $info;
}
}
}
// Recurse later on to get an improved ordering of the results.
if ($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper) {
$recurse[$prefix . $name] = $wrapper;
if ($recursions > 0) {
$matches += self::matchingDataSelector($wrapper, $param_info, $prefix . $name . ':', $recursions - 1, $suggestions);
}
elseif ($suggestions) {
// We may not recurse any more, but indicate the possibility to recurse.
$matches[$prefix . $name . ':'] = $wrapper->info();
if (!is_array($source) && $source instanceof EntityListWrapper) {
// Add some more possible list items.
for ($i = 1; $i < 4; $i++) {
$matches[$prefix . $i . ':'] = $wrapper->info();
}
}
}
}
}
return $matches;
}
/**
* Adds asserted metadata to the variable info. In case there are already
* assertions for a variable, the assertions are merged such that both apply.
*
* @see RulesData::applyMetadataAssertions()
*/
public static function addMetadataAssertions($var_info, $assertions) {
foreach ($assertions as $selector => $assertion) {
// Convert the selector back to underscores, such it matches the varname.
$selector = str_replace('-', '_', $selector);
$parts = explode(':', $selector);
if (isset($var_info[$parts[0]])) {
// Apply the selector to determine the right target array. We build an
// array like
// $var_info['rules assertion']['property1']['property2']['#info'] = ..
$target = &$var_info[$parts[0]]['rules assertion'];
foreach (array_slice($parts, 1) as $part) {
$target = &$target[$part];
}
// In case the assertion is directly for a variable, we have to modify
// the variable info directly. In case the asserted property is nested
// the info-has to be altered by RulesData::applyMetadataAssertions()
// before the child-wrapper is created.
if (count($parts) == 1) {
// Support asserting a type in case of generic entity references only.
if (isset($assertion['type']) && $var_info[$parts[0]]['type'] == 'entity') {
if (entity_get_info($assertion['type'])) {
$var_info[$parts[0]]['type'] = $assertion['type'];
}
unset($assertion['type']);
}
// Add any single bundle directly to the variable info, so the
// variable fits as argument for parameters requiring the bundle.
if (isset($assertion['bundle']) && count($bundles = (array) $assertion['bundle']) == 1) {
$var_info[$parts[0]]['bundle'] = reset($bundles);
}
}
// Add the assertions, but merge them with any previously added
// assertions if necessary.
$target['#info'] = isset($target['#info']) ? rules_update_array($target['#info'], $assertion) : $assertion;
// Add in a callback that the entity metadata wrapper pick up for
// altering the property info, such that we can add in the assertions.
$var_info[$parts[0]] += array('property info alter' => array('RulesData', 'applyMetadataAssertions'));
// In case there is a VARNAME_unchanged variable as it is used in update
// hooks, assume the assertions are valid for the unchanged variable
// too.
if (isset($var_info[$parts[0] . '_unchanged'])) {
$name = $parts[0] . '_unchanged';
$var_info[$name]['rules assertion'] = $var_info[$parts[0]]['rules assertion'];
$var_info[$name]['property info alter'] = array('RulesData', 'applyMetadataAssertions');
if (isset($var_info[$parts[0]]['bundle']) && !isset($var_info[$name]['bundle'])) {
$var_info[$name]['bundle'] = $var_info[$parts[0]]['bundle'];
}
}
}
}
return $var_info;
}
/**
* Property info alter callback for the entity metadata wrapper for applying
* the rules metadata assertions.
*
* @see RulesData::addMetadataAssertions()
*/
public static function applyMetadataAssertions(EntityMetadataWrapper $wrapper, $property_info) {
$info = $wrapper->info();
if (!empty($info['rules assertion'])) {
$assertion = $info['rules assertion'];
// In case there are list-wrappers pass through the assertions of the item
// but make sure we only apply the assertions for the list items for
// which the conditions are executed.
if (isset($info['parent']) && $info['parent'] instanceof EntityListWrapper) {
$assertion = isset($assertion[$info['name']]) ? $assertion[$info['name']] : array();
}
// Support specifying multiple bundles, whereas the added properties are
// the intersection of the bundle properties.
if (isset($assertion['#info']['bundle'])) {
$bundles = (array) $assertion['#info']['bundle'];
foreach ($bundles as $bundle) {
$properties[] = isset($property_info['bundles'][$bundle]['properties']) ? $property_info['bundles'][$bundle]['properties'] : array();
}
// Add the intersection.
$property_info['properties'] += count($properties) > 1 ? call_user_func_array('array_intersect_key', $properties) : reset($properties);
}
// Support adding directly asserted property info.
if (isset($assertion['#info']['property info'])) {
$property_info['properties'] += $assertion['#info']['property info'];
}
// Pass through any rules assertion of properties to their info, so any
// derived wrappers apply it.
foreach (element_children($assertion) as $key) {
$property_info['properties'][$key]['rules assertion'] = $assertion[$key];
$property_info['properties'][$key]['property info alter'] = array('RulesData', 'applyMetadataAssertions');
// Apply any 'type' and 'bundle' assertion directly to the propertyinfo.
if (isset($assertion[$key]['#info']['type'])) {
$type = $assertion[$key]['#info']['type'];
// Support asserting a type in case of generic entity references only.
if ($property_info['properties'][$key]['type'] == 'entity' && entity_get_info($type)) {
$property_info['properties'][$key]['type'] = $type;
}
}
if (isset($assertion[$key]['#info']['bundle'])) {
$bundle = (array) $assertion[$key]['#info']['bundle'];
// Add any single bundle directly to the variable info, so the
// property fits as argument for parameters requiring the bundle.
if (count($bundle) == 1) {
$property_info['properties'][$key]['bundle'] = reset($bundle);
}
}
}
}
return $property_info;
}
/**
* Property info alter callback for the entity metadata wrapper to inject
* metadata for the 'site' variable. In contrast to doing this via
* hook_rules_data_info() this callback makes use of the already existing
* property info cache for site information of entity metadata.
*
* @see RulesPlugin::availableVariables()
*/
public static function addSiteMetadata(EntityMetadataWrapper $wrapper, $property_info) {
$site_info = entity_get_property_info('site');
$property_info['properties'] += $site_info['properties'];
// Also invoke the usual callback for altering metadata, in case actions
// have specified further metadata.
return RulesData::applyMetadataAssertions($wrapper, $property_info);
}
}
/**
* A wrapper class similar to the EntityDrupalWrapper, but for non-entities.
*
* This class is intended to serve as base for a custom wrapper classes of
* identifiable data types, which are non-entities. By extending this class only
* the extractIdentifier() and load() methods have to be defined.
* In order to make the data type savable implement the
* RulesDataWrapperSavableInterface.
*
* That way it is possible for non-entity data types to be work with Rules, i.e.
* one can implement a 'ui class' with a direct input form returning the
* identifier of the data. However, instead of that it is suggested to implement
* an entity type, such that the same is achieved via general API functions like
* entity_load().
*/
abstract class RulesIdentifiableDataWrapper extends EntityStructureWrapper {
/**
* Contains the id.
*/
protected $id = FALSE;
/**
* Construct a new wrapper object.
*
* @param $type
* The type of the passed data.
* @param $data
* Optional. The data to wrap or its identifier.
* @param $info
* Optional. Used internally to pass info about properties down the tree.
*/
public function __construct($type, $data = NULL, $info = array()) {
parent::__construct($type, $data, $info);
$this->setData($data);
}
/**
* Sets the data internally accepting both the data id and object.
*/
protected function setData($data) {
if (isset($data) && $data !== FALSE && !is_object($data)) {
$this->id = $data;
$this->data = FALSE;
}
elseif (is_object($data)) {
// We got the data object passed.
$this->data = $data;
$id = $this->extractIdentifier($data);
$this->id = isset($id) ? $id : FALSE;
}
}
/**
* Returns the identifier of the wrapped data.
*/
public function getIdentifier() {
return $this->dataAvailable() && $this->value() ? $this->id : NULL;
}
/**
* Overridden.
*/
public function value(array $options = array()) {
$this->setData(parent::value());
if (!$this->data && !empty($this->id)) {
// Lazy load the data if necessary.
$this->data = $this->load($this->id);
if (!$this->data) {
throw new EntityMetadataWrapperException('Unable to load the ' . check_plain($this->type) . ' with the id ' . check_plain($this->id) . '.');
}
}
return $this->data;
}
/**
* Overridden to support setting the data by either the object or the id.
*/
public function set($value) {
if (!$this->validate($value)) {
throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
}
// As custom wrapper classes can only appear for Rules variables, but not
// as properties we don't have to care about updating the parent.
$this->clear();
$this->setData($value);
return $this;
}
/**
* Overridden.
*/
public function clear() {
$this->id = NULL;
parent::clear();
}
/**
* Prepare for serializiation.
*/
public function __sleep() {
$vars = parent::__sleep();
// Don't serialize the loaded data, except for the case the data is not
// saved yet.
if (!empty($this->id)) {
unset($vars['data']);
}
return $vars;
}
public function __wakeup() {
if ($this->id !== FALSE) {
// Make sure data is set, so the data will be loaded when needed.
$this->data = FALSE;
}
}
/**
* Extract the identifier of the given data object.
*
* @return
* The extracted identifier.
*/
abstract protected function extractIdentifier($data);
/**
* Load a data object given an identifier.
*
* @return
* The loaded data object, or FALSE if loading failed.
*/
abstract protected function load($id);
}
/**
* Interface that allows custom wrapper classes to declare that they are savable.
*/
interface RulesDataWrapperSavableInterface {
/**
* Save the currently wrapped data.
*/
public function save();
}

View File

@@ -0,0 +1,664 @@
<?php
/**
* @file
* Contains code for upgrading rule configurations from 6.x-1.x to 7.x-2.x.
*/
/**
* Form builder for the upgrade page.
*/
function rules_upgrade_form($form, &$form_state) {
if (!empty($form_state['export'])) {
foreach ($form_state['export'] as $key => $export) {
// Rules have been already converted and exported, so show the export.
$form['export'][$key] = array(
'#type' => 'textarea',
'#title' => t('Export %name', array('%name' => $key)),
'#description' => t('For importing copy the content of the text area and paste it into the import page of the Rules admin UI. In case the export does not pass the integrity check during import, try using the save to database method instead and manually fix your configuration after conversion.'),
'#rows' => 10,
'#default_value' => $export,
);
}
return $form;
}
$form['help'] = array(
'#prefix' => '<p>',
'#suffix' => '</p>',
'#markup' => t('This form allows you to convert rules or rule sets from Rules 1.x to Rules 2.x.') . ' ' .
t('In order to convert a rule or rule set make sure you have all dependend modules installed and upgraded, i.e. modules which provide Rules integration that has been used in your rules or rule sets. In addition those modules may need to implement some Rules specific update hooks for the conversion to properly work.') . ' ' .
t('After conversion, the old rules and rule sets will stay in the database until you manually delete them. That way you can make sure the conversion has gone right before you delete the old rules and rule sets.')
);
$option_rules = $option_sets = array();
if (!db_table_exists('rules_rules')) {
drupal_set_message('There are no Rules 1.x rules or rule sets left to convert.', 'error');
}
else {
foreach (_rules_upgrade_fetch_all_rules() as $name => $rule) {
if (!empty($rule['#set']) && strpos($rule['#set'], 'event_') === 0) {
$option_rules[$name] = $name . ': ' . $rule['#label'];
}
}
$query = db_select('rules_sets', 'r')->fields('r');
foreach ($query->execute() as $row) {
$set = unserialize($row->data);
$option_sets[$row->name] = $row->name . ': ' . $set['label'];
}
$form['clear'] = array(
'#prefix' => '<p>',
'#suffix' => '</p>',
'#markup' => t('Once you have successfully converted your configuration, you can clean up your database and <a href="!url">delete</a> all Rules 1.x configurations.', array('!url' => url('admin/config/workflow/rules/upgrade/clear')))
);
}
$form['rules'] = array(
'#type' => 'select',
'#title' => t('Rules'),
'#options' => $option_rules,
'#multiple' => TRUE,
);
$form['sets'] = array(
'#type' => 'select',
'#title' => t('Rule sets'),
'#options' => $option_sets,
'#multiple' => TRUE,
);
$form['method'] = array(
'#type' => 'radios',
'#title' => t('Method'),
'#options' => array(
'export' => t('Convert configuration and export it.'),
'save' => t('Convert configuration and save it.'),
),
'#default_value' => 'export',
);
$form['actions']['convert'] = array(
'#type' => 'submit',
'#value' => t('Convert'),
'#disabled' => !db_table_exists('rules_rules')
);
return $form;
}
/**
* Submit handler for the form.
*/
function rules_upgrade_form_submit($form, &$form_state) {
// Load all rules includes and install files so modules may put there upgrade
// information in both locations.
module_load_all_includes('rules.inc');
module_load_all_includes('install');
$configs = array();
try {
foreach ($form_state['values']['rules'] as $name) {
drupal_set_message(t('Converting %plugin %name...', array('%plugin' => t('rule'), '%name' => $name)));
$configs[$name] = rules_upgrade_convert_rule($name, _rules_upgrade_fetch_item($name, 'rules_rules'));
}
foreach ($form_state['values']['sets'] as $name) {
drupal_set_message(t('Converting %plugin %name...', array('%plugin' => t('rule set'), '%name' => $name)));
$configs[$name] = rules_upgrade_convert_rule_set($name, _rules_upgrade_fetch_item($name, 'rules_sets'));
}
drupal_set_message(t('Completed.'));
if ($form_state['values']['method'] == 'save') {
foreach ($configs as $config) {
$config->save();
}
drupal_set_message(t('Converted configurations have been saved to the database and will appear in the Rules administration interface.'));
}
elseif ($form_state['values']['method'] == 'export') {
$export = array();
foreach ($configs as $name => $config) {
$export[$name] = $config->export();
}
$form_state['export'] = $export;
$form_state['rebuild'] = TRUE;
}
}
catch (RulesException $e) {
drupal_set_message($e->getMessage(), 'error');
}
}
/**
* Confirm form for deleting data.
*/
function rules_upgrade_confirm_clear_form($form, $form_state) {
$confirm_question = t('Are you sure you want to drop the Rules 1.x tables from the database?');
$confirm_question_long = t('Are you sure you want to drop the Rules 1.x tables from the database? All Rules 1.x configurations will be deleted regardless whether they have been already converted.') . ' ' . t('This action cannot be undone.');
return confirm_form($form, $confirm_question, 'admin/config/workflow/rules/upgrade', $confirm_question_long, t('Delete data'), t('Cancel'));
}
function rules_upgrade_confirm_clear_form_submit($form, &$form_state) {
db_drop_table('rules_rules');
db_drop_table('rules_sets');
db_drop_table('rules_scheduler_d6');
drupal_set_message(t('Rules 1.x configurations have been deleted.'));
$form_state['redirect'] = 'admin';
}
/**
* Fetches a single item (rule | rule set).
*/
function _rules_upgrade_fetch_item($name, $table) {
$query = db_select($table, 'r')->fields('r')->condition('name', $name);
$row = $query->execute()->fetchAssoc();
return unserialize($row['data']);
}
/**
* Fetches all rules.
*/
function _rules_upgrade_fetch_all_rules() {
$static = drupal_static(__FUNCTION__);
if (!isset($static)) {
$query = db_select('rules_rules', 'r')->fields('r');
$static['rules'] = array();
foreach ($query->execute() as $row) {
$static['rules'][$row->name] = unserialize($row->data);
}
}
return $static['rules'];
}
/**
* Converts a single reaction rule.
*/
function rules_upgrade_convert_rule($name, $cfg_old) {
$config = rules_upgrade_plugin_factory($cfg_old);
$config->name = $name;
if ($config instanceof RulesReactionRule) {
rules_upgrade_convert_element($cfg_old, $config);
}
return $config;
}
/**
* Converts a single rule set, including all of its rules.
*/
function rules_upgrade_convert_rule_set($name, $cfg_old) {
$config = rules_plugin_factory('rule set');
$config->name = $name;
foreach (array('label', 'weight') as $key) {
if (isset($element[$key])) {
$target->$key = $element[$key];
}
}
if (isset($cfg_old['arguments'])) {
$vars = &$config->componentVariables();
foreach ($cfg_old['arguments'] as $var_name => $info) {
// Map data types as required.
if ($info['type'] == 'string') {
$info['type'] = 'text';
}
$vars[$var_name] = $info;
}
}
// Add in all rules of the set.
foreach(_rules_upgrade_fetch_all_rules() as $rule_name => $rule) {
if ($rule['#set'] == $name) {
drupal_set_message(' >> ' . t('Converting %plugin %name...', array('%plugin' => t('rule'), '%name' => $rule_name . ': ' . $rule['#label'])));
$new_rule = rules_upgrade_plugin_factory($rule);
rules_upgrade_convert_element($rule, $new_rule);
$new_rule->setParent($config);
}
}
return $config;
}
/**
* Convert a single element.
*
* @param $element
* The element to convert.
* @param $target
* The converted element to write to.
*/
function rules_upgrade_convert_element(array $element, RulesPlugin $target) {
foreach (array('active', 'label', 'weight') as $key) {
if (isset($element['#' . $key])) {
$target->$key = $element['#' . $key];
}
}
// Go through the parameters and take over its configuration if possible.
foreach ($target->pluginParameterInfo() as $name => $info) {
rules_upgrade_element_parameter_settings($element, $target, $name);
}
// @todo: Care about php input evaluator for non-text parameters.
// Take care of variable names and labels.
foreach ($target->pluginProvidesVariables() as $name => $info) {
rules_upgrade_element_variable_settings($element, $target, $name);
}
if ($target instanceof RulesConditionInterface && !empty($element['#negate'])) {
$target->negate(TRUE);
}
if ($target instanceof RulesReactionRule) {
// Cut of the 'event_' prefix.
$target->event(substr($element['#set'], 6));
}
if ($element['#type'] == 'rule') {
if (!empty($element['#conditions'])) {
foreach (element_children($element['#conditions']) as $key) {
$child = rules_upgrade_plugin_factory($element['#conditions'][$key]);
rules_upgrade_convert_element($element['#conditions'][$key], $child);
$target->condition($child);
}
}
if (!empty($element['#actions'])) {
foreach (element_children($element['#actions']) as $key) {
$child = rules_upgrade_plugin_factory($element['#actions'][$key]);
rules_upgrade_convert_element($element['#actions'][$key], $child);
$target->action($child);
}
}
}
// Invoke action/condition specific hooks and a general one.
if (($element['#type'] == 'action' || $element['#type'] == 'condition')) {
if (function_exists($function = $element['#name'] .'_upgrade')) {
$element_name = $function($element, $target);
}
elseif (isset($element['#info']['base']) && function_exists($function = $element['#info']['base'] .'_upgrade')) {
$element_name = $function($element, $target);
}
}
drupal_alter('rules_element_upgrade', $element, $target);
// Recurse down, if necessary.
foreach (element_children($element) as $key) {
$child = rules_upgrade_plugin_factory($element[$key]);
rules_upgrade_convert_element($element[$key], $child);
$child->setParent($target);
}
if ($target instanceof RulesContainerPlugin) {
$target->sortChildren();
}
}
/**
* Creates the right element.
*/
function rules_upgrade_plugin_factory($element) {
if ($element['#type'] == 'rule' && !empty($element['#set']) && strpos($element['#set'], 'event_') === 0) {
return rules_plugin_factory('reaction rule');
}
switch ($element['#type']) {
case 'OR':
return rules_plugin_factory('or');
case 'AND':
return rules_plugin_factory('and');
default:
return rules_plugin_factory($element['#type']);
case 'action':
case 'condition':
if (isset($element['#name'])) {
// Try to come up with the right action/condition name ourself, then
// invoke a hook.
$cache = rules_get_cache();
$items = $cache[$element['#type'] == 'action' ? 'action_info' : 'condition_info'];
if (isset($items[$element['#name']])) {
$element_name = $element['#name'];
}
elseif (($name = str_replace('rules_', '', $element['#name'])) && isset($items[$name])) {
$element_name = $name;
}
elseif (($name = str_replace($element['#type'] . '_', '', $element['#name'])) && isset($items[$name])) {
$element_name = $name;
}
elseif (($name = str_replace('rules_' . $element['#type'] . '_', '', $element['#name'])) && isset($items[$name])) {
$element_name = $name;
}
elseif (isset($element['#info']['base']) && isset($items[$element['#info']['base']])) {
$element_name = $name;
}
// Call the upgrade callback if one has been defined.
if (function_exists($function = $element['#name'] .'_upgrade_map_name') || (isset($element['#info']['base']) && function_exists($function = $element['#info']['base'] .'_upgrade_map_name'))) {
$element_name = $function($element);
}
if (!isset($element_name)) {
throw new RulesIntegrityException(t("Cannot find @plugin %name. Maybe a required is missing or the module has not implemented the upgrade functionality.", array('@plugin' => $element['#type'], '%name' => $element['#name'])));
}
return rules_plugin_factory($element['#type'], $element_name);
}
break;
}
}
/**
* Converts the settings for a given parameter.
*/
function rules_upgrade_element_parameter_settings($element, $target, $name, $new_name = NULL) {
if (!isset($new_name)) {
$new_name = $name;
}
if (isset($element['#settings'][$name])) {
// In case a single token has been used, just convert it to a data
// selector.
if (is_string($element['#settings'][$name]) && preg_match("/\[(.*)\]$/", $element['#settings'][$name], $matches)) {
$target->settings[$new_name . ':select'] = $matches[1];
}
else {
$target->settings[$new_name] = $element['#settings'][$name];
}
}
elseif (isset($element['#settings']['#argument map'][$name])) {
$target->settings[$new_name . ':select'] = $element['#settings']['#argument map'][$name];
}
}
/**
* Converts the settings for a given variable.
*/
function rules_upgrade_element_variable_settings($element, $target, $name, $new_name = NULL) {
if (!isset($new_name)) {
$new_name = $name;
}
if (isset($element['#settings']['#argument map'][$name])) {
$target->settings[$new_name . ':var'] = $element['#settings']['#argument map'][$name];
$target->settings[$new_name . ':label'] = $element['#info']['new variables'][$target->settings[$new_name . ':var']]['label'];
}
}
/**
* Upgrade callbacks for upgrading the provided Rules 1.x integration.
*/
// Comment.module integration.
function rules_action_load_comment_upgrade_map_name($element) {
return 'entity_fetch';
}
function rules_action_load_comment_upgrade($element, $target) {
$target->settings['type'] = 'comment';
rules_upgrade_element_parameter_settings($element, $target, 'cid', 'id');
rules_upgrade_element_variable_settings($element, $target, 'comment_loaded', 'entity_fetched');
}
// Node.module integration.
function rules_condition_content_is_type_upgrade_map_name($element) {
return 'node_is_of_type';
}
function rules_condition_content_is_published_upgrade_map_name($element) {
return 'node_is_published';
}
function rules_condition_content_is_sticky_upgrade_map_name($element) {
return 'node_is_sticky';
}
function rules_condition_content_is_promoted_upgrade_map_name($element) {
return 'node_is_promoted';
}
function rules_condition_content_is_new_upgrade_map_name($element) {
return 'entity_is_new';
}
function rules_condition_content_is_new_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'node', 'entity');
}
function rules_action_node_set_author_upgrade_map_name($element) {
return 'data_set';
}
function rules_action_node_set_author_upgrade($element, $target) {
$target->settings['data:select'] = $element['#settings']['#argument map']['node'] . ':author';
$target->settings['value:select'] = $element['#settings']['#argument map']['author'];
}
function rules_action_node_load_author_upgrade_map_name($element) {
return 'entity_fetch';
}
function rules_action_node_load_author_upgrade($element, $target) {
$target->settings['type'] = 'user';
$target->settings['id'] = $element['#settings']['#argument map']['node'] . ':author:uid';
}
function rules_action_set_node_title_upgrade_map_name($element) {
return 'data_set';
}
function rules_action_set_node_title_upgrade($element, $target) {
$target->settings['data:select'] = $element['#settings']['#argument map']['node'] . ':title';
$target->settings['value'] = $element['#settings']['title'];
}
function rules_action_add_node_upgrade_map_name($element) {
return 'entity_create';
}
function rules_action_add_node_upgrade($element, $target) {
$target->settings['type'] = 'node';
rules_upgrade_element_parameter_settings($element, $target, 'title', 'param_title');
rules_upgrade_element_parameter_settings($element, $target, 'author', 'param_author');
rules_upgrade_element_parameter_settings($element, $target, 'type', 'param_type');
rules_upgrade_element_variable_settings($element, $target, 'node_added', 'entity_created');
if (!empty($element['#settings']['node_access'])) {
drupal_set_message(t('Warning: The node-access check option for the node creation action is not supported any more.'));
}
}
function rules_action_load_node_upgrade_map_name($element) {
return 'entity_fetch';
}
function rules_action_load_node_upgrade($element, $target) {
$target->settings['type'] = 'node';
rules_upgrade_element_parameter_settings($element, $target, 'nid', 'id');
rules_upgrade_element_parameter_settings($element, $target, 'vid', 'revision_id');
rules_upgrade_element_variable_settings($element, $target, 'node_loaded', 'entity_fetched');
}
function rules_action_delete_node_upgrade_map_name($element) {
return 'entity_delete';
}
function rules_action_delete_node_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'node', 'entity');
}
function rules_core_node_publish_action_upgrade_map_name($element) {
return 'node_publish';
}
function rules_core_node_unpublish_action_upgrade_map_name($element) {
return 'node_unpublish';
}
function rules_core_node_make_sticky_action_upgrade_map_name($element) {
return 'node_make_sticky_action';
}
function rules_core_node_make_unsticky_action_upgrade_map_name($element) {
return 'node_make_unsticky_action';
}
function rules_core_node_promote_action_upgrade_map_name($element) {
return 'node_promote_action';
}
function rules_core_node_unpromote_action_upgrade_map_name($element) {
return 'node_unpromote_action';
}
// Path.module integration.
function rules_condition_url_has_alias_upgrade_map_name($element) {
return 'path_has_alias';
}
function rules_condition_url_has_alias_upgrade($element, $target) {
$target->settings['source'] = $element['#settings']['src'];
$target->settings['alias'] = $element['#settings']['dst'];
}
function rules_condition_alias_exists_upgrade_map_name($element) {
return 'path_alias_exists';
}
function rules_condition_alias_exists_upgrade($element, $target) {
$target->settings['alias'] = $element['#settings']['dst'];
}
function rules_action_path_alias_upgrade($element, $target) {
$target->settings['source'] = $element['#settings']['src'];
$target->settings['alias'] = $element['#settings']['dst'];
}
function rules_action_node_path_alias_upgrade($element, $target) {
$target->settings['alias'] = $element['#settings']['dst'];
}
// PHP.module integration.
function rules_condition_custom_php_upgrade_map_name($element) {
return 'php_eval';
}
function rules_action_custom_php_upgrade_map_name($element) {
return 'php_eval';
}
// General Rules integration.
function rules_condition_text_compare_upgrade_map_name($element) {
// @todo: Support regex.
return 'data_is';
}
function rules_condition_text_compare_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'text1', 'data');
rules_upgrade_element_parameter_settings($element, $target, 'text2', 'value');
}
function rules_condition_number_compare_upgrade_map_name($element) {
return 'data_is';
}
function rules_condition_number_compare_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'number1', 'data');
rules_upgrade_element_parameter_settings($element, $target, 'number2', 'value');
}
function rules_condition_check_boolean_upgrade_map_name($element) {
return 'data_is';
}
function rules_condition_check_boolean_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'boolean', 'data');
$target->settings['value'] = TRUE;
}
function rules_action_invoke_set_upgrade_map_name($element) {
return 'component_' . $element['#info']['set'];
}
function rules_action_invoke_set_upgrade($element, $target) {
foreach ($element['#info']['arguments'] as $name => $info) {
rules_upgrade_element_parameter_settings($element, $target, $name);
}
}
function rules_action_save_variable_upgrade_map_name($element) {
return isset($element['#info']['new variables']) ? 'variable_add' : 'entity_save';
}
function rules_action_save_variable_upgrade($element, $target) {
$type = $element['#info']['arguments']['var_name']['default value'];
if (isset($element['#info']['new variables'])) {
$target->settings['type'] = $type;
rules_upgrade_element_parameter_settings($element, $target, $type, 'value');
rules_upgrade_element_variable_settings($element, $target, $type, 'variable_added');
}
else {
rules_upgrade_element_parameter_settings($element, $target, $type, 'entity');
}
}
// System.module integration.
function rules_action_set_breadcrumb_upgrade_map_name($element) {
return 'breadcumb_set';
}
function rules_action_mail_to_user_upgrade_map_name($element) {
return 'mail';
}
function rules_action_mail_to_user_upgrade($element, $target) {
$target->settings['to:select'] = $element['#settings']['#argument map']['user'] . ':mail';
}
function rules_action_drupal_goto_upgrade_map_name($element) {
return 'redirect';
}
function rules_action_drupal_goto_upgrade($element, $target) {
$settings = $element['#settings'];
$target->settings['url'] = $settings['path'];
$target->settings['url'] .= $settings['query'] ? '?' . $settings['query'] : '';
$target->settings['url'] .= $settings['fragment'] ? '#' . $settings['fragment'] : '';
if ($settings['immediate']) {
drupal_set_message(t("Warning: The 'immediate' option for the page redirect action has been dropped in Rules 2.x."));
}
}
function rules_action_watchdog_upgrade_map_name($element) {
// @todo: Support action in Rules 2.x!
return NULL;
}
// Taxonomy.module integration.
// @todo: Finish.
function rules_action_taxonomy_load_term_upgrade_map_name($element) {
return 'entity_fetch';
}
function rules_action_taxonomy_add_term_upgrade_map_name($element) {
return 'entity_create';
}
function rules_action_taxonomy_delete_term_upgrade_map_name($element) {
return 'entity_delete';
}
function rules_action_taxonomy_term_assign_to_content_upgrade_map_name($element) {
// @todo : list.
return NULL;
}
function rules_action_taxonomy_term_remove_from_content_upgrade_map_name($element) {
// @todo : list.
return NULL;
}
function rules_action_taxonomy_load_vocab_upgrade_map_name($element) {
return 'entity_fetch';
}
function rules_action_taxonomy_add_vocab_upgrade_map_name($element) {
return 'data_set';
}
// User.module integration.
function rules_condition_user_hasrole_upgrade_map_name($element) {
return 'user_has_role';
}
function rules_condition_user_hasrole_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'user', 'account');
}
function rules_condition_user_comparison_upgrade_map_name($element) {
return 'data_is';
}
function rules_condition_user_comparison_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'user1', 'data');
rules_upgrade_element_parameter_settings($element, $target, 'user2', 'value');
}
function rules_action_user_addrole_upgrade_map_name($element) {
return 'user_add_role';
}
function rules_action_user_addrole_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'user', 'account');
}
function rules_action_user_removerole_upgrade_map_name($element) {
return 'user_remove_role';
}
function rules_action_user_removerole_upgrade($element, $target) {
rules_upgrade_element_parameter_settings($element, $target, 'user', 'account');
}
function rules_action_load_user_upgrade_map_name($element) {
if (!empty($element['#settings']['username'])) {
drupal_set_message(t('Warning: Directly upgrading the load user by name action is not supported.'));
}
return 'entity_fetch';
}
function rules_action_load_user_upgrade($element, $target) {
$target->settings['type'] = 'user';
rules_upgrade_element_parameter_settings($element, $target, 'userid', 'id');
rules_upgrade_element_variable_settings($element, $target, 'user_loaded', 'entity_fetched');
}
function rules_action_user_create_upgrade_map_name($element) {
return 'entity_create';
}
function rules_action_user_create_upgrade($element, $target) {
$target->settings['type'] = 'user';
rules_upgrade_element_parameter_settings($element, $target, 'username', 'param_name');
rules_upgrade_element_parameter_settings($element, $target, 'email', 'param_mail');
rules_upgrade_element_variable_settings($element, $target, 'user_added', 'entity_created');
}
function rules_core_user_block_user_action_upgrade_map_name($element) {
return 'user_block';
}
function rules_core_user_block_user_action_upgrade($element, $target) {
$target->settings['account:select'] = $element['#settings']['#argument map']['user'];
}