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,67 @@
<?php
/**
* @file rules integration for the comment module
*
* @addtogroup rules
* @{
*/
/**
* Implementation of hook_rules_event_info().
*/
function rules_comment_event_info() {
$defaults = array(
'group' => t('comment'),
'module' => 'comment',
'access callback' => 'rules_comment_integration_access',
);
return array(
'comment_insert' => $defaults + array(
'label' => t('After saving a new comment'),
'variables' => array(
'comment' => array('type' => 'comment', 'label' => t('created comment')),
),
),
'comment_update' => $defaults + array(
'label' => t('After updating an existing comment'),
'variables' => array(
'comment' => array('type' => 'comment', 'label' => t('updated comment')),
'comment_unchanged' => array('type' => 'comment', 'label' => t('unchanged comment'), 'handler' => 'rules_events_entity_unchanged'),
),
),
'comment_presave' => $defaults + array(
'label' => t('Before saving a comment'),
'variables' => array(
'comment' => array('type' => 'comment', 'label' => t('saved comment'), 'skip save' => TRUE),
'comment_unchanged' => array('type' => 'comment', 'label' => t('unchanged comment'), 'handler' => 'rules_events_entity_unchanged'),
),
),
'comment_view' => $defaults + array(
'label' => t('A comment is viewed'),
'variables' => array(
'comment' => array('type' => 'comment', 'label' => t('viewed comment')),
),
'help' => t("Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache."),
),
'comment_delete' => $defaults + array(
'label' => t('After deleting a comment'),
'variables' => array(
'comment' => array('type' => 'comment', 'label' => t('deleted comment')),
),
),
);
}
/**
* Comment integration access callback.
*/
function rules_comment_integration_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return entity_access('view', 'comment');
}
}
/**
* @}
*/

View File

@@ -0,0 +1,397 @@
<?php
/**
* @file
* Contains rules integration for the data module needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Action: Modify data.
*/
function rules_action_data_set($wrapper, $value, $settings, $state, $element) {
if ($wrapper instanceof EntityMetadataWrapper) {
try {
// Update the value first then save changes, if possible.
$wrapper->set($value);
}
catch (EntityMetadataWrapperException $e) {
throw new RulesEvaluationException('Unable to modify data "@selector": ' . $e->getMessage(), array('@selector' => $settings['data:select']));
}
// Save changes if a property of a variable has been changed.
if (strpos($element->settings['data:select'], ':') !== FALSE) {
$info = $wrapper->info();
// We always have to save the changes in the parent entity. E.g. when the
// node author is changed, we don't want to save the author but the node.
$state->saveChanges(implode(':', explode(':', $settings['data:select'], -1)), $info['parent']);
}
}
else {
// A not wrapped variable (e.g. a number) is being updated. Just overwrite
// the variable with the new value.
return array('data' => $value);
}
}
/**
* Info alter callback for the data_set action.
*/
function rules_action_data_set_info_alter(&$element_info, $element) {
$element->settings += array('data:select' => NULL);
if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) {
$info = $wrapper->info();
$element_info['parameter']['value']['type'] = $wrapper->type();
$element_info['parameter']['value']['options list'] = !empty($info['options list']) ? 'rules_data_selector_options_list' : FALSE;
}
}
/**
* Action: Calculate a value.
*/
function rules_action_data_calc($input1, $op, $input2, $settings, $state, $element) {
$info = $element->pluginParameterInfo();
// Make sure to apply date offsets intelligently.
if ($info['input_1']['type'] == 'date' && $info['input_2']['type'] == 'duration') {
$input2 = ($op == '-') ? $input2 * -1 : $input2;
return array('result' => (int) RulesDateOffsetProcessor::applyOffset($input1, $input2));
}
switch ($op) {
case '+':
$result = $input1 + $input2;
break;
case '-':
$result = $input1 - $input2;
break;
case '*':
$result = $input1 * $input2;
break;
case '/':
$result = $input1 / $input2;
break;
}
if (isset($result)) {
// Ensure results are valid integer values if necessary.
$var_info = rules_array_key($element->providesVariables());
if ($var_info['type'] == 'integer') {
$result = (int) $result;
}
return array('result' => $result);
}
}
/**
* Info alter callback for the data_calc action.
*/
function rules_action_data_calc_info_alter(&$element_info, RulesPlugin $element) {
if ($info = $element->getArgumentInfo('input_1')) {
// Only allow durations as offset for date values.
if ($info['type'] == 'date') {
$element_info['parameter']['input_2']['type'] = 'duration';
}
// Specify the data type of the result.
$element_info['provides']['result']['type'] = $info['type'];
if ($info['type'] == 'integer' && ($info2 = $element->getArgumentInfo('input_2')) && $info2['type'] == 'decimal') {
$element_info['provides']['result']['type'] = 'decimal';
}
// A division with two integers results in a decimal.
elseif (isset($element->settings['op']) && $element->settings['op'] == '/') {
$element_info['provides']['result']['type'] = 'decimal';
}
}
}
/**
* Action: Add a list item.
*/
function rules_action_data_list_add($list, $item, $unique = FALSE, $pos = 'end', $settings, $state) {
// Optionally, only add the list item if it is not yet contained.
if ($unique && rules_condition_data_list_contains($list, $item, $settings, $state)) {
return;
}
switch ($pos) {
case 'start':
array_unshift($list, $item);
break;
default:
$list[] = $item;
break;
}
return array('list' => $list);
}
/**
* Info alteration callback for the "Add and Remove a list item" actions.
*/
function rules_data_list_info_alter(&$element_info, RulesAbstractPlugin $element) {
// Update the required type for the list item if it is known.
$element->settings += array('list:select' => NULL);
if ($wrapper = $element->applyDataSelector($element->settings['list:select'])) {
if ($type = entity_property_list_extract_type($wrapper->type())) {
$info = $wrapper->info();
$element_info['parameter']['item']['type'] = $type;
$element_info['parameter']['item']['options list'] = !empty($info['options list']) ? 'rules_data_selector_options_list' : FALSE;
}
}
}
/**
* Action: Remove a list item.
*/
function rules_action_data_list_remove($list, $item) {
foreach (array_keys($list, $item) as $key) {
unset($list[$key]);
}
return array('list' => $list);
}
/**
* Action: Add variable.
*/
function rules_action_variable_add($args, $element) {
return array('variable_added' => $args['value']);
}
/**
* Info alteration callback for variable add action.
*/
function rules_action_variable_add_info_alter(&$element_info, RulesAbstractPlugin $element) {
if (isset($element->settings['type']) && $type = $element->settings['type']) {
$cache = rules_get_cache();
$type_info = $cache['data_info'][$type];
$element_info['parameter']['value']['type'] = $type;
$element_info['provides']['variable_added']['type'] = $type;
// For lists, we default to an empty list so subsequent actions can add
// items.
if (entity_property_list_extract_type($type)) {
$element_info['parameter']['value']['default value'] = array();
}
}
}
/**
* Action: Convert a value.
*/
function rules_action_data_convert($arguments, RulesPlugin $element, $state) {
$value_info = $element->getArgumentInfo('value');
$from_type = $value_info['type'];
$target_type = $arguments['type'];
// First apply the rounding behavior if given.
if (isset($arguments['rounding_behavior'])) {
switch ($arguments['rounding_behavior']) {
case 'up':
$arguments['value'] = ceil($arguments['value']);
break;
case 'down':
$arguments['value'] = floor($arguments['value']);
break;
default:
case 'round':
$arguments['value'] = round($arguments['value']);
break;
}
}
switch ($target_type) {
case 'decimal':
$result = floatval($arguments['value']);
break;
case 'integer':
$result = intval($arguments['value']);
break;
case 'text':
$result = strval($arguments['value']);
break;
}
return array('conversion_result' => $result);
}
/**
* Info alteration callback for variable add action.
*/
function rules_action_data_convert_info_alter(&$element_info, RulesAbstractPlugin $element) {
if (isset($element->settings['type']) && $type = $element->settings['type']) {
$element_info['provides']['conversion_result']['type'] = $type;
if ($type != 'integer') {
// Only support the rounding behavior option for integers.
unset($element_info['parameter']['rounding_behavior']);
}
// Configure compatible source-types:
switch ($type) {
case 'integer':
$sources = array('decimal', 'text', 'token', 'uri', 'date', 'duration', 'boolean');
break;
case 'decimal':
$sources = array('integer', 'text', 'token', 'uri', 'date', 'duration', 'boolean');
break;
case 'text':
$sources = array('integer', 'decimal', 'token', 'uri', 'date', 'duration', 'boolean');
break;
}
$element_info['parameter']['value']['type'] = $sources;
}
}
/**
* Action: Create data.
*/
function rules_action_data_create($args, $element) {
$type = $args['type'];
$values = array();
foreach ($element->pluginParameterInfo() as $name => $info) {
if ($name != 'type') {
// Remove the parameter name prefix 'param_'.
$values[substr($name, 6)] = $args[$name];
}
}
$cache = rules_get_cache();
$type_info = $cache['data_info'][$type];
if (isset($type_info['creation callback'])) {
try {
$data = $type_info['creation callback']($values, $type);
return array('data_created' => $data);
}
catch (EntityMetadataWrapperException $e) {
throw new RulesEvaluationException('Unable to create @data": ' . $e->getMessage(), array('@data' => $type), $element);
}
}
else {
throw new RulesEvaluationException('Unable to create @data, no creation callback found.', array('@data' => $type), $element, RulesLog::ERROR);
}
}
/**
* Info alteration callback for data create action.
*/
function rules_action_data_create_info_alter(&$element_info, RulesAbstractPlugin $element) {
if (!empty($element->settings['type'])) {
$type = $element->settings['type'];
$cache = rules_get_cache();
$type_info = $cache['data_info'][$type];
if (isset($type_info['property info'])) {
// Add the data type's properties as parameters.
foreach ($type_info['property info'] as $property => $property_info) {
// Prefix parameter names to avoid name clashes with existing parameters.
$element_info['parameter']['param_' . $property] = array_intersect_key($property_info, array_flip(array('type', 'label')));
if (empty($property_info['required'])) {
$element_info['parameter']['param_' . $property]['optional'] = TRUE;
}
}
}
$element_info['provides']['data_created']['type'] = $type;
}
}
/**
* Creation callback for array structured data.
*/
function rules_action_data_create_array($values = array(), $type) {
// $values is an array already, so we can just pass it to the wrapper.
return rules_wrap_data($values, array('type' => $type));
}
/**
* Condition: Compare data.
*/
function rules_condition_data_is($data, $op, $value) {
switch ($op) {
default:
case '==':
// In case both values evaluate to FALSE, further differentiate between
// NULL values and values evaluating to FALSE.
if (!$data && !$value) {
return (isset($data) && isset($value)) || (!isset($data) && !isset($value));
}
return $data == $value;
case '<':
return $data < $value;
case '>':
return $data > $value;
// Note: This is deprecated by the text comparison condition and IN below.
case 'contains':
return is_string($data) && strpos($data, $value) !== FALSE || is_array($data) && in_array($value, $data);
case 'IN':
return is_array($value) && in_array($data, $value);
}
}
/**
* Info alteration callback for the data_is condition.
*
* If we check the bundle property of a variable, add an assertion so that later
* evaluated elements can make use of this information.
*/
function rules_condition_data_is_info_alter(&$element_info, RulesAbstractPlugin $element) {
$element->settings += array('data:select' => NULL, 'op' => '==');
if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) {
$info = $wrapper->info();
$element_info['parameter']['value']['type'] = $element->settings['op'] == 'IN' ? 'list<' . $wrapper->type() . '>' : $wrapper->type();
$element_info['parameter']['value']['options list'] = !empty($info['options list']) ? 'rules_data_selector_options_list' : FALSE;
}
}
/**
* Condition: List contains.
*/
function rules_condition_data_list_contains($list, $item, $settings, $state) {
$wrapper = $state->currentArguments['item'];
if ($wrapper instanceof EntityStructureWrapper && $id = $wrapper->getIdentifier()) {
// Check for equal items using the identifier if there is one.
foreach ($state->currentArguments['list'] as $i) {
if ($i->getIdentifier() == $id) {
return TRUE;
}
}
return FALSE;
}
return in_array($item, $list);
}
/**
* Condition: Data value is empty.
*/
function rules_condition_data_is_empty($data) {
// Note that some primitive variables might not be wrapped at all.
if ($data instanceof EntityMetadataWrapper) {
try {
// We cannot use the dataAvailable() method from the wrapper because it
// is protected, so we catch possible exceptions with the value() method.
$value = $data->value();
return empty($value);
}
catch (EntityMetadataWrapperException $e) {
// An exception means that the wrapper is somehow broken and we treat
// that as empty.
return TRUE;
}
}
return empty($data);
}
/**
* Condition: Textual comparison.
*/
function rules_data_text_comparison($text, $text2, $op = 'contains') {
switch ($op) {
case 'contains':
return strpos($text, $text2) !== FALSE;
case 'starts':
return strpos($text, $text2) === 0;
case 'ends':
return strrpos($text, $text2) === (strlen($text) - strlen($text2));
case 'regex':
return (bool) preg_match('/'. str_replace('/', '\\/', $text2) .'/', $text);
}
}

View File

