FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

@@ -0,0 +1,195 @@
// Registers the rules namespace.
Drupal.rules = Drupal.rules || {};
(function($) {
Drupal.behaviors.rules_autocomplete = {
attach: function(context) {
var autocomplete_settings = Drupal.settings.rules_autocomplete;
$('input.rules-autocomplete').once(function() {
var input = this;
new Drupal.rules.autocomplete(input, autocomplete_settings[$(input).attr('id')]);
});
}
};
/**
* Rules autocomplete object.
*/
Drupal.rules.autocomplete = function(input, settings) {
this.id = settings.inputId;
this.uri = settings.source;
this.jqObject = $('#' + this.id);
this.cache = new Array();
this.jqObject.addClass('ui-corner-left');
this.opendByFocus = false;
this.focusOpens = true;
this.groupSelected = false;
this.button = $('<span>&nbsp;</span>');
this.button.attr( {
'tabIndex': -1,
'title': 'Show all items'
});
this.button.insertAfter(this.jqObject);
this.button.button( {
icons: {
primary: 'ui-icon-triangle-1-s'
},
text: false
});
// Don't round the left corners.
this.button.removeClass('ui-corner-all');
this.button.addClass('ui-corner-right ui-button-icon rules-autocomplete-button');
this.jqObject.autocomplete();
this.jqObject.autocomplete("option", "minLength", 0);
// Add a custom class, so we can style the autocomplete box without
// interfering with other jquery autocomplete widgets.
this.jqObject.autocomplete("widget").addClass('rules-autocomplete');
// Save the current rules_autocomplete object, so it can be used in
// handlers.
var instance = this;
// Event handlers
this.jqObject.focus(function() {
if (instance.focusOpens) {
instance.toggle();
instance.opendByFocus = true;
}
else {
instance.focusOpens = true;
}
});
// Needed when the window is closed but the textfield has the focus.
this.jqObject.click(function() {
// Since the focus event happens earlier then the focus event, we need to
// check here, if the window should be opened.
if (!instance.opendByFocus) {
instance.toggle();
}
else {
instance.opendByFocus = false;
}
});
this.jqObject.bind("autocompleteselect", function(event, ui) {
// If a group was selected then set the groupSelected to true for the
// overriden close function from jquery autocomplete.
if (ui.item.value.substring(ui.item.value.length - 1, ui.item.value.length) == ":") {
instance.groupSelected = true;
}
instance.focusOpens = false;
instance.opendByFocus = false;
});
this.jqObject.autocomplete("option", "source", function(request, response) {
if (request.term in instance.cache) {
response(instance.cache[request.term]);
return;
}
$.ajax( {
url: instance.uri + '/' + request.term,
dataType: "json",
success: function(data) {
instance.success(data, request, response);
}
});
});
// Since jquery autocomplete by default strips html text by using .text()
// we need our own _renderItem function to display html content.
this.jqObject.data("autocomplete")._renderItem = function(ul, item) {
return $("<li></li>").data("item.autocomplete", item).append("<a>" + item.label + "</a>").appendTo(ul);
};
// Override close function
this.jqObject.data("autocomplete").close = function (event) {
var value = this.element.val();
// If the selector is not a group, then trigger the close event an and
// hide the menu.
if (value === undefined || instance.groupSelected === false) {
clearTimeout(this.closing);
if (this.menu.element.is(":visible")) {
this._trigger("close", event);
this.menu.element.hide();
this.menu.deactivate();
}
}
else {
// Else keep all open and trigger a search for the group.
instance.jqObject.autocomplete("search", instance.jqObject.val());
// After the suggestion box was opened again, we want to be able to
// close it.
instance.groupSelected = false;
}
};
this.button.click(function() {
instance.toggle();
});
};
/**
* Success function for Rules autocomplete object.
*/
Drupal.rules.autocomplete.prototype.success = function(data, request, response) {
var list = new Array();
jQuery.each(data, function(index, value) {
list.push( {
label: value,
value: index
});
});
this.cache[request.term] = list;
response(list);
};
/**
* Open the autocomplete window.
* @param searchFor The term for will be searched for. If undefined then the
* entered input text will be used.
*/
Drupal.rules.autocomplete.prototype.open = function(searchFor) {
// If searchFor is undefined, we want to search for the passed argument.
this.jqObject.autocomplete("search", ((searchFor === undefined) ? this.jqObject.val() : searchFor));
this.button.addClass("ui-state-focus");
};
/**
* Close the autocomplete window.
*/
Drupal.rules.autocomplete.prototype.close = function() {
this.jqObject.autocomplete("close");
this.button.removeClass("ui-state-focus");
};
/**
* Toogle the autcomplete window.
*/
Drupal.rules.autocomplete.prototype.toggle = function() {
if (this.jqObject.autocomplete("widget").is(":visible")) {
this.close();
this.focusOpens = true;
}
else {
var groups = this.jqObject.val().split(":");
var selector = "";
for (var i=0; i<groups.length-1; i++) {
selector = selector.concat(groups[i]) + ":";
}
this.focusOpens = false;
this.jqObject.focus();
this.open(selector);
}
};
})(jQuery);

View File

@@ -0,0 +1,68 @@
/**
* @file
* Adds the collapsible functionality to the rules debug log.
*/
// Registers the rules namespace.
Drupal.rules = Drupal.rules || {};
(function($) {
Drupal.behaviors.rules_debug_log = {
attach: function(context) {
$('.rules-debug-open').click(function () {
var icon = $(this).children('span.ui-icon');
if ($(this).next().is(':hidden')) {
Drupal.rules.changeDebugIcon(icon, true);
}
else {
Drupal.rules.changeDebugIcon(icon, false);
}
$(this).next().toggle();
}).next().hide();
$('.rules-debug-open-main').click(function () {
var icon = $(this).children('span.ui-icon');
if ($(this).parent().next().is(':hidden')) {
Drupal.rules.changeDebugIcon(icon, true);
$(this).parent().children('.rules-debug-open-all').text(Drupal.t('-Close all-'));
}
else {
Drupal.rules.changeDebugIcon(icon, false);
$(this).parent().children('.rules-debug-open-all').text(Drupal.t('-Open all-'));
}
$(this).parent().next().toggle();
}).parent().next().hide();
$('.rules-debug-open-all').click(function() {
if ($('.rules-debug-open-main').parent().next().is(':hidden')) {
$('.rules-debug-open').next().show();
Drupal.rules.changeDebugIcon($('.rules-debug-open').children('span.ui-icon'), true);
$('.rules-debug-open-main').parent().next().show();
Drupal.rules.changeDebugIcon($(this).prev().children('span.ui-icon'), true);
$(this).text(Drupal.t('-Close all-'));
}
else {
$('.rules-debug-open-main').parent().next().hide();
Drupal.rules.changeDebugIcon($('.rules-debug-open-main').children('span.ui-icon'), false);
$(this).text(Drupal.t('-Open all-'));
$('.rules-debug-open').next().hide();
Drupal.rules.changeDebugIcon($(this).prev().children('span.ui-icon'), false);
}
});
}
};
/**
* Changes the icon of a collapsible div.
*/
Drupal.rules.changeDebugIcon = function(item, open) {
if (open == true) {
item.removeClass('ui-icon-triangle-1-e');
item.addClass('ui-icon-triangle-1-s');
}
else {
item.removeClass('ui-icon-triangle-1-s');
item.addClass('ui-icon-triangle-1-e');
}
}
})(jQuery);

View File

@@ -0,0 +1,196 @@
@CHARSET "UTF-8";
.rules-show-js, html.js .rules-hide-js {
display: none;
}
.rules-hide-js, html.js .rules-show-js {
display: block;
}
.rules-elements-table ul.action-links {
margin: 0px;
padding: 0;
}
.rules-elements-table ul.rules-operations li {
list-style: none;
float: left;
}
.rules-elements-table ul.rules-operations a {
background: none;
padding-left: 0px;
}
table tr.rules-elements-add {
background-color: #e5eff4;
}
.rules-elements-table ul.rules-operations-add a {
line-height: 1em;
}
tr.rules-elements-add td {
padding-top: 2px;
padding-bottom: 2px;
}
ul.rules-operations-add li {
float: left;
list-style-position: inside;
}
.rules-elements-table {
margin-bottom: 3em;
}
/* We cannot set a positive margin-top for rules tables as the table drag link
should be positioned directly on top of the table. Thus we use a large bottom
margin and fix the upper most margin: */
#rules-form-wrapper:first-child {
margin-top: 1.5em;
}
/* Fix table drag weights to don't take extra space */
.rules-elements-table .tabledrag-toggle-weight-wrapper {
position: absolute;
right: 0px;
}
.rules-elements-table caption, .rules-overview-table caption {
font-size: 110%;
font-weight: bold;
padding-bottom: 0.5em;
text-align: left;
}
.rules-overview-table {
margin: 1em 0;
}
.rules-content-group-integrity-error {
color: #df0101;
}
.rules-debug-log {
font: 81.3% "Lucida Grande","Lucida Sans Unicode",sans-serif;
background-color: #eeeeee;
border: 1px solid #cccccc;
color: #333333;
padding: 5px;
margin: 1.5em 0em;
}
.rules-debug-collapsible-link {
position: relative;
cursor: pointer;
/* The span element with the icon which opens the log, has a whitepsace.
Since we don't want the user to mark this white space, we prevent this
using the this code.*/
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
user-select: none;
}
.rules-debug-log-head {
font-weight: bold;
}
div.rules-debug-log-head {
margin: 0.5em 0em;
}
.rules-debug-icon-open {
position: relative;
float: left;
}
.rules-debug-open-all {
position: relative;
float: right;
}
.rules-debug-log ul {
padding-left: 2em;
}
.rules-debug-log .rules-debug-warn {
color: #df0101;
}
.rules-debug-log .rules-debug-error {
font-weight: bold;
color: #df0101;
}
#rules-filter-form {
margin-bottom: 1.5em;
}
.rules-parameter-label {
font-style: italic;
}
#rules-plugin-add-help {
margin-bottom: 1em;
}
.rules-element-content {
float: left;
}
form input.rules-switch-button {
-moz-border-radius: 5px 5px 5px 5px;
cursor: pointer;
font-size: 0.8em;
font-weight: normal;
margin-bottom: 1em;
padding: 2px;
text-align: center;
}
.rules-form-heading {
margin-top: 3em;
}
.rules-autocomplete-button {
top: 3px;
height: 22px;
}
ul.rules-autocomplete {
max-height: 23em;
overflow-y: auto;
}
ul.rules-autocomplete div {
padding-left: 5px;
}
ul.rules-autocomplete a.ui-corner-all {
padding: 0px;
}
ul.rules-autocomplete .rules-dsac-group {
background-color: #eee;
}
ul.rules-autocomplete .ui-corner-all {
-moz-border-radius: 0px;
}
/**
* Do not display the hide/show descriptions link above the permissions matrix.
*/
#rules-form-wrapper #edit-settings-access-permissions .compact-link {
display: none;
}
/* IE 6 hack for max-height. */
* html ul.rule-autocomplete{
height: 23em;
}

