EcommerceTaskCSVToVariations   F
last analyzed

Complexity

Total Complexity 79

Size/Duplication

Total Lines 559
Duplicated Lines 2.5 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 79
lcom 1
cbo 8
dl 14
loc 559
rs 2.08
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getDescription() 0 9 2
B run() 0 20 6
A addMoreProduct() 0 4 1
A addMoreProductForProductWithoutVariations() 0 4 1
A addMoreAttributeType() 0 4 1
A addMoreToAttributeValue() 0 4 1
A addMoreToVariation() 0 4 1
A reset() 0 4 1
B readFile() 0 42 8
B createProducts() 7 53 10
B findVariations() 7 36 8
A showData() 0 25 5
F createVariations() 0 128 19
B getExtraDataForVariations() 0 34 7
A addFieldToObject() 0 18 5
A alterationMessage() 0 12 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EcommerceTaskCSVToVariations often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EcommerceTaskCSVToVariations, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
4
/**
5
 * allows the creation of variations from a CSV
6
 * CSV will have the following fields:
7
 * ProductTitle,
8
 * Size,
9
 * Colour,
10
 * Price
11
 * If you like to add more fields, then it is recommended that you extend this BuildTask
12
 * to your own BuildTask.
13
 *
14
 */
15
16
class EcommerceTaskCSVToVariations extends BuildTask
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...
17
{
18
    protected $forreal = false;
19
20
    protected $title = "Create variations from a Spreadsheets (comma separated file CSV)";
21
22
    protected $description = "
23
        Does not delete any record, it only updates and adds.
24
        The minimum recommend columns are: ProductTitle (or ProductInternalItemID), Size, Colour, Price, InternalItemID.
25
        You can add ?forreal=1 to the URL to run the task for real.";
26
27
    /**
28
     * excluding base folder
29
     *
30
     * e.g. assets/files/mycsv.csv
31
     * @var String
32
     */
33
    private static $file_location = "";
0 ignored issues
show
Unused Code introduced by
The property $file_location 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...
34
35
    /**
36
     * Cell entry for a price that is not available
37
     * @var String
38
     */
39
    private static $no_price_available = "POA";
0 ignored issues
show
Unused Code introduced by
The property $no_price_available 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...
40
41
    /**
42
     * @var Array
43
     */
44
    private static $attribute_type_field_names = array(
0 ignored issues
show
Unused Code introduced by
The property $attribute_type_field_names 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...
45
        "Size",
46
        "Colour"
47
    );
48
49
    /**
50
     * Is the CSV separated by , or ; or [tab]?
51
     */
52
    protected $csvSeparator = ",";
53
54
55
    /**
56
     * @var Boolean
57
     */
58
    protected $debug = true;
59
60
61
    /**
62
     * the original data from the CVS
63
     * @var Array
64
     */
65
    protected $csv = array();
66
67
    /**
68
     * Structure will be as follows:
69
     *
70
     *     ProductID => array(
71
     *         "Product" => $product,
72
     *         "VariationRows" => array(
73
     *             [1] => array(
74
     *                 "Data" => array(),
75
     *                 "Variation" => $variation
76
     *             )
77
     *         )
78
     *     ),
79
     *     ProductID => array(
80
     *         "Product" => $product,
81
     *         "VariationRows" => array(
82
     *             [1] => array(
83
     *                 "Data" => array(),
84
     *                 "Variation" => $variation
85
     *             ),
86
     *             [2] => array(
87
     *                 "Data" => array(),
88
     *                 "Variation" => $variation
89
     *             )
90
     *         )
91
     *     )
92
     *
93
     * @var Array
94
     */
95
    protected $data = array();
96
97
    /**
98
     * list of products without variations
99
     * @return Array
100
     */
101
    protected $soleProduct = array();
102
103
    /**
104
     * The default page of where the products are added.
105
     * @var Int
106
     */
107
    protected $defaultProductParentID = 0;
108
109
    public function getDescription()
110
    {
111
        if ($this->csvSeparator == "\t") {
112
            $this->csvSeparatorName = "[TAB]";
0 ignored issues
show
Bug introduced by
The property csvSeparatorName does not seem to exist. Did you mean csvSeparator?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
113
        } else {
114
            $this->csvSeparatorName = $this->csvSeparator;
0 ignored issues
show
Bug introduced by
The property csvSeparatorName does not seem to exist. Did you mean csvSeparator?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
115
        }
116
        return $this->description .". The file to be used is: ".$this->Config()->get("file_location").". The columns need to be separated by '".$this->csvSeparatorName."'";
0 ignored issues
show
Bug introduced by
The property csvSeparatorName does not seem to exist. Did you mean csvSeparator?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
117
    }
118
119
    /**
120
     *
121
     */
122
    public function run($request)
0 ignored issues
show
Coding Style introduced by
run uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
123
    {
124
        increase_time_limit_to(3600);
125
        increase_memory_limit_to('512M');
126
        if ($request->param("forreal") || (isset($_GET["forreal"]) && $_GET["forreal"] == 1)) {
127
            $this->forreal = true;
128
        }
129
        if ($this->forreal) {
130
            $this->reset();
131
        }
132
        $this->readFile();
133
        $this->createProducts();
134
        $this->findVariations();
135
        if ($this->forreal) {
136
            $this->createVariations();
137
            $this->getExtraDataForVariations();
138
        } else {
139
            $this->showData();
140
        }
141
    }
142
143
    /**
144
     * do more with Product
145
     * @param Product $product
146
     * @param Array $row
147
     */
148
    protected function addMoreProduct($product, $row)
0 ignored issues
show
Unused Code introduced by
The parameter $product 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...
Unused Code introduced by
The parameter $row 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
    {
150
        //overwrite in an extension of this task
151
    }
152
153
    /**
154
     * do more with Product that does have any variations
155
     * @param Product $product
156
     * @param Array $row
157
     */
158
    protected function addMoreProductForProductWithoutVariations($product, $row)
0 ignored issues
show
Unused Code introduced by
The parameter $product 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...
Unused Code introduced by
The parameter $row 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...
159
    {
160
        //overwrite in an extension of this task
161
    }
162
163
    /**
164
     * do more with Product Variation
165
     * @param ProductAttributeType $attributeType
166
     * @param String $fieldName
167
     * @param Product $product
168
     */
169
    protected function addMoreAttributeType($attributeType, $fieldName, $product)
0 ignored issues
show
Unused Code introduced by
The parameter $attributeType 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...
Unused Code introduced by
The parameter $fieldName 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...
Unused Code introduced by
The parameter $product 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...
170
    {
171
        //overwrite in an extension of this task
172
    }
173
174
    /**
175
     * do more with Product Variation
176
     * @param ProductAttributeType $attributeValue
177
     * @param ProductAttributeType $attributeType
178
     * @param Product $product
179
     */
180
    protected function addMoreToAttributeValue($attributeValue, $attributeType, $product)
0 ignored issues
show
Unused Code introduced by
The parameter $attributeValue 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...
Unused Code introduced by
The parameter $attributeType 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...
Unused Code introduced by
The parameter $product 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...
181
    {
182
        //overwrite in an extension of this task
183
    }
184
185
    /**
186
     * do more with Product Variation
187
     * @param ProductVariation $variation
188
     * @param Array $variationData
189
     * @param Product $product
190
     */
191
    protected function addMoreToVariation($variation, $variationData, $product)
0 ignored issues
show
Unused Code introduced by
The parameter $variation 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...
Unused Code introduced by
The parameter $variationData 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...
Unused Code introduced by
The parameter $product 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...
192
    {
193
        //overwrite in an extension of this task
194
    }
195
196
197
    protected function reset()
198
    {
199
        //to do...
200
    }
201
202
    protected function readFile()
203
    {
204
        echo "================================================ READING FILE ================================================";
205
        $this->alterationMessage("<h3>".$this->getDescription()."</h3>", "created");
206
        $rowCount = 1;
207
        $rows = array();
208
        $fileLocation = $this->config()->get("file_location");
209
        $this->alterationMessage("$fileLocation is the file we are reading", "created");
210
        if (($handle = fopen($fileLocation, "r")) !== false) {
211
            while (($data = fgetcsv($handle, 100000, $this->csvSeparator)) !== false) {
212
                $rows[] = $data;
213
                $rowCount++;
214
            }
215
            fclose($handle);
216
        }
217
        //$rows = str_getcsv(file_get_contents(, ",", '"');
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
218
219
        $header = array_shift($rows);
220
221
        $this->csv = array();
222
        $rowCount = 1;
223
        foreach ($rows as $row) {
224
            if (count($header) != count($row)) {
225
                $this->alterationMessage("I am trying to merge ".implode(", ", $header)." with ".implode(", ", $row)." but the column count does not match!", "deleted");
226
                die("STOPPED");
0 ignored issues
show
Coding Style Compatibility introduced by
The method readFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
227
            }
228
            $this->csv[] = array_combine($header, $row);
229
            $rowCount++;
230
        }
231
        //data fixes
232
        foreach ($this->csv as $key => $row) {
233
            if (!isset($row["ProductTitle"])) {
234
                $this->csv[$key]["ProductTitle"] = "";
235
            }
236
            if (!isset($row["ProductInternalItemID"])) {
237
                $this->csv[$key]["ProductInternalItemID"] = $row["ProductTitle"];
238
            }
239
        }
240
        $this->alterationMessage("Imported ".count($this->csv)." rows with ".count($header)." cells each");
241
        $this->alterationMessage("Fields are: ".implode("<br /> - ............ ", $header));
242
        $this->alterationMessage("================================================", "show");
243
    }
244
245
    /**
246
     *
247
     *
248
     */
249
    protected function createProducts()
250
    {
251
        $this->alterationMessage("================================================ CREATING PRODUCTS ================================================", "show");
252
        $productsCompleted = array();
253
        foreach ($this->csv as $row) {
254
            if (!isset($productsCompleted[$row["ProductTitle"]])) {
255
                $filterArray = array(
256
                    "Title" => $row["ProductTitle"],
257
                    "InternalItemID" => $row["ProductInternalItemID"]
258
                );
259
                $product = DataObject::get_one(
260
                   'ProductPage',
261
                    $filterArray,
262
                    $cacheDataObjectGetOne = false
263
                );
264
                if ($product && $product->ParentID) {
265
                    $this->defaultProductParentID = $product->ParentID;
266
                } elseif (!$this->defaultProductParentID) {
267
                    $this->defaultProductParentID = DataObject::get_one(
268
                        'ProductGroup',
269
                        null,
270
                        $cacheDataObjectGetOne = false
271
                    )->ID;
272
                }
273
                if (!$product) {
274
                    $product = ProductPage::create($filterArray);
275
                    $product->MenuTitle = $row["ProductTitle"];
276
277
                    $this->alterationMessage("Creating Product: ".$row["ProductTitle"], "created");
278
                } else {
279
                    $this->alterationMessage("Product: ".$row["ProductTitle"]." already exists");
280
                }
281
                if (!$product->ParentID) {
282
                    $product->ParentID = $this->defaultProductParentID;
283
                }
284
                $product->Title = $row["ProductTitle"];
285
                $product->InternalItemID = $row["ProductInternalItemID"];
286 View Code Duplication
                if ($this->forreal) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
287
                    $this->addMoreProduct($product, $row);
288
                    $product->write("Stage");
289
                    if ($product->IsPublished()) {
290
                        $product->Publish('Stage', 'Live');
291
                    }
292
                }
293
                $productsCompleted[$row["ProductTitle"]] = $product->ID;
294
                $this->data[$product->ID] = array(
295
                    "Product" => $product,
296
                    "VariationRows" => array()
297
                );
298
            }
299
        }
300
        $this->alterationMessage("================================================", "show");
301
    }
