Completed
Push — master ( c2b612...40d106 )
by Tim
9s
created

UrlRewriteObserver::setUrlKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Observers\UrlRewriteObserver
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-product
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Observers;
22
23
use TechDivision\Import\Utils\StoreViewCodes;
24
use TechDivision\Import\Product\Utils\ColumnKeys;
25
use TechDivision\Import\Product\Utils\MemberNames;
26
use TechDivision\Import\Product\Utils\Filter\ConvertLiteralUrl;
27
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
28
29
/**
30
 * Observer that creates/updates the product's URL rewrites.
31
 *
32
 * @author    Tim Wagner <[email protected]>
33
 * @copyright 2016 TechDivision GmbH <[email protected]>
34
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
35
 * @link      https://github.com/techdivision/import-product
36
 * @link      http://www.techdivision.com
37
 */
38
class UrlRewriteObserver extends AbstractProductImportObserver
39
{
40
41
    /**
42
     * The entity type to load the URL rewrites for.
43
     *
44
     * @var string
45
     */
46
    const ENTITY_TYPE = 'product';
47
48
    /**
49
     * Will be invoked by the action on the events the listener has been registered for.
50
     *
51
     * @param array $row The row to handle
52
     *
53
     * @return array The modified row
54
     * @see \TechDivision\Import\Product\Observers\ImportObserverInterface::handle()
55
     */
56
    public function handle(array $row)
57
    {
58
59
        // load the header information
60
        $headers = $this->getHeaders();
61
62
        // query whether or not, we've found a new SKU => means we've found a new product
63
        if ($this->isLastSku($row[$headers[ColumnKeys::SKU]])) {
64
            return $row;
65
        }
66
67
        // prepare the URL key, return immediately if not available
68
        if ($this->prepareUrlKey($row) == null) {
69
            return $row;
70
        }
71
72
        // initialize the store view code
73
        $this->setStoreViewCode($row[$headers[ColumnKeys::STORE_VIEW_CODE]] ?: StoreViewCodes::DEF);
74
75
        // load the ID of the last entity
76
        $lastEntityId = $this->getLastEntityId();
77
78
        // initialize the entity type to use
79
        $entityType = UrlRewriteObserver::ENTITY_TYPE;
80
81
        // load the product category IDs
82
        $productCategoryIds = $this->getProductCategoryIds();
83
84
        // load the URL rewrites for the entity type and ID
85
        $urlRewrites = $this->getUrlRewritesByEntityTypeAndEntityId($entityType, $lastEntityId);
86
87
        // prepare the existing URLs => unserialize the metadata
88
        $existingProductCategoryUrlRewrites = $this->prepareExistingCategoryUrlRewrites($urlRewrites);
89
90
        // delete/create/update the URL rewrites
91
        $this->deleteUrlRewrites($existingProductCategoryUrlRewrites);
92
        $this->updateUrlRewrites($row, array_intersect_key($existingProductCategoryUrlRewrites, $productCategoryIds));
93
        $this->createUrlRewrites($row, $productCategoryIds);
94
95
        // returns the row
96
        return $row;
97
    }
98
99
    /**
100
     * Prepare's and set's the URL key from the passed row of the CSV file.
101
     *
102
     * @param array $row The row with the CSV data
103
     *
104
     * @return boolean TRUE, if the URL key has been prepared, else FALSE
105
     */
106
    protected function prepareUrlKey($row)
107
    {
108
109
        // load the header information
110
        $headers = $this->getHeaders();
111
112
        // query whether or not we've a URL key available in the CSV file row
113
        if (isset($row[$headers[ColumnKeys::URL_KEY]])) {
114
            $urlKey = $row[$headers[ColumnKeys::URL_KEY]];
115
        }
116
117
        // query whether or not an URL key has been specified in the CSV file
118
        if (empty($urlKey)) {
119
            // if not, try to use the product name
120
            if (isset($row[$headers[ColumnKeys::NAME]])) {
121
                $productName = $row[$headers[ColumnKeys::NAME]];
122
            }
123
124
            // if nor URL key AND product name are empty, return immediately
125
            if (empty($productName)) {
126
                return false;
127
            }
128
129
            // initialize the URL key with product name
130
            $urlKey = $productName;
131
        }
132
133
        // convert and set the URL key
134
        $this->setUrlKey($this->convertNameToUrlKey($urlKey));
135
136
        // return TRUE if the URL key has been prepared
137
        return true;
138
    }
139
140
    /**
141
     * Set's the prepared URL key.
142
     *
143
     * @param string $urlKey The prepared URL key
144
     *
145
     * @return void
146
     */
147
    protected function setUrlKey($urlKey)
148
    {
149
        $this->urlKey = $urlKey;
0 ignored issues
show
Bug introduced by
The property urlKey does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
150
    }
151
152
    /**
153
     * Return's the prepared URL key.
154
     *
155
     * @return string The prepared URL key
156
     */
157
    protected function getUrlKey()
158
    {
159
        return $this->urlKey;
160
    }
161
162
    /**
163
     * Initialize's and return's the URL key filter.
164
     *
165
     * @return \TechDivision\Import\Product\Utils\ConvertLiteralUrl The URL key filter
166
     */
167
    protected function getUrlKeyFilter()
168
    {
169
        return new ConvertLiteralUrl();
170
    }
171
172
    /**
173
     * Convert's the passed string into a valid URL key.
174
     *
175
     * @param string $string The string to be converted, e. g. the product name
176
     *
177
     * @return string The converted string as valid URL key
178
     */
179
    protected function convertNameToUrlKey($string)
180
    {
181
        return $this->getUrlKeyFilter()->filter($string);
182
    }
183
184
    /**
185
     * Convert's the passed URL rewrites into an array with the category ID from the
186
     * metadata as key and the URL rewrite as value.
187
     *
188
     * If now category ID can be found in the metadata, the ID of the store's root
189
     * category is used.
190
     *
191
     * @param array $urlRewrites The URL rewrites to convert
192
     *
193
     * @return array The converted array with the de-serialized category IDs as key
194
     */
195
    protected function prepareExistingCategoryUrlRewrites(array $urlRewrites)
196
    {
197
198
        // initialize the array for the existing URL rewrites
199
        $existingProductCategoryUrlRewrites = array();
200
201
        // load the store's root category
202
        $rootCategory = $this->getRootCategory();
203
204
        // iterate over the URL rewrites and convert them
205
        foreach ($urlRewrites as $urlRewrite) {
206
            // initialize the array with the metadata
207
            $metadata = array();
208
209
            // de-serialize the category ID from the metadata
210
            if ($md = $urlRewrite['metadata']) {
211
                $metadata = unserialize($md);
212
            }
213
214
            // use the store's category ID if not serialized metadata is available
215
            if (!isset($metadata['category_id'])) {
216
                $metadata['category_id'] = $rootCategory[MemberNames::ENTITY_ID];
217
            }
218
219
            // append the URL rewrite with the found category ID
220
            $existingProductCategoryUrlRewrites[$metadata['category_id']] = $urlRewrite;
221
        }
222
223
        // return the array with the existing URL rewrites
224
        return $existingProductCategoryUrlRewrites;
225
    }
226
227
    /**
228
     * Remove's the URL rewrites with the passed data.
229
     *
230
     * @param array $existingProductCategoryUrlRewrites The array with the URL rewrites to remove
231
     *
232
     * @return void
233
     */
234
    protected function deleteUrlRewrites(array $existingProductCategoryUrlRewrites)
235
    {
236
237
        // query whether or not we've any URL rewrites that have to be removed
238
        if (sizeof($existingProductCategoryUrlRewrites) === 0) {
239
            return;
240
        }
241
242
        // remove the URL rewrites
243
        foreach ($existingProductCategoryUrlRewrites as $categoryId => $urlRewrite) {
244
            $this->removeUrlRewrite(array(MemberNames::URL_REWRITE_ID => $urlRewrite[MemberNames::URL_REWRITE_ID]));
245
        }
246
    }
247
248
    /**
249
     * Create's the URL rewrites from the passed data.
250
     *
251
     * @param array $row                The data to create the URL rewrite from
252
     * @param array $productCategoryIds The categories to create a URL rewrite for
253
     *
254
     * @return void
255
     */
256
    protected function createUrlRewrites(array $row, array $productCategoryIds)
257
    {
258
259
        // query whether or not if there is any category to create a URL rewrite for
260
        if (sizeof($productCategoryIds) === 0) {
261
            return;
262
        }
263
264
        // load the header information
265
        $headers = $this->getHeaders();
0 ignored issues
show
Unused Code introduced by
$headers 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...
266
267
        // iterate over the categories and create the URL rewrites
268
        foreach ($productCategoryIds as $categoryId => $entityId) {
269
            // load the category to create the URL rewrite for
270
            $category = $this->getCategory($categoryId);
271
272
            // initialize the values
273
            $requestPath = $this->prepareRequestPath($row, $category);
274
            $targetPath = $this->prepareTargetPath($category);
275
            $metadata = serialize($this->prepareMetadata($category));
276
277
            // initialize the URL rewrite data
278
            $params = array('product', $entityId, $requestPath, $targetPath, 0, 1, null, 1, $metadata);
279
280
            // create the URL rewrite
281
            $this->persistUrlRewrite($params);
282
        }
283
    }
284
285
    /**
286
     * Update's existing URL rewrites by creating 301 redirect URL rewrites for each.
287
     *
288
     * @param array $row                                The row with the actual data
289
     * @param array $existingProductCategoryUrlRewrites The array with the existing URL rewrites
290
     *
291
     * @return void
292
     */
293
    protected function updateUrlRewrites(array $row, array $existingProductCategoryUrlRewrites)
294
    {
295
296
        // query whether or not, we've existing URL rewrites that need to be redirected
297
        if (sizeof($existingProductCategoryUrlRewrites) === 0) {
298
            return;
299
        }
300
301
        // load the header information
302
        $headers = $this->getHeaders();
0 ignored issues
show
Unused Code introduced by
$headers 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...
303
304
        // iterate over the URL redirects that have to be redirected
305
        foreach ($existingProductCategoryUrlRewrites as $categoryId => $urlRewrite) {
306
            // load the category data
307
            $category = $this->getCategory($categoryId);
308
309
            // initialize the values
310
            $entityId = $urlRewrite[MemberNames::ENTITY_ID];
311
            $requestPath = sprintf('%s', $urlRewrite['request_path']);
312
            $targetPath = $this->prepareTargetPathForRedirect($row, $category);
313
            $metadata = serialize($this->prepareMetadata($category));
314
315
            // initialize the URL rewrite data
316
            $params = array('product', $entityId, $requestPath, $targetPath, 301, 1, null, 0, $metadata);
317
318
            // create the 301 redirect URL rewrite
319
            $this->persistUrlRewrite($params);
320
        }
321
    }
322
323
    /**
324
     * Prepare's the target path for a URL rewrite.
325
     *
326
     * @param array $category The categroy with the URL path
327
     *
328
     * @return string The target path
329
     */
330
    protected function prepareTargetPath(array $category)
331
    {
332
333
        // load the actual entity ID
334
        $lastEntityId = $this->getLastEntityId();
335
336
        // initialize the target path
337
        $targetPath = '';
0 ignored issues
show
Unused Code introduced by
$targetPath 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...
338
339
        // query whether or not, the category is the root category
340
        if ($this->isRootCategory($category)) {
341
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
342
        } else {
343
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
344
        }
345
346
        // return the target path
347
        return $targetPath;
348
    }
349
350
    /**
351
     * Prepare's the request path for a URL rewrite.
352
     *
353
     * @param array $row      The actual row with the data
354
     * @param array $category The categroy with the URL path
355
     *
356
     * @return string The request path
357
     */
358 View Code Duplication
    protected function prepareRequestPath(array $row, array $category)
0 ignored issues
show
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...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
359
    {
360
361
        // load the header information
362
        $headers = $this->getHeaders();
0 ignored issues
show
Unused Code introduced by
$headers 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...
363
364
        // initialize the request path
365
        $requestPath = '';
0 ignored issues
show
Unused Code introduced by
$requestPath 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...
366
367
        // query whether or not, the category is the root category
368
        if ($this->isRootCategory($category)) {
369
            $requestPath = sprintf('%s.html', $this->getUrlKey());
370
        } else {
371
            $requestPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->getUrlKey());
372
        }
373
374
        // return the request path
375
        return $requestPath;
376
    }
