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 |