Webshop::createOrder()   F
last analyzed

Complexity

Conditions 26
Paths 1350

Size

Total Lines 104
Code Lines 61

Duplication

Lines 63
Ratio 60.58 %

Code Coverage

Tests 43
CRAP Score 70.1789

Importance

Changes 0
Metric Value
cc 26
eloc 61
nc 1350
nop 1
dl 63
loc 104
rs 2
c 0
b 0
f 0
ccs 43
cts 72
cp 0.5971
crap 70.1789

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Webshop s�rger for at holde styr webshoppen.
4
 *
5
 * @todo Indstilling af porto - skal det v�re et standardprodukt p� alle ordrer?
6
 *
7
 * @todo Standardprodukter p� ordrer.
8
 *
9
 * @todo Opf�rsel ift. lager i onlineshoppen.
10
 *
11
 * @todo B�r kunne tage h�jde for en tidsangivelse p� produkterne
12
 *
13
 * @package Intraface_Shop
14
 * @author Lars Olesen <[email protected]>
15
 *
16
 * @see Basket
17
 * @see WebshopServer.php / XML-RPC-serveren i xmlrpc biblioteket
18
 * @see Order
19
 * @see Contact
20
 */
21
class Webshop
22
{
23
    /**
24
     * @var object
25
     */
26
    public $kernel;
27
28
    /**
29
     * @var object
30
     */
31
    public $basket;
32
33
    /**
34
     * @var object
35
     */
36
    private $order;
37
38
    /**
39
     * @var object
40
     */
41
    private $contact;
42
43
    /**
44
     * @var object
45
     */
46
    public $error;
47
48
    /**
49
     * @var private
50
     */
51
    public $session_id;
52
53
    /**
54
     * Construktor
55
     *
56
     * @param object $kernel     Kernel object
57
     * @param string $session_id Unikt session id
58
     *
59
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
60
     */
61 6
    public function __construct($kernel, $session_id)
62
    {
63 6
        if (!is_object($kernel)) {
64
            throw new Exception('Webshopmodulet har brug for Kernel');
65
        }
66
67 6
        $this->kernel = $kernel;
68 6
        $this->kernel->useModule('debtor');
69 6
        $this->kernel->useModule('order');
70 6
        $this->kernel->useModule('product');
71 6
        $this->kernel->useModule('contact');
72
73 6
        $this->session_id = $session_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $session_id of type string is incompatible with the declared type object<private> of property $session_id.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
74 6
        $this->basket = $this->createBasket();
75
76 6
        $this->error = new Intraface_Error;
77 6
    }
78
79
    /**
80
     * Convenience method to create the basket
81
     *
82
     * @return object
83
     */
84 6
    private function createBasket()
85
    {
86 6
        return new Basket($this, $this->session_id);
87
    }
88
89
    /**
90
     * Gets the basket
91
     *
92
     * @return object
93
     */
94 1
    public function getBasket()
95
    {
96 1
        return $this->basket;
97
    }
98
99
    /**
100
     * Places the order and utilizes basket
101
     *
102
     * @param array $input Array with customer data
103
     *
104
     * @return integer Order id
105
     */
106 5
    private function createOrder($input)
107
    {
108 5 View Code Duplication
        if (isset($input['contact_id']) && (int)$input['contact_id'] > 0) {
109 3
            $this->contact = new Contact($this->kernel, (int)$input['contact_id']);
110
111
            $contact_person_id = 0;
112
            // It is a company and contactperson is given. We try to see if we can find the contact person.
113
            if (isset($input['contactperson']) && $input['contactperson'] != '') {
114
                $input['type'] = 'corporation';
115
116
                // If the contact is a company their might already be a contact person.
117
                if ($this->contact->get('type') == 'company' && isset($this->contact->contactperson)) {
118
                    $contact_persons = $this->contact->contactperson->getList();
119
                    foreach ($contact_persons as $contact_person) {
120
                        // This is only a comparing on name, this might not be enough.
121 3
                        if ($contact_person['name'] == $input['contactperson']) {
122
                            $contact_person_id = $contact_person['id'];
123
                            break;
124
                        }
125
                    }
126
                }
127
            }
128 3
        } else {
129 5
            $this->contact = new Contact($this->kernel);
130 5
            $contact_person_id = 0;
131
132
            // s�rger for at tjekke om det er et firma
133 5
            if (isset($input['contactperson']) && $input['contactperson'] != '') {
134
                $input['type'] = 'corporation'; // firma
135
            }
136
137 5
            if (isset($input['customer_ean']) && $input['customer_ean'] != '') {
138
                // sets preffered invoice to electronic.
139 3
                $input['type'] = 'corporation'; // firma
140 3
                $input['preferred_invoice'] = 3;
141 3
            } else {
142
                // sets preffered invoice to email. Should be a setting in webshop.
143 2
                $input['preferred_invoice'] = 2;
144
            }
145
        }
146
147 5
        if (isset($input['customer_ean'])) {
148 3
            $input['ean'] = $input['customer_ean'];
149 3
        }
150
151
152 5 View Code Duplication
        if (!isset($input['contact_id'])) {
153
            // opdaterer kontakten
154 5
            if (!$contact_id = $this->contact->save($input)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $contact_id is correct as $this->contact->save($input) (which targets Contact::save()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
155
                $this->error->merge($this->contact->getError()->getMessage());
156
                return false;
157
            }
158 5
        }
159
160
        // we update/add the contactperson.
161 5 View Code Duplication
        if (isset($input['type']) && $input['type'] == 'corporation') { // firma
162 3
            $this->contact->loadContactPerson($contact_person_id);
163 3
            settype($input['contactperson'], 'string');
164 3
            settype($input['contactemail'], 'string');
165 3
            settype($input['contactphone'], 'string');
166 3
            if (!$contact_person_id = $this->contact->contactperson->save(array('name'=>$input['contactperson'], 'email'=>$input['contactemail'], 'phone'=>$input['contactphone']))) {
167
                $this->error->merge($this->contact->getError()->getMessage());
168
                return false;
169
            }
170 3
        }
171
172 5
        $value['contact_id'] = $this->contact->get('id');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$value was never initialized. Although not strictly required by PHP, it is generally a good practice to add $value = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
173 5
        $value['contact_address_id'] = $this->contact->address->get('id');
174 5
        $value['contact_person_id'] = $contact_person_id;
175
176 5
        $value['this_date'] = date('d-m-Y');
177 5
        $value['due_date'] = date('d-m-Y');
178 5
        settype($input['description'], 'string');
179 5
        $value['description'] = $input['description'];
180 5
        settype($input['internal_note'], 'string');
181 5
        $value['internal_note'] = $input['internal_note'];
182 5
        settype($input['message'], 'string');
183 5
        $value['message'] = $input['message'];
184
185 5 View Code Duplication
        if (isset($input['customer_coupon']) && $input['customer_coupon'] != '') {
186
            if ($value['message'] != '') {
187
                $value['message'] .= "\n\n";
188
            }
189
            $value['message'] .= "Kundekupon:". $input['customer_coupon'];
190
        }
191
192 5 View Code Duplication
        if (isset($input['customer_comment']) && $input['customer_comment'] != '') {
193
            if ($value['message'] != '') {
194
                $value['message'] .= "\n\n";
195
            }
196
            $value['message'] .= "Kommentar:\n". $input['customer_comment'];
197
        }
198
199
200 5
        $this->order = new Debtor($this->kernel, 'order');
201 5
        $order_id = $this->order->update($value, 'webshop');
202
203 5
        if ($order_id == 0) {
204
            $this->error->merge($this->order->error->message);
205
            return false;
206
        }
207
208 5
        return $order_id;
209
    }
210
211
    /**
212
     * Places the order and utilizes basket
213
     *
214
     * @param array $input    Array with customer data
215
     * @param array $products Array with products
216
     *
217
     * @return integer Order id
218
     */
219 1 View Code Duplication
    public function placeManualOrder($input = array(), $products = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
    {
221 1
        $order_id = $this->createOrder($input);
222 1
        if ($order_id == 0) {
223
            $this->error->set('unable to create the order');
224
            return false;
225
        }
226
227 1
        if (!$this->addOrderLines($products)) {
228
            $this->error->set('unable add products to the order');
229
            return false;
230
        }
231
232 1
        if (!$this->sendEmail($order_id)) {
0 ignored issues
show
Security Bug introduced by
It seems like $order_id defined by $this->createOrder($input) on line 221 can also be of type false; however, Webshop::sendEmail() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
233
            $this->error->set('unable to send email to the customer');
234
            return false;
235
        }
236
237 1
        return $order_id;
238
    }
239
240
    /**
241
     * Places the order and utilizes basket
242
     *
243
     * @param array $input Array with customer data
244
     *
245
     * @return integer Order id
246
     */
247 4
    public function placeOrder($input)
248
    {
249 4
        if (!$order_id = $this->createOrder($input)) {
250
            $this->error->set('unable to create the order');
251
            return false;
252
        }
253
254 4
        $products = $this->basket->getItems();
255
256 4
        if (!$this->addOrderLines($products)) {
257
            $this->error->set('unable add products to the order');
258
            return false;
259
        }
260
261 4
        $this->basket->reset();
262
263 4
        if (!$this->sendEmail($order_id)) {
264
            $this->error->set('unable to send email to the customer');
265
            return false;
266
        }
267
268 4
        return $order_id;
269
    }
270
271
    /**
272
     * Adds the order lines
273
     *
274
     * @param array $products
275
     *
276
     * @return boolean
277
     */
278 5
    private function addOrderLines($products = array())
279
    {
280 5
        foreach ($products as $product) {
281
            $this->order->loadItem();
282
            $value['product_id'] = $product['id'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$value was never initialized. Although not strictly required by PHP, it is generally a good practice to add $value = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
283
            $value['quantity'] = $product['quantity'];
284
            $value['description'] = $product['text'];
285
            $this->order->item->save($value);
286 5
        }
287
288 5
        return true;
289
    }
290
291
    /**
292
     * Sends the email
293
     *
294
     * @param integer $order_id
295
     *
296
     * @return boolean
297
     */
298 5
    private function sendEmail($order_id)
299
    {
300 5
        $this->kernel->useShared('email');
301 5
        $email = new Email($this->kernel);
302
303 5
        if (!$email->save(array('contact_id' => $this->contact->get('id'),
0 ignored issues
show
Documentation introduced by
array('contact_id' => $t...elong_to' => $order_id) is of type array<string,?,{"contact..."belong_to":"integer"}>, but the function expects a object<Struct>.

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...
304 5
                                'subject' => 'Bekræftelse på bestilling #' . $order_id,
305 5
                                'body' => $this->kernel->setting->get('intranet', 'webshop.confirmation_text') . "\n" . $this->contact->getLoginUrl() . "\n\nVenlig hilsen\n" . $this->kernel->intranet->address->get('name'),
306 5
                                'from_email' => $this->kernel->intranet->address->get('email'),
307 5
                                'from_name' => $this->kernel->intranet->address->get('name'),
308 5
                                'type_id' => 12, // webshop
309 5
                                'belong_to' => $order_id))) {
310
            $this->error->merge($email->error->message);
0 ignored issues
show
Bug introduced by
The property message cannot be accessed from this context as it is declared private in class Ilib_Error.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
311
            return false;
312
        }
313
314 5
        if (!$email->queue()) {
315
            $this->error->merge($email->error->message);
0 ignored issues
show
Bug introduced by
The property message cannot be accessed from this context as it is declared private in class Ilib_Error.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
316
            return false;
317
        }
318
319 5
        return true;
320
    }
321
322
    /**
323
     * Places the order and utilizes basket
324
     *
325
     * @param integer $order_id
326
     * @param integer $transaction_number
327
     * @param integer $transaction_status
328
     * @param float   $transaction_amount
0 ignored issues
show
Documentation introduced by
There is no parameter named $transaction_amount. Did you maybe mean $amount?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
329
     *
330
     * @return boolean
331
     */
332 1
    public function addOnlinePayment($order_id, $transaction_number, $transaction_status, $amount)
333
    {
334 1
        if ($order_id == 0) {
335
            return 0;
336
        }
337
338 1
        if (!$this->kernel->intranet->hasModuleAccess('onlinepayment')) {
339
            return 0;
340
        }
341
342
        // hvad skal den her sikre?
343 1
        if (is_object($this->kernel->user) and $this->kernel->user->hasModuleAccess('onlinepayment')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
344 1
            return 0;
345
        }
346
347
        $this->kernel->useModule('onlinepayment');
348
        $onlinepayment = new OnlinePayment($this->kernel);
349
350
        $values = array('belong_to'          => 'order',
351
                        'belong_to_id'       => (int)$order_id,
352
                        'transaction_number' => $transaction_number,
353
                        'transaction_status' => $transaction_status,
354
                        'amount'             => $amount);
355
356
        if ($payment_id = $onlinepayment->save($values)) {
357
            return $payment_id;
358
        } else {
359
            $onlinepayment->error->view();
360
            return 0;
361
        }
362
    }
363
364
    /**
365
     * Returns receipt text
366
     *
367
     * @todo why return an array
368
     *
369
     * @return array with receipt
370
     */
371
    public function getReceiptText()
372
    {
373
        return array('receipt_text' => $this->kernel->setting->get('intranet', 'webshop.webshop_receipt'));
374
    }
375
376
    /**
377
     * Sets the order as sent
378
     *
379
     * @param integer $order_id
380
     *
381
     * @return boolean
382
     */
383 View Code Duplication
    public function setSent($order_id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
    {
385
        $order_id = (int)$order_id;
386
        if ($order_id == 0) {
387
            return 0;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 0; (integer) is incompatible with the return type documented by Webshop::setSent of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
388
        }
389
        $debtor = Debtor::factory($this->kernel, (int)$order_id);
390
        return $debtor->setSent();
391
    }
392
}
393