Completed
Push — master ( 381fd2...7bc757 )
by Tim
12s queued 10s
created

UrlRewriteUpdateObserver::getMetadata()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 9
cts 9
cp 1
rs 9.536
c 0
b 0
f 0
cc 3
nc 4
nop 1
crap 3
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\UrlRewrite\Observers\UrlRewriteUpdateObserver
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-url-rewrite
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\UrlRewrite\Observers;
22
23
use TechDivision\Import\Utils\StoreViewCodes;
24
use TechDivision\Import\Product\Utils\CoreConfigDataKeys;
25
use TechDivision\Import\Product\UrlRewrite\Utils\MemberNames;
26
use TechDivision\Import\Product\UrlRewrite\Utils\ColumnKeys;
27
use TechDivision\Import\Product\UrlRewrite\Utils\ConfigurationKeys;
28
use TechDivision\Import\Product\UrlRewrite\Utils\SqlStatementKeys;
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-url-rewrite
37
 * @link      http://www.techdivision.com
38
 */
39
class UrlRewriteUpdateObserver extends UrlRewriteObserver
40
{
41
42
    /**
43
     * Array with the existing URL rewrites of the actual product.
44
     *
45
     * @var array
46
     */
47
    protected $existingUrlRewrites = array();
48
49
    /**
50
     * Process the observer's business logic.
51
     *
52
     * @return void
53
     * @see \TechDivision\Import\Product\UrlRewrite\Observers\UrlRewriteObserver::process()
54
     */
55 1
    protected function process()
56
    {
57
58
        // process the new URL rewrites first
59 1
        parent::process();
60
61
        // load the root category
62 1
        $rootCategory = $this->getRootCategory();
63
64
        // create redirect URL rewrites for the existing URL rewrites
65 1
        foreach ($this->existingUrlRewrites as $existingUrlRewrite) {
66
            // query whether or not 301 redirects have to be created, so don't create redirects
67
            // if the product is NOT visible or the rewrite history has been deactivated
68 1
            if ($this->isVisible() && $this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_SAVE_REWRITES_HISTORY, true)) {
69
                // initialize the data with the URL rewrites new 301 configuration
70 1
                $attr = array(MemberNames::REDIRECT_TYPE => 301);
71
72
                // initialize the category with the root category
73 1
                $category = $rootCategory;
74
75
                // load the metadata from the existing URL rewrite
76 1
                $metadata = $this->getMetadata($existingUrlRewrite);
77
78
                // query whether or not the URL key of the existing URL rewrite has changed
79 1
                if (is_array($metadata) && isset($metadata[UrlRewriteObserver::CATEGORY_ID])) {
80 1
                    if (isset($this->urlRewrites[$metadata[UrlRewriteObserver::CATEGORY_ID]])) {
81
                        try {
82
                            // if yes, try to load the original category and OVERRIDE the default category
83 1
                            $category = $this->getCategory($metadata[UrlRewriteObserver::CATEGORY_ID], $this->getValue(ColumnKeys::STORE_VIEW_CODE));
84
                        } catch (\Exception $e) {
85
                            // if the old category can NOT be loaded, remove the
86
                            // category ID from the URL rewrites metadata
87
                            $attr[MemberNames::METADATA] = null;
88
89
                            // finally log a warning that the old category is not available ony more
90
                            $this->getSubject()
91
                                 ->getSystemLogger()
92
                                 ->warning(
93
                                     sprintf(
94
                                         'Category with ID "%d" is not longer available for URL rewrite with ID "%d"',
95
                                         $metadata[UrlRewriteObserver::CATEGORY_ID],
96
                                         $existingUrlRewrite[MemberNames::URL_REWRITE_ID]
97
                                     )
98
                                 );
99
                        }
100
                    }
101
                }
102
103
                // load target path/metadata for the actual category
104 1
                $targetPath = $this->prepareRequestPath($category);
105
106
                // skip update of URL rewrite, if nothing to change
107 1
                if ($targetPath === $existingUrlRewrite[MemberNames::TARGET_PATH] &&
108 1
                    301 === (int)$existingUrlRewrite[MemberNames::REDIRECT_TYPE]) {
109
                    // stop processing the URL rewrite
110
                    continue;
111
                }
112
113
                // skip update of URL rewrite, if resulting new target path EQUALS old request path
114 1
                if ($targetPath === $existingUrlRewrite[MemberNames::REQUEST_PATH]) {
115
                    // finally log a warning that the old category is not available ony more
116
                    $this->getSubject()
117
                         ->getSystemLogger()
118
                         ->warning(
119
                             sprintf(
120
                                 'New target path "%s" eqals request path for URL rewrite with ID "%d"',
121
                                 $existingUrlRewrite[MemberNames::REQUEST_PATH],
122
                                 $existingUrlRewrite[MemberNames::URL_REWRITE_ID]
123
                             )
124
                         );
125
126
                    // stop processing the URL rewrite
127
                    continue;
128
                }
129
130
                // set the target path
131 1
                $attr[MemberNames::TARGET_PATH] = $targetPath;
132
133
                // merge and return the prepared URL rewrite
134 1
                $existingUrlRewrite = $this->mergeEntity($existingUrlRewrite, $attr);
135
136
                // create the URL rewrite
137 1
                $this->persistUrlRewrite($existingUrlRewrite);
138
            } else {
139
                // query whether or not the URL rewrite has to be removed
140
                if ($this->getSubject()->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_URL_REWRITES) &&
141
                    $this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::CLEAN_UP_URL_REWRITES)
142
                ) {
143
                    // delete the existing URL rewrite
144
                    $this->deleteUrlRewrite(
145
                        array(MemberNames::URL_REWRITE_ID => $existingUrlRewrite[MemberNames::URL_REWRITE_ID]),
146
                        SqlStatementKeys::DELETE_URL_REWRITE
147
                    );
148
149
                    // log a message, that old URL rewrites have been cleaned-up
150
                    $this->getSubject()
151
                         ->getSystemLogger()
152
                         ->warning(
153
                             sprintf(
154
                                 'Cleaned-up URL rewrite "%s" for product with SKU "%s"',
155
                                 $existingUrlRewrite[MemberNames::REQUEST_PATH],
156
                                 $this->getValue(ColumnKeys::SKU)
157
                             )
158
                         );
159
                }
160
            }
