Completed
Push — master ( b9383d...d4bc8a )
by Tim
9s
created

UrlRewriteObserver::getUrlKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
     * The URL key from the CSV file column that has to be processed by the observer.
50
     *
51
     * @var string
52
     */
53
    protected $urlKey;
54
55
    /**
56
     * Will be invoked by the action on the events the listener has been registered for.
57
     *
58
     * @param array $row The row to handle
59
     *
60
     * @return array The modified row
61
     * @see \TechDivision\Import\Product\Observers\ImportObserverInterface::handle()
62
     */
63 3
    public function handle(array $row)
64
    {
65
66
        // load the header information
67 3
        $headers = $this->getHeaders();
68
69
        // query whether or not, we've found a new SKU => means we've found a new product
70 3
        if ($this->isLastSku($row[$headers[ColumnKeys::SKU]])) {
71
            return $row;
72
        }
73
74
        // prepare the URL key, return immediately if not available
75 3
        if ($this->prepareUrlKey($row) == null) {
76
            return $row;
77
        }
78
79
        // initialize the store view code
80 3
        $this->setStoreViewCode($row[$headers[ColumnKeys::STORE_VIEW_CODE]] ?: StoreViewCodes::DEF);
81
82
        // load the ID of the last entity
83 3
        $lastEntityId = $this->getLastEntityId();
84
85
        // initialize the entity type to use
86 3
        $entityType = UrlRewriteObserver::ENTITY_TYPE;
87
88
        // load the product category IDs
89 3
        $productCategoryIds = $this->getProductCategoryIds();
90
91
        // load the URL rewrites for the entity type and ID
92 3
        $urlRewrites = $this->getUrlRewritesByEntityTypeAndEntityId($entityType, $lastEntityId);
93
94
        // prepare the existing URLs => unserialize the metadata
95 3
        $existingProductCategoryUrlRewrites = $this->prepareExistingCategoryUrlRewrites($urlRewrites);
96
97
        // delete/create/update the URL rewrites
98 3
        $this->deleteUrlRewrites($existingProductCategoryUrlRewrites);
99 3
        $this->updateUrlRewrites(array_intersect_key($existingProductCategoryUrlRewrites, $productCategoryIds));
100 3
        $this->createUrlRewrites($productCategoryIds);
101
102
        // returns the row
103 3
        return $row;
104
    }
105
106
    /**
107
     * Prepare's and set's the URL key from the passed row of the CSV file.
108
     *
109
     * @param array $row The row with the CSV data
110
     *
111
     * @return boolean TRUE, if the URL key has been prepared, else FALSE
112
     */
113 3
    protected function prepareUrlKey($row)
114
    {
115
116
        // load the header information
117 3
        $headers = $this->getHeaders();
118
119
        // query whether or not we've a URL key available in the CSV file row
120 3
        if (isset($row[$headers[ColumnKeys::URL_KEY]])) {
121 3
            $urlKey = $row[$headers[ColumnKeys::URL_KEY]];
122 3
        }
123
124
        // query whether or not an URL key has been specified in the CSV file
125 3
        if (empty($urlKey)) {
126
            // if not, try to use the product name
127
            if (isset($row[$headers[ColumnKeys::NAME]])) {
128
                $productName = $row[$headers[ColumnKeys::NAME]];
129
            }
130
131
            // if nor URL key AND product name are empty, return immediately
132
            if (empty($productName)) {
133
                return false;
134
            }
135
136
            // initialize the URL key with product name
137
            $urlKey = $productName;
138
        }
139
140
        // convert and set the URL key
141 3
        $this->setUrlKey($this->convertNameToUrlKey($urlKey));
142
143
        // return TRUE if the URL key has been prepared
144 3
        return true;
145
    }
146
147
    /**
148
     * Set's the prepared URL key.
149
     *
150
     * @param string $urlKey The prepared URL key
151
     *
152
     * @return void
153
     */
