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