161
        }
162 1
    }
163
164
    /**
165
     * Remove's the passed URL rewrite from the existing one's.
166
     *
167
     * @param array $urlRewrite The URL rewrite to remove
168
     *
169
     * @return void
170
     */
171
    protected function removeExistingUrlRewrite(array $urlRewrite)
172
    {
173
174
        // load request path
175
        $requestPath = $urlRewrite[MemberNames::REQUEST_PATH];
176
177
        // query whether or not the URL rewrite exists and remove it, if available
178
        if (isset($this->existingUrlRewrites[$requestPath])) {
179
            unset($this->existingUrlRewrites[$requestPath]);
180
        }
181
    }
182
183
    /**
184
     * Prepare's the URL rewrites that has to be created/updated.
185
     *
186
     * @return void
187
     * @see \TechDivision\Import\Product\UrlRewrite\Observers\UrlRewriteObserver::prepareUrlRewrites()
188
     */
189 1
    protected function prepareUrlRewrites()
190
    {
191
192
        // (re-)initialize the array for the existing URL rewrites
193 1
        $this->existingUrlRewrites = array();
194
195
        // prepare the new URL rewrites first
196 1
        parent::prepareUrlRewrites();
197
198
        // load the store ID to use
199 1
        $storeId = $this->getSubject()->getRowStoreId();
0 ignored issues
show
Bug introduced by
The method getRowStoreId() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getRow()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
200
201
        // load the existing URL rewrites of the actual entity
202 1
        $existingUrlRewrites = $this->getUrlRewritesByEntityTypeAndEntityIdAndStoreId(
203 1
            UrlRewriteObserver::ENTITY_TYPE,
204 1
            $this->entityId,
205 1
            $storeId
206
        );
207
208
        // prepare the existing URL rewrites to improve searching them by request path
209 1
        foreach ($existingUrlRewrites as $existingUrlRewrite) {
210 1
            $this->existingUrlRewrites[$existingUrlRewrite[MemberNames::REQUEST_PATH]] = $existingUrlRewrite;
211
        }
212 1
    }
213
214
    /**
215
     * Initialize the category product with the passed attributes and returns an instance.
216
     *
217
     * @param array $attr The category product attributes
218
     *
219
     * @return array The initialized category product
220
     */
221 1
    protected function initializeUrlRewrite(array $attr)
222
    {
223
224
        // load the category ID of the passed URL rewrite entity
225 1
        $categoryId = $this->getCategoryIdFromMetadata($attr);
226
227
        // iterate over the available URL rewrites to find the one that matches the category ID
228 1
        foreach ($this->existingUrlRewrites as $urlRewrite) {
229
            // compare the category IDs AND the request path
230 1
            if ($categoryId === $this->getCategoryIdFromMetadata($urlRewrite) &&
231 1
                $attr[MemberNames::REQUEST_PATH] === $urlRewrite[MemberNames::REQUEST_PATH]
232
            ) {
233
                // if a URL rewrite has been found, do NOT create OR keep an existing redirect
234
                $this->removeExistingUrlRewrite($urlRewrite);
235
236
                // if the found URL rewrite has been autogenerated, then update it
237
                return $this->mergeEntity($urlRewrite, $attr);
238
            }
239
        }
240
241
        // simple return the attributes
242 1
        return $attr;
243
    }
