<?php /** * webtrees: online genealogy * Copyright (C) 2016 webtrees development team * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ namespace Fisharebest\Webtrees\Controller; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Database; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Filter; use Fisharebest\Webtrees\Functions\FunctionsPrint; use Fisharebest\Webtrees\Functions\FunctionsPrintFacts; use Fisharebest\Webtrees\GedcomCode\GedcomCodeName; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\User; /** * Controller for the individual page */ class IndividualController extends GedcomRecordController { /** @var int Count of names */ public $name_count = 0; /** @var int Count of names. */ public $total_names = 0; /** ModuleTabInterface[] List of tabs to show */ public $tabs; /** * Startup activity * * @param Individual|null $record */ public function __construct($record) { parent::__construct($record); // If we can display the details, add them to the page header if ($this->record && $this->record->canShow()) { $this->setPageTitle($this->record->getFullName() . ' ' . $this->record->getLifeSpan()); $this->tabs = Module::getActiveTabs($this->record->getTree()); } } /** * Get significant information from this page, to allow other pages such as * charts and reports to initialise with the same records * * @return Individual */ public function getSignificantIndividual() { if ($this->record) { return $this->record; } return parent::getSignificantIndividual(); } /** * Get significant information from this page, to allow other pages such as * charts and reports to initialise with the same records * * @return Family */ public function getSignificantFamily() { if ($this->record) { foreach ($this->record->getChildFamilies() as $family) { return $family; } foreach ($this->record->getSpouseFamilies() as $family) { return $family; } } return parent::getSignificantFamily(); } /** * Handle AJAX requests - to generate the tab content */ public function ajaxRequest() { // Search engines should not make AJAX requests if (Auth::isSearchEngine()) { http_response_code(403); exit; } // Initialise tabs $tab = Filter::get('module'); // A request for a non-existant tab? if (array_key_exists($tab, $this->tabs)) { $mod = $this->tabs[$tab]; } else { http_response_code(404); exit; } header("Content-Type: text/html; charset=UTF-8"); // AJAX calls do not have the meta tag headers and need this set header("X-Robots-Tag: noindex,follow"); // AJAX pages should not show up in search results, any links can be followed though echo $mod->getTabContent(); if (WT_DEBUG_SQL) { echo Database::getQueryLog(); } } /** * print information for a name record * * @param Fact $event the event object */ public function printNameRecord(Fact $event) { $factrec = $event->getGedcom(); // Create a dummy record, so we can extract the formatted NAME value from the event. $dummy = new Individual( 'xref', "0 @xref@ INDI\n1 DEAT Y\n" . $factrec, null, $event->getParent()->getTree() ); $all_names = $dummy->getAllNames(); $primary_name = $all_names[0]; $this->name_count++; if ($this->name_count > 1) { echo '<h3 class="name_two">', $dummy->getFullName(), '</h3>'; } // Other names accordion element echo '<div class="indi_name_details'; if ($event->isPendingDeletion()) { echo ' old'; } if ($event->isPendingAddition()) { echo ' new'; } echo '">'; echo '<div class="name1">'; echo '<dl><dt class="label">', I18N::translate('Name'), '</dt>'; $dummy->setPrimaryName(0); echo '<dd class="field">', $dummy->getFullName(); if ($this->name_count == 1) { if (Auth::isAdmin()) { $user = User::findByGenealogyRecord($this->record); if ($user) { echo '<span> - <a class="warning" href="admin_users.php?filter=' . Filter::escapeHtml($user->getUserName()) . '">' . Filter::escapeHtml($user->getUserName()) . '</a></span>'; } } } if ($this->record->canEdit() && !$event->isPendingDeletion()) { echo "<div class=\"deletelink noprint\"><a class=\"deleteicon\" href=\"#\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '" . $this->record->getXref() . "', '" . $event->getFactId() . "');\" title=\"" . I18N::translate('Delete this name') . "\"><span class=\"link_text\">" . I18N::translate('Delete this name') . "</span></a></div>"; echo "<div class=\"editlink noprint\"><a href=\"#\" class=\"editicon\" onclick=\"edit_name('" . $this->record->getXref() . "', '" . $event->getFactId() . "'); return false;\" title=\"" . I18N::translate('Edit the name') . "\"><span class=\"link_text\">" . I18N::translate('Edit the name') . "</span></a></div>"; } echo '</dd>'; echo '</dl>'; echo '</div>'; $ct = preg_match_all('/\n2 (\w+) (.*)/', $factrec, $nmatch, PREG_SET_ORDER); for ($i = 0; $i < $ct; $i++) { echo '<div>'; $fact = $nmatch[$i][1]; if ($fact != 'SOUR' && $fact != 'NOTE' && $fact != 'SPFX') { echo '<dl><dt class="label">', GedcomTag::getLabel($fact, $this->record), '</dt>'; echo '<dd class="field">'; // Before using dir="auto" on this field, note that Gecko treats this as an inline element but WebKit treats it as a block element if (isset($nmatch[$i][2])) { $name = Filter::escapeHtml($nmatch[$i][2]); $name = str_replace('/', '', $name); $name = preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $name); switch ($fact) { case 'TYPE': echo GedcomCodeName::getValue($name, $this->record); break; case 'SURN': // The SURN field is not necessarily the surname. // Where it is not a substring of the real surname, show it after the real surname. $surname = Filter::escapeHtml($primary_name['surname']); if (strpos($primary_name['surname'], str_replace(',', ' ', $nmatch[$i][2])) !== false) { echo '<span dir="auto">' . $surname . '</span>'; } else { echo I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $surname . '</span>', '<span dir="auto">' . $name . '</span>'); } break; default: echo '<span dir="auto">' . $name . '</span>'; break; } } echo '</dd>'; echo '</dl>'; } echo '</div>'; } if (preg_match("/\n2 SOUR/", $factrec)) { echo '<div id="indi_sour" class="clearfloat">', FunctionsPrintFacts::printFactSources($factrec, 2), '</div>'; } if (preg_match("/\n2 NOTE/", $factrec)) { echo '<div id="indi_note" class="clearfloat">', FunctionsPrint::printFactNotes($factrec, 2), '</div>'; } echo '</div>'; } /** * print information for a sex record * * @param Fact $event the Event object */ public function printSexRecord(Fact $event) { $sex = $event->getValue(); if (empty($sex)) { $sex = 'U'; } echo '<span id="sex" class="'; if ($event->isPendingDeletion()) { echo 'old '; } if ($event->isPendingAddition()) { echo 'new '; } switch ($sex) { case 'M': echo 'male_gender"'; if ($event->canEdit()) { echo ' title="', I18N::translate('Male'), ' - ', I18N::translate('Edit'), '"'; echo ' onclick="edit_record(\'' . $this->record->getXref() . '\', \'' . $event->getFactId() . '\'); return false;">'; } else { echo ' title="', I18N::translate('Male'), '">'; } break; case 'F': echo 'female_gender"'; if ($event->canEdit()) { echo ' title="', I18N::translate('Female'), ' - ', I18N::translate('Edit'), '"'; echo ' onclick="edit_record(\'' . $this->record->getXref() . '\', \'' . $event->getFactId() . '\'); return false;">'; } else { echo ' title="', I18N::translate('Female'), '">'; } break; default: echo 'unknown_gender"'; if ($event->canEdit()) { echo ' title="', I18N::translateContext('unknown gender', 'Unknown'), ' - ', I18N::translate('Edit'), '"'; echo ' onclick="edit_record(\'' . $this->record->getXref() . '\', \'' . $event->getFactId() . '\'); return false;">'; } else { echo ' title="', I18N::translateContext('unknown gender', 'Unknown'), '">'; } break; } echo '</span>'; } /** * get edit menu */ public function getEditMenu() { if (!$this->record || $this->record->isPendingDeletion()) { return null; } // edit menu $menu = new Menu(I18N::translate('Edit'), '#', 'menu-indi'); if (Auth::isEditor($this->record->getTree())) { $menu->addSubmenu(new Menu(I18N::translate('Add a name'), '#', 'menu-indi-addname', array( 'onclick' => 'return add_name("' . $this->record->getXref() . '");', ))); $has_sex_record = false; foreach ($this->record->getFacts() as $fact) { if ($fact->getTag() === 'SEX' && $fact->canEdit()) { $menu->addSubmenu(new Menu(I18N::translate('Edit the gender'), '#', 'menu-indi-editsex', array( 'onclick' => 'return edit_record("' . $this->record->getXref() . '", "' . $fact->getFactId() . '");', ))); $has_sex_record = true; break; } } if (!$has_sex_record) { $menu->addSubmenu(new Menu(I18N::translate('Edit the gender'), '#', 'menu-indi-editsex', array( 'onclick' => 'return add_new_record("' . $this->record->getXref() . '", "SEX");', ))); } if (count($this->record->getSpouseFamilies()) > 1) { $menu->addSubmenu(new Menu(I18N::translate('Re-order families'), '#', 'menu-indi-orderfam', array( 'onclick' => 'return reorder_families("' . $this->record->getXref() . '");', ))); } // delete $menu->addSubmenu(new Menu(I18N::translate('Delete'), '#', 'menu-indi-del', array( 'onclick' => 'return delete_record("' . I18N::translate('Are you sure you want to delete ā%sā?', Filter::escapeJs(Filter::unescapeHtml($this->record->getFullName()))) . '", "' . $this->record->getXref() . '");', ))); } // edit raw if (Auth::isAdmin() || Auth::isEditor($this->record->getTree()) && $this->record->getTree()->getPreference('SHOW_GEDCOM_RECORD')) { $menu->addSubmenu(new Menu(I18N::translate('Edit the raw GEDCOM'), '#', 'menu-indi-editraw', array( 'onclick' => 'return edit_raw("' . $this->record->getXref() . '");', ))); } return $menu; } /** * get the person box stylesheet class for the given person * * @param Individual $person * * @return string returns 'person_box', 'person_boxF', or 'person_boxNN' */ public function getPersonStyle($person) { switch ($person->getSex()) { case 'M': $class = 'person_box'; break; case 'F': $class = 'person_boxF'; break; default: $class = 'person_boxNN'; break; } if ($person->isPendingDeletion()) { $class .= ' old'; } elseif ($person->isPendingAddtion()) { $class .= ' new'; } return $class; } /** * Get significant information from this page, to allow other pages such as * charts and reports to initialise with the same records * * @return string */ public function getSignificantSurname() { if ($this->record) { list($surn) = explode(',', $this->record->getSortName()); return $surn; } else { return ''; } } /** * Get the contents of sidebar. * * @return string */ public function getSideBarContent() { global $controller; $html = ''; $active = 0; $n = 0; foreach (Module::getActiveSidebars($this->record->getTree()) as $mod) { if ($mod->hasSidebarContent()) { $html .= '<h3 id="' . $mod->getName() . '"><a href="#">' . $mod->getTitle() . '</a></h3>'; $html .= '<div id="sb_content_' . $mod->getName() . '">' . $mod->getSidebarContent() . '</div>'; // The family navigator should be opened by default if ($mod->getName() == 'family_nav') { $active = $n; } ++$n; } } if ($html) { $controller ->addInlineJavascript(' jQuery("#sidebarAccordion").accordion({ active:' . $active . ', heightStyle: "content", collapsible: true, }); '); return '<div id="sidebar"><div id="sidebarAccordion">' . $html . '</div></div>'; } else { return ''; } } /** * Get the description for the family. * * For example, "XXX's family with new wife". * * @param Family $family * @param Individual $individual * * @return string */ public function getSpouseFamilyLabel(Family $family, Individual $individual) { $spouse = $family->getSpouse($individual); if ($spouse) { return /* I18N: %s is the spouse name */ I18N::translate('Family with %s', $spouse->getFullName()); } else { return $family->getFullName(); } } }