Skip to content

Commit ec16b11

Browse files
committed
Fix: percent-encode paths when rewriting cached Portal Engine thumbnail URLs after a move
rewriteChildrenIndexPaths() rewrites the cached thumbnail URL for moved assets/data-objects in the search index, but only special-cased spaces (" " -> "%20") when matching the path prefix. Portal Engine's frontend thumbnail URLs are built via urlencode_ignore_slash() (rawurlencode() per path segment), which percent-encodes any non-unreserved character, including accented letters (e.g. "é" -> "%C3%A9"). So for any moved folder containing such characters, the prefix match silently failed and the cached thumbnail URL was left pointing at the pre-move path - breaking images in Portal Engine while the Studio backend (which re-fetches thumbnails live rather than reading the cached field) appeared unaffected. Pass a fully percent-encoded path variant into the Painless script and match/replace against it as a fallback, instead of only handling spaces. PEES-1245
1 parent 0451469 commit ec16b11

2 files changed

Lines changed: 92 additions & 7 deletions

File tree

src/SearchIndexAdapter/DefaultSearch/PathService.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ private function updatePath(string $indexName, string $currentPath, string $newP
124124
'params' => [
125125
'currentPath' => $currentPath . '/',
126126
'newPath' => $newPath . '/',
127+
// Portal Engine's frontend thumbnail URLs are built via
128+
// urlencode_ignore_slash() (rawurlencode() per path segment), so accented
129+
// and other non-unreserved characters end up percent-encoded there. Provide
130+
// the equivalently-encoded path so the script can match/replace that form too.
131+
'currentPathEncoded' => $this->encodePathSegments($currentPath) . '/',
132+
'newPathEncoded' => $this->encodePathSegments($newPath) . '/',
127133
'now' => date('c'),
128134
],
129135
],
@@ -188,15 +194,14 @@ private function getScriptSource(): string
188194
}
189195
}
190196
191-
String encodedCurrentPath = params.currentPath.replace(" ", "%20");
192197
String thumbPath = thumb.substring(pathStart);
193-
194-
if(thumbPath.startsWith(encodedCurrentPath) || thumbPath.startsWith(params.currentPath)) {
195-
String matchedPath = thumbPath.startsWith(encodedCurrentPath) ?
196-
encodedCurrentPath : params.currentPath;
198+
199+
if(thumbPath.startsWith(params.currentPathEncoded) || thumbPath.startsWith(params.currentPath)) {
200+
boolean matchedEncoded = thumbPath.startsWith(params.currentPathEncoded);
201+
String matchedPath = matchedEncoded ? params.currentPathEncoded : params.currentPath;
197202
String remainingPath = thumbPath.substring(matchedPath.length());
198-
String encodedNewPath = params.newPath.replace(" ", "%20");
199-
customFields.thumbnail = prefix + encodedNewPath + remainingPath;
203+
String replacementPath = matchedEncoded ? params.newPathEncoded : params.newPath;
204+
customFields.thumbnail = prefix + replacementPath + remainingPath;
200205
}
201206
202207
}
@@ -218,6 +223,15 @@ private function getScriptSource(): string
218223
ctx._source.system_fields.checksum = 0';
219224
}
220225

226+
/**
227+
* Mirrors urlencode_ignore_slash() (rawurlencode() applied per path segment), which is how
228+
* Portal Engine builds its frontend thumbnail URLs (ThumbnailService::getThumbnailPath()).
229+
*/
230+
private function encodePathSegments(string $path): string
231+
{
232+
return implode('/', array_map('rawurlencode', explode('/', $path)));
233+
}
234+
221235
private function countDocumentsByPath(string $indexName, string $path): int
222236
{
223237
$countResult = $this->client->search([
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\GenericDataIndexBundle\Tests\Unit\SearchIndexAdapter\DefaultSearch;
15+
16+
use Codeception\Test\Unit;
17+
use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\DefaultSearch\PathService;
18+
use Pimcore\Bundle\GenericDataIndexBundle\Service\SearchIndex\IndexService\ElementTypeAdapter\AdapterServiceInterface;
19+
use Pimcore\Bundle\GenericDataIndexBundle\Service\SearchIndex\SearchIndexConfigServiceInterface;
20+
use Pimcore\SearchClient\SearchClientInterface;
21+
use ReflectionMethod;
22+
23+
/**
24+
* Regression coverage for PEES-1245: Portal Engine's frontend thumbnail URLs are built via
25+
* urlencode_ignore_slash() (rawurlencode() per path segment), so a path containing accented or
26+
* other non-unreserved characters is stored percent-encoded in the search index. When such a
27+
* folder is moved, rewriteChildrenIndexPaths() must be able to match and rewrite that encoded
28+
* form as well as the plain form, otherwise the cached thumbnail URL is left stale.
29+
*
30+
* @internal
31+
*/
32+
final class PathServiceTest extends Unit
33+
{
34+
public function testEncodePathSegmentsEncodesAccentedCharactersButKeepsSlashes(): void
35+
{
36+
$this->assertSame(
37+
'/caf%C3%A9/sub-folder',
38+
$this->encodePathSegments('/café/sub-folder')
39+
);
40+
}
41+
42+
public function testEncodePathSegmentsEncodesSpaces(): void
43+
{
44+
$this->assertSame(
45+
'/my%20folder/asset',
46+
$this->encodePathSegments('/my folder/asset')
47+
);
48+
}
49+
50+
public function testEncodePathSegmentsLeavesUnreservedCharactersUntouched(): void
51+
{
52+
$this->assertSame(
53+
'/Folder-1/asset_2.jpg',
54+
$this->encodePathSegments('/Folder-1/asset_2.jpg')
55+
);
56+
}
57+
58+
private function encodePathSegments(string $path): string
59+
{
60+
$service = new PathService(
61+
$this->makeEmpty(SearchClientInterface::class),
62+
$this->makeEmpty(AdapterServiceInterface::class),
63+
$this->makeEmpty(SearchIndexConfigServiceInterface::class),
64+
);
65+
66+
$method = new ReflectionMethod(PathService::class, 'encodePathSegments');
67+
$method->setAccessible(true);
68+
69+
return $method->invoke($service, $path);
70+
}
71+
}

0 commit comments

Comments
 (0)