Complex classes like EbayEnterprise_Order_Model_Create often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use EbayEnterprise_Order_Model_Create, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 40 | class EbayEnterprise_Order_Model_Create |
||
| 41 | { |
||
| 42 | const MAGE_CUSTOMER_GENDER_MALE = 1; |
||
| 43 | const LEVEL_OF_SERVICE_REGULAR = 'REGULAR'; |
||
| 44 | const SHIPPING_CHARGE_TYPE_FLATRATE = 'FLATRATE'; |
||
| 45 | |||
| 46 | const ORDER_TYPE_SALES = 'SALES'; |
||
| 47 | const ORDER_TYPE_PURCHASE = 'PURCHASE'; |
||
| 48 | |||
| 49 | // Copy the constant over for interface consistency. |
||
| 50 | const STATE_NEW = Mage_Sales_Model_Order::STATE_NEW; |
||
| 51 | const STATUS_NEW = 'unsubmitted'; |
||
| 52 | |||
| 53 | const STATUS_SENT = 'pending'; |
||
| 54 | |||
| 55 | const ORDER_CREATE_FAIL_MESSAGE = 'EbayEnterprise_Order_Create_Fail_Message'; |
||
| 56 | |||
| 57 | /** @var string event dispatched before attaching the new payload to the order object */ |
||
| 58 | protected $_beforeAttachEvent = 'ebayenterprise_order_create_before_attach'; |
||
| 59 | /** @var string event dispatched before sending the request to ROM */ |
||
| 60 | protected $_beforeOrderSendEvent = 'ebayenterprise_order_create_before_send'; |
||
| 61 | /** @var string event dispatched when ROM order create was successful */ |
||
| 62 | protected $_successfulOrderCreateEvent = 'ebayenterprise_order_create_successful'; |
||
| 63 | /** @var string event dispatched to add payments to the request */ |
||
| 64 | protected $_paymentDataEvent = 'ebayenterprise_order_create_payment'; |
||
| 65 | /** @var string event dispatched to add context information to the request */ |
||
| 66 | protected $_contextDataEvent = 'ebayenterprise_order_create_context'; |
||
| 67 | /** @var string event dispatched to handle populating ship groups for addresses in the order */ |
||
| 68 | protected $_shipGroupEvent = 'ebayenterprise_order_create_ship_group'; |
||
| 69 | /** @var string event dispatched to handle populating order item payloads for items in the order */ |
||
| 70 | protected $_orderItemEvent = 'ebayenterprise_order_create_item'; |
||
| 71 | /** @var IBidirectionalApi */ |
||
| 72 | protected $_api; |
||
| 73 | /** @var IOrderCreateRequest */ |
||
| 74 | protected $_payload; |
||
| 75 | /** @var EbayEnterprise_Order_Helper_Data */ |
||
| 76 | protected $_helper; |
||
| 77 | /** @var EbayEnterprise_Eb2cCore_Helper_Data */ |
||
| 78 | protected $_coreHelper; |
||
| 79 | /** @var EbayEnterprise_Eb2cCore_Model_Config_Registry */ |
||
| 80 | protected $_config; |
||
| 81 | /** @var EbayEnterprise_MageLog_Helper_Data */ |
||
| 82 | protected $_logger; |
||
| 83 | /** @var EbayEnterprise_MageLog_Helper_Context */ |
||
| 84 | protected $_logContext; |
||
| 85 | /** @var EbayEnterprise_Order_Model_Create_Payment */ |
||
| 86 | protected $_defaultPaymentHandler; |
||
| 87 | /** @var EbayEnterprise_Order_Model_Create_Orderitem */ |
||
| 88 | protected $_defaultItemHandler; |
||
| 89 | /** @var Mage_Sales_Model_Order */ |
||
| 90 | protected $_order; |
||
| 91 | /** @var int counter to use for assigning line numbers */ |
||
| 92 | protected $_nextLineNumber = 0; |
||
| 93 | /** @var string[] */ |
||
| 94 | protected $_validGenderStrings = ['M', 'F']; |
||
| 95 | /** @var Mage_Core_Model_App */ |
||
| 96 | protected $_app; |
||
| 97 | /** @var EbayEnterprise_Order_Helper_Item_Selection */ |
||
| 98 | protected $_itemSelection; |
||
| 99 | |||
| 100 | public function __construct(array $args = []) |
||
| 101 | { |
||
| 102 | list( |
||
| 103 | $this->_logger, |
||
| 104 | $this->_helper, |
||
| 105 | $this->_coreHelper, |
||
| 106 | $this->_defaultPaymentHandler, |
||
| 107 | $this->_defaultItemHandler, |
||
| 108 | $this->_order, |
||
| 109 | $this->_config, |
||
| 110 | $this->_api, |
||
| 111 | $this->_payload, |
||
| 112 | $this->_logContext, |
||
| 113 | $this->_itemSelection |
||
| 114 | ) = $this->_enforceTypes( |
||
| 115 | $this->_nullCoalesce('logger', $args, Mage::helper('ebayenterprise_magelog')), |
||
| 116 | $this->_nullCoalesce('helper', $args, Mage::helper('ebayenterprise_order')), |
||
| 117 | $this->_nullCoalesce('core_helper', $args, Mage::helper('eb2ccore')), |
||
| 118 | $this->_nullCoalesce('default_payment_handler', $args, Mage::getModel('ebayenterprise_order/create_payment')), |
||
| 119 | $this->_nullCoalesce('default_item_handler', $args, Mage::getModel('ebayenterprise_order/create_orderitem')), |
||
| 120 | $args['order'], |
||
| 121 | $args['config'], |
||
| 122 | $args['api'], |
||
| 123 | $args['payload'], |
||
| 124 | $this->_nullCoalesce('log_context', $args, Mage::helper('ebayenterprise_magelog/context')), |
||
| 125 | $this->_nullCoalesce('item_selection', $args, Mage::helper('ebayenterprise_order/item_selection')) |
||
| 126 | ); |
||
| 127 | // Possibly one valid exception to the DI rule; we're so beholden to the Mage class anyway... |
||
| 128 | $this->_app = Mage::app(); |
||
| 129 | } |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Enforce injected types. |
||
| 133 | * |
||
| 134 | * @param EbayEnterprise_MageLog_Helper_Data |
||
| 135 | * @param EbayEnterprise_Order_Helper_Data |
||
| 136 | * @param EbayEnterprise_Eb2cCore_Helper_Data |
||
| 137 | * @param EbayEnterprise_Order_Model_Create_Payment |
||
| 138 | * @param EbayEnterprise_Order_Model_Create_Orderitem |
||
| 139 | * @param Mage_Sales_Model_Order |
||
| 140 | * @param EbayEnterprise_Eb2cCore_Model_Config_Registry |
||
| 141 | * @param IBidirectionalApi |
||
| 142 | * @param IOrderCreateRequest |
||
| 143 | * @param EbayEnterprise_Order_Helper_Item_Selection |
||
| 144 | * @return array |
||
| 145 | */ |
||
| 146 | protected function _enforceTypes( |
||
| 147 | EbayEnterprise_MageLog_Helper_Data $logger, |
||
| 148 | EbayEnterprise_Order_Helper_Data $helper, |
||
| 149 | EbayEnterprise_Eb2cCore_Helper_Data $coreHelper, |
||
| 150 | EbayEnterprise_Order_Model_Create_Payment $defaultPaymentHandler, |
||
| 151 | EbayEnterprise_Order_Model_Create_Orderitem $defaultItemHandler, |
||
| 152 | Mage_Sales_Model_Order $order, |
||
| 153 | EbayEnterprise_Eb2cCore_Model_Config_Registry $config, |
||
| 154 | IBidirectionalApi $api, |
||
| 155 | IOrderCreateRequest $payload, |
||
| 156 | EbayEnterprise_MageLog_Helper_Context $logContext, |
||
| 157 | EbayEnterprise_Order_Helper_Item_Selection $itemSelection |
||
| 158 | ) { |
||
| 159 | return func_get_args(); |
||
| 160 | } |
||
| 161 | |||
| 162 | /** |
||
| 163 | * Fill in default values. |
||
| 164 | * |
||
| 165 | * @param string |
||
| 166 | * @param array |
||
| 167 | * @param mixed |
||
| 168 | * @return mixed |
||
| 169 | */ |
||
| 170 | protected function _nullCoalesce($key, array $arr, $default) |
||
| 174 | |||
| 175 | /** |
||
| 176 | * Submit the order create request for the order. |
||
| 177 | * |
||
| 178 | * @return self |
||
| 179 | */ |
||
| 180 | public function send() |
||
| 181 | { |
||
| 182 | return $this |
||
| 183 | ->_prepareOrder() |
||
| 184 | ->_initPayload() |
||
| 185 | ->_send(); |
||
| 186 | } |
||
| 187 | |||
| 188 | /** |
||
| 189 | * Set the order state and status to "new" and "unsubmitted" to start. |
||
| 190 | * |
||
| 191 | * @return self |
||
| 192 | */ |
||
| 193 | protected function _prepareOrder() |
||
| 194 | { |
||
| 195 | $state = self::STATE_NEW; |
||
| 196 | $status = self::STATUS_NEW; |
||
| 197 | $this->_order->setState($state, $status); |
||
| 198 | return $this; |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * Fill out the order create request. |
||
| 203 | * (If the order already has one we can use, use it; |
||
| 204 | * otherwise create a new one.) |
||
| 205 | * |
||
| 206 | * @return self |
||
| 207 | */ |
||
| 208 | protected function _initPayload() |
||
| 209 | { |
||
| 210 | $raw = $this->_order->getEb2cOrderCreateRequest(); |
||
| 211 | if ($raw) { |
||
| 212 | $this->_rebuildPayload($raw); |
||
| 213 | } else { |
||
| 214 | $this->_buildNewPayload() |
||
| 215 | ->_attachRequest(); |
||
| 216 | } |
||
| 217 | return $this; |
||
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * rebuild the payload by deserializing the previous |
||
| 222 | * request |
||
| 223 | * @param string previously serialized request |
||
| 224 | * @return self |
||
| 225 | */ |
||
| 226 | protected function _rebuildPayload($raw) |
||
| 227 | { |
||
| 228 | try { |
||
| 229 | $this->_payload->deserialize($raw); |
||
| 230 | } catch (InvalidPayload $e) { |
||
| 231 | $this->_logger->critical( |
||
| 232 | 'Failed to rebuild previous order request {order_id}', |
||
| 233 | $this->_logContext->getMetaData(__CLASS__, ['order_id' => $this->_order->getIncrementId()]) |
||
| 234 | ); |
||
| 235 | } |
||
| 236 | return $this; |
||
| 237 | } |
||
| 238 | |||
| 239 | /** |
||
| 240 | * save the request to the order |
||
| 241 | * @return self |
||
| 242 | */ |
||
| 243 | protected function _attachRequest() |
||
| 244 | { |
||
| 245 | Mage::dispatchEvent($this->_beforeAttachEvent, [ |
||
| 246 | 'order' => $this->_order, |
||
| 247 | 'payload' => $this->_payload, |
||
| 248 | ]); |
||
| 249 | try { |
||
| 250 | $this->_order |
||
| 251 | ->setEb2cOrderCreateRequest($this->_payload->serialize()); |
||
| 252 | } catch (InvalidPayload $e) { |
||
| 253 | $this->_logger->critical( |
||
| 254 | 'Unable to attach request for order {order_id}', |
||
| 255 | $this->_logContext->getMetaData(__CLASS__, ['order_id' => $this->_order->getIncrementId()]) |
||
| 256 | ); |
||
| 257 | } |
||
| 258 | return $this; |
||
| 259 | } |
||
| 260 | |||
| 261 | /** |
||
| 262 | * Send the order create request to the api. |
||
| 263 | * |
||
| 264 | * @return self |
||
| 265 | */ |
||
| 266 | protected function _send() |
||
| 267 | { |
||
| 268 | Mage::dispatchEvent($this->_beforeOrderSendEvent, [ |
||
| 269 | 'order' => $this->_order, |
||
| 270 | 'payload' => $this->_payload, |
||
| 271 | ]); |
||
| 272 | |||
| 273 | $logger = $this->_logger; |
||
| 274 | $logContext = $this->_logContext; |
||
| 275 | |||
| 276 | try { |
||
| 277 | $reply = $this->_api |
||
| 278 | ->setRequestBody($this->_payload) |
||
| 279 | ->send() |
||
| 280 | ->getResponseBody(); |
||
| 281 | } catch (NetworkError $e) { |
||
| 282 | $logger->warning( |
||
| 283 | 'Caught a network error sending order create. See exception log for more details.', |
||
| 284 | $logContext->getMetaData(__CLASS__, ['exception_message' => $e->getMessage()]) |
||
| 285 | ); |
||
| 286 | $logger->logException($e, $logContext->getMetaData(__CLASS__, [], $e)); |
||
| 287 | return $this; |
||
| 288 | } catch (UnsupportedOperation $e) { |
||
| 289 | $logger->critical( |
||
| 290 | 'The order create operation is unsupported in the current configuration. Order saved, but not sent. See exception log for more details.', |
||
| 291 | $logContext->getMetaData(__CLASS__, ['exception_message' => $e->getMessage()]) |
||
| 292 | ); |
||
| 293 | $logger->logException($e, $logContext->getMetaData(__CLASS__, [], $e)); |
||
| 294 | return $this; |
||
| 295 | } catch (UnsupportedHttpAction $e) { |
||
| 296 | $logger->critical( |
||
| 297 | 'The order create operation is configured with an unsupported HTTP action. Order saved, but not sent. See exception log for more details.', |
||
| 298 | $logContext->getMetaData(__CLASS__, ['exception_message' => $e->getMessage()]) |
||
| 299 | ); |
||
| 300 | $logger->logException($e, $logContext->getMetaData(__CLASS__, [], $e)); |
||
| 301 | return $this; |
||
| 302 | } catch (Exception $e) { |
||
| 303 | throw $this->_logUnhandledException($e); |
||
| 304 | } |
||
| 305 | |||
| 306 | if ($reply->isSuccessful()) { |
||
| 307 | $this->_order->setStatus(self::STATUS_SENT); |
||
| 308 | Mage::dispatchEvent($this->_successfulOrderCreateEvent, [ |
||
| 309 | 'order' => $this->_order, |
||
| 310 | ]); |
||
| 311 | } else { |
||
| 312 | throw $this->_logUnhandledException(); |
||
| 313 | } |
||
| 314 | return $this; |
||
| 315 | } |
||
| 316 | |||
| 317 | /** |
||
| 318 | * Unhandled exceptions cause the entire order not to get saved. |
||
| 319 | * This is by design, so we don't report a false success or try |
||
| 320 | * to keep sending an order that has no hope for success. |
||
| 321 | * |
||
| 322 | * @param Exception|null The exception to log or null for the default. |
||
| 323 | * @return Exception The same (or default) exception after logging |
||
| 324 | */ |
||
| 325 | protected function _logUnhandledException(Exception $e = null) |
||
| 326 | { |
||
| 327 | if (!$e) { |
||
| 328 | $errorMessage = $this->_helper->__(self::ORDER_CREATE_FAIL_MESSAGE); |
||
| 329 | // Mage::exception adds '_Exception' to the end. |
||
| 330 | $exceptionClassName = Mage::getConfig()->getModelClassName('ebayenterprise_order/create'); |
||
| 331 | $e = Mage::exception($exceptionClassName, $errorMessage); |
||
| 332 | } |
||
| 333 | $this->_logger->warning( |
||
| 334 | 'Encountered unexpected exception attempting to send order create. See exception log for more details.', |
||
| 335 | $this->_logContext->getMetaData(__CLASS__, ['exception_message' => $e->getMessage()]) |
||
| 336 | ); |
||
| 337 | $this->_logger->logException($e, $this->_logContext->getMetaData(__CLASS__, [], $e)); |
||
| 338 | return $e; |
||
| 339 | } |
||
| 340 | |||
| 341 | /** |
||
| 342 | * Convert the order's billing address into an IMailingAddress |
||
| 343 | * so the SDK can use it. |
||
| 344 | * |
||
| 345 | * @param Mage_Customer_Model_Address_Abstract |
||
| 346 | * @return IMailingAddress |
||
| 347 | */ |
||
| 348 | protected function _getRomBillingAddress(Mage_Customer_Model_Address_Abstract $address) |
||
| 349 | { |
||
| 350 | $mailingAddress = $this->_payload->getDestinations()->getEmptyMailingAddress(); |
||
| 351 | return $this->_transferPhysicalAddressData( |
||
| 352 | $address, |
||
| 353 | $this->_transferPersonNameData($address, $mailingAddress) |
||
| 354 | ); |
||
| 355 | } |
||
| 356 | |||
| 357 | /** |
||
| 358 | * Fill in the values the order create request requires. |
||
| 359 | * |
||
| 360 | * @return self |
||
| 361 | */ |
||
| 362 | protected function _buildNewPayload() |
||
| 363 | { |
||
| 364 | $this->_payload |
||
| 365 | ->setBillingAddress($this->_getRomBillingAddress($this->_order->getBillingAddress())) |
||
| 366 | ->setCurrency($this->_order->getOrderCurrencyCode()) |
||
| 367 | ->setLevelOfService($this->_config->levelOfService) |
||
| 368 | ->setLocale($this->_getLocale()) |
||
| 369 | ->setOrderHistoryUrl($this->_helper->getOrderHistoryUrl($this->_order)) |
||
| 370 | ->setOrderId($this->_order->getIncrementId()) |
||
| 371 | ->setOrderTotal($this->_order->getBaseGrandTotal()) |
||
| 372 | ->setOrderType($this->_config->orderType) |
||
| 373 | ->setRequestId($this->_coreHelper->generateRequestId('OCR-')); |
||
| 374 | $createdAt = $this->_getAsDateTime($this->_order->getCreatedAt()); |
||
| 375 | if ($createdAt) { |
||
| 376 | $this->_payload->setCreateTime($createdAt); |
||
| 377 | } |
||
| 378 | return $this |
||
| 379 | ->handleTestOrder() |
||
| 380 | ->_setCustomerData($this->_order, $this->_payload) |
||
| 381 | ->_setOrderContext($this->_order, $this->_payload) |
||
| 382 | ->_setShipGroups($this->_order, $this->_payload) |
||
| 383 | ->_setPaymentData($this->_order, $this->_payload); |
||
| 384 | } |
||
| 385 | |||
| 386 | /** |
||
| 387 | * get the locale code for the order |
||
| 388 | * |
||
| 389 | * @return string |
||
| 390 | */ |
||
| 391 | protected function _getLocale() |
||
| 392 | { |
||
| 393 | $languageCode = $this->_coreHelper->getConfigModel()->setStore($this->_order->getStore())->languageCode; |
||
| 394 | $splitCode = explode('-', $languageCode); |
||
| 395 | if (!empty($splitCode[0]) && !empty($splitCode[1])) { |
||
| 396 | $result = strtolower($splitCode[0]) . '_' . strtoupper($splitCode[1]); |
||
| 397 | } else { |
||
| 398 | $logData = ['order_id' => $this->_order->getIncrementId(), 'language_code' => $languageCode]; |
||
| 399 | $this->_logger->critical( |
||
| 400 | "The store for order '{order_id}' is configured with an invalid language code: '{language_code}'", |
||
| 401 | $this->_logContext->getMetaData(__CLASS__, $logData) |
||
| 402 | ); |
||
| 403 | $result = ''; |
||
| 404 | } |
||
| 405 | return $result; |
||
| 406 | } |
||
| 407 | |||
| 408 | /** |
||
| 409 | * Set Customer information on the payload |
||
| 410 | * |
||
| 411 | * @param Mage_Sales_Model_Order |
||
| 412 | * @param IOrderCustomer |
||
| 413 | * @return self |
||
| 414 | */ |
||
| 415 | protected function _setCustomerData(Mage_Sales_Model_Order $order, IOrderCustomer $payload) |
||
| 416 | { |
||
| 417 | $payload |
||
| 418 | ->setFirstName($order->getCustomerFirstname()) |
||
| 419 | ->setLastName($order->getCustomerLastname()) |
||
| 420 | ->setMiddleName($order->getCustomerMiddlename()) |
||
| 421 | ->setHonorificName($order->getCustomerPrefix()) |
||
| 422 | ->setGender($this->_getCustomerGender($order)) |
||
| 423 | ->setCustomerId($this->_getCustomerId($order)) |
||
| 424 | ->setEmailAddress($order->getCustomerEmail()) |
||
| 425 | ->setTaxId($order->getCustomerTaxvat()) |
||
| 426 | ->setIsTaxExempt($order->getCustomer()->getTaxExempt()); |
||
| 427 | $dob = $this->_getAsDateTime($order->getCustomerDob()); |
||
| 428 | if ($dob) { |
||
| 429 | $payload->setDateOfBirth($dob); |
||
| 430 | } |
||
| 431 | return $this; |
||
| 432 | } |
||
| 433 | |||
| 434 | /** |
||
| 435 | * get an id for the customer |
||
| 436 | * @return string |
||
| 437 | */ |
||
| 438 | protected function _getCustomerId() |
||
| 439 | { |
||
| 440 | /** bool $isGuest */ |
||
| 441 | $isGuest = !$this->_order->getCustomerId(); |
||
| 442 | /** @var int $customerId */ |
||
| 443 | $customerId = $this->_order->getCustomerId() ?: $this->_getGuestCustomerId(); |
||
| 444 | /** @var mixed $store */ |
||
| 445 | $store = $this->_order->getStore(); |
||
| 446 | return $this->_helper->prefixCustomerId($customerId, $store, $isGuest); |
||
| 447 | } |
||
| 448 | |||
| 449 | /** |
||
| 450 | * generate a customer id for a guest |
||
| 451 | * @return string |
||
| 452 | */ |
||
| 453 | protected function _getGuestCustomerId() |
||
| 454 | { |
||
| 455 | $sessionIdHash = hash('sha256', $this->_getCustomerSession()->getEncryptedSessionId()); |
||
| 456 | // when placing the order as a guest, there is no customer increment; |
||
| 457 | // use a hash of the session id instead. |
||
| 458 | return substr($sessionIdHash, 0, 35); |
||
| 459 | } |
||
| 460 | |||
| 461 | /** |
||
| 462 | * get the customer session |
||
| 463 | * @return Mage_Customer_Model_Session |
||
| 464 | */ |
||
| 465 | protected function _getCustomerSession() |
||
| 469 | |||
| 470 | /** |
||
| 471 | * Add payment payloads to the request |
||
| 472 | * |
||
| 473 | * @param Mage_Sales_Model_Order |
||
| 474 | * @param IPaymentContainer |
||
| 475 | * @return self |
||
| 476 | */ |
||
| 477 | protected function _setPaymentData(Mage_Sales_Model_Order $order, IPaymentContainer $paymentContainer) |
||
| 478 | { |
||
| 479 | // allow event handlers to communicate whether a payment |
||
| 480 | // was handled or not |
||
| 481 | $processedPayments = new SplObjectStorage(); |
||
| 482 | Mage::dispatchEvent($this->_paymentDataEvent, [ |
||
| 483 | 'order' => $order, |
||
| 484 | 'payment_container' => $paymentContainer, |
||
| 485 | // Any handler of this event should attach payments to |
||
| 486 | // the processed payments object to signify that a payload |
||
| 487 | // was created. Handlers should avoid creating a new |
||
| 488 | // payload for any payment in the set of processed |
||
| 489 | // payments to avoid adding duplicate payment information |
||
| 490 | // to the request. |
||
| 491 | 'processed_payments' => $processedPayments, |
||
| 492 | ]); |
||
| 493 | $this->_defaultPaymentHandler |
||
| 494 | ->addPaymentsToPayload($order, $paymentContainer, $processedPayments); |
||
| 495 | return $this; |
||
| 496 | } |
||
| 497 | |||
| 498 | /** |
||
| 499 | * Add order context information to the request |
||
| 500 | * |
||
| 501 | * @param Mage_Sales_Model_Order |
||
| 502 | * @param IOrderContext |
||
| 503 | * @return self |
||
| 504 | */ |
||
| 505 | protected function _setOrderContext(Mage_Sales_Model_Order $order, IOrderContext $orderContext) |
||
| 506 | { |
||
| 507 | Mage::dispatchEvent($this->_contextDataEvent, [ |
||
| 508 | 'order' => $order, |
||
| 509 | 'order_context' => $orderContext, |
||
| 510 | ]); |
||
| 511 | return $this; |
||
| 512 | } |
||
| 513 | |||
| 514 | /** |
||
| 515 | * Add ship groups to the request. For each address in the order, dispatch |
||
| 516 | * an event for handling ship group destinations. For each ship group |
||
| 517 | * destination added, trigger additional events for each item in the ship |
||
| 518 | * group. |
||
| 519 | * |
||
| 520 | * @param Mage_Sales_Model_Order |
||
| 521 | * @param IOrderCreateRequest |
||
| 522 | * @return self |
||
| 523 | */ |
||
| 524 | protected function _setShipGroups(Mage_Sales_Model_Order $order, IOrderCreateRequest $request) |
||
| 525 | { |
||
| 526 | $shipGroups = $request->getShipGroups(); |
||
| 527 | $orderItems = $request->getOrderItems(); |
||
| 528 | $destinations = $request->getDestinations(); |
||
| 529 | $itemCollection = $order->getItemsCollection(); |
||
| 530 | foreach ($order->getAddressesCollection() as $address) { |
||
| 531 | $items = $this->_getItemsForAddress($address, $itemCollection); |
||
| 532 | if ($items) { |
||
| 533 | $shipGroups->offsetSet($this->_buildShipGroupForAddress( |
||
| 534 | $address, |
||
| 535 | $items, |
||
| 536 | $order, |
||
| 537 | $shipGroups, |
||
| 538 | $destinations, |
||
| 539 | $orderItems |
||
| 540 | )); |
||
| 541 | } |
||
| 542 | } |
||
| 543 | return $this; |
||
| 544 | } |
||
| 545 | |||
| 546 | /** |
||
| 547 | * Create a new ship group for the address and dispatch events to add |
||
| 548 | * destination, gifting and item data to the ship group. |
||
| 549 | * |
||
| 550 | * @param Mage_Customer_Model_Address_Abstract |
||
| 551 | * @param Mage_Sales_Model_Order_Item[] |
||
| 552 | * @param Mage_Sales_Model_Order |
||
| 553 | * @param IShipGroupIterable |
||
| 554 | * @param IOrderDestinationIterable |
||
| 555 | * @param IOrderItemIterable |
||
| 556 | * @return IShipGroup |
||
| 557 | */ |
||
| 558 | protected function _buildShipGroupForAddress( |
||
| 559 | Mage_Customer_Model_Address_Abstract $address, |
||
| 560 | array $items, |
||
| 561 | Mage_Sales_Model_Order $order, |
||
| 562 | IShipGroupIterable $shipGroups, |
||
| 563 | IOrderDestinationIterable $destinations, |
||
| 564 | IOrderItemIterable $orderItems |
||
| 565 | ) { |
||
| 566 | $shipGroup = $shipGroups->getEmptyShipGroup(); |
||
| 567 | // default set this value to flatrate shipping since magento doesn't |
||
| 568 | // currently allow us to figure out how much each item contributes to |
||
| 569 | // shipping. The value can be changed by responding to the following |
||
| 570 | // event. |
||
| 571 | $shipGroup->setChargeType(self::SHIPPING_CHARGE_TYPE_FLATRATE); |
||
| 572 | Mage::dispatchEvent($this->_shipGroupEvent, [ |
||
| 573 | 'address' => $address, |
||
| 574 | 'order' => $order, |
||
| 575 | 'ship_group_payload' => $shipGroup, |
||
| 576 | 'order_destinations_payload' => $destinations, |
||
| 577 | ]); |
||
| 578 | // If none of the event observers added a destination, include a default |
||
| 579 | // mapping of the address to a destination. |
||
| 580 | if (is_null($shipGroup->getDestination())) { |
||
| 581 | $shipGroup->setDestination($this->_buildDefaultDestination($address, $destinations)); |
||
| 582 | } |
||
| 583 | return $this->_addOrderItemReferences($shipGroup, $items, $orderItems, $address, $order); |
||
| 584 | } |
||
| 585 | |||
| 586 | /** |
||
| 587 | * @param IShipGroup |
||
| 588 | * @param Mage_Sales_Model_Order_Item[] |
||
| 589 | * @param IOrderItemIterable |
||
| 590 | * @param Mage_Sales_Model_Order_Address |
||
| 591 | * @param Mage_Sales_Model_Order |
||
| 592 | */ |
||
| 593 | protected function _addOrderItemReferences( |
||
| 594 | IShipGroup $shipGroup, |
||
| 595 | array $items, |
||
| 596 | IOrderItemIterable $orderItems, |
||
| 597 | Mage_Customer_Model_Address_Abstract $address, |
||
| 598 | Mage_Sales_Model_Order $order |
||
| 599 | ) { |
||
| 600 | $itemReferences = $shipGroup->getItemReferences(); |
||
| 601 | $shippingChargeType = $shipGroup->getChargeType(); |
||
| 602 | // Shipping will always be included for the first item - flat-rate or |
||
| 603 | // non-flat-rate shipping. |
||
| 604 | $includeShipping = true; |
||
| 605 | foreach ($items as $item) { |
||
| 606 | $this->_nextLineNumber++; |
||
| 607 | |||
| 608 | // Set line number for the item on the item object, only guaranteed |
||
| 609 | // link between a specific Magento order item and ROM item payload. |
||
| 610 | $item->setLineNumber($this->_nextLineNumber); |
||
| 611 | |||
| 612 | $itemPayload = $orderItems->getEmptyOrderItem(); |
||
| 613 | $this->_defaultItemHandler->buildOrderItem( |
||
| 614 | $itemPayload, |
||
| 615 | $item, |
||
| 616 | $address, |
||
| 617 | $this->_nextLineNumber, |
||
| 618 | $includeShipping |
||
| 619 | ); |
||
| 620 | Mage::dispatchEvent($this->_orderItemEvent, [ |
||
| 621 | 'item' => $item, |
||
| 622 | 'item_payload' => $itemPayload, |
||
| 623 | 'order' => $order, |
||
| 624 | 'address' => $address, |
||
| 625 | 'line_number' => $this->_nextLineNumber, |
||
| 626 | 'shipping_charge_type' => $shippingChargeType, |
||
| 627 | 'include_shipping' => $includeShipping, |
||
| 628 | ]); |
||
| 629 | $itemReferences->offsetSet( |
||
| 630 | $itemReferences->getEmptyItemReference()->setReferencedItem($itemPayload) |
||
| 631 | ); |
||
| 632 | // For non-flat-rate shipping, include shipping for every item. |
||
| 633 | // For flat-rate shipping, should only be included for the first |
||
| 634 | // item in the ship group. |
||
| 635 | $includeShipping = $shippingChargeType !== self::SHIPPING_CHARGE_TYPE_FLATRATE; |
||
| 636 | } |
||
| 637 | $shipGroup->setItemReferences($itemReferences); |
||
| 638 | return $shipGroup; |
||
| 639 | } |
||
| 640 | |||
| 641 | /** |
||
| 642 | * Get all items shipping to a given address. For billing addresses, this |
||
| 643 | * will be all virtual items in the order. For shipping addresses, any |
||
| 644 | * non-virtual items. Only items that are to be included in the order create |
||
| 645 | * request should be returned. |
||
| 646 | * |
||
| 647 | * @param Mage_Customer_Model_Address_Abstract |
||
| 648 | * @param Mage_Sales_Model_Resource_Order_Item_Collection |
||
| 649 | * @return Mage_Sales_Model_Order_Item[] |
||
| 650 | */ |
||
| 651 | protected function _getItemsForAddress( |
||
| 652 | Mage_Customer_Model_Address_Abstract $address, |
||
| 653 | Mage_Sales_Model_Resource_Order_Item_Collection $orderItems |
||
| 654 | ) { |
||
| 655 | // All items will have an `order_address_id` matching the id of the |
||
| 656 | // address the item ships to (including virtual items which "ship" to |
||
| 657 | // the billing address). |
||
| 658 | // Filter the given collection instead of using address methods to get |
||
| 659 | // items to prevent loading separate item collections for each address. |
||
| 660 | return $this->_itemSelection->selectFrom( |
||
| 661 | $orderItems->getItemsByColumnValue('order_address_id', $address->getId()) |
||
| 662 | ); |
||
| 663 | } |
||
| 664 | |||
| 665 | /** |
||
| 666 | * Build a default destination for an address. For billing addresses, this |
||
| 667 | * should result in an email address destination - destination for virtual |
||
| 668 | * items. For shipping addresses, a mailing address destination. |
||
| 669 | * |
||
| 670 | * @param Mage_Sales_Mdoel_Order_Address |
||
| 671 | * @param IOrderDestinationIterable |
||
| 672 | * @return IDestination |
||
| 673 | */ |
||
| 674 | protected function _buildDefaultDestination(Mage_Customer_Model_Address_Abstract $address, IOrderDestinationIterable $destinations) |
||
| 675 | { |
||
| 676 | return $this->_isAddressBilling($address) |
||
| 677 | ? $this->_buildVirtualDestination($address, $destinations) |
||
| 678 | : $this->_buildPhysicalDestination($address, $destinations); |
||
| 679 | } |
||
| 680 | |||
| 681 | /** |
||
| 682 | * Create a new mailing address destination from a magento order address. |
||
| 683 | * |
||
| 684 | * @param Mage_Customer_Model_Address_Abstract |
||
| 685 | * @param IOrderDestinationIterable Used to create the new email address destination payload |
||
| 686 | * @return IMailingAddressDestination |
||
| 687 | */ |
||
| 688 | protected function _buildPhysicalDestination(Mage_Customer_Model_Address_Abstract $address, IOrderDestinationIterable $destinations) |
||
| 689 | { |
||
| 690 | $destination = $destinations->getEmptyMailingAddress(); |
||
| 691 | return $this->_transferPhysicalAddressData( |
||
| 692 | $address, |
||
| 693 | $this->_transferPersonNameData($address, $destination) |
||
| 694 | ); |
||
| 695 | } |
||
| 696 | |||
| 697 | /** |
||
| 698 | * Create a new mailing address destination from a magento order address. |
||
| 699 | * |
||
| 700 | * @param Mage_Customer_Model_Address_Abstract |
||
| 701 | * @param IOrderDestinationIterable Used to create the new email address destination payload |
||
| 702 | * @return IEmailAddressDestination |
||
| 703 | */ |
||
| 704 | protected function _buildVirtualDestination(Mage_Customer_Model_Address_Abstract $address, IOrderDestinationIterable $destinations) |
||
| 705 | { |
||
| 706 | $destination = $destinations->getEmptyEmailAddress(); |
||
| 707 | return $this->_transferPersonNameData($address, $destination) |
||
| 708 | ->setEmailAddress($address->getEmail()); |
||
| 709 | } |
||
| 710 | |||
| 711 | /** |
||
| 712 | * Transfer person name data from the order address to the person name payload. |
||
| 713 | * |
||
| 714 | * @param Mage_Customer_Model_Address_Abstract |
||
| 715 | * @param IPersonName |
||
| 716 | * @return IPersonName |
||
| 717 | */ |
||
| 718 | protected function _transferPersonNameData(Mage_Customer_Model_Address_Abstract $address, IPersonName $personName) |
||
| 719 | { |
||
| 720 | return $personName |
||
| 721 | ->setFirstName($address->getFirstname()) |
||
| 722 | ->setLastName($address->getLastname()) |
||
| 723 | ->setMiddleName($address->getMiddlename()) |
||
| 724 | ->setHonorificName($address->getPrefix()); |
||
| 725 | } |
||
| 726 | |||
| 727 | /** |
||
| 728 | * Transfer physical address data from the order address to the physical |
||
| 729 | * address payload. |
||
| 730 | * |
||
| 731 | * @param Mage_Customer_Model_Address_Abstract |
||
| 732 | * @param IPhysicalAddress |
||
| 733 | * @return IPhysicalAddress |
||
| 734 | */ |
||
| 735 | protected function _transferPhysicalAddressData(Mage_Customer_Model_Address_Abstract $address, IPhysicalAddress $physicalAddress) |
||
| 736 | { |
||
| 737 | return $physicalAddress |
||
| 738 | // get all address street lines as a single, newline-delimited string |
||
| 739 | ->setLines($address->getStreet(-1)) |
||
| 740 | ->setCity($address->getCity()) |
||
| 741 | ->setMainDivision($address->getRegionCode()) |
||
| 742 | ->setCountryCode($address->getCountryId()) |
||
| 743 | ->setPostalCode($address->getPostcode()) |
||
| 744 | ->setPhone($address->getTelephone()); |
||
| 745 | } |
||
| 746 | |||
| 747 | /** |
||
| 748 | * Check for an order address to be a billing address. |
||
| 749 | * |
||
| 750 | * @param Mage_Customer_Model_Address_Abstract |
||
| 751 | * @return bool |
||
| 752 | */ |
||
| 753 | protected function _isAddressBilling(Mage_Customer_Model_Address_Abstract $address) |
||
| 757 | |||
| 758 | /** |
||
| 759 | * convert a mage date string to a datetime. |
||
| 760 | * if $dateString is invalid, return false. |
||
| 761 | * @param string |
||
| 762 | * @return DateTime|false |
||
| 763 | */ |
||
| 764 | protected function _getAsDateTime($dateString) |
||
| 768 | |||
| 769 | /** |
||
| 770 | * Get the gender code for the customer |
||
| 771 | * @param Mage_Sales_Model_Order |
||
| 772 | * @return string|null |
||
| 773 | */ |
||
| 774 | protected function _getCustomerGender(Mage_Sales_Model_Order $order) |
||
| 775 | { |
||
| 776 | return $this->_nullCoalesce( |
||
| 777 | $this->_getCustomerGenderLabel($order), |
||
| 778 | $this->_getValidGenderMappings(), |
||
| 779 | null |
||
| 780 | ); |
||
| 781 | } |
||
| 782 | |||
| 783 | /** |
||
| 784 | * get the valid set of associations for mapping Magento gender labels |
||
| 785 | * to ROM gender strings. |
||
| 786 | * @return array |
||
| 787 | */ |
||
| 788 | protected function _getValidGenderMappings() |
||
| 789 | { |
||
| 790 | // get the mapping from the config and filter it down so that only |
||
| 791 | // valid mappings exist |
||
| 792 | return array_intersect( |
||
| 793 | (array) $this->_config->genderMap, |
||
| 794 | $this->_validGenderStrings |
||
| 795 | ); |
||
| 796 | } |
||
| 797 | |||
| 798 | /** |
||
| 799 | * get the label for the customer's gender |
||
| 800 | * @param Mage_Sales_Model_Order |
||
| 801 | * @return string |
||
| 802 | */ |
||
| 803 | protected function _getCustomerGenderLabel(Mage_Sales_Model_Order $order) |
||
| 804 | { |
||
| 805 | $customerGenderLabel = null; |
||
| 806 | // get the label for the customer's gender and use the filtered mapping to |
||
| 807 | // get the ROM equivalent. |
||
| 808 | $optionId = $order->getCustomerGender(); |
||
| 809 | foreach ($this->_getGenderOptions() as $option) { |
||
| 810 | if ($option['value'] === $optionId) { |
||
| 811 | $customerGenderLabel = $option['label']; |
||
| 812 | break; |
||
| 813 | } |
||
| 814 | } |
||
| 815 | return $customerGenderLabel; |
||
| 816 | } |
||
| 817 | |||
| 818 | /** |
||
| 819 | * get available gender options |
||
| 820 | * @return array |
||
| 821 | */ |
||
| 822 | protected function _getGenderOptions() |
||
| 823 | { |
||
| 824 | return (array) Mage::getResourceSingleton('customer/customer') |
||
| 825 | ->getAttribute('gender') |
||
| 826 | ->getSource() |
||
| 827 | ->getAllOptions(); |
||
| 828 | } |
||
| 829 | |||
| 830 | /** |
||
| 831 | * Detect an order as a test order when the second street line |
||
| 832 | * address of the order billing address matches the constant |
||
| 833 | * IOrderCreateRequest::TEST_TYPE_AUTOCANCEL. Flag the OCR |
||
| 834 | * payload as a test order. |
||
| 835 | * |
||
| 836 | * @return self |
||
| 837 | */ |
||
| 838 | protected function handleTestOrder() |
||
| 839 | { |
||
| 840 | /** @var Mage_Customer_Model_Address_Abstract */ |
||
| 841 | $billingAddress = $this->_order->getBillingAddress(); |
||
| 842 | if ($this->isTestOrder($billingAddress)) { |
||
| 843 | $this->_payload->setTestType(IOrderCreateRequest::TEST_TYPE_AUTOCANCEL); |
||
| 844 | } |
||
| 845 | return $this; |
||
| 846 | } |
||
| 847 | |||
| 848 | /** |
||
| 849 | * Determine if an order should be sent to ROM as a test order by checking the second street |
||
| 850 | * address if it match the constant value IOrderCreateRequest::TEST_TYPE_AUTOCANCEL, then it is |
||
| 851 | * a test order, otherwise it is not a test order. |
||
| 852 | * |
||
| 853 | * @param Mage_Customer_Model_Address_Abstract |
||
| 854 | * @return bool |
||
| 855 | */ |
||
| 856 | protected function isTestOrder(Mage_Customer_Model_Address_Abstract $billingAddress) |
||
| 860 | } |
||
| 861 |