@@ -0,0 +1,709 @@
<?php
/**
* @file General data related rules integration
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the pseudo data module.
* @see rules_core_modules()
*/
function rules_data_file_info() {
return array('modules/data.eval');
}
/**
* Implements hook_rules_action_info() on behalf of the pseudo data module.
* @see rules_core_modules()
*/
function rules_data_action_info() {
$return['data_set'] = array(
'label' => t('Set a data value'),
'parameter' => array(
'data' => array(
'type' => '*',
'label' => t('Data'),
'description' => t('Specifies the data to be modified using a data selector, e.g. "node:author:name".'),
'restriction' => 'selector',
'wrapped' => TRUE,
'allow null' => TRUE,
),
'value' => array(
'type' => '*',
'label' => t('Value'),
'description' => t('The new value to set for the specified data.'),
'allow null' => TRUE,
'optional' => TRUE,
),
),
'group' => t('Data'),
'base' => 'rules_action_data_set',
);
$return['data_calc'] = array(
'label' => t('Calculate a value'),
'parameter' => array(
'input_1' => array(
'type' => array('decimal', 'date'),
'label' => t('Input value 1'),
'description' => t('The first input value for the calculation.'),
),
'op' => array(
'type' => 'text',
'label' => t('Operator'),
'description' => t('The calculation operator.'),
'options list' => 'rules_action_data_calc_operator_options',
'restriction' => 'input',
'default value' => '+',
),
'input_2' => array(
'type' => 'decimal',
'label' => t('Input value 2'),
'description' => t('The second input value.'),
),
),
'group' => t('Data'),
'base' => 'rules_action_data_calc',
'provides' => array(
'result' => array(
'type' => 'unknown',
'label' => t('Calculation result'),
),
),
);
$return['list_add'] = array(
'label' => t('Add an item to a list'),
'parameter' => array(
'list' => array(
'type' => 'list',
'label' => t('List', array(), array('context' => 'data_types')),
'description' => t('The data list, to which an item is to be added.'),
'restriction' => 'selector',
'allow null' => TRUE,
'save' => TRUE,
),
'item' => array(
'type' => 'unknown',
'label' => t('Item to add'),
),
'unique' => array(
'type' => 'boolean',
'label' => t('Enforce uniqueness'),
'description' => t('Only add the item to the list if it is not yet contained.'),
'optional' => TRUE,
'default value' => FALSE,
),
'pos' => array(
'type' => 'text',
'label' => t('Insert position'),
'optional' => TRUE,
'default value' => 'end',
'options list' => 'rules_action_data_list_add_positions',
),
),
'group' => t('Data'),
'base' => 'rules_action_data_list_add',
'callbacks' => array(
'info_alter' => 'rules_data_list_info_alter',
'form_alter' => 'rules_data_list_form_alter',
),
);
$return['list_remove'] = array(
'label' => t('Remove an item from a list'),
'parameter' => array(
'list' => array(
'type' => 'list',
'label' => t('List', array(), array('context' => 'data_types')),
'description' => t('The data list for which an item is to be removed.'),
'restriction' => 'selector',
'save' => TRUE,
),
'item' => array(
'type' => 'unknown',
'label' => t('Item to remove'),
),
),
'group' => t('Data'),
'base' => 'rules_action_data_list_remove',
'callbacks' => array(
'info_alter' => 'rules_data_list_info_alter',
'form_alter' => 'rules_data_list_form_alter',
),
);
$return['variable_add'] = array(
'label' => t('Add a variable'),
'named parameter' => TRUE,
'parameter' => array(
'type' => array(
'type' => 'text',
'label' => t('Type'),
'options list' => 'rules_data_action_variable_add_options',
'description' => t('Specifies the type of the variable that should be added.'),
'restriction' => 'input',
),
'value' => array(
'type' => 'unknown',
'label' => t('Value'),
'optional' => TRUE,
'description' => t('Optionally, specify the initial value of the variable.')
),
),
'provides' => array(
'variable_added' => array(
'type' => 'unknown',
'label' => t('Added variable'),
),
),
'group' => t('Data'),
'base' => 'rules_action_variable_add',
'callbacks' => array(
'form_alter' => 'rules_action_type_form_alter',
'validate' => 'rules_action_create_type_validate',
),
);
if (rules_data_action_data_create_options()) {
$return['data_create'] = array(
'label' => t('Create a data structure'),
'named parameter' => TRUE,
'parameter' => array(
'type' => array(
'type' => 'text',
'label' => t('Type'),
'options list' => 'rules_data_action_data_create_options',
'description' => t('Specifies the type of the data structure that should be created.'),
'restriction' => 'input',
),
// Further needed parameters depend on the type.
),
'provides' => array(
'data_created' => array(
'type' => 'unknown',
'label' => t('Created data'),
),
),
'group' => t('Data'),
'base' => 'rules_action_data_create',
'callbacks' => array(
'form_alter' => 'rules_action_type_form_alter',
'validate' => 'rules_action_create_type_validate',
),
);
}
$return['data_convert'] = array(
'label' => t('Convert data type'),
'parameter' => array(
'type' => array(
'type' => 'token',
'label' => t('Target type'),
'description' => t('The data type to convert a value to.'),
'options list' => 'rules_action_data_convert_types_options',
'restriction' => 'input',
),
'value' => array(
'type' => array('decimal', 'integer', 'text'),
'label' => t('Value to convert'),
'default mode' => 'selector',
),
// For to-integer conversion only.
'rounding_behavior' => array(
'type' => 'token',
'label' => t('Rounding behavior'),
'description' => t('The rounding behavior the conversion should use.'),
'options list' => 'rules_action_data_convert_rounding_behavior_options',
'restriction' => 'input',
'default value' => 'round',
'optional' => TRUE,
),
),
'provides' => array(
'conversion_result' => array(
'type' => 'unknown',
'label' => t('Conversion result'),
),
),
'group' => t('Data'),
'base' => 'rules_action_data_convert',
'named parameter' => TRUE,
'callbacks' => array(
'form_alter' => 'rules_action_type_form_alter',
),
);
return $return;
}
/**
* Data conversation action: Options list callback for the target type.
*/
function rules_action_data_convert_types_options(RulesPlugin $element, $param_name) {
return array(
'decimal' => t('Decimal'),
'integer' => t('Integer'),
'text' => t('Text'),
);
}
/**
* Data conversation action: Options list callback for rounding behavior.
*/
function rules_action_data_convert_rounding_behavior_options(RulesPlugin $element, $param_name) {
return array(
'down' => t('Always down (9.5 -> 9)'),
'round' => t('Round, half up (9.5 -> 10)'),
'up' => t('Always up (9.5 -> 10)'),
);
}
/**
* Customize access check for data set action.
*/
function rules_action_data_set_access(RulesAbstractPlugin $element) {
if (isset($element->settings['data:select']) && $wrapper = $element->applyDataSelector($element->settings['data:select'])) {
return $wrapper instanceof EntityMetadataWrapper && $wrapper->access('edit');
}
}
/**
* Custom validation callback for the data set action.
*/
function rules_action_data_set_validate(RulesAbstractPlugin $element) {
$element->settings += array('data:select' => NULL);
$info = $element->applyDataSelector($element->settings['data:select'])->info();
if (strpos($element->settings['data:select'], ':') !== FALSE && empty($info['setter callback'])) {
throw new RulesIntegrityException(t("The selected data property doesn't support writing."), array($element, 'parameter', 'data'));
}
}
/**
* Form alter callback for the data_set action.
*/
function rules_action_data_set_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
if (!empty($options['init']) && !isset($form_state['rules_element_step'])) {
$form['negate']['#access'] = FALSE;
unset($form['parameter']['value']);
unset($form['parameter']['language']);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Continue'),
'#limit_validation_errors' => array(array('parameter', 'data')),
'#submit' => array('rules_form_submit_rebuild'),
);
$form_state['rules_element_step'] = 'data_value';
// Clear the parameter mode for the value parameter, so its gets the proper
// default value based upon the type of the the selected data on rebuild.
unset($form_state['parameter_mode']['value']);
}
else {
// Change the data parameter to be not editable.
$form['parameter']['data']['settings']['#access'] = FALSE;
// TODO: improve display
$form['parameter']['data']['info'] = array(
'#prefix' => '<p>',
'#markup' => t('<strong>Selected data:</strong> %selector', array('%selector' => $element->settings['data:select'])),
'#suffix' => '</p>',
);
}
}
/**
* Form alter callback for the data calculation action.
*/
function rules_action_data_calc_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
$form['reload'] = array(
'#weight' => 5,
'#type' => 'submit',
'#name' => 'reload',
'#value' => t('Reload form'),
'#limit_validation_errors' => array(array('parameter', 'input_1')),
'#submit' => array('rules_form_submit_rebuild'),
'#ajax' => rules_ui_form_default_ajax(),
);
}
/**
* Custom validate callback for entity create, add variable and data create
* action.
*/
function rules_action_create_type_validate($element) {
if (!isset($element->settings['type'])) {
throw new RulesIntegrityException(t('Invalid type specified.'), array($element, 'parameter', 'type'));
}
}
/**
* Form alter callback for the list add and remove actions.
*
* Use multiple steps to configure the action to update the item configuration
* form once we know the data type.
*
* @see rules_data_list_info_alter()
*/
function rules_data_list_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
if (!empty($options['init']) && !isset($form_state['rules_element_step'])) {
unset($form['parameter']['item'], $form['parameter']['pos']);
$form_state['rules_element_step'] = 1;
$form['negate']['#access'] = FALSE;
$form['parameter']['unique']['#access'] = FALSE;
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Continue'),
'#limit_validation_errors' => array(array('parameter', 'list')),
'#submit' => array('rules_form_submit_rebuild'),
);
}
else {
// Change the list parameter to be not editable any more.
$form['parameter']['list']['settings']['#access'] = FALSE;
$form['parameter']['list']['info'] = array(
'#prefix' => '<p>',
'#markup' => t('<strong>Selected list:</strong> %selector', array('%selector' => $element->settings['list:select'])),
'#suffix' => '</p>',
);
}
}
/**
* Form alter callback for actions relying on the entity type or the data type.
*/
function rules_action_type_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
$first_step = empty($element->settings['type']);
$form['reload'] = array(
'#weight' => 5,
'#type' => 'submit',
'#name' => 'reload',
'#value' => $first_step ? t('Continue') : t('Reload form'),
'#limit_validation_errors' => array(array('parameter', 'type')),
'#submit' => array('rules_action_type_form_submit_rebuild'),
'#ajax' => rules_ui_form_default_ajax(),
);
// Use ajax and trigger as the reload button.
$form['parameter']['type']['settings']['type']['#ajax'] = $form['reload']['#ajax'] + array(
'event' => 'change',
'trigger_as' => array('name' => 'reload'),
);
if ($first_step) {
// In the first step show only the type select.
foreach (element_children($form['parameter']) as $key) {
if ($key != 'type') {
unset($form['parameter'][$key]);
}
}
unset($form['submit']);
unset($form['provides']);
// Disable #ajax for the first step as it has troubles with lazy-loaded JS.
// @todo: Re-enable once JS lazy-loading is fixed in core.
unset($form['parameter']['type']['settings']['type']['#ajax']);
unset($form['reload']['#ajax']);
}
else {
// Hide the reload button in case js is enabled and it's not the first step.
$form['reload']['#attributes'] = array('class' => array('rules-hide-js'));
}
}
/**
* FAPI submit callback for reloading the type form for entities or data types.
*/
function rules_action_type_form_submit_rebuild($form, &$form_state) {
rules_form_submit_rebuild($form, $form_state);
// Clear the parameter modes for the parameters, so they get the proper
// default values based upon the data types on rebuild.
$form_state['parameter_mode'] = array();
}
/**
* Options list callback for possible insertion positions.
*/
function rules_action_data_list_add_positions() {
return array(
'end' => t('Append the item to the end.'),
'start' => t('Prepend the item to the front.'),
);
}
/**
* Options list callback for variable add action.
*/
function rules_data_action_variable_add_options() {
return RulesPluginUI::getOptions('data');
}
/**
* Options list callback for the data calculation action.
*/
function rules_action_data_calc_operator_options(RulesPlugin $element, $param_name) {
$options = array(
'+' => '( + )',
'-' => '( - )',
'*' => '( * )',
'/' => '( / )',
);
// Only show +/- in case a date has been selected.
if (($info = $element->getArgumentInfo('input_1')) && $info['type'] == 'date') {
unset($options['*']);
unset($options['/']);
}
return $options;
}
/**
* Options list callback for data create action.
*/
function rules_data_action_data_create_options() {
$cache = rules_get_cache();
$data_info = $cache['data_info'];
$entity_info = entity_get_info();
// Remove entities.
$data_info = array_diff_key($data_info, $entity_info);
$options = array();
foreach ($data_info as $type => $properties) {
if (isset($properties['creation callback'])) {
// Add data types with creation callback only.
$options[$type] = $properties['label'];
}
}
natcasesort($options);
return $options;
}
/**
* Implements hook_rules_condition_info() on behalf of the pseudo data module.
* @see rules_core_modules()
*/
function rules_data_condition_info() {
return array(
'data_is' => array(
'label' => t('Data comparison'),
'parameter' => array(
'data' => array(
'type' => '*',
'label' => t('Data to compare'),
'description' => t('The data to be compared, specified by using a data selector, e.g. "node:author:name".'),
'allow null' => TRUE,
),
'op' => array(
'type' => 'text',
'label' => t('Operator'),
'description' => t('The comparison operator.'),
'optional' => TRUE,
'default value' => '==',
'options list' => 'rules_condition_data_is_operator_options',
'restriction' => 'input',
),
'value' => array(
'type' => '*',
'label' => t('Data value'),
'description' => t('The value to compare the data with.'),
'allow null' => TRUE,
),
),
'group' => t('Data'),
'base' => 'rules_condition_data_is',
),
'data_is_empty' => array(
'label' => t('Data value is empty'),
'parameter' => array(
'data' => array(
'type' => '*',
'label' => t('Data to check'),
'description' => t('The data to be checked to be empty, specified by using a data selector, e.g. "node:author:name".'),
'allow null' => TRUE,
'wrapped' => TRUE,
),
),
'group' => t('Data'),
'base' => 'rules_condition_data_is_empty',
),
'list_contains' => array(
'label' => t('List contains item'),
'parameter' => array(
'list' => array(
'type' => 'list',
'label' => t('List', array(), array('context' => 'data_types')),
'restriction' => 'selector',
),
'item' => array(
'type' => 'unknown',
'label' => t('Item'),
'description' => t('The item to check for.'),
),
),
'group' => t('Data'),
'base' => 'rules_condition_data_list_contains',
'callbacks' => array(
'info_alter' => 'rules_data_list_info_alter',
'form_alter' => 'rules_data_list_form_alter',
),
),
'text_matches' => array(
'label' => t('Text comparison'),
'parameter' => array(
'text' => array(
'type' => 'text',
'label' => t('Text'),
'restriction' => 'selector',
),
'match' => array(
'type' => 'text',
'label' => t('Matching text'),
),
'operation' => array(
'type' => 'text',
'label' => t('Comparison operation'),
'options list' => 'rules_data_text_comparison_operation_list',
'restriction' => 'input',
'default value' => 'contains',
'optional' => TRUE,
'description' => t('In case the comparison operation @regex is selected, the matching pattern will be interpreted as a <a href="@regex-wikipedia">regular expression</a>. Tip: <a href="@RegExr">RegExr: Online Regular Expression Testing Tool</a> is helpful for learning, writing, and testing Regular Expressions.', array('@regex-wikipedia' => 'http://en.wikipedia.org/wiki/Regular_expression', '@RegExr' => 'http://gskinner.com/RegExr/', '@regex' => t('regular expression'))),
),
),
'group' => t('Data'),
'base' => 'rules_data_text_comparison',
),
);
}
/**
* If the bundle is compared, add the metadata assertion so other elements
* can make use of properties specific to the bundle.
*/
function rules_condition_data_is_assertions($element) {
// Assert the bundle of entities, if its compared.
if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) {
$info = $wrapper->info();
if (isset($info['parent']) && $info['parent'] instanceof EntityDrupalWrapper) {
$entity_info = $info['parent']->entityInfo();
if (isset($entity_info['entity keys']['bundle']) && $entity_info['entity keys']['bundle'] == $info['name']) {
// Assert that the entity is of bundle $value.
$value = is_array($element->settings['value']) ? $element->settings['value'] : array($element->settings['value']);
// Chop of the last part of the selector.
$parts = explode(':', $element->settings['data:select'], -1);
return array(implode(':', $parts) => array('bundle' => $value));
}
}
}
}
/**
* Form alter callback for the condition data_is.
*
* Use multiple steps to configure the condition as the needed type of the value
* depends on the selected data.
*/
function rules_condition_data_is_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
if (!empty($options['init']) && !isset($form_state['rules_element_step'])) {
unset($form['parameter']['op'], $form['parameter']['value']);
$form['negate']['#access'] = FALSE;
$form_state['rules_element_step'] = 'data_value';
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Continue'),
'#limit_validation_errors' => array(array('parameter', 'data'), array('parameter', 'op')),
'#submit' => array('rules_form_submit_rebuild'),
);
// Clear the parameter mode for the value parameter, so its gets the proper
// default value based upon the type of the the selected data on rebuild.
unset($form_state['parameter_mode']['value']);
}
else {
// Change the data parameter to be not editable.
$form['parameter']['data']['settings']['#access'] = FALSE;
// TODO: improve display
$form['parameter']['data']['info'] = array(
'#prefix' => '<p>',
'#markup' => t('<strong>Selected data:</strong> %selector', array('%selector' => $element->settings['data:select'])),
'#suffix' => '</p>',
);
// Limit the operations to what makes sense for the selected data type.
$info = $element->pluginParameterInfo();
$data_info = $info['value'];
if ($element->settings['op'] == 'IN') {
$data_info['type'] = entity_property_list_extract_type($data_info['type']);
}
if (!RulesData::typesMatch($data_info, array('type' => array('decimal', 'date')))) {
$options =& $form['parameter']['op']['settings']['op']['#options'];
unset($options['<'], $options['>']);
}
// Remove 'contains' if it is not selected, as it is deprecated by the
// text comparison condition.
if ($element->settings['op'] != 'contains') {
unset($form['parameter']['op']['settings']['op']['#options']['contains']);
}
// Auto-refresh the form if the operation is changed, so the input form
// changes in case "is one of" requires a list value.
$form['parameter']['op']['settings']['op']['#ajax'] = rules_ui_form_default_ajax() + array(
'trigger_as' => array('name' => 'reload'),
);
// Provide a reload button for non-JS users.
$form['reload'] = array(
'#type' => 'submit',
'#value' => t('Reload form'),
'#limit_validation_errors' => array(array('parameter', 'data'), array('parameter', 'op')),
'#submit' => array('rules_form_submit_rebuild'),
'#ajax' => rules_ui_form_default_ajax(),
'#weight' => 5,
);
// Hide the reload button in case JS is enabled.
$form['reload']['#attributes'] = array('class' => array('rules-hide-js'));
}
}
/**
* Provides configuration help for the data_is condition.
*/
function rules_condition_data_is_help() {
return array('#markup' => t('Compare two data values of the same type with each other.'));
}
/**
* Options list callback for condition data_is.
*/
function rules_condition_data_is_operator_options() {
return array(
'==' => t('equals'),
'IN' => t('is one of'),
'<' => t('is lower than'),
'>' => t('is greater than'),
// Note: This is deprecated by the text comparison condition.
'contains' => t('contains'),
);
}
/**
* Options list callback for condition text_matches.
*/
function rules_data_text_comparison_operation_list() {
return array(
'contains' => t('contains'),
'starts' => t('starts with'),
'ends' => t('ends with'),
'regex' => t('regular expression'),
);
}
/**
* Returns the options list as specified by the selected property of the first parameter.
*
* @see rules_data_list_info_alter()
* @see rules_action_data_set_info_alter()
* @see rules_condition_data_is_info_alter()
*/
function rules_data_selector_options_list(RulesAbstractPlugin $element) {
$name = rules_array_key($element->pluginParameterInfo());
// If the selected data property has an option list, make use of it.
if (isset($element->settings[$name . ':select']) && $wrapper = $element->applyDataSelector($element->settings[$name . ':select'])) {
return $wrapper->optionsList($element instanceof RulesActionInterface ? 'edit' : 'view');
}
}
/**
* @}
*/