302
303
304
    protected function findVariations()
305
    {
306
        $this->alterationMessage("================================================ FINDING VARIATIONS ================================================", "show");
307
        foreach ($this->data as $productKey => $data) {
308
            $product = $data["Product"];
309
            $title = $product->Title;
310
            $internalItemID = $product->InternalItemID;
311
            foreach ($this->csv as $key => $row) {
312
                if (strtolower(trim($title)) == strtolower(trim($row["ProductTitle"])) || strtolower(trim($internalItemID)) == strtolower(trim($row["ProductInternalItemID"]))) {
313
                    $this->data[$product->ID]["VariationRows"][$key] = array(
314
                        "Data" => $row,
315
                        "Variation" => null
316
                    );
317
                }
318
            }
319
            if (count($this->data[$product->ID]["VariationRows"]) < 2) {
320
                $varData = array_shift($this->data[$product->ID]["VariationRows"]);
321
                $varDataRow = $varData["Data"];
322
                $this->addFieldToObject($product, $data, "Price", "");
323
                $this->addFieldToObject($product, $data, "InternalItemID", "");
324 View Code Duplication
                if ($this->forreal) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
325
                    $this->addMoreProductForProductWithoutVariations($product, $varDataRow);
326
                    $product->write("Stage");
327
                    if ($product->IsPublished()) {
328
                        $product->Publish('Stage', 'Live');
329
                    }
330
                }
331
                $this->soleProduct[$product->ID] = $product->Title.", ID: ".$product->ID;
332
                unset($this->data[$productKey]);
333
                $this->alterationMessage("Removing data for ".$product->Title." because there is only ONE variation. ", "deleted");
334
            } else {
335
                $this->alterationMessage("Found ".count($this->data[$product->ID]["VariationRows"])." Variations for ".$product->Title);
336
            }
337
        }
