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