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\Statistics\Repository; 21 22use Fisharebest\Webtrees\Date; 23use Fisharebest\Webtrees\Fact; 24use Fisharebest\Webtrees\GedcomRecord; 25use Fisharebest\Webtrees\I18N; 26use Fisharebest\Webtrees\Registry; 27use Fisharebest\Webtrees\Statistics\Repository\Interfaces\FamilyDatesRepositoryInterface; 28use Fisharebest\Webtrees\Tree; 29use Illuminate\Database\Capsule\Manager as DB; 30use Illuminate\Database\Query\Builder; 31 32use function abs; 33use function e; 34 35/** 36 * A repository providing methods for family dates related statistics (birth, death, marriage, divorce). 37 */ 38class FamilyDatesRepository implements FamilyDatesRepositoryInterface 39{ 40 /** 41 * Sorting directions. 42 */ 43 private const SORT_MIN = 'MIN'; 44 private const SORT_MAX = 'MAX'; 45 46 /** 47 * Event facts. 48 */ 49 private const EVENT_BIRTH = 'BIRT'; 50 private const EVENT_DEATH = 'DEAT'; 51 private const EVENT_MARRIAGE = 'MARR'; 52 private const EVENT_DIVORCE = 'DIV'; 53 54 private Tree $tree; 55 56 /** 57 * @param Tree $tree 58 */ 59 public function __construct(Tree $tree) 60 { 61 $this->tree = $tree; 62 } 63 64 /** 65 * Returns the first/last event record for the given event fact. 66 * 67 * @param string $fact 68 * @param string $operation 69 * 70 * @return object|null 71 */ 72 private function eventQuery(string $fact, string $operation): ?object 73 { 74 return DB::table('dates') 75 ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type']) 76 ->where('d_file', '=', $this->tree->id()) 77 ->where('d_fact', '=', $fact) 78 ->where('d_julianday1', '=', function (Builder $query) use ($operation, $fact): void { 79 $query->selectRaw($operation . '(d_julianday1)') 80 ->from('dates') 81 ->where('d_file', '=', $this->tree->id()) 82 ->where('d_fact', '=', $fact) 83 ->where('d_julianday1', '<>', 0); 84 }) 85 ->first(); 86 } 87 88 /** 89 * Returns the formatted year of the first/last occuring event. 90 * 91 * @param string $type The fact to query 92 * @param string $operation The sorting operation 93 * 94 * @return string 95 */ 96 private function getFirstLastEvent(string $type, string $operation): string 97 { 98 $row = $this->eventQuery($type, $operation); 99 $result = I18N::translate('This information is not available.'); 100 101 if ($row !== null) { 102 $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); 103 104 if ($record instanceof GedcomRecord && $record->canShow()) { 105 $result = $record->formatList(); 106 } else { 107 $result = I18N::translate('This information is private and cannot be shown.'); 108 } 109 } 110 111 return $result; 112 } 113 114 /** 115 * @return string 116 */ 117 public function firstBirth(): string 118 { 119 return $this->getFirstLastEvent(self::EVENT_BIRTH, self::SORT_MIN); 120 } 121 122 /** 123 * @return string 124 */ 125 public function lastBirth(): string 126 { 127 return $this->getFirstLastEvent(self::EVENT_BIRTH, self::SORT_MAX); 128 } 129 130 /** 131 * @return string 132 */ 133 public function firstDeath(): string 134 { 135 return $this->getFirstLastEvent(self::EVENT_DEATH, self::SORT_MIN); 136 } 137 138 /** 139 * @return string 140 */ 141 public function lastDeath(): string 142 { 143 return $this->getFirstLastEvent(self::EVENT_DEATH, self::SORT_MAX); 144 } 145 146 /** 147 * @return string 148 */ 149 public function firstMarriage(): string 150 { 151 return $this->getFirstLastEvent(self::EVENT_MARRIAGE, self::SORT_MIN); 152 } 153 154 /** 155 * @return string 156 */ 157 public function lastMarriage(): string 158 { 159 return $this->getFirstLastEvent(self::EVENT_MARRIAGE, self::SORT_MAX); 160 } 161 162 /** 163 * @return string 164 */ 165 public function firstDivorce(): string 166 { 167 return $this->getFirstLastEvent(self::EVENT_DIVORCE, self::SORT_MIN); 168 } 169 170 /** 171 * @return string 172 */ 173 public function lastDivorce(): string 174 { 175 return $this->getFirstLastEvent(self::EVENT_DIVORCE, self::SORT_MAX); 176 } 177 178 /** 179 * Returns the formatted year of the first/last occuring event. 180 * 181 * @param string $type The fact to query 182 * @param string $operation The sorting operation 183 * 184 * @return string 185 */ 186 private function getFirstLastEventYear(string $type, string $operation): string 187 { 188 $row = $this->eventQuery($type, $operation); 189 190 if ($row === null) { 191 return ''; 192 } 193 194 if ($row->year < 0) { 195 $row->year = abs($row->year) . ' B.C.'; 196 } 197 198 return (new Date($row->type . ' ' . $row->year)) 199 ->display(); 200 } 201 202 /** 203 * @return string 204 */ 205 public function firstBirthYear(): string 206 { 207 return $this->getFirstLastEventYear(self::EVENT_BIRTH, self::SORT_MIN); 208 } 209 210 /** 211 * @return string 212 */ 213 public function lastBirthYear(): string 214 { 215 return $this->getFirstLastEventYear(self::EVENT_BIRTH, self::SORT_MAX); 216 } 217 218 /** 219 * @return string 220 */ 221 public function firstDeathYear(): string 222 { 223 return $this->getFirstLastEventYear(self::EVENT_DEATH, self::SORT_MIN); 224 } 225 226 /** 227 * @return string 228 */ 229 public function lastDeathYear(): string 230 { 231 return $this->getFirstLastEventYear(self::EVENT_DEATH, self::SORT_MAX); 232 } 233 234 /** 235 * @return string 236 */ 237 public function firstMarriageYear(): string 238 { 239 return $this->getFirstLastEventYear(self::EVENT_MARRIAGE, self::SORT_MIN); 240 } 241 242 /** 243 * @return string 244 */ 245 public function lastMarriageYear(): string 246 { 247 return $this->getFirstLastEventYear(self::EVENT_MARRIAGE, self::SORT_MAX); 248 } 249 250 /** 251 * @return string 252 */ 253 public function firstDivorceYear(): string 254 { 255 return $this->getFirstLastEventYear(self::EVENT_DIVORCE, self::SORT_MIN); 256 } 257 258 /** 259 * @return string 260 */ 261 public function lastDivorceYear(): string 262 { 263 return $this->getFirstLastEventYear(self::EVENT_DIVORCE, self::SORT_MAX); 264 } 265 266 /** 267 * Returns the formatted name of the first/last occuring event. 268 * 269 * @param string $type The fact to query 270 * @param string $operation The sorting operation 271 * 272 * @return string 273 */ 274 private function getFirstLastEventName(string $type, string $operation): string 275 { 276 $row = $this->eventQuery($type, $operation); 277 278 if ($row !== null) { 279 $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); 280 281 if ($record instanceof GedcomRecord) { 282 return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>'; 283 } 284 } 285 286 return ''; 287 } 288 289 /** 290 * @return string 291 */ 292 public function firstBirthName(): string 293 { 294 return $this->getFirstLastEventName(self::EVENT_BIRTH, self::SORT_MIN); 295 } 296 297 /** 298 * @return string 299 */ 300 public function lastBirthName(): string 301 { 302 return $this->getFirstLastEventName(self::EVENT_BIRTH, self::SORT_MAX); 303 } 304 305 /** 306 * @return string 307 */ 308 public function firstDeathName(): string 309 { 310 return $this->getFirstLastEventName(self::EVENT_DEATH, self::SORT_MIN); 311 } 312 313 /** 314 * @return string 315 */ 316 public function lastDeathName(): string 317 { 318 return $this->getFirstLastEventName(self::EVENT_DEATH, self::SORT_MAX); 319 } 320 321 /** 322 * @return string 323 */ 324 public function firstMarriageName(): string 325 { 326 return $this->getFirstLastEventName(self::EVENT_MARRIAGE, self::SORT_MIN); 327 } 328 329 /** 330 * @return string 331 */ 332 public function lastMarriageName(): string 333 { 334 return $this->getFirstLastEventName(self::EVENT_MARRIAGE, self::SORT_MAX); 335 } 336 337 /** 338 * @return string 339 */ 340 public function firstDivorceName(): string 341 { 342 return $this->getFirstLastEventName(self::EVENT_DIVORCE, self::SORT_MIN); 343 } 344 345 /** 346 * @return string 347 */ 348 public function lastDivorceName(): string 349 { 350 return $this->getFirstLastEventName(self::EVENT_DIVORCE, self::SORT_MAX); 351 } 352 353 /** 354 * Returns the formatted place of the first/last occuring event. 355 * 356 * @param string $type The fact to query 357 * @param string $operation The sorting operation 358 * 359 * @return string 360 */ 361 private function getFirstLastEventPlace(string $type, string $operation): string 362 { 363 $row = $this->eventQuery($type, $operation); 364 365 if ($row != null) { 366 $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); 367 $fact = null; 368 369 if ($record instanceof GedcomRecord) { 370 $fact = $record->facts([$row->fact])->first(); 371 } 372 373 if ($fact instanceof Fact) { 374 return $fact->place()->shortName(); 375 } 376 } 377 378 return I18N::translate('This information is private and cannot be shown.'); 379 } 380 381 /** 382 * @return string 383 */ 384 public function firstBirthPlace(): string 385 { 386 return $this->getFirstLastEventPlace(self::EVENT_BIRTH, self::SORT_MIN); 387 } 388 389 /** 390 * @return string 391 */ 392 public function lastBirthPlace(): string 393 { 394 return $this->getFirstLastEventPlace(self::EVENT_BIRTH, self::SORT_MAX); 395 } 396 397 /** 398 * @return string 399 */ 400 public function firstDeathPlace(): string 401 { 402 return $this->getFirstLastEventPlace(self::EVENT_DEATH, self::SORT_MIN); 403 } 404 405 /** 406 * @return string 407 */ 408 public function lastDeathPlace(): string 409 { 410 return $this->getFirstLastEventPlace(self::EVENT_DEATH, self::SORT_MAX); 411 } 412 413 /** 414 * @return string 415 */ 416 public function firstMarriagePlace(): string 417 { 418 return $this->getFirstLastEventPlace(self::EVENT_MARRIAGE, self::SORT_MIN); 419 } 420 421 /** 422 * @return string 423 */ 424 public function lastMarriagePlace(): string 425 { 426 return $this->getFirstLastEventPlace(self::EVENT_MARRIAGE, self::SORT_MAX); 427 } 428 429 /** 430 * @return string 431 */ 432 public function firstDivorcePlace(): string 433 { 434 return $this->getFirstLastEventPlace(self::EVENT_DIVORCE, self::SORT_MIN); 435 } 436 437 /** 438 * @return string 439 */ 440 public function lastDivorcePlace(): string 441 { 442 return $this->getFirstLastEventPlace(self::EVENT_DIVORCE, self::SORT_MAX); 443 } 444} 445