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