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\Product\Utils\ColumnKeys; |
24
|
|
|
use TechDivision\Import\Product\Utils\MemberNames; |
25
|
|
|
use TechDivision\Import\Product\Utils\Filter\ConvertLiteralUrl; |
26
|
|
|
use TechDivision\Import\Product\Observers\AbstractProductImportObserver; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Observer that creates/updates the product's URL rewrites. |
30
|
|
|
* |
31
|
|
|
* @author Tim Wagner <[email protected]> |
32
|
|
|
* @copyright 2016 TechDivision GmbH <[email protected]> |
33
|
|
|
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
34
|
|
|
* @link https://github.com/techdivision/import-product |
35
|
|
|
* @link http://www.techdivision.com |
36
|
|
|
*/ |
37
|
|
|
class UrlRewriteObserver extends AbstractProductImportObserver |
38
|
|
|
{ |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The entity type to load the URL rewrites for. |
42
|
|
|
* |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
const ENTITY_TYPE = 'product'; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* The URL key from the CSV file column that has to be processed by the observer. |
49
|
|
|
* |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
protected $urlKey; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* The actual category ID to process. |
56
|
|
|
* |
57
|
|
|
* @var integer |
58
|
|
|
*/ |
59
|
|
|
protected $categoryId; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* The actual entity ID to process. |
63
|
|
|
* |
64
|
|
|
* @var integer |
65
|
|
|
*/ |
66
|
|
|
protected $entityId; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* The array with the URL rewrites that has to be created. |
70
|
|
|
* |
71
|
|
|
* @var array |
72
|
|
|
*/ |
73
|
|
|
protected $urlRewrites = array(); |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Will be invoked by the action on the events the listener has been registered for. |
77
|
|
|
* |
78
|
|
|
* @param array $row The row to handle |
79
|
|
|
* |
80
|
|
|
* @return array The modified row |
81
|
|
|
* @see \TechDivision\Import\Product\Observers\ImportObserverInterface::handle() |
82
|
|
|
*/ |
83
|
3 |
View Code Duplication |
public function handle(array $row) |
|
|
|
|
84
|
|
|
{ |
85
|
|
|
|
86
|
|
|
// initialize the row |
87
|
3 |
|
$this->setRow($row); |
88
|
|
|
|
89
|
|
|
// query whether or not, we've found a new SKU => means we've found a new product |
90
|
3 |
|
if ($this->isLastSku($this->getValue(ColumnKeys::SKU))) { |
91
|
|
|
return $this->getRow(); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// process the functionality and return the row |
95
|
3 |
|
$this->process(); |
96
|
|
|
|
97
|
|
|
// return the processed row |
98
|
3 |
|
return $this->getRow(); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Set's the prepared URL key. |
103
|
|
|
* |
104
|
|
|
* @param string $urlKey The prepared URL key |
105
|
|
|
* |
106
|
|
|
* @return void |
107
|
|
|
*/ |
108
|
3 |
|
protected function setUrlKey($urlKey) |
109
|
|
|
{ |
110
|
3 |
|
$this->urlKey = $urlKey; |
111
|
3 |
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Return's the prepared URL key. |
115
|
|
|
* |
116
|
|
|
* @return string The prepared URL key |
117
|
|
|
*/ |
118
|
3 |
|
protected function getUrlKey() |
119
|
|
|
{ |
120
|
3 |
|
return $this->urlKey; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Set's the actual category ID to process. |
125
|
|
|
* |
126
|
|
|
* @param integer $categoryId The category ID |
127
|
|
|
* |
128
|
|
|
* @return void |
129
|
|
|
*/ |
130
|
3 |
|
protected function setCategoryId($categoryId) |
131
|
|
|
{ |
132
|
3 |
|
$this->categoryId = $categoryId; |
133
|
3 |
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Return's the actual category ID to process. |
137
|
|
|
* |
138
|
|
|
* @return integer The category ID |
139
|
|
|
*/ |
140
|
3 |
|
protected function getCategoryId() |
141
|
|
|
{ |
142
|
3 |
|
return $this->categoryId; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Set's the actual entity ID to process. |
147
|
|
|
* |
148
|
|
|
* @param integer $entityId The entity ID |
149
|
|
|
* |
150
|
|
|
* @return void |
151
|
|
|
*/ |
152
|
3 |
|
protected function setEntityId($entityId) |
153
|
|
|
{ |
154
|
3 |
|
$this->entityId = $entityId; |
155
|
3 |
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Return's the actual entity ID to process. |
159
|
|
|
* |
160
|
|
|
* @return integer The entity ID |
161
|
|
|
*/ |
162
|
3 |
|
protected function getEntityId() |
163
|
|
|
{ |
164
|
3 |
|
return $this->entityId; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Process the observer's business logic. |
169
|
|
|
* |
170
|
|
|
* @return void |
171
|
|
|
*/ |
172
|
3 |
|
protected function process() |
173
|
|
|
{ |
174
|
|
|
|
175
|
|
|
// try to prepare the URL key, return immediately if not possible |
176
|
3 |
|
if (!$this->prepareUrlKey()) { |
177
|
|
|
return; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// initialize the store view code |
181
|
3 |
|
$this->prepareStoreViewCode(); |
182
|
|
|
|
183
|
|
|
// prepare the URL rewrites |
184
|
3 |
|
$this->prepareUrlRewrites(); |
185
|
|
|
|
186
|
|
|
// iterate over the categories and create the URL rewrites |
187
|
3 |
|
foreach ($this->urlRewrites as $urlRewrite) { |
188
|
|
|
// initialize and persist the URL rewrite |
189
|
3 |
|
if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) { |
190
|
3 |
|
$this->persistUrlRewrite($urlRewrite); |
191
|
3 |
|
} |
192
|
3 |
|
} |
193
|
3 |
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Initialize the category product with the passed attributes and returns an instance. |
197
|
|
|
* |
198
|
|
|
* @param array $attr The category product attributes |
199
|
|
|
* |
200
|
|
|
* @return array The initialized category product |
201
|
|
|
*/ |
202
|
2 |
|
protected function initializeUrlRewrite(array $attr) |
203
|
|
|
{ |
204
|
2 |
|
return $attr; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Prepare's the URL rewrites that has to be created/updated. |
209
|
|
|
* |
210
|
|
|
* @return void |
211
|
|
|
*/ |
212
|
3 |
|
protected function prepareUrlRewrites() |
213
|
|
|
{ |
214
|
|
|
|
215
|
|
|
// (re-)initialize the array for the URL rewrites |
216
|
3 |
|
$this->urlRewrites = array(); |
217
|
|
|
|
218
|
|
|
// load the root category, because we need that to create the default product URL rewrite |
219
|
3 |
|
$rootCategory = $this->getRootCategory(); |
220
|
|
|
|
221
|
|
|
// add the root category ID to the category => product relations |
222
|
3 |
|
$productCategoryIds = $this->getProductCategoryIds(); |
223
|
3 |
|
$productCategoryIds[$rootCategory[MemberNames::ENTITY_ID]] = $this->getLastEntityId(); |
224
|
|
|
|
225
|
|
|
// prepare the URL rewrites |
226
|
3 |
|
foreach ($productCategoryIds as $categoryId => $entityId) { |
227
|
|
|
// set category/entity ID |
228
|
3 |
|
$this->setCategoryId($categoryId); |
229
|
3 |
|
$this->setEntityId($entityId); |
230
|
|
|
|
231
|
|
|
// prepare the attributes for each URL rewrite |
232
|
3 |
|
$this->urlRewrites[$categoryId] = $this->prepareAttributes(); |
233
|
3 |
|
} |
234
|
3 |
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Prepare's and set's the URL key from the passed row of the CSV file. |
238
|
|
|
* |
239
|
|
|
* @return boolean TRUE, if the URL key has been prepared, else FALSE |
240
|
|
|
*/ |
241
|
3 |
|
protected function prepareUrlKey() |
242
|
|
|
{ |
243
|
|
|
|
244
|
|
|
// initialize the URL key |
245
|
3 |
|
$urlKey = null; |
246
|
|
|
|
247
|
|
|
// query whether or not we've a URL key available in the CSV file row |
248
|
3 |
|
if ($urlKeyFound = $this->getValue(ColumnKeys::URL_KEY)) { |
249
|
3 |
|
$urlKey = $urlKeyFound; |
250
|
3 |
|
} |
251
|
|
|
|
252
|
|
|
// query whether or not an URL key has been specified in the CSV file |
253
|
3 |
|
if (empty($urlKey)) { |
254
|
|
|
// initialize the product name |
255
|
|
|
$productName = null; |
256
|
|
|
// if not, try to use the product name |
257
|
|
|
if ($nameFound = $this->getValue(ColumnKeys::NAME)) { |
258
|
|
|
$productName = $nameFound; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
// if nor URL key AND product name are empty, return immediately |
262
|
|
|
if (empty($productName)) { |
263
|
|
|
return false; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
// initialize the URL key with product name |
267
|
|
|
$urlKey = $this->convertNameToUrlKey($productName); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
// convert and set the URL key |
271
|
3 |
|
$this->setUrlKey($urlKey); |
272
|
|
|
|
273
|
|
|
// return TRUE if the URL key has been prepared |
274
|
3 |
|
return true; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Prepare the attributes of the entity that has to be persisted. |
279
|
|
|
* |
280
|
|
|
* @return array The prepared attributes |
281
|
|
|
*/ |
282
|
3 |
|
protected function prepareAttributes() |
283
|
|
|
{ |
284
|
|
|
|
285
|
|
|
// load entity/category ID |
286
|
3 |
|
$entityId = $this->getEntityId(); |
287
|
3 |
|
$categoryId = $this->getCategoryId(); |
288
|
|
|
|
289
|
|
|
// load the store ID to use |
290
|
3 |
|
$storeId = $this->getRowStoreId(); |
291
|
|
|
|
292
|
|
|
// load the category to create the URL rewrite for |
293
|
3 |
|
$category = $this->getCategory($categoryId); |
294
|
|
|
|
295
|
|
|
// initialize the values |
296
|
3 |
|
$requestPath = $this->prepareRequestPath($category); |
297
|
3 |
|
$targetPath = $this->prepareTargetPath($category); |
298
|
3 |
|
$metadata = serialize($this->prepareMetadata($category)); |
299
|
|
|
|
300
|
|
|
// return the prepared URL rewrite |
301
|
3 |
|
return $this->initializeEntity( |
302
|
|
|
array( |
303
|
3 |
|
MemberNames::ENTITY_TYPE => UrlRewriteObserver::ENTITY_TYPE, |
304
|
3 |
|
MemberNames::ENTITY_ID => $entityId, |
305
|
3 |
|
MemberNames::REQUEST_PATH => $requestPath, |
306
|
3 |
|
MemberNames::TARGET_PATH => $targetPath, |
307
|
3 |
|
MemberNames::REDIRECT_TYPE => 0, |
308
|
3 |
|
MemberNames::STORE_ID => $storeId, |
309
|
3 |
|
MemberNames::DESCRIPTION => null, |
310
|
3 |
|
MemberNames::IS_AUTOGENERATED => 1, |
311
|
3 |
|
MemberNames::METADATA => $metadata |
312
|
3 |
|
) |
313
|
3 |
|
); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Prepare's the target path for a URL rewrite. |
318
|
|
|
* |
319
|
|
|
* @param array $category The categroy with the URL path |
320
|
|
|
* |
321
|
|
|
* @return string The target path |
322
|
|
|
*/ |
323
|
3 |
|
protected function prepareTargetPath(array $category) |
324
|
|
|
{ |
325
|
|
|
|
326
|
|
|
// load the actual entity ID |
327
|
3 |
|
$lastEntityId = $this->getPrimaryKey(); |
328
|
|
|
|
329
|
|
|
// query whether or not, the category is the root category |
330
|
3 |
|
if ($this->isRootCategory($category)) { |
331
|
3 |
|
$targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId); |
332
|
3 |
|
} else { |
333
|
2 |
|
$targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
// return the target path |
337
|
3 |
|
return $targetPath; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Prepare's the request path for a URL rewrite or the target path for a 301 redirect. |
342
|
|
|
* |
343
|
|
|
* @param array $category The categroy with the URL path |
344
|
|
|
* |
345
|
|
|
* @return string The request path |
346
|
|
|
*/ |
347
|
3 |
|
protected function prepareRequestPath(array $category) |
348
|
|
|
{ |
349
|
|
|
|
350
|
|
|
// query whether or not, the category is the root category |
351
|
3 |
|
if ($this->isRootCategory($category)) { |
352
|
3 |
|
$requestPath = sprintf('%s.html', $this->getUrlKey()); |
353
|
3 |
|
} else { |
354
|
2 |
|
$requestPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->getUrlKey()); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
// return the request path |
358
|
3 |
|
return $requestPath; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Prepare's the URL rewrite's metadata with the passed category values. |
363
|
|
|
* |
364
|
|
|
* @param array $category The category used for preparation |
365
|
|
|
* |
366
|
|
|
* @return array The metadata |
367
|
|
|
*/ |
368
|
3 |
|
protected function prepareMetadata(array $category) |
369
|
|
|
{ |
370
|
|
|
|
371
|
|
|
// initialize the metadata |
372
|
3 |
|
$metadata = array(); |
373
|
|
|
|
374
|
|
|
// query whether or not, the passed category IS the root category |
375
|
3 |
|
if ($this->isRootCategory($category)) { |
376
|
3 |
|
return $metadata; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
// if not, set the category ID in the metadata |
380
|
2 |
|
$metadata['category_id'] = $category[MemberNames::ENTITY_ID]; |
381
|
|
|
|
382
|
|
|
// return the metadata |
383
|
2 |
|
return $metadata; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Initialize's and return's the URL key filter. |
388
|
|
|
* |
389
|
|
|
* @return \TechDivision\Import\Product\Utils\ConvertLiteralUrl The URL key filter |
390
|
|
|
*/ |
391
|
|
|
protected function getUrlKeyFilter() |
392
|
|
|
{ |
393
|
|
|
return new ConvertLiteralUrl(); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* Convert's the passed string into a valid URL key. |
398
|
|
|
* |
399
|
|
|
* @param string $string The string to be converted, e. g. the product name |
400
|
|
|
* |
401
|
|
|
* @return string The converted string as valid URL key |
402
|
|
|
*/ |
403
|
|
|
protected function convertNameToUrlKey($string) |
404
|
|
|
{ |
405
|
|
|
return $this->getUrlKeyFilter()->filter($string); |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Return's the root category for the actual view store. |
410
|
|
|
* |
411
|
|
|
* @return array The store's root category |
412
|
|
|
* @throws \Exception Is thrown if the root category for the passed store code is NOT available |
413
|
|
|
*/ |
414
|
3 |
|
protected function getRootCategory() |
415
|
|
|
{ |
416
|
3 |
|
return $this->getSubject()->getRootCategory(); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Return's TRUE if the passed category IS the root category, else FALSE. |
421
|
|
|
* |
422
|
|
|
* @param array $category The category to query |
423
|
|
|
* |
424
|
|
|
* @return boolean TRUE if the passed category IS the root category |
425
|
|
|
*/ |
426
|
3 |
|
protected function isRootCategory(array $category) |
427
|
|
|
{ |
428
|
|
|
|
429
|
|
|
// load the root category |
430
|
3 |
|
$rootCategory = $this->getRootCategory(); |
431
|
|
|
|
432
|
|
|
// compare the entity IDs and return the result |
433
|
3 |
|
return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID]; |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Return's the store ID of the actual row, or of the default store |
438
|
|
|
* if no store view code is set in the CSV file. |
439
|
|
|
* |
440
|
|
|
* @param string|null $default The default store view code to use, if no store view code is set in the CSV file |
441
|
|
|
* |
442
|
|
|
* @return integer The ID of the actual store |
443
|
|
|
* @throws \Exception Is thrown, if the store with the actual code is not available |
444
|
|
|
*/ |
445
|
3 |
|
protected function getRowStoreId($default = null) |
446
|
|
|
{ |
447
|
3 |
|
return $this->getSubject()->getRowStoreId($default); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Return's the list with category IDs the product is related with. |
452
|
|
|
* |
453
|
|
|
* @return array The product's category IDs |
454
|
|
|
*/ |
455
|
3 |
|
protected function getProductCategoryIds() |
456
|
|
|
{ |
457
|
3 |
|
return $this->getSubject()->getProductCategoryIds(); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Return's the category with the passed ID. |
462
|
|
|
* |
463
|
|
|
* @param integer $categoryId The ID of the category to return |
464
|
|
|
* |
465
|
|
|
* @return array The category data |
466
|
|
|
*/ |
467
|
3 |
|
protected function getCategory($categoryId) |
468
|
|
|
{ |
469
|
3 |
|
return $this->getSubject()->getCategory($categoryId); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
/** |
473
|
|
|
* Persist's the URL write with the passed data. |
474
|
|
|
* |
475
|
|
|
* @param array $row The URL rewrite to persist |
476
|
|
|
* |
477
|
|
|
* @return void |
478
|
|
|
*/ |
479
|
3 |
|
protected function persistUrlRewrite($row) |
480
|
|
|
{ |
481
|
3 |
|
$this->getSubject()->persistUrlRewrite($row); |
482
|
3 |
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Return's the PK to create the product => attribute relation. |
486
|
|
|
* |
487
|
|
|
* @return integer The PK to create the relation with |
488
|
|
|
*/ |
489
|
3 |
|
protected function getPrimaryKey() |
490
|
|
|
{ |
491
|
3 |
|
return $this->getLastEntityId(); |
492
|
|
|
} |
493
|
|
|
} |
494
|
|
|
|
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.