$value) { $temp[$key]['#pid'] = $key; if (!isset($value['#weight'])) { $temp[$key]['#weight'] = 0; } } // Load and loop through all the database stored predicates. $result = db_query("SELECT * FROM {ca_predicates}"); while ($predicate = db_fetch_array($result)) { // Prepare the database data into a predicate. $predicate = ca_prepare_db_predicate($predicate); // Overrides the module defined predicate if it's been modified by a user. $temp[$predicate['#pid']] = $predicate; } usort($temp, 'ca_weight_sort'); // Copy the temporary array of predicates into a keyed array. $predicates = array(); foreach ($temp as $predicate) { $predicates[$predicate['#pid']] = $predicate; } // Load up the trigger data so we can display them by title. $triggers = module_invoke_all('ca_trigger'); // Build the header for the predicate tables based on the grouping type. if ($groupby == 'trigger') { $header = array( array('data' => t('Title'), 'class' => 'col-title'), t('Class'), t('Status'), t('Weight'), t('Operations'), ); $table_class = 'ca-predicate-trigger'; $table_label = ''. t('Trigger') .': '; } elseif ($groupby == 'class') { $header = array( array('data' => t('Title'), 'class' => 'col-title'), t('Trigger'), t('Status'), t('Weight'), t('Operations'), ); $table_class = 'ca-predicate-class'; $table_label = ''. t('Class') .': '; } $rows = array(); foreach ($predicates as $key => $value) { // Build the basic operation links for each predicate. $ops = array( l(t('edit'), CA_UI_PATH .'/'. $key .'/edit'), ); // Add the reset link if a module defined predicate has been modified. if (!is_numeric($key) && isset($value['#modified'])) { $ops[] = l(t('reset'), CA_UI_PATH .'/'. $key .'/reset'); } // Add a delete link if displaying a custom predicate. if (is_numeric($key)) { $ops[] = l(t('delete'), CA_UI_PATH .'/'. $key .'/delete'); } // Add the predicate's row to the table based on the grouping type. if ($groupby == 'trigger') { $tables[$triggers[$value['#trigger']]['#title']]['rows'][] = array( check_plain($value['#title']), strpos($value['#class'], 'custom') === 0 ? check_plain(substr($value['#class'], 7)) : $value['#class'], $value['#status'] == 0 ? t('Disabled') : t('Enabled'), array('data' => $value['#weight'], 'class' => 'ca-predicate-table-weight'), array('data' => implode(' ', $ops), 'class' => 'ca-predicate-table-ops'), ); } elseif ($groupby == 'class') { $class = strpos($value['#class'], 'custom') === 0 ? check_plain(substr($value['#class'], 7)) : $value['#class']; $tables[$class]['rows'][] = array( check_plain($value['#title']), check_plain($triggers[$value['#trigger']]['#title']), $value['#status'] == 0 ? t('Disabled') : t('Enabled'), array('data' => $value['#weight'], 'class' => 'ca-predicate-table-weight'), array('data' => implode(' ', $ops), 'class' => 'ca-predicate-table-ops'), ); } } // Put the tables in alphabetical order. ksort($tables); // Add the tables for each trigger to the output. foreach ($tables as $key => $value) { // Accommodate empty class names. if (empty($key)) { $key = t('- None -'); } $output .= theme('table', $header, $value['rows'], array('class' => $table_class), $table_label . check_plain($key)); } $output .= l(t('Add a predicate'), CA_UI_PATH .'/add'); return $output; } /** * Form to allow the creation and editing of conditional action predicates. */ function ca_predicate_meta_form($form_state, $pid) { // Load the predicate if an ID is passed in. if (!empty($pid) && $pid !== 0) { $predicate = ca_load_predicate($pid); // Fail out if we didn't find a predicate for that ID. if (empty($predicate)) { drupal_set_message(t('That predicate does not exist.'), 'error'); drupal_goto(CA_UI_PATH); } drupal_set_title($predicate['#title']); } $form['predicate_pid'] = array( '#type' => 'value', '#value' => $pid, ); $form['predicate_title'] = array( '#type' => 'textfield', '#title' => t('Title'), '#description' => t('Enter a title used for display on the overview tables.'), '#default_value' => $pid ? $predicate['#title'] : '', '#required' => TRUE, ); // Create an array of triggers for the select box. $triggers = module_invoke_all('ca_trigger'); foreach ($triggers as $key => $value) { $options[$value['#category']][$key] = $value['#title']; } $form['predicate_trigger'] = array( '#type' => 'select', '#title' => t('Trigger'), '#description' => t('Select the trigger for this predicate.
Cannot be modified if the predicate has conditions or actions.'), '#options' => $options, '#default_value' => $pid ? $predicate['#trigger'] : '', '#disabled' => empty($predicate['#conditions']) && empty($predicate['#actions']) ? FALSE : TRUE, '#required' => TRUE, ); $form['predicate_description'] = array( '#type' => 'textarea', '#title' => t('Description'), '#description' => t('Enter a description that summarizes the use and intent of the predicate.'), '#default_value' => $pid ? $predicate['#description'] : '', ); // Accommodate the mandatory custom prefix for predicate classes. $class = $predicate['#class']; if (strpos($class, 'custom') === 0) { $class = ltrim(substr($class, 6), ':'); } if (is_numeric($pid)) { $form['predicate_class'] = array( '#type' => 'textfield', '#title' => t('Class'), '#description' => t('Classes let you categorize your predicates based on the type of functionality they provide.'), '#field_prefix' => 'custom:', '#default_value' => $class, ); } else { $form['predicate_class'] = array( '#type' => 'value', '#value' => $class, ); } $form['predicate_status'] = array( '#type' => 'radios', '#title' => t('Status'), '#description' => t('Disabled predicates will not be processed when their trigger is pulled.'), '#options' => array( 1 => t('Enabled'), 0 => t('Disabled'), ), '#default_value' => $pid ? $predicate['#status'] : 1, ); $form['predicate_weight'] = array( '#type' => 'weight', '#title' => t('Weight'), '#description' => t('Predicates will be sorted by weight and processed sequentially.'), '#default_value' => ($pid && isset($predicate['#weight'])) ? $predicate['#weight'] : 0, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save predicate'), '#suffix' => l(t('Cancel'), CA_UI_PATH), ); return $form; } /** * @see ca_predicate_meta_form() */ function ca_predicate_meta_form_submit($form, &$form_state) { $form_state['redirect'] = CA_UI_PATH; $save = FALSE; // Load the original predicate. if ($form_state['values']['pid'] !== 0) { $predicate = ca_load_predicate($form_state['values']['predicate_pid']); $predicate['#pid'] = $form_state['values']['predicate_pid']; } // Setup a list of fields to check for and apply changes. $fields = array('title', 'trigger', 'description', 'class', 'status', 'weight'); // Compare the values from the form submission with what is already set. foreach ($fields as $field) { if ($form_state['values']['predicate_'. $field] != $predicate['#'. $field]) { $predicate['#'. $field] = $form_state['values']['predicate_'. $field]; $save = TRUE; } } // Add empty conditions and actions arrays if this is a new predicate. if (empty($predicate['#pid'])) { $predicate['#pid'] = variable_get('ca_predicates_pid', 1); variable_set('ca_predicates_pid', $predicate['#pid'] + 1); $predicate['#conditions'] = array(); $predicate['#actions'] = array(); // For new predicates, redirect to the conditions tab. $form_state['redirect'] = CA_UI_PATH .'/'. $predicate['#pid'] .'/edit/conditions'; } // Check to see if any changes were made and save if necessary. if ($save) { ca_save_predicate($predicate); } drupal_set_message(t('Predicate meta data saved.')); } /** * Form to reset a modified module defined predicate to its original state. */ function ca_predicate_delete_form($form_state, $pid) { $predicate = ca_load_predicate($pid); // Fail if we received an invalid predicate ID. if (empty($predicate)) { drupal_set_message(t('That predicate does not exist.'), 'error'); drupal_goto(CA_UI_PATH); } $form['predicate_pid'] = array( '#type' => 'value', '#value' => $pid, ); $form['predicate_title'] = array( '#type' => 'value', '#value' => $predicate['#title'], ); $description = '

