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="<?= $family->getHtmlUrl() ?>"> - <?= I18N::translate('View this family') ?></a> 119 </td> 120 </tr> 121 </table> 122 <table class="facts_table"> 123 <?php 124 125 ///// HUSB ///// 126 $found = false; 127 foreach ($family->getFacts('HUSB', false, $access_level) as $fact) { 128 $found |= !$fact->isPendingDeletion(); 129 $person = $fact->getTarget(); 130 if ($person instanceof Individual) { 131 if ($fact->isPendingAddition()) { 132 $class = 'facts_label new'; 133 } elseif ($fact->isPendingDeletion()) { 134 $class = 'facts_label old'; 135 } else { 136 $class = 'facts_label'; 137 } 138 ?> 139 <tr> 140 <td class="<?= $class ?>"> 141 <?= Functions::getCloseRelationshipName($controller->record, $person) ?> 142 </td> 143 <td class="<?= $controller->getPersonStyle($person) ?>"> 144 <?= Theme::theme()->individualBoxLarge($person) ?> 145 </td> 146 </tr> 147 <?php 148 } 149 } 150 if (!$found && $family->canEdit()) { 151 ?> 152 <tr> 153 <td class="facts_label"></td> 154 <td class="facts_value"> 155 <a href="edit_interface.php?action=add_spouse_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&famtag=HUSB"> 156 <?= I18N::translate('Add a husband to this family') ?> 157 </a> 158 </td> 159 </tr> 160 <?php 161 } 162 163 ///// WIFE ///// 164 $found = false; 165 foreach ($family->getFacts('WIFE', false, $access_level) as $fact) { 166 $person = $fact->getTarget(); 167 if ($person instanceof Individual) { 168 $found |= !$fact->isPendingDeletion(); 169 if ($fact->isPendingAddition()) { 170 $class = 'facts_label new'; 171 } elseif ($fact->isPendingDeletion()) { 172 $class = 'facts_label old'; 173 } else { 174 $class = 'facts_label'; 175 } 176 ?> 177 <tr> 178 <td class="<?= $class ?>"> 179 <?= Functions::getCloseRelationshipName($controller->record, $person) ?> 180 </td> 181 <td class="<?= $controller->getPersonStyle($person) ?>"> 182 <?= Theme::theme()->individualBoxLarge($person) ?> 183 </td> 184 </tr> 185 <?php 186 } 187 } 188 if (!$found && $family->canEdit()) { 189 ?> 190 <tr> 191 <td class="facts_label"></td> 192 <td class="facts_value"> 193 <a href="edit_interface.php?action=add_spouse_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&famtag=WIFE"> 194 <?= I18N::translate('Add a wife to this family') ?> 195 </a> 196 </td> 197 </tr> 198 <?php 199 } 200 201 ///// MARR ///// 202 $found = false; 203 $prev = new Date(''); 204 foreach ($family->getFacts(WT_EVENTS_MARR . '|' . WT_EVENTS_DIV, true) as $fact) { 205 $found |= !$fact->isPendingDeletion(); 206 if ($fact->isPendingAddition()) { 207 $class = ' new'; 208 } elseif ($fact->isPendingDeletion()) { 209 $class = ' old'; 210 } else { 211 $class = ''; 212 } 213 ?> 214 <tr> 215 <td class="facts_label"> 216 </td> 217 <td class="facts_value<?= $class ?>"> 218 <?= GedcomTag::getLabelValue($fact->getTag(), $fact->getDate()->display() . ' — ' . $fact->getPlace()->getFullName()) ?> 219 </td> 220 </tr> 221 <?php 222 if (!$prev->isOK() && $fact->getDate()->isOK()) { 223 $prev = $fact->getDate(); 224 } 225 } 226 if (!$found && $family->canShow() && $family->canEdit()) { 227 // Add a new marriage 228 ?> 229 <tr> 230 <td class="facts_label"> 231 </td> 232 <td class="facts_value"> 233 <a href="edit_interface.php?action=add&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&fact=MARR"> 234 <?= I18N::translate('Add marriage details') ?> 235 </a> 236 </td> 237 </tr> 238 <?php 239 } 240 241 ///// CHIL ///// 242 $child_number = 0; 243 foreach ($family->getFacts('CHIL', false, $access_level) as $fact) { 244 $person = $fact->getTarget(); 245 if ($person instanceof Individual) { 246 if ($fact->isPendingAddition()) { 247 $child_number++; 248 $class = 'facts_label new'; 249 } elseif ($fact->isPendingDeletion()) { 250 $class = 'facts_label old'; 251 } else { 252 $child_number++; 253 $class = 'facts_label'; 254 } 255 $next = new Date(''); 256 foreach ($person->getFacts(WT_EVENTS_BIRT, true) as $bfact) { 257 if ($bfact->getDate()->isOK()) { 258 $next = $bfact->getDate(); 259 break; 260 } 261 } 262 ?> 263 <tr> 264 <td class="<?= $class ?>"> 265 <?= self::ageDifference($prev, $next, $child_number) ?> 266 <?= Functions::getCloseRelationshipName($controller->record, $person) ?> 267 </td> 268 <td class="<?= $controller->getPersonStyle($person) ?>"> 269 <?= Theme::theme()->individualBoxLarge($person) ?> 270 </td> 271 </tr> 272 <?php 273 $prev = $next; 274 } 275 } 276 // Re-order children / add a new child 277 if ($family->canEdit()) { 278 if ($type == 'FAMS') { 279 $add_child_text = I18N::translate('Add a son or daughter'); 280 } else { 281 $add_child_text = I18N::translate('Add a brother or sister'); 282 } 283 ?> 284 <tr> 285 <td class="facts_label"> 286 <?php if (count($family->getChildren()) > 1): ?> 287 <a href="edit_interface.php?action=reorder-children&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>"> 288 <i class="icon-media-shuffle"></i> <?= I18N::translate('Re-order children') ?> 289 </a> 290 <?php endif; ?> 291 </td> 292 <td class="facts_value"> 293 <a href="edit_interface.php?action=add_child_to_family&ged=<?= $family->getTree()->getNameHtml() ?>&xref=<?= $family->getXref() ?>&gender=U"> 294 <?= $add_child_text ?> 295 </a> 296 <span style='white-space:nowrap;'> 297 <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> 298 <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> 299 </span> 300 </td> 301 </tr> 302 <?php 303 } 304 305 echo '</table>'; 306 } 307 308 /** {@inheritdoc} */ 309 public function getTabContent() { 310 global $controller; 311 312 ob_start(); 313 ?> 314 <table class="facts_table"> 315 <tr> 316 <td class="descriptionbox rela"> 317 <label> 318 <input id="show-date-differences" type="checkbox" checked> 319 <?= I18N::translate('Date differences') ?> 320 </label> 321 </td> 322 </tr> 323 </table> 324 <?php 325 $families = $controller->record->getChildFamilies(); 326 if (!$families && $controller->record->canEdit()) { 327 ?> 328 <table class="facts_table"> 329 <tr> 330 <td class="facts_value"> 331 <a href="edit_interface.php?action=add_parent_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&gender=M"> 332 <?= I18N::translate('Add a father') ?> 333 </a> 334 </td> 335 </tr> 336 <tr> 337 <td class="facts_value"> 338 <a href="edit_interface.php?action=add_parent_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&gender=F"> 339 <?= I18N::translate('Add a mother') ?> 340 </a> 341 </td> 342 </tr> 343 </table> 344 <?php 345 } 346 347 // parents 348 foreach ($families as $family) { 349 $this->printFamily($family, 'FAMC', $controller->record->getChildFamilyLabel($family)); 350 } 351 352 // step-parents 353 foreach ($controller->record->getChildStepFamilies() as $family) { 354 $this->printFamily($family, 'FAMC', $controller->record->getStepFamilyLabel($family)); 355 } 356 357 // spouses 358 $families = $controller->record->getSpouseFamilies(); 359 foreach ($families as $family) { 360 $this->printFamily($family, 'FAMS', $controller->getSpouseFamilyLabel($family, $controller->record)); 361 } 362 363 // step-children 364 foreach ($controller->record->getSpouseStepFamilies() as $family) { 365 $this->printFamily($family, 'FAMS', $family->getFullName()); 366 } 367 368 if ($controller->record->canEdit()) { 369 ?> 370 <br><table class="facts_table"> 371 <?php 372 if (count($families) > 1) { ?> 373 <tr> 374 <td class="facts_value"> 375 <a href="edit_interface.php?action=reorder-spouses&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>"> 376 <?= I18N::translate('Re-order families') ?> 377 </a> 378 </td> 379 </tr> 380 <?php } ?> 381 <tr> 382 <td class="facts_value"> 383 <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> 384 </td> 385 </tr> 386 <?php if ($controller->record->getSex() !== 'F') { ?> 387 <tr> 388 <td class="facts_value"> 389 <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> 390 </td> 391 </tr> 392 <tr> 393 <td class="facts_value"> 394 <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> 395 </td> 396 </tr> 397 <?php } 398 if ($controller->record->getSex() !== 'M') { ?> 399 <tr> 400 <td class="facts_value"> 401 <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> 402 </td> 403 </tr> 404 <tr> 405 <td class="facts_value"> 406 <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> 407 </td> 408 </tr> 409 <?php } ?> 410 <tr> 411 <td class="facts_value"> 412 <a href="edit_interface.php?action=add_child_to_individual&ged=<?= $controller->record->getTree()->getNameHtml() ?>&xref=<?= $controller->record->getXref() ?>&gender=U"> 413 <?= I18N::translate('Add a child to create a one-parent family') ?> 414 </a> 415 </td> 416 </tr> 417 </table> 418 <?php } ?> 419 <br> 420 <script> 421 //persistent_toggle("show-date-differences", ".elderdate"); 422 </script> 423 <?php 424 425 return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>'; 426 } 427 428 /** {@inheritdoc} */ 429 public function hasTabContent() { 430 return true; 431 } 432 /** {@inheritdoc} */ 433 public function isGrayedOut() { 434 return false; 435 } 436 /** {@inheritdoc} */ 437 public function canLoadAjax() { 438 return false; 439 } 440 441 /** {@inheritdoc} */ 442 public function getPreLoadContent() { 443 return ''; 444 } 445} 446