View File

@@ -0,0 +1,159 @@
<?php
/**
* @file
* Contains rules integration for entities needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Action: Fetch data.
*/
function rules_action_entity_fetch($type, $id, $revision) {
$info = entity_get_info($type);
// Support the revision parameter, if applicable.
if (!empty($info['entity keys']['revision']) && isset($revision)) {
$conditions = array($info['entity keys']['revision'] => $revision);
}
$return = entity_load($type, array($id), isset($conditions) ? $conditions : array());
$entity = reset($return);
if (!$entity) {
throw new RulesEvaluationException('Unable to load @entity with id "@id"', array('@id' => $id, '@entity' => $type));
}
return array('entity_fetched' => $entity);
}
/**
* Info alteration callback for the entity fetch action.
*/
function rules_action_entity_fetch_info_alter(&$element_info, RulesAbstractPlugin $element) {
$element->settings += array('type' => NULL);
$info = entity_get_info($element->settings['type']);
// Fix the type of the identifier.
$element_info['parameter']['id']['type'] = isset($info['entity keys']['name']) ? 'text' : 'integer';
// Add an optional revision parameter, if supported.
if (!empty($info['entity keys']['revision'])) {
$element_info['parameter']['revision_id'] = array(
'type' => 'integer',
'label' => t('Revision identifier'),
'optional' => TRUE,
);
}
$element_info['provides']['entity_fetched']['type'] = $element->settings['type'];
}
/**
* Action: Query entities.
*/
function rules_action_entity_query($type, $property, $value, $limit) {
$return = entity_property_query($type, $property, $value, $limit);
return array('entity_fetched' => array_values($return));
}
/**
* Info alteration callback for the entity query action.
*/
function rules_action_entity_query_info_alter(&$element_info, RulesAbstractPlugin $element) {
$element->settings += array('type' => NULL, 'property' => NULL);
if ($element->settings['type']) {
$element_info['parameter']['property']['options list'] = 'rules_action_entity_query_property_options_list';
if ($element->settings['property']) {
$wrapper = entity_metadata_wrapper($element->settings['type']);
if (isset($wrapper->{$element->settings['property']}) && $property = $wrapper->{$element->settings['property']}) {
$element_info['parameter']['value']['type'] = $property->type();
$element_info['parameter']['value']['options list'] = $property->optionsList() ? 'rules_action_entity_query_value_options_list' : FALSE;
}
}
}
$element_info['provides']['entity_fetched']['type'] = 'list<' . $element->settings['type'] . '>';
}
/**
* Action: Create entities.
*/
function rules_action_entity_create($args, $element) {
$values = array();
foreach ($element->pluginParameterInfo() as $name => $info) {
if ($name != 'type') {
// Remove the parameter name prefix 'param_'.
$values[substr($name, 6)] = $args[$name];
}
}
try {
$data = entity_property_values_create_entity($args['type'], $values);
return array('entity_created' => $data);
}
catch (EntityMetadataWrapperException $e) {
throw new RulesEvaluationException('Unable to create entity @type": ' . $e->getMessage(), array('@type' => $args['type']), $element);
}
}
/**
* Info alteration callback for the entity create action.
*/
function rules_action_entity_create_info_alter(&$element_info, RulesAbstractPlugin $element) {
if (!empty($element->settings['type']) && entity_get_info($element->settings['type'])) {
$wrapper = entity_metadata_wrapper($element->settings['type']);
// Add the data type's needed parameter for loading to the parameter info.
foreach ($wrapper as $name => $child) {
$info = $child->info();
if (!empty($info['required'])) {
$info += array('type' => 'text');
// Prefix parameter names to avoid name clashes with existing parameters.
$element_info['parameter']['param_' . $name] = array_intersect_key($info, array_flip(array('type', 'label', 'description')));
$element_info['parameter']['param_' . $name]['options list'] = $child->optionsList() ? 'rules_action_entity_parameter_options_list' : FALSE;
}
}
$element_info['provides']['entity_created']['type'] = $element->settings['type'];
if (($bundleKey = $wrapper->entityKey('bundle')) && isset($element->settings['param_' . $bundleKey])) {
$element_info['provides']['entity_created']['bundle'] = $element->settings['param_' . $bundleKey];
}
}
}
/**
* Action: Save entities.
*/
function rules_action_entity_save($wrapper, $immediate = FALSE, $settings, $state, $element) {
$state->saveChanges($settings['data:select'], $wrapper, $immediate);
}
/**
* Action: Delete entities.
*/
function rules_action_entity_delete($wrapper, $settings, $state, $element) {
try {
$wrapper->delete();
}
catch (EntityMetadataWrapperException $e) {
throw new RulesEvaluationException($e->getMessage(), array(), $element);
}
}
/**
* Condition: Entity is new.
*/
function rules_condition_entity_is_new($wrapper, $settings, $state, $element) {
return !$wrapper->getIdentifier() || !empty($entity->is_new);
}
/**
* Condition: Entity has field.
*/
function rules_condition_entity_has_field($wrapper, $field_name, $settings, $state) {
return isset($wrapper->$field_name) || isset($entity->$field_name);
}
/**
* Condition: Entity is of type.
*/
function rules_condition_entity_is_of_type($wrapper, $type) {
return $wrapper->type() == $type;
}

View File

@@ -0,0 +1,401 @@
<?php
/**
* @file General entity related rules integration
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the entity module.
* @see rules_core_modules()
*/
function rules_entity_file_info() {
return array('modules/entity.eval');
}
/**
* Implements hook_rules_action_info() on behalf of the entity module.
* @see rules_core_modules()
*/
function rules_entity_action_info() {
if (rules_entity_action_type_options('entity_fetch')) {
$return['entity_fetch'] = array(
'label' => t('Fetch entity by id'),
'parameter' => array(
'type' => array(
'type' => 'text',
'label' => t('Entity type'),
'options list' => 'rules_entity_action_type_options',
'description' => t('Specifies the type of entity that should be fetched.'),
'restriction' => 'input',
),
'id' => array('type' => 'unknown', 'label' => t('Identifier')),
),
'provides' => array(
'entity_fetched' => array('type' => 'unknown', 'label' => t('Fetched entity')),
),
'group' => t('Entities'),
'access callback' => 'rules_entity_action_access',
'base' => 'rules_action_entity_fetch',
'callbacks' => array(
'access' => 'rules_action_entity_createfetch_access',
'form_alter' => 'rules_action_type_form_alter',
),
);
$return['entity_query'] = array(
'label' => t('Fetch entity by property'),
'parameter' => array(
'type' => array(
'type' => 'text',
'label' => t('Entity type'),
'options list' => 'rules_entity_action_type_options',
'description' => t('Specifies the type of the entity that should be fetched.'),
'restriction' => 'input',
),
'property' => array(
'type' => 'text',
'label' => t('Property'),
'description' => t('The property by which the entity is to be selected.'),
'restriction' => 'input',
),
'value' => array(
'type' => 'unknown',
'label' => t('Value'),
'description' => t('The property value of the entity to be fetched.'),
),
'limit' => array(
'type' => 'integer',
'label' => t('Limit result count'),
'description' => t('Limit the maximum number of fetched entities.'),
'optional' => TRUE,
'default value' => '10',
),
),
'provides' => array(
'entity_fetched' => array('type' => 'list', 'label' => t('Fetched entity')),
),
'group' => t('Entities'),
'access callback' => 'rules_entity_action_access',
'base' => 'rules_action_entity_query',
'callbacks' => array(
'form_alter' => 'rules_action_type_form_alter',
),
);
}
if (rules_entity_action_type_options('entity_create')) {
$return['entity_create'] = array(
'label' => t('Create a new entity'),
'named parameter' => TRUE,
'parameter' => array(
'type' => array(
'type' => 'text',
'label' => t('Entity type'),
'options list' => 'rules_entity_action_type_options',
'description' => t('Specifies the type of the entity that should be created.'),
'restriction' => 'input',
),
// Further needed parameter depends on the type.
),
'provides' => array(
'entity_created' => array(
'type' => 'unknown',
'label' => t('Created entity'),
'save' => TRUE,
),
),
'group' => t('Entities'),
'access callback' => 'rules_entity_action_access',
'base' => 'rules_action_entity_create',
'callbacks' => array(
'access' => 'rules_action_entity_createfetch_access',
'form_alter' => 'rules_action_type_form_alter',
'validate' => 'rules_action_create_type_validate',
),
);
}
$return['entity_save'] = array(
'label' => t('Save entity'),
'parameter' => array(
'data' => array(
'type' => 'entity',
'label' => t('Entity'),
'description' => t('Specifies the entity, which should be saved permanently.'),
'restriction' => 'selector',
'wrapped' => TRUE,
),
'immediate' => array(
'type' => 'boolean',
'label' => t('Force saving immediately'),
'description' => t('Usually saving is postponed till the end of the evaluation, so that multiple saves can be fold into one. If this set, saving is forced to happen immediately.'),
'default value' => FALSE,
'optional' => TRUE,
'restriction' => 'input',
),
),
'group' => t('Entities'),
'access callback' => 'rules_entity_action_access',
'base' => 'rules_action_entity_save',
'callbacks' => array(
'access' => 'rules_action_entity_savedelete_access',
),
);
$return['entity_delete'] = array(
'label' => t('Delete entity'),
'parameter' => array(
'data' => array(
'type' => 'entity',
'label' => t('Entity'),
'description' => t('Specifies the entity, which should be deleted permanently.'),
'restriction' => 'selector',
'wrapped' => TRUE,
),
),
'group' => t('Entities'),
'access callback' => 'rules_entity_action_access',
'base' => 'rules_action_entity_delete',
'callbacks' => array(
'access' => 'rules_action_entity_savedelete_access',
),
);
return $return;
}
/**
* Custom access callback for data create and fetch action.
*/
function rules_action_entity_createfetch_access(RulesAbstractPlugin $element) {
$op = $element->getElementName() == 'entity_create' ? 'create' : 'view';
return entity_access($op, $element->settings['type']);
}
/**
* Custom access callback for the data query action.
*/
function rules_action_entity_query_access(RulesAbstractPlugin $element) {
if (!rules_action_entity_createfetch_access($element)) {
return FALSE;
}
$properties = entity_get_all_property_info($element->settings['type']);
if (isset($element->settings['property']) && isset($properties[$element->settings['property']]['access callback'])) {
return call_user_func($properties[$element->settings['property']]['access callback'], 'view', $element->settings['property'], $element->settings['type'], NULL, NULL);
}
return TRUE;
}
/**
* Options list callback for a parameter of entity_create.
*/
function rules_action_entity_parameter_options_list(RulesPlugin $element, $param_name) {
// Remove the parameter name prefix 'param_'.
$property_name = substr($param_name, 6);
$wrapper = entity_metadata_wrapper($element->settings['type']);
// The possible values of the "value" parameter are those of the data param.
return $wrapper->$property_name->optionsList();
}
/**
* Custom access callback for data save and delete action.
*/
function rules_action_entity_savedelete_access(RulesAbstractPlugin $element) {
if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) {
$op = $element->getElementName() == 'entity_save' ? 'save' : 'delete';
return $wrapper instanceof EntityDrupalWrapper && $wrapper->entityAccess($op);
}
return FALSE;
}
/**
* Returns the options list for choosing a property of an entity type.
*/
function rules_action_entity_query_property_options_list(RulesAbstractPlugin $element) {
$element->settings += array('type' => NULL);
if ($element->settings['type']) {
$properties = entity_get_all_property_info($element->settings['type']);
return rules_extract_property($properties, 'label');
}
}
/**
* Returns the options list specified for the chosen property.
*/
function rules_action_entity_query_value_options_list(RulesAbstractPlugin $element) {
// Get the possible values for the selected property.
$element->settings += array('type' => NULL, 'property' => NULL);
if ($element->settings['type'] && $element->settings['property']) {
$wrapper = entity_metadata_wrapper($element->settings['type']);
if (isset($wrapper->{$element->settings['property']}) && $property = $wrapper->{$element->settings['property']}) {
return $property->optionsList('view');
}
}
}
/**
* Options list callback for data actions.
*
* @param $element
* The element to return options for.
* @param $param
* The name of the parameter to return options for.
*/
function rules_entity_action_type_options($element, $name = NULL) {
// We allow calling this function with just the element name too. That way
// we ease manual re-use.
$name = is_object($element) ? $element->getElementName() : $element;
return ($name == 'entity_create') ? rules_entity_type_options('create') : rules_entity_type_options();
}
/**
* Returns options containing entity types having the given key set in the info.
*
* Additionally, we exclude all entity types that are marked as configuration.
*/
function rules_entity_type_options($key = NULL) {
$info = entity_get_info();
$types = array();
foreach ($info as $type => $entity_info) {
if (empty($entity_info['configuration']) && empty($entity_info['exportable'])) {
if (!isset($key) || entity_type_supports($type, $key)) {
$types[$type] = $entity_info['label'];
}
}
}
return $types;
}
/**
* Entity actions access callback.
*
* Returns TRUE if at least one type is available for configuring the action.
*/
function rules_entity_action_access($type, $name) {
if ($name == 'entity_fetch' || $name == 'entity_create' || $name == 'entity_query') {
$types = array_keys(rules_entity_action_type_options($name));
$op = $name == 'entity_create' ? 'create' : 'view';
}
elseif ($name == 'entity_save' || $name == 'entity_delete') {
$types = array_keys(entity_get_info());
$op = $name == 'entity_save' ? 'save' : 'delete';
}
foreach ($types as $key => $type) {
if (!entity_access($op, $type)) {
unset($types[$key]);
}
}
return !empty($types);
}
/**
* Implements hook_rules_condition_info() on behalf of the entity module.
* @see rules_core_modules()
*/
function rules_entity_condition_info() {
return array(
'entity_is_new' => array(
'label' => t('Entity is new'),
'parameter' => array(
'entity' => array(
'type' => 'entity',
'label' => t('Entity'),
'description' => t('Specifies the entity for which to evaluate the condition.'),
'restriction' => 'selector',
),
),
'group' => t('Entities'),
'base' => 'rules_condition_entity_is_new',
),
'entity_has_field' => array(
'label' => t('Entity has field'),
'parameter' => array(
'entity' => array(
'type' => 'entity',
'label' => t('Entity'),
'description' => t('Specifies the entity for which to evaluate the condition.'),
'restriction' => 'selector',
),
'field' => array(
'type' => 'text',
'label' => t('Field'),
'description' => t('The name of the field to check for.'),
'options list' => 'rules_condition_entity_has_field_options',
'restriction' => 'input',
),
),
'group' => t('Entities'),
'base' => 'rules_condition_entity_has_field',
),
'entity_is_of_type' => array(
'label' => t('Entity is of type'),
'parameter' => array(
'entity' => array(
'type' => 'entity',
'label' => t('Entity'),
'description' => t('Specifies the entity for which to evaluate the condition.'),
),
'type' => array(
'type' => 'token',
'label' => t('Entity type'),
'description' => t('The entity type to check for.'),
'options list' => 'rules_entity_action_type_options',
'restriction' => 'input',
),
),
'group' => t('Entities'),
'base' => 'rules_condition_entity_is_of_type',
),
);
}
/**
* Help callback for condition entity_is_new.
*/
function rules_condition_entity_is_new_help() {
return t('This condition determines whether the specified entity has just been created and has not yet been saved to the database.');
}
/**
* Returns options for choosing a field for the selected entity.
*/
function rules_condition_entity_has_field_options(RulesAbstractPlugin $element) {
$options = array();
foreach (field_info_fields() as $field_name => $field) {
$options[$field_name] = $field_name;
}
return $options;
}
/**
* Assert that the entity has the field, if there is metadata for the field.
*/
function rules_condition_entity_has_field_assertions($element) {
// Assert the field is there if the condition matches.
if ($wrapper = $element->applyDataSelector($element->settings['entity:select'])) {
$type = $wrapper->type();
$field_property = $element->settings['field'];
// Get all possible properties and check whether we have one for the field.
$properties = entity_get_all_property_info($type == 'entity' ? NULL : $type);
if (isset($properties[$field_property])) {
$assertion = array('property info' => array($field_property => $properties[$field_property]));
return array($element->settings['entity:select'] => $assertion);
}
}
}
/**
* Assert the selected entity type.
*/
function rules_condition_entity_is_of_type_assertions($element) {
if ($type = $element->settings['type']) {
return array('entity' => array('type' => $type));
}
}
/**
* @}
*/

