Skip to content

Commit 3d744c4

Browse files
committed
Add getFullNameWithNickname() for old-style nickname display
Webtrees ≤ 1.x rendered '1 NAME Martin /White/' + '2 NICK Chalky' as 'Martin "Chalky" White' by injecting the nickname between given names and surname. Webtrees 2.x dropped that automatic injection, so chart modules built on this base only show 'Martin White' even when a NICK is present in the GEDCOM. Add an opt-in NameProcessor method that reads the NICK attribute from the primary NAME fact and reproduces the old-style display without modifying the GEDCOM. Idempotent when the nickname is already inline. Chart consumers can call this instead of getFullName() when their tree owner wants the legacy display back, see issue magicsunday/webtrees-descendants-chart#79.
1 parent b89df62 commit 3d744c4

2 files changed

Lines changed: 122 additions & 0 deletions

File tree

src/Processor/NameProcessor.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,83 @@ public function getFullName(): string
182182
return $this->replacePlaceholders($this->primaryName[self::FULL_NAME_WITH_PLACEHOLDERS]);
183183
}
184184

185+
/**
186+
* Returns the GEDCOM `2 NICK` value of the individual's primary NAME fact, or
187+
* an empty string when no nickname is set. The lookup walks all NAME facts and
188+
* returns the first NICK it finds.
189+
*
190+
* @return string
191+
*/
192+
public function getNickname(): string
193+
{
194+
foreach ($this->individual->facts(['NAME']) as $nameFact) {
195+
$nick = $nameFact->attribute('NICK');
196+
197+
if ($nick !== '') {
198+
return $nick;
199+
}
200+
}
201+
202+
return '';
203+
}
204+
205+
/**
206+
* Returns the full name with the nickname injected in quotes between the given
207+
* names and the surname (e.g. `Martin "Chalky" White`). When the GEDCOM has no
208+
* NICK, or when the displayed name already contains the nickname inline, the
209+
* unmodified full name is returned.
210+
*
211+
* Mirrors the legacy webtrees ≤ 1.x behaviour and the `BertKoor/wt-module-old-nicknames`
212+
* data-fix output, but operates at display time without modifying the GEDCOM.
213+
*
214+
* @return string
215+
*/
216+
public function getFullNameWithNickname(): string
217+
{
218+
$nick = $this->getNickname();
219+
220+
if ($nick === '') {
221+
return $this->getFullName();
222+
}
223+
224+
return $this->injectNickname(
225+
$this->getFullName(),
226+
implode(' ', $this->getLastNames()),
227+
$nick
228+
);
229+
}
230+
231+
/**
232+
* Inserts the quoted nickname before the surname in a flat name string.
233+
* Idempotent: if the nickname is already present in quotes, the input is
234+
* returned unchanged.
235+
*
236+
* @param string $fullName Plain full name (e.g. "Martin White")
237+
* @param string $surname Surname tokens joined by spaces (e.g. "White" or "Van Der Berg")
238+
* @param string $nick Nickname without quotes (e.g. "Chalky")
239+
*
240+
* @return string
241+
*/
242+
private function injectNickname(string $fullName, string $surname, string $nick): string
243+
{
244+
if ($nick === '' || str_contains($fullName, '"' . $nick . '"')) {
245+
return $fullName;
246+
}
247+
248+
$position = ($surname !== '') ? strrpos($fullName, $surname) : false;
249+
250+
if ($position !== false) {
251+
return substr_replace(
252+
$fullName,
253+
'"' . $nick . '" ' . $surname,
254+
$position,
255+
strlen($surname)
256+
);
257+
}
258+
259+
return $fullName . ' "' . $nick . '"';
260+
}
261+
185262
/**
186263
* Splits a name into an array, removing all name placeholders.
187264
*

tests/NameProcessorTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,49 @@ public function getMarriedSurnamesReturnsEmptyWhenSpouseSurnameDoesNotMatchAnyMa
384384
$spouseStub
385385
));
386386
}
387+
388+
/**
389+
* @return array<string, array{string, string, string, string}>
390+
*/
391+
public static function injectNicknameDataProvider(): array
392+
{
393+
// [ fullName, surname, nick, expected ]
394+
return [
395+
'Empty nick returns input unchanged' => [
396+
'Martin White', 'White', '', 'Martin White',
397+
],
398+
'Inserts before single-word surname' => [
399+
'Martin White', 'White', 'Chalky', 'Martin "Chalky" White',
400+
],
401+
'Inserts before multi-word surname' => [
402+
'Hans Van Der Berg', 'Van Der Berg', 'Hänschen', 'Hans "Hänschen" Van Der Berg',
403+
],
404+
'Idempotent: nick already inline' => [
405+
'Martin "Chalky" White', 'White', 'Chalky', 'Martin "Chalky" White',
406+
],
407+
'No surname found: appends nick' => [
408+
'Anonymous', 'White', 'Chalky', 'Anonymous "Chalky"',
409+
],
410+
'Empty surname: appends nick' => [
411+
'Anonymous', '', 'Chalky', 'Anonymous "Chalky"',
412+
],
413+
'Hits last occurrence when surname is also a given name' => [
414+
'White Robert White', 'White', 'Bob', 'White Robert "Bob" White',
415+
],
416+
];
417+
}
418+
419+
/**
420+
* @throws ReflectionException
421+
*/
422+
#[Test]
423+
#[DataProvider('injectNicknameDataProvider')]
424+
public function injectNickname(string $fullName, string $surname, string $nick, string $expected): void
425+
{
426+
$processorStub = self::createStub(NameProcessor::class);
427+
$reflectionMethod = (new ReflectionClass(NameProcessor::class))->getMethod('injectNickname');
428+
$result = $reflectionMethod->invoke($processorStub, $fullName, $surname, $nick);
429+
430+
self::assertSame($expected, $result);
431+
}
387432
}

0 commit comments

Comments
 (0)