377
378
    /**
379
     * Prepare's the target path for a 301 redirect URL rewrite.
380
     *
381
     * @param array $row      The actual row with the data
382
     * @param array $category The categroy with the URL path
383
     *
384
     * @return string The target path
385
     */
386 View Code Duplication
    protected function prepareTargetPathForRedirect(array $row, array $category)
0 ignored issues
show
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...
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
387
    {
388
389
        // load the header information
390
        $headers = $this->getHeaders();
0 ignored issues
show
Unused Code introduced by
$headers 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...
391
392
        // initialize the target path
393
        $targetPath = '';
0 ignored issues
show
Unused Code introduced by
$targetPath 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...
394
395
        // query whether or not, the category is the root category
396
        if ($this->isRootCategory($category)) {
397
            $targetPath = sprintf('%s.html', $this->getUrlKey());
398
        } else {
399
            $targetPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->getUrlKey());
400
        }
401
402
        // return the target path
403
        return $targetPath;
404
    }
405
406
    /**
407
     * Prepare's the URL rewrite's metadata with the passed category values.
408
     *
409
     * @param array $category The category used for preparation
410
     *
411
     * @return array The metadata
412
     */
413
    protected function prepareMetadata(array $category)
414
    {
415
416
        // initialize the metadata
417
        $metadata = array();
418
419
        // query whether or not, the passed category IS the root category
420
        if ($this->isRootCategory($category)) {
421
            return $metadata;
422
        }
423
424
        // if not, set the category ID in the metadata
425
        $metadata['category_id'] = $category[MemberNames::ENTITY_ID];
426
427
        // return the metadata
428
        return $metadata;
429
    }
