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