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