Completed
Push — master ( 5a5008...1e6e7a )
by Tim
10s
created

UrlRewriteObserver::getEntityId()   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
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
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\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
     * Process the observer's business logic.
77
     *
78
     * @return void
79
     */
80 3
    protected function process()
81
    {
82
83
        // query whether or not, we've found a new SKU => means we've found a new product
84 3
        if ($this->hasBeenProcessed($this->getValue(ColumnKeys::SKU))) {
85
            return;
86
        }
87
88
        // try to prepare the URL key, return immediately if not possible
89 3
        if (!$this->prepareUrlKey()) {
90
            return;
91
        }
92
93
        // initialize the store view code
94 3
        $this->prepareStoreViewCode();
95
96
        // prepare the URL rewrites
97 3
        $this->prepareUrlRewrites();
98
99
        // iterate over the categories and create the URL rewrites
100 3
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
101
            // initialize and persist the URL rewrite
102 3
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
103
                // initialize URL rewrite and catagory ID
104 3
                $this->categoryId = $categoryId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $categoryId can also be of type string. However, the property $categoryId is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
105 3
                $this->entityId = $urlRewrite[MemberNames::ENTITY_ID];
106 3
                $this->urlRewriteId = $this->persistUrlRewrite($urlRewrite);
0 ignored issues
show
Bug introduced by
The property urlRewriteId 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...
107
108
                // initialize and persist the URL rewrite product => category relation
109 3
                $attr = $this->prepareUrlRewriteProductCategoryAttributes();
110 3
                $this->persistUrlRewriteProductCategory($this->initializeUrlRewriteProductCategory($attr));
111
            }
112
        }
113 3
    }
114
115
    /**
116
     * Initialize the category product with the passed attributes and returns an instance.
117
     *
118
     * @param array $attr The category product attributes
119
     *
120
     * @return array The initialized category product
121
     */
122 2
    protected function initializeUrlRewrite(array $attr)
123
    {
124 2
        return $attr;
125
    }
126
127
    /**
128
     * Initialize the URL rewrite product => category relation with the passed attributes
129
     * and returns an instance.
130
     *
131
     * @param array $attr The URL rewrite product => category relation attributes
132
     *
133
     * @return array The initialized URL rewrite product => category relation
134
     */
135 3
    protected function initializeUrlRewriteProductCategory($attr)
136
    {
137 3
        return $attr;
138
    }
139
140
    /**
141
     * Prepare's the URL rewrites that has to be created/updated.
142
     *
143
     * @return void
144
     */
145 3
    protected function prepareUrlRewrites()
146
    {
147
148
        // (re-)initialize the array for the URL rewrites
149 3
        $this->urlRewrites = array();
150
151
        // load the root category, because we need that to create the default product URL rewrite
152 3
        $rootCategory = $this->getRootCategory();
153
154
        // add the root category ID to the category => product relations
155 3
        $productCategoryIds = $this->getProductCategoryIds();
156 3
        $productCategoryIds[$rootCategory[MemberNames::ENTITY_ID]] = $this->getLastEntityId();
157
158
        // prepare the URL rewrites
159 3
        foreach ($productCategoryIds as $categoryId => $entityId) {
160
            // set category/entity ID
161 3
            $this->categoryId = $categoryId;
162 3
            $this->entityId = $entityId;
0 ignored issues
show
Documentation Bug introduced by
The property $entityId was declared of type integer, but $entityId is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
163
164
            // prepare the attributes for each URL rewrite
165 3
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
166
        }
167 3
    }
168
169
    /**
170
     * Prepare's and set's the URL key from the passed row of the CSV file.
171
     *
172
     * @return boolean TRUE, if the URL key has been prepared, else FALSE
173
     */
174 3
    protected function prepareUrlKey()
