Index: branches/5.2.x/units/gateways/gateways_config.php =================================================================== --- branches/5.2.x/units/gateways/gateways_config.php (revision 16377) +++ branches/5.2.x/units/gateways/gateways_config.php (revision 16378) @@ -1,74 +1,92 @@ 'gwf', 'ItemClass' => Array('class'=>'kDBItem','file'=>'','build_event'=>'OnItemBuild'), 'ListClass' => Array('class'=>'kDBList','file'=>'','build_event'=>'OnListBuild'), 'EventHandlerClass' => Array('class'=>'GatewayEventHandler','file'=>'gw_event_handler.php','build_event'=>'OnBuild'), 'TagProcessorClass' => Array('class'=>'GatewayTagProcessor','file'=>'gw_tag_processor.php','build_event'=>'OnBuild'), + + 'RegisterClasses' => array( + array('pseudo' => 'kGWBase', 'class' => 'kGWBase', 'file' => 'gw_classes/gw_base.php', 'build_event' => ''), + array('pseudo' => 'kAtosOriginGW', 'class' => 'kAtosOriginGW', 'file' => 'gw_classes/atosorigin.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWAuthorizeNet', 'class' => 'kGWAuthorizeNet', 'file' => 'gw_classes/authorizenet.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWGoogleCheckout', 'class' => 'kGWGoogleCheckout', 'file' => 'gw_classes/google_checkout.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWiDEALnl', 'class' => 'kGWiDEALnl', 'file' => 'gw_classes/ideal_nl.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kMultiCardsGW', 'class' => 'kMultiCardsGW', 'file' => 'gw_classes/multicards.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kPayboxGW', 'class' => 'kPayboxGW', 'file' => 'gw_classes/paybox.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kPaymentechGW', 'class' => 'kPaymentechGW', 'file' => 'gw_classes/paymentech.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWPayPal', 'class' => 'kGWPayPal', 'file' => 'gw_classes/paypal.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWPaypalDirect', 'class' => 'kGWPaypalDirect', 'file' => 'gw_classes/paypal_direct.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWRightConnect', 'class' => 'kGWRightConnect', 'file' => 'gw_classes/rightconnect.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kSellaGuestPayGW', 'class' => 'kSellaGuestPayGW', 'file' => 'gw_classes/sella_guestpay.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kVerisignPfLinkGW', 'class' => 'kVerisignPfLinkGW', 'file' => 'gw_classes/verisign_pflink.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + array('pseudo' => 'kGWWorldPay', 'class' => 'kGWWorldPay', 'file' => 'gw_classes/worldpay.php', 'build_event' => '', 'require_classes' => 'kGWBase'), + ), + 'AutoLoad' => true, 'Hooks' => Array( Array( 'Mode' => hBEFORE, 'Conditional' => false, 'HookToPrefix' => 'pt', 'HookToSpecial' => '', 'HookToEvent' => Array( 'OnListBuild' ), 'DoPrefix' => '', 'DoSpecial' => '', 'DoEvent' => 'OnCheckGateways', ), Array( 'Mode' => hBEFORE, 'Conditional' => true, 'HookToPrefix' => 'pt', 'HookToSpecial' => '', 'HookToEvent' => Array( 'OnCreate', 'OnSave', 'OnUpdate', 'onPreSaveAndGoToTab', 'onPreSaveAndGo' ), 'DoPrefix' => '', 'DoSpecial' => '', 'DoEvent' => 'OnSaveValues', ), ), 'QueryString' => Array( 1 => 'id', 2 => 'Page', 3 => 'PerPage', 4 => 'event', ), 'IDField' => 'GWConfigFieldId', 'TableName' => TABLE_PREFIX.'GatewayConfigFields', 'ListSQLs' => Array ( '' => 'SELECT * FROM %s', ), // key - special, value - list select sql - + 'ItemSQLs' => Array ( ''=>'SELECT * FROM %s', ), 'Fields' => Array ( 'GWConfigFieldId' => Array('type' => 'int', 'not_null' => 1, 'default' => 0, ), 'SystemFieldName' => Array('type' => 'string', 'not_null' => 1, 'default' => '', ), 'FieldName' => Array('type' => 'string', 'required' => true, 'max_len' => 100, 'not_null' => 1, 'default' => '', ), 'ElementType' => Array('type' => 'string', 'not_null' => 1, 'default' => 'text', ), 'ValueList' => Array('type' => 'string', 'default' => NULL), 'GatewayId' => Array('type' => 'int', 'not_null' => 1, 'default' => 0, ), ), 'VirtualFields' => Array ( 'Value' => Array('type' => 'string', 'default' => ''), - ), - ); \ No newline at end of file + ), + ); Index: branches/5.2.x/units/orders/orders_event_handler.php =================================================================== --- branches/5.2.x/units/orders/orders_event_handler.php (revision 16377) +++ branches/5.2.x/units/orders/orders_event_handler.php (revision 16378) @@ -1,4095 +1,4091 @@ 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 = $order_dummy->getStatusField(); if ( isset($field_values[$status_field]) && $order_dummy->GetDBField($status_field) != $field_values[$status_field] ) { // user can't change status by himself return false; } if ( $order_dummy->GetDBField($status_field) != ORDER_STATUS_INCOMPLETE ) { // user can't edit orders being processed return false; } if ( $event->Name == '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), 'OnCompleteOrder' => 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(); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - /* @var $gateway_object kGWBase */ + /** @var kGWBase $gateway_object */ + $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $payment_result = $gateway_object->DirectPayment($order->GetFieldValues(), $gw_data['gw_params']); $sql = 'UPDATE %s SET GWResult1 = %s WHERE %s = %s'; $sql = sprintf($sql, $order->TableName, $this->Conn->qstr($gateway_object->getGWResponce()), $order->IDField, $order->GetID() ); $this->Conn->Query($sql); $order->SetDBField('GWResult1', $gateway_object->getGWResponce() ); return array('result'=>$payment_result, 'data'=>$gateway_object->parsed_responce, 'gw_data' => $gw_data, 'error_msg'=>$gateway_object->getErrorMsg()); } /** * Returns parameters, used to send order-related e-mails * * @param OrdersItem $order * @return array */ function OrderEmailParams(&$order) { $billing_email = $order->GetDBField('BillingEmail'); $sql = 'SELECT Email FROM ' . $this->Application->getUnitOption('u', 'TableName') . ' WHERE PortalUserId = ' . $order->GetDBField('PortalUserId'); $user_email = $this->Conn->GetOne($sql); $ret = Array ( '_user_email' => $user_email, // for use when shipping vs user is required in InventoryAction 'to_name' => $order->GetDBField('BillingTo'), 'to_email' => $billing_email ? $billing_email : $user_email, ); return $ret; } function PrepareCoupons($event, &$order) { $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'); } if ($shipping_control === false || $shipping_control == SHIPPING_CONTROL_PREAUTH ) { $order->SetDBField('Status', ORDER_STATUS_PENDING); $order->Update(); } else { $this->SplitOrder($event, $order); } if (!$this->Application->isAdminUser) { // for tracking code $this->Application->StoreVar('last_order_amount', $order->GetDBField('TotalAmount')); $this->Application->StoreVar('last_order_number', $order->GetDBField('OrderNumber')); $this->Application->StoreVar('last_order_customer', $order->GetDBField('BillingTo')); $this->Application->StoreVar('last_order_user', $order->GetDBField('Username')); $event->redirect = $this->Application->GetVar('success_template'); $event->SetRedirectParam('m_cat_id', 0); } else { // $event->CallSubEvent('OnSave'); } $order_id = $order->GetId(); $order_idfield = $this->Application->getUnitOption('ord','IDField'); $order_table = $this->Application->getUnitOption('ord','TableName'); $original_amount = $order->GetDBField('SubTotal') + $order->GetDBField('ShippingCost') + $order->GetDBField('VAT') + $order->GetDBField('ProcessingFee') + $order->GetDBField('InsuranceFee') - $order->GetDBField('GiftCertificateDiscount'); $sql = 'UPDATE '.$order_table.' SET OriginalAmount = '.$original_amount.' WHERE '.$order_idfield.' = '.$order_id; $this->Conn->Query($sql); $this->Application->StoreVar('front_order_id', $order_id); $this->Application->RemoveVar('ord_id'); $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) { // such option combination is defined explicitly $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $sql = 'SELECT Availability FROM '.$poc_table.' WHERE CombinationCRC = '.$comb_salt; $comb_availble = $this->Conn->GetOne($sql); // 2.1. check if Availability flag is set, then if ($comb_availble == 1) { // 2.2. check for quantity in stock $table = Array(); $table['poc'] = $this->Application->getUnitOption('poc', 'TableName'); $table['p'] = $this->Application->getUnitOption('p', 'TableName'); $table['oi'] = $this->TablePrefix($event).'OrderItems'; $object = $event->getObject(); $ord_id = $object->GetID(); // 2.3. check if some amount of same combination & product are not already in shopping cart $sql = 'SELECT '. $table['p'].'.InventoryStatus,'. $table['p'].'.BackOrder, IF('.$table['p'].'.InventoryStatus = 2, '.$table['poc'].'.QtyInStock, '.$table['p'].'.QtyInStock) AS QtyInStock, IF('.$table['oi'].'.OrderItemId IS NULL, 0, '.$table['oi'].'.Quantity) AS Quantity FROM '.$table['p'].' LEFT JOIN '.$table['poc'].' ON '.$table['p'].'.ProductId = '.$table['poc'].'.ProductId LEFT JOIN '.$table['oi'].' ON ('.$table['oi'].'.OrderId = '.$ord_id.') AND ('.$table['oi'].'.OptionsSalt = '.$comb_salt.') AND ('.$table['oi'].'.ProductId = '.$product_id.') AND ('.$table['oi'].'.BackOrderFlag = 0) WHERE '.$table['poc'].'.CombinationCRC = '.$comb_salt; $product_info = $this->Conn->GetRow($sql); if ($product_info['InventoryStatus']) { $backordering = $this->Application->ConfigValue('Comm_Enable_Backordering'); if (!$backordering || $product_info['BackOrder'] == 0) { // backordering is not enabled generally or for this product directly, then check quantities in stock if ($qty + $product_info['Quantity'] > $product_info['QtyInStock']) { $this->Application->SetVar('opt_error', 2); $result = false; } } } } elseif ($comb_availble !== false) { $this->Application->SetVar('opt_error', 2); $result = false; } } if ($result) { $event->status = kEvent::erSUCCESS; $shop_cart_template = $this->Application->GetVar('shop_cart_template'); $event->redirect = $this->Application->isAdminUser || !$shop_cart_template ? true : $shop_cart_template; } else { $event->status = kEvent::erFAIL; } return $result; } /** * Enter description here... * * @param kEvent $event */ function OnUpdateItemOptions($event) { $opt_data = $this->Application->GetVar('options'); $options = getArrayValue($opt_data, $this->Application->GetVar('p_id')); if (!$options) { $qty_data = $this->Application->GetVar('qty'); $comb_id = key(getArrayValue($qty_data, $this->Application->GetVar('p_id'))); $options = unserialize($this->Conn->GetOne('SELECT Combination FROM '.TABLE_PREFIX.'ProductOptionCombinations WHERE CombinationId = '.$comb_id)); } if (!$options) return; $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_table = $this->Application->getUnitOption('p', 'TableName'); $products_idfield = $this->Application->getUnitOption('p', 'IDField'); $sql = 'SELECT AccessGroupId FROM %s WHERE %s = %s'; $item_data['PortalGroupId'] = $this->Conn->GetOne( sprintf($sql, $products_table, $products_idfield, $item_id) ); $pricing_table = $this->Application->getUnitOption('pr', 'TableName'); $pricing_idfield = $this->Application->getUnitOption('pr', 'IDField'); /* TODO check on implementation $sql = 'SELECT AccessDuration, AccessUnit, DurationType, AccessExpiration FROM %s WHERE %s = %s'; */ $sql = 'SELECT * FROM %s WHERE %s = %s'; $pricing_id = $this->GetPricingId($item_id, $item_data); $item_data['PricingId'] = $pricing_id; $pricing_info = $this->Conn->GetRow( sprintf($sql, $pricing_table, $pricing_idfield, $pricing_id ) ); $unit_secs = Array(1 => 1, 2 => 60, 3 => 3600, 4 => 86400, 5 => 604800, 6 => 2592000, 7 => 31536000); /* TODO check on implementation (code from customization healtheconomics.org) $item_data['DurationType'] = $pricing_info['DurationType']; $item_data['AccessExpiration'] = $pricing_info['AccessExpiration']; */ $item_data['Duration'] = $pricing_info['AccessDuration'] * $unit_secs[ $pricing_info['AccessUnit'] ]; return $item_data; } /** * Enter description here... * * @param kEvent $event */ function OnApplyCoupon($event) { $code = $this->Application->GetVar('coupon_code'); if ($code == '') { return ; } $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 < adodb_mktime()) || (isset($number_of_use) && $number_of_use <= 0)) { $event->status = kEvent::erFAIL; $object->setCheckoutError(OrderCheckoutErrorType::COUPON, OrderCheckoutError::COUPON_CODE_EXPIRED); $event->redirect = false; return ; } $last_used = adodb_mktime(); $coupon->SetDBField('LastUsedBy', $this->Application->RecallVar('user_id')); $coupon->SetDBField('LastUsedOn_date', $last_used); $coupon->SetDBField('LastUsedOn_time', $last_used); if ( isset($number_of_use) ) { $coupon->SetDBField('NumberOfUses', $number_of_use - 1); if ($number_of_use == 1) { $coupon->SetDBField('Status', 2); } } $coupon->Update(); $this->Application->setUnitOption('ord', 'AutoLoad', true); $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->getUnitOption('l', 'TableName').' WHERE LinkId = '.$link_id; $sql = 'SELECT ListingTypeId FROM '.$this->Application->getUnitOption('ls', 'TableName').' WHERE ItemResourceId = '.$this->Conn->GetOne($sql); $item_data['LinkId'] = $link_id; $item_data['ListingTypeId'] = $this->Conn->GetOne($sql); } $sql = 'SELECT VirtualProductId FROM '.$this->Application->getUnitOption('lst', 'TableName').' WHERE ListingTypeId = '.$item_data['ListingTypeId']; $item_id = $this->Conn->GetOne($sql); $event->setEventParam('ItemData', serialize($item_data)); $this->AddItemToOrder($event, $item_id); $shop_cart_template = $this->Application->GetVar('shop_cart_template'); if ( $shop_cart_template ) { $event->redirect = $shop_cart_template; } // don't pass unused info to shopping cart, brokes old mod-rewrites $event->SetRedirectParam('pass', 'm'); // not to pass link id $event->SetRedirectParam('m_cat_id', 0); // not to pass link id } function OnRemoveFromCart($event) { $ord_item_id = $this->Application->GetVar('orditems_id'); $ord_id = $this->getPassedID($event); $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'OrderItems WHERE OrderId = '.$ord_id.' AND OrderItemId = '.$ord_item_id); $this->OnRecalculateItems($event); } function OnCleanupCart($event) { $object = $event->getObject(); $sql = 'DELETE FROM '.TABLE_PREFIX.'OrderItems WHERE OrderId = '.$this->getPassedID($event); $this->Conn->Query($sql); $this->RemoveCoupon($object); $this->RemoveGiftCertificate($object); $this->OnRecalculateItems($event); } /** * Returns order id from session or last used * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { $event->setEventParam('raise_warnings', 0); $passed = parent::getPassedID($event); if ( $this->Application->isAdminUser ) { // work as usual in admin return $passed; } if ( $event->Special == 'last' ) { // return last order id (for using on thank you page) $order_id = $this->Application->RecallVar('front_order_id'); return $order_id > 0 ? $order_id : FAKE_ORDER_ID; // FAKE_ORDER_ID helps to keep parent filter for order items set in "kDBList::linkToParent" } $ses_id = $this->Application->RecallVar($event->getPrefixSpecial(true) . '_id'); if ( $passed && ($passed != $ses_id) ) { // order id given in url doesn't match our current order id $sql = 'SELECT PortalUserId FROM ' . TABLE_PREFIX . 'Orders WHERE OrderId = ' . $passed; $user_id = $this->Conn->GetOne($sql); if ( $user_id == $this->Application->RecallVar('user_id') ) { // current user is owner of order with given id -> allow him to view order details return $passed; } else { // current user is not owner of given order -> hacking attempt $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); return 0; } } // not passed or equals to ses_id return $ses_id > 0 ? $ses_id : FAKE_ORDER_ID; // FAKE_ORDER_ID helps to keep parent filter for order items set in "kDBList::linkToParent" } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { $id = $this->getPassedID($event); if ( $id == FAKE_ORDER_ID ) { // if we already know, that there is no such order, // then don't run database query, that will confirm that $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->getUnitOption('poc', 'TableName'); $query = ' SELECT SUM(IF( IF('.TABLE_PREFIX.'Products.InventoryStatus = 2, '.$poc_table.'.QtyInStock, '.TABLE_PREFIX.'Products.QtyInStock) - '.TABLE_PREFIX.'Products.QtyInStockMin >= ('.TABLE_PREFIX.'OrderItems.Quantity - '.TABLE_PREFIX.'OrderItems.QuantityReserved), 0, 1)) FROM '.TABLE_PREFIX.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Products ON '.TABLE_PREFIX.'Products.ProductId = '.TABLE_PREFIX.'OrderItems.ProductId LEFT JOIN '.$poc_table.' ON ('.$poc_table.'.CombinationCRC = '.TABLE_PREFIX.'OrderItems.OptionsSalt) AND ('.$poc_table.'.ProductId = '.TABLE_PREFIX.'OrderItems.ProductId) WHERE OrderId = '.$ord_id.' GROUP BY OrderId'; // IF (IF(InventoryStatus = 2, poc.QtyInStock, p.QtyInStock) - QtyInStockMin >= (Quantity - QuantityReserved), 0, 1 return ($this->Conn->GetOne($query) == 0); } /** * Return all option combinations used in order * * @param kDBList $order_items * @return Array */ function queryCombinations(&$order_items) { // 1. collect combination crc used in order $combinations = Array(); while (!$order_items->EOL()) { $row = $order_items->getCurrentRecord(); if ($row['OptionsSalt'] == 0) { $order_items->GoNext(); continue; } $combinations[] = '(poc.ProductId = '.$row['ProductId'].') AND (poc.CombinationCRC = '.$row['OptionsSalt'].')'; $order_items->GoNext(); } $order_items->GoFirst(); $combinations = array_unique($combinations); // if same combination+product found as backorder & normal order item if ($combinations) { // 2. query data about combinations $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $sql = 'SELECT CONCAT(poc.ProductId, "_", poc.CombinationCRC) AS CombinationKey, poc.* FROM '.$poc_table.' poc WHERE ('.implode(') OR (', $combinations).')'; return $this->Conn->Query($sql, 'CombinationKey'); } return Array(); } /** * Returns object to perform inventory actions on * * @param ProductsItem $product current product object in order * @param kDBItem $combination combination dummy object * @param Array $combination_data pre-queried combination data * @return kDBItem */ function &getInventoryObject(&$product, &$combination, $combination_data) { if ($product->GetDBField('InventoryStatus') == 2) { // inventory by option combinations $combination->SetDBFieldsFromHash($combination_data); $combination->setID($combination_data['CombinationId']); $change_item =& $combination; } else { // inventory by product ifself $change_item =& $product; } return $change_item; } /** * Approve order ("Pending" tab) * * @param kDBList $order_items * @return int new status of order if any */ function approveOrder(&$order_items) { $product_object = $this->Application->recallObject('p', null, Array('skip_autoload' => true)); $order_item = $this->Application->recallObject('orditems.-item', null, Array('skip_autoload' => true)); $combination_item = $this->Application->recallObject('poc.-item', null, Array('skip_autoload' => true)); $combinations = $this->queryCombinations($order_items); while (!$order_items->EOL()) { $rec = $order_items->getCurrentRecord(); $order_item->SetDBFieldsFromHash($rec); $order_item->SetId($rec['OrderItemId']); $order_item->SetDBField('QuantityReserved', 0); $order_item->Update(); $product_object->Load( $rec['ProductId'] ); if (!$product_object->GetDBField('InventoryStatus')) { // if no inventory info is collected, then skip this order item $order_items->GoNext(); continue; } $inv_object =& $this->getInventoryObject($product_object, $combination_item, $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ]); // decrease QtyReserved by amount of product used in order $inv_object->SetDBField('QtyReserved', $inv_object->GetDBField('QtyReserved') - $rec['Quantity']); $inv_object->Update(); if ($product_object->GetDBField('InventoryStatus') == 2) { // inventory by options, then restore changed combination values back to common $combinations array !!! $combinations[ $rec['ProductId'].'_'.$rec['OptionsSalt'] ] = $inv_object->GetFieldValues(); } $order_items->GoNext(); } return true; } /** * Restores reserved items in the order * * @param kDBList $order_items * @return bool */ function restoreOrder(&$order_items) { $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(); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - /* @var $gateway_object kGWBase */ + /** @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->getUnitOption('p', 'TableName').' WHERE FLOOR(Hits) = '.$hits; $hits = ( $res = $this->Conn->GetOne($sql) ) ? $res + 0.000001 : $hits; $product_object->SetDBField('Hits', $hits); $product_object->Update(); /*$sql = 'UPDATE '.$this->Application->getUnitOption('p', 'TableName').' SET Hits = Hits + '.$product_item['Quantity'].' WHERE ProductId = '.$product_item['ProductId']; $this->Conn->Query($sql);*/ } $this->PrepareCoupons($event, $object); $this->SplitOrder($event, $object); if ( $object->GetDBField('IsRecurringBilling') != 1 ) { $this->Application->emailUser('ORDER.APPROVE', null, $email_params); // Mask credit card with XXXX if ( $this->Application->ConfigValue('Comm_MaskProcessedCreditCards') ) { $this->maskCreditCard($object, 'PaymentAccount'); $set_new_status = 1; } } } break; case 'OnMassOrderDeny': case 'OnOrderDeny': foreach ($order_items->Records as $product_item) { if (!$product_item['ProductId']) { // product may have been deleted continue; } $this->raiseProductEvent('Deny', $product_item['ProductId'], $product_item); } if ( ($original_order_status != ORDER_STATUS_INCOMPLETE) && ($event->Name == 'OnMassOrderDeny' || $event->Name == 'OnOrderDeny') ) { $this->Application->emailUser('ORDER.DENY', null, $email_params); // inform payment gateway that order was declined $gw_data = $object->getGatewayData(); if ( $gw_data ) { - $this->Application->registerClass($gw_data['ClassName'], GW_CLASS_PATH . '/' . $gw_data['ClassFile']); + /** @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', adodb_mktime()); $object->UpdateFormattersSubFields(); $shipping_email = $object->GetDBField('ShippingEmail'); $email_params['to_email'] = $shipping_email ? $shipping_email : $email_params['_user_email']; $this->Application->emailUser('ORDER.SHIP', null, $email_params); // inform payment gateway that order was shipped $gw_data = $object->getGatewayData(); - $this->Application->registerClass($gw_data['ClassName'], GW_CLASS_PATH . '/' . $gw_data['ClassFile']); + /** @var kGWBase $gateway_object */ $gateway_object = $this->Application->recallObject($gw_data['ClassName']); - $gateway_object->OrderShipped($object->GetFieldValues(), $gw_data['gw_params']); } else { $sqe_errors = $this->Application->RecallVar('sqe_errors'); $sqe_errors = $sqe_errors ? unserialize($sqe_errors) : Array (); $sqe_errors[ $object->GetField('OrderNumber') ] = $ret['error_description']; $this->Application->StoreVar('sqe_errors', serialize($sqe_errors)); } break; case 'OnMassOrderProcess': case 'OnOrderProcess': if ( $this->ReadyToProcess($object->GetID()) ) { $event->CallSubEvent('OnReserveItems'); if ( $event->status == kEvent::erSUCCESS ) { $set_new_status = true; } $this->Application->emailUser('BACKORDER.PROCESS', null, $email_params); } else { $event->status = kEvent::erFAIL; } break; } if ( $set_new_status ) { $object->Update(); } } /** * Hides last 4 digits from credit card number * * @param OrdersItem $object * @param string $field */ function maskCreditCard(&$object, $field) { $value = $object->GetDBField($field); $value = preg_replace('/'.substr($value, -4).'$/', str_repeat('X', 4), $value); $object->SetDBField($field, $value); } /** * Set next available order number. * * @param kEvent $event Event. * * @return void */ protected function setNextOrderNumber(kEvent $event) { /** @var OrdersItem $object */ $object = $event->getObject(); $next_order_number = $this->getNextOrderNumber(); $object->SetDBField('Number', $next_order_number); $object->SetDBField('SubNumber', 0); // set virtual field too $number_format = (int)$this->Application->ConfigValue('Comm_Order_Number_Format_P'); $sub_number_format = (int)$this->Application->ConfigValue('Comm_Order_Number_Format_S'); $order_number = sprintf('%0' . $number_format . 'd', $next_order_number) . '-' . str_repeat('0', $sub_number_format); $object->SetDBField('OrderNumber', $order_number); } /** * Returns order number to be used next. * * @return integer */ protected function getNextOrderNumber() { $config_table = $this->Application->getUnitOption('conf', 'TableName'); $this->Conn->Query('LOCK TABLES ' . $config_table . ' WRITE'); $sql = 'UPDATE ' . $config_table . ' SET VariableValue = VariableValue + 1 WHERE VariableName = "Comm_Next_Order_Number"'; $this->Conn->Query($sql); $sql = 'SELECT VariableValue FROM ' . $config_table . ' WHERE VariableName = "Comm_Next_Order_Number"'; $next_order_number = $this->Conn->GetOne($sql); $this->Conn->Query('UNLOCK TABLES'); $this->Application->SetConfigValue('Comm_Next_Order_Number', $next_order_number); return $next_order_number - 1; } /** * [HOOK] Ensures, that "Next Order Number" system setting is within allowed limits. * * @param kEvent $event Event. * * @return void */ protected function OnBeforeNextOrderNumberChange(kEvent $event) { /** @var kDBItem $system_setting */ $system_setting = $event->MasterEvent->getObject(); $old_value = $system_setting->GetOriginalField('VariableValue'); $new_value = $system_setting->GetDBField('VariableValue'); if ( $system_setting->GetDBField('VariableName') != 'Comm_Next_Order_Number' || $new_value == $old_value ) { return; } $sql = 'SELECT MAX(Number) FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName'); $next_order_number = (int)$this->Conn->GetOne($sql) + 1; if ( $new_value < $next_order_number ) { $system_setting->SetError('VariableValue', 'value_out_of_range', null, array( 'min_value' => $next_order_number, 'max_value' => '∞', )); } } /** * Set's new order address based on another address from order (e.g. billing from shipping) * * @param unknown_type $object * @param unknown_type $from * @param unknown_type $to */ function DoResetAddress(&$object, $from, $to) { $fields = Array('To','Company','Phone','Fax','Email','Address1','Address2','City','State','Zip','Country'); if ($from == 'User') { // skip these fields when coping from user, because they are not present in user profile $tmp_fields = array_flip($fields); // unset($tmp_fields['Company'], $tmp_fields['Fax'], $tmp_fields['Address2']); $fields = array_flip($tmp_fields); } // apply modification foreach ($fields as $field_name) { $object->SetDBField($to.$field_name, $object->GetDBField($from.$field_name)); } } /** * Set's status incomplete to all cloned orders * * @param kEvent $event * @return void * @access protected */ protected function OnAfterClone(kEvent $event) { parent::OnAfterClone($event); $id = $event->getEventParam('id'); $table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); // set cloned order status to Incomplete $sql = 'UPDATE ' . $table . ' SET Status = 0 WHERE ' . $id_field . ' = ' . $id; $this->Conn->Query($sql); } /* ======================== COMMON CODE ======================== */ /** * Split one timestamp field into 2 virtual fields * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); $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'))); } 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->getUnitOption('poc', 'TableName'); $query = ' SELECT BackOrderFlag, '. $table_prefix.'OrderItems.OrderItemId, '. $table_prefix.'OrderItems.Quantity, '. $table_prefix.'OrderItems.QuantityReserved, IF('.TABLE_PREFIX.'Products.InventoryStatus = 2, '.$poc_table.'.QtyInStock, '.TABLE_PREFIX.'Products.QtyInStock) AS QtyInStock, '. TABLE_PREFIX.'Products.QtyInStockMin, '. $table_prefix.'OrderItems.ProductId, '. TABLE_PREFIX.'Products.InventoryStatus,'. $table_prefix.'OrderItems.OptionsSalt AS CombinationCRC FROM '.$table_prefix.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Products ON '.TABLE_PREFIX.'Products.ProductId = '.$table_prefix.'OrderItems.ProductId LEFT JOIN '.$poc_table.' ON ('.$poc_table.'.CombinationCRC = '.$table_prefix.'OrderItems.OptionsSalt) AND ('.$poc_table.'.ProductId = '.$table_prefix.'OrderItems.ProductId) WHERE OrderId = '.$ord_id.' AND '.TABLE_PREFIX.'Products.Type = 1 ORDER BY BackOrderFlag ASC'; $items = $this->Conn->Query($query); return $items; } function ReserveItems($event) { $table_prefix = $this->TablePrefix($event); $items =& $this->queryOrderItems($event, $table_prefix); foreach ($items as $an_item) { if (!$an_item['InventoryStatus']) { $to_reserve = $an_item['Quantity'] - $an_item['QuantityReserved']; } else { if ($an_item['BackOrderFlag'] > 0) { // we don't need to reserve if it's backordered item $to_reserve = 0; } else { $to_reserve = min($an_item['Quantity']-$an_item['QuantityReserved'], $an_item['QtyInStock']-$an_item['QtyInStockMin']); //it should be equal, but just in case } $to_backorder = $an_item['BackOrderFlag'] > 0 ? $an_item['Quantity']-$an_item['QuantityReserved'] : 0; } if ($to_backorder < 0) $to_backorder = 0; //just in case $query = ' UPDATE '.$table_prefix.'OrderItems SET QuantityReserved = IF(QuantityReserved IS NULL, '.$to_reserve.', QuantityReserved + '.$to_reserve.') WHERE OrderItemId = '.$an_item['OrderItemId']; $this->Conn->Query($query); if (!$an_item['InventoryStatus']) continue; $update_clause = ' QtyInStock = QtyInStock - '.$to_reserve.', QtyReserved = QtyReserved + '.$to_reserve.', QtyBackOrdered = QtyBackOrdered + '.$to_backorder; if ($an_item['InventoryStatus'] == 1) { // inventory by product, then update it's quantities $query = ' UPDATE '.TABLE_PREFIX.'Products SET '.$update_clause.' WHERE ProductId = '.$an_item['ProductId']; } else { // inventory = 2 -> by product option combinations $poc_idfield = $this->Application->getUnitOption('poc', 'IDField'); $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $query = ' UPDATE '.$poc_table.' SET '.$update_clause.' WHERE (ProductId = '.$an_item['ProductId'].') AND (CombinationCRC = '.$an_item['CombinationCRC'].')'; } $this->Conn->Query($query); } } function FreeItems($event) { $table_prefix = $this->TablePrefix($event); $items =& $this->queryOrderItems($event, $table_prefix); foreach ($items as $an_item) { $to_free = $an_item['QuantityReserved']; if ($an_item['InventoryStatus']) { if ($an_item['BackOrderFlag'] > 0) { // we don't need to free if it's backordered item $to_free = 0; } // what's not reserved goes to backorder in stock for orderitems marked with BackOrderFlag $to_backorder_free = $an_item['BackOrderFlag'] > 0 ? $an_item['Quantity'] - $an_item['QuantityReserved'] : 0; if ($to_backorder_free < 0) $to_backorder_free = 0; //just in case $update_clause = ' QtyInStock = QtyInStock + '.$to_free.', QtyReserved = QtyReserved - '.$to_free.', QtyBackOrdered = QtyBackOrdered - '.$to_backorder_free; if ($an_item['InventoryStatus'] == 1) { // inventory by product $query = ' UPDATE '.TABLE_PREFIX.'Products SET '.$update_clause.' WHERE ProductId = '.$an_item['ProductId']; } else { // inventory by option combinations $poc_idfield = $this->Application->getUnitOption('poc', 'IDField'); $poc_table = $this->Application->getUnitOption('poc', 'TableName'); $query = ' UPDATE '.$poc_table.' SET '.$update_clause.' WHERE (ProductId = '.$an_item['ProductId'].') AND (CombinationCRC = '.$an_item['CombinationCRC'].')'; } $this->Conn->Query($query); } $query = ' UPDATE '.$table_prefix.'OrderItems SET QuantityReserved = IF(QuantityReserved IS NULL, 0, QuantityReserved - '.$to_free.') WHERE OrderItemId = '.$an_item['OrderItemId']; $this->Conn->Query($query); } } /** * Enter description here... * * @param kEvent $event * @param OrdersItem $object */ function SplitOrder($event, &$object) { $affiliate_event = new kEvent('affil:OnOrderApprove'); $affiliate_event->setEventParam('Order_PrefixSpecial', $object->getPrefixSpecial() ); $this->Application->HandleEvent($affiliate_event); $table_prefix = $this->TablePrefix($event); $order =& $object; $ord_id = $order->GetId(); $shipping_option = $order->GetDBField('ShippingOption'); $backorder_select = $shipping_option == 0 ? '0' : '%s.BackOrderFlag'; // setting PackageNum to 0 for Non-tangible items, for tangibles first package num is always 1 $query = ' SELECT oi.OrderItemId FROM ' . $table_prefix . 'OrderItems oi LEFT JOIN ' . TABLE_PREFIX . 'Products p ON p.ProductId = oi.ProductId WHERE p.Type > 1 AND oi.OrderId = ' . $ord_id; $non_tangibles = $this->Conn->GetCol($query); if ($non_tangibles) { $query = ' UPDATE ' . $table_prefix . 'OrderItems SET PackageNum = 0 WHERE OrderItemId IN (' . implode(',', $non_tangibles) . ')'; $this->Conn->Query($query); } // grouping_data: // 0 => Product Type // 1 => if NOT tangibale and NOT downloadable - OrderItemId, // 2 => ProductId // 3 => Shipping PackageNum $query = 'SELECT ' . sprintf($backorder_select, $table_prefix . 'OrderItems') . ' AS BackOrderFlagCalc, PackageNum, ProductName, ShippingTypeId, CONCAT('.TABLE_PREFIX.'Products.Type, "_", IF ('.TABLE_PREFIX.'Products.Type NOT IN ('.PRODUCT_TYPE_DOWNLOADABLE.','.PRODUCT_TYPE_TANGIBLE.'), CONCAT(OrderItemId, "_", '.TABLE_PREFIX.'Products.ProductId), ""), "_", PackageNum ) AS Grouping, SUM(Quantity) AS TotalItems, SUM('.$table_prefix.'OrderItems.Weight*Quantity) AS TotalWeight, SUM(Price * Quantity) AS TotalAmount, SUM(QuantityReserved) AS TotalReserved, '.TABLE_PREFIX.'Products.Type AS ProductType FROM '.$table_prefix.'OrderItems LEFT JOIN '.TABLE_PREFIX.'Products ON '.TABLE_PREFIX.'Products.ProductId = '.$table_prefix.'OrderItems.ProductId WHERE OrderId = '.$ord_id.' GROUP BY BackOrderFlagCalc, Grouping ORDER BY BackOrderFlagCalc ASC, PackageNum ASC, ProductType ASC'; $sub_orders = $this->Conn->Query($query); $processed_sub_orders = Array(); // in case of recurring billing this will not be 0 as usual //$first_sub_number = ($event->Special == 'recurring') ? $object->getNextSubNumber() - 1 : 0; $first_sub_number = $object->GetDBField('SubNumber'); $next_sub_number = $first_sub_number; $group = 1; $order_has_gift = $order->GetDBField('GiftCertificateDiscount') > 0 ? 1 : 0; $skip_types = Array (PRODUCT_TYPE_TANGIBLE, PRODUCT_TYPE_DOWNLOADABLE); foreach ($sub_orders as $sub_order_data) { $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'); } } 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'); $processing_fee = $this->Conn->GetOne('SELECT ProcessingFee FROM '.$this->Application->getUnitOption('pt', 'TableName').' WHERE PaymentTypeId = '.$pt); $object->SetDBField( 'ProcessingFee', $processing_fee ); $this->UpdateTotals($event); } function UpdateTotals($event) { $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->getUnitOption('orditems', 'TableName').' WHERE OrderId = '.$object->GetDBField('OrderId'); $orditems = $this->Conn->GetCol($sql, 'ProductId'); $sql = 'SELECT coupi.ItemType, p.ProductId FROM '.$this->Application->getUnitOption('coupi', 'TableName').' coupi LEFT JOIN '.$this->Application->getUnitOption('p', 'TableName').' p ON coupi.ItemResourceId = p.ResourceId WHERE CouponId = '.$object->GetDBField('CouponId'); $discounts = $this->Conn->GetCol($sql, 'ProductId'); $discount_amount = 0; foreach($orditems as $product_id => $amount) { if(isset($discounts[$product_id]) || array_search('0', $discounts, true) !== false) { switch($coupon->GetDBField('Type')) { case 1: $discount_amount += $coupon->GetDBField('Amount') < $amount ? $coupon->GetDBField('Amount') : $amount; break; case 2: $discount_amount += $amount * $coupon->GetDBField('Amount') / 100; break; default: } break; } } $object->SetDBField('CouponDiscount', $discount_amount); return $discount_amount; }*/ /** * Jumps to selected order in order's list from search tab * * @param kEvent $event */ function OnGoToOrder($event) { $id = current($this->StoreSelectedIDs($event)); $id_field = $this->Application->getUnitOption($event->Prefix,'IDField'); $table = $this->Application->getUnitOption($event->Prefix,'TableName'); $sql = 'SELECT Status FROM %s WHERE %s = %s'; $order_status = $this->Conn->GetOne( sprintf($sql, $table, $id_field, $id) ); $prefix_special = $event->Prefix.'.'.$this->getSpecialByType($order_status); $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) { $ord_table = $this->Application->getUnitOption('ord', 'TableName'); $ord_idfield = $this->Application->getUnitOption('ord', 'IDField'); $processing_allowed = Array(ORDER_STATUS_PROCESSED, ORDER_STATUS_ARCHIVED); $sql = 'SELECT '.$ord_idfield.', PortalUserId, GroupId, NextCharge FROM '.$ord_table.' WHERE (IsRecurringBilling = 1) AND (NextCharge < '.$pre_expiration.') AND Status IN ('.implode(',', $processing_allowed).')'; return $this->Conn->Query($sql, $ord_idfield); } /** * [SCHEDULED TASK] Checks what orders should expire and renew automatically (if such flag set) * * @param kEvent $event */ function OnCheckRecurringOrders($event) { $skip_clause = Array(); $ord_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $ord_idfield = $this->Application->getUnitOption($event->Prefix, 'IDField'); $pre_expiration = adodb_mktime() + $this->Application->ConfigValue('Comm_RecurringChargeInverval') * 3600 * 24; $to_charge = $this->getRecurringOrders($pre_expiration); if ($to_charge) { $order_ids = Array(); foreach ($to_charge as $order_id => $record) { // skip virtual users (e.g. root, guest, etc.) & invalid subscriptions (with no group specified, no next charge, but Recurring flag set) if (!$record['PortalUserId'] || !$record['GroupId'] || !$record['NextCharge']) continue; $order_ids[] = $order_id; // prevent duplicate user+group pairs $skip_clause[ 'PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'] ] = $order_id; } // process only valid orders $temp_handler = $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler'); $cloned_order_ids = $temp_handler->CloneItems($event->Prefix, 'recurring', $order_ids); $order =& $this->Application->recallObject($event->Prefix.'.recurring', null, Array('skip_autoload' => true)); foreach ($cloned_order_ids as $order_id) { $order->Load($order_id); $this->Application->HandleEvent($complete_event, $event->Prefix.'.recurring:OnCompleteOrder' ); if ($complete_event->status == kEvent::erSUCCESS) { //send recurring ok email $this->Application->emailUser('ORDER.RECURRING.PROCESSED', null, $this->OrderEmailParams($order)); $this->Application->emailAdmin('ORDER.RECURRING.PROCESSED'); } else { //send Recurring failed event $order->SetDBField('Status', ORDER_STATUS_DENIED); $order->Update(); $this->Application->emailUser('ORDER.RECURRING.DENIED', null, $this->OrderEmailParams($order)); $this->Application->emailAdmin('ORDER.RECURRING.DENIED'); } } // remove recurring flag from all orders found, not to select them next time script runs $sql = 'UPDATE '.$ord_table.' SET IsRecurringBilling = 0 WHERE '.$ord_idfield.' IN ('.implode(',', array_keys($to_charge)).')'; $this->Conn->Query($sql); } if ( !is_object($event->MasterEvent) ) { // not called as hook return ; } $pre_expiration = adodb_mktime() + $this->Application->ConfigValue('User_MembershipExpirationReminder') * 3600 * 24; $to_charge = $this->getRecurringOrders($pre_expiration); foreach ($to_charge as $order_id => $record) { // skip virtual users (e.g. root, guest, etc.) & invalid subscriptions (with no group specified, no next charge, but Recurring flag set) if (!$record['PortalUserId'] || !$record['GroupId'] || !$record['NextCharge']) continue; // prevent duplicate user+group pairs $skip_clause[ 'PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'] ] = $order_id; } $skip_clause = array_flip($skip_clause); $event->MasterEvent->setEventParam('skip_clause', $skip_clause); } function OnGeneratePDF($event) { $this->OnLoadSelected($event); $this->Application->InitParser(); $o = $this->Application->ParseBlock(array('name'=>'in-commerce/orders/orders_pdf')); $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'); $calc_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields'); foreach ($calc_fields as $special => $fields) { $calc_fields[$special]['OrderNumber'] = str_replace('6', $order_number, $calc_fields[$special]['OrderNumber']); $calc_fields[$special]['OrderNumber'] = str_replace('3', $order_sub_number, $calc_fields[$special]['OrderNumber']); } $this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calc_fields); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $fields['Number']['format'] = str_replace('%06d', '%0'.$order_number.'d', $fields['Number']['format']); $fields['SubNumber']['format'] = str_replace('%03d', '%0'.$order_sub_number.'d', $fields['SubNumber']['format']); $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'] ); } $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); $user_forms = $this->Application->getUnitOption('u', 'Forms'); $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); $virtual_fields['UserPassword']['hashing_method'] = $user_forms['default']['Fields']['PasswordHashingMethod']['default']; $this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields); } /** * Allows configuring export options * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeExportBegin(kEvent $event) { parent::OnBeforeExportBegin($event); /** @var kDBItem $object */ $object = $this->Application->recallObject($event->Prefix . '.export'); $object->SetField('Number', 999999); $object->SetField('SubNumber', 999); } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array * @access protected */ public function getCustomExportColumns(kEvent $event) { $columns = parent::getCustomExportColumns($event); $new_columns = Array ( '__VIRTUAL__CustomerName' => 'CustomerName', '__VIRTUAL__TotalAmount' => 'TotalAmount', '__VIRTUAL__AmountWithoutVAT' => 'AmountWithoutVAT', '__VIRTUAL__SubtotalWithDiscount' => 'SubtotalWithDiscount', '__VIRTUAL__SubtotalWithoutDiscount' => 'SubtotalWithoutDiscount', '__VIRTUAL__OrderNumber' => 'OrderNumber', ); return array_merge($columns, $new_columns); } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { parent::OnSave($event); if ( $event->status != kEvent::erSUCCESS ) { return ; } foreach ( $this->trackCopiedOrderIDs($event) as $id ) { $this->Application->removeObject($event->getPrefixSpecial()); $an_event = new kEvent($this->Prefix . ':Dummy'); $this->Application->SetVar($this->Prefix . '_id', $id); $this->Application->SetVar($this->Prefix . '_mode', ''); // this is to fool ReserveItems to use live table $this->ReserveItems($an_event); } } /** * Occurs after an item has been copied to live table * Id of copied item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToLive(kEvent $event) { parent::OnAfterCopyToLive($event); $this->trackCopiedOrderIDs($event, $event->getEventParam('id')); } /** * Tracks copied order IDs. * * @param kEvent $event Event. * @param integer $id Order ID. * * @return array */ protected function trackCopiedOrderIDs(kEvent $event, $id = null) { $setting_name = $event->Prefix . '_copied_ids' . $this->Application->GetVar('wid'); $ids = $this->Application->GetVar($setting_name, array()); if ( isset($id) ) { array_push($ids, $id); $this->Application->SetVar($setting_name, $ids); } return $ids; } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { if ( $this->Application->isAdminUser ) { return true; } $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 < adodb_mktime()) || ($debit <= 0) ) { $event->status = kEvent::erFAIL; $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_CODE_EXPIRED); $event->redirect = false; return; } $object->SetDBField('GiftCertificateId', $gift_certificate->GetDBField('GiftCertificateId')); $object->Update(); $object->setCheckoutError(OrderCheckoutErrorType::GIFT_CERTIFICATE, OrderCheckoutError::GC_APPLIED); } /** * Removes gift certificate from order * * @param kEvent $event * @deprecated */ function OnRemoveGiftCertificate($event) { $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 */ function OnUpdateAjax($event) { $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); /* @var $ajax_form_helper AjaxFormHelper */ $ajax_form_helper->transitEvent($event, 'OnUpdate'); } } Index: branches/5.2.x/units/orders/order_validator.php =================================================================== --- branches/5.2.x/units/orders/order_validator.php (revision 16377) +++ branches/5.2.x/units/orders/order_validator.php (revision 16378) @@ -1,247 +1,246 @@ ErrorMsgs['credit_card_validation_error'] = '!lu_cc_validation_error!'; $this->ErrorMsgs['credit_card_expired'] = '!lu_cc_expired!'; } /** * Return error message for field * * @param string $field * @return string * @access public */ public function GetErrorMsg($field, $force_escape = null) { if ( $field != 'OrderNumber' ) { return parent::GetErrorMsg($field, $force_escape); } $number['error'] = parent::GetErrorMsg('Number', $force_escape); $number['pseudo'] = $this->GetErrorPseudo('Number'); $subnumber['error'] = parent::GetErrorMsg('SubNumber', $force_escape); $subnumber['pseudo'] = $this->GetErrorPseudo('SubNumber'); // if pseudo match & not empty -> return 1st // if one of pseudos not empty -> return it // if we got one pseudo "bad_type" and other pseudo "required", then return "bad_type" error message if ( $number['pseudo'] && ($number['pseudo'] == $subnumber['pseudo']) ) { return $number['error']; } if ( $number['pseudo'] && !$subnumber['pseudo'] ) { return $number['error']; } if ( !$number['pseudo'] && $subnumber['pseudo'] ) { return $subnumber['error']; } if ( $number['pseudo'] == 'bad_type' ) { return $number['error']; } if ( $subnumber['pseudo'] == 'bad_type' ) { return $subnumber['error']; } return ''; /*$msg = '[' . $number_error . '(' . $number_pseudo . ')] [' . $subnumber_error . '] (' . $subnumber_pseudo . ')'; return $msg;*/ } /** * Check field value by user-defined alghoritm * * @param string $field field name * @param Array $params field options from config * @return bool */ function CustomValidation($field, $params) { // TODO: move to OrdersEventHandler OR extend kValidator class $res = true; $res = $res && $this->ValidateCCNumber($field, $params); $res = $res && $this->ValidateCCExpiration($field, $params); return $res; } /** * Check if field value is valid credit card number against credit card type specified * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateCCNumber($field, $params) { $value = $this->dataSource->GetDBField($field); $cardtype_field = getArrayValue($params, 'cardtype_field'); if ( !$cardtype_field || !$value || !$this->dataSource->requireCreditCard() ) { return true; } if ( $this->Application->ConfigValue('Comm_MaskProcessedCreditCards') ) { $mask_found = strpos($value, str_repeat('X', 4)) !== false; if ( $this->Application->isAdminUser && $mask_found ) { // masked card numbers always appear valid in admin return true; } } if (defined('DEBUG_MODE') && kUtil::constOn('DBG_PAYMENT_GW')) { $gw_data = $this->dataSource->getGatewayData(); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - + /** @var kGWBase $gateway_object */ + $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $test_numbers = $gateway_object->GetTestCCNumbers(); if ( in_array($value, $test_numbers) ) { return true; } } $error_field = isset($params['error_field']) ? $params['error_field'] : $field; // '1' => 'Visa','2' => 'Mastercard', '3' => 'Amex', '4' => 'Discover', 5 => 'Diners Club', 6 => 'JBC' // Innocent until proven guilty $cc_valid = true; // Get rid of any non-digits $value = preg_replace('/[^\d]/', '', $value); // Perform card-specific checks, if applicable switch( $this->dataSource->GetDBField($cardtype_field) ) { case 2: // MasterCard $cc_valid = preg_match('/^5[1-5].{14}$/', $value); break; case 1: // Visa $cc_valid = preg_match('/^4.{15}$|^4.{12}$/', $value); break; case 3: // American Express $cc_valid = preg_match('/^3[47].{13}$/', $value); break; case 4: // Discover $cc_valid = preg_match('/^6011.{12}$/', $value); break; case 5: // Diners Club $cc_valid = preg_match('/^30[0-5].{11}$|^3[68].{12}$/', $value); break; case 6: // JBC $cc_valid = preg_match('/^3.{15}$|^2131|1800.{11}$/', $value); break; default: $this->SetError($error_field, 'credit_card_validation_error'); return false; break; } // The Luhn formula works right to left, so reverse the number. $value = strrev($value); $total = 0; for($x = 0; $x < strlen($value); $x++) { $digit = substr($value, $x, 1); // If it's an odd digit, double it if( $x / 2 != floor($x/2) ) { $digit *= 2; // If the result is two digits, add them if( strlen($digit) == 2 ) { $digit = substr($digit, 0, 1) + substr($digit, 1, 1); } } // Add the current digit, doubled and added if applicable, to the Total $total += $digit; } // If it passed (or bypassed) the card-specific check and the Total is // evenly divisible by 10, it's cool! if ($cc_valid && $total % 10 == 0) { return true; } $this->SetError($error_field, 'credit_card_validation_error'); return false; } /** * Check if field value is non-expired credit card expiration date * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateCCExpiration($field, $params) { $formatter = getArrayValue($params, 'formatter'); if ( ($formatter != 'kCCDateFormatter') || !$this->dataSource->requireCreditCard() ) { return true; } if ( !$this->Application->isAdminUser ) { // validate expiration date only for front if ( preg_match('/([\d]{2})\/([\d]{2})/', $this->dataSource->GetDBField($field), $rets) ) { $month = $rets[1]; $year = $rets[2]; $now_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y') ); $day_count = adodb_date('t', adodb_mktime(0, 0, 0, $month, 1, $year) ); $cc_date = adodb_mktime(23, 59, 59, $month, $day_count, $year); if ($cc_date < $now_date) { $error_field = isset($params['error_field']) ? $params['error_field'] : $field; $this->SetError($error_field, 'credit_card_expired'); return false; } } } return true; } -} \ No newline at end of file +} Index: branches/5.2.x/units/orders/orders_tag_processor.php =================================================================== --- branches/5.2.x/units/orders/orders_tag_processor.php (revision 16377) +++ branches/5.2.x/units/orders/orders_tag_processor.php (revision 16378) @@ -1,1696 +1,1692 @@ getObject($params); $type = getArrayValue($params,'type'); if($type == 'Company') { return $this->PrintCompanyLocation($params); } $fields = Array('City','State','Zip','Country'); $ret = ''; foreach($fields as $field) { $value = $object->GetField($type.$field); if ($field == 'Country' && $value) $ret .= '
'; if($value) $ret .= $value.', '; } return rtrim($ret,', '); } function PrintCompanyLocation($params) { $ret = ''; $fields = Array ('City','State','ZIP','Country'); foreach ($fields as $field) { $value = $this->Application->ConfigValue('Comm_'.$field); if ($field == 'Country') { $current_language = $this->Application->GetVar('m_lang'); $primary_language = $this->Application->GetDefaultLanguageId(); $sql = 'SELECT IF(l' . $current_language . '_Name = "", l' . $primary_language . '_Name, l' . $current_language . '_Name) FROM ' . TABLE_PREFIX . 'CountryStates WHERE IsoCode = ' . $this->Conn->qstr($value); $value = $this->Conn->GetOne($sql); } if ($field == 'Country' && $value) { $ret .= '
'; } if ($value) { $ret .= $value.', '; } } return rtrim($ret,', '); } function Orditems_LinkRemoveFromCart($params) { $params['m_cat_id'] = 0; $params['ord_event'] = 'OnRemoveFromCart'; if ( !isset($params['pass']) ) { $params['pass'] = 'm,orditems,ord'; } // Remove parameters, that indicate from where this aggregated tag was called. unset($params['PrefixSpecial'], $params['original_tag']); return $this->Application->ProcessParsedTag('m', 'Link', $params); } function Orderitems_ProductLink($params) { $object = $this->Application->recallObject('orditems'); $url_params = Array ( 'p_id' => $object->GetDBField('ProductId'), 'pass' => 'm,p', ); return $this->Application->HREF($params['template'], '', $url_params); } function Orderitems_ProductExists($params) { $object = $this->Application->recallObject('orditems'); return $object->GetDBField('ProductId') > 0; } function PrintCart($params) { $o = ''; $params['render_as'] = $params['item_render_as']; $tag_params = array_merge($params, Array ('per_page' => -1)); $o_items = $this->Application->ProcessParsedTag(rtrim('orditems.' . $this->Special, '.'), 'PrintList', $tag_params); if ( $o_items ) { if ( isset($params['header_render_as']) ) { $cart_params = array ('name' => $params['header_render_as']); $o .= $this->Application->ParseBlock($cart_params); } $o .= $o_items; if ( isset($params['footer_render_as']) ) { $cart_params = array ('name' => $params['footer_render_as']); $o .= $this->Application->ParseBlock($cart_params); } } elseif ( isset($params['empty_cart_render_as']) ) { $cart_params = array ('name' => $params['empty_cart_render_as']); $o = $this->Application->ParseBlock($cart_params); } return $o; } function ShopCartForm($params) { return $this->Application->ProcessParsedTag('m', 'ParseBlock', array_merge($params, Array( 'name' => 'kernel_form', 'PrefixSpecial'=>'ord' )) ); } function BackOrderFlag($params) { $object = $this->Application->recallObject('orditems'); return $object->GetDBField('BackOrderFlag'); } function OrderIcon($params) { $object = $this->Application->recallObject('orditems'); if ($object->GetDBField('BackOrderFlag') == 0) { return $params['ordericon']; } else { return $params['backordericon']; } } function Status($params) { $status_map = Array( 'incomplete' => ORDER_STATUS_INCOMPLETE, 'pending' => ORDER_STATUS_PENDING, 'backorder' => ORDER_STATUS_BACKORDERS, 'toship' => ORDER_STATUS_TOSHIP, 'processed' => ORDER_STATUS_PROCESSED, 'denied' => ORDER_STATUS_DENIED, 'archived' => ORDER_STATUS_ARCHIVED, ); $object = $this->getObject($params); $status = $object->GetDBField('Status'); $result = true; if (isset($params['is'])) { $result = $result && ($status == $status_map[$params['is']]); } if (isset($params['is_not'])) { $result = $result && ($status != $status_map[$params['is_not']]); } return $result; } function ItemsInCart($params) { $object = $this->getObject($params); /* @var $object kDBItem */ if ( $object->GetDBField('Status') != ORDER_STATUS_INCOMPLETE || $object->GetID() == FAKE_ORDER_ID ) { return 0; } $object = $this->Application->recallObject('orditems', 'orditems_List'); /* @var $object kDBList */ $object->Query(); return array_sum($object->GetCol('Quantity')); // $object->GetRecordsCount(); } function CartNotEmpty($params) { $object = $this->getObject($params); if ($object->GetDBField('Status') != ORDER_STATUS_INCOMPLETE || $object->GetID() == FAKE_ORDER_ID) { return 0; } $order_id = $this->Application->RecallVar('ord_id'); if ($order_id) { $sql = 'SELECT COUNT(*) FROM ' . TABLE_PREFIX . 'OrderItems WHERE OrderId = ' . $order_id; return $this->Conn->GetOne($sql); } return 0; } function CartIsEmpty($params) { return $this->CartNotEmpty($params) ? false : true; } function CartHasBackorders($params = Array ()) { $object = $this->getObject($params); $sql = 'SELECT COUNT(*) FROM ' . TABLE_PREFIX . 'OrderItems WHERE OrderId = ' . $object->GetID() . ' GROUP BY BackOrderFlag'; $different_types = $this->Conn->GetCol($sql); return count($different_types) > 1; } function PrintShippings($params) { $o = ''; $limitations_cache = Array (); $object = $this->getObject($params); /* @var $object kDBItem */ $ord_id = $object->GetID(); $oi_table = $this->Application->getUnitOption('orditems', 'TableName'); if ( $object->IsTempTable() ) { $oi_table = $this->Application->GetTempName($oi_table, 'prefix:' . $object->Prefix); } list ($split_shipments, $limit_types) = $this->GetShippingLimitations($ord_id); foreach ($split_shipments as $group => $data) { $sql = 'UPDATE ' . $oi_table . ' SET SplitShippingGroup = ' . $group . ' WHERE ProductId IN (' . implode(',', $data['Products']) . ')'; $this->Conn->Query($sql); $limitations_cache[$group] = $data['Types']; } $shipping_group_option = $object->GetDBField('ShippingGroupOption'); $shipping_group_select = $shipping_group_option == ORDER_GROUP_SHIPPMENTS_AUTO ? '0' : 'oi.SplitShippingGroup'; if ( count($split_shipments) > 1 ) { // different shipping limitations apply $this->Application->SetVar('shipping_limitations_apply', 1); if ( $limit_types == 'NONE' ) { // order can't be shipped with single shipping type $shipping_group_option = ORDER_GROUP_SHIPPMENTS_MANUAL; $shipping_group_select = 'oi.SplitShippingGroup'; $this->Application->SetVar('shipping_limitations_apply', 2); } } else { $this->Application->SetVar('shipping_limitations_apply', 0); } $shipping_option = $object->GetDBField('ShippingOption'); $weight_sql = 'IF(oi.Weight IS NULL, 0, oi.Weight * oi.Quantity)'; $sql = 'SELECT ' . ($shipping_option == ORDER_SHIP_ALL_TOGETHER ? '0' : 'oi.BackOrderFlag') . ' AS BackOrderFlagCalc, oi.ProductName, oi.ShippingTypeId, SUM(oi.Quantity) AS TotalItems, SUM(' . $weight_sql . ') AS TotalWeight, SUM(oi.Price * oi.Quantity) AS TotalAmount, SUM(oi.Quantity) - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, oi.Quantity, 0)) AS TotalItemsPromo, SUM(' . $weight_sql . ') - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, ' . $weight_sql . ', 0)) AS TotalWeightPromo, SUM(oi.Price * oi.Quantity) - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, oi.Price * oi.Quantity, 0)) AS TotalAmountPromo, ' . $shipping_group_select . ' AS SplitShippingGroupCalc FROM ' . $oi_table . ' oi LEFT JOIN ' . TABLE_PREFIX . 'Products p ON oi.ProductId = p.ProductId WHERE oi.OrderId = ' . $ord_id . ' AND p.Type = ' . PRODUCT_TYPE_TANGIBLE . ' GROUP BY BackOrderFlagCalc, SplitShippingGroupCalc ORDER BY BackOrderFlagCalc ASC, SplitShippingGroupCalc ASC'; $shipments = $this->Conn->Query($sql); $block_params = Array (); $block_params['name'] = $this->SelectParam($params, 'render_as,block'); $block_params['user_country_id'] = $object->GetDBField('ShippingCountry'); $block_params['user_state_id'] = $object->GetDBField('ShippingState'); $block_params['user_zip'] = $object->GetDBField('ShippingZip'); $block_params['user_city'] = $object->GetDBField('ShippingCity'); $block_params['user_addr1'] = $object->GetDBField('ShippingAddress1'); $block_params['user_addr2'] = $object->GetDBField('ShippingAddress2'); $block_params['user_name'] = $object->GetDBField('ShippingTo'); $group = 1; foreach ($shipments as $shipment) { $where = Array ('OrderId = ' . $ord_id); if ( $shipping_group_option == ORDER_GROUP_SHIPPMENTS_MANUAL ) { $where[] = 'SplitShippingGroup = ' . $shipment['SplitShippingGroupCalc']; } if ( $shipping_option != ORDER_SHIP_ALL_TOGETHER ) { $where[] = 'BackOrderFlag = ' . $shipment['BackOrderFlagCalc']; } $sql = 'UPDATE ' . $oi_table . ' SET PackageNum = ' . $group . ' WHERE ' . implode(' AND ', $where); $this->Conn->Query($sql); $group++; } $group = 1; $this->Application->RemoveVar('LastShippings'); $this->Application->SetVar('ShipmentsExists', 1); foreach ($shipments as $shipment) { $block_params['package_num'] = $group; $block_params['limit_types'] = $shipping_group_option == ORDER_GROUP_SHIPPMENTS_AUTO ? $limit_types : $limitations_cache[ $shipment['SplitShippingGroupCalc'] ]; $this->Application->SetVar('ItemShipmentsExists', 1); // also set from Order_PrintShippingTypes tag switch ( $shipment['BackOrderFlagCalc'] ) { case 0: if ( $this->CartHasBackOrders() && $shipping_option == ORDER_SHIP_ALL_TOGETHER ) { $block_params['shipment'] = $this->Application->Phrase('lu_all_available_backordered'); } else { $block_params['shipment'] = $this->Application->Phrase('lu_ship_all_available');; } break; case 1: $block_params['shipment'] = $this->Application->Phrase('lu_ship_all_backordered');; break; default: $block_params['shipment'] = $this->Application->Phrase('lu_ship_backordered'); break; } $block_params['promo_weight_metric'] = $shipment['TotalWeightPromo']; $block_params['promo_amount'] = $shipment['TotalAmountPromo']; $block_params['promo_items'] = $shipment['TotalItemsPromo']; $block_params['weight_metric'] = $shipment['TotalWeight']; $block_params['weight'] = $shipment['TotalWeight']; if ( $block_params['weight_metric'] == '' ) { $block_params['weight'] = $this->Application->Phrase('lu_NotAvailable'); } else { $block_params['weight'] = $this->_formatWeight( $block_params['weight'] ); } $block_params['items'] = $shipment['TotalItems']; $amount = $this->ConvertCurrency($shipment['TotalAmount'], $this->GetISO( $params['currency'] )); $amount = sprintf("%.2f", $amount); $block_params['amount'] = $shipment['TotalAmount']; $block_params['field_name'] = $this->InputName( Array('field' => 'ShippingTypeId') ) . '[' . $group . ']'; $parsed_block = $this->Application->ParseBlock($block_params); if ( $this->Application->GetVar('ItemShipmentsExists') ) { $o .= $parsed_block; } else { $this->Application->SetVar('ShipmentsExists', 0); if ( getArrayValue($params, 'no_shipments_render_as') ) { $block_params['name'] = $params['no_shipments_render_as']; return $this->Application->ParseBlock($block_params); } } $group++; } if ( getArrayValue($params, 'table_header_render_as') ) { $o = $this->Application->ParseBlock(Array ('name' => $params['table_header_render_as'])) . $o; } if ( getArrayValue($params, 'table_footer_render_as') ) { $o .= $this->Application->ParseBlock(Array ('name' => $params['table_footer_render_as'])); } return $o; } /** * Checks, that all given address fields are valid * * @param Array $params * @return bool */ function AddressValid($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $address_type = isset($params['type']) ? strtolower($params['type']) : 'shipping'; $address_type = ucfirst($address_type); $check_fields = Array ('Address1', 'City', 'Zip', 'Country'); foreach ($check_fields as $check_field) { if ( $object->GetDBField($address_type . $check_field) == '' ) { return false; } } return true; } function GetShippingLimitations($ord_id) { $limit_types = false; $types_index = $split_shipments = $cat_limitations = Array (); $sql = 'SELECT p.ShippingLimitation, p.ShippingMode, oi.ProductId AS ProductId FROM ' . TABLE_PREFIX . 'Products p LEFT JOIN ' . TABLE_PREFIX . 'OrderItems AS oi ON oi.ProductId = p.ProductId WHERE oi.OrderId = ' . $ord_id . ' AND p.Type = ' . PRODUCT_TYPE_TANGIBLE; $limitations = $this->Conn->Query($sql, 'ProductId'); // group products by shipping type range and calculate intersection of all types available for ALL products // the intersection calculation is needed to determine if the order can be shipped with single type or not if ($limitations) { foreach ($limitations as $product_id => $row) { // if shipping types are limited - get the types $types = $row['ShippingLimitation'] != '' ? explode('|', substr($row['ShippingLimitation'], 1, -1)) : Array ('ANY'); // if shipping is NOT limited to selected types (default - so products with no limitations at all also counts) if ($row['ShippingMode'] == PRODUCT_SHIPPING_MODE_ANY_AND_SELECTED) { array_push($types, 'ANY'); // can be shipped with ANY (literally) type $types = array_unique($types); } //adding product id to split_shipments group by types range $i = array_search(serialize($types), $types_index); if ($i === false) { $types_index[] = serialize($types); $i = count($types_index) - 1; } $split_shipments[$i]['Products'][] = $product_id; $split_shipments[$i]['Types'] = serialize($types); if ($limit_types === false) { // it is false only when we process first item with limitations $limit_types = $types; // initial scope } // this is to avoid ANY intersect CUST_1 = (), but allows ANY intersect CUST_1,ANY = (ANY) if ( in_array('ANY', $limit_types) && !in_array('ANY', $types) ) { array_splice($limit_types, array_search('ANY', $limit_types), 1, $types); } // this is to avoid CUST_1 intersect ANY = (), but allows CUST_1 intersect CUST_1,ANY = (ANY) if ( !in_array('ANY', $limit_types) && in_array('ANY', $types) ) { array_splice($types, array_search('ANY', $types), 1, $limit_types); } $limit_types = array_intersect($limit_types, $types); } $limit_types = count($limit_types) > 0 ? serialize(array_unique($limit_types)) : 'NONE'; } return Array ($split_shipments, $limit_types); } function PaymentTypeForm($params) { $object = $this->getObject($params); $payment_type_id = $object->GetDBField('PaymentType'); if($payment_type_id) { $this->Application->SetVar('pt_id', $payment_type_id); $block_params['name'] = $this->SelectParam($params, $this->UsingCreditCard($params) ? 'cc_render_as,block_cc' : 'default_render_as,block_default' ); return $this->Application->ParseBlock($block_params); } return ''; } /** * Returns true in case if credit card was used as payment type for order. * * @param array $params Tag params. * * @return boolean * @throws Exception When payment type not found. */ protected function UsingCreditCard(array $params) { static $payment_types; if ( !isset($payment_types) ) { $pt_table = $this->Application->getUnitOption('pt', 'TableName'); $sql = 'SELECT g.RequireCCFields, pt.PaymentTypeId FROM ' . $pt_table . ' pt JOIN ' . TABLE_PREFIX . 'Gateways g ON g.GatewayId = pt.GatewayId'; $payment_types = $this->Conn->GetCol($sql, 'PaymentTypeId'); } /** @var kDBItem $object */ $object = $this->getObject($params); $payment_type = $object->GetDBField('PaymentType'); if ( !$payment_type ) { $payment_type = $this->getPrimaryPaymentType(); $object->SetDBField('PaymentType', $payment_type); } if ( !isset($payment_types[$payment_type]) ) { throw new Exception('Unknown payment type: ' . $payment_type); } return $payment_types[$payment_type]; } /** * Get primary payment type. * * @return string */ protected function getPrimaryPaymentType() { static $primary_payment_type; if ( !isset($primary_payment_type) ) { $pt_table = $this->Application->getUnitOption('pt', 'TableName'); $sql = 'SELECT PaymentTypeId FROM ' . $pt_table . ' WHERE IsPrimary = 1'; $primary_payment_type = $this->Conn->GetOne($sql); } return $primary_payment_type; } function PaymentTypeDescription($params) { return $this->Application->ProcessParsedTag('pt', 'Field', array_merge($params, Array( 'field' => 'Description' )) ); } function PaymentTypeInstructions($params) { return $this->Application->ProcessParsedTag('pt', 'Field', array_merge($params, Array( 'field' => 'Instructions' )) ); } function PrintMonthOptions($params) { $object = $this->getObject($params); $date = explode('/', $object->GetDBField($params['date_field_name'])); if (!$date || sizeof($date) != 2) { $date=array("", ""); } $o = ''; $params['name'] = $params['block']; for ($i = 1; $i <= 12; $i++) { $month_str = str_pad($i, 2, "0", STR_PAD_LEFT); if ($date[0] == $month_str) { $params['selected'] = ' selected'; }else { $params['selected'] = ''; } $params['mm'] = $month_str; $o .= $this->Application->ParseBlock($params); } return $o; } function PrintYearOptions($params) { $object = $this->getObject($params); $value = $object->GetDBField( $params['field'] ); $block_params = $this->prepareTagParams($params); $block_params['name'] = $this->SelectParam($params, 'render_as,block'); $o = ''; $this_year = adodb_date('y'); for($i = $this_year; $i <= $this_year + 10; $i++) { $year_str = str_pad($i, 2, '0', STR_PAD_LEFT); $block_params['selected'] = ($value == $year_str) ? $params['selected'] : ''; $block_params['key'] = $year_str; $block_params['option'] = $year_str; $o .= $this->Application->ParseBlock($block_params); } return $o; } function PrintMyOrders($params) { } /** * Checks, that order data can be editied based on it's status * * @param Array $params * @return bool */ function OrderEditable($params) { $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); if ($this->Application->IsTempMode($this->Prefix, $this->Special)) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix); } // use direct select here (not $this->getObject) because this tag is // used even before "combined_header" block is used (on "orders_edit_items" template) $sql = 'SELECT Status, PaymentType FROM ' . $table_name . ' WHERE ' . $id_field . ' = ' . $this->Application->GetVar( $this->getPrefixSpecial() . '_id' ); $order_data = $this->Conn->GetRow($sql); if (!$order_data) { // new order adding, when even not in database return true; } switch ($order_data['Status']) { case ORDER_STATUS_INCOMPLETE: $ret = true; break; case ORDER_STATUS_PENDING: case ORDER_STATUS_BACKORDERS: $sql = 'SELECT PlacedOrdersEdit FROM ' . $this->Application->getUnitOption('pt', 'TableName') . ' WHERE ' . $this->Application->getUnitOption('pt', 'IDField') . ' = ' . $order_data['PaymentType']; $ret = $this->Conn->GetOne($sql); break; default: $ret = false; break; } return $ret; } function CheckoutSteps($params) { $steps = explode(',', $params['steps']); foreach ($steps as $key => $item) { $templates[$key] = trim($item); } $templates = explode(',', $params['templates']); foreach ($templates as $key => $item) { $templates[$key] = trim($item); } $total_steps = count($templates); $t = $this->Application->GetVar('t'); $o = ''; $block_params = array(); $i = 0; $passed_current = preg_match("/".preg_quote($templates[count($templates)-1], '/')."/", $t); foreach ($steps as $step => $name) { if (preg_match("/".preg_quote($templates[$step], '/')."/", $t)) { $block_params['name'] = $this->SelectParam($params, 'current_step_render_as,block_current_step'); $passed_current = true; } else { $block_params['name'] = $passed_current ? $this->SelectParam($params, 'render_as,block') : $this->SelectParam($params, 'passed_step_render_as,block_passed_step'); } $block_params['title'] = $this->Application->Phrase($name); $block_params['template'] = $templates[$i]; $block_params['template_link'] = $this->Application->HREF($templates[$step], '', Array('pass'=>'m')); $block_params['next_step_template'] = isset($templates[$i + 1]) ? $templates[$i + 1] : ''; $block_params['number'] = $i + 1; $i++; $o.= $this->Application->ParseBlock($block_params, 1); } return $o; } function ShowOrder($params) { $order_params = $this->prepareTagParams($params); // $order_params['Special'] = 'myorders'; // $order_params['PrefixSpecial'] = 'ord.myorders'; $order_params['name'] = $this->SelectParam($order_params, 'render_as,block'); // $this->Application->SetVar('ord.myorders_id', $this->Application->GetVar('ord_id')); $object = $this->getObject($params); if (!$object->GetDBField('OrderId')) { return; } return $this->Application->ParseBlock($order_params); } function BuildListSpecial($params) { if ($this->Special != '') { return $this->Special; } $list_unique_key = $this->getUniqueListKey($params); if ($list_unique_key == '') { return parent::BuildListSpecial($params); } return crc32($list_unique_key); } function ListOrders($params) { $o = ''; $params['render_as'] = $params['item_render_as']; $o_orders = $this->PrintList2($params); if ($o_orders) { $orders_params = array('name' => $params['header_render_as']); $o = $this->Application->ParseBlock($orders_params); $o .= $o_orders; } else { $orders_params = array('name' => $params['empty_myorders_render_as']); $o = $this->Application->ParseBlock($orders_params); } return $o; } function HasRecentOrders($params) { $per_page = $this->SelectParam($params, 'per_page,max_items'); if ($per_page !== false) { $params['per_page'] = $per_page; } return (int)$this->TotalRecords($params) > 0 ? 1 : 0; } function ListOrderItems($params) { $prefix_special = rtrim('orditems.'.$this->Special, '.'); return $this->Application->ProcessParsedTag($prefix_special, 'PrintList', array_merge($params, Array( 'per_page' => -1 )) ); } function OrdersLink(){ $params['pass']='m,ord'; $main_processor = $this->Application->recallObject('m_TagProcessor'); return $main_processor->Link($params); } function PrintAddresses($params) { $object = $this->getObject($params); $address_list = $this->Application->recallObject('addr','addr_List', Array('per_page'=>-1, 'skip_counting'=>true) ); $address_list->Query(); $address_id = $this->Application->GetVar($params['type'].'_address_id'); if (!$address_id) { $sql = 'SELECT '.$address_list->IDField.' FROM '.$address_list->TableName.' WHERE PortalUserId = '.$object->GetDBField('PortalUserId').' AND LastUsedAs'.ucfirst($params['type']).' = 1'; $address_id = (int)$this->Conn->GetOne($sql); } $ret = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $this->SelectParam($params, 'render_as,block'); $address_list->GoFirst(); while (!$address_list->EOL()) { $selected = ($address_list->GetID() == $address_id); if ($selected && $address_list->GetDBField('IsProfileAddress')) { $this->Application->SetVar($this->Prefix.'_IsProfileAddress', true); } $block_params['key'] = $address_list->GetID(); $block_params['value'] = $address_list->GetDBField('ShortAddress'); $block_params['selected'] = $selected ? ' selected="selected"' : ''; $ret .= $this->Application->ParseBlock($block_params, 1); $address_list->GoNext(); } return $ret; } function PrefillRegistrationFields($params) { if ( $this->Application->GetVar('fields_prefilled') ) { return false; } if ( isset($params['user_prefix']) ) { $user = $this->Application->recallObject($params['user_prefix']); /* @var $user kDBItem */ } else { $user = $this->Application->recallObject('u', null, Array ('skip_autoload' => true)); /* @var $user kDBItem */ } $order = $this->Application->recallObject($this->Prefix . '.last'); /* @var $order OrdersItem */ $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper OrderHelper */ $user_fields = $order_helper->getUserFields($order, $params['type'] == 'billing' ? 'Billing' : 'Shipping'); foreach ($user_fields as $field => $value) { if ( !$user->GetDBField($field) ) { $user->SetDBField($field, $value); } } $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $cs_helper->PopulateStates(new kEvent('u:OnBuild'), 'State', 'Country'); } function UserLink($params) { $object = $this->getObject($params); $user_id = $object->GetDBField( $params['user_field'] ); if ($user_id) { $url_params = Array ( 'm_opener' => 'd', 'u_mode' => 't', 'u_event' => 'OnEdit', 'u_id' => $user_id, 'pass' => 'all,u', 'no_pass_through' => 1, ); return $this->Application->HREF($params['edit_template'], '', $url_params); } } function UserFound($params) { $virtual_users = Array(USER_ROOT, USER_GUEST, 0); $object = $this->getObject($params); return !in_array( $object->GetDBField( $params['user_field'] ) , $virtual_users ); } /** * Returns a link for editing order * * @param Array $params * @return string */ function OrderLink($params) { $object = $this->getObject($params); $url_params = Array ( 'm_opener' => 'd', $this->Prefix.'_mode' => 't', $this->Prefix.'_event' => 'OnEdit', $this->Prefix.'_id' => $object->GetID(), 'pass' => 'all,'.$this->Prefix, 'no_pass_through' => 1, ); return $this->Application->HREF($params['edit_template'], '', $url_params); } function HasOriginalAmount($params) { $object = $this->getObject($params); $original_amount = $object->GetDBField('OriginalAmount'); return $original_amount && ($original_amount != $object->GetDBField('TotalAmount') ); } /** * Returns true, when order has tangible items * * @param Array $params * @return bool * * @todo This is copy from OrdersItem::HasTangibleItems. Copy to helper (and create it) and use here. */ function OrderHasTangibleItems($params) { $object = $this->getObject($params); if ($object->GetID() == FAKE_ORDER_ID) { return false; } $sql = 'SELECT COUNT(*) FROM '.TABLE_PREFIX.'OrderItems orditems LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = orditems.ProductId WHERE (orditems.OrderId = '.$object->GetID().') AND (p.Type = '.PRODUCT_TYPE_TANGIBLE.')'; return $this->Conn->GetOne($sql) ? true : false; } function ShipmentsExists($params) { return $this->Application->GetVar('ShipmentsExists') ? 1 : 0; } function Field($params) { $value = parent::Field($params); $field = $this->SelectParam($params,'name,field'); if( ($field == 'PaymentAccount') && getArrayValue($params,'masked') ) { $value = str_repeat('X',12).substr($value,-4); } return $value; } function CartHasError($params) { return $this->Application->RecallVar('checkout_errors'); } function CheckoutError($params) { $errors = $this->Application->RecallVar('checkout_errors'); if ( !$errors ) { return ''; } $this->Application->RemoveVar('checkout_errors'); $errors = unserialize($errors); if ( isset($errors[OrderCheckoutErrorType::COUPON]) ) { $mapping = Array ( OrderCheckoutError::COUPON_APPLIED => 'coupon_applied', OrderCheckoutError::COUPON_REMOVED => 'code_removed', OrderCheckoutError::COUPON_REMOVED_AUTOMATICALLY => 'code_removed_automatically', OrderCheckoutError::COUPON_CODE_INVALID => 'invalid_code', OrderCheckoutError::COUPON_CODE_EXPIRED => 'code_expired', ); $error_phrase = $mapping[ $errors[OrderCheckoutErrorType::COUPON] ]; } elseif ( isset($errors[OrderCheckoutErrorType::GIFT_CERTIFICATE]) ) { $mapping = Array ( OrderCheckoutError::GC_APPLIED => 'gift_certificate_applied', OrderCheckoutError::GC_REMOVED => 'gc_code_removed', OrderCheckoutError::GC_REMOVED_AUTOMATICALLY => 'gc_code_removed_automatically', OrderCheckoutError::GC_CODE_INVALID => 'invalid_gc_code', OrderCheckoutError::GC_CODE_EXPIRED => 'gc_code_expired', ); $error_phrase = $mapping[ $errors[OrderCheckoutErrorType::GIFT_CERTIFICATE] ]; } else { $mapping = Array ( OrderCheckoutError::QTY_UNAVAILABLE => 'qty_unavailable', OrderCheckoutError::QTY_OUT_OF_STOCK => 'outofstock', OrderCheckoutError::QTY_CHANGED_TO_MINIMAL => 'min_qty', ); foreach ($errors as $error_type => $error_code) { if ( isset($mapping[$error_code]) ) { $error_phrase = $mapping[$error_code]; break; } } if ( !isset($error_phrase) ) { $error_phrase = 'state_changed'; // 'changed_after_login' } } return $this->Application->Phrase( $params[$error_phrase] ); } /** * Returns checkout errors in JSON format * * @param Array $params * @return string */ function PrintOrderInfo($params) { $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper OrderHelper */ $object = $this->getObject($params); /* @var $object kDBItem */ $currency = isset($params['currency']) ? $params['currency'] : 'selected'; return json_encode( $order_helper->getOrderInfo($object, $currency) ); } /** * Returns currency mark (%s is $amount placemark) * * @param Array $params * @return string */ function CurrencyMask($params) { $iso = $this->GetISO( $params['currency'] ); return $this->AddCurrencySymbol('%s', $iso); } function CheckoutErrorNew($params) { $errors = $this->Application->RecallVar('checkout_errors'); if ( !$errors ) { return ''; } // $this->Application->RemoveVar('checkout_errors'); $errors = unserialize($errors); $reflection = new ReflectionClass('OrderCheckoutErrorType'); $error_types = $reflection->getConstants(); $reflection = new ReflectionClass('OrderCheckoutError'); $error_codes = $reflection->getConstants(); $ret = Array (); foreach ($errors as $error_type => $error_code) { $error_type = explode(':', $error_type); $error_explained = '' . array_search($error_type[0], $error_types) . ''; if ( $error_type[0] == OrderCheckoutErrorType::PRODUCT ) { $error_explained .= ' (ProductId = ' . $error_type[1] . '; OptionsSalt: ' . $error_type[2] . '; BackOrderFlag: ' . $error_type[3] . '; Field: ' . $error_type[4] . ')'; } $error_explained .= ' - ' . array_search($error_code, $error_codes) . ''; $ret[] = $error_explained; } return implode('
', $ret); } function GetFormAction($params) { $object = $this->getObject($params); /* @var $object OrdersItem */ $gw_data = $object->getGatewayData( isset($params['payment_type_id']) ? $params['payment_type_id'] : null ); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - /* @var $gateway_object kGWBase */ + /** @var kGWBase $gateway_object */ + $gateway_object = $this->Application->recallObject($gw_data['ClassName']); return $gateway_object->getFormAction($gw_data['gw_params']); } function GetFormHiddenFields($params) { $object = $this->getObject($params); /* @var $object OrdersItem */ $gw_data = $object->getGatewayData( isset($params['payment_type_id']) ? $params['payment_type_id'] : null ); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - /* @var $gateway_object kGWBase */ + /** @var kGWBase $gateway_object */ + $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $tpl = ''."\n"; $hidden_fields = $gateway_object->getHiddenFields($object->GetFieldValues(), $params, $gw_data['gw_params']); if ( !is_array($hidden_fields) ) { return $hidden_fields; } $ret = ''; foreach ($hidden_fields as $hidden_name => $hidden_value) { $ret .= sprintf($tpl, $hidden_name, $hidden_value); } return $ret; } function NeedsPlaceButton($params) { $object = $this->getObject($params); /* @var $object OrdersItem */ $gw_data = $object->getGatewayData( isset($params['payment_type_id']) ? $params['payment_type_id'] : null ); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - /* @var $gateway_object kGWBase */ + /** @var kGWBase $gateway_object */ + $gateway_object = $this->Application->recallObject($gw_data['ClassName']); return $gateway_object->NeedPlaceButton($object->GetFieldValues(), $params, $gw_data['gw_params']); } function HasGatewayError($params) { return $this->Application->RecallVar('gw_error'); } function ShowGatewayError($params) { $ret = $this->Application->RecallVar('gw_error'); $this->Application->RemoveVar('gw_error'); return $ret; } function ShippingType($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $shipping_info = unserialize($object->GetDBField('ShippingInfo')); if ( count($shipping_info) > 1 ) { return $this->Application->Phrase('lu_MultipleShippingTypes'); } if ( $shipping_info ) { $shipping_info = array_shift($shipping_info); return $shipping_info['ShippingName']; } return ''; } function DiscountHelpLink($params) { $params['pass'] = 'all,orditems'; $params['m_cat_id'] = 0; $m_tag_processor = $this->Application->recallObject('m_TagProcessor'); return $m_tag_processor->Link($params); } function DiscountField($params) { $orditems = $this->Application->recallObject( 'orditems' ); $item_data = $orditems->GetDBField('ItemData'); if(!$item_data) return ''; $item_data = unserialize($item_data); $discount_prefix = ($item_data['DiscountType'] == 'coupon') ? 'coup' : 'd'; $discount = $this->Application->recallObject($discount_prefix, null, Array('skip_autoload' => true)); if(!$discount->isLoaded()) { $discount->Load($item_data['DiscountId']); } return $discount->GetField( $this->SelectParam($params, 'field,name') ); } function HasDiscount($params) { $object = $this->getObject($params); return (float)$object->GetDBField('DiscountTotal') ? 1 : 0; } /** * Allows to check if required product types are present in order * * @param Array $params */ function HasProductType($params) { $product_types = Array('tangible' => 1, 'subscription' => 2, 'service' => 3, 'downloadable' => 4, 'package' => 5, 'gift' => 6); $object = $this->getObject($params); $sql = 'SELECT COUNT(*) FROM '.TABLE_PREFIX.'OrderItems oi LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId WHERE (oi.OrderId = '.$object->GetID().') AND (p.Type = '.$product_types[ $params['type'] ].')'; return $this->Conn->GetOne($sql); } function PrintSerializedFields($params) { $object = $this->getObject($params); $field = $this->SelectParam($params, 'field'); if (!$field) $field = $this->Application->GetVar('field'); $data = unserialize($object->GetDBField($field)); $o = ''; $block_params['name'] = $params['render_as']; foreach ($data as $field => $value) { $block_params['field'] = $field; $block_params['value'] = $value; $o .= $this->Application->ParseBlock($block_params); } return $o; } /** * Prints order totals * * @param Array $params * @return string * @access protected */ protected function PrintTotals($params) { $object = $this->getObject($params); /* @var $object OrdersItem */ if ( isset($params['element_order']) ) { // TODO: implement } else { // default element order $element_order = Array ( 'products' => 1, 'return' => 4, 'sub_total' => 5, 'discount' => 6, 'vat' => 7, 'shipping' => 8, 'processing' => 9, ); // show shipping & processing costs before tax, when they are taxable if ( $object->GetDBField('ShippingTaxable') ) { $element_order['shipping'] = 2; } if ( $object->GetDBField('ProcessingTaxable') ) { $element_order['processing'] = 3; } } $totals = Array (); if ( abs($object->GetDBField('SubTotal') - $object->GetDBField('AmountWithoutVAT')) > 0.01 ) { $totals[] = 'products'; } if ( $this->OrderHasTangibleItems($params) ) { $totals[] = 'shipping'; } if ( $object->GetDBField('ProcessingFee') > 0 ) { $totals[] = 'processing'; } if ( $object->GetDBField('ReturnTotal') > 0 ) { $totals[] = 'return'; } if ( $this->HasDiscount($params) ) { $totals[] = 'discount'; } $totals[] = 'sub_total'; if ( $object->GetDBField('VAT') > 0 ) { $totals[] = 'vat'; } $o = ''; asort($element_order, SORT_NUMERIC); $block_params = $this->prepareTagParams($params); foreach ($element_order as $type => $order) { $element = getArrayValue($params, $type . '_render_as'); if ( !in_array($type, $totals) || !$element ) { continue; } $block_params['name'] = $element; $o .= $this->Application->ParseBlock($block_params); } return $o; } function ShowDefaultAddress($params) { $address_type = ucfirst($params['type']); if ($this->Application->GetVar('check_'.strtolower($address_type).'_address')) { // form type doesn't match check type, e.g. shipping check on billing form return ''; } // for required field highlighting on form when no submit made $this->Application->SetVar('check_'.strtolower($address_type).'_address', 'true'); /*if ((strtolower($address_type) == 'billing') && $this->UsingCreditCard($params)) { $this->Application->SetVar('check_credit_card', 'true'); }*/ $this->Application->HandleEvent(new kEvent('ord:SetStepRequiredFields')); $user_id = $this->Application->RecallVar('user_id'); $sql = 'SELECT AddressId FROM '.TABLE_PREFIX.'Addresses WHERE PortalUserId = '.$user_id.' AND LastUsedAs'.$address_type.' = 1'; $address_id = $this->Conn->GetOne($sql); if (!$address_id) { return ''; } $addr_list = $this->Application->recallObject('addr', 'addr_List', Array('per_page'=>-1, 'skip_counting'=>true) ); $addr_list->Query(); $object = $this->getObject(); if (!$addr_list->CheckAddress($object->GetFieldValues(), $address_type)) { $addr_list->CopyAddress($address_id, $address_type); } } function IsProfileAddress($params) { $object = $this->getObject($params); $address_type = ucfirst($params['type']); return $object->IsProfileAddress($address_type); } function HasPayPalSubscription($params) { $object = $this->getObject($params); $sql = 'SELECT COUNT(*) FROM '.TABLE_PREFIX.'OrderItems oi LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId WHERE (oi.OrderId = '.$object->GetID().') AND (p.PayPalRecurring = 1)'; return $this->Conn->GetOne($sql); } function GetPayPalSubscriptionForm($params) { $object = $this->getObject($params); $gw_data = $object->getGatewayData($params['payment_type_id']); - $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); - $gateway_object = $this->Application->recallObject( $gw_data['ClassName'] ); - + /** @var kGWBase $gateway_object */ + $gateway_object = $this->Application->recallObject($gw_data['ClassName']); $sql = 'SELECT oi.* FROM '.TABLE_PREFIX.'OrderItems oi LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId WHERE (oi.OrderId = '.$object->GetID().') AND (p.PayPalRecurring = 1)'; $order_item = $this->Conn->GetRow($sql); $order_item_data = unserialize($order_item['ItemData']); $cycle = ceil($order_item_data['Duration'] / 86400); $cycle_units = 'D'; $item_data = $object->GetFieldValues(); $item_data['item_name'] = $order_item['ProductName']; $item_data['item_number'] = $order_item['OrderItemId']; $item_data['custom'] = $order_item['OrderId']; $item_data['a1'] = ''; $item_data['p1'] = ''; $item_data['t1'] = ''; $item_data['a2'] = ''; $item_data['p2'] = ''; $item_data['t2'] = ''; $item_data['a3'] = $order_item['Price']; //rate $item_data['p3'] = $cycle; //cycle $item_data['t3'] = $cycle_units; //cycle units D (days), W (weeks), M (months), Y (years) $item_data['src'] = '1'; // Recurring payments. If set to 1, the payment will recur unless your customer cancels the subscription before the end of the billing cycle. $item_data['sra'] = '1'; // Reattempt on failure. If set to 1, and the payment fails, the payment will be reattempted two more times. After the third failure, the subscription will be cancelled. $item_data['srt'] = ''; // Recurring Times. This is the number of payments which will occur at the regular rate. $hidden_fields = $gateway_object->getSubscriptionFields($item_data, $params, $gw_data['gw_params']); $ret = ''; if (!is_array($hidden_fields)) { return $hidden_fields; } $tpl = ''."\n"; foreach($hidden_fields as $hidden_name => $hidden_value) { $ret .= sprintf($tpl, $hidden_name, $hidden_value); } return $ret; } function UserHasPendingOrders($params) { $sql = 'SELECT OrderId FROM '.$this->Application->getUnitOption($this->Prefix, 'TableName').' WHERE PortalUserId = '.$this->Application->RecallVar('user_id').' AND Status = '.ORDER_STATUS_PENDING; return $this->Conn->GetOne($sql) ? 1 : 0; } function AllowAddAddress($params) { $user = $this->Application->recallObject('u.current'); if ($user->GetDBField('cust_shipping_addr_block')) return false; $address_list = $this->Application->recallObject('addr','addr_List', Array('per_page'=>-1, 'skip_counting'=>true) ); $address_list->Query(); $max = $this->Application->ConfigValue('MaxAddresses'); return $max <= 0 ? true : $address_list->GetRecordsCount() < $max; } /** * Creates link for removing coupon or gift certificate * * @param Array $params * @return string */ function RemoveCouponLink($params) { if ( !isset($params['pass']) ) { $params['pass'] = 'm,ord'; } $params['m_cat_id'] = 0; $link_type = $params['type']; unset($params['type']); $params['ord_event'] = strtolower($link_type) == 'coupon' ? 'OnRemoveCoupon' : 'OnRemoveGiftCertificate'; return $this->ItemLink($params); } /** * Calculates total weight of items in shopping cart * * @param Array $params * @return float */ function TotalOrderWeight($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $sql = 'SELECT SUM( IF(oi.Weight IS NULL, 0, oi.Weight * oi.Quantity) ) FROM '.TABLE_PREFIX.'OrderItems oi WHERE oi.OrderId = '.$object->GetID(); $total_weight = $this->Conn->GetOne($sql); if ($total_weight == '') { // zero weight -> return text about it return $this->Application->Phrase('lu_NotAvailable'); } return $this->_formatWeight($total_weight); } function _formatWeight($weight) { $regional = $this->Application->recallObject('lang.current'); /* @var $regional kDBItem */ switch ( $regional->GetDBField('UnitSystem') ) { case 1: // metric system -> add kg sign $weight .= ' ' . $this->Application->Phrase('lu_kg'); break; case 2: // uk system -> convert to pounds list ($pounds, $ounces) = kUtil::Kg2Pounds($weight); $weight = $pounds . ' ' . $this->Application->Phrase('lu_pounds') . ' ' . $ounces . ' ' . $this->Application->Phrase('lu_ounces'); break; } return $weight; } function InitCatalogTab($params) { $tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible $tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab $tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid // set default params (same as in catalog) if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi'; if ($tab_params['special'] === false) $tab_params['special'] = ''; if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes'; // pass params to block with tab content $params['name'] = $params['render_as']; $params['prefix'] = trim($this->Prefix.'.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.'); $params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.'); $params['tab_mode'] = $tab_params['mode']; $params['grid_name'] = ($tab_params['mode'] == 'multi') ? $params['default_grid'] : $params['radio_grid']; $params['tab_dependant'] = $tab_params['dependant']; $params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name // use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag return $this->Application->ParseBlock($params, 1); } /** * Checks if required payment method is available * * @param Array $params * @return bool */ function HasPaymentGateway($params) { static $payment_types = Array (); $gw_name = $params['name']; if (!array_key_exists($gw_name, $payment_types)) { $sql = 'SELECT pt.PaymentTypeId, pt.PortalGroups FROM '.TABLE_PREFIX.'PaymentTypes pt LEFT JOIN '.TABLE_PREFIX.'Gateways g ON pt.GatewayId = g.GatewayId WHERE (g.Name = '.$this->Conn->qstr($params['name']).') AND (pt.Status = '.STATUS_ACTIVE.')'; $payment_types[$gw_name] = $this->Conn->GetRow($sql); } if (!$payment_types[$gw_name]) { return false; } $pt_groups = explode(',', substr($payment_types[$gw_name]['PortalGroups'], 1, -1)); $user_groups = explode(',', $this->Application->RecallVar('UserGroups')); return array_intersect($user_groups, $pt_groups) ? $payment_types[$gw_name]['PaymentTypeId'] : false; } function DisplayPaymentGateway($params) { $payment_type_id = $this->HasPaymentGateway($params); if (!$payment_type_id) { return ''; } $object = $this->getObject($params); /* @var $object OrdersItem */ $gw_data = $object->getGatewayData($payment_type_id); $block_params = $gw_data['gw_params']; $block_params['name'] = $params['render_as']; $block_params['payment_type_id'] = $payment_type_id; return $this->Application->ParseBlock($block_params); } /** * Checks, that USPS returned valid label * * @param Array $params * @return bool */ function USPSLabelFound($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $full_path = USPS_LABEL_FOLDER . $object->GetDBField( $params['field'] ) . '.pdf'; return file_exists($full_path) && is_file($full_path); } /** * Prints SQE errors from session * * @param Array $params * @return string */ function PrintSQEErrors($params) { $sqe_errors = $this->Application->RecallVar('sqe_errors'); if (!$sqe_errors) { return ''; } $o = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; $sqe_errors = unserialize($sqe_errors); foreach ($sqe_errors as $order_number => $error_description) { $block_params['order_number'] = $order_number; $block_params['error_description'] = $error_description; $o .= $this->Application->ParseBlock($block_params); } $this->Application->RemoveVar('sqe_errors'); return $o; } /** * Creates a continue shopping link * * @param Array $params * @return string * @access protected */ protected function ContinueShoppingLink($params) { $order_helper = $this->Application->recallObject('OrderHelper'); /* @var $order_helper OrderHelper */ if ( isset($params['template']) ) { $template = $params['template']; unset($params['template']); } else { $template = ''; } return $this->Application->HREF($order_helper->getContinueShoppingTemplate($template), '', $params); } /** * Checks that billing address and shipping address are the same * * @param Array $params * @return string * @access protected */ protected function AddressesTheSame($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $address_fields = Array ('To', 'Company', 'Address1', 'Address2', 'City', 'Country', 'State', 'Zip'); foreach ($address_fields as $address_field) { if ( $object->GetDBField('Shipping' . $address_field) != $object->GetDBField('Billing' . $address_field) ) { return false; } } return true; } }