1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees; 21 22use Fig\Http\Message\RequestMethodInterface; 23use Fig\Http\Message\StatusCodeInterface; 24use Fisharebest\Webtrees\Contracts\UserInterface; 25use Fisharebest\Webtrees\Module\IndividualListModule; 26use Fisharebest\Webtrees\Services\GedcomImportService; 27use Fisharebest\Webtrees\Services\TreeService; 28use Fisharebest\Webtrees\Services\UserService; 29 30use PHPUnit\Framework\Attributes\CoversClass; 31 32use function array_map; 33use function preg_match_all; 34 35#[CoversClass(IndividualListModule::class)] 36class IndividualListTest extends TestCase 37{ 38 protected static bool $uses_database = true; 39 40 private Tree $tree; 41 private User $user; 42 43 public function setUp(): void 44 { 45 parent::setUp(); 46 47 I18N::init('en-US'); 48 49 $user_service = new UserService(); 50 $tree_service = new TreeService(new GedcomImportService()); 51 $this->tree = $tree_service->create('name', 'title'); 52 $this->user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 53 $this->user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 54 $this->user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 55 Auth::login($this->user); 56 // The default "John Doe" individual will confuse the test results... 57 Registry::individualFactory()->make('X1', $this->tree)->deleteRecord(); 58 } 59 60 public function testCollationOfInitials(): void 61 { 62 $module = new IndividualListModule(); 63 64 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Âaa/"); 65 $this->tree->createIndividual("0 @@ INDI\n1 NAME /aaa/"); 66 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Ååå/"); 67 $this->tree->createIndividual("0 @@ INDI\n1 NAME /æææ/"); 68 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Caa/"); 69 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Css/"); 70 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Dza/"); 71 72 I18N::init('en-US'); 73 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 74 $response = $module->handle($request); 75 $html = $response->getBody()->getContents(); 76 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 77 self::assertEquals(['A', 'C', 'D', 'Æ'], array_map(rawurldecode(...), $matches[1])); 78 79 I18N::init('sv'); 80 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 81 $response = $module->handle($request); 82 $html = $response->getBody()->getContents(); 83 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 84 self::assertEquals(['A', 'C', 'D', 'Å', 'Æ'], array_map(rawurldecode(...), $matches[1])); 85 86 I18N::init('hu'); 87 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 88 $response = $module->handle($request); 89 $html = $response->getBody()->getContents(); 90 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 91 self::assertEquals(['A', 'C', 'CS', 'DZ', 'Æ'], array_map(rawurldecode(...), $matches[1])); 92 } 93 94 public function xtestRedirectToCanonicalSurname(): void 95 { 96 $module = new IndividualListModule(); 97 98 I18N::init('en-US'); 99 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'Muller'], [], [], ['tree' => $this->tree]); 100 $response = $module->handle($request); 101 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 102 self::assertStringContainsString('surname=MULLER', $response->getHeaderLine('Location')); 103 104 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MÜLLER'], [], [], ['tree' => $this->tree]); 105 $response = $module->handle($request); 106 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 107 self::assertStringContainsString('surname=MULLER', $response->getHeaderLine('Location')); 108 109 I18N::init('de'); 110 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MÜLLER'], [], [], ['tree' => $this->tree]); 111 $response = $module->handle($request); 112 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 113 self::assertStringContainsString('surname=MUELLER', $response->getHeaderLine('Location')); 114 } 115 116 public function testCollationOfSurnames(): void 117 { 118 $module = new IndividualListModule(); 119 120 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Muller/"); 121 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Müller/"); 122 $i3 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Mueller/"); 123 124 I18N::init('en-US'); 125 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MULLER'], [], [], ['tree' => $this->tree]); 126 $response = $module->handle($request); 127 $html = $response->getBody()->getContents(); 128 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 129 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1], 'English, so U should match U and Ü'); 130 131 I18N::init('de'); 132 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MULLER'], [], [], ['tree' => $this->tree]); 133 $response = $module->handle($request); 134 $html = $response->getBody()->getContents(); 135 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 136 self::assertEqualsCanonicalizing([$i1->xref()], $matches[1], 'German, so U should only match U'); 137 138 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MUELLER'], [], [], ['tree' => $this->tree]); 139 $response = $module->handle($request); 140 $html = $response->getBody()->getContents(); 141 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 142 self::assertEqualsCanonicalizing([$i2->xref(), $i3->xref()], $matches[1], 'German, so UE should also match Ü'); 143 } 144 145 public function xtestUnknownVersusMissingSurname(): void 146 { 147 $module = new IndividualListModule(); 148 149 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John //"); 150 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John"); 151 152 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => '@'], [], [], ['tree' => $this->tree]); 153 $response = $module->handle($request); 154 $html = $response->getBody()->getContents(); 155 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 156 self::assertEqualsCanonicalizing([$i1->xref()], $matches[1]); 157 158 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => ','], [], [], ['tree' => $this->tree]); 159 $response = $module->handle($request); 160 $html = $response->getBody()->getContents(); 161 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 162 self::assertEqualsCanonicalizing([$i2->xref()], $matches[1]); 163 } 164 165 public function xtestAllSurnamesExcludesUnknownAndMissing(): void 166 { 167 $module = new IndividualListModule(); 168 169 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John /Black/"); 170 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME Mary /White/"); 171 $this->tree->createIndividual("0 @@ INDI\n1 NAME Peter //"); 172 $this->tree->createIndividual("0 @@ INDI\n1 NAME Paul"); 173 174 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['show_all' => 'yes'], [], [], ['tree' => $this->tree]); 175 $response = $module->handle($request); 176 $html = $response->getBody()->getContents(); 177 preg_match_all('/individual-list&surname=([A-Z]+)/', $html, $matches); 178 self::assertEqualsCanonicalizing(['BLACK', 'WHITE'], $matches[1]); 179 180 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['show_all' => 'yes', 'show' => 'indi'], [], [], ['tree' => $this->tree]); 181 $response = $module->handle($request); 182 $html = $response->getBody()->getContents(); 183 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 184 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1]); 185 } 186 187 public function xtestSurnameInitial(): void 188 { 189 $module = new IndividualListModule(); 190 191 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John /Black/"); 192 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME Mary /Brown/"); 193 $this->tree->createIndividual("0 @@ INDI\n1 NAME Peter /White/"); 194 $this->tree->createIndividual("0 @@ INDI\n1 NAME Paul /Green/"); 195 196 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => 'B'], [], [], ['tree' => $this->tree]); 197 $response = $module->handle($request); 198 $html = $response->getBody()->getContents(); 199 preg_match_all('/individual-list&surname=([A-Z]+)/', $html, $matches); 200 self::assertEqualsCanonicalizing(['BLACK', 'BROWN'], $matches[1]); 201 202 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => 'B', 'show' => 'indi'], [], [], ['tree' => $this->tree]); 203 $response = $module->handle($request); 204 $html = $response->getBody()->getContents(); 205 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 206 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1]); 207 } 208} 209