175
    {
176
177
        // initialize the URL key
178 3
        $urlKey = null;
179
180
        // query whether or not we've a URL key available in the CSV file row
181 3
        if ($urlKeyFound = $this->getValue(ColumnKeys::URL_KEY)) {
182 3
            $urlKey = $urlKeyFound;
183
        }
184
185
        // query whether or not an URL key has been specified in the CSV file
186 3
        if (empty($urlKey)) {
187
            // initialize the product name
188
            $productName = null;
189
            // if not, try to use the product name
190
            if ($nameFound = $this->getValue(ColumnKeys::NAME)) {
191
                $productName = $nameFound;
192
            }
193
194
            // if nor URL key AND product name are empty, return immediately
195
            if (empty($productName)) {
196
                return false;
197
            }
198
199
            // initialize the URL key with product name
200
            $urlKey = $this->convertNameToUrlKey($productName);
201
        }
202
203
        // convert and set the URL key
204 3
        $this->urlKey = $urlKey;
205
206
        // return TRUE if the URL key has been prepared
207 3
        return true;
208
    }
209
210
    /**
211
     * Prepare the attributes of the entity that has to be persisted.
212
     *
213
     * @return array The prepared attributes
214
     */
215 3
    protected function prepareAttributes()
216
    {
217
218
        // load the store ID to use
219 3
        $storeId = $this->getRowStoreId();
220
221
        // load the category to create the URL rewrite for
222 3
        $category = $this->getCategory($this->categoryId);
223
224
        // initialize the values
225 3
        $requestPath = $this->prepareRequestPath($category);
226 3
        $targetPath = $this->prepareTargetPath($category);
227 3
        $metadata = serialize($this->prepareMetadata($category));
228
229
        // return the prepared URL rewrite
230 3
        return $this->initializeEntity(
231
            array(
232 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
233 3
                MemberNames::ENTITY_ID        => $this->entityId,
234 3
                MemberNames::REQUEST_PATH     => $requestPath,
235 3
                MemberNames::TARGET_PATH      => $targetPath,
236 3
                MemberNames::REDIRECT_TYPE    => 0,
237 3
                MemberNames::STORE_ID         => $storeId,
238
                MemberNames::DESCRIPTION      => null,
239 3
                MemberNames::IS_AUTOGENERATED => 1,
240 3
                MemberNames::METADATA         => $metadata
241
            )
242
        );
243
    }
244
245
    /**
246
     * Prepare's the URL rewrite product => category relation attributes.
247
     *
248
     * @return arry The prepared attributes
249
     */
250 3
    protected function prepareUrlRewriteProductCategoryAttributes()
251
    {
252
253
        // return the prepared product
254 3
        return $this->initializeEntity(
255
            array(
256 3
                MemberNames::PRODUCT_ID => $this->entityId,
257 3
                MemberNames::CATEGORY_ID => $this->categoryId,
258 3
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
259
            )
260
        );
261
    }
262
263
    /**
264
     * Prepare's the target path for a URL rewrite.
265
     *
266
     * @param array $category The categroy with the URL path
267
     *
268
     * @return string The target path
269
     */
270 3
    protected function prepareTargetPath(array $category)
271
    {
272
273
        // load the actual entity ID
274 3
        $lastEntityId = $this->getPrimaryKey();
275
276
        // query whether or not, the category is the root category
277 3
        if ($this->isRootCategory($category)) {
278 3
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
279
        } else {
280 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
281
        }
282
283
        // return the target path
284 3
        return $targetPath;
285
    }
286
287
    /**
288
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
289
     *
290
     * @param array $category The categroy with the URL path
291
     *
292
     * @return string The request path
293
     */
294 3
    protected function prepareRequestPath(array $category)
295
    {
296
297
        // query whether or not, the category is the root category
298 3
        if ($this->isRootCategory($category)) {
299 3
            $requestPath = sprintf('%s.html', $this->urlKey);
300
        } else {
301 2
            $requestPath = sprintf('%s/%s.html', $category[MemberNames::URL_PATH], $this->urlKey);
302
        }
303
304
        // return the request path
305 3
        return $requestPath;
306
    }
307
308
    /**
309
     * Prepare's the URL rewrite's metadata with the passed category values.
310
     *
311
     * @param array $category The category used for preparation
312
     *
313
     * @return array The metadata
314
     */