View File

@@ -0,0 +1,168 @@
<?php
/**
* @file Invokes events on behalf core modules. Usually this should be
* directly in the module providing rules integration instead.
*
* @addtogroup rules
* @{
*/
/**
* Gets an unchanged entity that doesn't contain any recent changes. This
* handler assumes the name of the variable for the changed entity is the same
* as for the unchanged entity but without the trailing "_unchanged"; e.g., for
* the "node_unchanged" variable the handler assumes there is a "node" variable.
*/
function rules_events_entity_unchanged($arguments, $name, $info) {
// Cut of the trailing _unchanged.
$var_name = substr($name, 0, -10);
$entity = $arguments[$var_name];
if (isset($entity->original)) {
return $entity->original;
}
}
/**
* Generic entity events, used for core-entities for which we provide Rules
* integration only.
* We are implementing the generic-entity hooks instead of the entity-type
* specific hooks to ensure we come last. See http://drupal.org/node/1211946
* for details.
*/
/**
* Implements hook_entity_view().
*/
function rules_entity_view($entity, $type, $view_mode, $langcode) {
$entity_types = array(
'comment' => TRUE,
'node' => TRUE,
'user' => TRUE,
);
if (isset($entity_types[$type])) {
rules_invoke_event($type . '_view', $entity, $view_mode);
}
}
/**
* Implements hook_entity_presave().
*/
function rules_entity_presave($entity, $type) {
$entity_types = array(
'comment' => TRUE,
'node' => TRUE,
'taxonomy_term' => TRUE,
'taxonomy_vocabulary' => TRUE,
'user' => TRUE,
);
if (isset($entity_types[$type])) {
rules_invoke_event($type . '_presave', $entity);
}
}
/**
* Implements hook_entity_update().
*/
function rules_entity_update($entity, $type) {
$entity_types = array(
'comment' => TRUE,
'node' => TRUE,
'taxonomy_term' => TRUE,
'taxonomy_vocabulary' => TRUE,
'user' => TRUE,
);
if (isset($entity_types[$type])) {
rules_invoke_event($type . '_update', $entity);
}
}
/**
* Implements hook_entity_insert().
*/
function rules_entity_insert($entity, $type) {
$entity_types = array(
'comment' => TRUE,
'node' => TRUE,
'taxonomy_term' => TRUE,
'taxonomy_vocabulary' => TRUE,
'user' => TRUE,
);
if (isset($entity_types[$type])) {
rules_invoke_event($type . '_insert', $entity);
}
}
/**
* Implements hook_entity_delete().
*/
function rules_entity_delete($entity, $type) {
$entity_types = array(
'comment' => TRUE,
'node' => TRUE,
'taxonomy_term' => TRUE,
'taxonomy_vocabulary' => TRUE,
'user' => TRUE,
);
if (isset($entity_types[$type])) {
rules_invoke_event($type . '_delete', $entity);
}
}
/**
* Implements hook_user_login().
*/
function rules_user_login(&$edit, $account) {
rules_invoke_event('user_login', $account);
}
/**
* Implements hook_user_logout().
*/
function rules_user_logout($account) {
rules_invoke_event('user_logout', $account);
}
/**
* System events. Note that rules_init() is the main module file is used to
* invoke the init event.
*/
/**
* Implements hook_cron().
*/
function rules_cron() {
rules_invoke_event('cron');
}
/**
* Implements hook_watchdog().
*/
function rules_watchdog($log_entry) {
rules_invoke_event('watchdog', $log_entry);
}
/**
* Getter callback for the log entry message property.
*/
function rules_system_log_get_message($log_entry) {
return t($log_entry['message'], (array)$log_entry['variables']);
}
/**
* Gets all view modes of an entity for an entity_view event.
*/
function rules_get_entity_view_modes($name, $var_info) {
// Read the entity type from a special key out of the variable info.
$entity_type = $var_info['options list entity type'];
$info = entity_get_info($entity_type);
foreach ($info['view modes'] as $mode => $mode_info) {
$modes[$mode] = $mode_info['label'];
}
return $modes;
}
/**
* @}
*/

View File

@@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains rules integration for the node module needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Condition: Check for selected content types
*/
function rules_condition_node_is_of_type($node, $types) {
return in_array($node->type, $types);
}
/**
* Condition: Check if the node is published
*/
function rules_condition_node_is_published($node) {
return $node->status == 1;
}
/**
* Condition: Check if the node is sticky
*/
function rules_condition_node_is_sticky($node) {
return $node->sticky == 1;
}
/**
* Condition: Check if the node is promoted to the frontpage
*/
function rules_condition_node_is_promoted($node) {
return $node->promote == 1;
}
/**
* @}
*/

View File

@@ -0,0 +1,173 @@
<?php
/**
* @file rules integration for the node module
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the node module.
*/
function rules_node_file_info() {
return array('modules/node.eval');
}
/**
* Implements hook_rules_event_info() on behalf of the node module.
*/
function rules_node_event_info() {
$items = array(
'node_insert' => array(
'label' => t('After saving new content'),
'group' => t('Node'),
'variables' => rules_events_node_variables(t('created content')),
'access callback' => 'rules_node_integration_access',
),
'node_update' => array(
'label' => t('After updating existing content'),
'group' => t('Node'),
'variables' => rules_events_node_variables(t('updated content'), TRUE),
'access callback' => 'rules_node_integration_access',
),
'node_presave' => array(
'label' => t('Before saving content'),
'group' => t('Node'),
'variables' => rules_events_node_variables(t('saved content'), TRUE),
'access callback' => 'rules_node_integration_access',
),
'node_view' => array(
'label' => t('Content is viewed'),
'group' => t('Node'),
'help' => t("Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache."),
'variables' => rules_events_node_variables(t('viewed content')) + array(
'view_mode' => array(
'type' => 'text',
'label' => t('view mode'),
'options list' => 'rules_get_entity_view_modes',
// Add the entity-type for the options list callback.
'options list entity type' => 'node',
),
),
'access callback' => 'rules_node_integration_access',
),
'node_delete' => array(
'label' => t('After deleting content'),
'group' => t('Node'),
'variables' => rules_events_node_variables(t('deleted content')),
'access callback' => 'rules_node_integration_access',
),
);
// Specify that on presave the node is saved anyway.
$items['node_presave']['variables']['node']['skip save'] = TRUE;
return $items;
}
/**
* Returns some parameter suitable for using it with a node
*/
function rules_events_node_variables($node_label, $update = FALSE) {
$args = array(
'node' => array('type' => 'node', 'label' => $node_label),
);
if ($update) {
$args += array(
'node_unchanged' => array(
'type' => 'node',
'label' => t('unchanged content'),
'handler' => 'rules_events_entity_unchanged',
),
);
}
return $args;
}
/**
* Implements hook_rules_condition_info() on behalf of the node module.
*/
function rules_node_condition_info() {
$defaults = array(
'parameter' => array(
'node' => array('type' => 'node', 'label' => t('Content')),
),
'group' => t('Node'),
'access callback' => 'rules_node_integration_access',
);
$items['node_is_of_type'] = $defaults + array(
'label' => t('Content is of type'),
'help' => t('Evaluates to TRUE if the given content is of one of the selected content types.'),
'base' => 'rules_condition_node_is_of_type',
);
$items['node_is_of_type']['parameter']['type'] = array(
'type' => 'list<text>',
'label' => t('Content types'),
'options list' => 'node_type_get_names',
'description' => t('The content type(s) to check for.'),
'restriction' => 'input',
);
$items['node_is_published'] = $defaults + array(
'label' => t('Content is published'),
'base' => 'rules_condition_node_is_published',
);
$items['node_is_sticky'] = $defaults + array(
'label' => t('Content is sticky'),
'base' => 'rules_condition_node_is_sticky',
);
$items['node_is_promoted'] = $defaults + array(
'label' => t('Content is promoted to frontpage'),
'base' => 'rules_condition_node_is_promoted',
);
return $items;
}
/**
* Provides the content type of a node as asserted metadata.
*/
function rules_condition_node_is_of_type_assertions($element) {
return array('node' => array('bundle' => $element->settings['type']));
}
/**
* Implements hook_rules_action_info() on behalf of the node module.
*/
function rules_node_action_info() {
$defaults = array(
'parameter' => array(
'node' => array('type' => 'node', 'label' => t('Content'), 'save' => TRUE),
),
'group' => t('Node'),
'access callback' => 'rules_node_admin_access',
);
// Add support for hand-picked core actions.
$core_actions = node_action_info();
$actions = array('node_publish_action', 'node_unpublish_action', 'node_make_sticky_action', 'node_make_unsticky_action', 'node_promote_action', 'node_unpromote_action');
foreach ($actions as $base) {
$action_name = str_replace('_action', '', $base);
$items[$action_name] = $defaults + array(
'label' => $core_actions[$base]['label'],
'base' => $base,
);
}
return $items;
}
/**
* Node integration access callback.
*/
function rules_node_integration_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return entity_access('view', 'node');
}
}
/**
* Node integration admin access callback.
*/
function rules_node_admin_access() {
return user_access('administer nodes');
}
/**
* @}
*/

View File

