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