View File

@@ -0,0 +1,95 @@
/**
* JQuery UI style sheet fix for the seven theme.
*/
.ui-button {
border: 1px solid #cccccc;
background: #e6e6e6;
}
.ui-state-hover,
.ui-state-focus {
border: 1px solid #bbbbbb;
}
.ui-button.ui-state-active {
border: 1px solid #777777;
font-weight: bold;
}
/**
* Corner radius
*/
.ui-corner-tl {
-moz-border-radius-topleft: 4px;
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
}
.ui-corner-tr {
-moz-border-radius-topright: 4px;
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
}
.ui-corner-bl {
-moz-border-radius-bottomleft: 4px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ui-corner-br {
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.ui-corner-top {
-moz-border-radius-topleft: 4px;
-moz-border-radius-topright: 4px;
-webkit-border-top-left-radius: 4px;
-webkit-border-top-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.ui-corner-bottom {
-moz-border-radius-bottomleft: 4px;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-left-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.ui-corner-right {
-moz-border-radius-bottomright: 4px;
-moz-border-radius-topright: 4px;
-webkit-border-bottom-right-radius: 4px;
-webkit-border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
}
.ui-corner-left {
-moz-border-radius-bottomleft: 4px;
-moz-border-radius-topleft: 4px;
-webkit-border-bottom-left-radius: 4px;
-webkit-border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
}
.ui-corner-all {
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
}
/**
* Fix the position of the core-autocomplete popup when shown in the settings
* fieldset.
*/
.form-item-settings-tags {
position: relative;
}

View File

@@ -0,0 +1,316 @@
<?php
/**
* @file Contains the UI controller for Rules.
*/
/**
* Controller class for the Rules UI.
*
* The Rules UI controller defines the methods other modules may use in order
* to easily re-use the UI, regardless whether the rules admin module is
* enabled.
*/
class RulesUIController {
/**
* Generates menu items to manipulate rules configurations.
*
* @param $base_path
* The path to the overview page from where the configurations are edited.
*/
public function config_menu($base_path) {
$items = array();
$base_count = count(explode('/', $base_path));
$items[$base_path . '/manage/%rules_config'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Editing !plugin "!label"', $base_count + 1),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_form_edit_rules_config', $base_count + 1, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'type' => MENU_VISIBLE_IN_BREADCRUMB,
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/add/%rules_element'] = array(
// Adding another part to the path would hit the menu path-part-limit
// for base paths like admin/config/workflow/rules. Therefor we have to
// use this fugly way for setting the title.
'title callback' => 'rules_menu_add_element_title',
// Wrap the integer in an array, so it is passed as is.
'title arguments' => array(array($base_count + 4)),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_add_element', $base_count + 1, $base_count + 4, $base_count + 3, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'load arguments' => array($base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/add/event'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Adding event to !plugin "!label"', $base_count + 1),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_add_event', $base_count + 1, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'load arguments' => array($base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/delete/event'] = array(
//@todo: improve title.
'title' => 'Remove event',
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_remove_event', $base_count + 1, $base_count + 4, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'description' => 'Remove an event from a reaction rule.',
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/edit/%rules_element'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Editing !plugin "!label"', $base_count + 3),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_edit_element', $base_count + 1, $base_count + 3, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'load arguments' => array($base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/autocomplete'] = array(
'page callback' => 'rules_ui_form_data_selection_auto_completion',
'page arguments' => array($base_count + 3, $base_count + 4, $base_count + 5),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'type' => MENU_CALLBACK,
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/delete/%rules_element'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Editing !plugin "!label"', $base_count + 3),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_delete_element', $base_count + 1, $base_count + 3, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'load arguments' => array($base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/%'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_form_rules_config_confirm_op', $base_count + 1, $base_count + 2, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/clone'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Cloning !plugin "!label"', $base_count + 1),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_form_clone_rules_config', $base_count + 1, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/export'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Export of !plugin "!label"', $base_count + 1),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_form_export_rules_config', $base_count + 1, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('view', $base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
$items[$base_path . '/manage/%rules_config/execute'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Executing !plugin "!label"', $base_count + 1),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_ui_form_execute_rules_config', $base_count + 1, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'file' => 'ui/ui.forms.inc',
'file path' => drupal_get_path('module', 'rules'),
);
drupal_alter('rules_ui_menu', $items, $base_path, $base_count);
if (module_exists('rules_scheduler')) {
$items[$base_path . '/manage/%rules_config/schedule'] = array(
'title callback' => 'rules_get_title',
'title arguments' => array('Schedule !plugin "!label"', $base_count + 1),
'page callback' => 'drupal_get_form',
'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path),
'access callback' => 'rules_config_access',
'access arguments' => array('update', $base_count + 1),
'file' => 'rules_scheduler.admin.inc',
'file path' => drupal_get_path('module', 'rules_scheduler'),
);
}
return $items;
}
/**
* Generates the render array for a overview configuration table for arbitrary
* rule configs that match the given conditions.
*
* Note: The generated overview table contains multiple links for editing the
* rule configurations. For the links to properly work use
* RulesUIController::config_menu($base_path) to generate appropriate menu
* items for the path at which the overview table is displayed.
*
* @param $conditions
* An array of conditions as needed by rules_config_load_multiple().
* @param $options
* An array with optional options. Known keys are:
* - 'hide status op': If set to TRUE, enable/disable links are not added.
* Defaults to FALSE.
* - 'show plugin': If set to FALSE, the plugin is not shown. Defaults to
* TRUE.
* - 'show events': If set to TRUE, the event column is shown. Defaults to
* TRUE if only reaction rules are listed.
* - 'show execution op': If set to TRUE an operation for execution a
* component is shown for components, as well as a link to schedule a
* component if the rules scheduler module is enabled.
* - 'base path': Optionally, a different base path to use instead of the
* currently set RulesPluginUI::$basePath. If no base path has been set
* yet, the current path is used by default.
*
* @return Array
* A renderable array.
*/
public function overviewTable($conditions = array(), $options = array()) {
$options += array(
'hide status op' => FALSE,
'show plugin' => TRUE,
'show events' => isset($conditions['plugin']) && $conditions['plugin'] == 'reaction rule',
'show execution op' => !(isset($conditions['plugin']) && $conditions['plugin'] == 'reaction rule'),
);
if (!empty($options['base path'])) {
RulesPluginUI::$basePath = $options['base path'];
}
else if (!isset(RulesPluginUI::$basePath)) {
// Default to the current path, only if no path has been set yet.
RulesPluginUI::$basePath = current_path();
}
$entities = entity_load('rules_config', FALSE, $conditions);
ksort($entities);
// Prepare some variables used by overviewTableRow().
$this->event_info = rules_fetch_data('event_info');
$this->cache = rules_get_cache();
$rows = array();
foreach ($entities as $id => $entity) {
if (user_access('bypass rules access') || $entity->access()) {
$rows[] = $this->overviewTableRow($conditions, $id, $entity, $options);
}
}
// Assemble the right table header.
$header = array(t('Name'), t('Event'), t('Plugin'), t('Status'), array('data' => t('Operations')));
if (!$options['show events']) {
// Remove the event heading as there is no such column.
unset($header[1]);
}
if (!$options['show plugin']) {
unset($header[2]);
}
// Fix the header operation column colspan.
$num_cols = isset($rows[0]) ? count($rows[0]) : 0;
if (($addition = $num_cols - count($header)) > 0) {
$header[4]['colspan'] = $addition + 1;
}
$table = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('None.'),
);
$table['#attributes']['class'][] = 'rules-overview-table';
$table['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css';
// TODO: hide configs where access() is FALSE.
return $table;
}
/**
* Generates the row for a single rules config.
*
* @param $additional_cols
* Additional columns to be added after the entity label column.
*/
protected function overviewTableRow($conditions, $name, $config, $options) {
// Build content includes the label, as well as a short overview including
// the machine name.
$row[] = array('data' => $config->buildContent());
// Add events if the configs are assigned to events.
if ($options['show events']) {
$events = array();
if ($config instanceof RulesTriggerableInterface) {
foreach ($config->events() as $event_name) {
$this->event_info += array($event_name => array('label' => t('Unknown event "!event_name"', array('!event_name' => $event_name))));
$events[] = check_plain($this->event_info[$event_name]['label']);
}
}
$row[] = implode(", ", $events);
}
if ($options['show plugin']) {
$plugin = $config->plugin();
$row[] = isset($this->cache['plugin_info'][$plugin]['label']) ? $this->cache['plugin_info'][$plugin]['label'] : $plugin;
}
$row[] = array('data' => array(
'#theme' => 'entity_status',
'#status' => $config->status,
));
// Add operations depending on the options and the exportable status.
if (!$config->hasStatus(ENTITY_FIXED)) {
$row[] = l(t('edit'), RulesPluginUI::path($name), array('attributes' => array('class' => array('edit', 'action'))));
$row[] = l(t('translate'), RulesPluginUI::path($name, 'translate'), array('attributes' => array('class' => array('translate', 'action'))));
}
else {
$row[] = '';
$row[] = '';
}
if (!$options['hide status op']) {
// Add either an enable or disable link.
$text = $config->active ? t('disable') : t('enable');
$active_class = $config->active ? 'disable' : 'enable';
$link_path = RulesPluginUI::path($name, $active_class);
$row[] = $config->hasStatus(ENTITY_FIXED) ? '' : l($text, $link_path, array('attributes' => array('class' => array($active_class, 'action')), 'query' => drupal_get_destination()));
}
$row[] = l(t('clone'), RulesPluginUI::path($name, 'clone'), array('attributes' => array('class' => array('clone', 'action'))));
// Add execute link for for components.
if ($options['show execution op']) {
$row[] = ($config instanceof RulesTriggerableInterface) ? '' : l(t('execute'), RulesPluginUI::path($name, 'execute'), array('attributes' => array('class' => array('execute', 'action')), 'query' => drupal_get_destination()));
if (module_exists('rules_scheduler')) {
// Add schedule link for action components only.
$row[] = $config instanceof RulesActionInterface ? l(t('schedule'), RulesPluginUI::path($name, 'schedule'), array('attributes' => array('class' => array('schedule', 'action')), 'query' => drupal_get_destination())) : '';
}
}
if (!$config->hasStatus(ENTITY_IN_CODE) && !$config->hasStatus(ENTITY_FIXED)) {
$row[] = l(t('delete'), RulesPluginUI::path($name, 'delete'), array('attributes' => array('class' => array('delete', 'action')), 'query' => drupal_get_destination()));
}
elseif ($config->hasStatus(ENTITY_OVERRIDDEN) && !$config->hasStatus(ENTITY_FIXED)) {
$row[] = l(t('revert'), RulesPluginUI::path($name, 'revert'), array('attributes' => array('class' => array('revert', 'action')), 'query' => drupal_get_destination()));
}
else {
$row[] = '';
}
$row[] = l(t('export'), RulesPluginUI::path($name, 'export'), array('attributes' => array('class' => array('export', 'action'))));
return $row;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,521 @@
<?php
/**
* @file Contains data type related forms.
*/
/**
* Interface for data types providing a direct input form.
*/
interface RulesDataDirectInputFormInterface {
/**
* Constructs the direct input form.
*
* @return Array
* The direct input form.
*/
public static function inputForm($name, $info, $settings, RulesPlugin $element);
/**
* Render the configured value.
*
* @return Array
* A renderable array.
*/
public static function render($value);
}
/**
* Default UI related class for data types.
*/
class RulesDataUI {
/**
* Specifies the default input mode per data type.
*/
public static function getDefaultMode() {
return 'selector';
}
/**
* Provides the selection form for a parameter.
*/
public static function selectionForm($name, $info, $settings, RulesPlugin $element) {
if (!isset($settings[$name . ':select'])) {
$settings[$name . ':select'] = '';
$vars = $element->availableVariables();
// Default to variables with the same name as the parameter.
if (isset($vars[$name])) {
$settings[$name . ':select'] = $name;
}
// If there is only one match, use it by default.
elseif (count($matches = RulesData::matchingDataSelector($vars, $info, '', 1, FALSE)) == 1) {
$settings[$name . ':select'] = rules_array_key($matches);
}
}
$form[$name . ':select'] = array(
'#type' => 'rules_data_selection',
'#title' => t('Data selector'),
'#default_value' => $settings[$name . ':select'],
'#required' => empty($info['optional']),
'#autocomplete_path' => RulesPluginUI::path($element->root()->name, 'autocomplete' . '/' . $name),
// Make the autocomplete textfield big enough so that it can display
// descriptions without word wraps.
'#size' => 75,
'#description' => t("The data selector helps you drill down into the data available to Rules. <em>To make entity fields appear in the data selector, you may have to use the condition 'entity has field' (or 'content is of type').</em> More useful tips about data selection is available in <a href='@url'>the online documentation</a>.",
array('@url' => rules_external_help('data-selection'))),
);
$cache = rules_get_cache();
$form['types_help'] = array(
'#theme' => 'rules_settings_help',
'#heading' => t('Data types'),
);
if ($info['type'] == '*') {
$type_labels[] = t('any');
}
else {
$types = is_array($info['type']) ? $info['type'] : array($info['type']);
$type_labels = array();
foreach ($types as $type) {
$type_labels[] = drupal_ucfirst(isset($cache['data_info'][$type]['label']) ? $cache['data_info'][$type]['label'] : $type);
}
}
$form['types_help']['#text'] = format_plural(count($type_labels), 'Select data of the type %types.', 'Select data of the types %types.', array('%types' => implode(', ', $type_labels)));
if (!empty($info['translatable'])) {
if (empty($info['custom translation language'])) {
$text = t('If a multilingual data source (i.e. a translatable field) is given, the argument is translated to the current interface language.');
}
else {
$text = t('If a multilingual data source (i.e. a translatable field) is given, the argument is translated to the configured language.');
}
$form['translation'] = array(
'#theme' => 'rules_settings_help',
'#text' => $text,
'#heading' => t('Translation'),
);
}
$form['help'] = array(
'#theme' => 'rules_data_selector_help',
'#variables' => $element->availableVariables(),
'#parameter' => $info,
);
// Add data processor.
$settings += array($name . ':process' => array());
$form[$name . ':process'] = array();
RulesDataProcessor::attachForm($form[$name . ':process'], $settings[$name . ':process'], $info, $element->availableVariables());
return $form;
}
/**
* Renders the value by making use of the label if an options list is available.
*
* Used for data UI classes implementing the
* RulesDataDirectInputFormInterface.
*
* In case an options list is available, the the usual render() method won't
* be invoked, instead the selected entry is rendered via this method.
*
* @todo for Drupal 8: Refactor to avoid implementations have to care about
* option lists when generating the form, but not when rendering values.
*/
public static function renderOptionsLabel($value, $name, $info, RulesPlugin $element) {
if (!empty($info['options list'])) {
$element->call('loadBasicInclude');
$options = entity_property_options_flatten($info['options list']($element, $name));
if (!is_array($value) && isset($options[$value])) {
$value = $options[$value];
}
elseif (is_array($value)) {
foreach ($value as $key => $single_value) {
if (isset($options[$single_value])) {
$value[$key] = $options[$single_value];
}
}
$value = implode(', ', $value);
}
return array(
'content' => array('#markup' => check_plain($value)),
'#attributes' => array('class' => array('rules-parameter-options-entry')),
);
}
}
}
/**
* UI for textual data.
*/
class RulesDataUIText extends RulesDataUI implements RulesDataDirectInputFormInterface {
public static function getDefaultMode() {
return 'input';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
if (!empty($info['options list'])) {
// Make sure the .rules.inc of the providing module is included as the
// options list callback may reside there.
$element->call('loadBasicInclude');
$form[$name] = array(
'#type' => 'select',
'#options' => call_user_func($info['options list'], $element, $name),
);
}
else {
$form[$name] = array(
'#type' => 'textarea',
);
RulesDataInputEvaluator::attachForm($form, $settings, $info, $element->availableVariables());
}
$settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
$form[$name] += array(
'#title' => t('Value'),
'#default_value' => $settings[$name],
'#required' => empty($info['optional']),
'#after_build' => array('rules_ui_element_fix_empty_after_build'),
'#rows' => 3,
);
return $form;
}
public static function render($value) {
return array(
'content' => array('#markup' => check_plain($value)),
'#attributes' => array('class' => array('rules-parameter-text')),
);
}
}
/**
* UI for text tokens.
*/
class RulesDataUITextToken extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
if ($form[$name]['#type'] == 'textarea') {
$form[$name]['#element_validate'][] = 'rules_ui_element_token_validate';
$form[$name]['#description'] = t('May only contain lowercase letters, numbers, and underscores and has to start with a letter.');
$form[$name]['#rows'] = 1;
}
return $form;
}
}
/**
* UI for formatted text.
*/
class RulesDataUITextFormatted extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
$settings += array($name => isset($info['default value']) ? $info['default value'] : array('value' => NULL, 'format' => NULL));
$form[$name]['#type'] = 'text_format';
$form[$name]['#base_type'] = 'textarea';
$form[$name]['#default_value'] = $settings[$name]['value'];
$form[$name]['#format'] = $settings[$name]['format'];
return $form;
}
public static function render($value) {
return array(
'content' => array('#markup' => check_plain($value['value'])),
'#attributes' => array('class' => array('rules-parameter-text-formatted')),
);
}
}
/**
* UI for decimal data.
*/
class RulesDataUIDecimal extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
if (empty($info['options list'])) {
$form[$name]['#type'] = 'textfield';
}
$form[$name]['#element_validate'][] = 'rules_ui_element_decimal_validate';
$form[$name]['#rows'] = 1;
return $form;
}
}
/**
* UI for integers.
*/
class RulesDataUIInteger extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
if (empty($info['options list'])) {
$form[$name]['#type'] = 'textfield';
}
$form[$name]['#element_validate'][] = 'rules_ui_element_integer_validate';
return $form;
}
}
/**
* UI for boolean data.
*/
class RulesDataUIBoolean extends RulesDataUI implements RulesDataDirectInputFormInterface {
public static function getDefaultMode() {
return 'input';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
// Note: Due to the checkbox even optional parameter always receive a value.
$form[$name] = array(
'#type' => 'checkbox',
'#title' => check_plain($info['label']),
'#default_value' => $settings[$name],
);
return $form;
}
public static function render($value) {
return array(
'content' => array('#markup' => !empty($value) ? t('true') : t('false')),
'#attributes' => array('class' => array('rules-parameter-boolean')),
);
}
}
/**
* UI for dates.
*/
class RulesDataUIDate extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$settings += array($name => isset($info['default value']) ? $info['default value'] : (empty($info['optional']) ? gmdate('Y-m-d H:i:s', time()) : NULL));
// Convert any configured timestamp into a readable format.
if (is_numeric($settings[$name])) {
$settings[$name] = gmdate('Y-m-d H:i:s', $settings[$name]);
}
$form = parent::inputForm($name, $info, $settings, $element);
$form[$name]['#type'] = 'textfield';
$form[$name]['#element_validate'][] = 'rules_ui_element_date_validate';
// Note that the date input evaluator takes care for parsing dates using
// strtotime() into a timestamp, which is the internal date format.
$form[$name]['#description'] = t('The date in GMT. You may enter a fixed time (like %format) or any other values in GMT known by the PHP !strtotime function (like "+1 day"). Relative dates like "+1 day" or "now" relate to the evaluation time.',
array('%format' => gmdate('Y-m-d H:i:s', time() + 86400),
'!strtotime' => l('strtotime()', 'http://php.net/strtotime')));
//TODO: Leverage the jquery datepicker+timepicker once a module providing
//the timpeicker is available.
return $form;
}
public static function render($value) {
$value = is_numeric($value) ? format_date($value, 'short') : check_plain($value);
return array(
'content' => array('#markup' => $value),
'#attributes' => array('class' => array('rules-parameter-date')),
);
}
}
/**
* UI for duration type parameter.
*/
class RulesDataUIDuration extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
$form[$name]['#type'] = 'rules_duration';
$form[$name]['#after_build'][] = 'rules_ui_element_duration_after_build';
return $form;
}
public static function render($value) {
$value = is_numeric($value) ? format_interval($value) : check_plain($value);
return array(
'content' => array('#markup' => $value),
'#attributes' => array('class' => array('rules-parameter-duration')),
);
}
}
/**
* UI for the URI type parameter.
*/
class RulesDataUIURI extends RulesDataUIText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
$form[$name]['#rows'] = 1;
$form[$name]['#description'] = t('You may enter relative URLs like %url as well as absolute URLs like %absolute-url.', array('%url' => 'user/login?destination=node', '%absolute-url' => 'http://drupal.org'));
return $form;
}
}
/**
* UI for lists of textual data.
*/
class RulesDataUIListText extends RulesDataUIText {
public static function getDefaultMode() {
return 'input';
}
/**
* @todo This does not work for inputting textual values including "\n".
*/
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
$form = parent::inputForm($name, $info, $settings, $element);
if ($form[$name]['#type'] == 'textarea') {
// Fix up the value to be an array during after build.
$form[$name]['#delimiter'] = "\n";
$form[$name]['#after_build'][] = 'rules_ui_list_textarea_after_build';
$form[$name]['#pre_render'][] = 'rules_ui_list_textarea_pre_render';
$form[$name]['#default_value'] = !empty($settings[$name]) ? implode("\n", $settings[$name]) : NULL;
$form[$name]['#description'] = t('A list of values, one on each line.');
}
else {
$form[$name]['#multiple'] = TRUE;
}
return $form;
}
public static function render($value) {
return array(
'content' => array('#markup' => check_plain(implode(', ', $value))),
'#attributes' => array('class' => array('rules-parameter-list')),
);
}
}
/**
* UI for lists of integers.
*/
class RulesDataUIListInteger extends RulesDataUIListText {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$settings += array($name => isset($info['default value']) ? $info['default value'] : NULL);
$form = parent::inputForm($name, $info, $settings, $element);
if ($form[$name]['#type'] == 'textarea') {
$form[$name]['#description'] = t('A list of integers, separated by commas. E.g. enter "1, 2, 3".');
$form[$name]['#delimiter'] = ',';
$form[$name]['#default_value'] = !empty($settings[$name]) ? implode(", ", $settings[$name]) : NULL;
$form[$name]['#element_validate'][] = 'rules_ui_element_integer_list_validate';
$form[$name]['#rows'] = 1;
}
return $form;
}
}
/**
* UI for lists of tokens.
*/
class RulesDataUIListToken extends RulesDataUIListInteger {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
if ($form[$name]['#type'] == 'textarea') {
$form[$name]['#description'] = t('A list of text tokens, separated by commas. E.g. enter "one, two, three".');
$form[$name]['#element_validate'] = array('rules_ui_element_token_list_validate');
}
return $form;
}
}
/**
* UI for entity-based data types.
*/
class RulesDataUIEntity extends RulesDataUIText {
public static function getDefaultMode() {
return 'selector';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
if (empty($info['options list'])) {
$form[$name]['#type'] = 'textfield';
$entity_info = entity_get_info($info['type']);
if (empty($entity_info['entity keys']['name'])) {
$form[$name]['#element_validate'][] = 'rules_ui_element_integer_validate';
}
$form[$name]['#title'] = t('@entity identifier', array('@entity' => $entity_info['label']));
$entity_label = strtolower($entity_info['label'][0]) . substr($entity_info['label'], 1);
$form[$name]['#description'] = t('Specify an identifier of a @entity.', array('@entity' => $entity_label));
}
return $form;
}
}
/**
* UI for exportable entity-based data types.
*/
class RulesDataUIEntityExportable extends RulesDataUIEntity {
public static function getDefaultMode() {
return 'input';
}
}
/**
* UI for taxonomy vocabularies.
*
* @see RulesTaxonomyVocabularyWrapper
*/
class RulesDataUITaxonomyVocabulary extends RulesDataUIEntity {
public static function getDefaultMode() {
return 'input';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
// Add an options list of all vocabularies if there is none yet.
if (!isset($info['options list'])) {
$info['options list'] = array('RulesDataUITaxonomyVocabulary', 'optionsList');
}
return parent::inputForm($name, $info, $settings, $element);
}
public static function optionsList() {
$options = array();
foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocab) {
$options[$machine_name] = $vocab->name;
}
return $options;
}
}
/**
* UI for lists of entity-based data types.
*/
class RulesDataUIListEntity extends RulesDataUIListInteger {
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$form = parent::inputForm($name, $info, $settings, $element);
if (empty($info['options list'])) {
$entity_info = entity_get_info(entity_property_list_extract_type($info['type']));
if (!empty($entity_info['entity keys']['name'])) {
$form[$name]['#element_validate'] = array('rules_ui_element_token_list_validate');
}
$form[$name]['#title'] = t('@entity identifiers', array('@entity' => $entity_info['label']));
$entity_label = strtolower($entity_info['label'][0]) . substr($entity_info['label'], 1);
$form[$name]['#description'] = t('Specify a comma-separated list of identifiers of @entity entities.', array('@entity' => $entity_label));
}
return $form;
}
}