@@ -0,0 +1,148 @@
<?php
/**
* @file
* Contains rules integration for the path module needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Action implementation: Path alias.
*/
function rules_action_path_alias($source, $alias, $langcode = LANGUAGE_NONE) {
if (!$alias) {
path_delete(array('source' => $source, 'language' => $langcode));
}
elseif (!$source) {
path_delete(array('alias' => $alias, 'language' => $langcode));
}
// Only set the alias if the alias is not taken yet.
elseif (!path_load(array('alias' => $alias, 'language' => $langcode))) {
// Update the existing path or create a new one.
if ($path = path_load(array('source' => $source, 'language' => $langcode))) {
$path['alias'] = $alias;
}
else {
$path = array('source' => $source, 'alias' => $alias, 'language' => $langcode);
}
path_save($path);
}
else {
rules_log('The configured alias %alias already exists. Aborting.', array('%alias' => $alias));
}
}
/**
* Action Implementation: Set the URL alias for a node.
*/
function rules_action_node_path_alias($node, $alias) {
$langcode = isset($node->language) ? $node->language : LANGUAGE_NONE;
// Only set the alias if the alias is not taken yet.
if (($path = path_load(array('alias' => $alias, 'language' => $langcode))) && (empty($node->path['pid']) || $node->path['pid'] != $path['pid'])) {
rules_log('The configured alias %alias already exists. Aborting.', array('%alias' => $alias));
return FALSE;
}
$node->path['alias'] = $alias;
}
/**
* Action Implementation: Set the URL alias for a node.
*/
function rules_action_taxonomy_term_path_alias($term, $alias) {
// Only set the alias if the alias is not taken yet.
if (($path = path_load(array('alias' => $alias, 'language' => LANGUAGE_NONE))) && (empty($term->path['pid']) || $term->path['pid'] != $path['pid'])) {
rules_log('The configured alias %alias already exists. Aborting.', array('%alias' => $alias));
return FALSE;
}
$term->path['alias'] = $alias;
}
/**
* Condition implementation: Check if the path has an alias.
*/
function rules_condition_path_has_alias($source, $langcode = LANGUAGE_NONE) {
return (bool) drupal_lookup_path('alias', $source, $langcode);
}
/**
* Condition implementation: Check if the URL alias exists.
*/
function rules_condition_path_alias_exists($alias, $langcode = LANGUAGE_NONE) {
return (bool) drupal_lookup_path('source', $alias, $langcode);
}
/**
* Cleans the given path by replacing non ASCII characters with the replacment character.
*
* Path cleaning may be adapted by overriding the configuration variables
* @code rules_clean_path @endcode,
* @code rules_path_replacement_char @endcode and
* @code rules_path_transliteration @endcode
* in the site's settings.php file.
*/
function rules_path_default_cleaning_method($path) {
$replace = variable_get('rules_path_replacement_char', '-');
if ($replace) {
// If the transliteration module is enabled, transliterate the alias first.
if (module_exists('transliteration') && variable_get('rules_path_transliteration', TRUE)) {
$path = transliteration_get($path);
}
$array = variable_get('rules_clean_path', array('/[^a-zA-Z0-9\-_]+/', $replace));
$array[2] = $path;
// Replace it and remove trailing and leading replacement characters.
$output = trim(call_user_func_array('preg_replace', $array), $replace);
if (variable_get('rules_path_lower_case', TRUE)) {
$output = drupal_strtolower($output);
}
return $output;
}
else {
return $path;
}
}
/**
* Cleans the given string so it can be used as part of a URL path.
*/
function rules_clean_path($path) {
$function = variable_get('rules_path_cleaning_callback', 'rules_path_default_cleaning_method');
if (!function_exists($function)) {
rules_log('An invalid URL path cleaning callback has been configured. Falling back to the default cleaning method.', array(), RulesLog::WARN);
$function = 'rules_path_default_cleaning_method';
}
return $function($path);
}
/**
* CTools path cleaning callback.
*
* @see rules_admin_settings()
*/
function rules_path_clean_ctools($path) {
// Make use of the CTools cleanstring implementation.
ctools_include('cleanstring');
$settings = array(
'separator' => variable_get('rules_path_replacement_char', '-'),
'transliterate' => module_exists('transliteration') && variable_get('rules_path_transliteration', TRUE),
'lower case' => variable_get('rules_path_lower_case', TRUE),
);
return ctools_cleanstring($path, $settings);
}
/**
* Pathauto path cleaning callback.
*
* @see rules_admin_settings()
*/
function rules_path_clean_pathauto($path) {
module_load_include('inc', 'pathauto');
return pathauto_cleanstring($path);
}
/**
* @}
*/

View File

@@ -0,0 +1,171 @@
<?php
/**
* @file rules integration for the path module
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the path module.
*/
function rules_path_file_info() {
return array('modules/path.eval');
}
/**
* Implements hook_rules_action_info() on behalf of the path module.
*/
function rules_path_action_info() {
return array(
'path_alias' => array(
'label' => t('Create or delete any URL alias'),
'group' => t('Path'),
'parameter' => array(
'source' => array(
'type' => 'text',
'label' => t('Existing system path'),
'description' => t('Specifies the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1+2.') .' '. t('Leave it empty to delete URL aliases pointing to the given path alias.'),
'optional' => TRUE,
),
'alias' => array(
'type' => 'text',
'label' => t('URL alias'),
'description' => t('Specify an alternative path by which this data can be accessed. For example, "about" for an about page. Use a relative path and do not add a trailing slash.') .' '. t('Leave it empty to delete URL aliases pointing to the given system path.'),
'optional' => TRUE,
'cleaning callback' => 'rules_path_clean_replacement_values',
),
'language' => array(
'type' => 'token',
'label' => t('Language'),
'description' => t('If specified, the language for which the URL alias applies.'),
'options list' => 'entity_metadata_language_list',
'optional' => TRUE,
'default value' => LANGUAGE_NONE,
),
),
'base' => 'rules_action_path_alias',
'callbacks' => array('dependencies' => 'rules_path_dependencies'),
'access callback' => 'rules_path_integration_access',
),
'node_path_alias' => array(
'label' => t("Create or delete a content's URL alias"),
'group' => t('Path'),
'parameter' => array(
'node' => array(
'type' => 'node',
'label' => t('Content'),
'save' => TRUE,
),
'alias' => array(
'type' => 'text',
'label' => t('URL alias'),
'description' => t('Specify an alternative path by which the content can be accessed. For example, "about" for an about page. Use a relative path and do not add a trailing slash.') .' '. t('Leave it empty to delete the URL alias.'),
'optional' => TRUE,
'cleaning callback' => 'rules_path_clean_replacement_values',
),
),
'base' => 'rules_action_node_path_alias',
'callbacks' => array('dependencies' => 'rules_path_dependencies'),
'access callback' => 'rules_path_integration_access',
),
'taxonomy_term_path_alias' => array(
'label' => t("Create or delete a taxonomy term's URL alias"),
'group' => t('Path'),
'parameter' => array(
'node' => array(
'type' => 'taxonomy_term',
'label' => t('Taxonomy term'),
'save' => TRUE,
),
'alias' => array(
'type' => 'text',
'label' => t('URL alias'),
'description' => t('Specify an alternative path by which the term can be accessed. For example, "content/drupal" for a Drupal term. Use a relative path and do not add a trailing slash.') .' '. t('Leave it empty to delete the URL alias.'),
'optional' => TRUE,
'cleaning callback' => 'rules_path_clean_replacement_values',
),
),
'base' => 'rules_action_node_path_alias',
'callbacks' => array('dependencies' => 'rules_path_dependencies'),
'access callback' => 'rules_path_integration_access',
),
);
}
/**
* Callback to specify the path module as dependency.
*/
function rules_path_dependencies() {
return array('path');
}
/**
* Path integration access callback.
*/
function rules_path_integration_access($type, $name) {
if ($type == 'action' && $name == 'path_alias') {
return user_access('administer url aliases');
}
return user_access('create url aliases');
}
/**
* Implements hook_rules_condition_info() on behalf of the path module.
*/
function rules_path_condition_info() {
return array(
'path_has_alias' => array(
'label' => t('Path has URL alias'),
'group' => t('Path'),
'parameter' => array(
'source' => array(
'type' => 'text',
'label' => t('Existing system path'),
'description' => t('Specifies the existing path you wish to check for. For example: node/28, forum/1, taxonomy/term/1+2.'),
'optional' => TRUE,
),
'language' => array(
'type' => 'token',
'label' => t('Language'),
'description' => t('If specified, the language for which the URL alias applies.'),
'options list' => 'entity_metadata_language_list',
'optional' => TRUE,
'default value' => LANGUAGE_NONE,
),
),
'base' => 'rules_condition_path_has_alias',
'callbacks' => array('dependencies' => 'rules_path_dependencies'),
'access callback' => 'rules_path_integration_access',
),
'path_alias_exists' => array(
'label' => t('URL alias exists'),
'group' => t('Path'),
'parameter' => array(
'alias' => array(
'type' => 'text',
'label' => t('URL alias'),
'description' => t('Specify the URL alias to check for. For example, "about" for an about page.'),
'optional' => TRUE,
'cleaning callback' => 'rules_path_clean_replacement_values',
),
'language' => array(
'type' => 'token',
'label' => t('Language'),
'description' => t('If specified, the language for which the URL alias applies.'),
'options list' => 'entity_metadata_language_list',
'optional' => TRUE,
'default value' => LANGUAGE_NONE,
),
),
'base' => 'rules_condition_path_alias_exists',
'callbacks' => array('dependencies' => 'rules_path_dependencies'),
'access callback' => 'rules_path_integration_access',
),
);
}
/**
* @}
*/

View File