338
        $this->alterationMessage("================================================", "show");
339
    }
340
341
    protected function showData()
342
    {
343
        echo "<h2>Variation Summary</h2>";
344
        foreach ($this->data as $productKey => $value) {
345
            if (isset($value["Product"]) && $value["Product"]) {
346
                $this->data[$productKey]["Product"] = $value["Product"]->Title.", ID: ".$value["Product"]->ID;
347
            } else {
348
                $this->data[$productKey]["Product"] = "Not found";
349
            }
350
            $this->alterationMessage($this->data[$productKey]["Product"].", variations: ".count($this->data[$productKey]["VariationRows"]), "created");
351
        }
352
        echo "<h2>Products without variations</h2>";
353
        foreach ($this->soleProduct as $productKey => $value) {
354
            $this->alterationMessage($value, "created");
355
        }
356
        echo "<h2>Variation data</h2>";
357
        echo "<pre>";
358
        print_r($this->data);
359
        echo "</pre>";
360
        echo "<h2>CSV Data</h2>";
361
        echo "<pre>";
362
        print_r($this->csv);
363
        echo "</pre>";
364
        die("====================================================== STOPPED - add ?forreal=1 to run for real. ======================================");
0 ignored issues
show
Coding Style Compatibility introduced by
The method showData() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
365
    }
