Completed
Push — test-EZP-26707-issearchable-fu... ( 965a07...6812c2 )
by
unknown
33:45 queued 07:39
created

Handler::publishUrlAliasForLocation()   D

Complexity

Conditions 13
Paths 76

Size

Total Lines 116
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 68
nc 76
nop 6
dl 0
loc 116
rs 4.9922
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * File containing the UrlAlias Handler.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias;
12
13
use eZ\Publish\SPI\Persistence\Content\UrlAlias\Handler as UrlAliasHandlerInterface;
14
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
16
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
17
use eZ\Publish\Core\Base\Exceptions\ForbiddenException;
18
19
/**
20
 * The UrlAlias Handler provides nice urls management.
21
 *
22
 * Its methods operate on a representation of the url alias data structure held
23
 * inside a storage engine.
24
 */
25
class Handler implements UrlAliasHandlerInterface
26
{
27
    const ROOT_LOCATION_ID = 1;
28
29
    /**
30
     * This is intentionally hardcoded for now as:
31
     * 1. We don't implement this configuration option.
32
     * 2. Such option should not be in this layer, should be handled higher up.
33
     *
34
     * @deprecated
35
     */
36
    const CONTENT_REPOSITORY_ROOT_LOCATION_ID = 2;
37
38
    /**
39
     * UrlAlias Gateway.
40
     *
41
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway
42
     */
43
    protected $gateway;
44
45
    /**
46
     * Gateway for handling location data.
47
     *
48
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway
49
     */
50
    protected $locationGateway;
51
52
    /**
53
     * UrlAlias Mapper.
54
     *
55
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Mapper
56
     */
57
    protected $mapper;
58
59
    /**
60
     * Caching language handler.
61
     *
62
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler
63
     */
64
    protected $languageHandler;
65
66
    /**
67
     * URL slug converter.
68
     *
69
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter
70
     */
71
    protected $slugConverter;
72
73
    /**
74
     * Creates a new UrlAlias Handler.
75
     *
76
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway $gateway
77
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Mapper $mapper
78
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway $locationGateway
79
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
80
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter $slugConverter
81
     */
82
    public function __construct(
83
        Gateway $gateway,
84
        Mapper $mapper,
85
        LocationGateway $locationGateway,
86
        LanguageHandler $languageHandler,
87
        SlugConverter $slugConverter
88
    ) {
89
        $this->gateway = $gateway;
90
        $this->mapper = $mapper;
91
        $this->locationGateway = $locationGateway;
92
        $this->languageHandler = $languageHandler;
0 ignored issues
show
Documentation Bug introduced by
$languageHandler is of type object<eZ\Publish\SPI\Pe...ntent\Language\Handler>, but the property $languageHandler was declared to be of type object<eZ\Publish\Core\P...anguage\CachingHandler>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
93
        $this->slugConverter = $slugConverter;
94
    }
95
96
    /**
97
     * This method creates or updates an urlalias from a new or changed content name in a language
98
     * (if published). It also can be used to create an alias for a new location of content.
99
     * On update the old alias is linked to the new one (i.e. a history alias is generated).
100
     *
101
     * $alwaysAvailable controls whether the url alias is accessible in all
102
     * languages.
103
     *
104
     * @param mixed $locationId
105
     * @param mixed $parentLocationId
106
     * @param string $name the new name computed by the name schema or url alias schema
107
     * @param string $languageCode
108
     * @param bool $alwaysAvailable
109
     * @param bool $updatePathIdentificationString legacy storage specific for updating ezcontentobject_tree.path_identification_string
110
     */
111
    public function publishUrlAliasForLocation(
112
        $locationId,
113
        $parentLocationId,
114
        $name,
115
        $languageCode,
116
        $alwaysAvailable = false,
117
        $updatePathIdentificationString = false
118
    ) {
119
        $parentId = $this->getRealAliasId($parentLocationId);
120
        $uniqueCounter = $this->slugConverter->getUniqueCounterValue($name, $parentId == 0);
121
        $name = $this->slugConverter->convert($name, 'location_' . $locationId);
122
        $languageId = $this->languageHandler->loadByLanguageCode($languageCode)->id;
123
        $languageMask = $languageId | (int)$alwaysAvailable;
124
        $action = 'eznode:' . $locationId;
125
        $cleanup = false;
126
127
        // Exiting the loop with break;
128
        while (true) {
129
            $newText = '';
130
            if ($locationId != self::CONTENT_REPOSITORY_ROOT_LOCATION_ID) {
0 ignored issues
show
Deprecated Code introduced by
The constant eZ\Publish\Core\Persiste...SITORY_ROOT_LOCATION_ID has been deprecated.

This class constant has been deprecated.

Loading history...
131
                $newText = $name . ($uniqueCounter > 1 ? $uniqueCounter : '');
132
            }
133
            $newTextMD5 = $this->getHash($newText);
134
135
            // Try to load existing entry
136
            $row = $this->gateway->loadRow($parentId, $newTextMD5);
137
138
            // If nothing was returned insert new entry
139
            if (empty($row)) {
140
                // Check for existing active location entry on this level and reuse it's id
141
                $existingLocationEntry = $this->gateway->loadAutogeneratedEntry($action, $parentId);
142
                if (!empty($existingLocationEntry)) {
143
                    $cleanup = true;
144
                    $newId = $existingLocationEntry['id'];
145
                } else {
146
                    $newId = null;
147
                }
148
149
                $newId = $this->gateway->insertRow(
150
                    array(
151
                        'id' => $newId,
152
                        'link' => $newId,
153
                        'parent' => $parentId,
154
                        'action' => $action,
155
                        'lang_mask' => $languageMask,
156
                        'text' => $newText,
157
                        'text_md5' => $newTextMD5,
158
                    )
159
                );
160
161
                break;
162
            }
163
164
            // Row exists, check if it is reusable. There are 3 cases when this is possible:
165
            // 1. NOP entry
166
            // 2. existing location or custom alias entry
167
            // 3. history entry
168
            if ($row['action'] == 'nop:' || $row['action'] == $action || $row['is_original'] == 0) {
169
                // Check for existing location entry on this level, if it exists and it's id differs from reusable
170
                // entry id then reusable entry should be updated with the existing location entry id.
171
                // Note: existing location entry may be downgraded and relinked later, depending on its language.
172
                $existingLocationEntry = $this->gateway->loadAutogeneratedEntry($action, $parentId);
173
                $newId = $row['id'];
174
                if (!empty($existingLocationEntry)) {
175
                    if ($existingLocationEntry['id'] != $row['id']) {
176
                        $cleanup = true;
177
                        $newId = $existingLocationEntry['id'];
178
                    } else {
179
                        // If we are reusing existing location entry merge existing language mask
180
                        $languageMask |= ($row['lang_mask'] & ~1);
181
                    }
182
                }
183
                $this->gateway->updateRow(
184
                    $parentId,
185
                    $newTextMD5,
186
                    array(
187
                        'action' => $action,
188
                        // In case when NOP row was reused
189
                        'action_type' => 'eznode',
190
                        'lang_mask' => $languageMask,
191
                        // Updating text ensures that letter case changes are stored
192
                        'text' => $newText,
193
                        // Set "id" and "link" for case when reusable entry is history
194
                        'id' => $newId,
195
                        'link' => $newId,
196
                        // Entry should be active location entry (original and not alias).
197
                        // Note: this takes care of taking over custom alias entry for the location on the same level
198
                        // and with same name and action.
199
                        'alias_redirects' => 1,
200
                        'is_original' => 1,
201
                        'is_alias' => 0,
202
                    )
203
                );
204
205
                break;
206
            }
207
208
            // If existing row is not reusable, increment $uniqueCounter and try again
209
            $uniqueCounter += 1;
210
        }
211
212
        if ($updatePathIdentificationString) {
213
            $this->locationGateway->updatePathIdentificationString(
214
                $locationId,
215
                $parentLocationId,
216
                $this->slugConverter->convert($newText, 'node_' . $locationId, 'urlalias_compat')
0 ignored issues
show
Bug introduced by
The variable $newText does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
217
            );
218
        }
219
220
        /* @var $newId */
221
        /* @var $newTextMD5 */
222
        // Note: cleanup does not touch custom and global entries
223
        if ($cleanup) {
224
            $this->gateway->cleanupAfterPublish($action, $languageId, $newId, $parentId, $newTextMD5);
0 ignored issues
show
Bug introduced by
The variable $newId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $newTextMD5 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
225
        }
226
    }
227
228
    /**
229
     * Create a user chosen $alias pointing to $locationId in $languageCode.
230
     *
231
     * If $languageCode is null the $alias is created in the system's default
232
     * language. $alwaysAvailable makes the alias available in all languages.
233
     *
234
     * @param mixed $locationId
235
     * @param string $path
236
     * @param bool $forwarding
237
     * @param string $languageCode
238
     * @param bool $alwaysAvailable
239
     *
240
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias
241
     */
242
    public function createCustomUrlAlias($locationId, $path, $forwarding = false, $languageCode = null, $alwaysAvailable = false)
243
    {
244
        return $this->createUrlAlias(
245
            'eznode:' . $locationId,
246
            $path,
247
            $forwarding,
248
            $languageCode,
249
            $alwaysAvailable
250
        );
251
    }
252
253
    /**
254
     * Create a user chosen $alias pointing to a resource in $languageCode.
255
     * This method does not handle location resources - if a user enters a location target
256
     * the createCustomUrlAlias method has to be used.
257
     *
258
     * If $languageCode is null the $alias is created in the system's default
259
     * language. $alwaysAvailable makes the alias available in all languages.
260
     *
261
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException if the path already exists for the given language
262
     *
263
     * @param string $resource
264
     * @param string $path
265
     * @param bool $forwarding
266
     * @param string $languageCode
267
     * @param bool $alwaysAvailable
268
     *
269
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias
270
     */
271
    public function createGlobalUrlAlias($resource, $path, $forwarding = false, $languageCode = null, $alwaysAvailable = false)
272
    {
273
        return $this->createUrlAlias(
274
            $resource,
275
            $path,
276
            $forwarding,
277
            $languageCode,
278
            $alwaysAvailable
279
        );
280
    }
281
282
    /**
283
     * Internal method for creating global or custom URL alias (these are handled in the same way).
284
     *
285
     * @throws \eZ\Publish\Core\Base\Exceptions\ForbiddenException if the path already exists for the given language
286
     *
287
     * @param string $action
288
     * @param string $path
289
     * @param bool $forward
290
     * @param string|null $languageCode
291
     * @param bool $alwaysAvailable
292
     *
293
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias
294
     */
295
    protected function createUrlAlias($action, $path, $forward, $languageCode, $alwaysAvailable)
296
    {
297
        $pathElements = explode('/', $path);
298
        $topElement = array_pop($pathElements);
299
        $languageId = $this->languageHandler->loadByLanguageCode($languageCode)->id;
300
        $parentId = 0;
301
302
        // Handle all path elements except topmost one
303
        $isPathNew = false;
304
        foreach ($pathElements as $level => $pathElement) {
305
            $pathElement = $this->slugConverter->convert($pathElement, 'noname' . ($level + 1));
306
            $pathElementMD5 = $this->getHash($pathElement);
307
            if (!$isPathNew) {
308
                $row = $this->gateway->loadRow($parentId, $pathElementMD5);
309
                if (empty($row)) {
310
                    $isPathNew = true;
311
                } else {
312
                    $parentId = $row['link'];
313
                }
314
            }
315
316
            if ($isPathNew) {
317
                $parentId = $this->insertNopEntry($parentId, $pathElement, $pathElementMD5);
318
            }
319
        }
320
321
        // Handle topmost path element
322
        $topElement = $this->slugConverter->convert($topElement, 'noname' . (count($pathElements) + 1));
323
324
        // If last (next to topmost) entry parent is special root entry we handle topmost entry as first level entry
325
        // That is why we need to reset $parentId to 0
326
        if ($parentId != 0 && $this->gateway->isRootEntry($parentId)) {
327
            $parentId = 0;
328
        }
329
330
        $topElementMD5 = $this->getHash($topElement);
331
        // Set common values for two cases below
332
        $data = array(
333
            'action' => $action,
334
            'is_alias' => 1,
335
            'alias_redirects' => $forward ? 1 : 0,
336
            'parent' => $parentId,
337
            'text' => $topElement,
338
            'text_md5' => $topElementMD5,
339
            'is_original' => 1,
340
        );
341
        // Try to load topmost element
342
        if (!$isPathNew) {
343
            $row = $this->gateway->loadRow($parentId, $topElementMD5);
344
        }
345
346
        // If nothing was returned perform insert
347
        if ($isPathNew || empty($row)) {
348
            $data['lang_mask'] = $languageId | (int)$alwaysAvailable;
349
            $id = $this->gateway->insertRow($data);
350
        } elseif ($row['action'] == 'nop:' || $row['is_original'] == 0) {
351
            // Row exists, check if it is reusable. There are 2 cases when this is possible:
352
            // 1. NOP entry
353
            // 2. history entry
354
            $data['lang_mask'] = $languageId | (int)$alwaysAvailable;
355
            // If history is reused move link to id
356
            $data['link'] = $id = $row['id'];
357
            $this->gateway->updateRow(
358
                $parentId,
359
                $topElementMD5,
360
                $data
361
            );
362
        } else {
363
            throw new ForbiddenException("Path '%path%' already exists for the given language", ['%path%' => $path]);
364
        }
365
366
        $data['raw_path_data'] = $this->gateway->loadPathData($id);
367
368
        return $this->mapper->extractUrlAliasFromData($data);
369
    }
370
371
    /**
372
     * Convenience method for inserting nop type row.
373
     *
374
     * @param mixed $parentId
375
     * @param string $text
376
     * @param string $textMD5
377
     *
378
     * @return mixed
379
     */
380
    protected function insertNopEntry($parentId, $text, $textMD5)
381
    {
382
        return $this->gateway->insertRow(
383
            array(
384
                'lang_mask' => 1,
385
                'action' => 'nop:',
386
                'parent' => $parentId,
387
                'text' => $text,
388
                'text_md5' => $textMD5,
389
            )
390
        );
391
    }
392
393
    /**
394
     * List of user generated or autogenerated url entries, pointing to $locationId.
395
     *
396
     * @param mixed $locationId
397
     * @param bool $custom if true the user generated aliases are listed otherwise the autogenerated
398
     *
399
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias[]
400
     */
401 View Code Duplication
    public function listURLAliasesForLocation($locationId, $custom = false)
402
    {
403
        $data = $this->gateway->loadLocationEntries($locationId, $custom);
404
        foreach ($data as &$entry) {
405
            $entry['raw_path_data'] = $this->gateway->loadPathData($entry['id']);
406
        }
407
408
        return $this->mapper->extractUrlAliasListFromData($data);
409
    }
410
411
    /**
412
     * List global aliases.
413
     *
414
     * @param string|null $languageCode
415
     * @param int $offset
416
     * @param int $limit
417
     *
418
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias[]
419
     */
420 View Code Duplication
    public function listGlobalURLAliases($languageCode = null, $offset = 0, $limit = -1)
421
    {
422
        $data = $this->gateway->listGlobalEntries($languageCode, $offset, $limit);
423
        foreach ($data as &$entry) {
424
            $entry['raw_path_data'] = $this->gateway->loadPathData($entry['id']);
425
        }
426
427
        return $this->mapper->extractUrlAliasListFromData($data);
428
    }
429
430
    /**
431
     * Removes url aliases.
432
     *
433
     * Autogenerated aliases are not removed by this method.
434
     *
435
     * @param \eZ\Publish\SPI\Persistence\Content\UrlAlias[] $urlAliases
436
     *
437
     * @return bool
438
     */
439
    public function removeURLAliases(array $urlAliases)
440
    {
441
        foreach ($urlAliases as $urlAlias) {
442
            if ($urlAlias->isCustom) {
443
                list($parentId, $textMD5) = explode('-', $urlAlias->id);
444
                if (!$this->gateway->removeCustomAlias($parentId, $textMD5)) {
445
                    return false;
446
                }
447
            }
448
        }
449
450
        return true;
451
    }
452
453
    /**
454
     * Looks up a url alias for the given url.
455
     *
456
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
457
     * @throws \RuntimeException
458
     * @throws \eZ\Publish\Core\Base\Exceptions\NotFoundException
459
     *
460
     * @param string $url
461
     *
462
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias
463
     */
464
    public function lookup($url)
465
    {
466
        $urlHashes = array();
467
        foreach (explode('/', $url) as $level => $text) {
468
            $urlHashes[$level] = $this->getHash($text);
469
        }
470
471
        $data = $this->gateway->loadUrlAliasData($urlHashes);
472
        if (empty($data)) {
473
            throw new NotFoundException('URLAlias', $url);
474
        }
475
476
        $pathDepth = count($urlHashes);
477
        $hierarchyData = array();
478
        $isPathHistory = false;
479
        for ($level = 0; $level < $pathDepth; ++$level) {
480
            $prefix = $level === $pathDepth - 1 ? '' : 'ezurlalias_ml' . $level . '_';
481
            $isPathHistory = $isPathHistory ?: ($data[$prefix . 'link'] != $data[$prefix . 'id']);
482
            $hierarchyData[$level] = array(
483
                'id' => $data[$prefix . 'id'],
484
                'parent' => $data[$prefix . 'parent'],
485
                'action' => $data[$prefix . 'action'],
486
            );
487
        }
488
489
        $data['is_path_history'] = $isPathHistory;
490
        $data['raw_path_data'] = ($data['action_type'] == 'eznode' && !$data['is_alias'])
491
            ? $this->gateway->loadPathDataByHierarchy($hierarchyData)
492
            : $this->gateway->loadPathData($data['id']);
493
494
        return $this->mapper->extractUrlAliasFromData($data);
495
    }
496
497
    /**
498
     * Loads URL alias by given $id.
499
     *
500
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
501
     *
502
     * @param string $id
503
     *
504
     * @return \eZ\Publish\SPI\Persistence\Content\UrlAlias
505
     */
506
    public function loadUrlAlias($id)
507
    {
508
        list($parentId, $textMD5) = explode('-', $id);
509
        $data = $this->gateway->loadRow($parentId, $textMD5);
510
511
        if (empty($data)) {
512
            throw new NotFoundException('URLAlias', $id);
513
        }
514
515
        $data['raw_path_data'] = $this->gateway->loadPathData($data['id']);
516
517
        return $this->mapper->extractUrlAliasFromData($data);
518
    }
519
520
    /**
521
     * Notifies the underlying engine that a location has moved.
522
     *
523
     * This method triggers the change of the autogenerated aliases.
524
     *
525
     * @param mixed $locationId
526
     * @param mixed $oldParentId
527
     * @param mixed $newParentId
528
     */
529
    public function locationMoved($locationId, $oldParentId, $newParentId)
530
    {
531
        // @todo optimize: $newLocationAliasId is already available in self::publishUrlAliasForLocation() as $newId
532
        $newParentLocationAliasId = $this->getRealAliasId($newParentId);
533
        $newLocationAlias = $this->gateway->loadAutogeneratedEntry(
534
            'eznode:' . $locationId,
535
            $newParentLocationAliasId
536
        );
537
538
        $oldParentLocationAliasId = $this->getRealAliasId($oldParentId);
539
        $oldLocationAlias = $this->gateway->loadAutogeneratedEntry(
540
            'eznode:' . $locationId,
541
            $oldParentLocationAliasId
542
        );
543
544
        // Historize alias for old location
545
        $this->gateway->historizeId($oldLocationAlias['id'], $newLocationAlias['id']);
546
        // Reparent subtree of old location to new location
547
        $this->gateway->reparent($oldLocationAlias['id'], $newLocationAlias['id']);
548
    }
549
550
    /**
551
     * Notifies the underlying engine that a location has moved.
552
     *
553
     * This method triggers the creation of the autogenerated aliases for the copied locations
554
     *
555
     * @param mixed $locationId
556
     * @param mixed $oldParentId
0 ignored issues
show
Bug introduced by
There is no parameter named $oldParentId. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
557
     * @param mixed $newParentId
558
     */
559
    public function locationCopied($locationId, $newLocationId, $newParentId)
560
    {
561
        $newParentAliasId = $this->getRealAliasId($newLocationId);
562
        $oldParentAliasId = $this->getRealAliasId($locationId);
563
564
        $actionMap = $this->getCopiedLocationsMap($locationId, $newLocationId);
565
566
        $this->copySubtree(
567
            $actionMap,
568
            $oldParentAliasId,
569
            $newParentAliasId,
570
            $locationId
571
        );
572
    }
573
574
    /**
575
     * Returns possibly corrected alias id for given $locationId !! For use as parent id in logic.
576
     *
577
     * First level entries must have parent id set to 0 instead of their parent location alias id.
578
     * There are two cases when alias id needs to be corrected:
579
     * 1) location is special location without URL alias (location with id=1 in standard installation)
580
     * 2) location is site root location, having special root entry in the ezurlalias_ml table (location with id=2
581
     *    in standard installation)
582
     *
583
     * @param mixed $locationId
584
     *
585
     * @return mixed
586
     */
587
    protected function getRealAliasId($locationId)
588
    {
589
        // Absolute root location does have a url alias entry so we can skip lookup
590
        if ($locationId == self::ROOT_LOCATION_ID) {
591
            return 0;
592
        }
593
594
        $data = $this->gateway->loadAutogeneratedEntry('eznode:' . $locationId);
595
596
        // Root entries (URL wise) can return 0 as the returned value is used as parent (parent is 0 for root entries)
597
        if (empty($data) || $data['id'] != 0 && $data['parent'] == 0 && strlen($data['text']) == 0) {
598
            $id = 0;
599
        } else {
600
            $id = $data['id'];
601
        }
602
603
        return $id;
604
    }
605
606
    /**
607
     * Recursively copies aliases from old parent under new parent.
608
     *
609
     * @param array $actionMap
610
     * @param mixed $oldParentAliasId
611
     * @param mixed $newParentAliasId
612
     */
613
    protected function copySubtree($actionMap, $oldParentAliasId, $newParentAliasId)
614
    {
615
        $rows = $this->gateway->loadAutogeneratedEntries($oldParentAliasId);
616
        $newIdsMap = array();
617
        foreach ($rows as $row) {
618
            $oldParentAliasId = $row['id'];
619
620
            // Ensure that same action entries remain grouped by the same id
621
            if (!isset($newIdsMap[$oldParentAliasId])) {
622
                $newIdsMap[$oldParentAliasId] = $this->gateway->getNextId();
623
            }
624
625
            $row['action'] = $actionMap[$row['action']];
626
            $row['parent'] = $newParentAliasId;
627
            $row['id'] = $row['link'] = $newIdsMap[$oldParentAliasId];
628
            $this->gateway->insertRow($row);
629
630
            $this->copySubtree(
631
                $actionMap,
632
                $oldParentAliasId,
633
                $row['id']
634
            );
635
        }
636
    }
637
638
    /**
639
     * @param mixed $oldParentId
640
     * @param mixed $newParentId
641
     *
642
     * @return array
643
     */
644
    protected function getCopiedLocationsMap($oldParentId, $newParentId)
645
    {
646
        $originalLocations = $this->locationGateway->getSubtreeContent($oldParentId);
647
        $copiedLocations = $this->locationGateway->getSubtreeContent($newParentId);
648
649
        $map = array();
650
        foreach ($originalLocations as $index => $originalLocation) {
651
            $map['eznode:' . $originalLocation['node_id']] = 'eznode:' . $copiedLocations[$index]['node_id'];
652
        }
653
654
        return $map;
655
    }
656
657
    /**
658
     * Notifies the underlying engine that a location was deleted or moved to trash.
659
     *
660
     * @param mixed $locationId
661
     */
662
    public function locationDeleted($locationId)
663
    {
664
        $action = 'eznode:' . $locationId;
665
        $entry = $this->gateway->loadAutogeneratedEntry($action);
666
667
        $this->removeSubtree($entry['id'], $action, $entry['is_original']);
668
    }
669
670
    /**
671
     * Recursively removes aliases by given $id and $action.
672
     *
673
     * $original parameter is used to limit removal of moved Location aliases to history entries only.
674
     *
675
     * @param mixed $id
676
     * @param string $action
677
     * @param mixed $original
678
     */
679
    protected function removeSubtree($id, $action, $original)
680
    {
681
        // Remove first to avoid unnecessary recursion.
682
        if ($original) {
683
            // If entry is original remove all for action (history and custom entries included).
684
            $this->gateway->remove($action);
685
        } else {
686
            // Else entry is history, so remove only for action with the id.
687
            // This means $id grouped history entries are removed, other history, active autogenerated
688
            // and custom are left alone.
689
            $this->gateway->remove($action, $id);
690
        }
691
692
        // Load all autogenerated for parent $id, including history.
693
        $entries = $this->gateway->loadAutogeneratedEntries($id, true);
694
695
        foreach ($entries as $entry) {
696
            $this->removeSubtree($entry['id'], $entry['action'], $entry['is_original']);
697
        }
698
    }
699
700
    /**
701
     * @param string $text
702
     *
703
     * @return string
704
     */
705
    protected function getHash($text)
706
    {
707
        return md5(mb_strtolower($text, 'UTF-8'));
708
    }
709
}
710