Skip to content

Commit 69dbf03

Browse files
authored
Merge branch '4.6' into fix-nameschema-parenthesis
2 parents 67f9ce3 + 6285f76 commit 69dbf03

10 files changed

Lines changed: 156 additions & 61 deletions

File tree

phpstan-baseline-7.4.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,6 @@ parameters:
150150
count: 1
151151
path: src/lib/FieldType/Selection/SearchField.php
152152

153-
-
154-
message: '#^Parameter \#1 \$str of function mb_substr expects string, string\|false given\.$#'
155-
identifier: argument.type
156-
count: 1
157-
path: src/lib/FieldType/TextBlock/SearchField.php
158-
159153
-
160154
message: '#^Parameter \#1 \$str of function mb_substr expects string, string\|false given\.$#'
161155
identifier: argument.type

phpstan-baseline-doctrine-persistence-v3.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
parameters:
22
ignoreErrors:
3-
-
4-
message: '#^Call to function method_exists\(\) with Doctrine\\ORM\\EntityManagerInterface and ''isUninitializedObje…'' will always evaluate to true\.$#'
5-
identifier: function.alreadyNarrowedType
6-
count: 1
7-
path: src/lib/Persistence/Doctrine/SiteAccessAwareEntityManager.php
8-
93
-
104
message: '#^Method Doctrine\\Persistence\\ObjectManager\:\:clear\(\) invoked with 1 parameter, 0 required\.$#'
115
identifier: arguments.count

phpstan-baseline-gte-8.0.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,6 @@ parameters:
162162
count: 1
163163
path: src/lib/FieldType/Selection/SearchField.php
164164

165-
-
166-
message: '#^Parameter \#1 \$string of function mb_substr expects string, string\|false given\.$#'
167-
identifier: argument.type
168-
count: 1
169-
path: src/lib/FieldType/TextBlock/SearchField.php
170-
171165
-
172166
message: '#^Parameter \#1 \$string of function mb_substr expects string, string\|false given\.$#'
173167
identifier: argument.type

phpstan-baseline.neon

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11946,12 +11946,6 @@ parameters:
1194611946
count: 1
1194711947
path: src/lib/FieldType/StorageGateway.php
1194811948

11949-
-
11950-
message: '#^Parameter \#1 \$string of method Ibexa\\Core\\FieldType\\TextBlock\\SearchField\:\:extractShortText\(\) expects string, array\|bool\|float\|int\|string\|null given\.$#'
11951-
identifier: argument.type
11952-
count: 1
11953-
path: src/lib/FieldType/TextBlock/SearchField.php
11954-
1195511949
-
1195611950
message: '#^Access to an undefined property Ibexa\\Contracts\\Core\\FieldType\\Value\:\:\$text\.$#'
1195711951
identifier: property.notFound
@@ -18084,7 +18078,6 @@ parameters:
1808418078
count: 1
1808518079
path: src/lib/Persistence/Legacy/Content/FieldValue/Converter/DateAndTimeConverter.php
1808618080

18087-
1808818081
-
1808918082
message: '#^Method Ibexa\\Core\\Persistence\\Legacy\\Content\\FieldValue\\Converter\\DateAndTimeConverter\:\:toFieldDefinition\(\) has no return type specified\.$#'
1809018083
identifier: missingType.return
@@ -26173,12 +26166,6 @@ parameters:
2617326166
count: 1
2617426167
path: src/lib/Search/Common/FieldValueMapper/MultipleStringMapper.php
2617526168

26176-
-
26177-
message: '#^Method Ibexa\\Core\\Search\\Common\\FieldValueMapper\\StringMapper\:\:convert\(\) should return string but returns string\|null\.$#'
26178-
identifier: return.type
26179-
count: 1
26180-
path: src/lib/Search/Common/FieldValueMapper/StringMapper.php
26181-
2618226169
-
2618326170
message: '#^Method Ibexa\\Core\\Search\\Common\\IncrementalIndexer\:\:createSearchIndex\(\) has no return type specified\.$#'
2618426171
identifier: missingType.return
@@ -32305,7 +32292,6 @@ parameters:
3230532292
count: 1
3230632293
path: tests/integration/Core/Repository/Common/SlugConverter.php
3230732294

