$txnid, 'workflow' => N)) to ensure * hooks are called properly. See payment_set_status() for more information. */ /** * e-Commerce token type used to define tokens. * @see store_token_list(), store_token_values(). */ define('STORE_TOKEN_TYPE', 'store transaction'); /** * @name e-Commerce Mail Types * @{ * e-Commerce Mail types used for ec_mail function. * @see store_admin_settings_form(), store_send_invoice_email(), store_mail_reset(), store_token_list() */ define('ECMAIL_TYPE_CUSTOMER_INVOICE', 'store customer invoice'); define('ECMAIL_TYPE_PROCESSING_ERROR', 'store processing error'); define('ECMAIL_TYPE_CANCEL_TXN', 'store cancel transaction'); define('ECMAIL_TYPE_ASK_CUSTOMER', 'store ask customer'); /** * @} End of "e-Commerce Mail Types". */ /** * @name e-Commerce Mail Callbacks * @{ * e-Commerce Mail callbacks used in connection with e-Commerce Mail Types. * @see store_admin_settings_form(), store_mail_reset() */ define('MAILVAR_CUSTOMER_INVOICE', 'ec_mail_customer_invoice'); define('MAILVAR_PROCESSING_ERROR', 'ec_mail_processing_error'); define('MAILVAR_CANCEL_TXN', 'ec_mail_cancel_txn'); define('MAILVAR_ASK_CUSTOMER', 'ec_mail_ask_customer'); /** * @} End of "e-Commerce Mail Callbacks". */ /** * @name Transaction allocation statuses * @{ * These states are to determine if the transaction has been part paid/fully * paid or not paid. */ define('EC_ALLOC_PENDING', 0); define('EC_ALLOC_PART', 1); define('EC_ALLOC_COMPLETED', 2); /** * @} End of "Transaction allocation statuses" */ /** * @name Workflow statuses * @{ * These states indicate the state of a transaction and provide a generic * workflow. */ define('EC_WORKFLOW_RECEIVED', 1); define('EC_WORKFLOW_INVOICED', 2); define('EC_WORKFLOW_SHIPPED', 3); define('EC_WORKFLOW_WAITING_RESPONSE', 4); define('EC_WORKFLOW_CANCELED', 5); define('EC_WORKFLOW_COMPLETED', 6); define('EC_WORKFLOW_SECURITY_VIOLATION', 7); define('EC_WORKFLOW_PACKAGED', 8); /** * @} End of "Workflow statuses". */ // ------------------------------------------------------------------------- // Hooks: help, menu, form_alter, cron, links /** * Implementation of hook_help(). */ function store_help($section = 'admin/help#store') { switch ($section) { case 'admin/store/transaction/add': return '
'. t('No mail will be sent to the user.') .'
'; case 'admin/store/search': return t('Use this search form to find transaction related information.'); } } /** * Implementation of hook_menu(). */ function store_menu($may_cache) { global $user; $items = array(); $store_settings = user_access(EC_PERM_SETTINGS); $store_report = user_access(EC_PERM_REPORT); $store_manage = user_access(EC_PERM_MANAGE); // Goal is: // Transactions, Receipts // Cachable items. if ($may_cache) { $items[] = array( 'path' => 'admin/store/transaction', 'title' => t('Sales'), 'access' => $store_manage, 'description' => t('Store transactions'), 'callback' => 'drupal_goto', 'callback arguments' => array('admin/store/transaction/list'), 'type' => MENU_LOCAL_TASK, 'weight' => -5, ); $items[] = array( 'path' => 'admin/store/transaction/list', 'query' => 'txnid=>0', 'title' => t('Transactions'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'callback' => 'ec_search', 'callback arguments' => array('store_transaction'), ); $items[] = array( 'path' => 'admin/store/transaction/search', 'query' => 'txnid=>0', 'title' => t('Search'), 'callback' => 'ec_search', 'callback arguments' => array('store_transaction'), 'type' => MENU_LOCAL_TASK, 'description' => t('Search your store transaction history'), ); $items[] = array( 'path' => 'admin/store/transaction/create', 'title' => t('Add transaction'), 'callback' => 'drupal_get_form', 'callback arguments' => array('store_transaction_form'), 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); $items[] = array( 'path' => 'admin/store/transaction/edit', 'callback' => 'drupal_get_form', 'callback arguments' => array('store_transaction_form'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/delete', 'callback' => 'drupal_get_form', 'callback arguments' => array('store_transaction_confirm_delete'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/send', 'title' => t('Message preview'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/ask', 'title' => t('Ask customer'), 'callback' => 'store_transaction_ask_customer', 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/cancel', 'title' => t('Ask customer'), 'callback' => 'store_transaction_cancel', 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/print', 'title' => t('Print invoice'), 'callback' => 'store_invoice', 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/product/view', 'title' => t('Admin items ordered'), 'callback' => 'store_transaction_items', 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/store/transaction/product/edit', 'title' => t('Edit transaction items'), 'callback' => 'store_transaction_edit_items', 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/ecsettings/store', 'title' => t('Store'), 'description' => t('General settings for your e-Commerce installation. Store transaction notices. Email notifications. Behaviour of cart links.'), 'callback' => 'drupal_get_form', 'callback arguments' => 'store_ec_settings', 'access' => $store_settings, 'weight' => -1, ); $items[] = array( 'path' => 'admin/ecsettings/location', 'title' => t('Location'), 'description' => t('Location settings that may be used by modules such as shipping. Default country and units of measure.'), 'callback' => 'drupal_get_form', 'callback arguments' => 'store_location_settings_form', 'access' => $store_settings, ); } // Dynamic items if (!$may_cache) { // This provides a transaction overview for the current customer. if (arg(0) == 'user' && is_numeric(arg(1)) && ($customer = ec_customer_get_by_uid(arg(1)))) { ec_common_include('display'); $user_access = user_access(EC_PERM_MANAGE) || arg(1) == $user->uid; $items[] = array( 'path' => 'user/'. arg(1) .'/store', 'title' => t('Store'), 'callback' => 'store_user_history', 'callback arguments' => array($customer->ecid), 'access' => (user_access(EC_PERM_MANAGE) || arg(1) == $user->uid), 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'user/'. arg(1) .'/store/transactions', 'title' => t('Order history'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); } // Provide a convenient redirect link. if (arg(0) == 'store' && arg(1) == 'history') { drupal_goto('store/'. $user->uid .'/history'); } // Provide a flexible access transaction view url $items[] = array( 'path' => 'admin/store/transaction/view', 'title' => t('View transaction'), 'type' => MENU_CALLBACK, ); // This may not work yet. Trying to remove unneeded cruft. if (arg(0) == 'store' && arg(1) == 'transaction' && arg(2) == 'view') { $txnid = arg(3); if ($user->uid && ($customer = ec_customer_get_by_uid($user->uid)) && $customer->ecid) { $ecid = $customer->ecid; } $view_transaction = FALSE; if (is_numeric($txnid) && ($store_admin || (($ecid != 0) && db_result(db_query('SELECT COUNT(ecid) FROM {ec_transaction} WHERE txnid = %d AND ecid = %d', $txnid, $ecid))))) { $items[] = array( 'path' => 'store/transaction/view', 'title' => t('View transaction'), 'callback' => 'store_transaction_view', 'access' => $view_transaction, 'type' => MENU_CALLBACK, ); } } } return $items; } /** * Implementation of hook_form_alter() */ function store_form_alter($form_id, &$form) { if ($form_id == 'ec_search_form') { switch ($form['search_id']['#value']) { case 'store_transaction': include_once drupal_get_path('module', 'store') .'/store.admin.inc'; $form['advanced']['fields'] = array_merge_recursive($form['advanced']['fields'], _store_transactions_advanced_search_form($form['#query'])); break; } } } /** * Implementation of hook_link() */ function store_link($type, $node = NULL, $teaser = FALSE) { $l = array(); if ($type == 'store_transaction') { $txn =& $node; $l['txn_edit'] = array( 'href' => 'admin/store/transaction/edit/'. $txn->txnid, 'title' => t('Edit header'), 'query' => 'section=overview&'. drupal_get_destination(), 'html' => TRUE, ); } if ($type == 'ec_customer_list') { $customer =& $node; $l['transactions'] = array( 'href' => 'admin/store/transaction', 'title' => t('Transactions'), 'query' => 'ecid='. $customer->ecid, ); } if ($type == 'ec_receipt') { $i=0; $txnids = array(); $receipt =& $node; $result = db_query("SELECT etid FROM {ec_receipt_allocation} WHERE type = 'transaction' AND erid = %d GROUP BY etid", $receipt->erid); while ($etid = db_result($result, $i++)) { $txnids[] = $etid; } if (!empty($txnids)) { $l['store_transactions'] = array( 'href' => 'admin/store/transaction', 'title' => t('Transactions'), 'query' => 'txnid='. implode(',', $txnids), ); } } if ($type == 'ec_receipt_allocation') { $alloc =& $node; if ($alloc->type == 'transaction') { $l['view_invoice'] = array( 'title' => t('View Invoice'), 'href' => 'store/transaction/view/'. $alloc->etid, ); if (user_access(EC_PERM_MANAGE)) { $l['view_transaction'] = array( 'title' => t('View transaction'), 'href' => 'admin/store/transaction', 'query' => 'txnid='. $alloc->etid, ); } } } return $l; } /** * Implementation of hook_cron(). */ function store_cron() { include_once drupal_get_path('module', 'store') .'/store.inc'; return _store_cron(); } // ------------------------------------------------------------------------- // Transactionapi and related save /** * Implementation of hook_ec_transactionapi(). */ function store_ec_transactionapi(&$txn, $op, $arg3 = NULL, $arg4 = NULL) { switch ($op) { case 'load': if (empty($txn->mail)) { $txn->mail = ec_customer_get_email($txn->ecid); } $extra = array(); $extra['misc'] = array(); $result = db_query('SELECT * FROM {ec_transaction_misc} WHERE txnid = %d ORDER BY weight ASC', $txn->txnid); while ($misc = db_fetch_object($result)) { $extra['misc'][] = $misc; } // Load Addressess $extra['address'] = array(); $result = db_query('SELECT * FROM {ec_transaction_address} WHERE txnid = %d', $txn->txnid); while ($misc = db_fetch_object($result)) { $extra['address'][$misc->type] = $misc; } return $extra; break; case 'insert': case 'update': include_once drupal_get_path('module', 'store') .'/store.inc'; store_transaction_addresses_save($txn); if (is_array($txn->misc)) { foreach ($txn->misc as $misc) { $misc->txnid = $txn->txnid; $misc->vid = $misc->vid ? $misc->vid : 0; store_transaction_save_misc($misc); } } break; case 'delete': db_query('DELETE FROM {ec_transaction_address} WHERE txnid = %d', $txn->txnid); db_query('DELETE FROM {ec_transaction_misc} WHERE txnid = %d', $txn->txnid); break; } } /** * Save a transaction to the database. * * @param $txn * Object, Transaction object to save. */ function store_transaction_save($txn) { // TODO: Remove this before final release if (is_array($txn)) { drupal_set_message('You have used a call to store_transaction_save that has passed the transaction as an array. This is deprecated. store_transaction save is converting to object but please look into converting your module to avoid this message.'); $txn = (object) $txn; } /* * Populate all the missing values with values from the existing * transaction. This also ensures that the current transaction is in the * cache so we can use it later. */ if (isset($txn->txnid) && ($existing = store_transaction_load($txn->txnid))) { foreach ($existing as $key => $value) { if (!isset($txn->$key)) { $txn->$key = $value; } } } /* * Call any pre save hooks */ foreach (module_implements('ec_transaction_pre') as $module) { $f = $module .'_ec_transaction_pre'; if (function_exists($f)) { $f($txn, 'save'); } } // If missing set changed time. if (empty($txn->changed)) { $txn->changed = time(); } $transaction_fields = store_transaction_fields(); if (!empty($txn->txnid)) { foreach ($transaction_fields as $key) { if (isset($txn->$key) && !empty($txn->$key)) { $q[] = db_escape_string($key) ." = '%s'"; $v[] = $txn->$key; } } if (is_array($q) && is_array($v)) { $v[] = $txn->txnid; db_query('UPDATE {ec_transaction} SET '. implode(', ', $q) .' WHERE txnid = %d', $v); } store_invoke_ec_transactionapi($txn, 'update', $paid); } else { // INSERT // If missing set created time to current time. if (empty($txn->created)) { $txn->created = time(); } // If missing set workflow to default. if (empty($txn->workflow)) { $txn->workflow = 1; } $txn->txnid = db_next_id('{ec_transaction}_txnid'); foreach ($transaction_fields as $key) { if (isset($txn->$key)) { $k[] = db_escape_string($key); $v[] = $txn->$key; $s[] = "'%s'"; } } if (is_array($k) && is_array($v) && is_array($s)) { db_query('INSERT INTO {ec_transaction} ('. implode(', ', $k) .') VALUES('. implode(', ', $s) .')', $v); } store_invoke_ec_transactionapi($txn, 'insert'); } if (strstr($_GET['q'], 'transaction/create')) { drupal_set_message(t('the transaction has been created')); } /* * Call any post save hooks */ foreach (module_implements('ec_transaction_post') as $module) { $f = $module .'_ec_transaction_post'; if (function_exists($f)) { $f($txn, 'save'); } } // Reset the cache so the next load will take this transaction from the // database and not the cache. store_transaction_cache($txn->txnid, NULL, TRUE); return $txn->txnid; } /** * Save Miscellaneous transactions */ function store_transaction_save_misc($misc) { static $misc_cache = array(); $misc_fields = store_transaction_misc_fields(); if (empty($misc_cache[$misc->txnid])) { $result = db_query('SELECT type, vid FROM {ec_transaction_misc} WHERE txnid = %d', $misc->txnid); while ($row = db_fetch_object($result)) { $misc_cache[$misc->txnid][] = $row->type .'|'. $row->vid; } } if (is_array($misc_cache[$misc->txnid]) && in_array("{$misc->type}|{$misc->vid}", $misc_cache[$misc->txnid])) { // Update misc foreach ($misc_fields as $key) { if (isset($misc->$key)) { $q[] = db_escape_string($key) ." = '%s'"; $v[] = $misc->$key; } } $v[] = $misc->txnid; $v[] = $misc->type; $v[] = $misc->vid; db_query('UPDATE {ec_transaction_misc} SET '. implode(', ', $q) . " WHERE txnid = %d AND type = '%s' AND vid = %d", $v); } else { // Insert Misc foreach ($misc_fields as $key) { if (isset($misc->$key)) { $k[] = db_escape_string($key); $v[] = $misc->$key; $s[] = "'%s'"; } } db_query('INSERT INTO {ec_transaction_misc} ('. implode(', ', $k) .') VALUES('. implode(', ', $s) .')', $v); $misc_cache[$misc->txnid][] = "$misc->type|$misc->vid"; } } // ------------------------------------------------------------------------- // Allocation hooks /** * Implementation of hook_allocation_info() */ function store_allocation_info() { return array( 'transaction' => array( 'name' => t('e-Commerce transactions'), 'description' => t('Allocation interface for e-Commerce transactions.'), 'module' => 'store', ), ); } /** * Get the billing or shipping address from a * transaction * * @param &$txn * Object. The transaction object * @param $atype * String (Optional). What type of address is required: * billing (default) or shipping * @return * Array. The address object converted to an array */ function store_alloc_get_address($txn, $atype = 'billing') { if (!empty($txn->address[$atype])) { return (array)$txn->address[$atype]; } } /** * Get all customer data from a transaction * * @param &$txn * Object. The transaction object * @return * Object. The customer object */ function store_alloc_get_customer($txn) { return $txn->customer; } /** * Get the customer name from a given transaction. * If it was not previously defined into first and last * names, it must be extracted thru Regex * * @param &$txn * Object. The transaction object * @param $atype * String (Optional). What type of address is required: * billing (default) or shipping * @return * Array. fname for First Name and lname for Last Name */ function store_alloc_get_customer_names(&$txn, $atype = 'billing') { if ($address = $txn->address[$atype]) { $names = array(); if (!empty($address->firstname) or !empty($address->lastname)) { $names['fname'] = $address->firstname; $names['lname'] = $address->lastname; } elseif (!empty($address['firstname']) or !empty($address['lastname'])) { $names['fname'] = $address['firstname']; $names['lname'] = $address['lastname']; } else { $names = ec_common_split_name($address->fullname); } return !empty($names) ? $names : NULL; } } function store_alloc_get_currency($txn) { return $txn->currency; } function store_alloc_get_invoice_no($txn) { return $txn->txnid; } function store_alloc_get_items($txn) { if (!empty($txn->items) && is_array($txn->items)) { store_alloc_build_items($txn, 'init'); $items = array_map('store_alloc_build_items', $txn->items); return $items; } } function store_alloc_allocation($txn, $balance) { $is_shippable = FALSE; if (!empty($txn->items)) { foreach ($txn->items as $item) { if (product_is_shippable($item->vid)) { $is_shippable = TRUE; break; } } } if ($balance == 0) { $txn->allocation = EC_ALLOC_COMPLETED; if (!$is_shippable) { $txn->workflow = EC_WORKFLOW_COMPLETED; } } else { $txn->allocation = EC_ALLOC_PART; } store_transaction_save($txn); } function store_alloc_build_items($item, $op = 'process') { static $txn; if ($op == 'init') { $txn = $item; return; } return array( 'id' => $item->nid, 'name' => $item->title, 'qty' => $item->qty, 'amount' => store_adjust_misc($txn, $item), ); } /** * Implementation of hook_alloc_get_payment_data() */ function store_alloc_get_payment_data($txn) { return $txn->payment_data[$txn->payment_method]; } /** * Implmentation of hook_alloc_get_payment_form() */ function store_alloc_get_payment_form($txn) { return $txn->payment_form; } /** * Implmentation of hook_alloc_get_payment_type() */ function store_alloc_get_payment_type($txn) { return $txn->payment_method; } /** * Implmentation of hook_alloc_get_total() */ function store_alloc_get_total($txn) { return store_transaction_calc_gross($txn); } /** * Implementation of hook_alloc_load() */ function store_alloc_load($txnid) { return store_transaction_load($txnid); } /** * Implementation of hook_alloc_set_payment_data() */ function store_alloc_set_payment_data(&$txn, $payment_data) { $txn->payment_data[$txn->payment_method] = $payment_data; } // ------------------------------------------------------------------------- // Transaction hooks /** * Implementation of hook_ec_txn_workflow() */ function store_ec_txn_workflow($txn, $orig) { if ($txn->workflow == EC_WORKFLOW_COMPLETED) { store_send_invoice_email($txn); } } /** * Implementation of hook_ec_transaction_post() * * Check the transaction for changes in workflow and call * hook_ec_txn_workflow() to allow the module to update data outside of the * transaction. */ function store_ec_transaction_post($txn, $op) { if ($op == 'save') { $orig_txn = store_transaction_cache($txn->txnid); if (empty($orig_txn) || $orig_txn->allocation != $txn->allocation) { module_invoke_all('ec_txn_allocation', $txn, $orig_txn); } if (empty($orig_txn) || $orig_txn->workflow != $txn->workflow) { module_invoke_all('ec_txn_workflow', $txn, $orig_txn); } } } /** * A wrapper for theme('store_invoice') * * Because the menu callback can only handle functions with no parameters, we * use this function as a wrapper. */ function store_invoice() { $txnid = (int) arg(4); $wf = (int) $_GET['wf']; /* Adjust the workflow.. */ if (!empty($wf) && !empty($txnid)) { $txn = new StdClass; $txn->txnid = $txnid; $txn->workflow = $wf; store_transaction_save($txn); } $txn = store_transaction_load($txnid); return theme('store_invoice', $txn); } /** * Implementation of hook_invoiceapi() */ function store_invoiceapi(&$invoice, $op) { if (is_string($invoice) && $op == 'form') { return t('View Invoice'); } switch ($op) { case 'form': $output = theme('store_invoice', $invoice, FALSE, TRUE); $form['invoice_disp'] = array('#value' => $output); return $form; break; } } /** * Invoke a hook_ec_transactionapi() operation in all modules. * * @param &$txn * A transaction object. * @param $op * A string containing the name of the ec_transactionapi operation. * @param $a3, $a4 * Arguments to pass on to the hook, after the $txn and $op arguments. * @return * The returned value of the invoked hooks. */ function store_invoke_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) { $return = array(); foreach (module_implements('ec_transactionapi') as $name) { $function = $name .'_ec_transactionapi'; $result = $function($txn, $op, $a3, $a4); if (is_array($result)) { $return = array_merge($return, $result); } elseif (isset($result)) { $return[] = $result; } } return $return; } // ------------------------------------------------------------------------- // Localization functions /** * Search for the Id of a country. */ function store_get_country_id($name) { include_once drupal_get_path('module', 'store') .'/store.localization.inc'; return array_search(strtolower($name), array_map('strtolower', _store_location_countries())); } /** * Get the country name from the Id */ function store_get_country($id) { include_once drupal_get_path('module', 'store') .'/store.localization.inc'; $country = _store_location_countries(); return $country[$id]; } /** * Get a currently configured measure label. * Pass 'weight' and KG to get Kilograms * If you leave $measure empty, the saved value is used. * * @param $type * String, the measure type: weight, length, area or volume * @param $measure * String, the measure value * @return * String, the measure name */ function store_name_measure($type = 'weight', $measure = NULL) { include_once drupal_get_path('module', 'store') .'/store.localization.inc'; $all_measures = _store_location_measures(); if (empty($measure)) { switch ($type) { case 'weight': $measure = variable_get('ec_measure_weight', 'LB'); break; case 'length': $measure = variable_get('ec_measure_length', 'IN'); break; case 'area': $measure = variable_get('ec_measure_area', 'IN2'); break; case 'volume': $measure = variable_get('ec_measure_volume', 'IN3'); break; default: watchdog('store', 'An unexpected measure type '. $type . ' was encountered in store_name_measure.', WATCHDOG_ERROR); return; } } return $all_measures[$type][$measure]; } // ------------------------------------------------------------------------- // TODO - classify these /** * Sets the workflow state of the given transation to the state given. * This function accepts both the textual state and the numeric state. * If a textual state (valid values are in store_transaction_workflow()) is * given it will be converted to the appropriate value. * * @param $txnid * Transaction ID. * @param $state * Either a textual or numeric state value. */ function transaction_set_workflow($txnid, $state) { if (empty($state) || empty($txnid) || !is_numeric($txnid) || empty($txnid)) { return; } if (!is_numeric($state)) { $state = transaction_get_workflow_id($state); } store_transaction_save((object)array('txnid' => $txnid, 'workflow' => $state)); } /** * Add all the invisible miscellaneous transaction to an item */ function store_adjust_misc(&$txn, $product) { $price = $product->price; foreach ((array)$txn->misc as $key => $misc) { if (!empty($misc->vid) and $misc->vid == $product->vid && $misc->invisible) { $price+= $misc->price; $txn->misc[$key]->seen = TRUE; } } return $price; } /** * Recalc product prices and associated charges on the transaction. * * While building a transaction, events may occur which will influence the * price of a product. An example of this is when a user logs into the * system becomming a registered user compared to being an anonymous user. * If they a member then the system may give discounts to the users on * certain roles. Also if a customer is purchasing a membership then the * other products which are on the transaction may also be eligible for * discount. * Also in cases when the customer has made a purchase via the cart and has * not paid for the transaction, when the transaction is later paid for the * system should recalc the transaction to allow for any price adjustments, * or removal of specials that no longer apply. * * @param $txn * The transaction which needs to be recalculated. * * @return NULL */ function store_recalc_transaction(&$txn) { $item_idx = array(); if (!empty($txn->items)) { foreach ($txn->items as $nid => $item) { // reset price to original value. if ($node = node_load($item->nid)) { $txn->items[$nid]->price = $node->price; } $txn->items[$nid]->price = product_price_adjust($txn->items[$nid], 'cart', $txn); /* Delete any current specials */ if (!empty($txn->misc)) { foreach ($txn->misc as $key => $misc) { if ($misc->vid == $txn->items[$nid]->vid && $misc->description == 'special' && $misc->invisible) { unset($txn->misc[$key]); } } } // process any specials and add them to misc foreach (product_specials_get($txn->items[$nid], 'cart', FALSE, $txn) as $type => $special) { $misc = new StdClass; $misc->type = $type; $misc->vid = $txn->items[$nid]->vid; $misc->description = 'special'; $misc->invisible = 1; $misc->price = $special; $misc->qty = product_has_quantity($txn->items[$nid]) ? $txn->items[$nid]->qty : 1; $txn->misc[] = $misc; } $item_idx[$item->vid] = $nid; } } /* Remove any misc items to products that have been deleted */ if (!empty($txn->misc)) { foreach ($txn->misc as $key => $misc) { if (!empty($misc->vid) && $misc->description == 'special' && $misc->invisible && !isset($item_idx[$misc->vid])) { unset($txn->misc[$key]); } } } } /** * store_transaction_cache() * * The purpose is to cache transactions in memory so that when loading the * same transaction more than once in the same session it will load from * memory instead of getting it from the database again. * * @params * $txnid - the id for the transaction that is going to be stored or * retrieved * * $txn - The transaction that needs to be saved. * * $reset - Wipe the transaction from memory. * * @return return the Transaction object from the $txnid */ function store_transaction_cache($txnid, $txn = NULL, $reset = FALSE) { static $cache; if (!empty($reset)) { unset($cache[$txnid]); return; } if (!empty($txn)) { $cache[$txnid] = drupal_clone($txn); } return isset($cache[$txnid]) ? drupal_clone($cache[$txnid]) : FALSE; } /** * Calculate the total of the invoice */ function store_transaction_calc_gross($txn) { $total = 0; foreach ((array)$txn->items as $item) { $total+= product_has_quantity($item) ? $item->price * $item->qty : $item-> price; } foreach ((array)$txn->misc as $key => $misc) { if (!empty($misc->callback) and function_exists($misc->callback)) { $f = $misc->callback; $amount = $f($txn, $misc, $total); } elseif (!empty($misc->qty)) { $amount = ($misc->price * $misc->qty); } else { $amount = $misc->price; } if (empty($misc->already_added)) { $total+= $amount; } } return $total; } function store_transaction_cancel() { return drupal_get_form('store_transaction_mail_form', arg(4), t('Cancel transaction notification'), MAILVAR_CANCEL_TXN, TRUE, TRUE); } /** * Load a transaction. * * @param $txnid * Transaction id of the transaction to be loaded. * * @todo: Add some form of transaction cache so the we are not always hitting * the database to load transactions. */ function store_transaction_load($txnid) { if ($txn = store_transaction_cache($txnid)) { return $txn; } if (!($txn = db_fetch_object(db_query('SELECT * FROM {ec_transaction} AS st WHERE st.txnid = %d', $txnid)))) { return FALSE; } if ($extra = store_invoke_ec_transactionapi($txn, 'load')) { foreach ($extra as $key => $value) { $txn->$key = $value; } } store_transaction_cache($txn->txnid, $txn); return $txn; } // ------------------------------------------------------------------------- // Utility /** * Look for a miscellaneous items on a transaction. */ function store_search_misc($needle, $txn) { if (!empty($txn->misc)) { foreach ($txn->misc as $key => $misc) { foreach ($needle as $k => $value) { if ($misc->$k != $value) { continue 2; } } return $key; } } return FALSE; } /** * Return the fields of the transaction table. */ function store_transaction_fields() { return array('txnid', 'uid', 'ecid', 'type', 'mail', 'shipping_cost', 'payment_method', 'payment_status', 'allocation', 'workflow', 'currency', 'gross', 'created', 'changed', 'duedate', 'token'); } /** * Return the fields of the transaction_misc table. */ function store_transaction_misc_fields() { return array('txnid', 'type', 'vid', 'description', 'invisible', 'price', 'qty', 'weight'); } /** * Return the fields of the transaction_product table. */ function store_transaction_product_fields() { return array('txnid', 'nid', 'vid', 'title', 'price', 'qty', 'data'); } /** * Return the fields of the transaction_address table. */ function store_transaction_address_fields() { return array('fullname', 'firstname', 'lastname', 'street1', 'street2', 'zip', 'city', 'state', 'country'); } /** * Returns an array of allocation status IDs and corresponding descriptions. */ function store_transaction_allocation() { return array( EC_ALLOC_PENDING => t('Allocation pending'), EC_ALLOC_PART => t('Part allocation'), EC_ALLOC_COMPLETED => t('Full allocation'), ); } /** * Get the descriptive name of an allocation ID. */ function transaction_get_allocation($id) { $allocation = store_transaction_allocation(); return $allocation[$id]; } /** * Returns an array of workflow status IDs and corresponding descriptions. */ function store_transaction_workflow() { $transaction_workflow = array( EC_WORKFLOW_RECEIVED => t('received'), EC_WORKFLOW_INVOICED => t('invoiced'), EC_WORKFLOW_SHIPPED => t('shipped'), EC_WORKFLOW_PACKAGED => t('packaged'), EC_WORKFLOW_WAITING_RESPONSE => t('awaiting customer response'), EC_WORKFLOW_CANCELED => t('canceled'), EC_WORKFLOW_COMPLETED => t('completed'), EC_WORKFLOW_SECURITY_VIOLATION => t('security violation'), ); return $transaction_workflow; } /** * Get the descriptive name of an workflow ID. */ function transaction_get_workflow($id) { $workflow = store_transaction_workflow(); return $workflow[$id]; } /** * Legacy, get a workflow ID by descriptive name. */ function transaction_get_workflow_id($name) { return array_search(t($name), store_transaction_workflow()); } /** * Legacy; determine if a passed workflow id is valid. */ function transaction_valid_workflow($id) { return in_array($id, array_keys(store_transaction_workflow())); } /** * Get a quick html header for printable pages. */ function store_quicklist_header() { global $base_url; $base_url .= '/'; return '