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; 29use PHPUnit\Framework\Attributes\CoversClass; 30 31use function array_map; 32use function preg_match_all; 33 34#[CoversClass(IndividualListModule::class)] 35class IndividualListTest extends TestCase 36{ 37 protected static bool $uses_database = true; 38 39 private Tree $tree; 40 private User $user; 41 42 public function setUp(): void 43 { 44 parent::setUp(); 45 46 I18N::init('en-US'); 47 48 $user_service = new UserService(); 49 $tree_service = new TreeService(new GedcomImportService()); 50 $this->tree = $tree_service->create('name', 'title'); 51 $this->user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 52 $this->user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 53 $this->user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 54 Auth::login($this->user); 55 // The default "John Doe" individual will confuse the test results... 56 Registry::individualFactory()->make('X1', $this->tree)->deleteRecord(); 57 } 58 59 public function testCollationOfInitials(): void 60 { 61 $module = new IndividualListModule(); 62 63 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Âaa/"); 64 $this->tree->createIndividual("0 @@ INDI\n1 NAME /aaa/"); 65 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Ååå/"); 66 $this->tree->createIndividual("0 @@ INDI\n1 NAME /æææ/"); 67 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Caa/"); 68 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Css/"); 69 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Dza/"); 70 71 I18N::init('en-US'); 72 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 73 $response = $module->handle($request); 74 $html = $response->getBody()->getContents(); 75 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 76 self::assertEquals(['A', 'C', 'D', 'Æ'], array_map(rawurldecode(...), $matches[1])); 77 78 I18N::init('sv'); 79 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 80 $response = $module->handle($request); 81 $html = $response->getBody()->getContents(); 82 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 83 self::assertEquals(['A', 'C', 'D', 'Å', 'Æ'], array_map(rawurldecode(...), $matches[1])); 84 85 I18N::init('hu'); 86 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 87 $response = $module->handle($request); 88 $html = $response->getBody()->getContents(); 89 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 90 self::assertEquals(['A', 'C', 'CS', 'DZ', 'Æ'], array_map(rawurldecode(...), $matches[1])); 91 } 92 93 public function xtestRedirectToCanonicalSurname(): void 94 { 95 $module = new IndividualListModule(); 96 97 I18N::init('en-US'); 98 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'Muller'], [], [], ['tree' => $this->tree]); 99 $response = $module->handle($request); 100 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 101 self::assertStringContainsString('surname=MULLER', $response->getHeaderLine('Location')); 102 103 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MÜLLER'], [], [], ['tree' => $this->tree]); 104 $response = $module->handle($request); 105 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 106 self::assertStringContainsString('surname=MULLER', $response->getHeaderLine('Location')); 107 108 I18N::init('de'); 109 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MÜLLER'], [], [], ['tree' => $this->tree]); 110 $response = $module->handle($request); 111 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 112 self::assertStringContainsString('surname=MUELLER', $response->getHeaderLine('Location')); 113 } 114 115 public function testCollationOfSurnames(): void 116 { 117 $module = new IndividualListModule(); 118 119 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Muller/"); 120 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Müller/"); 121 $i3 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Mueller/"); 122 123 I18N::init('en-US'); 124 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MULLER'], [], [], ['tree' => $this->tree]); 125 $response = $module->handle($request); 126 $html = $response->getBody()->getContents(); 127 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 128 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1], 'English, so U should match U and Ü'); 129 130 I18N::init('de'); 131 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MULLER'], [], [], ['tree' => $this->tree]); 132 $response = $module->handle($request); 133 $html = $response->getBody()->getContents(); 134 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 135 self::assertEqualsCanonicalizing([$i1->xref()], $matches[1], 'German, so U should only match U'); 136 137 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MUELLER'], [], [], ['tree' => $this->tree]); 138 $response = $module->handle($request); 139 $html = $response->getBody()->getContents(); 140 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 141 self::assertEqualsCanonicalizing([$i2->xref(), $i3->xref()], $matches[1], 'German, so UE should also match Ü'); 142 } 143 144 public function xtestUnknownVersusMissingSurname(): void 145 { 146 $module = new IndividualListModule(); 147 148 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John //"); 149 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John"); 150 151 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => '@'], [], [], ['tree' => $this->tree]); 152 $response = $module->handle($request); 153 $html = $response->getBody()->getContents(); 154 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 155 self::assertEqualsCanonicalizing([$i1->xref()], $matches[1]); 156 157 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => ','], [], [], ['tree' => $this->tree]); 158 $response = $module->handle($request); 159 $html = $response->getBody()->getContents(); 160 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 161 self::assertEqualsCanonicalizing([$i2->xref()], $matches[1]); 162 } 163 164 public function xtestAllSurnamesExcludesUnknownAndMissing(): void 165 { 166 $module = new IndividualListModule(); 167 168 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John /Black/"); 169 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME Mary /White/"); 170 $this->tree->createIndividual("0 @@ INDI\n1 NAME Peter //"); 171 $this->tree->createIndividual("0 @@ INDI\n1 NAME Paul"); 172 173 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['show_all' => 'yes'], [], [], ['tree' => $this->tree]); 174 $response = $module->handle($request); 175 $html = $response->getBody()->getContents(); 176 preg_match_all('/individual-list&surname=([A-Z]+)/', $html, $matches); 177 self::assertEqualsCanonicalizing(['BLACK', 'WHITE'], $matches[1]); 178 179 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['show_all' => 'yes', 'show' => 'indi'], [], [], ['tree' => $this->tree]); 180 $response = $module->handle($request); 181 $html = $response->getBody()->getContents(); 182 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 183 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1]); 184 } 185 186 public function xtestSurnameInitial(): void 187 { 188 $module = new IndividualListModule(); 189 190 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John /Black/"); 191 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME Mary /Brown/"); 192 $this->tree->createIndividual("0 @@ INDI\n1 NAME Peter /White/"); 193 $this->tree->createIndividual("0 @@ INDI\n1 NAME Paul /Green/"); 194 195 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => 'B'], [], [], ['tree' => $this->tree]); 196 $response = $module->handle($request); 197 $html = $response->getBody()->getContents(); 198 preg_match_all('/individual-list&surname=([A-Z]+)/', $html, $matches); 199 self::assertEqualsCanonicalizing(['BLACK', 'BROWN'], $matches[1]); 200 201 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => 'B', 'show' => 'indi'], [], [], ['tree' => $this->tree]); 202 $response = $module->handle($request); 203 $html = $response->getBody()->getContents(); 204 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 205 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1]); 206 } 207} 208