PickUpOrDeliveryModifier::LiveCalculatedTotal()   F
last analyzed

Complexity

Conditions 51
Paths 1770

Size

Total Lines 179
Code Lines 107

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 179
rs 2
c 0
b 0
f 0
cc 51
eloc 107
nc 1770
nop 0

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
/**
4
 * @author Nicolaas [at] sunnysideup.co.nz
5
 * @package: ecommerce
6
 * @sub-package: ecommerce_delivery
7
 * @description: Shipping calculation scheme based on SimpleShippingModifier.
8
 * It lets you set fixed shipping costs, or a fixed
9
 * cost for each region you're delivering to.
10
 */
11
class PickUpOrDeliveryModifier extends OrderModifier
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
12
{
13
14
// ######################################## *** model defining static variables (e.g. $db, $has_one)
15
16
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
17
        "TotalWeight" => "Double",
18
        "RegionAndCountry" => "Varchar",
19
        "SerializedCalculationObject" => "Text",
20
        "DebugString" => "HTMLText",
21
        "SubTotalAmount" => "Currency"
22
    );
23
24
    private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
25
        "Option" => "PickUpOrDeliveryModifierOptions"
26
    );
27
28
    private static $singular_name = "Pickup / Delivery Charge";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $singular_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
29
    public function i18n_singular_name()
30
    {
31
        return _t("PickUpOrDeliveryModifier.DELIVERYCHARGE", "Delivery / Pick-up");
32
    }
33
34
    private static $plural_name = "Pickup / Delivery Charges";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $plural_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
35
    public function i18n_plural_name()
36
    {
37
        return _t("PickUpOrDeliveryModifier.DELIVERYCHARGES", "Delivery / Pick-up");
38
    }
39
40
    private static $include_form_in_order_table = true;
0 ignored issues
show
Unused Code introduced by
The property $include_form_in_order_table is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
41
42
43
    // ######################################## *** cms variables + functions (e.g. getCMSFields, $searchableFields)
44
45
    public function getCMSFields()
46
    {
47
        $fields = parent::getCMSFields();
48
        //debug fields
49
        $fields->removeByName("TotalWeight");
50
        $fields->addFieldToTab("Root.Debug", new ReadonlyField("TotalWeightShown", "total weight used for calculation", $this->TotalWeight));
0 ignored issues
show
Documentation introduced by
The property TotalWeight does not exist on object<PickUpOrDeliveryModifier>. 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...
51
        $fields->removeByName("SubTotalAmount");
52
        $fields->addFieldToTab("Root.Debug", new ReadonlyField("SubTotalAmountShown", "sub-total amount used for calculation", $this->SubTotalAmount));
0 ignored issues
show
Documentation introduced by
The property SubTotalAmount does not exist on object<PickUpOrDeliveryModifier>. 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...
53
        $fields->removeByName("SerializedCalculationObject");
54
        $fields->addFieldToTab("Root.Debug", new ReadonlyField("SerializedCalculationObjectShown", "debug data", unserialize($this->SerializedCalculationObject)));
0 ignored issues
show
Documentation introduced by
The property SerializedCalculationObject does not exist on object<PickUpOrDeliveryModifier>. 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...
55
        $fields->removeByName("DebugString");
56
        $fields->addFieldToTab("Root.Debug", new ReadonlyField("DebugStringShown", "steps taken", $this->DebugString));
0 ignored issues
show
Documentation introduced by
The property DebugString does not exist on object<PickUpOrDeliveryModifier>. 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...
57
        return $fields;
58
    }
59
60
61
    // ######################################## *** other (non) static variables (e.g. private static $special_name_for_something, protected $order)
62
63
    /**
64
     *@var String $weight_field - the field used in the Buyable to work out the weight.
65
     *
66
     */
67
    private static $weight_field = 'Weight';
0 ignored issues
show
Unused Code introduced by
The property $weight_field is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
68
69
    /**
70
     * @var Float $total_weight
71
     * the total amount of weight for the order
72
     * saved here for speed's sake
73
     */
74
    private static $_total_weight = null;
75
76
    /**
77
     * @var DataList
78
     */
79
    private static $available_options = null;
80
81
    /**
82
     * @var PickUpOrDeliveryModifierOptions
83
     * The most applicable option
84
     */
