# 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' => '' . $alt . '', - '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' => '' . $alt . '', + '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 IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWRfkdLEAzWvfgH9Vf/////// /76////+YMXe88Cr2XjOegFL2rKnCCkAJjnOB32A3YgAFAAB9uUNTvt5vR1qjejefTt1z73g3OZ8 88Z3jza8AJDvTDnb6zzaFebBQVLpIeuvRt7l9gHu3pQG85tviEvTKme+uu17aIPn31wL532xfatJ qpTrurALq++o+8r1vibWpoZnbdc7RS5m+3rj0HPfD7u3vt3vdSad3uEolNAb3Xwe6e2a0AA0oSr3 XhtHk9WptpVK74MHou3w7fTSimlCgDe3jz3bW0YIkAL3eC8PTT0OqO2qLc8e7xqgFCQDeeG9Xomq 0KKSL3T1j41VACBGrDa1Ume3oevQp7a6e+B9SrpXkmzaKUF81DvSqpT1o1dTOVEncN3bonpVJSlv SlBqQp7Chd5Ls92+s1rzwAACyV9xr77dHjN7zgefA1tuTC6wvvVJRKU9L0gApeiY0FfdlKCg6qQq qZsoxIjSsAE2xVaMGa2oBFba1tlaAlQDRIyyNRGjVFNKBq59MNHSrwsJDNoHvLh6PEtgYECZsBKA IUBQ0VVU97z3hVGwlEAAEIQmIATUmVNhNkp6HqhkaABhABtIBKAIIJokk1PUxqnplANNMmgaBoNA AADI0ADTECJRTSmYmaKeUeho1GjQABoDQaAGgGgAEmkiEITRMQUybQxJiNT1PI0gZD1M1NG01Mmh kaDQ0CJIhABNNAmAI0CYQJpiaaU9BT0niahkGmmRmUCpIgBAQQaTJlPCE1MkaaaepkzQjT0gDQAA BpEVXuiAnYJAVVf9qisgT0qiVERSAih52BKkRiJGP+FlgoMF+6MD4AJPpJWWLjhxVR/ooLG0X6vo +yBn2ZHopBNe59uhdTf2gb+2bc6xLu5koumZ5uHnNYOMTkl7M5mzANGsbuecm0LvbsyKk7XM28u6 nXSE7VVdfciIEfSD+MftmI9why59i+0PoBixQJLyPfC+rczIdyA0Fltygo3Yb4VD49dVUqHAmnJW ICQ4zjcSYNKFcnR9oWjW3zFVqwEE738oYkWlAki8ZshYFCFQFrQa44QrKIVxW5VwzDdzIrpMvsiy 5o8xv/amwqSCp9ToTBANTxuQF2bvtK6wqmkFIhp23ZcyVDk/7c86DmmfFhEIQQyNhqsaVqCHmhlV FFh/Z6U9pQAHv+le98HwfWmPwg/iJPgM/Cm/R+fEfGlc93HllAjS+qkhn4RL/CQZQL87zuCoRwS+ 292a/uB0H/qf8tLYx6y3acmI5vjf/zDU+v+e1tlWv4hsfwi8i9Ksf4RV2Vc5ZsqN/+sq3m4ozwU/ 3yRfuMP/k72XzGf/Z6E4q5/k4Y6p/ucKp0Lg/3wW487EP8CYyqrc/wPc//S919zsbORpVEP+D9y+ vn819ls6zHD5MYx24xOosarvODnEs1BDcdsm0ytc6sfLra5yqzHZ5SmbDQkjcyg8pri5ZW1mKcpS mhQe5tzitk75f0B1+A/BlFBEjRVRVs41hlbZb50DEfxa461te92mbT8ITH8LBBQYeqFZBJF2VSEY oO2GpdezAsY3KGwExrVMs+FQcnPbWlkmniyhOGSFE0yTMdLZnFKSCkPY73YfvJCRCYM8p9fOw2eh uiU9lChb6hmGNH2+DPy63Md5y5LHkcCsg5TIjKz8gCdu+qe9hgmPoknkwkPc63S+3X8mndiyRVJN sKyJR0k2CGtET4MwZjQYKSsCyE6GwIT3Qpw+YHHzedT2+wKIiYevBZpHv0wxnrmfHRIChXBIiQkK gLI0G2waVFBKRXXIEkXVzbrbpgmFBigGmuGrRrmOEc0WAFYRSKKERMPjZCZCYkLIHHPPC9Q1DYSc IMVPCFCMgaSEKyCERYhyYZ95DSQNevOH1yTKzJISL9LDuKrSKOCuozJYKnbR3DJIRqHhaJdIeO6Q wkhqb4uYcekgHDd9h4wnbCqovt/e/dcgyyWax8UgTuUZVpbGcC4gFKhpMT6H4fLXHPSHbKVCyFiq Hik1lu5KZCyO1C8kCQCRZJBMgOb27PoBeRY8+bhbZB3JJAm3bjcCQhCMIseE34Xz3phHKNV4Pr8/ h5kFh8UUBGCxYsFnwIQOKBmgbdZSBrhUL/YpqrpMd2VsDfiB9CNpEWSoFagFRSisSlBTMqkKhVWp QxMwshiVhLbJJNPfvhz8jayn+OhyX5xXzeTp4XOyuOteONm1s7wu1jIu7onnBN1O6kieeTXLntdp kYLUt9m6ycdE7Ng7gzCqlyhylhLzCZfMRKg6qN9zWcqkzx1tkXiOcmaklzuXk+zwb5yx4qy0IJIc MkmMnY5pBQmRCdkJ4ekBQMS21ngYHAgaZMEOkmHFgY92HTAmc07ILvXO9AVkCoshMYdmcsnCLNdq Bd2ChtDtlmJOebMZxgWaYbenu9mSTSbUd8WCwMQOzDQhtIaSMxGGAjZiEYEUYU6+vOSOt53s87ew e7WbwbEkgB1zpeFYVoyDASMjIFSKEqFSCySsK8IVIKvb95eJ90YmvNkDaQ8DQZHXTEl4gwYI9LTh ENlAIgGSrIifTBXnUXin0a4bJOT4NColc4ZupnSKKulqo7oms3dY4RYnQuVWkrFmiq2Rh3mBG6Fz ZMq6k4hxhJo7T4IdVaTwmuBW4M5t6MsBm8Eoad4b23csW9M6b3Hu0M5osRm5DwXzckbmDStegWOO E98AiIbN7RAHOJ0KHdtac2esq7nOrvOm8dXxAnOcUMQQD2qdTxiV3Ye8tLm9F7zu8I4rOnpFVQZK QAovGxNO9OCatCrWcBSL5NrKzRlDQ+51S6LocBLHZJ5N2zWiyCQsUkWCgLAOgknB3RRYpIsFAWAf OH0JpqRVAQQUB9wQgCNiIiAsFVXogKHGKip/4qxZAPlVKApaAApHBs9TcFBtBGARFU/29Rh6g5lO 5HqIf3Zxo7im4xjZvJ2uOT+UylgjgKID9ViZtVUtbJb+AWUKEYQYsHR2zAwuQggH/Uf4NSm1ybYL EGZTGRMdiRQUOEzOKqcgLVMOKX6JmYzQjIdguDU0IDKpVEhJwPpgdAG1PuKNBiGMv0PJo4EE8TNu keEMzx0HPVU1KcGsCSZCe1CzVvY3NQ4OtzDinvHOIMNSc9q5PvB/In163rfh60SZJiZUgVmIe0ol NKGUFylIRzCxDAKwMQlTMKARIyOWFtSlIBgn43TCQNJISKSjJoyS5+X0TgEWA8UpwlEQZEGetrP+ Wmg8ogX6Cmo70Q6hR/+yCC8hq3kCKPuv4FP/mON6aYfd3vCd6/xEQ6VT1QD5RPcgjIqhJBFZCyQj BgooAoIkUUURiIRYCirBiCIKiosEVFFkWKwZFJGIwSEUWRYMYDEiMFiyRBkFFkIgkBYEFgsEQGDI qILICgoqwFIChFCLBgIqwVYKKIwFixZEUZIpGKKsIBCMiyPPzzNoNFz+CAoe/3e7Qeh8Pdstesvh kuRAFQ7tu1yOc/LyhuBu1VCSe2MyjykJzcb1xrtd8c5yZ12vb5/bCWQ7CEBUZGoKMUjBYgiCAosF FYgqKwUUEQWPzT3T3ewOvPo68eMHd9iKey7M4QSNKg0gFIMAaIMavYGLrjF4mYmnk8mw/Z0RczyV ys1zGRre9VXbsi50OGRpwE9ChMIaCO6Ncro7yr1REwaa51C+cqTVMAGC6eTE4hWJHod9M9I6YRZG HhgFGEZuZEYCFrFzCLMKkLpDOdvlcDfJqimOdyapQNaHRxBE4o6Z1Clx2Y2kTzsjLqBo3ocEgdmw wB2Xva20bKEWCsUvHCMCR0KB2ggcxjdUIqOFQm2CCFycqgGNgsOisoOHoQJAe8yawwhhAXEyFOsD slJlaBwY4JgEShUyyYs7qnnA4wOFEkgjpgBFEDDztR3tDhUoIxGAEBKxvYAgcddFKOWL41LyePnK sF8QZlFbo9Y5GmzEcSAzik+IWxwxXgyRMWLQmZ7LxSWw4YbYY3nkyCpPDIhATFkckYYZC6EBPaLG kcpnNDa2vffG9vDQYgWN1YmXHbLTkyOxm5IY4tO5SdGxsWkejj5YvsmTlLmVyrMzXbTqTJ50dy1S tDSN1CjFk0suQxZGA1G8ypo9qpFAowOnqp8IJGkYYU7OXctzOlkURGEDgA1AWx2pHe8XTwO2S3zK wYpvdYGetoVfNlnSujluRFm/FN6HAo1BAkjwUhWoXcsIHjyxIBuTd3cTSHDS1wss3KIRJAsCAM45 uRoUzwq2e8sPclzveVezt1fNCA9RKjc60ekV4Ny6Eg2ZKzjjKWpAHxcMDBaCOEWiw4W2GNIs5iAJ 8BDM97dcsUG+5UkRUTszo7PZIaJ6dEZVuJEbBi6azK54HzNsdwKTRDpuWuEEjC2EPD0ifAULOTdt ZQJG8XHj0kVyCN4OdGCcHb5KsHdPOq6gkjsLVbyA33WC4lk2V6DXHnnLEkOe11WOEXihkXA3wcpB C/KrmiLMdIDyiwZSne96rEZyWFFqEa4qMUioHdEIESrs49K0dy+qLt9uZ3HktjmrIIzoFvXHXAOo D09DxwDIIQ8aCshR2jCtmIrWjUko9WSjKDXloMhvvwh7f7teRy5BET+uJ9Wgov3e+ub+7hx+HmER NJ9JRLvbx8MDn2tJnyotMyT5db2y5lDExRyTWkVnbVMffStZ2jAy/H8jHS4wkxsWIxDKVc2yykPC 2KOfe47YcsDyKHbovYfIe2Eb7PA/U3EuX6pCboGMKgNBVIgqeEQUkUUOuIiEikioY7q16PmBUB6D R7eN9vSbDlPPDo/6Hx+9+fRMRtJXDCr9RBo6Cl8dPgUGYkb7CYuNG5vdDj2TIlmBaUUQkhXGjjNh nFeE2Gc+P8LG4jYERLs0GuGAxQoSQ+LNNuH8uBcsHUQow9KnJn14BRxI/7TAISbyJVF2esqMNxkm oMtyl+QQ8YJCoIvoTilobvVvIJHa+wZEnk9RIJ/0nICjx5ri7rn1Hjkd1o9uIPlFoDWstw0cnoPW CPkoLa+Lkj1mQRn4uSIkHtDRQIQKJEWyFwKtxoTp+B/z9umv28azjvKgsJo3/aEIPFgX42E+0mSV 9V9FZj0KDSHL2tPks1aor5IeAEPdBiLhUgUW0tlmAKZElGAFFGMSEgMqEohEEYDZJYkU4gcoehpk MpgkkUykNXiWMQQfg35vrBXs2ujSYQVJNBC9thgn/XZb4Yd54Yaj1hhsQ1gVCayNP22KRH6dHX/p ++SjPfXhaiw4qbXhNIOnFF6DBhorxrgeV/O6DC23eIM2jqGVeZRD5+gydWn4Vlem6HpBQyuxQ/YY 9fSKiRe2Zm5Yp8ZV9bO6cY00aKmFIlBSYqqNSakC3l98iWxCc9q19IRBm4lR/yuGFmMYi+4hpEks omZFFQTR6Y6KRx/43pyMsOOBwIMkiIhwj4QMjdy5+0DWcnpvynae22/y286T4SxFK8JV0kkRHVgw fAwn44I00sM1cRb1NMLgrC2ujXy1q0eerTqxyrJ4YkZncpGBow0oNuDmSEVz/dTHTHgtC1zQgyiJ yqHKxIKqFMLMcIiJuDXGM7gxGVLVllYhWlT95a/SQhwKgqvjNfn11U1BW/MtCCikKygrL3uebxyy VsRio6MRneljRJInS4kJBiMFXEyJcQBzQQyLfD4dFEDB8wyPCih9GxHfOUrMD7teHoezNHkqUpP5 w8ZfW4j1EZgEEDzdA0P5RLoXh/tNsMigQ4ozt9JwyKKbVkfIGIlBQ6wuY+9mExHeGyfy9t7H5Giq d1StaMBjWwq5pNVFXh7AfFqKBUHeINyDKy8qfrHzTrQZzofIwTfz75EOjeZeMLQHtruJNs2qIiPl q4HLIVGHxOqDjOfPoyTZu9cawfbfDrROX6PFo9hEPOW3e++bPHTSzi8NF+fVxLp1aXuy11PccnHE Pp7yXHh80MENeWmmFYsRIjFilTsvGsdHcwS6flhPbsHwceU0r9ryMMNWjtPx2YXcn5q4cSGAPWeE RAXoAQSEB5yX4V58hjO1FRkrp7eur0774zopJRUk2O3SnPZtvaye7VfeIG2kQPD/k7ZPtTCfIocX 5iMxbfLWj9GjXM0mMqGksxovSdy2vbD3+At65Jr6kpPVklewrAhaNrpDFGEBQmDAKwUgMEIpAJUK hIWMgDRECouBnNFTo32eHPzgIhvC+8ooldlpuGKMcdjTEYI8Vjhnp5jEb6wc66+7KYPKUt3huwjq Y5g4rrKc6kHlf1uOJnBUik9LzlVwoKEwzKOvRlnFHBWVhffLQhRpd4z33gKBcCjSwpg5BxF71BpR V9IVOM5azt67IRp3aLXTyY7xhB7JOQ4oOxUf8O7jletNHSoOmCGs6f6Y4clZtRzhcIL+nu1XbR75 4itszlwQXwx+qDGyX7bdmHUaaebC5ZMsCkORHC+rEwEkDrhkQm2TXLZzszDbKNwpCloapeBnBD38 zOeLls9nW4GeuxWvv7HbPbvOeR8mFZFA196kR+LvUSvj5UqVZMrUaeBMI89VAVzFUVsI54aQIJ2W J8XjFa5qtSXgTklBKfWLficFTv+ft/fDCkvcTNFFkCRIRGREDD/pohrgX1cBA/eAn9A+pzkPx+/7 z+M/OoflP0w/W5+mNqbZZv1LZrzLHDOZ83KS2xnL4RfAecktDMMyxyxrtk2XSBJtHXLBx5TTt84u OruLBMK+GxLnkSHNtaJeq18/RgkoGEGoYQt0jRklE2gmxkRJlguUw7gUyYU4BR5GJQdJsZICUKxk NUv0/Kgor8bWpU+9aRWMFVERi/ktUdUKJXHEWZaKxK0EEFUa0mUrMRsKxWpfI8gVjF8Pn48MyCWq pUDRMSQWKmqkiPSIMgetWEhNI41dJhyg4MIo5Ey3JhIMoSZgQfcDpqbplgV/1NHl6/7P4eyeP7T+ 7bb7eThx+HDN6t2jk3E5+T6Hv1dnx6dGOlMe56VwvFHe/ZkV6ra2eOYu8u/TvC2t5Mza8P5rTFP6 1bj5T21/lOvxc1/adHuizp3W2t8Qo23idCHhY697QIq7O5v5lOUv9L/D4eXfQ1RvLdy2+EfmQaeO YV9Xe458yS0UXvJy9Vg9rmWmLh1bjrWaxvhuTQoOW3DG+W74tdcRvdzh73LOmN452+d9cXWeo4Xv eOc709xeXO9rJzrTsz68D7Q/Q7s32Hvta5j/EtzWD45XL51UQnXy+Hh5d134Xt9Pn8oPf+PT6Q9p /jiduzfu3H0b8vlX8P+f2/Ffv+Pq3z7e3ec3Mv+M/s5D3ofCHuJUR8kQAqAqj2hQe9EP2pCyH6mA cJUP6P1z+fNbClGQRhFwMKK/ss0zLZGWAwHVch/mQMYAuhn+DMIzbrLLZZP+OWQiGVGVVEWOqG0w /fKFd0pWo2haJCgqq8yklGBsQoyaL2y5UdBTM2lTBChoKcjgk4LQjENO3BIgsiSLDbVIrBYTJjLH KyMRFVIqjEURKlqKFiLUqERQUSu6VywiMWFYEBDR1kwEmi2sJbwqr0HAG4GgQ2YYYAjA7AnA/pnn YUaUMbsiHnPw/l/s5RjJELXv9p2j+L4yeATmMHwbOF3oIFrYBjh6j4GCODj5YYWLRvFH65pGBgVm EohzQsICMMQynSCgoMBJ2MN7JIGgtJwYlkJVRWhSArBRR4wyZUstWFrUGkQNTb0OB+YQ70L2ihrj txu3N4aQmMBKrqJw2ZgUFUZYP6A9DBYyBXP7Z+kvchpS/O0E7KUlAmIdCgbubXS/YQMIwDyOLzzs bnDAlBuX55sids4criFIloaMRjBnHVszZuXMPQ4pIaHNCkKsEPCzQl0yXDKDAftcwKPHI2CRZGeE gNJSTGCkL11dGg2kwjKlTGsYgsBnBzgGwHWS6BgTUBkiQGGxoyG4kmZQyGWEqGnSQyAQQlBgGDLo mKqzYJcz48/CE7O/woTuhh24YYFVgEhUTBxAJpM5up0brWpnNt6sHJMG+Nim9/wMJuozxmLefjLj 6hhNRjocAxNbYFQZUFUYhVR9tLIFdmNh11xhvHDcjunCwTXOGloU/RcmwOMpjRpU8+PZhjpp6yjh T0UXejC+7Onx7MNSqh6bC+KFiPN9UmJwl0X9Ik4MpNa8pweHPVcLqx6vpdFviQciyMYuOdgURvnJ tU3rtoSRBIz7/LwaD/3vl29OBTHV2BRAZgIhIU2rpFRf4izSIoogxoc0OMGQkGVB5eXYk+sWTlGf iyeBhKFWBfso3Of2XXB984NEPD0ZmkTPyL4zy1195C/C3vazvzS3VDBEJUBpNDzle6QXkm1QPpro VT9ktiuiPne/Es41Zh5cZMdPRdvcN/v/JzMH9HOcBRO6FVhUlDx259POeudc+Z2upVvRTHWpVhaX dveXDMy3mzE00ILllYjNWkEB4AQ6uesrDmhvrfJktZcV6yubHCnKWdDTVYZeEOAQxIIYIYUFMste 3gqMgG11xSpy+7KdO/HIcjrNEvneIa88Njqy9j9d1xDhlqsvb3jQ7SENGxv5ePiEq1uGETXoAtYD RiGIkggLCKqMIoxHRv3mzXw246QCm+gOh7tDHZ+qddPD2EkihtA9JYqD0HO9GfPw3iCP3aU+fa0z MXrC8j0en4vrA+xMnyxtg/K0E+zMyFutltE+FnBxNkIoXYKaEdG8wub0lOD+c/rpsNGxoPXY52a7 cN4M7IdSyYhMITjZmGmCm6aFFFHqlREUcaIjLxhhp10dqYjEMOWw5bDYkyVAJlmUglJYaDgEypGq UuiduOjjAzAOz2MlgaBsM4bMksjszY4CIUtKF0KCb4yBkYMzLszjhBVVSm4cWcUJFIGOYm5wSQQT UDSaFzdwhosRM6OtGtu5XtkyTRSKCJmrG4ZbVM4OLS2lyYcDo1lSiMuJlAlGBEkYCmEQVgxMYrNC ruMS2DZOc5zQorOeTnNvBGbazMdm7wbq2QWng0GA4JqCDVMCmQbGrgnDW0hdthsCYFBIYcSGBpIE DTDaIiSAonWEzW5CXE11TUOdzER4DM1w5dzRgXRbBSIiLgGuyibqh7VhaswxENLuTUEkxro4yByc E5uTiGZJ18hPYMF0Zs4BjImY5ze4o6ChYiaHaYHwlm4EB14NxgLKwtojCqFoWJwkPKDIiSI8iSnE yqyskwOXYiDoAOJQUFF1RYzIIODvlYc3DrsPEGUgspRGlujjNSE0khUgxho1dDpUFbbLYlKKIUso mkpDIUlFKiqLAqLaFi0tCsMAmBs3oww2JSBrjUM4kOOFmZZatbZjLN5REhociWko8HXCT7+loY0p fkDPf+4VTT1eD5pLEE0cmzD3njZvtvIRggGEp34vg4JMD1P1t5P3wn3KCWAOURQx+mRsUE2cmgYa VIqxkkhIvihEDRRllBk9hruHVDKYtfEz51wGluZ70GrLBLG0pxqkEZMlIl3Lc2G0eJhh4BTPHfW9 uE2w5TqDDC4ZV/FqYs1uHkOjuw9sNsVUnEJvreUVF7NR1nOmGbN24zbs0iVCgQ/QMWwp8Cj3LBcO bHIr5SAIUBibyrY5RhVRKPRMjoppPPP0FwgH6X27dWo0Tox+73w8LIoGFVUNL/5qo/tr1RwMqppE pGQ5Ot9XeFfYcSyaWqHN0eWGj3jtOGfqQ8SdkNHwtZj5RvZKCmp6Sk3htUzxh/LzqeF0zfij+iEj 785fbrujXUkTnvO6DWuv42FcmFIKxYWFMKT7I2bk/vfdxmNfgeHtewZ2g4XzUUM2Gel+TOs9ivKU UUrGn30M1dM+tNJ+dk1ppPOFoAJFmqFRSBTilhbCZMKSwFSROC7aRkFIMFEH+IZgguRgJIJBdoV4 sKSMZ6iVhukOkRBFECHDCo4ygEXihQGRUY/Yuksg2hWEjFGAwT8DIIyCSMgeEgMjIGoeKqRiCHSe vx75H15wJkCwA0Hso4cMpEwqpRl3zgYHvDnFODCjXv2HHToIGH7CfzT8E99+jzJWVcuRi4LqaVLT bxWyv4+TVl3R9/6TgPz/z+siGMijaFhBCcM8CdzUNuBx8krJ5aab4fN3xo35sr20uUP/M/5/WUNo hAgwiDJEUfyQJqyQm/rRQ8mfSw/jfnZpjNWqrjMTMsqAwSp55VEGGgQqw+nX1zQSG1wA4NLSQRJa mghN7S54WFb0EIHaOERJBkBEYwggwUPeCS8f34GRIRjAnFvx2tDN2VGSRhEREEQGMUigiIKiIKLF CDGTzZAsawlYqIQiQkGAoREYiAi1gW2i8UsURY+VCggAyQZjCxqFIIjBRZBYRCJKkkKQEhGAwy8Z JQZRLoLD6PrO2wXpWVTodMx+eeqQ2OxNpkFR06SYpKLhndBjxYU/xRBRUTw2zl9Stp2WyKkFqlCC VP0sMhRWoGGDWgMWK9Gepv7w/xYhdtCxnXbcxrzqqCCoRhmaWEflXVf8zOboZ3VxtXyp0rLOJRzk o5jUmyLC9z4APy4o5c/9n1mNbp/SVcvWX9uGFuF7wlMMn7yut07UyKaX74mce/v/g3OeuUHQdBUw icgVEw4sXrUbfAytAx6f1qqrqKAoNGMRWZD+CIOqQgvOBCG0S5sUWxdIRGBgWyEdER2eUdolNykX CxZIUUlKWT4FouqRKUNVBiTolhaU/Sj1hmULyMlHpGixg1n3pY0RRRJvu+QxdAkR/tBiDgRFYLAR QoIgWIKp/IIIhQxQQsRRhAQbhFDaIh1vAeB5UZoMqxUGQD1Bzq/2lC9aB1fMJi5zOZxsIaesxlRr EAvAlXlSlguiYcRPUNEwZJJyh/UyTSOSTkZoz5sEMZMEWJOBgi01kM4mqPX9RhIAfT3hVn8Tjh1X L4U1XVEetuW9liOJ3oYcS+0utaZn5GaYeGdGxpTqHfjU0bRVhDMWbXvJJLFy15PTrkknOHWLwQYK 8F5giPW2OKd3uwOlHtEdOw6jMYKanUJOuEaJOZLQWRgjGF0IaPCu3hS5JLpYknbJHCVDsWiayTk6 BxBviTkurgAcN+wLl9KOeP2IaJNmRNDOWhOQF0XTJ3R+wknQ0cnY0iMInIM0bHaRXB1oYA7lDNyg rFMQZinCkyQtrcN5u1tnUimtDWjtXaht1jQmNGSAXQyQflPYyfBDyxn1U+GFwZ8cZexrWc+745ra 3sG9YAoxX3Ohfhso7qmu9lTrdN/Vef2ggf9VwZF/SzUYkM6pFphxk3X3q6wStEEA4owPASEMLB+M 4wgrxsj65mUajTf7odM0C21dhKVqiCGGZaXx/zVx+OXmVyQMgjMwnLDn782+/9cA+IaPh6vHx77H f2+Jei2OF8bzyvevLHGYZeWa2UMJc4wYl7xLLWMYxaqz/k68/vMCfh+RjxBRlBgccpKKCb0QonAW /PKT4vC/rd5fnzD7/PfBx7aKPrRlhl0Y58UL0U5//TMdQjgGC+EUKIn1Ix3ShUxxCxlQKMRQ2QQp STFCzBJSlnbhurT65y5gWLMFmDCNoLEuMYEY0jCgL8gYFkspG4GyESkMJSkCgGwEobDHMLabNqKi 0/RDVa96SJQFFYZGVi4YA0FjAuJCwyS9hs0NFFrxLvefeiP8FUuf88jljb9QV/n/PcnzIv1i1Aoh IIofAP1NL4ts8Hthfl+1otpbZbYW0tpbZLaFtgW0tttltktoW22y2y2222lttoW0C220LaW0LbbZ bZbbbLbattttlttoW22ltktttttttoW0tttlttttLbbbaW2222W30HhDR+6/6f2fr9ff1b7kJH5d /L3Z/RWT/d73UbL7LrGKtXCs741+V3ZYFP41bGWKfbFdcr/w/3Vm2MHUrL9uP2ap5bJcQc1vDue3 4i5f7Gfdj6fTzzyfqSYM+v3fb8dH7vu+q/XSrns8b9WNmZmOCY+W5opmHf8sn4jUp4QE+7d/Q7OJ f7kz1dC7a9RllL5dsijRDwy2JU6tMG+JJ452BAj6BAgAUw5dcXKat2EkRlEgTuMIcpIppIsIcITp AnKpp0hM3QOGQCod0hri46ZDukFk4ZKlQ5SIwIKCxiCkUFJFWbYCyqg8WVIdIHAgIk6TlMZJyh3Y HLIOs11snSQBehhM5vCSd+9k2yPekle6dJ2QEBGAZ5w3QZADMSQwQIGHTe7F3OVTW5Y2phstCJXH FS+Gft1v4666a8H3rxvKUopJMmgKidLTiG8LyTRytbpZprvXFLtWjMoZ2qImyLYkaW5N4Yg5lEGO zDB/Y/2boRGRFuhOy2uFzKlcBUbEEGqJbXdUjBSENqQgWjJUkkhuqQl0pIqD3Hr8es6IIINbCQlM Igiby8LDv0yHBhotbTtYpEAERFEEck0V8V5BrddEgsphSm6aM8XuTpOjBk0ggohg0cmCSFRAS19d dMnLFCHi6NLLkluzdxpWMiaidm05ZqECmFHCmzZLC0PygFhAQPauE0xioO18Sr02gd+DmTBkrPBC AxHoenlApP4hT0Se3tBIf1kgHA5ikxiAUx27PmT1QWEKGpildUzMg7OQ+u+qbjXChbXlAlRVTG3W 0E0IM4TkXMp5NWt0TCtBlaEpUq64v2pHm0Z2gHG+RccaJgEfXcwdJx1ViyIiOYsejRR2QYG3x1os +8/OY6RQtdO7jK6svXp8MKIkJNiRqpIrn45IQ8fHJnEqRGYb90kbcViRMrIQtPBfqxYngy6TVs73 zajjQw6rMbfK5la+djeURIoGzRVjLMVrm6Y8opJESm+aOl5MbKlkpF98AvCFuNqYN9lOb4jZQsK8 mMkQTZWJA2WoRLHRwaI8b+iA7N+/RLb91+a1p3kZIQwaWEkmO1oIdKgIBpI0GocD03WBxSBVIliZ wQ3ihUqZ4e8sC7JxMSgzQaGSHDUjVNKorDU0cxBFRB2iELUk6qEjBlLEkyVIx5I203MrQ82O0Upl k52Uygj4DqEaEKnoiJIqQFlotkYOIPLGtWLjPsICAw0bPMVJWczg4ZRU25zdtZvUthnLqNwW0Bhv M9kguhEzomagRrUgNMeJpBANi3iNZvQ+hQx27XaUIB0ZoHs2a1rBRk110dllmtmuAVXg8fNLlh0w HaMmipNpFhIy1SOrOA4UizlO+DQQM1vC7wO52S1ckqc9OvgpMljR1zRzhEQR0qSE3hJPc/pvdjXX s6KadMGZ25MHDVzIE04ZUHEhU4kcGPqYrcoQGR5pQtWD6Dp5JgiNKryQCoFiawHDzZfN7VqX2XMm CuSlbSLTHWJ3kWyERxEk5jG13hq2oaLLYyZwbBEeTN7NWL1V9N5mORs0ZFdRbAoQHlRmCpaGjRcp pKmGmyRQaR62028oXcmq2KLdo5guyMxsvL0aZrFRSwTc15EkpLybu2RhZ1KFzGBXR4maOSBZoyQI iZze4IiNY2j+pxAOA0tr4ND3PZxU66aeepDrXPr2KDi6IimTB0PNjOxsuRvdJwU0u6sOkwcsWj8U faF0Q+0jycoeQL4CmQh0gFacFM5ylUJEsuI6H2LlE0keaPpyhxDgPfcPpHZ3/BI+Hoj5Rz851ycA 78ZhhwQvMIA1FPMiuJfDttbOIWQCpJDkGQm0JWLDTrW8CQh5OnB3GbQJF3Pw1QfyITKFEUPRdKMA MgR00HThA6Xiaw2nTzGcjtBQ0xSBUBIiLuT0uuXAAAkwKOnBemHpjNxxcGAyXT0gdLBABxQuKeoc SEKXczbYAJA0hGCAYAMlBdhod6gJmRyxF8YWXydO3sJWqcUWlNaGHlyoPT6W2kxG9E7qYbRRT8SG iog5NmumnLBnv4W9/DbnOk0p+1SrtT8ZHUNnJELp2SMEYyTCRjIwgW1dXeeOB/Cpv0ydIebZOFBV EMsM6kszrW9IiI/QSTTUG85f9msXW5QzrCkJC9RIkMQWCKBoS2qYLLABXlo1KmQjEHHmhCxJakTd bNLNr6jaoOfyZpMOVYn2LukDaU1CAgfoOiyEtlGR+WuWEs9DuJ9Vbo/MIPKFO3A70ARE+CelixAm 064hdOzT7giJIAlM2cCG/I0JwqZVJKhBURZudaI6il0QkkOh/Z9BCBMQjCVp2RXbGiDUHiCB48qB WQUIUIloJsl5JX7/qal7+u4x79MejXKECFCuogjeHrZu02qYSFsfCGWSMnRsIAxJ2fIMDbaGdImT NHdrobZExtaSWKkkpBAaX2eXcERPAIiOKSkbNEyRnI40qBhMbjzcy5nTjvaSc9sLr4eODnqZxNQ6 SNWXUUESxUEmEO9kmzCILJCUK7pzTA5giTCcJMnlcaasF2RV55s75RLnKkk1UJUpePuGNQpN+sp9 qA6yad4rWscjbtvj91e1+N5O9RveqmtXhWlc6+vi51mflclUvOo4Wt8GZ4271rvWpOTTNXRxjFIz rmpV+GpV785lWk4OrGUFniNs5biX4c46eFSdu5eL3zzPPPb8dqrdvrfUT3lamMi9cqa5uZxb7zvh stxno4xrW9rPNzvM7cERIOeDaU+HOl4O6NiHwY9KtOH20HwPa2trt2VRnB3VhkevHRUw31I7oJS5 pBF0IKCT7EPXqw2Zckc3NGN1YPgbLyeDTkjRasKqS6TGxhgFdoHVIyYzZGeA5sr6I4V430lnNxh/ QyRtwCCEkcgkuhueRgDJjbEXGiby1AxZTAxxmRQwNGQLCkz0DifZEOJJwOVFVDaxKgUFYbOCMQu7 UbYuopHCFjgCg8trLpjo3MZEVJJzY+ElXRi8G0zNznk1FyII3IlyhWKgq6RBEegJAnWRBJnXjthI LrtDlGBzPS7gwIMO6FCnDjAvHqdnn8gERPMERGkS9iyEeTpRhA0RK656qbSCiFBUyg0EkLcmBiOq +aAuK0ho0HJEeNzhi6BdujRZHMqUg0joF2O86biHGK6keTDVHqUwPCYIm0hww4dvVzdtnINoTdPL CCxBgCqIbOxzAxgWdKGR5IiQ6Mdp5wiQV61VUslT9W22iTgM8njQ07c0YdsRY2mCrOu+JOTZizb4 yMtHhqjRjOIXKkcUjs8WJw0ed4d2lebRhLLNy5zVp2b8oPPUcGUq00RW5vdCpREDkdFW4NLFeWsw owUCl4WpV2O9NmvZF5sGblvBY0QhvI8tbtYa2WM5VAv4ctXZscSSZqiLI1Asb78HBVNTUETYvbL5 C9lNSGDFxsy2HE9zHo65Ug+McwWbweyy2cXBn6xK8h9L8VChcihhXHInC++RJBwTksQwlzPyUAs2 iCQnMk41dsMMcpDWHbpuy1VymkSX1JMmKirzwcSTN3YPBxtId+m267jKDoVDNZYioI/RAcUaRMND SqgiMZAhr4qdpjCidozTjoA9fJ9Ze6CPmNRYokxwp1hlIeoah0y6+IdFSToGsTZ4s0vTUYLcSMDc S1leOu2KmutImcgm1zqGRguhXQJRsUvJJk1xSXRDDu5WGDLfyYOrZrcqlKIMFwokp8N7dEXLMTdS prkwkjhIl7d5yiCJKWxlIAIiVBBArhU0cGiNlHV4NDp3aXJDypgZfBUWqRYw+Ahiw0e1IJdxcrgj QNShwaHkoUYLMUW5sxjm9r74csavuPghv4dN82aMMhYsYGCjhyQ0PL8lqTsDjhiTPFiuUiTF5GES znFy2bFXl5PHmS+jREgywuKFaCOwvwaweYGx1g0UJ+4uaDx7KF9BN4opmDHgo0s25HRisCrxT2iI YUJsszBaeo7QM6MjB8INDglMiakCgtXLkjus8Db7njA9hHBgUtoQ2i+JmM6HR7/gD3K70DnVggh7 XLDpfEzK3x9ofsJEfe9IOxD3h2PsvYTyL8t+y06e6dnXh7ELFNCT5xOs7b4Tml7khww47jQmhfmK tLCObPkoGC9CYBQQBQUcM4Vhs943GkQOFEJ22OABjcEjObTFB4XxYwOrcy1oFrecsWRYaHDAHCFy XlMUBSkpBOseabl6Q6y1Pw85d1yd0wIaAcR6ETl9+b0573bPl8OOOk6ynjG9RXSDE1lItSdYmygs rcwuKXUlkuN3Wbr7s2/JdcMPPHQZYbEkF8kKQ5zJt08Xub33CPbwaJkD6RBO8PvQkkbqkCSW11Zo z4RZJ8EIafF1gSygk+jo1IhoLNwImB6IioJLA8qxBN3aQE8JocQ+36qICxZQRjc+rKc4IPyJBiYa Jyx+fT8yThfx7N3irddk8KbvOJDyK6OryVGe9fXDFg4V93ve/ftoA0Q5h4edCPWF3vISCucxQoRL KRV35Gk2shmDNkcstudXG57feHE+DCJCJPY5E2jCxhGjkRqBW7URyIaKRi7YE36MOq+BlInjnqyY xLbatXBJnYa40NJ8Gxg8yOJDaSBhqnoLhIKkRS6tplRhCpXHjojoZozkS8M0m/ba5Mt4XmHauZJq ioN2LQnfBaG2zKH1wJjzYOzciPQzap2Fzp5qIl1EuXHm3AKbONORHlkdElyKPFHmtKjjbDgYZNmx kHTrc0otWKUFEJeNaphdXFDghnyMC+fUpZhYuhgPQRTVXDxEmARQKDDp829NnsYVVCzjg3vmbyJd AkMrwcEStj0EHHPLqCkCZ3oULkjg9L0dHY+gh6/MeD2nZIr7MsMRUcr8XwXg1iXJM0xZZ9sUb0RO HlaJOYwzuTRUEMw2vYbC59Ip0WLYg1ZuB/Dpz1J9vuyZI6LDGoqiJgsJQ73LnJEXQy7BzB/xALl0 QNIdtHO7KoaBjFBRm5RiFxBUiZDBXoJ2QTi3YndAJbHcEKWMTt1ZAq6RkUyN32wcuSyBikWHm84H ViZH7wPIijw4LEzg5ySmDheh20s9jb5JRUVVNdbPSFFT7EOhga+BEQ5QC0soygZBE9kzk5akCzYx vs8pHz2a87jaxOpI5U4M6N0xIppxxBLnQkh2FVFkqMCbDVCcUglZ6RzIZRBitPCICdiDBPiew1xS IkkHs9xWEQcYHRE5sNFEo58F5IIcE4W1A9qQqJQqAcuDK3wn2wrHJ8INGdyeiZAiMLaJFD9yBY4K qnSA3hU3sGwa5woZY9xeKBygkEB0EUYQuNGLbZxHquIuPieWTsUO+o0NyRxM7SjHuPuc9Qe77yN4 Tg9qcHLFEqwtvl8UQOrUsDN2bMcxWCjFYoq44BCTWvLEB7pyuOlgaaQjHwedxpOdKwbsLlm8QOVz Vhq5WJaM7MGe6t3DI2Vt06sWPtPlivvy0iQOLz4JMsYLh5SOxIk+mblRgp26D4CJlp54kOSpvr9t tOTI0pTA8fTA5OK3bWE7jizjkqDkQfyuipjA4fcc3kHg1usUObqSWqbqTak5zopuZmWyjyI3S2eX eNuQNEieTSMJXJBJBhhKlbSL0TK3WDEwNLDS7jb5HqGipL2kxxseHD80xb4UqRYpjgjcfK4MH8kL mqEnGBkeueSOJZbfGCpSlOlIikp5viDOa4cq5zrl3HJYhgego0yQMBpz37csjvkXogIdtHwsK13S /DUGOj2cEnKrYD312IPsSoHUJzClI2DyRgnkJYtB+J+Ae+FpJmTXOHp5o+kPByifaD0oYCbkA3oW eVQurwE5X3CX0T1dWOeA9vzKvkQj3xvwi5SZPoB2ucox55QZ8WfYEkFIBdlBaN+Sa1nIEm8aqdIi yjqC51CEYeCtwUAiOpEyYd4xNd5zDegAzgQsTUZxFhi8ELnJNWqi82YjXJtQsE93DYfbrR2u9neW DiKy6huO1MxT4dtXNyHPMHMvfQcNxiMQsF1QUVG1zpxXHZpLVTIiEzar0MVRFRjBHcTtIcIvJKmd pEuhC6KRhA5AThj84ho4Y+U8ZFFEPucvlYuzbBjEYLhVrCPz6wCJgKIusEJfLogEUlYWkuK/Ojiu yJC3FwtU2gF+GPZ9w5diuil/oMeJZ7dENNyUTRU7Oi88d+Oa+ZzbLtXDob8NJSlcFpSyqYwne/Ig gXRCWqSYTM7tYQSriNqoQagjakF3nNFzaz50bVgJYQuglGiCpEVBRVHrvKIHSA0ognh8+fphJs9D b0bKSjZ7ihD5Ry2dEuM6OCpxzySwE2zzhpmKvVGNViQg1lDSYRH9RpHbZJy3sF2+G7fJopHE0hZ0 bLumCK2QGjYV60DiJSaBwdPbjkw4WrzD8VGnHyR5SiPaM2Q64jUoNfnzRAnLwj2Y4epJiDFUFpWS SBLIiWEHoBMZasxTHXJd6IPUjK7MF7dd3X70e3Rd3ZlYClWa0+Ki1UodcFToERFaWO9LXdPawOtO HegI0ZCONJqSt7OR6tVTsKIk9MOXK1mno8cMNZKMqdoiAxBDs8YRgeohc07JAu+mGG2oiCc+nPCN VZkFMF+Gq2ZzfsSVKeQYjywGCgxbblAPdAiIcoAu375HAedWlE5OR13oCcPJHiDoEzqFWUGogqHq 4u0weRzZsPsIlDfBZGjTsWPDH3IkT8obffrI0sxGWe5SDCaJU8iJKGgSzR0RUaVNN612qcg8bnVT R3I8zLaiYM5/HgtrM8NFOjUBt0fljjrOHT6Ipw8n51qiWcICJgVEQRWqxgPy1BN9t0fOGyaJa7iX TiQ0hq2h4/s12F6VK8XMk5dQcVsIxwbJkDMGig6RHIo9Y+xU/QvLbMksPLKrIsfGfOLQLjpFiePh K0I8Yi7daiGTFy4yzeab3sc6YStmw/RRlZDtnA67/CF+wZ5OYsFGXc8HTUb3LlR94SgVrOYIlFpK kCmz517PsTp8ovJaQPslHTos4N10bKDyETs4u0vkme1iJygmWiIiLBWSwdNIlws5kOD3QMD+rDdL EtYUo8eaPA0Y8/ZEJiGe/lArUsRLnAZ7imitZGDXGxRji0jCxMmRpSMjGSpySt8CmIm3uvFGlCuB xdGDhzwzjcMEjLYVwcjmjzzJfMnsqnO/ZOC/iIcHCl9FQYOJNQQZT2hE9HmL59nhybuYJREyfmod +IzwOI4FYKz7SHlCBkuKlBQ6HddSLhmJvg9kDjGTpAgowZ8onPw9FhVDnQuZN59nMiyJDS+pUiOO RSRFijzB72IEc7iUgaBGiwTzEuD0Cc3wziIe8951iUrsE8dSHvOs+DZHmHr9Ecyfij6epF/BUbNJ Jbuk9CPR1dOXXbP1V1yVt80rrJbZWVjO6LWPNUrCgtAKKF36hZVIjysqppSEYlkO0NLooR4VFy3G JJuJOECATvHyuGqHNPNbmSZyFszRauUw5DFsR6E2k1RsdA5yZ0ZidodeDFg66aNUmGkxsbe7SFdj U7Q4yK4+cbMekL0HHw59trZcrb53dG0Tf3ZFW6DZFklIYAtmEHQg0ERu7M9r3zaMxu05Mc5fZoQ8 2O3HZsFStJEmWbLTq5Ym3DOe7CLo4nEvMp8sZV1+Mqbq4J68kymd+GBhrklFCYqaOHJmwSLcc66N SQO1ksv0Z8tngpVl1HrtGQ07N5kNIia6DGRAQJScl+ncsFAObIcEycnD89x6qM6DvngYQ42Q0IJn gq05mRGEnNF2DZgJNVSTEMOZxIW0DUiVSy0iWSSpC686TKGI5q35tVji1HnwO42xcnxa4xwEFu4c yJRBBtsEHbiglqiNEWiaXyYLSmljq6LMfDkznG665kfOUzIUwhzCDioPREQoFh2SjkZknl5X6K7q h794k+m1s4YlfijRfaogKICBAhNMCJoZAONvOEQIGTdaZ+7uXFhB60dFgqktaHClhcnZgSRPehJ6 ISGZLYXJhyyOE5Bvr67PZokwaJ4Nk9LT8sgysr4PXoQYQLOaRBE+MLfTh0bBmTbsYsIjesZJppHN HgScmTvU2anMrFGtQ1bANJkhiFyRYag5kcXNkxTAh6DZY1GPA43w6lxl3lGUZe7ESBFgYUcoKRGg 0wjTLi2iCi5L80OjJwMMDdjC3JdLISVLH0mMeMmT32amTmTCAsUuhUZzrDenlStVI1WbD7PJs4Sd nAggdnt/oOxueg0+y5I7TFiNmSmccPGm8FOmnAwywlnIWKhyGJw5iEZFfM/ObWpKEOkBsemGLffR yWZyjzNzmk4K4s5pAKmImCcqROXnNQmq3CrZqK22mO04Q55NpnEsTGG6tbmxYY68EhBkr8GyKKXe o/Apg76NqPur0Lv5X6ydGF0e0WckCwo0R1abiIjTmOy26Sc7oaW5ZU4ROmne1ZcuWphEQR3DJxMD aYlHDDfolVicm01W5ld0SQPbEfSBJBJjz7KBibjCsFhFkXh3SHzYE0wUFg+8oip+AbbAxkUFIsFB QWCgsFgoKRYsX7htoYkWCxQUFIsUFkUFgsUH6uyZMQWLFBYoKCxYsWKCxYsWKqKPFNGTs48x3c7G HHYPIoU4xoRoIiNuqx7tcCIlABBLQsjOWMbyOjNwo0sDj7G561FLEi5i7Bx+mWJo8yKZuefs4p1P jZYeeiKPLjV0OGnE2kCUdkx5EwC1FJSIkBRvCly5bZdJlHkdvsTZEYa1YwKVCYgthppMauOiPtSn SdHidUIZrzXZdZTaYufZcqZ9s117ytGdzQwZXkQfZTBosUS59wCIkZkj5NMljMaQKJk5iQI8wPwI UiW5KbLwe3EHQxYWuB0sSYbdZ1omZwuwiPH8vbjZqo8iJTBhWKuFLVcaaOA9mvsjuio7Q+odGBq/ Al33I7w4cVIj7EeRXeB2CaQd571DvE6QyQuoUEPwkwBruIK08LqHZ7osZ5/cUkrBo+bPSA77Jhhr tlhnCIvzIuIBiQKdCRG+RYcS1ZGERs35mVVAd4FIyYFuZNmJadoWGRbwWObJsCwEOpRN9b3uia53 l2FwvL5Z52pxVzm3MbtLwURXAEESSRBMEw4l+sefGkHXqNhfcLI5JMUWgvhIa1DlasZjWWdUyYMY jVaSYXkR7OQJOREf6knQsWkw+7/r9XPMxU4HU/KZz6PApBPGId1G8Mmj37FH4lkwaJljg4K2e2Th lmVJUAkFRCSAC2mejDjixJbCI+8uHXZpRunQzqTjMYORiCaVEQoVHF0EiI9Cdr6w0YRLFu3PHFJ+ EHR1dGE30zZXkxqPFwOUgTOTTxq5JP4lnsgnOSRlgwU29BOz0Abt+wRZHaOmUN7GEuCharkROADQ +KNRw3Rm98hvhBw5zvokjOoTnsSc+Xeq+tni2QqSMo2jzwh1c9Ty6hTsUBxJo9giEmICOezrOSkX ktoEzuYkq73XtChLs2xEogJtJnM4THFJ4NimTClUq6Rns4m4oObINrbGMchhjGOwDjHddNLGz7GU SXhvi6vPXKQ7vUc7uyxzdGyKA0tk66LQoTmbLSUzYedC3Gjx0uWnofYgXuxYMANHfZCpSxtgQJIC PJPuMIOXycYzYimDRqhcjjK7Y++Bzzz2aN/D65uRzg8a5SaoojoNY0to5KMDhRR0yxjwWYWnNyma G8FVOeDBcJGEdF7sxaZJueNGKtDBg0OLXx2Y2bNKoOa/Ugm+ePNUacoUV2ZVd0DlBFj7kNdnHYyb NI6kbiDhBpge+6p5IE8d5IePrFIddiwxHMMAzWGBTBd9BwpR5xAoeI8yLTi08mukn7AcZ8TFZhkL 4TFSRK4zMXyEIEkHZyPRJOI5PYAs4wZ3x3GX0ykywtjFqDxRw8U8RJlkdkB+HdGCOz6+Ry6JKwpB 84HGQcraB0Kx0VRHlHFi54lxBm+jiZIZcsPbcZc5pURM+yCDPQob4HRWxku/KczMkjCmgdQgVg1j cl7D3DEZuaMCpazESw8dE2opYu8vAqROO8DJO1S3zR8IJMqMgWRmzgKpUV47JwT+wB8oVJFBnXKU IGei5ItccbKvISLGqmyOxbkGEmjC59kqXHmiZFI2IFTAZwYDU5WkNIkSBAurx4yJcdMgK0oRM/oR owhhe/dVqc4z8o+KjhocEToyTvrZooHXRccPc1pM1QYTIdDzIVBW3tiy1e2NWMxSo0ZFzK3L5kWL MFL4HGeTku7BxtWfPZyzWdl3Ryh0DePtD5wPanrieaPeHSpjzobtQntE8wTqBlHmjxcexHOKDekD 1QXyG+GOcePLyZMIybGJ4yxyhSLYMp8xz5PhGEFIy8mSyzYxBmNzl7QQlCjFXNGYwsGA54GfLHc6 XnQzLVS6TYWZc9IOcg8XWeQe9UmZozqOzQkmgRzTcXluVhDN5jNxhKWibLMRQzvsGBFEU/WNbxkw IKg5BQBmCESozA7roZt9EN+ZsH7H0cGhDkQZYp77rpI6b+yG3X3vW43ao699+2PVzMwMFEOJvLBE 9+xzlwrxs7QMEhraagqWGZYIzDJ3x39WIgJxkGREm7kupVZ49M3p3j1aAC+HPEgpGTwtsziYyRmy WRs4dcoMJBqer4BObR7BEfophRT3raevrmDWOLLvvjKW2tAnr133sojFQYzbuONbJBrmmUQRyKgI EsFRsSMnfC+TnpDbyYpMe7ZzzJMZEjGgVAXseEDBQYdCjbdiRArZnBYj1B5rHcmK0464HmA4mSLl VOJdQn0zedKAqRYMReHQq/92TAggPUoUMIfVsn9iumDzRoYC6NmJQUm8psaFF4GPnqDnWBTRwVoi jRRF8FnKhR9FMKXRrx+1BnYTFSNKB4TwOcHw23Guzs5dTog2XoUUz4Y7LGMlP5ejSIdHRKOdlc6S htikbHK5q51vrszwlYVXj169DB6nVRlBPtkZAcU8OBq8IXx5g+OGjnAg54YhSz9CIyhRMbyNKnOD FZEUNS4NsGLzPajFGkBbnPe1jM7jOEYmR52CI4JdqlYWx2MD20OwhcnsbdjSHVYNycdU6I9MBiqI qxdeowaLveDHFSgpxHMzHj3zRgfpT68Log3HobayWP6Nxn2XtDaqsLaM3LQnRfhozi/WD5vfner0 5m9V0eEYezXsnaxGJcKnBr4oDi+WZFgyBTATIEBpYH1mWOqnRY3hhdiWJlw6IQXgcZwXwZpCxwS3 0lWQYskuNkxaeQIiUQBBPqm1OTv4mgGSHJsaaMmYGBp0VrwgcHQT5TRZJuGlhzTSR2kwXLYVNHAO GbeTBRpsV4UYotd7sb2pJppkSb6L6h5ohAwZUnk6ChWjTgdI2TOBSAPjgoOGlnOKHRKKMKiwNGGl YcbQD8ZxUjBrsztgGsKkguN5N7ixXjb7iNuTIyJbJlw+KB7Igj0CqgdUXZowU0Ssd+WnApLguJ9B HWKXCriV8EaG1J1gTKBlHJE5GEC1mMZzVkImYr0ccQHrBpx8HJoau9nhyZgotGMlGixxR6k3ZzHS czZQ7DzE9A8eh086tIe1HALqaRNJlO8TmGaNYasRusnTq7PDwrP1cMO+SfMKOWLxycLUBPByfniS PdG+DM2hdYTe7J7KnnO8O8aKsQBEJIZr210YAyL1CtgQI1DnFyHJY3ndEm5TBoJwkBh7ANzqYggy ZbgnZzeu7pZfKUFQ7YuZMpBI5AxW+DpdkbqGNoaiJXdkR2x6QPSPdmvTfN7pERQJxDBF4WiWkMya ROMdcMrcay3LCjVlGTP9NZDnccp9Sjh9SQgIGdaI482J6kaYJH1e8RHyQCsPWnU5CCIOueocHSPN 7zchkPY2Uqf0flE1z1nV2DRzlY5Wd+bRRAOBUQR4gIFjjdhyIZpyw4RCaIKPwWRXZrBlo9pSmOOp UEOswjEQXsYyFq59x1CyGCyDXsow4Im3kUT190ogUC4qCMERUQN4NbgZaQpJCczcHEjTEL+YiUOy q9jB7YVo5dPaiJEhHLxCiDs9i5Y2SGbJ34dXg5dXQ5YWicZrIZlM6ka+RtdCpQbI6HiGxEhENYSX gOaQLSGbR0VwFVSLaStJNOhdruO3RIoIWWi8jh3S7yyqugcuHrWmShyG3NY9FdtzWhq4EJgAeSuv McxyFboSiPFXZIAMVj0RINMct7HgJNBE4flpLZS7O87vF8sw5eZvUvdoysBqmj4J3I94DkCrl8mY JopjiA8Zpk63ZxlpShNorTHJI28wTe1BKcjzxYtU8mysZYWMYoV7DThFHDgmOsSajal7G7TVBkGd lhBH9N2vBk7PmCT4fGMGmz3PZOaSMcT7v1EE0ODJBwti6m9kyBEXZm92Fdsbkq+gwqaiUtkR1D2j m387G+lRCJMtJZr17Wj2Ono9sGGS5O0Dd+6DsK5TFXoPYa7ZKLjkiEaae8hzfY4O2ip2JD1OumzL FB3IwHWeiJ2juddMZd8GSJpclQTmmySaIDTsWTrJ10uejU3GiYPMM1SUdcbsWjQ5Uotll9TTt5dr Nal8SSdCQNsuRgwOfYlonPBo+X5mz0cDt55aRIy+3PMmDst/eVchc1tPkgmimXiil6XBxDZsqPHl RZTGL5R5Yotq3j69ybPh17sNdjG+jsUYaAsltIhyakZdUkOSmc1gYHkLE+DKjisg08kSOuybITJ2 UQZHtJ1nkyPz65OTJiVIjwNKEmj74OXhiTgrJaExJyQck+ZMop0VweqhzO2HOOBjw4nOjZkkmmOe WOIfeNYGz3Bg4KbWRUnYoSGS0MngQEDak1FFK0ITIFydi1suTBjtmszUy7MHJw0hnCm76Q8nT4yT 7kadYdm1X1insBcq1vmJ7BDwEziahDz1Acu+s/C3THmvVp6sxVQhCesoMMHnyvKt5MjnXkGZ+Fuf QVL2JF2k54LBE4EmA4oxOKGJBXMQE6h5PR2Y4w+insTw71C0KCKEuZoUxWKxaauwOqKZxl6ZA4Q+ cHMImxeTdzOVgfLLnGbaM3nDygYQ5RODlBZ4Pw47qq1M2iXE3lItDWF8L330mkpu+bbPPr1vxV7I kUi08H1vwUZs2bRCTxb++VQqUHjFQBnylG4g2B8Rx8blWzmtBETAohVRAzjGZD2yOwoyRcaMGbRq ne5jz7M1YzM5h+5DSIgnQqCOYMIqbTw0n0qzDM/AvzvF/wiHsxk7KQUwwiQiBs+vhwB3S4hBAahd 9yw9X4UNDcD3ADi0XFP8wLHUSx93M19NmNv0wIyfEZzbJobZSBoVEHiiFsHGrIPBEiXZmVyhB6Xb 4aGya4TGJl3RpFpSfkkGLfhjTL0yyGqjkxgmWOuDCa5IIiULZKKjjFJoA4mgKMG4uygMvY2X4HFx hWj36mill2CJrjZK0lYfrejYPLNcakPuqIHJQQY9/Upbg0iTlnivT/Qv2cdi1NkBo9jxo0u8Tuwu laUO4EgDHY/KIeCZ4n4O45RnUzfZUAZvgYNQTrk6JFdpAGMToaKOPqmHIBkUTNnSpRYNnYrk5jSr Cyjs6Lr80CXMMB1Y0NwRZdxBfB3wYOyBtX6aj1o9aZzAxeuFNMQQc1Y4c2eQeVKlSxOZIYWxVldJ SFG9yg8XnNPKMjDrmho5nA4U3cvXF7vjUtPA40PYcFYmRsIC3PugZjnatZwuGcuMrS4YcKaMLkvo iOShSnWLGFfxg4NWoaMGmkZ40FDVNjaQeNtNURDSKiNt9zxDBDETg+vQwpJoja8ydKUZ8KBTHeDu FouxxjJk6wQ8DnAps1sic7V5fYIiO/IIifZEQM7HEIjMTG8UVhLYg7I8+oI8fvgpY0SHDzgs2ryI pkYp47U2RPgIWuD8HBAy81Gw8qotSk8HHahIZQq0uWIkh8tWNgiPJjiYJqiP4FPQQaE6jjejWBuY GC8jOtNJmSYzgzcmVec4dWIcjK4vgaMI7w4Ik0kGCji6SoYLwMk9kmE01Hd0ii2oMGev00YRSxMc 9RSLGTLnBqBbjg/eCLWYwywnAcHGXsFxiNjM3PjldMry3U6xDYrRh1GxedGTJjymDlR5Yzdt8btp J5s8UckfONAqPwkTwg8YdDh0odoukT0RNIiHrV5U6ANoudQpOxX1CeCM1ml15vQ7geGKPxQOnr7u 3fq34nT6phmIfSCtEk6eFuF9cDnIPalUQOVlLfb2pwJ3YiAYWinrkNFZYmx2u4JxI4AMZevjUw9Z R26OyxfeVJbcUCOGzEWVJvOzdWKUminIKHWosXlkZ3lqWRzs11dvnRVTKFBAVvJvhlpj2zz4nxMU maNSOSxRUKRdrVon6uavKwggnDxbBzfT45U9EljEHILgL7z8sHRxIemNpJg6cbsfibU+TVnqGcUT k2OIP5wZ2fqDg9iwZLX6XINBJXk/FYV2H481Z0LoOCxQ9ACsci1ARCnRggQSV1e/i8Y3xjca4WxW IEQcCPgx5R9LR+p375axBOHxkUUjpURNIgJXCULwe7MY7wS7V58IiK6Xo5ETAwxRwwhhEHLxogsR 0ROhHEKTOnMF8kp6k0EnEEmTGAgxSdTh9DONEnYsEh5I5NF3UJohMnfVIEjGZNgRA90C1ONNUkx0 2stCCVsWknFI6fW3bBrhjjzUbqiLrsnZnw5OLEC4wmITLOjnQyRRkTJUkpiPgHM7962HT+6Id2Lq 64+F+/mzM0NnJbwzSeMwVLIh2Z5dUfyx7TTBq0aRkveMTsPlrE5xkiqD6txO3XTh+mLpAB4Pk764 sRD3dQY5DkSS5Dwgzsg0ZNKYIvIjaFsGFoXsWChUicEJ0mwEfEHMWrRY5Vo17TFZRN74K2kX5U84 F7OLBjOYFBkSVjorBgtSAw5MNYRR3xRE7p7iVIX8abPSHZQc0Ug1hgtFt+1fBEm0gjp20yxydRUY YPBiML8ZM5HFM8djSbORY2pifto4KTKpeyzxogY7DysBw0g90370VUm/IybRjokePHEzJchO+zgZ c5GdAiTkVe0zJ9iZL1EPRO/FoHPWVR3d9tPWDsEx3nB0NlKGlyMi7Z0W5HGtQYbkzThzmQ7wga06 v9jwsg+WezR0NVD9aQNVsRYv8AREVEAQRQREdKdWclSePGC5geEy5GLqwzAucaKkDA6VoFBhMdcc SIjxwdYxb1RA/SJUrJ5g9wOLpN6kY7FPf4NEHssqTw40fBvllmMHwuYeY74aZyUZOdc8yR6o3Zro 4PXRg7+kQWgcu+GCZpUqVP3ogfZCeFqkRhQmMGkyvfrkaHRJgp9UC1ZS7aHlCRE9OBxkv1TZT7zP CoSOeTuVFuUHGSMCI0fg6JRLjRhsuVjdkmtvZ019RufHNnBoccwKkQ/BCHRJ8+ZzRyegsx8bTnsY bTl0BwOhDFTWLoE7hOo3o+ivSmYi+cD7RG6doUjzTwg3R9wdpE9PpPmTmfNQ78B7xesO0F+gB7A4 Pjy+VlZ69neKd81grBUDRbVLnidmzC5ejq87YWyz5jIgK5tyZshNzTwQytsNCi3n3hwRAGAREGIA kFealNsN3hSJZCA79psBXWCssWNgkmTN8t5BE8XUkmoRMU11fr1JwSTMkDHFcknQkm8RtOcmkk+Y 0z5hE4X28IkDr1JJUgmGHc/op/PbRe33D+hkD7/sKcM00UVWHqzsmEYfvfMULaKTEUlEFhxbBSGx BZWfd8rtKhucXE8mv32cLxTQMHiimnQHbdw8+xrATGLUFIjBXBOBGgdBHUT9TmK1Mc0PNzO+ZF1G +iiz4vDnhKME0msonuOM9+WX5qYn7vhhcEQkBW4K1rT/KykgobZ+b+6wNIiIiIMGR/zn8H/fUDHd sCRPkZ+zyBoFeQqAEhIhCJCSQIpICiKKqwWDBgsiIpGMFFkijBEVIsSIooCsEUFkQFWQk+Y+KLFg oosigsBRViyKCrFiqoqgoCqCigqxYqxUVWKoKosiqqqCirFVRVUVVioYqAIiCDBgwYQVEYMYwYyM SEFRERkZGEYRIJGRgCqiIgqwBEVUBAVYiCqoCCCMFRFUjBIDGKiMYgwFQZCSSBEkSEmQuqKKKH3K j/NEWJFWwIC2gEgiqEiCvLFG3z0VAyrD54S+Yw+gg/wBWAK/I0mP6qdHVcCgjeCRRUWFJ/1/Ih/2 FuXj/qQh/wLFg76gTojIJBec/S2BCe6xbHUonjBigEigMRIixAUkQIpGQFfaZGB7KCp/Gf2H9puT Um4/PIpaMBYiS2UneCBWf2fzAoSLP7j+o42KcREVkVNE/WfrUUgaIN/tZYi2wlU9fUCyaOD9h3Jm yaG21irVFoqI581xhUKVzf+1pl2eh9BNagj9HklkA4Hnos4eD++QzIAPe2xS0bO5zkOcmGkh5Bmj RWCxJN5PJZtAVl0qqCq3F8Jiw4GWSBnKVaTgf98Fs7ul1WyCBFgSTVFKICHXAStKdJrbpjByILik KlOsoKo2EGjE7F4gJ/QywgZqjIQKCUMaCAnfFZkcPu4i62fYHmHR2Be7AaFiDBZZaVIU+Y9PZaOD qxEetnPi4tVEMKEqhKOtk3qf1hr5gIf/S9zD54B8E1zAYjCL1SqDQxKGBJiUBS+ZTRpI/iPzJ+Uk GfrKBn2RPxF0GBRP4g3DjTsMmb1cMCpgWqLyXD6Qf7oiYpjSmc4w/7QNYWIMaSsj+cT0Nw5GJ9J7 tj16OzozIRwSkh/SjvpuWQpuH7mw0lig+iODgXKUc0hH944wQvCjcUNcCON0JIch9+UmHsPae08D /3OOJo96P0Q809oIbwC2qpYnyemnnnTJhghxH8USx6N9Q6DZTr1AbP4Wa+CmsDuSaC8UWEMbpmS6 Me91s5L454xrJkKXhZajai65giw/iaKqqxQYSvhlps5883Q1ksgTIGNqDCCDgUBDkxCtAkKwxJLv N2sQ6PMt1rmM18NeDcs21gIS0I4A9yKA5IgpJjzM8JbBTjzBAUCzAw5NSVWfzobJBTqaQCF8BQnk Y724AngTwQZJeSWQ8kOF6OGQTid+uNa2EvROR53yYWsStZyi0UNBJKYIAgDIMgxjGJCCQgkSIiAI BGMGDEiRIkRiMSJFixiYnE9nO+BZ9y0q0G5EwcVUpbjiNNpQqTKMyyMhkywgylRIGmVtatkBE16E muRwps9Euem8kmqPaSc6QGXVLw+RgXS8b8Ee7mlioosW8Zo1hnERVOddsmZmynggHKQCGxgFBiwg jxouCdRgCCAjAKa1cASeKELFcVjkmWi+Ai/G+GSYwqJRKiZXbBk087vZElBBMJyqIk8uoHnJ1wwk BkiSeO2c9+bCJkewYOwRpg4DORIUBE/a2glBM8uoK2BnQZAI5iZCXlomYtrgqpi7cVgaKUwqsDKa zrTQiHErIshKJNsYJYQgenAQ9OO3MFBEiJnmVES4qRa/XDFmx6w4aSBpwxWcUWn+juySzy9WY5GG vPCEk2WTTCGzWKFrF2UxVlZaLsmUClUVJ14yrFbAmLg1DAbLo205aJ/kYpzRVKfsVLIUqXw/dXf3 vwT7pl/ez8GBdgfGYJAjsCA6H8n804xI1D8FCV0SPzEaf6L/ufmv5sVYXJDm+4EEjXIqsa04lMfk t6IDEQLyJmz4JOseHxsts6lVkFfQVGDD5giAFT/rU6KI0ZOhQIgggTq4uNs0kTsdcWwZGWhGZyXG D8jxxD3OeDJYfcmMLEQzmZEySbXSlSDCbotKB6iHkgJy8QiP06ZM1WXBOWIN2ROWm9vqMN1KEy48 IGehBA8hEsJPRI0QYDs3Jvk5IZUyeG7JVA0fY9nPNu+yjZDmqjCVWcO7MacmjZONyyilIFStdDzR MkVxpzhhy08BMnnPZ2NySNLh6IjrJWzKISfmJIO44Pc9vl9c/6SsHrrQxR/tTdDlnB8wSQdQ/RIw kxkMDx3A1R27D5QMAtYOnY4pojkYXLYMDbPnB1voo7ODRZodTJgslyxWHP1wclWjBx2zHI/JJFGL X/uBNNC6jHDcHBa1YrxvUn3yPXI/kVJGNQ8ue+a6+jos5vHvSzs0ZPCJCIvfRNHzHjzgmWsduSVb YoMOtN/8fsdEjPEzitjg4FOD8o9hJg7HDSJvfBbJ2KnYaW+IiH81iJJSp0bt3pszyuUr0kYQp6n1 pYnzinkeZoq7oeeZ5wPIc5Ynjy7SiSPAxrfDlPyiZT9INUf0ogUlEjSv0Kd+7dWvoDrdRuUOV7hc QOqIj/upJEk9yW6x6+Q/RGq5VHSjEo/tXsLBBNn7hANeDXqcluU77BynsQ9BYDCI0eUcTJHwGkZt EJWHkToRCcLX9Pu94duQ6AYpJUCL/eWKEiFBBsGV0b2UurCwxjcUlDGaTy8u01bn4o8qcbDqNI6n 8QgwhS6clsY/1I66fXk9bn6Pm6GT6KaUevVhkergYk2ns4gWO0TzK1wMD2pYbnzY6Ka0pQtMcKON ESyTgLQmMNa9kTIwcI6Qp6GCZKRYwdiDoKNx1B9nkEfEIdvxosBaAEtSJZhB84kCiymMx90Rh4yP USYxjEJ7zY/QIGhTyD1MFlkIh/eZHq5TkMdUSj4WrkKS79pAsZkqC2MA2XeCEXcmeLoV0H1eJgQN JP3hm9BhCsrsIwEa1U5JqQH2mPTRGCH/2gepGPmybRP5J3LEWpAG6AT+BjtBW+wsQkd5SLz8oR6T AfoGUU5aBXJQ5EzmBnGUkI8omKaYKbl7CSWzjV52HtRhJd+cPNF2T1pzcx/ZBj18q7/vV5XxQC7u RD7SBWzLndiacaYiout+e2j7TBc6DimXBNPhiCftEFIQUP8YAp/ygiGlIIXgjII6yinkeyIdcKhU ARANb4a0henFQ4EgTXsloS0zAH7nrMSIBvAJgs52yapmar8hi6V9ES01vkNUlSlgpSEaE/roScl3 PNvfp+2ZkO6GaMsGfRqpFwP00k1I51CMCjAoIkSkTa2HX1TMMUT6E124ebKMhN6alVuexholsS+U LokjZCN5TIzpKk0UpICMIUENoYow6pSi4g7jpFmdc0RcyA/eCbBPuEoBHTkx+ahRyj0kILkXdyBA ub1dAP82Agb05t6zRyXvBAB3iRdOwTxokhBUzeiv3o7TssJoOB9GSYfh93P34Y+43iIWsSYB+JZr Bv9TP7fYsiijyA00eh2mQJwGDy8v5YHH8r3xdhPW9n9bg8gXKCYLsn0OftoE38q/cYwNHtT257MZ nQy15JSRHeon9KBHBJwNoOIFhhlprQpLm2zRMkw5MJYswwF3Eg06Er0Hjt1YPWI4ZTZ69kxgvIwx pKKN76IOuDjgZCyeCgYgRmKm5OLjB6OmQyRoSLCxFQqmcVKnsYLm8ElnDgx6PTnrG+OjEm5cyKZH HIowGDpbEx5tps3Y0SaeBBpBcwwKXYKXMXxdTRIrPh0Lo7LJwpMIiG1Qg83nmzgySZSCMX1aKUcT ITMuBS+KjMwpfKXH3iVTAZY+40cQuYRz33MGSlTc4DaFzESZBg9/M65JifyJoJY8Oxd2YOvNAXAo /PYQSMTD9ljb4H0mT08+PiUoXPxMGkIlxnSfz/v0HQqIupth0UHufo2evV+DsWsVH89hbECxOf0A /AoIJo6hMaSiYOzwbT7Ffrn8C60DyYOW/ZaVwvTY54vO3bN5Mp01O0GdqrMb1UdUs8iQHevr3yUZ ksnCi0MRGURzst+F6O3NimcGxTPFXutCkiqbHnQs9lB8KtejbD9EJMPUQjShLgzuhky7VxopUo/A 4iGmDR7I8iDBsLc3pOBU8mfhJ2KCw2T6UY84OxA7OJSODAwbEU4oekTsCJ1IbvVJoEGmtYMQgyxr yvvEfzo/iQSSZMHbZwub8czkUYPNOY8UbgUYagw22h48XYyZ0ZQkwe/l/k1dLOr8k5Mg5vz9Seg9 09UR71D+qUltw/QO1H1aIQIMgiYo7Qi/uHcIh8dmw+SmkfgjgPSH7X2+09xJP4h/x8e3vKfkj8JD zP5qdwmw5NrDlekQ8Efo+FtimhHFPcdinc5kD5p7yGdH/IAFSUZqKkEEv6bnkt7Kll53+pxpUFMo rFBAFP4YRo+Ez+gX959e6ncbfYvCnrEpsiRKkhhEkTTggQPiMLHSkoUsx7GDEfbccd3IvVeq1OHs tCz9nvj07t1cjcHRPVZHOh9yQF8DFFCLWQDvevDWjsbn0cQxJwPLsJm8lhpPDRShFw5xZpIyXCZ3 QPymqky/cLkNBPOi94v0GmeRhj3aeaLc8Yk49cG6c5YUjgYU92ty8o5rr16obgYHmzRZLMGUUkZn SpMk+Q8aWtdkUz0RIl7kVzoTRMhMzqo7gqVEljRF4popBpovA3vJgpe+YMKE0y25uDsEuRZgUYrB tutl9j0MMa54aE5FyPPPIaBVB45OSpNHJTLozNsehkovJviDgaDZ0NLyHmIWLyYO43ItRSWok6mB kCJLJmBoypYqWJkqpo0MmR/xiHcQQOZmpbJmSe0QvokcmLXm2b9m1sljDXezRrg1XfvJij2+r9UH yQRs8KJFUkBFR4cf1ROXUbSuOOPI8YWNwXefyM2Dk9lGJ87HHUcuPTWnnFeVfLOAMYlJE+FtLXk3 Ly/SMgQQIoqLAQiof+oBl4kCEQJGHHlecTnnYcnHF4nOduPQeB8zyPQnQ8hhSpijjzmXxVxWc/ga PhgVzy+yZgbcBxs0FFmIKo2dJwaMluUcGjvvD6OYNc649N2zVw5NoT+upVNo24YpggxXacOTND+C BCxEwQI4Zs+Pbt2vM2cHPYy5lhxC3RJTZqX0G6+nP5h4dIQgwgyDGESEd+/MdBr17i3ECA4y1TQY JSgSsiXOxwYxShx45JV7qcKrdkSfF4DDcqifA2/NnBuCkEVEmiINRUCOiY2r3vwcBsiHcbGeJGaS K6bsY7obMoMO3a6xEkF7bjaQvDE1mY1YVpD1xYsHkkJOWxygrBt29sMFuYtrwYyJ3EULnJ2ufmCw D4ZPUOxJ8sA2QsB6SUYWgoEFYiqxiuoFYBYgWUKRCdN/AS/s/bRoNpSx1iCH8m7tTd7RNR2ieCOc yKaphhCnwB4CjlLT7QST36DUTUgrQZ2PZN5N/1SDAkRslKpraUuOci3AsBURSwCwAoWAJBFUQU9p Dn9GQ7w7xd8FMsxhRTRg2Nd7jC3twkJrsmfHzfBQ2mbKYZTWuYBA6s4nSxGMLweSUCEICJIixUFW JFjGJGSQRJCM76YtLYSBqDYRZjAsZAH8Alm4gXkJGlHB+8zo6SR0lQmKfBA6DtRwA9YfmL9zwAUf FpQZeTzG+hGbSYnAldbKTgHAZjbbMd5vKAVhViMcAIVBvVMhKhk7r99eXtvq8vOxo1ho/uUosRmp LWywK0YVkaejJ6JmqWVLD1wpllUXeYA7gevFJ6mroyU3NKKzlhILUUq0C0WQW96M76Y7wrsPM3Qt wPdymgX4vRxChQlD9uilu6WTqtf2K/g/bR8gREoWNMxI4bo/Bjoo5wsHRpPGMFinhTGjQ4oteWZB EkVfziArSqYilTBbBVRpEqRQPvTd4jGGRXE8zzi4wuTcPHmRhiJRYh1cNzIlryJuKmzgMt6yXqjm DskIJs57KY4JngcrzG0cynJhSRJhhT9g4LEzZ4BETgwaBxguZvsfAYOiORD5iCnMlJjUY1qjDRQ3 PQ8YKa0cMgmjO6MLgc6scfSdHI44ZO0c2Kx+RBTI9BWk2vYeaAIJkNDSxovnMzBKAhCxK6xOHGaS aVmRR5swNKlQ8gRABhsnaM2Gal3lC5INDDxIfkYVu5lDdzeCZnBthMNUip+s5wXKlDS3Gmw5BETX F3iwNC6oiHYRPTEL1OaHRusryFWzmDToZSJMmT55HkBxdRrRpoqOLdyB0dcVBTIoSnsiFeJz6MG5 ivA4chRPGTymY79u0CSnQOdyS7ucKLIsOqTtIaYGt1XJkXIbOOwz5/wQDB8tieUOZr/WguFqAr23 lAWAwVwPxAh4UFRLrAwsUIetXYbDcbzy5GkefBjkmRSh5EPIZA87pBlCOUQVPn8fgg8UFlI2o1GH ouI/JBLci7iHEtF2ehZthj+NYiUOR402SKtPkIXxWuCotB40KjC8qWaIPPyiPRrzEUGOIbp0V0MH hIpJGGJJE2TQ4imj8MRA3IQYyBAhEWKQFT+J4L5kEoFFVAEUIHjRMsaJl3AwgHQ7nOiA45mVsOO0 siSiS6jPt7IgeyGEz0lQDoDiAa5PRoiHpG/75cffKFUFUDCK8YNe/hhoylQhR/GEghYTwl3sqhu+ v2Hp5ocrEZBVbwSMSRAZBhFRWoqAVBVJBU9JUBJtMt2cUPlFkEE0y1JQU84mjt8jkPA7yjsJ495e u6jEwhzWPE8DSHaZuui3ecPlg5Xb3+52Pr4OHtp7cAietaYo9Jmy/V7rI5xRD+HhibdSOAdXngIB gNxPyTZt9x3d4RbK1RSbeJ+GQ70fJHaCKdwB484tDq0voQdxmPZpCiHAhYqrjKLh3K4Ar5h1o0+a GQgpngoDIAZhIHy7hvgo4p6QqPmcJg8ni6GA/g8Zavj6Yx3r1Bk5FXEms4Ld8rTYAb+j+ZBMGhEe gy5Q8oDmc/urMCSeDqZZ2/H4MnVsCmuv3MKak3BG7lHJEX0Dyw8BwWG0nLCp06Z43xNmg+2TERNH ECDgggKhYpee+nQiNgoNZCw9mpjE0WoclMJ2LJeR3QtQoUBhUCkXB1J47bJ12seejLUVRYsxklHz ZPAPoiWIBoCCuwg5F3AuBV7xTUaaXXMoYqiB3vA+1W+RY5pxxsYZOXaHRjdS+kNMunN7bFMvaorI EuwIZtje9ZkKJEYQROvPFVRgpdVa1f2+mIftAvIZJj59WuktdD0YwAGAqIST2KJJEkNXmuKGRQOG ZDjZ47lDhhxomKqFkivS3mcEkkjM0FsBY6JrILCMinQG60m9FgWAxImRIwiiRlYTU7t5WwssYZrQ Uw30uVQrSrVaxK5N22PoUMqHNZzXxdG2F42VHe91NrqHhPBqHR+/ownZh2E8oiBeTq6IsXiE994B 9A587jkQbcKPSAnPpQfaiCwhsXEWFDcWRIlD3K4XOockNiSQYkBiAIAkCbYSgoIpYjlx95QyHKtk wOhcszmfuNCkE4V9WVmnnvYuB1CFIM1rO+tiTIxGB1CMEghQwYkWDIwzLQIhazN9+SpRKarEsZ3p Og6Drw7Yesh0nje+cwPLWc7YwyKqWLVOj2aknn6HkhwfuHIie5/AAdIWx51dPd8fLx/V8t67u+0+ Ge8P4/SfH0rl/Hrmf39+Ynt5upjztfxT1+77+G74+ZT2+/nr5fH5fD6+RzwMPWLXC+Y7ZMUgNE8D Dwm4gzBH52HjhvmeCRohcdeorTzJCkaBUqWKjs0UsVkjjJgmLMkZHpMvhbl33uXeWy2P9X1u5ypO CMTiFBcDinDBF0Gak3vmbINYVJZMdhCFDkc1S5YpLnlyXUiW0c8N0QYUKtIQwNOR4uio77E0VUU0 bdHc5z48fPXj5fT6t4uMPbdct8fB0uzfP4Z9I1ranw1d/ztGe2LOH0rNrdfH4Y6rOLJY8ajP4L4+ us34LEs/KsaQ+sd2Y7ePr1r679/P031T4evEY1hj25tOmeKeJ/Gzt+1PbseLXZ3tW0OGL3vz5Yh5 HmvHO8XjD455+PatvSGOflX09fhvx5wbLz9fn449vtjz19Dr3xDqfwPt9naOD3PmZPA49X42esT1 hJpUmxx8G+xsqlBw0Z8Hm95JhHgmOSYX3YhssQkTcMJ00PMkGTbjDR85SMEsji5Hgb+QBSNa7ccE zZS5wUQjN0Rxkom2A4aRMnKX28qLYY8kMPyAXvYg29yzRuehmSpEs2iRwWHYINjo5Lkoj6nXNOSe yh4DgQhUcy+dl0xkuMDkNlxHl3kyZY5YXGlC+vYQg86OATVZFzTiw2TzB11yTOhlmG8liFMvsc6S MDn5oDRyOIELNNXiYNknD+MJ0UN+BDz+p6iKcNERjGHRTBzewEQsCIYuAKoutAeWRYpA6FgLFBBR BixixYjFViKRZFBEYshChCID8DNxik4gRKJi17AoHsNCYI9sxYQCYU0yEEnQMyEurntEitKQgQgI hjEiEAyEgB21N6Nn2UDrIb7Lov3LRga6MwuEoi2pHNYTE1c842OB0nMdJwzHQ5us6jrMjP56beMH 2VExxYeuOjk26HxmqO6T+ni8qpO4ge5wfHdB3YUbA7fLsb7nQz5viLUYR73keIPFYUKSqYM9jKlo Pmd8WQODGUVcF1z9xJaMF2EIOMc5obo0cahoYc8ljcjLeTQ0cZMWMcn81D3dOGo7jJQyD1Ir/Pqd FGCerrYgEBbwSpJ8i7RPyg0A9fz83q6gSYDUGisoyMd0jT8xRkpkggldn57Vnzg+p4O0PrDB66dz 5pzYwDGig0COGeBk3w1E9szOEzA8i/qXPplNUTwwT+g/Hp+xigib+nzIjRfj8vUio06LFwWh8ofQ MiqWZjMnxQ+acqPEe2AeJuL7OvkPPl89uRjkTuhaEkVnmkDsfbzxMUuronf+4ERLnwJD2xqXM+Yz Sy8tXELO3GMARE+38Pdfo2H11L+Hn9uvt5cff7/w+yrHXP3LffPp/QsOfh++3qY8nfH50XvP98/n 3+r5frV/EJ6vAERPwIeBUBRFWKpE2wh5iconwPn033s05Lo9jPsp49ctHLMs++ZGd3Nk1u+b9OOu aQ/SqqKKRDTNypjvh0bm/LdOej4s3xOvNM58B5RD/BayfYcwZXBXHNiLXQ+KHqgf5/8IlYXoWkOU toHtubcFSBcsWLBFBW0KCEC2Hps5bdfUek6/ryf+r+v0+b4/XNvr+0vf8ePH547e/fw2Xn77z+at 4/Uvvb7e36r7da+o/aBER8nny+z6s+/8f0Si1fr7Z4ynfM0ZWvmW6qG1EEStrhszBMPoERPvSrPx /Xzg4+P9F6lx+SeNxrWo0pfLOzdRWcx3DPVY+3bMa0V/ZBESQEROQ39QDPs9YgSJWlPdGQhBX6Rq CHV2nqPQ1c/25yM/ae0952OdsllixPv/AxR/D8G9oSAEv9o4QkpuieKGcz+8V4kiQi/aBDUv/Ug4 qckh8GqSJRKwtp0wz3Hn6kw+VB83WGMIpImSf1mgNUU7uX6ZQUJ3ShJBOxUgNArBAgIhSNKl9jtM QLB0EYSKS0LJ2jXQiRon3leN6WsG5IGaNCl22+I85H6ke1zaMUySZ1KlKpIwngIPgRcTC2T4+4Sm Iir8L+3IczXMDUhw699CaVY+0Ycm6PKqqtG2QOuIEqReOMIbHjLDDLCosIkQBZLmGtGjVkDVpxFE 0yzTChBBSg20lSDFKZ65dUqciTlhSGkoHDBGAAoENOZTmWw4YGoKIIO2gVpBbqQAkBZQFNQkhAWB S0iICoro70JJjAA/JMh7SENCRJJw0yiuKWjij7C/9W7huhgePfMYzQZWsISGMLuV6TfZBT6kFboa 8LoIIAkm/P3duZo0gfHezJDMLIG/G3JuNTaonRpnRaZBGRMqjbchwgdvp0Dbk5gJ3b3tRQqECrXS DoMpFmq0J/TQSA0ZwJGtYWG++wmC4mmod2GJCdMETZFgMioaM79b57Bqa63J1AZARJAihEJBGAaF AD2SQuCusMETSh9v3IdthDaxIkXoguJY32UKCKxhEgAWIhUQOJdUjFVLJZQJBINAruiOo1wbH1AM UtB0Ds02rdyD3eA8bB4WGmGFhREZBRQZD8ISyaCJkYxUFFFSojpHag5Tj4e4eKRMYFVJCMPXqHYR EVRRkFVQDyPWSSbV0iQ3er3hJJISQhIRXX4gG3MgIn95QuUkkSBFIRJHkR250zZxId0tAKEgxLJC VNAxgn+F1spdahzh60DYAKMO2Miv0IJzAryhrNyC9zIkn8iVigT9KAFGFG63Pynkfd/Pd7OT4BcR n8T+IxkGSJH9p/E/pT3/edEylapIbMqRYZP4wFfCty7M5JMmNkn7uzO5NLartzAhoco4NLAcn+T/ H+SjnO0dCGJmNSGGy4wyKaIm8n9hDkoUHnmaojR7NDqMNR8DB4nJ0UcG9UfuJJ+4KgoqKXFYigon BkiPI8QnfJtB3WIwelCWuHDyDCrojyRtHnF6Xiu9MRmCGBrhYGDR+RDLiudnRsrskgOD2WnhZA3J gjT0UeFyVZ14w29SICBs66R3/lIIr+8nSbyxQou6F6R+ozXRILGh2BTJIH4ae6Aw8u0YGVLsJGjA uGjKK7PIfosUkwOX8M7PYtCmyfD1X4ohohxQ+Axn0LZzsw7lHrEqYFNhke6wKKElip0assUsU7yP tERzodSyiSxtUd74sbpWFjTo7jIqYKFjEh4ZkGHEcitMQDFnCiYL2MmBti7VDQ8sVMfoumaFeeKC yhssrB/BYg2WdHt3OaE5LVCxtdnHtmH5KDONrrwUoxHjvEeKgiIn9KJXP9yCcdddElMlihq07M4O qDWkhrUEiRNG2l4Ixopo4rfIqNecljdYymSyYBt5OhT1ibFkLDsjMUd5jBk8IO+TPhqh3VY3ClbB kmULXGjxkS2R5NShK9yJMfItm5IwSC8TBZ+luDuURM39vdfjrkucRtwN9CUxVbd+7bNdZ3UeTdhi oIl/r+D6yfzf8KutQ5A/miqL4Ojo8qeLPPg0iQadHbtqLhvXlWYin7j94jzyPJLt9DyRAZSjRGCj ihymE9vQnQrEqVeWeVeK7BuycU+62i8cnTyCJytZs5vvtJP0odAB3C7cgTj0+s7JyMOmMJaSEcMV pOuQxsUUl/pBH9Ix1h+0iFC2cVUpnUi10tIP6CIUjCovEQwT+e4woRY3RwXvbBYToTQGZtlVHUZZ C4/BT2AJ7zAQQ7FcABDzQiEVzqCfsT9prEmnlSQLCWLUqytiMKQsDQK3OYFc5cMBwerq5ywGdAwE xGK6z9P6mbj9yGZzk1grodELvPCKIUf1bP2j+a9LuATpE8Tact4gO2CFy8JWYnQQbqqowcoHXFCx jPih6L4hE0ifUSB02MX7BuHeJ7T9kPQU+WBkCuxWnkgtC5BY7FOIm03ifITLJHPGoKMl4Clua0qf fa6BhBBAhWE6W336UQjiDd7UU71YFNKR5TcG5E1n8mKWATgrnRN6FurcVmZsjUIrLsjj8T6I8vTQ mLojDWPbUeMR5TeR5E5QD3ZhN1+oE3qRTqVshsDog+OQ43VS/0eY2M3JTUKpp+YneAXUbN31Bgh0 3ZgFKhjJJ8UIfrHwh4JPnHoHsaPYfQftH2/ywI8IQBaMn1tBHMMOd64y3LzhmyRRJKY4sGWUHHV+ UTLKZds0cye5GuAech9HOToCJ5Ih852ex+7Iv+5bCH32S2BMKnqXG70T4J84P0QfgjZrCbUFisiA iDEiJIoJGKiKMBFgLJEUEjIKRAihIpFJIBILAZAro5+f1dON7YF8MTydvw4Cl1CRQyz5Z/LNpx04 5ZY5ZJwEJ9sDQQfcp6jSJ9x8BcwdAkf17CRZEGBGESRIQEc6cUqPxiekwh6HxDZ9InznmjzkbfkS bT6QWDaRUdPMh/5PSANXnTw6kI2BPf/uR8eBnM76CzYJ3CPtQYLzixX0uJmQ7QcKpc+scEIZj6Aq rha0ZXqDlBW4l4YiCFtSK9odavwAOsHDvQ4gFzkRMUPixDVBsh5C7M4vuB3IPr20vuEs2tmyGhHu 9sTF7MbSPKyWmT849pGhc6MbFlFHfDRLzBK6wx9AV0/zPDt0YpmfX8c7RsksWpq3xtM/aA9k/cFI FBD0HzriXnt9+hW+IMsFpNMDD5fj3KarRWTevz3FsUwugVHV6CSdaSwIKbDKsBsaN9Zi4K839s4i dsNNey2s5iJexKAaDglQ5pBXq66BWz6hPxTeBIfoT50rP7dSn+92mRNAwu7pxJ9PcPBAhNKPPBqE hJREtBA1d4nrDvDabkDBNayKvCgKQ0XAA92gA5hOoT8DwVXBgENTgD0f6YgsYkJApaAsIKBEHHNP UN7ln/AIjWZgvATj2z+XMocgGoEA/XlHITI8Z8hPyUo9RI+xqiEZUhEKokhtD8xWw2IImOHtS2Yi 4akMlFD0exzB8hUAyXWOxB5Xs+ut054JAaQHTBsClUBY9maGmkphO/YdH4X7CAXMjotmnhA2dHbn AMPk64NMDqPAme+yBoWICwJGWF5+PKXB5gu8HfyajBELECw1+h1Ar9gK84K3MGQhBda9EJEkQCJA RIshBkABJEBYCsGArEICIRFgKxVV8HsCAo6RECwBEP0ToQ6OUETyhOmNH+JOYPNCAadpAioqSqKK HQW8ItNXu/NFPuRCOsfu/v/w0uge0A/w7iRV/lE5qUxwIbAOos1C0S68Y8xeTlfqzp8yiyrPkjOI y9GsiJmgiSUj/MYh5KagV7bbYpT+7SUkGGvwhquk+HKo0SP2+w+5kmp/CTdJn5EQr9h+avjJ3NUZ QP0w/kR/L+gLlQ/e5RdABo6UT0TgJ6jIOhAOOaTln3nyfqgmF9qM/ZDyR0Ccm9AzA4qOtD9hMEPe oFCcRPgdwYOtX8OYTiXQwE9T6eJ3oGsMl3q8RO1DML0GQn4xCSS0AKjru0KlFUqlJ7aIu3+iPFxH vRUeT0fA1iPjECKhzK7UOP7KNIbgwHfCAxghIR61JOkJ9xcN4e/1xLSazQ5vF9KvIUiJmQPaer3S 0obDKoIU2QznSAmkTn60IfOaaRIHedKbS2PqSPkZ95E+aaRBrUdiNxKF3BzEDCLRXrEtdtDWeaBy CeXAPIXlaDI53lO2mEEKPZZCo0EoVLYZRcxwMCpMDBoYVJdaVEbmgt1jIQIjIQ0OGLBVp9+wxEqi KkpgvYsvPFfJMDI6IepgpCyGDfLCkxnAUgAruVcH1IcgnBAFwYCp2aEO7mFOlHWCtBkAr0hzhQCu Ej4hy6r2DaTEs8ZSLxgtFWvSWuSsRP5InH7xRWExOcMVd53Y92gDpeM6QChw0uUo7XHSMm7nEt2C 30grr9aO0zJlNxJHbzo0LYDomBIjHDxBWlqQIQkgoQgKIqwZq0xomiEnx2YwaQrVSWzC2uAmYSUt CBLIJIREKkppB0GsILCQh8viC907WuU1vqDlbOCqLHqBl1iaCYqQBQskBtCMbIVQURH32OZTUgmC adstuQDNynGPuPb5yeXEpIKNGpa+JDZkNalLSjEpYUlQ0kAyA5IwKRIQiwIJEgwiMYRhGEODk7SH aMUFFFkSLJGSXB6dYghp1oHt1I9PNnNGrtIYRXqKjPUdOkEx3SZFH3FCmSN04aBTaUhAhOQgwgFv uBXap/S7hOIEBX+HehrEXwYkksdPsh1aIh2SdAqSe52SxxJ3FmuU8UOv3IdY92R0G7nASFHKw33P ITl5sKCq9xVIWQDvzBXQyCyHKgQKiFUmRZFI3JJdJPxh0jGQmTyYFhOSXDzzeR0hcsw9yPQp+R/a woYUwpAEoMpBKRhSRLEpEsSwEoDJZBLBCgRKQYFCJYlIlGwSgywglAZKQSwQoCUbBKDLIlBlIlGw SjYJYlIlGwSwQpEsBApEo2CWCFkSxKCWCFgJRsEoMsiUGUiUaCUGWCUaAloUo0Eo0iUGWRKNglgh SJRoJYlgJQGSglGwSjQSjQSjYJYIUEsSwSjYJQZQSjQSjQSxKCUbBKNBLEoJRoJRoJQZQSjfyBX2 BzKaEfmcxsLUcWMkapLySyBLy5IpYSmMjQlv4jRjMMY2BfsR8BN/qIQ90T5nCNJ+SPoHyR70Z6ST mzqFOhS1JCyGp1CcBuQz+pKyMVYI0kg+uWIgcZIK+Riod3+62S3ApdKgMICulgtAehyavJE+xTab Xy6gTUkBIxV6XehQgJaiWK78xQrRUkp+RTaiqfeyjkKRFoyyE8DthhQ1MC6IvtZFT3kzmg3vxQOV Q+Q26AVovcKL/kVkEk6hNE3JOoAb/l/l2Fvv4XvuUKnEMEGiJQTOCvKhPi/gj8H8dejM/YgByrO1 X3TdKPWST9aP1Qn1xR5yQZHtnJ4+J0P4FMqvib4ItmqRF/NLkgHoJNiMM/ASqBXWodgAjyBgv94r vcECWPUJ8RLCfw58CQ0Q+b7c6GSmv2CHd2dZRjpFDIpPdD11Cmil8yy+cgutZLaTRMMzWBrJdaHH 67JglpTTotRIllQUbYQz8OamIVsaa4s1q1tKtkEpQJiCxYSGnMaXtO//ot0BOAYcooEUJAFBtFhR Dn5bAJpCYwPJkNjEIFspUBYUKIUSSmWcu8STDSHzSfsh/D59CZ/NNh6H78Ps9TrdI9sHpEdoYcxQ IhzCHMUHLBiRaGLgxAGzFkVFFKHDzNEe2fsYmVRj83yK/HMtb9z4Ew+t2RrOAPsSFhq4Y4EIH1Bw apl+zUhwDf2ie4RC6+4mIxFCmCgdIiDPd8T5BKhXIgWISRofV/RyUSEhvaQcXGkKIBeyKAXGLWGA ohgWNwlGA+owAoYEIwYNEJBGo2Gx0+e6pWBihFnI9VGEyOrrKkmLRmz9PQvPqBXDDe6bmWcG7IEj lE6Yh+aOIon4AGYARqAAbxr2NhcVufX8AD7zmBXe7DuNybbl7nATV0ISdVHciesTbYSEPgKaw96P L7UWH++fXMJ60TzEvvNbwoTlRoCJ9x2Da+0JhEJdoKKULbg0CIe9H4IdqGoU7JARFAnE91IExCRi SJFEPUS/Lh+3CPE2m6blRFkA0iwsQETTUMEsShbJBgoCoChCDIySEKhVqUsRbiT6FlJdEPRJg/QM QskIcJiCRoJIDhK0JJQhIcwxCFkVSCFv1TDuk9qnejJH3IpNWhCmhJpkgpnAPmJ2J93l7DtOcuN2 C/4wWkD91/hZ+M3SaZDaRIc9IvVG4E7u2SDXuGgSAWoBxwxlSRorJ5DmM6p8+xTykbQ++D8keiyP DD5KHvexHEk/WftpBnCLvROB2tiP0fF8ULrT0CJ6kU6Oc9QUhSIMaiK0Q4LREjGE9YT/WDb2Wmnm mqOEJGCQTcMj069SVaqtaqJVVXPvFQyE/MbG54IkHmBaR51dCF0IP5o/YGKIUqch/SCFlcdALnAO T/e8IpSRkqIPUOTxli4jNFSTZJNv9Yph/SEgiEYSCyLrQ9gPLAgTzmZxIsI54wDBAkFhwkmCAoQk PhEVogimy9AAXIKIHwigELORFQ0RVIRFevPpHk9cSzVqoLEkD/YokozwbxBDqzCicmbHFvqEUNRV GoaWJBPYC6c0BD/Ninccnn0cQV29AfRLc9BZTmDUfnkAr7XlOJIJmkCmyBZCw2itHMj8zWOvEphC EBqqBZFZKc0xN99yOGCKAdXHTnYwCRHPKiVBT9jWLYhBQXAtAFFgwgjFlkNEApCCAfsNT/JKlBB2 E3CQl0VEFBZQSKwAwiL/WAn53JeB+11f6EUMxi4gZISwe0ZCiDGENEEEWB0lSQrhsQXeI7NCiou9 NOZ0OXXvQTOqC25If5mc7wsCKZCfcHMX7iPz3b4OEfBMDSwTsDSh9QBDvjAV4TZgH1AiMBihX7B+ p/3YnK7eO5N8FTewRwAHIK+/ZE9GKVAT9Vc3GGBFzM0obYeyQBOkIaPiH2RQhGszxDWo/apEf4id JpUwTpL/Za2qVg4uIF8IgOAYDiYn77gV+zJTJT7QenmS7ALh2jvA8pIR9bQemQPOFDqR9xkAZfb9 65jX5EkxIhRIF4sZ4VLfKCmoQbVQjTF/D99REkkYISD2TMAI+xHBDOAbyCqPU9BW/9Od0cXE5KzE P3dEnIIRUXSB/RW3Hi8Wgwl0s7eb/sp0IYD0j8IVNfbdF4951JDyhI/kk/j80dOh6HIqS1ITRo5Q YrjZI9/sDQ0rLKk1ngm4sZ0NxEUIJn7B5kft/uy0PWBQaQk++FasGhGm1NNmEQ8WyLYBbVhFwL1G 9MpqwUJINL6gVlkJZQzuZTVuIkkVGCCCAhPOEtYjEFIqqgsfQD7CGyHqfbFSAAoiqQP2xEgg9Q8T Wjrg+4OgFzfeJiJY1JBIexX9EOiKT8P5YFiYn7yPgeBpE1kTweUS8S8MZDIPzaUrGScSOqIYw5RN CHpdH+e4TE0LQnvAPsEv1fR09SGwD0KDoQ1c6JS/ur0eSH8gDIshoMUNp1NJ6DmB7DxD1naHryQu h4AF7kTFtC61j7mgC5QoiQRAWSTKWTiM/GkCoTb9qBqR7kQ6FDiPYawPUxA9RzBSkJInfCiQiiIi IyIkYILBL7sALo1gzKAVgJBJEWSLGKRJEWIkDgSVAAwjFJJKwUIVhKiqbjXQtiIh7yCJUFCQBAx+ YpkrF77+V2tAmSOQ/FQuCtvI1AHSod4dutXvExEmpAM4IkIMigjwQz/uRvEgeGyMyHTxJJnmf2co lUPwh6acsEsHXkwYhCEOi0JrMZqYBMqRaViJaAURSJTVMlHFVAf0aEQ+b10CirqAxHxR4GSsPiEW MWBBhFhBRBGMUgsFAVQAVAVZEihFAJABcIgrQhBRApwCpRxTIMuSip+qzR4qlkowDwJOxqSrAIxS 8QNiP+PFDP3n8YOByEZM2sdFNBCCaQ9mviA+0zid2HlvQ7EzRxhsXYNndrGrCZ4U2ymCKi2E/zxd yaIbQM+PUfMEYcwpOP4NnZkjEh+y2WjSz+nMImNWn8AK/5ArYAR0jH0E3eH1HeqqjBgK2p6jgv4P AApDMj9wpyPrCF0HOaEz9hC71D9p1wj1+yLJpR3foulaaFS/FnogDKL7kNibcq9A4WQT0UaFumAa EZCJW+kcXkiSIsgJIKiWQMFVgbjLxnLmOlKoSkqEkpROxHveMWKogRiI96o7KxxVF0bp4vUAN3+z SWPbPUvwTmHkT3RBAbBxpNZxA5oiTAqoSGEDnQJisZeiSqIqAAbRaaVD8RY3QU7hIXgrnQOgD91N 4vMjBU6YjKInctFfeK2sB/YXPZVRLCUWgtIOACEUosz2ekoA9ETnB9w7ROxTa1wiCe4JIyjMCoKM BZGIXAkmTGgV/BCiy3KRo+4mbl5uljkgdOOP29o0W0zAzPCY7dnd2JxMC6kLAs/y5nYOxtTuEbLj 58/TqzsIgIdJEnkO7ngGZ7YQFgvcYCQAKhIxW5MDR3vdaAMEg16z7jA10PEIGVUNSTFDspVIgQSx G7oNLa1gHlbP8go4MhcSEMuI8mBwbwTyYavJ2hoI6ZwIBGCwvQjbvpvbC6EFvPZzguB9BgR2SAoC FqRfUQ51ZtN+zrZvM2w7WpNnAJKd05N3dl64q7s1Xc1fW7Z8g6RHgZpgzHq86mFlm8koJHVMIp66 3LJxJMwDnthPCHaPlzKJgiAzXMw1dSaQ1s1A2b2eX2uc0OEUnUJqsoOjsC9DoJwCKkIJtGZczUHY 3UBB1qvFixgOrLclDkjCbfN07RYvZxM3QX6WaggqxBVYojBU00VFQSJRDlBEEBIgEWMGQwmfiJiI aRoMUdwmAn4pyfI9gnobAPngHunuzznonriomNmjVw1eUNZ7EMhuCe+XaKalLbSMNUZ1qsnAkPZB YsHgFVuig1ARkFaCnSWRvmoMgakpC88Og0LIOFhT47kmEDE7sohmavmKyDAovjYDHMXUV/ih1hT0 neP8YrCP1PZrsQT+HZ9ewcn8ztWb3Xsq9YrUtS1vUl5JMtt7yGJyf3w73qmCU8S7+EUnERvPmg18 qMBWjnKEqRUI+OVjA841Efilzyq3tMTwyLKqqowhYnCGQWbfIhY/WnE4sJCSRhJCdCGhX7Djc2of woLFAVREjGAKLFixQWRYpFFFixSCiCsFigsUBQUFIopFFCIwYyKqKqKsUgqiIsi9BCB5+AL5yohS TrjUVheDGKFoCepD0D79vUE13HN0X8zTz9hdWTeIgnOYP9obQRDrQDYJ4Cc8BYoSKAoKRQigskUF iigCkWLBQWLFBYoKCxYKKKCwUFFiixRYpFigosUWKKLFFixRRRRYoooqqV+TC8jzcmv1R1R+/PrE 6pgHlO/eNO8TKN3Q5mPz+805mvSCd0e6R3hKJr1JO0+KBnV5IgkhIDIgMiiJpqgBpD+KEvprQB+a Wag99m/NKHvmwYIuZpShCDIkQoje8IShqiMLq7XIFdfR3xGmCQGPlposxpkiuHaadK2RxikwkQhF HUo86KAWS0idZFfIodVlDnMwBiN+5oqmiw0G39ia1NaD73ndAm/3o50TWGsddBPScHaPJtLmx+Yn jtTapBwA8OsTLxNI7McfNC5qO8o7J+XlpxbXuUSkjYBGklgKaAHzfAxD02vlGRwwpDsclgb8aRMU 1ZkUGRETMmHkVG+oRJCgzTglBt3stUGJhV3s2GwME7hGp1HMTcCypq0MERO4HTAngfAJRkDS9Ixq ie8MyKio6YkyaQh2gsIhtvRmtBrlREW6AQNJNvePPNuyx1SUws4yzfBrM5iIvDwGHYLPbq6DCQ7S jScsBYGmBnAaMJQ3rCG921HpTt7Anys7GLGHDOwk0QIxoCUpCVSClSQwqkl0UHU55A5vhmQeCCHl TImRO6Pl2CaOQDZPKanR0UOCjJuMQkmx7yLlMe6YTGudqQnY0gmyUGXMASlgASRGAEMEzoHUmgfc eitD8lD3dJcU/IHnE+gnoJ4h2A+9HUJ9VcdiHmj+RvDgnIdCW6QV5+qOJYouB0gXXKiumxguF2iW EtkhSqBSTIAQYzJI50BdBo3lJBCu4A1NToIQNTdCEDUPnyU2k4Ezqn1ElCxFOs8EMByFOxBjQdKH w5xQqENaY0J+2+VxL0Jqmr7FRKkhPrPukO3qAgege/0DMPS1r77gI5bMMqiKyKEYqY1IsURRAVKN GwyolQMI4WsMSFwyZEASInEoUiBk1QLlgUpYQqQs9PtinAce8JJs4QFVk5DUoZBCCCNpvV1BAcZ7 3r1vOSusuUEomUuN91l72VClEguTlEJuPJm+44vmCbiIZWDSAHOLsuuAK2EwQw1khjDbKhO5DWgh rPbAEQzgriJhrByBWK0QLU90lMrE/yiojyQN5M0P0gBn5w2qjYNKK47pyI2aeVVzERWlTCCvYh4A GWYDV6qPcArdDnHOhFdevpw+m2WvIsjG1rBgjssNI4VkUIA4wsNUiEAEiwEVkFhpCssLlIIsESZJ LINZQYG00mIa3q5NIFEpK2hYgMEUYqqKYQQoDIYQsJWKMCg4RCKluWYZhZATDJRWGZELIMEBgtsI xY5ZbhIjMPeAZjoT5LE1B9t1RzC5h4CeKFx0gs+rmQwIINoBxm18YKH32NZxoP4vMB3uSjy3JJt1 nirFIIowIkavrvaqp+cjVH2SaIY85U2SXTdKJ7EKjePZ6+GmONBCB6djm5hTEFDIKDMY3UoyWjkl rFWJPEV9YhQ2gnNVAoBig0ZIu0FYiZj7org/RHqMD6i4IhSqxAc+KOvP9hDUJBJAoIvYUUPzBY3A kkYMiJBi9gZeoHMHOC8QiKtQRJDj1FkoKtQ2IYBXO+O80CQ3gk9qHxPBH2IbUYofN+OlHtQ6Q4oH S/Y+h1Bm/mf8Qt7VFRfeqG1B9YQe9GURel+R+EqVDtxQpAwSIIVhYMKwf30yGQ1Az/2UKOSGoJFi JgwsSfo1pUPupebYsYQmWLkRJLoxpg3sRVxllEvFE0lGAyIQYRklQKRJWSvJDRNCKKPKWQFgjwNR ODgzEn5xNmDaCdShTBCVWHAHAFMsmIsRDMGIxUwyGWi0yKLulwgaGKLAQECjUVEqOCKLHRbCOTSL RqZiqMWKEGKwSkwSIIVMKixlJjESjUyZFmZiQsGFwhQDIgtSjBjKhkCznQaILBBDWoklAxFggguK YoFliBIUFsPb26WIpzhGQUYQAZFH7BPsF9SfgJ2H4ifMW4HQhmpB8/wkIJSyp+xgg3Uj7X2id6G1 T70riFPrwE5HMmxPzcNpiIfc+mFGvXjEYisRiKMFSIJIhGfzgsEVrAqVsBqtSCKyCIP1Q2n+kM88 4CvU6gkQhEELonuQm9wTt5aEU2WuRDx/ndATkQ4j7oCgdhkYKp+jipHgDczyCEPtms+7NnFucmSo fhgHgJfgK8RzKWYIWgFOjN0/6Foaqk1GvEmZVfVrpTuII/oJAcjqjkETNooE0I2uSxRQe+0Ja0gN EOniUgPN9rtP6xkEkQ8ze5oEgHMEUabpFdrteJDtAPwQn8JD9MJ0fQfYfeLiKKZW3ALiFghgD+XU bFbumwbEdjoEwB7h3AvdgoesxHJsdfYgHQHqD1diFIlUkoNglrPYQfIspWro+PBLI8uyRJAi++1S wnT0khPzG+BhUjC+B0VxMwIQEUiBgTF1feYJZh5T1+iPdI1IpFHrhl6Q8ZPY4ydjmEEsdgdbsE2C bQf0yChYm7oOhXMJ3Pb0HyO3n9BBD9XghmUue42C3YyDI2tCSkfcZRIxCBgAl3CJ8j6K0CeKlvzE 1wbwP45DLiHtj6KdfjPzg9U9aNEbfjh2wkeQN46wvfUB4J4K2vfgD3rdLIvflS6D5WbiojJzhy9i OIn1a/UlJJpk1t0EcBK1jD1hfMJnRvnKZyI+hsR0fpwVxDxEsj7ST5nOyqNqPWhCnvR5+UJ8/lD5 fQfUpyL0iWWkOU8xMynrR7+8A8+RN7Z4JYg9qT3TEmsTj2SJ2RkkVD0Oibhe4H/PQpCAieSh27yy B2iQAUdoh0KFhNQOInW9ghxHQr0MOQ1HFOrzPT12c8Zf8pBSqRC0jjIFfhtBoSBbtHBERYKmQIqC MaEKFTkU8QIhiapn5fTz0FBNsYOilvikwwzMxoMhin2DO8ISBkjoQkDDua3sdBcOClgAY0Yu0JJ2 7djyZEmyMod+qNGz3J5p5IfeTyhpnJMkMtpoKDhTrYOsKGy2aVsxWlIFlIBgRbbFOBHdvk4SoTVm R5FaF8BTgr3BmHe53bv2hQlw/yBW2oFUXgpgiObcPMJ618kDMqK6JwQN4LrNgmhRpD6oceqOhsnN +U9yHpf2NfInpIZEn2h7OjyhiL6DZt7PLkV9ADtD0F60uj8RIdbR0o+6J7vZD6B7BxHSHxm+sk+0 NkdoeOcoIhDESvJHqy60ODoQ6RdKNIPMjpDzQ7lPn1fyPiwh8S9yP5v7/xkwT0BWegBoR4idCd/K HkciBvE9g+o3qgmwYJ/4I/3oxERFj+afqwIZAif1jK6k0fzbmoGiJHtPein/8XckU4UJAX5HSxA=