244
245
    /**
246
     * Extracts the category ID of the passed URL rewrite entity, if available, and return's it.
247
     *
248
     * @param array $attr The URL rewrite entity to extract and return the category ID for
249
     *
250
     * @return integer|null The category ID if available, else NULL
251
     */
252 1
    protected function getCategoryIdFromMetadata(array $attr)
253
    {
254
255
        // load the metadata of the passed URL rewrite entity
256 1
        $metadata = $this->getMetadata($attr);
257
258
        // return the category ID from the metadata
259 1
        return (integer) $metadata[UrlRewriteObserver::CATEGORY_ID];
260
    }
261
262
    /**
263
     * Initialize the URL rewrite product => category relation with the passed attributes
264
     * and returns an instance.
265
     *
266
     * @param array $attr The URL rewrite product => category relation attributes
267
     *
268
     * @return array|null The initialized URL rewrite product => category relation
269
     */
270 1
    protected function initializeUrlRewriteProductCategory($attr)
271
    {
272
273
        // try to load the URL rewrite product category relation
274 1
        if ($urlRewriteProductCategory = $this->loadUrlRewriteProductCategory($attr[MemberNames::URL_REWRITE_ID])) {
275
            return $this->mergeEntity($urlRewriteProductCategory, $attr);
276
        }
277
278
        // simple return the URL rewrite product category
279 1
        return $attr;
280
    }
281
282
    /**
283
     * Return's the unserialized metadata of the passed URL rewrite. If the
284
     * metadata doesn't contain a category ID, the category ID of the root
285
     * category will be added.
286
     *
287
     * @param array $urlRewrite The URL rewrite to return the metadata for
288
     *
289
     * @return array The metadata of the passed URL rewrite
290
     */
291 1
    protected function getMetadata($urlRewrite)
292
    {
293
294
        // initialize the array with the metaddata
295 1
        $metadata = array();
296
297
        // try to unserialize the metadata from the passed URL rewrite
298 1
        if (isset($urlRewrite[MemberNames::METADATA])) {
299 1
            $metadata = json_decode($urlRewrite[MemberNames::METADATA], true);
300
        }
301
302
        // query whether or not a category ID has been found
303 1
        if (isset($metadata[UrlRewriteObserver::CATEGORY_ID])) {
304
            // if yes, return the metadata
305 1
            return $metadata;
306
        }
307
308
        // if not, append the ID of the root category
309 1
        $rootCategory = $this->getRootCategory();
310 1
        $metadata[UrlRewriteObserver::CATEGORY_ID] = (integer) $rootCategory[MemberNames::ENTITY_ID];
311
312
        // and return the metadata
313 1
        return $metadata;
314
    }
315
316
    /**
317
     * Return's the category with the passed ID.
318
     *
319
     * @param integer $categoryId    The ID of the category to return
320
     * @param string  $storeViewCode The store view code of the category to return, defaults to "admin"
321
     *
322
     * @return array The category data
323
     */
324 1
    protected function getCategory($categoryId, $storeViewCode = StoreViewCodes::ADMIN)
325
    {
326 1
        return $this->getSubject()->getCategory($categoryId, $storeViewCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getCategory() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...jects\UrlRewriteSubject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
327
    }
328
329
    /**
330
     * Return's the URL rewrites for the passed URL entity type and ID.
331
     *
332
     * @param string  $entityType The entity type to load the URL rewrites for
333
     * @param integer $entityId   The entity ID to load the URL rewrites for
334
     * @param integer $storeId    The store ID to load the URL rewrites for
335
     *
336
     * @return array The URL rewrites
337
     */
338 1
    protected function getUrlRewritesByEntityTypeAndEntityIdAndStoreId($entityType, $entityId, $storeId)
339
    {
340 1
        return $this->getProductUrlRewriteProcessor()->getUrlRewritesByEntityTypeAndEntityIdAndStoreId($entityType, $entityId, $storeId);
341
    }
342
343
    /**
344
     * Return's the URL rewrite product category relation for the passed
345
     * URL rewrite ID.
346
     *
347
     * @param integer $urlRewriteId The URL rewrite ID to load the URL rewrite product category relation for
348
     *
349
     * @return array|false The URL rewrite product category relations
350
     */
351 1
    protected function loadUrlRewriteProductCategory($urlRewriteId)
352
    {
353 1
        return $this->getProductUrlRewriteProcessor()->loadUrlRewriteProductCategory($urlRewriteId);
354
    }
355
356
    /**
357
     * Delete's the URL rewrite with the passed attributes.
358
     *
359
     * @param array       $row  The attributes of the entity to delete
360
     * @param string|null $name The name of the prepared statement that has to be executed
361
     *
362
     * @return void
363
     */
364
    protected function deleteUrlRewrite($row, $name = null)
365
    {
366
        $this->getProductUrlRewriteProcessor()->deleteUrlRewrite($row, $name);
367
    }
368
}
369