@@ -0,0 +1,151 @@
<?php
/**
* @file
* Contains rules integration for the php module needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* A class implementing a rules input evaluator processing PHP.
*/
class RulesPHPEvaluator extends RulesDataInputEvaluator {
public static function access() {
return user_access('use PHP for settings');
}
public static function getUsedVars($text, $var_info) {
if (strpos($text, '<?') !== FALSE) {
$used_vars = array();
foreach ($var_info as $name => $info) {
if (strpos($text, '$' . $name) !== FALSE) {
$used_vars[] = $name;
}
}
return $used_vars;
}
}
public function prepare($text, $var_info) {
// A returned NULL skips the evaluator.
$this->setting = self::getUsedVars($text, $var_info);
}
/**
* Evaluates PHP code contained in $text. This doesn't apply $options, thus
* the PHP code is responsible for behaving appropriately.
*/
public function evaluate($text, $options, RulesState $state) {
$vars['eval_options'] = $options;
foreach ($this->setting as $key => $var_name) {
$vars[$var_name] = $state->get($var_name);
}
return rules_php_eval($text, rules_unwrap_data($vars));
}
public static function help($var_info) {
module_load_include('inc', 'rules', 'rules/modules/php.rules');
$render = array(
'#type' => 'fieldset',
'#title' => t('PHP Evaluation'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
) + rules_php_evaluator_help($var_info);
return $render;
}
}
/**
* A data processor using PHP.
*/
class RulesPHPDataProcessor extends RulesDataProcessor {
protected static function form($settings, $var_info) {
$settings += array('code' => '');
$form = array(
'#type' => 'fieldset',
'#title' => t('PHP evaluation'),
'#collapsible' => TRUE,
'#collapsed' => empty($settings['code']),
'#description' => t('Enter PHP code to process the selected argument value.'),
);
$form['code'] = array(
'#type' => 'textarea',
'#title' => t('Code'),
'#description' => t('Enter PHP code without &lt;?php ?&gt; delimiters that returns the processed value. The selected value is available in the variable $value. Example: %code', array('%code' => 'return $value + 1;')),
'#default_value' => $settings['code'],
'#weight' => 5,
);
return $form;
}
public static function access() {
return user_access('use PHP for settings');
}
public function process($value, $info, RulesState $state, RulesPlugin $element) {
$value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value;
return rules_php_eval_return($this->setting['code'], array('value' => $value));
}
}
/**
* Action and condition callback: Execute PHP code.
*/
function rules_execute_php_eval($code, $settings, $state, $element) {
$data = array();
if (!empty($settings['used_vars'])) {
foreach ($settings['used_vars'] as $key => $var_name) {
$data[$var_name] = $state->get($var_name);
}
}
return rules_php_eval_return($code, rules_unwrap_data($data));
}
/**
* Evalutes the given PHP code, with the given variables defined.
*
* @param $code
* The PHP code to run, with <?php ?>
* @param $arguments
* Array containing variables to be extracted to the code.
*
* @return
* The output of the php code.
*/
function rules_php_eval($code, $arguments = array()) {
extract($arguments);
ob_start();
print eval('?>'. $code);
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Evalutes the given PHP code, with the given variables defined. This is like
* rules_php_eval() but does return the returned data from the PHP code.
*
* @param $code
* The PHP code to run, without <?php ?>
* @param $arguments
* Array containing variables to be extracted to the code.
*
* @return
* The return value of the evaled code.
*/
function rules_php_eval_return($code, $arguments = array()) {
extract($arguments);
return eval($code);
}
/**
* @}
*/

View File

@@ -0,0 +1,158 @@
<?php
/**
* @file rules integration for the php module
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the php module.
*/
function rules_php_file_info() {
return array('modules/php.eval');
}
/**
* Implements hook_rules_evaluator_info() on behalf of the php module.
*/
function rules_php_evaluator_info() {
return array(
'php' => array(
'class' => 'RulesPHPEvaluator',
'type' => array('text', 'uri'),
'weight' => -10,
'module' => 'php',
),
);
}
/**
* Implements hook_rules_data_processor_info() on behalf of the php module.
*/
function rules_php_data_processor_info() {
return array(
'php' => array(
'class' => 'RulesPHPDataProcessor',
'type' => array('text', 'token', 'decimal', 'integer', 'date', 'duration', 'boolean', 'uri'),
'weight' => 10,
'module' => 'php',
),
);
}
/**
* Implements hook_rules_action_info() on behalf of the php module.
*/
function rules_php_action_info() {
return array(
'php_eval' => array(
'label' => t('Execute custom PHP code'),
'group' => t('PHP'),
'parameter' => array(
'code' => array(
'restriction' => 'input',
'type' => 'text',
'label' => t('PHP code'),
'description' => t('Enter PHP code without &lt;?php ?&gt; delimiters.'),
),
),
'base' => 'rules_execute_php_eval',
'access callback' => 'rules_php_integration_access',
),
);
}
/**
* Alter the form for improved UX.
*/
function rules_execute_php_eval_form_alter(&$form, &$form_state) {
// Remove the PHP evaluation help to avoid confusion whether <?php tags should
// be used. But keep the help about available variables.
$form['parameter']['code']['settings']['help']['php']['#type'] = 'container';
$form['parameter']['code']['settings']['help']['php']['top']['#markup'] = t('The following variables are available and may be used by your PHP code:');
}
/**
* Process the settings to prepare code execution.
*/
function rules_execute_php_eval_process(RulesAbstractPlugin $element) {
$element->settings['used_vars'] = RulesPHPEvaluator::getUsedVars('<?' . $element->settings['code'], $element->availableVariables());
}
/**
* Specify the php module as dependency.
*/
function rules_execute_php_eval_dependencies() {
return array('php');
}
/**
* PHP integration access callback.
*/
function rules_php_integration_access() {
return user_access('use PHP for settings');
}
/**
* Implements hook_rules_condition_info() on behalf of the PHP module.
*/
function rules_php_condition_info() {
return array(
'php_eval' => array(
'label' => t('Execute custom PHP code'),
'group' => t('PHP'),
'parameter' => array(
'code' => array(
'restriction' => 'input',
'type' => 'text',
'label' => t('PHP code'),
'description' => t('Enter PHP code without &lt;?php ?&gt; delimiters that returns a boolean value; e.g. <code>@code</code>.', array('@code' => "return arg(0) == 'node';")),
),
),
'base' => 'rules_execute_php_eval',
'access callback' => 'rules_php_integration_access',
),
);
}
/**
* Generates help for the PHP actions, conditions and input evaluator.
*/
function rules_php_evaluator_help($var_info, $action_help = FALSE) {
$render['top'] = array(
'#prefix' => '<p>',
'#suffix' => '</p>',
'#markup' => t('PHP code inside of &lt;?php ?&gt; delimiters will be evaluated and replaced by its output. E.g. &lt;? echo 1+1?&gt; will be replaced by 2.')
. ' ' . t('Furthermore you can make use of the following variables:'),
);
$render['vars'] = array(
'#theme' => 'table',
'#header' => array(t('Variable name'), t('Type'), t('Description')),
'#attributes' => array('class' => array('rules-php-help')),
);
$cache = rules_get_cache();
foreach ($var_info as $name => $info) {
$row = array();
$row[] = '$'. check_plain($name);
$label = isset($cache['data_info'][$info['type']]['label']) ? $cache['data_info'][$info['type']]['label'] : $info['type'];
$row[] = check_plain(drupal_ucfirst($label));
$row[] = check_plain($info['label']);
$render['vars']['#rows'][] = $row;
}
if ($action_help) {
$render['updated_help'] = array(
'#prefix' => '<p>',
'#suffix' => '</p>',
'#markup' => t("If you want to change a variable just return an array of new variable values, e.g.: !code", array('!code' => '<pre>return array("node" => $node);</pre>')),
);
}
return $render;
}
/**
* @}
*/

View File

@@ -0,0 +1,242 @@
<?php
/**
* @file
* Contains rules core integration needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Action and condition callback: Invokes a rules component.
*
* We do not use the execute() method, but handle executing ourself. That way
* we can utilize the existing state for saving passed variables.
*/
function rules_element_invoke_component($arguments, RulesPlugin $element) {
$info = $element->info();
$state = $arguments['state'];
$wrapped_args = $state->currentArguments;
if ($component = rules_get_cache('comp_' . $info['#config_name'])) {
$replacements = array('%label' => $component->label(), '@plugin' => $component->plugin());
// Handle recursion prevention.
if ($state->isBlocked($component)) {
return rules_log('Not evaluating @plugin %label to prevent recursion.', $replacements, RulesLog::INFO, $component);
}
$state->block($component);
rules_log('Evaluating @plugin %label.', $replacements, RulesLog::INFO, $component, TRUE);
module_invoke_all('rules_config_execute', $component);
// Manually create a new evaluation state and evaluate the component.
$args = array_intersect_key($wrapped_args, $component->parameterInfo());
$new_state = $component->setUpState($wrapped_args);
$return = $component->evaluate($new_state);
// Care for the right return value in case we have to provide vars.
if ($component instanceof RulesActionInterface && !empty($info['provides'])) {
$return = array();
foreach ($info['provides'] as $var => $var_info) {
$return[$var] = $new_state->get($var);
}
}
// Now merge the info about to be saved variables in the parent state.
$state->mergeSaveVariables($new_state, $component, $element->settings);
$state->unblock($component);
// Cleanup the state, what saves not mergable variables now.
$new_state->cleanup();
rules_log('Finished evaluation of @plugin %label.', $replacements, RulesLog::INFO, $component, FALSE);
return $return;
}
else {
throw new RulesEvaluationException('Unable to get the component %name', array('%name' => $info['#config_name']), $element, RulesLog::ERROR);
}
}
/**
* A class implementing a rules input evaluator processing date input. This is
* needed to treat relative date inputs for strtotime right, consider "now".
*/
class RulesDateInputEvaluator extends RulesDataInputEvaluator {
const DATE_REGEX_LOOSE = '/^(\d{4})-?(\d{2})-?(\d{2})([T\s]?(\d{2}):?(\d{2}):?(\d{2})?)?$/';
public function prepare($text, $var_info) {
if (is_numeric($text)) {
// Let rules skip this input evaluators in case it's already a timestamp.
$this->setting = NULL;
}
}
public function evaluate($text, $options, RulesState $state) {
return self::gmstrtotime($text);
}
/**
* Convert a time string to a GMT (UTC) unix timestamp.
*/
public static function gmstrtotime($date) {
// Pass the current timestamp in UTC to ensure the retrieved time is UTC.
return strtotime($date, time());
}
/**
* Determine whether the given date string specifies a fixed date.
*/
public static function isFixedDateString($date) {
return is_string($date) && preg_match(self::DATE_REGEX_LOOSE, $date);
}
}
/**
* A class implementing a rules input evaluator processing URI inputs to make
* sure URIs are absolute and path aliases get applied.
*/
class RulesURIInputEvaluator extends RulesDataInputEvaluator {
public function prepare($uri, $var_info) {
if (!isset($this->processor) && valid_url($uri, TRUE)) {
// Only process if another evaluator is used or the url is not absolute.
$this->setting = NULL;
}
}
public function evaluate($uri, $options, RulesState $state) {
if (!url_is_external($uri)) {
// Extract the path and build the URL using the url() function, so URL
// aliases are applied and query parameters and fragments get handled.
$url = drupal_parse_url($uri);
$url_options = array('absolute' => TRUE);
$url_options['query'] = $url['query'];
$url_options['fragment'] = $url['fragment'];
return url($url['path'], $url_options);
}
elseif (valid_url($uri)) {
return $uri;
}
throw new RulesEvaluationException('Input evaluation generated an invalid URI.', array(), NULL, RulesLog::WARN);
}
}
/**
* A data processor for applying date offsets.
*/
class RulesDateOffsetProcessor extends RulesDataProcessor {
protected static function form($settings, $var_info) {
$settings += array('value' => '');
$form = array(
'#type' => 'fieldset',
'#title' => t('Add offset'),
'#collapsible' => TRUE,
'#collapsed' => empty($settings['value']),
'#description' => t('Add an offset to the selected date.'),
);
$form['value'] = array(
'#type' => 'rules_duration',
'#title' => t('Offset'),
'#description' => t('Note that you can also specify negative numbers.'),
'#default_value' => $settings['value'],
'#weight' => 5,
);
return $form;
}
public function process($value, $info, RulesState $state, RulesPlugin $element) {
$value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value;
return RulesDateOffsetProcessor::applyOffset($value, $this->setting['value']);
}
/**
* Intelligently applies the given date offset in seconds.
*
* Intelligently apply duration values > 1 day, i.e. convert the duration
* to its biggest possible unit (months, days) and apply it to the date with
* the given unit. That's necessary as the number of days in a month
* differs, as well as the number of hours for a day (on DST changes).
*/
public static function applyOffset($timestamp, $offset) {
if (abs($offset) >= 86400) {
// Get the days out of the seconds.
$days = intval($offset / 86400);
$sec = $offset % 86400;
// Get the months out of the number of days.
$months = intval($days / 30);
$days = $days % 30;
// Apply the offset using the DateTime::modify and convert it back to a
// timestamp.
$date = date_create("@$timestamp");
$date->modify("$months months $days days $sec seconds");
return $date->format('U');
}
else {
return $timestamp + $offset;
}
}
}
/**
* A data processor for applying numerical offsets.
*/
class RulesNumericOffsetProcessor extends RulesDataProcessor {
protected static function form($settings, $var_info) {
$settings += array('value' => '');
$form = array(
'#type' => 'fieldset',
'#title' => t('Add offset'),
'#collapsible' => TRUE,
'#collapsed' => empty($settings['value']),
'#description' => t('Add an offset to the selected number. E.g. an offset of "1" adds 1 to the number before it is passed on as argument.'),
);
$form['value'] = array(
'#type' => 'textfield',
'#title' => t('Offset'),
'#description' => t('Note that you can also specify negative numbers.'),
'#default_value' => $settings['value'],
'#element_validate' => array('rules_ui_element_integer_validate'),
'#weight' => 5,
);
return $form;
}
public function process($value, $info, RulesState $state, RulesPlugin $element) {
$value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value;
return $value + $this->setting['value'];
}
}
/**
* A custom wrapper class for vocabularies that is capable of loading vocabularies by machine name.
*/
class RulesTaxonomyVocabularyWrapper extends EntityDrupalWrapper {
/**
* Overridden to support identifying vocabularies by machine names.
*/
protected function setEntity($data) {
if (isset($data) && $data !== FALSE && !is_object($data) && !is_numeric($data)) {
// The vocabulary name has been passed.
parent::setEntity(taxonomy_vocabulary_machine_name_load($data));
}
else {
parent::setEntity($data);
}
}
/**
* Overriden to permit machine names as values.
*/
public function validate($value) {
if (isset($value) && is_string($value)) {
return TRUE;
}
return parent::validate($value);
}
}

View File

@@ -0,0 +1,314 @@
<?php
/**
* @file Rules core integration providing data types and conditions and
* actions to invoke configured components.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the pseudo rules_core module.
*
* @see rules_core_modules()
*/
function rules_rules_core_file_info() {
return array('modules/rules_core.eval');
}
/**
* Implements hook_rules_data_info() on behalf of the pseudo rules_core module.
*
* @see rules_core_modules()
*/
function rules_rules_core_data_info() {
$return = array(
'text' => array(
'label' => t('text'),
'ui class' => 'RulesDataUIText',
'token type' => 'rules_text',
),
'token' => array(
'label' => t('text token'),
'parent' => 'text',
'ui class' => 'RulesDataUITextToken',
'token type' => 'rules_token',
),
// A formatted text as used by entity metadata.
'text_formatted' => array(
'label' => t('formatted text'),
'ui class' => 'RulesDataUITextFormatted',
'wrap' => TRUE,
'property info' => entity_property_text_formatted_info(),
),
'decimal' => array(
'label' => t('decimal number'),
'parent' => 'text',
'ui class' => 'RulesDataUIDecimal',
'token type' => 'rules_decimal',
),
'integer' => array(
'label' => t('integer'),
'class' => 'RulesIntegerWrapper',
'parent' => 'decimal',
'ui class' => 'RulesDataUIInteger',
'token type' => 'rules_integer',
),
'date' => array(
'label' => t('date'),
'ui class' => 'RulesDataUIDate',
'token type' => 'rules_date',
),
'duration' => array(
'label' => t('duration'),
'parent' => 'integer',
'ui class' => 'RulesDataUIDuration',
'token type' => 'rules_duration',
),
'boolean' => array(
'label' => t('truth value'),
'ui class' => 'RulesDataUIBoolean',
'token type' => 'rules_boolean',
),
'uri' => array(
'label' => t('URI'),
'parent' => 'text',
// Clean inserted tokens with "rawurlencode".
'cleaning callback' => 'rawurlencode',
'ui class' => 'RulesDataUIURI',
'token type' => 'rules_uri',
),
'list' => array(
'label' => t('list', array(), array('context' => 'data_types')),
'wrap' => TRUE,
'group' => t('List', array(), array('context' => 'data_types')),
),
'list<text>' => array(
'label' => t('list of text'),
'ui class' => 'RulesDataUIListText',
'wrap' => TRUE,
'group' => t('List', array(), array('context' => 'data_types')),
),
'list<integer>' => array(
'label' => t('list of integer'),
'ui class' => 'RulesDataUIListInteger',
'wrap' => TRUE,
'group' => t('List', array(), array('context' => 'data_types')),
),
'list<token>' => array(
'label' => t('list of text tokens'),
'ui class' => 'RulesDataUIListToken',
'wrap' => TRUE,
'group' => t('List', array(), array('context' => 'data_types')),
),
'entity' => array(
'label' => t('any entity'),
'group' => t('Entity'),
'is wrapped' => TRUE,
),
);
foreach (entity_get_info() as $type => $info) {
if (!empty($info['label'])) {
$return[$type] = array(
'label' => strtolower($info['label'][0]) . substr($info['label'], 1),
'parent' => 'entity',
'wrap' => TRUE,
'group' => t('Entity'),
'ui class' => empty($info['exportable']) ? 'RulesDataUIEntity' : 'RulesDataUIEntityExportable',
);
}
}
if (module_exists('taxonomy')) {
// For exportability identify vocabularies by name.
$return['taxonomy_vocabulary']['wrapper class'] = 'RulesTaxonomyVocabularyWrapper';
$return['taxonomy_vocabulary']['ui class'] = 'RulesDataUITaxonomyVocabulary';
}
return $return;
}
/**
* Implements hook_rules_data_info_alter() on behalf of the pseudo rules_core module.
*
* Makes sure there is a list<type> data type for each type registered.
*
* @see rules_rules_data_info_alter()
*/
function rules_rules_core_data_info_alter(&$data_info) {
foreach ($data_info as $type => $info) {
if (!entity_property_list_extract_type($type)) {
$list_type = "list<$type>";
if (!isset($data_info[$list_type])) {
$data_info[$list_type] = array(
'label' => t('list of @type_label items', array('@type_label' => $info['label'])),
'wrap' => TRUE,
'group' => t('List', array(), array('context' => 'data_types')),
);
if (isset($info['parent']) && $info['parent'] == 'entity') {
$data_info[$list_type]['ui class'] = 'RulesDataUIListEntity';
}
}
}
}
}
/**
* Implements hook_rules_evaluator_info() on behalf of the pseudo rules_core
* module.
*
* @see rules_core_modules()
*/
function rules_rules_core_evaluator_info() {
return array(
// Process strtotime() inputs to timestamps.
'date' => array(
'class' => 'RulesDateInputEvaluator',
'type' => 'date',
'weight' => -10,
),
// Post-process any input value to absolute URIs.
'uri' => array(
'class' => 'RulesURIInputEvaluator',
'type' => 'uri',
'weight' => 50,
),
);
}
/**
* Implements hook_rules_data_processor_info() on behalf of the pseudo
* rules_core module.
*
* @see rules_core_modules()
*/
function rules_rules_core_data_processor_info() {
return array(
'date_offset' => array(
'class' => 'RulesDateOffsetProcessor',
'type' => 'date',
'weight' => -2,
),
'num_offset' => array(
'class' => 'RulesNumericOffsetProcessor',
'type' => array('integer', 'decimal'),
'weight' => -2,
),
);
}
/**
* Implements hook_rules_condition_info() on behalf of the pseudo rules_core
* module.
*
* @see rules_core_modules()
*/
function rules_rules_core_condition_info() {
$defaults = array(
'group' => t('Components'),
'base' => 'rules_element_invoke_component',
'named parameter' => TRUE,
'access callback' => 'rules_element_invoke_component_access_callback',
);
$items = array();
foreach (rules_get_components(FALSE, 'condition') as $name => $config) {
$items['component_' . $name] = $defaults + array(
'label' => $config->plugin() . ': ' . drupal_ucfirst($config->label()),
'parameter' => $config->parameterInfo(),
);
$items['component_' . $name]['#config_name'] = $name;
}
return $items;
}
/**
* Implements hook_rules_action_info() on behalf of the pseudo rules_core
* module.
*
* @see rules_core_modules()
*/
function rules_rules_core_action_info() {
$defaults = array(
'group' => t('Components'),
'base' => 'rules_element_invoke_component',
'named parameter' => TRUE,
'access callback' => 'rules_element_invoke_component_access_callback',
);
$items = array();
foreach (rules_get_components(FALSE, 'action') as $name => $config) {
$items['component_' . $name] = $defaults + array(
'label' => $config->plugin() . ': ' . drupal_ucfirst($config->label()),
'parameter' => $config->parameterInfo(),
'provides' => $config->providesVariables(),
);
$items['component_' . $name]['#config_name'] = $name;
}
return $items;
}
/**
* Implements RulesPluginUIInterface::operations() for the action.
*/
function rules_element_invoke_component_operations(RulesPlugin $element) {
$defaults = $element->extender('RulesPluginUI')->operations();
$info = $element->info();
// Add an operation for editing the component.
$defaults['#links']['component'] = array(
'title' => t('edit component'),
'href' => RulesPluginUI::path($info['#config_name']),
);
return $defaults;
}
/**
* Validate callback to make sure the invoked component exists and is not dirty.
*
* @see rules_scheduler_action_schedule_validate()
*/
function rules_element_invoke_component_validate(RulesPlugin $element) {
$info = $element->info();
$component = rules_config_load($info['#config_name']);
// Check if a component exists.
if (!$component) {
throw new RulesIntegrityException(t('The component %config does not exist.', array('%config' => $info['#config_name'])), $element);
}
// Check if a component is marked as dirty.
rules_config_update_dirty_flag($component);
if (!empty($component->dirty)) {
throw new RulesIntegrityException(t('The utilized component %config fails the integrity check.', array('%config' => $info['#config_name'])), $element);
}
}
/**
* Implements the features export callback of the RulesPluginFeaturesIntegrationInterace.
*/
function rules_element_invoke_component_features_export(&$export, &$pipe, $module_name = '', $element) {
// Add the used component to the pipe.
$info = $element->info();
$pipe['rules_config'][] = $info['#config_name'];
}
/**
* Access callback for the invoke component condition/action.
*/
function rules_element_invoke_component_access_callback($type, $name) {
// Cut of the leading 'component_' from the action name.
$component = rules_config_load(substr($name, 10));
if (!$component) {
// Missing component.
return FALSE;
}
// If access is not exposed for this component, default to component access.
if (empty($component->access_exposed)) {
return $component->access();
}
// Apply the permissions.
return user_access('bypass rules access') || user_access("use Rules component $component->name");
}
/**
* @}
*/

View File

@@ -0,0 +1,267 @@
<?php
/**
* @file
* Contains rules integration for the system module needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Action: Show a drupal message.
*/
function rules_action_drupal_message($message, $status, $repeat) {
drupal_set_message(filter_xss_admin($message), $status, $repeat);
}
/**
* Action: Page redirect.
*
* @see rules_page_build()
* @see rules_drupal_goto_alter()
*/
function rules_action_drupal_goto($url, $force = FALSE, $destination = FALSE) {
// Don't let administrators lock them out from Rules administration pages.
if (isset($_GET['q']) && strpos($_GET['q'], 'admin/config/workflow/rules') === 0) {
rules_log('Skipped page redirect on Rules administration page.', array(), RulesLog::WARN);
return;
}
// Do not redirect during batch processing.
if (($batch = batch_get()) && isset($batch['current_set'])) {
rules_log('Skipped page redirect during batch processing.');
return;
}
// Keep the current destination parameter if there is one set.
if ($destination) {
$url .= strpos($url, '?') === FALSE ? '?' : '&';
$url .= drupal_http_build_query(drupal_get_destination());
}
// If force is enabled, remove any destination parameter.
if ($force && isset($_GET['destination'])) {
unset($_GET['destination']);
}
// We don't invoke drupal_goto() right now, as this would end the the current
// page execution unpredictly for modules. So we'll take over drupal_goto()
// calls from somewhere else via hook_drupal_goto_alter() and make sure
// a drupal_goto() is invoked before the page is output with
// rules_page_build().
$GLOBALS['_rules_action_drupal_goto_do'] = array($url, $force);
}
/**
* Action: Set breadcrumb.
*/
function rules_action_breadcrumb_set(array $titles, array $paths) {
$trail = array(l(t('Home'), ''));
foreach ($titles as $i => $title) {
// Skip empty titles.
if ($title = trim($title)) {
// Output plaintext instead of a link if there is a title
// without a path.
$path = trim($paths[$i]);
if (!empty($paths[$i]) && $paths[$i] != '<none>') {
$trail[] = l($title, trim($paths[$i]));
}
else {
$trail[] = check_plain($title);
}
}
}
drupal_set_breadcrumb($trail);
}
/**
* Action Implementation: Send mail.
*/
function rules_action_mail($to, $subject, $message, $from = NULL, $langcode, $settings, RulesState $state, RulesPlugin $element) {
$to = str_replace(array("\r", "\n"), '', $to);
$from = !empty($from) ? str_replace(array("\r", "\n"), '', $from) : NULL;
$params = array(
'subject' => $subject,
'message' => $message,
'langcode' => $langcode,
);
// Set a unique key for this mail.
$name = isset($element->root()->name) ? $element->root()->name : 'unnamed';
$key = 'rules_action_mail_' . $name . '_' . $element->elementId();
$languages = language_list();
$language = isset($languages[$langcode]) ? $languages[$langcode] : language_default();
$message = drupal_mail('rules', $key, $to, $language, $params, $from);
if ($message['result']) {
watchdog('rules', 'Successfully sent email to %recipient', array('%recipient' => $to));
}
}
/**
* Action: Send mail to all users of a specific role group(s).
*/
function rules_action_mail_to_users_of_role($roles, $subject, $message, $from = NULL, $settings, RulesState $state, RulesPlugin $element) {
$from = !empty($from) ? str_replace(array("\r", "\n"), '', $from) : NULL;
// All authenticated users, which is everybody.
if (in_array(DRUPAL_AUTHENTICATED_RID, $roles)) {
$result = db_query('SELECT mail FROM {users} WHERE uid > 0');
}
else {
$rids = implode(',', $roles);
// Avoid sending emails to members of two or more target role groups.
$result = db_query('SELECT DISTINCT u.mail FROM {users} u INNER JOIN {users_roles} r ON u.uid = r.uid WHERE r.rid IN ('. $rids .')');
}
// Now, actually send the mails.
$params = array(
'subject' => $subject,
'message' => $message,
);
// Set a unique key for this mail.
$name = isset($element->root()->name) ? $element->root()->name : 'unnamed';
$key = 'rules_action_mail_to_users_of_role_' . $name . '_' . $element->elementId(); $languages = language_list();
$message = array('result' => TRUE);
foreach ($result as $row) {
$message = drupal_mail('rules', $key, $row->mail, language_default(), $params, $from);
if (!$message['result']) {
break;
}
}
if ($message['result']) {
$role_names = array_intersect_key(user_roles(TRUE), array_flip($roles));
watchdog('rules', 'Successfully sent email to the role(s) %roles.', array('%roles' => implode(', ', $role_names)));
}
}
/**
* Implements hook_mail().
*
* Set's the message subject and body as configured.
*/
function rules_mail($key, &$message, $params) {
$message['subject'] .= str_replace(array("\r", "\n"), '', $params['subject']);
$message['body'][] = $params['message'];
}
/**
* A class implementing a rules input evaluator processing tokens.
*/
class RulesTokenEvaluator extends RulesDataInputEvaluator {
public function prepare($text, $var_info) {
$text = is_array($text) ? implode('', $text) : $text;
// Skip this evaluator if there are no tokens.
$this->setting = token_scan($text) ? TRUE : NULL;
}
/**
* We replace the tokens on our own as we cannot use token_replace(), because
* token usually assumes that $data['node'] is a of type node, which doesn't
* hold in general in our case.
* So we properly map variable names to variable data types and then run the
* replacement ourself.
*/
public function evaluate($text, $options, RulesState $state) {
$var_info = $state->varInfo();
$options += array('sanitize' => FALSE);
$replacements = array();
$data = array();
// We also support replacing tokens in a list of textual values.
$whole_text = is_array($text) ? implode('', $text) : $text;
foreach (token_scan($whole_text) as $var_name => $tokens) {
$var_name = str_replace('-', '_', $var_name);
if (isset($var_info[$var_name]) && ($token_type = _rules_system_token_map_type($var_info[$var_name]['type']))) {
// We have to key $data with the type token uses for the variable.
$data = rules_unwrap_data(array($token_type => $state->get($var_name)), array($token_type => $var_info[$var_name]));
$replacements += token_generate($token_type, $tokens, $data, $options);
}
else {
$replacements += token_generate($var_name, $tokens, array(), $options);
}
// Remove tokens if no replacement value is found. As token_replace() does
// if 'clear' is set.
$replacements += array_fill_keys($tokens, '');
}
// Optionally clean the list of replacement values.
if (!empty($options['callback']) && function_exists($options['callback'])) {
$function = $options['callback'];
$function($replacements, $data, $options);
}
// Actually apply the replacements.
$tokens = array_keys($replacements);
$values = array_values($replacements);
if (is_array($text)) {
foreach ($text as $i => $text_item) {
$text[$i] = str_replace($tokens, $values, $text_item);
}
return $text;
}
return str_replace($tokens, $values, $text);
}
/**
* Create documentation about the available replacement patterns.
*
* @param array $var_info
* Array with the available variables.
*
* @return array
* Renderable array with the replacement pattern documentation.
*/
public static function help($var_info) {
$render = array(
'#type' => 'fieldset',
'#title' => t('Replacement patterns'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t('Note that token replacements containing chained objects such as [node:author:uid] are not listed here, but are still available. The <em>data selection</em> input mode may help you find more complex replacement patterns. See <a href="@url">the online documentation</a> for more information about complex replacement patterns.',
array('@url' => rules_external_help('chained-tokens'))),
);
$token_info = token_info();
foreach ($var_info as $name => $info) {
$token_types[$name] = _rules_system_token_map_type($info['type']);
}
foreach ($token_types as $name => $token_type) {
if (isset($token_info['types'][$token_type])) {
$render[$name] = array(
'#theme' => 'table',
'#header' => array(t('Token'), t('Label'), t('Description')),
'#prefix' => '<h3>' . t('Replacement patterns for %label', array('%label' => $var_info[$name]['label'])) . '</h3>',
);
foreach ($token_info['tokens'][$token_type] as $token => $info) {
$token = '[' . str_replace('_', '-', $name) . ':' . $token . ']';
$render[$name]['#rows'][$token] = array(
check_plain($token),
check_plain($info['name']),
check_plain($info['description']),
);
}
}
}
return $render;
}
}
/**
* Looks for a token type mapping. Defaults to passing through the type.
*/
function _rules_system_token_map_type($type) {
$entity_info = entity_get_info();
if (isset($entity_info[$type]['token type'])) {
return $entity_info[$type]['token type'];
}
$cache = rules_get_cache();
if (isset($cache['data_info'][$type]['token type'])) {
return $cache['data_info'][$type]['token type'];
}
return $type;
}
/**
* @}
*/

View File

@@ -0,0 +1,288 @@
<?php
/**
* @file rules integration for the system module
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the system module.
*/
function rules_system_file_info() {
return array('modules/system.eval');
}
/**
* Implements hook_rules_event_info() on behalf of the system module.
*/
function rules_system_event_info() {
return array(
'init' => array(
'label' => t('Drupal is initializing'),
'group' => t('System'),
'help' => t("Be aware that some actions might initialize the theme system. After that, it's impossible for any module to change the used theme."),
'access callback' => 'rules_system_integration_access',
),
'cron' => array(
'label' => t('Cron maintenance tasks are performed'),
'group' => t('System'),
'access callback' => 'rules_system_integration_access',
),
'watchdog' => array(
'label' => t('System log entry is created'),
'variables' => array(
'log_entry' => array(
'type' => 'log_entry',
'label' => t('Log entry'),
),
),
'group' => t('System'),
'access callback' => 'rules_system_integration_access',
),
);
}
/**
* Implements hook_rules_data_info() on behalf of the system module.
* @see rules_core_modules()
*/
function rules_system_data_info() {
return array(
'log_entry' => array(
'label' => t('watchdog log entry'),
'wrap' => TRUE,
'property info' => _rules_system_watchdog_log_entry_info(),
),
);
}
/**
* Defines property info for watchdog log entries, used by the log entry data
* type to provide an useful metadata wrapper.
*/
function _rules_system_watchdog_log_entry_info() {
return array(
'type' => array(
'type' => 'text',
'label' => t('The category to which this message belongs'),
),
'message' => array(
'type' => 'text',
'label' => ('Log message'),
'getter callback' => 'rules_system_log_get_message',
'sanitized' => TRUE,
),
'severity' => array(
'type' => 'integer',
'label' => t('Severity'),
'options list' => 'watchdog_severity_levels',
),
'request_uri' => array(
'type' => 'uri',
'label' => t('Request uri'),
),
'link' => array(
'type' => 'text',
'label' => t('An associated, HTML formatted link'),
),
);
}
/**
* Implements hook_rules_action_info() on behalf of the system module.
*/
function rules_system_action_info() {
return array(
'drupal_message' => array(
'label' => t('Show a message on the site'),
'group' => t('System'),
'parameter' => array(
'message' => array(
'type' => 'text',
'label' => t('Message'),
'sanitize' => TRUE,
'translatable' => TRUE,
),
'type' => array(
'type' => 'token',
'label' => t('Message type'),
'options list' => 'rules_action_drupal_message_types',
'default value' => 'status',
'optional' => TRUE,
),
'repeat' => array(
'type' => 'boolean',
'label' => t('Repeat message'),
'description' => t("If disabled and the message has been already shown, then the message won't be repeated."),
'default value' => TRUE,
'optional' => TRUE,
'restriction' => 'input',
),
),
'base' => 'rules_action_drupal_message',
'access callback' => 'rules_system_integration_access',
),
'redirect' => array(
'label' => t('Page redirect'),
'group' => t('System'),
'parameter' => array(
'url' => array(
'type' => 'uri',
'label' => t('URL'),
'description' => t('A Drupal path, path alias, or external URL to redirect to. Enter (optional) queries after "?" and (optional) anchor after "#".'),
),
'force' => array(
'type' => 'boolean',
'label' => t('Force redirect'),
'restriction' => 'input',
'description' => t("Force the redirect even if another destination parameter is present. Per default Drupal would redirect to the path given as destination parameter, in case it is set. Usually the destination parameter is set by appending it to the URL, e.g. !example_url", array('!example_url' => 'http://example.com/user/login?destination=node/2')),
'optional' => TRUE,
'default value' => TRUE,
),
'destination' => array(
'type' => 'boolean',
'label' => t('Append destination parameter'),
'restriction' => 'input',
'description' => t('Whether to append a destination parameter to the URL, so another redirect issued later on would lead back to the origin page.'),
'optional' => TRUE,
'default value' => FALSE,
),
),
'base' => 'rules_action_drupal_goto',
'access callback' => 'rules_system_integration_access',
),
'breadcrumb_set' => array(
'label' => t('Set breadcrumb'),
'group' => t('System'),
'parameter' => array(
'titles' => array(
'type' => 'list<text>',
'label' => t('Titles'),
'description' => t('A list of titles for the breadcrumb links.'),
'translatable' => TRUE,
),
'paths' => array(
'type' => 'list<text>',
'label' => t('Paths'),
'description' => t('A list of Drupal paths for the breadcrumb links, matching the order of the titles.'),
),
),
'base' => 'rules_action_breadcrumb_set',
'access callback' => 'rules_system_integration_access',
),
'mail' => array(
'label' => t('Send mail'),
'group' => t('System'),
'parameter' => array(
'to' => array(
'type' => 'text',
'label' => t('To'),
'description' => t('The e-mail address or addresses where the message will be sent to. The formatting of this string must comply with RFC 2822.'),
),
'subject' => array(
'type' => 'text',
'label' => t('Subject'),
'description' => t("The mail's subject."),
'translatable' => TRUE,
),
'message' => array(
'type' => 'text',
'label' => t('Message'),
'description' => t("The mail's message body."),
'translatable' => TRUE,
),
'from' => array(
'type' => 'text',
'label' => t('From'),
'description' => t("The mail's from address. Leave it empty to use the site-wide configured address."),
'optional' => TRUE,
),
'language' => array(
'type' => 'token',
'label' => t('Language'),
'description' => t('If specified, the language used for getting the mail message and subject.'),
'options list' => 'entity_metadata_language_list',
'optional' => TRUE,
'default value' => LANGUAGE_NONE,
'default mode' => 'selector',
),
),
'base' => 'rules_action_mail',
'access callback' => 'rules_system_integration_access',
),
'mail_to_users_of_role' => array(
'label' => t('Send mail to all users of a role'),
'group' => t('System'),
'parameter' => array(
'roles' => array(
'type' => 'list<integer>',
'label' => t('Roles'),
'options list' => 'entity_metadata_user_roles',
'description' => t('Select the roles whose users should receive the mail.'),
),
'subject' => array(
'type' => 'text',
'label' => t('Subject'),
'description' => t("The mail's subject."),
),
'message' => array(
'type' => 'text',
'label' => t('Message'),
'description' => t("The mail's message body."),
),
'from' => array(
'type' => 'text',
'label' => t('From'),
'description' => t("The mail's from address. Leave it empty to use the site-wide configured address."),
'optional' => TRUE,
),
),
'base' => 'rules_action_mail_to_users_of_role',
'access callback' => 'rules_system_integration_access',
),
);
}
/**
* Help callback for the "Send mail to users of a role" action.
*/
function rules_action_mail_to_users_of_role_help() {
return t('WARNING: This may cause problems if there are too many users of these roles on your site, as your server may not be able to handle all the mail requests all at once.');
}
/**
* System integration access callback.
*/
function rules_system_integration_access($type, $name) {
return user_access('administer site configuration');
}
/**
* Options list callback defining drupal_message types.
*/
function rules_action_drupal_message_types() {
return array(
'status' => t('Status'),
'warning' => t('Warning'),
'error' => t('Error'),
);
}
/**
* Implements hook_rules_evaluator_info() on behalf of the system module.
*/
function rules_system_evaluator_info() {
return array(
'token' => array(
'class' => 'RulesTokenEvaluator',
'type' => array('text', 'uri', 'list<text>', 'list<uri>'),
'weight' => 0,
),
);
}
/**
* @}
*/

View File

@@ -0,0 +1,100 @@
<?php
/**
* @file rules integration for the taxonomy_term module
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function rules_taxonomy_event_info() {
$defaults_term = array(
'group' => t('Taxonomy'),
'access callback' => 'rules_taxonomy_term_integration_access',
'module' => 'taxonomy',
);
$defaults_vocab = array(
'group' => t('Taxonomy'),
'access callback' => 'rules_taxonomy_vocabulary_integration_access',
'module' => 'taxonomy',
);
return array(
'taxonomy_term_insert' => $defaults_term + array(
'label' => t('After saving a new term'),
'variables' => array(
'term' => array('type' => 'taxonomy_term', 'label' => t('created term')),
),
),
'taxonomy_term_update' => $defaults_term + array(
'label' => t('After updating an existing term'),
'variables' => array(
'term' => array('type' => 'taxonomy_term', 'label' => t('updated term')),
'term_unchanged' => array('type' => 'taxonomy_term', 'label' => t('unchanged term'), 'handler' => 'rules_events_entity_unchanged'),
),
),
'taxonomy_term_presave' => $defaults_term + array(
'label' => t('Before saving a taxonomy term'),
'variables' => array(
'term' => array('type' => 'taxonomy_term', 'label' => t('saved term'), 'skip save' => TRUE),
'term_unchanged' => array('type' => 'taxonomy_term', 'label' => t('unchanged term'), 'handler' => 'rules_events_entity_unchanged'),
),
),
'taxonomy_term_delete' => $defaults_term + array(
'label' => t('After deleting a term'),
'variables' => array(
'term' => array('type' => 'taxonomy_term', 'label' => t('deleted term')),
),
),
'taxonomy_vocabulary_insert' => $defaults_vocab + array(
'label' => t('After saving a new vocabulary'),
'variables' => array(
'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('created vocabulary')),
),
),
'taxonomy_vocabulary_update' => $defaults_vocab + array(
'label' => t('After updating an existing vocabulary'),
'variables' => array(
'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('updated vocabulary')),
'vocabulary_unchanged' => array('type' => 'taxonomy_vocabulary', 'label' => t('unchanged vocabulary'), 'handler' => 'rules_events_entity_unchanged'),
),
),
'taxonomy_vocabulary_presave' => $defaults_vocab + array(
'label' => t('Before saving a vocabulary'),
'variables' => array(
'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('saved vocabulary'), 'skip save' => TRUE),
'vocabulary_unchanged' => array('type' => 'taxonomy_vocabulary', 'label' => t('unchanged vocabulary'), 'handler' => 'rules_events_entity_unchanged'),
),
),
'taxonomy_vocabulary_delete' => $defaults_vocab + array(
'label' => t('After deleting a vocabulary'),
'variables' => array(
'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('deleted vocabulary')),
),
),
);
}
/**
* Taxonomy term integration access callback.
*/
function rules_taxonomy_term_integration_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return entity_access('view', 'taxonomy_term');
}
}
/**
* Taxonomy vocabulary integration access callback.
*/
function rules_taxonomy_vocabulary_integration_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return entity_access('view', 'taxonomy_vocabulary');
}
}
/**
* @}
*/