32308-
3230932295
-
3231032296
message: '#^Cannot call method getValue\(\) on Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Field\|null\.$#'
3231132297
identifier: method.nonObject
@@ -41930,7 +41916,6 @@ parameters:
4193041916
count: 1
4193141917
path: tests/integration/Core/Repository/ObjectStateServiceTest.php
4193241918

41933-
4193441919
-
4193541920
message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\ObjectStateServiceTest\:\:assertObjectsLoadedByIdentifiers\(\) has no return type specified\.$#'
4193641921
identifier: missingType.return
@@ -41961,7 +41946,6 @@ parameters:
4196141946
count: 1
4196241947
path: tests/integration/Core/Repository/ObjectStateServiceTest.php
4196341948

41964-
4196541949
-
4196641950
message: '#^Method Ibexa\\Tests\\Integration\\Core\\Repository\\ObjectStateServiceTest\:\:deleteExistingObjectStateGroups\(\) has no return type specified\.$#'
4196741951
identifier: missingType.return
@@ -66760,12 +66744,6 @@ parameters:
6676066744
count: 1
6676166745
path: tests/lib/Persistence/Legacy/User/Role/LimitationConverterTest.php
6676266746

66763-
-
66764-
message: '#^Instanceof between Ibexa\\Contracts\\Core\\Persistence\\User\\Policy and Ibexa\\Contracts\\Core\\Persistence\\User\\Policy will always evaluate to true\.$#'
66765-
identifier: instanceof.alwaysTrue
66766-
count: 1
66767-
path: tests/lib/Persistence/Legacy/User/UserHandlerTest.php
66768-
6676966747
-
6677066748
message: '#^Method Ibexa\\Tests\\Core\\Persistence\\Legacy\\User\\UserHandlerTest\:\:createRole\(\) has no return type specified\.$#'
6677166749
identifier: missingType.return
@@ -67084,12 +67062,6 @@ parameters:
6708467062
count: 5
6708567063
path: tests/lib/Persistence/Legacy/User/UserHandlerTest.php
6708667064

67087-
-
67088-
message: '#^Right side of && is always true\.$#'
67089-
identifier: booleanAnd.rightAlwaysTrue
67090-
count: 1
67091-
path: tests/lib/Persistence/Legacy/User/UserHandlerTest.php
67092-
6709367065
-
6709467066
message: '#^Cannot call method fetchAll\(\) on Doctrine\\DBAL\\ForwardCompatibility\\Result\|int\|string\.$#'
6709567067
identifier: method.nonObject

src/lib/FieldType/TextBlock/SearchField.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function getIndexData(Field $field, FieldDefinition $fieldDefinition)
2121
return [
2222
new Search\Field(
2323
'value',
24-
$this->extractShortText($field->value->data),
24+
$this->extractText($field->value->data),
2525
new Search\FieldType\StringField()
2626
),
2727
new Search\Field(
@@ -33,15 +33,17 @@ public function getIndexData(Field $field, FieldDefinition $fieldDefinition)
3333
}
3434

3535
/**
36-
* Extracts short snippet of the given $string.
37-
*
38-
* @param string $string
39-
*
40-
* @return string
36+
* @param mixed $string
4137
*/
42-
private function extractShortText($string)
38+
private function extractText($string): string
4339
{
44-
return mb_substr(strtok(trim((string)$string), "\r\n"), 0, 255);
40+
if (!is_string($string)) {
41+
return '';
42+
}
43+
44+
$lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $string));
45+
46+
return implode(' ', array_map('trim', $lines));
4547
}
4648

4749
public function getIndexDefinition()

src/lib/Resources/settings/search_engines/field_value_mappers.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ services:
6262
- { name: ibexa.search.common.field_value.mapper, maps: Ibexa\Contracts\Core\Search\FieldType\PriceField }
6363

