Index: branches/5.3.x/units/helpers/order_helper.php =================================================================== --- branches/5.3.x/units/helpers/order_helper.php (revision 16505) +++ branches/5.3.x/units/helpers/order_helper.php (revision 16506) @@ -1,232 +1,250 @@ Application->RecallVar('checkout_errors'); $ret = Array ( 'order' => Array ( 'CouponId' => (int)$object->GetDBField('CouponId'), 'CouponName' => (string)$object->GetDBField('CouponName'), 'GiftCertificateId' => (int)$object->GetDBField('GiftCertificateDiscount'), 'GiftCertificateDiscount' => $this->convertCurrency($object->GetDBField('GiftCertificateDiscount'), $currency), 'DiscountTotal' => $this->convertCurrency($object->GetDBField('DiscountTotal'), $currency), 'SubTotal' => $this->convertCurrency($object->GetDBField('SubTotal'), $currency), ), 'items' => Array (), 'errors' => $errors ? unserialize($errors) : Array (), ); $items = $this->Application->recallObject('orditems', 'orditems_List', Array ('per_page' => -1)); /* @var $items kDBList */ $items->Query(); $items->GoFirst(); $ret['order']['ItemsInCart'] = array_sum( $items->GetCol('Quantity') ); if ( $items->EOL() ) { return $ret; } $product = $this->Application->recallObject('p', null, Array ('skip_autoload' => true)); /* @var $product kCatDBItem */ $sql = $product->GetSelectSQL() . ' WHERE ' . $product->TableName . '.' . $product->IDField . ' IN (' . implode(',', $items->GetCol('ProductId')) . ')'; $products = $this->Conn->Query($sql, $product->IDField); while ( !$items->EOL() ) { // prepare product from order item $product->LoadFromHash( $products[ $items->GetDBField('ProductId') ] ); $this->Application->SetVar('orditems_id', $items->GetID()); // for edit/delete links using GET // weird code from orditems:PrintList $this->Application->SetVar('p_id', $product->GetID()); $this->Application->SetVar('m_cat_id', $product->GetDBField('CategoryId')); // collect order item info $url_params = Array ( 'p_id' => $product->GetID(), 'm_cat_id' => $product->GetDBField('CategoryId'), 'pass' => 'm,p', ); $product_url = $this->Application->HREF('__default__', '', $url_params); $item_data = $items->GetDBField('ItemData'); $item_data = $item_data ? unserialize($item_data) : Array (); $row_index = $items->GetDBField('ProductId') . ':' . $items->GetDBField('OptionsSalt') . ':' . $items->GetDBField('BackOrderFlag'); $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ // TODO: find a way to specify thumbnail size & default image $ret['items'][$row_index] = Array ( 'product_url' => $product_url, 'product_type' => $product->GetDBField('Type'), 'options' => isset($item_data['Options']) ? $item_data['Options'] : false, 'free_promo_shipping' => $this->eligibleForFreePromoShipping($items), 'fields' => Array ( 'OrderItemId' => $items->GetDBField('OrderItemId'), 'ProductName' => $items->GetDBField('ProductName'), 'PrimaryImage' => $product->GetField('PrimaryImage', 'resize:58x58;default:img/no_picture.gif'), 'BackOrderFlag' => (int)$items->GetDBField('BackOrderFlag'), 'FlatPrice' => $this->convertCurrency($items->GetDBField('FlatPrice'), $currency), 'Price' => $this->convertCurrency($items->GetDBField('Price'), $currency), 'Quantity' => (int)$items->GetDBField('Quantity'), 'Virtual' => (int)$items->GetDBField('Virtual'), 'Type' => (int)$product->GetDBField('Type'), 'cust_Availability' => $product->GetDBField('cust_Availability'), ), ); $items->GoNext(); } if ( $remove_errors ) { $this->Application->RemoveVar('checkout_errors'); } return $ret; } function convertCurrency($amount, $currency) { $converter = $this->Application->recallObject('CurrencyRates'); /* @var $converter CurrencyRates */ // convert primary currency to selected (if they are the same, converter will just return) return (float)$converter->Convert($amount, 'PRIMARY', $this->getISO($currency)); } function getISO($currency) { if ($currency == 'selected') { $iso = $this->Application->RecallVar('curr_iso'); } elseif ($currency == 'primary' || $currency == '') { $iso = $this->Application->GetPrimaryCurrency(); } else { //explicit currency $iso = strtoupper($currency); } return $iso; } /** * Checks, that given order item is eligible for free promo shipping * * @param kDBItem|kDBList $order_item * @return bool */ function eligibleForFreePromoShipping(&$order_item) { if ( $order_item->GetDBField('Type') != PRODUCT_TYPE_TANGIBLE ) { return false; } $free_shipping = $order_item->GetDBField('MinQtyFreePromoShipping'); return $free_shipping > 0 && $free_shipping <= $order_item->GetDBField('Quantity'); } /** * Returns a template, that will be used to continue shopping from "shopping cart" template * * @param string $template * @return string * @access public */ public function getContinueShoppingTemplate($template = '') { if ( !$template || $template == '__default__' ) { $template = $this->Application->RecallVar('continue_shopping'); } if ( !$template ) { $template = 'in-commerce/index'; } return $template; } /** - * Detects credit card type by it's number + * Detects credit card type by it's number. * - * @param string $number - * @return int - * @access public + * @param string $number Credit card number. + * + * @return integer + * @deprecated */ public function getCreditCartType($number) { - // Get rid of any non-digits + @trigger_error( + 'Usage of deprecated method OrderHelper::getCreditCartType. Use OrderHelper::getCreditCardType.', + E_USER_DEPRECATED + ); + + return $this->getCreditCardType($number); + } + + /** + * Detects credit card type by it's number. + * + * @param string $number Credit card number. + * + * @return integer + */ + public function getCreditCardType($number) + { + // Get rid of any non-digits. $number = preg_replace('/[^\d]/', '', $number); $mapping = Array ( '/^4.{15}$|^4.{12}$/' => 1, // Visa '/^5[1-5].{14}$/' => 2, // MasterCard '/^3[47].{13}$/' => 3, // American Express '/^6011.{12}$/' => 4, // Discover '/^30[0-5].{11}$|^3[68].{12}$/' => 5, // Diners Club '/^3.{15}$|^2131|1800.{11}$/' => 6, // JBC ); foreach ($mapping as $number_regex => $card_type) { if ( preg_match($number_regex, $number) ) { return $card_type; } } return false; } /** * Extracts fields, used to created user from order * * @param OrdersItem $order * @param string $field_prefix * @return Array * @access public */ public function getUserFields(&$order, $field_prefix = 'Billing') { $fields_hash = Array (); $names = explode(' ', $order->GetDBField($field_prefix . 'To'), 2); $fields_hash['FirstName'] = (string)getArrayValue($names, 0); $fields_hash['LastName'] = (string)getArrayValue($names, 1); $order_fields = Array ( 'Company', 'Phone', 'Fax', 'Email', 'Address1' => 'Street', 'Address2' => 'Street2', 'City', 'State', 'Zip', 'Country' ); foreach ($order_fields as $src_field => $dst_field) { if ( is_numeric($src_field) ) { $src_field = $dst_field; } $fields_hash[$dst_field] = $order->GetDBField($field_prefix . $src_field); } return $fields_hash; } } Index: branches/5.3.x/units/shipping_quote_engines/shipping_quote_engine_event_handler.php =================================================================== --- branches/5.3.x/units/shipping_quote_engines/shipping_quote_engine_event_handler.php (revision 16505) +++ branches/5.3.x/units/shipping_quote_engines/shipping_quote_engine_event_handler.php (revision 16506) @@ -1,158 +1,141 @@ getObject(); /* @var $object kDBItem */ $engine = $this->Application->recallObject($object->GetDBField('ClassName')); /* @var $engine ShippingQuoteEngine */ $engine_fields = $engine->GetEngineFields(); $properties = $object->GetDBField('Properties'); $properties = $properties ? unserialize($properties) : Array (); // common fields for all shipping quote engines if ( $object->GetDBField('AccountPassword') != '' ) { // don't erase password by accident $engine_fields[] = 'AccountPassword'; } // save shipping quote specific fields foreach ($engine_fields as $engine_field) { $properties[$engine_field] = $object->GetDBField($engine_field); } $object->SetDBField('Properties', serialize($properties)); $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $from_country = $this->Application->ConfigValue('Comm_Shipping_Country'); $has_states = strlen($from_country) == 3 ? $cs_helper->CountryHasStates($from_country) : false; $valid_address = $from_country && ($has_states && $this->Application->ConfigValue('Comm_Shipping_State') || !$has_states) && $this->Application->ConfigValue('Comm_Shipping_City') && $this->Application->ConfigValue('Comm_Shipping_ZIP'); if ( !function_exists('curl_init') ) { $object->SetError('Status', 'curl_not_present'); } elseif ( ($object->GetDBField('Status') == STATUS_ACTIVE) && !$valid_address ) { $object->SetError('Status', 'from_info_not_filled_in'); } } /** - * Apply same processing to each item being selected in grid - * - * @param kEvent $event - * @return void - * @access protected - */ - protected function iterateItems(kEvent $event) - { - parent::iterateItems($event); - - if ( $event->Name == 'OnMassApprove' ) { - $event->status = kEvent::erSUCCESS; - $event->redirect = true; - } - } - - /** * Sets virtual fields from serialized properties array * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); $object = $event->getObject(); /* @var $object kDBItem */ $properties = $object->GetDBField('Properties'); if ( $properties ) { $object->SetDBFieldsFromHash(unserialize($properties)); } } /** * Deletes cached shipping quotes on any setting change * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); $this->_deleteQuoteCache(); } /** * Deletes cached shipping quotes on any setting change * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); $this->_deleteQuoteCache(); } /** * Deletes cached shipping quotes on any setting change * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); $this->_deleteQuoteCache(); } /** * Deletes cached shipping quotes * */ function _deleteQuoteCache() { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache WHERE VarName LIKE "ShippingQuotes%"'; $this->Conn->Query($sql); } -} \ No newline at end of file +} Index: branches/5.3.x/units/orders/orders_event_handler.php =================================================================== --- branches/5.3.x/units/orders/orders_event_handler.php (revision 16505) +++ branches/5.3.x/units/orders/orders_event_handler.php (revision 16506) @@ -1,4115 +1,4138 @@ 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'); $order_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); /* @var $order_dummy OrdersItem */ 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 = $event->getUnitConfig()->getStatusField(true); 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 == 'OnUpdate' ) { - // all checks were ok -> it's user's order -> allow to modify - return true; - } } } } 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) { $object = $event->getObject(); /* @var $object kDBItem */ $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 ; } $object = $event->getObject(); /* @var $object OrdersItem */ $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) { $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $address = $this->Application->recallObject('addr.-item','addr', Array('skip_autoload' => true)); /* @var $address AddressesItem */ $addr_list = $this->Application->recallObject('addr', 'addr_List', Array('per_page'=>-1, 'skip_counting'=>true) ); /* @var $addr_list AddressesList */ $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) { $user = $this->Application->recallObject('u.current'); /* @var $user UsersItem */ $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) { $affiliate_user = $this->Application->recallObject('affil.-item', null, Array('skip_autoload' => true) ); /* @var $affiliate_user kDBItem */ $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->getUnitConfig('u')->getTableName() . ' 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 $order->getEmailParams($ret); } function PrepareCoupons($event, &$order) { $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); /* @var $order_items kDBList */ $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 $coupon = $this->Application->recallObject('coup',null,array('skip_autload' => true)); /* @var $coupon kDBItem */ $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 $order OrdersItem */ 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); $order = $event->getObject(); /* @var $order OrdersItem */ $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) $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); /* @var $order_items kDBList */ $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', null, $order->getEmailParams()); } 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(); $config = $event->getUnitConfig(); $original_amount = $order->GetDBField('SubTotal') + $order->GetDBField('ShippingCost') + $order->GetDBField('VAT') + $order->GetDBField('ProcessingFee') + $order->GetDBField('InsuranceFee') - $order->GetDBField('GiftCertificateDiscount'); $sql = 'UPDATE '. $config->getTableName() .' SET OriginalAmount = '.$original_amount.' WHERE '. $config->getIDField() .' = '.$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) { $object = $event->getObject(); /* @var $object OrdersItem */ 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); $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) { $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper 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') ) { $object = $event->getObject(); /* @var $object OrdersItem */ 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); $object = $event->getObject(); /* @var $object kDBItem */ $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) { $order = $event->getObject(); /* @var $order OrdersItem */ $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); $object = $event->getObject(); /* @var $object OrdersItem */ $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 $object = $event->getObject(); /* @var $object OrdersItem */ $object->SetDBField('TotalAmount', $object->getTotalAmount()); } } /** * Creates new address * * @param kEvent $event */ function createMissingAddresses($event) { if ( !$this->Application->LoggedIn() ) { return ; } $object = $event->getObject(); /* @var $object kDBItem */ $addr_list = $this->Application->recallObject('addr', 'addr_List', Array ('per_page' => -1, 'skip_counting' => true)); /* @var $addr_list kDBList */ $addr_list->Query(); $address_dummy = $this->Application->recallObject('addr.-item', null, Array ('skip_autoload' => true)); /* @var $address_dummy AddressesItem */ $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; } $object = $event->getObject(); /* @var $object kDBItem */ // 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'); $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper 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; $product = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); /* @var $product ProductsItem */ $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(); $product_package_item = $this->Application->recallObject('p.-packageitem'); /* @var $product_package_item ProductsItem */ $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) { $poc_config = $this->Application->getUnitConfig('poc'); // such option combination is defined explicitly $poc_table = $poc_config->getTableName(); $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'] = $poc_config->getTableName(); $table['p'] = $this->Application->getUnitConfig('p')->getTableName(); $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; $ord_item = $this->Application->recallObject('orditems.-opt', null, Array ('skip_autoload' => true)); /* @var $ord_item kDBItem */ $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_config = $this->Application->getUnitConfig('p'); $sql = 'SELECT AccessGroupId FROM ' . $products_config->getTableName() . ' WHERE ' . $products_config->getIDField() . ' = ' . $item_id; $item_data['PortalGroupId'] = $this->Conn->GetOne($sql); /* TODO check on implementation $sql = 'SELECT AccessDuration, AccessUnit, DurationType, AccessExpiration FROM %s WHERE %s = %s'; */ $pricing_config = $this->Application->getUnitConfig('pr'); $pricing_id = $this->GetPricingId($item_id, $item_data); $sql = 'SELECT * FROM ' . $pricing_config->getTableName() . ' WHERE ' . $pricing_config->getIDField() . ' = ' . $pricing_id; $pricing_info = $this->Conn->GetRow($sql); $item_data['PricingId'] = $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 ; } $object = $event->getObject(); /* @var $object OrdersItem */ $coupon = $this->Application->recallObject('coup', null, Array ('skip_autoload' => true)); /* @var $coupon kDBItem */ $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 < time()) || (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 = time(); $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->getUnitConfig('ord')->setAutoLoad(true); $order = $this->Application->recallObject('ord'); /* @var $order OrdersItem */ $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) { $object = $event->getObject(); /* @var $object OrdersItem */ $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) { $coupon = $this->Application->recallObject('coup', null, Array('skip_autoload' => true)); /* @var $coupon kDBItem */ $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->getUnitConfig('l')->getTableName().' WHERE LinkId = '.$link_id; $sql = 'SELECT ListingTypeId FROM '.$this->Application->getUnitConfig('ls')->getTableName().' WHERE ItemResourceId = '.$this->Conn->GetOne($sql); $item_data['LinkId'] = $link_id; $item_data['ListingTypeId'] = $this->Conn->GetOne($sql); } $sql = 'SELECT VirtualProductId FROM '.$this->Application->getUnitConfig('lst')->getTableName().' 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 $object = $event->getObject(); /* @var $object kDBItem */ $object->Clear($id); return; } parent::LoadItem($event); } /** * Creates new shopping cart * * @param kEvent $event */ function _createNewCart($event) { $object = $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ $this->setNextOrderNumber($event); $object->SetDBField('Status', ORDER_STATUS_INCOMPLETE); $object->SetDBField('VisitId', $this->Application->RecallVar('visit_id') ); // get user if ( $this->Application->LoggedIn() ) { $user = $this->Application->recallObject('u.current'); /* @var $user UsersItem */ $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) { $order = $event->getObject(); /* @var $order OrdersItem */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $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); } 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; } $order = $event->getObject(); /* @var $order OrdersItem */ $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); $object = $event->getObject(); /* @var $object kDBItem */ $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) { $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); /* @var $order_items kDBList */ $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 $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); /* @var $product_object kCatDBItem */ $product_object->SwitchToLive(); $order_item = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); /* @var $order_item kDBItem */ $combination_item = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); /* @var $combination_item kDBItem */ $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 $object = $event->getObject(); /* @var $object kDBItem */ // 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->getUnitConfig('poc')->getTableName(); $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->getUnitConfig('poc')->getTableName(); $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) { $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); /* @var $product_object kCatDBItem */ $product_object->SwitchToLive(); $order_item = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); /* @var $order_item kDBItem */ $combination_item = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); /* @var $combination_item kDBItem */ $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(); $product_h = $this->Application->recallObject('p_EventHandler'); /* @var $product_h ProductsEventHandler */ 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 $object = $this->Application->recallObject($event->Prefix . '.-inv', null, Array ('skip_autoload' => true)); /* @var $object kDBItem */ $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, ); $order_items = $this->Application->recallObject('orditems.-inv','orditems_List',Array('skip_counting'=>true,'per_page'=>-1) ); /* @var $order_items kDBList */ $order_items->linkToParent('-inv'); $order_items->Query(); $order_items->GoFirst(); $object = $this->Application->recallObject($event->Prefix.'.-inv'); /* @var $object OrdersItem */ 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) { $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); /* @var $product_object ProductsItem */ 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->getUnitConfig('p')->getTableName().' 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->getUnitConfig('p')->getTableName() . ' 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) { $quote_engine_collector = $this->Application->recallObject('ShippingQuoteCollector'); /* @var $quote_engine_collector 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)) { $shipping_quote_engine = $this->Application->recallObject($sqe_class_name); /* @var $shipping_quote_engine ShippingQuoteEngine */ $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', time()); $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->getUnitConfig('conf')->getTableName(); $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 ' . $event->getUnitConfig()->getTableName(); $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'); $config = $event->getUnitConfig(); // set cloned order status to Incomplete $sql = 'UPDATE ' . $config->getTableName() . ' SET Status = 0 WHERE ' . $config->getIDField() . ' = ' . $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); $object = $event->getObject(); /* @var $object kDBItem */ // 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')); $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $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); $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $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); $object = $event->getObject(); /* @var $object OrdersItem */ $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); } } } $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $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') ) { $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper OrderHelper */ - $object->SetDBField('PaymentCardType', $order_helper->getCreditCartType($object->GetDBField('PaymentAccount'))); + $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) { $order = $event->getObject(); /* @var $order OrdersItem */ $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper 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 { $user = $this->Application->recallObject('u.current'); /* @var $user UsersItem */ $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); $object = $event->getObject(); /* @var $object kDBList */ $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->getUnitConfig('poc')->getTableName(); $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_table = $this->Application->getUnitConfig('poc')->getTableName(); $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_table = $this->Application->getUnitConfig('poc')->getTableName(); $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) { $sub_order = $this->Application->recallObject('ord.-sub'.$next_sub_number, 'ord'); /* @var $sub_order OrdersItem */ 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', null, $sub_order->getEmailParams()); } } 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 $product = $this->Application->recallObject('p.toadd', null, Array('skip_autoload' => true)); /* @var $product kDBItem */ $product->Load($item_id); $object = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); /* @var $object kDBItem */ $order = $this->Application->recallObject('ord'); /* @var $order kDBItem */ 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; } $manager = $this->Application->recallObject('OrderManager'); /* @var $manager 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; } $object = $event->getObject(); /* @var $object OrdersItem */ $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 ; } $order = $event->getObject(); /* @var $order OrdersItem */ 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; } $manager = $this->Application->recallObject('OrderManager'); /* @var $manager 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) { $object = $event->getObject(); /* @var $object OrdersItem */ $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'); $quote_engine_collector = $this->Application->recallObject('ShippingQuoteCollector'); /* @var $quote_engine_collector 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)) { $shipping_quote_engine = $this->Application->recallObject($sqe_class_name); /* @var $shipping_quote_engine ShippingQuoteEngine */ // 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) { $object = $event->getObject(); /* @var $object OrdersItem */ 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'); $sql = 'SELECT ProcessingFee FROM ' . $this->Application->getUnitConfig('pt')->getTableName() . ' WHERE PaymentTypeId = ' . $pt; $processing_fee = $this->Conn->GetOne($sql); $object->SetDBField( 'ProcessingFee', $processing_fee ); $this->UpdateTotals($event); } function UpdateTotals($event) { $object = $event->getObject(); /* @var $object OrdersItem */ $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->getUnitConfig('orditems')->getTableName().' WHERE OrderId = '.$object->GetDBField('OrderId'); $orditems = $this->Conn->GetCol($sql, 'ProductId'); $sql = 'SELECT coupi.ItemType, p.ProductId FROM '.$this->Application->getUnitConfig('coupi')->getTableName().' coupi LEFT JOIN '.$this->Application->getUnitConfig('p')->getTableName().' 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)); $config = $event->getUnitConfig(); $id_field = $config->getIDField(); $sql = 'SELECT Status FROM ' . $config->getTableName() . ' WHERE ' . $id_field . ' = ' . $id; $order_status = $this->Conn->GetOne($sql); $prefix_special = $event->Prefix.'.'.$this->getSpecialByType($order_status); $orders_list = $this->Application->recallObject($prefix_special, $event->Prefix.'_List', Array('per_page'=>-1) ); /* @var $orders_list kDBList */ $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) { $object = $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ $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) { $config = $this->Application->getUnitConfig('ord'); $ord_id_field = $config->getIDField(); $processing_allowed = Array(ORDER_STATUS_PROCESSED, ORDER_STATUS_ARCHIVED); $sql = 'SELECT '.$ord_id_field.', PortalUserId, GroupId, NextCharge FROM '. $config->getTableName() .' WHERE (IsRecurringBilling = 1) AND (NextCharge < '.$pre_expiration.') AND Status IN ('.implode(',', $processing_allowed).')'; return $this->Conn->Query($sql, $ord_id_field); } /** * [SCHEDULED TASK] Checks what orders should expire and renew automatically (if such flag set) * * @param kEvent $event */ function OnCheckRecurringOrders($event) { $skip_clause = Array(); $pre_expiration = time() + $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', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $cloned_order_ids = $temp_handler->CloneItems($event->Prefix, 'recurring', $order_ids); $order = $this->Application->recallObject($event->Prefix.'.recurring', null, Array('skip_autoload' => true)); /* @var $order OrdersItem */ foreach ($cloned_order_ids as $order_id) { $order->Load($order_id); $complete_event = new kEvent($event->Prefix . '.recurring:OnCompleteOrder'); $this->Application->HandleEvent($complete_event); 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', null, $order->getEmailParams()); } 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', null, $order->getEmailParams()); } } // remove recurring flag from all orders found, not to select them next time script runs $config = $event->getUnitConfig(); $sql = 'UPDATE '. $config->getTableName() .' SET IsRecurringBilling = 0 WHERE '. $config->getIDField() .' IN ('.implode(',', array_keys($to_charge)).')'; $this->Conn->Query($sql); } if ( !is_object($event->MasterEvent) ) { // not called as hook return ; } $pre_expiration = time() + $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')); $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper 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 '
Click here 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'); $config = $event->getUnitConfig(); $calc_fields = $config->getSetting('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']); } $config->setSetting('CalculatedFields', $calc_fields); $fields = $config->getFields(); $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']); $site_helper = $this->Application->recallObject('SiteHelper'); /* @var $site_helper 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'] ); } $config->setFields($fields); $user_default_form = $this->Application->getUnitConfig('u')->getFieldByName('default'); $virtual_fields = $config->getVirtualFields(); $virtual_fields['UserPassword']['hashing_method'] = $user_default_form['Fields']['PasswordHashingMethod']['default']; $config->setVirtualFields($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; } $object = $event->getObject(); /* @var $object kDBItem */ 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; } $object = $event->getObject(); /* @var $object OrdersItem */ $gift_certificate = $this->Application->recallObject('gc', null, Array ('skip_autoload' => true)); /* @var $gift_certificate kDBItem */ $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 < time()) || ($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) { $object = $event->getObject(); /* @var $object OrdersItem */ $this->RemoveGiftCertificate($object); $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_REMOVED); $event->CallSubEvent('OnRecalculateItems'); } function RemoveGiftCertificate(&$object) { $object->RemoveGiftCertificate(); } function RecalculateGift($event) { $object = $event->getObject(); /* @var $object OrdersItem */ 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'); $object = $event->getObject(); /* @var $object kDBItem */ $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'); $object = $event->getObject(); /* @var $object kDBItem */ $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 + * @param kEvent $event Event. + * + * @return void */ - function OnUpdateAjax($event) + protected function OnUpdateAjax(kEvent $event) { + /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); - /* @var $ajax_form_helper 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.3.x/units/affiliates/affiliates_event_handler.php =================================================================== --- branches/5.3.x/units/affiliates/affiliates_event_handler.php (revision 16505) +++ branches/5.3.x/units/affiliates/affiliates_event_handler.php (revision 16506) @@ -1,675 +1,669 @@ Array ('self' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( $event->Name == 'OnBecomeAffiliate' || $event->Name == 'OnChangePaymentType' ) { return $this->Application->LoggedIn() && $this->Application->ConfigValue('Comm_RegisterAsAffiliate'); } return parent::CheckPermission($event); } /** * Allows to get ID of affiliate record, associated with currently logged-in user * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( $event->Special == 'user' ) { $config = $event->getUnitConfig(); $event->setEventParam('raise_warnings', 0); $sql = 'SELECT ' . $config->getIDField() . ' FROM ' . $config->getTableName() . ' WHERE PortalUserId = ' . (int)$this->Application->RecallVar('user_id'); $id = $this->Conn->GetOne($sql); if ( $id ) { return $id; } } return parent::getPassedID($event); } /** * Generate new affiliate code * * @param kEvent $event * @return string */ function generateAffiliateCode($event) { // accepts 1 - 36 $number_length = 11; $num_chars = Array( '1'=>'a','2'=>'b','3'=>'c','4'=>'d','5'=>'e','6'=>'f', '7'=>'g','8'=>'h','9'=>'i','10'=>'j','11'=>'k','12'=>'l', '13'=>'m','14'=>'n','15'=>'o','16'=>'p','17'=>'q','18'=>'r', '19'=>'s','20'=>'t','21'=>'u','22'=>'v','23'=>'w','24'=>'x', '25'=>'y','26'=>'z','27'=>'0','28'=>'1','29'=>'2','30'=>'3', '31'=>'4','32'=>'5','33'=>'6','34'=>'7','35'=>'8','36'=>'9'); $ret = ''; for ($i = 1; $i <= $number_length; $i++) { mt_srand((double)microtime() * 1000000); $num = mt_rand(1,36); $ret .= $num_chars[$num]; } $ret = strtoupper($ret); $config = $event->getUnitConfig(); $id_field = $config->getIDField(); $table = $config->getTableName(); $sql = 'SELECT %s FROM %s WHERE AffiliateCode = %s'; $code_found = $this->Conn->GetOne( sprintf($sql, $id_field, $table, $this->Conn->qstr($ret) ) ); return $code_found ? $this->generateAffiliateCode($event) : $ret; } /** * Creates new affiliate code when new affiliate is created * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $object = $event->getObject(); /* @var $object kDBItem */ $object->SetDBField('AffiliateCode', $this->generateAffiliateCode($event)); if ( $object->getFormName() == 'registration' ) { if ( $this->Application->LoggedIn() ) { $object->SetDBField('PortalUserId', $this->Application->RecallVar('user_id')); } $object->SetDBField('AffiliatePlanId', $this->_getPrimaryAffiliatePlan()); } } /** * Ensures, that user can only update his affiliate record * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); if ( !$this->Application->isAdmin ) { $object = $event->getObject(); /* @var $object kDBItem */ $object->SetDBField('PortalUserId', $object->GetOriginalField('PortalUserId')); if ( $object->GetDBField('PortalUserId') != $this->Application->RecallVar('user_id') ) { $object->SetError('PortalUserId', 'not_owner'); } } } /** * [HOOK] Stores affiliate id using method from Config (session or cookie) if correct code is present in url * * @param kEvent $event * @return bool */ function OnStoreAffiliate($event) { if ( defined('IS_INSTALL') && IS_INSTALL ) { return; } $object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); /* @var $object kDBItem */ $affiliate_storage_method = $this->Application->ConfigValue('Comm_AffiliateStorageMethod'); $affiliate = $this->Application->GetVar('affiliate'); if ( $affiliate ) { $object->Load($affiliate, 'AffiliateCode'); } elseif ( $affiliate_storage_method == 2 ) { $affiliate_id = $this->Application->GetVar('affiliate_id'); $object->Load($affiliate_id); } if ( $object->isLoaded() && ($object->GetDBField('Status') == 1) ) { // user is found with such email $affiliate_user = $this->Application->recallObject('u.affiliate', null, Array ('skip_autoload' => true)); /* @var $affiliate_user UsersItem */ $affiliate_user->Load($object->GetDBField('PortalUserId')); if ( $affiliate_user->GetDBField('Status') == 1 ) { $affiliate_id = $object->GetDBField('AffiliateId'); $this->Application->setVisitField('AffiliateId', $affiliate_id); if ( $affiliate_storage_method == 1 ) { $this->Application->StoreVar('affiliate_id', $affiliate_id); // per session } else { // in cookie $this->Application->Session->SetCookie('affiliate_id', $affiliate_id, $this->getCookieExpiration()); } } } } /** * Returns affiliate cookie expiration date * * @return int */ function getCookieExpiration() { $expire = $this->Application->ConfigValue('Comm_AffiliateCookieDuration'); // days return time() + $expire * 24 * 60 * 60; } /** * Calculate what amount is earned by affiliate based on it's affiliate plan & store it * * @param kEvent $event * @author Alex */ function OnOrderApprove($event) { $order = $this->Application->recallObject($event->getEventParam('Order_PrefixSpecial')); /* @var $order OrdersItem */ $affiliate_id = $order->GetDBField('AffiliateId'); if ( !$affiliate_id ) { return false; } $object = $event->getObject(Array ('ship_autoload' => true)); /* @var $object kDBItem */ if ( $object->Load($affiliate_id) ) { $affiliate_plan = $this->Application->recallObject('ap', null, Array ('skip_autoload' => true)); /* @var $affiliate_plan kDBItem */ $affiliate_plan->Load($object->GetDBField('AffiliatePlanId')); if ( $affiliate_plan->isLoaded() ) { $sql = 'SELECT SUM(Quantity) FROM %s WHERE OrderId = %s'; $orderitems_table = $this->Application->getUnitConfig('orditems')->getTableName(); $items_sold = $this->Conn->GetOne(sprintf($sql, $orderitems_table, $order->GetID())); $object->SetDBField('AccumulatedAmount', $object->GetDBField('AccumulatedAmount') + $order->GetDBField('TotalAmount')); $object->SetDBField('ItemsSold', $object->GetDBField('ItemsSold') + $items_sold); switch ($affiliate_plan->GetDBField('PlanType')) { case 1: // by amount $value = $object->GetDBField('AccumulatedAmount'); break; case 2: // by items sold (count) $value = $object->GetDBField('ItemsSold'); break; } $apb_table = $this->Application->getUnitConfig('apbrackets')->getTableName(); $sql = 'SELECT Percent FROM %1$s WHERE (%2$s >= FromAmount) AND ( (%2$s <= ToAmount) OR (ToAmount = -1) ) AND (AffiliatePlanId = %3$s)'; $commission_percent = $this->Conn->GetOne(sprintf($sql, $apb_table, $this->Conn->qstr($value), $affiliate_plan->GetID())); // process only orders of current affiliate from period start to this order date $period_ends = $order->GetDBField('OrderDate'); $period_starts = $this->getPeriodStartTS($period_ends, $affiliate_plan->GetDBField('ResetInterval')); $sql = 'SELECT AffiliateCommission, (SubTotal+ShippingCost+VAT) AS TotalAmount, OrderId FROM ' . $order->TableName . ' WHERE OrderDate >= %s AND OrderDate <= %s AND AffiliateId = ' . $affiliate_id; $amount_to_pay_before = 0; $amount_to_pay_after = 0; $order_update_sql = 'UPDATE ' . $order->TableName . ' SET AffiliateCommission = %s WHERE ' . $order->IDField . ' = %s'; $orders = $this->Conn->Query(sprintf($sql, $period_starts, $period_ends), 'OrderId'); if ( $orders ) { foreach ($orders as $order_id => $order_data) { $amount_to_pay_before += $order_data['AffiliateCommission']; $commission = $order_data['TotalAmount'] * ($commission_percent / 100); $this->Conn->Query(sprintf($order_update_sql, $this->Conn->qstr($commission), $order_id)); $amount_to_pay_after += $commission; } } $object->SetDBField('AmountToPay', $object->GetDBField('AmountToPay') - $amount_to_pay_before + $amount_to_pay_after); $object->SetDBField('LastOrderDate_date', $order->GetDBField('OrderDate_date')); $object->SetDBField('LastOrderDate_time', $order->GetDBField('OrderDate_time')); $object->Update(); $order->SetDBField('AffiliateCommission', $commission); // set last commission to this order, because ApproveEvent was called for him } } } /** * [HOOK] Validates affiliate fields on user registration form * * @param kEvent $event * @return void * @access protected */ protected function OnValidateAffiliate($event) { if ( $this->Application->GetVar('RegisterAsAffiliate') != 'on' || $event->MasterEvent->status != kEvent::erSUCCESS ) { return; } $object = $event->getObject( Array('form_name' => 'registration', 'skip_autoload' => true) ); /* @var $object kDBItem */ $object->setID(0); $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( !$object->Validate() ) { $user = $event->MasterEvent->getObject(); /* @var $user kDBItem */ $user->Validate(); $event->MasterEvent->status = kEvent::erFAIL; } } /** * [AFTER HOOK] to u:OnCreate * * @param kEvent $event */ function OnRegisterAffiliate($event) { if ( $this->Application->GetVar('RegisterAsAffiliate') != 'on' || $event->MasterEvent->status != kEvent::erSUCCESS ) { return; } $object = $event->getObject(); /* @var $object kDBItem */ $user = $event->MasterEvent->getObject(); /* @var $user UsersItem */ $object->SetDBField('PortalUserId', $user->GetID()); if ( $object->Create() ) { $send_params = $object->getEmailParams(); $this->Application->emailUser('AFFILIATE.REGISTER', $user->GetID(), $send_params); $this->Application->emailAdmin('AFFILIATE.REGISTER', null, $send_params); } } /** * Returns primary affiliate plan * * @return int * @access protected */ protected function _getPrimaryAffiliatePlan() { $sql = 'SELECT AffiliatePlanId FROM ' . $this->Application->getUnitConfig('ap')->getTableName() . ' WHERE IsPrimary = 1'; return (int)$this->Conn->GetOne($sql); } /** * Creates affiliate record for logged-in user * * @param kEvent $event */ function OnBecomeAffiliate($event) { $object = $event->getObject( Array('form_name' => 'registration', 'skip_autoload' => true) ); /* @var $object UsersItem */ $event->CallSubEvent('OnCreate'); if ( $event->status == kEvent::erSUCCESS ) { $event->SetRedirectParam('opener', 's'); $next_template = $this->Application->GetVar('next_template'); if ( $next_template ) { $event->redirect = $next_template; } } } /** * Change affiliate payment type of affiliate record associated with logged-in user * * @param kEvent $event * @return void * @access protected */ protected function OnChangePaymentType($event) { $event->CallSubEvent('OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { $object = $event->getObject(); /* @var $object kDBItem */ $send_params = $object->getEmailParams(); $this->Application->emailUser('AFFILIATE.PAYMENT.TYPE.CHANGED', $object->GetDBField('PortalUserId'), $send_params); $this->Application->emailAdmin('AFFILIATE.PAYMENT.TYPE.CHANGED', null, $send_params); $next_template = $this->Application->GetVar('next_template'); if ( $next_template ) { $event->redirect = $this->Application->GetVar('next_template'); } $event->SetRedirectParam('opener', 's'); } } /** * If new payments made, then send email about that * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); $payment_object = $this->Application->recallObject('apayments', 'apayments', Array ('skip_autoload' => true)); /* @var $payment_object kDBItem */ $id = $event->getEventParam('id'); $ap_table = $this->Application->getUnitConfig('apayments')->getTableName(); $sql = 'SELECT AffiliatePaymentId FROM ' . $ap_table . ' WHERE AffiliateId = ' . $id; $live_ids = $this->Conn->GetCol($sql); $sql = 'SELECT AffiliatePaymentId FROM ' . $payment_object->TableName . ' WHERE AffiliateId = ' . $id; $temp_ids = $this->Conn->GetCol($sql); $new_ids = array_diff($temp_ids, $live_ids); foreach ($new_ids as $payment_id) { $payment_object->Load($payment_id); $send_params = $payment_object->getEmailParams(); $this->Application->emailUser('AFFILIATE.PAYMENT', $payment_object->GetDBField('PortalUserId'), $send_params); $this->Application->emailAdmin('AFFILIATE.PAYMENT', null, $send_params); } $object = $event->getObject(); /* @var $object kDBItem */ $passed_id = $event->getEventParam('id'); if ( $object->GetID() != $passed_id ) { $object->Load($passed_id); } $sql = 'SELECT Status FROM ' . $event->getUnitConfig()->getTableName() . ' WHERE ' . $object->IDField . ' = ' . $object->GetID(); $old_status = $this->Conn->GetOne($sql); if ( $old_status == 2 && $object->GetDBField('Status') == 1 ) { $send_params = $object->getEmailParams(); $this->Application->emailUser('AFFILIATE.REGISTRATION.APPROVED', $object->GetDBField('PortalUserId'), $send_params); $this->Application->emailAdmin('AFFILIATE.REGISTRATION.APPROVED', null, $send_params); } } /** * [HOOK] Resets statistics (accumulated amount & items sold) for affiliates based on ResetInterval in their plan * * @param kEvent $event * @author Alex */ function OnResetStatistics($event) { if ( defined('IS_INSTALL') && IS_INSTALL ) { return; } $intervals = Array (86400 => 'la_day', 604800 => 'la_week', 2628000 => 'la_month', 7884000 => 'la_quartely', 31536000 => 'la_year'); $affiliates_table = $event->getUnitConfig()->getTableName(); $affiliate_plan_table = $this->Application->getUnitConfig('ap')->getTableName(); $base_time = time(); $where_clause = Array (); foreach ($intervals as $interval_length => $interval_description) { $start_timestamp = $this->getPeriodStartTS($base_time, $interval_length); $where_clause[] = 'ap.ResetInterval = ' . $interval_length . ' AND LastOrderDate < ' . $start_timestamp; } $sql = 'SELECT AffiliateId FROM ' . $affiliates_table . ' a LEFT JOIN ' . $affiliate_plan_table . ' ap ON a.AffiliatePlanId = ap.AffiliatePlanId WHERE (' . implode(') OR (', $where_clause) . ')'; $affiliate_ids = $this->Conn->GetCol($sql); if ( !$affiliate_ids ) { return; } if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) { $this->Application->Debugger->appendHTML('Affiliates Pending Totals Reset: '); $this->Application->Debugger->dumpVars($affiliate_ids); } $fields_hash = Array ( 'AccumulatedAmount' => 0, 'ItemsSold' => 0, 'LastOrderDate' => $base_time, ); $this->Conn->doUpdate($fields_hash, $affiliates_table, 'AffiliateId IN (' . implode(',', $affiliate_ids) . ')'); } /** * Returns calendar period start timestamp based on current timestamp ($base_time) and $period_length * * @param int $base_time * @param int $period_length * @return int * @author Alex */ function getPeriodStartTS($base_time, $period_length) { $start_timestamp = 0; switch ($period_length) { case 86400: // day $start_timestamp = mktime(0, 0, 0, date('m', $base_time), date('d', $base_time), date('Y', $base_time)); break; case 604800: // week $day_seconds = 86400; $first_week_day = $this->Application->ConfigValue('FirstDayOfWeek'); $morning = mktime(0, 0, 0, date('m', $base_time), date('d', $base_time), date('Y', $base_time)); $week_day = date('w', $morning); if ( $week_day == $first_week_day ) { // if it is already first week day, then don't search for previous week day $day_diff = 0; } else { // this way, because sunday is 0, but not 7 as it should be $day_diff = $week_day != 0 ? $week_day - $first_week_day : 7 - $first_week_day; } $start_timestamp = $morning - $day_diff * $day_seconds; break; case 2628000: // month $start_timestamp = mktime(0, 0, 0, date('m', $base_time), 1, date('Y', $base_time)); break; case 7884000: // quartal $first_quartal_month = (ceil(date('m', $base_time) / 3) - 1) * 3 + 1; $start_timestamp = mktime(0, 0, 0, $first_quartal_month, 1, date('Y', $base_time)); break; case 31536000: $start_timestamp = mktime(0, 0, 0, 1, 1, date('Y', $base_time)); break; } return $start_timestamp; } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $status_field = $event->getUnitConfig()->getStatusField(true); foreach ($ids as $id) { $object->Load($id); switch ($event->Name) { case 'OnMassApprove': $object->SetDBField($status_field, 1); break; case 'OnMassDecline': $object->SetDBField($status_field, 0); break; case 'OnMassMoveUp': $object->SetDBField('Priority', $object->GetDBField('Priority') + 1); break; case 'OnMassMoveDown': $object->SetDBField('Priority', $object->GetDBField('Priority') - 1); break; } if ( $object->Update() ) { $send_params = $object->getEmailParams(); switch ($event->Name) { case 'OnMassApprove': $this->Application->emailUser('AFFILIATE.REGISTRATION.APPROVED', $object->GetDBField('PortalUserId'), $send_params); $this->Application->emailAdmin('AFFILIATE.REGISTRATION.APPROVED', null, $send_params); break; case 'OnMassDecline': $this->Application->emailUser('AFFILIATE.REGISTRATION.DENIED', $object->GetDBField('PortalUserId'), $send_params); $this->Application->emailAdmin('AFFILIATE.REGISTRATION.DENIED', null, $send_params); break; } - $event->status = kEvent::erSUCCESS; $event->SetRedirectParam('opener', 's'); //stay! } - else { - $event->status = kEvent::erFAIL; - $event->redirect = false; - break; - } } } } /** * Checks that user in affiliate record matches current user * (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { if ( $this->Application->isAdminUser ) { return true; } $object = $event->getObject(); /* @var $object kDBItem */ if ( !$object->isLoaded() ) { return true; } return $object->GetDBField('PortalUserId') == $this->Application->RecallVar('user_id'); } } Index: branches/5.3.x/install/upgrades.php =================================================================== --- branches/5.3.x/install/upgrades.php (revision 16505) +++ branches/5.3.x/install/upgrades.php (revision 16506) @@ -1,194 +1,195 @@ dependencies = Array ( '4.3.9' => Array ('Core' => '4.3.9'), '5.0.0' => Array ('Core' => '5.0.0'), '5.0.1' => Array ('Core' => '5.0.1'), '5.0.2-B1' => Array ('Core' => '5.0.2-B1'), '5.0.2-B2' => Array ('Core' => '5.0.2-B2'), '5.0.2-RC1' => Array ('Core' => '5.0.2-RC1'), '5.0.2' => Array ('Core' => '5.0.2'), '5.0.3-B1' => Array ('Core' => '5.0.3-B1'), '5.0.3-B2' => Array ('Core' => '5.0.3-B2'), '5.0.3-RC1' => Array ('Core' => '5.0.3-RC1'), '5.0.3' => Array ('Core' => '5.0.3'), '5.0.4-B1' => Array ('Core' => '5.0.4-B1'), '5.0.4-B2' => Array ('Core' => '5.0.4-B2'), '5.0.4' => Array ('Core' => '5.0.4'), '5.1.0-B1' => Array ('Core' => '5.1.0-B1'), '5.1.0-B2' => Array ('Core' => '5.1.0-B2'), '5.1.0-RC1' => Array ('Core' => '5.1.0-RC1'), '5.1.0' => Array ('Core' => '5.1.0'), '5.1.1-B1' => Array ('Core' => '5.1.1-B1'), '5.1.1-B2' => Array ('Core' => '5.1.1-B2'), '5.1.1-RC1' => Array ('Core' => '5.1.1-RC1'), '5.1.1' => Array ('Core' => '5.1.1'), '5.1.2-B1' => Array ('Core' => '5.1.2-B1'), '5.1.2-B2' => Array ('Core' => '5.1.2-B2'), '5.1.2-RC1' => Array ('Core' => '5.1.2-RC1'), '5.1.2' => Array ('Core' => '5.1.2'), '5.1.3-B1' => Array ('Core' => '5.1.3-B1'), '5.1.3-B2' => Array ('Core' => '5.1.3-B2'), '5.1.3-RC1' => Array ('Core' => '5.1.3-RC1'), '5.1.3-RC2' => Array ('Core' => '5.1.3-RC2'), '5.1.3' => Array ('Core' => '5.1.3'), '5.2.0-B1' => Array ('Core' => '5.2.0-B1'), '5.2.0-B2' => Array ('Core' => '5.2.0-B2'), '5.2.0-B3' => Array ('Core' => '5.2.0-B3'), '5.2.0-RC1' => Array ('Core' => '5.2.0-RC1'), '5.2.0' => Array ('Core' => '5.2.0'), '5.2.1-B1' => Array ('Core' => '5.2.1-B1'), '5.2.1-B2' => Array ('Core' => '5.2.1-B2'), '5.2.1-RC1' => Array ('Core' => '5.2.1-RC1'), '5.2.1' => Array ('Core' => '5.2.1'), + '5.2.2-B1' => Array ('Core' => '5.2.2-B1'), '5.3.0-B1' => Array ('Core' => '5.3.0-B1'), ); } /** * Changes table structure, where multilingual fields of TEXT type are present * * @param string $mode when called mode {before, after) */ function Upgrade_5_0_0($mode) { if ($mode == 'after') { // update icon $categories_config = $this->Application->getUnitConfig('c'); $root_category = $this->Application->findModule('Name', 'In-Commerce', 'RootCat'); $sql = 'UPDATE ' . $categories_config->getTableName() . ' SET UseMenuIconUrl = 1, MenuIconUrl = "in-commerce/img/menu_products.gif" WHERE ' . $categories_config->getIDField() . ' = ' . $root_category; $this->Conn->Query($sql); $this->_updateDetailTemplate('p', 'in-commerce/product/details', 'in-commerce/designs/detail'); // copy store name to company name $store_name = $this->Application->ConfigValue('Comm_StoreName'); $sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationValues SET VariableValue = ' . $this->Conn->qstr($store_name) . ' WHERE VariableName = "Comm_CompanyName"'; $this->Conn->Query($sql); } } /** * Update to 5.0.1, update details template * * @param string $mode when called mode {before, after) */ function Upgrade_5_0_1($mode) { if ($mode == 'after') { $this->_updateDetailTemplate('p', 'in-commerce/designs/detail', 'in-commerce/products/product_detail'); // clean incomplete orders 5+ hours old // don't use ORDER_STATUS_INCOMPLETE constant, since it's not available upgrade $delete_timestamp = time() - (3600 * 5); $sql = 'SELECT OrderId FROM ' . TABLE_PREFIX . 'Orders WHERE Status = ' . 0 . ' AND OrderDate < ' . $delete_timestamp; $orders_to_delete = $this->Conn->GetCol($sql); if ( $orders_to_delete && is_array($orders_to_delete) ) { $this->Conn->Query( 'DELETE FROM ' . TABLE_PREFIX . 'OrderItems WHERE OrderId IN ( ' . implode(',', $orders_to_delete) . ' )' ); $this->Conn->Query( 'DELETE FROM ' . TABLE_PREFIX . 'Orders WHERE Status = ' . 0 . ' AND OrderDate < ' . $delete_timestamp ); } // delete old events $events_to_delete = Array ( 'SITE.SUGGEST' ); $sql = 'SELECT EventId FROM ' . TABLE_PREFIX . 'Events WHERE Event IN ("' . implode('","', $events_to_delete) . '")'; $event_ids = $this->Conn->GetCol($sql); if ($event_ids) { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'EmailMessage WHERE EventId IN (' . implode(',', $event_ids) . ')'; $this->Conn->Query($sql); $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Events WHERE EventId IN (' . implode(',', $event_ids) . ')'; $this->Conn->Query($sql); $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Phrase WHERE Phrase IN ("la_event_user.suggest_site")'; $this->Conn->Query($sql); } } } /** * Update to 5.2.0-RC1 * * @param string $mode when called mode {before, after) */ public function Upgrade_5_2_0_RC1($mode) { if ( $mode != 'before' ) { return; } $table_name = $this->Application->getUnitConfig('pt')->getTableName(); $table_structure = $this->Conn->Query('DESCRIBE ' . $table_name, 'Field'); if ( isset($table_structure['Description']) ) { $sql = 'UPDATE ' . $table_name . ' SET Description = "" WHERE Description IS NULL'; $this->Conn->Query($sql); $sql = 'ALTER TABLE ' . $table_name . ' CHANGE `Description` `Description` VARCHAR(255) NOT NULL DEFAULT ""'; $this->Conn->Query($sql); } $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->createFields('pt'); if ( isset($table_structure['Description']) ) { $sql = 'UPDATE ' . $table_name . ' SET l' . $this->Application->GetDefaultLanguageId() . '_Description = Description, l' . $this->Application->GetDefaultLanguageId() . '_Instructions = Instructions'; $this->Conn->Query($sql); $sql = 'ALTER TABLE ' . $table_name . ' DROP Description, DROP Instructions'; $this->Conn->Query($sql); } } - } \ No newline at end of file + } Index: branches/5.3.x/install/upgrades.sql =================================================================== --- branches/5.3.x/install/upgrades.sql (revision 16505) +++ branches/5.3.x/install/upgrades.sql (revision 16506) @@ -1,308 +1,310 @@ # ===== v 4.3.9 ===== INSERT INTO ImportScripts VALUES (DEFAULT, 'Products from CSV file [In-Commerce]', '', 'p', 'In-Commerce', '', 'CSV', '1'); ALTER TABLE Products ADD OnSale TINYINT(1) NOT NULL default '0' AFTER Featured, ADD INDEX (OnSale); UPDATE Phrase SET Module = 'In-Commerce' WHERE Phrase IN ('lu_comm_Images', 'lu_comm_ImagesHeader'); # ===== v 5.0.0 ===== UPDATE Category SET Template = '/in-commerce/designs/section' WHERE Template = 'in-commerce/store/category'; UPDATE Category SET CachedTemplate = '/in-commerce/designs/section' WHERE CachedTemplate = 'in-commerce/store/category'; UPDATE ConfigurationValues SET VariableValue = '/in-commerce/designs/section' WHERE VariableName = 'p_CategoryTemplate'; UPDATE ConfigurationValues SET VariableValue = 'in-commerce/designs/detail' WHERE VariableName = 'p_ItemTemplate'; DELETE FROM PersistantSessionData WHERE VariableName IN ('affil_columns_.', 'ap_columns_.', 'apayments_columns_.', 'apayments.log_columns_.', 'd_columns_.', 'coup_columns_.', 'file_columns_.', 'po_columns_.', 'z_columns_.', 'tax_columns_.'); DELETE FROM PersistantSessionData WHERE VariableName LIKE '%ord.%'; INSERT INTO Permissions VALUES (DEFAULT, 'in-commerce:products.view', 11, 1, 1, 0); INSERT INTO Permissions VALUES (DEFAULT, 'in-commerce:setting_folder.view', 11, 1, 1, 0); INSERT INTO ShippingQuoteEngines VALUES (DEFAULT, 'USPS.com', 0, 0, 0, 'a:21:{s:12:"AccountLogin";s:0:"";s:15:"AccountPassword";N;s:10:"UPSEnabled";N;s:10:"UPSAccount";s:0:"";s:11:"UPSInvoiced";N;s:10:"FDXEnabled";N;s:10:"FDXAccount";s:0:"";s:10:"DHLEnabled";N;s:10:"DHLAccount";s:0:"";s:11:"DHLInvoiced";N;s:10:"USPEnabled";N;s:10:"USPAccount";s:0:"";s:11:"USPInvoiced";N;s:10:"ARBEnabled";N;s:10:"ARBAccount";s:0:"";s:11:"ARBInvoiced";N;s:10:"1DYEnabled";N;s:10:"2DYEnabled";N;s:10:"3DYEnabled";N;s:10:"GNDEnabled";N;s:10:"ShipMethod";N;}', 'USPS'); INSERT INTO ConfigurationAdmin VALUES ('Comm_CompanyName', 'la_Text_ContactsGeneral', 'la_text_CompanyName', 'text', NULL, NULL, 10.01, 0, 0); INSERT INTO ConfigurationValues VALUES (DEFAULT, 'Comm_CompanyName', '', 'In-Commerce', 'in-commerce:contacts'); UPDATE ConfigurationAdmin SET prompt = 'la_text_StoreName', DisplayOrder = 10.02 WHERE VariableName = 'Comm_StoreName'; INSERT INTO ConfigurationAdmin VALUES ('Comm_Contacts_Name', 'la_Text_ContactsGeneral', 'la_text_ContactName', 'text', NULL, NULL, 10.03, 0, 0); INSERT INTO ConfigurationValues VALUES (DEFAULT, 'Comm_Contacts_Name', '', 'In-Commerce', 'in-commerce:contacts'); UPDATE ConfigurationAdmin SET DisplayOrder = 10.04 WHERE VariableName = 'Comm_Contacts_Phone'; UPDATE ConfigurationAdmin SET DisplayOrder = 10.05 WHERE VariableName = 'Comm_Contacts_Fax'; UPDATE ConfigurationAdmin SET DisplayOrder = 10.06 WHERE VariableName = 'Comm_Contacts_Email'; UPDATE ConfigurationAdmin SET DisplayOrder = 10.07 WHERE VariableName = 'Comm_Contacts_Additional'; DELETE FROM Phrase WHERE Phrase IN ('la_fld_ManufacturerId', 'la_fld_DiscountId', 'la_fld_CouponId', 'la_fld_AffiliatePlanId', 'la_fld_AffiliateId', 'la_fld_ZoneId', 'la_fld_EngineId', 'la_fld_ShippingId', 'la_fld_ProductId', 'la_fld_OptionId', 'la_fld_CurrencyId', 'la_fld_Zone_Name'); UPDATE Phrase SET Module = 'In-Commerce' WHERE ((Phrase LIKE '%Product%' OR Phrase LIKE '%Shipping%' OR Phrase LIKE '%Coupon%' OR Phrase LIKE '%Discount%' OR Phrase LIKE '%Report%' OR Phrase LIKE '%Currency%' OR Phrase LIKE '%Cart%') AND (Module = 'Core')); # ===== v 5.0.1 ===== UPDATE ConfigurationValues SET VariableValue = 'in-commerce/products/product_detail' WHERE VariableName = 'p_ItemTemplate'; UPDATE ConfigurationAdmin SET ValueList = '1=la_opt_Session,2=la_opt_PermanentCookie' WHERE VariableName = 'Comm_AffiliateStorageMethod'; UPDATE ConfigurationAdmin SET ValueList = 'ASC=la_common_Ascending,DESC=la_common_Descending' WHERE VariableName IN ('product_OrderProductsByDir', 'product_OrderProductsThenByDir'); UPDATE ConfigurationAdmin SET ValueList = '1=la_opt_PriceCalculationByPrimary,2=la_opt_PriceCalculationByOptimal' WHERE VariableName = 'Comm_PriceBracketCalculation'; UPDATE ConfigurationAdmin SET ValueList = '1=la_opt_Sec,60=la_opt_Min,3600=la_opt_Hour,86400=la_opt_Day,604800=la_opt_Week,2419200=la_opt_Month,29030400=la_opt_Year' WHERE VariableName IN ('product_ReviewDelay_Interval', 'product_RatingDelay_Interval'); UPDATE CustomField SET FieldLabel = 'la_fld_cust_p_ItemTemplate', Prompt = 'la_fld_cust_p_ItemTemplate' WHERE FieldName = 'p_ItemTemplate'; UPDATE Events SET Type = 1 WHERE Event = 'BACKORDER.FULLFILL'; UPDATE ConfigurationAdmin SET ValueList = 'style="width: 50px;"' WHERE VariableName IN ('product_RatingDelay_Value', 'product_ReviewDelay_Value'); # ===== v 5.0.2-B1 ===== ALTER TABLE AffiliatePayments CHANGE Comment Comment text NULL, CHANGE PaymentDate PaymentDate INT(10) UNSIGNED NULL DEFAULT NULL; ALTER TABLE AffiliatePaymentTypes CHANGE Description Description text NULL; ALTER TABLE Affiliates CHANGE Comments Comments text NULL, CHANGE CreatedOn CreatedOn INT(11) NULL DEFAULT NULL; ALTER TABLE Manufacturers CHANGE Description Description text NULL; ALTER TABLE Orders CHANGE UserComment UserComment text NULL, CHANGE AdminComment AdminComment text NULL, CHANGE GWResult1 GWResult1 MEDIUMTEXT NULL, CHANGE GWResult2 GWResult2 MEDIUMTEXT NULL, CHANGE OrderDate OrderDate INT(10) UNSIGNED NULL DEFAULT NULL, CHANGE PaymentExpires PaymentExpires INT(10) UNSIGNED NULL DEFAULT NULL; ALTER TABLE PaymentTypes CHANGE PortalGroups PortalGroups text NULL; ALTER TABLE ProductOptionCombinations CHANGE Combination Combination text NULL; ALTER TABLE Products CHANGE ShippingLimitation ShippingLimitation text NULL, CHANGE PackageContent PackageContent MEDIUMTEXT NULL; ALTER TABLE ShippingQuoteEngines CHANGE Properties Properties text NULL; ALTER TABLE ShippingType CHANGE PortalGroups PortalGroups text NULL; ALTER TABLE ProductFiles CHANGE ProductId ProductId INT(11) NOT NULL DEFAULT '0', CHANGE `Name` `Name` VARCHAR(255) NOT NULL DEFAULT '', CHANGE Version Version VARCHAR(100) NOT NULL DEFAULT '', CHANGE FilePath FilePath VARCHAR(255) NOT NULL DEFAULT '', CHANGE RealPath RealPath VARCHAR(255) NOT NULL DEFAULT '', CHANGE Size Size INT(11) NOT NULL DEFAULT '0', CHANGE AddedOn AddedOn INT(11) NULL DEFAULT NULL; ALTER TABLE UserFileAccess CHANGE ProductId ProductId INT( 11 ) NOT NULL DEFAULT '0', CHANGE PortalUserId PortalUserId INT( 11 ) NOT NULL DEFAULT '0'; ALTER TABLE GatewayConfigFields CHANGE ValueList ValueList MEDIUMTEXT NULL; ALTER TABLE Currencies CHANGE `Status` `Status` SMALLINT(6) NOT NULL DEFAULT '1', CHANGE Modified Modified INT(11) NULL DEFAULT NULL; ALTER TABLE GiftCertificates CHANGE `Status` `Status` TINYINT(1) NOT NULL DEFAULT '2'; ALTER TABLE UserDownloads CHANGE StartedOn StartedOn INT(11) NULL DEFAULT NULL, CHANGE EndedOn EndedOn INT(11) NULL DEFAULT NULL; # ===== v 5.0.2-B2 ===== # ===== v 5.0.2-RC1 ===== # ===== v 5.0.2 ===== # ===== v 5.0.3-B1 ===== UPDATE Phrase SET PhraseType = 1 WHERE Phrase IN ( 'la_ship_All_Together', 'la_ship_Backorders_Upon_Avail', 'la_ship_Backorder_Separately', 'lu_ship_Shipment', 'lu_ship_ShippingType' ); # ===== v 5.0.3-B2 ===== # ===== v 5.0.3-RC1 ===== # ===== v 5.0.3 ===== # ===== v 5.0.4-B1 ===== # ===== v 5.0.4-B2 ===== # ===== v 5.0.4 ===== # ===== v 5.1.0-B1 ===== UPDATE Modules SET Path = 'modules/in-commerce/' WHERE `Name` = 'In-Commerce'; UPDATE ConfigurationValues SET ValueList = '0=lu_none||SELECT l%3$s_Name AS OptionName, IsoCode AS OptionValue FROM CountryStates WHERE Type = 1 ORDER BY OptionName' WHERE ValueList = '0=lu_none||SELECT DestName AS OptionName, DestAbbr AS OptionValue FROM StdDestinations WHERE DestParentId IS NULL Order BY OptionName'; ALTER TABLE SiteDomains ADD COLUMN BillingCountry varchar(3) NOT NULL DEFAULT '', ADD COLUMN ShippingCountry varchar(3) NOT NULL DEFAULT '', ADD COLUMN PrimaryCurrencyId int(11) NOT NULL DEFAULT '0', ADD COLUMN Currencies varchar(255) NOT NULL DEFAULT '', ADD COLUMN PrimaryPaymentTypeId int(11) NOT NULL DEFAULT '0', ADD COLUMN PaymentTypes varchar(255) NOT NULL DEFAULT '', ADD INDEX (BillingCountry), ADD INDEX (ShippingCountry), ADD INDEX (PrimaryCurrencyId), ADD INDEX (Currencies), ADD INDEX (PrimaryPaymentTypeId), ADD INDEX (PaymentTypes); UPDATE Phrase SET Module = 'Core' WHERE Phrase IN ('la_btn_Add', 'la_fld_RecipientName', 'la_fld_SenderName'); DELETE FROM Permissions WHERE Permission LIKE 'in-commerce:incommerce_configemail%'; # ===== v 5.1.0-B2 ===== # ===== v 5.1.0-RC1 ===== UPDATE Phrase SET PhraseType = 1 WHERE Phrase IN ( 'la_col_Qty', 'la_col_QtyBackordered', 'la_ItemBackordered', 'la_ship_all_together', 'la_ship_backorders_upon_avail', 'la_ship_backorder_separately', 'la_tooltip_New_Coupon', 'la_tooltip_New_Discount' ); DELETE FROM Phrase WHERE Phrase = 'la_comm_ProductsByManuf'; # ===== v 5.1.0 ===== ALTER TABLE Products CHANGE CachedRating CachedRating varchar(10) NOT NULL default '0'; # ===== v 5.1.1-B1 ===== ALTER TABLE Orders CHANGE ShippingOption ShippingOption TINYINT(4) NOT NULL DEFAULT '0'; ALTER TABLE ProductFiles CHANGE AddedById AddedById INT(11) NULL DEFAULT NULL; UPDATE ProductFiles SET AddedById = NULL WHERE AddedById = 0; ALTER TABLE Products CHANGE CreatedById CreatedById INT(11) NULL DEFAULT NULL , CHANGE ModifiedById ModifiedById INT(11) NULL DEFAULT NULL; UPDATE Products SET CreatedById = NULL WHERE CreatedById = 0; UPDATE Products SET ModifiedById = NULL WHERE ModifiedById = 0; # ===== v 5.1.1-B2 ===== # ===== v 5.1.1-RC1 ===== # ===== v 5.1.1 ===== # ===== v 5.1.2-B1 ===== DELETE FROM Phrase WHERE PhraseKey = 'LA_TITLE_ADDING_ORDER_ITEM'; # ===== v 5.1.2-B2 ===== UPDATE Phrase SET l<%PRIMARY_LANGUAGE%>_Translation = REPLACE(l<%PRIMARY_LANGUAGE%>_Translation, 'Discounts & Coupons', 'Discounts & Certificates') WHERE PhraseKey = 'LA_TAB_DISCOUNTSANDCOUPONS'; # ===== v 5.1.2-RC1 ===== UPDATE Phrase SET Module = 'Core' WHERE PhraseKey = 'LA_FLD_ISOCODE' OR PhraseKey = 'LA_COL_ISOCODE'; # ===== v 5.1.2 ===== # ===== v 5.1.3-B1 ===== ALTER TABLE AffiliatePlansBrackets CHANGE Percent Percent DECIMAL (10,2) NOT NULL DEFAULT '0.00'; # ===== v 5.1.3-B2 ===== # ===== v 5.1.3-RC1 ===== UPDATE ConfigurationValues SET VariableValue = 'in-commerce/products/product_detail' WHERE VariableName = 'p_ItemTemplate' AND VariableValue = 'in-commerce/designs/detail'; # ===== v 5.1.3-RC2 ===== # ===== v 5.1.3 ===== # ===== v 5.2.0-B1 ===== UPDATE SearchConfig SET DisplayName = REPLACE(DisplayName, 'lu_', 'lc_') WHERE DisplayName IN ('lu_field_descriptionex', 'lu_field_manufacturer', 'lu_field_qtysold', 'lu_field_topseller'); INSERT INTO SystemSettings VALUES(DEFAULT, 'OrderVATIncluded', '0', 'In-Commerce', 'in-commerce:general', 'la_Text_Orders', 'la_config_OrderVATIncluded', 'checkbox', NULL, NULL, 10.12, '0', '0', NULL); ALTER TABLE Orders ADD VATIncluded TINYINT(1) UNSIGNED NOT NULL DEFAULT '0'; INSERT INTO ItemFilters VALUES (DEFAULT, 'p', 'ManufacturerId', 'checkbox', 1, NULL), (DEFAULT, 'p', 'Price', 'range', 1, 11), (DEFAULT, 'p', 'EditorsPick', 'radio', 1, NULL); DELETE FROM LanguageLabels WHERE PhraseKey = 'LA_COL_ITEMNAME'; DELETE FROM LanguageLabels WHERE PhraseKey IN ('LA_ALLOWORDERINGINNONPRIMARYCURRENCY', 'LA_ALLOWORDERDIFFERENTTYPES'); DELETE FROM SystemSettings WHERE VariableName IN ('Comm_AllowOrderingInNonPrimaryCurrency', 'Comm_Allow_Order_Different_Types'); UPDATE SystemSettings SET DisplayOrder = 20.01 WHERE VariableName = 'Comm_ExchangeRateSource'; UPDATE SystemSettings SET DisplayOrder = DisplayOrder - 0.01 WHERE VariableName IN ( 'Comm_Enable_Backordering', 'Comm_Process_Backorders_Auto', 'Comm_Next_Order_Number', 'Comm_Order_Number_Format_P', 'Comm_Order_Number_Format_S', 'Comm_RecurringChargeInverval', 'Comm_AutoProcessRecurringOrders', 'MaxAddresses', 'Comm_MaskProcessedCreditCards', 'OrderVATIncluded' ); INSERT INTO SystemSettings VALUES(DEFAULT, 'MaxCompareProducts', '3', 'In-Commerce', 'in-commerce:output', 'la_Text_Products', 'la_config_MaxCompareProducts', 'text', NULL, NULL, 10.12, 0, 1, NULL); # ===== v 5.2.0-B2 ===== UPDATE Products main_table SET main_table.CachedReviewsQty = (SELECT COUNT(*) FROM <%TABLE_PREFIX%>CatalogReviews review_table WHERE review_table.ItemId = main_table.ResourceId); # ===== v 5.2.0-B3 ===== ALTER TABLE OrderItems CHANGE OptionsSalt OptionsSalt BIGINT(11) NULL DEFAULT '0'; UPDATE OrderItems SET OptionsSalt = CAST((OptionsSalt & 0xFFFFFFFF) AS UNSIGNED INTEGER) WHERE OptionsSalt < 0; ALTER TABLE ProductOptionCombinations CHANGE CombinationCRC CombinationCRC BIGINT(11) NOT NULL DEFAULT '0'; UPDATE ProductOptionCombinations SET CombinationCRC = CAST((CombinationCRC & 0xFFFFFFFF) AS UNSIGNED INTEGER) WHERE CombinationCRC < 0; # ===== v 5.2.0-RC1 ===== DELETE FROM Currencies WHERE ISO = 'NZD' LIMIT 1; # ===== v 5.2.0 ===== INSERT INTO Permissions VALUES(DEFAULT, 'in-commerce:general.add', 11, 1, 1, 0); INSERT INTO Permissions VALUES(DEFAULT, 'in-commerce:output.add', 11, 1, 1, 0); INSERT INTO Permissions VALUES(DEFAULT, 'in-commerce:contacts.add', 11, 1, 1, 0); # ===== v 5.2.1-B1 ===== ALTER TABLE Affiliates CHANGE PortalUserId PortalUserId INT(10) NULL DEFAULT NULL; UPDATE Affiliates SET PortalUserId = NULL WHERE PortalUserId = 0; # ===== v 5.2.1-B2 ===== UPDATE Modules SET ClassNamespace = 'Intechnic\\InPortal\\Modules\\InCommerce' WHERE `Name` = 'In-Commerce'; # ===== v 5.2.1-RC1 ===== # ===== v 5.2.1 ===== +# ===== v 5.2.2-B1 ===== + # ===== v 5.3.0-B1 ===== ALTER TABLE Affiliates DROP SSN; DELETE FROM LanguageLabels WHERE PhraseKey IN ('LA_FLD_SSN', 'LU_COMM_SSNFIELD', 'LU_FLD_SSNFIELD'); UPDATE Modules SET ClassNamespace = 'InPortal\\Modules\\InCommerce' WHERE `Name` = 'In-Commerce'; INSERT INTO SearchConfig VALUES ('Products', 'MetaKeywords', 0, 1, '', 'lc_field_MetaKeywords', 'In-Commerce', 'la_Text_Products', 24, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL); INSERT INTO SearchConfig VALUES ('Products', 'MetaDescription', 0, 1, '', 'lc_field_MetaDescription', 'In-Commerce', 'la_Text_Products', 25, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL); Index: branches/5.3.x/install.php =================================================================== --- branches/5.3.x/install.php (revision 16505) +++ branches/5.3.x/install.php (revision 16506) @@ -1,58 +1,58 @@ toolkit; /* @var $toolkit kInstallToolkit */ } $application =& kApplication::Instance(); $application->Init(); if ( $application->RecallVar('user_id') != USER_ROOT ) { die('restricted access!'); } $category =& $toolkit->createModuleCategory('Products', 'Product Catalog', '#in-commerce/section_design#', 'in-commerce/img/menu_products.gif'); $toolkit->RunSQL('/' . $module_folder . '/install/install_schema.sql'); $toolkit->RunSQL('/' . $module_folder . '/install/install_data.sql', '{ProductCatId}', $category->GetID()); -$toolkit->ImportLanguage('/' . $module_folder . '/install/english'); +$toolkit->ImportLanguage('/' . $module_folder . '/install/english', isset($constants_file)); $toolkit->SetModuleRootCategory(basename($module_folder), $category->GetID()); $toolkit->linkCustomFields(basename($module_folder), 'p', 11); // to create Custom Fields for Products $toolkit->linkCustomFields('KERNEL', 'u', 6); // to create shipping related Custom Fields for Users $toolkit->linkCustomFields('KERNEL', 'c', 1); // to create ItemTemplate custom field $toolkit->setModuleItemTemplate($category, 'p', '#in-commerce/item_design#'); $toolkit->finalizeModuleInstall($module_folder, true); Property changes on: branches/5.3.x ___________________________________________________________________ Modified: svn:mergeinfo Merged /w/in-commerce/branches/5.2.x:r16447,16458,16462,16464,16469,16489 Merged /w/in-commerce/releases/5.2.2-B1:r16493