# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: lyle@ubercart.org-20100624204922-c4f9rv96vec1dn3y
# target_branch: bzr://bazaar.ubercart.org/drupal7-uc3/ubercart
# testament_sha1: 623938896fabc965bdb66edb6f006a26050cf523
# timestamp: 2010-06-24 16:50:34 -0400
# base_revision_id: lyle@ubercart.org-20100621191724-nl79yu6bspkxm132
#
# Begin patch
=== modified file 'payment/uc_payment/uc_payment.info'
--- payment/uc_payment/uc_payment.info 2010-04-05 19:25:15 +0000
+++ payment/uc_payment/uc_payment.info 2010-06-21 19:34:42 +0000
@@ -1,7 +1,6 @@
; $Id$
name = Payment
description = Defines an API to let payment modules interact with the cart.
-dependencies[] = ca
dependencies[] = uc_order
dependencies[] = uc_store
package = "Ubercart - core (optional)"
=== modified file 'payment/uc_payment/uc_payment.module'
--- payment/uc_payment/uc_payment.module 2010-06-11 19:09:52 +0000
+++ payment/uc_payment/uc_payment.module 2010-06-14 15:27:41 +0000
@@ -15,7 +15,6 @@
require_once('uc_payment_checkout_pane.inc');
require_once('uc_payment_order_pane.inc');
-require_once('uc_payment.ca.inc');
/*******************************************************************************
* Hook Functions (Drupal)
@@ -579,7 +578,7 @@
$account = user_load($uid);
module_invoke_all('uc_payment_entered', $order, $method, $amount, $account, $data, $comment);
- ca_pull_trigger('uc_payment_entered', $order, $account);
+ rules_invoke_event('uc_payment_entered', $order, $account);
}
/**
=== renamed file 'payment/uc_payment/uc_payment.ca.inc' => 'payment/uc_payment/uc_payment.rules.inc'
--- payment/uc_payment/uc_payment.ca.inc 2010-06-03 14:49:55 +0000
+++ payment/uc_payment/uc_payment.rules.inc 2010-06-17 19:25:47 +0000
@@ -3,173 +3,64 @@
/**
* @file
- * This file contains the Conditional Actions hooks and functions necessary to make the
- * order related entity, conditions, events, and actions work.
- */
-
-
-/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
-/**
- * Implement hook_ca_predicate().
- */
-function uc_payment_ca_predicate() {
- $predicates = array();
-
- // Set the order status to "Payment Received" when a payment is received
- // and the balance is less than or equal to 0.
- $predicates['uc_payment_received'] = array(
- '#title' => t('Update order status on full payment'),
- '#description' => t('Only happens when a payment is entered and the balance is <= $0.00.'),
- '#class' => 'payment',
- '#trigger' => 'uc_payment_entered',
- '#status' => 1,
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_payment_condition_order_balance',
- '#title' => t('If the balance is less than or equal to $0.00.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => FALSE,
- 'balance_comparison' => 'less_equal',
- ),
- ),
- array(
- '#name' => 'uc_order_status_condition',
- '#title' => t('If the order status is not already Payment Received.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => TRUE,
- 'order_status' => 'payment_received',
- ),
- ),
- ),
- ),
- '#actions' => array(
- array(
- '#name' => 'uc_order_update_status',
- '#title' => t('Update the order status to Payment Received.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'order_status' => 'payment_received',
- ),
- ),
- ),
- );
-
- // Set the order status to "Completed" when checkout is complete, none
- // of the products are shippable, and the balance is less than or equal to 0.
- $predicates['uc_checkout_complete_paid'] = array(
- '#title' => t('Update order status upon checkout completion with full payment'),
- '#trigger' => 'uc_checkout_complete',
- '#class' => 'payment',
- '#status' => 1,
- '#weight' => 1,
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_payment_condition_order_balance',
- '#title' => t('If the balance is less than or equal to $0.00.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => FALSE,
- 'balance_comparison' => 'less_equal',
- ),
- ),
- array(
- '#name' => 'uc_order_condition_is_shippable',
- '#title' => t('If the order is not shippable.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => TRUE,
- ),
- ),
- ),
- ),
- '#actions' => array(
- array(
- '#name' => 'uc_order_update_status',
- '#title' => t('Update the order status to Completed.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'order_status' => 'completed',
- ),
- )
- ),
- );
-
- return $predicates;
-}
-
-/**
- * Implement hook_ca_trigger().
- */
-function uc_payment_ca_trigger() {
- $triggers['uc_payment_entered'] = array(
- '#title' => t('A payment gets entered for an order'),
- '#category' => t('Payment'),
- '#arguments' => array(
+ * Rules definitions.
+ */
+
+/**
+ * Implement hook_rules_event_info().
+ */
+function uc_payment_rules_event_info() {
+ $events['uc_payment_entered'] = array(
+ 'label' => t('A payment gets entered for an order'),
+ 'group' => t('Payment'),
+ 'variables' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
),
'account' => array(
- '#entity' => 'user',
- '#title' => t('User'),
+ 'type' => 'user',
+ 'label' => t('User'),
),
),
);
- return $triggers;
+ return $events;
}
/**
- * Implement hook_ca_condition().
+ * Implement hook_rules_condition_info().
*/
-function uc_payment_ca_condition() {
+function uc_payment_rules_condition_info() {
$conditions['uc_payment_condition_order_balance'] = array(
- '#title' => t('Check the order balance'),
- '#category' => t('Payment'),
- '#callback' => 'uc_payment_condition_order_balance',
- '#arguments' => array('order' => array('#entity' => 'uc_order')),
- );
- $conditions['uc_order_condition_payment_method'] = array(
- '#title' => t('Check the payment method'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_condition_payment_method',
- '#arguments' => array('order' => array('#entity' => 'uc_order')),
+ 'label' => t('Check the order balance'),
+ 'group' => t('Payment'),
+ 'base' => 'uc_payment_condition_order_balance',
+ 'parameter' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ 'restriction' => 'selector',
+ ),
+ 'balance_comparison' => array(
+ 'type' => 'text',
+ 'label' => t('Operator'),
+ 'options list' => 'uc_payment_condition_balance_options',
+ 'restriction' => 'input',
+ ),
+ ),
);
return $conditions;
}
-
-/******************************************************************************
- * Condition Callbacks and Forms *
- ******************************************************************************/
-
-// Check the current order balance.
-function uc_payment_condition_order_balance($order, $settings) {
+/**
+ * Condition: Check the current order balance.
+ */
+function uc_payment_condition_order_balance($order, $balance_comparison) {
$balance = uc_payment_balance($order);
- switch ($settings['balance_comparison']) {
+ switch ($balance_comparison) {
case 'less':
return $balance < 0;
case 'less_equal':
@@ -181,7 +72,7 @@
}
}
-function uc_payment_condition_order_balance_form($form_state, $settings = array()) {
+function uc_payment_condition_balance_options() {
$context = array(
'revision' => 'formatted',
'type' => 'amount',
@@ -194,32 +85,6 @@
'greater' => t('Balance is greater than !zero.', $zero),
);
- $form['balance_comparison'] = array(
- '#type' => 'radios',
- '#title' => t('Balance comparison type'),
- '#options' => $options,
- '#default_value' => isset($settings['balance_comparison']) ? $settings['balance_comparison'] : 'equal',
- );
-
- return $form;
-}
-
-// Check the order payment method.
-function uc_order_condition_payment_method($order, $settings) {
- return $order->payment_method == $settings['payment_method'];
-}
-
-function uc_order_condition_payment_method_form($form_state, $settings = array()) {
- foreach (_uc_payment_method_list() as $method) {
- $options[$method['id']] = $method['title'];
- }
-
- $form['payment_method'] = array(
- '#type' => 'radios',
- '#title' => t('Payment method'),
- '#options' => $options,
- '#default_value' => $settings['payment_method'],
- );
-
- return $form;
-}
+ return $options;
+}
+
=== added file 'payment/uc_payment/uc_payment.rules_defaults.inc'
--- payment/uc_payment/uc_payment.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ payment/uc_payment/uc_payment.rules_defaults.inc 2010-06-21 18:52:52 +0000
@@ -0,0 +1,59 @@
+label = t('Update order status on full payment');
+ $rule->active = TRUE;
+ $rule->event('uc_payment_entered')
+ ->condition(rules_and()
+ ->condition('uc_payment_condition_order_balance', array(
+ 'order:select' => 'order',
+ 'balance_comparison' => 'less_equal',
+ ))
+ ->condition(rules_condition('data_is', array(
+ 'data:select' => 'order:order_status',
+ 'value' => 'in_checkout',
+ ))
+ ->negate()))
+ ->action('uc_order_update_status', array(
+ 'order:select' => 'order',
+ 'order_status' => 'payment_received',
+ ));
+ $configs['uc_payment_received'] = $rule;
+
+ // Set the order status to "Completed" when checkout is complete, none
+ // of the products are shippable, and the balance is less than or equal to 0.
+ $rule = rules_reaction_rule();
+ $rule->label = t('Update order status upon checkout completion with full payment');
+ $rule->active = TRUE;
+ $rule->event('uc_checkout_complete')
+ ->condition(rules_and()
+ ->condition('uc_payment_condition_order_balance', array(
+ 'order:select' => 'order',
+ 'balance_comparison' => 'less_equal',
+ ))
+ ->condition(rules_condition('uc_order_condition_is_shippable', array(
+ 'order:select' => 'order',
+ ))->negate())
+ )
+ ->action('uc_order_update_status', array(
+ 'order:select' => 'order',
+ 'order_status' => 'payment_received',
+ ));
+ $configs['uc_checkout_complete_paid'] = $rule;
+
+ return $configs;
+}
=== modified file 'shipping/uc_flatrate/uc_flatrate.admin.inc'
--- shipping/uc_flatrate/uc_flatrate.admin.inc 2010-04-05 14:37:18 +0000
+++ shipping/uc_flatrate/uc_flatrate.admin.inc 2010-06-10 19:18:33 +0000
@@ -27,12 +27,11 @@
$row[] = uc_price($method->base_rate, $context);
$row[] = uc_price($method->product_rate, $context);
$row[] = l(t('edit'), 'admin/store/settings/quotes/methods/flatrate/' . $method->mid);
- $row[] = l(t('conditions'), CA_UI_PATH . '/uc_flatrate_get_quote_' . $method->mid . '/edit/conditions');
$rows[] = $row;
}
if (count($rows)) {
- $header = array(t('Title'), t('Label'), t('Base rate'), t('Default product rate'), array('data' => t('Operations'), 'colspan' => 2));
+ $header = array(t('Title'), t('Label'), t('Base rate'), t('Default product rate'), array('data' => t('Operations'), 'colspan' => 1));
$build['methods'] = array(
'#theme' => 'table',
'#header' => $header,
@@ -101,6 +100,11 @@
'#field_suffix' => $sign_flag ? $currency_sign : '',
);
+ $conditions = rules_config_load('get_quote_from_flatrate_' . $mid);
+ if ($conditions) {
+ $conditions->form($form, $form_state);
+ }
+
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
@@ -195,7 +199,8 @@
db_delete('uc_flatrate_products')
->condition('mid', $form_state['values']['mid'])
->execute();
- ca_delete_predicate('uc_flatrate_get_quote_' . $form_state['values']['mid']);
+
+ rules_config_delete('get_quote_from_flatrate_' . $form_state['values']['mid']);
$enabled = variable_get('uc_quote_enabled', array());
unset($enabled['flatrate_' . $form_state['values']['mid']]);
=== modified file 'shipping/uc_flatrate/uc_flatrate.module'
--- shipping/uc_flatrate/uc_flatrate.module 2010-04-05 14:37:18 +0000
+++ shipping/uc_flatrate/uc_flatrate.module 2010-06-10 15:53:53 +0000
@@ -167,41 +167,6 @@
******************************************************************************/
/**
- * Implement hook_ca_predicate().
- *
- * Connect the quote action with the quote event.
- */
-function uc_flatrate_ca_predicate() {
- $enabled = variable_get('uc_quote_enabled', array());
- $predicates = array();
-
- $result = db_query("SELECT mid, title FROM {uc_flatrate_methods}");
- foreach ($result as $method) {
- // Ensure default status is set.
- $enabled += array('flatrate_' . $method->mid => TRUE);
-
- $predicates['uc_flatrate_get_quote_' . $method->mid] = array(
- '#title' => t('Shipping quote via @method', array('@method' => $method->title)),
- '#trigger' => 'get_quote_from_flatrate_' . $method->mid,
- '#class' => 'uc_flatrate',
- '#status' => $enabled['flatrate_' . $method->mid],
- '#actions' => array(
- array(
- '#name' => 'uc_quote_action_get_quote',
- '#title' => t('Fetch a flatrate shipping quote.'),
- '#argument_map' => array(
- 'order' => 'order',
- 'method' => 'method',
- ),
- ),
- ),
- );
- }
-
- return $predicates;
-}
-
-/**
* Implement hook_uc_shipping_method().
*/
function uc_flatrate_uc_shipping_method() {
=== modified file 'shipping/uc_quote/uc_quote.info'
--- shipping/uc_quote/uc_quote.info 2010-02-02 15:41:04 +0000
+++ shipping/uc_quote/uc_quote.info 2010-06-10 14:30:33 +0000
@@ -1,11 +1,12 @@
; $Id$
name = Shipping Quotes
description = Retrieve and display quotes for shipping products.
+dependencies[] = rules
dependencies[] = uc_cart
-dependencies[] = ca
package = "Ubercart - core (optional)"
core = 7.x
files[] = uc_quote.module
files[] = uc_quote.install
files[] = uc_quote.admin.inc
files[] = uc_quote.pages.inc
+files[] = uc_quote.rules_defaults.inc
=== added file 'shipping/uc_quote/uc_quote.info.inc'
--- shipping/uc_quote/uc_quote.info.inc 1970-01-01 00:00:00 +0000
+++ shipping/uc_quote/uc_quote.info.inc 2010-06-24 20:39:24 +0000
@@ -0,0 +1,18 @@
+ 'text',
+ 'label' => t('Shipping type'),
+ 'getter callback' => 'uc_product_get_shipping_type', // not a typical callback.
+ );
+}
=== modified file 'shipping/uc_quote/uc_quote.module'
--- shipping/uc_quote/uc_quote.module 2010-06-21 19:17:24 +0000
+++ shipping/uc_quote/uc_quote.module 2010-06-24 20:39:24 +0000
@@ -316,212 +316,6 @@
}
/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
-/**
- * Implement hook_ca_trigger().
- *
- * Register an event for each shipping method. Enabled methods have active
- * configurations.
- */
-function uc_quote_ca_trigger() {
- $methods = module_invoke_all('uc_shipping_method');
- $triggers = array();
- foreach ($methods as $id => $method) {
- $triggers['get_quote_from_' . $id] = array(
- '#title' => t('Getting shipping quote via !method', array('!method' => $method['title'])),
- '#category' => t('Quote'),
- '#hidden' => TRUE,
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- 'method' => array('#entity' => 'quote_method', '#title' => t('Quote method')),
- 'account' => array('#entity' => 'user', '#title' => t('User account')),
- ),
- );
- }
- return $triggers;
-}
-
-/**
- * Implement hook_ca_condition().
- */
-function uc_quote_ca_condition() {
- return array(
- 'uc_quote_condition_product_shipping_type' => array(
- '#title' => t("Order has a product of a particular shipping type"),
- '#category' => t('Order: Product'),
- '#callback' => 'uc_quote_condition_product_shipping_type',
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- ),
- ),
- 'uc_quote_condition_order_shipping_method' => array(
- '#title' => t("Order has a shipping quote from a particular method"),
- '#category' => t('Order: Shipping Quote'),
- '#callback' => 'uc_quote_condition_order_shipping_method',
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- ),
- ),
- );
-}
-
-/**
- * Return TRUE if the order has a product of the chosen shipping type.
- *
- * @see uc_quote_condition_product_shipping_type_form()
- */
-function uc_quote_condition_product_shipping_type($order, $settings) {
- $result = FALSE;
- foreach ($order->products as $product) {
- if ($product->nid && uc_product_get_shipping_type($product) == $settings['type']) {
- $result = TRUE;
- break;
- }
- }
- return $result;
-}
-
-/**
- * Settings form for uc_quote_condition_product_shipping_type().
- *
- * @ingroup forms
- * @see uc_quote_condition_product_shipping_type()
- */
-function uc_quote_condition_product_shipping_type_form($form_state, $settings = array()) {
- $form = array();
-
- $options = array();
- $types = uc_quote_get_shipping_types();
- foreach ($types as $id => $type) {
- $options[$id] = $type['title'];
- }
- $form['type'] = array('#type' => 'select',
- '#title' => t('Shipping type'),
- '#options' => $options,
- '#default_value' => $settings['type'],
- );
-
- return $form;
-}
-
-/**
- * Check an order's shipping method.
- *
- * @see uc_quote_condition_order_shipping_method_form()
- */
-function uc_quote_condition_order_shipping_method($order, $settings) {
- // Check the easy way first.
- if (is_array($order->quote)) {
- return $order->quote['method'] == $settings['method'];
- }
- // Otherwise, look harder.
- if (is_array($order->line_items)) {
- $methods = module_invoke_all('uc_shipping_method');
- $accessorials = $methods[$settings['method']]['quote']['accessorials'];
-
- foreach ($order->line_items as $line_item) {
- if ($line_item['type'] == 'shipping' && in_array($line_item['title'], $accessorials)) {
- return TRUE;
- }
- }
- }
- return FALSE;
-}
-
-/**
- * @ingroup forms
- * @see uc_quote_condition_order_shipping_method()
- */
-function uc_quote_condition_order_shipping_method_form($form_state, $settings = array()) {
- $form = array();
- $methods = module_invoke_all('uc_shipping_method');
- $enabled = variable_get('uc_quote_enabled', array());
-
- $options = array();
- foreach ($methods as $id => $method) {
- $options[$id] = $method['title'];
- if (!isset($enabled[$id]) || !$enabled[$id]) {
- $options[$id] .= ' ' . t('(disabled)');
- }
- }
-
- $form['method'] = array(
- '#type' => 'select',
- '#title' => t('Shipping quote method'),
- '#default_value' => $settings['method'],
- '#options' => $options,
- );
-
- return $form;
-}
-
-/**
- * Implement hook_ca_action().
- */
-function uc_quote_ca_action() {
- return array(
- 'uc_quote_action_get_quote' => array(
- '#title' => t('Fetch a shipping quote'),
- '#category' => t('Quote'),
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- 'method' => array('#entity' => 'quote_method', '#title' => t('Quote method')),
- ),
- ),
- );
-}
-
-/**
- * Retrieve shipping quote.
- *
- * @param $order
- * The order the quote is for.
- * @param $method
- * The shipping method to generate the quote.
- * @return
- * Array of shipping quotes.
- */
-function uc_quote_action_get_quote($order, $method) {
- $details = array();
- foreach ($order as $key => $value) {
- if (substr($key, 0, 9) == 'delivery_') {
- $field = substr($key, 9);
- $details[$field] = $value;
- }
- }
- ob_start();
- // Load include file containing quote callback, if there is one
- if (isset($method['quote']['file'])) {
- $inc_file = drupal_get_path('module', $method['module']) . '/' . $method['quote']['file'];
- if (is_file($inc_file)) {
- require_once $inc_file;
- }
- }
-
- if (function_exists($method['quote']['callback'])) {
- // This feels wrong, but it's the only way I can ensure that shipping
- // methods won't mess up the products in their methods.
- $products = array();
- foreach ($order->products as $key => $item) {
- if (uc_cart_product_is_shippable($item)) {
- $products[$key] = clone $item;
- }
- }
- $quote_data = call_user_func($method['quote']['callback'], $products, $details, $method);
- }
- $messages = ob_get_contents();
- ob_end_clean();
-
- if ($messages && variable_get('uc_quote_log_errors', FALSE)) {
- watchdog('quote', '!messages', array('!messages' => $messages), WATCHDOG_WARNING);
- watchdog('quote', '
@data
', array('@data' => print_r($quote_data, TRUE)), WATCHDOG_WARNING);
- }
- return $quote_data;
-}
-
-/******************************************************************************
* Ubercart Hooks *
******************************************************************************/
@@ -597,7 +391,6 @@
'method' => $order->quote['method'],
'accessorials' => $order->quote['accessorials'],
'rate' => $order->quote['rate'],
- 'quote_form' => $order->quote['quote_form'],
))
->execute();
}
=== modified file 'shipping/uc_quote/uc_quote.pages.inc'
--- shipping/uc_quote/uc_quote.pages.inc 2010-06-03 20:06:32 +0000
+++ shipping/uc_quote/uc_quote.pages.inc 2010-06-10 14:30:33 +0000
@@ -52,28 +52,9 @@
);
$quote_data = array();
- $arguments = array(
- 'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- '#data' => $order,
- ),
- 'method' => array(
- '#entity' => 'quote_method',
- '#title' => t('Quote method'),
- // #data => each $method in the following foreach() loop;
- ),
- 'account' => array(
- '#entity' => 'user',
- '#title' => t('User'),
- '#data' => $account,
- ),
- );
foreach ($methods as $method) {
- $arguments['method']['#data'] = $method;
- $predicates = ca_load_trigger_predicates('get_quote_from_' . $method['id']);
- $predicate = array_shift($predicates);
- if ($predicate && ca_evaluate_conditions($predicate, $arguments)) {
+ $set = rules_config_load('get_quote_from_' . $method['id']);
+ if (!$set || $set->execute($order)) {
$data = uc_quote_action_get_quote($order, $method, $user);
foreach ($data as &$quote) {
@@ -94,3 +75,51 @@
return $quote_data;
}
+/**
+ * Retrieve shipping quote.
+ *
+ * @param $order
+ * The order the quote is for.
+ * @param $method
+ * The shipping method to generate the quote.
+ * @return
+ * Array of shipping quotes.
+ */
+function uc_quote_action_get_quote($order, $method) {
+ $details = array();
+ foreach ($order as $key => $value) {
+ if (substr($key, 0, 9) == 'delivery_') {
+ $field = substr($key, 9);
+ $details[$field] = $value;
+ }
+ }
+ ob_start();
+ // Load include file containing quote callback, if there is one
+ if (isset($method['quote']['file'])) {
+ $inc_file = drupal_get_path('module', $method['module']) . '/' . $method['quote']['file'];
+ if (is_file($inc_file)) {
+ require_once $inc_file;
+ }
+ }
+
+ if (function_exists($method['quote']['callback'])) {
+ // This feels wrong, but it's the only way I can ensure that shipping
+ // methods won't mess up the products in their methods.
+ $products = array();
+ foreach ($order->products as $key => $item) {
+ if (uc_cart_product_is_shippable($item)) {
+ $products[$key] = clone $item;
+ }
+ }
+ $quote_data = call_user_func($method['quote']['callback'], $products, $details, $method);
+ }
+ $messages = ob_get_contents();
+ ob_end_clean();
+
+ if ($messages && variable_get('uc_quote_log_errors', FALSE)) {
+ watchdog('quote', '!messages', array('!messages' => $messages), WATCHDOG_WARNING);
+ watchdog('quote', '@data
', array('@data' => print_r($quote_data, TRUE)), WATCHDOG_WARNING);
+ }
+ return $quote_data;
+}
+
=== added file 'shipping/uc_quote/uc_quote.rules.inc'
--- shipping/uc_quote/uc_quote.rules.inc 1970-01-01 00:00:00 +0000
+++ shipping/uc_quote/uc_quote.rules.inc 2010-06-24 20:39:24 +0000
@@ -0,0 +1,67 @@
+ array(
+ 'label' => t("Order has a shipping quote from a particular method"),
+ 'group' => t('Order: Shipping Quote'),
+ 'base' => 'uc_quote_condition_order_shipping_method',
+ 'parameter' => array(
+ 'order' => array('type' => 'uc_order', 'label' => t('Order')),
+ 'method' => array('type' => 'text', 'label' => t('Shipping method'), 'options list' => 'uc_quote_condition_order_shipping_method_options'),
+ ),
+ ),
+ );
+}
+
+/**
+ * Check an order's shipping method.
+ */
+function uc_quote_condition_order_shipping_method($order, $method) {
+ // Check the easy way first.
+ if (is_array($order->quote)) {
+ return $order->quote['method'] == $method;
+ }
+ // Otherwise, look harder.
+ if (is_array($order->line_items)) {
+ $methods = module_invoke_all('uc_shipping_method');
+ $accessorials = $methods[$method]['quote']['accessorials'];
+
+ foreach ($order->line_items as $line_item) {
+ if ($line_item['type'] == 'shipping' && in_array($line_item['title'], $accessorials)) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Options callback.
+ *
+ * @see uc_quote_condition_order_shipping_method()
+ */
+function uc_quote_condition_order_shipping_method_options() {
+ $methods = module_invoke_all('uc_shipping_method');
+ $enabled = variable_get('uc_quote_enabled', array());
+
+ $options = array();
+ foreach ($methods as $id => $method) {
+ $options[$id] = $method['title'];
+ if (!isset($enabled[$id]) || !$enabled[$id]) {
+ $options[$id] .= ' ' . t('(disabled)');
+ }
+ }
+
+ return $options;
+}
+
=== added file 'shipping/uc_quote/uc_quote.rules_defaults.inc'
--- shipping/uc_quote/uc_quote.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ shipping/uc_quote/uc_quote.rules_defaults.inc 2010-06-10 19:17:11 +0000
@@ -0,0 +1,22 @@
+ array('type' => 'uc_order', 'label' => 'Order'),
+ ));
+ $set->label = t('@method conditions', array('@method' => $method['title']));
+
+ $configs['get_quote_from_' . $method['id']] = $set;
+ }
+
+ return $configs;
+}
=== modified file 'shipping/uc_shipping/uc_shipping.module'
--- shipping/uc_shipping/uc_shipping.module 2010-06-03 14:04:58 +0000
+++ shipping/uc_shipping/uc_shipping.module 2010-06-23 20:36:18 +0000
@@ -8,8 +8,6 @@
* and tracking numbers.
*/
-require_once('uc_shipping.ca.inc');
-
/******************************************************************************
* Drupal hooks *
******************************************************************************/
@@ -627,7 +625,7 @@
module_invoke_all('uc_shipment', 'save', $shipment);
$order = uc_order_load($shipment->order_id);
- ca_pull_trigger('uc_shipment_save', $order, $shipment);
+ rules_invoke_event('uc_shipment_save', $order, $shipment);
}
/**
=== renamed file 'shipping/uc_shipping/uc_shipping.ca.inc' => 'shipping/uc_shipping/uc_shipping.rules.inc'
--- shipping/uc_shipping/uc_shipping.ca.inc 2009-08-17 18:54:05 +0000
+++ shipping/uc_shipping/uc_shipping.rules.inc 2010-06-24 18:23:23 +0000
@@ -3,35 +3,218 @@
/**
* @file
- * Conditional actions hooks for uc_shipping.module.
+ * Rules hooks for uc_shipping.module.
*/
-function uc_shipping_ca_entity() {
+function uc_shipping_rules_data_info() {
+ $address_info = uc_address_property_info();
+
$entities['uc_shipment'] = array(
- '#title' => t('Ubercart shipment object'),
- '#type' => 'object',
- '#load' => 'uc_shipping_shipment_load',
- '#save' => 'uc_shipping_shipment_save',
+ 'label' => t('Ubercart shipment object'),
+ 'group' => t('Ubercart'),
+ 'wrap' => TRUE,
+ 'property info' => array(
+ 'sid' => array(
+ 'type' => 'integer',
+ 'label' => t('Shipment ID'),
+ ),
+ 'order-id' => array(
+ 'type' => 'integer',
+ 'label' => t('Order ID'),
+ ),
+ 'origin' => array(
+ 'type' => 'struct',
+ 'label' => t('Origin address'),
+ 'description' => t('The origin location for the shipment.'),
+ 'getter callback' => 'uc_shipping_address_property_get',
+ 'setter callback' => 'uc_shipping_address_property_set',
+ 'setter permission' => 'fulfill orders',
+ 'property info' => $address_info,
+ ),
+ 'destination' => array(
+ 'type' => 'struct',
+ 'label' => t('Destination address'),
+ 'description' => t('The destination location for the shipment.'),
+ 'getter callback' => 'uc_shipping_address_property_get',
+ 'setter callback' => 'uc_shipping_address_property_set',
+ 'setter permission' => 'fulfill orders',
+ 'property info' => $address_info,
+ ),
+ 'shipping-method' => array(
+ 'type' => 'text',
+ 'label' => t('Shipping method'),
+ 'description' => t('The transportation method used to ship.'),
+ ),
+ 'accessorials' => array(
+ 'type' => 'text',
+ 'label' => t('Accessorials'),
+ 'description' => t('Shipping options and special instructions.'),
+ ),
+ 'carrier' => array(
+ 'type' => 'text',
+ 'label' => t('Carrier'),
+ 'description' > t('The company making the delivery.'),
+ ),
+ 'transaction-id' => array(
+ 'type' => 'text',
+ 'label' => t('Transaction ID'),
+ 'description' => t("The carrier's shipment identifier."),
+ ),
+ 'tracking-number' => array(
+ 'type' => 'text',
+ 'label' => t('Tracking number'),
+ 'description' => t('The number used by the carrier to locate the shipment while it is in transit.'),
+ ),
+ 'ship-date' => array(
+ 'type' => 'date',
+ 'label' => t('Ship date'),
+ 'description' => t('The time the shipment was sent out.'),
+ ),
+ 'expected-delivery' => array(
+ 'type' => 'date',
+ 'label' => t('Expected delivery'),
+ 'description' => t('The time the shipment is expected to be delivered'),
+ ),
+ 'cost' => array(
+ 'type' => 'decimal',
+ 'label' => t('Cost'),
+ 'description' => t('The cost of the shipment.'),
+ ),
+ 'packages' => array(
+ 'type' => 'list',
+ 'label' => t('Packages'),
+ 'description' => t('The physical items being shipped.'),
+ 'property info' => array(
+ 'package-id' => array(
+ 'type' => 'integer',
+ 'label' => t('Package ID'),
+ ),
+ 'shipping-type' => array(
+ 'type' => 'text',
+ 'label' => t('Shipping type'),
+ 'description' => t('The basic type of shipment, e.g.: small package, freight, etc.'),
+ ),
+ 'pkg-type' => array(
+ 'type' => 'text',
+ 'label' => t('Package type'),
+ 'description' => t('The type of packaging.'),
+ ),
+ 'length' => array(
+ 'type' => 'decimal',
+ 'label' => t('Length'),
+ 'description' => t('The package length.'),
+ ),
+ 'width' => array(
+ 'type' => 'decimal',
+ 'label' => t('Width'),
+ 'description' => t('The package width.'),
+ ),
+ 'height' => array(
+ 'type' => 'decimal',
+ 'label' => t('Height'),
+ 'description' => t('The package height.'),
+ ),
+ 'value' => array(
+ 'type' => 'decimal',
+ 'label' => t('Value'),
+ 'description' => t('The monetary value of the package contents.'),
+ ),
+ 'tracking-number' => array(
+ 'type' => 'text',
+ 'label' => t('Tracking number'),
+ 'description' => t('The number used by the carrier to locate the shipment while it is in transit.'),
+ ),
+ 'description' => array(
+ 'type' => 'text',
+ 'label' => t('Description'),
+ 'description' => t('The package description'),
+ ),
+ 'weight' => array(
+ 'type' => 'decimal',
+ 'label' => t('Weight'),
+ 'description' => t('The physical weight of the package.'),
+ ),
+ 'products' => array(
+ 'type' => 'list',
+ 'label' => t('Products'),
+ 'description' => t('The package contents.'),
+ ),
+ 'label-image' => array(
+ 'type' => 'file',
+ 'label' => t('Label image'),
+ 'description' => t('An image of the shipping label.'),
+ ),
+ ),
+ ),
+ ),
);
return $entities;
}
-function uc_shipping_ca_trigger() {
- $triggers['uc_shipment_save'] = array(
- '#title' => t('A shipment is saved'),
- '#category' => t('Fulfillment'),
- '#arguments' => array(
+/**
+ * Entity metadata callback to get origin or destination address of an shipment.
+ */
+function uc_shipping_address_property_get($shipment, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'origin':
+ $type = 'o_';
+ break;
+
+ case 'destination':
+ $type = 'd_';
+ break;
+
+ default:
+ return NULL;
+ }
+
+ $address = new UcAddress();
+
+ foreach ($address as $field => $value) {
+ $address->{$field} = $shipment->{$type . $field};
+ }
+
+ return $address;
+}
+
+/**
+ * Entity metadata callback to set origin or destination address of an order.
+ */
+function uc_shipping_address_property_set($shipment, $name, $address) {
+ switch ($name) {
+ case 'origin':
+ $type = 'o_';
+ break;
+
+ case 'destination':
+ $type = 'd_';
+ break;
+
+ default:
+ return;
+ }
+
+ foreach ($address as $field => $value) {
+ $shipment->{$type . $field} = $value;
+ }
+}
+
+function uc_shipping_rules_event_info() {
+ $events['uc_shipment_save'] = array(
+ 'label' => t('A shipment is saved'),
+ 'group' => t('Fulfillment'),
+ 'variables' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
),
'shipment' => array(
- '#entity' => 'uc_shipment',
- '#title' => t('Shipment'),
+ 'type' => 'uc_shipment',
+ 'label' => t('Shipment'),
),
),
);
- return $triggers;
+ return $events;
}
=== modified file 'shipping/uc_ups/uc_ups.admin.inc'
--- shipping/uc_ups/uc_ups.admin.inc 2010-06-03 14:23:08 +0000
+++ shipping/uc_ups/uc_ups.admin.inc 2010-06-10 15:53:53 +0000
@@ -130,9 +130,12 @@
'#description' => t('When enabled, products are insured for their full value.'),
);
- $form['#validate'][] = 'uc_ups_admin_settings_validate';
+ $conditions = rules_config_load('get_quote_from_ups');
+ if ($conditions) {
+ $conditions->form($form, $form_state);
+ }
- return system_settings_form($form);
+ return $form;
}
/**
@@ -159,6 +162,47 @@
}
/**
+ * Submit handler for uc_ups_admin_settings().
+ *
+ * Emulate system_settings_form_submit(), but only on a subset of the form values.
+ *
+ * @see uc_ups_admin_settings()
+ */
+function uc_ups_admin_settings_submit($form, &$form_state) {
+ $fields = array(
+ 'uc_ups_access_license',
+ 'uc_ups_shipper_number',
+ 'uc_ups_user_id',
+ 'uc_ups_password',
+ 'uc_ups_connection_address',
+ 'uc_ups_services',
+ 'uc_ups_pickup_type',
+ 'uc_ups_classification',
+ 'uc_ups_negotiated_rates',
+ 'uc_ups_residential_quotes',
+ 'uc_ups_markup_type',
+ 'uc_ups_markup',
+ 'uc_ups_all_in_one',
+ 'uc_ups_unit_system',
+ 'uc_ups_insurance',
+ );
+
+ foreach ($fields as $key) {
+ $value = $form_state['values'][$key];
+
+ if (is_array($value) && isset($form_state['values']['array_filter'])) {
+ $value = array_keys(array_filter($value));
+ }
+ variable_set($key, $value);
+ }
+
+ drupal_set_message(t('The configuration options have been saved.'));
+
+ cache_clear_all();
+ drupal_theme_rebuild();
+}
+
+/**
* Last chance for user to review shipment.
*
* @ingroup forms
=== modified file 'shipping/uc_ups/uc_ups.module'
--- shipping/uc_ups/uc_ups.module 2010-06-03 14:23:08 +0000
+++ shipping/uc_ups/uc_ups.module 2010-06-10 15:53:53 +0000
@@ -207,40 +207,6 @@
}
/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
-/**
- * Implement hook_ca_predicate().
- *
- * Connect the UPS quote action and event.
- */
-function uc_ups_ca_predicate() {
- $enabled = variable_get('uc_quote_enabled', array()) + array('ups' => FALSE);
-
- $predicates = array(
- 'uc_ups_get_quote' => array(
- '#title' => t('Shipping quote from UPS'),
- '#trigger' => 'get_quote_from_ups',
- '#class' => 'uc_ups',
- '#status' => $enabled['ups'],
- '#actions' => array(
- array(
- '#name' => 'uc_quote_action_get_quote',
- '#title' => t('Fetch a shipping quote'),
- '#argument_map' => array(
- 'order' => 'order',
- 'method' => 'method',
- ),
- ),
- ),
- ),
- );
-
- return $predicates;
-}
-
-/******************************************************************************
* Ubercart Hooks *
******************************************************************************/
=== modified file 'shipping/uc_usps/uc_usps.admin.inc'
--- shipping/uc_usps/uc_usps.admin.inc 2010-03-16 21:15:17 +0000
+++ shipping/uc_usps/uc_usps.admin.inc 2010-06-10 15:53:53 +0000
@@ -42,6 +42,17 @@
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
+ $form['domestic']['env_conditions'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Envelope service conditions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $conditions = rules_config_load('get_quote_from_usps_env');
+ if ($conditions) {
+ $conditions->form($form['domestic']['env_conditions'], $form_state);
+ }
+
$form['domestic']['uc_usps_services'] = array(
'#type' => 'checkboxes',
'#title' => t('USPS parcel services'),
@@ -50,6 +61,17 @@
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
+ $form['domestic']['parcel_conditions'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Parcel service conditions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $conditions = rules_config_load('get_quote_from_usps');
+ if ($conditions) {
+ $conditions->form($form['domestic']['parcel_conditions'], $form_state);
+ }
+
$form['international'] = array(
'#type' => 'fieldset',
'#title' => t('USPS International'),
@@ -66,6 +88,17 @@
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
+ $form['international']['env_conditions'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Envelope service conditions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $conditions = rules_config_load('get_quote_from_usps_intl_env');
+ if ($conditions) {
+ $conditions->form($form['international']['env_conditions'], $form_state);
+ }
+
$form['international']['uc_usps_intl_services'] = array(
'#type' => 'checkboxes',
'#title' => t('USPS international parcel services'),
@@ -74,6 +107,17 @@
'#description' => t('Select the USPS services that are available to customers. Be sure to include the services that the Postal Service agrees are available to you.'),
);
+ $form['international']['parcel_conditions'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Parcel service conditions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $conditions = rules_config_load('get_quote_from_usps_intl');
+ if ($conditions) {
+ $conditions->form($form['international']['parcel_conditions'], $form_state);
+ }
+
$form['uc_usps_markup_type'] = array(
'#type' => 'select',
'#title' => t('Markup type'),
@@ -101,7 +145,7 @@
'#description' => t('Indicate whether each product is quoted as shipping separately or all in one package. Orders with one kind of product will still use the package quantity to determine the number of packages needed, however.'),
);
- return system_settings_form($form);
+ return $form;
}
/**
@@ -113,3 +157,34 @@
}
}
+/**
+ * Submit handler for uc_usps_admin_settings form.
+ */
+function uc_usps_admin_settings_submit($form, &$form_state) {
+ $fields = array(
+ 'uc_usps_user_id',
+ 'uc_usps_online_rates',
+ 'uc_usps_env_services',
+ 'uc_usps_services',
+ 'uc_usps_intl_env_services',
+ 'uc_usps_intl_services',
+ 'uc_usps_markup_type',
+ 'uc_usps_markup',
+ 'uc_usps_all_in_once',
+ );
+
+ foreach ($fields as $key) {
+ $value = $form_state['values'][$key];
+
+ if (is_array($value) && isset($form_state['values']['array_filter'])) {
+ $value = array_keys(array_filter($value));
+ }
+ variable_set($key, $value);
+ }
+
+ drupal_set_message(t('The configuration options have been saved.'));
+
+ cache_clear_all();
+ drupal_theme_rebuild();
+}
+
=== modified file 'shipping/uc_usps/uc_usps.info'
--- shipping/uc_usps/uc_usps.info 2010-02-02 15:41:04 +0000
+++ shipping/uc_usps/uc_usps.info 2010-06-10 18:09:44 +0000
@@ -8,3 +8,4 @@
files[] = uc_usps.install
files[] = uc_usps.admin.inc
files[] = uc_usps.countries.inc
+files[] = uc_usps.rules_defaults.inc
=== modified file 'shipping/uc_usps/uc_usps.module'
--- shipping/uc_usps/uc_usps.module 2010-06-03 14:23:08 +0000
+++ shipping/uc_usps/uc_usps.module 2010-06-10 15:53:53 +0000
@@ -157,136 +157,6 @@
}
/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
-/**
- * Implement hook_ca_predicate().
- *
- * Connect USPS quote action and event.
- */
-function uc_usps_ca_predicate() {
- $enabled = variable_get('uc_quote_enabled', array()) + array(
- 'usps' => FALSE,
- 'usps_env' => FALSE,
- 'usps_intl' => FALSE,
- 'usps_intl_env' => FALSE,
- );
- $quote_action = array(
- '#name' => 'uc_quote_action_get_quote',
- '#title' => t('Fetch a shipping quote'),
- '#argument_map' => array(
- 'order' => 'order',
- 'method' => 'method',
- ),
- );
- // Domestic areas include U.S., American Samoa, Guam, Puerto Rico, and the Virgin Islands
- $countries = array(16, 316, 630, 840, 850);
- $predicates = array(
- 'uc_usps_get_quote' => array(
- '#title' => t('Shipping quote from USPS'),
- '#trigger' => 'get_quote_from_usps',
- '#class' => 'uc_usps',
- '#status' => $enabled['usps'],
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_condition_delivery_country',
- '#title' => t('Is in domestic US areas (US, AS, GU, PR, VI)'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'countries' => $countries,
- ),
- ),
- ),
- ),
- '#actions' => array(
- $quote_action,
- ),
- ),
- 'uc_usps_get_env_quote' => array(
- '#title' => t('Shipping quote from USPS'),
- '#trigger' => 'get_quote_from_usps_env',
- '#class' => 'uc_usps',
- '#status' => $enabled['usps_env'],
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_condition_delivery_country',
- '#title' => t('Is in domestic US areas (US, AS, GU, PR, VI)'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'countries' => $countries,
- ),
- ),
- ),
- ),
- '#actions' => array(
- $quote_action,
- ),
- ),
- 'uc_usps_get_intl_quote' => array(
- '#title' => t('Shipping quote from USPS Intl.'),
- '#trigger' => 'get_quote_from_usps_intl',
- '#class' => 'uc_usps',
- '#status' => $enabled['usps_intl'],
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_condition_delivery_country',
- '#title' => t('Is not in domestic US areas (US, AS, GU, PR, VI)'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => TRUE,
- 'countries' => $countries,
- ),
- ),
- ),
- ),
- '#actions' => array(
- $quote_action,
- ),
- ),
- 'uc_usps_get_intl_env_quote' => array(
- '#title' => t('Shipping quote from USPS Intl.'),
- '#trigger' => 'get_quote_from_usps_intl_env',
- '#class' => 'uc_usps',
- '#status' => $enabled['usps_intl_env'],
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_condition_delivery_country',
- '#title' => t('Is not in domestic US areas (US, AS, GU, PR, VI)'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => TRUE,
- 'countries' => $countries,
- ),
- ),
- ),
- ),
- '#actions' => array(
- $quote_action,
- ),
- ),
- );
-
- return $predicates;
-}
-
-/******************************************************************************
* Ubercart Hooks *
******************************************************************************/
=== added file 'shipping/uc_usps/uc_usps.rules_defaults.inc'
--- shipping/uc_usps/uc_usps.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ shipping/uc_usps/uc_usps.rules_defaults.inc 2010-06-10 15:53:53 +0000
@@ -0,0 +1,42 @@
+ t('American Samoa'),
+ 316 => t('Guam'),
+ 630 => t('Puerto Rico'),
+ 840 => t('United States'),
+ 850 => t('Virgin Islands (US)'),
+ );
+
+ $domestic = rules_or();
+ $domestic->label = t('Order delivers to one of');
+ $domestic_env = clone $domestic;
+
+ $foreign = rules_or();
+ $foreign->negate();
+ $foreign->label = t('NOT Order delivers to one of');
+ $foreign_env = clone $foreign;
+
+ foreach ($countries as $country => $name) {
+ $condition = rules_condition('data_is', array('data:select' => 'order:delivery-address:country', 'value' => $country));
+ $condition->label = $name;
+
+ $domestic->condition(clone $condition);
+ $domestic_env->condition(clone $condition);
+ $foreign->condition(clone $condition);
+ $foreign_env->condition($condition);
+ }
+
+
+ $configs['get_quote_from_usps']->condition($domestic);
+ $configs['get_quote_from_usps_env']->condition($domestic_env);
+ $configs['get_quote_from_usps_intl']->condition($foreign);
+ $configs['get_quote_from_usps_intl_env']->condition($foreign_env);
+}
+
=== modified file 'shipping/uc_weightquote/uc_weightquote.admin.inc'
--- shipping/uc_weightquote/uc_weightquote.admin.inc 2010-04-05 14:37:18 +0000
+++ shipping/uc_weightquote/uc_weightquote.admin.inc 2010-06-10 19:18:33 +0000
@@ -4,7 +4,6 @@
/**
* @file
* Weight quote shipping method administration menu items.
- *
*/
/**
@@ -29,15 +28,14 @@
$row[] = uc_price($method->base_rate, $context);
$row[] = uc_price($method->product_rate, $context);
$row[] = l(t('edit'), 'admin/store/settings/quotes/methods/weightquote/' . $method->mid);
- $row[] = l(t('conditions'), CA_UI_PATH . '/uc_weightquote_get_quote_' . $method->mid . '/edit/conditions');
$rows[] = $row;
}
if (count($rows)) {
- $header = array(t('Title'), t('Label'), t('Base rate'), t('Default product rate'), array('data' => t('Operations'), 'colspan' => 2));
+ $header = array(t('Title'), t('Label'), t('Base rate'), t('Default product rate'), array('data' => t('Operations'), 'colspan' => 1));
$build['methods'] = array(
'#theme' => 'table',
'#header' => $header,
- 'rows' => $rows,
+ '#rows' => $rows,
);
}
@@ -105,6 +103,11 @@
'#field_suffix' => t('!sign per !unit', array('!sign' => $sign_flag ? $currency_sign : '', '!unit' => variable_get('uc_weight_unit', 'lb'))),
);
+ $conditions = rules_config_load('get_quote_from_weightquote_' . $mid);
+ if ($conditions) {
+ $conditions->form($form, $form_state);
+ }
+
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
@@ -199,7 +202,8 @@
db_delete('uc_weightquote_products')
->condition('mid', $mid)
->execute();
- ca_delete_predicate('uc_weightquote_get_quote_' . $form_state['values']['mid']);
+
+ rules_config_delete('get_quote_from_weightquote_' . $mid);
$enabled = variable_get('uc_quote_enabled', array());
unset($enabled['weightquote_' . $mid]);
=== modified file 'shipping/uc_weightquote/uc_weightquote.module'
--- shipping/uc_weightquote/uc_weightquote.module 2010-04-05 14:37:18 +0000
+++ shipping/uc_weightquote/uc_weightquote.module 2010-06-10 19:18:33 +0000
@@ -169,41 +169,6 @@
******************************************************************************/
/**
- * Implement hook_ca_predicate().
- *
- * Connect the quote action with the quote event.
- */
-function uc_weightquote_ca_predicate() {
- $enabled = variable_get('uc_quote_enabled', array());
- $predicates = array();
-
- $result = db_query("SELECT mid, title FROM {uc_weightquote_methods}");
- foreach ($result as $method) {
- // Ensure the default status is set.
- $enabled += array('weightquote_' . $method->mid => FALSE);
-
- $predicates['uc_weightquote_get_quote_' . $method->mid] = array(
- '#title' => t('Shipping quote via @method', array('@method' => $method->title)),
- '#trigger' => 'get_quote_from_weightquote_' . $method->mid,
- '#class' => 'uc_weightquote',
- '#status' => $enabled['weightquote_' . $method->mid],
- '#actions' => array(
- array(
- '#name' => 'uc_quote_action_get_quote',
- '#title' => t('Fetch a weightquote shipping quote.'),
- '#argument_map' => array(
- 'order' => 'order',
- 'method' => 'method',
- ),
- ),
- ),
- );
- }
-
- return $predicates;
-}
-
-/**
* Implement hook_uc_shipping_method().
*/
function uc_weightquote_uc_shipping_method() {
=== modified file 'uc_attribute/uc_attribute.module'
--- uc_attribute/uc_attribute.module 2010-04-08 18:27:20 +0000
+++ uc_attribute/uc_attribute.module 2010-06-24 20:49:22 +0000
@@ -13,8 +13,6 @@
* hook system.
*/
-require_once('uc_attribute.ca.inc');
-
/******************************************************************************
* Drupal Hooks *
******************************************************************************/
=== renamed file 'uc_attribute/uc_attribute.ca.inc' => 'uc_attribute/uc_attribute.rules.inc'
--- uc_attribute/uc_attribute.ca.inc 2010-03-03 15:47:24 +0000
+++ uc_attribute/uc_attribute.rules.inc 2010-06-24 20:03:28 +0000
@@ -3,22 +3,23 @@
/**
* @file
- * Conditional Actions file for Ubercart attributes.
+ * Rules hooks for Ubercart attributes.
*/
/**
- * Implement hook_ca_condition().
+ * Implement hook_rules_condition_info().
*/
-function uc_attribute_ca_condition() {
+function uc_attribute_rules_condition_info() {
$conditions = array();
$conditions['uc_attribute_ordered_product_option'] = array(
- '#title' => t('Order has a product with a particular attribute option'),
- '#description' => t('Search the products of an order for a particular option.'),
- '#category' => t('Order: Product'),
- '#callback' => 'uc_attribute_condition_ordered_product_option',
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
+ 'label' => t('Order has a product with a particular attribute option'),
+ 'description' => t('Search the products of an order for a particular option.'),
+ 'group' => t('Order: Product'),
+ 'base' => 'uc_attribute_condition_ordered_product_option',
+ 'parameter' => array(
+ 'product' => array('type' => 'uc_order_product', 'label' => t('Product')),
+ 'option' => array('type' => 'integer', 'label' => t('Attribute option'), 'options list' => 'uc_attribute_condition_ordered_product_options_list'),
),
);
@@ -30,7 +31,7 @@
*
* @see uc_attribute_condition_ordered_product_option_form()
*/
-function uc_attribute_condition_ordered_product_option($order, $settings) {
+function uc_attribute_condition_ordered_product_option($order) {
$result = FALSE;
$match = unserialize($settings['attribute_option']);
@@ -71,21 +72,16 @@
}
/**
+ * Options callback.
+ *
* @see uc_attribute_condition_ordered_product_option()
*/
-function uc_attribute_condition_ordered_product_option_form($form, &$form_state, $settings = array()) {
+function uc_attribute_condition_ordered_product_options_list() {
$options = array();
$result = db_query("SELECT a.aid, a.name AS attr_name, a.ordering, o.oid, o.name AS opt_name, o.ordering FROM {uc_attributes} AS a JOIN {uc_attribute_options} AS o ON a.aid = o.aid ORDER BY a.ordering, o.ordering");
foreach ($result as $option) {
$options[$option->attr_name][$option->oid] = $option->opt_name;
}
- $form['attribute_option'] = array(
- '#type' => 'select',
- '#title' => t('Attribute option'),
- '#default_value' => $settings['attribute_option'],
- '#options' => $options,
- );
-
- return $form;
+ return $options;
}
=== modified file 'uc_cart/uc_cart.info'
--- uc_cart/uc_cart.info 2010-02-25 16:24:47 +0000
+++ uc_cart/uc_cart.info 2010-06-21 19:34:42 +0000
@@ -1,7 +1,6 @@
; $Id$
name = Cart
description = REQUIRED. Controls the shopping cart for an Ubercart e-commerce site.
-dependencies[] = ca
dependencies[] = uc_order
dependencies[] = uc_product
package = Ubercart - core
@@ -9,7 +8,8 @@
files[] = uc_cart.module
files[] = uc_cart.install
files[] = uc_cart.admin.inc
-files[] = uc_cart.ca.inc
files[] = uc_cart.pages.inc
+files[] = uc_cart.rules.inc
+files[] = uc_cart.rules_defaults.inc
files[] = uc_cart.test
files[] = uc_cart_checkout_pane.inc
=== modified file 'uc_cart/uc_cart.module'
--- uc_cart/uc_cart.module 2010-06-03 14:53:42 +0000
+++ uc_cart/uc_cart.module 2010-06-15 13:40:04 +0000
@@ -12,7 +12,6 @@
*/
require_once('uc_cart_checkout_pane.inc');
-require_once('uc_cart.ca.inc');
/*******************************************************************************
* Hook Functions (Drupal)
@@ -1341,7 +1340,7 @@
unset($_SESSION['cart_order'], $_SESSION['do_complete'], $_SESSION['new_user']);
module_invoke_all('uc_uc_checkout_complete', $order, $account);
- ca_pull_trigger('uc_checkout_complete', $order, $account);
+ rules_invoke_event('uc_checkout_complete', $order);
return array(
'#theme' => 'uc_cart_complete_sale',
=== renamed file 'uc_cart/uc_cart.ca.inc' => 'uc_cart/uc_cart.rules.inc'
--- uc_cart/uc_cart.ca.inc 2010-04-08 21:22:14 +0000
+++ uc_cart/uc_cart.rules.inc 2010-05-27 20:39:22 +0000
@@ -3,157 +3,29 @@
/**
* @file
- * This file contains the Conditional Actions hooks and functions necessary to
+ * This file contains the Rules hooks and functions necessary to
* make the cart related entity, conditions, events, and actions work.
*/
/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
-/**
- * Implement hook_ca_condition().
- */
-function uc_cart_ca_condition() {
- $conditions['uc_cart_condition_product_class'] = array(
- '#title' => t('Order has a product of a particular class'),
- '#category' => t('Order: Product'),
- '#callback' => 'uc_cart_condition_product_class',
- '#arguments' => array(
- 'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- ),
- ),
- );
-
- return $conditions;
-}
-
-/**
- * Implement hook_ca_trigger().
- */
-function uc_cart_ca_trigger() {
- $triggers['uc_checkout_complete'] = array(
- '#title' => t('Customer completes checkout'),
- '#category' => t('Cart'),
- '#arguments' => array(
- 'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- ),
- 'account' => array(
- '#entity' => 'user',
- '#title' => t('Customer user account'),
- )
- ),
- );
-
- return $triggers;
-}
-
-/**
- * Implement hook_ca_predicate().
- */
-function uc_cart_ca_predicate() {
- $predicates = array();
-
- // Setup a default predicate for customer checkout notifications.
- $predicates['uc_checkout_customer_notification'] = array(
- '#title' => t('E-mail customer checkout notification'),
- '#description' => t('E-mail the customer an invoice from their recent order.'),
- '#class' => 'notification',
- '#status' => 1,
- '#trigger' => 'uc_checkout_complete',
- '#actions' => array(
- array(
- '#name' => 'uc_order_email_invoice',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[uc_order:email]',
- 'subject' => t('Your Order at [store:name]'),
- 'template' => 'customer',
- 'view' => 'checkout-mail',
- ),
- ),
- ),
- );
-
- // Setup a default predicate for admin checkout notifications.
- $predicates['uc_checkout_admin_notification'] = array(
- '#title' => t('E-mail admin checkout notification'),
- '#description' => t('E-mail a short order summary to an administrator when a customer checks out.'),
- '#class' => 'notification',
- '#status' => 1,
- '#trigger' => 'uc_checkout_complete',
- '#actions' => array(
- array(
- '#name' => 'uc_order_email_invoice',
- '#title' => t('Send an e-mail to the administrator(s)'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => variable_get('uc_store_email', ''),
- 'subject' => t('New Order at [store:name]'),
- 'template' => 'admin',
- 'view' => 'admin-mail',
- ),
- ),
- ),
- );
-
- return $predicates;
-}
-
-/******************************************************************************
- * Condition Callbacks and Forms *
- ******************************************************************************/
-
-/**
- * Check that an order has a product of the selected class.
- *
- * @see uc_cart_condition_product_class_form()
- */
-function uc_cart_condition_product_class($order, $settings) {
- $result = FALSE;
- $types = array();
- foreach ($order->products as $product) {
- // Ignore "blank line" custom products.
- if ($product->nid) {
- // Cache product types to avoid extra queries.
- if (!isset($types[$product->nid])) {
- if (isset($product->type)) {
- $types[$product->nid] = $product->type;
- }
- else {
- $types[$product->nid] = db_query("SELECT type FROM {node} WHERE nid = :nid", array(':nid' => $product->nid))->fetchField();
- }
- }
- if ($types[$product->nid] == $settings['class']) {
- $result = TRUE;
- break;
- }
- }
- }
-
- return $result;
-}
-
-/**
- * @see uc_cart_condition_product_class()
- */
-function uc_cart_condition_product_class_form($form, &$form_state, $settings = array()) {
- $form['class'] = array('#type' => 'select',
- '#title' => t('Product class'),
- '#options' => uc_product_type_names(),
- '#default_value' => $settings['class'],
- );
-
- return $form;
-}
+ * Rules Hooks *
+ ******************************************************************************/
+
+/**
+ * Implement hook_rules_event_info().
+ */
+function uc_cart_rules_event_info() {
+ $events['uc_checkout_complete'] = array(
+ 'label' => t('Customer completes checkout'),
+ 'group' => t('Cart'),
+ 'variables' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
+ ),
+ );
+
+ return $events;
+}
+
=== added file 'uc_cart/uc_cart.rules_defaults.inc'
--- uc_cart/uc_cart.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ uc_cart/uc_cart.rules_defaults.inc 2010-06-24 18:22:38 +0000
@@ -0,0 +1,47 @@
+label = t('E-mail customer checkout notification');
+ $rule->active = TRUE;
+ $rule->event('uc_checkout_complete')
+ ->action('uc_order_email_invoice', array(
+ 'order:select' => 'order',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[order:email]',
+ 'subject' => t('Your Order at [store:name]'),
+ 'template' => 'customer',
+ 'view' => 'checkout-mail',
+ ));
+
+ $configs['uc_checkout_customer_notification'] = $rule;
+
+ // Setup a default predicate for admin checkout notifications.
+ $rule = rules_reaction_rule();
+ $rule ->label = t('E-mail admin checkout notification');
+ $rule->active = TRUE;
+ $rule->event('uc_checkout_complete')
+ ->action('uc_order_email_invoice', array(
+ 'order:select' => 'order',
+ 'from' => uc_store_email_from(),
+ 'addresses' => variable_get('uc_store_email', ''),
+ 'subject' => t('New Order at [store:name]'),
+ 'template' => 'admin',
+ 'view' => 'admin-mail',
+ ));
+
+ $configs['uc_checkout_admin_notification'] = $rule;
+
+ return $configs;
+}
+
=== modified file 'uc_file/uc_file.install'
--- uc_file/uc_file.install 2010-04-01 18:42:54 +0000
+++ uc_file/uc_file.install 2010-06-21 18:54:19 +0000
@@ -147,7 +147,7 @@
'file_key' => array(
'description' => 'A hash of the data in this row.',
'type' => 'varchar',
- 'length' => 32,
+ 'length' => 64,
'not null' => TRUE,
'default' => '',
),
@@ -227,3 +227,15 @@
return t("Variable 'uc_file_file_mask' changed to @mask.", array('@mask', $mask));
}
+
+/**
+ * Increase the length of {uc_file_users}.file_key.
+ */
+function uc_file_update_7001() {
+ db_change_field('uc_file_users', 'file_key', 'file_key', array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ));
+}
=== modified file 'uc_file/uc_file.module'
--- uc_file/uc_file.module 2010-04-26 18:08:32 +0000
+++ uc_file/uc_file.module 2010-06-21 19:08:20 +0000
@@ -14,8 +14,6 @@
* Development sponsored by the Ubercart project. http://www.ubercart.org
*/
-require_once('uc_file.ca.inc');
-
/**
* The max amount of files shown on any page that displays files
* in a table/pager.
@@ -28,15 +26,6 @@
******************************************************************************/
/**
- * Implement hook_help().
- */
-function uc_file_help($path, $arg) {
- if ($path == 'node/%/edit/features' && $arg[4] == 'file') {
- return t('Add file downloads through this page and then use the conditional actions interface to limit which orders they are applied to. Most important is the order status on which file download access will be triggered.', array('!url' => url(CA_UI_PATH)));
- }
-}
-
-/**
* Implement hook_form_alter().
*/
function uc_file_form_alter(&$form, &$form_state, $form_id) {
@@ -712,10 +701,24 @@
$granularity_value = $time_status ? $file_product->time_granularity : 'never';
}
else {
- $default_shippable = $node->shippable;
- $download_status = FALSE;
- $address_status = FALSE;
- $time_status = FALSE;
+ $file_product = FALSE;
+
+ $default_feature = NULL;
+
+ $default_model = '';
+ $default_filename = '';
+ $default_description = '';
+ $default_shippable = $node->shippable;
+
+ $download_status = FALSE;
+ $download_value = NULL;
+
+ $address_status = FALSE;
+ $address_value = NULL;
+
+ $time_status = FALSE;
+ $quantity_value = NULL;
+ $granularity_value = 'never';
}
$form['nid'] = array(
@@ -1690,7 +1693,7 @@
$file_user['expiration'] = $file_user['expiration'] ? $file_user['expiration'] : 0;
// Calculate hash
- $file_user['file_key'] = $file_user['file_key'] ? $file_user['file_key'] : drupal_get_token(serialize($file_user));
+ $file_user['file_key'] = isset($file_user['file_key']) && $file_user['file_key'] ? $file_user['file_key'] : drupal_get_token(serialize($file_user));
// Write and queue the file_user object.
drupal_write_record('uc_file_users', $file_user, $key);
=== renamed file 'uc_file/uc_file.ca.inc' => 'uc_file/uc_file.rules.inc'
--- uc_file/uc_file.ca.inc 2010-04-26 18:08:32 +0000
+++ uc_file/uc_file.rules.inc 2010-06-24 17:26:23 +0000
@@ -3,130 +3,174 @@
/**
* @file
- * This file contains the Conditional Actions hooks and functions necessary to
- * make the file-related entity, conditions, events, and actions work.
+ * Rules hooks and functions for uc_file.module.
*/
-/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
/**
- * Implement hook_ca_entity().
+ * Implement hook_rules_data_info().
*
* An entity is defined in order to get file expiration(s) down to token in the
* email.
*/
-function uc_file_ca_entity() {
-
- // CA entity for a file expiration object.
- $entities['uc_file_expiration'] = array(
- '#title' => t('Ubercart file expiration(s)'),
- '#type' => 'array',
- );
-
- return $entities;
-}
-
-/**
- * Implement hook_ca_predicate().
- */
-function uc_file_ca_predicate() {
-
- // Renew all the files on an order when the status matches what's set in the files admin settings.
- $configurations['uc_file_renewal'] = array(
- '#title' => t('Renew purchased files'),
- '#description' => t('Renew purchased files if the order status matches.'),
- '#class' => 'renewal',
- '#trigger' => 'uc_order_status_update',
- '#status' => 1,
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_status_condition',
- '#title' => t('If the order status is completed.'),
- '#argument_map' => array(
- 'order' => 'updated_order',
- ),
- '#settings' => array(
- 'order_status' => 'completed',
- ),
- ),
- ),
- ),
- '#actions' => array(
- array(
- '#name' => 'uc_file_order_renew',
- '#title' => t('Update all file expirations for this order.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- ),
- ),
- );
-
- $order_args = array(
- 'order' => 'order',
- 'expiration' => 'expiration',
- );
-
- // Notify the user when a file is granted.
- $configurations['uc_file_notify_grant_trigger'] = array(
- '#title' => t('Notify customer when a file is granted'),
- '#description' => t('Notify the customer when they have had a file granted on their user.'),
- '#class' => 'notification',
- '#trigger' => 'uc_file_notify_grant',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_file_order_email',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => $order_args,
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[uc_order:email]',
- 'subject' => t("File Downloads for Order# [uc_order:order-id]"),
- 'message' => t("Your order (order# [uc_order:link]) at [store:name] included file download(s). You may access them with the following link(s):\n\n[uc_file:downloads]\n\nAfter downloading these files these links will have expired. If you need to download the files again, you can login at [site:login-link] and visit the \"My Account\" section of the site.\n\nThanks again, \n\n[store:name]\n[site:slogan]"),
- 'format' => filter_default_format(),
- ),
- ),
- ),
- );
-
- return $configurations;
-}
-
-/**
- * Implement hook_ca_action().
- */
-function uc_file_ca_action() {
-
+function uc_file_rules_data_info() {
+ $data['uc_file_expiration'] = array(
+ 'label' => t('Ubercart file expiration(s)'),
+ 'wrap' => TRUE,
+ 'property info' => array(
+ 'fuid' => array(
+ 'type' => 'integer',
+ 'label' => t('File-user ID'),
+ 'description' => t('Primary key for user permission to download a file.'),
+ ),
+ 'fid' => array(
+ 'type' => 'integer',
+ 'label' => t('File ID'),
+ 'description' => t('The file that may be downloaded.'),
+ ),
+ 'file' => array(
+ 'type' => 'file',
+ 'label' => t('File'),
+ 'description' => t('The file that may be downloaded.'),
+ 'getter callback' => 'uc_file_get_expiration_properties',
+ 'setter callback' => 'uc_file_set_expiration_properties',
+ ),
+ 'uid' => array(
+ 'type' => 'integer',
+ 'label' => t('User ID'),
+ 'description' => t('The user account ID.'),
+ ),
+ 'user' => array(
+ 'type' => 'user',
+ 'label' => t('User'),
+ 'description' => t('The user account that may download the file.'),
+ 'getter callback' => 'uc_file_get_expiration_properties',
+ 'setter callback' => 'uc_file_set_expiration_properties',
+ ),
+ 'pfid' => array(
+ 'type' => 'integer',
+ 'label' => t('Product feature ID'),
+ 'description' => t('The product feature that granted permission to download the file.'),
+ ),
+ 'file-key' => array(
+ 'type' => 'text',
+ 'label' => t('File key'),
+ 'description' => t('A hash of the permission and expiraiton data.'),
+ ),
+ 'granted' => array(
+ 'type' => 'date',
+ 'label' => t('Granted time'),
+ 'description' => t('The time the permission to download the file was granted.'),
+ ),
+ 'expiration' => array(
+ 'type' => 'date',
+ 'label' => t('Expiration time'),
+ 'description' => t('The time when the permission to download the file will expire.'),
+ ),
+ 'accessed' => array(
+ 'type' => 'integer',
+ 'label' => t('Accesses'),
+ 'description' => t('The number of times the file has been accessed.'),
+ ),
+ 'addresses' => array(
+ 'type' => 'list',
+ 'label' => t('IP addresses'),
+ 'description' => t('List of IP addresses to which the file has been downloaded.'),
+ ),
+ 'download-limit' => array(
+ 'type' => 'integer',
+ 'label' => t('Download limit'),
+ 'description' => t('The numer of times the user may download the file.'),
+ ),
+ 'address-limit' => array(
+ 'type' => 'integer',
+ 'label' => t('Address limit'),
+ 'description' => t('The number of different IP addresses that may download the file.'),
+ ),
+ ),
+ 'group' => t('File download'),
+ 'token type' => 'uc_file',
+ );
+
+ return $data;
+}
+
+/**
+ * Callback for getting download expiration properties.
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function uc_file_get_expiration_properties($expiration, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'user':
+ return $expiration->uid;
+ case 'file':
+ return $expiration->fid;
+ }
+}
+
+/**
+ * Callback for setting download expiration properties.
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function uc_file_set_expiration_properties($expiration, $name, $value) {
+ if ($name == 'user') {
+ $expiration->uid = $value;
+ }
+ elseif ($name == 'file') {
+ $expiration->fid = $value;
+ }
+}
+
+/**
+ * Implement hook_rules_action_info().
+ */
+function uc_file_rules_action_info() {
+ // Renew file expirations.
$actions['uc_file_order_renew'] = array(
- '#title' => t('Renew the files on an order.'),
- '#category' => t('renewal'),
- '#callback' => 'uc_file_action_order_renew',
- '#arguments' => array(
+ 'label' => t('Renew the files on an order.'),
+ 'group' => t('renewal'),
+ 'base' => 'uc_file_action_order_renew',
+ 'parameter' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
),
),
);
// Send an email to an order with a file expiration
$actions['uc_file_order_email'] = array(
- '#title' => t('Send an order email regarding files.'),
- '#category' => t('Notification'),
- '#callback' => 'uc_file_action_order_email',
- '#arguments' => array(
+ 'label' => t('Send an order email regarding files.'),
+ 'group' => t('Notification'),
+ 'base' => 'uc_file_action_order_email',
+ 'parameter' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
),
'expiration' => array(
- '#entity' => 'uc_file_expiration',
- '#title' => t('File expiration'),
+ 'type' => 'uc_file_expiration',
+ 'label' => t('File expiration'),
+ ),
+ 'from' => array(
+ 'type' => 'text',
+ 'label' => t('Sender'),
+ ),
+ 'addresses' => array(
+ 'type' => 'text',
+ 'label' => t('Recipients'),
+ ),
+ 'subject' => array(
+ 'type' => 'text',
+ 'label' => t('Subject'),
+ ),
+ 'message' => array(
+ 'type' => 'text',
+ 'label' => t('Message'),
+ ),
+ 'format' => array(
+ 'type' => 'integer',
+ 'label' => t('Message format'),
+ 'options list' => 'uc_file_message_formats',
),
),
);
@@ -135,38 +179,56 @@
}
/**
- * Implement hook_ca_trigger().
- */
-function uc_file_ca_trigger() {
-
- $args = array(
- 'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- ),
- 'expiration' => array(
- '#entity' => 'uc_file_expiration',
- '#title' => t('File expiration'),
- ),
- );
-
- $triggers['uc_file_notify_grant'] = array(
- '#title' => t('E-mail for granted files'),
- '#category' => t('Notification'),
- '#arguments' => $args,
- );
-
- return $triggers;
+ * Options list callback for message formats.
+ */
+function uc_file_message_formats() {
+ global $user;
+
+ $options = array();
+ $formats = filter_formats($user);
+ foreach ($formats as $format) {
+ $options[$format->format] = $format->name;
+ }
+
+ return $options;
+}
+
+/**
+ * Implement hook_rules_event_info().
+ */
+function uc_file_rules_event_info() {
+ $events['uc_file_notify_grant'] = array(
+ 'label' => t('E-mail for granted files'),
+ 'group' => t('Notification'),
+ 'variables' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
+ 'expiration' => array(
+ 'type' => 'uc_file_expiration',
+ 'label' => t('File expiration'),
+ ),
+ ),
+ );
+
+ return $events;
}
/**
* Send an email with order and file replacement tokens.
*
* The recipients, subject, and message fields take order token replacements.
- *
- * @see uc_file_action_order_email_form()
*/
-function uc_file_action_order_email($order, $file_expiration, $settings) {
+function uc_file_action_order_email($order, $file_expiration, $from, $addresses, $subject, $message, $format) {
+ $settings = array(
+ 'from' => $from,
+ 'addresses' => $addresses,
+ 'subject' => $subject,
+ 'message' => $message,
+ 'format' => $format,
+ );
+
// Token replacements for the subject and body
$settings['replacements'] = array(
'uc_order' => $order,
@@ -191,63 +253,6 @@
}
/**
- * @see uc_file_action_order_email()
- */
-function uc_file_action_order_email_form($form_state, $settings = array()) {
- return uc_file_build_email_form($form_state, $settings, array('site', 'store', 'uc_order', 'uc_file'));
-}
-
-/**
- * Build an email action settings form.
- */
-function uc_file_build_email_form($form, &$form_state, $settings, $token_filters) {
- $form['from'] = array(
- '#type' => 'textfield',
- '#title' => t('Sender'),
- '#default_value' => $settings['from'],
- '#description' => t('The "From" address.'),
- '#required' => TRUE,
- );
- $form['addresses'] = array(
- '#type' => 'textarea',
- '#title' => t('Recipients'),
- '#default_value' => $settings['addresses'],
- '#description' => t('Enter the email addresses to receive the notifications, one on each line. You may use order tokens for dynamic email addresses.'),
- '#required' => TRUE,
- );
- $form['subject'] = array(
- '#type' => 'textfield',
- '#title' => t('Subject'),
- '#default_value' => $settings['subject'],
- '#required' => TRUE,
- );
- $form['message'] = array(
- '#type' => 'textarea',
- '#title' => t('Message'),
- '#default_value' => $settings['message'],
- '#text_format' => $settings['format'],
- );
-
- $form['token_help'] = array(
- '#type' => 'fieldset',
- '#title' => t('Replacement patterns'),
- '#description' => t('You can make use of the replacement patterns in the recipients field, the subject, and the message body.'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- foreach ($token_filters as $name) {
- $form['token_help'][$name] = array(
- '#type' => 'fieldset',
- '#title' => t('@name replacement patterns', array('@name' => drupal_ucfirst($name))),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- }
-
- return $form;
-}
-
-/**
* Renew an orders product files.
*
* @param $order
@@ -290,7 +295,7 @@
$file_modification = array(
'download_limit' => uc_file_get_download_limit($file),
'address_limit' => uc_file_get_address_limit($file),
- 'expiration' => _uc_file_expiration_date(uc_file_get_time_limit($file), $file_user->expiration),
+ 'expiration' => _uc_file_expiration_date(uc_file_get_time_limit($file), ($file_user ? $file_user->expiration : NULL)),
);
// Add file_user(s) for this file/directory. (No overwrite)
@@ -312,6 +317,6 @@
// Notify the user of their download(s).
if ($user_downloads) {
- ca_pull_trigger('uc_file_notify_grant', $order, $user_downloads);
+ rules_invoke_event('uc_file_notify_grant', $order, $user_downloads);
}
}
=== added file 'uc_file/uc_file.rules_defaults.inc'
--- uc_file/uc_file.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ uc_file/uc_file.rules_defaults.inc 2010-06-22 16:53:15 +0000
@@ -0,0 +1,42 @@
+label = t('Renew purchased files');
+ $rule->active = TRUE;
+ $rule->event('uc_order_status_update');
+ $rule->condition('data_is', array('data:select' => 'updated_order:order-status', 'value' => 'completed'))
+ ->action('uc_file_order_renew', array(
+ 'order:select' => 'order',
+ ));
+ $configs['uc_file_renewal'] = $rule;
+
+ // Notify the user when a file is granted.
+ $rule = rules_reaction_rule();
+ $rule->label = t('Notify customer when a file is granted');
+ $rule->active = TRUE;
+ $rule->event('uc_file_notify_grant');
+ $rule->action('uc_file_order_email', array(
+ 'order:select' => 'order',
+ 'expiration:select' => 'expiration',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[order:email]',
+ 'subject' => t("File Downloads for Order# [order:order-id]"),
+ 'message' => t("Your order (order# [order:link]) at [store:name] included file download(s). You may access them with the following link(s):\n\n[expiration:downloads]\n\nAfter downloading these files these links will have expired. If you need to download the files again, you can login at [site:login-link] and visit the \"My Account\" section of the site.\n\nThanks again, \n\n[store:name]\n[site:slogan]"),
+ 'format' => filter_default_format(),
+ ));
+ $configs['uc_file_notify_grant_trigger'] = $rule;
+
+ return $configs;
+}
+
=== modified file 'uc_order/uc_order.admin.inc'
--- uc_order/uc_order.admin.inc 2010-05-24 21:22:53 +0000
+++ uc_order/uc_order.admin.inc 2010-06-21 19:34:42 +0000
@@ -99,7 +99,7 @@
$form['customer']['uc_cust_order_invoice_template'] = array(
'#type' => 'select',
'#title' => t('On-site invoice template'),
- '#description' => t('Select the invoice template to use when invoices are viewed on the site.
This is separate from the template used to e-mail invoices to customers which is configured through Conditional actions.', array('!url' => url(CA_UI_PATH))),
+ '#description' => t('Select the invoice template to use when invoices are viewed on the site.
This is separate from the template used to e-mail invoices to customers which is configured through Rules.', array('!url' => url(RULES_UI_PATH))),
'#options' => uc_order_template_options(),
'#summary' => t('You are using the %template order invoice template.', array('%template' => variable_get('uc_cust_order_invoice_template', 'customer'))),
'#default_value' => variable_get('uc_cust_order_invoice_template', 'customer'),
=== modified file 'uc_order/uc_order.api.php'
--- uc_order/uc_order.api.php 2010-06-03 14:04:58 +0000
+++ uc_order/uc_order.api.php 2010-06-21 19:34:42 +0000
@@ -91,7 +91,7 @@
*/
function hook_uc_line_item_alter(&$item, $order) {
$account = user_load($order->uid);
- ca_pull_trigger('calculate_line_item_discounts', $item, $account);
+ rules_invoke_event('calculate_line_item_discounts', $item, $account);
}
/**
@@ -272,6 +272,15 @@
}
/**
+ * Respond to order product deletion.
+ */
+function hook_uc_order_product_delete($order_product_id) {
+ // Put back the stock.
+ $product = db_query("SELECT model, qty FROM {uc_order_products} WHERE order_product_id = :id", array(':id' => $order_product_id))->fetchObject();
+ uc_stock_adjust($product->model, $product->qty);
+}
+
+/**
* Register static order states.
*
* Order states are module-defined categories for order statuses. Each state
=== modified file 'uc_order/uc_order.info'
--- uc_order/uc_order.info 2010-04-05 19:24:56 +0000
+++ uc_order/uc_order.info 2010-06-21 19:34:42 +0000
@@ -1,13 +1,13 @@
; $Id$
name = Order
description = REQUIRED. Receive and manage orders through your website.
-dependencies[] = ca
package = Ubercart - core
core = 7.x
files[] = uc_order.module
files[] = uc_order.install
files[] = uc_order.admin.inc
-files[] = uc_order.ca.inc
files[] = uc_order.line_item.inc
files[] = uc_order.order_pane.inc
+files[] = uc_order.rules.inc
+files[] = uc_order.rules_defaults.inc
files[] = uc_order.tokens.inc
=== added file 'uc_order/uc_order.info.inc'
--- uc_order/uc_order.info.inc 1970-01-01 00:00:00 +0000
+++ uc_order/uc_order.info.inc 2010-06-24 18:23:07 +0000
@@ -0,0 +1,193 @@
+ array(
+ 'properties' => array(
+ 'order-product-id' => array(
+ 'type' => 'integer',
+ 'label' => t('Order product ID'),
+ 'description' => t('The unique ID for the purchased product.'),
+ ),
+ 'order-id' => array(
+ 'type' => 'integer',
+ 'label' => t('Order ID'),
+ 'description' => t('The order ID that owns the product.'),
+ 'required' => TRUE,
+ ),
+ 'nid' => array(
+ 'type' => 'integer',
+ 'label' => t('Node ID'),
+ 'description' => t('The unique ID of the node representing the product.'),
+ 'clear' => array('node'),
+ ),
+ 'node' => array(
+ 'type' => 'node',
+ 'label' => t('Node'),
+ 'description' => t('The node representing the product.'),
+ ),
+ 'title' => array(
+ 'type' => 'text',
+ 'label' => t('Title'),
+ 'description' => t('The product title.'),
+ ),
+ 'model' => array(
+ 'type' => 'text',
+ 'label' => t('Model/SKU'),
+ 'description' => t('The model number of the product.'),
+ ),
+ 'qty' => array(
+ 'type' => 'integer',
+ 'label' => t('Quantity'),
+ 'description' => t('The number of the same product ordered.'),
+ ),
+ 'cost' => array(
+ 'type' => 'decimal',
+ 'label' => t('Cost'),
+ 'description' => t('The cost to the store for the product.'),
+ ),
+ 'price' => array(
+ 'type' => 'decimal',
+ 'label' => t('Price'),
+ 'description' => t('The price paid for the ordered product.'),
+ ),
+ 'weight' => array(
+ 'type' => 'decimal',
+ 'label' => t('Weight'),
+ 'description' => t('The physical weight of the product.'),
+ ),
+ ),
+ ),
+ 'uc_order' => array(
+ 'properties' => array(
+ 'order-id' => array(
+ 'type' => 'integer',
+ 'label' => t('Order ID'),
+ 'description' => t('Primary key: the order ID.'),
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'uid' => array(
+ 'type' => 'integer',
+ 'label' => t('User ID'),
+ 'description' => t('The unique ID of the customer who placed the order.'),
+ 'clear' => array('customer'),
+ 'query callback' => 'entity_metadata_table_query',
+ 'setter permission' => 'edit orders',
+ ),
+ 'customer' => array(
+ 'type' => 'user',
+ 'label' => t('Customer'),
+ 'destription' => t('The user who placed the order.'),
+ 'getter callback' => 'uc_order_uc_order_get_properties',
+ 'setter callback' => 'uc_order_uc_order_set_properties',
+ 'setter permission' => 'edit orders',
+ 'clear' => array('uid'),
+ ),
+ 'delivery-address' => array(
+ 'type' => 'struct',
+ 'label' => t('Delivery address'),
+ 'description' => t('The destination of the shipped products.'),
+ 'getter callback' => 'uc_order_address_property_get',
+ 'setter callback' => 'uc_order_address_property_set',
+ 'setter permission' => 'edit orders',
+ 'property info' => $address_info,
+ ),
+ 'billing-address' => array(
+ 'type' => 'struct',
+ 'label' => t('Billing address'),
+ 'description' => t('The destination of the bill and invoice.'),
+ 'getter callback' => 'uc_order_address_property_get',
+ 'setter callback' => 'uc_order_address_property_set',
+ 'setter permission' => 'edit orders',
+ 'property info' => $address_info,
+ ),
+ 'order-status' => array(
+ 'type' => 'text',
+ 'label' => t('Order status'),
+ 'description' => t('The status of the order.'),
+ 'required' => TRUE,
+ 'options list' => 'uc_order_status_list',
+ 'setter permission' => 'edit orders',
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'order-total' => array(
+ 'type' => 'decimal',
+ 'label' => t('Order total'),
+ 'description' => t('The total amount due for the order.'),
+ 'getter callback' => 'uc_order_get_total',
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'primary-email' => array(
+ 'type' => 'text',
+ 'label' => t('Primary email'),
+ 'description' => t('The primary email address of the customer.'),
+ 'setter permission' => 'edit orders',
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'payment-method' => array(
+ 'type' => 'text',
+ 'label' => t('Payment method'),
+ 'description' => t('The method of payment.'),
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'created' => array(
+ 'type' => 'date',
+ 'label' => t('Date created'),
+ 'description' => t('The date the order was placed.'),
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'modified' => array(
+ 'type' => 'date',
+ 'label' => t('Date modified'),
+ 'description' => t('The date the order was most recently changed.'),
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'host' => array(
+ 'type' => 'text',
+ 'label' => t('Host IP'),
+ 'description' => t('Host IP address of the person paying for the order.'),
+ 'query callback' => 'entity_metadata_table_query',
+ ),
+ 'products' => array(
+ 'type' => 'list',
+ 'label' => t('Products'),
+ 'description' => t('The products that have been ordered.'),
+ 'getter callback' => 'uc_order_get_properties',
+ 'setter callback' => 'uc_order_set_properties',
+ 'setter permission' => 'edit orders',
+ 'clear' => array('order_total'),
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * Callback for getting node properties.
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function uc_order_uc_order_get_properties($order, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'customer':
+ return $order->uid;
+ }
+}
+
+/**
+ * Callback for setting node properties.
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function uc_order_uc_order_set_properties($order, $name, $value) {
+ if ($name == 'customer') {
+ $order->uid = $value;
+ }
+}
+
=== modified file 'uc_order/uc_order.module'
--- uc_order/uc_order.module 2010-05-24 19:55:46 +0000
+++ uc_order/uc_order.module 2010-06-24 17:26:40 +0000
@@ -13,7 +13,6 @@
require_once('uc_order.order_pane.inc');
require_once('uc_order.line_item.inc');
-require_once('uc_order.ca.inc');
class UcOrder {
@@ -444,6 +443,56 @@
}
/**
+ * Implement hook_entity_info().
+ */
+function uc_order_entity_info() {
+ return array(
+ 'uc_order' => array(
+ 'label' => t('Order'),
+ 'base table' => 'uc_orders',
+ 'uri callback' => 'uc_order_uri',
+ 'fieldable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'order_id',
+ ),
+ 'bundles' => array(
+ 'uc_order' => array(
+ 'label' => t('Order'),
+ ),
+ ),
+ 'view modes' => array(
+ 'full' => array(
+ 'label' => t('Invoice'),
+ 'admin' => array(
+ 'path' => 'admin/store/settings/orders',
+ 'access arguments' => array('administer store'),
+ ),
+ ),
+ ),
+ 'access callback' => 'uc_order_order_entity_access',
+ 'create callback' => 'uc_order_new',
+ 'save callback' => 'uc_order_save',
+ 'delete callback' => 'uc_order_delete',
+ ),
+ 'uc_order_product' => array(
+ 'label' => t('Order product'),
+ 'base table' => 'uc_order_products',
+ 'entity keys' => array(
+ 'id' => 'order_product_id',
+ ),
+ 'bundles' => array(
+ 'uc_order_product' => array(
+ 'label' => t('Order product'),
+ ),
+ ),
+ 'access callback' => 'uc_order_order_product_access',
+ 'save callback' => 'uc_order_product_entity_save',
+ 'delete callback' => 'uc_order_product_delete',
+ ),
+ );
+}
+
+/**
* Implement hook_user_view().
*/
function uc_order_user_view($account, $view_mode) {
@@ -494,10 +543,10 @@
// Apply an input format to the message body if specified.
if (isset($params['message_format'])) {
- $message['body'] = check_markup($body, $params['message_format'], $langcode);
+ $message['body'] = explode("\n", check_markup($body, $params['message_format'], $langcode));
}
else {
- $message['body'] = $body;
+ $message['body'] = explode("\n", $body);
}
break;
@@ -923,54 +972,47 @@
* Module and Helper Functions
******************************************************************************/
+function uc_order_order_entity_access($op, $order = NULL, $account = NULL) {
+ if ($op == 'delete') {
+ if (!empty($order)) {
+ return uc_order_can_delete($order, $account);
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ if ($op == 'edit') {
+ return user_access('edit orders', $account);
+ }
+
+ if ($op == 'view') {
+ if (user_access('view all_orders', $account)) {
+ return TRUE;
+ }
+
+ return (!empty($order) && $order->uid == $account->uid && user_access('view own orders', $account));
+ }
+}
+
/**
* Generate a new order for user $uid.
*/
function uc_order_new($uid = 0, $state = 'in_checkout') {
- $order = new stdClass();
+ $order = new UcOrder();
if ($uid > 0) {
$user = user_load($uid);
$email = $user->mail;
+ $order->primary_email = $email;
}
$order->uid = $uid;
$order->order_status = uc_order_state_default($state);
- $order->primary_email = $email;
- $order->products = array();
+ $order->created = REQUEST_TIME;
+ $order->modified = REQUEST_TIME;
- $order->order_id = db_insert('uc_orders')
- ->fields(array(
- 'uid' => $uid,
- 'order_status' => $order->order_status,
- 'order_total' => 0,
- 'primary_email' => $email,
- 'delivery_first_name' => '',
- 'delivery_last_name' => '',
- 'delivery_phone' => '',
- 'delivery_company' => '',
- 'delivery_street1' => '',
- 'delivery_street2' => '',
- 'delivery_city' => '',
- 'delivery_zone' => 0,
- 'delivery_postal_code' => '',
- 'delivery_country' => 0,
- 'billing_first_name' => '',
- 'billing_last_name' => '',
- 'billing_phone' => '',
- 'billing_company' => '',
- 'billing_street1' => '',
- 'billing_street2' => '',
- 'billing_city' => '',
- 'billing_zone' => 0,
- 'billing_postal_code' => '',
- 'billing_country' => 0,
- 'payment_method' => '',
- 'data' => '',
- 'created' => REQUEST_TIME,
- 'modified' => REQUEST_TIME,
- ))
- ->execute();
+ drupal_write_record('uc_orders', $order);
uc_order_module_invoke('new', $order, NULL);
@@ -1021,6 +1063,15 @@
return $user;
}
+function uc_order_order_product_access($op, $product = NULL, $account = NULL) {
+ if (isset($product) && $product->order_id) {
+ $order = uc_order_load($product->order_id);
+ return uc_order_order_entity_access($op, $order, $account);
+ }
+
+ return FALSE;
+}
+
/**
* Save a product to an order.
*/
@@ -1039,6 +1090,26 @@
}
/**
+ * API wrapper for uc_order_product_save().
+ *
+ * @todo: Change the signature for uc_order_product_save() to this one.
+ */
+function uc_order_product_entity_save($product) {
+ return uc_order_product_save($product->order_id, $product);
+}
+
+/**
+ * Remove a product from an order.
+ */
+function uc_order_product_delete($order_product_id) {
+ db_delete('uc_order_products')
+ ->condition('order_product_id', $order_product_id)
+ ->execute();
+
+ module_invoke_all('uc_order_product_delete', $order_product_id);
+}
+
+/**
* Load an order from the database.
*/
function uc_order_load($order_id) {
@@ -1297,7 +1368,7 @@
uc_order_log_changes($order->order_id, $change);
$updated = uc_order_load($order_id);
- ca_pull_trigger('uc_order_status_update', $order, $updated);
+ rules_invoke_event('uc_order_status_update', $order, $updated);
return TRUE;
}
@@ -1750,6 +1821,7 @@
}
if ($sql) {
+ $ids = array();
foreach ($statuses[$scope] as $status) {
$ids[] = $status['id'];
}
@@ -1816,38 +1888,14 @@
);
}
- if (user_access('delete orders') || user_access('unconditionally delete orders')) {
- // Unconditional deletion perms are always TRUE.
- $can_delete = TRUE;
- if (!user_access('unconditionally delete orders')) {
- // Only users with unconditional deletion perms can delete completed orders.
- if ($state == 'completed') {
- $can_delete = FALSE;
- }
- else {
- // See if any modules have a say in this order's eligibility for deletion
- foreach (module_implements('uc_order') as $module) {
- $function = $module . '_uc_order';
- // $order must be passed by reference.
- if (function_exists($function) && ($response = $function('can_delete', $order, NULL))) {
- // Break out early if possible.
- if ($response === FALSE) {
- $can_delete = FALSE;
- break;
- }
- }
- }
- }
- }
- if ($can_delete) {
- $alt = t('Delete order @order_id.', $order_id);
- $actions[] = array(
- 'name' => t('Delete'),
- 'url' => 'admin/store/orders/' . $order->order_id . '/delete',
- 'icon' => '',
- 'title' => $alt,
- );
- }
+ if (uc_order_can_delete($order)) {
+ $alt = t('Delete order @order_id.', $order_id);
+ $actions[] = array(
+ 'name' => t('Delete'),
+ 'url' => 'admin/store/orders/' . $order->order_id . '/delete',
+ 'icon' => '',
+ 'title' => $alt,
+ );
}
$extra = module_invoke_all('uc_order_actions', $order);
@@ -1872,13 +1920,84 @@
*
* Access callback for admin/store/orders/%uc_order/delete.
*/
-function uc_order_can_delete($order) {
- $can_delete = FALSE;
- foreach (uc_order_actions($order) as $action) {
- if ($action['name'] == t('Delete')) {
+function uc_order_can_delete($order, $account = NULL) {
+ if (user_access('unconditionally delete orders', $account)) {
+ // Unconditional deletion perms are always TRUE.
+ return TRUE;
+ }
+ elseif (user_access('delete orders', $account)) {
+ // Only users with unconditional deletion perms can delete completed orders.
+ if ($state == 'completed') {
+ return FALSE;
+ }
+ else {
$can_delete = TRUE;
+ // See if any modules have a say in this order's eligibility for deletion
+ foreach (module_implements('uc_order') as $module) {
+ $function = $module . '_uc_order';
+ // $order must be passed by reference.
+ if (function_exists($function) && ($response = $function('can_delete', $order, NULL))) {
+ // Break out early if possible.
+ if ($response === FALSE) {
+ $can_delete = FALSE;
+ break;
+ }
+ }
+ }
+
+ return $can_delete;
}
}
-
- return $can_delete;
-}
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Entity metadata callback to get delivery or billing address of an order.
+ */
+function uc_order_address_property_get($order, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'delivery-address':
+ $type = 'delivery_';
+ break;
+
+ case 'billing-address':
+ $type = 'billing_';
+ break;
+
+ default:
+ return NULL;
+ }
+
+ $address = new UcAddress();
+
+ foreach ($address as $field => $value) {
+ $address->{$field} = $order->{$type . $field};
+ }
+
+ return $address;
+}
+
+/**
+ * Entity metadata callback to set delivery or billing address of an order.
+ */
+function uc_order_address_property_set($order, $name, $address) {
+ switch ($name) {
+ case 'delivery-address':
+ $type = 'delivery_';
+ break;
+
+ case 'billing-address':
+ $type = 'billing_';
+ break;
+
+ default:
+ return;
+ }
+
+ foreach ($address as $field => $value) {
+ $order->{$type . $field} = $value;
+ }
+}
+
=== modified file 'uc_order/uc_order.order_pane.inc'
--- uc_order/uc_order.order_pane.inc 2010-06-03 19:26:32 +0000
+++ uc_order/uc_order.order_pane.inc 2010-06-15 13:40:04 +0000
@@ -678,14 +678,7 @@
$order_product_id = intval($form_state['triggering_element']['#return_value']);
- // Put back the stock.
- if (module_exists('uc_stock')) {
- $product = db_query("SELECT model, qty FROM {uc_order_products} WHERE order_product_id = :id", array(':id' => $order_product_id))->fetchObject();
- uc_stock_adjust($product->model, $product->qty);
- }
- db_delete('uc_order_products')
- ->condition('order_product_id', $order_product_id)
- ->execute();
+ uc_order_product_delete($order_product_id);
$order = $form_state['build_info']['args'][0];
$matches = array();
@@ -1148,7 +1141,7 @@
// Let conditional actions send email if requested.
if ($form_state['values']['notify']) {
$order = uc_order_load($form_state['values']['order_id']);
- ca_pull_trigger('uc_order_status_email_update', $order);
+ rules_invoke_event('uc_order_status_email_update', $order);
}
drupal_set_message(t('Order updated.'));
=== renamed file 'uc_order/uc_order.ca.inc' => 'uc_order/uc_order.rules.inc'
--- uc_order/uc_order.ca.inc 2010-04-08 21:22:14 +0000
+++ uc_order/uc_order.rules.inc 2010-06-15 13:32:57 +0000
@@ -3,285 +3,120 @@
/**
* @file
- * This file contains the Conditional Actions hooks and functions necessary to
- * make the order related entity, conditions, events, and actions work.
+ * This file contains the Rules hooks and functions necessary to make the order
+ * related entity, conditions, events, and actions work.
*/
/******************************************************************************
- * Conditional Actions Hooks *
+ * Rules Hooks *
******************************************************************************/
/**
- * Implement hook_ca_entity().
+ * Implement hook_rules_data_info().
*/
-function uc_order_ca_entity() {
- $entities['uc_order'] = array(
- '#title' => t('Ubercart order object'),
- '#type' => 'object',
- '#load' => 'uc_order_load',
- '#save' => 'uc_order_save',
- );
- $entities['uc_line_item'] = array(
- '#title' => t('Order line item'),
- '#type' => 'array',
- );
-
- return $entities;
+function uc_order_rules_data_info() {
+ $types['uc_order'] = array(
+ 'label' => t('Ubercart order object'),
+ 'wrap' => TRUE,
+ 'group' => t('Ubercart'),
+ );
+ $types['uc_order_product'] = array(
+ 'label' => t('Ubercart ordered product'),
+ 'wrap' => TRUE,
+ 'parent' => 'node',
+ 'group' => t('Ubercart'),
+ );
+
+ $types['uc_line_item'] = array(
+ 'label' => t('Order line item'),
+ 'wrap' => TRUE,
+ 'group' => t('Ubercart'),
+ 'token type' => FALSE,
+ );
+
+ return $types;
}
/**
- * Implement hook_ca_trigger().
+ * Implement hook_rules_event_info().
*/
-function uc_order_ca_trigger() {
- $triggers['uc_order_status_update'] = array(
- '#title' => t('Order status gets updated'),
- '#category' => t('Order'),
- '#arguments' => array(
+function uc_order_rules_event_info() {
+ $events['uc_order_status_update'] = array(
+ 'label' => t('Order status gets updated'),
+ 'group' => t('Order'),
+ 'variables' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Original order'),
+ 'type' => 'uc_order',
+ 'label' => t('Original order'),
),
'updated_order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Updated order'),
+ 'type' => 'uc_order',
+ 'label' => t('Updated order'),
),
),
);
- $triggers['uc_order_status_email_update'] = array(
- '#title' => t('E-mail requested for order status update'),
- '#category' => t('Order'),
- '#arguments' => array(
+ $events['uc_order_status_email_update'] = array(
+ 'label' => t('E-mail requested for order status update'),
+ 'group' => t('Order'),
+ 'variables' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- ),
- ),
- );
-
- return $triggers;
-}
-
-/**
- * Implement hook_ca_predicate().
- */
-function uc_order_ca_predicate() {
- $predicates = array();
-
- $predicates['uc_order_update_email_customer'] = array(
- '#title' => t('E-mail an order update notification'),
- '#description' => t('Notify the customer when the order status is changed.'),
- '#class' => 'notification',
- '#status' => 1,
- '#trigger' => 'uc_order_status_email_update',
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_status_condition',
- '#title' => t('If the order status is not still In Checkout.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => TRUE,
- 'order_status' => 'in_checkout',
- ),
- ),
- ),
- ),
- '#actions' => array(
- array(
- '#name' => 'uc_order_email',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[uc_order:email]',
- 'subject' => t('Order #[uc_order:order-id] Update'),
- 'message' => uc_get_message('order_update_email'),
- 'format' => filter_default_format(),
- ),
- ),
- ),
- );
-
- return $predicates;
-}
-
-/**
- * Implement hook_ca_condition().
- */
-function uc_order_ca_condition() {
- $order_arg = array(
- '#entity' => 'uc_order',
- );
-
- $conditions['uc_order_status_condition'] = array(
- '#title' => t('Check the order status'),
- '#description' => t('Returns TRUE if the current order status matches the status specified below.'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_condition_check_order_status',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
-
- $conditions['uc_order_condition_total'] = array(
- '#title' => t('Check the order total'),
- '#description' => t('Returns TRUE if the current order total is within the parameters below.'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_condition_total',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
-
- $conditions['uc_order_condition_delivery_postal_code'] = array(
- '#title' => t("Check an order's shipping postal code"),
- '#category' => t('Order: Shipping address'),
- '#description' => t('Returns TRUE if the shipping postal code is in the specified area.'),
- '#callback' => 'uc_order_condition_delivery_postal_code',
- '#arguments' => array(
- 'order' => $order_arg
- ),
- );
- $conditions['uc_order_condition_delivery_zone'] = array(
- '#title' => t("Check an order's shipping @zone", array('@zone' => uc_get_field_name('zone'))),
- '#category' => t('Order: Shipping address'),
- '#description' => t('Returns TRUE if the shipping @zone is in the specified list.', array('@zone' => uc_get_field_name('zone'))),
- '#callback' => 'uc_order_condition_delivery_zone',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_delivery_country'] = array(
- '#title' => t("Check an order's shipping country"),
- '#category' => t('Order: Shipping address'),
- '#description' => t('Returns TRUE if the shipping country is in the specified list.'),
- '#callback' => 'uc_order_condition_delivery_country',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_billing_postal_code'] = array(
- '#title' => t("Check an order's billing postal code"),
- '#category' => t('Order: Billing address'),
- '#description' => t('Returns TRUE if the billing postal code is in the specified area.'),
- '#callback' => 'uc_order_condition_billing_postal_code',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_billing_zone'] = array(
- '#title' => t("Check an order's billing @zone", array('@zone' => uc_get_field_name('zone'))),
- '#category' => t('Order: Billing address'),
- '#description' => t('Returns TRUE if the billing @zone is in the specified list.', array('@zone' => uc_get_field_name('zone'))),
- '#callback' => 'uc_order_condition_billing_zone',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_billing_country'] = array(
- '#title' => t("Check an order's billing country"),
- '#category' => t('Order: Billing address'),
- '#description' => t('Returns TRUE if the billing country is in the specified list.'),
- '#callback' => 'uc_order_condition_billing_country',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
+ ),
+ );
+
+ return $events;
+}
+
+/**
+ * Implement hook_rules_condition_info().
+ */
+function uc_order_rules_condition_info() {
$conditions['uc_order_condition_has_products'] = array(
- '#title' => t("Check an order's products"),
- '#category' => t('Order: Product'),
- '#description' => t('Returns TRUE if the order has any, all, or only the products in the list.'),
- '#callback' => 'uc_order_condition_has_products',
- '#arguments' => array(
- 'order' => $order_arg,
+ 'label' => t("Check an order's products"),
+ 'group' => t('Order: Product'),
+ 'base' => 'uc_order_condition_has_products',
+ 'parameter' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
),
);
$conditions['uc_order_condition_count_products'] = array(
- '#title' => t("Check an order's number of products"),
- '#category' => t('Order: Product'),
- '#description' => t('Determines if the order has the specified number of products, possibly of a certain type.'),
- '#callback' => 'uc_order_condition_count_products',
- '#arguments' => array(
- 'order' => $order_arg,
+ 'label' => t("Check an order's number of products"),
+ 'group' => t('Order: Product'),
+ 'base' => 'uc_order_condition_count_products',
+ 'parameter' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
),
);
$conditions['uc_order_condition_products_weight'] = array(
- '#title' => t("Check an order's total weight"),
- '#category' => t('Order: Product'),
- '#description' => t('Determines if the order has the specified weight, possibly counting only a certain type of product.'),
- '#callback' => 'uc_order_condition_products_weight',
- '#arguments' => array(
- 'order' => $order_arg,
+ 'label' => t("Check an order's total weight"),
+ 'group' => t('Order: Product'),
+ 'help' => t('Compare the weight of all of the products, or the weight of just one type in the order.'),
+ 'base' => 'uc_order_condition_products_weight',
+ 'parameter' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
),
);
$conditions['uc_order_condition_is_shippable'] = array(
- '#title' => t('Check if an order can be shipped'),
- '#category' => t('Order'),
- '#description' => t('Returns TRUE if the order has any shippable products.'),
- '#callback' => 'uc_order_condition_is_shippable',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
-
- $conditions['uc_order_condition_user_name'] = array(
- '#title' => t('Check the user name.'),
- '#category' => t('Order: User'),
- '#description' => t('Returns TRUE if the user name matches the condition.'),
- '#callback' => 'uc_order_condition_user_name',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_user_mail'] = array(
- '#title' => t('Check the user email address.'),
- '#category' => t('Order: User'),
- '#description' => t('Returns TRUE if the user email addresses matches the condition.'),
- '#callback' => 'uc_order_condition_user_mail',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_user_created'] = array(
- '#title' => t('Check the user creation date.'),
- '#category' => t('Order: User'),
- '#description' => t('Returns TRUE if the user creation date matches the condition.'),
- '#callback' => 'uc_order_condition_user_created',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_user_login'] = array(
- '#title' => t('Check the user last login date.'),
- '#category' => t('Order: User'),
- '#description' => t('Returns TRUE if the user last login date matches the condition.'),
- '#callback' => 'uc_order_condition_user_login',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_user_language'] = array(
- '#title' => t('Check the user language setting.'),
- '#category' => t('Order: User'),
- '#description' => t('Returns TRUE if the user language setting matches the condition.'),
- '#callback' => 'uc_order_condition_user_language',
- '#arguments' => array(
- 'order' => $order_arg,
- ),
- );
- $conditions['uc_order_condition_user_roles'] = array(
- '#title' => t('Check the role of the user.'),
- '#category' => t('Order: User'),
- '#description' => t('Returns TRUE if the user roles match your settings.'),
- '#callback' => 'uc_order_condition_user_roles',
- '#arguments' => array(
- 'order' => $order_arg,
+ 'label' => t('Check if an order can be shipped'),
+ 'group' => t('Order'),
+ 'base' => 'uc_order_condition_is_shippable',
+ 'parameter' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
),
);
@@ -289,297 +124,111 @@
}
/**
- * Implement hook_ca_action().
+ * Implement hook_rules_action_info().
*/
-function uc_order_ca_action() {
+function uc_order_rules_action_info() {
$order_arg = array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
);
$actions['uc_order_update_status'] = array(
- '#title' => t('Update the order status'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_action_update_status',
- '#arguments' => array(
+ 'label' => t('Update the order status'),
+ 'group' => t('Order'),
+ 'base' => 'uc_order_action_update_status',
+ 'parameter' => array(
'order' => $order_arg,
+ 'order_status' => array(
+ 'type' => 'text',
+ 'label' => t('Status'),
+ 'options list' => 'uc_order_action_update_status_options',
+ ),
),
);
$actions['uc_order_action_add_comment'] = array(
- '#title' => t('Add a comment to the order'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_action_add_comment',
- '#arguments' => array(
+ 'label' => t('Add a comment to the order'),
+ 'group' => t('Order'),
+ 'base' => 'uc_order_action_add_comment',
+ 'parameter' => array(
'order' => $order_arg,
+ 'comment' => array(
+ 'type' => 'text',
+ 'label' => t('Comment'),
+ ),
+ 'comment_type' => array(
+ 'type' => 'text',
+ 'label' => t('Comment type'),
+ 'restriction' => 'input',
+ 'options list' => 'uc_order_action_comment_types',
+ ),
),
);
$actions['uc_order_email'] = array(
- '#title' => t('Send an order email'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_action_email',
- '#arguments' => array(
+ 'label' => t('Send an order email'),
+ 'group' => t('Order'),
+ 'base' => 'uc_order_action_email',
+ 'parameter' => array(
'order' => $order_arg,
+ 'from' => array(
+ 'type' => 'text',
+ 'label' => t('Sender'),
+ ),
+ 'addresses' => array(
+ 'type' => 'text',
+ 'label' => t('Recipients'),
+ 'description' => t('Enter the email addresses to receive the notifications, one on each line. You may use order tokens for dynamic email addresses.'),
+ ),
+ 'subject' => array(
+ 'type' => 'text',
+ 'label' => t('Subject'),
+ ),
+ 'message' => array(
+ 'type' => 'text',
+ 'label' => t('Message'),
+ ),
),
);
$actions['uc_order_email_invoice'] = array(
- '#title' => t('Email an order invoice'),
- '#category' => t('Order'),
- '#callback' => 'uc_order_action_email_invoice',
- '#arguments' => array(
+ 'label' => t('Email an order invoice'),
+ 'group' => t('Order'),
+ 'base' => 'uc_order_action_email_invoice',
+ 'parameter' => array(
'order' => $order_arg,
+ 'from' => array(
+ 'type' => 'text',
+ 'label' => t('Sender'),
+ ),
+ 'addresses' => array(
+ 'type' => 'text',
+ 'label' => t('Recipients'),
+ ),
+ 'subject' => array(
+ 'type' => 'text',
+ 'label' => t('Subject'),
+ ),
+ 'template' => array(
+ 'type' => 'text',
+ 'label' => t('Invoice template'),
+ 'options list' => 'uc_order_template_options',
+ 'restriction' => 'input',
+ ),
+ 'view' => array(
+ 'type' => 'text',
+ 'label' => t('Included information'),
+ 'options list' => 'uc_order_action_email_invoice_view_options',
+ 'restriction' => 'input',
+ ),
),
);
return $actions;
}
-
/******************************************************************************
* Condition Callbacks and Forms *
******************************************************************************/
/**
- * Check the current order status.
- *
- * @see uc_order_condition_check_order_status_form()
- */
-function uc_order_condition_check_order_status($order, $settings) {
- // Return TRUE if the order status matches.
- return $order->order_status == $settings['order_status'];
-}
-
-/**
- * @see uc_order_condition_check_order_status()
- */
-function uc_order_condition_check_order_status_form($form_state, $settings = array()) {
- foreach (uc_order_status_list('general') as $status) {
- $options[$status['id']] = $status['title'];
- }
- foreach (uc_order_status_list('specific') as $status) {
- $options[$status['id']] = $status['title'];
- }
- $form['order_status'] = array(
- '#type' => 'select',
- '#title' => t('Order status'),
- '#options' => $options,
- '#default_value' => $settings['order_status'],
- );
-
- return $form;
-}
-
-/**
- * Check the current order balance.
- *
- * @see uc_order_condition_total_form()
- */
-function uc_order_condition_total($order, $settings) {
- switch ($settings['order_total_comparison']) {
- case 'less':
- return $order->order_total < $settings['order_total_value'];
- case 'less_equal':
- return $order->order_total <= $settings['order_total_value'];
- case 'equal':
- return $order->order_total == $settings['order_total_value'];
- case 'greater_equal':
- return $order->order_total >= $settings['order_total_value'];
- case 'greater':
- return $order->order_total > $settings['order_total_value'];
- }
-}
-
-/**
- * @see uc_order_condition_total()
- */
-function uc_order_condition_total_form($form_state, $settings = array()) {
- $form['order_total_value'] = array(
- '#type' => 'textfield',
- '#title' => t('Order total value'),
- '#description' => t('Specify a value to compare the order total against.'),
- '#default_value' => $settings['order_total_value'],
- '#size' => 16,
- '#field_prefix' => variable_get('uc_sign_after_amount', FALSE) ? '' : variable_get('uc_currency_sign', '$'),
- '#field_suffix' => variable_get('uc_sign_after_amount', FALSE) ? variable_get('uc_currency_sign', '$') : '',
- );
-
- $options = array(
- 'less' => t('Total is less than specified value.'),
- 'less_equal' => t('Total is less than or equal to specified value.'),
- 'equal' => t('Total is equal to specified value.'),
- 'greater_equal' => t('Total is greater than or equal to specified value.'),
- 'greater' => t('Total is greater than specified value.'),
- );
- $form['order_total_comparison'] = array(
- '#type' => 'radios',
- '#title' => t('Order total comparison type'),
- '#options' => $options,
- '#default_value' => isset($settings['order_total_comparison']) ? $settings['order_total_comparison'] : 'greater_equal',
- );
-
- return $form;
-}
-
-/**
- * Check an order's delivery postal code.
- *
- * @see uc_order_condition_delivery_postal_code_form()
- */
-function uc_order_condition_delivery_postal_code($order, $settings) {
- // Trim the wildcard off the pattern.
- $pattern = rtrim($settings['pattern'], '*');
-
- // Return TRUE if the delivery postal code begins with the pattern.
- return strpos($order->delivery_postal_code, $pattern) === 0;
-}
-
-/**
- * @see uc_order_condition_delivery_postal_code()
- */
-function uc_order_condition_delivery_postal_code_form($form_state, $settings = array()) {
- $form['pattern'] = array(
- '#type' => 'textfield',
- '#title' => uc_get_field_name('postal_code'),
- '#default_value' => $settings['pattern'],
- '#description' => t('Specify a postal code or postal code pattern. Use "*" as a wild card to specify a range of postal codes.
Example: In the US, 402* represents all areas from 40200 to 40299.'),
- '#size' => 15,
- // '#required' => TRUE,
- );
-
- return $form;
-}
-
-/**
- * Check an order's delivery zone.
- *
- * @see uc_order_condition_delivery_zone_form()
- */
-function uc_order_condition_delivery_zone($order, $settings) {
- return in_array($order->delivery_zone, $settings['zones']);
-}
-
-/**
- * @see uc_order_condition_delivery_zone_form()
- */
-function uc_order_condition_delivery_zone_form($form_state, $settings = array()) {
- $result = db_query("SELECT z.*, c.country_name FROM {uc_zones} AS z LEFT JOIN {uc_countries} AS c ON z.zone_country_id = c.country_id ORDER BY c.country_name, z.zone_name");
- foreach ($result as $zone) {
- $options[$zone->country_name][$zone->zone_id] = $zone->zone_name;
- }
-
- $form['zones'] = array(
- '#type' => 'select',
- '#title' => uc_get_field_name('zone'),
- '#options' => $options,
- '#default_value' => $settings['zones'],
- '#multiple' => TRUE,
- // '#required' => TRUE,
- );
-
- return $form;
-}
-
-/**
- * Check an order's delivery country.
- *
- * @see uc_order_condition_delivery_country_form()
- */
-function uc_order_condition_delivery_country($order, $settings) {
- return in_array($order->delivery_country, $settings['countries']);
-}
-
-/**
- * @see uc_order_condition_delivery_country()
- */
-function uc_order_condition_delivery_country_form($form_state, $settings = array()) {
- $form['countries'] = uc_country_select(uc_get_field_name('country'));
- $form['countries']['#default_value'] = $settings['countries'];
- $form['countries']['#multiple'] = TRUE;
- // $form['countries']['#required'] = TRUE;
-
- return $form;
-}
-
-/**
- * Check an order's billing postal code.
- *
- * @see uc_order_condition_billing_postal_code_form()
- */
-function uc_order_condition_billing_postal_code($order, $settings) {
- // Trim the wildcard off the pattern.
- $pattern = rtrim($settings['pattern'], '*');
-
- // Return TRUE if the delivery postal code begins with the pattern.
- return strpos($order->billing_postal_code, $pattern) === 0;
-}
-
-/**
- * @see uc_order_condition_billing_postal_code()
- */
-function uc_order_condition_billing_postal_code_form($form_state, $settings = array()) {
- $form['pattern'] = array(
- '#type' => 'textfield',
- '#title' => uc_get_field_name('postal_code'),
- '#default_value' => $settings['pattern'],
- '#description' => t('Specify a postal code or postal code pattern. Use "*" as a wild card to specify a range of postal codes.
Example: In the US, 402* represents all areas from 40200 to 40299.'),
- '#size' => 15,
- // '#required' => TRUE,
- );
-
- return $form;
-}
-
-/**
- * Check an order's billing zone.
- *
- * @see uc_order_condition_billing_zone_form()
- */
-function uc_order_condition_billing_zone($order, $settings) {
- return in_array($order->billing_zone, $settings['zones']);
-}
-
-/**
- * @see uc_order_condition_billing_zone()
- */
-function uc_order_condition_billing_zone_form($form_state, $settings = array()) {
- $result = db_query("SELECT z.*, c.country_name FROM {uc_zones} AS z LEFT JOIN {uc_countries} AS c ON z.zone_country_id = c.country_id ORDER BY c.country_name, z.zone_name");
- foreach ($result as $zone) {
- $options[$zone->country_name][$zone->zone_id] = $zone->zone_name;
- }
-
- $form['zones'] = array(
- '#type' => 'select',
- '#title' => uc_get_field_name('zone'),
- '#options' => $options,
- '#default_value' => $settings['zones'],
- '#multiple' => TRUE,
- // '#required' => TRUE,
- );
-
- return $form;
-}
-
-/**
- * Check an order's billing country.
- *
- * @see uc_order_condition_billing_country_form()
- */
-function uc_order_condition_billing_country($order, $settings) {
- return in_array($order->billing_country, $settings['countries']);
-}
-
-/**
- * @see uc_order_condition_billing_country()
- */
-function uc_order_condition_billing_country_form($form_state, $settings = array()) {
- $form['countries'] = uc_country_select(uc_get_field_name('country'));
- $form['countries']['#default_value'] = $settings['countries'];
- $form['countries']['#multiple'] = TRUE;
- // $form['countries']['#required'] = TRUE;
-
- return $form;
-}
-
-/**
* Check that the order has the selected combination of products.
*
* @see uc_order_condition_has_products_form()
@@ -806,255 +455,6 @@
return uc_order_is_shippable($order);
}
-/**
- * Check a user name.
- *
- * @see uc_order_condition_user_name()
- */
-function uc_order_condition_user_name($order, $settings) {
- if (empty($order->uid)) {
- // Anonymous users have no names.
- return empty($settings['name']);
- }
- else {
- // Load from the order.
- if ($account = uc_order_user_load($order)) {
- return $account->name == $settings['name'];
- }
- // Or, if the user doesn't exist anymore, return FALSE
- else {
- return FALSE;
- }
- }
-}
-
-/**
- * @see uc_order_condition_user_name()
- */
-function uc_order_condition_user_name_form($form_state, $settings = array()) {
- $form['name'] = array(
- '#type' => 'textfield',
- '#title' => t('User name'),
- '#default_value' => isset($settings['name']) ? $settings['name'] : '',
- '#autocomplete_path' => 'user/autocomplete',
- '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
- );
-
- return $form;
-}
-
-/**
- * Check an user email.
- *
- * @see uc_order_condition_user_email_form()
- */
-function uc_order_condition_user_mail($order, $settings) {
- $account = uc_order_user_load($order);
- return $account->mail == $settings['mail'];
-}
-
-/**
- * @see uc_order_condition_user_mail()
- */
-function uc_order_condition_user_mail_form($form_state, $settings = array()) {
- $form['mail'] = array(
- '#type' => 'textfield',
- '#title' => t('User email'),
- '#default_value' => isset($settings['mail']) ? $settings['mail'] : '',
- );
-
- return $form;
-}
-
-/**
- * Check an user creation date.
- *
- * @see uc_order_condition_user_created_form()
- */
-function uc_order_condition_user_created($order, $settings) {
- $account = uc_order_user_load($order);
-
- // Get the beginning of the day the user registered.
- $user_created = $account->created - ($account->created % 86400);
- $settings_created = gmmktime(0, 0, 0, $settings['created']['month'], $settings['created']['day'], $settings['created']['year']);
-
- switch ($settings['operator']) {
- case 'less':
- return $user_created < $settings_created;
- case 'less_equal':
- return $user_created <= $settings_created;
- case 'equal':
- return $user_created == $settings_created;
- case 'not_equal':
- return $user_created != $settings_created;
- case 'greater_equal':
- return $user_created >= $settings_created;
- case 'greater':
- return $user_created > $settings_created;
- }
-}
-
-/**
- * @see uc_order_condition_user_created()
- */
-function uc_order_condition_user_created_form($form_state, $settings = array()) {
- $form['operator'] = array(
- '#type' => 'radios',
- '#title' => t('Operator'),
- '#options' => array(
- 'less' => t('User was created before the specified date.'),
- 'less_equal' => t('User was created on or before the specified date.'),
- 'equal' => t('User was created on the specified date.'),
- 'not_equal' => t('User was not created on the specified date.'),
- 'greater_equal' => t('User was created on or after the specified date.'),
- 'greater' => t('User was created after the specified date.'),
- ),
- '#default_value' => isset($settings['operator']) ? $settings['operator'] : 'equal',
- );
- $form['created'] = array(
- '#type' => 'date',
- '#title' => t('User creation date'),
- '#default_value' => isset($settings['created']) ? $settings['created'] : '',
- );
-
- return $form;
-}
-
-/**
- * Check an user last login date.
- *
- * @see uc_order_condition_user_login_form()
- */
-function uc_order_condition_user_login($order, $settings) {
- $account = uc_order_user_load($order);
-
- // Get the beginning of the day the user last logged in.
- $user_login = $account->login - ($account->login % 86400);
- $settings_login = gmmktime(0, 0, 0, $settings['login']['month'], $settings['login']['day'], $settings['login']['year']);
-
- switch ($settings['operator']) {
- case 'less':
- return $user_login < $settings_login;
- case 'less_equal':
- return $user_login <= $settings_login;
- case 'equal':
- return $user_login == $settings_login;
- case 'not_equal':
- return $user_login != $settings_login;
- case 'greater_equal':
- return $user_login >= $settings_login;
- case 'greater':
- return $user_login > $settings_login;
- }
-}
-
-/**
- * @see uc_order_condition_user_login()
- */
-function uc_order_condition_user_login_form($form_state, $settings = array()) {
- $form['operator'] = array(
- '#type' => 'radios',
- '#title' => t('Operator'),
- '#options' => array(
- 'less' => t('User last logged in before the specified date.'),
- 'less_equal' => t('User last logged in on, or before the specified date.'),
- 'equal' => t('User last logged in on the specified date.'),
- 'not_equal' => t('User did not log in last on the specified date.'),
- 'greater_equal' => t('User last logged in on or after the specified date.'),
- 'greater' => t('User last logged after the specified date.'),
- ),
- '#default_value' => isset($settings['operator']) ? $settings['operator'] : 'equal',
- );
- $form['login'] = array(
- '#type' => 'date',
- '#title' => t('User last login date'),
- '#default_value' => isset($settings['created']) ? $settings['created'] : '',
- );
-
- return $form;
-}
-
-/**
- * Check an user language setting.
- *
- * @see uc_order_condition_user_language_form()
- */
-function uc_order_condition_user_language($order, $settings) {
- $account = uc_order_user_load($order);
- $user_prefered_language = user_preferred_language($account);
- return $user_prefered_language->language == $settings['language'];
-}
-
-/**
- * @see uc_order_condition_user_language()
- */
-function uc_order_condition_user_language_form($form_state, $settings = array()) {
- $languages = language_list();
- $options = array();
- foreach ($languages as $language) {
- $options[$language->language] = $language->name . ' (' . $language->native . ')';
- }
- $form['language'] = array(
- '#type' => 'select',
- '#title' => t('User language setting'),
- '#options' => $options,
- '#default_value' => isset($settings['language']) ? $settings['language'] : '',
- );
-
- return $form;
-}
-
-/**
- * Check an user roles.
- *
- * @see uc_order_condition_user_roles_form()
- */
-function uc_order_condition_user_roles($order, $settings) {
- $account = uc_order_user_load($order);
- $settings['roles'] = array_filter($settings['roles']);
-
- if ($settings['operator'] == 'AND') {
- foreach ($settings['roles'] as $key) {
- if (!isset($account->roles[$key])) {
- return FALSE;
- }
- }
- return TRUE;
- }
- else {
- foreach ($settings['roles'] as $key) {
- if (isset($account->roles[$key])) {
- return TRUE;
- }
- }
- return FALSE;
- }
-}
-
-/**
- * @see uc_order_condition_user_roles()
- */
-function uc_order_condition_user_roles_form($form_state, $settings = array()) {
- $form['operator'] = array(
- '#type' => 'radios',
- '#title' => t('Operator'),
- '#description' => t('If you use an AND case and want to check a custom role, make sure authenticated user is checked too or the condition will return FALSE.'),
- '#options' => array(
- 'AND' => t('AND: If the user has all of these roles.'),
- 'OR' => t('OR: If the user has any of these roles.'),
- ),
- '#default_value' => isset($settings['operator']) ? $settings['operator'] : 'AND',
- );
- $form['roles'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Roles'),
- '#options' => user_roles(),
- '#default_value' => isset($settings['roles']) ? $settings['roles'] : array(),
- );
-
- return $form;
-}
-
/******************************************************************************
* Action Callbacks and Forms *
******************************************************************************/
@@ -1073,21 +473,17 @@
/**
* @see uc_order_action_update_status()
*/
-function uc_order_action_update_status_form($form_state, $settings = array()) {
+function uc_order_action_update_status_options() {
+ $options = array();
+
foreach (uc_order_status_list('general') as $status) {
$options[$status['id']] = $status['title'];
}
foreach (uc_order_status_list('specific') as $status) {
$options[$status['id']] = $status['title'];
}
- $form['order_status'] = array(
- '#type' => 'select',
- '#title' => t('Order status'),
- '#options' => $options,
- '#default_value' => $settings['order_status'],
- );
- return $form;
+ return $options;
}
/**
@@ -1105,25 +501,12 @@
/**
* @see uc_order_action_add_comment()
*/
-function uc_order_action_add_comment_form($form_state, $settings = array()) {
- $form['comment_type'] = array(
- '#type' => 'radios',
- '#title' => t('Select an order comment type'),
- '#options' => array(
- 'admin' => t('Enter this as an admin comment.'),
- 'order' => t('Enter this as a customer order comment.'),
- 'notified' => t('Enter this as a customer order comment with a notified icon.'),
- ),
- '#default_value' => $settings['comment_type'],
- );
- $form['comment'] = array(
- '#type' => 'textarea',
- '#title' => t('Comment'),
- '#description' => t('Enter the comment message. Uses order, store, and site tokens.', array('!url' => url('admin/store/help/tokens'))),
- '#default_value' => $settings['comment'],
- );
-
- return $form;
+function uc_order_action_order_comment_types() {
+ return array(
+ 'admin' => t('Enter this as an admin comment.'),
+ 'order' => t('Enter this as a customer order comment.'),
+ 'notified' => t('Enter this as a customer order comment with a notified icon.'),
+ );
}
/**
@@ -1247,61 +630,10 @@
/**
* @see uc_order_action_email_invoice()
*/
-function uc_order_action_email_invoice_form($form_state, $settings = array()) {
- $form['from'] = array(
- '#type' => 'textfield',
- '#title' => t('Sender'),
- '#default_value' => isset($settings['from']) ? $settings['from'] : uc_store_email_from(),
- '#description' => t('The "From" address.'),
- '#required' => TRUE,
- );
- $form['addresses'] = array(
- '#type' => 'textarea',
- '#title' => t('Recipients'),
- '#default_value' => isset($settings['addresses']) ? $settings['addresses'] : '[uc_order:email]',
- '#description' => t('Enter the email addresses to receive the notifications, one on each line. You may use order tokens for dynamic email addresses.'),
- '#required' => TRUE,
- );
- $form['subject'] = array(
- '#type' => 'textfield',
- '#title' => t('Subject'),
- '#default_value' => $settings['subject'],
- '#required' => TRUE,
- );
-
- $form['token_help'] = array(
- '#type' => 'fieldset',
- '#title' => t('Replacement patterns'),
- '#description' => t('You can make use of the replacement patterns in the recipients, the subject, and the template file.'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- foreach (array('global', 'order') as $name) {
- $form['token_help'][$name] = array(
- '#type' => 'fieldset',
- '#title' => t('@name replacement patterns', array('@name' => drupal_ucfirst($name))),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- }
-
- $form['template'] = array(
- '#type' => 'select',
- '#title' => t('Invoice template'),
- '#description' => t('Select the invoice template to use for this email.'),
- '#options' => uc_order_template_options(),
- '#default_value' => $settings['template'],
- );
- $form['view'] = array(
- '#type' => 'radios',
- '#title' => t('Included information'),
- '#options' => array(
- 'print' => t('Show the business header and shipping method.'),
- 'admin-mail' => t('Show all of the above plus the help text, email text, and store footer.'),
- 'checkout-mail' => t('Show all of the above plus the "thank you" message.'),
- ),
- '#default_value' => $settings['view'],
- );
-
- return $form;
+function uc_order_action_email_invoice_view_options() {
+ return array(
+ 'print' => t('Show the business header and shipping method.'),
+ 'admin-mail' => t('Show all of the above plus the help text, email text, and store footer.'),
+ 'checkout-mail' => t('Show all of the above plus the "thank you" message.'),
+ );
}
=== added file 'uc_order/uc_order.rules_defaults.inc'
--- uc_order/uc_order.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ uc_order/uc_order.rules_defaults.inc 2010-06-21 18:52:52 +0000
@@ -0,0 +1,31 @@
+label = t('E-mail an order update notification');
+ $rule->active = TRUE;
+ $rule->event('uc_order_status_email_update')
+ ->condition(rules_condition('data_is', array('data:select' => 'order:order-status', 'value' => 'in_checkout'))->negate())
+ ->action('uc_order_email', array(
+ 'order:select' => 'order',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[uc_order:email]',
+ 'subject' => t('Order #[uc_order:order-id] Update'),
+ 'message' => uc_get_message('order_update_email'),
+ 'format' => filter_default_format(),
+ ));
+
+ $configs['uc_order_update_email_customer'] = $rule;
+
+ return $configs;
+}
+
=== modified file 'uc_order/uc_order.tokens.inc'
--- uc_order/uc_order.tokens.inc 2010-04-05 19:24:56 +0000
+++ uc_order/uc_order.tokens.inc 2010-06-22 16:51:50 +0000
@@ -157,8 +157,12 @@
break;
}
- $values[$tokens['url']] = $url;
- $values[$tokens['link']] = l($order->order_id, $url);
+ if (isset($tokens['url'])) {
+ $values[$tokens['url']] = $url;
+ }
+ if (isset($tokens['link'])) {
+ $values[$tokens['link']] = l($order->order_id, $url);
+ }
break;
case 'admin-url':
@@ -170,8 +174,12 @@
break;
}
- $values[$tokens['admin-url']] = $admin_url;
- $values[$tokens['admin-link']] = l($order->order_id, $admin_url);
+ if (isset($tokens['admin-url'])) {
+ $values[$tokens['admin-url']] = $admin_url;
+ }
+ if (isset($tokens['admin-link'])) {
+ $values[$tokens['admin-link']] = l($order->order_id, $admin_url);
+ }
break;
case 'subtotal':
=== modified file 'uc_product/uc_product.admin.inc'
--- uc_product/uc_product.admin.inc 2010-05-18 20:14:02 +0000
+++ uc_product/uc_product.admin.inc 2010-06-24 17:27:12 +0000
@@ -393,58 +393,21 @@
/**
* Displays the product features tab on a product node edit form.
*/
-function uc_product_features($node) {
+function uc_product_features($node, $fid = NULL, $pfid = NULL, $op = NULL) {
drupal_set_title($node->title);
$build = array();
- if (arg(4)) {
- // First check to see if we're trying to remove a feature.
- if (intval(arg(5)) > 0 && arg(6) == 'delete') {
- $result = db_query("SELECT * FROM {uc_product_features} WHERE pfid = :pfid AND fid = :fid", array(
- ':pfid' => arg(5),
- ':fid' => arg(4),
- ));
- if ($feature = $result->fetchAssoc()) {
- // If the user confirmed the delete, process it!
- if ($_POST['pf_delete']) {
- // Call the delete function for this product feature if it exists.
- $func = uc_product_feature_data($feature['fid'], 'delete');
- if (function_exists($func)) {
- $func($feature);
- }
-
- // Remove the product feature data from the database.
- db_delete('uc_product_features')
- ->condition('pfid', arg(5))
- ->execute();
-
- drupal_set_message(t('The product feature has been deleted.'));
- drupal_goto('node/' . arg(1) . '/edit/features');
- }
-
- // Show the confirmation form for deleting this feature.
- $question = $node->title;
- $description = t('Are you sure you wish to delete this %feature?', array('%feature' => uc_product_feature_data($feature['fid'], 'title')))
- . '' . t('Description') . ':
' . $feature['description'] . '
';
- $form = array();
- return confirm_form($form, $question, 'node/' . arg(1) . '/edit/features', $description, t('Delete'), t('Cancel'), 'pf_delete');
- }
- else {
- drupal_set_message(t("That product feature doesn't exist."), 'error');
- drupal_goto('node/' . arg(1) . '/edit/features');
- }
- }
-
+ if ($fid) {
// Handle adding or editing product features.
- $func = uc_product_feature_data(arg(4), 'callback');
+ $func = uc_product_feature_data($fid, 'callback');
if (function_exists($func)) {
- if (arg(5) == 'add') {
+ if ($op == 'add') {
$build = drupal_get_form($func, $node, array());
}
- elseif (intval(arg(5)) > 0) {
+ elseif (intval($pfid) > 0) {
$result = db_query("SELECT * FROM {uc_product_features} WHERE pfid = :pfid AND fid = :fid", array(
- ':pfid' => arg(5),
- ':fid' => arg(4),
+ ':pfid' => $pfid,
+ ':fid' => $fid,
));
if ($feature = $result->fetchAssoc()) {
$build = drupal_get_form($func, $node, $feature);
@@ -498,6 +461,44 @@
return $build;
}
+function uc_product_feature_delete_confirm($form, $form_state, $node, $fid, $pfid) {
+ $result = db_query("SELECT * FROM {uc_product_features} WHERE pfid = :pfid AND fid = :fid", array(
+ ':pfid' => $pfid,
+ ':fid' => $fid,
+ ));
+ if ($feature = $result->fetchAssoc()) {
+ $form = array('#feature' => $feature);
+
+ $question = $node->title;
+ $description = t('Are you sure you wish to delete this %feature?', array('%feature' => uc_product_feature_data($feature['fid'], 'title')))
+ . '' . t('Description') . ':
' . $feature['description'] . '
';
+ return confirm_form($form, $question, 'node/' . $node->nid . '/edit/features', $description, t('Delete'), t('Cancel'));
+ }
+ else {
+ drupal_set_message(t("That product feature doesn't exist."), 'error');
+ drupal_goto('node/' . $node->nid . '/edit/features');
+ }
+}
+
+function uc_product_feature_delete_confirm_submit($form, &$form_state) {
+ $node = $form_state['build_info']['args'][0];
+ $feature = $form['#feature'];
+
+ // Call the delete function for this product feature if it exists.
+ $func = uc_product_feature_data($feature['fid'], 'delete');
+ if (function_exists($func)) {
+ $func($feature);
+ }
+
+ // Remove the product feature data from the database.
+ db_delete('uc_product_features')
+ ->condition('pfid', $feature['pfid'])
+ ->execute();
+
+ drupal_set_message(t('The product feature has been deleted.'));
+ $form_state['redirect'] = 'node/' . $node->nid . '/edit/features';
+}
+
/**
* Add the form for adding a product feature to the features tab.
*
=== modified file 'uc_product/uc_product.module'
--- uc_product/uc_product.module 2010-06-21 13:36:42 +0000
+++ uc_product/uc_product.module 2010-06-24 17:27:12 +0000
@@ -114,6 +114,20 @@
'type' => MENU_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
+ $items['node/%node/edit/features/%/add'] = array(
+ 'page arguments' => array(1, 4, NULL, 5),
+ 'access callback' => 'uc_product_feature_access',
+ 'access arguments' => array(1),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['node/%node/edit/features/%/%/delete'] = array(
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('uc_product_feature_delete_confirm', 1, 4, 5),
+ 'access callback' => 'uc_product_feature_access',
+ 'access arguments' => array(1),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'uc_product.admin.inc',
+ );
}
$items['admin/store/settings/products/defaults'] = array(
=== modified file 'uc_roles/uc_roles.module'
--- uc_roles/uc_roles.module 2010-05-17 14:07:01 +0000
+++ uc_roles/uc_roles.module 2010-06-24 17:25:14 +0000
@@ -13,8 +13,6 @@
* Development sponsored by the Ubercart project. http://www.ubercart.org
*/
-require_once 'uc_roles.ca.inc';
-
/******************************************************************************
* Hook Functions (Drupal) *
******************************************************************************/
@@ -24,7 +22,7 @@
*/
function uc_roles_help($path, $arg) {
if ($path == 'node/%/edit/features' && $arg[4] == 'role') {
- return t('Add roles through this page and then use the conditional actions interface to limit which orders they are applied to. Most important is the order status on which role granting will be triggered.', array('!url' => url(CA_UI_PATH)));
+ return t('Add roles through this page and then use the Rules interface to limit which orders they are applied to. Most important is the order status on which role granting will be triggered.', array('!url' => url(RULES_UI_PATH)));
}
switch ($path) {
case 'admin/people/expiration':
@@ -56,7 +54,7 @@
// Role expired.
elseif ($expiration->expiration <= REQUEST_TIME) {
uc_roles_revoke($account, $expiration->rid);
- ca_pull_trigger('uc_roles_notify_revoke', $account, $expiration);
+ rules_invoke_event('uc_roles_notify_revoke', $account, $expiration);
}
// Remind the user about an upcoming expiration.
@@ -69,7 +67,7 @@
// If we're past the expiration time minus the reminder time.
$threshold = _uc_roles_get_expiration(-$reminder_qty, $reminder_granularity, $expiration->expiration);
if ($threshold <= REQUEST_TIME) {
- ca_pull_trigger('uc_roles_notify_reminder', $account, $expiration);
+ rules_invoke_event('uc_roles_notify_reminder', $account, $expiration);
db_update('uc_roles_expirations')
->fields(array('notified' => 1))
=== renamed file 'uc_roles/uc_roles.ca.inc' => 'uc_roles/uc_roles.rules.inc'
--- uc_roles/uc_roles.ca.inc 2010-04-26 18:08:32 +0000
+++ uc_roles/uc_roles.rules.inc 2010-06-24 17:25:14 +0000
@@ -3,296 +3,226 @@
/**
* @file
- * This file contains the Conditional Actions hooks and functions necessary to make the
- * roles-related entity, conditions, events, and actions work.
+ * Rules hooks for uc_roles.module.
*/
-/******************************************************************************
- * Conditional Actions Hooks *
- ******************************************************************************/
-
/**
- * Implement hook_ca_entity().
+ * Implement hook_rules_data_info().
*
- * An entity is defined in order to get a role expiration down
- * to token in the email.
+ * An entity is defined in order to get role expiration tokens in the email.
*/
-function uc_roles_ca_entity() {
+function uc_roles_rules_data_info() {
// CA entity for a role expiration object.
$entities['uc_roles_expiration'] = array(
- '#title' => t('Ubercart role expiration'),
- '#type' => 'object',
+ 'label' => t('Ubercart role expiration'),
+ 'wrap' => TRUE,
+ 'property info' => array(
+ 'reid' => array(
+ 'type' => 'integer',
+ 'label' => t('Role expiration ID'),
+ 'description' => t('Primary key for role expirations.'),
+ ),
+ 'uid' => array(
+ 'type' => 'integer',
+ 'label' => t('User ID'),
+ 'description' => t('The user account ID.'),
+ ),
+ 'user' => array(
+ 'type' => 'user',
+ 'label' => t('User'),
+ 'description' => t('The user account that has the role.'),
+ 'getter callback' => 'uc_roles_get_expiration_properties',
+ 'setter callback' => 'uc_roles_set_expiration_properties',
+ ),
+ 'rid' => array(
+ 'type' => 'integer',
+ 'label' => t('Role ID'),
+ 'description' => t('The granted role.'),
+ ),
+ 'expiration' => array(
+ 'type' => 'date',
+ 'label' => t('Expiration time'),
+ 'description' => t('The time the role will be removed from the user.'),
+ ),
+ 'notified' => array(
+ 'type' => 'boolean',
+ 'label' => t('Notified'),
+ 'description' => t('Indicates the user has been warned that the role will be removed soon.'),
+ ),
+ ),
);
return $entities;
}
/**
- * Implement hook_ca_predicate().
- */
-function uc_roles_ca_predicate() {
- $predicates = array();
-
- // Renew all the roles on an order when the status matches what's set in the roles admin settings.
- $predicates['uc_role_renewal'] = array(
- '#title' => t('Grant or renew purchased roles'),
- '#description' => t('Grant or renew purchased roles if the order status matches.'),
- '#class' => 'renewal',
- '#trigger' => 'uc_order_status_update',
- '#status' => 1,
- '#conditions' => array(
- '#operator' => 'AND',
- '#conditions' => array(
- array(
- '#name' => 'uc_order_status_condition',
- '#title' => t('If the original order status was not Completed.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'negate' => TRUE,
- 'order_status' => 'completed',
- ),
- ),
- array(
- '#name' => 'uc_order_status_condition',
- '#title' => t('If the updated order status is Completed.'),
- '#argument_map' => array(
- 'order' => 'updated_order',
- ),
- '#settings' => array(
- 'order_status' => 'completed',
- ),
- ),
- ),
- ),
- '#actions' => array(
- array(
- '#name' => 'uc_roles_order_renew',
- '#title' => t('Update all role expirations for this order.'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
- '#settings' => array(
- 'message' => FALSE,
- ),
- ),
- ),
- );
-
- $order_args = array(
- 'order' => 'order',
- 'expiration' => 'expiration',
- );
-
- $user_args = array(
- 'account' => 'account',
- 'expiration' => 'expiration',
- );
-
- // Notify the user when a role is granted.
- $predicates['uc_role_notify_grant'] = array(
- '#title' => t('Notify customer when a role is granted'),
- '#description' => t('Notify the customer when they have had a role granted on their user.'),
- '#class' => 'notification',
- '#trigger' => 'uc_roles_notify_grant',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_roles_order_email',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => $order_args,
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[uc_order:email]',
- 'subject' => uc_get_message('uc_roles_grant_subject'),
- 'message' => uc_get_message('uc_roles_grant_message'),
- 'format' => filter_default_format(),
- ),
- ),
- ),
- );
-
- // Notify the user when a role is revoked.
- $predicates['uc_role_notify_revoke'] = array(
- '#title' => t('Notify customer when a role is revoked'),
- '#description' => t('Notify the customer when they have had a role revoked from their user.'),
- '#class' => 'notification',
- '#trigger' => 'uc_roles_notify_revoke',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_roles_user_email',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => $user_args,
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[user:mail]',
- 'subject' => uc_get_message('uc_roles_revoke_subject'),
- 'message' => uc_get_message('uc_roles_revoke_message'),
- 'format' => filter_default_format(),
- ),
- ),
- ),
- );
-
- // Notify the user when a role is renewed.
- $predicates['uc_role_notify_renew'] = array(
- '#title' => t('Notify customer when a role is renewed'),
- '#description' => t('Notify the customer when they have had a role renewed on their user.'),
- '#class' => 'notification',
- '#trigger' => 'uc_roles_notify_renew',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_roles_order_email',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => $order_args,
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[uc_order:email]',
- 'subject' => uc_get_message('uc_roles_renew_subject'),
- 'message' => uc_get_message('uc_roles_renew_message'),
- 'format' => filter_default_format(),
- ),
- ),
- ),
- );
-
- // Notify the user when a role is about to expire.
- $predicates['uc_role_notify_reminder'] = array(
- '#title' => t('Notify customer when a role is about to expire'),
- '#description' => t('Notify the customer when they have had a role that is about to expire.'),
- '#class' => 'notification',
- '#trigger' => 'uc_roles_notify_reminder',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_roles_user_email',
- '#title' => t('Send an e-mail to the customer'),
- '#argument_map' => $user_args,
- '#settings' => array(
- 'from' => uc_store_email_from(),
- 'addresses' => '[user:mail]',
- 'subject' => uc_get_message('uc_roles_reminder_subject'),
- 'message' => uc_get_message('uc_roles_reminder_message'),
- 'format' => filter_default_format(),
- ),
- ),
- ),
- );
-
- return $predicates;
-}
-
-/**
- * Implement hook_ca_action().
- */
-function uc_roles_ca_action() {
+ * Callback for getting role expiration properties.
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function uc_roles_get_expiration_properties($expiration, array $options, $name, $entity_type) {
+ switch ($name) {
+ case 'user':
+ return $expiration->uid;
+ }
+}
+
+/**
+ * Callback for setting role expiration properties.
+ * @see entity_metadata_node_entity_info_alter()
+ */
+function uc_roles_set_expiration_properties($expiration, $name, $value) {
+ if ($name == 'user') {
+ $expiration->uid = $value;
+ }
+}
+
+/**
+ * Implement hook_rules_action_info().
+ */
+function uc_roles_rules_action_info() {
// Renew a role expiration.
$actions['uc_roles_order_renew'] = array(
- '#title' => t('Renew the roles on an order.'),
- '#category' => t('renewal'),
- '#callback' => 'uc_roles_action_order_renew',
- '#arguments' => array(
+ 'lable' => t('Renew the roles on an order.'),
+ 'group' => t('Renewal'),
+ 'base' => 'uc_roles_action_order_renew',
+ 'parameter' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- ),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
+ 'message' => array(
+ 'type' => 'boolean',
+ 'label' => t('Display messages to alert users of any new or updated roles.'),
+ ),
+ ),
+ );
+
+ $email_args = array(
+ 'expiration' => array(
+ 'type' => 'uc_roles_expiration',
+ 'label' => t('Role expiration'),
+ ),
+ 'from' => array(
+ 'type' => 'text',
+ 'label' => t('Sender'),
+ ),
+ 'addresses' => array(
+ 'type' => 'text',
+ 'label' => t('Recipients'),
+ ),
+ 'subject' => array(
+ 'type' => 'text',
+ 'label' => t('Subject'),
+ ),
+ 'message' => array(
+ 'type' => 'text',
+ 'label' => t('Message'),
+ ),
+ 'format' => array(
+ 'type' => 'integer',
+ 'label' => t('Message format'),
+ 'options list' => 'uc_roles_message_formats',
),
);
// Send an email to an order with a role expiration
$actions['uc_roles_order_email'] = array(
- '#title' => t('Send an order email regarding roles.'),
- '#category' => t('Notification'),
- '#callback' => 'uc_roles_action_order_email',
- '#arguments' => array(
+ 'label' => t('Send an order email regarding roles.'),
+ 'group' => t('Notification'),
+ 'base' => 'uc_roles_action_order_email',
+ 'parameter' => array(
'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- ),
- 'expiration' => array(
- '#entity' => 'uc_roles_expiration',
- '#title' => t('Role expiration'),
- ),
- ),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
+ ),
+ ) + $email_args,
);
// Send an email to a user with a role expiration
$actions['uc_roles_user_email'] = array(
- '#title' => t('Send an order email regarding roles.'),
- '#category' => t('Notification'),
- '#callback' => 'uc_roles_action_user_email',
- '#arguments' => array(
+ 'label' => t('Send an order email regarding roles.'),
+ 'group' => t('Notification'),
+ 'base' => 'uc_roles_action_user_email',
+ 'parameter' => array(
'account' => array(
- '#entity' => 'user',
- '#title' => t('User'),
- ),
- 'expiration' => array(
- '#entity' => 'uc_roles_expiration',
- '#title' => t('Role expiration'),
- ),
- ),
+ 'type' => 'user',
+ 'label' => t('User'),
+ ),
+ ) + $email_args,
);
return $actions;
}
/**
- * Implement hook_ca_trigger().
- */
-function uc_roles_ca_trigger() {
+ * Options list callback for message formats.
+ */
+function uc_roles_message_formats() {
+ global $user;
+
+ $options = array();
+ $formats = filter_formats($user);
+ foreach ($formats as $format) {
+ $options[$format->format] = $format->name;
+ }
+
+ return $options;
+}
+
+/**
+ * Implement hook_rules_event_info().
+ */
+function uc_roles_rules_event_info() {
$order = array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
);
$account = array(
- '#entity' => 'user',
- '#title' => t('User'),
+ 'type' => 'user',
+ 'label' => t('User'),
);
$expiration = array(
- '#entity' => 'uc_roles_expiration',
- '#title' => t('Role expiration'),
- );
-
- $triggers['uc_roles_notify_grant'] = array(
- '#title' => t('E-mail for granted roles'),
- '#category' => t('Notification'),
- '#arguments' => array(
- 'order' => $order,
- 'expiration' => $expiration,
- ),
- );
-
- $triggers['uc_roles_notify_revoke'] = array(
- '#title' => t('E-mail for revoked roles'),
- '#category' => t('Notification'),
- '#arguments' => array(
- 'account' => $account,
- 'expiration' => $expiration,
- ),
- );
-
- $triggers['uc_roles_notify_renew'] = array(
- '#title' => t('E-mail for renewed roles'),
- '#category' => t('Notification'),
- '#arguments' => array(
- 'order' => $order,
- 'expiration' => $expiration,
- ),
- );
-
- $triggers['uc_roles_notify_reminder'] = array(
- '#title' => t('E-mail for role expiration reminders'),
- '#category' => t('Notification'),
- '#arguments' => array(
- 'account' => $account,
- 'expiration' => $expiration,
- ),
- );
-
- return $triggers;
+ 'type' => 'uc_roles_expiration',
+ 'label' => t('Role expiration'),
+ );
+
+ $events['uc_roles_notify_grant'] = array(
+ 'label' => t('E-mail for granted roles'),
+ 'group' => t('Notification'),
+ 'variables' => array(
+ 'order' => $order,
+ 'expiration' => $expiration,
+ ),
+ );
+
+ $events['uc_roles_notify_revoke'] = array(
+ 'label' => t('E-mail for revoked roles'),
+ 'group' => t('Notification'),
+ 'variables' => array(
+ 'account' => $account,
+ 'expiration' => $expiration,
+ ),
+ );
+
+ $events['uc_roles_notify_renew'] = array(
+ 'label' => t('E-mail for renewed roles'),
+ 'group' => t('Notification'),
+ 'variables' => array(
+ 'order' => $order,
+ 'expiration' => $expiration,
+ ),
+ );
+
+ $events['uc_roles_notify_reminder'] = array(
+ 'label' => t('E-mail for role expiration reminders'),
+ 'group' => t('Notification'),
+ 'variables' => array(
+ 'account' => $account,
+ 'expiration' => $expiration,
+ ),
+ );
+
+ return $events;
}
/**
@@ -302,7 +232,14 @@
*
* @see uc_roles_action_order_email_form()
*/
-function uc_roles_action_order_email($order, $role_expiration, $settings) {
+function uc_roles_action_order_email($order, $role_expiration, $from, $addresses, $subject, $message, $format) {
+ $settings = array(
+ 'from' => $from,
+ 'addresses' => $addresses,
+ 'subject' => $subject,
+ 'message' => $message,
+ 'format' => $format,
+ );
// Token replacements for the subject and body
$settings['replacements'] = array(
'uc_order' => $order,
@@ -327,22 +264,20 @@
}
/**
- * Email settings form.
- *
- * @see uc_roles_action_order_email()
- */
-function uc_roles_action_order_email_form($form_state, $settings = array()) {
- return uc_roles_build_email_form($form_state, $settings, array('global', 'uc_roles', 'user'));
-}
-
-/**
* Send an email with order and role replacement tokens.
*
* The recipients, subject, and message fields take order token replacements.
*
* @see uc_roles_action_user_email_form()
*/
-function uc_roles_action_user_email($account, $role_expiration, $settings) {
+function uc_roles_action_user_email($account, $role_expiration, $from, $addresses, $subject, $message, $format) {
+ $settings = array(
+ 'from' => $from,
+ 'addresses' => $addresses,
+ 'subject' => $subject,
+ 'message' => $message,
+ 'format' => $format,
+ );
// Token replacements for the subject and body
$settings['replacements'] = array(
'user' => $account,
@@ -367,65 +302,6 @@
}
/**
- * Email settings form.
- *
- * @see uc_roles_action_user_email()
- */
-function uc_roles_action_user_email_form($form_state, $settings = array()) {
- return uc_roles_build_email_form($form_state, $settings, array('global', 'uc_roles', 'user'));
-}
-
-/**
- * Build an email settings form.
- */
-function uc_roles_build_email_form($form, &$form_state, $settings, $token_filters) {
- $form['from'] = array(
- '#type' => 'textfield',
- '#title' => t('Sender'),
- '#default_value' => $settings['from'],
- '#description' => t('The "From" address.'),
- '#required' => TRUE,
- );
- $form['addresses'] = array(
- '#type' => 'textarea',
- '#title' => t('Recipients'),
- '#default_value' => $settings['addresses'],
- '#description' => t('Enter the email addresses to receive the notifications, one on each line. You may use order tokens for dynamic email addresses.'),
- '#required' => TRUE,
- );
- $form['subject'] = array(
- '#type' => 'textfield',
- '#title' => t('Subject'),
- '#default_value' => $settings['subject'],
- '#required' => TRUE,
- );
- $form['message'] = array(
- '#type' => 'textarea',
- '#title' => t('Message'),
- '#default_value' => $settings['message'],
- '#text_format' => $settings['format'],
- );
-
- $form['token_help'] = array(
- '#type' => 'fieldset',
- '#title' => t('Replacement patterns'),
- '#description' => t('You can make use of the replacement patterns in the recipients field, the subject, and the message body.'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- foreach ($token_filters as $name) {
- $form['token_help'][$name] = array(
- '#type' => 'fieldset',
- '#title' => t('@name replacement patterns', array('@name' => drupal_ucfirst($name))),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- }
-
- return $form;
-}
-
-/**
* Renew an orders product roles.
*
* This function updates expiration time on all roles found on all products
@@ -440,7 +316,7 @@
* An Ubercart order object.
* @see uc_roles_action_order_renew_form()
*/
-function uc_roles_action_order_renew($order, $settings) {
+function uc_roles_action_order_renew($order) {
// Load the order's user and exit if not available.
if (!($account = user_load($order->uid))) {
return;
@@ -490,7 +366,7 @@
uc_order_comment_save($order->order_id, $account->uid, $comment);
// Trigger role email.
- ca_pull_trigger('uc_roles_notify_' . $op, $order, $new_expiration);
+ rules_invoke_event('uc_roles_notify_' . $op, $order, $new_expiration);
}
}
}
=== added file 'uc_roles/uc_roles.rules_defaults.inc'
--- uc_roles/uc_roles.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ uc_roles/uc_roles.rules_defaults.inc 2010-06-24 17:25:14 +0000
@@ -0,0 +1,101 @@
+label = t('Grant or renew purchased roles');
+ $rule->active = TRUE;
+ $rule->event('uc_order_status_update');
+ $rule->condition(rules_condition('data_is', array('data:select' => 'order:order-status', 'value' => 'completed'))->negate())
+ ->condition('data_is', array('data:select' => 'updated_order:order-status', 'value' => 'completed'))
+ ->action('uc_roles_order_renew', array('order:select' => 'order', 'message' => FALSE));
+ $configs['uc_role_renewal'] = $rule;
+
+ $order_args = array(
+ 'order:select' => 'order',
+ 'expiration:select' => 'expiration',
+ );
+
+ $user_args = array(
+ 'account:select' => 'account',
+ 'expiration:select' => 'expiration',
+ );
+
+ // Notify the user when a role is granted.
+ $rule = rules_reaction_rule();
+ $rule->label = t('Notify customer when a role is granted');
+ $rule->active = TRUE;
+ $rule->event('uc_roles_notify_grant');
+ $rule->action('uc_roles_order_email', array(
+ 'order:select' => 'order',
+ 'expiration:select' => 'expiration',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[order:email]',
+ 'subject' => uc_get_message('uc_roles_grant_subject'),
+ 'message' => uc_get_message('uc_roles_grant_message'),
+ 'format' => filter_default_format(),
+ ));
+ $configs['uc_role_notify_grant'] = $rule;
+
+ // Notify the user when a role is revoked.
+ $rule = rules_reaction_rule();
+ $rule->label = t('Notify customer when a role is revoked');
+ $rule->active = TRUE;
+ $rule->event('uc_roles_notify_revoke');
+ $rule->action('uc_roles_user_email', array(
+ 'account:select' => 'account',
+ 'expiration:select' => 'expiration',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[account:mail]',
+ 'subject' => uc_get_message('uc_roles_revoke_subject'),
+ 'message' => uc_get_message('uc_roles_revoke_message'),
+ 'format' => filter_default_format(),
+ ));
+ $configs['uc_role_notify_revoke'] = $rule;
+
+ // Notify the user when a role is renewed.
+ $rule = rules_reaction_rule();
+ $rule->label = t('Notify customer when a role is renewed');
+ $rule->active = TRUE;
+ $rule->event('uc_roles_notify_renew');
+ $rule->action('uc_roles_order_email', array(
+ 'order:select' => 'order',
+ 'expiration:select' => 'expiriation',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[order:email]',
+ 'subject' => uc_get_message('uc_roles_renew_subject'),
+ 'message' => uc_get_message('uc_roles_renew_message'),
+ 'format' => filter_default_format(),
+ ));
+ $configs['uc_role_notify_renew'] = $rule;
+
+ // Notify the user when a role is about to expire.
+ $rule = rules_reaction_rule();
+ $rule->label = t('Notify customer when a role is about to expire');
+ $rule->active = TRUE;
+ $rule->event('uc_roles_notify_reminder');
+ $rule->action('uc_roles_user_email', array(
+ 'account:select' => 'account',
+ 'expiration:select' => 'expiration',
+ 'from' => uc_store_email_from(),
+ 'addresses' => '[account:mail]',
+ 'subject' => uc_get_message('uc_roles_reminder_subject'),
+ 'message' => uc_get_message('uc_roles_reminder_message'),
+ 'format' => filter_default_format(),
+ ));
+ $configs['uc_role_notify_reminder'] = $rule;
+
+ return $configs;
+}
+
=== modified file 'uc_stock/uc_stock.info'
--- uc_stock/uc_stock.info 2010-04-05 19:55:02 +0000
+++ uc_stock/uc_stock.info 2010-06-22 18:05:23 +0000
@@ -1,7 +1,6 @@
; $Id$
name = Stock
description = Manage stock levels of your Ubercart products
-dependencies[] = ca
dependencies[] = uc_product
dependencies[] = uc_reports
dependencies[] = uc_store
=== modified file 'uc_stock/uc_stock.module'
--- uc_stock/uc_stock.module 2010-04-13 13:27:26 +0000
+++ uc_stock/uc_stock.module 2010-06-22 18:05:23 +0000
@@ -14,8 +14,6 @@
* Development sponsored by the Ubercart project. http://www.ubercart.org
*/
-require_once('uc_stock.ca.inc');
-
/******************************************************************************
* Hook Functions (Drupal) *
******************************************************************************/
@@ -148,6 +146,15 @@
return $messages;
}
+/**
+ * Implement hook_uc_order_product_delete().
+ */
+function uc_stock_uc_order_product_delete($order_product_id) {
+ // Put back the stock.
+ $product = db_query("SELECT model, qty FROM {uc_order_products} WHERE order_product_id = :id", array(':id' => $order_product_id))->fetchObject();
+ uc_stock_adjust($product->model, $product->qty);
+}
+
/******************************************************************************
* Module and Helper Functions *
******************************************************************************/
=== renamed file 'uc_stock/uc_stock.ca.inc' => 'uc_stock/uc_stock.rules.inc'
--- uc_stock/uc_stock.ca.inc 2010-04-13 13:27:26 +0000
+++ uc_stock/uc_stock.rules.inc 2010-06-22 18:05:23 +0000
@@ -3,61 +3,32 @@
/**
* @file
- * This file contains all the Workflow-NG hooks that are neccesary for Workflow
- * integeration with the uc_stock module
+ * Rules hooks for uc_stock.module.
*/
-/******************************************************************************
- * Conditional Action Hooks *
- ******************************************************************************/
-
/**
- * Implement hook_ca_predicate().
+ * Implement hook_rules_action_info().
*/
-function uc_stock_ca_predicate() {
- $predicates['uc_stock_decrement_on_order'] = array(
- '#title' => t('Decrement stock upon order submission'),
- '#trigger' => 'uc_checkout_complete',
- '#class' => 'uc_stock',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_stock_action_decrement_stock',
- '#title' => t('Decrement stock of products in order'),
- '#argument_map' => array(
- 'order' => 'order',
- ),
+function uc_stock_rules_action_info() {
+ $actions['uc_stock_action_decrement_stock'] = array(
+ 'label' => t('Decrement stock of products on the order with tracking activated.'),
+ 'group' => t('Stock'),
+ 'base' => 'uc_stock_action_decrement_stock',
+ 'parameter' => array(
+ 'order' => array(
+ 'type' => 'uc_order',
+ 'label' => t('Order'),
),
),
);
- return $predicates;
-}
-
-/**
- * Implement hook_action().
- */
-function uc_stock_ca_action() {
- $actions['uc_stock_action_decrement_stock'] = array(
- '#title' => t('Decrement stock of products on the order with tracking activated.'),
- '#callback' => 'uc_stock_action_decrement_stock',
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- ),
- '#category' => t('Stock'),
- );
-
return $actions;
}
-/******************************************************************************
- * Conditional Action Callbacks and Forms *
- ******************************************************************************/
-
/**
* Decrease the stock of ordered products.
*/
-function uc_stock_action_decrement_stock($order, $settings) {
+function uc_stock_action_decrement_stock($order) {
if (is_array($order->products)) {
array_walk($order->products, 'uc_stock_adjust_product_stock', $order);
}
=== added file 'uc_stock/uc_stock.rules_defaults.inc'
--- uc_stock/uc_stock.rules_defaults.inc 1970-01-01 00:00:00 +0000
+++ uc_stock/uc_stock.rules_defaults.inc 2010-06-22 18:05:23 +0000
@@ -0,0 +1,23 @@
+label = t('Decrement stock upon order submission');
+ $rule->active = TRUE;
+ $rule->event('uc_checkout_complete');
+ $rule->action('uc_stock_action_decrement_stock', array('order:select' => 'order'));
+ $configs['uc_stock_decrement_on_order'] = $rule;
+
+ return $configs;
+}
+
=== modified file 'uc_store/uc_store.module'
--- uc_store/uc_store.module 2010-06-11 19:31:18 +0000
+++ uc_store/uc_store.module 2010-06-24 18:23:07 +0000
@@ -68,10 +68,10 @@
public $street2 = '';
public $city = '';
public $zone = 0;
- public $postal_code = 0;
+ public $postal_code = '';
public $country = 0;
public $phone = '';
- public $emial = '';
+ public $email = '';
function __construct() { }
@@ -691,6 +691,74 @@
}
/**
+ * Helper function for hook_entity_property_info() and hook_rules_data_info().
+ *
+ * Should be used by implementations of those hooks that wish to wrap address
+ * selectors.
+ */
+function uc_address_property_info() {
+ return array(
+ 'first-name' => array(
+ 'type' => 'text',
+ 'label' => t('First name'),
+ 'description' => t('First name of the addressee.'),
+ ),
+ 'last-name' => array(
+ 'type' => 'text',
+ 'label' => t('Last name'),
+ 'description' => t('Last naem of the addressee.'),
+ ),
+ 'company' => array(
+ 'type' => 'text',
+ 'label' => t('Company'),
+ 'description' => t('Name of the company at the address.'),
+ ),
+ 'street1' => array(
+ 'type' => 'text',
+ 'label' => t('Street line 1'),
+ 'description' => t('First line of the street address.'),
+ ),
+ 'street2' => array(
+ 'type' => 'text',
+ 'label' => t('Street line 2'),
+ 'description' => t('Second line of the street address.'),
+ ),
+ 'city' => array(
+ 'type' => 'text',
+ 'label' => t('City'),
+ 'description' => t('Address city.'),
+ ),
+ 'zone' => array(
+ 'type' => 'integer',
+ 'label' => t('Zone'),
+ 'description' => t('Address state/province/zone.'),
+ 'options list' => 'uc_zone_option_list',
+ ),
+ 'postal-code' => array(
+ 'type' => 'text',
+ 'label' => t('Postal code'),
+ 'description' => t('Address post code.'),
+ ),
+ 'country' => array(
+ 'type' => 'integer',
+ 'label' => t('Country'),
+ 'description' => t('Address country.'),
+ 'options list' => 'uc_country_option_list',
+ ),
+ 'phone' => array(
+ 'type' => 'text',
+ 'label' => t('Phone'),
+ 'description' => t('Contact phone number.'),
+ ),
+ 'email' => array(
+ 'type' => 'text',
+ 'label' => t('Email'),
+ 'description' => t('Contact email address.'),
+ ),
+ );
+}
+
+/**
* Form to configure address fields.
*
* @ingroup forms
@@ -1289,6 +1357,19 @@
}
/**
+ * Helper function to return zone options, grouped by country.
+ */
+function uc_zone_option_list() {
+ $result = db_query("SELECT z.*, c.country_name FROM {uc_zones} AS z LEFT JOIN {uc_countries} AS c ON z.zone_country_id = c.country_id ORDER BY c.country_name, z.zone_name");
+
+ foreach ($result as $zone) {
+ $options[$zone->country_name][$zone->zone_id] = $zone->zone_name;
+ }
+
+ return $options;
+}
+
+/**
* Retrieve a country's name from the database, using its ID.
*
* @param $id
@@ -1339,6 +1420,20 @@
return $select;
}
+function uc_country_option_list() {
+ $result = db_query("SELECT * FROM {uc_countries} WHERE version > :version ORDER BY :sort", array(':version' => 0, ':sort' => 'country_name'));
+
+ $options = array();
+ while ($country = $result->fetchAssoc()) {
+ $options[$country['country_id']] = $country['country_name'];
+ }
+ if (count($options) == 0) {
+ $options[] = t('No countries found.');
+ }
+
+ return $options;
+}
+
/**
* Create a day select box for a form.
*/
=== modified file 'uc_taxes/uc_taxes.admin.inc'
--- uc_taxes/uc_taxes.admin.inc 2010-03-24 13:11:48 +0000
+++ uc_taxes/uc_taxes.admin.inc 2010-06-10 19:18:33 +0000
@@ -12,7 +12,7 @@
function uc_taxes_admin_settings() {
$rows = array();
- $header = array(t('Name'), t('Rate'), t('Taxed products'), t('Taxed product types'), t('Taxed line items'), t('Weight'), array('data' => t('Operations'), 'colspan' => 4));
+ $header = array(t('Name'), t('Rate'), t('Taxed products'), t('Taxed product types'), t('Taxed line items'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
// Loop through all the defined tax rates.
foreach (uc_taxes_rate_load() as $rate_id => $rate) {
@@ -27,7 +27,6 @@
implode(', ', $rate->taxed_line_items),
$rate->weight,
l(t('edit'), 'admin/store/settings/taxes/' . $rate_id . '/edit'),
- l(t('conditions'), CA_UI_PATH . '/uc_taxes_' . $rate_id . '/edit/conditions'),
l(t('clone'), 'admin/store/settings/taxes/' . $rate_id . '/clone'),
l(t('delete'), 'admin/store/settings/taxes/' . $rate_id . '/delete'),
);
@@ -35,7 +34,7 @@
// Let the user know if no tax rates are defined.
if (empty($rows)) {
- $rows[] = array(array('data' => t('No rates available.'), 'colspan' => 10));
+ $rows[] = array(array('data' => t('No rates available.'), 'colspan' => 9));
}
// Build the output including the table and a link to add a new rate.
@@ -140,6 +139,11 @@
'#default_value' => $rate_id ? $rate->weight : 0,
);
+ if (module_exists('rules')) {
+ $conditions = rules_config_load('uc_taxes_' . $rate->id);
+ $conditions->form($form, $form_state);
+ }
+
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
@@ -184,11 +188,14 @@
);
uc_taxes_rate_save((object) $rate);
- // Update the name of the associated predicate.
- db_update('ca_predicates')
- ->fields(array('title' => $rate['name']))
- ->condition('pid', 'uc_taxes_' . $rate['id'])
- ->execute();
+ // Update the name of the associated conditions.
+ if (module_exists('rules')) {
+ $conditions = rules_config_load('uc_taxes_' . $rate['id']);
+ if ($conditions) {
+ $conditions->label = $rate['name'];
+ $conditions->save();
+ }
+ }
// Display a message and redirect back to the overview.
drupal_set_message(t('Tax rate %name saved.', array('%name' => $form_state['values']['name'])));
@@ -211,10 +218,10 @@
// Save the new rate.
$rate = uc_taxes_rate_save($rate);
- // Clone the associated predicate as well.
- if ($predicate = ca_load_predicate('uc_taxes_' . $rate_id)) {
- $predicate['#pid'] = 'uc_taxes_' . $rate->id;
- ca_save_predicate($predicate);
+ // Clone the associated conditions as well.
+ if ($conditions = rules_config_load('uc_taxes_' . $rate_id)) {
+ unset($conditions->id);
+ $conditions->save('uc_taxes_' . $rate->id);
}
// Display a message and redirect back to the overview.
=== modified file 'uc_taxes/uc_taxes.api.php'
--- uc_taxes/uc_taxes.api.php 2010-03-16 21:15:17 +0000
+++ uc_taxes/uc_taxes.api.php 2010-06-09 19:05:00 +0000
@@ -20,17 +20,6 @@
* An array of tax line item objects keyed by a module-specific id.
*/
function hook_uc_calculate_tax($order) {
- global $user;
- if (is_numeric($order)) {
- $order = uc_order_load($order);
- $account = user_load(array('uid' => $order->uid));
- }
- elseif ((int)$order->uid) {
- $account = user_load(array('uid' => intval($order->uid)));
- }
- else {
- $account = $user;
- }
if (!is_object($order)) {
return array();
}
@@ -54,25 +43,8 @@
$use_same_rates = FALSE;
}
- $arguments = array(
- 'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- '#data' => $order,
- ),
- 'tax' => array(
- '#entity' => 'tax',
- '#title' => t('Tax rule'),
- // #data => each $tax in the following foreach() loop;
- ),
- 'account' => array(
- '#entity' => 'user',
- '#title' => t('User'),
- '#data' => $account,
- ),
- );
+ $use_rules = module_exists('rules');
- $predicates = ca_load_trigger_predicates('calculate_taxes');
foreach (uc_taxes_rate_load() as $tax) {
if ($use_same_rates) {
foreach ((array)$order->line_items as $old_line) {
@@ -83,9 +55,16 @@
}
}
- $arguments['tax']['#data'] = $tax;
- if (ca_evaluate_conditions($predicates['uc_taxes_' . $tax->id], $arguments)) {
- $line_item = uc_taxes_action_apply_tax($order, $tax);
+ if ($use_rules) {
+ $set = rules_config_load('uc_taxes_' . $tax->id);
+ $apply = $set->execute($order);
+ }
+ else {
+ $apply = TRUE;
+ }
+
+ if ($apply) {
+ $line_item = uc_taxes_apply_tax($order, $tax);
if ($line_item) {
$order->taxes[$line_item->id] = $line_item;
}
=== modified file 'uc_taxes/uc_taxes.info'
--- uc_taxes/uc_taxes.info 2010-02-02 15:41:04 +0000
+++ uc_taxes/uc_taxes.info 2010-06-21 19:34:42 +0000
@@ -4,10 +4,10 @@
dependencies[] = uc_store
dependencies[] = uc_payment
dependencies[] = uc_product
-dependencies[] = ca
package = "Ubercart - core (optional)"
core = 7.x
files[] = uc_taxes.module
files[] = uc_taxes.install
files[] = uc_taxes.admin.inc
files[] = uc_taxes.ca.inc
+files[] = uc_taxes.rules_defaults.inc
=== modified file 'uc_taxes/uc_taxes.module'
--- uc_taxes/uc_taxes.module 2010-06-03 19:26:32 +0000
+++ uc_taxes/uc_taxes.module 2010-06-21 19:34:42 +0000
@@ -8,27 +8,11 @@
* Allows tax rules to be set up and applied to orders.
*/
-require_once('uc_taxes.ca.inc');
-
/******************************************************************************
* Drupal Hooks *
******************************************************************************/
/**
- * Implement hook_help().
- */
-function uc_taxes_help($path, $arg) {
- $output = '';
-
- switch ($path) {
- case 'admin/store/settings/taxes':
- return t('Add tax rates through this page and then use the conditional actions interface to add conditions to the taxes that limit which orders they are applied to. Especially important are the geographic area conditions for the delivery address. Use the conditions link to jump to a particular tax rate conditions configuration page.', array('!url' => url(CA_UI_PATH)));
- }
-
- return $output;
-}
-
-/**
* Implement hook_permission().
*/
function uc_taxes_permission() {
@@ -276,6 +260,10 @@
drupal_write_record('uc_taxes', $rate, array('id'));
}
+ if (module_exists('rules')) {
+ rules_clear_cache();
+ }
+
return $rate;
}
@@ -328,8 +316,10 @@
->condition('id', $rate_id)
->execute();
- // Delete the associated predicated if it has been saved to the database.
- ca_delete_predicate('uc_taxes_' . $rate_id);
+ // Delete the associated conditions if they have been saved to the database.
+ if (module_exists('rules')) {
+ rules_config_delete(array('uc_taxes_' . $rate_id));
+ }
}
/**
@@ -351,17 +341,6 @@
* Calculate the amount and types of taxes that apply to an order.
*/
function uc_taxes_uc_calculate_tax($order) {
- global $user;
- if (is_numeric($order)) {
- $order = uc_order_load($order);
- $account = user_load($order->uid);
- }
- elseif ((int)$order->uid) {
- $account = user_load(intval($order->uid));
- }
- else {
- $account = $user;
- }
if (!is_object($order)) {
return array();
}
@@ -385,25 +364,8 @@
$use_same_rates = FALSE;
}
- $arguments = array(
- 'order' => array(
- '#entity' => 'uc_order',
- '#title' => t('Order'),
- '#data' => $order,
- ),
- 'tax' => array(
- '#entity' => 'tax',
- '#title' => t('Tax rule'),
- // #data => each $tax in the following foreach() loop;
- ),
- 'account' => array(
- '#entity' => 'user',
- '#title' => t('User'),
- '#data' => $account,
- ),
- );
+ $use_rules = module_exists('rules');
- $predicates = ca_load_trigger_predicates('calculate_taxes');
foreach (uc_taxes_rate_load() as $tax) {
if ($use_same_rates) {
foreach ((array)$order->line_items as $old_line) {
@@ -414,9 +376,16 @@
}
}
- $arguments['tax']['#data'] = $tax;
- if (ca_evaluate_conditions($predicates['uc_taxes_' . $tax->id], $arguments)) {
- $line_item = uc_taxes_action_apply_tax($order, $tax);
+ if ($use_rules) {
+ $set = rules_config_load('uc_taxes_' . $tax->id);
+ $apply = $set->execute($order);
+ }
+ else {
+ $apply = TRUE;
+ }
+
+ if ($apply) {
+ $line_item = uc_taxes_apply_tax($order, $tax);
if ($line_item) {
$order->taxes[$line_item->id] = $line_item;
}
=== renamed file 'uc_taxes/uc_taxes.ca.inc' => 'uc_taxes/uc_taxes.rules_defaults.inc'
--- uc_taxes/uc_taxes.ca.inc 2010-03-16 21:15:17 +0000
+++ uc_taxes/uc_taxes.rules_defaults.inc 2010-06-10 19:17:11 +0000
@@ -3,109 +3,27 @@
/**
* @file
- * This file contains the Conditional Action hooks and functions necessary to
- * make the tax related entity, conditions, events, and actions work.
- */
-
-/******************************************************************************
- * Conditional Action Hooks *
- ******************************************************************************/
-
-/**
- * Implement hook_ca_entity().
- */
-function uc_taxes_ca_entity() {
- $entities['tax'] = array(
- '#title' => t('Tax rule'),
- '#type' => 'object',
- );
- return $entities;
-}
-
-/**
- * Implement hook_ca_trigger().
- *
- * Register an event for each tax rule in {uc_taxes}.
- */
-function uc_taxes_ca_trigger() {
- $triggers = array();
-
- $triggers['calculate_taxes'] = array(
- '#title' => t('Calculate taxes'),
- '#category' => t('Taxes'),
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- 'tax' => array('#entity' => 'tax', '#title' => t('Tax rule')),
- 'account' => array('#entity' => 'user', '#title' => t('User account')),
- ),
- );
-
- return $triggers;
-}
-
-/**
- * Implement hook_action_info().
- */
-function uc_taxes_ca_action() {
- $actions = array();
-
- // Loop through all the defined tax rates.
- foreach (uc_taxes_rate_load() as $rate) {
- $actions['uc_taxes_action_apply_tax_' . $rate->id] = array(
- '#title' => t('Apply !name', array('!name' => $rate->name)),
- '#category' => t('Taxes'),
- '#callback' => 'uc_taxes_action_apply_tax',
- '#arguments' => array(
- 'order' => array('#entity' => 'uc_order', '#title' => t('Order')),
- 'tax' => array('#entity' => 'tax', '#title' => t('Tax')),
- ),
- );
- }
-
- return $actions;
-}
-
-/**
- * Action callback to calculate a tax.
- *
- * @param $order
- * The order object being considered.
- * @param $tax
- * The tax rule calculating the amount.
- * @return
- * The line item array representing the amount of tax.
- */
-function uc_taxes_action_apply_tax($order, $tax) {
- return uc_taxes_apply_tax($order, $tax);
-}
-
-/**
- * Implement hook_ca_predicate().
- *
- * Create a predicate for each event corresponding to a tax rule.
- */
-function uc_taxes_ca_predicate() {
- $predicates = array();
-
- // Loop through all the defined tax rates.
- foreach (uc_taxes_rate_load() as $rate) {
- $predicates['uc_taxes_' . $rate->id] = array(
- '#title' => $rate->name,
- '#class' => 'taxes',
- '#trigger' => 'calculate_taxes',
- '#status' => 1,
- '#actions' => array(
- array(
- '#name' => 'uc_taxes_action_apply_tax_' . $rate->id,
- '#title' => t('Apply !name', array('!name' => $rate->name)),
- '#argument_map' => array(
- 'order' => 'order',
- 'tax' => 'tax',
- ),
- ),
- ),
- );
- }
-
- return $predicates;
+ * This file contains the default Rules configurations that allow conditions to
+ * be applied to taxes.
+ */
+
+/**
+ * Implement hook_default_rules_configuration().
+ *
+ * Create a condition set for each tax rule.
+ */
+function uc_taxes_default_rules_configuration() {
+ $configs = array();
+
+ // Loop through all the defined tax rates.
+ foreach (uc_taxes_rate_load() as $rate) {
+ $set = rules_and(array(
+ 'order' => array('type' => 'uc_order', 'label' => 'Order'),
+ ));
+ $set->label = t('@name conditions', array('@name' => $rate->name));
+
+ $configs['uc_taxes_' . $rate->id] = $set;
+ }
+
+ return $configs;
}
# Begin bundle
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