6464
Ibexa\Core\Search\Common\FieldValueMapper\StringMapper:
65+
autoconfigure: true
6566
class: Ibexa\Core\Search\Common\FieldValueMapper\StringMapper
6667
tags:
6768
- { name: ibexa.search.common.field_value.mapper, maps: Ibexa\Contracts\Core\Search\FieldType\StringField }

src/lib/Search/Common/FieldValueMapper/StringMapper.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,27 @@
99
use Ibexa\Contracts\Core\Search\Field;
1010
use Ibexa\Contracts\Core\Search\FieldType;
1111
use Ibexa\Core\Search\Common\FieldValueMapper;
12+
use Psr\Log\LoggerAwareInterface;
13+
use Psr\Log\LoggerAwareTrait;
14+
use Psr\Log\LoggerInterface;
15+
use Psr\Log\NullLogger;
1216

1317
/**
1418
* Common string field value mapper implementation.
1519
*/
16-
class StringMapper extends FieldValueMapper
20+
class StringMapper extends FieldValueMapper implements LoggerAwareInterface
1721
{
22+
use LoggerAwareTrait;
23+
24+
public function __construct(
25+
?LoggerInterface $logger = null
26+
) {
27+
$this->logger = $logger ?? new NullLogger();
28+
}
29+
1830
public const REPLACE_WITH_SPACE_PATTERN = '([\x09\x0B\x0C]+)';
1931
public const REMOVE_PATTERN = '([\x00-\x08\x0E-\x1F]+)';
32+
public const MAX_TERM_LENGTH = 32766;
2033

2134
public function canMap(Field $field): bool
2235
{
@@ -43,11 +56,28 @@ protected function convert($value): string
4356
);
4457

4558
// Remove non-printable characters.
46-
return preg_replace(
59+
$value = preg_replace(
4760
self::REMOVE_PATTERN,
4861
'',
4962
(string)$value
5063
);
64+
65+
// Enforce Lucene's bytes MAX_TERM_LENGTH to avoid silent indexing failures
66+
$value = (string)$value;
67+
$truncated = mb_strcut($value, 0, self::MAX_TERM_LENGTH);
68+
69+
if (strlen($truncated) < strlen($value)) {
70+
$this->logger->warning(
71+
sprintf(
72+
'String field value was truncated from %d to %d bytes (max term length: %d).',
73+
strlen($value),
74+
strlen($truncated),
75+
self::MAX_TERM_LENGTH
76+
)
77+
);
78+
}
79+
80+
return $truncated;
5181
}
5282
}
5383

tests/integration/Core/Repository/FieldType/TextBlockIntegrationTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,24 +287,24 @@ public function providerForTestIsNotEmptyValue()
287287

288288
protected function getValidSearchValueOne()
289289
{
290-
return 'caution is the " path to mediocrity' . PHP_EOL . 'something completely different';
290+
return 'caution is the " path to mediocrity something completely different';
291291
}
292292

293293
protected function getSearchTargetValueOne()
294294
{
295295
// ensure case-insensitivity
296-
return strtoupper('caution is the " path to mediocrity');
296+
return strtoupper('caution is the " path to mediocrity something completely different');
297297
}
298298

299299
protected function getValidSearchValueTwo()
300300
{
301-
return "truth suffers from ' too much analysis\n hello and goodbye";
301+
return "truth suffers from ' too much analysis hello and goodbye";
302302
}
303303

304304
protected function getSearchTargetValueTwo()
305305
{
306306
// ensure case-insensitivity
307-
return strtoupper("truth suffers from ' too much analysis");
307+
return strtoupper("truth suffers from ' too much analysis hello and goodbye");
308308
}
309309

310310
protected function getFullTextIndexedFieldData()

tests/lib/Search/Common/FieldValueMapper/RemoteIdentifierMapperTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Ibexa\Contracts\Core\Search\FieldType\StringField;
1616
use Ibexa\Core\Search\Common\FieldValueMapper\RemoteIdentifierMapper;
1717
use Ibexa\Tests\Core\Search\TestCase;
18+
use Psr\Log\LoggerInterface;
1819