366
367
    protected function createVariations()
368
    {
369
        $this->alterationMessage("================================================ CREATING VARIATIONS ================================================", "show");
370
        foreach ($this->data as $data) {
371
            $types = array();
372
            $values = array();
373
            $product = $data["Product"];
374
            $arrayForCreation = array();
375
            $variationFilter = array();
0 ignored issues
show
Unused Code introduced by
$variationFilter 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...
376
            $this->alterationMessage("<h1>Working out variations for ".$product->Title."</h1>");
377
            //create attribute types for one product
378
            $this->alterationMessage("....Creating attribute types");
379
            foreach ($this->Config()->get("attribute_type_field_names") as $fieldKey => $fieldName) {
380
                $startMessage = "........Checking field $fieldName";
381
                $attributeTypeName = trim($data["Product"]->Title)."_".$fieldName;
382
                $filterArray = array("Name" => $attributeTypeName);
383
                $type = DataObject::get_one(
384
                    'ProductAttributeType',
385
                    $filterArray,
386
                    $cacheDataObjectGetOne = false
387
                );
388
                if (!$type) {
389
                    $this->alterationMessage($startMessage." ... creating new attribute type: ".$attributeTypeName, "created");
390
                    $type = ProductAttributeType::create($filterArray);
391
                    $type->Label = $attributeTypeName;
0 ignored issues
show
Documentation introduced by
The property Label does not exist on object<ProductAttributeType>. 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...
392
                    $type->Sort = $fieldKey;
0 ignored issues
show
Documentation introduced by
The property Sort does not exist on object<ProductAttributeType>. 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...
393
                } else {
394
                    $this->alterationMessage($startMessage." ... 	found existing attribute type: ".$attributeTypeName);
395
                }
396
                $this->addMoreAttributeType($type, $fieldName, $product);
0 ignored issues
show
Compatibility introduced by
$type of type object<DataObject> is not a sub-type of object<ProductAttributeType>. It seems like you assume a child class of the class DataObject 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...
397
                $type->write();
398
                $types[$fieldName] = $type;
399
                $product->VariationAttributes()->add($type);
400
            }
401
            //go through each variation to make the values
402
            $this->alterationMessage("....Creating attribute values");
403
            foreach ($data["VariationRows"] as $key => $row) {
404
                //go through each value
405
                foreach ($this->Config()->get("attribute_type_field_names") as $fieldName) {
406
                    if (!isset($row["Data"][$fieldName])) {
407
                        $this->alterationMessage("ERROR; $fieldName not set at all....", "deleted");
408
                        continue;
409
                    } elseif (!trim($row["Data"][$fieldName])) {
410
                        $this->alterationMessage("skipping $fieldName as there are no entries...");
411
                        continue;
412
                    }
413
                    $startMessage = "........Checking field $fieldName";
414
                    //create attribute value
415
                    $attributeValueName = $row["Data"][$fieldName];
416
                    $filterArray = array("Code" => $attributeValueName, "TypeID" => $types[$fieldName]->ID);
417
                    $value = DataObject::get_one(
418
                        'ProductAttributeValue',
419
                        $filterArray,
420
                        $cacheDataObjectGetOne = false
421
                    );
422
                    if (!$value) {
423
                        $this->alterationMessage($startMessage."............creating new attribute value:  <strong>".$attributeValueName."</strong> for ".$types[$fieldName]->Name, "created");
424
                        $value = ProductAttributeValue::create($filterArray);
425
                        $value->Code = $attributeValueName;
0 ignored issues
show
Documentation introduced by
The property Code does not exist on object<ProductAttributeValue>. 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...
426
                        $value->Value = $attributeValueName;
0 ignored issues
show
Documentation introduced by
The property Value does not exist on object<ProductAttributeValue>. 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...
427
                    } else {
428
                        $this->alterationMessage($startMessage."............found existing attribute value: <strong>".$attributeValueName."</strong> for ".$types[$fieldName]->Name);
429
                    }
430
                    $this->addMoreAttributeType($value, $types[$fieldName], $product);
0 ignored issues
show
Compatibility introduced by
$value of type object<DataObject> is not a sub-type of object<ProductAttributeType>. It seems like you assume a child class of the class DataObject 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...
431
                    $value->write();
432
                    $values[$fieldName] = $value;
433
434
                    //add at arrays for creation...
435
                    if (!isset($arrayForCreation[$types[$fieldName]->ID])) {
436
                        $arrayForCreation[$types[$fieldName]->ID] = array();
437
                    }
438
                    $arrayForCreation[$types[$fieldName]->ID][] = $value->ID;
439
                    if (!isset($variationFilters[$key])) {
440
                        $variationFilters[$key] = array();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$variationFilters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $variationFilters = 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...
441
                    }
442
                    $variationFilters[$key][$types[$fieldName]->ID] = $value->ID;
0 ignored issues
show
Bug introduced by
The variable $variationFilters 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...
443
                }
444
            }
445
            //remove attribute types without values... (i.e. product only has size of colour)
446
            foreach ($product->VariationAttributes() as $productTypeToBeDeleted) {
447
                if ($productTypeToBeDeleted->Values()->count() == 0) {
448
                    $this->alterationMessage("....deleting attribute type with no values: ".$productTypeToBeDeleted->Title);
449
                    $product->VariationAttributes()->remove($productTypeToBeDeleted);
450
                }
451
            }
452
            $this->alterationMessage("....Creating Variations ///");
453
            //$this->alterationMessage("....Creating Variations From: ".print_r(array_walk($arrayForCreation, array($this, 'implodeWalk'))));
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
454
            //generate variations
455
            $variationAttributeValuesPerVariation = array();
456
            foreach ($arrayForCreation as $typeID => $variationEntry) {
457
                foreach ($variationEntry as $positionOfVariation => $attributeValueID) {
458
                    $variationAttributeValuesPerVariation[$positionOfVariation][$typeID] = $attributeValueID;
459
                }
460
            }
461
462
            foreach ($variationAttributeValuesPerVariation as $variationAttributes) {
463
                $variation = $product->getVariationByAttributes($variationAttributes);
464
                if ($variation instanceof ProductVariation) {
465
                    $this->alterationMessage(".... Variation " . $variation->FullName . " Already Exists ///");
0 ignored issues
show
Documentation introduced by
The property FullName does not exist on object<ProductVariation>. 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...
466
                } else {
467
                    //2. if not, create variation with attributes
468
                    $className = $product->getClassNameOfVariations();
469
                    $newVariation = new $className(
470
                        array(
471
                            'ProductID' => $product->ID,
472
                            'Price' => $product->Price
473
                        )
474
                    );
475
                    $newVariation->setSaveParentProduct(false);
476
                    $newVariation->write();
477
                    $newVariation->AttributeValues()->addMany($variationAttributes);
478
                    $this->alterationMessage(".... Variation " . $newVariation->FullName . " created ///", "created");
479
                }
480
            }
481
482
            //find variations and add to VariationsRows
483
            foreach ($data["VariationRows"] as $key => $row) {
484
                $variation = $product->getVariationByAttributes($variationFilters[$key]);
485
                if ($variation instanceof ProductVariation) {
486
                    $this->alterationMessage("........Created variation, ".$variation->getTitle());
487
                    $this->data[$product->ID]["VariationRows"][$key]["Variation"] = $variation;
488
                } else {
489
                    $this->alterationMessage("........Could not find variation", "deleted");
490
                }
491
            }
492
        }
493
        $this->alterationMessage("================================================", "show");
494
    }