154 3
    protected function setUrlKey($urlKey)
155
    {
156 3
        $this->urlKey = $urlKey;
157 3
    }
158
159
    /**
160
     * Return's the prepared URL key.
161
     *
162
     * @return string The prepared URL key
163
     */
164 3
    protected function getUrlKey()
165
    {
166 3
        return $this->urlKey;
167
    }
168
169
    /**
170
     * Initialize's and return's the URL key filter.
171
     *
172
     * @return \TechDivision\Import\Product\Utils\ConvertLiteralUrl The URL key filter
173
     */
174 3
    protected function getUrlKeyFilter()
175
    {
176 3
        return new ConvertLiteralUrl();
177
    }
178
179
    /**
180
     * Convert's the passed string into a valid URL key.
181
     *
182
     * @param string $string The string to be converted, e. g. the product name
183
     *
184
     * @return string The converted string as valid URL key
185
     */
186 3
    protected function convertNameToUrlKey($string)
187
    {
188 3
        return $this->getUrlKeyFilter()->filter($string);
189
    }
190
191
    /**
192
     * Convert's the passed URL rewrites into an array with the category ID from the
193
     * metadata as key and the URL rewrite as value.
194
     *
195
     * If now category ID can be found in the metadata, the ID of the store's root
196
     * category is used.
197
     *
198
     * @param array $urlRewrites The URL rewrites to convert
199
     *
200
     * @return array The converted array with the de-serialized category IDs as key
201
     */
202 3
    protected function prepareExistingCategoryUrlRewrites(array $urlRewrites)
203
    {
204
205
        // initialize the array for the existing URL rewrites
206 3
        $existingProductCategoryUrlRewrites = array();
207
208
        // load the store's root category
209 3
        $rootCategory = $this->getRootCategory();
210
211
        // iterate over the URL rewrites and convert them
212 3
        foreach ($urlRewrites as $urlRewrite) {
213
            // initialize the array with the metadata
214 2
            $metadata = array();
215
216
            // de-serialize the category ID from the metadata
217 2
            if ($md = $urlRewrite['metadata']) {
218 2
                $metadata = unserialize($md);
219 2
            }
220
221
            // use the store's category ID if not serialized metadata is available
222 2
            if (!isset($metadata['category_id'])) {
223 2
                $metadata['category_id'] = $rootCategory[MemberNames::ENTITY_ID];
224 2
            }
225
226
            // append the URL rewrite with the found category ID
227 2
            $existingProductCategoryUrlRewrites[$metadata['category_id']] = $urlRewrite;
228 3
        }
229
230
        // return the array with the existing URL rewrites
231 3
        return $existingProductCategoryUrlRewrites;
232
    }
233
234
    /**
235
     * Remove's the URL rewrites with the passed data.
236
     *
237
     * @param array $existingProductCategoryUrlRewrites The array with the URL rewrites to remove
238
     *
239
     * @return void
240
     */
241 3
    protected function deleteUrlRewrites(array $existingProductCategoryUrlRewrites)
242
    {
243
244
        // query whether or not we've any URL rewrites that have to be removed
245 3
        if (sizeof($existingProductCategoryUrlRewrites) === 0) {
246 1
            return;
247
        }
248
249
        // remove the URL rewrites
250 2
        foreach ($existingProductCategoryUrlRewrites as $urlRewrite) {
251 2
            $this->removeUrlRewrite(array(MemberNames::URL_REWRITE_ID => $urlRewrite[MemberNames::URL_REWRITE_ID]));
252 2
        }
253 2
    }
254
255
    /**
256
     * Create's the URL rewrites from the passed data.
257
     *
258
     * @param array $productCategoryIds The categories to create a URL rewrite for
259
     *
260
     * @return void
261
     */
262 3
    protected function createUrlRewrites(array $productCategoryIds)
