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