'. check_plain($predicate['#title']) .'
' . check_plain($predicate['#description']) .'

' . t('This action cannot be undone.') .'

'; return confirm_form($form, t('Are you sure you want to !op this predicate?', array('!op' => is_numeric($pid) ? t('delete') : t('reset'))), CA_UI_PATH, $description); } /** * @see ca_predicate_delete_form() */ function ca_predicate_delete_form_submit($form, &$form_state) { ca_delete_predicate($form_state['values']['predicate_pid']); drupal_set_message(t('Predicate %title !op.', array('%title' => $form_state['values']['predicate_title'], '!op' => is_numeric($form_state['values']['predicate_pid']) ? t('deleted') : t('reset')))); $form_state['redirect'] = CA_UI_PATH; } /** * Build a form for adding and editing actions on a predicate. * * @param $form_state * Used by FAPI; the current state of the form as it processes. * @param $pid * The ID of the predicate whose actions are being modified. * @param $title * TRUE or FALSE to specify whether or not to set the predicate's title to be * the title of the page. * @return * The form array for the actions form. * * @ingroup forms * @see * ca_actions_form_add_action_submit() * ca_actions_form_add_action_submit() * ca_actions_form_save_changes_submit() */ function ca_actions_form($form_state, $pid, $title = TRUE) { // Locate the specified predicate. $predicate = ca_load_predicate($pid); // Return an empty form if the predicate does not exist. if (empty($predicate)) { return array(); } // Include the JS for conditional actions forms. drupal_add_js(drupal_get_path('module', 'ca') .'/ca.js'); // Display the title if appropriate. if ($title) { drupal_set_title($predicate['#title']); } // Load up any actions that could possibly be on this predicate. $action_data = ca_load_action(); $form['pid'] = array( '#type' => 'value', '#value' => $pid, ); $form['actions'] = array( '#type' => 'fieldset', '#title' => t('Actions'), '#description' => t('These actions will be performed in order when this predicate passes the conditions evaluation.'), '#tree' => TRUE, ); // Loop through the actions and add them to the form if valid. $i = 0; foreach ($predicate['#actions'] as $key => $action) { // Add it to the form if the action's callback exists. $callback = $action_data[$action['#name']]['#callback']; if (function_exists($callback)) { $form['actions'][$i] = array( '#type' => 'fieldset', '#title' => t('Action: @title', array('@title' => $action_data[$action['#name']]['#title'])), '#description' => $action_data[$action['#name']]['#description'], '#collapsible' => TRUE, '#collapsed' => isset($_SESSION['ca_action_focus']) && $i == $_SESSION['ca_action_focus'] ? FALSE : TRUE, ); $form['actions'][$i]['name'] = array( '#type' => 'value', '#value' => $action['#name'], ); $form['actions'][$i]['title'] = array( '#type' => 'textfield', '#title' => t('Title'), '#default_value' => $action['#title'], ); $form['actions'][$i]['argument_map'] = array( '#type' => 'fieldset', '#title' => t('Arguments'), '#description' => t('Some triggers pass in multiple options for arguments related to a particular action, so you must specify which options to use in the fields below.'), '#collapsible' => TRUE, ); // Setup a variable to decide if we can collapse the arguments fieldset. $collapsed = TRUE; foreach ($action_data[$action['#name']]['#arguments'] as $key => $value) { // Load the available arguments for each entity as received by the // trigger when it was pulled. $options = ca_load_trigger_arguments($predicate['#trigger'], $value['#entity']); // If we have more than one option for any argument, do not collapse. if (count($options) > 1) { $collapsed = FALSE; } $form['actions'][$i]['argument_map'][$key] = array( '#type' => 'select', '#title' => check_plain($value['#title']), '#options' => $options, '#default_value' => $action['#argument_map'][$key], ); } $form['actions'][$i]['argument_map']['#collapsed'] = $collapsed; // Add the action's form elements if any exist. $callback .= '_form'; if (function_exists($callback)) { // Load the trigger data. $trigger_data = ca_load_trigger($predicate['#trigger']); $form['actions'][$i]['settings'] = $callback(array(), $action['#settings'], $trigger_data['#arguments']); } $form['actions'][$i]['remove'] = array( '#type' => 'submit', '#value' => t('Remove this action'), '#name' => 'ca_remove_action_'. $i, '#submit' => array('ca_actions_form_remove_action_submit'), '#attributes' => array('class' => 'ca-remove-confirm'), ); $i++; } } // Unset the session variable used to focus on the new action. unset($_SESSION['ca_action_focus']); $form['actions']['add_action'] = array( '#type' => 'select', '#title' => t('Available actions'), '#options' => ca_load_trigger_actions($predicate['#trigger']), ); $form['actions']['add'] = array( '#type' => 'submit', '#value' => t('Add action'), '#submit' => array('ca_actions_form_add_action_submit'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save changes'), '#submit' => array('ca_actions_form_save_changes_submit'), ); filter_configure_form($form); return $form; } /** * Remove action submit handler. * * @see ca_actions_form() */ function ca_actions_form_remove_action_submit($form, &$form_state) { // Save the existing actions to preserve any changes. ca_actions_form_update_actions($form_state['values']['pid'], $form_state['values']['actions']); // Remove the appropriate action from the predicate and save it. ca_remove_action($form_state['values']['pid'], $form_state['clicked_button']['#parents'][1]); drupal_set_message(t('Action removed.')); } /** * Add action submit handler. * * @see ca_actions_form() */ function ca_actions_form_add_action_submit($form, &$form_state) { // Save the existing actions to preserve any changes. $predicate = ca_actions_form_update_actions($form_state['values']['pid'], $form_state['values']['actions']); // Store the presumed key of the new action. $_SESSION['ca_action_focus'] = count($predicate['#actions']); // Add the specified action to the predicate. ca_add_action($form_state['values']['pid'], $form_state['values']['actions']['add_action']); drupal_set_message(t('Action added.')); } /** * Save changes submit handler for the actions form. * * @see ca_actions_form() */ function ca_actions_form_save_changes_submit($form, &$form_state) { // Save the existing actions to preserve any changes. ca_actions_form_update_actions($form_state['values']['pid'], $form_state['values']['actions']); drupal_set_message(t('Actions saved.')); } /** * Updates a predicate's actions based on the values from an actions form. * * @param $pid * The ID of the predicate whose actions should be updated. * @param $data * The actions values from the form state that should be used to update the * predicate; normally $form_state['values']['actions']. * @return * An array representing the full, updated predicate. */ function ca_actions_form_update_actions($pid, $data) { $actions = array(); // Unset top level components we don't want to get in the way. unset($data['add_action'], $data['add']); // Loop through the actions from the form and add them to our temporary array. foreach ((array) $data as $key => $value) { $actions[] = array( '#name' => $value['name'], '#title' => $value['title'], '#argument_map' => $value['argument_map'], '#settings' => empty($value['settings']) ? array() : $value['settings'], ); } // Load the predicate as it is now. $predicate = ca_load_predicate($pid); // Update the actions and save the result. $predicate['#actions'] = $actions; ca_save_predicate($predicate); return $predicate; } /** * Build a form for adding and editing conditions on a predicate. * * @ingroup forms * @see * ca_conditions_form_add_condition_group_submit() * ca_conditions_form_remove_condition_group_submit() * ca_conditions_form_add_condition_submit() * ca_conditions_form_remove_condition_submit() * ca_conditions_form_save_changes_submit() */ function ca_conditions_form($form_state, $pid, $title = TRUE) { // Locate the specified predicate. $predicate = ca_load_predicate($pid); // Return an empty form if the predicate does not exist. if (empty($predicate)) { return array(); } // Include the JS for conditional actions forms. drupal_add_js(drupal_get_path('module', 'ca') .'/ca.js'); // Display the title if appropriate. if ($title) { drupal_set_title($predicate['#title']); } // Initialize the conditions available for this predicate. ca_load_trigger_conditions($predicate['#trigger']); // Add the predicate ID to the form array. $form['pid'] = array( '#type' => 'value', '#value' => $pid, ); // If the predicate conditions array isn't already a container of containers, // make it so. i.e. group => array('group' => array('cond', 'cond')) if (empty($predicate['#conditions'])) { $predicate['#conditions'] = ca_new_conditions(); } else { $key = array_shift(array_keys($predicate['#conditions']['#conditions'])); if (empty($predicate['#conditions']['#conditions'][$key]['#operator'])) { $predicate['#conditions'] = array( '#operator' => 'AND', '#conditions' => array( $predicate['#conditions'], ), ); } } // Recurse through the predicate's conditions to build the form. $form['conditions'] = _ca_conditions_form_tree($predicate['#conditions'], $predicate['#trigger']); $form['conditions'] = $form['conditions'][0]; // Always remove the top level condition group controls. unset($form['conditions']['condition']); unset($form['conditions']['add_condition']); unset($form['conditions']['remove_condition_group']); // If there are more than one top level condition groups... if (count($form['conditions']['conditions']) > 1) { // Update the top level fieldset. $form['conditions']['#title'] = t('Condition groups'); $form['conditions']['#collapsible'] = FALSE; // Adjust the titles of the operator options to be more explicit. $form['conditions']['operator']['#options'] = array( 'AND' => t('AND. If all of these condition groups are TRUE.'), 'OR' => t('OR. If any of these condition groups are TRUE.'), ); } else { // Otherwise remove the top level fieldset and operator. unset($form['conditions']['#type']); unset($form['conditions']['operator']); // Adjust the second level conditions group to not collapse. $form['conditions']['conditions'][0]['#collapsible'] = FALSE; } // Set the conditions array to a tree so the submitted form values mirror the // structure of a predicate's #conditions array. $form['conditions']['#tree'] = TRUE; $form['save'] = array( '#type' => 'submit', '#value' => t('Save changes'), '#submit' => array('ca_conditions_form_save_changes_submit'), ); $form['add_condition_group'] = array( '#type' => 'submit', '#value' => t('Add condition group'), '#submit' => array('ca_conditions_form_add_condition_group_submit'), ); filter_configure_form($form); return $form; } /** * Recursively add logical groups to the conditions form as fieldsets. * * @param $condition * An array consisting of an #operator and another array of #conditions, or * a condition array with data pertaining to its callback function. * @param $trigger * The trigger name and data concerning the arguments that are passed in to * the condition. * @return * A form array representing the tree of conditions. */ function _ca_conditions_form_tree($condition, $trigger) { // We need an index so condition group fieldsets have unique keys. static $index = 0; $form = array(); // If we're dealing with a conditions group and not a single condition. if (isset($condition['#operator']) && is_array($condition['#conditions'])) { $level = $index; $form[$level] = array( '#type' => 'fieldset', '#title' => t('Conditions group'), '#collapsible' => TRUE, ); // Add an operator radio select for the group. $form[$level]['operator'] = array( '#type' => 'radios', '#title' => t('Operator'), '#options' => array( 'AND' => t('AND. If all of these conditions are TRUE.'), 'OR' => t('OR. If any of these conditions are TRUE.'), ), '#default_value' => $condition['#operator'], ); $form[$level]['conditions'] = array(); // For each condition in #conditions, add a condition fieldset to the form. foreach ($condition['#conditions'] as $sub_condition) { $result = _ca_conditions_form_tree($sub_condition, $trigger); if (is_array($result)) { $form[$level]['conditions'] = array_merge($form[$level]['conditions'], $result); } } $form[$level]['condition'] = array( '#type' => 'select', '#title' => t('Available conditions'), '#options' => ca_load_trigger_conditions(), ); $form[$level]['add_condition'] = array( '#type' => 'submit', '#value' => t('Add condition'), '#submit' => array('ca_conditions_form_add_condition_submit'), '#name' => 'add_condition_'. $level, ); $form[$level]['remove_condition_group'] = array( '#type' => 'submit', '#value' => t('Remove group'), '#submit' => array('ca_conditions_form_remove_condition_group_submit'), '#attributes' => array('class' => 'ca-remove-confirm'), '#name' => 'remove_condition_group_'. $level, ); $index++; return $form; } elseif (!empty($condition)) { // Load the data for the conditions as defined by modules. $condition_data = ca_load_condition(); // Otherwise add a fieldset for the individual condition. $form[0] = array( '#type' => 'fieldset', '#title' => t('Condition: @title', array('@title' => $condition_data[$condition['#name']]['#title'])), '#description' => t('@description', array('@description' => $condition_data[$condition['#name']]['#description'])), '#collapsible' => TRUE, '#collapsed' => isset($condition['#expanded']) ? FALSE : TRUE, ); // Add form elements to the fieldset to adjust the condition's settings. $form[0] += _ca_conditions_form_condition($condition, $trigger); return $form; } } /** * Add a single condition's fieldset to the conditions form. * * @param $condition * The condition data array. * @param $trigger * The trigger name and data concerning the arguments that are passed in to * the condition. * @return * A form array representing the condition. */ function _ca_conditions_form_condition($condition, $trigger) { static $identifier = 0; // Load the data for the conditions as defined by modules. $condition_data = ca_load_condition(); // Condition name is a hard reference to the condition and so not editable. $form['name'] = array( '#type' => 'value', '#value' => $condition['#name'], ); // The title for this particular instance of this condition. $form['title'] = array( '#type' => 'textfield', '#title' => t('Title'), '#default_value' => $condition['#title'], ); $form['argument_map'] = array( '#type' => 'fieldset', '#title' => t('Arguments'), '#description' => t('Some triggers pass in multiple options for arguments related to a particular condition, so you must specify which options to use in the fields below.'), '#collapsible' => TRUE, ); // Setup a variable to decide if we can collapse the arguments fieldset. $collapsed = TRUE; // Cast to an array to accommodate conditions that need no arguments. foreach ((array) $condition_data[$condition['#name']]['#arguments'] as $key => $value) { // Load the available arguments for each entity as received by the // trigger when it was pulled. $options = ca_load_trigger_arguments($trigger, $value['#entity']); // If we have more than one option for any argument, do not collapse. if (count($options) > 1) { $collapsed = FALSE; } $form['argument_map'][$key] = array( '#type' => 'select', '#title' => check_plain($value['#title']), '#options' => $options, '#default_value' => $condition['#argument_map'][$key], ); } $form['argument_map']['#collapsed'] = $collapsed; // Whether or not to negate the result of this condition. $form['settings']['negate'] = array( '#type' => 'checkbox', '#title' => t('Negate this condition.'), '#description' => t('Return FALSE if the condition is TRUE and vice versa.'), '#default_value' => $condition['#settings']['negate'], ); // Get the callback for the condition we're displaying. $callback = $condition_data[$condition['#name']]['#callback'] .'_form'; if (function_exists($callback)) { // Load the trigger data. $trigger_data = ca_load_trigger($trigger); // Add the condition's form elements to the fieldset. $form['settings'] += $callback(array(), $condition['#settings'], $trigger_data['#arguments']); } $form['remove'] = array( '#type' => 'submit', '#value' => t('Remove this condition'), '#submit' => array('ca_conditions_form_remove_condition_submit'), '#attributes' => array('class' => 'ca-remove-confirm'), '#name' => 'remove_condition_group_'. $identifier++, ); return $form; } /** * Submit handler for button to add a condition group. * * @see ca_conditions_form() */ function ca_conditions_form_add_condition_group_submit($form, &$form_state) { // Save the existing conditions to preserve any changes. ca_conditions_form_update_conditions($form_state['values']['pid'], $form_state['values']['conditions']); // Add the condition group to the predicate. ca_add_condition_group($form_state['values']['pid']); // Display a confirmation message. drupal_set_message(t('Condition group added.')); } /** * Submit handler for button to remove a condition grou. * * @see ca_conditions_form() */ function ca_conditions_form_remove_condition_group_submit($form, &$form_state) { $group_key = FALSE; // Loop through the array keys of the conditions group. foreach (array_keys($form['conditions']['conditions']) as $key => $value) { // If we find the key we're looking for... if (is_numeric($value) && $value == $form_state['clicked_button']['#array_parents'][2]) { // Store its position as the new group key once the conditions are saved. // This is necessary because the save function will truncate any holes so // the keys are in numerical order starting with 0. $group_key = $key; } } // Save the existing conditions to preserve any changes. ca_conditions_form_update_conditions($form_state['values']['pid'], $form_state['values']['conditions']); // Fail if we didn't find a new group key for some reason. if ($group_key === FALSE) { drupal_set_message(t('An error occurred when trying to add the condition. Please try again.'), 'error'); } else { // Remove the condition group from the predicate. ca_remove_condition_group($form_state['values']['pid'], $group_key); // Display a confirmation message. drupal_set_message(t('Condition group removed.')); } } /** * Submit handler for button to add a condition to a condition group. * * @see ca_conditions_form() */ function ca_conditions_form_add_condition_submit($form, &$form_state) { $group_key = FALSE; // Loop through the array keys of the conditions group. foreach (array_keys($form['conditions']['conditions']) as $key => $value) { // If we find the key we're looking for... if (is_numeric($value) && $value == $form_state['clicked_button']['#array_parents'][2]) { // Store its position as the new group key once the conditions are saved. // This is necessary because the save function will truncate any holes so // the keys are in numerical order starting with 0. $group_key = $key; } } // Save the existing conditions to preserve any changes. ca_conditions_form_update_conditions($form_state['values']['pid'], $form_state['values']['conditions']); // Fail if we didn't find a new group key for some reason. if ($group_key === FALSE) { drupal_set_message(t('An error occurred when trying to add the condition. Please try again.'), 'error'); } else { // Otherwise add the condition to the specified group. $name = $form_state['values']['conditions']['conditions'][$group_key]['condition']; ca_add_condition($form_state['values']['pid'], $name, $group_key); $condition = ca_load_condition($name); drupal_set_message(t('%title condition added.', array('%title' => $condition['#title']))); } } /** * Submit handler for button to remove a condition from a condition group. * * @see ca_conditions_form() */ function ca_conditions_form_remove_condition_submit($form, &$form_state) { $group_key = FALSE; $cond_key = FALSE; // Loop through the array keys of the conditions group. foreach (array_keys($form['conditions']['conditions']) as $key => $value) { // If we find the key we're looking for... if (is_numeric($value) && $value == $form_state['clicked_button']['#array_parents'][2]) { // Store its position as the new group key once the conditions are saved. // This is necessary because the save function will truncate any holes so // the keys are in numerical order starting with 0. $group_key = $key; } } // Loop through the array keys of the conditions in the group. foreach (array_keys($form['conditions']['conditions'][$form_state['clicked_button']['#array_parents'][2]]['conditions']) as $key => $value) { // If we find the key we're looking for... if (is_numeric($value) && $value == $form_state['clicked_button']['#array_parents'][4]) { // Store its position as the new condition key once the conditions are // saved. This is necessary because the save function will truncate any // holes so the keys are in numerical order starting with 0. $cond_key = $key; } } // Save the existing conditions to preserve any changes. ca_conditions_form_update_conditions($form_state['values']['pid'], $form_state['values']['conditions']); // Fail if we didn't find a new group key for some reason. if ($group_key === FALSE || $cond_key === FALSE) { drupal_set_message(t('An error occurred when trying to remove the condition. Please try again.'), 'error'); } else { // Otherwise remove the condition from the specified group. ca_remove_condition($form_state['values']['pid'], $group_key, $cond_key); drupal_set_message(t('Condition removed.')); } } /** * Save changes submit handler for the conditions form. * * @see ca_conditions_form() */ function ca_conditions_form_save_changes_submit($form, &$form_state) { // Save the existing conditions to preserve any changes. ca_conditions_form_update_conditions($form_state['values']['pid'], $form_state['values']['conditions']); drupal_set_message(t('Conditions saved.')); } /** * Update a predicate's conditions based on the values from a conditions form. * * @param $pid * The ID of the predicate whose conditions should be updated. * @param $data * The conditions values from the form state that should be used to update the * predicate; normally $form_state['values']['conditions']. * @return * An array representing the full, updated predicate. */ function ca_conditions_form_update_conditions($pid, $data) { // Build the new conditions array from scratch. $conditions = ca_new_conditions(); // Override the top level operator if need be. if (isset($data['operator'])) { $conditions['#operator'] = $data['operator']; } // Use a variable to track the group array key so we can "reset" the keys. $group = 0; // Loop through each second level condition group. foreach ($data['conditions'] as $key => $value) { // Save the operator setting. $conditions['#conditions'][$group]['#operator'] = $value['operator']; $conditions['#conditions'][$group]['#conditions'] = array(); // If conditions exist... if (is_array($value['conditions']) && count($value['conditions']) > 0) { // Use a variable to track the condition array key as for groups. $condition = 0; // Loop through the conditions in this group. foreach ($value['conditions'] as $cond_key => $cond_value) { // Save the condition's settings. $conditions['#conditions'][$group]['#conditions'][$condition] = array( '#name' => $cond_value['name'], '#title' => $cond_value['title'], '#argument_map' => $cond_value['argument_map'], '#settings' => $cond_value['settings'], ); $condition++; } } $group++; } // Load the predicate as it is now. $predicate = ca_load_predicate($pid); // Update the actions and save the result. $predicate['#conditions'] = $conditions; ca_save_predicate($predicate); return $predicate; } /** * Landing page for converting Workflow-ng configurations. * * From this page, the user can begin the batch processing of any Workflow-ng * configurations in the site's database into Conditional Actions predicates. * * @ingroup forms * @see ca_conversion_form_submit() */ function ca_conversion_form() { $form = array(); $form['help'] = array( '#value' => '
'. t('Use this form during the update process from Ubercart 1.0 to 2.0 to convert your Workflow-ng configurations to Conditional Actions predicates. Once your configurations are converted, you should complete your uninstallation of Workflow-ng.') .'
', ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Convert configurations'), ); return $form; } /** * @see ca_conversion_form_submit() */ function ca_conversion_form_submit($form, &$form_state) { // Convert workflow-ng configurations in a batch process because there may be // a lot of them. $batch = array( 'operations' => array( array('_ca_convert_configurations', array()), ), 'finished' => '_ca_conversion_finished', 'title' => t('Converting Workflow-ng configurations'), 'init_message' => t('Beginning conversion...'), 'progress_message' => t('@percentage% converted'), 'file' => drupal_get_path('module', 'ca') . '/ca.admin.inc', ); batch_set($batch); } /** * Batch API callback for Workflow-ng configuration conversion. */ function _ca_convert_configurations(&$context) { global $user; if (db_table_exists('workflow_ng_cfgs')) { if (!isset($context['sandbox']['progress'])) { // Initialize batch data. $context['sandbox']['progress'] = 0; $context['sandbox']['current_cfg'] = ''; $context['sandbox']['max'] = db_result(db_query("SELECT COUNT(name) FROM {workflow_ng_cfgs} WHERE altered = 1")); } $limit = 25; $result = db_query_range("SELECT name, data FROM {workflow_ng_cfgs} WHERE altered = 1 AND name > '%s' ORDER BY name", $context['sandbox']['current_cfg'], 0, $limit); $predicates = array(); while ($configuration_row = db_fetch_object($result)) { $predicate = array('#pid' => $configuration_row->name); $conditions = array(); $actions = array(); if ($configuration = unserialize($configuration_row->data)) { $predicate['#class'] = $configuration['#module']; $predicate['#title'] = $configuration['#label']; // Convert event names to corresponding triggers. switch ($configuration['#event']) { case 'order_status_update': $trigger = 'uc_order_status_update'; break; case 'payment_entered': $trigger = 'uc_payment_entered'; break; case 'checkout_complete': $trigger = 'uc_checkout_complete'; break; default: $trigger = $configuration['#event']; break; } // In UC 1.x, taxes had an event for each tax rate, using the same // action. In UC 2.x, they use the same trigger, but have separate // actions. if (strpos($trigger, 'calculate_tax_') === 0) { $tax_id = substr($trigger, 14); $trigger = 'calculate_taxes'; } $predicate['#trigger'] = $trigger; $predicate['#weight'] = $configuration['#weight']; $predicate['#status'] = $configuration['#active']; // Numeric keys in $configuration could be for conditions or actions. // Take each and categorize them to add them to the predicate later. for ($i = 0; isset($configuration[$i]); $i++) { if ($configuration[$i]['#type'] == 'action') { $action = _ca_convert_actions($configuration[$i], $tax_id); if ($action) { $actions[] = $action; } } else { $condition = _ca_convert_conditions($configuration[$i]); if ($condition) { $conditions[] = $condition; } } } // Conditions are optional. If there are no actions, there's no reason // to make a predicate. if ($conditions) { $predicate['#conditions'] = array( '#operator' => 'AND', '#conditions' => $conditions, ); } if ($actions) { $predicate['#actions'] = $actions; $predicate['#uid'] = $user->uid; ca_save_predicate($predicate); } } // Store some result for post-processing in the finished callback. $context['results'][] = $configuration_row->name; // Update our progress information. $context['sandbox']['progress']++; $context['sandbox']['current_cfg'] = $configuration_row->name; $context['message'] = t('Now processing %cfg', array('%cfg' => $configuration_row->name)); } // Inform the batch engine that we are not finished, // and provide an estimation of the completion level we reached. if ($context['sandbox']['progress'] != $context['sandbox']['max']) { $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; } } } /** * Helper function for converting Ubercart's Workflow-ng conditions. */ function _ca_convert_conditions($condition_tree) { static $condition_data; $ca_condition = array(); if (!isset($condition_data)) { $condition_data = module_invoke_all('ca_condition'); } // Handle multiple conditions recursively. if ($condition_tree['#type'] == 'AND' || $condition_tree['#type'] == 'OR') { $ca_condition['#operator'] = $condition_tree['#type']; $ca_condition['#conditions'] = array(); for ($i = 0; isset($condition_tree[$i]); $i++) { $condition = _ca_convert_conditions($condition_tree[$i]); if ($condition) { $ca_condition['#conditions'][] = $condition; } } } else if ($condition_tree['#type'] == 'condition') { // Some conditions were renamed but check the same things. switch ($condition_tree['#name']) { case 'uc_order_condition_order_total': $ca_condition['#name'] = 'uc_order_condition_total'; break; case 'uc_payment_condition_balance': $ca_condition['#name'] = 'uc_payment_condition_order_balance'; break; case 'workflow_ng_condition_custom_php': $ca_condition['#name'] = 'ca_condition_custom_php'; break; case 'workflow_ng_condition_user_hasrole': $ca_condition['#name'] = 'ca_condition_user_roles'; $condition_tree['#settings']['operator'] = $condition_tree['#settings']['operation']; unset($condition_tree['#settings']['operation']); break; default: $ca_condition['#name'] = $condition_tree['#name']; break; } if (isset($condition_tree['#label'])) { $ca_condition['#title'] = $condition_tree['#label']; } else { $ca_condition['#title'] = $condition_data[$ca_condition['#name']]['#title']; } $ca_condition['#argument_map'] = $condition_tree['#argument map']; foreach ($ca_condition['#argument_map'] as $key => $value) { if ($key == 'user') { $key = 'account'; } if ($value == 'user') { $value = 'account'; } $ca_condition['#argument_map'][$key] = $value; } $ca_condition['#settings'] = $condition_tree['#settings']; if (isset($condition_tree['#negate'])) { $ca_condition['#settings']['negate'] = $condition_tree['#negate']; } } return $ca_condition; } /** * Helper function for converting Ubercart's Workflow-ng actions. */ function _ca_convert_actions($wf_action, $tax_id = NULL) { static $action_data; $action = $wf_action; if (!isset($action_data)) { $action_data = module_invoke_all('ca_action'); } // Some actions were renamed, but do the same things. switch ($action['#name']) { case 'uc_order_action_update_status': $action['#name'] = 'uc_order_update_status'; break; case 'uc_taxes_action_apply_tax': $action['#name'] = 'uc_taxes_action_apply_tax_'. $tax_id; break; case 'workflow_ng_action_custom_php': $action['#name'] = 'ca_action_custom_php'; break; } if (isset($action['#label'])) { $action['#title'] = $action['#label']; unset($action['#label']); } else { $action['#title'] = $action_data[$action['#name']]['#title']; } return $action; } /** * Batch finalization function to hide the conversion tab. */ function _ca_conversion_finished() { // Set a variable to hide the conversion tab. variable_set('ca_show_conversion_tab', FALSE); // Display a message and redirect to the overview page. drupal_set_message('Your Workflow-ng configurations have been converted. Please verify the results in your Conditional Actions predicates below.'); drupal_goto(CA_UI_PATH); }