View File

@@ -0,0 +1,920 @@
<?php
/**
* @file Rules UI forms
*/
/**
* Ajax callback for reloading the whole form.
*/
function rules_ui_form_ajax_reload_form($form, $form_state) {
return $form;
}
/**
* Defines #ajax properties.
*/
function rules_ui_form_default_ajax($effect = 'slide') {
return array(
'callback' => 'rules_ui_form_ajax_reload_form',
'wrapper' => 'rules-form-wrapper',
'effect' => $effect,
'speed' => 'fast',
);
}
/**
* Submit handler for switching the parameter input mode.
*/
function rules_ui_parameter_replace_submit($form, &$form_state) {
if (isset($form_state['triggering_element'])) {
$name = $form_state['triggering_element']['#parameter'];
$form_state['parameter_mode'][$name] = $form_state['parameter_mode'][$name] == 'selector' ? 'input' : 'selector';
}
$form_state['rebuild'] = TRUE;
}
/**
* General form submit handler, that rebuilds the form
*/
function rules_form_submit_rebuild($form, &$form_state) {
$form_state['rebuild'] = TRUE;
}
/**
* Edit a rules configuration.
*/
function rules_ui_form_edit_rules_config($form, &$form_state, $rules_config, $base_path) {
RulesPluginUI::$basePath = $base_path;
$form_state += array('rules_element' => $rules_config);
// Add the rule configuration's form.
$rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE));
$form['#validate'] = array('rules_ui_form_rules_config_validate');
return $form;
}
/**
* General rules configuration form validation callback. Also populates the
* rules configuration with the form values.
*/
function rules_ui_form_rules_config_validate($form, &$form_state) {
$form_state['rules_element']->form_validate($form, $form_state);
}
/**
* Edit a rules configuration form submit callback.
*/
function rules_ui_form_edit_rules_config_submit($form, &$form_state) {
$form_state['rules_element']->form_submit($form, $form_state);
drupal_set_message(t('Your changes have been saved.'));
if (empty($form_state['redirect'])) {
$form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']);
}
}
/**
* Clone a rules configuration form.
*/
function rules_ui_form_clone_rules_config($form, &$form_state, $rules_config, $base_path) {
RulesPluginUI::$basePath = $base_path;
$rules_config = clone $rules_config;
$rules_config->module = 'rules';
$rules_config->id = NULL;
$rules_config->name = '';
$rules_config->label .= ' (' . t('cloned') . ')';
$rules_config->status = ENTITY_CUSTOM;
$form['#validate'][] = 'rules_ui_form_rules_config_validate';
$form['#submit'][] = 'rules_ui_form_edit_rules_config_submit';
$form_state += array('rules_element' => $rules_config, 'op' => 'clone');
// Add the rule configuration's form.
$rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE, 'init' => TRUE));
// Open the settings fieldset so altering the name is easier.
$form['settings']['#collapsed'] = FALSE;
return $form;
}
/**
* A simple form just showing a textarea with the export.
*/
function rules_ui_form_export_rules_config($form, &$form_state, $rules_config, $base_path) {
$form['export'] = array(
'#type' => 'textarea',
'#title' => t('Export'),
'#description' => t('For importing copy the content of the text area and paste it into the import page.'),
'#rows' => 25,
'#default_value' => $rules_config->export(),
);
return $form;
}
/**
* Configuration form to directly execute a rules configuration.
*/
function rules_ui_form_execute_rules_config($form, &$form_state, $rules_config, $base_path) {
// Only components can be executed.
if (!($rules_config instanceof RulesTriggerableInterface)) {
RulesPluginUI::$basePath = $base_path;
// Create either the appropriate action or condition element.
$element = rules_plugin_factory($rules_config instanceof RulesActionInterface ? 'action' : 'condition', 'component_' . $rules_config->name);
$form['exec_help'] = array(
'#prefix' => '<p>',
'#markup' => t('This form allows you to manually trigger the execution of the @plugin "%label". If this component requires any parameters, input the suiting execution arguments below.', array('@plugin' => $rules_config->plugin(), '%label' => $rules_config->label())),
'#suffix' => '</p>',
);
$element->form($form, $form_state);
// For conditions hide the option to negate them.
if (isset($form['negate'])) {
$form['negate']['#access'] = FALSE;
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Execute'),
'#weight' => 20,
);
// Re-use the validation callback, which will also populate the action with
// the configuration settings in the form.
$form['#validate'] = array('rules_ui_form_rules_config_validate');
return $form;
}
drupal_not_found();
exit;
}
/**
* Submit callback for directly executing a component.
*/
function rules_ui_form_execute_rules_config_submit($form, &$form_state) {
$element = $form_state['rules_element'];
$result = $element->execute();
if ($element instanceof RulesActionInterface) {
drupal_set_message(t('Component %label has been executed.', array('%label' => $element->label())));
}
else {
drupal_set_message(t('Component %label evaluated to %result.', array('%label' => $element->label(), '%result' => $result ? 'true' : 'false')));
}
}
/**
* Gets the confirmation question for valid operations, or else FALSE.
*/
function rules_ui_confirm_operations($op, $rules_config) {
$vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label());
switch ($op) {
case 'enable':
return array(t('Are you sure you want to enable the %plugin %label?', $vars), '');
case 'disable':
return array(t('Are you sure you want to disable the %plugin %label?', $vars), '');
case 'revert':
return array(t('Are you sure you want to revert the %plugin %label?', $vars), t('This action cannot be undone.'));
case 'delete':
return array(t('Are you sure you want to delete the %plugin %label?', $vars), t('This action cannot be undone.'));
default:
return FALSE;
}
}
/**
* Confirmation form for applying the operation to the config.
*/
function rules_ui_form_rules_config_confirm_op($form, &$form_state, $rules_config, $op, $base_path) {
if (list($confirm_question, $description) = rules_ui_confirm_operations($op, $rules_config)) {
RulesPluginUI::$basePath = $base_path;
$form_state += array('rules_config' => $rules_config, 'op' => $op);
return confirm_form($form, $confirm_question, $base_path, $description, t('Confirm'), t('Cancel'));
}
else {
drupal_not_found();
exit;
}
}
/**
* Applies the operation and returns the message to show to the user. Also the
* operation is logged to the watchdog. Note that the string is defined two
* times so that the translation extractor can find it.
*/
function rules_ui_confirm_operation_apply($op, $rules_config) {
$vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label());
$edit_link = l(t('edit'), RulesPluginUI::path($rules_config->name));
switch ($op) {
case 'enable':
$rules_config->active = TRUE;
$rules_config->save();
watchdog('rules', 'Enabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link);
return t('Enabled %plugin %label.', $vars);
case 'disable':
$rules_config->active = FALSE;
$rules_config->save();
watchdog('rules', 'Disabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link);
return t('Disabled %plugin %label.', $vars);
case 'revert':
$rules_config->delete();
watchdog('rules', 'Reverted %plugin %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
return t('Reverted %plugin %label to the defaults.', $vars);
case 'delete':
$rules_config->delete();
watchdog('rules', 'Deleted %plugin %label.', $vars);
return t('Deleted %plugin %label.', $vars);
}
}
/**
* Rule config deletion form submit callback.
*/
function rules_ui_form_rules_config_confirm_op_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$msg = rules_ui_confirm_operation_apply($form_state['op'], $form_state['rules_config']);
drupal_set_message($msg);
}
}
/**
* Add a new element a rules configuration.
*/
function rules_ui_add_element($form, &$form_state, $rules_config, $plugin_name, RulesContainerPlugin $parent, $base_path) {
$cache = rules_get_cache();
if (!isset($cache['plugin_info'][$plugin_name]['class'])) {
drupal_not_found();
exit;
}
RulesPluginUI::$basePath = $base_path;
$plugin_is_abstract = in_array('RulesAbstractPlugin', class_parents($cache['plugin_info'][$plugin_name]['class']));
// In the first step create the element and in the second step show its edit
// form.
if ($plugin_is_abstract && !isset($form_state['rules_element'])) {
RulesPluginUI::formDefaults($form, $form_state);
$form_state += array('parent_element' => $parent, 'plugin' => $plugin_name);
$form['element_name'] = array(
'#type' => 'select',
'#title' => t('Select the %element to add', array('%element' => $plugin_name)),
'#options' => RulesPluginUI::getOptions($plugin_name),
'#ajax' => rules_ui_form_default_ajax() + array(
'trigger_as' => array('name' => 'continue'),
),
);
$form['continue'] = array(
'#type' => 'submit',
'#name' => 'continue',
'#value' => t('Continue'),
'#ajax' => rules_ui_form_default_ajax(),
);
}
elseif (!$plugin_is_abstract) {
// Create the initial, empty element.
$element = rules_plugin_factory($plugin_name);
// Always add the new element at the bottom, thus set an appropriate weight.
$iterator = $parent->getIterator();
if ($sibling = end($iterator)) {
$element->weight = $sibling->weight + 1;
}
$element->setParent($parent);
$form_state['rules_element'] = $element;
}
if (isset($form_state['rules_element'])) {
$form_state['rules_element']->form($form, $form_state, array('button' => TRUE, 'init' => TRUE));
$form['#validate'][] = 'rules_ui_edit_element_validate';
$form['#submit'][] = 'rules_ui_edit_element_submit';
}
return $form;
}
/**
* Add element submit callback.
* Used for "abstract plugins" to create the initial element object with the
* given implemenation name and rebuild the form.
*/
function rules_ui_add_element_submit($form, &$form_state) {
$element = rules_plugin_factory($form_state['plugin'], $form_state['values']['element_name']);
// Always add the new element at the bottom, thus set an appropriate weight.
$iterator = $form_state['parent_element']->getIterator();
if ($sibling = end($iterator)) {
$element->weight = $sibling->weight + 1;
}
// Clear the element settings so they won't be processed on serialization as
// there is nothing to be processed yet.
$element->settings = array();
$element->setParent($form_state['parent_element']);
$form_state['rules_element'] = $element;
$form_state['rebuild'] = TRUE;
}
/**
* Delete elements.
*/
function rules_ui_delete_element($form, &$form_state, $rules_config, $rules_element, $base_path) {
RulesPluginUI::$basePath = $base_path;
if (empty($form_state['rules_config'])) {
// Before modifying the rules config we have to clone it, so any
// modifications won't appear in the static cache of the loading controller.
$rules_config = clone $rules_config;
// Also get the element from the cloned config.
$rules_element = $rules_config->elementMap()->lookup($rules_element->elementId());
$form_state['rules_config'] = $rules_config;
$form_state['rules_element'] = $rules_element;
$form_state['element_parent'] = $rules_element->parentElement();
}
// Try deleting the element and warn the user if something breaks, but
// save the parent for determining the right redirect target on submit.
$removed_plugin = $form_state['rules_element']->plugin();
$rules_element->delete();
if (empty($rules_config->dirty) && empty($form_state['input'])) {
try {
$rules_config->integrityCheck();
}
catch (RulesIntegrityException $e) {
$args = array(
'@plugin' => $e->element->plugin(),
'%label' => $e->element->label(),
'@removed-plugin' => $removed_plugin,
'!url' => url(RulesPluginUI::path($form_state['rules_config']->name, 'edit', $e->element)),
);
drupal_set_message(t('Deleting this @removed-plugin would break your configuration as some of its provided variables are utilized by the @plugin <a href="!url">%label</a>.', $args), 'warning');
}
}
$confirm_question = t('Are you sure you want to delete the %element_plugin %element_name?', array('%element_plugin' => $rules_element->plugin(), '%element_name' => $rules_element->label(), '%plugin' => $rules_config->plugin(), '%label' => $rules_config->label()));
return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}
/**
* Rule config deletion form submit callback.
*/
function rules_ui_delete_element_submit($form, &$form_state) {
$rules_config = $form_state['rules_config'];
$rules_config->save();
if (empty($form_state['redirect'])) {
$form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['element_parent']);
}
}
/**
* Configure a rule element.
*/
function rules_ui_edit_element($form, &$form_state, $rules_config, $element, $base_path) {
RulesPluginUI::$basePath = $base_path;
$form_state += array('rules_element' => $element);
$form_state['rules_element']->form($form, $form_state, array('button' => TRUE));
return $form;
}
/**
* Validate the element configuration.
*/
function rules_ui_edit_element_validate($form, &$form_state) {
$form_state['rules_element']->form_validate($form, $form_state);
}
/**
* Submit the element configuration.
*/
function rules_ui_edit_element_submit($form, &$form_state) {
$form_state['rules_element']->form_submit($form, $form_state);
drupal_set_message(t('Your changes have been saved.'));
if (empty($form_state['redirect'])) {
$form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']);
}
}
/**
* Add a new event.
*/
function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config, $base_path) {
RulesPluginUI::$basePath = $base_path;
$form_state += array('rules_config' => $rules_config);
$events = array_diff_key(rules_fetch_data('event_info'), array_flip($rules_config->events()));
$form['help'] = array(
'#markup' => t('Select the event to add. However note that all added events need to provide all variables that should be available to your rule.'),
);
$form['event'] = array(
'#type' => 'select',
'#title' => t('React on event'),
'#options' => RulesPluginUI::getOptions('event', $events),
'#description' => t('Whenever the event occurs, rule evaluation is triggered.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Add'),
);
$form_state['redirect'] = RulesPluginUI::path($rules_config->name);
return $form;
}
/**
* Submit callback that just adds the selected event.
*
* @see rules_admin_add_reaction_rule()
*/
function rules_ui_add_event_apply($form, &$form_state) {
$form_state['rules_config']->event($form_state['values']['event']);
}
/**
* Submit the event configuration.
*/
function rules_ui_add_event_submit($form, &$form_state) {
rules_ui_add_event_apply($form, $form_state);
$rules_config = $form_state['rules_config'];
// Tell the user if this breaks something, but let him proceed.
if (empty($rules_config->dirty)) {
try {
$rules_config->integrityCheck();
}
catch (RulesIntegrityException $e) {
$warning = TRUE;
drupal_set_message(t('Added the event, but it does not provide all variables utilized.'), 'warning');
}
}
$rules_config->save();
if (!isset($warning)) {
$events = rules_fetch_data('event_info');
$label = $events[$form_state['values']['event']]['label'];
drupal_set_message(t('Added event %event.', array('%event' => $label)));
}
}
/**
* Form to remove a event from a rule.
*/
function rules_ui_remove_event($form, &$form_state, $rules_config, $event, $base_path) {
RulesPluginUI::$basePath = $base_path;
$form_state += array('rules_config' => $rules_config, 'rules_event' => $event);
$events = rules_fetch_data('event_info');
$form_state['event_label'] = $events[$event]['label'];
$confirm_question = t('Are you sure you want to remove the event?');
return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('You are about to remove the event %event.', array('%event' => $form_state['event_label'])), t('Remove'), t('Cancel'));
}
/**
* Submit the event configuration.
*/
function rules_ui_remove_event_submit($form, &$form_state) {
$rules_config = $form_state['rules_config'];
$rules_config->removeEvent($form_state['rules_event']);
// Tell the user if this breaks something, but let him proceed.
if (empty($rules_config->dirty)) {
try {
$rules_config->integrityCheck();
}
catch (RulesIntegrityException $e) {
$warning = TRUE;
drupal_set_message(t('Removed the event, but it had provided some variables which are now missing.'), 'warning');
}
}
$rules_config->save();
if (!isset($warning)) {
drupal_set_message(t('Event %event has been removed.', array('%event' => $form_state['event_label'])));
}
$form_state['redirect'] = RulesPluginUI::path($rules_config->name);
}
/**
* Import form for rule configurations.
*/
function rules_ui_import_form($form, &$form_state, $base_path) {
RulesPluginUI::$basePath = $base_path;
RulesPluginUI::formDefaults($form, $form_state);
$form['import'] = array(
'#type' => 'textarea',
'#title' => t('Import'),
'#description' => t('Paste an exported Rules configuration here.'),
'#rows' => 20,
);
$form['overwrite'] = array(
'#title' => t('Overwrite'),
'#type' => 'checkbox',
'#description' => t('If checked, any existing configuration with the same identifier will be replaced by the import.'),
'#default_value' => FALSE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
return $form;
}
/**
* Validation callback for the import form.
*/
function rules_ui_import_form_validate($form, &$form_state) {
if ($rules_config = rules_import($form_state['values']['import'], $error_msg)) {
// Store the successfully imported entity in $form_state.
$form_state['rules_config'] = $rules_config;
if (!$form_state['values']['overwrite']) {
// Check for existing entities with the same identifier.
if (rules_config_load($rules_config->name)) {
$vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label());
form_set_error('import', t('Import of @entity %label failed, a @entity with the same machine name already exists. Check the overwrite option to replace it.', $vars));
}
}
try {
$rules_config->integrityCheck();
}
catch (RulesIntegrityException $e) {
form_set_error('import', t('Integrity check for the imported configuration failed. Error message: %message.', array('%message' => $e->getMessage())));
}
if (!user_access('bypass rules access') && !$rules_config->access()) {
form_set_error('import', t('You have insufficient access permissions for importing this Rules configuration.'));
}
}
else {
form_set_error('import', t('Import failed.'));
if ($error_msg) {
drupal_set_message($error_msg, 'error');
}
}
}
/**
* Submit callback for the import form.
*/
function rules_ui_import_form_submit($form, &$form_state) {
$rules_config = $form_state['rules_config'];
if ($existing_config = rules_config_load($rules_config->name)) {
// Copy DB id and remove the new indicator to overwrite the existing record.
$rules_config->id = $existing_config->id;
unset($rules_config->is_new);
}
$rules_config->save();
$vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label());
watchdog('rules_config', 'Imported @entity %label.', $vars);
drupal_set_message(t('Imported @entity %label.', $vars));
$form_state['redirect'] = RulesPluginUI::$basePath;
}
/**
* FAPI process callback for the data selection widget.
* This finalises the auto completion callback path by appending the form build
* id.
*/
function rules_data_selection_process($element, &$form_state, $form) {
$element['#autocomplete_path'] .= '/' . $form['#build_id'];
$form_state['cache'] = TRUE;
return $element;
}
/**
* Autocomplete data selection results.
*/
function rules_ui_form_data_selection_auto_completion($parameter, $form_build_id, $string = '') {
// Get the form and its state from the cache to get the currently edited
// or created element.
$form_state = form_state_defaults();
$form = form_get_cache($form_build_id, $form_state);
if (!isset($form_state['rules_element'])) {
return;
}
$element = $form_state['rules_element'];
$params = $element->pluginParameterInfo();
$matches = array();
if (isset($params[$parameter])) {
$parts = explode(':', $string);
// Remove the last part as it might be unfinished.
$last_part = array_pop($parts);
$selector = implode(':', $parts);
// Start with the partly given selector or from scratch.
$result = array();
if ($selector && $wrapper = $element->applyDataSelector($selector)) {
$result = RulesData::matchingDataSelector($wrapper, $params[$parameter], $selector . ':', 0);
}
elseif (!$selector) {
$result = RulesData::matchingDataSelector($element->availableVariables(), $params[$parameter], '', 0);
}
foreach ($result as $selector => $info) {
// If we have an uncomplete last part, take it into account now.
$attributes = array();
if (!$last_part || strpos($selector, $string) === 0) {
$attributes['class'][] = 'rules-dsac-item';
$attributes['title'] = isset($info['description']) ? strip_tags($info['description']) : '';
if ($selector[strlen($selector) - 1] == ':') {
$attributes['class'][] = 'rules-dsac-group';
$text = check_plain($selector) . '... (' . check_plain($info['label']) . ')';
}
else {
$text = check_plain($selector) . ' (' . check_plain($info['label']) . ')';
}
$matches[$selector] = "<div" . drupal_attributes($attributes) . ">$text</div>";
}
}
}
drupal_json_output($matches);
}
/**
* FAPI validation of an integer element. Copy of the private function
* _element_validate_integer().
*/
function rules_ui_element_integer_validate($element, &$form_state) {;
$value = $element['#value'];
if (isset($value) && $value !== '' && (!is_numeric($value) || intval($value) != $value)) {
form_error($element, t('%name must be an integer value.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
}
}
/**
* FAPI validation of a decimal element. Improved version of the private
* function _element_validate_number().
*/
function rules_ui_element_decimal_validate($element, &$form_state) {
// Substitute the decimal separator ",".
$value = strtr($element['#value'], ',', '.');
if ($value != '' && !is_numeric($value)) {
form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
}
elseif ($value != $element['#value']) {
form_set_value($element, $value, $form_state);
}
}
/**
* FAPI validation of a date element. Makes sure the specified date format is
* correct and converts date values specifiy a fixed (= non relative) date to
* a timestamp. Relative dates are handled by the date input evaluator.
*/
function rules_ui_element_date_validate($element, &$form_state) {
$value = $element['#value'];
if ($value == '' || (is_numeric($value) && intval($value) == $value)) {
// The value is a timestamp.
return;
}
elseif (is_string($value) && RulesDateInputEvaluator::gmstrtotime($value) === FALSE) {
form_error($element, t('Wrong date format. Specify the date in the format %format.', array('%format' => gmdate('Y-m-d H:i:s', time() + 86400))));
}
elseif (is_string($value) && RulesDateInputEvaluator::isFixedDateString($value)) {
// As the date string specifies a fixed format, we can convert it now.
$value = RulesDateInputEvaluator::gmstrtotime($value);
form_set_value($element, $value, $form_state);
}
}
/**
* FAPI process callback for the duration element type.
*/
function rules_ui_element_duration_process($element, &$form_state) {
$element['value'] = array(
'#type' => 'textfield',
'#size' => 8,
'#element_validate' => array('rules_ui_element_integer_validate'),
'#default_value' => $element['#default_value'],
'#required' => !empty($element['#required']),
);
$element['multiplier'] = array(
'#type' => 'select',
'#options' => rules_ui_element_duration_multipliers(),
'#default_value' => 1,
);
// Put the child elements in a container-inline div.
$element['value']['#prefix'] = '<div class="rules-duration container-inline">';
$element['multiplier']['#suffix'] = '</div>';
// Set an appropriate multiplier.
if (!empty($element['value']['#default_value'])) {
foreach (array_keys(rules_ui_element_duration_multipliers()) as $m) {
if ($element['value']['#default_value'] % $m == 0) {
$element['multiplier']['#default_value'] = $m;
}
}
// Divide value by the multiplier, so the display is correct.
$element['value']['#default_value'] /= $element['multiplier']['#default_value'];
}
return $element;
}
/**
* Defines possible duration multiplier.
*/
function rules_ui_element_duration_multipliers() {
return array(
1 => t('seconds'),
60 => t('minutes'),
3600 => t('hours'),
// Just use approximate numbers for days (might last 23h on DST change),
// months and years.
86400 => t('days'),
86400 * 30 => t('months'),
86400 * 30 * 12 => t('years'),
);
}
/**
* Helper function to determine the value for a rules duration form
* element.
*/
function rules_ui_element_duration_value($element, $input = FALSE) {
// This runs before child elements are processed, so we cannot calculate the
// value here. But we have to make sure the value is an array, so the form
// API is able to process the children to set their values in the array. Thus
// once the form API has finished processing the element, the value is an
// array containing the child element values. Then finally the after build
// callback converts it back to the numeric value and sets that.
return array();
}
/**
* FAPI after build callback for the duration parameter type form.
* Fixes up the form value by applying the multiplier.
*/
function rules_ui_element_duration_after_build($element, &$form_state) {
if ($element['value']['#value'] !== '') {
$element['#value'] = $element['value']['#value'] * $element['multiplier']['#value'];
form_set_value($element, $element['#value'], $form_state);
}
else {
$element['#value'] = NULL;
form_set_value($element, NULL, $form_state);
}
return $element;
}
/**
* FAPI after build callback to ensure empty form elements result in no value.
*/
function rules_ui_element_fix_empty_after_build($element, &$form_state) {
if (isset($element['#value']) && $element['#value'] === '') {
$element['#value'] = NULL;
form_set_value($element, NULL, $form_state);
}
// Work-a-round for the text_format element.
elseif ($element['#type'] == 'text_format' && !isset($element['value']['#value'])) {
form_set_value($element, NULL, $form_state);
}
return $element;
}
/**
* FAPI after build callback for specifying a list of values.
*
* Turns the textual value in an array by splitting the text in chunks using the
* delimiter set at $element['#delimiter'].
*/
function rules_ui_list_textarea_after_build($element, &$form_state) {
$element['#value'] = $element['#value'] ? explode($element['#delimiter'], $element['#value']) : array();
$element['#value'] = array_map('trim', $element['#value']);
form_set_value($element, $element['#value'], $form_state);
return $element;
}
/**
* FAPI pre render callback. Turns the value back to a string for rendering.
*
* @see rules_ui_list_textarea_after_build()
*/
function rules_ui_list_textarea_pre_render($element) {
$element['#value'] = implode($element['#delimiter'], $element['#value']);
return $element;
}
/**
* FAPI callback to validate a list of integers.
*/
function rules_ui_element_integer_list_validate($element, &$form_state) {
foreach ($element['#value'] as $value) {
if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
form_error($element, t('Each value must be an integer.'));
}
}
}
/**
* FAPI callback to validate a token.
*/
function rules_ui_element_token_validate($element) {
$value = $element['#value'];
if (isset($value) && $value !== '' && !entity_property_verify_data_type($value, 'token')) {
form_error($element, t('%name may only contain lowercase letters, numbers, and underscores and has to start with a letter.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
}
}
/**
* FAPI callback to validate a list of tokens.
*/
function rules_ui_element_token_list_validate($element, &$form_state) {
foreach ($element['#value'] as $value) {
if ($value !== '' && !entity_property_verify_data_type($value, 'token')) {
form_error($element, t('Each value may only contain lowercase letters, numbers, and underscores and has to start with a letter.'));
}
}
}
/**
* FAPI callback to validate a machine readable name.
*/
function rules_ui_element_machine_name_validate($element, &$form_state) {
if ($element['#value'] && !preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
form_error($element, t('Machine-readable names must contain only lowercase letters, numbers, and underscores.'));
}
}
/**
* FAPI callback to validate the form for editing variable info.
* @see RulesPluginUI::getVariableForm()
*/
function rules_ui_element_variable_form_validate($elements, &$form_state) {
$names = array();
foreach (element_children($elements['items']) as $item_key) {
$element = &$elements['items'][$item_key];
if ($element['name']['#value'] || $element['type']['#value'] || $element['label']['#value']) {
foreach (array('name' => t('Machine name'), 'label' => t('Label'), 'type' => t('Data type')) as $key => $title) {
if (!$element[$key]['#value']) {
form_error($element[$key], t('!name field is required.', array('!name' => $title)));
}
}
if (isset($names[$element['name']['#value']])) {
form_error($element['name'], t('The machine-readable name %name is already taken.', array('%name' => $element['name']['#value'])));
}
$names[$element['name']['#value']] = TRUE;
}
}
}
/**
* Helper to sort elements by their 'weight' key.
*/
function rules_element_sort_helper($a, $b) {
$a += array('weight' => 0);
$b += array('weight' => 0);
if ($a['weight'] == $b['weight']) {
return 0;
}
return ($a['weight'] < $b['weight']) ? -1 : 1;
}
/**
* Form after build handler to set the static base path.
*
* @see RulesPluginUI::formDefaults()
*/
function rules_form_after_build_restore_base_path($form, &$form_state) {
if (isset($form_state['_rules_base_path'])) {
RulesPluginUI::$basePath = $form_state['_rules_base_path'];
}
return $form;
}
/**
* AJAX page callback to load tag suggestions.
*
* Largely copied from taxonomy_autocomplete().
*/
function rules_autocomplete_tags($tags_typed = '') {
// The user enters a comma-separated list of tags. We only autocomplete the
// last tag.
$tags_typed = drupal_explode_tags($tags_typed);
$tag_last = drupal_strtolower(array_pop($tags_typed));
$tag_matches = array();
if ($tag_last != '') {
$query = db_select('rules_tags', 'rt');
// Do not select already entered terms.
if (!empty($tags_typed)) {
$query->condition('rt.tag', $tags_typed, 'NOT IN');
}
// Select rows that match by tag name.
$tags_return = $query
->distinct()
->fields('rt', array('tag'))
->condition('rt.tag', '%' . db_like($tag_last) . '%', 'LIKE')
->groupBy('rt.tag')
->range(0, 10)
->execute()
->fetchCol('rt.tag');
$prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
foreach ($tags_return as $name) {
$n = $name;
// Tag names containing commas or quotes must be wrapped in quotes.
if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $name) . '"';
}
$tag_matches[$prefix . $n] = check_plain($name);
}
}
drupal_json_output($tag_matches);
}

View File

@@ -0,0 +1,234 @@
<?php
/**
* @file Contains UI for diverse plugins provided by Rules.
*/
/**
* Rule specific UI.
*/
class RulesRuleUI extends RulesActionContainerUI {
protected $rule, $conditions;
public function __construct(FacesExtendable $object) {
parent::__construct($object);
$this->rule = $object;
$this->conditions = $this->rule->conditionContainer();
}
public function form(&$form, &$form_state, $options = array()) {
$form_state['rules_element'] = $this->rule;
$label = $this->element->label();
// Automatically add a counter to unlabelled rules.
if ($label == t('unlabeled') && !$this->element->isRoot() && !empty($options['init'])) {
$parent = $this->element->parentElement();
$label .= ' ' . count($parent->getIterator());
}
// Components have already a label. If used inside a rule-set add a label
// though. It's called 'Name' in the UI though.
if (!$this->element->isRoot()) {
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#default_value' => empty($options['init']) ? $label : '',
'#required' => TRUE,
'#weight' => 5,
'#description' => t('A human-readable name shortly describing the rule.'),
);
}
$form += array('conditions' => array('#weight' => -5, '#tree' => TRUE));
$this->conditions->form($form['conditions'], $form_state);
unset($form['conditions']['negate']);
// Add actions form.
$iterator = new RecursiveIteratorIterator($this->rule->actions(), RecursiveIteratorIterator::SELF_FIRST);
parent::form($form, $form_state, $options, $iterator);
// Hide nested elements during creation.
$form['elements']['#access'] = empty($options['init']);
$form['conditions']['elements']['#access'] = empty($options['init']);
$form_state['redirect'] = RulesPluginUI::path($this->element->root()->name, 'edit', $this->element);
if (!empty($options['button'])) {
$form['submit']['#value'] = t('Save changes');
}
}
/**
* Applies the values of the form to the rule configuration.
*/
function form_extract_values($form, &$form_state) {
$form_values = RulesPluginUI::getFormStateValues($form, $form_state);
// Run condition and action container value extraction.
if (isset($form['conditions'])) {
$this->conditions->extender('RulesConditionContainerUI')->form_extract_values($form['conditions'], $form_state);
}
if (!empty($form_values['label'])) {
$this->element->label = $form_values['label'];
}
parent::form_extract_values($form, $form_state);
}
public function operations() {
// When rules are listed only show the edit and delete operations.
$ops = parent::operations();
$ops['#links'] = array_intersect_key($ops['#links'], array_flip(array('edit', 'delete')));
return $ops;
}
}
/**
* Reaction rule specific UI.
*/
class RulesReactionRuleUI extends RulesRuleUI {
public function form(&$form, &$form_state, $options = array()) {
$form['events'] = array(
'#type' => 'container',
'#weight' => -10,
'#access' => empty($options['init']),
);
$event_info = rules_fetch_data('event_info');
$form['events']['table'] = array(
'#theme' => 'table',
'#caption' => 'Events',
'#header' => array('Event', 'Operations'),
'#empty' => t('None'),
);
$form['events']['table']['#attributes']['class'][] = 'rules-elements-table';
foreach ($this->rule->events() as $event_name) {
$event_info += array($event_name => array('label' => t('Unknown event "!event_name"', array('!event_name' => $event_name))));
$form['events']['table']['#rows'][$event_name] = array(
check_plain($event_info[$event_name]['label']),
'<span class="rules_rule_event">' . l(t('delete'), RulesPluginUI::path($this->rule->name, 'delete/event/' . $event_name)) . '</span>',
);
}
// Add the "add event" row.
$cell['colspan'] = 3;
$cell['data']['#theme'] = 'links__rules';
$cell['data']['#attributes']['class'][] = 'rules-operations-add';
$cell['data']['#attributes']['class'][] = 'action-links';
$cell['data']['#links']['add_event'] = array(
'title' => t('Add event'),
'href' => RulesPluginUI::path($this->rule->name, 'add/event'),
'query' => drupal_get_destination(),
);
$form['events']['table']['#rows'][] = array('data' => array($cell), 'class' => array('rules-elements-add'));
parent::form($form, $form_state, $options);
unset($form['label']);
}
/**
* Adds the configuration settings form (label, tags, description, ..).
*/
public function settingsForm(&$form, &$form_state) {
parent::settingsForm($form, $form_state);
$form['settings']['active'] = array(
'#type' => 'checkbox',
'#title' => t('Active'),
'#default_value' => !isset($this->rule->active) || $this->rule->active,
);
$form['settings']['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
'#default_value' => $this->element->weight,
'#weight' => 5,
'#delta' => 10,
'#description' => t('Order rules that react on the same event. Rules with a higher weight are evaluated after rules with less weight.'),
);
unset($form['settings']['component_provides']);
}
public function settingsFormExtractValues($form, &$form_state) {
$form_values = RulesPluginUI::getFormStateValues($form['settings'], $form_state);
parent::settingsFormExtractValues($form, $form_state);
$this->rule->active = $form_values['active'];
$this->rule->weight = $form_values['weight'];
}
}
/**
* Rule set specific UI.
*/
class RulesRuleSetUI extends RulesActionContainerUI {
public function form(&$form, &$form_state, $options = array(), $iterator = NULL) {
// Pass an iterator just iterating over the rules, thus no further child
// elements will be displayed.
parent::form($form, $form_state, $options, $this->element->getIterator());
// Only show the add rule link.
$form['elements']['#add']['#links'] = array_intersect_key($form['elements']['#add']['#links'], array('add_rule' => 1));
$form['elements']['#attributes']['class'][] = 'rules-rule-set';
$form['elements']['#caption'] = t('Rules');
}
}
/**
* UI for Rules loops.
*/
class RulesLoopUI extends RulesActionContainerUI {
public function form(&$form, &$form_state, $options = array()) {
parent::form($form, $form_state, $options);
$settings = $this->element->settings;
$form['item'] = array(
'#type' => 'fieldset',
'#title' => t('Current list item'),
'#description' => t('The variable used for holding each list item in the loop. This variable will be available inside the loop only.'),
'#tree' => TRUE,
);
$form['item']['label'] = array(
'#type' => 'textfield',
'#title' => t('Variable label'),
'#default_value' => $settings['item:label'],
'#required' => TRUE,
);
$form['item']['var'] = array(
'#type' => 'textfield',
'#title' => t('Variable name'),
'#default_value' => $settings['item:var'],
'#description' => t('The variable name must contain only lowercase letters, numbers, and underscores and must be unique in the current scope.'),
'#element_validate' => array('rules_ui_element_machine_name_validate'),
'#required' => TRUE,
);
}
function form_extract_values($form, &$form_state) {
parent::form_extract_values($form, $form_state);
$form_values = RulesPluginUI::getFormStateValues($form, $form_state);
$this->element->settings['item:var'] = $form_values['item']['var'];
$this->element->settings['item:label'] = $form_values['item']['label'];
}
public function form_validate($form, &$form_state) {
parent::form_validate($form, $form_state);
$vars = $this->element->availableVariables();
$name = $this->element->settings['item:var'];
if (isset($vars[$name])) {
form_error($form['item']['var'], t('The variable name %name is already taken.', array('%name' => $name)));
}
}
public function buildContent() {
$content = parent::buildContent();
$content['description']['item'] = array(
'#caption' => t('List item'),
'#theme' => 'rules_content_group',
);
$content['description']['item']['var'] = array(
'#theme' => 'rules_variable_view',
'#info' => $this->element->listItemInfo(),
'#name' => $this->element->settings['item:var'],
);
return $content;
}
}

