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