| Total Complexity | 80 |
| Total Lines | 428 |
| Duplicated Lines | 0 % |
| Changes | 3 | ||
| Bugs | 0 | Features | 0 |
Complex classes like FunctionsPrint often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use FunctionsPrint, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 60 | class FunctionsPrint |
||
| 61 | { |
||
| 62 | /** |
||
| 63 | * print a note record |
||
| 64 | * |
||
| 65 | * @param Tree $tree |
||
| 66 | * @param Note|null $note |
||
| 67 | * @param string $nrec the note record to print |
||
| 68 | * |
||
| 69 | * @return string |
||
| 70 | */ |
||
| 71 | private static function printNoteRecord(Tree $tree, $note, string $nrec = ''): string |
||
| 72 | { |
||
| 73 | $shared = true; |
||
| 74 | if ($note === null) { |
||
| 75 | if (preg_match('/^0 @(' . Gedcom::REGEX_XREF . ')@ NOTE/', $nrec, $match)) { |
||
| 76 | // Shared note. |
||
| 77 | $note = Registry::noteFactory()->make($match[1], $tree); |
||
| 78 | } else { |
||
| 79 | // Inline note, create a dummy note object |
||
| 80 | $nrec = '0 @_@ NOTE ' . preg_replace(["/\d (NOTE|CON[C,T]) ?/", "/\r?\n/"], ["", "\n1 CONT "], $nrec); |
||
| 81 | $note = Registry::noteFactory()->new('', $nrec, null, $tree); |
||
| 82 | $shared = false; |
||
| 83 | } |
||
| 84 | } |
||
| 85 | // It must exist. |
||
| 86 | assert($note instanceof Note); |
||
| 87 | |||
| 88 | if ($note->canShow()) { |
||
| 89 | $one_line_only = $note->getNote() === strip_tags(html_entity_decode($note->fullName(), ENT_QUOTES, "UTF-8")); |
||
| 90 | if ($shared) { |
||
| 91 | $first_line = '<a href="' . e($note->url()) . '">' . $note->fullName() . '</a>'; |
||
| 92 | $label = I18N::translate('Shared note'); |
||
| 93 | } else { |
||
| 94 | $first_line = $note->fullName(); |
||
| 95 | $label = I18N::translate('Note'); |
||
| 96 | } |
||
| 97 | |||
| 98 | return view('note', [ |
||
| 99 | 'label' => $label, |
||
| 100 | 'first_line' => $first_line, |
||
| 101 | 'paragraphs' => $note->getHtml(), |
||
| 102 | 'one_line_only' => $one_line_only, |
||
| 103 | 'id' => 'collapse-' . Uuid::uuid4()->toString(), |
||
| 104 | 'expanded' => (bool) $tree->getPreference('EXPAND_NOTES'), |
||
| 105 | ]); |
||
| 106 | } |
||
| 107 | |||
| 108 | return ''; |
||
| 109 | } |
||
| 110 | |||
| 111 | /** |
||
| 112 | * Print all of the notes in this fact record |
||
| 113 | * |
||
| 114 | * @param Tree $tree |
||
| 115 | * @param string $factrec The fact to print the notes from |
||
| 116 | * @param int $level The level of the notes |
||
| 117 | * |
||
| 118 | * @return string HTML |
||
| 119 | */ |
||
| 120 | public static function printFactNotes(Tree $tree, string $factrec, int $level): string |
||
| 121 | { |
||
| 122 | $data = ''; |
||
| 123 | $previous_spos = 0; |
||
| 124 | $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER); |
||
| 125 | for ($j = 0; $j < $ct; $j++) { |
||
| 126 | $spos1 = strpos($factrec, $match[$j][0], $previous_spos); |
||
| 127 | $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1); |
||
| 128 | if (!$spos2) { |
||
| 129 | $spos2 = strlen($factrec); |
||
| 130 | } |
||
| 131 | $previous_spos = $spos2; |
||
| 132 | $nrec = substr($factrec, $spos1, $spos2 - $spos1); |
||
| 133 | if (!isset($match[$j][1])) { |
||
| 134 | $match[$j][1] = ''; |
||
| 135 | } |
||
| 136 | if (!preg_match('/^@(' . Gedcom::REGEX_XREF . ')@$/', $match[$j][1], $nmatch)) { |
||
| 137 | $data .= self::printNoteRecord($tree, null, $nrec); |
||
| 138 | } else { |
||
| 139 | $note = Registry::noteFactory()->make($nmatch[1], $tree); |
||
| 140 | $data .= self::printNoteRecord($tree, $note); |
||
| 141 | } |
||
| 142 | } |
||
| 143 | |||
| 144 | return $data; |
||
| 145 | } |
||
| 146 | |||
| 147 | /** |
||
| 148 | * Format age of parents in HTML |
||
| 149 | * |
||
| 150 | * @param Individual $person child |
||
| 151 | * @param Date $birth_date |
||
| 152 | * |
||
| 153 | * @return string HTML |
||
| 154 | */ |
||
| 155 | public static function formatParentsAges(Individual $person, Date $birth_date): string |
||
| 156 | { |
||
| 157 | $html = ''; |
||
| 158 | $families = $person->childFamilies(); |
||
| 159 | // Multiple sets of parents (e.g. adoption) cause complications, so ignore. |
||
| 160 | if ($birth_date->isOK() && $families->count() === 1) { |
||
| 161 | $family = $families->first(); |
||
| 162 | foreach ($family->spouses() as $parent) { |
||
| 163 | if ($parent->getBirthDate()->isOK()) { |
||
| 164 | $sex = '<small>' . view('icons/sex', ['sex' => $parent->sex()]) . '</small>'; |
||
| 165 | $age = new Age($parent->getBirthDate(), $birth_date); |
||
| 166 | $deatdate = $parent->getDeathDate(); |
||
| 167 | switch ($parent->sex()) { |
||
| 168 | case 'F': |
||
| 169 | // Highlight mothers who die in childbirth or shortly afterwards |
||
| 170 | if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) { |
||
| 171 | $html .= ' <span title="' . I18N::translate('Death of a mother') . '" class="parentdeath">' . $sex . I18N::number($age->ageYears()) . '</span>'; |
||
| 172 | } else { |
||
| 173 | $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . I18N::number($age->ageYears()) . '</span>'; |
||
| 174 | } |
||
| 175 | break; |
||
| 176 | case 'M': |
||
| 177 | // Highlight fathers who die before the birth |
||
| 178 | if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) { |
||
| 179 | $html .= ' <span title="' . I18N::translate('Death of a father') . '" class="parentdeath">' . $sex . I18N::number($age->ageYears()) . '</span>'; |
||
| 180 | } else { |
||
| 181 | $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . I18N::number($age->ageYears()) . '</span>'; |
||
| 182 | } |
||
| 183 | break; |
||
| 184 | default: |
||
| 185 | $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . I18N::number($age->ageYears()) . '</span>'; |
||
| 186 | break; |
||
| 187 | } |
||
| 188 | } |
||
| 189 | } |
||
| 190 | if ($html) { |
||
| 191 | $html = '<span class="age">' . $html . '</span>'; |
||
| 192 | } |
||
| 193 | } |
||
| 194 | |||
| 195 | return $html; |
||
| 196 | } |
||
| 197 | |||
| 198 | /** |
||
| 199 | * Print fact DATE/TIME |
||
| 200 | * |
||
| 201 | * @param Fact $event event containing the date/age |
||
| 202 | * @param GedcomRecord $record the person (or couple) whose ages should be printed |
||
| 203 | * @param bool $anchor option to print a link to calendar |
||
| 204 | * @param bool $time option to print TIME value |
||
| 205 | * |
||
| 206 | * @return string |
||
| 207 | */ |
||
| 208 | public static function formatFactDate(Fact $event, GedcomRecord $record, bool $anchor, bool $time): string |
||
| 209 | { |
||
| 210 | $element_factory = Registry::elementFactory(); |
||
| 211 | |||
| 212 | $factrec = $event->gedcom(); |
||
| 213 | $html = ''; |
||
| 214 | // Recorded age |
||
| 215 | if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { |
||
| 216 | $fact_age = $element_factory->make($event->tag() . ':AGE')->value($match[1], $record->tree()); |
||
| 217 | } else { |
||
| 218 | $fact_age = ''; |
||
| 219 | } |
||
| 220 | if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { |
||
| 221 | $husb_age = $element_factory->make($event->tag() . ':HUSB:AGE')->value($match[1], $record->tree()); |
||
| 222 | } else { |
||
| 223 | $husb_age = ''; |
||
| 224 | } |
||
| 225 | if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { |
||
| 226 | $wife_age = $element_factory->make($event->tag() . ':WIFE:AGE')->value($match[1], $record->tree()); |
||
| 227 | } else { |
||
| 228 | $wife_age = ''; |
||
| 229 | } |
||
| 230 | |||
| 231 | // Calculated age |
||
| 232 | [, $fact] = explode(':', $event->tag()); |
||
| 233 | |||
| 234 | if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) { |
||
| 235 | $date = new Date($match[1]); |
||
| 236 | $html .= ' ' . $date->display($anchor); |
||
| 237 | // time |
||
| 238 | if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) { |
||
| 239 | $html .= ' – <span class="date">' . $match[1] . '</span>'; |
||
| 240 | } |
||
| 241 | if ($record instanceof Individual) { |
||
| 242 | if (in_array($fact, Gedcom::BIRTH_EVENTS, true) && $record->tree()->getPreference('SHOW_PARENTS_AGE')) { |
||
| 243 | // age of parents at child birth |
||
| 244 | $html .= self::formatParentsAges($record, $date); |
||
| 245 | } |
||
| 246 | if ($fact !== 'BIRT' && $fact !== 'CHAN' && $fact !== '_TODO') { |
||
| 247 | // age at event |
||
| 248 | $birth_date = $record->getBirthDate(); |
||
| 249 | // Can't use getDeathDate(), as this also gives BURI/CREM events, which |
||
| 250 | // wouldn't give the correct "days after death" result for people with |
||
| 251 | // no DEAT. |
||
| 252 | $death_event = $record->facts(['DEAT'])->first(); |
||
| 253 | if ($death_event instanceof Fact) { |
||
| 254 | $death_date = $death_event->date(); |
||
| 255 | } else { |
||
| 256 | $death_date = new Date(''); |
||
| 257 | } |
||
| 258 | $ageText = ''; |
||
| 259 | if ($fact === 'DEAT' || Date::compare($date, $death_date) <= 0 || !$record->isDead()) { |
||
| 260 | // Before death, print age |
||
| 261 | $age = (string) new Age($birth_date, $date); |
||
| 262 | |||
| 263 | // Only show calculated age if it differs from recorded age |
||
| 264 | if ($age !== '') { |
||
| 265 | if ( |
||
| 266 | $fact_age !== '' && !str_starts_with($fact_age, $age) || |
||
| 267 | $fact_age === '' && $husb_age === '' && $wife_age === '' || |
||
| 268 | $husb_age !== '' && !str_starts_with($husb_age, $age) && $record->sex() === 'M' || |
||
| 269 | $wife_age !== '' && !str_starts_with($wife_age, $age) && $record->sex() === 'F' |
||
| 270 | ) { |
||
| 271 | switch ($record->sex()) { |
||
| 272 | case 'M': |
||
| 273 | /* I18N: The age of an individual at a given date */ |
||
| 274 | $ageText = I18N::translateContext('Male', '(aged %s)', $age); |
||
| 275 | break; |
||
| 276 | case 'F': |
||
| 277 | /* I18N: The age of an individual at a given date */ |
||
| 278 | $ageText = I18N::translateContext('Female', '(aged %s)', $age); |
||
| 279 | break; |
||
| 280 | default: |
||
| 281 | /* I18N: The age of an individual at a given date */ |
||
| 282 | $ageText = I18N::translate('(aged %s)', $age); |
||
| 283 | break; |
||
| 284 | } |
||
| 285 | } |
||
| 286 | } |
||
| 287 | } |
||
| 288 | if ($fact !== 'DEAT' && $death_date->isOK() && Date::compare($death_date, $date) <= 0) { |
||
| 289 | $death_day = $death_date->minimumDate()->day(); |
||
| 290 | $event_day = $date->minimumDate()->day(); |
||
| 291 | if ($death_day !== 0 && $event_day !== 0 && Date::compare($death_date, $date) === 0) { |
||
| 292 | // On the exact date of death? |
||
| 293 | // NOTE: this path is never reached. Keep the code (translation) in case |
||
| 294 | // we decide to re-introduce it. |
||
| 295 | $ageText = I18N::translate('(on the date of death)'); |
||
| 296 | } else { |
||
| 297 | // After death |
||
| 298 | $age = (string) new Age($death_date, $date); |
||
| 299 | $ageText = I18N::translate('(%s after death)', $age); |
||
| 300 | } |
||
| 301 | // Family events which occur after death are probably errors |
||
| 302 | if ($event->record() instanceof Family) { |
||
| 303 | $ageText .= view('icons/warning'); |
||
| 304 | } |
||
| 305 | } |
||
| 306 | if ($ageText !== '') { |
||
| 307 | $html .= ' <span class="age">' . $ageText . '</span>'; |
||
| 308 | } |
||
| 309 | } |
||
| 310 | } |
||
| 311 | } |
||
| 312 | // print gedcom ages |
||
| 313 | $age_labels = [ |
||
| 314 | I18N::translate('Age') => $fact_age, |
||
| 315 | I18N::translate('Husband') => $husb_age, |
||
| 316 | I18N::translate('Wife') => $wife_age, |
||
| 317 | ]; |
||
| 318 | |||
| 319 | foreach (array_filter($age_labels) as $label => $age) { |
||
| 320 | $html .= ' <span class="label">' . $label . ':</span> <span class="age">' . $age . '</span>'; |
||
| 321 | } |
||
| 322 | |||
| 323 | return $html; |
||
| 324 | } |
||
| 325 | |||
| 326 | /** |
||
| 327 | * print fact PLACe TEMPle STATus |
||
| 328 | * |
||
| 329 | * @param Fact $event gedcom fact record |
||
| 330 | * @param bool $anchor to print a link to placelist |
||
| 331 | * @param bool $sub_records to print place subrecords |
||
| 332 | * @param bool $lds to print LDS TEMPle and STATus |
||
| 333 | * |
||
| 334 | * @return string HTML |
||
| 335 | */ |
||
| 336 | public static function formatFactPlace(Fact $event, bool $anchor, bool $sub_records, bool $lds): string |
||
| 337 | { |
||
| 338 | $tree = $event->record()->tree(); |
||
| 339 | |||
| 340 | if ($anchor) { |
||
| 341 | // Show the full place name, for facts/events tab |
||
| 342 | $html = $event->place()->fullName(true); |
||
| 343 | } else { |
||
| 344 | // Abbreviate the place name, for chart boxes |
||
| 345 | return $event->place()->shortName(); |
||
| 346 | } |
||
| 347 | |||
| 348 | if ($sub_records) { |
||
| 349 | $placerec = Functions::getSubRecord(2, '2 PLAC', $event->gedcom()); |
||
| 350 | if ($placerec !== '') { |
||
| 351 | if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) { |
||
| 352 | foreach ($matches[1] as $match) { |
||
| 353 | $wt_place = new Place($match, $tree); |
||
| 354 | $html .= ' - ' . $wt_place->fullName(); |
||
| 355 | } |
||
| 356 | } |
||
| 357 | |||
| 358 | $latitude = $event->latitude(); |
||
| 359 | $longitude = $event->longitude(); |
||
| 360 | |||
| 361 | if ($latitude !== null && $longitude !== null) { |
||
| 362 | $html .= '<br><span class="label">' . I18N::translate('Latitude') . ': </span>' . $latitude; |
||
| 363 | $html .= ' <span class="label">' . I18N::translate('Longitude') . ': </span>' . $longitude; |
||
| 364 | |||
| 365 | // Links to external maps |
||
| 366 | $html .= app(ModuleService::class) |
||
| 367 | ->findByInterface(ModuleMapLinkInterface::class) |
||
| 368 | ->map(fn (ModuleMapLinkInterface $module): string => ' ' . $module->mapLink($event)) |
||
| 369 | ->implode(''); |
||
| 370 | } |
||
| 371 | |||
| 372 | if (preg_match('/\d NOTE (.*)/', $placerec, $match)) { |
||
| 373 | $html .= '<br>' . self::printFactNotes($tree, $placerec, 3); |
||
| 374 | } |
||
| 375 | } |
||
| 376 | } |
||
| 377 | if ($lds) { |
||
| 378 | if (preg_match('/2 TEMP (.*)/', $event->gedcom(), $match)) { |
||
| 379 | $element = Registry::elementFactory()->make($event->tag() . ':TEMP'); |
||
| 380 | $html .= $element->labelValue($match[1], $tree); |
||
| 381 | } |
||
| 382 | if (preg_match('/2 STAT (.*)/', $event->gedcom(), $match)) { |
||
| 383 | $element = Registry::elementFactory()->make($event->tag() . ':STAT'); |
||
| 384 | $html .= $element->labelValue($match[1], $tree); |
||
| 385 | if (preg_match('/3 DATE (.*)/', $event->gedcom(), $match)) { |
||
| 386 | $date = new Date($match[1]); |
||
| 387 | $element = Registry::elementFactory()->make($event->tag() . ':STAT:DATE'); |
||
| 388 | $html .= $element->labelValue($date->display(), $tree); |
||
| 389 | } |
||
| 390 | } |
||
| 391 | } |
||
| 392 | |||
| 393 | return $html; |
||
| 394 | } |
||
| 395 | |||
| 396 | /** |
||
| 397 | * Print a new fact box on details pages |
||
| 398 | * |
||
| 399 | * @param Individual|Family $record |
||
| 400 | * |
||
| 401 | * @return void |
||
| 402 | */ |
||
| 403 | public static function printAddNewFact(GedcomRecord $record): void |
||
| 488 | ]); |
||
| 489 | } |
||
| 490 | } |
||
| 491 |