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