View File

@@ -0,0 +1,102 @@
<?php
/**
* @file
* Contains rules integration for the user module needed during evaluation.
*
* @addtogroup rules
* @{
*/
/**
* Condition user: condition to check whether user has particular roles
*/
function rules_condition_user_has_role($account, $roles, $operation = 'AND') {
switch ($operation) {
case 'OR':
foreach ($roles as $rid) {
if (isset($account->roles[$rid])) {
return TRUE;
}
}
return FALSE;
case 'AND':
foreach ($roles as $rid) {
if (!isset($account->roles[$rid])) {
return FALSE;
}
}
return TRUE;
}
}
/**
* Condition: User is blocked.
*/
function rules_condition_user_is_blocked($account) {
return $account->status == 0;
}
/**
* Action: Adds roles to a particular user.
*/
function rules_action_user_add_role($account, $roles) {
if ($account->uid || !empty($account->is_new)) {
// Get role list (minus the anonymous)
$role_list = user_roles(TRUE);
foreach ($roles as $rid) {
$account->roles[$rid] = $role_list[$rid];
}
if (!empty($account->is_new) && $account->uid) {
// user_save() inserts roles after invoking hook_user_insert() anyway, so
// we skip saving to avoid errors due saving them twice.
return FALSE;
}
}
else {
return FALSE;
}
}
/**
* Action: Remove roles from a given user.
*/
function rules_action_user_remove_role($account, $roles) {
if ($account->uid || !empty($account->is_new)) {
foreach ($roles as $rid) {
// If the user has this role, remove it.
if (isset($account->roles[$rid])) {
unset($account->roles[$rid]);
}
}
if (!empty($account->is_new) && $account->uid) {
// user_save() inserts roles after invoking hook_user_insert() anyway, so
// we skip saving to avoid errors due saving them twice.
return FALSE;
}
}
else {
return FALSE;
}
}
/**
* Action: Block a user.
*/
function rules_action_user_block($account) {
$account->status = 0;
drupal_session_destroy_uid($account->uid);
}
/**
* Action: Unblock a user.
*/
function rules_action_user_unblock($account) {
$account->status = 1;
}
/**
* @}
*/