315 3
    protected function prepareMetadata(array $category)
316
    {
317
318
        // initialize the metadata
319 3
        $metadata = array();
320
321
        // query whether or not, the passed category IS the root category
322 3
        if ($this->isRootCategory($category)) {
323 3
            return $metadata;
324
        }
325
326
        // if not, set the category ID in the metadata
327 2
        $metadata['category_id'] = $category[MemberNames::ENTITY_ID];
328
329
        // return the metadata
330 2
        return $metadata;
331
    }
332
333
    /**
334
     * Initialize's and return's the URL key filter.
335
     *
336
     * @return \TechDivision\Import\Product\Utils\ConvertLiteralUrl The URL key filter
337
     */
338
    protected function getUrlKeyFilter()
339
    {
340
        return new ConvertLiteralUrl();
341
    }
342
343
    /**
344
     * Convert's the passed string into a valid URL key.
345
     *
346
     * @param string $string The string to be converted, e. g. the product name
347
     *
348
     * @return string The converted string as valid URL key
349
     */
350
    protected function convertNameToUrlKey($string)
351
    {
352
        return $this->getUrlKeyFilter()->filter($string);
353
    }
354
355
    /**
356
     * Return's the root category for the actual view store.
357
     *
358
     * @return array The store's root category
359
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
360
     */
361 3
    protected function getRootCategory()
362
    {
363 3
        return $this->getSubject()->getRootCategory();
364
    }
365
366
    /**
367
     * Return's TRUE if the passed category IS the root category, else FALSE.
368
     *
369
     * @param array $category The category to query
370
     *
371
     * @return boolean TRUE if the passed category IS the root category
372
     */
373 3
    protected function isRootCategory(array $category)
374
    {
375
376
        // load the root category
377 3
        $rootCategory = $this->getRootCategory();
378
379
        // compare the entity IDs and return the result
380 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
381
    }
382
383
    /**
384
     * Return's the store ID of the actual row, or of the default store
385
     * if no store view code is set in the CSV file.
386
     *
387
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
388
     *
389
     * @return integer The ID of the actual store
390
     * @throws \Exception Is thrown, if the store with the actual code is not available
391
     */
392 3
    protected function getRowStoreId($default = null)
393
    {
394 3
        return $this->getSubject()->getRowStoreId($default);
395
    }
396
397
    /**
398
     * Return's the list with category IDs the product is related with.
399
     *
400
     * @return array The product's category IDs
401
     */
402 3
    protected function getProductCategoryIds()
403
    {
404 3
        return $this->getSubject()->getProductCategoryIds();
405
    }
406
407
    /**
408
     * Return's the category with the passed ID.
409
     *
410
     * @param integer $categoryId The ID of the category to return
411
     *
412
     * @return array The category data
413
     */
414 3
    protected function getCategory($categoryId)
415
    {
416 3
        return $this->getSubject()->getCategory($categoryId);
417
    }
418
419
    /**
420
     * Persist's the URL rewrite with the passed data.
421
     *
422
     * @param array $row The URL rewrite to persist
423
     *
424
     * @return string The ID of the persisted entity
425
     */
426 3
    protected function persistUrlRewrite($row)
427
    {
428 3
        return $this->getSubject()->persistUrlRewrite($row);
429
    }
430
431
    /**
432
     * Persist's the URL rewrite product => category relation with the passed data.
433
     *
434
     * @param array $row The URL rewrite product => category relation to persist
435
     *
436
     * @return void
437
     */
438 3
    protected function persistUrlRewriteProductCategory($row)
439
    {
440 3
        $this->getSubject()->persistUrlRewriteProductCategory($row);
441 3
    }
442
443
    /**
444
     * Return's the PK to create the product => attribute relation.
445
     *
446
     * @return integer The PK to create the relation with
447
     */
448 3
    protected function getPrimaryKey()
449
    {
450 3
        return $this->getLastEntityId();
451
    }
452
}
453