Completed
Push — master ( 6b46ba...13a39e )
by Tim
06:32 queued 04:28
created

UrlRewriteObserver::setStoreViewCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 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 3
    public function handle(array $row)
57
    {
58
59
        // load the header information
60 3
        $headers = $this->getHeaders();
61
62
        // query whether or not, we've found a new SKU => means we've found a new product
63 3
        if ($this->isLastSku($row[$headers[ColumnKeys::SKU]])) {
64
            return $row;
65
        }
66
67
        // prepare the URL key, return immediately if not available
68 3
        if ($this->prepareUrlKey($row) == null) {
69
            return $row;
70
        }
71
72
        // initialize the store view code
73 3
        $this->setStoreViewCode($row[$headers[ColumnKeys::STORE_VIEW_CODE]] ?: StoreViewCodes::DEF);
74
75
        // load the ID of the last entity
76 3
        $lastEntityId = $this->getLastEntityId();
77
78
        // initialize the entity type to use
79 3
        $entityType = UrlRewriteObserver::ENTITY_TYPE;
80
81
        // load the product category IDs
82 3
        $productCategoryIds = $this->getProductCategoryIds();
83
84
        // load the URL rewrites for the entity type and ID
85 3
        $urlRewrites = $this->getUrlRewritesByEntityTypeAndEntityId($entityType, $lastEntityId);
86
87
        // prepare the existing URLs => unserialize the metadata
88 3
        $existingProductCategoryUrlRewrites = $this->prepareExistingCategoryUrlRewrites($urlRewrites);
89
90
        // delete/create/update the URL rewrites
91 3
        $this->deleteUrlRewrites($existingProductCategoryUrlRewrites);
92 3
        $this->updateUrlRewrites(array_intersect_key($existingProductCategoryUrlRewrites, $productCategoryIds));
93 3
        $this->createUrlRewrites($productCategoryIds);
94
95
        // returns the row
96 3
        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 3
    protected function prepareUrlKey($row)
107
    {
108
109
        // load the header information
110 3
        $headers = $this->getHeaders();
111
112
        // query whether or not we've a URL key available in the CSV file row
113 3
        if (isset($row[$headers[ColumnKeys::URL_KEY]])) {
114 3
            $urlKey = $row[$headers[ColumnKeys::URL_KEY]];
115
        }
116
117
        // query whether or not an URL key has been specified in the CSV file
118 3
        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 3
        $this->setUrlKey($this->convertNameToUrlKey($urlKey));
135
136
        // return TRUE if the URL key has been prepared
137 3
        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 3
    protected function setUrlKey($urlKey)
148
    {
149 3
        $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 3
    }
151
152
    /**
153
     * Return's the prepared URL key.
154
     *
155
     * @return string The prepared URL key
156
     */
157 3
    protected function getUrlKey()
158
    {
159 3
        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 3
    protected function getUrlKeyFilter()
168
    {
169 3
        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 3
    protected function convertNameToUrlKey($string)
180
    {
181 3
        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 3
    protected function prepareExistingCategoryUrlRewrites(array $urlRewrites)
196
    {
197
198
        // initialize the array for the existing URL rewrites
199 3
        $existingProductCategoryUrlRewrites = array();
200
201
        // load the store's root category
202 3
        $rootCategory = $this->getRootCategory();
203
204
        // iterate over the URL rewrites and convert them
205 3
        foreach ($urlRewrites as $urlRewrite) {
206
            // initialize the array with the metadata
207 2
            $metadata = array();
208
209
            // de-serialize the category ID from the metadata
210 2
            if ($md = $urlRewrite['metadata']) {
211 2
                $metadata = unserialize($md);
212
            }
213
214
            // use the store's category ID if not serialized metadata is available
215 2
            if (!isset($metadata['category_id'])) {
216 2
                $metadata['category_id'] = $rootCategory[MemberNames::ENTITY_ID];
217
            }
218
219
            // append the URL rewrite with the found category ID
220 2
            $existingProductCategoryUrlRewrites[$metadata['category_id']] = $urlRewrite;
221
        }
222
223
        // return the array with the existing URL rewrites
224 3
        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 3
    protected function deleteUrlRewrites(array $existingProductCategoryUrlRewrites)
235
    {
236
237
        // query whether or not we've any URL rewrites that have to be removed
238 3
        if (sizeof($existingProductCategoryUrlRewrites) === 0) {
239 1
            return;
240
        }
241
242
        // remove the URL rewrites
243 2
        foreach ($existingProductCategoryUrlRewrites as $urlRewrite) {
244 2
            $this->removeUrlRewrite(array(MemberNames::URL_REWRITE_ID => $urlRewrite[MemberNames::URL_REWRITE_ID]));
245
        }
246 2
    }
247
248
    /**
249
     * Create's the URL rewrites from the passed data.
250
     *
251
     * @param array $productCategoryIds The categories to create a URL rewrite for
252
     *
253
     * @return void
254
     */
255 3
    protected function createUrlRewrites(array $productCategoryIds)
256
    {
257
258
        // query whether or not if there is any category to create a URL rewrite for
259 3
        if (sizeof($productCategoryIds) === 0) {
260
            return;
261
        }
262
263
        // iterate over the categories and create the URL rewrites
264 3
        foreach ($productCategoryIds as $categoryId => $entityId) {
265
            // load the category to create the URL rewrite for
266 3
            $category = $this->getCategory($categoryId);
267
268
            // initialize the values
269 3
            $requestPath = $this->prepareRequestPath($category);
270 3
            $targetPath = $this->prepareTargetPath($category);
271 3
            $metadata = serialize($this->prepareMetadata($category));
272
273
            // initialize the URL rewrite data
274 3
            $params = array('product', $entityId, $requestPath, $targetPath, 0, 1, null, 1, $metadata);
275
276
            // create the URL rewrite
277 3
            $this->persistUrlRewrite($params);
278
        }
279 3
    }
280
281
    /**
282
     * Update's existing URL rewrites by creating 301 redirect URL rewrites for each.
283
     *
284
     * @param array $existingProductCategoryUrlRewrites The array with the existing URL rewrites
285
     *
286
     * @return void
287
     */
288 3
    protected function updateUrlRewrites(array $existingProductCategoryUrlRewrites)
289
    {
290
291
        // query whether or not, we've existing URL rewrites that need to be redirected
292 3
        if (sizeof($existingProductCategoryUrlRewrites) === 0) {
293 1
            return;
294
        }
295
296
        // iterate over the URL redirects that have to be redirected
297 2
        foreach ($existingProductCategoryUrlRewrites as $categoryId => $urlRewrite) {
298
            // load the category data
299 2
            $category = $this->getCategory($categoryId);
300
301
            // initialize the values
302 2
            $entityId = $urlRewrite[MemberNames::ENTITY_ID];
303 2
            $requestPath = sprintf('%s', $urlRewrite['request_path']);
304 2
            $targetPath = $this->prepareTargetPathForRedirect($category);
305 2
            $metadata = serialize($this->prepareMetadata($category));
306
307
            // initialize the URL rewrite data
308 2
            $params = array('product', $entityId, $requestPath, $targetPath, 301, 1, null, 0, $metadata);
309
310
            // create the 301 redirect URL rewrite
311 2
            $this->persistUrlRewrite($params);
312
        }
313 2
    }
314
315
    /**
316
     * Prepare's the target path for a URL rewrite.
317
     *
318
     * @param array $category The categroy with the URL path
319
     *
320
     * @return string The target path
321
     */
322 3
    protected function prepareTargetPath(array $category)
323
    {
324
325
        // load the actual entity ID
326 3
        $lastEntityId = $this->getLastEntityId();
327
328
        // initialize the target path
329 3
        $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...
330
331
        // query whether or not, the category is the root category
332 3
        if ($this->isRootCategory($category)) {
333 3
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
334
        } else {
335 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
336
        }
337
338
        // return the target path
339 3
        return $targetPath;
340
    }
341
342
    /**
343
     * Prepare's the request path for a URL rewrite.
344
     *
345
     * @param array $category The categroy with the URL path
346
     *
347
     * @return string The request path
348
     */
349 3 View Code Duplication
    protected function prepareRequestPath(array $category)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
350
    {
351
352
        // initialize the request path
353 3
        $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...
354
355
        // query whether or not, the category is the root category
356 3
        if ($this->isRootCategory($category)) {
357 3
            $requestPath = sprintf('%s.html', $this->getUrlKey());
358
        } else {
359 2
            $requestPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->getUrlKey());
360
        }
361
362
        // return the request path
363 3
        return $requestPath;
364
    }
365
366
    /**
367
     * Prepare's the target path for a 301 redirect URL rewrite.
368
     *
369
     * @param array $category The categroy with the URL path
370
     *
371
     * @return string The target path
372
     */
373 2 View Code Duplication
    protected function prepareTargetPathForRedirect(array $category)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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