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