1<?php 2 3use Fisharebest\Webtrees\Auth; 4use Fisharebest\Webtrees\Fact; 5use Fisharebest\Webtrees\Http\RequestHandlers\AddNewFact; 6use Fisharebest\Webtrees\Http\RequestHandlers\PendingChangesAcceptRecord; 7use Fisharebest\Webtrees\Http\RequestHandlers\PendingChangesRejectRecord; 8use Fisharebest\Webtrees\I18N; 9use Fisharebest\Webtrees\Individual; 10use Fisharebest\Webtrees\Media; 11use Fisharebest\Webtrees\Module\ModuleSidebarInterface; 12use Fisharebest\Webtrees\Module\ModuleTabInterface; 13use Fisharebest\Webtrees\Tree; 14use Fisharebest\Webtrees\View; 15use Illuminate\Support\Collection; 16 17/** 18 * @var string $age 19 * @var Individual $individual 20 * @var string $user_link 21 * @var Collection<ModuleSidebarInterface> $sidebars 22 * @var Collection<Media> $individual_media 23 * @var Collection<Fact> $name_records 24 * @var Collection<Fact> $sex_records 25 * @var Collection<ModuleTabInterface $tabs 26 * @var Tree $tree 27 */ 28?> 29 30<?php if ($individual->isPendingDeletion()) : ?> 31 <?php if (Auth::isModerator($individual->tree())) : ?> 32 <?= view('components/alert-warning-dismissible', [ 33 'alert' => /* I18N: %1$s is “accept”, %2$s is “reject”. These are links. */ 34 I18N::translate('This individual has been deleted. You should review the deletion and then %1$s or %2$s it.', '<a href="#" class="alert-link" data-post-url="' . e(route(PendingChangesAcceptRecord::class, ['tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) . '">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>', '<a href="#" class="alert-link" data-post-url="' . e(route(PendingChangesRejectRecord::class, ['tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) . '">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>') . ' ' . view('help/link', ['topic' => 'pending_changes']), 35 ]) ?> 36 <?php elseif (Auth::isEditor($individual->tree())) : ?> 37 <?= view('components/alert-warning-dismissible', ['alert' => I18N::translate('This individual has been deleted. The deletion will need to be reviewed by a moderator.') . ' ' . view('help/link', ['topic' => 'pending_changes'])]) ?> 38 <?php endif ?> 39<?php elseif ($individual->isPendingAddition()) : ?> 40 <?php if (Auth::isModerator($individual->tree())) : ?> 41 <?= view('components/alert-warning-dismissible', [ 42 'alert' => /* I18N: %1$s is “accept”, %2$s is “reject”. These are links. */ 43 I18N::translate('This individual has been edited. You should review the changes and then %1$s or %2$s them.', '<a href="#" class="alert-link" data-post-url="' . e(route(PendingChangesAcceptRecord::class, ['tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) . '">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>', '<a href="#" class="alert-link" data-post-url="' . e(route(PendingChangesRejectRecord::class, ['tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) . '">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>') . ' ' . view('help/link', ['topic' => 'pending_changes']), 44 ]) ?> 45 <?php elseif (Auth::isEditor($individual->tree())) : ?> 46 <?= view('components/alert-warning-dismissible', ['alert' => I18N::translate('This individual has been edited. The changes need to be reviewed by a moderator.') . ' ' . view('help/link', ['topic' => 'pending_changes'])]) ?> 47 <?php endif ?> 48<?php endif ?> 49 50<div class="d-flex mb-4"> 51 <h2 class="wt-page-title mx-auto"> 52 <?= $individual->fullName() ?><?= $user_link ?>, <?= $individual->lifespan() ?> <?= $age ?> 53 </h2> 54 <?php if ($individual->canEdit()) : ?> 55 <?= view('individual-page-menu', ['record' => $individual]) ?> 56 <?php endif ?> 57</div> 58 59<div class="row"> 60 <div class="<?= $sidebars->isEmpty() ? 'col-sm-12' : 'col-sm-8' ?>"> 61 <div class="row mb-4"> 62 <!-- Individual images --> 63 <?php if ($individual_media->isNotEmpty() || $tree->getPreference('USE_SILHOUETTE') === '1') : ?> 64 <div class="col-sm-3"> 65 <?php if ($individual_media->isEmpty()) : ?> 66 <div class="img-thumbnail"> 67 <i class="wt-individual-silhouette wt-individual-silhouette-<?= strtolower($individual->sex()) ?>"></i> 68 </div> 69 <?php elseif ($individual_media->count() === 1) : ?> 70 <?= $individual_media->first()->displayImage(200, 260, 'crop', ['class' => 'img-thumbnail img-fluid w-100']) ?> 71 <?php else : ?> 72 <div id="individual-images" class="carousel slide" data-ride="carousel" data-interval="false"> 73 <div class="carousel-inner"> 74 <?php foreach ($individual_media as $n => $media_file) : ?> 75 <div class="carousel-item <?= $n === 0 ? 'active' : '' ?>"> 76 <?= $media_file->displayImage(200, 260, 'crop', ['class' => 'img-thumbnail img-fluid w-100']) ?> 77 </div> 78 <?php endforeach ?> 79 </div> 80 <a class="carousel-control-prev" href="#individual-images" role="button" data-slide="prev"> 81 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 82 <span class="sr-only"><?= I18N::translate('previous') ?></span> 83 </a> 84 <a class="carousel-control-next" href="#individual-images" role="button" data-slide="next"> 85 <span class="carousel-control-next-icon" aria-hidden="true"></span> 86 <span class="sr-only"><?= I18N::translate('next') ?></span> 87 </a> 88 </div> 89 <?php endif ?> 90 91 <?php if (Auth::isEditor($individual->tree())) : ?> 92 <div class="text-center"> 93 <a href="<?= e(route(AddNewFact::class, ['tree' => $individual->tree()->name(), 'xref' => $individual->xref(), 'fact' => 'OBJE'])) ?>"> 94 <?= I18N::translate('Add a media object') ?> 95 </a> 96 </div> 97 <?php endif ?> 98 </div> 99 <?php endif ?> 100 101 <!-- Name accordion --> 102 <div class="col-sm accordion" id="individual-names"> 103 <?php foreach ($name_records as $name_record) : ?> 104 <?= $name_record ?> 105 <?php endforeach ?> 106 107 <?php foreach ($sex_records as $sex_record) : ?> 108 <?= $sex_record ?> 109 <?php endforeach ?> 110 </div> 111 </div> 112 113 <div class="wt-tabs-individual" id="individual-tabs"> 114 <ul class="nav nav-tabs flex-wrap" role="tablist"> 115 <?php foreach ($tabs as $tab) : ?> 116 <li class="nav-item" role="presentation"> 117 <a class="nav-link<?= $tab->isGrayedOut($individual) ? ' text-muted' : '' ?>" data-toggle="tab" role="tab" data-href="<?= e(route('module', ['module' => $tab->name(), 'action' => 'Tab', 'tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) ?>" href="#<?= $tab->name() ?>"> 118 <?= $tab->tabTitle() ?> 119 </a> 120 </li> 121 <?php endforeach ?> 122 </ul> 123 124 <div class="tab-content"> 125 <?php foreach ($tabs as $tab) : ?> 126 <div id="<?= $tab->name() ?>" class="tab-pane fade wt-ajax-load" role="tabpanel"><?php if (!$tab->canLoadAjax()) : 127 ?><?= $tab->getTabContent($individual) ?><?php 128 endif ?></div> 129 <?php endforeach ?> 130 </div> 131 </div> 132 </div> 133 <?php if ($sidebars->isNotEmpty()) : ?> 134 <div class="col-sm-4 accordion" id="sidebar"> 135 <?php foreach ($sidebars as $sidebar) : ?> 136 <div class="card"> 137 <div class="card-header" role="tab" id="sidebar-header-<?= $sidebar->name() ?>"> 138 <div class="card-title mb-0"> 139 <a data-toggle="collapse" href="#sidebar-content-<?= $sidebar->name() ?>" aria-expanded="<?= $sidebar->name() === 'family_nav' ? 'true' : 'false' ?>" aria-controls="sidebar-content-<?= $sidebar->name() ?>"> 140 <?= view('icons/expand') ?> 141 <?= view('icons/collapse') ?> 142 <?= $sidebar->sidebarTitle() ?> 143 </a> 144 </div> 145 </div> 146 147 <div id="sidebar-content-<?= $sidebar->name() ?>" class="collapse<?= $sidebar->name() === 'family_nav' ? ' show' : '' ?>" data-parent="#sidebar" aria-labelledby="sidebar-header-<?= $sidebar->name() ?>"> 148 <div class="card-body"> 149 <?= $sidebar->getSidebarContent($individual) ?> 150 </div> 151 </div> 152 </div> 153 <?php endforeach ?> 154 </div> 155 <?php endif ?> 156</div> 157 158<?php View::push('javascript') ?> 159<script> 160 "use strict"; 161 162 // Bootstrap tabs - load content dynamically using AJAX 163 $('a[data-toggle="tab"][data-href]').on('show.bs.tab', function () { 164 let target = $(this.hash + ':empty'); 165 if (target.length > 0) { 166 // Start the download immediately... 167 let download = fetch(this.dataset.href); 168 169 // ...but don't insert it until the tab is ready. 170 $(this).one('shown.bs.tab', () => { 171 download 172 .then(data => data.text()) 173 .then(data => target.html(data)); 174 }); 175 } 176 }); 177 178 // If the URL contains a fragment, then activate the corresponding tab. 179 // Use a prefix on the fragment, to prevent scrolling to the element. 180 let target = window.location.hash.replace("tab-", ""); 181 let tab = $("#individual-tabs .nav-link[href='" + target + "']"); 182 // If not, then activate the first tab. 183 if (tab.length === 0) { 184 tab = $("#individual-tabs .nav-link:first"); 185 } 186 tab.tab("show"); 187 188 // If the user selects a tab, update the URL to reflect this 189 $('#individual-tabs a[data-toggle="tab"]').on('shown.bs.tab', function (e) { 190 window.location.hash = "tab-" + e.target.href.substring(e.target.href.indexOf('#') + 1); 191 }); 192</script> 193<?php View::endpush() ?> 194 195<?= view('modals/ajax') ?> 196