263
    {
264
265
        // query whether or not if there is any category to create a URL rewrite for
266 3
        if (sizeof($productCategoryIds) === 0) {
267
            return;
268
        }
269
270
        // iterate over the categories and create the URL rewrites
271 3
        foreach ($productCategoryIds as $categoryId => $entityId) {
272
            // load the category to create the URL rewrite for
273 3
            $category = $this->getCategory($categoryId);
274
275
            // initialize the values
276 3
            $requestPath = $this->prepareRequestPath($category);
277 3
            $targetPath = $this->prepareTargetPath($category);
278 3
            $metadata = serialize($this->prepareMetadata($category));
279
280
            // initialize the URL rewrite data
281 3
            $params = array('product', $entityId, $requestPath, $targetPath, 0, 1, null, 1, $metadata);
282
283
            // create the URL rewrite
284 3
            $this->persistUrlRewrite($params);
285 3
        }
286 3
    }
287
288
    /**
289
     * Update's existing URL rewrites by creating 301 redirect URL rewrites for each.
290
     *
291
     * @param array $existingProductCategoryUrlRewrites The array with the existing URL rewrites
292
     *
293
     * @return void
294
     */
295 3
    protected function updateUrlRewrites(array $existingProductCategoryUrlRewrites)
296
    {
297
298
        // query whether or not, we've existing URL rewrites that need to be redirected
299 3
        if (sizeof($existingProductCategoryUrlRewrites) === 0) {
300 1
            return;
301
        }
302
303
        // iterate over the URL redirects that have to be redirected
304 2
        foreach ($existingProductCategoryUrlRewrites as $categoryId => $urlRewrite) {
305
            // load the category data
306 2
            $category = $this->getCategory($categoryId);
307
308
            // initialize the values
309 2
            $entityId = $urlRewrite[MemberNames::ENTITY_ID];
310 2
            $requestPath = sprintf('%s', $urlRewrite['request_path']);
311 2
            $targetPath = $this->prepareTargetPathForRedirect($category);
312 2
            $metadata = serialize($this->prepareMetadata($category));
313
314
            // initialize the URL rewrite data
315 2
            $params = array('product', $entityId, $requestPath, $targetPath, 301, 1, null, 0, $metadata);
316
317
            // create the 301 redirect URL rewrite
318 2
            $this->persistUrlRewrite($params);
319 2
        }
320 2
    }
321
322
    /**
323
     * Prepare's the target path for a URL rewrite.
324
     *
325
     * @param array $category The categroy with the URL path
326
     *
327
     * @return string The target path
328
     */
329 3
    protected function prepareTargetPath(array $category)
330
    {
331
332
        // load the actual entity ID
333 3
        $lastEntityId = $this->getLastEntityId();
334
335
        // initialize the target path
336 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...
337
338
        // query whether or not, the category is the root category
339 3
        if ($this->isRootCategory($category)) {
340 3
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
341 3
        } else {
342 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
343
        }
344
345
        // return the target path
346 3
        return $targetPath;
347
    }
348
349
    /**
350
     * Prepare's the request path for a URL rewrite.
351
     *
352
     * @param array $category The categroy with the URL path
353
     *
354
     * @return string The request path
355
     */
356 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...
357
    {
358
359
        // initialize the request path
360 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...
361
362
        // query whether or not, the category is the root category
363 3
        if ($this->isRootCategory($category)) {
364 3
            $requestPath = sprintf('%s.html', $this->getUrlKey());
365 3
        } else {
366 2
            $requestPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->getUrlKey());
367
        }
368
369
        // return the request path
370 3
        return $requestPath;
371
    }
372
373
    /**
374
     * Prepare's the target path for a 301 redirect URL rewrite.
375
     *
376
     * @param array $category The categroy with the URL path
377
     *
378
     * @return string The target path
379
     */