85
    private static $selected_option = null;
86
87
    /**
88
     * @var Double
89
     * the total amount charged in the end.
90
     * saved here for speed's sake
91
     */
92
    private static $_actual_charges = 0;
93
94
    /**
95
     * @var Boolean
96
     * the total amount charged in the end
97
     * saved here for speed's sake
98
     */
99
    private static $calculations_done = false;
100
101
    /**
102
     * @var String
103
     * Debugging tool
104
     */
105
    protected $debugMessage = "";
106
107
    // ######################################## *** CRUD functions (e.g. canEdit)
108
109
    // ######################################## *** init and update functions
110
111
    /**
112
     * set the selected option (selected by user using form)
113
     * @param Int $optionID
114
     */
115
    public function setOption($optionID)
116
    {
117
        $optionID = intval($optionID);
118
        $this->OptionID = $optionID;
0 ignored issues
show
Documentation introduced by
The property OptionID does not exist on object<PickUpOrDeliveryModifier>. 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...
119
        $this->write();
120
    }
121
122
    /**
123
     * updates database fields
124
     * @param Bool $force - run it, even if it has run already
125
     * @return void
126
     */
127
    public function runUpdate($force = true)
128
    {
129
        $this->debugMessage = "";
130
        self::$calculations_done = false;
131
        self::$selected_option = null;
132
        self::$available_options = null;
133
        $this->checkField("OptionID");
134
        $this->checkField("SerializedCalculationObject");
135
        $this->checkField("TotalWeight");
136
        $this->checkField("SubTotalAmount");
137
        $this->checkField("RegionAndCountry");
138
        $this->checkField("CalculatedTotal");
139
        $this->checkField("DebugString");
140
        parent::runUpdate($force);
141
    }
142
143
    // ######################################## *** form functions (e. g. Showform and getform)
144
145
146
147
    /**
148
     * standard Modifier Method
149
     * @return Boolean
150
     */
151
    public function ShowForm()
152
    {
153
        if ($this->ShowInTable()) {
154
            if ($this->Order()->Items()) {
155
                if ($options = $this->liveOptions()) {
156
                    return $options->count() > 1;
157
                }
158
            }
159
        }
160
        return false;
161
    }
162
163
    /**
164
     * Should the form be included in the editable form
165
     * on the checkout page?
166
     * @return Boolean
167
     */
168
    public function ShowFormInEditableOrderTable()
169
    {
170
        return ($this->ShowForm() && $this->Config()->get("include_form_in_order_table")) ? true : false;
171
    }
172
173
    /**
174
     *
175
     * @return Form
176
     */
177
    public function getModifierForm(Controller $optionalController = null, Validator $optionalValidator = null)
178
    {
179
        Requirements::themedCSS("PickUpOrDeliveryModifier", "ecommerce_delivery");
180
        Requirements::javascript(THIRDPARTY_DIR."/jquery/jquery.js");
181
        //Requirements::block(THIRDPARTY_DIR."/jquery/jquery.js");
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
182
        //Requirements::javascript(Director::protocol()."ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js");
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183
        Requirements::javascript(THIRDPARTY_DIR."/jquery-form/jquery.form.js");
184
        Requirements::javascript("ecommerce_delivery/javascript/PickUpOrDeliveryModifier.js");
185
        $array = PickUpOrDeliveryModifierOptions::get_all_as_country_array();
186
        if ($array && is_array($array) && count($array)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array 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...
187
            $js = "\n".'var PickUpOrDeliveryModifierOptions = []';
188
            $count = 0;
189
            foreach ($array as $key => $option) {
190
                if ($option && is_array($option) && count($option)) {
191
                    if ($count == 0) {
192
                        $js .= "\n".'    PickUpOrDeliveryModifierOptions["'.$key.'"] = new Array("'.implode('","', $option).'")';
193
                    } else {
194
                        $js .= "\n".'    PickUpOrDeliveryModifierOptions["'.$key.'"] = new Array("'.implode('","', $option).'")';
195
                    }
196
                    $count++;
197
                }
198
            }
199
            if ($js) {
200
                //add final semi-comma
201
                $js .= "";
202
                Requirements::customScript($js, "PickupOrDeliveryModifier");
203
            }
204
        }
205
        $fields = new FieldList();
206
        $fields->push($this->headingField());
207
        $fields->push($this->descriptionField());
208
        $options = $this->liveOptions()->map('ID', 'Name');//$this->getOptionListForDropDown();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
209
        $optionID = $this->LiveOptionID();
210
        $fields->push(OptionSetField::create('PickupOrDeliveryType', 'Preference', $options, $optionID));
211
        $actions = new FieldList(
212
            new FormAction('processOrderModifier', 'Update Pickup / Delivery Option')
213
        );
214
        return new PickUpOrDeliveryModifier_Form($optionalController, 'PickUpOrDeliveryModifier', $fields, $actions, $optionalValidator);
215
    }
