1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 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\Functions\FunctionsPrint; 25use Fisharebest\Webtrees\Gedcom; 26use Fisharebest\Webtrees\GedcomRecord; 27use Fisharebest\Webtrees\GedcomTag; 28use Fisharebest\Webtrees\Header; 29use Fisharebest\Webtrees\I18N; 30use Fisharebest\Webtrees\Statistics\Repository\Interfaces\EventRepositoryInterface; 31use Fisharebest\Webtrees\Tree; 32use Illuminate\Database\Capsule\Manager as DB; 33use Illuminate\Database\Eloquent\Model; 34use Illuminate\Database\Query\Builder; 35 36/** 37 * A repository providing methods for event related statistics. 38 */ 39class EventRepository implements EventRepositoryInterface 40{ 41 /** 42 * Sorting directions. 43 */ 44 private const SORT_ASC = 'ASC'; 45 private const SORT_DESC = 'DESC'; 46 47 /** 48 * Event facts. 49 */ 50 private const EVENT_BIRTH = 'BIRT'; 51 private const EVENT_DEATH = 'DEAT'; 52 private const EVENT_MARRIAGE = 'MARR'; 53 private const EVENT_DIVORCE = 'DIV'; 54 private const EVENT_ADOPTION = 'ADOP'; 55 private const EVENT_BURIAL = 'BURI'; 56 private const EVENT_CENSUS = 'CENS'; 57 58 /** 59 * @var Tree 60 */ 61 private $tree; 62 63 /** 64 * Constructor. 65 * 66 * @param Tree $tree 67 */ 68 public function __construct(Tree $tree) 69 { 70 $this->tree = $tree; 71 } 72 73 /** 74 * Returns the total number of a given list of events (with dates). 75 * 76 * @param array $events The list of events to count (e.g. BIRT, DEAT, ...) 77 * 78 * @return int 79 */ 80 private function getEventCount(array $events): int 81 { 82 $query = DB::table('dates') 83 ->where('d_file', '=', $this->tree->id()); 84 85 $no_types = [ 86 'HEAD', 87 'CHAN', 88 ]; 89 90 if ($events) { 91 $types = []; 92 93 foreach ($events as $type) { 94 if (strncmp($type, '!', 1) === 0) { 95 $no_types[] = substr($type, 1); 96 } else { 97 $types[] = $type; 98 } 99 } 100 101 if ($types) { 102 $query->whereIn('d_fact', $types); 103 } 104 } 105 106 return $query->whereNotIn('d_fact', $no_types) 107 ->count(); 108 } 109 110 /** 111 * @inheritDoc 112 */ 113 public function totalEvents(array $events = []): string 114 { 115 return I18N::number( 116 $this->getEventCount($events) 117 ); 118 } 119 120 /** 121 * @inheritDoc 122 */ 123 public function totalEventsBirth(): string 124 { 125 return $this->totalEvents(Gedcom::BIRTH_EVENTS); 126 } 127 128 /** 129 * @inheritDoc 130 */ 131 public function totalBirths(): string 132 { 133 return $this->totalEvents([self::EVENT_BIRTH]); 134 } 135 136 /** 137 * @inheritDoc 138 */ 139 public function totalEventsDeath(): string 140 { 141 return $this->totalEvents(Gedcom::DEATH_EVENTS); 142 } 143 144 /** 145 * @inheritDoc 146 */ 147 public function totalDeaths(): string 148 { 149 return $this->totalEvents([self::EVENT_DEATH]); 150 } 151 152 /** 153 * @inheritDoc 154 */ 155 public function totalEventsMarriage(): string 156 { 157 return $this->totalEvents(Gedcom::MARRIAGE_EVENTS); 158 } 159 160 /** 161 * @inheritDoc 162 */ 163 public function totalMarriages(): string 164 { 165 return $this->totalEvents([self::EVENT_MARRIAGE]); 166 } 167 168 /** 169 * @inheritDoc 170 */ 171 public function totalEventsDivorce(): string 172 { 173 return $this->totalEvents(Gedcom::DIVORCE_EVENTS); 174 } 175 176 /** 177 * @inheritDoc 178 */ 179 public function totalDivorces(): string 180 { 181 return $this->totalEvents([self::EVENT_DIVORCE]); 182 } 183 184 /** 185 * Retursn the list of common facts used query the data. 186 * 187 * @return array 188 */ 189 private function getCommonFacts(): array 190 { 191 // The list of facts used to limit the query result 192 return array_merge( 193 Gedcom::BIRTH_EVENTS, 194 Gedcom::MARRIAGE_EVENTS, 195 Gedcom::DIVORCE_EVENTS, 196 Gedcom::DEATH_EVENTS 197 ); 198 } 199 200 /** 201 * @inheritDoc 202 */ 203 public function totalEventsOther(): string 204 { 205 $no_facts = array_map( 206 static function (string $fact): string { 207 return '!' . $fact; 208 }, 209 $this->getCommonFacts() 210 ); 211 212 return $this->totalEvents($no_facts); 213 } 214 215 /** 216 * Returns the first/last event record from the given list of event facts. 217 * 218 * @param string $direction The sorting direction of the query (To return first or last record) 219 * 220 * @return Model|Builder|object|null 221 */ 222 private function eventQuery(string $direction) 223 { 224 return DB::table('dates') 225 ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type']) 226 ->where('d_file', '=', $this->tree->id()) 227 ->where('d_gid', '<>', Header::RECORD_TYPE) 228 ->whereIn('d_fact', $this->getCommonFacts()) 229 ->where('d_julianday1', '<>', 0) 230 ->orderBy('d_julianday1', $direction) 231 ->orderBy('d_type') 232 ->first(); 233 } 234 235 /** 236 * Returns the formatted first/last occuring event. 237 * 238 * @param string $direction The sorting direction 239 * 240 * @return string 241 */ 242 private function getFirstLastEvent(string $direction): string 243 { 244 $row = $this->eventQuery($direction); 245 $result = I18N::translate('This information is not available.'); 246 247 if ($row) { 248 $record = GedcomRecord::getInstance($row->id, $this->tree); 249 250 if ($record && $record->canShow()) { 251 $result = $record->formatList(); 252 } else { 253 $result = I18N::translate('This information is private and cannot be shown.'); 254 } 255 } 256 257 return $result; 258 } 259 260 /** 261 * @inheritDoc 262 */ 263 public function firstEvent(): string 264 { 265 return $this->getFirstLastEvent(self::SORT_ASC); 266 } 267 268 /** 269 * @inheritDoc 270 */ 271 public function lastEvent(): string 272 { 273 return $this->getFirstLastEvent(self::SORT_DESC); 274 } 275 276 /** 277 * Returns the formatted year of the first/last occuring event. 278 * 279 * @param string $direction The sorting direction 280 * 281 * @return string 282 */ 283 private function getFirstLastEventYear(string $direction): string 284 { 285 $row = $this->eventQuery($direction); 286 287 if (!$row) { 288 return ''; 289 } 290 291 return (new Date($row->type . ' ' . $row->year)) 292 ->display(); 293 } 294 295 /** 296 * @inheritDoc 297 */ 298 public function firstEventYear(): string 299 { 300 return $this->getFirstLastEventYear(self::SORT_ASC); 301 } 302 303 /** 304 * @inheritDoc 305 */ 306 public function lastEventYear(): string 307 { 308 return $this->getFirstLastEventYear(self::SORT_DESC); 309 } 310 311 /** 312 * Returns the formatted type of the first/last occuring event. 313 * 314 * @param string $direction The sorting direction 315 * 316 * @return string 317 */ 318 private function getFirstLastEventType(string $direction): string 319 { 320 $row = $this->eventQuery($direction); 321 322 if ($row) { 323 $event_types = [ 324 self::EVENT_BIRTH => I18N::translate('birth'), 325 self::EVENT_DEATH => I18N::translate('death'), 326 self::EVENT_MARRIAGE => I18N::translate('marriage'), 327 self::EVENT_ADOPTION => I18N::translate('adoption'), 328 self::EVENT_BURIAL => I18N::translate('burial'), 329 self::EVENT_CENSUS => I18N::translate('census added'), 330 ]; 331 332 return $event_types[$row->fact] ?? GedcomTag::getLabel($row->fact); 333 } 334 335 return ''; 336 } 337 338 /** 339 * @inheritDoc 340 */ 341 public function firstEventType(): string 342 { 343 return $this->getFirstLastEventType(self::SORT_ASC); 344 } 345 346 /** 347 * @inheritDoc 348 */ 349 public function lastEventType(): string 350 { 351 return $this->getFirstLastEventType(self::SORT_DESC); 352 } 353 354 /** 355 * Returns the formatted name of the first/last occuring event. 356 * 357 * @param string $direction The sorting direction 358 * 359 * @return string 360 */ 361 private function getFirstLastEventName(string $direction): string 362 { 363 $row = $this->eventQuery($direction); 364 365 if ($row) { 366 $record = GedcomRecord::getInstance($row->id, $this->tree); 367 368 if ($record) { 369 return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>'; 370 } 371 } 372 373 return ''; 374 } 375 376 /** 377 * @inheritDoc 378 */ 379 public function firstEventName(): string 380 { 381 return $this->getFirstLastEventName(self::SORT_ASC); 382 } 383 384 /** 385 * @inheritDoc 386 */ 387 public function lastEventName(): string 388 { 389 return $this->getFirstLastEventName(self::SORT_DESC); 390 } 391 392 /** 393 * Returns the formatted place of the first/last occuring event. 394 * 395 * @param string $direction The sorting direction 396 * 397 * @return string 398 */ 399 private function getFirstLastEventPlace(string $direction): string 400 { 401 $row = $this->eventQuery($direction); 402 403 if ($row) { 404 $record = GedcomRecord::getInstance($row->id, $this->tree); 405 $fact = null; 406 407 if ($record) { 408 $fact = $record->facts([$row->fact])->first(); 409 } 410 411 if ($fact instanceof Fact) { 412 return FunctionsPrint::formatFactPlace($fact, true, true, true); 413 } 414 } 415 416 return I18N::translate('Private'); 417 } 418 419 /** 420 * @inheritDoc 421 */ 422 public function firstEventPlace(): string 423 { 424 return $this->getFirstLastEventPlace(self::SORT_ASC); 425 } 426 427 /** 428 * @inheritDoc 429 */ 430 public function lastEventPlace(): string 431 { 432 return $this->getFirstLastEventPlace(self::SORT_DESC); 433 } 434} 435