. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Repository; use Fisharebest\Webtrees\Date; use Fisharebest\Webtrees\DB; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Statistics\Repository\Interfaces\FamilyDatesRepositoryInterface; use Fisharebest\Webtrees\Tree; use Illuminate\Database\Query\Builder; use function abs; use function e; /** * A repository providing methods for family dates related statistics (birth, death, marriage, divorce). */ class FamilyDatesRepository implements FamilyDatesRepositoryInterface { /** * Sorting directions. */ private const string SORT_MIN = 'MIN'; private const string SORT_MAX = 'MAX'; /** * Event facts. */ private const string EVENT_BIRTH = 'BIRT'; private const string EVENT_DEATH = 'DEAT'; private const string EVENT_MARRIAGE = 'MARR'; private const string EVENT_DIVORCE = 'DIV'; private Tree $tree; /** * @param Tree $tree */ public function __construct(Tree $tree) { $this->tree = $tree; } /** * Returns the first/last event record for the given event fact. * * @param string $fact * @param string $operation * * @return object{id:string,year:int,fact:string,type:string}|null */ private function eventQuery(string $fact, string $operation): object|null { return DB::table('dates') ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type']) ->where('d_file', '=', $this->tree->id()) ->where('d_fact', '=', $fact) ->where('d_julianday1', '=', function (Builder $query) use ($operation, $fact): void { $query->selectRaw($operation . '(d_julianday1)') ->from('dates') ->where('d_file', '=', $this->tree->id()) ->where('d_fact', '=', $fact) ->where('d_julianday1', '<>', 0); }) ->limit(1) ->get() ->map(static fn (object $row): object => (object) [ 'id' => $row->id, 'year' => (int) $row->year, 'fact' => $row->fact, 'type' => $row->type, ]) ->first(); } /** * Returns the formatted year of the first/last occurring event. * * @param string $type The fact to query * @param string $operation The sorting operation * * @return string */ private function getFirstLastEvent(string $type, string $operation): string { $row = $this->eventQuery($type, $operation); $result = I18N::translate('This information is not available.'); if ($row !== null) { $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); if ($record instanceof GedcomRecord && $record->canShow()) { $result = $record->formatList(); } else { $result = I18N::translate('This information is private and cannot be shown.'); } } return $result; } /** * @return string */ public function firstBirth(): string { return $this->getFirstLastEvent(self::EVENT_BIRTH, self::SORT_MIN); } /** * @return string */ public function lastBirth(): string { return $this->getFirstLastEvent(self::EVENT_BIRTH, self::SORT_MAX); } /** * @return string */ public function firstDeath(): string { return $this->getFirstLastEvent(self::EVENT_DEATH, self::SORT_MIN); } /** * @return string */ public function lastDeath(): string { return $this->getFirstLastEvent(self::EVENT_DEATH, self::SORT_MAX); } /** * @return string */ public function firstMarriage(): string { return $this->getFirstLastEvent(self::EVENT_MARRIAGE, self::SORT_MIN); } /** * @return string */ public function lastMarriage(): string { return $this->getFirstLastEvent(self::EVENT_MARRIAGE, self::SORT_MAX); } /** * @return string */ public function firstDivorce(): string { return $this->getFirstLastEvent(self::EVENT_DIVORCE, self::SORT_MIN); } /** * @return string */ public function lastDivorce(): string { return $this->getFirstLastEvent(self::EVENT_DIVORCE, self::SORT_MAX); } /** * Returns the formatted year of the first/last occurring event. * * @param string $type The fact to query * @param string $operation The sorting operation * * @return string */ private function getFirstLastEventYear(string $type, string $operation): string { $row = $this->eventQuery($type, $operation); if ($row === null) { return ''; } if ($row->year < 0) { $row->year = abs($row->year) . ' B.C.'; } return (new Date($row->type . ' ' . $row->year)) ->display(); } /** * @return string */ public function firstBirthYear(): string { return $this->getFirstLastEventYear(self::EVENT_BIRTH, self::SORT_MIN); } /** * @return string */ public function lastBirthYear(): string { return $this->getFirstLastEventYear(self::EVENT_BIRTH, self::SORT_MAX); } /** * @return string */ public function firstDeathYear(): string { return $this->getFirstLastEventYear(self::EVENT_DEATH, self::SORT_MIN); } /** * @return string */ public function lastDeathYear(): string { return $this->getFirstLastEventYear(self::EVENT_DEATH, self::SORT_MAX); } /** * @return string */ public function firstMarriageYear(): string { return $this->getFirstLastEventYear(self::EVENT_MARRIAGE, self::SORT_MIN); } /** * @return string */ public function lastMarriageYear(): string { return $this->getFirstLastEventYear(self::EVENT_MARRIAGE, self::SORT_MAX); } /** * @return string */ public function firstDivorceYear(): string { return $this->getFirstLastEventYear(self::EVENT_DIVORCE, self::SORT_MIN); } /** * @return string */ public function lastDivorceYear(): string { return $this->getFirstLastEventYear(self::EVENT_DIVORCE, self::SORT_MAX); } /** * Returns the formatted name of the first/last occurring event. * * @param string $type The fact to query * @param string $operation The sorting operation * * @return string */ private function getFirstLastEventName(string $type, string $operation): string { $row = $this->eventQuery($type, $operation); if ($row !== null) { $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); if ($record instanceof GedcomRecord) { return '' . $record->fullName() . ''; } } return ''; } /** * @return string */ public function firstBirthName(): string { return $this->getFirstLastEventName(self::EVENT_BIRTH, self::SORT_MIN); } /** * @return string */ public function lastBirthName(): string { return $this->getFirstLastEventName(self::EVENT_BIRTH, self::SORT_MAX); } /** * @return string */ public function firstDeathName(): string { return $this->getFirstLastEventName(self::EVENT_DEATH, self::SORT_MIN); } /** * @return string */ public function lastDeathName(): string { return $this->getFirstLastEventName(self::EVENT_DEATH, self::SORT_MAX); } /** * @return string */ public function firstMarriageName(): string { return $this->getFirstLastEventName(self::EVENT_MARRIAGE, self::SORT_MIN); } /** * @return string */ public function lastMarriageName(): string { return $this->getFirstLastEventName(self::EVENT_MARRIAGE, self::SORT_MAX); } /** * @return string */ public function firstDivorceName(): string { return $this->getFirstLastEventName(self::EVENT_DIVORCE, self::SORT_MIN); } /** * @return string */ public function lastDivorceName(): string { return $this->getFirstLastEventName(self::EVENT_DIVORCE, self::SORT_MAX); } /** * Returns the formatted place of the first/last occurring event. * * @param string $type The fact to query * @param string $operation The sorting operation * * @return string */ private function getFirstLastEventPlace(string $type, string $operation): string { $row = $this->eventQuery($type, $operation); if ($row != null) { $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree); $fact = null; if ($record instanceof GedcomRecord) { $fact = $record->facts([$row->fact])->first(); } if ($fact instanceof Fact) { return $fact->place()->shortName(); } } return I18N::translate('This information is private and cannot be shown.'); } /** * @return string */ public function firstBirthPlace(): string { return $this->getFirstLastEventPlace(self::EVENT_BIRTH, self::SORT_MIN); } /** * @return string */ public function lastBirthPlace(): string { return $this->getFirstLastEventPlace(self::EVENT_BIRTH, self::SORT_MAX); } /** * @return string */ public function firstDeathPlace(): string { return $this->getFirstLastEventPlace(self::EVENT_DEATH, self::SORT_MIN); } /** * @return string */ public function lastDeathPlace(): string { return $this->getFirstLastEventPlace(self::EVENT_DEATH, self::SORT_MAX); } /** * @return string */ public function firstMarriagePlace(): string { return $this->getFirstLastEventPlace(self::EVENT_MARRIAGE, self::SORT_MIN); } /** * @return string */ public function lastMarriagePlace(): string { return $this->getFirstLastEventPlace(self::EVENT_MARRIAGE, self::SORT_MAX); } /** * @return string */ public function firstDivorcePlace(): string { return $this->getFirstLastEventPlace(self::EVENT_DIVORCE, self::SORT_MIN); } /** * @return string */ public function lastDivorcePlace(): string { return $this->getFirstLastEventPlace(self::EVENT_DIVORCE, self::SORT_MAX); } }