216
217
    // ######################################## *** template functions (e.g. ShowInTable, TableTitle, etc...) ... USES DB VALUES
218
219
    /**
220
     * @return Boolean
221
     */
222
    public function ShowInTable()
223
    {
224
        return true;
225
    }
226
227
    /**
228
     * @return Boolean
229
     */
230
    public function CanBeRemoved()
231
    {
232
        return false;
233
    }
234
235
    /**
236
     * NOTE: the function below is  HACK and needs fixing proper.
237
     *
238
     */
239
    public function CartValue()
240
    {
241
        return $this->getCartValue();
242
    }
243
    public function getCartValue()
244
    {
245
        return $this->LiveCalculatedTotal();
246
    }
247
248
    // ######################################## ***  inner calculations.... USES CALCULATED VALUES
249
250
    /**
251
     * returns the current selected option as object
252
     * @return PickUpOrDeliveryModifierOptions;
0 ignored issues
show
Documentation introduced by
The doc-type PickUpOrDeliveryModifierOptions; could not be parsed: Expected "|" or "end of type", but got ";" at position 31. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
253
     */
254
    protected function liveOptionObject()
255
    {
256
        return PickUpOrDeliveryModifierOptions::get()->byID($this->LiveOptionID());
257
    }
258
259
    /**
260
     * works out if Weight is applicable at all
261
     * @return Boolean
262
     */
263
    protected function useWeight()
264
    {
265
        return EcommerceDBConfig::current_ecommerce_db_config()->ProductsHaveWeight;
266
    }
267
268
    /**
269
     * Returns the available delivery options based on the current country and region
270
     * for the order.
271
     * Must always return something!
272
     * @return DataList
273
     */
274
    protected function liveOptions()
275
    {
276
        if (!self::$available_options) {
277
            $countryID = EcommerceCountry::get_country_id();
278
            $regionID = EcommerceRegion::get_region_id();
279
            $weight = $this->LiveTotalWeight();
0 ignored issues
show
Unused Code introduced by
$weight is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
280
            $options = PickUpOrDeliveryModifierOptions::get();
281
            if ($options->count()) {
282
                foreach ($options as $option) {
283
                    //check countries
284
                    if ($countryID) {
285
                        $availableInCountriesList = $option->AvailableInCountries();
286
                        //exclude if not found in country list
287
                        if ($availableInCountriesList->Count() > 0 && ! $availableInCountriesList->find('ID', $countryID)) {
288
                            continue;
289
                        }
290
                        //exclude if in exclusion list
291
                        $excludedFromCountryList = $option->ExcludeFromCountries();
292
                        if ($excludedFromCountryList->Count() > 0 && $excludedFromCountryList->find('ID', $countryID)) {
293
                            continue;
294
                        }
295
                    }
296
                    //check regions
297
                    if ($regionID) {
298
                        $optionRegions = $option->AvailableInRegions();
299
                        //exclude if not found in region list
300
                        if ($optionRegions->Count() > 0 && ! $optionRegions->find('ID', $regionID)) {
301
                            continue;
302
                        }
303
                    }
304
                    $result[] = $option;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = 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...
305
                }
306
            }
307
            if (! isset($result)) {
308
                $result[] = PickUpOrDeliveryModifierOptions::default_object();
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
309
            }
310
            self::$available_options = new ArrayList($result);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ArrayList($result) of type object<ArrayList> is incompatible with the declared type object<DataList> of property $available_options.

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...
311
        }
312
        return self::$available_options;
313
    }
314
315
316
    // ######################################## *** calculate database fields: protected function Live[field name]  ... USES CALCULATED VALUES
