mirror of
https://github.com/YunoHost-Apps/webtrees_ynh.git
synced 2024-09-03 18:26:37 +02:00
293 lines
9.6 KiB
PHP
293 lines
9.6 KiB
PHP
|
<?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\Family;
|
||
|
use Fisharebest\Webtrees\Filter;
|
||
|
use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
|
||
|
use Fisharebest\Webtrees\GedcomTag;
|
||
|
use Fisharebest\Webtrees\I18N;
|
||
|
use Fisharebest\Webtrees\Individual;
|
||
|
use Fisharebest\Webtrees\Soundex;
|
||
|
|
||
|
/**
|
||
|
* Controller for the branches list
|
||
|
*/
|
||
|
class BranchesController extends PageController {
|
||
|
/** @var string Generate the branches for this surname */
|
||
|
private $surname;
|
||
|
|
||
|
/** @var bool Whether to use Standard phonetic matching */
|
||
|
private $soundex_std;
|
||
|
|
||
|
/** @var bool Whether to use Daitch-Mokotov phonetic matching */
|
||
|
private $soundex_dm;
|
||
|
|
||
|
/** @var Individual[] Everyone with the selected surname */
|
||
|
private $individuals = array();
|
||
|
|
||
|
/** @var Individual[] Ancestors of the root person - for SOSA numbers */
|
||
|
private $ancestors = array();
|
||
|
|
||
|
/**
|
||
|
* Create a branches list controller
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
global $WT_TREE;
|
||
|
|
||
|
parent::__construct();
|
||
|
|
||
|
$this->surname = Filter::get('surname');
|
||
|
$this->soundex_std = Filter::getBool('soundex_std');
|
||
|
$this->soundex_dm = Filter::getBool('soundex_dm');
|
||
|
|
||
|
if ($this->surname) {
|
||
|
$this->setPageTitle(/* I18N: %s is a surname */
|
||
|
I18N::translate('Branches of the %s family', Filter::escapeHtml($this->surname)));
|
||
|
$this->loadIndividuals();
|
||
|
$self = Individual::getInstance($WT_TREE->getUserPreference(Auth::user(), 'gedcomid'), $WT_TREE);
|
||
|
if ($self) {
|
||
|
$this->loadAncestors($self, 1);
|
||
|
}
|
||
|
} else {
|
||
|
$this->setPageTitle(/* I18N: Branches of a family tree */ I18N::translate('Branches'));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The surname to be used on this page.
|
||
|
*
|
||
|
* @return null|string
|
||
|
*/
|
||
|
public function getSurname() {
|
||
|
return $this->surname;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Should we use Standard phonetic matching
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public function getSoundexStd() {
|
||
|
return $this->soundex_std;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Should we use Daitch-Mokotov phonetic matching
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public function getSoundexDm() {
|
||
|
return $this->soundex_dm;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fetch all individuals with a matching surname
|
||
|
*/
|
||
|
private function loadIndividuals() {
|
||
|
global $WT_TREE;
|
||
|
|
||
|
$sql =
|
||
|
"SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom" .
|
||
|
" FROM `##individuals`" .
|
||
|
" JOIN `##name` ON (i_id=n_id AND i_file=n_file)" .
|
||
|
" WHERE n_file = ?" .
|
||
|
" AND n_type != ?" .
|
||
|
" AND (n_surn = ? OR n_surname = ?";
|
||
|
$args = array($WT_TREE->getTreeId(), '_MARNM', $this->surname, $this->surname);
|
||
|
if ($this->soundex_std) {
|
||
|
$sdx = Soundex::russell($this->surname);
|
||
|
if ($sdx !== null) {
|
||
|
foreach (explode(':', $sdx) as $value) {
|
||
|
$sql .= " OR n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
|
||
|
$args[] = $value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ($this->soundex_dm) {
|
||
|
$sdx = Soundex::daitchMokotoff($this->surname);
|
||
|
if ($sdx !== null) {
|
||
|
foreach (explode(':', $sdx) as $value) {
|
||
|
$sql .= " OR n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
|
||
|
$args[] = $value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$sql .= ')';
|
||
|
$rows = Database::prepare($sql)->execute($args)->fetchAll();
|
||
|
$this->individuals = array();
|
||
|
foreach ($rows as $row) {
|
||
|
$this->individuals[] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
|
||
|
}
|
||
|
// Sort by birth date, oldest first
|
||
|
usort($this->individuals, '\Fisharebest\Webtrees\Individual::compareBirthDate');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load the ancestors of an individual, so we can highlight them in the list
|
||
|
*
|
||
|
* @param Individual $ancestor
|
||
|
* @param int $sosa
|
||
|
*/
|
||
|
private function loadAncestors(Individual $ancestor, $sosa) {
|
||
|
if ($ancestor) {
|
||
|
$this->ancestors[$sosa] = $ancestor;
|
||
|
foreach ($ancestor->getChildFamilies() as $family) {
|
||
|
foreach ($family->getSpouses() as $parent) {
|
||
|
$this->loadAncestors($parent, $sosa * 2 + ($parent->getSex() == 'F' ? 1 : 0));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* For each individual with no ancestors, list their descendants.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getPatriarchsHtml() {
|
||
|
$html = '';
|
||
|
foreach ($this->individuals as $individual) {
|
||
|
foreach ($individual->getChildFamilies() as $family) {
|
||
|
foreach ($family->getSpouses() as $parent) {
|
||
|
if (in_array($parent, $this->individuals, true)) {
|
||
|
continue 3;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$html .= $this->getDescendantsHtml($individual);
|
||
|
}
|
||
|
|
||
|
return $html;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generate a recursive list of descendants of an individual.
|
||
|
* If parents are specified, we can also show the pedigree (adopted, etc.).
|
||
|
*
|
||
|
* @param Individual $individual
|
||
|
* @param Family|null $parents
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
private function getDescendantsHtml(Individual $individual, Family $parents = null) {
|
||
|
// A person has many names. Select the one that matches the searched surname
|
||
|
$person_name = '';
|
||
|
foreach ($individual->getAllNames() as $name) {
|
||
|
list($surn1) = explode(",", $name['sort']);
|
||
|
if (// one name is a substring of the other
|
||
|
stripos($surn1, $this->surname) !== false ||
|
||
|
stripos($this->surname, $surn1) !== false ||
|
||
|
// one name sounds like the other
|
||
|
$this->soundex_std && Soundex::compare(Soundex::russell($surn1), Soundex::russell($this->surname)) ||
|
||
|
$this->soundex_dm && Soundex::compare(Soundex::daitchMokotoff($surn1), Soundex::daitchMokotoff($this->surname))
|
||
|
) {
|
||
|
$person_name = $name['full'];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// No matching name? Typically children with a different surname. The branch stops here.
|
||
|
if (!$person_name) {
|
||
|
return '<li title="' . strip_tags($individual->getFullName()) . '">' . $individual->getSexImage() . '…</li>';
|
||
|
}
|
||
|
|
||
|
// Is this individual one of our ancestors?
|
||
|
$sosa = array_search($individual, $this->ancestors, true);
|
||
|
if ($sosa) {
|
||
|
$sosa_class = 'search_hit';
|
||
|
$sosa_html = ' <a class="details1 ' . $individual->getBoxStyle() . '" title="' . I18N::translate('Sosa') . '" href="relationship.php?pid2=' . $this->ancestors[1]->getXref() . '&pid1=' . $individual->getXref() . '">' . $sosa . '</a>' . self::sosaGeneration($sosa);
|
||
|
} else {
|
||
|
$sosa_class = '';
|
||
|
$sosa_html = '';
|
||
|
}
|
||
|
|
||
|
// Generate HTML for this individual, and all their descendants
|
||
|
$indi_html = $individual->getSexImage() . '<a class="' . $sosa_class . '" href="' . $individual->getHtmlUrl() . '">' . $person_name . '</a> ' . $individual->getLifeSpan() . $sosa_html;
|
||
|
|
||
|
// If this is not a birth pedigree (e.g. an adoption), highlight it
|
||
|
if ($parents) {
|
||
|
$pedi = '';
|
||
|
foreach ($individual->getFacts('FAMC') as $fact) {
|
||
|
if ($fact->getTarget() === $parents) {
|
||
|
$pedi = $fact->getAttribute('PEDI');
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ($pedi && $pedi != 'birth') {
|
||
|
$indi_html = '<span class="red">' . GedcomCodePedi::getValue($pedi, $individual) . '</span> ' . $indi_html;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// spouses and children
|
||
|
$spouse_families = $individual->getSpouseFamilies();
|
||
|
if ($spouse_families) {
|
||
|
usort($spouse_families, '\Fisharebest\Webtrees\Family::compareMarrDate');
|
||
|
$fam_html = '';
|
||
|
foreach ($spouse_families as $family) {
|
||
|
$fam_html .= $indi_html; // Repeat the individual details for each spouse.
|
||
|
|
||
|
$spouse = $family->getSpouse($individual);
|
||
|
if ($spouse) {
|
||
|
$sosa = array_search($spouse, $this->ancestors, true);
|
||
|
if ($sosa) {
|
||
|
$sosa_class = 'search_hit';
|
||
|
$sosa_html = ' <a class="details1 ' . $spouse->getBoxStyle() . '" title="' . I18N::translate('Sosa') . '" href="relationship.php?pid2=' . $this->ancestors[1]->getXref() . '&pid1=' . $spouse->getXref() . '"> ' . $sosa . ' </a>' . self::sosaGeneration($sosa);
|
||
|
} else {
|
||
|
$sosa_class = '';
|
||
|
$sosa_html = '';
|
||
|
}
|
||
|
$marriage_year = $family->getMarriageYear();
|
||
|
if ($marriage_year) {
|
||
|
$fam_html .= ' <a href="' . $family->getHtmlUrl() . '" title="' . strip_tags($family->getMarriageDate()->display()) . '"><i class="icon-rings"></i>' . $marriage_year . '</a>';
|
||
|
} elseif ($family->getFirstFact('MARR')) {
|
||
|
$fam_html .= ' <a href="' . $family->getHtmlUrl() . '" title="' . GedcomTag::getLabel('MARR') . '"><i class="icon-rings"></i></a>';
|
||
|
} elseif ($family->getFirstFact('_NMR')) {
|
||
|
$fam_html .= ' <a href="' . $family->getHtmlUrl() . '" title="' . GedcomTag::getLabel('_NMR') . '"><i class="icon-rings"></i></a>';
|
||
|
}
|
||
|
$fam_html .= ' ' . $spouse->getSexImage() . '<a class="' . $sosa_class . '" href="' . $spouse->getHtmlUrl() . '">' . $spouse->getFullName() . '</a> ' . $spouse->getLifeSpan() . ' ' . $sosa_html;
|
||
|
}
|
||
|
|
||
|
$fam_html .= '<ol>';
|
||
|
foreach ($family->getChildren() as $child) {
|
||
|
$fam_html .= $this->getDescendantsHtml($child, $family);
|
||
|
}
|
||
|
$fam_html .= '</ol>';
|
||
|
}
|
||
|
|
||
|
return '<li>' . $fam_html . '</li>';
|
||
|
} else {
|
||
|
// No spouses - just show the individual
|
||
|
return '<li>' . $indi_html . '</li>';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert a SOSA number into a generation number. e.g. 8 = great-grandfather = 3 generations
|
||
|
*
|
||
|
* @param int $sosa
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
private static function sosaGeneration($sosa) {
|
||
|
$generation = (int) log($sosa, 2) + 1;
|
||
|
|
||
|
return '<sup title="' . I18N::translate('Generation') . '">' . $generation . '</sup>';
|
||
|
}
|
||
|
}
|