View File

@@ -0,0 +1,236 @@
<?php
/**
* @file rules integration for the user module
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_file_info() on behalf of the user module.
*/
function rules_user_file_info() {
return array('modules/user.eval');
}
/**
* Implementation of hook_rules_event_info().
*/
function rules_user_event_info() {
return array(
'user_insert' => array(
'label' => t('After saving a new user account'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('registered user')),
),
'access callback' => 'rules_user_integration_access',
),
'user_update' => array(
'label' => t('After updating an existing user account'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('updated user')),
'account_unchanged' => array('type' => 'user', 'label' => t('unchanged user'), 'handler' => 'rules_events_entity_unchanged'),
),
'access callback' => 'rules_user_integration_access',
),
'user_presave' => array(
'label' => t('Before saving a user account'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('saved user'), 'skip save' => TRUE),
'account_unchanged' => array('type' => 'user', 'label' => t('unchanged user'), 'handler' => 'rules_events_entity_unchanged'),
),
'access callback' => 'rules_user_integration_access',
),
'user_view' => array(
'label' => t('User account page is viewed'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('viewed user')),
'view_mode' => array(
'type' => 'text',
'label' => t('view mode'),
'options list' => 'rules_get_entity_view_modes',
),
),
'access callback' => 'rules_user_integration_access',
'help' => t("Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache."),
),
'user_delete' => array(
'label' => t('After a user account has been deleted'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('deleted user')),
),
'access callback' => 'rules_user_integration_access',
),
'user_login' => array(
'label' => t('User has logged in'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('logged in user')),
),
'access callback' => 'rules_user_integration_access',
),
'user_logout' => array(
'label' => t('User has logged out'),
'group' => t('User'),
'variables' => array(
'account' => array('type' => 'user', 'label' => t('logged out user')),
),
'access callback' => 'rules_user_integration_access',
),
);
}
/**
* Options list for user cancel methods.
* @todo: Use for providing a user_cancel action.
*/
function rules_user_cancel_methods() {
module_load_include('inc', 'user', 'user.pages');
foreach (user_cancel_methods() as $method => $form) {
$methods[$method] = $form['#title'];
}
return $methods;
}
/**
* User integration access callback.
*/
function rules_user_integration_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return entity_access('view', 'user');
}
// Else return admin access.
return user_access('administer users');
}
/**
* Implements hook_rules_condition_info() on behalf of the user module.
*/
function rules_user_condition_info() {
return array(
'user_has_role' => array(
'label' => t('User has role(s)'),
'parameter' => array(
'account' => array('type' => 'user', 'label' => t('User')),
'roles' => array(
'type' => 'list<integer>',
'label' => t('Roles'),
'options list' => 'rules_user_roles_options_list',
),
'operation' => array(
'type' => 'text',
'label' => t('Match roles'),
'options list' => 'rules_user_condition_operations',
'restriction' => 'input',
'optional' => TRUE,
'default value' => 'AND',
'description' => t('If matching against all selected roles, the user must have <em>all</em> the roles selected.'),
),
),
'group' => t('User'),
'access callback' => 'rules_user_integration_access',
'base' => 'rules_condition_user_has_role',
),
'user_is_blocked' => array(
'label' => t('User is blocked'),
'parameter' => array(
'account' => array('type' => 'user', 'label' => t('User')),
),
'group' => t('User'),
'access callback' => 'rules_user_integration_access',
'base' => 'rules_condition_user_is_blocked',
),
);
}
/**
* User has role condition help callback.
*/
function rules_condition_user_has_role_help() {
return t('Whether the user has the selected role(s).');
}
/**
* Options list callback for the operation parameter of condition user has role.
*/
function rules_user_condition_operations() {
return array(
'AND' => t('all'),
'OR' => t('any'),
);
}
/**
* Implements hook_rules_action_info() on behalf of the user module.
*/
function rules_user_action_info() {
$defaults = array(
'parameter' => array(
'account' => array(
'type' => 'user',
'label' => t('User'),
'description' => t('The user whose roles should be changed.'),
'save' => TRUE,
),
'roles' => array(
'type' => 'list<integer>',
'label' => t('Roles'),
'options list' => 'rules_user_roles_options_list',
),
),
'group' => t('User'),
'access callback' => 'rules_user_role_change_access',
);
$items['user_add_role'] = $defaults + array(
'label' => t('Add user role'),
'base' => 'rules_action_user_add_role',
);
$items['user_remove_role'] = $defaults + array(
'label' => t('Remove user role'),
'base' => 'rules_action_user_remove_role',
);
$defaults = array(
'parameter' => array(
'account' => array(
'type' => 'user',
'label' => t('User'),
'save' => TRUE,
),
),
'group' => t('User'),
'access callback' => 'rules_user_integration_access',
);
$items['user_block'] = $defaults + array(
'label' => t('Block a user'),
'base' => 'rules_action_user_block',
);
$items['user_unblock'] = $defaults + array(
'label' => t('Unblock a user'),
'base' => 'rules_action_user_unblock',
);
return $items;
}
/**
* User integration role actions access callback.
*/
function rules_user_role_change_access() {
return entity_metadata_user_roles() && user_access('administer permissions');
}
/**
* Options list callback for user roles.
*/
function rules_user_roles_options_list($element) {
return entity_metadata_user_roles('roles', array(), $element instanceof RulesConditionInterface ? 'view' : 'edit');
}
/**
* @}
*/