495
496
    protected function getExtraDataForVariations()
497
    {
498
        $this->alterationMessage("================================================ ADDING EXTRA DATA ================================================", "show");
499
        foreach ($this->data as $productData) {
500
            $product = $productData["Product"];
501
            $this->alterationMessage("<h1>Adding extra data for ".$product->Title." with ".(count($productData["VariationRows"]))."</h1>"." Variations");
502
            foreach ($productData["VariationRows"] as $key => $row) {
503
                $variation = $row["Variation"];
504
                $variationData = $row["Data"];
505
                if ($variation instanceof ProductVariation) {
506
                    $this->alterationMessage("<h3>....Updating ".$variation->getTitle()."</h3>", "show");
507
                    if (isset($variationData["Price"])) {
508
                        if ($price = floatval($variationData["Price"]) - 0) {
509
                            if (floatval($variation->Price) != floatval($price)) {
0 ignored issues
show
Documentation introduced by
The property Price does not exist on object<ProductVariation>. 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...
510
                                $this->alterationMessage("........Price = ".$price, "created");
511
                                $variation->Price = $price;
0 ignored issues
show
Documentation introduced by
The property Price does not exist on object<ProductVariation>. 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...
512
                            }
513
                        } else {
514
                            $this->alterationMessage("........NO Price", "deleted");
515
                        }
516
                    } else {
517
                        $this->alterationMessage("........NO Price field", "deleted");
518
                    }
519
                    $this->addFieldToObject($variation, $variationData, "Price", "");
520
                    $this->addFieldToObject($variation, $variationData, "InternalItemID", "");
521
                    $this->addMoreToVariation($variation, $variationData, $product);
522
                    $variation->write();
523
                } else {
524
                    $this->alterationMessage("....Could not find variation for ".print_r($row), "deleted");
525
                }
526
            }
527
        }
528
        $this->alterationMessage("================================================", "show");
529
    }