1920
/**
2021
* @covers \Ibexa\Core\Search\Common\FieldValueMapper\RemoteIdentifierMapper
@@ -26,7 +27,9 @@ final class RemoteIdentifierMapperTest extends TestCase
2627

2728
protected function setUp(): void
2829
{
29-
$this->mapper = new RemoteIdentifierMapper();
30+
$this->mapper = new RemoteIdentifierMapper(
31+
$this->createMock(LoggerInterface::class)
32+
);
3033
}
3134

3235
/**
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\Core\Search\Common\FieldValueMapper;
10+
11+
use Ibexa\Contracts\Core\Search\Field;
12+
use Ibexa\Contracts\Core\Search\FieldType\StringField;
13+
use Ibexa\Core\Search\Common\FieldValueMapper\StringMapper;
14+
use PHPUnit\Framework\TestCase;
15+
16+
/**
17+
* @covers \Ibexa\Core\Search\Common\FieldValueMapper\StringMapper
18+
*/
19+
final class StringMapperTest extends TestCase
20+
{
21+
private StringMapper $mapper;
22+
23+
protected function setUp(): void
24+
{
25+
$this->mapper = new StringMapper();
26+
}
27+
28+
public function testCanMap(): void
29+
{
30+
$field = $this->createFieldWithValue('hello', new StringField());
31+
32+
self::assertTrue($this->mapper->canMap($field));
33+
}
34+
35+
public function testMapsPlainString(): void
36+
{
37+
$field = $this->createFieldWithValue('hello world', new StringField());
38+
39+
self::assertSame('hello world', $this->mapper->map($field));
40+
}
41+
42+
public function testStripsNonPrintableCharacters(): void
43+
{
44+
$field = $this->createFieldWithValue("hello\x01\x02world", new StringField());
45+
46+
self::assertSame('helloworld', $this->mapper->map($field));
47+
}
48+
49+
public function testReplacesTabAndVerticalWhitespaceWithSpace(): void
50+
{
51+
$field = $this->createFieldWithValue("hello\x09world\x0Bfoo", new StringField());
52+
53+
self::assertSame('hello world foo', $this->mapper->map($field));
54+
}
55+
56+
public function testTruncatesToMaxTermLength(): void
57+
{
58+
$longValue = str_repeat('a', StringMapper::MAX_TERM_LENGTH + 100);
59+
$field = $this->createFieldWithValue($longValue, new StringField());
60+
$result = $this->mapper->map($field);
61+
62+
self::assertSame(StringMapper::MAX_TERM_LENGTH, strlen($result));
63+
}
64+
65+
public function testTruncatesMultibyteStringAtCharacterBoundary(): void
66+
{
67+
// Each UTF-8 character here is 3 bytes (€ = E2 82 AC).
68+
// Fill up to just past the limit so truncation must happen on a char boundary.
69+
$char = '';
70+
$charBytes = strlen($char);
71+
72+
self::assertSame(3, $charBytes);
73+
74+
$count = (int) ceil((StringMapper::MAX_TERM_LENGTH + $charBytes) / $charBytes);
75+
$longValue = str_repeat($char, $count);
76+
77+
$field = $this->createFieldWithValue($longValue, new StringField());
78+
$result = $this->mapper->map($field);
79+
80+
self::assertSame(StringMapper::MAX_TERM_LENGTH, strlen($result));
81+
// Result must be valid UTF-8 (no split mid-character).
82+
self::assertSame(1, preg_match('//u', $result));
83+
}
84+
85+
public function testValueWithinLimitIsNotTruncated(): void
86+
{
87+
$value = str_repeat('a', StringMapper::MAX_TERM_LENGTH);
88+
$field = $this->createFieldWithValue($value, new StringField());
89+
90+
self::assertSame($value, $this->mapper->map($field));
91+
}
92+
93+
private function createFieldWithValue(string $value, StringField $type): Field
94+
{
95+
$field = $this->createMock(Field::class);
96+
$field
97+
->method('getValue')
98+
->willReturn($value);
99+
$field
100+
->method('getType')
101+
->willReturn($type);
102+
103+
return $field;
104+
}
105+
}

0 commit comments

Comments
 (0)