Completed
Pull Request — master (#58)
by Tim
06:12
created

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