1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2017 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 */ 16namespace Fisharebest\Webtrees\Module; 17 18use Fisharebest\Webtrees\Auth; 19use Fisharebest\Webtrees\Date; 20use Fisharebest\Webtrees\Family; 21use Fisharebest\Webtrees\Functions\Functions; 22use Fisharebest\Webtrees\GedcomTag; 23use Fisharebest\Webtrees\I18N; 24use Fisharebest\Webtrees\Individual; 25use Fisharebest\Webtrees\Theme; 26 27/** 28 * Class RelativesTabModule 29 */ 30class RelativesTabModule extends AbstractModule implements ModuleTabInterface { 31 /** 32 * How should this module be labelled on tabs, menus, etc.? 33 * 34 * @return string 35 */ 36 public function getTitle() { 37 return /* I18N: Name of a module */ I18N::translate('Families'); 38 } 39 40 /** 41 * A sentence describing what this module does. 42 * 43 * @return string 44 */ 45 public function getDescription() { 46 return /* I18N: Description of the “Families” module */ I18N::translate('A tab showing the close relatives of an individual.'); 47 } 48 49 /** 50 * The user can re-arrange the tab order, but until they do, this 51 * is the order in which tabs are shown. 52 * 53 * @return int 54 */ 55 public function defaultTabOrder() { 56 return 20; 57 } 58 59 /** 60 * Display the age difference between marriages and the births of children. 61 * 62 * @param Date $prev 63 * @param Date $next 64 * @param int $child_number 65 * 66 * @return string 67 */ 68 private static function ageDifference(Date $prev, Date $next, $child_number = 0) { 69 if ($prev->isOK() && $next->isOK()) { 70 $days = $next->maximumJulianDay() - $prev->minimumJulianDay(); 71 if ($days < 0) { 72 // Show warning triangle if dates in reverse order 73 $diff = '<i class="icon-warning"></i> '; 74 } elseif ($child_number > 1 && $days > 1 && $days < 240) { 75 // Show warning triangle if children born too close together 76 $diff = '<i class="icon-warning"></i> '; 77 } else { 78 $diff = ''; 79 } 80 81 $months = round($days * 12 / 365.25); // Approximate - we do not know the calendar 82 if (abs($months) == 12 || abs($months) >= 24) { 83 $diff .= I18N::plural('%s year', '%s years', round($months / 12), I18N::number(round($months / 12))); 84 } elseif ($months != 0) { 85 $diff .= I18N::plural('%s month', '%s months', $months, I18N::number($months)); 86 } 87 88 return '<div class="elderdate age">' . $diff . '</div>'; 89 } else { 90 return ''; 91 } 92 } 93 94 /** 95 * Print a family group. 96 * 97 * @param Family $family 98 * @param string $type 99 * @param string $label 100 */ 101 private function printFamily(Family $family, $type, $label) { 102 global $controller; 103 104 if ($family->getTree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS')) { 105 $access_level = Auth::PRIV_HIDE; 106 } else { 107 $access_level = Auth::accessLevel($family->getTree()); 108 } 109 110 ?> 111 <table> 112 <tr> 113 <td> 114 <i class="icon-cfamily"></i> 115 </td> 116 <td> 117 <span class="subheaders"> <?= $label ?></span> 118 <a href="<?= e($family->url()) ?>"> - <?= I18N::translate('View this family') ?></a> 119 </td> 120 </tr> 121 </table> 122 123 <table class="table table-sm wt-facts-table"> 124 <caption></caption> 125 <tbody> 126 <?php 127 128 ///// HUSB ///// 129 $found = false; 130 foreach ($family->getFacts('HUSB', false, $access_level) as $fact) { 131 $found |= !$fact->isPendingDeletion(); 132 $person = $fact->getTarget(); 133 if ($person instanceof Individual) { 134 $row_class = 'wt-gender-' . $person->getSex(); 135 if ($fact->isPendingAddition()) { 136 $row_class .= ' new'; 137 } elseif ($fact->isPendingDeletion()) { 138 $row_class .= ' old'; 139 } 140 $icon = $controller->record === $person ? '<i class="icon-selected"></i>' : ''; 141 ?> 142 <tr class="<?= $row_class ?>"> 143 <th scope="row"> 144 <?= $icon ?> 145 <?= Functions::getCloseRelationshipName($controller->record, $person) ?> 146 </th> 147 <td class="border-0 p-0"> 148 <?= Theme::theme()->individualBoxLarge($person) ?> 149 </td> 150 </tr> 151 <?php 152 } 153 } 154 if (!$found && $family->canEdit()) { 155 ?> 156 <tr> 157 <th></th> 158 <td scope="row"> 159 <a href="edit_interface.php?action=add_spouse_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&famtag=HUSB"> 160 <?= I18N::translate('Add a husband to this family') ?> 161 </a> 162 </td> 163 </tr> 164 <?php 165 } 166 167 ///// WIFE ///// 168 $found = false; 169 foreach ($family->getFacts('WIFE', false, $access_level) as $fact) { 170 $person = $fact->getTarget(); 171 if ($person instanceof Individual) { 172 $found |= !$fact->isPendingDeletion(); 173 $row_class = 'wt-gender-' . $person->getSex(); 174 if ($fact->isPendingAddition()) { 175 $row_class .= ' new'; 176 } elseif ($fact->isPendingDeletion()) { 177 $row_class .= ' old'; 178 } 179 $icon = $controller->record === $person ? '<i class="icon-selected"></i>' : ''; 180 ?> 181 <tr class="<?= $row_class ?>"> 182 <th scope="row"> 183 <?= $icon ?> 184 <?= Functions::getCloseRelationshipName($controller->record, $person) ?> 185 </th> 186 <td class="border-0 p-0"> 187 <?= Theme::theme()->individualBoxLarge($person) ?> 188 </td> 189 </tr> 190 <?php 191 } 192 } 193 if (!$found && $family->canEdit()) { 194 ?> 195 <tr> 196 <th scope="row"></th> 197 <td> 198 <a href="edit_interface.php?action=add_spouse_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&famtag=WIFE"> 199 <?= I18N::translate('Add a wife to this family') ?> 200 </a> 201 </td> 202 </tr> 203 <?php 204 } 205 206 ///// MARR ///// 207 $found = false; 208 $prev = new Date(''); 209 foreach ($family->getFacts(WT_EVENTS_MARR . '|' . WT_EVENTS_DIV, true) as $fact) { 210 $found |= !$fact->isPendingDeletion(); 211 if ($fact->isPendingAddition()) { 212 $row_class = 'new'; 213 } elseif ($fact->isPendingDeletion()) { 214 $row_class = 'old'; 215 } else { 216 $row_class = ''; 217 } 218 ?> 219 <tr class="<?= $row_class ?>"> 220 <th scope="row"> 221 </th> 222 <td> 223 <?= GedcomTag::getLabelValue($fact->getTag(), $fact->getDate()->display() . ' — ' . $fact->getPlace()->getFullName()) ?> 224 </td> 225 </tr> 226 <?php 227 if (!$prev->isOK() && $fact->getDate()->isOK()) { 228 $prev = $fact->getDate(); 229 } 230 } 231 if (!$found && $family->canShow() && $family->canEdit()) { 232 // Add a new marriage 233 ?> 234 <tr> 235 <th scope="row"> 236 </th> 237 <td> 238 <a href="edit_interface.php?action=add&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&fact=MARR"> 239 <?= I18N::translate('Add marriage details') ?> 240 </a> 241 </td> 242 </tr> 243 <?php 244 } 245 246 ///// CHIL ///// 247 $child_number = 0; 248 foreach ($family->getFacts('CHIL', false, $access_level) as $fact) { 249 $person = $fact->getTarget(); 250 if ($person instanceof Individual) { 251 $row_class = 'wt-gender-' . $person->getSex(); 252 if ($fact->isPendingAddition()) { 253 $child_number++; 254 $row_class .= ' new'; 255 } elseif ($fact->isPendingDeletion()) { 256 $row_class .= ' old'; 257 } else { 258 $child_number++; 259 } 260 $next = new Date(''); 261 foreach ($person->getFacts(WT_EVENTS_BIRT, true) as $bfact) { 262 if ($bfact->getDate()->isOK()) { 263 $next = $bfact->getDate(); 264 break; 265 } 266 } 267 $icon = $controller->record === $person ? '<i class="icon-selected"></i>' : ''; 268 ?> 269 <tr class="<?= $row_class ?>"> 270 <th scope="row"> 271 <?= $icon ?> 272 <?= self::ageDifference($prev, $next, $child_number) ?> 273 <?= Functions::getCloseRelationshipName($controller->record, $person) ?> 274 </th> 275 <td class="border-0 p-0"> 276 <?= Theme::theme()->individualBoxLarge($person) ?> 277 </td> 278 </tr> 279 <?php 280 $prev = $next; 281 } 282 } 283 // Re-order children / add a new child 284 if ($family->canEdit()) { 285 if ($type == 'FAMS') { 286 $add_child_text = I18N::translate('Add a son or daughter'); 287 } else { 288 $add_child_text = I18N::translate('Add a brother or sister'); 289 } 290 ?> 291 <tr> 292 <th scope="row"> 293 <?php if (count($family->getChildren()) > 1): ?> 294 <a href="edit_interface.php?action=reorder-children&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>"> 295 <i class="icon-media-shuffle"></i> <?= I18N::translate('Re-order children') ?> 296 </a> 297 <?php endif; ?> 298 </th> 299 <td> 300 <a href="edit_interface.php?action=add_child_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&gender=U"> 301 <?= $add_child_text ?> 302 </a> 303 <span style='white-space:nowrap;'> 304 <a href="edit_interface.php?action=add_child_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&gender=M" class="icon-sex_m_15x15"></a> 305 <a href="edit_interface.php?action=add_child_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&gender=F" class="icon-sex_f_15x15"></a> 306 </span> 307 </td> 308 </tr> 309 <?php 310 } 311 312 echo '</tbody>'; 313 echo '</table>'; 314 } 315 316 /** {@inheritdoc} */ 317 public function getTabContent() { 318 global $controller; 319 320 ob_start(); 321 ?> 322 <table class="table table-sm wt-facts-table" role="presentation"> 323 <tbody> 324 <tr> 325 <td> 326 <label> 327 <input id="show-date-differences" type="checkbox" checked> 328 <?= I18N::translate('Date differences') ?> 329 </label> 330 </td> 331 </tr> 332 </tbody> 333 </table> 334 <?php 335 $families = $controller->record->getChildFamilies(); 336 if (!$families && $controller->record->canEdit()) { 337 ?> 338 <table class="table table-sm wt-facts-table"> 339 <tbody> 340 <tr> 341 <td> 342 <a href="edit_interface.php?action=add_parent_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&gender=M"> 343 <?= I18N::translate('Add a father') ?> 344 </a> 345 </td> 346 </tr> 347 <tr> 348 <td> 349 <a href="edit_interface.php?action=add_parent_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&gender=F"> 350 <?= I18N::translate('Add a mother') ?> 351 </a> 352 </td> 353 </tr> 354 </tbody> 355 </table> 356 <?php 357 } 358 359 // parents 360 foreach ($families as $family) { 361 $this->printFamily($family, 'FAMC', $controller->record->getChildFamilyLabel($family)); 362 } 363 364 // step-parents 365 foreach ($controller->record->getChildStepFamilies() as $family) { 366 $this->printFamily($family, 'FAMC', $controller->record->getStepFamilyLabel($family)); 367 } 368 369 // spouses 370 $families = $controller->record->getSpouseFamilies(); 371 foreach ($families as $family) { 372 $this->printFamily($family, 'FAMS', $controller->getSpouseFamilyLabel($family, $controller->record)); 373 } 374 375 // step-children 376 foreach ($controller->record->getSpouseStepFamilies() as $family) { 377 $this->printFamily($family, 'FAMS', $family->getFullName()); 378 } 379 380 if ($controller->record->canEdit()) { 381 ?> 382 <br> 383 <table class="table table-sm wt-facts-table"> 384 <tbody> 385 <?php if (count($families) > 1) { ?> 386 <tr> 387 <td> 388 <a href="edit_interface.php?action=reorder-spouses&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>"> 389 <?= I18N::translate('Re-order families') ?> 390 </a> 391 </td> 392 </tr> 393 <?php } ?> 394 <tr> 395 <td> 396 <a href="edit_interface.php?action=addfamlink&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>"><?= I18N::translate('Link this individual to an existing family as a child') ?></a> 397 </td> 398 </tr> 399 <?php if ($controller->record->getSex() !== 'F') { ?> 400 <tr> 401 <td> 402 <a href="edit_interface.php?action=add_spouse_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&sex=F"><?= I18N::translate('Add a wife') ?></a> 403 </td> 404 </tr> 405 <tr> 406 <td> 407 <a href="edit_interface.php?action=linkspouse&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&famtag=WIFE"><?= I18N::translate('Add a wife using an existing individual') ?></a> 408 </td> 409 </tr> 410 <?php } ?> 411 <?php if ($controller->record->getSex() !== 'M') { ?> 412 <tr> 413 <td> 414 <a href="edit_interface.php?action=add_spouse_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&sex=M"><?= I18N::translate('Add a husband') ?></a> 415 </td> 416 </tr> 417 <tr> 418 <td> 419 <a href="edit_interface.php?action=linkspouse&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&famtag=HUSB"><?= I18N::translate('Add a husband using an existing individual') ?></a> 420 </td> 421 </tr> 422 <?php } ?> 423 <tr> 424 <td> 425 <a href="edit_interface.php?action=add_child_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&gender=U"> 426 <?= I18N::translate('Add a child to create a one-parent family') ?> 427 </a> 428 </td> 429 </tr> 430 </tbody> 431 </table> 432 <?php } ?> 433 <br> 434 <script> 435 //persistent_toggle("show-date-differences", ".elderdate"); 436 </script> 437 <?php 438 439 return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>'; 440 } 441 442 /** {@inheritdoc} */ 443 public function hasTabContent() { 444 return true; 445 } 446 /** {@inheritdoc} */ 447 public function isGrayedOut() { 448 return false; 449 } 450 /** {@inheritdoc} */ 451 public function canLoadAjax() { 452 return false; 453 } 454 455 /** {@inheritdoc} */ 456 public function getPreLoadContent() { 457 return ''; 458 } 459} 460