-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathUrlKeyUtil.php
More file actions
311 lines (274 loc) · 13.4 KB
/
Copy pathUrlKeyUtil.php
File metadata and controls
311 lines (274 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<?php
/**
* TechDivision\Import\Utils\UrlKeyUtil
*
* PHP version 7
*
* @author Tim Wagner <t.wagner@techdivision.com>
* @copyright 2021 TechDivision GmbH <info@techdivision.com>
* @license https://opensource.org/licenses/MIT
* @link https://github.com/techdivision/import
* @link http://www.techdivision.com
*/
namespace TechDivision\Import\Utils;
use TechDivision\Import\Loaders\LoaderInterface;
use TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface;
use TechDivision\Import\Services\UrlKeyAwareProcessorInterface;
/**
* Utility class that provides functionality to make URL keys unique.
*
* @author Tim Wagner <t.wagner@techdivision.com>
* @copyright 2021 TechDivision GmbH <info@techdivision.com>
* @license https://opensource.org/licenses/MIT
* @link https://github.com/techdivision/import
* @link http://www.techdivision.com
*/
class UrlKeyUtil implements UrlKeyUtilInterface
{
/**
* The URL key aware processor instance.
*
* \TechDivision\Import\Services\UrlKeyAwareProcessorInterface
*/
protected $urlKeyAwareProcessor;
/**
* The array with the entity type and store view specific suffixes.
*
* @var array
*/
protected $suffixes = array();
/**
* The URL rewrite entity type to use.
*
* @var \TechDivision\Import\Utils\EnumInterface
*/
protected $urlRewriteEntityType;
/**
* The array with the entity type code > configuration key mapping.
*
* @var array
*/
protected $entityTypeCodeToConfigKeyMapping = array(
EntityTypeCodes::CATALOG_PRODUCT => CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX,
EntityTypeCodes::CATALOG_CATEGORY => CoreConfigDataKeys::CATALOG_SEO_CATEGORY_URL_SUFFIX
);
/**
* Construct a new instance.
*
* @param \TechDivision\Import\Services\UrlKeyAwareProcessorInterface $urlKeyAwareProcessor The URL key aware processor instance
* @param \TechDivision\Import\Loaders\LoaderInterface $coreConfigDataLoader The core config data loader instance
* @param \TechDivision\Import\Loaders\LoaderInterface $storeIdLoader The core config data loader instance
* @param \TechDivision\Import\Utils\EnumInterface $urlRewriteEntityType The URL rewrite entity type to use
*/
public function __construct(
UrlKeyAwareProcessorInterface $urlKeyAwareProcessor,
LoaderInterface $coreConfigDataLoader,
LoaderInterface $storeIdLoader,
EnumInterface $urlRewriteEntityType
) {
// initialize the URL kew aware processor instance
$this->urlKeyAwareProcessor = $urlKeyAwareProcessor;
$this->urlRewriteEntityType = $urlRewriteEntityType;
// load the available stores
$storeIds = $storeIdLoader->load();
// initialize the URL suffixs from the Magento core configuration
foreach ($storeIds as $storeId) {
// prepare the array with the entity type and store ID specific suffixes
foreach ($this->entityTypeCodeToConfigKeyMapping as $entityTypeCode => $configKey) {
// load the suffix for the given entity type => configuration key and store ID
$suffix = $coreConfigDataLoader->load($configKey, '.html', ScopeKeys::SCOPE_DEFAULT, $storeId);
// register the suffux in the array
$this->suffixes[$entityTypeCode][$storeId] = $suffix;
}
}
}
/**
* Returns the URL key aware processor instance.
*
* @return \TechDivision\Import\Services\UrlKeyAwareProcessorInterface The processor instance
*/
protected function getUrlKeyAwareProcessor()
{
return $this->urlKeyAwareProcessor;
}
/**
* Load's and return's the URL rewrite for the given request path and store ID
*
* @param string $requestPath The request path to load the URL rewrite for
* @param int $storeId The store ID to load the URL rewrite for
*
* @return array|null The URL rewrite found for the given request path and store ID
*/
protected function loadUrlRewriteByRequestPathAndStoreId(string $requestPath, int $storeId)
{
return $this->getUrlKeyAwareProcessor()->loadUrlRewriteByRequestPathAndStoreId($requestPath, $storeId);
}
/**
* Make's the passed URL key unique by adding/raising a number to the end.
*
* @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject The subject to make the URL key unique for
* @param array $entity The entity to make the URL key unique for
* @param string $urlKey The URL key to make unique
* @param string|null $urlPath The URL path to make unique (only used for categories)
*
* @return string The unique URL key
*/
protected function doMakeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, ?string $urlPath = null) : string
{
// initialize the store view ID, use the default store view if no store view has
// been set, because the default url_key value has been set in default store view
$storeId = (int) $subject->getRowStoreId();
$entityTypeCode = $subject->getEntityTypeCode();
// initialize entity ID + type from the passed entity
$entityId = (int) $entity[MemberNames::ENTITY_ID];
$entityType = (string) $this->urlRewriteEntityType;
// initialize the counter
$counter = 0;
// initialize the counters
$matchingCounters = array();
$notMatchingCounters = array();
// pre-initialze the URL by concatenating path and/or key to query for
$url = $urlPath ? sprintf('%s/%s', $urlPath, $urlKey) : $urlKey;
do {
// prepare the request path to load an existing URL rewrite
$requestPath = sprintf('%s%s', $url, $this->suffixes[$entityTypeCode][$storeId]);
// try to load an existing URL rewrite
$urlRewrite = $this->loadUrlRewriteByRequestPathAndStoreId($requestPath, $storeId);
// query whether or not an entity with the given
// request path and store ID is available
if ($urlRewrite) {
// if yes, query if this IS the URL key of the passed entity
if (((int) $urlRewrite[MemberNames::ENTITY_ID] === $entityId || (int) $urlRewrite[MemberNames::ENTITY_ID] < 0) &&
((int) $urlRewrite[MemberNames::STORE_ID] === $storeId) &&
$urlRewrite[MemberNames::ENTITY_TYPE] === $entityType
) {
// Possibly clean an old cache entry with the correct ID at Multi Store Setup.
if ((int) $urlRewrite[MemberNames::ENTITY_ID] < 0) {
$this->getUrlKeyAwareProcessor()->persistUrlRewrite(
array(
MemberNames::URL_REWRITE_ID => md5(sprintf('%d-%s', $storeId, $requestPath)),
MemberNames::REDIRECT_TYPE => 0,
MemberNames::STORE_ID => $storeId,
MemberNames::ENTITY_ID => $entityId,
MemberNames::REQUEST_PATH => $requestPath,
MemberNames::ENTITY_TYPE => $entityType,
EntityStatus::MEMBER_NAME => EntityStatus::STATUS_CREATE
)
);
}
// add the matching counter
$matchingCounters[] = $counter;
// stop further processing here, because we've a matching
// URL key and that's all we want for the moment
break;
} else {
$notMatchingCounters[] = $counter;
}
// prepare the next URL key to query for
$url = sprintf('%s-%d', $urlKey, ++$counter);
} else {
// we've temporary persist a dummy URL rewrite to keep track of the new URL key, e. g. for
// the case the import contains another product or category that wants to use the same one
$this->getUrlKeyAwareProcessor()->persistUrlRewrite(
array(
MemberNames::URL_REWRITE_ID => md5(sprintf('%d-%s', $storeId, $requestPath)),
MemberNames::REDIRECT_TYPE => 0,
MemberNames::STORE_ID => $storeId,
MemberNames::ENTITY_ID => $entityId,
MemberNames::REQUEST_PATH => $requestPath,
MemberNames::ENTITY_TYPE => $entityType,
EntityStatus::MEMBER_NAME => EntityStatus::STATUS_CREATE
)
);
}
} while ($urlRewrite);
// sort the array ascending according to the counter
asort($matchingCounters);
asort($notMatchingCounters);
// this IS the URL key of the passed entity => we've an UPDATE
if (sizeof($matchingCounters) > 0) {
// load highest counter
$counter = end($matchingCounters);
// if the counter is > 0, we've to append it to the new URL key
if ($counter > 0) {
$urlKey = sprintf('%s-%d', $urlKey, $counter);
}
} elseif (sizeof($notMatchingCounters) > 0) {
// load the last entry that contains the
// the last NOT matching counter
$newCounter = end($notMatchingCounters);
// create a new URL key by raising the counter
$urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
}
// return the passed URL key, if NOT
return $urlKey;
}
/**
* Make's the passed URL key unique by adding the next number to the end.
*
* @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject The subject to make the URL key unique for
* @param array $entity The entity to make the URL key unique for
* @param string $urlKey The URL key to make unique
* @param array $urlPaths The URL paths to make unique
*
* @return string The unique URL key
*/
public function makeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, array $urlPaths = array()) : string
{
// in general, we want to start at -1, because if NO URL paths has been given
// e. g. we've a product or a root category, we want to make sure that we've
// no URL collisions.
$i = -1;
// only in case we've a category AND URL paths have been given, we start at 0,
// because the we always want to make sure that also the URL path will be taken
// into account when we make the URL key unique.
if ($this->urlRewriteEntityType->equals(UrlRewriteEntityType::CATEGORY) && sizeof($urlPaths) > 0) {
$i = 0;
}
// iterate over the passed URL paths
// and try to find a unique URL key
for ($i; $i < sizeof($urlPaths); $i++) {
// try to make the URL key unique for the given URL path
$proposedUrlKey = $this->doMakeUnique($subject, $entity, $urlKey, isset($urlPaths[$i]) ? $urlPaths[$i] : null);
// if the URL key is NOT the same as the passed one or with the parent URL path
// it can NOT be used, so we've to persist it temporarily and try it again for
// all the other URL paths until we found one that works with every URL path
if ($urlKey !== $proposedUrlKey) {
// temporarily persist the URL key
$urlKey = $proposedUrlKey;
// reset the counter and restart the
// iteration with the first URL path
$i = -2;
}
}
// return the unique URL key
return $urlKey;
}
/**
* Load the url_key if exists
*
* @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject The subject to make the URL key unique for
* @param int $primaryKeyId The ID from category or product
*
* @return string|null The URL key
*/
public function loadUrlKey(UrlKeyAwareSubjectInterface $subject, $primaryKeyId)
{
// initialize the entity type ID
$entityType = $subject->getEntityType();
$entityTypeId = (int)$entityType[MemberNames::ENTITY_TYPE_ID];
// initialize the store view ID, use the admin store view if no store view has
// been set, because the default url_key value has been set in admin store view
$storeId = $subject->getRowStoreId(StoreViewCodes::ADMIN);
// try to load the attribute
$attribute = $this->getUrlKeyAwareProcessor()
->loadVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndPrimaryKey(
MemberNames::URL_KEY,
$entityTypeId,
$storeId,
$primaryKeyId
);
// return the attribute value or null, if not available
return $attribute ? $attribute['value'] : null;
}
}