430
431
    /**
432
     * Set's the store view code the create the product/attributes for.
433
     *
434
     * @param string $storeViewCode The store view code
435
     *
436
     * @return void
437
     */
438
    public function setStoreViewCode($storeViewCode)
439
    {
440
        $this->getSubject()->setStoreViewCode($storeViewCode);
441
    }
442
443
    /**
444
     * Return's the root category for the actual view store.
445
     *
446
     * @return array The store's root category
447
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
448
     */
449
    public function getRootCategory()
450
    {
451
        return $this->getSubject()->getRootCategory();
452
    }
453
454
    /**
455
     * Return's TRUE if the passed category IS the root category, else FALSE.
456
     *
457
     * @param array $category The category to query
458
     *
459
     * @return boolean TRUE if the passed category IS the root category
460
     */
461
    public function isRootCategory(array $category)
462
    {
463
464
        // load the root category
465
        $rootCategory = $this->getRootCategory();
466
467
        // compare the entity IDs and return the result
468
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
469
    }
470
471
    /**
472
     * Return's the list with category IDs the product is related with.
473
     *
474
     * @return array The product's category IDs
475
     */
476
    public function getProductCategoryIds()
477
    {
478
        return $this->getSubject()->getProductCategoryIds();
479
    }
480
481
    /**
482
     * Return's the category with the passed ID.
483
     *
484
     * @param integer $categoryId The ID of the category to return
485
     *
486
     * @return array The category data
487
     */