View File

@@ -0,0 +1,287 @@
<?php
/**
* @file Rules theme functions
*/
/**
* Themes a tree of rule elements in a draggable table.
*
* @ingroup themeable
*/
function theme_rules_elements($variables) {
$form = $variables['element'];
$form['#theme'] = 'table';
$form['#header'] = array(t('Elements'), t('Weight'), t('Operations'));
$form['#attributes']['id'] = 'rules-' . drupal_html_id($form['#caption']) . '-id';
foreach (element_children($form) as $element_id) {
$element = &$form[$element_id];
// Add special classes to be used for tabledrag.js.
$element['parent_id']['#attributes']['class'] = array('rules-parent-id');
$element['element_id']['#attributes']['class'] = array('rules-element-id');
$element['weight']['#attributes']['class'] = array('rules-element-weight');
$row = array();
$row[] = theme('indentation', array('size' => $element['#depth'])) . drupal_render($element['label']);
$row[] = drupal_render($element['weight']) . drupal_render($element['parent_id']) . drupal_render($element['element_id']);
$row[] = array('class' => 'rules-operations', 'data' => $element['operations']);
$row = array('data' => $row) + $element['#attributes'];
$row['class'][] = 'draggable';
if (!$element['#container']) {
$row['class'][] = 'tabledrag-leaf';
}
$form['#rows'][] = $row;
}
if (!empty($form['#rows'])) {
drupal_add_tabledrag($form['#attributes']['id'], 'match', 'parent', 'rules-parent-id', 'rules-parent-id', 'rules-element-id', TRUE, 10);
drupal_add_tabledrag($form['#attributes']['id'], 'order', 'sibling', 'rules-element-weight');
}
else {
$form['#rows'][] = array(array('data' => t('None'), 'colspan' => 3));
}
if (!empty($form['#add'])) {
$row = array();
$row[] = array('data' => $form['#add'], 'colspan' => 3);
$form['#rows'][] = array('data' => $row, 'class' => array('rules-elements-add'));
}
// Add a wrapping div.
return '<div class="rules-elements-table">' . drupal_render($form) . '</div>';
}
/**
* Themes the rules form for editing the used variables.
*
* @see RulesPluginUI::getVariableForm()
* @ingroup themeable
*/
function theme_rules_ui_variable_form($variables) {
$elements = $variables['element'];
$table['#theme'] = 'table';
$table['#header'] = array(t('Data type'), t('Label'), t('Machine name'), t('Usage'), array('data' => t('Weight'), 'class' => array('tabledrag-hide')));
$table['#attributes']['id'] = 'rules-' . drupal_html_id($elements['#title']) . '-id';
foreach (element_children($elements['items']) as $key) {
$element = &$elements['items'][$key];
// Add special classes to be used for tabledrag.js.
$element['weight']['#attributes']['class'] = array('rules-element-weight');
$row = array();
$row[] = array('data' => $element['type']);
$row[] = array('data' => $element['label']);
$row[] = array('data' => $element['name']);
$row[] = array('data' => $element['usage']);
$row[] = array('data' => $element['weight']);
$row = array('data' => $row) + $element['#attributes'];
$row['class'][] = 'draggable';
$table['#rows'][] = $row;
}
$elements['items']['#printed'] = TRUE;
if (!empty($table['#rows'])) {
drupal_add_tabledrag($table['#attributes']['id'], 'order', 'sibling', 'rules-element-weight');
}
// Theme it like a form item, but with the description above the content.
$attributes['class'][] = 'form-item';
$attributes['class'][] = 'rules-variables-form';
$output = '<div' . drupal_attributes($attributes) . '>' . "\n";
$output .= theme('form_element_label', $variables);
if (!empty($elements['#description'])) {
$output .= ' <div class="description">' . $elements['#description'] . "</div>\n";
}
$output .= ' ' . drupal_render($table) . "\n";
// Add in any further children elements.
foreach (element_children($elements, TRUE) as $key) {
$output .= drupal_render($elements[$key]);
}
$output .= "</div>\n";
return $output;
}
/**
* Themes a view of multiple configuration items.
* @ingroup themeable
*/
function theme_rules_content_group($variables) {
$element = $variables['element'];
$output = array();
foreach (element_children($element) as $key) {
$output[] = drupal_render($element[$key]);
}
$output = array_filter($output);
$heading = !empty($element['#caption']) ? "<span class='rules-content-heading'>" . $element['#caption'] . ': </span>' : '';
if (!empty($output)) {
$element['#attributes']['class'][] = 'rules-element-content-group';
return '<div' . drupal_attributes($element['#attributes']) . '>' . $heading . implode(', ', $output) . '</div>';
}
}
/**
* Themes the view of a single parameter configuration.
* @ingroup themeable
*/
function theme_rules_parameter_configuration($variables) {
$element = $variables['element'];
$content = drupal_render_children($element);
// Add the full content to the span's title, but don't use drupal_attributes
// for that as this would invoke check_plain() again.
$title = strip_tags($content);
$element['#attributes']['class'][] = 'rules-parameter-configuration';
$attributes = drupal_attributes($element['#attributes']) . " title='$title'";
$label_attributes['class'][] = 'rules-parameter-label';
if (!empty($element['#info']['description'])) {
$label_attributes['title'] = $element['#info']['description'];
}
$label_attributes = drupal_attributes($label_attributes);
$output = "<span $label_attributes>" . check_plain($element['#info']['label']) . ': </span>';
$output .= "<span $attributes>" . truncate_utf8($content, 30, TRUE, TRUE) . "</span>";
return $output;
}
/**
* Themes info about variables.
* @ingroup themeable
*/
function theme_rules_variable_view($variables) {
$element = $variables['element'];
$label_attributes['class'][] = 'rules-variable-label';
$label_attributes['title'] = '';
if (!empty($element['#info']['description'])) {
$label_attributes['title'] = $element['#info']['description'] . ' ';
}
$label_attributes['title'] .= t('Data type: !type', array('!type' => $element['#info']['type']));
$label_attributes = drupal_attributes($label_attributes);
$output = check_plain($element['#info']['label']);
$output .= ' (' . check_plain($element['#name']) . ')';
return "<span $label_attributes>" . $output . '</span>';
}
/**
* Themes help for using the data selector.
* @ingroup themeable
*/
function theme_rules_data_selector_help($variables) {
$variables_info = $variables['variables'];
$param_info = $variables['parameter'];
$render = array(
'#type' => 'fieldset',
'#title' => t('Data selectors'),
'#pre_render' => array(),
'#attributes' => array(),
);
// Make it manually collapsible as we cannot use #collapsible without the
// FAPI element processor.
$render['#attached']['js'][] = 'misc/collapse.js';
$render['#attributes']['class'][] = 'collapsible';
$render['#attributes']['class'][] = 'collapsed';
$render['table'] = array(
'#theme' => 'table',
'#header' => array(t('Selector'), t('Label'), t('Description')),
);
foreach (RulesData::matchingDataSelector($variables_info, $param_info) as $selector => $info) {
$info += array('label' => '', 'description' => '');
$render['table']['#rows'][] = array(check_plain($selector), check_plain(drupal_ucfirst($info['label'])), check_plain($info['description']));
}
return drupal_render($render);
}
/**
* Themes the rules log debug output.
* @ingroup themeable
*/
function theme_rules_log($variables) {
$element = $variables['element'];
drupal_add_css(drupal_get_path('module', 'rules') . '/ui/rules.ui.css');
// Add jquery ui core css and functions, which are needed for the icons.
drupal_add_library('system', 'ui');
drupal_add_js(drupal_get_path('module', 'rules') . '/ui/rules.debug.js');
$output = '<div class="rules-debug-log">';
$output .= '<h3 class="rules-debug-log-head">';
$output .= '<span class="rules-debug-open-main rules-debug-collapsible-link">';
$output .= '<span unselectable="on" class="ui-icon ui-icon-triangle-1-e rules-debug-icon-open">&nbsp;</span>';
$output .= t('Rules evaluation log');
$output .= '</span>';
$output .= '</span>';
$output .= '<span class="rules-debug-open-all rules-debug-collapsible-link">-' . t('Open all') . '-<span>';
$output .= '</h3>';
$output .= '<div>';
$output .= $element['#children'];
$output .= '</div>';
$output .= '</div>';
return $output;
}
/**
* Theme rules debug log elements.
* @ingroup themeable
*/
function theme_rules_debug_element($variables) {
$output = '<div class="rules-debug-block">';
$output .= '<div class="rules-debug-log-head rules-debug-open rules-debug-collapsible-link">"';
$output .= '<span unselectable="on" class="ui-icon ui-icon-triangle-1-e rules-debug-icon-open">&nbsp;</span>';
$output .= $variables['head'];
if (isset($variables['link'])) {
$output .= ' ' . $variables['link'];
}
$output .= '</div>';
$output .= $variables['log'];
$output .= '</div>';
return $output;
}
/**
* Themes rules autocomplete forms.
* @ingroup themeable
*/
function theme_rules_autocomplete($variables) {
$element = $variables['element'];
drupal_add_js(drupal_get_path('module', 'rules') . '/ui/rules.autocomplete.js');
drupal_add_library('system', 'ui.autocomplete');
drupal_add_library('system', 'ui.button');
$element['#attributes']['type'] = 'text';
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
_form_set_class($element, array('form-text', 'rules-autocomplete'));
$id = $element['#attributes']['id'];
$id_button = drupal_html_id($id . '-button');
$js_vars['rules_autocomplete'][$id] = array(
'inputId' => $id,
'source' => url($element['#autocomplete_path'], array('absolute' => TRUE)),
);
drupal_add_js($js_vars, 'setting');
$output = '<div class="rules-autocomplete">';
$output .= '<input' . drupal_attributes($element['#attributes']) . ' />';
$output .= '</div>';
return $output;
}
/**
* General theme function for displaying settings related help.
* @ingroup themeable
*/
function theme_rules_settings_help($variables) {
$text = $variables['text'];
$heading = $variables['heading'];
return "<p class=\"rules-settings-help\"><strong>$heading:</strong> $text</p>";
}