Index: branches/5.2.x/units/pricing/pricing_event_handler.php =================================================================== --- branches/5.2.x/units/pricing/pricing_event_handler.php (revision 16692) +++ branches/5.2.x/units/pricing/pricing_event_handler.php (revision 16693) @@ -1,525 +1,524 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); // include globals.php from current folder kUtil::includeOnce(MODULES_PATH . '/in-commerce/units/pricing/globals.php'); class PricingEventHandler extends kDBEventHandler { /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnMoreBrackets' => Array ('subitem' => 'add|edit'), 'OnInfinity' => Array ('subitem' => 'add|edit'), 'OnArrange' => Array ('subitem' => 'add|edit'), 'OnDeleteBrackets' => Array ('subitem' => 'add|edit'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Define alternative event processing method names * * @return void * @see kEventHandler::$eventMethods * @access protected */ protected function mapEvents() { parent::mapEvents(); // ensure auto-adding of approve/decline and so on events $brackets_events = Array( 'OnMoreBrackets' => 'PricingBracketsAction', 'OnArrange' => 'PricingBracketsAction', 'OnInfinity' => 'PricingBracketsAction', 'OnDeleteBrackets' => 'PricingBracketsAction', ); $this->eventMethods = array_merge($this->eventMethods, $brackets_events); } function PricingBracketsAction($event) { $event->redirect=false; $temp = $this->Application->GetVar($event->getPrefixSpecial(true)); // $object = $event->getObject(); // $formatter = $this->Application->recallObject('kFormatter'); // $temp = $formatter->TypeCastArray($temp, $object); //uasort($temp, 'pr_bracket_comp'); $bracket = $this->Application->recallObject($event->getPrefixSpecial()); foreach($temp as $id => $record) { if( $record['MaxQty'] == '∞' || $record['MaxQty'] == '∞') { $temp[$id]['MaxQty'] = -1; } } $group_id = $this->Application->getVar('group_id'); if($group_id>0){ $where_group=' GroupId = '.$group_id.' '; } else { $where_group= ' TRUE '; } switch ($event->Name) { case 'OnMoreBrackets': $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$bracket->IDField.') FROM '.$bracket->TableName); if($new_id > 0) $new_id = 0; do { $new_id--; } while ($this->check_array($this->Application->GetVar($event->getPrefixSpecial(true)), 'PriceId', $new_id)); $last_max_qty = $this->Conn->GetOne('SELECT MAX(MaxQty) FROM '.$bracket->TableName.' WHERE '.$where_group); $min_qty = $this->Conn->GetOne('SELECT MIN(MaxQty) FROM '.$bracket->TableName.' WHERE '.$where_group); if ($min_qty==-1) $last_max_qty = -1; if (!$last_max_qty) $last_max_qty=1; for($i = $new_id; $i > $new_id - 5; $i--) { $temp[$i]['PriceId'] = $i; $temp[$i]['MinQty'] = ($i == $new_id-4 && $last_max_qty != -1) ? $last_max_qty : ''; $temp[$i]['MaxQty'] = ($i == $new_id-4 && $last_max_qty != -1) ? -1 : ''; $temp[$i]['Price'] = ''; $temp[$i]['Cost'] = ''; $temp[$i]['Points'] = ''; $temp[$i]['Negotiated'] = '0'; $temp[$i]['IsPrimary'] = '0'; $temp[$i]['GroupId'] = $group_id; } $this->Application->SetVar($event->getPrefixSpecial(true), $temp); $event->CallSubEvent('OnPreSaveBrackets'); break; case 'OnArrange': $temp=$this->OnArrangeBrackets($event, $temp, $bracket); $this->Application->SetVar($event->getPrefixSpecial(true), $temp); $event->CallSubEvent('OnPreSaveBrackets'); break; case 'OnInfinity': $temp=$this->OnArrangeBrackets($event, $temp, $bracket); $this->Application->SetVar($event->getPrefixSpecial(true), $temp); $event->CallSubEvent('OnPreSaveBrackets'); $infinite_exists = $this->Conn->GetOne('SELECT count(*) FROM '.$bracket->TableName.' WHERE MaxQty=-1 '.' AND '.$where_group); if($infinite_exists==0){ reset($temp); $last_bracket=end($temp); $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$bracket->IDField.') FROM '.$bracket->TableName); $brackets_exist = (int)$this->Conn->GetOne('SELECT COUNT(*) FROM '.$bracket->TableName.' WHERE '.$where_group); if($new_id > 0) $new_id = 0; do { $new_id--; } while ($this->check_array($this->Application->GetVar($event->getPrefixSpecial(true)), 'PriceId', $new_id)); $infinite_bracket['PriceId'] = $new_id; $infinite_bracket['MinQty'] = ($brackets_exist>0)?$last_bracket['MaxQty']:1; $infinite_bracket['MaxQty'] = '-1'; $infinite_bracket['Price'] = ''; $infinite_bracket['Cost'] = ''; $infinite_bracket['Points'] = ''; $infinite_bracket['Negotiated'] = '0'; $infinite_bracket['IsPrimary'] = '0'; $infinite_bracket['GroupId'] = $group_id; $temp[$new_id]=$infinite_bracket; reset($temp); } $this->Application->SetVar($event->getPrefixSpecial(true), $temp); $event->CallSubEvent('OnPreSaveBrackets'); break; case 'OnDeleteBrackets': if ($group_id) { $temp = ''; // delete all pricings from "pr_tang" var $sql = 'DELETE FROM ' . $bracket->TableName . ' WHERE ProductId = ' . $this->Application->GetVar('p_id') . ' AND GroupId = ' . $group_id; $this->Conn->Query($sql); } break; default: } $this->Application->SetVar($event->getPrefixSpecial(true), $temp); // store pr_tang var } function OnPreSaveBrackets(kEvent $event) { if( $this->Application->GetVar('pr_tang') ) { /** @var kDBItem $object */ $object = $event->getObject(); $product_id = $this->Application->GetVar('p_id'); $group_id = $this->Application->getVar('group_id'); $sql = 'SELECT PriceId FROM ' . $object->TableName . ' WHERE ProductId = ' . $product_id . ' ' . ($group_id? 'AND GroupId = ' . $group_id : ''); $stored_ids = $this->Conn->GetCol($sql); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); // get pr_tang var uasort($items_info, 'pr_bracket_comp'); foreach ($items_info as $item_id => $field_values) { if (in_array($item_id, $stored_ids)) { //if it's already exist $object->Load($item_id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if (!$object->Validate()) { unset($stored_ids[array_search($item_id, $stored_ids)]); $event->redirect = false; continue; } if( $object->Update($item_id) ) { $event->status=kEvent::erSUCCESS; } else { $event->status=kEvent::erFAIL; $event->redirect=false; break; } unset($stored_ids[array_search($item_id, $stored_ids)]); } else { $object->Clear(0); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $object->SetDBField('ProductId', $product_id); if( $object->Create() ) { $event->status=kEvent::erSUCCESS; } } } // delete foreach ($stored_ids as $stored_id) { $this->Conn->Query('DELETE FROM ' . $object->TableName . ' WHERE PriceId = ' . $stored_id); } } } /** * Apply custom processing to item * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $event, $type) { /** @var kDBItem $bracket */ $bracket = $event->getObject(); switch ($type) { case 'before': $bracket->SetDBField('ProductId', $this->Application->GetVar('p_id')); if ( $bracket->GetDBField('MaxQty') == '∞' || $bracket->GetDBField('MaxQty') == '∞' ) { $bracket->SetDBField('MaxQty', -1); } break; } } function OnArrangeBrackets($event, &$temp, &$bracket) { $temp_orig = $temp; reset($temp); if (is_array($temp)) { // array to store max values (2nd column) $end_values = Array(); // get minimal value of Min $first_elem=current($temp); $start = $first_elem['MinQty']; if (!$start){ $start = 1; } foreach($temp as $id => $record) { /* This 3-ifs logic fixes collision with invalid input values having 1 pricing record. The logic is: 1) If we got Max less than Min, we set Min to 1 that gives us integrity. 2) If we got equal values for Min and Max, we set range 1..Max like in previous. But if Min was 1 and Max was 1 we set full range 1..infinity 3) If we got Max = 0 we just set it tom infinity because we can't guess what user meant */ if (sizeof($temp) == 1 && $record['MinQty'] > ($record['MaxQty'] == -1 ? $record['MinQty']+1 : $record['MaxQty']) ){ $record['MinQty'] = 1; $temp[$id]['MinQty'] = 1; $start = 1; } if (sizeof($temp) == 1 && $record['MinQty'] == $record['MaxQty']){ if ($record['MaxQty'] == 1){ $record['MaxQty'] = -1; $temp[$id]['MaxQty'] = -1; } else { $record['MinQty'] = 1; $temp[$id]['MinQty'] = 1; } } if (sizeof($temp) == 1 && $record['MaxQty'] == 0){ $record['MaxQty'] = -1; $temp[$id]['MaxQty'] = -1; } if( // MAX is less than start ($record['MaxQty'] <= $start && $record['MaxQty'] != -1) || // Max is empty !$record['MaxQty'] || // Max already defined in $end_values (array_search($record['MaxQty'], $end_values) !== false) ) { // then delete from brackets list unset($temp[$id]); } else { // this is when ok - add to end_values list $end_values[] = $record['MaxQty']; } } // sort brackets by 2nd column (Max values) uasort($temp, 'pr_bracket_comp'); reset($temp); - $first_item=each($temp); - $first_item_key=$first_item['key']; + $first_item_key = key($temp); $group_id = $this->Application->getVar('group_id'); $default_group = $this->Application->ConfigValue('User_LoggedInGroup'); if($group_id>0){ $where_group=' AND GroupId = '.$group_id.' '; } $ids = $this->Conn->GetCol('SELECT PriceId FROM '.$bracket->TableName.' WHERE ProductId='.$this->Application->GetVar('p_id').' '.$where_group); if(is_array($ids)) { usort($ids, 'pr_bracket_id_sort'); } $min_id = min( min($ids) - 1, -1 ); foreach($temp as $key => $record) { $temp[$key]['MinQty']=$start; $temp[$key]['IsPrimary']=0; $temp[$key]['GroupId']=$group_id; $start=$temp[$key]['MaxQty']; } if ($temp[$first_item_key]['GroupId'] == $default_group) { $temp[$first_item_key]['IsPrimary']=1; } } return $temp; } /** * Set's price as primary for product * * @param kEvent $event */ function OnSetPrimary($event) { $object = $event->getObject( Array('skip_autoload' => true) ); $this->StoreSelectedIDs($event); $ids=$this->getSelectedIDs($event); if($ids) { $id = array_shift($ids); $table_info = $object->getLinkedInfo(); $this->Conn->Query('UPDATE '.$object->TableName.' SET IsPrimary = 0 WHERE '.$table_info['ForeignKey'].' = '.$table_info['ParentId']); $this->Conn->Query('UPDATE '.$object->TableName.' SET IsPrimary = 1 WHERE ('.$table_info['ForeignKey'].' = '.$table_info['ParentId'].') AND (PriceId = '.$id.')'); } $event->SetRedirectParam('opener', 's'); } /** * Resets primary mark for other prices of given product, when current pricing is primary * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); /** @var kDBItem $object */ $object = $event->getObject(); if ( $object->GetDBField('IsPrimary') == 1 ) { // make all prices non primary, when this one is $sql = 'UPDATE ' . $object->TableName . ' SET IsPrimary = 0 WHERE (ProductId = ' . $object->GetDBField('ProductId') . ') AND (' . $object->IDField . ' <> ' . $object->GetID() . ')'; $this->Conn->Query($sql); } } /** * Occurs before creating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $table_info = $object->getLinkedInfo($event->Special); $table_info['ParentId'] = ($table_info['ParentId'] ? $table_info['ParentId'] : 0); if ( $object->GetDBField('IsPrimary') == 1 ) { $sql = 'UPDATE ' . $object->TableName . ' SET IsPrimary = 0 WHERE ' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId']; $this->Conn->Query($sql); } else { $sql = 'SELECT COUNT(*) FROM ' . $object->TableName . ' WHERE ' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId']; $prices_qty = $this->Conn->GetOne($sql); if ( $prices_qty == 0 ) { $object->SetDBField('IsPrimary', 1); } } } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { /** @var kDBList $object */ $object = $event->getObject(); if ( $this->Application->isAdminUser ) { return; } if ( $this->Application->ConfigValue('Comm_PriceBracketCalculation') == 1 ) { $sql = 'SELECT PrimaryGroupId FROM ' . TABLE_PREFIX . 'Users WHERE PortalUserId = ' . $this->Application->GetVar('u_id'); $pricing_group = $this->Conn->GetOne($sql); if ( $pricing_group ) { $sql = 'SELECT COUNT(*) FROM ' . TABLE_PREFIX . 'ProductsPricing WHERE ProductId = ' . $this->Application->GetVar('p_id') . ' AND GroupId = ' . $pricing_group . ' AND Price IS NOT NULL'; $pricing_for_group_exists = $this->Conn->GetOne($sql); } if ( !$pricing_group || !$pricing_for_group_exists ) { $pricing_group = $this->Application->ConfigValue('User_LoggedInGroup'); } } else { $user_groups = $this->Application->RecallVar('UserGroups'); //$cheapest_group = $this->Conn->GetOne('SELECT GroupId FROM '.$object->TableName.' WHERE ProductId='.$this->Application->GetVar('p_id').' AND Price IS NOT NULL AND GroupId IN ('.$user_groups.') AND MinQty = 1 GROUP BY GroupId ORDER BY Price ASC'); $sql = 'SELECT PriceId, Price, GroupId FROM ' . $object->TableName . ' WHERE ProductId = ' . $this->Application->GetVar('p_id') . ' AND Price IS NOT NULL AND GroupId IN (' . $user_groups . ') ORDER BY GroupId ASC, MinQty ASC'; $effective_brackets = $this->Conn->Query($sql, 'PriceId'); $group_prices = array (); $min_price = -1; $cheapest_group = 0; foreach ($effective_brackets as $bracket) { if ( !isset($group_prices[$bracket['GroupId']]) ) { $group_prices[$bracket['GroupId']] = $bracket['Price']; if ( $bracket['Price'] < $min_price || $min_price == -1 ) { $min_price = $bracket['Price']; $cheapest_group = $bracket['GroupId']; } } } if ( !$cheapest_group ) { $cheapest_group = $this->Application->ConfigValue('User_LoggedInGroup'); } $pricing_group = $cheapest_group; } $object->addFilter('price_user_group', $object->TableName . '.GroupId=' . $pricing_group); } } Index: branches/5.2.x/units/shipping_quote_engines/usps.php =================================================================== --- branches/5.2.x/units/shipping_quote_engines/usps.php (revision 16692) +++ branches/5.2.x/units/shipping_quote_engines/usps.php (revision 16693) @@ -1,1341 +1,1341 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); define('MODULE_SHIPPING_USPS_TEXT_TITLE', 'United States Postal Service'); define('MODULE_SHIPPING_USPS_TEXT_DESCRIPTION', 'You will need to have registered an account with USPS. Click <a target="_blank" href="https://secure.shippingapis.com/registration/"><strong>HERE</strong></a> for registration details. USPS expects you to use pounds as weight measure for your products.'); define('MODULE_SHIPPING_USPS_TEXT_ERROR', 'An error occured with the USPS shipping calculations.<br>If you prefer to use USPS as your shipping method, please contact the store owner.'); define('MODULE_SHIPPING_USPS_TEXT_DAY', 'Day'); define('MODULE_SHIPPING_USPS_TEXT_DAYS', 'Days'); define('MODULE_SHIPPING_USPS_TEXT_WEEKS', 'Weeks'); define('MODULE_SHIPPING_USPS_STATUS', 'True'); // Do you want to offer USPS shipping? define('MODULE_SHIPPING_USPS_SERVER', 'production'); // An account at USPS is needed to use the Production server // production othervise value may be 'test' define('MODULE_SHIPPING_USPS_HANDLING', '0'); // Handling fee for this shipping method define('MODULE_SHIPPING_USPS_TAX_CLASS', '0'); // Use the following tax class on the shipping fee define('MODULE_SHIPPING_USPS_ZONE', '0'); // If a zone is selected, only enable this shipping method for that zone. define('MODULE_SHIPPING_USPS_SORT_ORDER', '0'); // Sort order of display. define('MODULE_SHIPPING_USPS_TYPES', 'PRIORITY, PARCEL'); // EXPRESS, FIRST CLASS, BMP, MEDIA 'Select the domestic services to be offered: define('MODULE_SHIPPING_USPS_TYPES_INTL', 'EXPRESS MAIL INTERNATIONAL (EMS), EXPRESS MAIL INT, EXPRESS MAIL INT FLAT RATE ENV, PRIORITY MAIL INT, PRIORITY MAIL INT FLAT RATE ENV, PRIORITY MAILINT FLAT RATE BOX, FIRST-CLASS MAIL INT');// 'GLOBAL EXPRESS, GLOBAL EXPRESS NON-DOC RECT, GLOBAL EXPRESS NON-DOC NON-RECT, Select the international services to be offered: define('MODULE_SHIPPING_USPS_OPTIONS', 'Display weight, Display transit time'); // //configuration values for insurance define('MODULE_SHIPPING_USPS_INS1', '1.65');// 'US/Canada insurance for totals $.01-$50.00 define('MODULE_SHIPPING_USPS_INS2', '2.05');// 'US/Canada insurance for totals $50.01-$100 define('MODULE_SHIPPING_USPS_INS3', '2.45');// 'US/Canada insurance for totals $100.01-$200 define('MODULE_SHIPPING_USPS_INS4', '4.60');// 'US/Canada insurance for totals $200.01-$300 define('MODULE_SHIPPING_USPS_INS5', '.90');// 'US/Canada insurance for every $100 over $300 (add) define('MODULE_SHIPPING_USPS_INS6', '2.40');// 'International insurance for totals $.01-$50.00 define('MODULE_SHIPPING_USPS_INS7', '3.30');// 'International insurance for totals $50.01-$100 define('MODULE_SHIPPING_USPS_INS8', '4.20');// 'International insurance for totals $100.01-$200 define('MODULE_SHIPPING_USPS_INS9', '5.10');// 'International insurance for totals $200.01-$300 define('MODULE_SHIPPING_USPS_INS10', '.90');// 'International insurance for every $100 over $300 (add) define('MODULE_SHIPPING_USPS_INSURE', 'True');// 'Insure packages shipped by USPS? define('MODULE_SHIPPING_USPS_INSURE_TAX', 'True');// 'Insure tax on packages shipped by USPS? class USPS extends ShippingQuoteEngine { var $countries, $pounds, $ounces, $insurance_cost = 0, $shipping_origin_country, $store_first_name, $store_last_name, $company_name, $store_name, $store_address1, $store_address2, $store_city, $store_state, $store_zip5, $store_zip4, $store_phone, $usps_userid; var $order = Array(); var $types = Array(); var $intl_types = Array(); /** * Path to a request log file * * @var string */ var $logFilePath = ''; /** * Creates USPS processing class * */ public function __construct() { parent::__construct(); $this->logFilePath = (defined('RESTRICTED') ? RESTRICTED : WRITEABLE . '/user_files') . '/usps.log'; // EXPRESS, FIRST CLASS, PRIORITY, PARCEL, BMP, MEDIA $this->types = Array( 'EXPRESS' => 'Express Mail', 'FIRST CLASS' => 'First Class Mail', 'PRIORITY' => 'Priority Mail', 'PARCEL' => 'Parcel Post', 'BPM' => 'Bound Printed Matter', 'MEDIA' => 'Media Mail' ); $this->intl_types = Array( // 'GLOBAL EXPRESS' => 'Global Express Guaranteed', // 'GLOBAL EXPRESS NON-DOC RECT' => 'Global Express Guaranteed Non-Document Rectangular', // 'GLOBAL EXPRESS NON-DOC NON-RECT' => 'Global Express Guaranteed Non-Document Non-Rectangular', 'EXPRESS MAIL INT' => 'Express Mail International (EMS)', 'EXPRESS MAIL INT FLAT RATE ENV' => 'Express Mail International (EMS) Flat Rate Envelope', 'PRIORITY MAIL INT' => 'Priority Mail International', 'PRIORITY MAIL INT FLAT RATE ENV' => 'Priority Mail International Flat Rate Envelope', 'PRIORITY MAIL INT FLAT RATE BOX' => 'Priority Mail International Flat Rate Box', 'FIRST-CLASS MAIL INT' => 'First-Class Mail International' ); // get 2-symbol country code $country = $this->Application->ConfigValue('Comm_Shipping_Country'); if ($country != '') { $this->shipping_origin_country = $this->GetUSPSCountry($country, ''); } $contact_name = trim($this->_prepare_xml_param($this->Application->ConfigValue('Comm_Contacts_Name'))); $split_pos = strpos($contact_name, ' '); if ($split_pos === false) { $this->store_first_name = $contact_name; $this->store_last_name = ''; } else { $this->store_first_name = substr($contact_name, 0, $split_pos); $this->store_last_name = trim(substr($contact_name, $split_pos)); } $this->company_name = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_CompanyName')); $this->store_name = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_StoreName')); $this->store_address1 = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_AddressLine1')); $this->store_address2 = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_AddressLine2')); if ($this->store_address2 == '') { $this->store_address2 = $this->store_address1; $this->store_address1 = ''; } $this->store_city = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_City')); $this->store_state = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_State')); $zip = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_ZIP')); $this->store_zip5 = substr($zip, 0, 5); $this->store_zip4 = trim(substr($zip, 6), '-'); $this->store_phone = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Contacts_Phone')); // get username and password fron config. $a_params = $this->LoadParams(); $this->usps_userid = $a_params['AccountLogin']; // Note by Erik: DO NOT CHANGE THIS ARRAY. It's values are sent to USPS service and any changes may impact class main functionality. $this->countries = array( 'AF' => 'Afghanistan', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AD' => 'Andorra', 'AO' => 'Angola', 'AI' => 'Anguilla', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia', 'AW' => 'Aruba', 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan', 'BS' => 'Bahamas', 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados', 'BY' => 'Belarus', 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BM' => 'Bermuda', 'BT' => 'Bhutan', 'BO' => 'Bolivia', 'BA' => 'Bosnia-Herzegovina', 'BW' => 'Botswana', 'BR' => 'Brazil', 'VG' => 'British Virgin Islands', 'BN' => 'Brunei Darussalam', 'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'MM' => 'Burma', 'BI' => 'Burundi', 'KH' => 'Cambodia', 'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde', 'KY' => 'Cayman Islands', 'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China', 'CX' => 'Christmas Island (Australia)', 'CC' => 'Cocos Island (Australia)', 'CO' => 'Colombia', 'KM' => 'Comoros', 'CG' => 'Congo (Brazzaville),Republic of the', 'ZR' => 'Congo, Democratic Republic of the', 'CK' => 'Cook Islands (New Zealand)', 'CR' => 'Costa Rica', 'CI' => 'Cote d\'Ivoire (Ivory Coast)', 'HR' => 'Croatia', 'CU' => 'Cuba', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DK' => 'Denmark', 'DJ' => 'Djibouti', 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'TP' => 'East Timor (Indonesia)', 'EC' => 'Ecuador', 'EG' => 'Egypt', 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea', 'ER' => 'Eritrea', 'EE' => 'Estonia', 'ET' => 'Ethiopia', 'FK' => 'Falkland Islands', 'FO' => 'Faroe Islands', 'FJ' => 'Fiji', 'FI' => 'Finland', 'FR' => 'France', 'GF' => 'French Guiana', 'PF' => 'French Polynesia', 'GA' => 'Gabon', 'GM' => 'Gambia', 'GE' => 'Georgia, Republic of', 'DE' => 'Germany', 'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GB' => 'Great Britain and Northern Ireland', 'GR' => 'Greece', 'GL' => 'Greenland', 'GD' => 'Grenada', 'GP' => 'Guadeloupe', 'GT' => 'Guatemala', 'GN' => 'Guinea', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HT' => 'Haiti', 'HN' => 'Honduras', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IS' => 'Iceland', 'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran', 'IQ' => 'Iraq', 'IE' => 'Ireland', 'IL' => 'Israel', 'IT' => 'Italy', 'JM' => 'Jamaica', 'JP' => 'Japan', 'JO' => 'Jordan', 'KZ' => 'Kazakhstan', 'KE' => 'Kenya', 'KI' => 'Kiribati', 'KW' => 'Kuwait', 'KG' => 'Kyrgyzstan', 'LA' => 'Laos', 'LV' => 'Latvia', 'LB' => 'Lebanon', 'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libya', 'LI' => 'Liechtenstein', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MO' => 'Macao', 'MK' => 'Macedonia, Republic of', 'MG' => 'Madagascar', 'MW' => 'Malawi', 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta', 'MQ' => 'Martinique', 'MR' => 'Mauritania', 'MU' => 'Mauritius', 'YT' => 'Mayotte (France)', 'MX' => 'Mexico', 'MD' => 'Moldova', 'MC' => 'Monaco (France)', 'MN' => 'Mongolia', 'MS' => 'Montserrat', 'MA' => 'Morocco', 'MZ' => 'Mozambique', 'NA' => 'Namibia', 'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands', 'AN' => 'Netherlands Antilles', 'NC' => 'New Caledonia', 'NZ' => 'New Zealand', 'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria', 'KP' => 'North Korea (Korea, Democratic People\'s Republic of)', 'NO' => 'Norway', 'OM' => 'Oman', 'PK' => 'Pakistan', 'PA' => 'Panama', 'PG' => 'Papua New Guinea', 'PY' => 'Paraguay', 'PE' => 'Peru', 'PH' => 'Philippines', 'PN' => 'Pitcairn Island', 'PL' => 'Poland', 'PT' => 'Portugal', 'QA' => 'Qatar', 'RE' => 'Reunion', 'RO' => 'Romania', 'RU' => 'Russia', 'RW' => 'Rwanda', 'SH' => 'Saint Helena', 'KN' => 'Saint Kitts (St. Christopher and Nevis)', 'LC' => 'Saint Lucia', 'PM' => 'Saint Pierre and Miquelon', 'VC' => 'Saint Vincent and the Grenadines', 'SM' => 'San Marino', 'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal', 'YU' => 'Serbia-Montenegro', 'SC' => 'Seychelles', 'SL' => 'Sierra Leone', 'SG' => 'Singapore', 'SK' => 'Slovak Republic', 'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia', 'ZA' => 'South Africa', 'GS' => 'South Georgia (Falkland Islands)', 'KR' => 'South Korea (Korea, Republic of)', 'ES' => 'Spain', 'LK' => 'Sri Lanka', 'SD' => 'Sudan', 'SR' => 'Suriname', 'SZ' => 'Swaziland', 'SE' => 'Sweden', 'CH' => 'Switzerland', 'SY' => 'Syrian Arab Republic', 'TW' => 'Taiwan', 'TJ' => 'Tajikistan', 'TZ' => 'Tanzania', 'TH' => 'Thailand', 'TG' => 'Togo', 'TK' => 'Tokelau (Union) Group (Western Samoa)', 'TO' => 'Tonga', 'TT' => 'Trinidad and Tobago', 'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan', 'TC' => 'Turks and Caicos Islands', 'TV' => 'Tuvalu', 'UG' => 'Uganda', 'UA' => 'Ukraine', 'AE' => 'United Arab Emirates', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', 'VU' => 'Vanuatu', 'VA' => 'Vatican City', 'VE' => 'Venezuela', 'VN' => 'Vietnam', 'WF' => 'Wallis and Futuna Islands', 'WS' => 'Western Samoa', 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe' ); $this->countryinsure = array( 'AF' => 0, 'AL' => 0, 'DZ' => 2185, 'AD' => 5000, 'AO' => 0, 'AI' => 415, 'AG' => 60, 'AR' => 5000, 'AM' => 1350, 'AW' => 830, 'AU' => 3370, 'AT' => 5000, 'AZ' => 5000, 'BS' => 2795, 'BH' => 0, 'BD' => 5000, 'BB' => 220, 'BY' => 1323, 'BE' => 5000, 'BZ' => 1600, 'BJ' => 170, 'BM' => 440, 'BT' => 440, 'BO' => 0, 'BA' => 5000, 'BW' => 145, 'BR' => 5000, 'VG' => 165, 'BN' => 4405, 'BG' => 1030, 'BF' => 530, 'MM' => 4045, 'BI' => 790, 'KH' => 0, 'CM' => 5000, 'CA' => 675, 'CV' => 0, 'KY' => 0, 'CF' => 4405, 'TD' => 440, 'CL' => 0, 'CN' => 1130, 'CX' => 3370, 'CC' => 3370, 'CO' => 0, 'KM' => 690, 'CG' => 1685, 'ZR' => 0, 'CK' => 980, 'CR' => 0, 'CI' => 5000, 'HR' => 5000, 'CU' => 0, 'CY' => 5000, 'CZ' => 5000, 'DK' => 5000, 'DJ' => 880, 'DM' => 0, 'DO' => 0, 'TP' => 0, 'EC' => 0, 'EG' => 1685, 'SV' => 0, 'GQ' => 0, 'ER' => 0, 'EE' => 2020, 'ET' => 1000, 'FK' => 510, 'FO' => 5000, 'FJ' => 600, 'FI' => 5000, 'FR' => 5000, 'GF' => 5000, 'PF' => 1015, 'GA' => 485, 'GM' => 2575, 'GE' => 1350, 'DE' => 5000, 'GH' => 5000, 'GI' => 5000, 'GB' => 857, 'GR' => 5000, 'GL' => 5000, 'GD' => 350, 'GP' => 5000, 'GT' => 0, 'GN' => 875, 'GW' => 21, 'GY' => 10, 'HT' => 0, 'HN' => 0, 'HK' => 5000, 'HU' => 5000, 'IS' => 5000, 'IN' => 2265, 'ID' => 0, 'IR' => 0, 'IQ' => 0, 'IE' => 5000, 'IL' => 0, 'IT' => 5000, 'JM' => 0, 'JP' => 5000, 'JO' => 0, 'KZ' => 5000, 'KE' => 815, 'KI' => 0, 'KW' => 1765, 'KG' => 1350, 'LA' => 0, 'LV' => 1350, 'LB' => 440, 'LS' => 440, 'LR' => 440, 'LY' => 0, 'LI' => 5000, 'LT' => 5000, 'LU' => 5000, 'MO' => 4262, 'MK' => 2200, 'MG' => 675, 'MW' => 50, 'MY' => 1320, 'MV' => 0, 'ML' => 950, 'MT' => 5000, 'MQ' => 5000, 'MR' => 635, 'MU' => 270, 'YT' => 5000, 'MX' => 0, 'MD' => 1350, 'MC' => 5000, 'MN' => 440, 'MS' => 2200, 'MA' => 5000, 'MZ' => 0, 'NA' => 4405, 'NR' => 220, 'NP' => 0, 'NL' => 5000, 'AN' => 830, 'NC' => 1615, 'NZ' => 980, 'NI' => 440, 'NE' => 810, 'NG' => 205, 'KP' => 0, 'NO' => 0, 'OM' => 575, 'PK' => 270, 'PA' => 0, 'PG' => 445, 'PY' => 0, 'PE' => 0, 'PH' => 270, 'PN' => 0, 'PL' => 1350, 'PT' => 5000, 'QA' => 2515, 'RE' => 5000, 'RO' => 5000, 'RU' => 5000, 'RW' => 0, 'SH' => 170, 'KN' => 210, 'LC' => 400, 'PM' => 5000, 'VC' => 130, 'SM' => 5000, 'ST' => 440, 'SA' => 0, 'SN' => 865, 'YU' => 5000, 'SC' => 0, 'SL' => 0, 'SG' => 4580, 'SK' => 5000, 'SI' => 4400, 'SB' => 0, 'SO' => 440, 'ZA' => 1760, 'GS' => 510, 'KR' => 5000, 'ES' => 5000, 'LK' => 35, 'SD' => 0, 'SR' => 535, 'SZ' => 560, 'SE' => 5000, 'CH' => 5000, 'SY' => 3080, 'TW' => 1350, 'TJ' => 1350, 'TZ' => 230, 'TH' => 1350, 'TG' => 2190, 'TK' => 295, 'TO' => 515, 'TT' => 930, 'TN' => 2200, 'TR' => 880, 'TM' => 675, 'TC' => 0, 'TV' => 4715, 'UG' => 0, 'UA' => 5000, 'AE' => 5000, 'UY' => 0, 'UZ' => 5000, 'VU' => 0, 'VA' => 5000, 'VE' => 0, 'VN' => 0, 'WF' => 1615, 'WS' => 295, 'YE' => 0, 'ZM' => 540, 'ZW' => 600, 'US' => 5000 ); } function SetInsurance() { $this->insurance_cost = 0; // Insurance module by Kevin Shelton // divide the value of the order among the packages based on the order total or subtotal depending on whether or not you have configured to insure tax $shipping_weight = $this->order['ShippingWeight']; $shipping_num_boxes = $this->order['ShippingNumBoxes']; $costperpkg = $this->order['SubTotal'] / $shipping_num_boxes; // retrieve the maximum allowed insurance for the destination country and if the package value exceeds it then set package value to the maximum allowed $maxins = $this->countryinsure[$this->order['ShippingCountry']]; if ($costperpkg > $maxins) $costperpkg = $maxins; // if insurance not allowed for destination or insurance is turned off add nothing to shipping cost if (($maxins == 0) || (MODULE_SHIPPING_USPS_INSURE == 'False')) { $insurance = 0; } // US and Canada share the same insurance calculation (though not the same maximum) else if (($this->order['ShippingCountry'] == 'US') || ($this->order['ShippingCountry'] == 'CA')) { if ($costperpkg<=50) { $insurance=MODULE_SHIPPING_USPS_INS1; } else if ($costperpkg<=100) { $insurance=MODULE_SHIPPING_USPS_INS2; } else if ($costperpkg<=200) { $insurance=MODULE_SHIPPING_USPS_INS3; } else if ($costperpkg<=300) { $insurance=MODULE_SHIPPING_USPS_INS4; } else { $insurance = MODULE_SHIPPING_USPS_INS4 + ((ceil($costperpkg/100) -3) * MODULE_SHIPPING_USPS_INS5); } } // if insurance allowed and is not US or Canada then calculate international insurance else { if ($costperpkg<=50) { $insurance=MODULE_SHIPPING_USPS_INS6; } else if ($costperpkg<=100) { $insurance=MODULE_SHIPPING_USPS_INS7; } else if ($costperpkg<=200) { $insurance=MODULE_SHIPPING_USPS_INS8; } else if ($costperpkg<=300) { $insurance=MODULE_SHIPPING_USPS_INS9; } else { $insurance = MODULE_SHIPPING_USPS_INS9 + ((ceil($costperpkg/100) - 3) * MODULE_SHIPPING_USPS_INS10); } } // usps doesnt accept zero weight $shipping_weight = ($shipping_weight < 0.1 ? 0.1 : $shipping_weight); $shipping_pounds = floor ($shipping_weight); $shipping_ounces = round(16 * ($shipping_weight - floor($shipping_weight))); $this->_setWeight($shipping_pounds, $shipping_ounces); // Added by Kevin Chen (kkchen@uci.edu); Fixes the Parcel Post Bug July 1, 2004 // Refer to http://www.usps.com/webtools/htm/Domestic-Rates.htm documentation // Thanks Ryan if($shipping_pounds > 35 || ($shipping_pounds == 0 && $shipping_ounces < 6)){ $this->_setMachinable('False'); } else{ $this->_setMachinable('True'); } $this->insurance_cost = $insurance; // End Kevin Chen July 1, 2004 } function _setService($service) { $this->service = $service; } function _setWeight($pounds, $ounces=0) { $this->pounds = $pounds; $this->ounces = $ounces; } function _setContainer($container) { $this->container = $container; } function _setSize($size) { $this->size = $size; } function _setMachinable($machinable) { $this->machinable = $machinable; } function PhoneClean($phone) { $res = preg_replace('/[(]|[)]|[\-]|[ ]|[#]|[\.]|[a-z](.*)|[A-Z](.*)/g', '', $phone); if ( strlen($res) > 10 ) { $res = substr($res, 0, 10); } return $res != '' ? $res : $phone; } function GetQuote($method = '') { if ( isset($this->types[$method]) || in_array($method, $this->intl_types)) { $this->_setService($method); } $this -> _setContainer('None'); $this -> _setSize('REGULAR'); $this -> SetInsurance(); // ??? if ($this->order['ShippingCountry'] == $this->shipping_origin_country) { $request='<?xml version="1.0"?>'; // PASSWORD="'.$this->usps_password.'" $request.= '<RateV3Request USERID="'.$this->usps_userid.'">'; $services_count = 0; if (isset($this->service)) { $this->types = array($this->service => $this->types[$this->service]); } $dest_zip = str_replace(' ', '', $this->order['ShippingZip']); $dest_zip = substr($dest_zip, 0, 5); reset($this->types); $allowed_types = explode(", ", MODULE_SHIPPING_USPS_TYPES); - while (list($key, $value) = each($this->types)) - { + foreach ( array_keys($this->types) as $key ) { if ( !in_array($key, $allowed_types) ) continue; $request .= '<Package ID="'.$services_count.'">'. '<Service>'.$key.'</Service>'. '<ZipOrigination>'.$this->store_zip5.'</ZipOrigination>'. '<ZipDestination>'.$dest_zip.'</ZipDestination>'. '<Pounds>'.$this->pounds.'</Pounds>'. '<Ounces>'.$this->ounces.'</Ounces>'. '<Size>'.$this->size.'</Size>'. '<Machinable>'.$this->machinable.'</Machinable>'. '</Package>'; $services_count++; } + $request .= '</RateV3Request>'; $api_query = 'RateV3'; } else { $request = '<IntlRateRequest USERID="'.$this->usps_userid.'">'. '<Package ID="0">'. '<Pounds>'.$this->pounds.'</Pounds>'. '<Ounces>'.$this->ounces.'</Ounces>'. '<MailType>Package</MailType>'. '<Country>'.$this->countries[$this->order['ShippingCountry']].'</Country>'. '</Package>'. '</IntlRateRequest>'; $api_query = 'IntlRate'; } $request = 'API='.$api_query.'&XML=' . kUtil::escape($request, kUtil::ESCAPE_URL); $body = $this->PostQuery($request); $body = str_replace(chr(146), '', $body); // for bad ` // check for errors if (strpos($body, '<Error>') !== false) { $errors = Array (); preg_match_all('/<Number>(.*?)<\/Number>/s', $body, $error_numbers); preg_match_all('/<Description>(.*?)<\/Description>/s', $body, $error_descriptions); foreach ($error_numbers[1] as $index => $error_number) { $errors[$index] = $error_descriptions[1][$index]; if ($this->Application->isDebugMode()) { $errors[$index] .= ' (' . $error_number . ')'; } } $errors = array_unique($errors); // we may have same errors on many packages, so don't show duplicates return Array('error' => implode('<br/>', $errors)); } // parse response /** @var kXMLHelper $xml_helper */ $xml_helper = $this->Application->recallObject('kXMLHelper'); /** @var kXMLNode $root_node */ $root_node =& $xml_helper->Parse($body); $rates = Array(); // Domestic shipping if ($this->order['ShippingCountry'] == $this->shipping_origin_country) { $i = 0; $postage_node =& $root_node->FindChild('Package'); do { // $parcel_node =& $postage_node->firstChild; $service = $postage_node->FindChildValue('MailService'); if ( $service != '' ) { $i++; $rates[$i] = Array(); $rates[$i]['Title'] = $service; $rates[$i]['Rate'] = $this->insurance_cost + $postage_node->FindChildValue('Rate'); } } while ( $postage_node =& $postage_node->NextSibling()); } else { // for International Rates !!! $allowed_types = array(); foreach( explode(", ", MODULE_SHIPPING_USPS_TYPES_INTL) as $value ) { $allowed_types[$value] = $this->intl_types[$value]; } $i = 0; $service_node =& $root_node->FindChild('Service'); do { $service = trim($service_node->FindChildValue('SvcDescription')); if( !in_array($service, $allowed_types) ) continue; $i++; if ( $service_node->FindChildValue('MaxWeight') >= $this->pounds ) { $rates[$i] = Array(); $rates[$i]['Title'] = $service; $rates[$i]['MaxDimensions'] = $service_node->FindChildValue('MaxDimensions'); $rates[$i]['MaxWeight'] = $service_node->FindChildValue('MaxWeight'); $rates[$i]['SvcCommitments'] = $service_node->FindChildValue('SvcCommitments'); $rates[$i]['Rate'] = $this->insurance_cost + $service_node->FindChildValue('Postage'); } } while ( $service_node =& $service_node->NextSibling()); } // print_r($rates); // die('here'); return $rates; } function PostOrder() { $request=''; $base_request = ''; $this->SetInsurance(); // $this->order['ShippingCountry'] = $this->GetUSPSCountry($this->order['ShippingCountry']); // Domestic Order if ($this->order['ShippingCountry'] == $this->shipping_origin_country) { // $dest_zip = str_replace(' ', '', $this->order['ShippingZip5']); $this->order['ShippingZip5'] = substr($this->order['ShippingZip5'], 0, 5); $WeightInOunces = floor($this->pounds * 16 + $this->ounces); $base_request =' <Option>1</Option> <ImageParameters></ImageParameters> <FromName>'.$this->store_name.'</FromName> <FromFirm>'.$this->company_name.'</FromFirm> <FromAddress1>'.$this->store_address1.'</FromAddress1> <FromAddress2>'.$this->store_address2.'</FromAddress2> <FromCity>'.$this->store_city.'</FromCity> <FromState>'.$this->store_state.'</FromState> <FromZip5>'.$this->store_zip5.'</FromZip5> <FromZip4>'.$this->store_zip4.'</FromZip4> <ToName>'.$this->order['FirstName'].' '.$this->order['LastName'].'</ToName> <ToFirm>'.$this->order['ShippingCompany'].'</ToFirm> <ToAddress1>'.$this->order['ShippingAddress2'].'</ToAddress1> <ToAddress2>'.$this->order['ShippingAddress1'].'</ToAddress2> <ToCity>'.$this->order['ShippingCity'].'</ToCity> <ToState>'.$this->order['ShippingState'].'</ToState> <ToZip5>'.$this->order['ShippingZip5'].'</ToZip5> <ToZip4>'.$this->order['ShippingZip4'].'</ToZip4> <WeightInOunces>'.$WeightInOunces.'</WeightInOunces> <ServiceType>'.$this->order['ShippingService'].'</ServiceType> <ImageType>PDF</ImageType> <LabelDate>'.date('m/d/Y',time()).'</LabelDate> <CustomerRefNo></CustomerRefNo> <AddressServiceRequested></AddressServiceRequested> <SenderName></SenderName><SenderEMail></SenderEMail> <RecipientName></RecipientName> <RecipientEMail></RecipientEMail> '; $api_query = 'DeliveryConfirmationV3'; $xml_request = 'DeliveryConfirmationV3.0Request'; } else { // International Order(s) $shipping_service = strtolower($this->order['ShippingService']); $base_request = '<Option/> <ImageParameters/> <FromFirstName>'.$this->store_first_name.'</FromFirstName> <FromLastName>'.$this->store_last_name.'</FromLastName> <FromFirm>'.$this->company_name.'</FromFirm> <FromAddress1>'.$this->store_address1.'</FromAddress1> <FromAddress2>'.$this->store_address2.'</FromAddress2> <FromCity>'.$this->store_city.'</FromCity> <FromState>'.$this->store_state.'</FromState> <FromZip5>'.$this->store_zip5.'</FromZip5> <FromPhone>'.$this->PhoneClean($this->store_phone).'</FromPhone> <ToName>'.$this->order['FirstName'].' '.$this->order['LastName'].'</ToName> <ToFirm>'.$this->order['ShippingCompany'].'</ToFirm> <ToAddress1></ToAddress1> <ToAddress2>'.$this->order['ShippingAddress2'].'</ToAddress2> <ToAddress3>'.$this->order['ShippingAddress1'].'</ToAddress3> <ToCity>'.$this->order['ShippingCity'].'</ToCity>'; if ( $this->order['ShippingProvince'] != '' ) { $base_request.=' <ToProvince>'.$this->order['ShippingProvince'].'</ToProvince>'; } $base_request.=' <ToCountry>'.$this->countries[$this->order['ShippingCountry']].'</ToCountry> <ToPostalCode>'.$this->order['ShippingZip'].'</ToPostalCode> <ToPOBoxFlag>N</ToPOBoxFlag> <ToPhone>'.$this->PhoneClean($this->order['ShippingPhone']).'</ToPhone> <ToFax>'.$this->PhoneClean($this->order['ShippingFax']).'</ToFax> <ToEmail>'.$this->order['Email'].'</ToEmail> <ShippingContents>'; // add items foreach ( $this->order['Items'] as $k => $value ) { $base_request.=' <ItemDetail> <Description>Computer Parts</Description> <Quantity>'.$value['Qty'].'</Quantity> <Value>'.($value['Price'] * $value['Qty']).'</Value> <NetPounds>'.$value['NetPounds'].'</NetPounds> <NetOunces>'.$value['NetOunces'].'</NetOunces> <HSTariffNumber>123456</HSTariffNumber> <CountryOfOrigin>United States</CountryOfOrigin> </ItemDetail>'; } // end add items $base_request.=' </ShippingContents> <GrossPounds>'.$this->pounds.'</GrossPounds> <GrossOunces>'.$this->ounces.'</GrossOunces> <ContentType>MERCHANDISE</ContentType> <Agreement>Y</Agreement> <InvoiceNumber>'.$this->order['InvoiceNumber'].'</InvoiceNumber> <ImageType>PDF</ImageType> <ImageLayout>ALLINONEFILE</ImageLayout> <LabelDate>'.date('m/d/Y',time()).'</LabelDate> '; if (strpos($shipping_service, 'express') !== false) { $xml_request = 'ExpressMailIntlRequest'; $api_query = 'ExpressMailIntl'; } elseif (strpos($shipping_service, 'priority') !== false) { $xml_request = 'PriorityMailIntlRequest'; $api_query = 'PriorityMailIntl'; } else { $xml_request = 'FirstClassMailIntlRequest'; $api_query = 'FirstClassMailIntl'; } } $request.= '<'.$xml_request.' USERID="'.$this->usps_userid.'">'; $request.= $base_request; $request.= '</'.$xml_request.'>'; // die($request); $request = 'API='.$api_query.'&XML='.kUtil::escape($request, kUtil::ESCAPE_URL); $body = $this->PostQuery($request, 1); // check for errors if (strpos($body, '<Error>') !== false) { $errors = Array (); preg_match_all('/<Number>(.*?)<\/Number>/s', $body, $error_numbers); preg_match_all('/<Description>(.*?)<\/Description>/s', $body, $error_descriptions); foreach ($error_numbers[1] as $index => $error_number) { $errors[$index] = Array ('error_number' => $error_number, 'error_description' => $error_descriptions[1][$index]); } // TODO: find a way to return other error messages in same package as well return $errors[0]; } // parse response $xml_helper = $this->Application->recallObject('kXMLHelper'); /** @var kXMLNode $root_node */ $root_node =& $xml_helper->Parse($body); $Postage = 0; $label_file = $TrackingNumber = $PostnetBarCode = ''; // Domestic shipping if ($this->order['ShippingCountry'] == $this->shipping_origin_country ) { $delivery_node =& $root_node->FindChild('DeliveryConfirmationV3.0Response'); do { $TrackingNumber = $delivery_node->FindChildValue('DeliveryConfirmationNumber'); $PostnetBarCode = $delivery_node->FindChildValue('Postnet'); $DeliveryConfirmationLabel = base64_decode($delivery_node->FindChildValue('DeliveryConfirmationLabel')); } while ( $delivery_node =& $delivery_node->NextSibling()); } else { if (strpos($shipping_service, 'express') !== false) { $node_title = 'ExpressMailIntlResponse'; } elseif (strpos($shipping_service, 'priority') !== false) { $node_title = 'PriorityMailIntlResponse'; } else { $node_title = 'FirstClassMailIntlResponse'; } $delivery_node =& $root_node->FindChild($node_title); $PostnetBarCode = $delivery_node->FindChildValue('BarcodeNumber'); $Postage = $delivery_node->FindChildValue('Postage'); $DeliveryConfirmationLabel = base64_decode($delivery_node->FindChildValue('LabelImage')); } if ( $TrackingNumber != '' ) { $label_file = USPS_LABEL_FOLDER.$TrackingNumber.".pdf"; } elseif ( $PostnetBarCode != '' ) { $label_file = USPS_LABEL_FOLDER.$PostnetBarCode.".pdf"; } if ( $label_file != '' ) { /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $file_helper->CheckFolder(USPS_LABEL_FOLDER); if (!$handle = fopen($label_file, 'a')) echo "Cannot open file ($label_file)"; if ( @fwrite($handle, $DeliveryConfirmationLabel) === FALSE) echo "Cannot write to file ($label_file)"; } return array('TrackingNumber' => $TrackingNumber, 'PostnetBarCode' => $PostnetBarCode, 'Postage' => $Postage); } function GetUSPSCountry($country, $default = 'US') { /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $country = $cs_helper->getCountryIso($country); return $country == '' ? $default : $country; } function GetShippingQuotes($params = null) { $weights = kUtil::Kg2Pounds($params['packages']['0']['weight']); $weight = ''; $weight = $weights[0]; if ( $weights[1] != '' ) { $weight.='.'.$weights[1]; } $country = $this->GetUSPSCountry($params['dest_country']); $this->order = Array(); $this->order['ShippingWeight'] = $weight; $this->order['ShippingNumBoxes'] = 1; $this->order['ShippingZip'] = $params['dest_postal']; $this->order['SubTotal'] = $params['amount']; $this->order['ShippingCountry'] = $country; $shipping_types = Array(); $rates = $this->GetQuote(); if ( !isset($rates['error']) ) { $this->Application->RemoveVar('sqe_error'); $i = 1; foreach ($rates as $k => $rate ) { $shipping_types['USPS_'.$i] = Array( 'ShippingId' => 'USPS_'.$i, 'TotalCost' => $rate['Rate'], 'ShippingName' => $rate['Title'], 'Type' => '1', 'CODFlat' => '0', 'CODPercent' => '0', 'PortalGroups' => ',15,', 'InsuranceFee' => '', 'COD' => '0', 'SelectedOnly' => '0', 'Code' => $rate['Title'] ); $i++; } } else { // for Front-End (shipping screen) and Admin (Shipping Tab on editing order) $this->Application->StoreVar('sqe_error', $rates['error']); } $this->Application->StoreVar('current_usps_shipping_types', serialize($shipping_types)); return $shipping_types; } function TrackOrder($TrackingNumber='') { if ( $TrackingNumber != '' ) { // http://testing.shippingapis.com/ShippingAPITest.dll?API=TrackV2&XML=<TrackFieldRequest USERID="402INTEC7634"><TrackID ID="EJ958083578US"></TrackID></TrackFieldRequest> $request = '<TrackRequest USERID="'.$this->usps_userid.'"><TrackID ID="'.$TrackingNumber.'"></TrackID></TrackRequest>'; $api_query = 'TrackV2'; $request = 'API='.$api_query.'&XML='.kUtil::escape($request, kUtil::ESCAPE_URL); $body = $this->PostQuery($request); // check for errors if (strpos($body, '<Error>') !== false) { $errors = Array (); preg_match_all('/<Number>(.*?)<\/Number>/s', $body, $error_numbers); preg_match_all('/<Description>(.*?)<\/Description>/s', $body, $error_descriptions); foreach ($error_numbers[1] as $index => $error_number) { $errors[$index] = $error_descriptions[1][$index]; if ($this->Application->isDebugMode()) { $errors[$index] .= ' (' . $error_number . ')'; } } $errors = array_unique($errors); // we may have same errors on many packages, so don't show duplicates return Array('error' => implode('<br/>', $errors)); } $xml_helper = $this->Application->recallObject('kXMLHelper'); /** @var kXMLNode $root_node */ $root_node =& $xml_helper->Parse($body); // Tracking Shipping $delivery_node =& $root_node->FindChild('TrackInfo'); $TrackSummary = $delivery_node->FindChildValue('TrackSummary'); // echo ' TrackSummary ('.$TrackingNumber.') = '.$TrackSummary.'<br>'; return strpos($TrackSummary, 'delivered') !== false ? 1 : 0; } else return false; } function ProcessTrackOrders() { $sql = sprintf('SELECT `OrderId`, `ShippingTracking` FROM %s WHERE `Status` > 3 AND `Delivered` = 0', TABLE_PREFIX.'Orders'); $orders = $this->Application->Conn->Query($sql); foreach ( $orders as $k => $order ) { // try to track order if ( $order['ShippingTracking'] != '' && $this->TrackOrder($order['ShippingTracking']) ) { $update_order = sprintf("UPDATE %s SET `Delivered` = 1 WHERE %s = %s", TABLE_PREFIX.'Orders', $this->Application->getUnitOption('ord', 'IDField'), $order[$this->Application->getUnitOption('ord', 'IDField')] ); $this->Application->Conn->Query($update_order); } } } function PostQuery($request, $secure=0) { switch (MODULE_SHIPPING_USPS_SERVER) { case 'production': $usps_server = $secure > 0 ? 'https://secure.shippingapis.com' : 'http://production.shippingapis.com' ; $api_dll = 'ShippingAPI.dll'; break; case 'test': $usps_server = $secure > 0 ? 'https://secure.shippingapis.com' : 'http://testing.shippingapis.com'; $api_dll = 'ShippingAPITest.dll'; break; } $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $usps_server.'/'.$api_dll.'?'.$request); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $body = curl_exec($curl); curl_close($curl); if ($this->logFilePath) { $fp = fopen($this->logFilePath, 'a'); if ($fp) { $request_url = sprintf("Date %s : IP %s\n\nPost\n\n%s\n\nReplay\n\n%s\n\n", adodb_date('m/d/Y H:i:s'), $this->Application->getClientIp(), $usps_server . '/' . $api_dll . '?' . urldecode($request), $body ); fwrite($fp, $request_url); fclose($fp); } } return $body; } /** * Returns available shipping types * * @return Array * @todo Get possible shipping types based on MODULE_SHIPPING_USPS_TYPES and MODULE_SHIPPING_USPS_TYPES_INTL consntants */ function GetAvailableTypes() { return Array ( Array ( '_ClassName' => get_class($this), '_Id' => 'USPS', '_Name' => 'USPS (Default)' ) ); } function LoadParams() { $sql = 'SELECT Properties FROM '.$this->Application->getUnitOption('sqe', 'TableName').' WHERE ClassName="USPS"'; $params = $this->Conn->GetOne($sql); return unserialize($params); } function _prepare_xml_param($value) { return strip_tags($value); } /** * Returns virtual field names, that will be saved as properties * * @return Array */ function GetEngineFields() { return Array ('AccountLogin'); } /** * Creates new USPS order * * @param OrdersItem $object * @param bool $dry_run * @return Array */ function MakeOrder(&$object, $dry_run = false) { $ShippingInfo = unserialize($object->GetDBField('ShippingInfo')); $ShippingCode = $USPSMethod = ''; $ShippingCountry = $this->GetUSPSCountry($object->GetDBField('ShippingCountry')); $UserName = explode(" ", $object->GetDBField('ShippingTo')); $item_table = TABLE_PREFIX.'OrderItems'; if ($this->Application->isAdminUser) { // this strange contraption actually uses temp table from object (when in temp mode) $order_table = $object->TableName; $item_table = str_replace('Orders', 'OrderItems', $order_table); } $sOrder = Array ( 'FirstName' => $UserName[0], 'LastName' => $UserName[1], 'ShippingCompany' => $object->GetDBField('ShippingCompany'), 'ShippingAddress1' => $object->GetDBField('ShippingAddress1'), 'ShippingAddress2' => $object->GetDBField('ShippingAddress2'), 'ShippingCity' => $object->GetDBField('ShippingCity'), 'ShippingZip' => $object->GetDBField('ShippingZip'), 'ShippingCountry' => $ShippingCountry, 'ShippingPhone' => $this->PhoneClean($object->GetDBField('ShippingPhone')), 'ShippingFax' => $this->PhoneClean($object->GetDBField('ShippingFax')), 'ShippingNumBoxes' => '1', ); $sql = 'SELECT SUM(`Quantity` * `Weight`) FROM ' . $item_table . ' WHERE ' . $object->IDField . ' = ' . $object->GetID(); $weight = $this->Application->Conn->GetOne($sql); $f_weight = kUtil::Kg2Pounds($weight); $sOrder['ShippingWeight'] = $f_weight[0].'.'.$f_weight[1]; foreach ($ShippingInfo as $k => $ShippingRow) { $ShippingCode = $ShippingRow['Code']; } if ( $object->GetDBField('ShippingCountry') == 'USA' ) { $sOrder['ShippingState'] = $object->GetDBField('ShippingState'); $USPSMethod = $ShippingCode; unset($sOrder['ShippingZip']); $sOrder['ShippingZip5'] = substr(trim($object->GetDBField('ShippingZip')), 0, 5); $sOrder['ShippingZip4'] = ''; $sOrder['SubTotal'] = $object->GetDBField('SubTotal'); } else { $USPSMethod = array_search($ShippingCode, $this->intl_types); $sOrder['ShippingProvince'] = ''; if ( $ShippingCountry == 'CA' ) { $sOrder['ShippingProvince'] = $object->GetField('ShippingState'); } // add items $sql = 'SELECT `Quantity`, `Weight`, `Price` FROM ' . $item_table . ' WHERE ' . $object->IDField . ' = ' . $object->GetID(); $order_items = $this->Application->Conn->Query($sql); $i = 1; $Items = Array(); foreach ($order_items as $k => $order_item) { $p_weight = Array(); $p_weight = kUtil::Kg2Pounds($order_item['Weight']); $Items[$i] = Array('Qty' => $order_item['Quantity'], 'Price' => $order_item['Price'], 'NetPounds' => $p_weight[0], 'NetOunces' => $p_weight[1]); $i++; } $sOrder['Items'] = $Items; $sOrder['InvoiceNumber'] = $object->GetDBField('OrderNumber'); } $sOrder['ShippingService'] = $USPSMethod; // make USPS order $this->order = $sOrder; $usps_data = $this->PostOrder(); // if errors if ( array_key_exists('error_number', $usps_data) ) { return $usps_data; } if ( array_key_exists('Postage', $usps_data) ) { $ShippingPrice = ''; $ShippingPrice = $usps_data['Postage']; } $ShippingTracking = ''; if ( isset($usps_data['TrackingNumber']) && $usps_data['TrackingNumber'] != '' ) { $ShippingTracking = $usps_data['TrackingNumber']; } if ( isset($usps_data['PostnetBarCode']) && $usps_data['PostnetBarCode'] != '' && $ShippingTracking == '' ) { $ShippingTracking = $usps_data['PostnetBarCode']; } if ($dry_run == false) { $object->SetDBField('ShippingTracking', $ShippingTracking); $object->Update(); } else { $full_path = USPS_LABEL_FOLDER . $ShippingTracking . ".pdf"; if (file_exists($full_path)) { unlink($full_path); } } return $usps_data; } -} \ No newline at end of file +} Index: branches/5.2.x/units/product_option_combinations/product_option_combinations_event_handler.php =================================================================== --- branches/5.2.x/units/product_option_combinations/product_option_combinations_event_handler.php (revision 16692) +++ branches/5.2.x/units/product_option_combinations/product_option_combinations_event_handler.php (revision 16693) @@ -1,448 +1,449 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ProductOptionCombinationsEventHandler extends kDBEventHandler { /** * Apply custom processing to item * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $event, $type) { if ( $type == 'after' ) { return; } switch ($event->Name) { case 'OnCreate': case 'OnUpdate': /** @var kDBItem $object */ $object = $event->getObject(); $options = unserialize($object->GetDBField('Combination')); ksort($options); $object->SetDBField('CombinationCRC', kUtil::crc32(serialize($options))); break; case 'OnMassDelete': // delete only option combinations that has no associated inventory /** @var kDBItem $object */ $object = $event->getObject(); $ids = $event->getEventParam('ids'); $sql = 'SELECT ' . $object->IDField . ' FROM ' . $object->TableName . ' WHERE (' . $object->IDField . ' IN (' . implode(',', $ids) . ')) AND (QtyInStock = 0) AND (QtyReserved = 0) AND (QtyBackOrdered = 0) AND (QtyOnOrder = 0)'; $event->setEventParam('ids', $this->Conn->GetCol($sql)); break; } } /** * GetOptionValues * * @param kEvent $event */ function GetOptionValues($event, $option_id) { $object = $event->getObject(); if ($object->IsTempTable()) { $table = $this->Application->GetTempName(TABLE_PREFIX.'ProductOptions', 'prefix:'.$event->Prefix); } else { $table = TABLE_PREFIX.'ProductOptions'; } $query = 'SELECT `Values` FROM '.$table.' WHERE ProductOptionId = '.$option_id; return explode(',', $this->Conn->GetOne($query)); } function CreateCombinations(kEvent $event, $fields, $current_option=null) { $recursed = false; $combination = $fields['Combination']; foreach ($combination as $option_id => $option) { if ($option_id == $current_option || $recursed) continue; if ($option == '_ANY_') { $recursed = true; $values = $this->GetOptionValues($event, $option_id); foreach ($values as $a_value) { $fields['Combination'][$option_id] = $a_value; $this->CreateCombinations($event, $fields, $option_id); } } } if (!$recursed) { /** @var kDBItem $object */ $object = $event->getObject(); $salt = $fields['Combination']; ksort($salt); $object->Load(kUtil::crc32(serialize($salt)), 'CombinationCRC'); $object->SetFieldsFromHash($fields); $event->setEventParam('form_data', $fields); $this->customProcessing($event,'before'); if ( $object->isLoaded() ) { // Update if such combination already exists if( $object->Update() ) { $this->customProcessing($event,'after'); $event->status=kEvent::erSUCCESS; } } else { if( $object->Create($event->getEventParam('ForceCreateId')) ) { $this->customProcessing($event,'after'); $event->status=kEvent::erSUCCESS; } } } } function UpdateCombinations(kEvent $event, $fields, $current_option=null) { $recursed = false; $combination = $fields['Combination']; foreach ($combination as $option_id => $option) { if ($option_id == $current_option || $recursed) continue; if ($option == '_ANY_') { $recursed = true; $values = $this->GetOptionValues($event, $option_id); foreach ($values as $a_value) { $fields['Combination'][$option_id] = $a_value; $this->UpdateCombinations($event, $fields, $option_id); } } } if (!$recursed) { /** @var kDBItem $object */ $object = $event->getObject(); $edit_id = $object->GetId(); $salt = $fields['Combination']; ksort($salt); // try to load combination by salt - if loaded, it will update the combination $object->Load(kUtil::crc32(serialize($salt)), 'CombinationCRC'); if ( !$object->isLoaded() ) { $object->Load($edit_id); } $object->SetFieldsFromHash($fields); $event->setEventParam('form_data', $fields); $this->customProcessing($event,'before'); if( $object->Update() ) { $this->customProcessing($event,'after'); $event->status=kEvent::erSUCCESS; } } } /** * Creates new kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( !$items_info ) { return; } - list($id, $field_values) = each($items_info); + $id = key($items_info); + $field_values = $items_info[$id]; $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( !$object->Validate() ) { $event->status = kEvent::erFAIL; $event->redirect = false; $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); return; } $this->CreateCombinations($event, $field_values); } /** * Updates kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnUpdate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject( Array('skip_autoload' => true) ); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) { foreach($items_info as $id => $field_values) { $object->Load($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if (!$object->Validate()) { $event->status = kEvent::erFAIL; $event->redirect = false; return; } $this->UpdateCombinations($event, $field_values); /*$this->customProcessing($event, 'before'); if( $object->Update($id) ) { $this->customProcessing($event, 'after'); $event->status=kEvent::erSUCCESS; } else { $event->status=kEvent::erFAIL; $event->redirect=false; break; }*/ } } $this->Application->SetVar($event->GetPrefixSpecial().'_id', ''); } /** * Builds item (loads if needed) * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ protected function OnItemBuild(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $this->dbBuild($object, $event); $sql = $this->ItemPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); // 2. loads if allowed $auto_load = $this->Application->getUnitOption($event->Prefix, 'AutoLoad'); $skip_autoload = $event->getEventParam('skip_autoload'); if ( $auto_load && !$skip_autoload ) { $this->LoadItem($event); } /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); $actions->Set($event->getPrefixSpecial() . '_GoTab', ''); $actions->Set($event->getPrefixSpecial() . '_GoId', ''); } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $id = $this->getPassedID($event); if ( !$id ) { $event->CallSubEvent('OnNew'); return; } if ( $object->Load($id) ) { /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); $actions->Set($event->getPrefixSpecial() . '_id', $object->GetId()); } } /** * Returns special of main item for linking with sub-item * * @param kEvent $event * @return string * @access protected */ protected function getMainSpecial(kEvent $event) { $special = $event->getEventParam('main_special'); if ( $special === false || $special == '$main_special' ) { $special = $event->Special; } if ( $special == 'grid' ) { $special = ''; } return $special; } /** * Occurs before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { parent::OnBeforeClone($event); $event->Init($event->Prefix, '-item'); /** @var kDBItem $object */ $object = $event->getObject(); $options_mapping = $this->Application->GetVar('poc_mapping'); if ( !$options_mapping ) { return; } foreach ($options_mapping as $original => $new) { $n_combs = array (); $comb_data = unserialize($object->GetDBField('Combination')); foreach ($comb_data as $key => $val) { $n_key = $key == $original ? $new : $key; $n_combs[$n_key] = $val; } ksort($n_combs); $n_combs = serialize($n_combs); $n_crc = kUtil::crc32($n_combs); $object->SetDBField('Combination', $n_combs); $object->SetDBField('CombinationCRC', $n_crc); } } /** * Restore back values from live table to temp table before overwriting live with temp * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); // check if product inventory management is via options and then proceed $id = $event->getEventParam('id'); $products_table = $this->Application->getUnitOption('p', 'TableName'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $sql = 'SELECT p.InventoryStatus FROM ' . $products_table . ' p LEFT JOIN ' . $table_name . ' poc ON poc.ProductId = p.ProductId WHERE poc.' . $id_field . ' = ' . $id; $inventory_status = $this->Conn->GetOne($sql); if ( $inventory_status == ProductInventory::BY_OPTIONS ) { /** @var kDBItem $live_object */ $live_object = $this->Application->recallObject($event->Prefix . '.itemlive', null, Array ('skip_autoload' => true)); $live_object->SwitchToLive(); $live_object->Load($id); /** @var kDBItem $temp_object */ $temp_object = $this->Application->recallObject($event->Prefix . '.itemtemp', null, Array ('skip_autoload' => true)); $temp_object->SwitchToTemp(); $temp_object->Load($id); $temp_object->SetDBFieldsFromHash($live_object->GetFieldValues(), Array ('QtyInStock', 'QtyReserved', 'QtyBackOrdered', 'QtyOnOrder')); $temp_object->Update(); } } /** * Create search filters based on search query * * @param kEvent $event * @return void * @access protected */ protected function OnSearch(kEvent $event) { parent::OnSearch($event); $this->_saveProduct($event); } /** * Clear search keywords * * @param kEvent $event * @return void * @access protected */ protected function OnSearchReset(kEvent $event) { parent::OnSearchReset($event); $this->_saveProduct($event); } /** * Makes event remember product id (if passed) * * @param kEvent $event */ function _saveProduct($event) { $product_id = $this->Application->GetVar('p_id'); if ($product_id) { $event->SetRedirectParam('p_id', $product_id); } } } Index: branches/5.2.x/units/taxes/taxes_tag_processor.php =================================================================== --- branches/5.2.x/units/taxes/taxes_tag_processor.php (revision 16692) +++ branches/5.2.x/units/taxes/taxes_tag_processor.php (revision 16693) @@ -1,273 +1,273 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class TaxesTagProcessor extends kDBTagProcessor { function ShowCountries($params) { /** @var kDBItem $object */ $object = $this->getObject($params); $destination_table = $this->getDestinationsTable($params); $selected_country_id = (int)$this->Application->GetVar('CountrySelector'); $name_field = 'l' . $this->Application->GetVar('m_lang') . '_Name'; $id_field = $this->Application->getUnitOption('country-state', 'IDField'); $table_name = $this->Application->getUnitOption('country-state', 'TableName'); switch ($params['show']) { case 'current': // selected countries in current zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_COUNTRY . ' AND zd.TaxZoneId = ' . $object->GetID() . ' ORDER BY cs.' . $name_field; break; case 'available': // available countries in current zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_COUNTRY . ' AND zd.TaxZoneId IS NULL ORDER BY cs.' . $name_field; break; case 'all': // always preselect 1st country, when user haven't selected any if (!$selected_country_id) { $sql = 'SELECT StdDestId FROM ' . $destination_table . ' WHERE TaxZoneId = ' . $object->GetID(); $selected_country_id = $this->Conn->GetOne($sql); if ($selected_country_id) { $this->Application->SetVar('CountrySelector', $selected_country_id); } } // all countries $sql = 'SELECT ' . $name_field . ', ' . $id_field . ' FROM ' . $table_name . ' WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' ORDER BY ' . $name_field; break; case 'has_states': /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $has_states = $cs_helper->getCountriesWithStates(); if ($selected_country_id && !array_key_exists($selected_country_id, $has_states)) { - list ($selected_country_id, ) = each($has_states); + $selected_country_id = key($has_states); $this->Application->SetVar('CountrySelector', $selected_country_id); } // preselect country from 1st found state if (!$selected_country_id) { $sql = 'SELECT cs.StateCountryId FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE (cs.Type = ' . DESTINATION_TYPE_STATE . ') AND (zd.TaxZoneId = ' . $object->GetID() . ')'; $selected_country_id = $this->Conn->GetOne($sql); if ($selected_country_id) { $this->Application->SetVar('CountrySelector', $selected_country_id); } else { - list ($selected_country_id, ) = each($has_states); + $selected_country_id = key($has_states); $this->Application->SetVar('CountrySelector', $selected_country_id); } } // gets only countries with states $sql = 'SELECT ' . $name_field . ', ' . $id_field . ' FROM ' . $table_name . ' WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' AND ' . $id_field . ' IN (' . implode(',', array_keys($has_states)) . ') ORDER BY ' . $name_field; break; default: throw new Exception('Unknown "show" parameter value "' . $params['show'] . '" used'); break; } $ret = ''; $countries = $this->Conn->GetCol($sql, $id_field); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['block']; foreach ($countries as $country_id => $country_name) { $block_params['id'] = $country_id; $block_params['destination_title'] = $country_name; $block_params['selected'] = $selected_country_id == $country_id ? ' selected="selected"' : ''; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } function ShowStates($params) { /** @var kDBItem $object */ $object = $this->getObject($params); $destination_table = $this->getDestinationsTable($params); $name_field = 'l' . $this->Application->GetVar('m_lang') . '_Name'; $id_field = $this->Application->getUnitOption('country-state', 'IDField'); $table_name = $this->Application->getUnitOption('country-state', 'TableName'); $country_id = $this->Application->GetVar('CountrySelector'); switch ($params['show']) { case 'current': // selected states for current country and zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_STATE . ' AND cs.StateCountryId = ' . $country_id . ' AND zd.TaxZoneId = ' . $object->GetID() . ' ORDER BY cs.' . $name_field; break; case 'available': // available states for current country and zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_STATE . ' AND zd.TaxZoneId IS NULL AND cs.StateCountryId = ' . $country_id . ' ORDER BY cs.' . $name_field; break; case 'all': // all possible states for selected country $sql = 'SELECT ' . $name_field . ', ' . $id_field . ' FROM ' . $table_name . ' WHERE Type = ' . DESTINATION_TYPE_STATE . ' AND StateCountryId = ' . $country_id . ' ORDER BY ' . $name_field; break; default: throw new Exception('Unknown "show" parameter value "' . $params['show'] . '" used'); break; } $ret = ''; $states = $this->Conn->GetCol($sql, $id_field); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['block']; foreach($states as $state_id => $state_name) { $block_params['id'] = $state_id; $block_params['destination_title'] = $state_name; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } function ShowZips($params) { /** @var kDBItem $object */ $object = $this->getObject($params); $destination_table = $this->getDestinationsTable($params); $country_id = (int)$this->Application->GetVar('CountrySelector'); $current_sql = 'SELECT DestValue FROM ' . $destination_table . ' WHERE COALESCE(DestValue, "") <> "" AND TaxZoneId = ' . $object->GetID() . ' ORDER BY DestValue'; switch ($params['show']) { case 'current': $sql = $current_sql; break; case 'available': $selected_zips = $this->Conn->GetCol($current_sql); $selected_zips = $this->Conn->qstrArray($selected_zips); $sql = 'SELECT DISTINCT DestValue FROM ' . $this->Application->getUnitOption('taxdst', 'TableName') . ' WHERE COALESCE(DestValue, "") <> "" AND TaxZoneId <> ' . $object->GetID() . ' AND ' . ($selected_zips ? 'DestValue NOT IN (' . implode(',', $selected_zips) . ') AND' : '') . ' StdDestId = ' . $country_id . ' ORDER BY DestValue'; break; default: throw new Exception('Unknown "show" parameter value "' . $params['show'] . '" used'); break; } $zips = $this->Conn->GetCol($sql); $ret = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['block']; foreach($zips as $zip) { $block_params['id'] = '0|' . $zip; $block_params['destination_title'] = $zip; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Returns table for shipping zone destinations * * @param Array $params * @return string */ function getDestinationsTable($params) { static $table_name = ''; if (!$table_name) { /** @var kDBItem $object */ $object = $this->getObject($params); $table_name = $this->Application->getUnitOption('taxdst', 'TableName'); if ($object->IsTempTable()) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix); } } return $table_name; } -} \ No newline at end of file +} Index: branches/5.2.x/units/coupons/coupons_event_handler.php =================================================================== --- branches/5.2.x/units/coupons/coupons_event_handler.php (revision 16692) +++ branches/5.2.x/units/coupons/coupons_event_handler.php (revision 16693) @@ -1,235 +1,236 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class CouponsEventHandler extends kDBEventHandler { /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnItemBuild' => Array ('self' => true), 'OnApplyClone' => Array ('self' => 'add'), 'OnPrepareClone' => Array ('self' => 'view'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Prepares coupon cloning * * @param kEvent $event */ function OnPrepareClone($event) { $this->StoreSelectedIDs($event); $event->CallSubEvent('OnNew'); /** @var kDBItem $object */ $object = $event->getObject(); $this->setCloningRequired($object); $clone_count = $this->Application->RecallVar('CoupLastCloneCount'); if ( is_numeric($clone_count) && $clone_count > 0 ) { $object->SetDBField('CouponCount', $clone_count); } $expire_days = $this->Application->ConfigValue('Comm_DefaultCouponDuration'); $default_expiration = strtotime('+' . $expire_days . ' days'); $object->SetDBField('DefaultExpiration_date', $default_expiration); $object->SetDBField('DefaultExpiration_time', $default_expiration); } /** * Occurs before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { parent::OnBeforeClone($event); /** @var kDBItem $object */ $object = $event->getObject(); $this->SetNewCode($object); $object->SetDBField('LastUsedBy', NULL); $object->SetDBField('LastUsedOn', NULL); $object->SetDBField('NumberOfUses', NULL); $expiration = $this->Application->GetVar('clone_coupon_expiration'); $object->SetDBField('Expiration_date', $expiration); $object->SetDBField('Expiration_time', $expiration); } function OnApplyClone(kEvent $event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return; } /** @var kDBItem $object */ $object = $event->getObject( Array ('skip_autoload' => true) ); $this->setCloningRequired($object); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); - list($id, $field_values) = each($items_info); + $id = key($items_info); + $field_values = $items_info[$id]; $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( !$object->Validate() ) { $event->status = kEvent::erFAIL; return ; } /** @var kTempTablesHandler $temp */ $temp = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $original_coupon_ids = $this->getSelectedIDs($event, true); $clone_count = $object->GetDBField('CouponCount'); $this->Application->StoreVar('CoupLastCloneCount', $clone_count); $this->Application->SetVar('clone_coupon_expiration', $object->GetDBField('DefaultExpiration')); for ($i = 0; $i < $clone_count; $i++) { $temp->CloneItems($event->Prefix, $event->Special, $original_coupon_ids); } $event->SetRedirectParam('opener', 'u'); } /** * Sets fields required during coupon cloning * * @param kDBItem $object * @return void * @access protected */ protected function setCloningRequired(&$object) { $this->RemoveRequiredFields($object); $object->setRequired('CouponCount'); $object->setRequired('DefaultExpiration'); } function SetNewCode(&$item) { do{ $new_code = $this->RandomCouponCode(); $exists = $this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'ProductsCoupons WHERE Code='.$this->Conn->qstr($new_code)); if ($exists){ $new_code = false; } } while (!$new_code); $item->SetDBField('Code', $new_code); } function RandomCouponCode() { $rand_code = ''; for ($i = 0; $i < 10; $i++) { $is_letter = rand(0, 1); $rand_code .= ($is_letter ? chr(rand(65, 90)) : rand(0, 9)); } return $rand_code; } /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $exp_date = adodb_mktime(); $default_duration = $this->Application->ConfigValue('Comm_DefaultCouponDuration'); if ( $default_duration && $default_duration > 0 ) { $exp_date += (int)$default_duration * 86400; } $object->SetDBField('Expiration_date', $exp_date); } /** * Occurs before updating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->itemChanged($event); } /** * Occurs before creating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $this->itemChanged($event); } /** * Occurs before item is changed * * @param kEvent $event */ function itemChanged($event) { /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('Amount', abs($object->GetDBField('Amount'))); } } Index: branches/5.2.x/units/zones/zones_tag_processor.php =================================================================== --- branches/5.2.x/units/zones/zones_tag_processor.php (revision 16692) +++ branches/5.2.x/units/zones/zones_tag_processor.php (revision 16693) @@ -1,272 +1,272 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ZonesTagProcessor extends kDBTagProcessor { function ShowCountries($params) { /** @var kDBItem $object */ $object = $this->getObject($params); $destination_table = $this->getDestinationsTable($params); $selected_country_id = (int)$this->Application->GetVar('CountrySelector'); $name_field = 'l' . $this->Application->GetVar('m_lang') . '_Name'; $id_field = $this->Application->getUnitOption('country-state', 'IDField'); $table_name = $this->Application->getUnitOption('country-state', 'TableName'); switch ($params['show']) { case 'current': // selected countries in current zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_COUNTRY . ' AND zd.ShippingZoneId = ' . $object->GetID() . ' ORDER BY cs.' . $name_field; break; case 'available': // available countries in current zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_COUNTRY . ' AND zd.ShippingZoneId IS NULL ORDER BY cs.' . $name_field; break; case 'all': // always preselect 1st country, when user haven't selected any if (!$selected_country_id) { $sql = 'SELECT StdDestId FROM ' . $destination_table . ' WHERE ShippingZoneId = ' . $object->GetID(); $selected_country_id = $this->Conn->GetOne($sql); if ($selected_country_id) { $this->Application->SetVar('CountrySelector', $selected_country_id); } } // all countries $sql = 'SELECT ' . $name_field . ', ' . $id_field . ' FROM ' . $table_name . ' WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' ORDER BY ' . $name_field; break; case 'has_states': /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $has_states = $cs_helper->getCountriesWithStates(); if ($selected_country_id && !array_key_exists($selected_country_id, $has_states)) { - list ($selected_country_id, ) = each($has_states); + $selected_country_id = key($has_states); $this->Application->SetVar('CountrySelector', $selected_country_id); } // preselect country from 1st found state if (!$selected_country_id) { $sql = 'SELECT cs.StateCountryId FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE (cs.Type = ' . DESTINATION_TYPE_STATE . ') AND (zd.ShippingZoneId = ' . $object->GetID() . ')'; $selected_country_id = $this->Conn->GetOne($sql); if ($selected_country_id) { $this->Application->SetVar('CountrySelector', $selected_country_id); } else { - list ($selected_country_id, ) = each($has_states); + $selected_country_id = key($has_states); $this->Application->SetVar('CountrySelector', $selected_country_id); } } // gets only countries with states $sql = 'SELECT ' . $name_field . ', ' . $id_field . ' FROM ' . $table_name . ' WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' AND ' . $id_field . ' IN (' . implode(',', array_keys($has_states)) . ') ORDER BY ' . $name_field; break; default: throw new Exception('Unknown "show" parameter value "' . $params['show'] . '" used'); break; } $ret = ''; $countries = $this->Conn->GetCol($sql, $id_field); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['block']; foreach ($countries as $country_id => $country_name) { $block_params['id'] = $country_id; $block_params['destination_title'] = $country_name; $block_params['selected'] = $selected_country_id == $country_id ? ' selected="selected"' : ''; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } function ShowStates($params) { /** @var kDBItem $object */ $object = $this->getObject($params); $destination_table = $this->getDestinationsTable($params); $name_field = 'l' . $this->Application->GetVar('m_lang') . '_Name'; $id_field = $this->Application->getUnitOption('country-state', 'IDField'); $table_name = $this->Application->getUnitOption('country-state', 'TableName'); $country_id = $this->Application->GetVar('CountrySelector'); switch ($params['show']) { case 'current': // selected states for current country and zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_STATE . ' AND cs.StateCountryId = ' . $country_id . ' AND zd.ShippingZoneId = ' . $object->GetID() . ' ORDER BY cs.' . $name_field; break; case 'available': // available states for current country and zone $sql = 'SELECT cs.' . $name_field . ', cs.' . $id_field . ' FROM ' . $table_name . ' cs LEFT JOIN ' . $destination_table . ' zd ON zd.StdDestId = cs.' . $id_field . ' WHERE cs.Type = ' . DESTINATION_TYPE_STATE . ' AND zd.ShippingZoneId IS NULL AND cs.StateCountryId = ' . $country_id . ' ORDER BY cs.' . $name_field; break; case 'all': // all possible states for selected country $sql = 'SELECT ' . $name_field . ', ' . $id_field . ' FROM ' . $table_name . ' WHERE Type = ' . DESTINATION_TYPE_STATE . ' AND StateCountryId = ' . $country_id . ' ORDER BY ' . $name_field; break; default: throw new Exception('Unknown "show" parameter value "' . $params['show'] . '" used'); break; } $ret = ''; $states = $this->Conn->GetCol($sql, $id_field); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['block']; foreach($states as $state_id => $state_name) { $block_params['id'] = $state_id; $block_params['destination_title'] = $state_name; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } function ShowZips($params) { /** @var kDBItem $object */ $object = $this->getObject($params); $destination_table = $this->getDestinationsTable($params); $country_id = (int)$this->Application->GetVar('CountrySelector'); $current_sql = 'SELECT DestValue FROM ' . $destination_table . ' WHERE COALESCE(DestValue, "") <> "" AND ShippingZoneID = ' . $object->GetID() . ' ORDER BY DestValue'; switch ($params['show']) { case 'current': $sql = $current_sql; break; case 'available': $selected_zips = $this->Conn->GetCol($current_sql); $selected_zips = $this->Conn->qstrArray($selected_zips); $sql = 'SELECT DISTINCT DestValue FROM ' . $this->Application->getUnitOption('dst', 'TableName') . ' WHERE COALESCE(DestValue, "") <> "" AND ShippingZoneID <> ' . $object->GetID() . ' AND ' . ($selected_zips ? 'DestValue NOT IN (' . implode(',', $selected_zips) . ') AND' : '') . ' StdDestId = ' . $country_id . ' ORDER BY DestValue'; break; default: throw new Exception('Unknown "show" parameter value "' . $params['show'] . '" used'); break; } $zips = $this->Conn->GetCol($sql); $ret = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['block']; foreach($zips as $zip) { $block_params['id'] = '0|' . $zip; $block_params['destination_title'] = $zip; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Returns table for shipping zone destinations * * @param Array $params * @return string */ function getDestinationsTable($params) { static $table_name = ''; if (!$table_name) { /** @var kDBItem $object */ $object = $this->getObject($params); $table_name = $this->Application->getUnitOption('dst', 'TableName'); if ($object->IsTempTable()) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix); } } return $table_name; } -} \ No newline at end of file +} Index: branches/5.2.x/units/orders/orders_event_handler.php =================================================================== --- branches/5.2.x/units/orders/orders_event_handler.php (revision 16692) +++ branches/5.2.x/units/orders/orders_event_handler.php (revision 16693) @@ -1,4116 +1,4117 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class OrdersEventHandler extends kDBEventHandler { /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( !$this->Application->isAdminUser ) { if ( $event->Name == 'OnCreate' ) { // user can't initiate custom order creation directly return false; } $user_id = $this->Application->RecallVar('user_id'); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { // when POST is present, then check when is beeing submitted $order_session_id = $this->Application->RecallVar($event->getPrefixSpecial(true) . '_id'); /** @var OrdersItem $order_dummy */ $order_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); foreach ($items_info as $id => $field_values) { if ( $order_session_id != $id ) { // user is trying update not his order, even order from other guest return false; } $order_dummy->Load($id); // session_id matches order_id from submit if ( $order_dummy->GetDBField('PortalUserId') != $user_id ) { // user performs event on other user order return false; } $status_field = $order_dummy->getStatusField(); if ( isset($field_values[$status_field]) && $order_dummy->GetDBField($status_field) != $field_values[$status_field] ) { // user can't change status by himself return false; } if ( $order_dummy->GetDBField($status_field) != ORDER_STATUS_INCOMPLETE ) { // user can't edit orders being processed return false; } } } } if ( $event->Name == 'OnQuietPreSave' ) { $section = $event->getSection(); if ( $this->isNewItemCreate($event) ) { return $this->Application->CheckPermission($section . '.add', 1); } else { return $this->Application->CheckPermission($section . '.add', 1) || $this->Application->CheckPermission($section . '.edit', 1); } } return parent::CheckPermission($event); } /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( // admin 'OnRecalculateItems' => Array('self' => 'add|edit'), 'OnResetToUser' => Array('self' => 'add|edit'), 'OnResetToBilling' => Array('self' => 'add|edit'), 'OnResetToShipping' => Array('self' => 'add|edit'), 'OnMassOrderApprove' => Array('self' => 'advanced:approve'), 'OnMassOrderDeny' => Array('self' => 'advanced:deny'), 'OnMassOrderArchive' => Array('self' => 'advanced:archive'), 'OnMassPlaceOrder' => Array('self' => 'advanced:place'), 'OnMassOrderProcess' => Array('self' => 'advanced:process'), 'OnMassOrderShip' => Array('self' => 'advanced:ship'), 'OnResetToPending' => Array('self' => 'advanced:reset_to_pending'), 'OnLoadSelected' => Array('self' => 'view'), // print in this case 'OnGoToOrder' => Array('self' => 'view'), // front-end 'OnViewCart' => Array('self' => true), 'OnAddToCart' => Array('self' => true), 'OnRemoveFromCart' => Array('self' => true), 'OnUpdateCart' => Array('self' => true), 'OnUpdateCartJSON' => Array('self' => true), 'OnUpdateItemOptions' => Array('self' => true), 'OnCleanupCart' => Array('self' => true), 'OnContinueShopping' => Array('self' => true), 'OnCheckout' => Array('self' => true), 'OnSelectAddress' => Array('self' => true), 'OnProceedToBilling' => Array('self' => true), 'OnProceedToPreview' => Array('self' => true), 'OnProceedToPreviewAjax' => array('self' => true), 'OnCompleteOrder' => Array('self' => true), 'OnUpdate' => array('self' => true), 'OnUpdateAjax' => Array('self' => true), 'OnRemoveCoupon' => Array('self' => true), 'OnRemoveGiftCertificate' => Array('self' => true), 'OnCancelRecurring' => Array('self' => true), 'OnAddVirtualProductToCart' => Array('self' => true), 'OnItemBuild' => Array('self' => true), 'OnDownloadLabel' => Array('self' => true, 'subitem' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Define alternative event processing method names * * @return void * @see kEventHandler::$eventMethods * @access protected */ protected function mapEvents() { parent::mapEvents(); $common_events = Array ( 'OnResetToUser' => 'OnResetAddress', 'OnResetToBilling' => 'OnResetAddress', 'OnResetToShipping' => 'OnResetAddress', 'OnMassOrderProcess' => 'MassInventoryAction', 'OnMassOrderApprove' => 'MassInventoryAction', 'OnMassOrderDeny' => 'MassInventoryAction', 'OnMassOrderArchive' => 'MassInventoryAction', 'OnMassOrderShip' => 'MassInventoryAction', 'OnOrderProcess' => 'InventoryAction', 'OnOrderApprove' => 'InventoryAction', 'OnOrderDeny' => 'InventoryAction', 'OnOrderArchive' => 'InventoryAction', 'OnOrderShip' => 'InventoryAction', ); $this->eventMethods = array_merge($this->eventMethods, $common_events); } /* ======================== FRONT ONLY ======================== */ function OnQuietPreSave($event) { /** @var kDBItem $object */ $object = $event->getObject(); $object->IgnoreValidation = true; $event->CallSubEvent('OnPreSave'); $object->IgnoreValidation = false; } /** * Sets new address to order * * @param kEvent $event */ function OnSelectAddress($event) { if ($this->Application->isAdminUser) { return ; } /** @var OrdersItem $object */ $object = $event->getObject(); $shipping_address_id = $this->Application->GetVar('shipping_address_id'); $billing_address_id = $this->Application->GetVar('billing_address_id'); if ($shipping_address_id || $billing_address_id) { /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /** @var AddressesItem $address */ $address = $this->Application->recallObject('addr.-item','addr', Array('skip_autoload' => true)); /** @var AddressesList $addr_list */ $addr_list = $this->Application->recallObject('addr', 'addr_List', Array('per_page'=>-1, 'skip_counting'=>true) ); $addr_list->Query(); } if ($shipping_address_id > 0) { $addr_list->CopyAddress($shipping_address_id, 'Shipping'); $address->Load($shipping_address_id); $address->MarkAddress('Shipping'); $cs_helper->PopulateStates($event, 'ShippingState', 'ShippingCountry'); $object->setRequired('ShippingState', false); } elseif ($shipping_address_id == -1) { $object->ResetAddress('Shipping'); } if ($billing_address_id > 0) { $addr_list->CopyAddress($billing_address_id, 'Billing'); $address->Load($billing_address_id); $address->MarkAddress('Billing'); $cs_helper->PopulateStates($event, 'BillingState', 'BillingCountry'); $object->setRequired('BillingState', false); } elseif ($billing_address_id == -1) { $object->ResetAddress('Billing'); } $event->redirect = false; $object->IgnoreValidation = true; $this->RecalculateTax($event); $object->Update(); } /** * Updates order with registred user id * * @param kEvent $event */ function OnUserCreate($event) { if( !($event->MasterEvent->status == kEvent::erSUCCESS) ) return false; $ses_id = $this->Application->RecallVar('front_order_id'); if($ses_id) { $this->updateUserID($ses_id, $event); $this->Application->RemoveVar('front_order_id'); } } /** * Updates shopping cart with logged-in user details * * @param kEvent $event * @return void * @access protected */ protected function OnUserLogin($event) { if ( ($event->MasterEvent->status != kEvent::erSUCCESS) || kUtil::constOn('IS_INSTALL') ) { // login failed OR login during installation return; } $ses_id = $this->Application->RecallVar('ord_id'); if ( $ses_id ) { $this->updateUserID($ses_id, $event); } $user_id = $this->Application->RecallVar('user_id'); $affiliate_id = $this->isAffiliate($user_id); if ( $affiliate_id ) { $this->Application->setVisitField('AffiliateId', $affiliate_id); } $event->CallSubEvent('OnRecalculateItems'); } /** * Puts ID of just logged-in user into current order * * @param int $order_id * @param kEvent $event * @return void */ function updateUserID($order_id, $event) { /** @var UsersItem $user */ $user = $this->Application->recallObject('u.current'); $affiliate_id = $this->isAffiliate( $user->GetID() ); $fields_hash = Array ( 'PortalUserId' => $user->GetID(), 'BillingEmail' => $user->GetDBField('Email'), ); if ( $affiliate_id ) { $fields_hash['AffiliateId'] = $affiliate_id; } /** @var OrdersItem $object */ $object = $this->Application->recallObject($event->Prefix . '.-item', null, array('skip_autoload' => true)); $object->Load($order_id); if ( $object->isLoaded() ) { $object->SetDBFieldsFromHash($fields_hash); $object->Update(); } } function isAffiliate($user_id) { /** @var kDBItem $affiliate_user */ $affiliate_user = $this->Application->recallObject('affil.-item', null, Array('skip_autoload' => true) ); $affiliate_user->Load($user_id, 'PortalUserId'); return $affiliate_user->isLoaded() ? $affiliate_user->GetDBField('AffiliateId') : 0; } /** * Charge order * * @param OrdersItem $order * @return Array */ function ChargeOrder(&$order) { $gw_data = $order->getGatewayData(); /** @var kGWBase $gateway_object */ $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $payment_result = $gateway_object->DirectPayment($order->GetFieldValues(), $gw_data['gw_params']); $sql = 'UPDATE %s SET GWResult1 = %s WHERE %s = %s'; $sql = sprintf($sql, $order->TableName, $this->Conn->qstr($gateway_object->getGWResponce()), $order->IDField, $order->GetID() ); $this->Conn->Query($sql); $order->SetDBField('GWResult1', $gateway_object->getGWResponce() ); return array('result'=>$payment_result, 'data'=>$gateway_object->parsed_responce, 'gw_data' => $gw_data, 'error_msg'=>$gateway_object->getErrorMsg()); } /** * Returns parameters, used to send order-related e-mails * * @param OrdersItem $order * @return array */ function OrderEmailParams(&$order) { $billing_email = $order->GetDBField('BillingEmail'); $sql = 'SELECT Email FROM ' . $this->Application->getUnitOption('u', 'TableName') . ' WHERE PortalUserId = ' . $order->GetDBField('PortalUserId'); $user_email = $this->Conn->GetOne($sql); $ret = Array ( '_user_email' => $user_email, // for use when shipping vs user is required in InventoryAction 'to_name' => $order->GetDBField('BillingTo'), 'to_email' => $billing_email ? $billing_email : $user_email, ); return $ret; } function PrepareCoupons($event, &$order) { /** @var kDBList $order_items */ $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); $order_items->linkToParent($order->Special); $order_items->Query(); $order_items->GoFirst(); $assigned_coupons = array(); $coup_handler = $this->Application->recallObject('coup_EventHandler'); foreach($order_items->Records as $product_item) { if ($product_item['ItemData']) { $item_data = unserialize($product_item['ItemData']); if (isset($item_data['AssignedCoupon']) && $item_data['AssignedCoupon']) { $coupon_id = $item_data['AssignedCoupon']; // clone coupon, get new coupon ID /** @var kDBItem $coupon */ $coupon = $this->Application->recallObject('coup',null,array('skip_autload' => true)); $coupon->Load($coupon_id); if (!$coupon->isLoaded()) continue; $coup_handler->SetNewCode($coupon); $coupon->NameCopy(); $coupon->SetDBField('Name', $coupon->GetDBField('Name').' (Order #'.$order->GetField('OrderNumber').')'); $coupon->Create(); // add coupon code to array array_push($assigned_coupons, $coupon->GetDBField('Code')); } } } /** @var OrdersItem $order */ if ($assigned_coupons) { $comments = $order->GetDBField('AdminComment'); if ($comments) $comments .= "\r\n"; $comments .= "Issued coupon(s): ". join(',', $assigned_coupons); $order->SetDBField('AdminComment', $comments); $order->Update(); } if ($assigned_coupons) $this->Application->SetVar('order_coupons', join(',', $assigned_coupons)); } /** * Completes order if possible * * @param kEvent $event * @return bool */ function OnCompleteOrder($event) { $this->LockTables($event); $reoccurring_order = substr($event->Special, 0, 9) == 'recurring'; if ( !$reoccurring_order && !$this->CheckQuantites($event) ) { // don't check quantities (that causes recalculate) for reoccurring orders return; } $this->ReserveItems($event); /** @var OrdersItem $order */ $order = $event->getObject(); $charge_result = $this->ChargeOrder($order); if (!$charge_result['result']) { $this->FreeItems($event); $this->Application->StoreVar('gw_error', $charge_result['error_msg']); //$this->Application->StoreVar('gw_error', getArrayValue($charge_result, 'data', 'responce_reason_text') ); $event->redirect = $this->Application->GetVar('failure_template'); $event->SetRedirectParam('m_cat_id', 0); if ($event->Special == 'recurring') { // if we set failed status for other than recurring special the redirect will not occur $event->status = kEvent::erFAIL; } return false; } // call CompleteOrder events for items in order BEFORE SplitOrder (because ApproveEvents are called there) /** @var kDBList $order_items */ $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); $order_items->linkToParent($order->Special); $order_items->Query(true); $order_items->GoFirst(); foreach($order_items->Records as $product_item) { if (!$product_item['ProductId']) continue; // product may have been deleted $this->raiseProductEvent('CompleteOrder', $product_item['ProductId'], $product_item); } $shipping_control = getArrayValue($charge_result, 'gw_data', 'gw_params', 'shipping_control'); if ($event->Special != 'recurring') { if ($shipping_control && $shipping_control != SHIPPING_CONTROL_PREAUTH ) { // we have to do it here, because the coupons are used in the e-mails $this->PrepareCoupons($event, $order); } $this->Application->emailUser('ORDER.SUBMIT', null, $this->OrderEmailParams($order)); $this->Application->emailAdmin('ORDER.SUBMIT'); } if ($shipping_control === false || $shipping_control == SHIPPING_CONTROL_PREAUTH ) { $order->SetDBField('Status', ORDER_STATUS_PENDING); $order->Update(); } else { $this->SplitOrder($event, $order); } if (!$this->Application->isAdminUser) { // for tracking code $this->Application->StoreVar('last_order_amount', $order->GetDBField('TotalAmount')); $this->Application->StoreVar('last_order_number', $order->GetDBField('OrderNumber')); $this->Application->StoreVar('last_order_customer', $order->GetDBField('BillingTo')); $this->Application->StoreVar('last_order_user', $order->GetDBField('Username')); $event->redirect = $this->Application->GetVar('success_template'); $event->SetRedirectParam('m_cat_id', 0); } else { // $event->CallSubEvent('OnSave'); } $order_id = $order->GetId(); $order_idfield = $this->Application->getUnitOption('ord','IDField'); $order_table = $this->Application->getUnitOption('ord','TableName'); $original_amount = $order->GetDBField('SubTotal') + $order->GetDBField('ShippingCost') + $order->GetDBField('VAT') + $order->GetDBField('ProcessingFee') + $order->GetDBField('InsuranceFee') - $order->GetDBField('GiftCertificateDiscount'); $sql = 'UPDATE '.$order_table.' SET OriginalAmount = '.$original_amount.' WHERE '.$order_idfield.' = '.$order_id; $this->Conn->Query($sql); // Remember order ID for use on "Thank You" page. $this->Application->StoreVar('front_order_id', $order_id); // Remove globals, set from "_createNewCart" method. $this->Application->DeleteVar('ord_id'); $this->Application->RemoveVar('ord_id'); // Prevent accidental access to non-Incomplete order. $this->Application->removeObject($event->getPrefixSpecial()); $this->Application->Session->SetCookie('shop_cart_cookie', '', strtotime('-1 month')); } /** * Set billing address same as shipping * * @param kEvent $event */ function setBillingAddress($event) { /** @var OrdersItem $object */ $object = $event->getObject(); if ( $object->HasTangibleItems() ) { if ( $this->Application->GetVar('same_address') ) { // copy shipping address to billing $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); - list($id, $field_values) = each($items_info); + $id = key($items_info); $address_fields = Array ( 'To', 'Company', 'Phone', 'Fax', 'Email', 'Address1', 'Address2', 'City', 'State', 'Zip', 'Country' ); foreach ($address_fields as $address_field) { $items_info[$id]['Billing' . $address_field] = $object->GetDBField('Shipping' . $address_field); } $this->Application->SetVar($event->getPrefixSpecial(true), $items_info); } } } /** * Enter description here... * * @param kEvent $event */ function OnProceedToPreview($event) { $this->setBillingAddress($event); $event->CallSubEvent('OnUpdate'); $event->redirect = $this->Application->GetVar('preview_template'); } function OnViewCart($event) { $this->StoreContinueShoppingLink(); $event->redirect = $this->Application->GetVar('viewcart_template'); } function OnContinueShopping($event) { /** @var OrderHelper $order_helper */ $order_helper = $this->Application->recallObject('OrderHelper'); $template = $this->Application->GetVar('continue_shopping_template'); $event->redirect = $order_helper->getContinueShoppingTemplate($template); } /** * Enter description here... * * @param kEvent $event */ function OnCheckout($event) { $this->OnUpdateCart($event); if ( !$event->getEventParam('RecalculateChangedCart') ) { /** @var OrdersItem $object */ $object = $event->getObject(); if ( !$object->HasTangibleItems() ) { $object->SetDBField('ShippingTo', ''); $object->SetDBField('ShippingCompany', ''); $object->SetDBField('ShippingPhone', ''); $object->SetDBField('ShippingFax', ''); $object->SetDBField('ShippingEmail', ''); $object->SetDBField('ShippingAddress1', ''); $object->SetDBField('ShippingAddress2', ''); $object->SetDBField('ShippingCity', ''); $object->SetDBField('ShippingState', ''); $object->SetDBField('ShippingZip', ''); $object->SetDBField('ShippingCountry', ''); $object->SetDBField('ShippingType', 0); $object->SetDBField('ShippingCost', 0); $object->SetDBField('ShippingCustomerAccount', ''); $object->SetDBField('ShippingTracking', ''); $object->SetDBField('ShippingDate', 0); $object->SetDBField('ShippingOption', 0); $object->SetDBField('ShippingInfo', ''); $object->Update(); } $event->redirect = $this->Application->GetVar('next_step_template'); $order_id = $this->Application->GetVar('order_id'); if ( $order_id !== false ) { $event->SetRedirectParam('ord_id', $order_id); } } } /** * Restores order from cookie * * @param kEvent $event * @return void * @access protected */ protected function OnRestoreOrder(kEvent $event) { if ( $this->Application->isAdmin || $this->Application->RecallVar('ord_id') ) { // admin OR there is an active order -> don't restore from cookie return; } $shop_cart_cookie = $this->Application->GetVarDirect('shop_cart_cookie', 'Cookie'); if ( !$shop_cart_cookie ) { return; } $user_id = $this->Application->RecallVar('user_id'); $sql = 'SELECT OrderId FROM ' . TABLE_PREFIX . 'Orders WHERE (OrderId = ' . (int)$shop_cart_cookie . ') AND (Status = ' . ORDER_STATUS_INCOMPLETE . ') AND (PortalUserId = ' . $user_id . ')'; $order_id = $this->Conn->GetOne($sql); if ( $order_id ) { $this->Application->StoreVar('ord_id', $order_id); } } /** * Redirect user to Billing checkout step * * @param kEvent $event */ function OnProceedToBilling($event) { $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { - list($id, $field_values) = each($items_info); + $id = key($items_info); + $field_values = $items_info[$id]; /** @var kDBItem $object */ $object = $event->getObject(); $payment_type_id = $object->GetDBField('PaymentType'); if ( !$payment_type_id ) { $default_type = $this->_getDefaultPaymentType(); if ( $default_type ) { $field_values['PaymentType'] = $default_type; $items_info[$id] = $field_values; $this->Application->SetVar($event->getPrefixSpecial(true), $items_info); } } } $event->CallSubEvent('OnUpdate'); $event->redirect = $this->Application->GetVar('next_step_template'); } /** * Removes reoccurring mark from the order * * @param kEvent $event * @return void */ protected function OnCancelRecurring($event) { /** @var OrdersItem $order */ $order = $event->getObject(); $order->SetDBField('IsRecurringBilling', 0); $order->Update(); if ( $this->Application->GetVar('cancelrecurring_ok_template') ) { $event->redirect = $this->Application->GetVar('cancelrecurring_ok_template'); } } /** * Occurs after updating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); /** @var OrdersItem $object */ $object = $event->getObject(); $cvv2 = $object->GetDBField('PaymentCVV2'); if ( $cvv2 !== false ) { $this->Application->StoreVar('CVV2Code', $cvv2); } } /** * Updates kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnUpdate(kEvent $event) { $this->setBillingAddress($event); parent::OnUpdate($event); if ($this->Application->isAdminUser) { return ; } else { $event->SetRedirectParam('opener', 's'); } if ($event->status == kEvent::erSUCCESS) { $this->createMissingAddresses($event); } else { // strange: recalculate total amount on error /** @var OrdersItem $object */ $object = $event->getObject(); $object->SetDBField('TotalAmount', $object->getTotalAmount()); } } /** * Creates new address * * @param kEvent $event */ function createMissingAddresses($event) { if ( !$this->Application->LoggedIn() ) { return ; } /** @var kDBItem $object */ $object = $event->getObject(); /** @var kDBList $addr_list */ $addr_list = $this->Application->recallObject('addr', 'addr_List', Array ('per_page' => -1, 'skip_counting' => true)); $addr_list->Query(); /** @var AddressesItem $address_dummy */ $address_dummy = $this->Application->recallObject('addr.-item', null, Array ('skip_autoload' => true)); $address_prefixes = Array ('Billing', 'Shipping'); $address_fields = Array ( 'To', 'Company', 'Phone', 'Fax', 'Email', 'Address1', 'Address2', 'City', 'State', 'Zip', 'Country' ); foreach ($address_prefixes as $address_prefix) { $address_id = $this->Application->GetVar(strtolower($address_prefix) . '_address_id'); if ( !$this->Application->GetVar('check_' . strtolower($address_prefix) . '_address') ) { // form type doesn't match check type, e.g. shipping check on billing form continue; } if ( $address_id > 0 ) { $address_dummy->Load($address_id); } else { $address_dummy->SetDBField('PortalUserId', $this->Application->RecallVar('user_id')); } foreach ($address_fields as $address_field) { $address_dummy->SetDBField($address_field, $object->GetDBField($address_prefix . $address_field)); } $address_dummy->MarkAddress($address_prefix, false); $ret = ($address_id > 0) ? $address_dummy->Update() : $address_dummy->Create(); } } /** * Updates shopping cart content * * @param kEvent $event * @return void * @access protected */ protected function OnUpdateCart($event) { $this->Application->HandleEvent(new kEvent('orditems:OnUpdate')); $event->CallSubEvent('OnRecalculateItems'); } /** * Updates cart and returns various info in JSON format * * @param kEvent $event */ function OnUpdateCartJSON($event) { if ( $this->Application->GetVar('ajax') != 'yes' ) { return; } /** @var kDBItem $object */ $object = $event->getObject(); // 1. delete given order item by id $delete_id = $this->Application->GetVar('delete_id'); if ( $delete_id !== false ) { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'OrderItems WHERE OrderId = ' . $object->GetID() . ' AND OrderItemId = ' . (int)$delete_id; $this->Conn->Query($sql); } // 2. remove coupon $remove = $this->Application->GetVar('remove'); if ( $remove == 'coupon' ) { $this->RemoveCoupon($object); $object->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_REMOVED); } elseif ( $remove == 'gift_certificate' ) { $this->RemoveGiftCertificate($object); $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_REMOVED); } // 3. update product quantities and recalculate all discounts $this->Application->HandleEvent(new kEvent('orditems:OnUpdate')); $event->CallSubEvent('OnRecalculateItems'); // 4. remove "orditems" object of kDBItem class, since getOrderInfo uses kDBList object under same prefix $this->Application->removeObject('orditems'); /** @var OrderHelper $order_helper */ $order_helper = $this->Application->recallObject('OrderHelper'); $event->status = kEvent::erSTOP; $currency = $this->Application->GetVar('currency', 'selected'); echo json_encode( $order_helper->getOrderInfo($object, $currency) ); } /** * Adds item to cart * * @param kEvent $event */ function OnAddToCart($event) { $this->StoreContinueShoppingLink(); $qty = $this->Application->GetVar('qty'); $options = $this->Application->GetVar('options'); // multiple or options add $items = Array(); if (is_array($qty)) { foreach ($qty as $item_id => $combinations) { if (is_array($combinations)) { foreach ($combinations as $comb_id => $comb_qty) { if ($comb_qty == 0) continue; $items[] = array('item_id' => $item_id, 'qty' => $comb_qty, 'comb' => $comb_id); } } else { $items[] = array('item_id' => $item_id, 'qty' => $combinations); } } } if (!$items) { if (!$qty || is_array($qty)) $qty = 1; $item_id = $this->Application->GetVar('p_id'); if (!$item_id) return ; $items = array(array('item_id' => $item_id, 'qty' => $qty)); } // remember item data passed to event when called $default_item_data = $event->getEventParam('ItemData'); $default_item_data = $default_item_data ? unserialize($default_item_data) : Array(); foreach ($items as $an_item) { $item_id = $an_item['item_id']; $qty = $an_item['qty']; $comb = getArrayValue($an_item, 'comb'); $item_data = $default_item_data; /** @var ProductsItem $product */ $product = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); $product->Load($item_id); $event->setEventParam('ItemData', null); if ($product->GetDBField('AssignedCoupon')) { $item_data['AssignedCoupon'] = $product->GetDBField('AssignedCoupon'); } // 1. store options information OR if ($comb) { $combination = $this->Conn->GetOne('SELECT Combination FROM '.TABLE_PREFIX.'ProductOptionCombinations WHERE CombinationId = '.$comb); $item_data['Options'] = unserialize($combination); } elseif (is_array($options)) { $item_data['Options'] = $options[$item_id]; } // 2. store subscription information OR if( $product->GetDBField('Type') == 2 ) // subscriptions { $item_data = $this->BuildSubscriptionItemData($item_id, $item_data); } // 3. store package information if( $product->GetDBField('Type') == 5 ) // package { $package_content_ids = $product->GetPackageContentIds(); /** @var ProductsItem $product_package_item */ $product_package_item = $this->Application->recallObject('p.-packageitem'); $package_item_data = array(); foreach ($package_content_ids as $package_item_id){ $product_package_item->Load($package_item_id); $package_item_data[$package_item_id] = array(); if( $product_package_item->GetDBField('Type') == 2 ) // subscriptions { $package_item_data[$package_item_id] = $this->BuildSubscriptionItemData($package_item_id, $item_data); } } $item_data['PackageContent'] = $product->GetPackageContentIds(); $item_data['PackageItemsItemData'] = $package_item_data; } $event->setEventParam('ItemData', serialize($item_data)); // 1 for PacakgeNum when in admin - temporary solution to overcome splitting into separate sub-orders // of orders with items added through admin when approving them $this->AddItemToOrder($event, $item_id, $qty, $this->Application->isAdminUser ? 1 : null); } if ($event->status == kEvent::erSUCCESS && !$event->redirect) { $event->SetRedirectParam('pass', 'm'); $event->SetRedirectParam('pass_category', 0); //otherwise mod-rewrite shop-cart URL will include category $event->redirect = true; } else { if ($this->Application->isAdminUser) { $event->SetRedirectParam('opener', 'u'); } } } /** * Returns table prefix from event (temp or live) * * @param kEvent $event * @return string * @todo Needed? Should be refactored (by Alex) */ function TablePrefix(kEvent $event) { return $this->UseTempTables($event) ? $this->Application->GetTempTablePrefix('prefix:' . $event->Prefix) . TABLE_PREFIX : TABLE_PREFIX; } /** * Check if required options are selected & selected option combination is in stock * * @param kEvent $event * @param Array $options * @param int $product_id * @param int $qty * @param int $selection_mode * @return bool */ function CheckOptions($event, &$options, $product_id, $qty, $selection_mode) { // 1. check for required options $selection_filter = $selection_mode == 1 ? ' AND OptionType IN (1,3,6) ' : ''; $req_options = $this->Conn->GetCol('SELECT ProductOptionId FROM '.TABLE_PREFIX.'ProductOptions WHERE ProductId = '.$product_id.' AND Required = 1 '.$selection_filter); $result = true; foreach ($req_options as $opt_id) { if (!getArrayValue($options, $opt_id)) { $this->Application->SetVar('opt_error', 1); //let the template know we have an error $result = false; } } // 2. check for option combinations in stock $comb_salt = $this->OptionsSalt($options, true); if ($comb_salt) { // such option combination is defined explicitly $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $sql = 'SELECT Availability FROM '.$poc_table.' WHERE CombinationCRC = '.$comb_salt; $comb_availble = $this->Conn->GetOne($sql); // 2.1. check if Availability flag is set, then if ($comb_availble == 1) { // 2.2. check for quantity in stock $table = Array(); $table['poc'] = $this->Application->getUnitOption('poc', 'TableName'); $table['p'] = $this->Application->getUnitOption('p', 'TableName'); $table['oi'] = $this->TablePrefix($event).'OrderItems'; $object = $event->getObject(); $ord_id = $object->GetID(); // 2.3. check if some amount of same combination & product are not already in shopping cart $sql = 'SELECT '. $table['p'].'.InventoryStatus,'. $table['p'].'.BackOrder, IF('.$table['p'].'.InventoryStatus = 2, '.$table['poc'].'.QtyInStock, '.$table['p'].'.QtyInStock) AS QtyInStock, IF('.$table['oi'].'.OrderItemId IS NULL, 0, '.$table['oi'].'.Quantity) AS Quantity FROM '.$table['p'].' LEFT JOIN '.$table['poc'].' ON '.$table['p'].'.ProductId = '.$table['poc'].'.ProductId LEFT JOIN '.$table['oi'].' ON ('.$table['oi'].'.OrderId = '.$ord_id.') AND ('.$table['oi'].'.OptionsSalt = '.$comb_salt.') AND ('.$table['oi'].'.ProductId = '.$product_id.') AND ('.$table['oi'].'.BackOrderFlag = 0) WHERE '.$table['poc'].'.CombinationCRC = '.$comb_salt; $product_info = $this->Conn->GetRow($sql); if ($product_info['InventoryStatus']) { $backordering = $this->Application->ConfigValue('Comm_Enable_Backordering'); if (!$backordering || $product_info['BackOrder'] == 0) { // backordering is not enabled generally or for this product directly, then check quantities in stock if ($qty + $product_info['Quantity'] > $product_info['QtyInStock']) { $this->Application->SetVar('opt_error', 2); $result = false; } } } } elseif ($comb_availble !== false) { $this->Application->SetVar('opt_error', 2); $result = false; } } if ($result) { $event->status = kEvent::erSUCCESS; $shop_cart_template = $this->Application->GetVar('shop_cart_template'); $event->redirect = $this->Application->isAdminUser || !$shop_cart_template ? true : $shop_cart_template; } else { $event->status = kEvent::erFAIL; } return $result; } /** * Enter description here... * * @param kEvent $event */ function OnUpdateItemOptions($event) { $opt_data = $this->Application->GetVar('options'); $options = getArrayValue($opt_data, $this->Application->GetVar('p_id')); if (!$options) { $qty_data = $this->Application->GetVar('qty'); $comb_id = key(getArrayValue($qty_data, $this->Application->GetVar('p_id'))); $options = unserialize($this->Conn->GetOne('SELECT Combination FROM '.TABLE_PREFIX.'ProductOptionCombinations WHERE CombinationId = '.$comb_id)); } if (!$options) return; /** @var kDBItem $ord_item */ $ord_item = $this->Application->recallObject('orditems.-opt', null, Array ('skip_autoload' => true)); $ord_item->Load($this->Application->GetVar('orditems_id')); // assuming that quantity cannot be changed during order item editing if (!$this->CheckOptions($event, $options, $ord_item->GetDBField('ProductId'), 0, $ord_item->GetDBField('OptionsSelectionMode'))) return; $item_data = unserialize($ord_item->GetDBField('ItemData')); $item_data['Options'] = $options; $ord_item->SetDBField('ItemData', serialize($item_data)); $ord_item->SetDBField('OptionsSalt', $this->OptionsSalt($options)); $ord_item->Update(); $event->CallSubEvent('OnRecalculateItems'); if ($event->status == kEvent::erSUCCESS && $this->Application->isAdminUser) { $event->SetRedirectParam('opener', 'u'); } } function BuildSubscriptionItemData($item_id, $item_data) { $products_table = $this->Application->getUnitOption('p', 'TableName'); $products_idfield = $this->Application->getUnitOption('p', 'IDField'); $sql = 'SELECT AccessGroupId FROM %s WHERE %s = %s'; $item_data['PortalGroupId'] = $this->Conn->GetOne( sprintf($sql, $products_table, $products_idfield, $item_id) ); $pricing_table = $this->Application->getUnitOption('pr', 'TableName'); $pricing_idfield = $this->Application->getUnitOption('pr', 'IDField'); /* TODO check on implementation $sql = 'SELECT AccessDuration, AccessUnit, DurationType, AccessExpiration FROM %s WHERE %s = %s'; */ $sql = 'SELECT * FROM %s WHERE %s = %s'; $pricing_id = $this->GetPricingId($item_id, $item_data); $item_data['PricingId'] = $pricing_id; $pricing_info = $this->Conn->GetRow( sprintf($sql, $pricing_table, $pricing_idfield, $pricing_id ) ); $unit_secs = Array(1 => 1, 2 => 60, 3 => 3600, 4 => 86400, 5 => 604800, 6 => 2592000, 7 => 31536000); /* TODO check on implementation (code from customization healtheconomics.org) $item_data['DurationType'] = $pricing_info['DurationType']; $item_data['AccessExpiration'] = $pricing_info['AccessExpiration']; */ $item_data['Duration'] = $pricing_info['AccessDuration'] * $unit_secs[ $pricing_info['AccessUnit'] ]; return $item_data; } /** * Enter description here... * * @param kEvent $event */ function OnApplyCoupon($event) { $code = $this->Application->GetVar('coupon_code'); if ($code == '') { return ; } /** @var OrdersItem $object */ $object = $event->getObject(); /** @var kDBItem $coupon */ $coupon = $this->Application->recallObject('coup', null, Array ('skip_autoload' => true)); $coupon->Load($code, 'Code'); if ( !$coupon->isLoaded() ) { $event->status = kEvent::erFAIL; $object->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_CODE_INVALID); $event->redirect = false; // check!!! return ; } $expire_date = $coupon->GetDBField('Expiration'); $number_of_use = $coupon->GetDBField('NumberOfUses'); if ( $coupon->GetDBField('Status') != 1 || ($expire_date && $expire_date < adodb_mktime()) || (isset($number_of_use) && $number_of_use <= 0)) { $event->status = kEvent::erFAIL; $object->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_CODE_EXPIRED); $event->redirect = false; return ; } $last_used = adodb_mktime(); $coupon->SetDBField('LastUsedBy', $this->Application->RecallVar('user_id')); $coupon->SetDBField('LastUsedOn_date', $last_used); $coupon->SetDBField('LastUsedOn_time', $last_used); if ( isset($number_of_use) ) { $coupon->SetDBField('NumberOfUses', $number_of_use - 1); if ($number_of_use == 1) { $coupon->SetDBField('Status', 2); } } $coupon->Update(); $this->Application->setUnitOption('ord', 'AutoLoad', true); /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord'); $order->SetDBField('CouponId', $coupon->GetDBField('CouponId')); $order->SetDBField('CouponName', $coupon->GetDBField('Name')); // calculated field $order->Update(); $object->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_APPLIED); // OnApplyCoupon is called as hook for OnUpdateCart/OnCheckout, which calls OnRecalcualate themself } /** * Removes coupon from order * * @param kEvent $event * @deprecated */ function OnRemoveCoupon($event) { /** @var OrdersItem $object */ $object = $event->getObject(); $this->RemoveCoupon($object); $object->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_REMOVED); $event->CallSubEvent('OnRecalculateItems'); } /** * Removes coupon from a given order * * @param OrdersItem $object */ function RemoveCoupon(&$object) { /** @var kDBItem $coupon */ $coupon = $this->Application->recallObject('coup', null, Array('skip_autoload' => true)); $coupon->Load( $object->GetDBField('CouponId') ); if ( $coupon->isLoaded() ) { $coupon->SetDBField('NumberOfUses', $coupon->GetDBField('NumberOfUses') + 1); $coupon->SetDBField('Status', STATUS_ACTIVE); $coupon->Update(); } $object->SetDBField('CouponId', 0); $object->SetDBField('CouponName', ''); // calculated field $object->SetDBField('CouponDiscount', 0); } /** * Enter description here... * * @param kEvent $event */ function OnAddVirtualProductToCart($event) { $l_info = $this->Application->GetVar('l'); if($l_info) { foreach($l_info as $link_id => $link_info) {} $item_data['LinkId'] = $link_id; $item_data['ListingTypeId'] = $link_info['ListingTypeId']; } else { $link_id = $this->Application->GetVar('l_id'); $sql = 'SELECT ResourceId FROM '.$this->Application->getUnitOption('l', 'TableName').' WHERE LinkId = '.$link_id; $sql = 'SELECT ListingTypeId FROM '.$this->Application->getUnitOption('ls', 'TableName').' WHERE ItemResourceId = '.$this->Conn->GetOne($sql); $item_data['LinkId'] = $link_id; $item_data['ListingTypeId'] = $this->Conn->GetOne($sql); } $sql = 'SELECT VirtualProductId FROM '.$this->Application->getUnitOption('lst', 'TableName').' WHERE ListingTypeId = '.$item_data['ListingTypeId']; $item_id = $this->Conn->GetOne($sql); $event->setEventParam('ItemData', serialize($item_data)); $this->AddItemToOrder($event, $item_id); $shop_cart_template = $this->Application->GetVar('shop_cart_template'); if ( $shop_cart_template ) { $event->redirect = $shop_cart_template; } // don't pass unused info to shopping cart, brokes old mod-rewrites $event->SetRedirectParam('pass', 'm'); // not to pass link id $event->SetRedirectParam('m_cat_id', 0); // not to pass link id } function OnRemoveFromCart($event) { $ord_item_id = $this->Application->GetVar('orditems_id'); $ord_id = $this->getPassedID($event); $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'OrderItems WHERE OrderId = '.$ord_id.' AND OrderItemId = '.$ord_item_id); $this->OnRecalculateItems($event); } function OnCleanupCart($event) { $object = $event->getObject(); $sql = 'DELETE FROM '.TABLE_PREFIX.'OrderItems WHERE OrderId = '.$this->getPassedID($event); $this->Conn->Query($sql); $this->RemoveCoupon($object); $this->RemoveGiftCertificate($object); $this->OnRecalculateItems($event); } /** * Returns order id from session or last used * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { $event->setEventParam('raise_warnings', 0); $passed = parent::getPassedID($event); if ( $this->Application->isAdminUser ) { // work as usual in admin return $passed; } if ( $event->Special == 'last' ) { // return last order id (for using on thank you page) $order_id = $this->Application->RecallVar('front_order_id'); return $order_id > 0 ? $order_id : FAKE_ORDER_ID; // FAKE_ORDER_ID helps to keep parent filter for order items set in "kDBList::linkToParent" } $ses_id = $this->Application->RecallVar($event->getPrefixSpecial(true) . '_id'); if ( $passed && ($passed != $ses_id) ) { // order id given in url doesn't match our current order id $sql = 'SELECT PortalUserId FROM ' . TABLE_PREFIX . 'Orders WHERE OrderId = ' . $passed; $user_id = $this->Conn->GetOne($sql); if ( $user_id == $this->Application->RecallVar('user_id') ) { // current user is owner of order with given id -> allow him to view order details return $passed; } else { // current user is not owner of given order -> hacking attempt $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); return 0; } } // not passed or equals to ses_id return $ses_id > 0 ? $ses_id : FAKE_ORDER_ID; // FAKE_ORDER_ID helps to keep parent filter for order items set in "kDBList::linkToParent" } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { $id = $this->getPassedID($event); if ( $id == FAKE_ORDER_ID ) { // if we already know, that there is no such order, // then don't run database query, that will confirm that /** @var kDBItem $object */ $object = $event->getObject(); $object->Clear($id); return; } parent::LoadItem($event); } /** * Creates new shopping cart * * @param kEvent $event */ function _createNewCart($event) { /** @var kDBItem $object */ $object = $event->getObject( Array('skip_autoload' => true) ); $this->setNextOrderNumber($event); $object->SetDBField('Status', ORDER_STATUS_INCOMPLETE); $object->SetDBField('VisitId', $this->Application->RecallVar('visit_id') ); // get user if ( $this->Application->LoggedIn() ) { /** @var UsersItem $user */ $user = $this->Application->recallObject('u.current'); $user_id = $user->GetID(); $object->SetDBField('BillingEmail', $user->GetDBField('Email')); } else { $user_id = USER_GUEST; } $object->SetDBField('PortalUserId', $user_id); // get affiliate $affiliate_id = $this->isAffiliate($user_id); if ( $affiliate_id ) { $object->SetDBField('AffiliateId', $affiliate_id); } else { $affiliate_storage_method = $this->Application->ConfigValue('Comm_AffiliateStorageMethod'); if ( $affiliate_storage_method == 1 ) { $object->SetDBField('AffiliateId', (int)$this->Application->RecallVar('affiliate_id')); } else { $object->SetDBField('AffiliateId', (int)$this->Application->GetVar('affiliate_id')); } } // get payment type $default_type = $this->_getDefaultPaymentType(); if ( $default_type ) { $object->SetDBField('PaymentType', $default_type); } // vat setting $object->SetDBField('VATIncluded', $this->Application->ConfigValue('OrderVATIncluded')); $created = $object->Create(); if ( $created ) { $id = $object->GetID(); $this->Application->SetVar($event->getPrefixSpecial(true) . '_id', $id); $this->Application->StoreVar($event->getPrefixSpecial(true) . '_id', $id); $this->Application->Session->SetCookie('shop_cart_cookie', $id, strtotime('+1 month')); return $id; } return 0; } /** * Returns default payment type for order * * @return int */ function _getDefaultPaymentType() { $default_type = $this->Application->siteDomainField('PrimaryPaymentTypeId'); if (!$default_type) { $sql = 'SELECT PaymentTypeId FROM ' . TABLE_PREFIX . 'PaymentTypes WHERE IsPrimary = 1'; $default_type = $this->Conn->GetOne($sql); } return $default_type; } function StoreContinueShoppingLink() { $this->Application->StoreVar('continue_shopping', 'external:'.PROTOCOL.SERVER_NAME.$this->Application->RecallVar('last_url')); } /** * Sets required fields for order, based on current checkout step * !!! Do not use switch here, since all cases may be on the same form simultaneously * * @param kEvent $event */ function SetStepRequiredFields($event) { /** @var OrdersItem $order */ $order = $event->getObject(); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ($items_info) { // updated address available from SUBMIT -> use it - list($id, $field_values) = each($items_info); + $field_values = current($items_info); } else { // no updated address -> use current address $field_values = Array ( 'ShippingCountry' => $order->GetDBField('ShippingCountry'), 'BillingCountry' => $order->GetDBField('BillingCountry'), 'PaymentType' => $order->GetDBField('PaymentType'), ); } // shipping address required fields if ($this->Application->GetVar('check_shipping_address')) { $has_tangibles = $order->HasTangibleItems(); $req_fields = array('ShippingTo', 'ShippingAddress1', 'ShippingCity', 'ShippingZip', 'ShippingCountry', /*'ShippingPhone',*/ 'BillingEmail'); $order->setRequired($req_fields, $has_tangibles); $order->setRequired('ShippingState', $cs_helper->CountryHasStates( $field_values['ShippingCountry'] )); } // billing address required fields if ($this->Application->GetVar('check_billing_address')) { $req_fields = array('BillingTo', 'BillingAddress1', 'BillingCity', 'BillingZip', 'BillingCountry', 'BillingPhone', 'BillingEmail'); $order->setRequired($req_fields); $order->setRequired('BillingState', $cs_helper->CountryHasStates( $field_values['BillingCountry'] )); } $check_cc = $this->Application->GetVar('check_credit_card'); if ( $check_cc && ($field_values['PaymentType'] == $order->GetDBField('PaymentType')) ) { // cc check required AND payment type was not changed during SUBMIT if ( $this->Application->isAdminUser ) { $req_fields = Array (/*'PaymentCardType',*/ 'PaymentAccount', /*'PaymentNameOnCard',*/ 'PaymentCCExpDate'); } else { $req_fields = Array (/*'PaymentCardType',*/ 'PaymentAccount', /*'PaymentNameOnCard',*/ 'PaymentCCExpDate', 'PaymentCVV2'); } $order->setRequired($req_fields); } } /** * Set's order's user_id to user from session or Guest otherwise * * @param kEvent $event */ function CheckUser($event) { if ( $this->Application->isAdminUser || defined('GW_NOTIFY') || defined('CRON') ) { // 1. don't check, when Administrator is editing the order. // 2. don't check while processing payment gateways, because they can do cross-domain ssl redirects. // 3. don't check from CRON, because it's like Admin updates orders on other user behalf. return; } /** @var OrdersItem $order */ $order = $event->getObject(); $ses_user = $this->Application->RecallVar('user_id'); if ( $order->GetDBField('PortalUserId') != $ses_user ) { if ( $ses_user == 0 ) { $ses_user = USER_GUEST; } $order->SetDBField('PortalUserId', $ses_user); // since CheckUser is called in OnBeforeItemUpdate, we don't need to call udpate here, just set the field } } /* ======================== ADMIN ONLY ======================== */ /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $this->setNextOrderNumber($event); $object->SetDBField('OrderIP', $this->Application->getClientIp()); $order_type = $this->getTypeBySpecial( $this->Application->GetVar('order_type') ); $object->SetDBField('Status', $order_type); } /** * When cloning orders set new order number to them * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { parent::OnBeforeClone($event); /** @var OrdersItem $object */ $object = $event->getObject(); if ( substr($event->Special, 0, 9) == 'recurring' ) { $object->SetDBField('SubNumber', $object->getNextSubNumber()); } else { $this->setNextOrderNumber($event); } $reset_fields = array( 'OnHold', 'OrderDate', 'ShippingCost', 'ShippingTracking', 'ShippingDate', 'ReturnTotal', 'OriginalAmount', 'ShippingInfo', 'GWResult1', 'GWResult2', 'AffiliateCommission', 'ProcessingFee', 'InsuranceFee', ); foreach ( $reset_fields as $reset_field ) { $field_options = $object->GetFieldOptions($reset_field); $object->SetDBField($reset_field, $field_options['default']); } $object->SetDBField('OrderIP', $this->Application->getClientIp()); $object->UpdateFormattersSubFields(); } function OnReserveItems($event) { /** @var kDBList $order_items */ $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); $order_items->linkToParent('-inv'); // force re-query, since we are updateing through orditem ITEM, not the list, and // OnReserveItems may be called 2 times when fullfilling backorders through product edit - first time // from FullFillBackorders and second time from OnOrderProcess $order_items->Query(true); $order_items->GoFirst(); // query all combinations used in this order /** @var kCatDBItem $product_object */ $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); $product_object->SwitchToLive(); /** @var kDBItem $order_item */ $order_item = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); /** @var kDBItem $combination_item */ $combination_item = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); $combinations = $this->queryCombinations($order_items); $event->status = kEvent::erSUCCESS; while (!$order_items->EOL()) { $rec = $order_items->getCurrentRecord(); $product_object->Load( $rec['ProductId'] ); if (!$product_object->GetDBField('InventoryStatus')) { $order_items->GoNext(); continue; } $inv_object =& $this->getInventoryObject($product_object, $combination_item, $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ]); $lack = $rec['Quantity'] - $rec['QuantityReserved']; if ( $lack > 0 ) { // Reserve lack or what is available (in case if we need to reserve anything, by Alex). $to_reserve = min( $lack, $inv_object->GetDBField('QtyInStock') - $product_object->GetDBField('QtyInStockMin') ); // If we can't reserve the full lack. if ( $to_reserve < $lack ) { $event->status = kEvent::erFAIL; } // Reserve in order. $order_item->SetDBFieldsFromHash($rec); $order_item->SetDBField('QuantityReserved', $rec['QuantityReserved'] + $to_reserve); $new_lack = $order_item->GetDBField('Quantity') - $order_item->GetDBField('QuantityReserved'); $order_item->SetDBField('BackOrderFlag', abs($new_lack) <= 0.0001 ? 0 : 1); $order_item->SetId($rec['OrderItemId']); $order_item->Update(); // Update product - increase reserved, decrease in stock. $inv_object->SetDBField('QtyReserved', $inv_object->GetDBField('QtyReserved') + $to_reserve); $inv_object->SetDBField('QtyInStock', $inv_object->GetDBField('QtyInStock') - $to_reserve); $inv_object->SetDBField('QtyBackOrdered', $inv_object->GetDBField('QtyBackOrdered') - $to_reserve); $inv_object->Update(); if ( $product_object->GetDBField('InventoryStatus') == 2 ) { // Inventory by options, then restore changed combination // values back to common $combinations array !!! $combinations[$rec['ProductId'] . '_' . $rec['OptionsSalt']] = $inv_object->GetFieldValues(); } } $order_items->GoNext(); } return true; } function OnOrderPrint($event) { $event->SetRedirectParam('opener', 's'); } /** * Processes order each tab info resetting to other tab info / to user info * * @param kEvent $event * @access public */ function OnResetAddress($event) { $to_tab = $this->Application->GetVar('to_tab'); $from_tab = substr($event->Name, strlen('OnResetTo')); // load values from db /** @var kDBItem $object */ $object = $event->getObject(); // update values from submit $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->DoResetAddress($object, $from_tab, $to_tab); $object->Update(); $event->redirect = false; } /** * Processes item selection from popup item selector * * @todo Is this called ? (by Alex) * @param kEvent $event */ function OnProcessSelected($event) { $selected_ids = $this->Application->GetVar('selected_ids'); $product_ids = $selected_ids['p']; if ($product_ids) { $product_ids = explode(',', $product_ids); // !!! LOOK OUT - Adding items to Order in admin is handled in order_ITEMS_event_handler !!! foreach ($product_ids as $product_id) { $this->AddItemToOrder($event, $product_id); } } $event->SetRedirectParam('opener', 'u'); } function OnMassPlaceOrder($event) { $object = $event->getObject( Array('skip_autoload' => true) ); $ids = $this->StoreSelectedIDs($event); if($ids) { foreach($ids as $id) { $object->Load($id); $this->DoPlaceOrder($event); } } $event->status = kEvent::erSUCCESS; } /** * Universal * Checks if QtyInStock is enough to fullfill backorder (Qty - QtyReserved in order) * * @param int $ord_id * @return bool */ function ReadyToProcess($ord_id) { $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $query = ' SELECT SUM(IF( IF('.TABLE_PREFIX.'Products.InventoryStatus = 2, '.$poc_table.'.QtyInStock, '.TABLE_PREFIX.'Products.QtyInStock) - '.TABLE_PREFIX.'Products.QtyInStockMin >= ('.TABLE_PREFIX.'OrderItems.Quantity - '.TABLE_PREFIX.'OrderItems.QuantityReserved), 0, 1)) FROM '.TABLE_PREFIX.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Products ON '.TABLE_PREFIX.'Products.ProductId = '.TABLE_PREFIX.'OrderItems.ProductId LEFT JOIN '.$poc_table.' ON ('.$poc_table.'.CombinationCRC = '.TABLE_PREFIX.'OrderItems.OptionsSalt) AND ('.$poc_table.'.ProductId = '.TABLE_PREFIX.'OrderItems.ProductId) WHERE OrderId = '.$ord_id.' GROUP BY OrderId'; // IF (IF(InventoryStatus = 2, poc.QtyInStock, p.QtyInStock) - QtyInStockMin >= (Quantity - QuantityReserved), 0, 1 return ($this->Conn->GetOne($query) == 0); } /** * Return all option combinations used in order * * @param kDBList $order_items * @return Array */ function queryCombinations(&$order_items) { // 1. collect combination crc used in order $combinations = Array(); while (!$order_items->EOL()) { $row = $order_items->getCurrentRecord(); if ($row['OptionsSalt'] == 0) { $order_items->GoNext(); continue; } $combinations[] = '(poc.ProductId = '.$row['ProductId'].') AND (poc.CombinationCRC = '.$row['OptionsSalt'].')'; $order_items->GoNext(); } $order_items->GoFirst(); $combinations = array_unique($combinations); // if same combination+product found as backorder & normal order item if ($combinations) { // 2. query data about combinations $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $sql = 'SELECT CONCAT(poc.ProductId, "_", poc.CombinationCRC) AS CombinationKey, poc.* FROM '.$poc_table.' poc WHERE ('.implode(') OR (', $combinations).')'; return $this->Conn->Query($sql, 'CombinationKey'); } return Array(); } /** * Returns object to perform inventory actions on * * @param ProductsItem $product current product object in order * @param kDBItem $combination combination dummy object * @param Array $combination_data pre-queried combination data * @return kDBItem */ function &getInventoryObject(&$product, &$combination, $combination_data) { if ($product->GetDBField('InventoryStatus') == 2) { // inventory by option combinations $combination->SetDBFieldsFromHash($combination_data); $combination->setID($combination_data['CombinationId']); $change_item =& $combination; } else { // inventory by product ifself $change_item =& $product; } return $change_item; } /** * Approve order ("Pending" tab) * * @param kDBList $order_items * @return int new status of order if any */ function approveOrder(&$order_items) { $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); $order_item = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); $combination_item = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); $combinations = $this->queryCombinations($order_items); while (!$order_items->EOL()) { $rec = $order_items->getCurrentRecord(); $order_item->SetDBFieldsFromHash($rec); $order_item->SetId($rec['OrderItemId']); $order_item->SetDBField('QuantityReserved', 0); $order_item->Update(); $product_object->Load( $rec['ProductId'] ); if (!$product_object->GetDBField('InventoryStatus')) { // if no inventory info is collected, then skip this order item $order_items->GoNext(); continue; } $inv_object =& $this->getInventoryObject($product_object, $combination_item, $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ]); // decrease QtyReserved by amount of product used in order $inv_object->SetDBField('QtyReserved', $inv_object->GetDBField('QtyReserved') - $rec['Quantity']); $inv_object->Update(); if ($product_object->GetDBField('InventoryStatus') == 2) { // inventory by options, then restore changed combination values back to common $combinations array !!! $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ] = $inv_object->GetFieldValues(); } $order_items->GoNext(); } return true; } /** * Restores reserved items in the order * * @param kDBList $order_items * @return bool */ function restoreOrder(&$order_items) { /** @var kCatDBItem $product_object */ $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); $product_object->SwitchToLive(); /** @var kDBItem $order_item */ $order_item = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); /** @var kDBItem $combination_item */ $combination_item = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); $combinations = $this->queryCombinations($order_items); while( !$order_items->EOL() ) { $rec = $order_items->getCurrentRecord(); $product_object->Load( $rec['ProductId'] ); if (!$product_object->GetDBField('InventoryStatus')) { // if no inventory info is collected, then skip this order item $order_items->GoNext(); continue; } $inv_object =& $this->getInventoryObject($product_object, $combination_item, $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ]); // cancelling backorderd qty if any $lack = $rec['Quantity'] - $rec['QuantityReserved']; if ($lack > 0 && $rec['BackOrderFlag'] > 0) { // lack should have been recorded as QtyBackOrdered $inv_object->SetDBField('QtyBackOrdered', $inv_object->GetDBField('QtyBackOrdered') - $lack); } // canceling reservation in stock $inv_object->SetDBField('QtyReserved', $inv_object->GetDBField('QtyReserved') - $rec['QuantityReserved']); // putting remaining freed qty back to stock $inv_object->SetDBField('QtyInStock', $inv_object->GetDBField('QtyInStock') + $rec['QuantityReserved']); $inv_object->Update(); /** @var ProductsEventHandler $product_h */ $product_h = $this->Application->recallObject('p_EventHandler'); if ($product_object->GetDBField('InventoryStatus') == 2) { // inventory by options, then restore changed combination values back to common $combinations array !!! $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ] = $inv_object->GetFieldValues(); // using freed qty to fulfill possible backorders $product_h->FullfillBackOrders($product_object, $inv_object->GetID()); } else { // using freed qty to fulfill possible backorders $product_h->FullfillBackOrders($product_object, 0); } $order_item->SetDBFieldsFromHash($rec); $order_item->SetId($rec['OrderItemId']); $order_item->SetDBField('QuantityReserved', 0); $order_item->Update(); $order_items->GoNext(); } return true; } /** * Approve order + special processing * * @param kEvent $event */ function MassInventoryAction($event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } // process order products /** @var kDBItem $object */ $object = $this->Application->recallObject($event->Prefix . '.-inv', null, Array ('skip_autoload' => true)); $ids = $this->StoreSelectedIDs($event); if ( $ids ) { foreach ($ids as $id) { $object->Load($id); $this->InventoryAction($event); } } } function InventoryAction($event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return; } $event_status_map = Array( 'OnMassOrderApprove' => ORDER_STATUS_TOSHIP, 'OnOrderApprove' => ORDER_STATUS_TOSHIP, 'OnMassOrderDeny' => ORDER_STATUS_DENIED, 'OnOrderDeny' => ORDER_STATUS_DENIED, 'OnMassOrderArchive' => ORDER_STATUS_ARCHIVED, 'OnOrderArchive' => ORDER_STATUS_ARCHIVED, 'OnMassOrderShip' => ORDER_STATUS_PROCESSED, 'OnOrderShip' => ORDER_STATUS_PROCESSED, 'OnMassOrderProcess' => ORDER_STATUS_TOSHIP, 'OnOrderProcess' => ORDER_STATUS_TOSHIP, ); /** @var kDBList $order_items */ $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); $order_items->linkToParent('-inv'); $order_items->Query(); $order_items->GoFirst(); /** @var OrdersItem $object */ $object = $this->Application->recallObject($event->Prefix.'.-inv'); if ($object->GetDBField('OnHold')) { // any actions have no effect while on hold return ; } // save original order status $original_order_status = $object->GetDBField('Status'); // preparing new status, but not setting it yet $object->SetDBField('Status', $event_status_map[$event->Name]); $set_new_status = false; $event->status = kEvent::erSUCCESS; $email_params = $this->OrderEmailParams($object); switch ($event->Name) { case 'OnMassOrderApprove': case 'OnOrderApprove': $set_new_status = false; //on successful approve order will be split and new orders will have new statuses if ($object->GetDBField('ChargeOnNextApprove')) { $charge_info = $this->ChargeOrder($object); if (!$charge_info['result']) { break; } // removing ChargeOnNextApprove $object->SetDBField('ChargeOnNextApprove', 0); $sql = 'UPDATE '.$object->TableName.' SET ChargeOnNextApprove = 0 WHERE '.$object->IDField.' = '.$object->GetID(); $this->Conn->Query($sql); } // charge user for order in case if we user 2step charging (e.g. AUTH_ONLY + PRIOR_AUTH_CAPTURE) $gw_data = $object->getGatewayData(); /** @var kGWBase $gateway_object */ $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $charge_result = $gateway_object->Charge($object->GetFieldValues(), $gw_data['gw_params']); $sql = 'UPDATE %s SET GWResult2 = %s WHERE %s = %s'; $sql = sprintf($sql, $object->TableName, $this->Conn->qstr($gateway_object->getGWResponce()), $object->IDField, $object->GetID() ); $this->Conn->Query($sql); $object->SetDBField('GWResult2', $gateway_object->getGWResponce() ); if ($charge_result) { /** @var ProductsItem $product_object */ $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); foreach ($order_items->Records as $product_item) { if (!$product_item['ProductId']) { // product may have been deleted continue; } $product_object->Load($product_item['ProductId']); $hits = floor( $product_object->GetDBField('Hits') ) + 1; $sql = 'SELECT MAX(Hits) FROM '.$this->Application->getUnitOption('p', 'TableName').' WHERE FLOOR(Hits) = '.$hits; $hits = ( $res = $this->Conn->GetOne($sql) ) ? $res + 0.000001 : $hits; $product_object->SetDBField('Hits', $hits); $product_object->Update(); /*$sql = 'UPDATE '.$this->Application->getUnitOption('p', 'TableName').' SET Hits = Hits + '.$product_item['Quantity'].' WHERE ProductId = '.$product_item['ProductId']; $this->Conn->Query($sql);*/ } $this->PrepareCoupons($event, $object); $this->SplitOrder($event, $object); if ( $object->GetDBField('IsRecurringBilling') != 1 ) { $this->Application->emailUser('ORDER.APPROVE', null, $email_params); // Mask credit card with XXXX if ( $this->Application->ConfigValue('Comm_MaskProcessedCreditCards') ) { $this->maskCreditCard($object, 'PaymentAccount'); $set_new_status = 1; } } } break; case 'OnMassOrderDeny': case 'OnOrderDeny': foreach ($order_items->Records as $product_item) { if (!$product_item['ProductId']) { // product may have been deleted continue; } $this->raiseProductEvent('Deny', $product_item['ProductId'], $product_item); } if ( ($original_order_status != ORDER_STATUS_INCOMPLETE) && ($event->Name == 'OnMassOrderDeny' || $event->Name == 'OnOrderDeny') ) { $this->Application->emailUser('ORDER.DENY', null, $email_params); // inform payment gateway that order was declined $gw_data = $object->getGatewayData(); if ( $gw_data ) { /** @var kGWBase $gateway_object */ $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $gateway_object->OrderDeclined($object->GetFieldValues(), $gw_data['gw_params']); } } // !!! LOOK HERE !!! // !!!! no break !!!! here on purpose!!! case 'OnMassOrderArchive': case 'OnOrderArchive': // it's critical to update status BEFORE processing items because // FullfillBackorders could be called during processing and in case // of order denial/archive fullfill could reserve the qtys back for current backorder $object->Update(); $this->restoreOrder($order_items); $set_new_status = false; // already set break; case 'OnMassOrderShip': case 'OnOrderShip': $ret = Array (); $shipping_info = $object->GetDBField('ShippingInfo'); if ($shipping_info) { /** @var ShippingQuoteCollector $quote_engine_collector */ $quote_engine_collector = $this->Application->recallObject('ShippingQuoteCollector'); $shipping_info = unserialize($shipping_info); $sqe_class_name = $quote_engine_collector->GetClassByType($shipping_info, 1); } // try to create usps order if (($object->GetDBField('ShippingType') == 0) && ($sqe_class_name !== false)) { /** @var ShippingQuoteEngine $shipping_quote_engine */ $shipping_quote_engine = $this->Application->recallObject($sqe_class_name); $ret = $shipping_quote_engine->MakeOrder($object); } if ( !array_key_exists('error_number', $ret) ) { $set_new_status = $this->approveOrder($order_items); // $set_new_status = $this->shipOrder($order_items); $object->SetDBField('ShippingDate', adodb_mktime()); $object->UpdateFormattersSubFields(); $shipping_email = $object->GetDBField('ShippingEmail'); $email_params['to_email'] = $shipping_email ? $shipping_email : $email_params['_user_email']; $this->Application->emailUser('ORDER.SHIP', null, $email_params); // inform payment gateway that order was shipped $gw_data = $object->getGatewayData(); /** @var kGWBase $gateway_object */ $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $gateway_object->OrderShipped($object->GetFieldValues(), $gw_data['gw_params']); } else { $sqe_errors = $this->Application->RecallVar('sqe_errors'); $sqe_errors = $sqe_errors ? unserialize($sqe_errors) : Array (); $sqe_errors[ $object->GetField('OrderNumber') ] = $ret['error_description']; $this->Application->StoreVar('sqe_errors', serialize($sqe_errors)); } break; case 'OnMassOrderProcess': case 'OnOrderProcess': if ( $this->ReadyToProcess($object->GetID()) ) { $event->CallSubEvent('OnReserveItems'); if ( $event->status == kEvent::erSUCCESS ) { $set_new_status = true; } $this->Application->emailUser('BACKORDER.PROCESS', null, $email_params); } else { $event->status = kEvent::erFAIL; } break; } if ( $set_new_status ) { $object->Update(); } } /** * Hides last 4 digits from credit card number * * @param OrdersItem $object * @param string $field */ function maskCreditCard(&$object, $field) { $value = $object->GetDBField($field); $value = preg_replace('/'.substr($value, -4).'$/', str_repeat('X', 4), $value); $object->SetDBField($field, $value); } /** * Set next available order number. * * @param kEvent $event Event. * * @return void */ protected function setNextOrderNumber(kEvent $event) { /** @var OrdersItem $object */ $object = $event->getObject(); $next_order_number = $this->getNextOrderNumber(); $object->SetDBField('Number', $next_order_number); $object->SetDBField('SubNumber', 0); // set virtual field too $number_format = (int)$this->Application->ConfigValue('Comm_Order_Number_Format_P'); $sub_number_format = (int)$this->Application->ConfigValue('Comm_Order_Number_Format_S'); $order_number = sprintf('%0' . $number_format . 'd', $next_order_number) . '-' . str_repeat('0', $sub_number_format); $object->SetDBField('OrderNumber', $order_number); } /** * Returns order number to be used next. * * @return integer */ protected function getNextOrderNumber() { $config_table = $this->Application->getUnitOption('conf', 'TableName'); $this->Conn->Query('LOCK TABLES ' . $config_table . ' WRITE'); $sql = 'UPDATE ' . $config_table . ' SET VariableValue = VariableValue + 1 WHERE VariableName = "Comm_Next_Order_Number"'; $this->Conn->Query($sql); $sql = 'SELECT VariableValue FROM ' . $config_table . ' WHERE VariableName = "Comm_Next_Order_Number"'; $next_order_number = $this->Conn->GetOne($sql); $this->Conn->Query('UNLOCK TABLES'); $this->Application->SetConfigValue('Comm_Next_Order_Number', $next_order_number); return $next_order_number - 1; } /** * [HOOK] Ensures, that "Next Order Number" system setting is within allowed limits. * * @param kEvent $event Event. * * @return void */ protected function OnBeforeNextOrderNumberChange(kEvent $event) { /** @var kDBItem $system_setting */ $system_setting = $event->MasterEvent->getObject(); $old_value = $system_setting->GetOriginalField('VariableValue'); $new_value = $system_setting->GetDBField('VariableValue'); if ( $system_setting->GetDBField('VariableName') != 'Comm_Next_Order_Number' || $new_value == $old_value ) { return; } $sql = 'SELECT MAX(Number) FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName'); $next_order_number = (int)$this->Conn->GetOne($sql) + 1; if ( $new_value < $next_order_number ) { $system_setting->SetError('VariableValue', 'value_out_of_range', null, array( 'min_value' => $next_order_number, 'max_value' => '∞', )); } } /** * Set's new order address based on another address from order (e.g. billing from shipping) * * @param unknown_type $object * @param unknown_type $from * @param unknown_type $to */ function DoResetAddress(&$object, $from, $to) { $fields = Array('To','Company','Phone','Fax','Email','Address1','Address2','City','State','Zip','Country'); if ($from == 'User') { // skip these fields when coping from user, because they are not present in user profile $tmp_fields = array_flip($fields); // unset($tmp_fields['Company'], $tmp_fields['Fax'], $tmp_fields['Address2']); $fields = array_flip($tmp_fields); } // apply modification foreach ($fields as $field_name) { $object->SetDBField($to.$field_name, $object->GetDBField($from.$field_name)); } } /** * Set's status incomplete to all cloned orders * * @param kEvent $event * @return void * @access protected */ protected function OnAfterClone(kEvent $event) { parent::OnAfterClone($event); $id = $event->getEventParam('id'); $table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); // set cloned order status to Incomplete $sql = 'UPDATE ' . $table . ' SET Status = 0 WHERE ' . $id_field . ' = ' . $id; $this->Conn->Query($sql); } /* ======================== COMMON CODE ======================== */ /** * Split one timestamp field into 2 virtual fields * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); /** @var kDBItem $object */ $object = $event->getObject(); // get user fields $user_id = $object->GetDBField('PortalUserId'); if ( $user_id ) { $sql = 'SELECT *, CONCAT(FirstName,\' \',LastName) AS UserTo FROM ' . TABLE_PREFIX . 'Users WHERE PortalUserId = ' . $user_id; $user_info = $this->Conn->GetRow($sql); $fields = Array( 'UserTo'=>'UserTo','UserPhone'=>'Phone','UserFax'=>'Fax','UserEmail'=>'Email', 'UserAddress1'=>'Street','UserAddress2'=>'Street2','UserCity'=>'City','UserState'=>'State', 'UserZip'=>'Zip','UserCountry'=>'Country','UserCompany'=>'Company' ); foreach ($fields as $object_field => $user_field) { $object->SetDBField($object_field, $user_info[$user_field]); } } $object->SetDBField('PaymentCVV2', $this->Application->RecallVar('CVV2Code')); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $cs_helper->PopulateStates($event, 'ShippingState', 'ShippingCountry'); $cs_helper->PopulateStates($event, 'BillingState', 'BillingCountry'); $this->SetStepRequiredFields($event); // needed in OnAfterItemUpdate $this->Application->SetVar('OriginalShippingOption', $object->GetDBField('ShippingOption')); } /** * Processes states * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $cs_helper->PopulateStates($event, 'ShippingState', 'ShippingCountry'); $cs_helper->PopulateStates($event, 'BillingState', 'BillingCountry'); } /** * Processes states * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); /** @var OrdersItem $object */ $object = $event->getObject(); $old_payment_type = $object->GetOriginalField('PaymentType'); $new_payment_type = $object->GetDBField('PaymentType'); if ( $new_payment_type != $old_payment_type ) { // payment type changed -> check that it's allowed $available_payment_types = $this->Application->siteDomainField('PaymentTypes'); if ( $available_payment_types ) { if ( strpos($available_payment_types, '|' . $new_payment_type . '|') === false ) { // payment type isn't allowed in site domain $object->SetDBField('PaymentType', $old_payment_type); } } } /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $cs_helper->PopulateStates($event, 'ShippingState', 'ShippingCountry'); $cs_helper->PopulateStates($event, 'BillingState', 'BillingCountry'); if ( $object->HasTangibleItems() ) { $cs_helper->CheckStateField($event, 'ShippingState', 'ShippingCountry', false); } $cs_helper->CheckStateField($event, 'BillingState', 'BillingCountry', false); if ( $object->GetDBField('Status') > ORDER_STATUS_PENDING ) { return ; } $this->CheckUser($event); if ( !$object->GetDBField('OrderIP') ) { $object->SetDBField('OrderIP', $this->Application->getClientIp()); } $shipping_option = $this->Application->GetVar('OriginalShippingOption'); $new_shipping_option = $object->GetDBField('ShippingOption'); if ( $shipping_option != $new_shipping_option ) { $this->UpdateShippingOption($event); } else { $this->UpdateShippingTypes($event); } $this->RecalculateProcessingFee($event); $this->UpdateShippingTotal($event); $this->RecalculateGift($event); // guess fields from "One Step Checkout" form if ( $object->GetDBField('PaymentAccount') ) { /** @var OrderHelper $order_helper */ $order_helper = $this->Application->recallObject('OrderHelper'); $object->SetDBField( 'PaymentCardType', $order_helper->getCreditCardType($object->GetDBField('PaymentAccount')) ); } else { $object->SetDBField('PaymentCardType', ''); } if ( !$object->GetDBField('PaymentNameOnCard') ) { $object->SetDBField('PaymentNameOnCard', $object->GetDBField('BillingTo')); } if ( is_object($event->MasterEvent) && $event->MasterEvent->Name == 'OnUpdateAjax' && $this->Application->GetVar('create_account') && $object->Validate() ) { $this->createAccountFromOrder($event); } } /** * Creates user account * * @param kEvent $event * @return void * @access protected */ protected function createAccountFromOrder($event) { /** @var OrdersItem $order */ $order = $event->getObject(); /** @var OrderHelper $order_helper */ $order_helper = $this->Application->recallObject('OrderHelper'); $user_fields = $order_helper->getUserFields($order); $user_fields['Password'] = $order->GetDBField('UserPassword_plain'); $user_fields['VerifyPassword'] = $order->GetDBField('VerifyUserPassword_plain'); if ( $order->GetDBField('PortalUserId') == USER_GUEST ) { // will also auto-login user when created $this->Application->SetVar('u_register', Array (USER_GUEST => $user_fields)); $this->Application->HandleEvent(new kEvent('u.register:OnCreate')); } else { /** @var UsersItem $user */ $user = $this->Application->recallObject('u.current'); $user->SetFieldsFromHash($user_fields); if ( !$user->Update() ) { $order->SetError('BillingEmail', $user->GetErrorPseudo('Email')); } } } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); /** @var kDBList $object */ $object = $event->getObject(); $types = $event->getEventParam('types'); if ( $types == 'myorders' || $types == 'myrecentorders' ) { $user_id = $this->Application->RecallVar('user_id'); $object->addFilter('myitems_user1', '%1$s.PortalUserId = ' . $user_id); $object->addFilter('myitems_user2', '%1$s.PortalUserId > 0'); $object->addFilter('Status', '%1$s.Status != 0'); } else if ($event->Special == 'returns') { // $object->addFilter('returns_filter',TABLE_PREFIX.'Orders.Status = '.ORDER_STATUS_PROCESSED.' AND ( // SELECT SUM(ReturnType) // FROM '.TABLE_PREFIX.'OrderItems oi // WHERE oi.OrderId = '.TABLE_PREFIX.'Orders.OrderId // ) > 0'); $object->addFilter('returns_filter', TABLE_PREFIX . 'Orders.Status = ' . ORDER_STATUS_PROCESSED . ' AND ' . TABLE_PREFIX . 'Orders.ReturnTotal > 0'); } else if ( $event->Special == 'user' ) { $user_id = $this->Application->GetVar('u_id'); $object->addFilter('user_filter', '%1$s.PortalUserId = ' . $user_id); } else { $special = $event->Special ? $event->Special : $this->Application->GetVar('order_type'); if ( $special != 'search' ) { // don't filter out orders by special in case of search tab $object->addFilter('status_filter', '%1$s.Status=' . $this->getTypeBySpecial($special)); } if ( $event->getEventParam('selected_only') ) { $ids = $this->StoreSelectedIDs($event); $object->addFilter('selected_filter', '%1$s.OrderId IN (' . implode(',', $ids) . ')'); } } } function getTypeBySpecial($special) { $special2type = Array('incomplete'=>0,'pending'=>1,'backorders'=>2,'toship'=>3,'processed'=>4,'denied'=>5,'archived'=>6); return $special2type[$special]; } function getSpecialByType($type) { $type2special = Array(0=>'incomplete',1=>'pending',2=>'backorders',3=>'toship',4=>'processed',5=>'denied',6=>'archived'); return $type2special[$type]; } function LockTables($event) { $read = Array(); $write_lock = ''; $read_lock = ''; $write = Array('Orders','OrderItems','Products'); foreach ($write as $tbl) { $write_lock .= TABLE_PREFIX.$tbl.' WRITE,'; } foreach ($read as $tbl) { $read_lock .= TABLE_PREFIX.$tbl.' READ,'; } $write_lock = rtrim($write_lock, ','); $read_lock = rtrim($read_lock, ','); $lock = trim($read_lock.','.$write_lock, ','); //$this->Conn->Query('LOCK TABLES '.$lock); } /** * Checks shopping cart products quantities * * @param kEvent $event * @return bool */ function CheckQuantites($event) { if ( $this->OnRecalculateItems($event) ) { // if something has changed in the order if ( $this->Application->isAdminUser ) { if ( $this->UseTempTables($event) ) { $event->redirect = 'in-commerce/orders/orders_edit_items'; } } else { $event->redirect = $this->Application->GetVar('viewcart_template'); } return false; } return true; } function DoPlaceOrder($event) { $order = $event->getObject(); $table_prefix = $this->TablePrefix($event); $this->LockTables($event); if (!$this->CheckQuantites($event)) return false; //everything is fine - we could reserve items $this->ReserveItems($event); $this->SplitOrder($event, $order); return true; } function &queryOrderItems($event, $table_prefix) { $order = $event->getObject(); $ord_id = $order->GetId(); // TABLE_PREFIX and $table_prefix are NOT the same !!! $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $query = ' SELECT BackOrderFlag, '. $table_prefix.'OrderItems.OrderItemId, '. $table_prefix.'OrderItems.Quantity, '. $table_prefix.'OrderItems.QuantityReserved, IF('.TABLE_PREFIX.'Products.InventoryStatus = 2, '.$poc_table.'.QtyInStock, '.TABLE_PREFIX.'Products.QtyInStock) AS QtyInStock, '. TABLE_PREFIX.'Products.QtyInStockMin, '. $table_prefix.'OrderItems.ProductId, '. TABLE_PREFIX.'Products.InventoryStatus,'. $table_prefix.'OrderItems.OptionsSalt AS CombinationCRC FROM '.$table_prefix.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Products ON '.TABLE_PREFIX.'Products.ProductId = '.$table_prefix.'OrderItems.ProductId LEFT JOIN '.$poc_table.' ON ('.$poc_table.'.CombinationCRC = '.$table_prefix.'OrderItems.OptionsSalt) AND ('.$poc_table.'.ProductId = '.$table_prefix.'OrderItems.ProductId) WHERE OrderId = '.$ord_id.' AND '.TABLE_PREFIX.'Products.Type = 1 ORDER BY BackOrderFlag ASC'; $items = $this->Conn->Query($query); return $items; } function ReserveItems($event) { $table_prefix = $this->TablePrefix($event); $items =& $this->queryOrderItems($event, $table_prefix); foreach ($items as $an_item) { if (!$an_item['InventoryStatus']) { $to_reserve = $an_item['Quantity'] - $an_item['QuantityReserved']; } else { if ($an_item['BackOrderFlag'] > 0) { // we don't need to reserve if it's backordered item $to_reserve = 0; } else { $to_reserve = min($an_item['Quantity']-$an_item['QuantityReserved'], $an_item['QtyInStock']-$an_item['QtyInStockMin']); //it should be equal, but just in case } $to_backorder = $an_item['BackOrderFlag'] > 0 ? $an_item['Quantity']-$an_item['QuantityReserved'] : 0; } if ($to_backorder < 0) $to_backorder = 0; //just in case $query = ' UPDATE '.$table_prefix.'OrderItems SET QuantityReserved = IF(QuantityReserved IS NULL, '.$to_reserve.', QuantityReserved + '.$to_reserve.') WHERE OrderItemId = '.$an_item['OrderItemId']; $this->Conn->Query($query); if (!$an_item['InventoryStatus']) continue; $update_clause = ' QtyInStock = QtyInStock - '.$to_reserve.', QtyReserved = QtyReserved + '.$to_reserve.', QtyBackOrdered = QtyBackOrdered + '.$to_backorder; if ($an_item['InventoryStatus'] == 1) { // inventory by product, then update it's quantities $query = ' UPDATE '.TABLE_PREFIX.'Products SET '.$update_clause.' WHERE ProductId = '.$an_item['ProductId']; } else { // inventory = 2 -> by product option combinations $poc_idfield = $this->Application->getUnitOption('poc', 'IDField'); $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $query = ' UPDATE '.$poc_table.' SET '.$update_clause.' WHERE (ProductId = '.$an_item['ProductId'].') AND (CombinationCRC = '.$an_item['CombinationCRC'].')'; } $this->Conn->Query($query); } } function FreeItems($event) { $table_prefix = $this->TablePrefix($event); $items =& $this->queryOrderItems($event, $table_prefix); foreach ($items as $an_item) { $to_free = $an_item['QuantityReserved']; if ($an_item['InventoryStatus']) { if ($an_item['BackOrderFlag'] > 0) { // we don't need to free if it's backordered item $to_free = 0; } // what's not reserved goes to backorder in stock for orderitems marked with BackOrderFlag $to_backorder_free = $an_item['BackOrderFlag'] > 0 ? $an_item['Quantity'] - $an_item['QuantityReserved'] : 0; if ($to_backorder_free < 0) $to_backorder_free = 0; //just in case $update_clause = ' QtyInStock = QtyInStock + '.$to_free.', QtyReserved = QtyReserved - '.$to_free.', QtyBackOrdered = QtyBackOrdered - '.$to_backorder_free; if ($an_item['InventoryStatus'] == 1) { // inventory by product $query = ' UPDATE '.TABLE_PREFIX.'Products SET '.$update_clause.' WHERE ProductId = '.$an_item['ProductId']; } else { // inventory by option combinations $poc_idfield = $this->Application->getUnitOption('poc', 'IDField'); $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $query = ' UPDATE '.$poc_table.' SET '.$update_clause.' WHERE (ProductId = '.$an_item['ProductId'].') AND (CombinationCRC = '.$an_item['CombinationCRC'].')'; } $this->Conn->Query($query); } $query = ' UPDATE '.$table_prefix.'OrderItems SET QuantityReserved = IF(QuantityReserved IS NULL, 0, QuantityReserved - '.$to_free.') WHERE OrderItemId = '.$an_item['OrderItemId']; $this->Conn->Query($query); } } /** * Enter description here... * * @param kEvent $event * @param OrdersItem $object */ function SplitOrder($event, &$object) { $affiliate_event = new kEvent('affil:OnOrderApprove'); $affiliate_event->setEventParam('Order_PrefixSpecial', $object->getPrefixSpecial() ); $this->Application->HandleEvent($affiliate_event); $table_prefix = $this->TablePrefix($event); $order =& $object; $ord_id = $order->GetId(); $shipping_option = $order->GetDBField('ShippingOption'); $backorder_select = $shipping_option == 0 ? '0' : '%s.BackOrderFlag'; // setting PackageNum to 0 for Non-tangible items, for tangibles first package num is always 1 $query = ' SELECT oi.OrderItemId FROM ' . $table_prefix . 'OrderItems oi LEFT JOIN ' . TABLE_PREFIX . 'Products p ON p.ProductId = oi.ProductId WHERE p.Type > 1 AND oi.OrderId = ' . $ord_id; $non_tangibles = $this->Conn->GetCol($query); if ($non_tangibles) { $query = ' UPDATE ' . $table_prefix . 'OrderItems SET PackageNum = 0 WHERE OrderItemId IN (' . implode(',', $non_tangibles) . ')'; $this->Conn->Query($query); } // grouping_data: // 0 => Product Type // 1 => if NOT tangibale and NOT downloadable - OrderItemId, // 2 => ProductId // 3 => Shipping PackageNum $query = 'SELECT ' . sprintf($backorder_select, $table_prefix . 'OrderItems') . ' AS BackOrderFlagCalc, PackageNum, ProductName, ShippingTypeId, CONCAT('.TABLE_PREFIX.'Products.Type, "_", IF ('.TABLE_PREFIX.'Products.Type NOT IN ('.PRODUCT_TYPE_DOWNLOADABLE.','.PRODUCT_TYPE_TANGIBLE.'), CONCAT(OrderItemId, "_", '.TABLE_PREFIX.'Products.ProductId), ""), "_", PackageNum ) AS Grouping, SUM(Quantity) AS TotalItems, SUM('.$table_prefix.'OrderItems.Weight*Quantity) AS TotalWeight, SUM(Price * Quantity) AS TotalAmount, SUM(QuantityReserved) AS TotalReserved, '.TABLE_PREFIX.'Products.Type AS ProductType FROM '.$table_prefix.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Products ON '.TABLE_PREFIX.'Products.ProductId = '.$table_prefix.'OrderItems.ProductId WHERE OrderId = '.$ord_id.' GROUP BY BackOrderFlagCalc, Grouping ORDER BY BackOrderFlagCalc ASC, PackageNum ASC, ProductType ASC'; $sub_orders = $this->Conn->Query($query); $processed_sub_orders = Array(); // in case of recurring billing this will not be 0 as usual //$first_sub_number = ($event->Special == 'recurring') ? $object->getNextSubNumber() - 1 : 0; $first_sub_number = $object->GetDBField('SubNumber'); $next_sub_number = $first_sub_number; $group = 1; $order_has_gift = $order->GetDBField('GiftCertificateDiscount') > 0 ? 1 : 0; $skip_types = Array (PRODUCT_TYPE_TANGIBLE, PRODUCT_TYPE_DOWNLOADABLE); foreach ($sub_orders as $sub_order_data) { /** @var OrdersItem $sub_order */ $sub_order = $this->Application->recallObject('ord.-sub'.$next_sub_number, 'ord'); if ( $this->UseTempTables($event) && $next_sub_number == 0 ) { $sub_order =& $order; } else { foreach ( $order->GetFieldValues() as $field => $value ) { $sub_order->SetOriginalField($field, $value); } } $sub_order->SetDBFieldsFromHash($order->GetFieldValues()); $sub_order->SetDBField('SubNumber', $next_sub_number); $sub_order->SetDBField('SubTotal', $sub_order_data['TotalAmount']); $grouping_data = explode('_', $sub_order_data['Grouping']); $named_grouping_data['Type'] = $grouping_data[0]; if (!in_array($named_grouping_data['Type'], $skip_types)) { $named_grouping_data['OrderItemId'] = $grouping_data[1]; $named_grouping_data['ProductId'] = $grouping_data[2]; $named_grouping_data['PackageNum'] = $grouping_data[3]; } else { $named_grouping_data['PackageNum'] = $grouping_data[2]; } if ($named_grouping_data['Type'] == PRODUCT_TYPE_TANGIBLE) { $sub_order->SetDBField('ShippingCost', getArrayValue( unserialize($order->GetDBField('ShippingInfo')), $sub_order_data['PackageNum'], 'TotalCost') ); $sub_order->SetDBField('InsuranceFee', getArrayValue( unserialize($order->GetDBField('ShippingInfo')), $sub_order_data['PackageNum'], 'InsuranceFee') ); $sub_order->SetDBField('ShippingInfo', serialize(Array(1 => getArrayValue( unserialize($order->GetDBField('ShippingInfo')), $sub_order_data['PackageNum'])))); } else { $sub_order->SetDBField('ShippingCost', 0); $sub_order->SetDBField('InsuranceFee', 0); $sub_order->SetDBField('ShippingInfo', ''); //otherwise orders w/o shipping wills still have shipping info! } $amount_percent = $sub_order->getTotalAmount() * 100 / $order->getTotalAmount(); // proportional affiliate commission splitting if ($order->GetDBField('AffiliateCommission') > 0) { $sub_order->SetDBField('AffiliateCommission', $order->GetDBField('AffiliateCommission') * $amount_percent / 100 ); } $amount_percent = ($sub_order->GetDBField('SubTotal') + $sub_order->GetDBField('ShippingCost')) * 100 / ($order->GetDBField('SubTotal') + $order->GetDBField('ShippingCost')); if ($order->GetDBField('ProcessingFee') > 0) { $sub_order->SetDBField('ProcessingFee', round($order->GetDBField('ProcessingFee') * $amount_percent / 100, 2)); } $sub_order->RecalculateTax(); $original_amount = $sub_order->GetDBField('SubTotal') + $sub_order->GetDBField('ShippingCost') + $sub_order->GetDBField('VAT') + $sub_order->GetDBField('ProcessingFee') + $sub_order->GetDBField('InsuranceFee') - $sub_order->GetDBField('GiftCertificateDiscount'); $sub_order->SetDBField('OriginalAmount', $original_amount); if ($named_grouping_data['Type'] == 1 && ($sub_order_data['BackOrderFlagCalc'] > 0 || ($sub_order_data['TotalItems'] != $sub_order_data['TotalReserved'])) ) { $sub_order->SetDBField('Status', ORDER_STATUS_BACKORDERS); if ($event->Special != 'recurring') { // just in case if admin uses tangible backordered products in recurring orders $this->Application->emailUser('BACKORDER.ADD', null, $this->OrderEmailParams($sub_order)); $this->Application->emailAdmin('BACKORDER.ADD'); } } else { switch ($named_grouping_data['Type']) { case PRODUCT_TYPE_DOWNLOADABLE: $sql = 'SELECT oi.* FROM '.TABLE_PREFIX.'OrderItems oi LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId WHERE (OrderId = %s) AND (p.Type = '.PRODUCT_TYPE_DOWNLOADABLE.')'; $downl_products = $this->Conn->Query( sprintf($sql, $ord_id) ); $product_ids = Array(); foreach ($downl_products as $downl_product) { $this->raiseProductEvent('Approve', $downl_product['ProductId'], $downl_product, $next_sub_number); $product_ids[] = $downl_product['ProductId']; } break; case PRODUCT_TYPE_TANGIBLE: $sql = 'SELECT ' . sprintf($backorder_select, 'oi') . ' AS BackOrderFlagCalc, oi.* FROM ' . TABLE_PREFIX . 'OrderItems oi LEFT JOIN ' . TABLE_PREFIX . 'Products p ON p.ProductId = oi.ProductId WHERE (OrderId = %s) AND (p.Type = ' . PRODUCT_TYPE_TANGIBLE . ') HAVING BackOrderFlagCalc = 0'; $products = $this->Conn->Query( sprintf($sql, $ord_id) ); foreach ($products as $product) { $this->raiseProductEvent('Approve', $product['ProductId'], $product, $next_sub_number); } break; default: $order_item_fields = $this->Conn->GetRow('SELECT * FROM '.TABLE_PREFIX.'OrderItems WHERE OrderItemId = '.$named_grouping_data['OrderItemId']); $this->raiseProductEvent('Approve', $named_grouping_data['ProductId'], $order_item_fields, $next_sub_number); break; } $sub_order->SetDBField('Status', $named_grouping_data['Type'] == PRODUCT_TYPE_TANGIBLE ? ORDER_STATUS_TOSHIP : ORDER_STATUS_PROCESSED); } if ($next_sub_number == $first_sub_number) { $sub_order->SetId($order->GetId()); $sub_order->Update(); } else { $sub_order->Create(); } switch ($named_grouping_data['Type']) { case PRODUCT_TYPE_TANGIBLE: $query = ' UPDATE ' . $table_prefix . 'OrderItems SET OrderId = %s, PackageNum = 1 WHERE OrderId = %s AND PackageNum = %s'; $query = sprintf($query, $sub_order->GetID(), $ord_id, $sub_order_data['PackageNum']); break; case PRODUCT_TYPE_DOWNLOADABLE: $query = ' UPDATE ' . $table_prefix . 'OrderItems SET OrderId = %s, PackageNum = 1 WHERE OrderId = %s AND ProductId IN (%s)'; $query = sprintf($query, $sub_order->GetID(), $ord_id, implode(',', $product_ids)); break; default: $query = ' UPDATE ' . $table_prefix . 'OrderItems SET OrderId = %s, PackageNum = 1 WHERE OrderId = %s AND OrderItemId = %s'; $query = sprintf($query, $sub_order->GetID(), $ord_id, $named_grouping_data['OrderItemId']); break; } $this->Conn->Query($query); if ($order_has_gift) { // gift certificate can be applied only after items are assigned to suborder $sub_order->RecalculateGift($event); $original_amount = $sub_order->GetDBField('SubTotal') + $sub_order->GetDBField('ShippingCost') + $sub_order->GetDBField('VAT') + $sub_order->GetDBField('ProcessingFee') + $sub_order->GetDBField('InsuranceFee') - $sub_order->GetDBField('GiftCertificateDiscount'); $sub_order->SetDBField('OriginalAmount', $original_amount); $sub_order->Update(); } $processed_sub_orders[] = $sub_order->GetID(); $next_sub_number = $sub_order->getNextSubNumber(); $group++; } foreach ($processed_sub_orders as $sub_id) { // update DiscountTotal field $sql = 'SELECT SUM(ROUND(FlatPrice-Price,2)*Quantity) FROM '.$table_prefix.'OrderItems WHERE OrderId = '.$sub_id; $discount_total = $this->Conn->GetOne($sql); $sql = 'UPDATE '.$sub_order->TableName.' SET DiscountTotal = '.$this->Conn->qstr($discount_total).' WHERE OrderId = '.$sub_id; $this->Conn->Query($sql); } } /** * Call products linked event when spefcfic action is made to product in order * * @param string $event_type type of event to get from product ProcessingData = {Approve,Deny,CompleteOrder} * @param int $product_id ID of product to gather processing data from * @param Array $order_item_fields OrderItems table record fields (with needed product & order in it) */ function raiseProductEvent($event_type, $product_id, $order_item_fields, $next_sub_number=null) { $sql = 'SELECT ProcessingData FROM '.TABLE_PREFIX.'Products WHERE ProductId = '.$product_id; $processing_data = $this->Conn->GetOne($sql); if ($processing_data) { $processing_data = unserialize($processing_data); $event_key = getArrayValue($processing_data, $event_type.'Event'); // if requested type of event is defined for product, only then process it if ($event_key) { $event = new kEvent($event_key); $event->setEventParam('field_values', $order_item_fields); $event->setEventParam('next_sub_number', $next_sub_number); $this->Application->HandleEvent($event); } } } function OptionsSalt($options, $comb_only=false) { $helper = $this->Application->recallObject('kProductOptionsHelper'); return $helper->OptionsSalt($options, $comb_only); } /** * Enter description here... * * @param kEvent $event * @param int $item_id */ function AddItemToOrder($event, $item_id, $qty = null, $package_num = null) { if (!isset($qty)) { $qty = 1; } // Loading product to add /** @var kDBItem $product */ $product = $this->Application->recallObject('p.toadd', null, Array('skip_autoload' => true)); $product->Load($item_id); /** @var kDBItem $object */ $object = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); /** @var kDBItem $order */ $order = $this->Application->recallObject('ord'); if (!$order->isLoaded() && !$this->Application->isAdmin) { // no order was created before -> create one now if ($this->_createNewCart($event)) { $this->LoadItem($event); } } if (!$order->isLoaded()) { // was unable to create new order return false; } $item_data = $event->getEventParam('ItemData'); $item_data = $item_data ? unserialize($item_data) : Array (); $options = getArrayValue($item_data, 'Options'); if ( !$this->CheckOptions($event, $options, $item_id, $qty, $product->GetDBField('OptionsSelectionMode')) ) { return; } /** @var OrderManager $manager */ $manager = $this->Application->recallObject('OrderManager'); $manager->setOrder($order); $manager->addProduct($product, $event->getEventParam('ItemData'), $qty, $package_num); $this->Application->HandleEvent(new kEvent('ord:OnRecalculateItems')); } /** * Enter description here... * * @param kEvent $event */ function UpdateShippingTotal($event) { if ( $this->Application->GetVar('ebay_notification') == 1 ) { // TODO: get rid of this "if" return; } /** @var OrdersItem $object */ $object = $event->getObject(); $shipping_total = $insurance_fee = 0; $shipping_info = $object->GetDBField('ShippingInfo') ? unserialize($object->GetDBField('ShippingInfo')) : false; if ( is_array($shipping_info) ) { foreach ($shipping_info as $a_shipping) { // $id_elements = explode('_', $a_shipping['ShippingTypeId']); $shipping_total += $a_shipping['TotalCost']; $insurance_fee += $a_shipping['InsuranceFee']; } } $object->SetDBField('ShippingCost', $shipping_total); $object->SetDBField('InsuranceFee', $insurance_fee); // no need to update, it will be called in calling method $this->RecalculateTax($event); } /** * Recompile shopping cart, splitting or grouping orders and backorders depending on total quantities. * First it counts total qty for each ProductId, and then creates order for available items * and backorder for others. It also updates the sub-total for the order * * @param kEvent $event * @return bool Returns true if items splitting/grouping were changed */ function OnRecalculateItems($event) { if (is_object($event->MasterEvent) && ($event->MasterEvent->status != kEvent::erSUCCESS)) { // e.g. master order update failed, don't recalculate order products return ; } /** @var OrdersItem $order */ $order = $event->getObject(); if ( !$order->isLoaded() ) { $this->LoadItem($event); // try to load } $ord_id = (int)$order->GetID(); if ( !$order->isLoaded() ) return; //order has not been created yet if( $order->GetDBField('Status') != ORDER_STATUS_INCOMPLETE ) { return; } /** @var OrderManager $manager */ $manager = $this->Application->recallObject('OrderManager'); $manager->setOrder($order); $result = $manager->calculate(); if ( $order->GetDBField('CouponId') && $order->GetDBField('CouponDiscount') == 0 ) { $this->RemoveCoupon($order); $order->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_REMOVED_AUTOMATICALLY); } if ( $result ) { $this->UpdateShippingOption($event); } $this->UpdateShippingTotal($event); $this->RecalculateProcessingFee($event); $this->RecalculateTax($event); $this->RecalculateGift($event); if ( $event->Name != 'OnAfterItemUpdate' ) { $order->Update(); } $event->setEventParam('RecalculateChangedCart', $result); if ( is_object($event->MasterEvent) ) { $event->MasterEvent->setEventParam('RecalculateChangedCart', $result); } /*if ( $result && !getArrayValue($event->redirect_params, 'checkout_error') ) { $event->SetRedirectParam('checkout_error', OrderCheckoutError::STATE_CHANGED); }*/ if ( $result && is_object($event->MasterEvent) && $event->MasterEvent->Name == 'OnUserLogin' ) { $shop_cart_template = $this->Application->GetVar('shop_cart_template'); if ( $shop_cart_template && is_object($event->MasterEvent->MasterEvent) ) { // $event->MasterEvent->MasterEvent->SetRedirectParam('checkout_error', OrderCheckoutError::CHANGED_AFTER_LOGIN); $event->MasterEvent->MasterEvent->redirect = $shop_cart_template; } } return $result; } /* function GetShippingCost($user_country_id, $user_state_id, $user_zip, $weight, $items, $amount, $shipping_type) { $this->Application->recallObject('ShippingQuoteEngine'); $shipping_h = $this->Application->recallObject('CustomShippingQuoteEngine'); $query = $shipping_h->QueryShippingCost($user_country_id, $user_state_id, $user_zip, $weight, $items, $amount, $shipping_type); $cost = $this->Conn->GetRow($query); return $cost['TotalCost']; }*/ /** * Return product pricing id for given product, if not passed - return primary pricing ID * * @param int $product_id ProductId * @return float */ function GetPricingId($product_id, $item_data) { if (!is_array($item_data)) { $item_data = unserialize($item_data); } $price_id = getArrayValue($item_data, 'PricingId'); if (!$price_id) { $price_id = $this->Application->GetVar('pr_id'); } if (!$price_id){ $price_id = $this->Conn->GetOne('SELECT PriceId FROM '.TABLE_PREFIX.'ProductsPricing WHERE ProductId='.$product_id.' AND IsPrimary=1'); } return $price_id; } function UpdateShippingOption($event) { $object = $event->getObject(); $shipping_option = $object->GetDBField('ShippingOption'); if($shipping_option == '') return; $table_prefix = $this->TablePrefix($event); if ($shipping_option == 1 || $shipping_option == 0) { // backorder separately $query = 'UPDATE '.$table_prefix.'OrderItems SET BackOrderFlag = 1 WHERE OrderId = '.$object->GetId().' AND BackOrderFlag > 1'; $this->Conn->Query($query); } if ($shipping_option == 2) { $query = 'SELECT * FROM '.$table_prefix.'OrderItems WHERE OrderId = '.$object->GetId().' AND BackOrderFlag >= 1 ORDER By ProductName asc'; $items = $this->Conn->Query($query); $backorder_flag = 2; foreach ($items as $an_item) { $query = 'UPDATE '.$table_prefix.'OrderItems SET BackOrderFlag = '.$backorder_flag.' WHERE OrderItemId = '.$an_item['OrderItemId']; $this->Conn->Query($query); $backorder_flag++; } } } /** * Updates shipping types * * @param kEvent $event * @return bool */ function UpdateShippingTypes($event) { /** @var OrdersItem $object */ $object = $event->getObject(); $ord_id = $object->GetID(); $order_info = $this->Application->GetVar('ord'); $shipping_ids = getArrayValue($order_info, $ord_id, 'ShippingTypeId'); if (!$shipping_ids) { return; } $ret = true; $shipping_types = Array(); $last_shippings = unserialize( $this->Application->RecallVar('LastShippings') ); $template = $this->Application->GetVar('t'); $shipping_templates = Array ('in-commerce/checkout/shipping', 'in-commerce/orders/orders_edit_shipping'); /** @var ShippingQuoteCollector $quote_engine_collector */ $quote_engine_collector = $this->Application->recallObject('ShippingQuoteCollector'); foreach ($shipping_ids as $package => $id) { // try to validate $shipping_types[$package] = $last_shippings[$package][$id]; $sqe_class_name = $quote_engine_collector->GetClassByType($shipping_types, $package); if (($object->GetDBField('ShippingType') == 0) && ($sqe_class_name !== false) && in_array($template, $shipping_templates)) { /** @var ShippingQuoteEngine $shipping_quote_engine */ $shipping_quote_engine = $this->Application->recallObject($sqe_class_name); // USPS related part // TODO: remove USPS condition from here // set first of found shippings just to check if any errors are returned $current_usps_shipping_types = unserialize($this->Application->RecallVar('current_usps_shipping_types')); $object->SetDBField('ShippingInfo', serialize( Array($package => $current_usps_shipping_types[$id])) ); $sqe_data = $shipping_quote_engine->MakeOrder($object, true); if ( $sqe_data ) { if ( !isset($sqe_data['error_number']) ) { // update only international shipping if ( $object->GetDBField('ShippingCountry') != 'USA') { $shipping_types[$package]['TotalCost'] = $sqe_data['Postage']; } } else { $ret = false; $this->Application->StoreVar('sqe_error', $sqe_data['error_description']); } } $object->SetDBField('ShippingInfo', ''); } } $object->SetDBField('ShippingInfo', serialize($shipping_types)); return $ret; } /*function shipOrder(&$order_items) { $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); $order_item = $this->Application->recallObject('orditems.-item'); while( !$order_items->EOL() ) { $rec = $order_items->getCurrentRecord(); $order_item->SetDBFieldsFromHash($rec); $order_item->SetId($rec['OrderItemId']); $order_item->SetDBField('QuantityReserved', 0); $order_item->Update(); $order_items->GoNext(); } return true; }*/ function RecalculateTax($event) { /** @var OrdersItem $object */ $object = $event->getObject(); if ($object->GetDBField('Status') > ORDER_STATUS_PENDING) { return; } $object->RecalculateTax(); } function RecalculateProcessingFee($event) { $object = $event->getObject(); // Do not reset processing fee while orders are being split (see SplitOrder) if (preg_match("/^-sub/", $object->Special)) return; if ($object->GetDBField('Status') > ORDER_STATUS_PENDING) return; //no changes for orders other than incomple or pending $pt = $object->GetDBField('PaymentType'); $processing_fee = $this->Conn->GetOne('SELECT ProcessingFee FROM '.$this->Application->getUnitOption('pt', 'TableName').' WHERE PaymentTypeId = '.$pt); $object->SetDBField( 'ProcessingFee', $processing_fee ); $this->UpdateTotals($event); } function UpdateTotals($event) { /** @var OrdersItem $object */ $object = $event->getObject(); $object->UpdateTotals(); } /*function CalculateDiscount($event) { $object = $event->getObject(); $coupon = $this->Application->recallObject('coup', null, Array('skip_autoload' => true)); if(!$coupon->Load( $object->GetDBField('CouponId'), 'CouponId' )) { return false; } $sql = 'SELECT Price * Quantity AS Amount, ProductId FROM '.$this->Application->getUnitOption('orditems', 'TableName').' WHERE OrderId = '.$object->GetDBField('OrderId'); $orditems = $this->Conn->GetCol($sql, 'ProductId'); $sql = 'SELECT coupi.ItemType, p.ProductId FROM '.$this->Application->getUnitOption('coupi', 'TableName').' coupi LEFT JOIN '.$this->Application->getUnitOption('p', 'TableName').' p ON coupi.ItemResourceId = p.ResourceId WHERE CouponId = '.$object->GetDBField('CouponId'); $discounts = $this->Conn->GetCol($sql, 'ProductId'); $discount_amount = 0; foreach($orditems as $product_id => $amount) { if(isset($discounts[$product_id]) || array_search('0', $discounts, true) !== false) { switch($coupon->GetDBField('Type')) { case 1: $discount_amount += $coupon->GetDBField('Amount') < $amount ? $coupon->GetDBField('Amount') : $amount; break; case 2: $discount_amount += $amount * $coupon->GetDBField('Amount') / 100; break; default: } break; } } $object->SetDBField('CouponDiscount', $discount_amount); return $discount_amount; }*/ /** * Jumps to selected order in order's list from search tab * * @param kEvent $event */ function OnGoToOrder($event) { $id = current($this->StoreSelectedIDs($event)); $id_field = $this->Application->getUnitOption($event->Prefix,'IDField'); $table = $this->Application->getUnitOption($event->Prefix,'TableName'); $sql = 'SELECT Status FROM %s WHERE %s = %s'; $order_status = $this->Conn->GetOne( sprintf($sql, $table, $id_field, $id) ); $prefix_special = $event->Prefix.'.'.$this->getSpecialByType($order_status); /** @var kDBList $orders_list */ $orders_list = $this->Application->recallObject($prefix_special, $event->Prefix.'_List', Array('per_page'=>-1) ); $orders_list->Query(); foreach ($orders_list->Records as $row_num => $record) { if ( $record[$id_field] == $id ) { break; } } $per_page = $this->getPerPage( new kEvent($prefix_special.':OnDummy') ); $page = ceil( ($row_num+1) / $per_page ); $this->Application->StoreVar($prefix_special.'_Page', $page); $event->redirect = 'in-commerce/orders/orders_'.$this->getSpecialByType($order_status).'_list'; } /** * Reset's any selected order state to pending * * @param kEvent $event */ function OnResetToPending($event) { /** @var kDBItem $object */ $object = $event->getObject( Array('skip_autoload' => true) ); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { foreach ($items_info as $id => $field_values) { $object->Load($id); $object->SetDBField('Status', ORDER_STATUS_PENDING); if ( $object->Update() ) { $event->status = kEvent::erSUCCESS; } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } } } /** * Creates list from items selected in grid * * @param kEvent $event */ function OnLoadSelected($event) { $event->setPseudoClass('_List'); /** @var kDBList $object */ $object = $event->getObject(array('selected_only' => true, 'per_page' => -1)); $event->redirect = false; } /** * Return orders list, that will expire in time specified * * @param int $pre_expiration timestamp * @return Array */ function getRecurringOrders($pre_expiration) { $ord_table = $this->Application->getUnitOption('ord', 'TableName'); $ord_idfield = $this->Application->getUnitOption('ord', 'IDField'); $processing_allowed = Array(ORDER_STATUS_PROCESSED, ORDER_STATUS_ARCHIVED); $sql = 'SELECT '.$ord_idfield.', PortalUserId, GroupId, NextCharge FROM '.$ord_table.' WHERE (IsRecurringBilling = 1) AND (NextCharge < '.$pre_expiration.') AND Status IN ('.implode(',', $processing_allowed).')'; return $this->Conn->Query($sql, $ord_idfield); } /** * [SCHEDULED TASK] Checks what orders should expire and renew automatically (if such flag set) * * @param kEvent $event */ function OnCheckRecurringOrders($event) { $skip_clause = Array(); $ord_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $ord_idfield = $this->Application->getUnitOption($event->Prefix, 'IDField'); $pre_expiration = adodb_mktime() + $this->Application->ConfigValue('Comm_RecurringChargeInverval') * 3600 * 24; $to_charge = $this->getRecurringOrders($pre_expiration); if ($to_charge) { $order_ids = Array(); foreach ($to_charge as $order_id => $record) { // skip virtual users (e.g. root, guest, etc.) & invalid subscriptions (with no group specified, no next charge, but Recurring flag set) if (!$record['PortalUserId'] || !$record['GroupId'] || !$record['NextCharge']) continue; $order_ids[] = $order_id; // prevent duplicate user+group pairs $skip_clause[ 'PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'] ] = $order_id; } // process only valid orders $temp_handler = $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler'); $cloned_order_ids = $temp_handler->CloneItems($event->Prefix, 'recurring', $order_ids); $order =& $this->Application->recallObject($event->Prefix.'.recurring', null, Array('skip_autoload' => true)); foreach ($cloned_order_ids as $order_id) { $order->Load($order_id); $this->Application->HandleEvent($complete_event, $event->Prefix.'.recurring:OnCompleteOrder' ); if ($complete_event->status == kEvent::erSUCCESS) { //send recurring ok email $this->Application->emailUser('ORDER.RECURRING.PROCESSED', null, $this->OrderEmailParams($order)); $this->Application->emailAdmin('ORDER.RECURRING.PROCESSED'); } else { //send Recurring failed event $order->SetDBField('Status', ORDER_STATUS_DENIED); $order->Update(); $this->Application->emailUser('ORDER.RECURRING.DENIED', null, $this->OrderEmailParams($order)); $this->Application->emailAdmin('ORDER.RECURRING.DENIED'); } } // remove recurring flag from all orders found, not to select them next time script runs $sql = 'UPDATE '.$ord_table.' SET IsRecurringBilling = 0 WHERE '.$ord_idfield.' IN ('.implode(',', array_keys($to_charge)).')'; $this->Conn->Query($sql); } if ( !is_object($event->MasterEvent) ) { // not called as hook return ; } $pre_expiration = adodb_mktime() + $this->Application->ConfigValue('User_MembershipExpirationReminder') * 3600 * 24; $to_charge = $this->getRecurringOrders($pre_expiration); foreach ($to_charge as $order_id => $record) { // skip virtual users (e.g. root, guest, etc.) & invalid subscriptions (with no group specified, no next charge, but Recurring flag set) if (!$record['PortalUserId'] || !$record['GroupId'] || !$record['NextCharge']) continue; // prevent duplicate user+group pairs $skip_clause[ 'PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'] ] = $order_id; } $skip_clause = array_flip($skip_clause); $event->MasterEvent->setEventParam('skip_clause', $skip_clause); } function OnGeneratePDF($event) { $this->OnLoadSelected($event); $this->Application->InitParser(); $o = $this->Application->ParseBlock(array('name'=>'in-commerce/orders/orders_pdf')); /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $file_helper->CheckFolder(EXPORT_PATH); $htmlFile = EXPORT_PATH . '/tmp.html'; $fh = fopen($htmlFile, 'w'); fwrite($fh, $o); fclose($fh); // return; // require_once (FULL_PATH.'html2pdf/PDFEncryptor.php'); // Full path to the file to be converted // $htmlFile = dirname(__FILE__) . '/test.html'; // The default domain for images that use a relative path // (you'll need to change the paths in the test.html page // to an image on your server) $defaultDomain = DOMAIN; // Full path to the PDF we are creating $pdfFile = EXPORT_PATH . '/tmp.pdf'; // Remove old one, just to make sure we are making it afresh @unlink($pdfFile); $pdf_helper = $this->Application->recallObject('kPDFHelper'); $pdf_helper->FileToFile($htmlFile, $pdfFile); return ; // DOM PDF VERSION /*require_once(FULL_PATH.'/dompdf/dompdf_config.inc.php'); $dompdf = new DOMPDF(); $dompdf->load_html_file($htmlFile); if ( isset($base_path) ) { $dompdf->set_base_path($base_path); } $dompdf->set_paper($paper, $orientation); $dompdf->render(); file_put_contents($pdfFile, $dompdf->output()); return ;*/ // Instnatiate the class with our variables require_once (FULL_PATH.'/html2pdf/HTML_ToPDF.php'); $pdf = new HTML_ToPDF($htmlFile, $defaultDomain, $pdfFile); $pdf->setHtml2Ps('/usr/bin/html2ps'); $pdf->setPs2Pdf('/usr/bin/ps2pdf'); $pdf->setGetUrl('/usr/local/bin/curl -i'); // Set headers/footers $pdf->setHeader('color', 'black'); $pdf->setFooter('left', ''); $pdf->setFooter('right', '$D'); $pdf->setDefaultPath(BASE_PATH.'/kernel/admin_templates/'); $result = $pdf->convert(); // Check if the result was an error if (PEAR::isError($result)) { $this->Application->ApplicationDie($result->getMessage()); } else { $download_url = rtrim($this->Application->BaseURL(), '/') . EXPORT_BASE_PATH . '/tmp.pdf'; echo "PDF file created successfully: $result"; echo '<br />Click <a href="' . $download_url . '">here</a> to view the PDF file.'; } } /** * Occurs, when config was parsed, allows to change config data dynamically * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { return ; } $order_number = (int)$this->Application->ConfigValue('Comm_Order_Number_Format_P'); $order_sub_number = (int)$this->Application->ConfigValue('Comm_Order_Number_Format_S'); $calc_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields'); foreach ($calc_fields as $special => $fields) { $calc_fields[$special]['OrderNumber'] = str_replace('6', $order_number, $calc_fields[$special]['OrderNumber']); $calc_fields[$special]['OrderNumber'] = str_replace('3', $order_sub_number, $calc_fields[$special]['OrderNumber']); } $this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calc_fields); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $fields['Number']['format'] = str_replace('%06d', '%0'.$order_number.'d', $fields['Number']['format']); $fields['SubNumber']['format'] = str_replace('%03d', '%0'.$order_sub_number.'d', $fields['SubNumber']['format']); /** @var SiteHelper $site_helper */ $site_helper = $this->Application->recallObject('SiteHelper'); $fields['BillingCountry']['default'] = $site_helper->getDefaultCountry('Billing'); $fields['ShippingCountry']['default'] = $site_helper->getDefaultCountry('Shipping'); if (!$this->Application->isAdminUser) { $user_groups = explode(',', $this->Application->RecallVar('UserGroups')); $default_group = $this->Application->ConfigValue('User_LoggedInGroup'); if (!in_array($default_group, $user_groups)){ $user_groups[] = $default_group; } $sql_part = ''; // limit payment types by domain $payment_types = $this->Application->siteDomainField('PaymentTypes'); if (strlen($payment_types)) { $payment_types = explode('|', substr($payment_types, 1, -1)); $sql_part .= ' AND PaymentTypeId IN (' . implode(',', $payment_types) . ')'; } // limit payment types by user group $sql_part .= ' AND (PortalGroups LIKE "%%,'.implode(',%%" OR PortalGroups LIKE "%%,', $user_groups).',%%")'; $fields['PaymentType']['options_sql'] = str_replace( 'ORDER BY ', $sql_part . ' ORDER BY ', $fields['PaymentType']['options_sql'] ); } $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); $user_forms = $this->Application->getUnitOption('u', 'Forms'); $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); $virtual_fields['UserPassword']['hashing_method'] = $user_forms['default']['Fields']['PasswordHashingMethod']['default']; $this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields); } /** * Allows configuring export options * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeExportBegin(kEvent $event) { parent::OnBeforeExportBegin($event); /** @var kDBItem $object */ $object = $this->Application->recallObject($event->Prefix . '.export'); $object->SetField('Number', 999999); $object->SetField('SubNumber', 999); } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array * @access protected */ public function getCustomExportColumns(kEvent $event) { $columns = parent::getCustomExportColumns($event); $new_columns = Array ( '__VIRTUAL__CustomerName' => 'CustomerName', '__VIRTUAL__TotalAmount' => 'TotalAmount', '__VIRTUAL__AmountWithoutVAT' => 'AmountWithoutVAT', '__VIRTUAL__SubtotalWithDiscount' => 'SubtotalWithDiscount', '__VIRTUAL__SubtotalWithoutDiscount' => 'SubtotalWithoutDiscount', '__VIRTUAL__OrderNumber' => 'OrderNumber', ); return array_merge($columns, $new_columns); } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { parent::OnSave($event); if ( $event->status != kEvent::erSUCCESS ) { return ; } foreach ( $this->trackCopiedOrderIDs($event) as $id ) { $this->Application->removeObject($event->getPrefixSpecial()); $an_event = new kEvent($this->Prefix . ':Dummy'); $this->Application->SetVar($this->Prefix . '_id', $id); $this->Application->SetVar($this->Prefix . '_mode', ''); // this is to fool ReserveItems to use live table $this->ReserveItems($an_event); } } /** * Occurs after an item has been copied to live table * Id of copied item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToLive(kEvent $event) { parent::OnAfterCopyToLive($event); $this->trackCopiedOrderIDs($event, $event->getEventParam('id')); } /** * Tracks copied order IDs. * * @param kEvent $event Event. * @param integer $id Order ID. * * @return array */ protected function trackCopiedOrderIDs(kEvent $event, $id = null) { $setting_name = $event->Prefix . '_copied_ids' . $this->Application->GetVar('wid'); $ids = $this->Application->GetVar($setting_name, array()); if ( isset($id) ) { array_push($ids, $id); $this->Application->SetVar($setting_name, $ids); } return $ids; } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { if ( $this->Application->isAdminUser ) { return true; } /** @var kDBItem $object */ $object = $event->getObject(); if ( !$object->isLoaded() ) { return true; } return $object->GetDBField('PortalUserId') == $this->Application->RecallVar('user_id'); } // ===== Gift Certificates Related ===== /** * Enter description here... * * @param kEvent $event */ function OnApplyGiftCertificate($event) { $code = $this->Application->GetVar('giftcert_code'); if ( $code == '' ) { return; } /** @var OrdersItem $object */ $object = $event->getObject(); /** @var kDBItem $gift_certificate */ $gift_certificate = $this->Application->recallObject('gc', null, Array ('skip_autoload' => true)); $gift_certificate->Load($code, 'Code'); if ( !$gift_certificate->isLoaded() ) { $event->status = kEvent::erFAIL; $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_CODE_INVALID); $event->redirect = false; // check!!! return; } $debit = $gift_certificate->GetDBField('Debit'); $expire_date = $gift_certificate->GetDBField('Expiration'); if ( $gift_certificate->GetDBField('Status') != 1 || ($expire_date && $expire_date < adodb_mktime()) || ($debit <= 0) ) { $event->status = kEvent::erFAIL; $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_CODE_EXPIRED); $event->redirect = false; return; } $object->SetDBField('GiftCertificateId', $gift_certificate->GetDBField('GiftCertificateId')); $object->Update(); $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_APPLIED); } /** * Removes gift certificate from order * * @param kEvent $event * @deprecated */ function OnRemoveGiftCertificate($event) { /** @var OrdersItem $object */ $object = $event->getObject(); $this->RemoveGiftCertificate($object); $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_REMOVED); $event->CallSubEvent('OnRecalculateItems'); } function RemoveGiftCertificate(&$object) { $object->RemoveGiftCertificate(); } function RecalculateGift($event) { /** @var OrdersItem $object */ $object = $event->getObject(); if ($object->GetDBField('Status') > ORDER_STATUS_PENDING) { return ; } $object->RecalculateGift($event); } function GetWholeOrderGiftCertificateDiscount($gift_certificate_id) { if (!$gift_certificate_id) { return 0; } $sql = 'SELECT Debit FROM '.TABLE_PREFIX.'GiftCertificates WHERE GiftCertificateId = '.$gift_certificate_id; return $this->Conn->GetOne($sql); } /** * Downloads shipping tracking bar code, that was already generated by USPS service * * @param kEvent $event */ function OnDownloadLabel($event) { $event->status = kEvent::erSTOP; ini_set('memory_limit', '300M'); ini_set('max_execution_time', '0'); /** @var kDBItem $object */ $object = $event->getObject(); $file = $object->GetDBField('ShippingTracking') . '.pdf'; $full_path = USPS_LABEL_FOLDER . $file; if ( !file_exists($full_path) || !is_file($full_path) ) { return; } $this->Application->setContentType(kUtil::mimeContentType($full_path), false); header('Content-Disposition: attachment; filename="' . $file . '"'); readfile($full_path); } /** * Occurs before validation attempt * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemValidate(kEvent $event) { parent::OnBeforeItemValidate($event); $create_account = $this->Application->GetVar('create_account'); /** @var kDBItem $object */ $object = $event->getObject(); $required_fields = Array ('UserPassword', 'UserPassword_plain', 'VerifyUserPassword', 'VerifyUserPassword_plain'); $object->setRequired($required_fields, $create_account); $billing_email = $object->GetDBField('BillingEmail'); if ( $create_account && $object->GetDBField('PortalUserId') == USER_GUEST && $billing_email ) { // check that e-mail available $sql = 'SELECT PortalUserId FROM ' . TABLE_PREFIX . 'Users WHERE Email = ' . $this->Conn->qstr($billing_email); $user_id = $this->Conn->GetOne($sql); if ( $user_id ) { $object->SetError('BillingEmail', 'unique'); } } } /** * Performs order update and returns results in format, needed by FormManager * * @param kEvent $event Event. * * @return void */ protected function OnUpdateAjax(kEvent $event) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $ajax_form_helper->transitEvent($event, 'OnUpdate'); } /** * Performs order update after billing step submission and returns results in format, needed by FormManager * * @param kEvent $event Event. * * @return void */ protected function OnProceedToPreviewAjax(kEvent $event) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $ajax_form_helper->transitEvent($event, 'OnProceedToPreview'); } } Index: branches/5.2.x/units/products/products_event_handler.php =================================================================== --- branches/5.2.x/units/products/products_event_handler.php (revision 16692) +++ branches/5.2.x/units/products/products_event_handler.php (revision 16693) @@ -1,1600 +1,1600 @@ <?php /** * @version $Id$ * @package In-Commerce * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license Commercial License * This software is protected by copyright law and international treaties. * Unauthorized reproduction or unlicensed usage of the code of this program, * or any portion of it may result in severe civil and criminal penalties, * and will be prosecuted to the maximum extent possible under the law * See http://www.in-portal.org/commercial-license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ProductsEventHandler extends kCatDBEventHandler { /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array( // front 'OnCancelAction' => Array('self' => true), 'OnRateProduct' => Array('self' => true), 'OnClearRecent' => Array('self' => true), 'OnRecommendProduct' => Array('self' => true), 'OnAddToCompare' => Array('self' => true), 'OnRemoveFromCompare' => Array('self' => true), 'OnCancelCompare' => Array('self' => true), // admin 'OnQtyAdd' => Array('self' => 'add|edit'), 'OnQtyRemove' => Array('self' => 'add|edit'), 'OnQtyOrder' => Array('self' => 'add|edit'), 'OnQtyReceiveOrder' => Array('self' => 'add|edit'), 'OnQtyCancelOrder' => Array('self' => 'add|edit'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Define alternative event processing method names * * @return void * @see kEventHandler::$eventMethods * @access protected */ protected function mapEvents() { parent::mapEvents(); // ensure auto-adding of approve/decine and so on events $product_events = Array ( 'OnQtyAdd'=>'InventoryAction', 'OnQtyRemove'=>'InventoryAction', 'OnQtyOrder'=>'InventoryAction', 'OnQtyReceiveOrder'=>'InventoryAction', 'OnQtyCancelOrder'=>'InventoryAction', ); $this->eventMethods = array_merge($this->eventMethods, $product_events); } /** * Sets default processing data for subscriptions * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $product_approve_events = Array ( 2 => 'p:OnSubscriptionApprove', 4 => 'p:OnDownloadableApprove', 5 => 'p:OnPackageApprove' ); $product_type = $object->GetDBField('Type'); $type_found = in_array($product_type, array_keys($product_approve_events)); if ( $type_found && !$object->GetDBField('ProcessingData') ) { $processing_data = Array ('ApproveEvent' => $product_approve_events[$product_type]); $object->SetDBField('ProcessingData', serialize($processing_data)); } } /** * Process product count manipulations * * @param kEvent $event * @access private */ function InventoryAction($event) { /** @var kDBItem $object */ $object = $event->getObject(); $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ($object->GetDBField('InventoryStatus') == 2) { // inventory by options (use first selected combination in grid) $combinations = $this->Application->GetVar('poc_grid'); - list ($combination_id, ) = each($combinations); + $combination_id = key($combinations); } else { // inventory by product $combination_id = 0; } // save id of selected option combination & preselect it in grid $this->Application->SetVar('combination_id', $combination_id); $this->ScheduleInventoryAction($event->Name, $object->GetId(), $object->GetDBField('Qty'), $combination_id); $object->Validate(); if ( !$object->GetErrorPseudo('Qty') ){ // only update, when no error on that field $this->modifyInventory($event->Name, $object, $object->GetDBField('Qty'), $combination_id); } $object->SetDBField('Qty', null); $event->redirect = false; } /** * Perform inventory action on supplied object * * @param string $action event name which is actually called by user * @param ProductsItem $product * @param int $qty * @param int $combination_id */ function modifyInventory($action, &$product, $qty, $combination_id) { if ($product->GetDBField('InventoryStatus') == 2) { // save inventory changes to option combination instead of product $object = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); $object->Load($combination_id); } elseif ($combination_id > 0) { // combination id present, but not inventory by combinations => skip return false; } elseif ($product->GetDBField('InventoryStatus') == 1) { // save inventory changes to product $object =& $product; } else { // product has inventory actions, but don't use inventory => skip return false; } if (!$object->isLoaded()) { // product/combination in action doesn't exist in database by now return false; } switch ($action) { case 'OnQtyAdd': $object->SetDBField('QtyInStock', $object->GetDBField('QtyInStock') + $qty); break; case 'OnQtyRemove': if ($object->GetDBField('QtyInStock') < $qty) { $qty = $object->GetDBField('QtyInStock'); } $object->SetDBField('QtyInStock', $object->GetDBField('QtyInStock') - $qty); break; case 'OnQtyOrder': $object->SetDBField('QtyOnOrder', $object->GetDBField('QtyOnOrder') + $qty); break; case 'OnQtyReceiveOrder': $object->SetDBField('QtyOnOrder', $object->GetDBField('QtyOnOrder') - $qty); $object->SetDBField('QtyInStock', $object->GetDBField('QtyInStock') + $qty); break; case 'OnQtyCancelOrder': $object->SetDBField('QtyOnOrder', $object->GetDBField('QtyOnOrder') - $qty); break; } return $object->Update(); } function ScheduleInventoryAction($action, $prod_id, $qty, $combination_id = 0) { $inv_actions = $this->Application->RecallVar('inventory_actions'); if (!$inv_actions) { $inv_actions = Array(); } else { $inv_actions = unserialize($inv_actions); } array_push($inv_actions, Array('action' => $action, 'product_id' => $prod_id, 'combination_id' => $combination_id, 'qty' => $qty)); $this->Application->StoreVar('inventory_actions', serialize($inv_actions)); } function RealInventoryAction($action, $prod_id, $qty, $combination_id) { $product = $this->Application->recallObject('p.liveitem', null, Array('skip_autoload' => true)); $product->SwitchToLive(); $product->Load($prod_id); $this->modifyInventory($action, $product, $qty, $combination_id); } function RunScheduledInventoryActions($event) { $inv_actions = $this->Application->GetVar('inventory_actions'); if (!$inv_actions) { return; } $inv_actions = unserialize($inv_actions); $products = array(); foreach($inv_actions as $an_action) { $this->RealInventoryAction($an_action['action'], $an_action['product_id'], $an_action['qty'], $an_action['combination_id']); array_push($products, $an_action['product_id'].'_'.$an_action['combination_id']); } $products = array_unique($products); if ($products) { $product_obj = $this->Application->recallObject('p.liveitem', null, Array('skip_autoload' => true)); $product_obj->SwitchToLive(); foreach ($products as $product_key) { list($prod_id, $combination_id) = explode('_', $product_key); $product_obj->Load($prod_id); $this->FullfillBackOrders($product_obj, $combination_id); } } } /** * In case if products arrived into inventory and they are required by old (non processed) orders, then use them (products) in that orders * * @param ProductsItem $product * @param int $combination_id */ function FullfillBackOrders(&$product, $combination_id) { if ( !$this->Application->ConfigValue('Comm_Process_Backorders_Auto') ) return; if ($combination_id && ($product->GetDBField('InventoryStatus') == 2)) { // if combination id present and inventory by combinations $poc_idfield = $this->Application->getUnitOption('poc', 'IDField'); $poc_tablename = $this->Application->getUnitOption('poc', 'TableName'); $sql = 'SELECT QtyInStock FROM '.$poc_tablename.' WHERE '.$poc_idfield.' = '.$combination_id; $stock_qty = $this->Conn->GetOne($sql); } else { // inventory by product $stock_qty = $product->GetDBField('QtyInStock'); } $qty = (int) $stock_qty - $product->GetDBField('QtyInStockMin'); $prod_id = $product->GetID(); if ($prod_id <= 0 || !$prod_id || $qty <= 0) return; //selecting up to $qty backorders with $prod_id where full qty is not reserved $query = 'SELECT '.TABLE_PREFIX.'Orders.OrderId FROM '.TABLE_PREFIX.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Orders ON '.TABLE_PREFIX.'Orders.OrderId = '.TABLE_PREFIX.'OrderItems.OrderId WHERE (ProductId = '.$prod_id.') AND (Quantity > QuantityReserved) AND (Status = '.ORDER_STATUS_BACKORDERS.') GROUP BY '.TABLE_PREFIX.'Orders.OrderId ORDER BY OrderDate ASC LIMIT 0,'.$qty; //assuming 1 item per order - minimum possible $orders = $this->Conn->GetCol($query); if ( !$orders ) { return; } /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord.-inv', null, array('skip_autoload' => true)); foreach ($orders as $ord_id) { $order->Load($ord_id); $this->Application->emailAdmin('BACKORDER.FULLFILL'); // Reserve what's possible in any case. $reserve_event = new kEvent('ord:OnReserveItems'); $this->Application->HandleEvent($reserve_event); // In case the order is ready to process - process it. if ( $reserve_event->status == kEvent::erSUCCESS ) { $this->Application->HandleEvent(new kEvent('ord:OnOrderProcess')); } } } /** * Occurs before an item is deleted from live table when copying from temp * (temp handler deleted all items from live and then copy over all items from temp) * Id of item being deleted is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); /** @var kCatDBItem $product */ $product = $this->Application->recallObject($event->Prefix . '.itemlive', null, Array ('skip_autoload' => true)); $product->SwitchToLive(); $id = $event->getEventParam('id'); if ( !$product->Load($id) ) { // this will make sure New product will not be overwritten with empty data return ; } /** @var kCatDBItem $temp */ $temp = $this->Application->recallObject($event->Prefix . '.itemtemp', null, Array ('skip_autoload' => true)); $temp->SwitchToTemp(); $temp->Load($id); $temp->SetDBFieldsFromHash($product->GetFieldValues(), Array ('QtyInStock', 'QtyReserved', 'QtyBackOrdered', 'QtyOnOrder')); $temp->Update(); } /** * Removes any information about current/selected ids * from Application variables and Session * * @param kEvent $event * @return void * @access protected */ protected function clearSelectedIDs(kEvent $event) { parent::clearSelectedIDs($event); $this->Application->SetVar('inventory_actions', $this->Application->RecallVar('inventory_actions')); $this->Application->RemoveVar('inventory_actions'); } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { parent::OnSave($event); if ( $event->status == kEvent::erSUCCESS ) { $this->RunScheduledInventoryActions($event); } } /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::onPreCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('Type', $this->Application->GetVar($event->getPrefixSpecial(true) . '_new_type')); } /** * Saves edited item in temp table and loads * item with passed id in current template * Used in Prev/Next buttons * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndGo(kEvent $event) { $event->CallSubEvent('OnPreSave'); $this->LoadItem($event); /** @var kDBItem $object */ $object = $event->getObject(); $from_type = $object->GetDBField('Type'); if ( $event->status == kEvent::erSUCCESS ) { $this->Application->SetVar($event->getPrefixSpecial() . '_id', $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId')); $this->LoadItem($event); $to_type = $object->GetDBField('Type'); if ( $from_type != $to_type ) { $from_tabs = $this->GetTabs($from_type); $from_tab_i = array_search($this->Application->GetVar('t'), $from_tabs); $to_tabs = $this->GetTabs($to_type); $to_tab = $this->Application->GetVar('t'); $found = false; while (!isset($to_tabs[$from_tab_i]) && $from_tab_i < count($to_tabs)) { $from_tab_i++; } if ( !isset($to_tabs[$from_tab_i]) ) { $from_tab_i = 0; } $to_tab = $to_tabs[$from_tab_i]; $event->redirect = $to_tab; } } } function GetTabs($type) { switch($type) { case 1: return Array( 0 => 'in-commerce/products/products_edit', 1 => 'in-commerce/products/products_inventory', 2 => 'in-commerce/products/products_pricing', 3 => 'in-commerce/products/products_categories', 4 => 'in-commerce/products/products_images', 5 => 'in-commerce/products/products_reviews', 6 => 'in-commerce/products/products_custom', ); case 2: return Array( 0 => 'in-commerce/products/products_edit', 1 => 'in-commerce/products/products_access', /*2 => 'in-commerce/products/products_access_pricing',*/ 3 => 'in-commerce/products/products_categories', 4 => 'in-commerce/products/products_images', 5 => 'in-commerce/products/products_reviews', 6 => 'in-commerce/products/products_custom', ); case 3: return Array( 0 => 'in-commerce/products/products_edit', 2 => 'in-commerce/products/products_access_pricing', 3 => 'in-commerce/products/products_categories', 4 => 'in-commerce/products/products_images', 5 => 'in-commerce/products/products_reviews', 6 => 'in-commerce/products/products_custom', ); case 4: return Array( 0 => 'in-commerce/products/products_edit', 2 => 'in-commerce/products/products_files', 3 => 'in-commerce/products/products_categories', 4 => 'in-commerce/products/products_images', 5 => 'in-commerce/products/products_reviews', 6 => 'in-commerce/products/products_custom', ); } } /** * Return type clauses for list bulding on front * * @param kEvent $event * @return Array */ function getTypeClauses($event) { $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); $except_types = $event->getEventParam('except'); $except_types = $except_types ? explode(',', $except_types) : Array (); /** @var kDBList $object */ $object = $event->getObject(); $type_clauses = parent::getTypeClauses($event); $type_clauses['featured']['include'] = '%1$s.Featured = 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1'; $type_clauses['featured']['except'] = '%1$s.Featured != 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1'; $type_clauses['featured']['having_filter'] = false; $type_clauses['onsale']['include'] = '%1$s.OnSale = 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1'; $type_clauses['onsale']['except'] = '%1$s.OnSale != 1 AND ' . TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1'; $type_clauses['onsale']['having_filter'] = false; // products from selected manufacturer: begin $manufacturer = $event->getEventParam('manufacturer'); if ( !$manufacturer ) { $manufacturer = $this->Application->GetVar('manuf_id'); } if ( $manufacturer ) { $type_clauses['manufacturer']['include'] = '%1$s.ManufacturerId = ' . $manufacturer . ' AND PrimaryCat = 1'; $type_clauses['manufacturer']['except'] = '%1$s.ManufacturerId != ' . $manufacturer . ' AND PrimaryCat = 1'; $type_clauses['manufacturer']['having_filter'] = false; } // products from selected manufacturer: end // recent products: begin $recent = $this->Application->RecallVar('recent_products'); if ( $recent ) { $recent = unserialize($recent); $type_clauses['recent']['include'] = '%1$s.ProductId IN (' . implode(',', $recent) . ') AND PrimaryCat = 1'; $type_clauses['recent']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $recent) . ') AND PrimaryCat = 1'; } else { $type_clauses['recent']['include'] = '0'; $type_clauses['recent']['except'] = '1'; } $type_clauses['recent']['having_filter'] = false; // recent products: end // compare products: begin if ( in_array('compare', $types) || in_array('compare', $except_types) ) { $compare_products = $this->getCompareProducts(); if ( $compare_products ) { $compare_products = $this->Conn->qstrArray($compare_products); $type_clauses['compare']['include'] = '%1$s.ProductId IN (' . implode(',', $compare_products) . ') AND PrimaryCat = 1'; $type_clauses['compare']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $compare_products) . ') AND PrimaryCat = 1'; } else { $type_clauses['compare']['include'] = '0'; $type_clauses['compare']['except'] = '1'; } $type_clauses['compare']['having_filter'] = false; if ( $event->getEventParam('per_page') === false ) { $event->setEventParam('per_page', $this->Application->ConfigValue('MaxCompareProducts')); } } // compare products: end // products already in shopping cart: begin if ( in_array('in_cart', $types) || in_array('in_cart', $except_types) ) { $order_id = $this->Application->RecallVar('ord_id'); if ( $order_id ) { $sql = 'SELECT ProductId FROM ' . TABLE_PREFIX . 'OrderItems WHERE OrderId = ' . $order_id; $in_cart = $this->Conn->GetCol($sql); if ( $in_cart ) { $type_clauses['in_cart']['include'] = '%1$s.ProductId IN (' . implode(',', $in_cart) . ') AND PrimaryCat = 1'; $type_clauses['in_cart']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $in_cart) . ') AND PrimaryCat = 1'; } else { $type_clauses['in_cart']['include'] = '0'; $type_clauses['in_cart']['except'] = '1'; } } else { $type_clauses['in_cart']['include'] = '0'; $type_clauses['in_cart']['except'] = '1'; } $type_clauses['in_cart']['having_filter'] = false; } // products already in shopping cart: end // my downloadable products: begin if ( in_array('my_downloads', $types) || in_array('my_downloads', $except_types) ) { $user_id = $this->Application->RecallVar('user_id'); $sql = 'SELECT ProductId FROM ' . TABLE_PREFIX . 'UserFileAccess WHERE PortalUserId = ' . $user_id; $my_downloads = $user_id > 0 ? $this->Conn->GetCol($sql) : false; if ( $my_downloads ) { $type_clauses['my_downloads']['include'] = '%1$s.ProductId IN (' . implode(',', $my_downloads) . ') AND PrimaryCat = 1'; $type_clauses['my_downloads']['except'] = '%1$s.ProductId NOT IN (' . implode(',', $my_downloads) . ') AND PrimaryCat = 1'; } else { $type_clauses['my_downloads']['include'] = '0'; $type_clauses['my_downloads']['except'] = '1'; } $type_clauses['my_downloads']['having_filter'] = false; } // my downloadable products: end // my favorite products: begin if ( in_array('wish_list', $types) || in_array('wish_list', $except_types) ) { $sql = 'SELECT ResourceId FROM ' . $this->Application->getUnitOption('fav', 'TableName') . ' WHERE PortalUserId = ' . (int)$this->Application->RecallVar('user_id'); $wishlist_ids = $this->Conn->GetCol($sql); if ( $wishlist_ids ) { $type_clauses['wish_list']['include'] = '%1$s.ResourceId IN (' . implode(',', $wishlist_ids) . ') AND PrimaryCat = 1'; $type_clauses['wish_list']['except'] = '%1$s.ResourceId NOT IN (' . implode(',', $wishlist_ids) . ') AND PrimaryCat = 1'; } else { $type_clauses['wish_list']['include'] = '0'; $type_clauses['wish_list']['except'] = '1'; } $type_clauses['wish_list']['having_filter'] = false; } // my favorite products: end // products from package: begin if ( in_array('content', $types) || in_array('content', $except_types) ) { $object->removeFilter('category_filter'); $object->AddGroupByField('%1$s.ProductId'); /** @var ProductsItem $object_product */ $object_product = $this->Application->recallObject($event->Prefix); $content_ids_array = $object_product->GetPackageContentIds(); if ( sizeof($content_ids_array) == 0 ) { $content_ids_array = array ('-1'); } if ( sizeof($content_ids_array) > 0 ) { $type_clauses['content']['include'] = '%1$s.ProductId IN (' . implode(',', $content_ids_array) . ')'; } else { $type_clauses['content']['include'] = '0'; } $type_clauses['related']['having_filter'] = false; } // products from package: end $object->addFilter('not_virtual', '%1$s.Virtual = 0'); if ( !$this->Application->isAdminUser ) { $object->addFilter('expire_filter', '%1$s.Expire IS NULL OR %1$s.Expire > ' . adodb_mktime()); } return $type_clauses; } function OnClearRecent($event) { $this->Application->RemoveVar('recent_products'); } /** * Occurs, when user rates a product * * @param kEvent $event */ function OnRateProduct($event) { $event->SetRedirectParam('pass', 'all,p'); $event->redirect = $this->Application->GetVar('success_template'); /** @var kDBItem $object */ $object = $event->getObject(); $user_id = $this->Application->RecallVar('user_id'); $sql = ' SELECT * FROM ' . TABLE_PREFIX . 'SpamControl WHERE ItemResourceId=' . $object->GetDBField('ResourceId') . ' AND IPaddress="' . $this->Application->getClientIp() . '" AND PortalUserId=' . $user_id . ' AND DataType="Rating"'; $res = $this->Conn->GetRow($sql); if ( $res && $res['Expire'] < adodb_mktime() ) { $sql = ' DELETE FROM ' . TABLE_PREFIX . 'SpamControl WHERE ItemResourceId=' . $object->GetDBField('ResourceId') . ' AND IPaddress="' . $this->Application->getClientIp() . '" AND PortalUserId=' . $user_id . ' AND DataType="Rating"'; $this->Conn->Query($sql); unset($res); } $new_rating = $this->Application->GetVar('rating'); if ( $new_rating !== false && !$res ) { $rating = $object->GetDBField('CachedRating'); $votes = $object->GetDBField('CachedVotesQty'); $new_votes = $votes + 1; $rating = (($rating * $votes) + $new_rating) / $new_votes; $object->SetDBField('CachedRating', $rating); $object->SetDBField('CachedVotesQty', $new_votes); $object->Update(); $expire = adodb_mktime() + $this->Application->ConfigValue('product_ReviewDelay_Value') * $this->Application->ConfigValue('product_ReviewDelay_Interval'); $sql = ' INSERT INTO ' . TABLE_PREFIX . 'SpamControl (ItemResourceId, IPaddress, PortalUserId, DataType, Expire) VALUES (' . $object->GetDBField('ResourceId') . ', "' . $this->Application->getClientIp() . '", ' . $user_id . ', "Rating", ' . $expire . ')'; $this->Conn->Query($sql); } else { $event->status == kEvent::erFAIL; $event->redirect = false; $object->SetError('CachedRating', 'too_frequent', 'lu_ferror_rate_duplicate'); } } /** * Enter description here... * * @param kEvent $event */ function OnCancelAction($event) { $event->SetRedirectParam('pass', 'all,p'); $event->redirect = $this->Application->GetVar('cancel_template'); } /** * Enter description here... * * @param kEvent $event */ function OnRecommendProduct($event) { // used for error reporting only -> rewrite code + theme (by Alex) $object = $this->Application->recallObject('u', null, Array('skip_autoload' => true)); // TODO: change theme too /** @var kDBItem $object */ $friend_email = $this->Application->GetVar('friend_email'); $friend_name = $this->Application->GetVar('friend_name'); $my_email = $this->Application->GetVar('your_email'); $my_name = $this->Application->GetVar('your_name'); $my_message = $this->Application->GetVar('your_message'); $send_params = array(); $send_params['to_email']=$friend_email; $send_params['to_name']=$friend_name; $send_params['from_email']=$my_email; $send_params['from_name']=$my_name; $send_params['message']=$my_message; if ( preg_match('/' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . '/', $friend_email) ) { $user_id = $this->Application->RecallVar('user_id'); $email_sent = $this->Application->emailUser('PRODUCT.SUGGEST', $user_id, $send_params); $this->Application->emailAdmin('PRODUCT.SUGGEST'); if ( $email_sent ) { $event->setRedirectParams(Array ('opener' => 's', 'pass' => 'all')); $event->redirect = $this->Application->GetVar('template_success'); } else { // $event->setRedirectParams(Array('opener' => 's', 'pass' => 'all')); // $event->redirect = $this->Application->GetVar('template_fail'); $object->SetError('Email', 'send_error', 'lu_email_send_error'); $event->status = kEvent::erFAIL; } } else { $object->SetError('Email', 'invalid_email', 'lu_InvalidEmail'); $event->status = kEvent::erFAIL; } } /** * Creates/updates virtual product based on listing type data * * @param kEvent $event */ function OnSaveVirtualProduct($event) { $object = $event->getObject( Array('skip_autoload' => true) ); $listing_type = $this->Application->recallObject('lst', null, Array('skip_autoload' => true)); $listing_type->Load($event->MasterEvent->getEventParam('id')); $product_id = $listing_type->GetDBField('VirtualProductId'); if ($product_id) { $object->Load($product_id); } if (!$listing_type->GetDBField('EnableBuying')) { if ($product_id) { // delete virtual product here $temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $temp_handler->DeleteItems($event->Prefix, $event->Special, Array($product_id)); $listing_type->SetDBField('VirtualProductId', 0); $listing_type->Update(); } return true; } $ml_formatter = $this->Application->recallObject('kMultiLanguage'); $object->SetDBField($ml_formatter->LangFieldName('Name'), $listing_type->GetDBField('ShopCartName') ); $object->SetDBField($ml_formatter->LangFieldName('Description'), $listing_type->GetDBField('Description')); $object->SetDBField('SKU', 'ENHANCE_LINK_'.abs( crc32( $listing_type->GetDBField('Name') ) ) ); if ($product_id) { $object->Update(); } else { $object->SetDBField('Type', 2); $object->SetDBField('Status', 1); $object->SetDBField('HotItem', 0); $object->SetDBField('PopItem', 0); $object->SetDBField('NewItem', 0); $object->SetDBField('Virtual', 1); // $processing_data = Array('ApproveEvent' => 'ls:EnhanceLinkAfterOrderApprove', 'ExpireEvent' => 'ls:ExpireLink'); $processing_data = Array( 'ApproveEvent' => 'ls:EnhanceLinkAfterOrderApprove', 'DenyEvent' => 'ls:EnhanceLinkAfterOrderDeny', 'CompleteOrderEvent' => 'ls:EnhancedLinkOnCompleteOrder', 'ExpireEvent' => 'ls:ExpireLink', 'HasNewProcessing' => 1); $object->SetDBField('ProcessingData', serialize($processing_data)); $object->Create(); $listing_type->SetDBField('VirtualProductId', $object->GetID()); $listing_type->Update(); } $additiona_fields = Array( 'AccessDuration' => $listing_type->GetDBField('Duration'), 'AccessUnit' => $listing_type->GetDBField('DurationType'), ); $this->setPrimaryPrice($object->GetID(), (double)$listing_type->GetDBField('Price'), $additiona_fields); } /** * [HOOK] Deletes virtual product when listing type is deleted * * @param kEvent $event */ function OnDeleteListingType($event) { /** @var kDBItem $listing_type */ $listing_type = $event->MasterEvent->getObject(); $product_id = $listing_type->GetDBField('VirtualProductId'); if ( $product_id ) { $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); $temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($product_id)); } } /** * Extends user membership in group when his order is approved * * @param kEvent $event */ function OnSubscriptionApprove($event) { $field_values = $event->getEventParam('field_values'); $item_data = unserialize($field_values['ItemData']); if ( !getArrayValue($item_data,'PortalGroupId') ) { // is subscription product, but no group defined in it's properties trigger_error('Invalid product <b>'.$field_values['ProductName'].'</b> (id: '.$field_values['ProductId'].')', E_USER_WARNING); return false; } $sql = 'SELECT PortalUserId FROM ' . $this->Application->getUnitOption('ord', 'TableName') . ' WHERE ' . $this->Application->getUnitOption('ord', 'IDField') . ' = ' . $field_values['OrderId']; $user_id = $this->Conn->GetOne($sql); $group_id = $item_data['PortalGroupId']; $duration = $item_data['Duration']; $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'UserGroupRelations WHERE PortalUserId = ' . $user_id; $user_groups = $this->Conn->Query($sql, 'GroupId'); if ( !isset($user_groups[$group_id]) ) { $expire = adodb_mktime() + $duration; } else { $expire = $user_groups[$group_id]['MembershipExpires']; $expire = $expire < adodb_mktime() ? adodb_mktime() + $duration : $expire + $duration; } /*// Customization healtheconomics.org if ($item_data['DurationType'] == 2) { $expire = $item_data['AccessExpiration']; } // Customization healtheconomics.org --*/ $fields_hash = Array ( 'PortalUserId' => $user_id, 'GroupId' => $group_id, 'MembershipExpires' => $expire, ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserGroupRelations', 'REPLACE'); $sub_order = $this->Application->recallObject('ord.-sub'.$event->getEventParam('next_sub_number'), 'ord'); $sub_order->SetDBField('IsRecurringBilling', getArrayValue($item_data, 'IsRecurringBilling') ? 1 : 0); $sub_order->SetDBField('GroupId', $group_id); $sub_order->SetDBField('NextCharge_date', $expire); $sub_order->SetDBField('NextCharge_time', $expire); } function OnDownloadableApprove($event) { $field_values = $event->getEventParam('field_values'); $product_id = $field_values['ProductId']; $sql = 'SELECT PortalUserId FROM '.$this->Application->getUnitOption('ord', 'TableName').' WHERE OrderId = '.$field_values['OrderId']; $user_id = $this->Conn->GetOne($sql); $sql = 'INSERT INTO '.TABLE_PREFIX.'UserFileAccess VALUES("", '.$product_id.', '.$user_id.')'; $this->Conn->Query($sql); } protected function OnPackageApprove(kEvent $event) { $field_values = $event->getEventParam('field_values'); $item_data = unserialize($field_values['ItemData']); $package_content_ids = $item_data['PackageContent']; /** @var ProductsItem $object_item */ $object_item = $this->Application->recallObject('p.packageitem', null, array ('skip_autoload' => true)); foreach ($package_content_ids as $package_item_id) { $object_field_values = array (); // query processing data from product and run approve event $sql = 'SELECT ProcessingData FROM ' . TABLE_PREFIX . 'Products WHERE ProductId = ' . $package_item_id; $processing_data = $this->Conn->GetOne($sql); if ( $processing_data ) { $processing_data = unserialize($processing_data); $approve_event = new kEvent($processing_data['ApproveEvent']); //$order_item_fields = $this->Conn->GetRow('SELECT * FROM '.TABLE_PREFIX.'OrderItems WHERE OrderItemId = '.$grouping_data[1]); $object_item->Load($package_item_id); $object_field_values['OrderId'] = $field_values['OrderId']; $object_field_values['ProductId'] = $package_item_id; $object_field_values['ItemData'] = serialize($item_data['PackageItemsItemData'][$package_item_id]); $approve_event->setEventParam('field_values', $object_field_values); $this->Application->HandleEvent($approve_event); } } } /** * Saves edited item into temp table * If there is no id, new item is created in temp table * * @param kEvent $event * @return void * @access protected */ protected function OnPreSave(kEvent $event) { $this->CheckRequiredOptions($event); parent::OnPreSave($event); } /** * Set new price to ProductsPricing * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); $this->_updateProductPrice($event); } /** * Set new price to ProductsPricing * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); $this->_updateProductPrice($event); } /** * Updates product's primary price based on Price virtual field value * * @param kEvent $event */ function _updateProductPrice($event) { /** @var kDBItem $object */ $object = $event->getObject(); $price = $object->GetDBField('Price'); // always create primary pricing, to show on Pricing tab (in admin) for tangible products $force_create = ($object->GetDBField('Type') == PRODUCT_TYPE_TANGIBLE) && is_null($price); if ($force_create || ($price != $object->GetOriginalField('Price'))) { // new product OR price was changed in virtual field $this->setPrimaryPrice($object->GetID(), (float)$price); } } function CheckRequiredOptions($event) { $object = $event->getObject(); if ($object->GetDBField('ProductId') == '') return ; // if product does not have ID - it's not yet created $opt_object = $this->Application->recallObject('po', null, Array('skip_autoload' => true) ); $has_required = $this->Conn->GetOne('SELECT COUNT(*) FROM '.$opt_object->TableName.' WHERE Required = 1 AND ProductId = '.$object->GetDBField('ProductId')); //we need to imitate data sumbit, as parent' PreSave sets object values from $items_info $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); $items_info[$object->GetDBField('ProductId')]['HasRequiredOptions'] = $has_required ? '1' : '0'; $this->Application->SetVar($event->getPrefixSpecial(true), $items_info); $object->SetDBField('HasRequiredOptions', $has_required ? 1 : 0); } /** * Sets required price in primary price backed, if it's missing, then create it * * @param int $product_id * @param double $price * @param Array $additional_fields * @return bool */ function setPrimaryPrice($product_id, $price, $additional_fields = Array()) { /** @var kDBItem $pr_object */ $pr_object = $this->Application->recallObject('pr.-item', null, Array('skip_autoload' => true) ); $pr_object->Load( Array('ProductId' => $product_id, 'IsPrimary' => 1) ); $sql = 'SELECT COUNT(*) FROM '.$pr_object->TableName.' WHERE ProductId = '.$product_id; $has_pricings = $this->Conn->GetOne($sql); if ($additional_fields) { $pr_object->SetDBFieldsFromHash($additional_fields); } if( ($price === false) && $has_pricings ) return false; if( $pr_object->isLoaded() ) { $pr_object->SetField('Price', $price); return $pr_object->Update(); } else { $group_id = $this->Application->ConfigValue('User_LoggedInGroup'); $field_values = Array('ProductId' => $product_id, 'IsPrimary' => 1, 'MinQty' => 1, 'MaxQty' => -1, 'GroupId'=>$group_id); $pr_object->SetDBFieldsFromHash($field_values); $pr_object->SetField('Price', $price); return $pr_object->Create(); } } /** * Occurs after deleting item, id of deleted item * is stored as 'id' param of event * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); $product_id = $event->getEventParam('id'); if ( !$product_id ) { return; } $sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserFileAccess WHERE ProductId = ' . $product_id; $this->Conn->Query($sql); } /** * Load price from temp table if product mode is temp table * * @param kEvent $event */ /** * Load price from temp table if product mode is temp table * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); /** @var ProductsItem $object */ $object = $event->getObject(); $a_pricing = $object->getPrimaryPricing(); if ( !$a_pricing ) { // pricing doesn't exist for new products $price = $cost = null; } else { $price = (float)$a_pricing['Price']; $cost = (float)$a_pricing['Cost']; } // set original fields to use them in OnAfterItemCreate/OnAfterItemUpdate later $object->SetDBField('Price', $price); $object->SetOriginalField('Price', $price); $object->SetDBField('Cost', $cost); $object->SetOriginalField('Cost', $cost); } /** * Allows to add products to package besides all that parent method does * * @param kEvent $event */ function OnProcessSelected($event) { $dst_field = $this->Application->RecallVar('dst_field'); if ($dst_field == 'PackageContent') { $this->OnAddToPackage($event); } elseif ($dst_field == 'AssignedCoupon') { $coupon_id = $this->Application->GetVar('selected_ids'); $object = $event->getObject(); $object->SetDBField('AssignedCoupon', $coupon_id); $this->RemoveRequiredFields($object); $object->Update(); } else { parent::OnProcessSelected($event); } $this->finalizePopup($event); } /** * Called when some products are selected in products selector for this prefix * * @param kEvent $event */ function OnAddToPackage($event) { $selected_ids = $this->Application->GetVar('selected_ids'); // update current package content with selected products /** @var ProductsItem $object */ $object = $event->getObject(); $product_ids = $selected_ids['p'] ? explode(',', $selected_ids['p']) : Array(); if ($product_ids) { $current_ids = $object->GetPackageContentIds(); $current_ids = array_unique(array_merge($current_ids, $product_ids)); // remove package product from selected list $this_product = array_search($object->GetID(), $current_ids); if ($this_product !== false) { unset($current_ids[$this_product]); } $dst_field = $this->Application->RecallVar('dst_field'); $object->SetDBField($dst_field, '|'.implode('|', $current_ids).'|'); $object->Update(); $this->ProcessPackageItems($event); } $this->finalizePopup($event); } function ProcessPackageItems(kEvent $event) { //$this->Application->SetVar('p_mode', 't'); /** @var ProductsItem $object */ $object = $event->getObject(); $content_ids = $object->GetPackageContentIds(); if (sizeof($content_ids) > 0) { $total_weight = $this->Conn->GetOne('SELECT SUM(Weight) FROM '.TABLE_PREFIX.'Products WHERE ProductId IN ('.implode(', ', $content_ids).') AND Type=1'); if (!$total_weight) $total_weight = 0; $this->Conn->Query('UPDATE '.$object->TableName.' SET Weight='.$total_weight.' WHERE ProductId='.$object->GetID()); } /* $this->Application->SetVar('p_mode', false); $list = $this->Application->recallObject('p.content', 'p_List', array('types'=>'content')); $this->Application->SetVar('p_mode', 't'); $list->Query(); $total_weight_a = 0; $total_weight_b = 0; $list->GoFirst(); while (!$list->EOL()) { if ($list->GetDBField('Type')==1){ $total_weight_a += $list->GetField('Weight_a'); $total_weight_b += $list->GetField('Weight_b'); } $list->GoNext(); } $object->SetField('Weight_a', $total_weight_a); $object->SetField('Weight_b', $total_weight_b); */ //$object->Update(); } /** * Enter description here... * * @param kEvent $event */ function OnSaveItems($event) { //$event->CallSubEvent('OnUpdate'); $event->redirect = false; //$event->setRedirectParams(Array ('opener' => 's', 'pass' => 'all,p')); } /** * Removes product from package * * @param kEvent $event */ function OnRemovePackageItem($event) { $this->Application->SetVar('p_mode', 't'); $object = $event->getObject(); $items_info = $this->Application->GetVar('p_content'); if($items_info) { $product_ids = array_keys($items_info); $current_ids = $object->GetPackageContentIds(); $current_ids_flip = array_flip($current_ids); foreach($product_ids as $key=>$val){ unset($current_ids_flip[$val]); } $current_ids = array_keys($current_ids_flip); $current_ids_str = '|'.implode('|', array_unique($current_ids)).'|'; $object->SetDBField('PackageContent', $current_ids_str); } $object->Update(); $this->ProcessPackageItems($event); } /** * Occurs before deleting item, id of item being * deleted is stored as 'id' event param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemDelete(kEvent $event) { parent::OnBeforeItemDelete($event); /** @var kDBItem $object */ $object = $event->getObject(); $sql = 'SELECT COUNT(*) FROM ' . TABLE_PREFIX . 'Products WHERE PackageContent LIKE "%|' . $object->GetID() . '%"'; $product_includes_in = $this->Conn->GetOne($sql); if ( $product_includes_in > 0 ) { $event->status = kEvent::erFAIL; } } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array * @access protected */ public function getCustomExportColumns(kEvent $event) { $columns = parent::getCustomExportColumns($event); $new_columns = Array ( '__VIRTUAL__Price' => 'Price', '__VIRTUAL__Cost' => 'Cost', ); return array_merge($columns, $new_columns); } /** * Sets non standart virtual fields (e.g. to other tables) * * @param kEvent $event */ function setCustomExportColumns($event) { parent::setCustomExportColumns($event); /** @var kDBItem $object */ $object = $event->getObject(); $this->setPrimaryPrice($object->GetID(), (double)$object->GetDBField('Price'), Array ('Cost' => (double)$object->GetDBField('Cost'))); } function OnPreSaveAndOpenPopup($event) { /** @var kDBItem $object */ $object = $event->getObject(); $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); $event->redirect = $this->Application->GetVar('t'); // pass ID too, in case if product is created by OnPreSave call to ensure proper editing $event->SetRedirectParam('pass', 'all'); $event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID()); } /** * Returns ID of current item to be edited * by checking ID passed in get/post as prefix_id * or by looking at first from selected ids, stored. * Returned id is also stored in Session in case * it was explicitly passed as get/post * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( $this->Application->isAdminUser ) { $event->setEventParam('raise_warnings', 0); } $passed = parent::getPassedID($event); if ( $passed ) { return $passed; } if ( $this->Application->isAdminUser ) { // we may get product id out of OrderItem, if it exists /** @var OrdersItem $ord_item */ $ord_item = $this->Application->recallObject('orditems', null, Array ('raise_warnings' => 0)); if ( $ord_item->GetDBField('ProductId') ) { $passed = $ord_item->GetDBField('ProductId'); } } return $passed; } /** * Occurs, when config was parsed, allows to change config data dynamically * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); if (!$this->Application->LoggedIn()) { return ; } $user_id = $this->Application->RecallVar('user_id'); $sql = 'SELECT PrimaryGroupId FROM ' . TABLE_PREFIX . 'Users WHERE PortalUserId = ' . $user_id; $primary_group_id = $this->Conn->GetOne($sql); if (!$primary_group_id) { return; } $sub_select = ' SELECT pp.Price FROM ' . TABLE_PREFIX . 'ProductsPricing AS pp WHERE pp.ProductId = %1$s.ProductId AND GroupId = ' . $primary_group_id . ' ORDER BY MinQty LIMIT 0,1'; $calculated_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields'); $calculated_fields['']['Price'] = 'IFNULL((' . $sub_select . '), ' . $calculated_fields['']['Price'] . ')'; $this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calculated_fields); } /** * Starts product editing, remove any pending inventory actions * * @param kEvent $event * @return void * @access protected */ protected function OnEdit(kEvent $event) { $this->Application->RemoveVar('inventory_actions'); parent::OnEdit($event); } /** * Adds "Shop Cart" tab on paid listing type editing tab * * @param kEvent $event */ function OnModifyPaidListingConfig($event) { $edit_tab_presets = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'EditTabPresets'); $edit_tab_presets['Default']['shopping_cart'] = Array ('title' => 'la_tab_ShopCartEntry', 't' => 'in-commerce/paid_listings/paid_listing_type_shopcart', 'priority' => 2); $this->Application->setUnitOption($event->MasterEvent->Prefix, 'EditTabPresets', $edit_tab_presets); } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event * @return void * @access protected */ protected function OnCloneSubItem(kEvent $event) { parent::OnCloneSubItem($event); if ( $event->MasterEvent->Prefix == 'rev' ) { $clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones'); $subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix; $clones[$subitem_prefix]['ConfigMapping'] = Array ( 'PerPage' => 'Comm_Perpage_Reviews', 'ReviewDelayInterval' => 'product_ReviewDelay_Value', 'ReviewDelayValue' => 'product_ReviewDelay_Interval', ); $this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones); } } /** * Adds product to comparison list * * @param kEvent $event * @return void * @access protected */ protected function OnAddToCompare(kEvent $event) { $products = $this->getCompareProducts(); $product_id = (int)$this->Application->GetVar($event->Prefix . '_id'); if ( $product_id ) { $max_products = $this->Application->ConfigValue('MaxCompareProducts'); if ( count($products) < $max_products ) { $products[] = $product_id; $this->Application->Session->SetCookie('compare_products', implode('|', array_unique($products))); $event->SetRedirectParam('result', 'added'); } else { $event->SetRedirectParam('result', 'error'); } } $event->SetRedirectParam('pass', 'm,p'); } /** * Adds product to comparison list * * @param kEvent $event * @return void * @access protected */ protected function OnRemoveFromCompare(kEvent $event) { $products = $this->getCompareProducts(); $product_id = (int)$this->Application->GetVar($event->Prefix . '_id'); if ( $product_id && in_array($product_id, $products) ) { $products = array_diff($products, Array ($product_id)); $this->Application->Session->SetCookie('compare_products', implode('|', array_unique($products))); $event->SetRedirectParam('result', 'removed'); } $event->SetRedirectParam('pass', 'm,p'); } /** * Cancels product compare * * @param kEvent $event * @return void * @access protected */ protected function OnCancelCompare(kEvent $event) { $this->Application->Session->SetCookie('compare_products', '', -1); $event->SetRedirectParam('result', 'all_removed'); } /** * Returns products, that needs to be compared with each other * * @return Array * @access protected */ protected function getCompareProducts() { $products = $this->Application->GetVarDirect('compare_products', 'Cookie'); $products = $products ? explode('|', $products) : Array (); return $products; } }