530
531
    /**
532
     * adds a field to the variation
533
     * @param ProductVariation | Product $variation
534
     * @param array $variationData - the array of data
535
     * @param String $objectField - the name of the field on the variation itself
536
     * @param String $arrayField - the name of the field in the variationData
537
     *
538
     */
539
    protected function addFieldToObject($variation, $variationData, $objectField, $arrayField = "")
540
    {
541
        if (!$arrayField) {
542
            $arrayField = $objectField;
543
        }
544
        if (isset($variationData[$arrayField])) {
545
            if ($value = $variationData[$arrayField]) {
546
                if ($variation->$objectField != $value) {
547
                    $this->alterationMessage("........$objectField = ".$value, "changed");
548
                }
549
                $variation->$objectField = $value;
550
            } else {
551
                $this->alterationMessage("........NO $arrayField value", "deleted");
552
            }
553
        } else {
554
            $this->alterationMessage("........NO $arrayField field", "deleted");
555
        }
556
    }
557
558
    /*
559
     * @param string $message
560
     * @param string $style
561
     */
562
    protected function alterationMessage($message, $style = "")
563
    {
564
        if (!Director::isDev() || $style) {
565
            DB::alteration_message($message, $style);
566
            ob_start();
567
            ob_end_flush();
568
        } else {
569
            echo ".";
570
            ob_start();
571
            ob_end_flush();
572
        }
573
    }
574
}
575
576
577
class EcommerceTaskCSVToVariations_EXT extends Extension
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
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...
578
{
579
    private static $allowed_actions = 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 $allowed_actions 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...
580
        "ecommercetaskcsvtovariations" => true
581
    );
582
583
    //NOTE THAT updateEcommerceDevMenuConfig adds to Config options
584
    //but you can als have: updateEcommerceDevMenuDebugActions
585
    public function updateEcommerceDevMenuRegularMaintenance($buildTasks)
586
    {
587
        $buildTasks[] = "ecommercetaskcsvtovariations";
588
        return $buildTasks;
589
    }
590
591
    public function ecommercetaskcsvtovariations($request)
592
    {
593
        $this->owner->runTask("ecommercetaskcsvtovariations", $request);
594
    }
595
}
596