GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

EbayEnterprise_Order_Model_Create::_send()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 50
Code Lines 38

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 50
rs 8.6315
cc 6
eloc 38
nc 6
nop 0
1
<?php
2
/**
3
 * Copyright (c) 2013-2014 eBay Enterprise, Inc.
4
 *
5
 * NOTICE OF LICENSE
6
 *
7
 * This source file is subject to the Open Software License (OSL 3.0)
8
 * that is bundled with this package in the file LICENSE.md.
9
 * It is also available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * @copyright   Copyright (c) 2013-2014 eBay Enterprise, Inc. (http://www.ebayenterprise.com/)
13
 * @license     http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
14
 */
15
16
use \eBayEnterprise\RetailOrderManagement\Api\IBidirectionalApi;
17
use \eBayEnterprise\RetailOrderManagement\Api\Exception\NetworkError;
18
use \eBayEnterprise\RetailOrderManagement\Api\Exception\UnsupportedHttpAction;
19
use \eBayEnterprise\RetailOrderManagement\Api\Exception\UnsupportedOperation;
20
use \eBayEnterprise\RetailOrderManagement\Payload\Checkout\IPersonName;
21
use \eBayEnterprise\RetailOrderManagement\Payload\Checkout\IPhysicalAddress;
22
use \eBayEnterprise\RetailOrderManagement\Payload\Exception\InvalidPayload;
23
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IDestination;
24
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IOrderContext;
25
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IOrderCreateRequest;
26
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IOrderCustomer;
27
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IOrderDestinationIterable;
28
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IOrderItemIterable;
29
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IOrderItemReferenceContainer;
30
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IPaymentContainer;
31
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IShipGroupIterable;
32
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IShipGroup;
33
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IEmailAddressDestination;
34
use \eBayEnterprise\RetailOrderManagement\Payload\Order\IMailingAddressDestination;
35
36
/**
37
 * Fills out the Order Create Request payload and triggers
38
 * transmitting it to the service.
39
 */
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(
0 ignored issues
show
Unused Code introduced by
The method parameter $logger is never used
Loading history...
Unused Code introduced by
The method parameter $helper is never used
Loading history...
Unused Code introduced by
The method parameter $coreHelper is never used
Loading history...
Unused Code introduced by
The method parameter $defaultPaymentHandler is never used
Loading history...
Unused Code introduced by
The method parameter $defaultItemHandler is never used
Loading history...
Unused Code introduced by
The method parameter $order is never used
Loading history...
Unused Code introduced by
The method parameter $config is never used
Loading history...
Unused Code introduced by
The method parameter $api is never used
Loading history...
Unused Code introduced by
The method parameter $payload is never used
Loading history...
Unused Code introduced by
The method parameter $logContext is never used
Loading history...
Unused Code introduced by
The method parameter $itemSelection is never used
Loading history...
147
        EbayEnterprise_MageLog_Helper_Data $logger,
0 ignored issues
show
Unused Code introduced by
The parameter $logger is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
148
        EbayEnterprise_Order_Helper_Data $helper,
0 ignored issues
show
Unused Code introduced by
The parameter $helper is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
149
        EbayEnterprise_Eb2cCore_Helper_Data $coreHelper,
0 ignored issues
show
Unused Code introduced by
The parameter $coreHelper is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
150
        EbayEnterprise_Order_Model_Create_Payment $defaultPaymentHandler,
0 ignored issues
show
Unused Code introduced by
The parameter $defaultPaymentHandler is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
151
        EbayEnterprise_Order_Model_Create_Orderitem $defaultItemHandler,
0 ignored issues
show
Unused Code introduced by
The parameter $defaultItemHandler is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
152
        Mage_Sales_Model_Order $order,
0 ignored issues
show
Unused Code introduced by
The parameter $order is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
153
        EbayEnterprise_Eb2cCore_Model_Config_Registry $config,
0 ignored issues
show
Unused Code introduced by
The parameter $config is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
154
        IBidirectionalApi $api,
0 ignored issues
show
Unused Code introduced by
The parameter $api is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
155
        IOrderCreateRequest $payload,
0 ignored issues
show
Unused Code introduced by
The parameter $payload is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
156
        EbayEnterprise_MageLog_Helper_Context $logContext,
0 ignored issues
show
Unused Code introduced by
The parameter $logContext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
157
        EbayEnterprise_Order_Helper_Item_Selection $itemSelection
0 ignored issues
show
Unused Code introduced by
The parameter $itemSelection is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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)
171
    {
172
        return isset($arr[$key]) ? $arr[$key] : $default;
173
    }
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
0 ignored issues
show
Documentation introduced by
Should the return type not be IPhysicalAddress?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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)
0 ignored issues
show
Documentation introduced by
$this->_transferPersonNa...dress, $mailingAddress) is of type object<eBayEnterprise\Re...d\Checkout\IPersonName>, but the function expects a object<eBayEnterprise\Re...ckout\IPhysicalAddress>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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()))
0 ignored issues
show
Compatibility introduced by
$this->_getRomBillingAdd...r->getBillingAddress()) of type object<eBayEnterprise\Re...ckout\IPhysicalAddress> is not a sub-type of object<eBayEnterprise\Re...\Order\IMailingAddress>. It seems like you assume a child interface of the interface eBayEnterprise\RetailOrd...eckout\IPhysicalAddress to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
366
            ->setCurrency($this->_order->getOrderCurrencyCode())