380 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...
381
    {
382
383
        // initialize the target path
384 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...
385
386
        // query whether or not, the category is the root category
387 2
        if ($this->isRootCategory($category)) {
388 2
            $targetPath = sprintf('%s.html', $this->getUrlKey());
389 2
        } else {
390 1
            $targetPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->getUrlKey());
391
        }
392
393
        // return the target path
394 2
        return $targetPath;
395
    }
396
397
    /**
398
     * Prepare's the URL rewrite's metadata with the passed category values.
399
     *
400
     * @param array $category The category used for preparation
401
     *
402
     * @return array The metadata
403
     */
404 3
    protected function prepareMetadata(array $category)
405
    {
406
407
        // initialize the metadata
408 3
        $metadata = array();
409
410
        // query whether or not, the passed category IS the root category
411 3
        if ($this->isRootCategory($category)) {
412 3
            return $metadata;
413
        }
414
415
        // if not, set the category ID in the metadata
416 2
        $metadata['category_id'] = $category[MemberNames::ENTITY_ID];
417
418
        // return the metadata
419 2
        return $metadata;
420
    }
421
422
    /**
423
     * Set's the store view code the create the product/attributes for.
424
     *
425
     * @param string $storeViewCode The store view code
426
     *
427
     * @return void
428
     */
429 3
    public function setStoreViewCode($storeViewCode)
430
    {
431 3
        $this->getSubject()->setStoreViewCode($storeViewCode);
432 3
    }
433
434
    /**
435
     * Return's the root category for the actual view store.
436
     *
437
     * @return array The store's root category
438
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
439
     */
440 3
    public function getRootCategory()
441
    {
442 3
        return $this->getSubject()->getRootCategory();
443
    }
444
445
    /**
446
     * Return's TRUE if the passed category IS the root category, else FALSE.
447
     *
448
     * @param array $category The category to query
449
     *
450
     * @return boolean TRUE if the passed category IS the root category
451
     */
452 3
    public function isRootCategory(array $category)
453
    {
454
455
        // load the root category
456 3
        $rootCategory = $this->getRootCategory();
457
458
        // compare the entity IDs and return the result
459 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
460
    }
461
462
    /**
463
     * Return's the list with category IDs the product is related with.
464
     *
465
     * @return array The product's category IDs
466
     */
467 4
    public function getProductCategoryIds()
468
    {
469 4
        return $this->getSubject()->getProductCategoryIds();
470
    }
471
472
    /**
473
     * Return's the category with the passed ID.
474
     *
475
     * @param integer $categoryId The ID of the category to return
476
     *
477
     * @return array The category data
478
     */
479 3
    public function getCategory($categoryId)
480
    {
481 3
        return $this->getSubject()->getCategory($categoryId);
482
    }
483
484
    /**
485
     * Return's the URL rewrites for the passed URL entity type and ID.
486
     *
487
     * @param string  $entityType The entity type to load the URL rewrites for
488
     * @param integer $entityId   The entity ID to laod the rewrites for
489
     *
490
     * @return array The URL rewrites
491
     */
492 4
    public function getUrlRewritesByEntityTypeAndEntityId($entityType, $entityId)
493
    {
494 4
        return $this->getSubject()->getUrlRewritesByEntityTypeAndEntityId($entityType, $entityId);
495
    }
496
497
    /**
498
     * Persist's the URL write with the passed data.
499
     *
500
     * @param array $row The URL rewrite to persist
501
     *
502
     * @return void
503
     */
504 4
    public function persistUrlRewrite($row)
505
    {
506 4
        $this->getSubject()->persistUrlRewrite($row);
507 4
    }
508
509
    /**
510
     * Delete's the URL rewrite with the passed attributes.
511
     *
512
     * @param array $row The attributes of the entity to remove
513
     *
514
     * @return void
515
     */
516 3
    public function removeUrlRewrite($row)
517
    {
518 3
        $this->getSubject()->removeUrlRewrite($row);
519 3
    }
520
}
521