317
318
    /**
319
     * Precondition : There are always options available.
320
     * @return Int
321
     */
322
    protected function LiveOptionID()
323
    {
324
        if (!self::$selected_option) {
325
            $options = $this->liveOptions();
326
            if (self::$selected_option = $options->find('ID', $this->OptionID)) {
0 ignored issues
show
Documentation introduced by
The property OptionID does not exist on object<PickUpOrDeliveryModifier>. 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...
Documentation Bug introduced by
It seems like $options->find('ID', $this->OptionID) can also be of type object<DataObject>. However, the property $selected_option is declared as type object<PickUpOrDeliveryModifierOptions>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
327
                //do nothing;
328
            } else {
329
                self::$selected_option = $options->find('IsDefault', 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to self::$selected_option is correct as $options->find('IsDefault', 1) (which targets DataList::find()) 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...
330
                if (! self::$selected_option) {
331
                    self::$selected_option = $options->First();
0 ignored issues
show
Documentation Bug introduced by
It seems like $options->First() can also be of type object<DataObject>. However, the property $selected_option is declared as type object<PickUpOrDeliveryModifierOptions>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
332
                }
333
            }
334
        }
335
        return self::$selected_option->ID;
336
    }
337
338
    /**
339
     * @return String
340
     */
341
    protected function LiveName()
342
    {
343
        $obj = $this->liveOptionObject();
344
        if (is_object($obj)) {
345
            $v = $obj->Name;
346
            if ($obj->ExplanationPageID) {
347
                $page = $obj->ExplanationPage();
348
                if ($page) {
349
                    $v .= '<div id="PickUpOrDeliveryModifierExplanationLink"><a href="'.$page->Link().'" class="externalLink">'.convert::raw2sql($page->Title).'</a></div>';
350
                }
351
            }
352
            return $v;
353
        }
354
        return _t("PickUpOrDeliveryModifier.POSTAGEANDHANDLING", "Postage and Handling");
355
    }
356
357
    /**
358
     * cached in Order, no need to cache here.
359
     * @return Double
360
     */
361
    protected function LiveSubTotalAmount()
362
    {
363
        $order = $this->Order();
364
        return $order->SubTotal();
365
    }
366
367
    /**
368
     * description of region and country being shipped to.
369
     * @return PickUpOrDeliveryModifierOptions | NULL
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

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...
370
     */
371
    protected function LiveSerializedCalculationObject()
372
    {
373
        $obj = $this->liveOptionObject();
374
        if ($obj) {
375
            return serialize($obj);
376
        }
377
    }
378
379
    /**
380
     * description of region and country being shipped to.
381
     * @return String
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

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...
382
     */
383
    protected function LiveRegionAndCountry()
384
    {
385
        $details = array();
386
        $option = $this->Option();
0 ignored issues
show
Bug introduced by
The method Option() does not exist on PickUpOrDeliveryModifier. Did you maybe mean setOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
387
        if ($option) {
388
            $regionID = EcommerceRegion::get_region_id();
389
            if ($regionID) {
390
                $region = EcommerceRegion::get()->byID($regionID);
391
                if ($region) {
392
                    $details[] = $region->Name;
393
                }
394
            }
395
            $countryID = EcommerceCountry::get_country_id();
396
            if ($countryID) {
397
                $country = EcommerceCountry::get()->byID($countryID);
398
                if ($country) {
399
                    $details[] = $country->Name;
400
                }
401
            }
402
        } else {
403
            return _t("PickUpOrDeliveryModifier.NOTSELECTED", "No delivery option has been selected");
404
        }
405
        if (count($details)) {
406
            return implode(", ", $details);
407
        }
408
    }
409
410
    /**
411
    * @return Double
412
    **/
413
    protected function LiveCalculatedTotal()
414
    {
415
        //________________ start caching mechanism
416
        if (self::$calculations_done) {
417
            return self::$_actual_charges;
418
        }
419
        self::$calculations_done = true;
420
        //________________ end caching mechanism
421
422
        self::$_actual_charges = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $_actual_charges was declared of type double, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
423
        //do we have enough information
424
        $obj = $this->liveOptionObject();
425
        $items = $this->Order()->Items();
426
        if (is_object($obj) && $obj->exists() && $items && $items->count()) {
427
428
429
            //are ALL products excluded?
430
            if ($obj->ExcludedProducts() && $obj->ExcludedProducts()->count()) {
431
                $hasIncludedProduct = false;
432
                $excludedProductIDArray = $obj->ExcludedProducts()->column('ID');
433
                //are all the products excluded?
434
                foreach ($items as $orderItem) {
435
                    $product = $orderItem->Product();
436
                    if ($product) {
437
                        if (in_array($product->ID, $excludedProductIDArray)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
438
                            //do nothing
439
                        } else {
440
                            $hasIncludedProduct = true;
441
                            break;
442
                        }
443
                    }
444
                }
445
                if ($hasIncludedProduct === false) {
446
                    $this->debugMessage .= "<hr />all products are excluded from delivery charges";
447
                    return self::$_actual_charges;
448
                }
449
            }
450
451
            $this->debugMessage .= "<hr />option selected: ".$obj->Title.", and items present";
452
            //lets check sub-total
453
            $subTotalAmount = $this->LiveSubTotalAmount();
454
            $this->debugMessage .= "<hr />sub total amount is: \$". $subTotalAmount;
455
            // no need to charge, order is big enough
456
            $minForZeroRate = floatval($obj->MinimumOrderAmountForZeroRate);
457
            $maxForZeroRate = floatval($obj->FreeShippingUpToThisOrderAmount);
458
459
            $weigth = $weight = $this->LiveTotalWeight();
0 ignored issues
show
Unused Code introduced by
$weigth is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
460
            $weightBrackets = $obj->WeightBrackets();
461
            $subTotalBrackets = $obj->SubTotalBrackets();
462
463
            // zero becauase over minForZeroRate
464
            if ($minForZeroRate > 0 && $minForZeroRate < $subTotalAmount) {
465
                self::$_actual_charges =  0;
466
                $this->debugMessage .= "<hr />Minimum Order Amount For Zero Rate: ".$obj->MinimumOrderAmountForZeroRate." is lower than amount  ordered: ".self::$_actual_charges;
467
            }
468
469
            //zero because below maxForZeroRate
470
            elseif ($maxForZeroRate > 0 && $maxForZeroRate > $subTotalAmount) {
471
                self::$_actual_charges =  0;
472
                $this->debugMessage .= "<hr />Maximum Order Amount For Zero Rate: ".$obj->FreeShippingUpToThisOrderAmount." is higher than amount ordered: ".self::$_actual_charges;
473
            } else {
474
                //examine weight brackets
475
                if ($weight && $weightBrackets->count()) {
476
                    $this->debugMessage .= "<hr />there is weight: {$weight}gr.";
477
                    //weight brackets
478
                    $foundWeightBracket = null;
479
                    $weightBracketQuantity = 1;
480
                    $additionalWeightBracket = null;
481
                    $minimumMinimum = null;
482
                    $maximumMaximum = null;
483
                    foreach ($weightBrackets as $weightBracket) {
484
                        if ((! $foundWeightBracket) && ($weightBracket->MinimumWeight <= $weight) && ($weight <= $weightBracket->MaximumWeight)) {
485
                            $foundWeightBracket = $weightBracket;
486
                        }
487
                        //look for absolute min and max
488
                        if ($minimumMinimum === null || ($weightBracket->MinimumWeight > $minimumMinimum->MinimumWeight)) {
489
                            $minimumMinimum = $weightBracket;
490
                        }
491
                        if ($maximumMaximum === null || ($weightBracket->MaximumWeight > $maximumMaximum->MaximumWeight)) {
492
                            $maximumMaximum = $weightBracket;
493
                        }
494
                    }
495
                    if (! $foundWeightBracket) {
496
                        if ($weight < $minimumMinimum->MinimumWeight) {
497
                            $foundWeightBracket = $minimumMinimum;
498
                        } elseif ($weight > $maximumMaximum->MaximumWeight) {
499
                            $foundWeightBracket = $maximumMaximum;
500
                            $weightBracketQuantity = floor($weight / $maximumMaximum->MaximumWeight);
501
                            $restWeight = $weight - ($maximumMaximum->MaximumWeight * $weightBracketQuantity);
502
                            $additionalWeightBracket = null;
503
                            foreach ($weightBrackets as $weightBracket) {
504
                                if (($weightBracket->MinimumWeight <= $restWeight) && ($restWeight <= $weightBracket->MaximumWeight)) {
505
                                    $additionalWeightBracket = $weightBracket;
506
                                    break;
507
                                }
508
                            }
509
                        }
510
                    }
511
                    //we found some applicable weight brackets
512
                    if ($foundWeightBracket) {
513
                        self::$_actual_charges += $foundWeightBracket->FixedCost * $weightBracketQuantity;
514
                        $this->debugMessage .= "<hr />found Weight Bracket (from {$foundWeightBracket->MinimumWeight}gr. to {$foundWeightBracket->MaximumWeight}gr.): \${$foundWeightBracket->FixedCost} ({$foundWeightBracket->Name}) from  times $weightBracketQuantity";
515
                        if ($additionalWeightBracket) {
516
                            self::$_actual_charges += $additionalWeightBracket->FixedCost;
517
                            $this->debugMessage .= "<hr />+ additional Weight Bracket (from {$additionalWeightBracket->MinimumWeight}gr. to {$additionalWeightBracket->MaximumWeight}gr.): \${$additionalWeightBracket->FixedCost} ({$foundWeightBracket->Name})";
518
                        }
519
                    }
520
                }
521
522
523
524
                // weight based on multiplier ...
525
                elseif ($weight && $obj->WeightMultiplier) {
526
                    // add weight based shipping
527
                    if (!$obj->WeightUnit) {
528
                        $obj->WeightUnit = 1;
529
                    }
530
                    $this->debugMessage .= "<hr />actual weight:".$weight." multiplier = ".$obj->WeightMultiplier." weight unit = ".$obj->WeightUnit." ";
531
                    //legacy fix
532
                    $units = ceil($weight / $obj->WeightUnit);
533
                    $weightCharge =  $units * $obj->WeightMultiplier;
534
                    self::$_actual_charges += $weightCharge;
535
                    $this->debugMessage .= "<hr />weight charge: ".$weightCharge;
536
                }
537
538
539
                //examine price brackets
540
                elseif ($subTotalAmount && $subTotalBrackets->count()) {
541
                    $this->debugMessage .= "<hr />there is subTotal: {$subTotalAmount} and subtotal brackets.";
542
                    //subTotal brackets
543
                    $foundSubTotalBracket = null;
544
                    foreach ($subTotalBrackets as $subTotalBracket) {
545
                        if ((! $foundSubTotalBracket) && ($subTotalBracket->MinimumSubTotal <= $subTotalAmount) && ($subTotalAmount <= $subTotalBracket->MaximumSubTotal)) {
546
                            $foundSubTotalBracket = $subTotalBracket;
547
                            break;
548
                        }
549
                    }
550
                    //we found some applicable subTotal brackets
551
                    if ($foundSubTotalBracket) {
552
                        self::$_actual_charges += $foundSubTotalBracket->FixedCost;
553
                        $this->debugMessage .= "<hr />found SubTotal Bracket (between {$foundSubTotalBracket->MinimumSubTotal} and {$foundSubTotalBracket->MaximumSubTotal}): \${$foundSubTotalBracket->FixedCost} ({$foundSubTotalBracket->Name}) ";
554
                    }
555
                }
556
557
                // add percentage
558
                if ($obj->Percentage) {
559
                    $percentageCharge = $subTotalAmount * $obj->Percentage;
560
                    self::$_actual_charges += $percentageCharge;
561
                    $this->debugMessage .= "<hr />percentage charge: \$".$percentageCharge;
562
                }
563
564
                // add fixed price
565
                if ($obj->FixedCost <> 0) {
566
                    self::$_actual_charges += $obj->FixedCost;
567
                    $this->debugMessage .= "<hr />fixed charge: \$". $obj->FixedCost;
568
                }
569
            }
570
            //is it enough?
571
            if (self::$_actual_charges < $obj->MinimumDeliveryCharge && $obj->MinimumDeliveryCharge > 0) {
572
                $oldActualCharge = self::$_actual_charges;
573
                self::$_actual_charges = $obj->MinimumDeliveryCharge;
574
                $this->debugMessage .= "<hr />too little: actual charge: ".$oldActualCharge.", minimum delivery charge: ".$obj->MinimumDeliveryCharge;
575
            }
576
            // is it too much
577
            if (self::$_actual_charges > $obj->MaximumDeliveryCharge  && $obj->MaximumDeliveryCharge > 0) {
578
                self::$_actual_charges = $obj->MaximumDeliveryCharge;
579
                $this->debugMessage .= "<hr />too much: ".self::$_actual_charges.", maximum delivery charge is ".$obj->MaximumDeliveryCharge;
580
            }
581
        } else {
582
            if (!$items) {
583
                $this->debugMessage .= "<hr />no items present";
584
            } else {
585
                $this->debugMessage .= "<hr />no delivery option available";
586
            }
587
        }
588
        $this->debugMessage .= "<hr />final score: \$".self::$_actual_charges;
589
        //special case, we are using weight and there is no weight!
590
        return self::$_actual_charges;
591
    }
592
593
    /**
594
     *
595
     *
596
     * @return Double
0 ignored issues
show
Documentation introduced by
Should the return type not be double|integer?

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...
597
     */
598
    protected function LiveTotalWeight()
599
    {
600
        if (self::$_total_weight === null) {
601
            self::$_total_weight = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $_total_weight was declared of type double, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
602
            if ($this->useWeight()) {
603
                if ($fieldName = Config::inst()->get('PickUpOrDeliveryModifier', 'weight_field')) {
604
                    $items = $this->Order()->Items();
605
                    //get index numbers for bonus products - this can only be done now once they have actually been added
606
                    if ($items && $items->count()) {
607
                        foreach ($items as $itemIndex => $item) {
608
                            $buyable = $item->Buyable();
609
                            if ($buyable) {
610
                                // Calculate the total weight of the order
611
                                if (! empty($buyable->$fieldName) && $item->Quantity) {
612
                                    self::$_total_weight += $buyable->$fieldName * $item->Quantity;
613
                                }
614
                            }
615
                        }
616
                    }
617
                }
618
            }
619
        }
620
        return self::$_total_weight;
621
    }
622
623
    /**
624
     * returns an explanation of cost.
625
     * @return String
626
     */
627
    protected function LiveDebugString()
628
    {
629
        return $this->debugMessage;
630
    }
631
632
633
    // ######################################## *** Type Functions (IsChargeable, IsDeductable, IsNoChange, IsRemoved)
634
635
    public function IsChargeable()
636
    {
637
        return true;
638
    }
639
640
    // ######################################## *** standard database related functions (e.g. onBeforeWrite, onAfterWrite, etc...)
641
642
    public function requireDefaultRecords()
643
    {
644
        parent::requireDefaultRecords();
645
        // we must check for individual database types here because each deals with schema in a none standard way
646
        $modifiers = PickUpOrDeliveryModifier::get()->filter(array("OptionID" => 0));
647
        if ($modifiers->count()) {
648
            DB::alteration_message("You need to upgrade PickUpOrDeliveryModifier <a href=\"/dev/tasks/EcommerceTaskUpgradePickUpOrDeliveryModifier\">do it now!</a>", "deleted");
649
        }
650
    }
651
    // ######################################## *** AJAX related functions
652
    /**
653
     *
654
     * @param Array $js javascript array
655
     * @return Array for AJAX JSON
656
     **/
657
    public function updateForAjax(array $js)
658
    {
659
        $js = parent::updateForAjax($js);
660
        $jsonOptions = array();
661
        $liveOptions = $this->LiveOptions();
662
        if ($liveOptions && $liveOptions->count()) {
663
            $optionsArray = $liveOptions->map('ID', 'Name');
664
            if ($optionsArray && !is_array($optionsArray)) {
665
                $optionsArray = $optionsArray->toArray();
666
            }
667
            if ($optionsArray && count($optionsArray)) {
668
                foreach ($optionsArray as $id => $name) {
669
                    $jsonOptions[] = array('id' => $id, 'name' => $name);
670
                }
671
            }
672
        }
673
        $js[] = array(
674
            't' => 'dropdown',
675
            's' => 'PickupOrDeliveryType',
676
            'p' => $this->LiveOptionID(),
677
            'v' => $jsonOptions
678
        );
679
        return $js;
680
    }
681
682
    // ######################################## *** debug functions
683
}
684