367
            ->setLevelOfService($this->_config->levelOfService)
0 ignored issues
show
Documentation introduced by
The property levelOfService does not exist on object<EbayEnterprise_Eb..._Model_Config_Registry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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)
0 ignored issues
show
Documentation introduced by
The property orderType does not exist on object<EbayEnterprise_Eb..._Model_Config_Registry>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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;
0 ignored issues
show
Documentation introduced by
The property languageCode does not exist on object<EbayEnterprise_Eb..._Model_Config_Registry>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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))
0 ignored issues
show
Unused Code introduced by
The call to EbayEnterprise_Order_Mod...reate::_getCustomerId() has too many arguments starting with $order.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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()
466
    {
467
        return Mage::getSingleton('customer/session');
468
    }
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items of type Mage_Sales_Model_Order_Item[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
533
                $shipGroups->offsetSet($this->_buildShipGroupForAddress(
534
                    $address,
535
                    $items,
536
                    $order,
537
                    $shipGroups,
538
                    $destinations,
0 ignored issues
show
Compatibility introduced by
$destinations of type object<eBayEnterprise\Re...t\IDestinationIterable> is not a sub-type of object<eBayEnterprise\Re...derDestinationIterable>. It seems like you assume a child interface of the interface eBayEnterprise\RetailOrd...ut\IDestinationIterable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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));
0 ignored issues
show
Bug introduced by
It seems like $this->_buildDefaultDest...address, $destinations) targeting EbayEnterprise_Order_Mod...ildDefaultDestination() can also be of type object<eBayEnterprise\Re...ckout\IPhysicalAddress>; however, eBayEnterprise\RetailOrd...Group::setDestination() does only seem to accept object<eBayEnterprise\Re...oad\Order\IDestination>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be IEmailAddressDestination|IPhysicalAddress?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the return type not be IPhysicalAddress?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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)
0 ignored issues
show
Documentation introduced by
$this->_transferPersonNa...$address, $destination) is of type object<eBayEnterprise\Re...d\Checkout\IPersonName>, but the function expects a object<eBayEnterprise\Re...ckout\IPhysicalAddress>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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)
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eBayEnterprise\RetailOrd...ad\Checkout\IPersonName as the method setEmailAddress() does only exist in the following implementations of said interface: eBayEnterprise\RetailOrd...ail\OrderDetailCustomer, eBayEnterprise\RetailOrd...EmailAddressDestination, eBayEnterprise\RetailOrd...rder\OrderCreateRequest, eBayEnterprise\RetailOrd...EmailAddressDestination.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
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
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eBayEnterprise\RetailOrd...eckout\IPhysicalAddress as the method setPhone() does only exist in the following implementations of said interface: eBayEnterprise\RetailOrd...ad\Order\MailingAddress, eBayEnterprise\RetailOrd...rder\ProxyPickupDetails, eBayEnterprise\RetailOrd...oad\Order\StoreLocation.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
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)
754
    {
755
        return $address->getAddressType() === Mage_Customer_Model_Address_Abstract::TYPE_BILLING;
756
    }
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)
765
    {
766
        return DateTime::createFromFormat('Y-m-d H:i:s', $dateString);
767
    }
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)
857
    {
858
        return $billingAddress->getStreet(2) === IOrderCreateRequest::TEST_TYPE_AUTOCANCEL;
859
    }
860
}
861