488
    public function getCategory($categoryId)
489
    {
490
        return $this->getSubject()->getCategory($categoryId);
491
    }
492
493
    /**
494
     * Return's the URL rewrites for the passed URL entity type and ID.
495
     *
496
     * @param string  $entityType The entity type to load the URL rewrites for
497
     * @param integer $entityId   The entity ID to laod the rewrites for
498
     *
499
     * @return array The URL rewrites
500
     */
501
    public function getUrlRewritesByEntityTypeAndEntityId($entityType, $entityId)
502
    {
503
        return $this->getSubject()->getUrlRewritesByEntityTypeAndEntityId($entityType, $entityId);
504
    }
505
506
    /**
507
     * Persist's the URL write with the passed data.
508
     *
509
     * @param array $row The URL rewrite to persist
510
     *
511
     * @return void
512
     */
513
    public function persistUrlRewrite($row)
514
    {
515
        $this->getSubject()->persistUrlRewrite($row);
516
    }
517
518
    /**
519
     * Delete's the URL rewrite with the passed attributes.
520
     *
521
     * @param array $row The attributes of the entity to remove
522
     *
523
     * @return void
524
     */
525
    public function removeUrlRewrite($row)
526
    {
527
        $this->getSubject()->removeUrlRewrite($row);
528
    }
529
}
530