summaryrefslogtreecommitdiff
path: root/includes/functions
diff options
context:
space:
mode:
Diffstat (limited to 'includes/functions')
-rw-r--r--includes/functions/functions.php2219
-rw-r--r--includes/functions/functions_charts.php550
-rw-r--r--includes/functions/functions_date.php116
-rw-r--r--includes/functions/functions_db.php1079
-rw-r--r--includes/functions/functions_edit.php1706
-rw-r--r--includes/functions/functions_export.php308
-rw-r--r--includes/functions/functions_import.php1153
-rw-r--r--includes/functions/functions_mediadb.php91
-rw-r--r--includes/functions/functions_print.php867
-rw-r--r--includes/functions/functions_print_facts.php1144
-rw-r--r--includes/functions/functions_print_lists.php2289
-rw-r--r--includes/functions/functions_rtl.php1141
12 files changed, 0 insertions, 12663 deletions
diff --git a/includes/functions/functions.php b/includes/functions/functions.php
deleted file mode 100644
index 63ad6d59c4..0000000000
--- a/includes/functions/functions.php
+++ /dev/null
@@ -1,2219 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\File;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Site;
-
-/**
- * Check with the webtrees.net server for the latest version of webtrees.
- * Fetching the remote file can be slow, so check infrequently, and cache the result.
- * Pass the current versions of webtrees, PHP and MySQL, as the response
- * may be different for each. The server logs are used to generate
- * installation statistics which can be found at http://svn.webtrees.net/statistics.html
- *
- * @return null|string
- */
-function fetch_latest_version() {
- $last_update_timestamp = Site::getPreference('LATEST_WT_VERSION_TIMESTAMP');
- if ($last_update_timestamp < WT_TIMESTAMP - 24 * 60 * 60) {
- $row = Database::prepare("SHOW VARIABLES LIKE 'version'")->fetchOneRow();
- $params = '?w=' . WT_VERSION . '&p=' . PHP_VERSION . '&m=' . $row->value . '&o=' . (DIRECTORY_SEPARATOR === '/' ? 'u' : 'w');
- $latest_version_txt = File::fetchUrl('http://dev.webtrees.net/build/latest-version.txt' . $params);
- if ($latest_version_txt) {
- Site::setPreference('LATEST_WT_VERSION', $latest_version_txt);
- Site::setPreference('LATEST_WT_VERSION_TIMESTAMP', WT_TIMESTAMP);
-
- return $latest_version_txt;
- } else {
- // Cannot connect to server - use cached version (if we have one)
- return Site::getPreference('LATEST_WT_VERSION');
- }
- } else {
- return Site::getPreference('LATEST_WT_VERSION');
- }
-}
-
-/**
- * Convert a file upload PHP error code into user-friendly text.
- *
- * @param int $error_code
- *
- * @return string
- */
-function file_upload_error_text($error_code) {
- switch ($error_code) {
- case UPLOAD_ERR_OK:
- return I18N::translate('File successfully uploaded');
- case UPLOAD_ERR_INI_SIZE:
- case UPLOAD_ERR_FORM_SIZE:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('The uploaded file exceeds the allowed size.');
- case UPLOAD_ERR_PARTIAL:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('The file was only partially uploaded. Please try again.');
- case UPLOAD_ERR_NO_FILE:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('No file was received. Please try again.');
- case UPLOAD_ERR_NO_TMP_DIR:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('The PHP temporary folder is missing.');
- case UPLOAD_ERR_CANT_WRITE:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('PHP failed to write to disk.');
- case UPLOAD_ERR_EXTENSION:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('PHP blocked the file because of its extension.');
- default:
- return 'Error: ' . $error_code;
- }
-}
-
-/**
- * get a gedcom subrecord
- *
- * searches a gedcom record and returns a subrecord of it. A subrecord is defined starting at a
- * line with level N and all subsequent lines greater than N until the next N level is reached.
- * For example, the following is a BIRT subrecord:
- * <code>1 BIRT
- * 2 DATE 1 JAN 1900
- * 2 PLAC Phoenix, Maricopa, Arizona</code>
- * The following example is the DATE subrecord of the above BIRT subrecord:
- * <code>2 DATE 1 JAN 1900</code>
- *
- * @param int $level the N level of the subrecord to get
- * @param string $tag a gedcom tag or string to search for in the record (ie 1 BIRT or 2 DATE)
- * @param string $gedrec the parent gedcom record to search in
- * @param int $num this allows you to specify which matching <var>$tag</var> to get. Oftentimes a
- * gedcom record will have more that 1 of the same type of subrecord. An individual may have
- * multiple events for example. Passing $num=1 would get the first 1. Passing $num=2 would get the
- * second one, etc.
- *
- * @return string the subrecord that was found or an empty string "" if not found.
- */
-function get_sub_record($level, $tag, $gedrec, $num = 1) {
- if (empty($gedrec)) {
- return '';
- }
- // -- adding \n before and after gedrec
- $gedrec = "\n" . $gedrec . "\n";
- $tag = trim($tag);
- $searchTarget = "~[\n]" . $tag . "[\s]~";
- $ct = preg_match_all($searchTarget, $gedrec, $match, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
- if ($ct == 0) {
- return '';
- }
- if ($ct < $num) {
- return '';
- }
- $pos1 = $match[$num - 1][0][1];
- $pos2 = strpos($gedrec, "\n$level", $pos1 + 1);
- if (!$pos2) {
- $pos2 = strpos($gedrec, "\n1", $pos1 + 1);
- }
- if (!$pos2) {
- $pos2 = strpos($gedrec, "\nWT_", $pos1 + 1); // WT_SPOUSE, WT_FAMILY_ID ...
- }
- if (!$pos2) {
- return ltrim(substr($gedrec, $pos1));
- }
- $subrec = substr($gedrec, $pos1, $pos2 - $pos1);
-
- return ltrim($subrec);
-}
-
-/**
- * get CONT lines
- *
- * get the N+1 CONT or CONC lines of a gedcom subrecord
- *
- * @param int $nlevel the level of the CONT lines to get
- * @param string $nrec the gedcom subrecord to search in
- *
- * @return string a string with all CONT lines merged
- */
-function get_cont($nlevel, $nrec) {
- $text = '';
-
- $subrecords = explode("\n", $nrec);
- foreach ($subrecords as $thisSubrecord) {
- if (substr($thisSubrecord, 0, 2) !== $nlevel . ' ') {
- continue;
- }
- $subrecordType = substr($thisSubrecord, 2, 4);
- if ($subrecordType === 'CONT') {
- $text .= "\n" . substr($thisSubrecord, 7);
- }
- }
-
- return $text;
-}
-
-/**
- * Sort a list events for the today/upcoming blocks
- *
- * @param array $a
- * @param array $b
- *
- * @return int
- */
-function event_sort($a, $b) {
- if ($a['jd'] == $b['jd']) {
- if ($a['anniv'] == $b['anniv']) {
- return I18N::strcasecmp($a['fact'], $b['fact']);
- } else {
- return $a['anniv'] - $b['anniv'];
- }
- } else {
- return $a['jd'] - $b['jd'];
- }
-}
-
-/**
- * Sort a list events for the today/upcoming blocks
- *
- * @param array $a
- * @param array $b
- *
- * @return int
- */
-function event_sort_name($a, $b) {
- if ($a['jd'] == $b['jd']) {
- return GedcomRecord::compare($a['record'], $b['record']);
- } else {
- return $a['jd'] - $b['jd'];
- }
-}
-
-/**
- * A multi-key sort
- * 1. First divide the facts into two arrays one set with dates and one set without dates
- * 2. Sort each of the two new arrays, the date using the compare date function, the non-dated
- * using the compare type function
- * 3. Then merge the arrays back into the original array using the compare type function
- *
- * @param Fact[] $arr
- */
-function sort_facts(&$arr) {
- $dated = array();
- $nondated = array();
- //-- split the array into dated and non-dated arrays
- $order = 0;
- foreach ($arr as $event) {
- $event->sortOrder = $order;
- $order++;
- if ($event->getDate()->isOk()) {
- $dated[] = $event;
- } else {
- $nondated[] = $event;
- }
- }
-
- //-- sort each type of array
- usort($dated, '\Fisharebest\Webtrees\Fact::compareDate');
- usort($nondated, '\Fisharebest\Webtrees\Fact::compareType');
-
- //-- merge the arrays back together comparing by Facts
- $dc = count($dated);
- $nc = count($nondated);
- $i = 0;
- $j = 0;
- $k = 0;
- // while there is anything in the dated array continue merging
- while ($i < $dc) {
- // compare each fact by type to merge them in order
- if ($j < $nc && Fact::compareType($dated[$i], $nondated[$j]) > 0) {
- $arr[$k] = $nondated[$j];
- $j++;
- } else {
- $arr[$k] = $dated[$i];
- $i++;
- }
- $k++;
- }
-
- // get anything that might be left in the nondated array
- while ($j < $nc) {
- $arr[$k] = $nondated[$j];
- $j++;
- $k++;
- }
-
-}
-
-/**
- * For close family relationships, such as the families tab and the family navigator
- * Display a tick if both individuals are the same.
- *
- * @param Individual $person1
- * @param Individual $person2
- *
- * @return string
- */
-function get_close_relationship_name(Individual $person1, Individual $person2) {
- if ($person1 === $person2) {
- $label = '<i class="icon-selected" title="' . I18N::translate('self') . '"></i>';
- } else {
- $label = get_relationship_name(get_relationship($person1, $person2));
- }
-
- return $label;
-}
-
-/**
- * For facts on the individual/family pages.
- *
- * @param Individual $person1
- * @param Individual $person2
- *
- * @return string
- */
-function get_associate_relationship_name(Individual $person1, Individual $person2) {
- if ($person1 === $person2) {
- $label = I18N::translate('self');
- } else {
- $label = get_relationship_name(get_relationship($person1, $person2));
- }
-
- return $label;
-}
-
-/**
- * Get relationship between two individuals in the gedcom
- *
- * @param Individual $person1 The person to compute the relationship from
- * @param Individual $person2 The person to compute the relatiohip to
- * @param int $maxlength The maximum length of path
- *
- * @return array|bool An array of nodes on the relationship path, or false if no path found
- */
-function get_relationship(Individual $person1, Individual $person2, $maxlength = 4) {
- if ($person1 === $person2) {
- return false;
- }
-
- $spouse_codes = array('M' => 'hus', 'F' => 'wif', 'U' => 'spo');
- $parent_codes = array('M' => 'fat', 'F' => 'mot', 'U' => 'par');
- $child_codes = array('M' => 'son', 'F' => 'dau', 'U' => 'chi');
- $sibling_codes = array('M' => 'bro', 'F' => 'sis', 'U' => 'sib');
-
- //-- current path nodes
- $p1nodes = array();
- //-- ids visited
- $visited = array();
-
- //-- set up first node for person1
- $node1 = array(
- 'path' => array($person1),
- 'length' => 0,
- 'indi' => $person1,
- 'relations' => array('self'),
- );
- $p1nodes[] = $node1;
-
- $visited[$person1->getXref()] = true;
-
- $found = false;
- while (!$found) {
- //-- search the node list for the shortest path length
- $shortest = -1;
- foreach ($p1nodes as $index => $node) {
- if ($shortest == -1) {
- $shortest = $index;
- } else {
- $node1 = $p1nodes[$shortest];
- if ($node1['length'] > $node['length']) {
- $shortest = $index;
- }
- }
- }
- if ($shortest === -1) {
- return false;
- }
- $node = $p1nodes[$shortest];
- if ($maxlength == 0 || count($node['path']) <= $maxlength) {
- $indi = $node['indi'];
- //-- check all parents and siblings of this node
- foreach ($indi->getChildFamilies(Auth::PRIV_HIDE) as $family) {
- $visited[$family->getXref()] = true;
- foreach ($family->getSpouses(Auth::PRIV_HIDE) as $spouse) {
- if (!isset($visited[$spouse->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $spouse;
- $node1['indi'] = $spouse;
- $node1['relations'][] = $parent_codes[$spouse->getSex()];
- $p1nodes[] = $node1;
- if ($spouse === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$spouse->getXref()] = true;
- }
- }
- }
- foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
- if (!isset($visited[$child->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $child;
- $node1['indi'] = $child;
- $node1['relations'][] = $sibling_codes[$child->getSex()];
- $p1nodes[] = $node1;
- if ($child === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$child->getXref()] = true;
- }
- }
- }
- }
- //-- check all spouses and children of this node
- foreach ($indi->getSpouseFamilies(Auth::PRIV_HIDE) as $family) {
- $visited[$family->getXref()] = true;
- foreach ($family->getSpouses(Auth::PRIV_HIDE) as $spouse) {
- if (!in_array($spouse->getXref(), $node1) || !isset($visited[$spouse->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $spouse;
- $node1['indi'] = $spouse;
- $node1['relations'][] = $spouse_codes[$spouse->getSex()];
- $p1nodes[] = $node1;
- if ($spouse === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$spouse->getXref()] = true;
- }
- }
- }
- foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
- if (!isset($visited[$child->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $child;
- $node1['indi'] = $child;
- $node1['relations'][] = $child_codes[$child->getSex()];
- $p1nodes[] = $node1;
- if ($child === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$child->getXref()] = true;
- }
- }
- }
- }
- }
- unset($p1nodes[$shortest]);
- }
-
- return $resnode;
-}
-
-/**
- * Convert the result of get_relationship() into a relationship name.
- *
- * @param mixed[][] $nodes
- *
- * @return string
- */
-function get_relationship_name($nodes) {
- if (!is_array($nodes)) {
- return '';
- }
- $person1 = $nodes['path'][0];
- $person2 = $nodes['path'][count($nodes['path']) - 1];
- $path = array_slice($nodes['relations'], 1);
- // Look for paths with *specific* names first.
- // Note that every combination must be listed separately, as the same English
- // name can be used for many different relationships. e.g.
- // brother’s wife & husband’s sister = sister-in-law.
- //
- // $path is an array of the 12 possible gedcom family relationships:
- // mother/father/parent
- // brother/sister/sibling
- // husband/wife/spouse
- // son/daughter/child
- //
- // This is always the shortest path, so “father, daughter” is “half-sister”, not “sister”.
- //
- // This is very repetitive in English, but necessary in order to handle the
- // complexities of other languages.
-
- return get_relationship_name_from_path(
- implode('', array_slice($nodes['relations'], 1)),
- $nodes['path'][0],
- $nodes['path'][count($nodes['path']) - 1]
- );
-}
-
-/**
- * @param int $n
- * @param string $sex
- *
- * @return string
- */
-function cousin_name($n, $sex) {
- switch ($sex) {
- case 'M':
- switch ($n) {
- case 1:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'first cousin');
- case 2:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'second cousin');
- case 3:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'third cousin');
- case 4:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fourth cousin');
- case 5:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fifth cousin');
- case 6:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'sixth cousin');
- case 7:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'seventh cousin');
- case 8:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'eighth cousin');
- case 9:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'ninth cousin');
- case 10:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'tenth cousin');
- case 11:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'eleventh cousin');
- case 12:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'twelfth cousin');
- case 13:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'thirteenth cousin');
- case 14:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fourteenth cousin');
- case 15:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fifteenth cousin');
- default:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', '%s × cousin', I18N::number($n));
- }
- case 'F':
- switch ($n) {
- case 1:
- return I18N::translateContext('FEMALE', 'first cousin');
- case 2:
- return I18N::translateContext('FEMALE', 'second cousin');
- case 3:
- return I18N::translateContext('FEMALE', 'third cousin');
- case 4:
- return I18N::translateContext('FEMALE', 'fourth cousin');
- case 5:
- return I18N::translateContext('FEMALE', 'fifth cousin');
- case 6:
- return I18N::translateContext('FEMALE', 'sixth cousin');
- case 7:
- return I18N::translateContext('FEMALE', 'seventh cousin');
- case 8:
- return I18N::translateContext('FEMALE', 'eighth cousin');
- case 9:
- return I18N::translateContext('FEMALE', 'ninth cousin');
- case 10:
- return I18N::translateContext('FEMALE', 'tenth cousin');
- case 11:
- return I18N::translateContext('FEMALE', 'eleventh cousin');
- case 12:
- return I18N::translateContext('FEMALE', 'twelfth cousin');
- case 13:
- return I18N::translateContext('FEMALE', 'thirteenth cousin');
- case 14:
- return I18N::translateContext('FEMALE', 'fourteenth cousin');
- case 15:
- return I18N::translateContext('FEMALE', 'fifteenth cousin');
- default:
- return I18N::translateContext('FEMALE', '%s × cousin', I18N::number($n));
- }
- default:
- switch ($n) {
- case 1:
- return I18N::translateContext('MALE/FEMALE', 'first cousin');
- case 2:
- return I18N::translateContext('MALE/FEMALE', 'second cousin');
- case 3:
- return I18N::translateContext('MALE/FEMALE', 'third cousin');
- case 4:
- return I18N::translateContext('MALE/FEMALE', 'fourth cousin');
- case 5:
- return I18N::translateContext('MALE/FEMALE', 'fifth cousin');
- case 6:
- return I18N::translateContext('MALE/FEMALE', 'sixth cousin');
- case 7:
- return I18N::translateContext('MALE/FEMALE', 'seventh cousin');
- case 8:
- return I18N::translateContext('MALE/FEMALE', 'eighth cousin');
- case 9:
- return I18N::translateContext('MALE/FEMALE', 'ninth cousin');
- case 10:
- return I18N::translateContext('MALE/FEMALE', 'tenth cousin');
- case 11:
- return I18N::translateContext('MALE/FEMALE', 'eleventh cousin');
- case 12:
- return I18N::translateContext('MALE/FEMALE', 'twelfth cousin');
- case 13:
- return I18N::translateContext('MALE/FEMALE', 'thirteenth cousin');
- case 14:
- return I18N::translateContext('MALE/FEMALE', 'fourteenth cousin');
- case 15:
- return I18N::translateContext('MALE/FEMALE', 'fifteenth cousin');
- default:
- return I18N::translateContext('MALE/FEMALE', '%s × cousin', I18N::number($n));
- }
- }
-}
-
-/**
- * A variation on cousin_name(), for constructs such as “sixth great-nephew”
- * Currently used only by Spanish relationship names.
- *
- * @param int $n
- * @param string $sex
- * @param string $relation
- *
- * @return string
- */
-function cousin_name2($n, $sex, $relation) {
- switch ($sex) {
- case 'M':
- switch ($n) {
- case 1: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE', 'first %s', $relation);
- case 2:
- return I18N::translateContext('MALE', 'second %s', $relation);
- case 3:
- return I18N::translateContext('MALE', 'third %s', $relation);
- case 4:
- return I18N::translateContext('MALE', 'fourth %s', $relation);
- case 5:
- return I18N::translateContext('MALE', 'fifth %s', $relation);
- default: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE', '%1$s × %2$s', I18N::number($n), $relation);
- }
- case 'F':
- switch ($n) {
- case 1: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('FEMALE', 'first %s', $relation);
- case 2:
- return I18N::translateContext('FEMALE', 'second %s', $relation);
- case 3:
- return I18N::translateContext('FEMALE', 'third %s', $relation);
- case 4:
- return I18N::translateContext('FEMALE', 'fourth %s', $relation);
- case 5:
- return I18N::translateContext('FEMALE', 'fifth %s', $relation);
- default: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
- }
- default:
- switch ($n) {
- case 1: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE/FEMALE', 'first %s', $relation);
- case 2:
- return I18N::translateContext('MALE/FEMALE', 'second %s', $relation);
- case 3:
- return I18N::translateContext('MALE/FEMALE', 'third %s', $relation);
- case 4:
- return I18N::translateContext('MALE/FEMALE', 'fourth %s', $relation);
- case 5:
- return I18N::translateContext('MALE/FEMALE', 'fifth %s', $relation);
- default: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE/FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
- }
- }
-}
-
-/**
- * @param string $path
- * @param Individual $person1
- * @param Individual $person2
- *
- * @return string
- */
-function get_relationship_name_from_path($path, Individual $person1 = null, Individual $person2 = null) {
- if (!preg_match('/^(mot|fat|par|hus|wif|spo|son|dau|chi|bro|sis|sib)*$/', $path)) {
- // TODO: Update all the “3 RELA ” values in class_person
- return '<span class="error">' . $path . '</span>';
- }
- // The path does not include the starting person. In some languages, the
- // translation for a man’s (relative) is different from a woman’s (relative),
- // due to inflection.
- $sex1 = $person1 ? $person1->getSex() : 'U';
-
- // The sex of the last person in the relationship determines the name in
- // many cases. e.g. great-aunt / great-uncle
- if (preg_match('/(fat|hus|son|bro)$/', $path)) {
- $sex2 = 'M';
- } elseif (preg_match('/(mot|wif|dau|sis)$/', $path)) {
- $sex2 = 'F';
- } else {
- $sex2 = 'U';
- }
-
- switch ($path) {
- case '':
- return I18N::translate('self');
- // Level One relationships
- case 'mot':
- return I18N::translate('mother');
- case 'fat':
- return I18N::translate('father');
- case 'par':
- return I18N::translate('parent');
- case 'hus':
- if ($person1 && $person2) {
- foreach ($person1->getSpouseFamilies() as $family) {
- if ($person2 === $family->getSpouse($person1)) {
- if ($family->getFacts('_NMR')) {
- if ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translateContext('MALE', 'ex-partner');
- } else {
- return I18N::translateContext('MALE', 'partner');
- }
- } elseif ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translate('ex-husband');
- }
- }
- }
- }
-
- return I18N::translate('husband');
- case 'wif':
- if ($person1 && $person1) {
- foreach ($person1->getSpouseFamilies() as $family) {
- if ($person2 === $family->getSpouse($person1)) {
- if ($family->getFacts('_NMR')) {
- if ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translateContext('FEMALE', 'ex-partner');
- } else {
- return I18N::translateContext('FEMALE', 'partner');
- }
- } elseif ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translate('ex-wife');
- }
- }
- }
- }
-
- return I18N::translate('wife');
- case 'spo':
- if ($person1 && $person2) {
- foreach ($person1->getSpouseFamilies() as $family) {
- if ($person2 === $family->getSpouse($person1)) {
- if ($family->getFacts('_NMR')) {
- if ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translateContext('MALE/FEMALE', 'ex-partner');
- } else {
- return I18N::translateContext('MALE/FEMALE', 'partner');
- }
- } elseif ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translate('ex-spouse');
- }
- }
- }
- }
-
- return I18N::translate('spouse');
- case 'son':
- return I18N::translate('son');
- case 'dau':
- return I18N::translate('daughter');
- case 'chi':
- return I18N::translate('child');
- case 'bro':
- if ($person1 && $person2) {
- $dob1 = $person1->getBirthDate();
- $dob2 = $person2->getBirthDate();
- if ($dob1->isOK() && $dob2->isOK()) {
- if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
- // Exclude BEF, AFT, etc.
- return I18N::translate('twin brother');
- } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
- return I18N::translate('younger brother');
- } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
- return I18N::translate('elder brother');
- }
- }
- }
-
- return I18N::translate('brother');
- case 'sis':
- if ($person1 && $person2) {
- $dob1 = $person1->getBirthDate();
- $dob2 = $person2->getBirthDate();
- if ($dob1->isOK() && $dob2->isOK()) {
- if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
- // Exclude BEF, AFT, etc.
- return I18N::translate('twin sister');
- } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
- return I18N::translate('younger sister');
- } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
- return I18N::translate('elder sister');
- }
- }
- }
-
- return I18N::translate('sister');
- case 'sib':
- if ($person1 && $person2) {
- $dob1 = $person1->getBirthDate();
- $dob2 = $person2->getBirthDate();
- if ($dob1->isOK() && $dob2->isOK()) {
- if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
- // Exclude BEF, AFT, etc.
- return I18N::translate('twin sibling');
- } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
- return I18N::translate('younger sibling');
- } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
- return I18N::translate('elder sibling');
- }
- }
- }
-
- return I18N::translate('sibling');
-
- // Level Two relationships
- case 'brochi':
- return I18N::translateContext('brother’s child', 'nephew/niece');
- case 'brodau':
- return I18N::translateContext('brother’s daughter', 'niece');
- case 'broson':
- return I18N::translateContext('brother’s son', 'nephew');
- case 'browif':
- return I18N::translateContext('brother’s wife', 'sister-in-law');
- case 'chichi':
- return I18N::translateContext('child’s child', 'grandchild');
- case 'chidau':
- return I18N::translateContext('child’s daughter', 'granddaughter');
- case 'chihus':
- return I18N::translateContext('child’s husband', 'son-in-law');
- case 'chison':
- return I18N::translateContext('child’s son', 'grandson');
- case 'chispo':
- return I18N::translateContext('child’s spouse', 'son/daughter-in-law');
- case 'chiwif':
- return I18N::translateContext('child’s wife', 'daughter-in-law');
- case 'dauchi':
- return I18N::translateContext('daughter’s child', 'grandchild');
- case 'daudau':
- return I18N::translateContext('daughter’s daughter', 'granddaughter');
- case 'dauhus':
- return I18N::translateContext('daughter’s husband', 'son-in-law');
- case 'dauson':
- return I18N::translateContext('daughter’s son', 'grandson');
- case 'fatbro':
- return I18N::translateContext('father’s brother', 'uncle');
- case 'fatchi':
- return I18N::translateContext('father’s child', 'half-sibling');
- case 'fatdau':
- return I18N::translateContext('father’s daughter', 'half-sister');
- case 'fatfat':
- return I18N::translateContext('father’s father', 'paternal grandfather');
- case 'fatmot':
- return I18N::translateContext('father’s mother', 'paternal grandmother');
- case 'fatpar':
- return I18N::translateContext('father’s parent', 'paternal grandparent');
- case 'fatsib':
- return I18N::translateContext('father’s sibling', 'aunt/uncle');
- case 'fatsis':
- return I18N::translateContext('father’s sister', 'aunt');
- case 'fatson':
- return I18N::translateContext('father’s son', 'half-brother');
- case 'fatwif':
- return I18N::translateContext('father’s wife', 'step-mother');
- case 'husbro':
- return I18N::translateContext('husband’s brother', 'brother-in-law');
- case 'huschi':
- return I18N::translateContext('husband’s child', 'step-child');
- case 'husdau':
- return I18N::translateContext('husband’s daughter', 'step-daughter');
- case 'husfat':
- return I18N::translateContext('husband’s father', 'father-in-law');
- case 'husmot':
- return I18N::translateContext('husband’s mother', 'mother-in-law');
- case 'hussib':
- return I18N::translateContext('husband’s sibling', 'brother/sister-in-law');
- case 'hussis':
- return I18N::translateContext('husband’s sister', 'sister-in-law');
- case 'husson':
- return I18N::translateContext('husband’s son', 'step-son');
- case 'motbro':
- return I18N::translateContext('mother’s brother', 'uncle');
- case 'motchi':
- return I18N::translateContext('mother’s child', 'half-sibling');
- case 'motdau':
- return I18N::translateContext('mother’s daughter', 'half-sister');
- case 'motfat':
- return I18N::translateContext('mother’s father', 'maternal grandfather');
- case 'mothus':
- return I18N::translateContext('mother’s husband', 'step-father');
- case 'motmot':
- return I18N::translateContext('mother’s mother', 'maternal grandmother');
- case 'motpar':
- return I18N::translateContext('mother’s parent', 'maternal grandparent');
- case 'motsib':
- return I18N::translateContext('mother’s sibling', 'aunt/uncle');
- case 'motsis':
- return I18N::translateContext('mother’s sister', 'aunt');
- case 'motson':
- return I18N::translateContext('mother’s son', 'half-brother');
- case 'parbro':
- return I18N::translateContext('parent’s brother', 'uncle');
- case 'parchi':
- return I18N::translateContext('parent’s child', 'half-sibling');
- case 'pardau':
- return I18N::translateContext('parent’s daughter', 'half-sister');
- case 'parfat':
- return I18N::translateContext('parent’s father', 'grandfather');
- case 'parmot':
- return I18N::translateContext('parent’s mother', 'grandmother');
- case 'parpar':
- return I18N::translateContext('parent’s parent', 'grandparent');
- case 'parsib':
- return I18N::translateContext('parent’s sibling', 'aunt/uncle');
- case 'parsis':
- return I18N::translateContext('parent’s sister', 'aunt');
- case 'parson':
- return I18N::translateContext('parent’s son', 'half-brother');
- case 'parspo':
- return I18N::translateContext('parent’s spouse', 'step-parent');
- case 'sibchi':
- return I18N::translateContext('sibling’s child', 'nephew/niece');
- case 'sibdau':
- return I18N::translateContext('sibling’s daughter', 'niece');
- case 'sibson':
- return I18N::translateContext('sibling’s son', 'nephew');
- case 'sibspo':
- return I18N::translateContext('sibling’s spouse', 'brother/sister-in-law');
- case 'sischi':
- return I18N::translateContext('sister’s child', 'nephew/niece');
- case 'sisdau':
- return I18N::translateContext('sister’s daughter', 'niece');
- case 'sishus':
- return I18N::translateContext('sister’s husband', 'brother-in-law');
- case 'sisson':
- return I18N::translateContext('sister’s son', 'nephew');
- case 'sonchi':
- return I18N::translateContext('son’s child', 'grandchild');
- case 'sondau':
- return I18N::translateContext('son’s daughter', 'granddaughter');
- case 'sonson':
- return I18N::translateContext('son’s son', 'grandson');
- case 'sonwif':
- return I18N::translateContext('son’s wife', 'daughter-in-law');
- case 'spobro':
- return I18N::translateContext('spouse’s brother', 'brother-in-law');
- case 'spochi':
- return I18N::translateContext('spouse’s child', 'step-child');
- case 'spodau':
- return I18N::translateContext('spouse’s daughter', 'step-daughter');
- case 'spofat':
- return I18N::translateContext('spouse’s father', 'father-in-law');
- case 'spomot':
- return I18N::translateContext('spouse’s mother', 'mother-in-law');
- case 'sposis':
- return I18N::translateContext('spouse’s sister', 'sister-in-law');
- case 'sposon':
- return I18N::translateContext('spouse’s son', 'step-son');
- case 'spopar':
- return I18N::translateContext('spouse’s parent', 'mother/father-in-law');
- case 'sposib':
- return I18N::translateContext('spouse’s sibling', 'brother/sister-in-law');
- case 'wifbro':
- return I18N::translateContext('wife’s brother', 'brother-in-law');
- case 'wifchi':
- return I18N::translateContext('wife’s child', 'step-child');
- case 'wifdau':
- return I18N::translateContext('wife’s daughter', 'step-daughter');
- case 'wiffat':
- return I18N::translateContext('wife’s father', 'father-in-law');
- case 'wifmot':
- return I18N::translateContext('wife’s mother', 'mother-in-law');
- case 'wifsib':
- return I18N::translateContext('wife’s sibling', 'brother/sister-in-law');
- case 'wifsis':
- return I18N::translateContext('wife’s sister', 'sister-in-law');
- case 'wifson':
- return I18N::translateContext('wife’s son', 'step-son');
-
- // Level Three relationships
- // I have commented out some of the unknown-sex relationships that are unlikely to to occur.
- // Feel free to add them in, if you think they might be needed
- case 'brochichi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s child’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s child’s child', 'great-nephew/niece');
- }
- case 'brochidau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s child’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s child’s daughter', 'great-niece');
- }
- case 'brochison':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s child’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) brother’s child’s son', 'great-nephew');
- }
- case 'brodauchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s daughter’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s daughter’s child', 'great-nephew/niece');
- }
- case 'brodaudau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s daughter’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s daughter’s daughter', 'great-niece');
- }
- case 'brodauhus':
- return I18N::translateContext('brother’s daughter’s husband', 'nephew-in-law');
- case 'brodauson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s daughter’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) brother’s daughter’s son', 'great-nephew');
- }
- case 'brosonchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s son’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s son’s child', 'great-nephew/niece');
- }
- case 'brosondau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s son’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s son’s daughter', 'great-niece');
- }
- case 'brosonson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s son’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) brother’s son’s son', 'great-nephew');
- }
- case 'brosonwif':
- return I18N::translateContext('brother’s son’s wife', 'niece-in-law');
- case 'browifbro':
- return I18N::translateContext('brother’s wife’s brother', 'brother-in-law');
- case 'browifsib':
- return I18N::translateContext('brother’s wife’s sibling', 'brother/sister-in-law');
- case 'browifsis':
- return I18N::translateContext('brother’s wife’s sister', 'sister-in-law');
- case 'chichichi':
- return I18N::translateContext('child’s child’s child', 'great-grandchild');
- case 'chichidau':
- return I18N::translateContext('child’s child’s daughter', 'great-granddaughter');
- case 'chichison':
- return I18N::translateContext('child’s child’s son', 'great-grandson');
- case 'chidauchi':
- return I18N::translateContext('child’s daughter’s child', 'great-grandchild');
- case 'chidaudau':
- return I18N::translateContext('child’s daughter’s daughter', 'great-granddaughter');
- case 'chidauhus':
- return I18N::translateContext('child’s daughter’s husband', 'granddaughter’s husband');
- case 'chidauson':
- return I18N::translateContext('child’s daughter’s son', 'great-grandson');
- case 'chisonchi':
- return I18N::translateContext('child’s son’s child', 'great-grandchild');
- case 'chisondau':
- return I18N::translateContext('child’s son’s daughter', 'great-granddaughter');
- case 'chisonson':
- return I18N::translateContext('child’s son’s son', 'great-grandson');
- case 'chisonwif':
- return I18N::translateContext('child’s son’s wife', 'grandson’s wife');
- case 'dauchichi':
- return I18N::translateContext('daughter’s child’s child', 'great-grandchild');
- case 'dauchidau':
- return I18N::translateContext('daughter’s child’s daughter', 'great-granddaughter');
- case 'dauchison':
- return I18N::translateContext('daughter’s child’s son', 'great-grandson');
- case 'daudauchi':
- return I18N::translateContext('daughter’s daughter’s child', 'great-grandchild');
- case 'daudaudau':
- return I18N::translateContext('daughter’s daughter’s daughter', 'great-granddaughter');
- case 'daudauhus':
- return I18N::translateContext('daughter’s daughter’s husband', 'granddaughter’s husband');
- case 'daudauson':
- return I18N::translateContext('daughter’s daughter’s son', 'great-grandson');
- case 'dauhusfat':
- return I18N::translateContext('daughter’s husband’s father', 'son-in-law’s father');
- case 'dauhusmot':
- return I18N::translateContext('daughter’s husband’s mother', 'son-in-law’s mother');
- case 'dauhuspar':
- return I18N::translateContext('daughter’s husband’s parent', 'son-in-law’s parent');
- case 'dausonchi':
- return I18N::translateContext('daughter’s son’s child', 'great-grandchild');
- case 'dausondau':
- return I18N::translateContext('daughter’s son’s daughter', 'great-granddaughter');
- case 'dausonson':
- return I18N::translateContext('daughter’s son’s son', 'great-grandson');
- case 'dausonwif':
- return I18N::translateContext('daughter’s son’s wife', 'grandson’s wife');
- case 'fatbrochi':
- return I18N::translateContext('father’s brother’s child', 'first cousin');
- case 'fatbrodau':
- return I18N::translateContext('father’s brother’s daughter', 'first cousin');
- case 'fatbroson':
- return I18N::translateContext('father’s brother’s son', 'first cousin');
- case 'fatbrowif':
- return I18N::translateContext('father’s brother’s wife', 'aunt');
- case 'fatfatbro':
- return I18N::translateContext('father’s father’s brother', 'great-uncle');
- case 'fatfatfat':
- return I18N::translateContext('father’s father’s father', 'great-grandfather');
- case 'fatfatmot':
- return I18N::translateContext('father’s father’s mother', 'great-grandmother');
- case 'fatfatpar':
- return I18N::translateContext('father’s father’s parent', 'great-grandparent');
- case 'fatfatsib':
- return I18N::translateContext('father’s father’s sibling', 'great-aunt/uncle');
- case 'fatfatsis':
- return I18N::translateContext('father’s father’s sister', 'great-aunt');
- case 'fatmotbro':
- return I18N::translateContext('father’s mother’s brother', 'great-uncle');
- case 'fatmotfat':
- return I18N::translateContext('father’s mother’s father', 'great-grandfather');
- case 'fatmotmot':
- return I18N::translateContext('father’s mother’s mother', 'great-grandmother');
- case 'fatmotpar':
- return I18N::translateContext('father’s mother’s parent', 'great-grandparent');
- case 'fatmotsib':
- return I18N::translateContext('father’s mother’s sibling', 'great-aunt/uncle');
- case 'fatmotsis':
- return I18N::translateContext('father’s mother’s sister', 'great-aunt');
- case 'fatparbro':
- return I18N::translateContext('father’s parent’s brother', 'great-uncle');
- case 'fatparfat':
- return I18N::translateContext('father’s parent’s father', 'great-grandfather');
- case 'fatparmot':
- return I18N::translateContext('father’s parent’s mother', 'great-grandmother');
- case 'fatparpar':
- return I18N::translateContext('father’s parent’s parent', 'great-grandparent');
- case 'fatparsib':
- return I18N::translateContext('father’s parent’s sibling', 'great-aunt/uncle');
- case 'fatparsis':
- return I18N::translateContext('father’s parent’s sister', 'great-aunt');
- case 'fatsischi':
- return I18N::translateContext('father’s sister’s child', 'first cousin');
- case 'fatsisdau':
- return I18N::translateContext('father’s sister’s daughter', 'first cousin');
- case 'fatsishus':
- return I18N::translateContext('father’s sister’s husband', 'uncle');
- case 'fatsisson':
- return I18N::translateContext('father’s sister’s son', 'first cousin');
- case 'fatwifchi':
- return I18N::translateContext('father’s wife’s child', 'step-sibling');
- case 'fatwifdau':
- return I18N::translateContext('father’s wife’s daughter', 'step-sister');
- case 'fatwifson':
- return I18N::translateContext('father’s wife’s son', 'step-brother');
- case 'husbrowif':
- return I18N::translateContext('husband’s brother’s wife', 'sister-in-law');
- case 'hussishus':
- return I18N::translateContext('husband’s sister’s husband', 'brother-in-law');
- case 'motbrochi':
- return I18N::translateContext('mother’s brother’s child', 'first cousin');
- case 'motbrodau':
- return I18N::translateContext('mother’s brother’s daughter', 'first cousin');
- case 'motbroson':
- return I18N::translateContext('mother’s brother’s son', 'first cousin');
- case 'motbrowif':
- return I18N::translateContext('mother’s brother’s wife', 'aunt');
- case 'motfatbro':
- return I18N::translateContext('mother’s father’s brother', 'great-uncle');
- case 'motfatfat':
- return I18N::translateContext('mother’s father’s father', 'great-grandfather');
- case 'motfatmot':
- return I18N::translateContext('mother’s father’s mother', 'great-grandmother');
- case 'motfatpar':
- return I18N::translateContext('mother’s father’s parent', 'great-grandparent');
- case 'motfatsib':
- return I18N::translateContext('mother’s father’s sibling', 'great-aunt/uncle');
- case 'motfatsis':
- return I18N::translateContext('mother’s father’s sister', 'great-aunt');
- case 'mothuschi':
- return I18N::translateContext('mother’s husband’s child', 'step-sibling');
- case 'mothusdau':
- return I18N::translateContext('mother’s husband’s daughter', 'step-sister');
- case 'mothusson':
- return I18N::translateContext('mother’s husband’s son', 'step-brother');
- case 'motmotbro':
- return I18N::translateContext('mother’s mother’s brother', 'great-uncle');
- case 'motmotfat':
- return I18N::translateContext('mother’s mother’s father', 'great-grandfather');
- case 'motmotmot':
- return I18N::translateContext('mother’s mother’s mother', 'great-grandmother');
- case 'motmotpar':
- return I18N::translateContext('mother’s mother’s parent', 'great-grandparent');
- case 'motmotsib':
- return I18N::translateContext('mother’s mother’s sibling', 'great-aunt/uncle');
- case 'motmotsis':
- return I18N::translateContext('mother’s mother’s sister', 'great-aunt');
- case 'motparbro':
- return I18N::translateContext('mother’s parent’s brother', 'great-uncle');
- case 'motparfat':
- return I18N::translateContext('mother’s parent’s father', 'great-grandfather');
- case 'motparmot':
- return I18N::translateContext('mother’s parent’s mother', 'great-grandmother');
- case 'motparpar':
- return I18N::translateContext('mother’s parent’s parent', 'great-grandparent');
- case 'motparsib':
- return I18N::translateContext('mother’s parent’s sibling', 'great-aunt/uncle');
- case 'motparsis':
- return I18N::translateContext('mother’s parent’s sister', 'great-aunt');
- case 'motsischi':
- return I18N::translateContext('mother’s sister’s child', 'first cousin');
- case 'motsisdau':
- return I18N::translateContext('mother’s sister’s daughter', 'first cousin');
- case 'motsishus':
- return I18N::translateContext('mother’s sister’s husband', 'uncle');
- case 'motsisson':
- return I18N::translateContext('mother’s sister’s son', 'first cousin');
- case 'parbrowif':
- return I18N::translateContext('parent’s brother’s wife', 'aunt');
- case 'parfatbro':
- return I18N::translateContext('parent’s father’s brother', 'great-uncle');
- case 'parfatfat':
- return I18N::translateContext('parent’s father’s father', 'great-grandfather');
- case 'parfatmot':
- return I18N::translateContext('parent’s father’s mother', 'great-grandmother');
- case 'parfatpar':
- return I18N::translateContext('parent’s father’s parent', 'great-grandparent');
- case 'parfatsib':
- return I18N::translateContext('parent’s father’s sibling', 'great-aunt/uncle');
- case 'parfatsis':
- return I18N::translateContext('parent’s father’s sister', 'great-aunt');
- case 'parmotbro':
- return I18N::translateContext('parent’s mother’s brother', 'great-uncle');
- case 'parmotfat':
- return I18N::translateContext('parent’s mother’s father', 'great-grandfather');
- case 'parmotmot':
- return I18N::translateContext('parent’s mother’s mother', 'great-grandmother');
- case 'parmotpar':
- return I18N::translateContext('parent’s mother’s parent', 'great-grandparent');
- case 'parmotsib':
- return I18N::translateContext('parent’s mother’s sibling', 'great-aunt/uncle');
- case 'parmotsis':
- return I18N::translateContext('parent’s mother’s sister', 'great-aunt');
- case 'parparbro':
- return I18N::translateContext('parent’s parent’s brother', 'great-uncle');
- case 'parparfat':
- return I18N::translateContext('parent’s parent’s father', 'great-grandfather');
- case 'parparmot':
- return I18N::translateContext('parent’s parent’s mother', 'great-grandmother');
- case 'parparpar':
- return I18N::translateContext('parent’s parent’s parent', 'great-grandparent');
- case 'parparsib':
- return I18N::translateContext('parent’s parent’s sibling', 'great-aunt/uncle');
- case 'parparsis':
- return I18N::translateContext('parent’s parent’s sister', 'great-aunt');
- case 'parsishus':
- return I18N::translateContext('parent’s sister’s husband', 'uncle');
- case 'parspochi':
- return I18N::translateContext('parent’s spouse’s child', 'step-sibling');
- case 'parspodau':
- return I18N::translateContext('parent’s spouse’s daughter', 'step-sister');
- case 'parsposon':
- return I18N::translateContext('parent’s spouse’s son', 'step-brother');
- case 'sibchichi':
- return I18N::translateContext('sibling’s child’s child', 'great-nephew/niece');
- case 'sibchidau':
- return I18N::translateContext('sibling’s child’s daughter', 'great-niece');
- case 'sibchison':
- return I18N::translateContext('sibling’s child’s son', 'great-nephew');
- case 'sibdauchi':
- return I18N::translateContext('sibling’s daughter’s child', 'great-nephew/niece');
- case 'sibdaudau':
- return I18N::translateContext('sibling’s daughter’s daughter', 'great-niece');
- case 'sibdauhus':
- return I18N::translateContext('sibling’s daughter’s husband', 'nephew-in-law');
- case 'sibdauson':
- return I18N::translateContext('sibling’s daughter’s son', 'great-nephew');
- case 'sibsonchi':
- return I18N::translateContext('sibling’s son’s child', 'great-nephew/niece');
- case 'sibsondau':
- return I18N::translateContext('sibling’s son’s daughter', 'great-niece');
- case 'sibsonson':
- return I18N::translateContext('sibling’s son’s son', 'great-nephew');
- case 'sibsonwif':
- return I18N::translateContext('sibling’s son’s wife', 'niece-in-law');
- case 'sischichi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s child’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s child’s child', 'great-nephew/niece');
- }
- case 'sischidau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s child’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s child’s daughter', 'great-niece');
- }
- case 'sischison':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s child’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) sister’s child’s son', 'great-nephew');
- }
- case 'sisdauchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s daughter’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s daughter’s child', 'great-nephew/niece');
- }
- case 'sisdaudau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s daughter’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s daughter’s daughter', 'great-niece');
- }
- case 'sisdauhus':
- return I18N::translateContext('sisters’s daughter’s husband', 'nephew-in-law');
- case 'sisdauson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s daughter’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) sister’s daughter’s son', 'great-nephew');
- }
- case 'sishusbro':
- return I18N::translateContext('sister’s husband’s brother', 'brother-in-law');
- case 'sishussib':
- return I18N::translateContext('sister’s husband’s sibling', 'brother/sister-in-law');
- case 'sishussis':
- return I18N::translateContext('sister’s husband’s sister', 'sister-in-law');
- case 'sissonchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s son’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s son’s child', 'great-nephew/niece');
- }
- case 'sissondau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s son’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s son’s daughter', 'great-niece');
- }
- case 'sissonson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s son’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) sister’s son’s son', 'great-nephew');
- }
- case 'sissonwif':
- return I18N::translateContext('sisters’s son’s wife', 'niece-in-law');
- case 'sonchichi':
- return I18N::translateContext('son’s child’s child', 'great-grandchild');
- case 'sonchidau':
- return I18N::translateContext('son’s child’s daughter', 'great-granddaughter');
- case 'sonchison':
- return I18N::translateContext('son’s child’s son', 'great-grandson');
- case 'sondauchi':
- return I18N::translateContext('son’s daughter’s child', 'great-grandchild');
- case 'sondaudau':
- return I18N::translateContext('son’s daughter’s daughter', 'great-granddaughter');
- case 'sondauhus':
- return I18N::translateContext('son’s daughter’s husband', 'granddaughter’s husband');
- case 'sondauson':
- return I18N::translateContext('son’s daughter’s son', 'great-grandson');
- case 'sonsonchi':
- return I18N::translateContext('son’s son’s child', 'great-grandchild');
- case 'sonsondau':
- return I18N::translateContext('son’s son’s daughter', 'great-granddaughter');
- case 'sonsonson':
- return I18N::translateContext('son’s son’s son', 'great-grandson');
- case 'sonsonwif':
- return I18N::translateContext('son’s son’s wife', 'grandson’s wife');
- case 'sonwiffat':
- return I18N::translateContext('son’s wife’s father', 'daughter-in-law’s father');
- case 'sonwifmot':
- return I18N::translateContext('son’s wife’s mother', 'daughter-in-law’s mother');
- case 'sonwifpar':
- return I18N::translateContext('son’s wife’s parent', 'daughter-in-law’s parent');
- case 'wifbrowif':
- return I18N::translateContext('wife’s brother’s wife', 'sister-in-law');
- case 'wifsishus':
- return I18N::translateContext('wife’s sister’s husband', 'brother-in-law');
-
- // Some “special case” level four relationships that have specific names in certain languages
- case 'fatfatbrowif':
- return I18N::translateContext('father’s father’s brother’s wife', 'great-aunt');
- case 'fatfatsibspo':
- return I18N::translateContext('father’s father’s sibling’s spouse', 'great-aunt/uncle');
- case 'fatfatsishus':
- return I18N::translateContext('father’s father’s sister’s husband', 'great-uncle');
- case 'fatmotbrowif':
- return I18N::translateContext('father’s mother’s brother’s wife', 'great-aunt');
- case 'fatmotsibspo':
- return I18N::translateContext('father’s mother’s sibling’s spouse', 'great-aunt/uncle');
- case 'fatmotsishus':
- return I18N::translateContext('father’s mother’s sister’s husband', 'great-uncle');
- case 'fatparbrowif':
- return I18N::translateContext('father’s parent’s brother’s wife', 'great-aunt');
- case 'fatparsibspo':
- return I18N::translateContext('father’s parent’s sibling’s spouse', 'great-aunt/uncle');
- case 'fatparsishus':
- return I18N::translateContext('father’s parent’s sister’s husband', 'great-uncle');
- case 'motfatbrowif':
- return I18N::translateContext('mother’s father’s brother’s wife', 'great-aunt');
- case 'motfatsibspo':
- return I18N::translateContext('mother’s father’s sibling’s spouse', 'great-aunt/uncle');
- case 'motfatsishus':
- return I18N::translateContext('mother’s father’s sister’s husband', 'great-uncle');
- case 'motmotbrowif':
- return I18N::translateContext('mother’s mother’s brother’s wife', 'great-aunt');
- case 'motmotsibspo':
- return I18N::translateContext('mother’s mother’s sibling’s spouse', 'great-aunt/uncle');
- case 'motmotsishus':
- return I18N::translateContext('mother’s mother’s sister’s husband', 'great-uncle');
- case 'motparbrowif':
- return I18N::translateContext('mother’s parent’s brother’s wife', 'great-aunt');
- case 'motparsibspo':
- return I18N::translateContext('mother’s parent’s sibling’s spouse', 'great-aunt/uncle');
- case 'motparsishus':
- return I18N::translateContext('mother’s parent’s sister’s husband', 'great-uncle');
- case 'parfatbrowif':
- return I18N::translateContext('parent’s father’s brother’s wife', 'great-aunt');
- case 'parfatsibspo':
- return I18N::translateContext('parent’s father’s sibling’s spouse', 'great-aunt/uncle');
- case 'parfatsishus':
- return I18N::translateContext('parent’s father’s sister’s husband', 'great-uncle');
- case 'parmotbrowif':
- return I18N::translateContext('parent’s mother’s brother’s wife', 'great-aunt');
- case 'parmotsibspo':
- return I18N::translateContext('parent’s mother’s sibling’s spouse', 'great-aunt/uncle');
- case 'parmotsishus':
- return I18N::translateContext('parent’s mother’s sister’s husband', 'great-uncle');
- case 'parparbrowif':
- return I18N::translateContext('parent’s parent’s brother’s wife', 'great-aunt');
- case 'parparsibspo':
- return I18N::translateContext('parent’s parent’s sibling’s spouse', 'great-aunt/uncle');
- case 'parparsishus':
- return I18N::translateContext('parent’s parent’s sister’s husband', 'great-uncle');
- case 'fatfatbrodau':
- return I18N::translateContext('father’s father’s brother’s daughter', 'first cousin once removed ascending');
- case 'fatfatbroson':
- return I18N::translateContext('father’s father’s brother’s son', 'first cousin once removed ascending');
- case 'fatfatbrochi':
- return I18N::translateContext('father’s father’s brother’s child', 'first cousin once removed ascending');
- case 'fatfatsisdau':
- return I18N::translateContext('father’s father’s sister’s daughter', 'first cousin once removed ascending');
- case 'fatfatsisson':
- return I18N::translateContext('father’s father’s sister’s son', 'first cousin once removed ascending');
- case 'fatfatsischi':
- return I18N::translateContext('father’s father’s sister’s child', 'first cousin once removed ascending');
- case 'fatmotbrodau':
- return I18N::translateContext('father’s mother’s brother’s daughter', 'first cousin once removed ascending');
- case 'fatmotbroson':
- return I18N::translateContext('father’s mother’s brother’s son', 'first cousin once removed ascending');
- case 'fatmotbrochi':
- return I18N::translateContext('father’s mother’s brother’s child', 'first cousin once removed ascending');
- case 'fatmotsisdau':
- return I18N::translateContext('father’s mother’s sister’s daughter', 'first cousin once removed ascending');
- case 'fatmotsisson':
- return I18N::translateContext('father’s mother’s sister’s son', 'first cousin once removed ascending');
- case 'fatmotsischi':
- return I18N::translateContext('father’s mother’s sister’s child', 'first cousin once removed ascending');
- case 'motfatbrodau':
- return I18N::translateContext('mother’s father’s brother’s daughter', 'first cousin once removed ascending');
- case 'motfatbroson':
- return I18N::translateContext('mother’s father’s brother’s son', 'first cousin once removed ascending');
- case 'motfatbrochi':
- return I18N::translateContext('mother’s father’s brother’s child', 'first cousin once removed ascending');
- case 'motfatsisdau':
- return I18N::translateContext('mother’s father’s sister’s daughter', 'first cousin once removed ascending');
- case 'motfatsisson':
- return I18N::translateContext('mother’s father’s sister’s son', 'first cousin once removed ascending');
- case 'motfatsischi':
- return I18N::translateContext('mother’s father’s sister’s child', 'first cousin once removed ascending');
- case 'motmotbrodau':
- return I18N::translateContext('mother’s mother’s brother’s daughter', 'first cousin once removed ascending');
- case 'motmotbroson':
- return I18N::translateContext('mother’s mother’s brother’s son', 'first cousin once removed ascending');
- case 'motmotbrochi':
- return I18N::translateContext('mother’s mother’s brother’s child', 'first cousin once removed ascending');
- case 'motmotsisdau':
- return I18N::translateContext('mother’s mother’s sister’s daughter', 'first cousin once removed ascending');
- case 'motmotsisson':
- return I18N::translateContext('mother’s mother’s sister’s son', 'first cousin once removed ascending');
- case 'motmotsischi':
- return I18N::translateContext('mother’s mother’s sister’s child', 'first cousin once removed ascending');
- }
-
- // Some “special case” level five relationships that have specific names in certain languages
- if (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandfather’s brother’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandfather’s brother’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandfather’s brother’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandfather’s sister’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandfather’s sister’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandfather’s sister’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandfather’s sibling’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandfather’s sibling’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandfather’s sibling’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandmother’s brother’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandmother’s brother’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandmother’s brother’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandmother’s sister’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandmother’s sister’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandmother’s sister’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandmother’s sibling’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandmother’s sibling’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandmother’s sibling’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandparent’s brother’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandparent’s brother’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandparent’s brother’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandparent’s sister’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandparent’s sister’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandparent’s sister’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandparent’s sibling’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandparent’s sibling’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandparent’s sibling’s grandchild', 'second cousin');
- }
-
- // Look for generic/pattern relationships.
- if (preg_match('/^((?:mot|fat|par)+)(bro|sis|sib)$/', $path, $match)) {
- // siblings of direct ancestors
- $up = strlen($match[1]) / 3;
- $bef_last = substr($path, -6, 3);
- switch ($up) {
- case 3:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great-grandfather’s brother', 'great-great-uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great-grandmother’s brother', 'great-great-uncle');
- } else {
- return I18N::translateContext('great-grandparent’s brother', 'great-great-uncle');
- }
- case 'F':
- return I18N::translate('great-great-aunt');
- default:
- return I18N::translate('great-great-aunt/uncle');
- }
- case 4:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great-great-grandfather’s brother', 'great-great-great-uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great-great-grandmother’s brother', 'great-great-great-uncle');
- } else {
- return I18N::translateContext('great-great-grandparent’s brother', 'great-great-great-uncle');
- }
- case 'F':
- return I18N::translate('great-great-great-aunt');
- default:
- return I18N::translate('great-great-great-aunt/uncle');
- }
- case 5:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great-great-great-grandfather’s brother', 'great ×4 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great-great-great-grandmother’s brother', 'great ×4 uncle');
- } else {
- return I18N::translateContext('great-great-great-grandparent’s brother', 'great ×4 uncle');
- }
- case 'F':
- return I18N::translate('great ×4 aunt');
- default:
- return I18N::translate('great ×4 aunt/uncle');
- }
- case 6:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×4 grandfather’s brother', 'great ×5 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×4 grandmother’s brother', 'great ×5 uncle');
- } else {
- return I18N::translateContext('great ×4 grandparent’s brother', 'great ×5 uncle');
- }
- case 'F':
- return I18N::translate('great ×5 aunt');
- default:
- return I18N::translate('great ×5 aunt/uncle');
- }
- case 7:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×5 grandfather’s brother', 'great ×6 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×5 grandmother’s brother', 'great ×6 uncle');
- } else {
- return I18N::translateContext('great ×5 grandparent’s brother', 'great ×6 uncle');
- }
- case 'F':
- return I18N::translate('great ×6 aunt');
- default:
- return I18N::translate('great ×6 aunt/uncle');
- }
- case 8:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×6 grandfather’s brother', 'great ×7 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×6 grandmother’s brother', 'great ×7 uncle');
- } else {
- return I18N::translateContext('great ×6 grandparent’s brother', 'great ×7 uncle');
- }
- case 'F':
- return I18N::translate('great ×7 aunt');
- default:
- return I18N::translate('great ×7 aunt/uncle');
- }
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 uncle is a Danish great ×10 uncle.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'da':
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s uncle', I18N::number($up - 4));
- case 'F':
- return I18N::translate('great ×%s aunt', I18N::number($up - 4));
- default:
- return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 4));
- }
- case 'pl':
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×(%s-1) grandfather’s brother', 'great ×%s uncle', I18N::number($up - 2));
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×(%s-1) grandmother’s brother', 'great ×%s uncle', I18N::number($up - 2));
- } else {
- return I18N::translateContext('great ×(%s-1) grandparent’s brother', 'great ×%s uncle', I18N::number($up - 2));
- }
- case 'F':
- return I18N::translate('great ×%s aunt', I18N::number($up - 2));
- default:
- return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 2));
- }
- case 'it': // Source: Michele Locati
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s uncle', I18N::number($up - 1));
- case 'F':
- return I18N::translate('great ×%s aunt', I18N::number($up - 1));
- default:
- return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 1));
- }
- }
- }
- }
- if (preg_match('/^(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
- // direct descendants of siblings
- $down = strlen($match[1]) / 3 + 1; // Add one, as we count generations from the common ancestor
- $first = substr($path, 0, 3);
- switch ($down) {
- case 4:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-grandson', 'great-great-nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-grandson', 'great-great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) great-great-nephew', 'great-great-nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-granddaughter', 'great-great-niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-granddaughter', 'great-great-niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-niece', 'great-great-niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-grandchild', 'great-great-nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-grandchild', 'great-great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-nephew/niece', 'great-great-nephew/niece');
- }
- }
- case 5:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-grandson', 'great-great-great-nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-grandson', 'great-great-great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) great-great-great-nephew', 'great-great-great-nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-granddaughter', 'great-great-great-niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-granddaughter', 'great-great-great-niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-great-niece', 'great-great-great-niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-grandchild', 'great-great-great-nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-grandchild', 'great-great-great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-great-nephew/niece', 'great-great-great-nephew/niece');
- }
- }
- case 6:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-great-grandson', 'great ×4 nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-great-grandson', 'great ×4 nephew');
- } else {
- return I18N::translateContext('(a woman’s) great ×4 nephew', 'great ×4 nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-great-granddaughter', 'great ×4 niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-great-granddaughter', 'great ×4 niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×4 niece', 'great ×4 niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-great-grandchild', 'great ×4 nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-great-grandchild', 'great ×4 nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×4 nephew/niece', 'great ×4 nephew/niece');
- }
- }
- case 7:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×4 grandson', 'great ×5 nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×4 grandson', 'great ×5 nephew');
- } else {
- return I18N::translateContext('(a woman’s) great ×5 nephew', 'great ×5 nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×4 granddaughter', 'great ×5 niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×4 granddaughter', 'great ×5 niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×5 niece', 'great ×5 niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×4 grandchild', 'great ×5 nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×4 grandchild', 'great ×5 nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×5 nephew/niece', 'great ×5 nephew/niece');
- }
- }
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 nephew is a Polish great ×11 nephew.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'pl': // Source: Lukasz Wilenski
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
- } else {
- return I18N::translateContext('(a woman’s) great ×%s nephew', 'great ×%s nephew', I18N::number($down - 3));
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
- } else {
- return I18N::translateContext('(a woman’s) great ×%s niece', 'great ×%s niece', I18N::number($down - 3));
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
- } else {
- return I18N::translateContext('(a woman’s) great ×%s nephew/niece', 'great ×%s nephew/niece', I18N::number($down - 3));
- }
- }
- case 'he': // Source: Meliza Amity
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s nephew', I18N::number($down - 1));
- case 'F':
- return I18N::translate('great ×%s niece', I18N::number($down - 1));
- default:
- return I18N::translate('great ×%s nephew/niece', I18N::number($down - 1));
- }
- case 'it': // Source: Michele Locati.
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s nephew', I18N::number($down - 2));
- case 'F':
- return I18N::translate('great ×%s niece', I18N::number($down - 2));
- default:
- return I18N::translate('great ×%s nephew/niece', I18N::number($down - 2));
- }
- }
- }
- }
- if (preg_match('/^((?:mot|fat|par)*)$/', $path, $match)) {
- // direct ancestors
- $up = strlen($match[1]) / 3;
- switch ($up) {
- case 4:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-grandfather');
- case 'F':
- return I18N::translate('great-great-grandmother');
- default:
- return I18N::translate('great-great-grandparent');
- }
- case 5:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-great-grandfather');
- case 'F':
- return I18N::translate('great-great-great-grandmother');
- default:
- return I18N::translate('great-great-great-grandparent');
- }
- case 6:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×4 grandfather');
- case 'F':
- return I18N::translate('great ×4 grandmother');
- default:
- return I18N::translate('great ×4 grandparent');
- }
- case 7:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×5 grandfather');
- case 'F':
- return I18N::translate('great ×5 grandmother');
- default:
- return I18N::translate('great ×5 grandparent');
- }
- case 8:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×6 grandfather');
- case 'F':
- return I18N::translate('great ×6 grandmother');
- default:
- return I18N::translate('great ×6 grandparent');
- }
- case 9:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×7 grandfather');
- case 'F':
- return I18N::translate('great ×7 grandmother');
- default:
- return I18N::translate('great ×7 grandparent');
- }
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 grandfather is a Danish great ×11 grandfather.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'da': // Source: Patrick Sorensen
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
- }
- case 'it': // Source: Michele Locati
- case 'es': // Source: Wes Groleau
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandfather', I18N::number($up));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up));
- }
- case 'fr': // Source: Jacqueline Tetreault
- case 'fr_CA':
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandfather', I18N::number($up - 1));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 1));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 1));
- }
- case 'nn': // Source: Hogne Røed Nilsen (https://bugs.launchpad.net/webtrees/+bug/1168553)
- case 'nb':
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
- }
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s grandfather', I18N::number($up - 2));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 2));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 2));
- }
- }
- }
- }
- if (preg_match('/^((?:son|dau|chi)*)$/', $path, $match)) {
- // direct descendants
- $up = strlen($match[1]) / 3;
- switch ($up) {
- case 4:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-grandson');
- case 'F':
- return I18N::translate('great-great-granddaughter');
- default:
- return I18N::translate('great-great-grandchild');
- }
-
- case 5:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-great-grandson');
- case 'F':
- return I18N::translate('great-great-great-granddaughter');
- default:
- return I18N::translate('great-great-great-grandchild');
- }
-
- case 6:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×4 grandson');
- case 'F':
- return I18N::translate('great ×4 granddaughter');
- default:
- return I18N::translate('great ×4 grandchild');
- }
-
- case 7:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×5 grandson');
- case 'F':
- return I18N::translate('great ×5 granddaughter');
- default:
- return I18N::translate('great ×5 grandchild');
- }
-
- case 8:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×6 grandson');
- case 'F':
- return I18N::translate('great ×6 granddaughter');
- default:
- return I18N::translate('great ×6 grandchild');
- }
-
- case 9:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×7 grandson');
- case 'F':
- return I18N::translate('great ×7 granddaughter');
- default:
- return I18N::translate('great ×7 grandchild');
- }
-
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 grandson is a Danish great ×11 grandson.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'nn': // Source: Hogne Røed Nilsen
- case 'nb':
- case 'da': // Source: Patrick Sorensen
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandson', I18N::number($up - 3));
- case 'F':
- return I18N::translate('great ×%s granddaughter', I18N::number($up - 3));
- default:
- return I18N::translate('great ×%s grandchild', I18N::number($up - 3));
- }
- case 'it': // Source: Michele Locati
- case 'es': // Source: Wes Groleau (adding doesn’t change behavior, but needs to be better researched)
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
-
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s grandson', I18N::number($up - 2));
- case 'F':
- return I18N::translate('great ×%s granddaughter', I18N::number($up - 2));
- default:
- return I18N::translate('great ×%s grandchild', I18N::number($up - 2));
- }
- }
- }
- }
- if (preg_match('/^((?:mot|fat|par)+)(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
- // cousins in English
- $ascent = $match[1];
- $descent = $match[2];
- $up = strlen($ascent) / 3;
- $down = strlen($descent) / 3;
- $cousin = min($up, $down); // Moved out of switch (en/default case) so that
- $removed = abs($down - $up); // Spanish (and other languages) can use it, too.
-
- // Different languages have different rules for naming cousins. For example,
- // an English “second cousin once removed” is a Polish “cousin of 7th degree”.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'pl': // Source: Lukasz Wilenski
- return cousin_name($up + $down + 2, $sex2);
- case 'it':
- // Source: Michele Locati. See italian_cousins_names.zip
- // http://webtrees.net/forums/8-translation/1200-great-xn-grandparent?limit=6&start=6
- return cousin_name($up + $down - 3, $sex2);
- case 'es':
- // Source: Wes Groleau. See http://UniGen.us/Parentesco.html & http://UniGen.us/Parentesco-D.html
- if ($down == $up) {
- return cousin_name($cousin, $sex2);
- } elseif ($down < $up) {
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('sib' . $descent, null, null));
- } else {
- switch ($sex2) {
- case 'M':
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('bro' . $descent, null, null));
- case 'F':
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('sis' . $descent, null, null));
- default:
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('sib' . $descent, null, null));
- }
- }
- case 'en_AU': // See: http://en.wikipedia.org/wiki/File:CousinTree.svg
- case 'en_GB':
- case 'en_US':
- default:
- switch ($removed) {
- case 0:
- return cousin_name($cousin, $sex2);
- case 1:
- if ($up > $down) {
- /* I18N: %s=“fifth cousin”, etc. http://www.ancestry.com/learn/library/article.aspx?article=2856 */
- return I18N::translate('%s once removed ascending', cousin_name($cousin, $sex2));
- } else {
- /* I18N: %s=“fifth cousin”, etc. http://www.ancestry.com/learn/library/article.aspx?article=2856 */
- return I18N::translate('%s once removed descending', cousin_name($cousin, $sex2));
- }
- case 2:
- if ($up > $down) {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s twice removed ascending', cousin_name($cousin, $sex2));
- } else {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s twice removed descending', cousin_name($cousin, $sex2));
- }
- case 3:
- if ($up > $down) {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s three times removed ascending', cousin_name($cousin, $sex2));
- } else {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s three times removed descending', cousin_name($cousin, $sex2));
- }
- default:
- if ($up > $down) {
- /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
- return I18N::translate('%1$s %2$s times removed ascending', cousin_name($cousin, $sex2), I18N::number($removed));
- } else {
- /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
- return I18N::translate('%1$s %2$s times removed descending', cousin_name($cousin, $sex2), I18N::number($removed));
- }
- }
- }
- }
-
- // Split the relationship into sub-relationships, e.g., third-cousin’s great-uncle.
- // Try splitting at every point, and choose the path with the shorted translated name.
-
- $relationship = null;
- $path1 = substr($path, 0, 3);
- $path2 = substr($path, 3);
- while ($path2) {
- $tmp = I18N::translate(
- // I18N: A complex relationship, such as “third-cousin’s great-uncle”
- '%1$s’s %2$s',
- get_relationship_name_from_path($path1, null, null), // TODO: need the actual people
- get_relationship_name_from_path($path2, null, null)
- );
- if (!$relationship || strlen($tmp) < strlen($relationship)) {
- $relationship = $tmp;
- }
- $path1 .= substr($path2, 0, 3);
- $path2 = substr($path2, 3);
- }
-
- return $relationship;
-}
-
-/**
- * Function to build an URL querystring from GET variables
- * Optionally, add/replace specified values
- *
- * @param null|string[] $overwrite
- * @param null|string $separator
- *
- * @return string
- */
-function get_query_url($overwrite = null, $separator = '&') {
- if (empty($_GET)) {
- $get = array();
- } else {
- $get = $_GET;
- }
- if (is_array($overwrite)) {
- foreach ($overwrite as $key => $value) {
- $get[$key] = $value;
- }
- }
-
- $query_string = '';
- foreach ($get as $key => $value) {
- if (!is_array($value)) {
- $query_string .= $separator . rawurlencode($key) . '=' . rawurlencode($value);
- } else {
- foreach ($value as $k => $v) {
- $query_string .= $separator . rawurlencode($key) . '%5B' . rawurlencode($k) . '%5D=' . rawurlencode($v);
- }
- }
- }
- $query_string = substr($query_string, strlen($separator)); // Remove leading “&amp;”
- if ($query_string) {
- return WT_SCRIPT_NAME . '?' . $query_string;
- } else {
- return WT_SCRIPT_NAME;
- }
-}
-
-/**
- * Determines whether the passed in filename is a link to an external source (i.e. contains “://”)
- *
- * @param string $file
- *
- * @return bool
- */
-function isFileExternal($file) {
- return strpos($file, '://') !== false;
-}
diff --git a/includes/functions/functions_charts.php b/includes/functions/functions_charts.php
deleted file mode 100644
index ab4e24f7fb..0000000000
--- a/includes/functions/functions_charts.php
+++ /dev/null
@@ -1,550 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Theme;
-
-/**
- * print a table cell with sosa number
- *
- * @param int $sosa
- * @param string $pid optional pid
- * @param string $arrowDirection direction of link arrow
- */
-function print_sosa_number($sosa, $pid = "", $arrowDirection = "up") {
- if (substr($sosa, -1, 1) == ".") {
- $personLabel = substr($sosa, 0, -1);
- } else {
- $personLabel = $sosa;
- }
- if ($arrowDirection == "blank") {
- $visibility = "hidden";
- } else {
- $visibility = "normal";
- }
- echo "<td class=\"subheaders center\" style=\"vertical-align: middle; text-indent: 0px; margin-top: 0px; white-space: nowrap; visibility: ", $visibility, ";\">";
- echo $personLabel;
- if ($sosa != "1" && $pid != "") {
- if ($arrowDirection == "left") {
- $dir = 0;
- } elseif ($arrowDirection == "right") {
- $dir = 1;
- } elseif ($arrowDirection == "down") {
- $dir = 3;
- } else {
- $dir = 2; // either 'blank' or 'up'
- }
- echo '<br>';
- print_url_arrow('#' . $pid, $pid, $dir);
- }
- echo '</td>';
-}
-
-/**
- * print the parents table for a family
- *
- * @param Family $family family gedcom ID
- * @param int $sosa child sosa number
- * @param string $label indi label (descendancy booklet)
- * @param string $parid parent ID (descendancy booklet)
- * @param string $gparid gd-parent ID (descendancy booklet)
- * @param int $show_full large or small box
- */
-function print_family_parents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1) {
-
- if ($show_full) {
- $pbheight = Theme::theme()->parameter('chart-box-y') + 14;
- } else {
- $pbheight = Theme::theme()->parameter('compact-chart-box-y') + 14;
- }
-
- $husb = $family->getHusband();
- if ($husb) {
- echo '<a name="', $husb->getXref(), '"></a>';
- } else {
- $husb = new Individual('M', "0 @M@ INDI\n1 SEX M", null, $family->getTree());
- }
- $wife = $family->getWife();
- if ($wife) {
- echo '<a name="', $wife->getXref(), '"></a>';
- } else {
- $wife = new Individual('F', "0 @F@ INDI\n1 SEX F", null, $family->getTree());
- }
-
- if ($sosa) {
- echo '<p class="name_head">', $family->getFullName(), '</p>';
- }
-
- /**
- * husband side
- */
- echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td rowspan=\"2\">";
- echo "<table border='0'><tr>";
-
- if ($parid) {
- if ($husb->getXref() == $parid) {
- print_sosa_number($label);
- } else {
- print_sosa_number($label, "", "blank");
- }
- } elseif ($sosa) {
- print_sosa_number($sosa * 2);
- }
- if ($husb->isPendingAddtion()) {
- echo '<td valign="top" class="facts_value new">';
- } elseif ($husb->isPendingDeletion()) {
- echo '<td valign="top" class="facts_value old">';
- } else {
- echo '<td valign="top">';
- }
- print_pedigree_person($husb, $show_full);
- echo "</td></tr></table>";
- echo "</td>";
- // husband’s parents
- $hfam = $husb->getPrimaryChildFamily();
- if ($hfam) {
- // remove the|| test for $sosa
- echo "<td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-vline') . "\" width=\"3\" height=\"" . ($pbheight + 9) . "\" alt=\"\"></td>";
- echo "<td><img class=\"line5\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- // husband’s father
- if ($hfam && $hfam->getHusband()) {
- echo "<table border='0'><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4, $hfam->getHusband()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo "<td valign=\"top\">";
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo "</td></tr></table>";
- } elseif ($hfam && !$hfam->getHusband()) {
- // Empty box for grandfather
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo '</td></tr></table>';
- }
- echo "</td>";
- }
- if ($hfam && ($sosa != -1)) {
- echo '<td valign="middle" rowspan="2">';
- print_url_arrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
- echo '</td>';
- }
- if ($hfam) {
- // husband’s mother
- echo "</tr><tr><td><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- if ($hfam && $hfam->getWife()) {
- echo "<table border='0'><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4 + 1, $hfam->getWife()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo '<td valign="top">';
- print_pedigree_person($hfam->getWife(), $show_full);
- echo '</td></tr></table>';
- } elseif ($hfam && !$hfam->getWife()) {
- // Empty box for grandmother
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getWife(), $show_full);
- echo '</td></tr></table>';
- }
- echo '</td>';
- }
- echo '</tr></table>';
- if ($sosa && $family->canShow()) {
- foreach ($family->getFacts(WT_EVENTS_MARR) as $fact) {
- echo '<a href="', $family->getHtmlUrl(), '" class="details1">';
- echo str_repeat('&nbsp;', 10);
- echo $fact->summary();
- echo '</a>';
- }
- } else {
- echo '<br>';
- }
-
- /**
- * wife side
- */
- echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td rowspan=\"2\">";
- echo "<table><tr>";
- if ($parid) {
- if ($wife->getXref() == $parid) {
- print_sosa_number($label);
- } else {
- print_sosa_number($label, "", "blank");
- }
- } elseif ($sosa) {
- print_sosa_number($sosa * 2 + 1);
- }
- if ($wife->isPendingAddtion()) {
- echo '<td valign="top" class="facts_value new">';
- } elseif ($wife->isPendingDeletion()) {
- echo '<td valign="top" class="facts_value old">';
- } else {
- echo '<td valign="top">';
- }
- print_pedigree_person($wife, $show_full);
- echo "</td></tr></table>";
- echo "</td>";
- // wife’s parents
- $hfam = $wife->getPrimaryChildFamily();
-
- if ($hfam) {
- echo "<td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-vline') . "\" width=\"3\" height=\"" . ($pbheight + 9) . "\" alt=\"\"></td>";
- echo "<td><img class=\"line5\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- // wife’s father
- if ($hfam && $hfam->getHusband()) {
- echo "<table><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4 + 2, $hfam->getHusband()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo "<td valign=\"top\">";
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo "</td></tr></table>";
- } elseif ($hfam && !$hfam->getHusband()) {
- // Empty box for grandfather
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo '</td></tr></table>';
- }
- echo "</td>";
- }
- if ($hfam && ($sosa != -1)) {
- echo '<td valign="middle" rowspan="2">';
- print_url_arrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
- echo '</td>';
- }
- if ($hfam) {
- // wife’s mother
- echo "</tr><tr><td><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- if ($hfam && $hfam->getWife()) {
- echo "<table><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4 + 3, $hfam->getWife()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo "<td valign=\"top\">";
- print_pedigree_person($hfam->getWife(), $show_full);
- echo "</td></tr></table>";
- } elseif ($hfam && !$hfam->getWife()) {
- // Empty box for grandmother
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getWife(), $show_full);
- echo '</td></tr></table>';
- }
- echo '</td>';
- }
- echo "</tr></table>";
-}
-
-/**
- * print the children table for a family
- *
- * @param Family $family family
- * @param string $childid child ID
- * @param int $sosa child sosa number
- * @param string $label indi label (descendancy booklet)
- * @param int $show_cousins display cousins on chart
- * @param int $show_full large or small box
- */
-function print_family_children(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1) {
-
- if ($show_full) {
- $bheight = Theme::theme()->parameter('chart-box-y');
- } else {
- $bheight = Theme::theme()->parameter('compact-chart-box-y');
- }
-
- $pbheight = $bheight + 14;
-
- $children = $family->getChildren();
- $numchil = count($children);
-
- echo '<table border="0" cellpadding="0" cellspacing="2"><tr>';
- if ($sosa > 0) {
- echo '<td></td>';
- }
- echo '<td><span class="subheaders">';
- if ($numchil == 0) {
- echo I18N::translate('No children');
- } else {
- echo I18N::plural('%s child', '%s children', $numchil, $numchil);
- }
- echo '</span>';
-
- if ($sosa == 0 && Auth::isEditor($family->getTree())) {
- echo '<br>';
- echo "<a href=\"#\" onclick=\"return add_child_to_family('", $family->getXref(), "', 'U');\">" . I18N::translate('Add a child to this family') . "</a>";
- echo ' <a class="icon-sex_m_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'M\');" title="', I18N::translate('son'), '"></a>';
- echo ' <a class="icon-sex_f_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'F\');" title="', I18N::translate('daughter'), '"></a>';
- echo '<br><br>';
- }
- echo '</td>';
- if ($sosa > 0) {
- echo '<td></td><td></td>';
- }
- echo '</tr>';
-
- $nchi = 1;
- if ($children) {
- foreach ($children as $child) {
- echo '<tr>';
- if ($sosa != 0) {
- if ($child->getXref() == $childid) {
- print_sosa_number($sosa, $childid);
- } elseif (empty($label)) {
- print_sosa_number("");
- } else {
- print_sosa_number($label . ($nchi++) . ".");
- }
- }
- if ($child->isPendingAddtion()) {
- echo '<td valign="middle" class="new">';
- } elseif ($child->isPendingDeletion()) {
- echo '<td valign="middle" class="old">';
- } else {
- echo '<td valign="middle">';
- }
- print_pedigree_person($child, $show_full);
- echo "</td>";
- if ($sosa != 0) {
- // loop for all families where current child is a spouse
- $famids = $child->getSpouseFamilies();
-
- $maxfam = count($famids) - 1;
- for ($f = 0; $f <= $maxfam; $f++) {
- $famid_child = $famids[$f]->getXref();
- // multiple marriages
- if ($f > 0) {
- echo '</tr><tr><td></td>';
- echo '<td valign="top"';
- if (I18N::direction() === 'rtl') {
- echo ' align="left">';
- } else {
- echo ' align="right">';
- }
-
- //find out how many cousins there are to establish vertical line on second families
- $fchildren = $famids[$f]->getChildren();
- $kids = count($fchildren);
- $Pheader = ($bheight - 1) * $kids;
- $PBadj = 6; // default
- if ($show_cousins > 0) {
- if ($kids) {
- $PBadj = max(0, $Pheader / 2 + $kids * 4.5);
- }
- }
-
- if ($f == $maxfam) {
- echo "<img height=\"" . ((($bheight / 2)) + $PBadj) . "px\"";
- } else {
- echo "<img height=\"" . $pbheight . "px\"";
- }
- echo " width=\"3\" src=\"" . Theme::theme()->parameter('image-vline') . "\" alt=\"\">";
- echo "</td>";
- }
- echo "<td class=\"details1\" valign=\"middle\" align=\"center\">";
- $spouse = $famids[$f]->getSpouse($child);
-
- $marr = $famids[$f]->getFirstFact('MARR');
- $div = $famids[$f]->getFirstFact('DIV');
- if ($marr) {
- // marriage date
- echo $marr->getDate()->minimumDate()->format('%Y');
- // divorce date
- if ($div) {
- echo '–', $div->getDate()->minimumDate()->format('%Y');
- }
- }
- echo "<br><img width=\"100%\" class=\"line5\" height=\"3\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\">";
- echo "</td>";
- // spouse information
- echo "<td style=\"vertical-align: center;";
- if (!empty($divrec)) {
- echo " filter:alpha(opacity=40);opacity:0.4;\">";
- } else {
- echo "\">";
- }
- print_pedigree_person($spouse, $show_full);
- echo "</td>";
- // cousins
- if ($show_cousins) {
- print_cousins($famid_child, $show_full);
- }
- }
- }
- echo "</tr>";
- }
- } elseif ($sosa < 1) {
- // message 'no children' except for sosa
- if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
- echo '<tr><td><i class="icon-childless"></i> ' . I18N::translate('This family remained childless') . '</td></tr>';
- }
- }
- echo "</table><br>";
-}
-
-/**
- * print a family with Sosa-Stradonitz numbering system
- * ($rootid=1, father=2, mother=3 ...)
- *
- * @param string $famid family gedcom ID
- * @param string $childid tree root ID
- * @param int $sosa starting sosa number
- * @param string $label indi label (descendancy booklet)
- * @param string $parid parent ID (descendancy booklet)
- * @param string $gparid gd-parent ID (descendancy booklet)
- * @param int $show_cousins display cousins on chart
- * @param int $show_full large or small box
- */
-function print_sosa_family($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1) {
- global $WT_TREE;
-
- echo '<hr>';
- echo '<p style="page-break-before: always;">';
- if (!empty($famid)) {
- echo '<a name="', $famid, '"></a>';
- }
- print_family_parents(Family::getInstance($famid, $WT_TREE), $sosa, $label, $parid, $gparid, $show_full);
- echo '<br>';
- echo '<table><tr><td valign="top">';
- print_family_children(Family::getInstance($famid, $WT_TREE), $childid, $sosa, $label, $show_cousins, $show_full);
- echo '</td></tr></table>';
- echo '<br>';
-}
-
-/**
- * print an arrow to a new url
- *
- * @param string $url target url
- * @param string $label arrow label
- * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
- */
-function print_url_arrow($url, $label, $dir = 2) {
- if ($url === '') {
- return;
- }
-
- // arrow direction
- $adir = $dir;
- if (I18N::direction() === 'rtl' && $dir === 0) {
- $adir = 1;
- }
- if (I18N::direction() === 'rtl' && $dir === 1) {
- $adir = 0;
- }
-
- // arrow style 0 1 2 3
- $array_style = array('icon-larrow', 'icon-rarrow', 'icon-uarrow', 'icon-darrow');
- $astyle = $array_style[$adir];
-
- // Labels include people’s names, which may contain markup
- echo '<a href="' . $url . '" title="' . strip_tags($label) . '" class="' . $astyle . '"></a>';
-}
-
-/**
- * builds and returns sosa relationship name in the active language
- *
- * @param string $sosa sosa number
- *
- * @return string
- */
-function get_sosa_name($sosa) {
- $path = '';
- while ($sosa > 1) {
- if ($sosa % 2 == 1) {
- $sosa -= 1;
- $path = 'mot' . $path;
- } else {
- $path = 'fat' . $path;
- }
- $sosa /= 2;
- }
-
- return get_relationship_name_from_path($path, null, null);
-}
-
-/**
- * print cousins list
- *
- * @param string $famid family ID
- * @param int $show_full large or small box
- */
-function print_cousins($famid, $show_full = 1) {
- global $WT_TREE;
-
- if ($show_full) {
- $bheight = Theme::theme()->parameter('chart-box-y');
- } else {
- $bheight = Theme::theme()->parameter('compact-chart-box-y');
- }
-
- $family = Family::getInstance($famid, $WT_TREE);
- $fchildren = $family->getChildren();
-
- $kids = count($fchildren);
-
- echo '<td valign="middle" height="100%">';
- if ($kids) {
- echo '<table cellspacing="0" cellpadding="0" border="0" ><tr valign="middle">';
- if ($kids > 1) {
- echo '<td rowspan="', $kids, '" valign="middle" align="right"><img width="3px" height="', (($bheight + 9) * ($kids - 1)), 'px" src="', Theme::theme()->parameter('image-vline'), '" alt=""></td>';
- }
- $ctkids = count($fchildren);
- $i = 1;
- foreach ($fchildren as $fchil) {
- if ($i == 1) {
- echo '<td><img width="10px" height="3px" align="top"';
- } else {
- echo '<td><img width="10px" height="3px"';
- }
- if (I18N::direction() === 'ltr') {
- echo ' style="padding-right: 2px;"';
- } else {
- echo ' style="padding-left: 2px;"';
- }
- echo ' src="', Theme::theme()->parameter('image-hline'), '" alt=""></td><td>';
- print_pedigree_person($fchil, $show_full);
- echo '</td></tr>';
- if ($i < $ctkids) {
- echo '<tr>';
- $i++;
- }
- }
- echo '</table>';
- } else {
- // If there is known that there are no children (as opposed to no known children)
- if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
- echo ' <i class="icon-childless" title="', I18N::translate('This family remained childless'), '"></i>';
- }
- }
- echo '</td>';
-}
diff --git a/includes/functions/functions_date.php b/includes/functions/functions_date.php
deleted file mode 100644
index ff40204fd3..0000000000
--- a/includes/functions/functions_date.php
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\I18N;
-
-/**
- * @param string $age_string
- * @param bool $show_years
- *
- * @return string
- */
-function get_age_at_event($age_string, $show_years) {
- switch (strtoupper($age_string)) {
- case 'CHILD':
- return I18N::translate('Child');
- case 'INFANT':
- return I18N::translate('Infant');
- case 'STILLBORN':
- return I18N::translate('Stillborn');
- default:
- return preg_replace_callback(
- array(
- '/(\d+)([ymwd])/',
- ),
- function ($match) use ($age_string, $show_years) {
- switch ($match[2]) {
- case 'y':
- if ($show_years || preg_match('/[dm]/', $age_string)) {
- return I18N::plural('%s year', '%s years', $match[1], I18N::digits($match[1]));
- } else {
- return I18N::digits($match[1]);
- }
- case 'm':
- return I18N::plural('%s month', '%s months', $match[1], I18N::digits($match[1]));
- case 'w':
- return I18N::plural('%s week', '%s weeks', $match[1], I18N::digits($match[1]));
- case 'd':
- return I18N::plural('%s day', '%s days', $match[1], I18N::digits($match[1]));
- }
- },
- $age_string
- );
- }
-}
-
-/**
- * Convert a unix timestamp into a formatted date-time value, for logs, etc.
- * Don't attempt to convert into other calendars, as not all days start at
- * midnight, and we can only get it wrong.
- *
- * @param int $time
- *
- * @return string
- */
-function format_timestamp($time) {
- $time_fmt = I18N::timeFormat();
- // PHP::date() doesn't do I18N. Do it ourselves....
- preg_match_all('/%[^%]/', $time_fmt, $matches);
- foreach ($matches[0] as $match) {
- switch ($match) {
- case '%a':
- $t = gmdate('His', $time);
- if ($t == '000000') {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - exactly 00:00:00 */ I18N::translate('midnight'), $time_fmt);
- } elseif ($t < '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - between 00:00:01 and 11:59:59 */ I18N::translate('a.m.'), $time_fmt);
- } elseif ($t == '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - exactly 12:00:00 */ I18N::translate('noon'), $time_fmt);
- } else {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - between 12:00:01 and 23:59:59 */ I18N::translate('p.m.'), $time_fmt);
- }
- break;
- case '%A':
- $t = gmdate('His', $time);
- if ($t == '000000') {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - exactly 00:00:00 */ I18N::translate('Midnight'), $time_fmt);
- } elseif ($t < '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - between 00:00:01 and 11:59:59 */ I18N::translate('A.M.'), $time_fmt);
- } elseif ($t == '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - exactly 12:00:00 */ I18N::translate('Noon'), $time_fmt);
- } else {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - between 12:00:01 and 23:59:59 */ I18N::translate('P.M.'), $time_fmt);
- }
- break;
- default:
- $time_fmt = str_replace($match, I18N::digits(gmdate(substr($match, -1), $time)), $time_fmt);
- }
- }
-
- return timestamp_to_gedcom_date($time)->display() . '<span class="date"> - ' . $time_fmt . '</span>';
-}
-
-/**
- * Convert a unix-style timestamp into a Date object
- *
- * @param int $time
- *
- * @return Date
- */
-function timestamp_to_gedcom_date($time) {
- return new Date(strtoupper(gmdate('j M Y', $time)));
-}
diff --git a/includes/functions/functions_db.php b/includes/functions/functions_db.php
deleted file mode 100644
index d387a081cf..0000000000
--- a/includes/functions/functions_db.php
+++ /dev/null
@@ -1,1079 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Date\FrenchDate;
-use Fisharebest\Webtrees\Date\GregorianDate;
-use Fisharebest\Webtrees\Date\HijriDate;
-use Fisharebest\Webtrees\Date\JalaliDate;
-use Fisharebest\Webtrees\Date\JewishDate;
-use Fisharebest\Webtrees\Date\JulianDate;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Soundex;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Tree;
-
-/**
- * Fetch all records linked to a record - when deleting an object, we must
- * also delete all links to it.
- *
- * @param string $xref
- * @param int $gedcom_id
- *
- * @return string[]
- */
-function fetch_all_links($xref, $gedcom_id) {
- return
- Database::prepare(
- "SELECT l_from FROM `##link` WHERE l_file = ? AND l_to = ?" .
- " UNION " .
- "SELECT xref FROM `##change` WHERE status = 'pending' AND gedcom_id = ? AND new_gedcom LIKE" .
- " CONCAT('%@', ?, '@%')"
- )->execute(array(
- $gedcom_id,
- $xref,
- $gedcom_id,
- $xref,
- ))->fetchOneColumn();
-}
-
-/**
- * Get a list of all the sources.
- *
- * @param Tree $tree
- *
- * @return Source[] array
- */
-function get_source_list(Tree $tree) {
- $rows = Database::prepare(
- "SELECT s_id AS xref, s_gedcom AS gedcom FROM `##sources` WHERE s_file = :tree_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- $list = array();
- foreach ($rows as $row) {
- $list[] = Source::getInstance($row->xref, $tree, $row->gedcom);
- }
- usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
-
- return $list;
-}
-
-/**
- * Get a list of all the repositories.
- *
- * @param Tree $tree
- *
- * @return Repository[] array
- */
-function get_repo_list(Tree $tree) {
- $rows = Database::prepare(
- "SELECT o_id AS xref, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO' AND o_file = ?"
- )->execute(array(
- $tree->getTreeId(),
- ))->fetchAll();
-
- $list = array();
- foreach ($rows as $row) {
- $list[] = Repository::getInstance($row->xref, $tree, $row->gedcom);
- }
- usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
-
- return $list;
-}
-
-/**
- * Get a list of all the shared notes.
- *
- * @param Tree $tree
- *
- * @return Note[] array
- */
-function get_note_list(Tree $tree) {
- $rows = Database::prepare(
- "SELECT o_id AS xref, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'NOTE' AND o_file = :tree_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- $list = array();
- foreach ($rows as $row) {
- $list[] = Note::getInstance($row->xref, $tree, $row->gedcom);
- }
- usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
-
- return $list;
-}
-
-/**
- * Search all individuals
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Individual[]
- */
-function search_indis(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom FROM `##individuals` WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND i_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND i_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the names of individuals
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Individual[]
- */
-function search_indis_names(array $query, array $trees) {
- $sql = "SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom, n_full FROM `##individuals` JOIN `##name` ON i_id=n_id AND i_file=n_file WHERE 1";
- $args = array();
-
- // Convert the query into a SQL expression
- foreach ($query as $n => $q) {
- $sql .= " AND n_full COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND i_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- $indi = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // The individual may have private names - and the DB search may have found it.
- if ($indi->canShowName()) {
- foreach ($indi->getAllNames() as $num => $name) {
- if ($name['fullNN'] === $row->n_full) {
- $indi->setPrimaryName($num);
- // We need to clone $indi, as we may have multiple references to the
- // same person in this list, and the "primary name" would otherwise
- // be shared amongst all of them.
- $list[] = clone $indi;
- // Only need to match an individual on one name
- break;
- }
- }
- }
- }
-
- return $list;
-}
-
-/**
- * Search for individuals names/places using soundex
- *
- * @param string $soundex
- * @param string $lastname
- * @param string $firstname
- * @param string $place
- * @param Tree[] $trees
- *
- * @return Individual[]
- */
-function search_indis_soundex($soundex, $lastname, $firstname, $place, array $trees) {
- switch ($soundex) {
- case 'Russell':
- $givn_sdx = Soundex::russell($firstname);
- $surn_sdx = Soundex::russell($lastname);
- $plac_sdx = Soundex::russell($place);
- break;
- case 'DaitchM':
- $givn_sdx = Soundex::daitchMokotoff($firstname);
- $surn_sdx = Soundex::daitchMokotoff($lastname);
- $plac_sdx = Soundex::daitchMokotoff($place);
- break;
- default:
- throw new \DomainException('soundex: ' . $soundex);
- }
-
- // Nothing to search for? Return nothing.
- if (!$givn_sdx && !$surn_sdx && !$plac_sdx) {
- return array();
- }
-
- $sql = "SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom FROM `##individuals`";
- $args = array();
-
- if ($place) {
- $sql .= " JOIN `##placelinks` ON pl_file = i_file AND pl_gid = i_id";
- $sql .= " JOIN `##places` ON p_file = pl_file AND pl_p_id = p_id";
- }
- if ($firstname || $lastname) {
- $sql .= " JOIN `##name` ON i_file=n_file AND i_id=n_id";
- }
- $sql .= " AND i_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- if ($firstname && $givn_sdx) {
- $sql .= " AND (";
- $givn_sdx = explode(':', $givn_sdx);
- foreach ($givn_sdx as $n => $sdx) {
- $sql .= $n ? " OR " : "";
- switch ($soundex) {
- case 'Russell':
- $sql .= "n_soundex_givn_std LIKE CONCAT('%', :given_name_" . $n . ", '%')";
- break;
- case 'DaitchM':
- $sql .= "n_soundex_givn_dm LIKE CONCAT('%', :given_name_" . $n . ", '%')";
- break;
- }
- $args['given_name_' . $n] = $sdx;
- }
- $sql .= ")";
- }
-
- if ($lastname && $surn_sdx) {
- $sql .= " AND (";
- $surn_sdx = explode(':', $surn_sdx);
- foreach ($surn_sdx as $n => $sdx) {
- $sql .= $n ? " OR " : "";
- switch ($soundex) {
- case 'Russell':
- $sql .= "n_soundex_surn_std LIKE CONCAT('%', :surname_" . $n . ", '%')";
- break;
- case 'DaitchM':
- $sql .= "n_soundex_surn_dm LIKE CONCAT('%', :surname_" . $n . ", '%')";
- break;
- }
- $args['surname_' . $n] = $sdx;
- }
- $sql .= ")";
- }
-
- if ($place && $plac_sdx) {
- $sql .= " AND (";
- $plac_sdx = explode(':', $plac_sdx);
- foreach ($plac_sdx as $n => $sdx) {
- $sql .= $n ? " OR " : "";
- switch ($soundex) {
- case 'Russell':
- $sql .= "p_std_soundex LIKE CONCAT('%', :place_" . $n . ", '%')";
- break;
- case 'DaitchM':
- $sql .= "p_dm_soundex LIKE CONCAT('%', :place_" . $n . ", '%')";
- break;
- }
- $args['place_' . $n] = $sdx;
- }
- $sql .= ")";
- }
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- $indi = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- if ($indi->canShowName()) {
- $list[] = $indi;
- }
- }
-
- return $list;
-}
-
-/**
- * get recent changes since the given julian day inclusive
- *
- * @param int $jd leave empty to include all
- * @param bool $allgeds
- *
- * @return string[] List of XREFs of records with changes
- */
-function get_recent_changes($jd = 0, $allgeds = false) {
- global $WT_TREE;
-
- $sql = "SELECT d_gid FROM `##dates` WHERE d_fact='CHAN' AND d_julianday1>=?";
- $vars = array($jd);
- if (!$allgeds) {
- $sql .= " AND d_file=?";
- $vars[] = $WT_TREE->getTreeId();
- }
- $sql .= " ORDER BY d_julianday1 DESC";
-
- return Database::prepare($sql)->execute($vars)->fetchOneColumn();
-}
-
-/**
- * Search family records
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Family[]
- */
-function search_fams(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom FROM `##families` WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND f_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND f_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the names of the husb/wife in a family
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Family[]
- */
-function search_fams_names(array $query, array $trees) {
- // No query => no results
- if (!$query) {
- return array();
- }
-
- $sql =
- "SELECT DISTINCT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom" .
- " FROM `##families`" .
- " LEFT JOIN `##name` husb ON f_husb = husb.n_id AND f_file = husb.n_file" .
- " LEFT JOIN `##name` wife ON f_wife = wife.n_id AND f_file = wife.n_file" .
- " WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $sql .= " AND (husb.n_full COLLATE :husb_collate_" . $n . " LIKE CONCAT('%', :husb_query_" . $n . ", '%') OR wife.n_full COLLATE :wife_collate_" . $n . " LIKE CONCAT('%', :wife_query_" . $n . ", '%'))";
- $args['husb_collate_' . $n] = I18N::collation();
- $args['husb_query_' . $n] = Filter::escapeLike($q);
- $args['wife_collate_' . $n] = I18N::collation();
- $args['wife_query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND f_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- $indi = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- if ($indi->canShowName()) {
- $list[] = $indi;
- }
- }
-
- return $list;
-}
-
-/**
- * Search the sources
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The tree to search
- *
- * @return Source[]
- */
-function search_sources($query, $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom FROM `##sources` WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND s_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND s_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Source::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the shared notes
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The tree to search
- *
- * @return Note[]
- */
-function search_notes(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'NOTE'";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND o_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Note::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the repositories
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Repository[]
- */
-function search_repos(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO'";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND o_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Repository::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Find the record for the given rin.
- *
- * @param string $rin
- *
- * @return string
- */
-function find_rin_id($rin) {
- global $WT_TREE;
-
- $xref =
- Database::prepare("SELECT i_id FROM `##individuals` WHERE i_rin=? AND i_file=?")
- ->execute(array($rin, $WT_TREE->getTreeId()))
- ->fetchOne();
-
- return $xref ? $xref : $rin;
-}
-
-/**
- * Get array of common surnames
- *
- * This function returns a simple array of the most common surnames
- * found in the individuals list.
- *
- * @param int $min The number of times a surname must occur before it is added to the array
- * @param Tree $tree
- *
- * @return mixed[][]
- */
-function get_common_surnames($min, Tree $tree) {
- $COMMON_NAMES_ADD = $tree->getPreference('COMMON_NAMES_ADD');
- $COMMON_NAMES_REMOVE = $tree->getPreference('COMMON_NAMES_REMOVE');
-
- $topsurns = get_top_surnames($tree->getTreeId(), $min, 0);
- foreach (explode(',', $COMMON_NAMES_ADD) as $surname) {
- if ($surname && !array_key_exists($surname, $topsurns)) {
- $topsurns[$surname] = $min;
- }
- }
- foreach (explode(',', $COMMON_NAMES_REMOVE) as $surname) {
- unset($topsurns[I18N::strtoupper($surname)]);
- }
-
- //-- check if we found some, else recurse
- if (empty($topsurns) && $min > 2) {
- return get_common_surnames($min / 2, $tree);
- } else {
- uksort($topsurns, '\Fisharebest\Webtrees\I18N::strcasecmp');
- foreach ($topsurns as $key => $value) {
- $topsurns[$key] = array('name' => $key, 'match' => $value);
- }
-
- return $topsurns;
- }
-}
-
-/**
- * get the top surnames
- *
- * @param int $ged_id fetch surnames from this gedcom
- * @param int $min only fetch surnames occuring this many times
- * @param int $max only fetch this number of surnames (0=all)
- *
- * @return string[]
- */
-function get_top_surnames($ged_id, $min, $max) {
- // Use n_surn, rather than n_surname, as it is used to generate URLs for
- // the indi-list, etc.
- $max = (int) $max;
- if ($max == 0) {
- return
- Database::prepare(
- "SELECT SQL_CACHE n_surn, COUNT(n_surn) FROM `##name`" .
- " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '', '?', 'UNKNOWN')" .
- " GROUP BY n_surn HAVING COUNT(n_surn) >= :min" .
- " ORDER BY 2 DESC"
- )->execute(array(
- 'tree_id' => $ged_id,
- 'min' => $min,
- ))->fetchAssoc();
- } else {
- return
- Database::prepare(
- "SELECT SQL_CACHE n_surn, COUNT(n_surn) FROM `##name`" .
- " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '', '?', 'UNKNOWN')" .
- " GROUP BY n_surn HAVING COUNT(n_surn) >= :min" .
- " ORDER BY 2 DESC" .
- " LIMIT :limit"
- )->execute(array(
- 'tree_id' => $ged_id,
- 'min' => $min,
- 'limit' => $max,
- ))->fetchAssoc();
- }
-}
-
-/**
- * Get a list of events whose anniversary occured on a given julian day.
- * Used on the on-this-day/upcoming blocks and the day/month calendar views.
- *
- * @param int $jd the julian day
- * @param string $facts restrict the search to just these facts or leave blank for all
- * @param Tree $tree the tree to search
- *
- * @return Fact[]
- */
-function get_anniversary_events($jd, $facts, Tree $tree) {
- $found_facts = array();
- foreach (array(
- new GregorianDate($jd),
- new JulianDate($jd),
- new FrenchDate($jd),
- new JewishDate($jd),
- new HijriDate($jd),
- new JalaliDate($jd),
- ) as $anniv) {
- // Build a SQL where clause to match anniversaries in the appropriate calendar.
- $ind_sql =
- "SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact" .
- " FROM `##dates` JOIN `##individuals` ON d_gid = i_id AND d_file = i_file" .
- " WHERE d_type = :type AND d_file = :tree_id";
- $fam_sql =
- "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact" .
- " FROM `##dates` JOIN `##families` ON d_gid = f_id AND d_file = f_file" .
- " WHERE d_type = :type AND d_file = :tree_id";
- $args = array(
- 'type' => $anniv->format('%@'),
- 'tree_id' => $tree->getTreeId(),
- );
-
- $where = "";
- // SIMPLE CASES:
- // a) Non-hebrew anniversaries
- // b) Hebrew months TVT, SHV, IYR, SVN, TMZ, AAV, ELL
- if (!$anniv instanceof JewishDate || in_array($anniv->m, array(1, 5, 6, 9, 10, 11, 12, 13))) {
- // Dates without days go on the first day of the month
- // Dates with invalid days go on the last day of the month
- if ($anniv->d === 1) {
- $where .= " AND d_day <= 1";
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day";
- $args['day'] = $anniv->d;
- }
- $where .= " AND d_mon = :month";
- $args['month'] = $anniv->m;
- } else {
- // SPECIAL CASES:
- switch ($anniv->m) {
- case 2:
- // 29 CSH does not include 30 CSH (but would include an invalid 31 CSH if there were no 30 CSH)
- if ($anniv->d === 1) {
- $where .= " AND d_day <= 1 AND d_mon = 2";
- } elseif ($anniv->d === 30) {
- $where .= " AND d_day >= 30 AND d_mon = 2";
- } elseif ($anniv->d === 29 && $anniv->daysInMonth() === 29) {
- $where .= " AND (d_day = 29 OR d_day > 30) AND d_mon = 2";
- } else {
- $where .= " AND d_day = :day AND d_mon = 2";
- $args['day'] = $anniv->d;
- }
- break;
- case 3:
- // 1 KSL includes 30 CSH (if this year didn’t have 30 CSH)
- // 29 KSL does not include 30 KSL (but would include an invalid 31 KSL if there were no 30 KSL)
- if ($anniv->d === 1) {
- $tmp = new JewishDate(array($anniv->y, 'CSH', 1));
- if ($tmp->daysInMonth() === 29) {
- $where .= " AND (d_day <= 1 AND d_mon = 3 OR d_day = 30 AND d_mon = 2)";
- } else {
- $where .= " AND d_day <= 1 AND d_mon = 3";
- }
- } elseif ($anniv->d === 30) {
- $where .= " AND d_day >= 30 AND d_mon = 3";
- } elseif ($anniv->d == 29 && $anniv->daysInMonth() === 29) {
- $where .= " AND (d_day = 29 OR d_day > 30) AND d_mon = 3";
- } else {
- $where .= " AND d_day = :day AND d_mon = 3";
- $args['day'] = $anniv->d;
- }
- break;
- case 4:
- // 1 TVT includes 30 KSL (if this year didn’t have 30 KSL)
- if ($anniv->d === 1) {
- $tmp = new JewishDate(array($anniv->y, 'KSL', 1));
- if ($tmp->daysInMonth() === 29) {
- $where .= " AND (d_day <=1 AND d_mon = 4 OR d_day = 30 AND d_mon = 3)";
- } else {
- $where .= " AND d_day <= 1 AND d_mon = 4";
- }
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day AND d_mon=4";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day AND d_mon=4";
- $args['day'] = $anniv->d;
- }
- break;
- case 7: // ADS includes ADR (non-leap)
- if ($anniv->d === 1) {
- $where .= " AND d_day <= 1";
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day";
- $args['day'] = $anniv->d;
- }
- $where .= " AND (d_mon = 6 AND MOD(7 * d_year + 1, 19) >= 7 OR d_mon = 7)";
- break;
- case 8: // 1 NSN includes 30 ADR, if this year is non-leap
- if ($anniv->d === 1) {
- if ($anniv->isLeapYear()) {
- $where .= " AND d_day <= 1 AND d_mon = 8";
- } else {
- $where .= " AND (d_day <= 1 AND d_mon = 8 OR d_day = 30 AND d_mon = 6)";
- }
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day AND d_mon = 8";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day AND d_mon = 8";
- $args['day'] = $anniv->d;
- }
- break;
- }
- }
- // Only events in the past (includes dates without a year)
- $where .= " AND d_year <= :year";
- $args['year'] = $anniv->y;
-
- if ($facts) {
- // Restrict to certain types of fact
- $where .= " AND d_fact IN (";
- preg_match_all('/([_A-Z]+)/', $facts, $matches);
- foreach ($matches[1] as $n => $fact) {
- $where .= $n ? ", " : "";
- $where .= ":fact_" . $n;
- $args['fact_' . $n] = $fact;
- }
- $where .= ")";
- } else {
- // If no facts specified, get all except these
- $where .= " AND d_fact NOT IN ('CHAN', 'BAPL', 'SLGC', 'SLGS', 'ENDL', 'CENS', 'RESI', '_TODO')";
- }
-
- $order_by = " ORDER BY d_day, d_year DESC";
-
- // Now fetch these anniversaries
- foreach (array('INDI' => $ind_sql . $where . $order_by, 'FAM' => $fam_sql . $where . $order_by) as $type => $sql) {
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- if ($type === 'INDI') {
- $record = Individual::getInstance($row->xref, $tree, $row->gedcom);
- } else {
- $record = Family::getInstance($row->xref, $tree, $row->gedcom);
- }
- $anniv_date = new Date($row->d_type . ' ' . $row->d_day . ' ' . $row->d_month . ' ' . $row->d_year);
- foreach ($record->getFacts() as $fact) {
- if (($fact->getDate()->minimumDate() == $anniv_date->minimumDate() || $fact->getDate()->maximumDate() == $anniv_date->minimumDate()) && $fact->getTag() === $row->d_fact) {
- $fact->anniv = $row->d_year === 0 ? 0 : $anniv->y - $row->d_year;
- $found_facts[] = $fact;
- }
- }
- }
- }
- }
-
- return $found_facts;
-}
-
-/**
- * Get a list of events which occured during a given date range.
- *
- * @param int $jd1 the start range of julian day
- * @param int $jd2 the end range of julian day
- * @param string $facts restrict the search to just these facts or leave blank for all
- * @param Tree $tree the tree to search
- *
- * @return Fact[]
- */
-function get_calendar_events($jd1, $jd2, $facts, Tree $tree) {
- // If no facts specified, get all except these
- $skipfacts = "CHAN,BAPL,SLGC,SLGS,ENDL,CENS,RESI,NOTE,ADDR,OBJE,SOUR,PAGE,DATA,TEXT";
- if ($facts != '_TODO') {
- $skipfacts .= ',_TODO';
- }
-
- $found_facts = array();
-
- // Events that start or end during the period
- $where = "WHERE (d_julianday1>={$jd1} AND d_julianday1<={$jd2} OR d_julianday2>={$jd1} AND d_julianday2<={$jd2})";
-
- // Restrict to certain types of fact
- if (empty($facts)) {
- $excl_facts = "'" . preg_replace('/\W+/', "','", $skipfacts) . "'";
- $where .= " AND d_fact NOT IN ({$excl_facts})";
- } else {
- $incl_facts = "'" . preg_replace('/\W+/', "','", $facts) . "'";
- $where .= " AND d_fact IN ({$incl_facts})";
- }
- // Only get events from the current gedcom
- $where .= " AND d_file=" . $tree->getTreeId();
-
- // Now fetch these events
- $ind_sql = "SELECT d_gid AS xref, i_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact, d_type FROM `##dates`, `##individuals` {$where} AND d_gid=i_id AND d_file=i_file GROUP BY d_julianday1, d_gid ORDER BY d_julianday1";
- $fam_sql = "SELECT d_gid AS xref, f_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact, d_type FROM `##dates`, `##families` {$where} AND d_gid=f_id AND d_file=f_file GROUP BY d_julianday1, d_gid ORDER BY d_julianday1";
- foreach (array('INDI' => $ind_sql, 'FAM' => $fam_sql) as $type => $sql) {
- $rows = Database::prepare($sql)->fetchAll();
- foreach ($rows as $row) {
- if ($type === 'INDI') {
- $record = Individual::getInstance($row->xref, $tree, $row->gedcom);
- } else {
- $record = Family::getInstance($row->xref, $tree, $row->gedcom);
- }
- $anniv_date = new Date($row->d_type . ' ' . $row->d_day . ' ' . $row->d_month . ' ' . $row->d_year);
- foreach ($record->getFacts() as $fact) {
- if (($fact->getDate()->minimumDate() == $anniv_date->minimumDate() || $fact->getDate()->maximumDate() == $anniv_date->minimumDate()) && $fact->getTag() === $row->d_fact) {
- $fact->anniv = 0;
- $found_facts[] = $fact;
- }
- }
- }
- }
-
- return $found_facts;
-}
-
-/**
- * Get the list of current and upcoming events, sorted by anniversary date
- *
- * @param int $jd1
- * @param int $jd2
- * @param string $events
- * @param Tree $tree
- *
- * @return Fact[]
- */
-function get_events_list($jd1, $jd2, $events, Tree $tree) {
- $found_facts = array();
- for ($jd = $jd1; $jd <= $jd2; ++$jd) {
- $found_facts = array_merge($found_facts, get_anniversary_events($jd, $events, $tree));
- }
-
- return $found_facts;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Check if a media file is shared (i.e. used by another gedcom)
- *
- * @param string $file_name
- * @param int $ged_id
- *
- * @return bool
- */
-function is_media_used_in_other_gedcom($file_name, $ged_id) {
- return
- (bool) Database::prepare("SELECT COUNT(*) FROM `##media` WHERE m_filename LIKE ? AND m_file<>?")
- ->execute(array("%{$file_name}", $ged_id))
- ->fetchOne();
-}
-
-/**
- * @param int $user_id
- *
- * @return string[][]
- */
-function get_user_blocks($user_id) {
- global $WT_TREE;
-
- $blocks = array('main' => array(), 'side' => array());
- $rows = Database::prepare(
- "SELECT SQL_CACHE location, block_id, module_name" .
- " FROM `##block`" .
- " JOIN `##module` USING (module_name)" .
- " JOIN `##module_privacy` USING (module_name)" .
- " WHERE user_id=?" .
- " AND status='enabled'" .
- " AND `##module_privacy`.gedcom_id=?" .
- " AND access_level>=?" .
- " ORDER BY location, block_order"
- )->execute(array($user_id, $WT_TREE->getTreeId(), Auth::accessLevel($WT_TREE)))->fetchAll();
- foreach ($rows as $row) {
- $blocks[$row->location][$row->block_id] = $row->module_name;
- }
-
- return $blocks;
-}
-
-/**
- * Get the blocks for the specified tree
- *
- * @param int $gedcom_id
- *
- * @return string[][]
- */
-function get_gedcom_blocks($gedcom_id) {
- if ($gedcom_id < 0) {
- $access_level = Auth::PRIV_NONE;
- } else {
- $access_level = Auth::accessLevel(Tree::findById($gedcom_id));
- }
-
- $blocks = array('main' => array(), 'side' => array());
- $rows = Database::prepare(
- "SELECT SQL_CACHE location, block_id, module_name" .
- " FROM `##block`" .
- " JOIN `##module` USING (module_name)" .
- " JOIN `##module_privacy` USING (module_name, gedcom_id)" .
- " WHERE gedcom_id = :tree_id" .
- " AND status='enabled'" .
- " AND access_level >= :access_level" .
- " ORDER BY location, block_order"
- )->execute(array(
- 'tree_id' => $gedcom_id,
- 'access_level' => $access_level,
- ))->fetchAll();
- foreach ($rows as $row) {
- $blocks[$row->location][$row->block_id] = $row->module_name;
- }
-
- return $blocks;
-}
-
-/**
- * Update favorites after merging records.
- *
- * @param string $xref_from
- * @param string $xref_to
- * @param Tree $tree
- *
- * @return int
- */
-function update_favorites($xref_from, $xref_to, Tree $tree) {
- return
- Database::prepare("UPDATE `##favorite` SET xref=? WHERE xref=? AND gedcom_id=?")
- ->execute(array($xref_to, $xref_from, $tree->getTreeId()))
- ->rowCount();
-}
diff --git a/includes/functions/functions_edit.php b/includes/functions/functions_edit.php
deleted file mode 100644
index cef7c08aa0..0000000000
--- a/includes/functions/functions_edit.php
+++ /dev/null
@@ -1,1706 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\ConfigData;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\CensusAssistantModule;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * Create a <select> control for a form.
- *
- * @param string $name
- * @param string[] $values
- * @param string|null $empty
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function select_edit_control($name, $values, $empty, $selected, $extra = '') {
- if (is_null($empty)) {
- $html = '';
- } else {
- if (empty($selected)) {
- $html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>';
- } else {
- $html = '<option value="">' . Filter::escapeHtml($empty) . '</option>';
- }
- }
- // A completely empty list would be invalid, and break various things
- if (empty($values) && empty($html)) {
- $html = '<option value=""></option>';
- }
- foreach ($values as $key => $value) {
- // PHP array keys are cast to integers! Cast them back
- if ((string) $key === (string) $selected) {
- $html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>';
- } else {
- $html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>';
- }
- }
- if (substr($name, -2) === '[]') {
- // id attribute is not used for arrays
- return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>';
- } else {
- return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>';
- }
-}
-
-/**
- * Create a set of radio buttons for a form
- *
- * @param string $name The ID for the form element
- * @param string[] $values Array of value=>display items
- * @param string $selected The currently selected item
- * @param string $extra Additional markup for the label
- *
- * @return string
- */
-function radio_buttons($name, $values, $selected, $extra = '') {
- $html = '';
- foreach ($values as $key => $value) {
- $html .=
- '<label ' . $extra . '>' .
- '<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"';
- // PHP array keys are cast to integers! Cast them back
- if ((string) $key === (string) $selected) {
- $html .= ' checked';
- }
- $html .= '>' . Filter::escapeHtml($value) . '</label>';
- }
-
- return $html;
-}
-
-/**
- * Print an edit control for a Yes/No field
- *
- * @param string $name
- * @param bool $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_yes_no($name, $selected = false, $extra = '') {
- return radio_buttons(
- $name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
- );
-}
-
-/**
- * Print an edit control for a checkbox.
- *
- * @param string $name
- * @param bool $is_checked
- * @param string $extra
- *
- * @return string
- */
-function checkbox($name, $is_checked = false, $extra = '') {
- return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
-}
-
-/**
- * Print an edit control for a checkbox, with a hidden field to store one of the two states.
- * By default, a checkbox is either set, or not sent.
- * This function gives us a three options, set, unset or not sent.
- * Useful for dynamically generated forms where we don't know what elements are present.
- *
- * @param string $name
- * @param int $is_checked 0 or 1
- * @param string $extra
- *
- * @return string
- */
-function two_state_checkbox($name, $is_checked = 0, $extra = '') {
- return
- '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
- '<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
- ($is_checked ? ' checked' : '') .
- ' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>';
-}
-
-/**
- * Function edit_language_checkboxes
- *
- * @param string $parameter_name
- * @param array $accepted_languages
- *
- * @return string
- */
-function edit_language_checkboxes($parameter_name, $accepted_languages) {
- $html = '';
- foreach (I18N::activeLocales() as $locale) {
- $html .= '<div class="checkbox">';
- $html .= '<label title="' . $locale->languageTag() . '">';
- $html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"';
- $html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>';
- $html .= $locale->endonym();
- $html .= '</label>';
- $html .= '</div>';
- }
-
- return $html;
-}
-
-/**
- * Print an edit control for access level.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_access_level($name, $selected = '', $extra = '') {
- $ACCESS_LEVEL = array(
- Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
- Auth::PRIV_USER => I18N::translate('Show to members'),
- Auth::PRIV_NONE => I18N::translate('Show to managers'),
- Auth::PRIV_HIDE => I18N::translate('Hide from everyone'),
- );
-
- return select_edit_control($name, $ACCESS_LEVEL, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a RESN field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_resn($name, $selected = '', $extra = '') {
- $RESN = array(
- '' => '',
- 'none' => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
- 'privacy' => I18N::translate('Show to members'),
- 'confidential' => I18N::translate('Show to managers'),
- 'locked' => I18N::translate('Only managers can edit'),
- );
-
- return select_edit_control($name, $RESN, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a contact method field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_contact($name, $selected = '', $extra = '') {
- // Different ways to contact the users
- $CONTACT_METHODS = array(
- 'messaging' => I18N::translate('Internal messaging'),
- 'messaging2' => I18N::translate('Internal messaging with emails'),
- 'messaging3' => I18N::translate('webtrees sends emails with no storage'),
- 'mailto' => I18N::translate('Mailto link'),
- 'none' => I18N::translate('No contact'),
- );
-
- return select_edit_control($name, $CONTACT_METHODS, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a language field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_language($name, $selected = '', $extra = '') {
- $languages = array();
- foreach (I18N::activeLocales() as $locale) {
- $languages[$locale->languageTag()] = $locale->endonym();
- }
-
- return select_edit_control($name, $languages, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a range of integers.
- *
- * @param string $name
- * @param string $selected
- * @param int $min
- * @param int $max
- * @param string $extra
- *
- * @return string
- */
-function edit_field_integers($name, $selected = '', $min, $max, $extra = '') {
- $array = array();
- for ($i = $min; $i <= $max; ++$i) {
- $array[$i] = I18N::number($i);
- }
-
- return select_edit_control($name, $array, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a username.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_username($name, $selected = '', $extra = '') {
- $all_users = Database::prepare(
- "SELECT user_name, CONCAT_WS(' ', real_name, '-', user_name) FROM `##user` ORDER BY real_name"
- )->fetchAssoc();
- // The currently selected user may not exist
- if ($selected && !array_key_exists($selected, $all_users)) {
- $all_users[$selected] = $selected;
- }
-
- return select_edit_control($name, $all_users, '-', $selected, $extra);
-}
-
-/**
- * Print an edit control for a ADOP field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- * @param Individual $individual
- *
- * @return string
- */
-function edit_field_adop($name, $selected = '', $extra = '', Individual $individual = null) {
- return select_edit_control($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a PEDI field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- * @param Individual $individual
- *
- * @return string
- */
-function edit_field_pedi($name, $selected = '', $extra = '', Individual $individual = null) {
- return select_edit_control($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
-}
-
-/**
- * Print an edit control for a NAME TYPE field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- * @param Individual $individual
- *
- * @return string
- */
-function edit_field_name_type($name, $selected = '', $extra = '', Individual $individual = null) {
- return select_edit_control($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
-}
-
-/**
- * Print an edit control for a RELA field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_rela($name, $selected = '', $extra = '') {
- $rela_codes = GedcomCodeRela::getValues();
- // The user is allowed to specify values that aren't in the list.
- if (!array_key_exists($selected, $rela_codes)) {
- $rela_codes[$selected] = $selected;
- }
-
- return select_edit_control($name, $rela_codes, '', $selected, $extra);
-}
-
-/**
- * Remove all links from $gedrec to $xref, and any sub-tags.
- *
- * @param string $gedrec
- * @param string $xref
- *
- * @return string
- */
-function remove_links($gedrec, $xref) {
- $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec);
-
- return $gedrec;
-}
-
-/**
- * Generates javascript code for calendar popup in user’s language.
- *
- * @param string $id
- *
- * @return string
- */
-function print_calendar_popup($id) {
- return
- ' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
- '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewmedia_link($element_id) {
- return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a new media object') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewrepository_link($element_id) {
- return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a new repository') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewnote_link($element_id) {
- return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a new shared note') . '"></a>';
-}
-
-/**
- * @param string $note_id
- *
- * @return string
- */
-function print_editnote_link($note_id) {
- return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit shared note') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewsource_link($element_id) {
- return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a new source') . '"></a>';
-}
-
-/**
- * add a new tag input field
- *
- * called for each fact to be edited on a form.
- * Fact level=0 means a new empty form : data are POSTed by name
- * else data are POSTed using arrays :
- * glevels[] : tag level
- * islink[] : tag is a link
- * tag[] : tag name
- * text[] : tag value
- *
- * @param string $tag fact record to edit (eg 2 DATE xxxxx)
- * @param string $upperlevel optional upper level tag (eg BIRT)
- * @param string $label An optional label to echo instead of the default
- * @param string $extra optional text to display after the input field
- * @param Individual $person For male/female translations
- *
- * @return string
- */
-function add_simple_tag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) {
- global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
-
- // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
- static $source_element_id;
-
- $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN');
- preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match);
- list(, $level, $fact, $value) = $match;
- $level = (int) $level;
-
- // element name : used to POST data
- if ($level === 0) {
- if ($upperlevel) {
- $element_name = $upperlevel . '_' . $fact;
- } else {
- $element_name = $fact;
- }
- } else {
- $element_name = 'text[]';
- }
- if ($level === 1) {
- $main_fact = $fact;
- }
-
- // element id : used by javascript functions
- if ($level === 0) {
- $element_id = $fact;
- } else {
- $element_id = $fact . Uuid::uuid4();
- }
- if ($upperlevel) {
- $element_id = $upperlevel . '_' . $fact . Uuid::uuid4();
- }
-
- // field value
- $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#');
- if ($islink) {
- $value = trim(substr($tag, strlen($fact) + 3), ' @\r');
- } else {
- $value = (string) substr($tag, strlen($fact) + 3);
- }
- if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') {
- $islink = true;
- }
-
- if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') {
- $islink = true;
- $fact = 'NOTE';
- }
-
- // label
- echo '<tr id="', $element_id, '_tr"';
- if ($fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') {
- echo ' style="display:none;"';
- }
- echo '>';
-
- if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') {
- echo '<td class="optionbox wrap width25">';
- } else {
- echo '<td class="descriptionbox wrap width25">';
- }
-
- // tag name
- if ($label) {
- echo $label;
- } elseif ($upperlevel) {
- echo GedcomTag::getLabel($upperlevel . ':' . $fact);
- } else {
- echo GedcomTag::getLabel($fact);
- }
-
- // If using GEDFact-assistant window
- if ($action === 'addnewnote_assisted') {
- // Do not print on GEDFact Assistant window
- } else {
- // Not all facts have help text.
- switch ($fact) {
- case 'NAME':
- if ($upperlevel !== 'REPO') {
- echo help_link($fact);
- }
- break;
- case 'DATE':
- case 'PLAC':
- case 'RESN':
- case 'ROMN':
- case 'SURN':
- case '_HEB':
- echo help_link($fact);
- break;
- }
- }
- // tag level
- if ($level > 0) {
- if ($fact === 'TEXT' && $level > 1) {
- echo '<input type="hidden" name="glevels[]" value="', $level - 1, '">';
- echo '<input type="hidden" name="islink[]" value="0">';
- echo '<input type="hidden" name="tag[]" value="DATA">';
- // leave data text[] value empty because the following TEXT line will cause the DATA to be added
- echo '<input type="hidden" name="text[]" value="">';
- }
- echo '<input type="hidden" name="glevels[]" value="', $level, '">';
- echo '<input type="hidden" name="islink[]" value="', $islink, '">';
- echo '<input type="hidden" name="tag[]" value="', $fact, '">';
- }
- echo '</td>';
-
- // value
- echo '<td class="optionbox wrap">';
-
- // retrieve linked NOTE
- if ($fact === 'NOTE' && $islink) {
- $note1 = Note::getInstance($value, $WT_TREE);
- if ($note1) {
- $noterec = $note1->getGedcom();
- preg_match('/' . $value . '/i', $noterec, $notematch);
- $value = $notematch[0];
- }
- }
-
- if (in_array($fact, ConfigData::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) {
- echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">';
- if ($level <= 1) {
- echo '<input type="checkbox" ';
- if ($value) {
- echo 'checked';
- }
- echo ' onclick="if (this.checked) ', $element_id, '.value=\'Y\'; else ', $element_id, '.value=\'\';">';
- echo I18N::translate('yes');
- }
-
- } elseif ($fact === 'TEMP') {
- echo select_edit_control($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value);
- } elseif ($fact === 'ADOP') {
- echo edit_field_adop($element_name, $value, '', $person);
- } elseif ($fact === 'PEDI') {
- echo edit_field_pedi($element_name, $value, '', $person);
- } elseif ($fact === 'STAT') {
- echo select_edit_control($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value);
- } elseif ($fact === 'RELA') {
- echo edit_field_rela($element_name, strtolower($value));
- } elseif ($fact === 'QUAY') {
- echo select_edit_control($element_name, GedcomCodeQuay::getValues(), '', $value);
- } elseif ($fact === '_WT_USER') {
- echo edit_field_username($element_name, $value);
- } elseif ($fact === 'RESN') {
- echo edit_field_resn($element_name, $value);
- } elseif ($fact === '_PRIM') {
- echo '<select id="', $element_id, '" name="', $element_name, '" >';
- echo '<option value=""></option>';
- echo '<option value="Y" ';
- if ($value === 'Y') {
- echo ' selected';
- }
- echo '>', /* I18N: option in list box “always use this image” */ I18N::translate('always'), '</option>';
- echo '<option value="N" ';
- if ($value === 'N') {
- echo 'selected';
- }
- echo '>', /* I18N: option in list box “never use this image” */ I18N::translate('never'), '</option>';
- echo '</select>';
- echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>';
- } elseif ($fact === 'SEX') {
- echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" ';
- if ($value === 'M') {
- echo 'selected';
- }
- echo '>', I18N::translate('Male'), '</option><option value="F" ';
- if ($value === 'F') {
- echo 'selected';
- }
- echo '>', I18N::translate('Female'), '</option><option value="U" ';
- if ($value === 'U' || empty($value)) {
- echo 'selected';
- }
- echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>';
- } elseif ($fact === 'TYPE' && $level === 3) {
- //-- Build the selector for the Media 'TYPE' Fact
- echo '<select name="text[]"><option selected value="" ></option>';
- $selectedValue = strtolower($value);
- if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) {
- echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>';
- }
- foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) {
- echo '<option value="', $typeName, '" ';
- if ($selectedValue === $typeName) {
- echo 'selected';
- }
- echo '>', $typeValue, '</option>';
- }
- echo '</select>';
- } elseif (($fact === 'NAME' && $upperlevel !== 'REPO') || $fact === '_MARNM') {
- // Populated in javascript from sub-tags
- echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">';
- echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>';
- echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit name') . '"></a>';
- } else {
- // textarea
- if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) {
- echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>';
- } else {
- // text
- // If using GEDFact-assistant window
- if ($action === 'addnewnote_assisted') {
- echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"';
- } else {
- echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"';
- }
- echo ' class="', $fact, '"';
- if (in_array($fact, $subnamefacts)) {
- echo ' onblur="updatewholename();" onkeyup="updatewholename();"';
- }
-
- // Extra markup for specific fact types
- switch ($fact) {
- case 'ALIA':
- case 'ASSO':
- echo ' data-autocomplete-type="INDI"';
- break;
- case '_ASSO':
- echo ' data-autocomplete-type="ASSO"';
- break;
- case 'DATE':
- echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
- break;
- case 'GIVN':
- echo ' autofocus data-autocomplete-type="GIVN"';
- break;
- case 'LATI':
- echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
- break;
- case 'LONG':
- echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
- break;
- case 'NOTE':
- // Shared notes. Inline notes are handled elsewhere.
- echo ' data-autocomplete-type="NOTE"';
- break;
- case 'OBJE':
- echo ' data-autocomplete-type="OBJE"';
- break;
- case 'PAGE':
- echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="' . $source_element_id . '"';
- break;
- case 'PLAC':
- echo ' data-autocomplete-type="PLAC"';
- break;
- case 'REPO':
- echo ' data-autocomplete-type="REPO"';
- break;
- case 'SOUR':
- $source_element_id = $element_id;
- echo ' data-autocomplete-type="SOUR"';
- break;
- case 'SURN':
- case '_MARNM_SURN':
- echo ' data-autocomplete-type="SURN"';
- break;
- case 'TIME':
- echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5]0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */ I18N::translate('hh:mm or hh:mm:ss') . '"';
- break;
- }
- echo '>';
- }
-
- $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE');
-
- // split PLAC
- if ($fact === 'PLAC') {
- echo '<div id="', $element_id, '_pop" style="display: inline;">';
- echo print_specialchar_link($element_id), ' ', print_findplace_link($element_id);
- echo '<span onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>';
- echo '</div>';
- if (Module::getModuleByName('places_assistant')) {
- \PlacesAssistantModule::setup_place_subfields($element_id);
- \PlacesAssistantModule::print_place_subfields($element_id);
- }
- } elseif (!in_array($fact, $tmp_array)) {
- echo print_specialchar_link($element_id);
- }
- }
- // MARRiage TYPE : hide text field and show a selection list
- if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') {
- echo '<script>';
- echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\'';
- echo '</script>';
- echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >';
- foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) {
- if ($key === 'Unknown') {
- echo '<option value="" ';
- } else {
- echo '<option value="', $key, '" ';
- }
- $a = strtolower($key);
- $b = strtolower($value);
- if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) {
- echo 'selected';
- }
- echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>';
- }
- echo '</select>';
- } elseif ($fact === 'TYPE' && $level === 0) {
- // NAME TYPE : hide text field and show a selection list
- $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"';
- echo edit_field_name_type($element_name, $value, $onchange, $person);
- echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>';
- }
-
- // popup links
- switch ($fact) {
- case 'DATE':
- echo print_calendar_popup($element_id);
-
- // Allow the GEDFact_assistant module to show a census-date selector
- if (Module::getModuleByName('GEDFact_assistant')) {
- echo CensusAssistantModule::censusDateSelector($action, $upperlevel, $element_id);
- }
- break;
- case 'FAMC':
- case 'FAMS':
- echo print_findfamily_link($element_id);
- break;
- case 'ALIA':
- case 'ASSO':
- case '_ASSO':
- echo print_findindi_link($element_id, $element_id . '_description');
- break;
- case 'FILE':
- print_findmedia_link($element_id, '0file');
- break;
- case 'SOUR':
- echo print_findsource_link($element_id, $element_id . '_description'), ' ', print_addnewsource_link($element_id);
- //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
- if ($level === 1) {
- echo '<br>';
- switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
- case '2': // records
- $level1_checked = 'checked';
- $level2_checked = '';
- break;
- case '1': // facts
- $level1_checked = '';
- $level2_checked = 'checked';
- break;
- case '0': // none
- default:
- $level1_checked = '';
- $level2_checked = '';
- break;
- }
- if (strpos($bdm, 'B') !== false) {
- echo '&nbsp;<input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">';
- echo I18N::translate('Individual');
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
- foreach ($matches[1] as $match) {
- if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
- echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">';
- echo GedcomTag::getLabel($match);
- }
- }
- }
- }
- if (strpos($bdm, 'D') !== false) {
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
- foreach ($matches[1] as $match) {
- if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
- echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">';
- echo GedcomTag::getLabel($match);
- }
- }
- }
- }
- if (strpos($bdm, 'M') !== false) {
- echo '&nbsp;<input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">';
- echo I18N::translate('Family');
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
- foreach ($matches[1] as $match) {
- echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">';
- echo GedcomTag::getLabel($match);
- }
- }
- }
- }
- break;
- case 'REPO':
- echo print_findrepository_link($element_id), ' ', print_addnewrepository_link($element_id);
- break;
- case 'NOTE':
- // Shared Notes Icons ========================================
- if ($islink) {
- // Print regular Shared Note icons ---------------------------
- echo ' ', print_findnote_link($element_id, $element_id . '_description'), ' ', print_addnewnote_link($element_id);
- if ($value) {
- echo ' ', print_editnote_link($value);
- }
-
- // Allow the GEDFact_assistant module to create a formatted shared note.
- if ($upperlevel === 'CENS' && Module::getModuleByName('GEDFact_assistant')) {
- echo CensusAssistantModule::addNoteWithAssistantLink($element_id, $xref, $action);
- }
- }
- break;
- case 'OBJE':
- echo print_findmedia_link($element_id, '1media');
- if (!$value) {
- echo ' ', print_addnewmedia_link($element_id);
- $value = 'new';
- }
- break;
- }
-
- echo '<div id="' . $element_id . '_description">';
-
- // current value
- if ($fact === 'DATE') {
- $date = new Date($value);
- echo $date->display();
- }
- if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') {
- if ($level === 1) {
- echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>';
- } else {
- echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>';
- }
- }
-
- if ($value && $value !== 'new' && $islink) {
- switch ($fact) {
- case 'ALIA':
- case 'ASSO':
- case '_ASSO':
- $tmp = Individual::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'SOUR':
- $tmp = Source::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'NOTE':
- $tmp = Note::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'OBJE':
- $tmp = Media::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'REPO':
- $tmp = Repository::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- }
- }
-
- // pastable values
- if ($fact === 'FORM' && $upperlevel === 'OBJE') {
- print_autopaste_link($element_id, ConfigData::fileFormats());
- }
- echo '</div>', $extra, '</td></tr>';
-
- return $element_id;
-}
-
-/**
- * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc.
- *
- * @param string $tag
- * @param int $level
- * @param string $parent_tag
- */
-function print_add_layer($tag, $level = 2, $parent_tag = '') {
- global $WT_TREE;
-
- switch ($tag) {
- case 'SOUR':
- echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a new source citation'), '</a>';
- echo '<br>';
- echo '<div id="newsource" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 SOUR
- add_simple_tag($level . ' SOUR @');
- // 3 PAGE
- add_simple_tag(($level + 1) . ' PAGE');
- // 3 DATA
- // 4 TEXT
- add_simple_tag(($level + 2) . ' TEXT');
- if ($WT_TREE->getPreference('FULL_SOURCES')) {
- // 4 DATE
- add_simple_tag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
- // 3 QUAY
- add_simple_tag(($level + 1) . ' QUAY');
- }
- // 3 OBJE
- add_simple_tag(($level + 1) . ' OBJE');
- // 3 SHARED_NOTE
- add_simple_tag(($level + 1) . ' SHARED_NOTE');
- echo '</table></div>';
- break;
-
- case 'ASSO':
- case 'ASSO2':
- //-- Add a new ASSOciate
- if ($tag === 'ASSO') {
- echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new associate'), '</a>';
- echo '<br>';
- echo '<div id="newasso" style="display: none;">';
- } else {
- echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new associate'), '</a>';
- echo '<br>';
- echo '<div id="newasso2" style="display: none;">';
- }
- echo '<table class="facts_table">';
- // 2 ASSO
- add_simple_tag($level . ' ASSO @');
- // 3 RELA
- add_simple_tag(($level + 1) . ' RELA');
- // 3 NOTE
- add_simple_tag(($level + 1) . ' NOTE');
- // 3 SHARED_NOTE
- add_simple_tag(($level + 1) . ' SHARED_NOTE');
- echo '</table></div>';
- break;
-
- case 'NOTE':
- //-- Retrieve existing note or add new note to fact
- echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new note'), '</a>';
- echo '<br>';
- echo '<div id="newnote" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 NOTE
- add_simple_tag($level . ' NOTE');
- echo '</table></div>';
- break;
-
- case 'SHARED_NOTE':
- echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new shared note'), '</a>';
- echo '<br>';
- echo '<div id="newshared_note" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 SHARED NOTE
- add_simple_tag($level . ' SHARED_NOTE', $parent_tag);
- echo '</table></div>';
- break;
-
- case 'OBJE':
- if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
- echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new media object'), '</a>';
- echo '<br>';
- echo '<div id="newobje" style="display: none;">';
- echo '<table class="facts_table">';
- add_simple_tag($level . ' OBJE');
- echo '</table></div>';
- }
- break;
-
- case 'RESN':
- echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new restriction'), '</a>';
- echo '<br>';
- echo '<div id="newresn" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 RESN
- add_simple_tag($level . ' RESN');
- echo '</table></div>';
- break;
- }
-}
-
-/**
- * Add some empty tags to create a new fact.
- *
- * @param string $fact
- */
-function addSimpleTags($fact) {
- global $WT_TREE;
-
- // For new individuals, these facts default to "Y"
- if ($fact === 'MARR') {
- add_simple_tag('0 ' . $fact . ' Y');
- } else {
- add_simple_tag('0 ' . $fact);
- }
-
- if (!in_array($fact, ConfigData::nonDateFacts())) {
- add_simple_tag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE'));
- }
-
- if (!in_array($fact, ConfigData::nonPlaceFacts())) {
- add_simple_tag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC'));
-
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $tag) {
- add_simple_tag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag));
- }
- }
- add_simple_tag('0 MAP', $fact);
- add_simple_tag('0 LATI', $fact);
- add_simple_tag('0 LONG', $fact);
- }
-}
-
-/**
- * Assemble the pieces of a newly created record into gedcom
- *
- * @return string
- */
-function addNewName() {
- global $WT_TREE;
-
- $gedrec = "\n1 NAME " . Filter::post('NAME');
-
- $tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX');
-
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) {
- $tags = array_merge($tags, $match[1]);
- }
-
- // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
- $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
- if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
- $tags[] = '_MARNM';
- }
-
- foreach (array_unique($tags) as $tag) {
- $TAG = Filter::post($tag);
- if ($TAG) {
- $gedrec .= "\n2 {$tag} {$TAG}";
- }
- }
-
- return $gedrec;
-}
-
-/**
- * @return string
- */
-function addNewSex() {
- switch (Filter::post('SEX', '[MF]', 'U')) {
- case 'M':
- return "\n1 SEX M";
- case 'F':
- return "\n1 SEX F";
- default:
- return "\n1 SEX U";
- }
-}
-
-/**
- * @param string $fact
- *
- * @return string
- */
-function addNewFact($fact) {
- global $WT_TREE;
-
- $FACT = Filter::post($fact);
- $DATE = Filter::post($fact . '_DATE');
- $PLAC = Filter::post($fact . '_PLAC');
- if ($DATE || $PLAC || $FACT && $FACT !== 'Y') {
- if ($FACT && $FACT !== 'Y') {
- $gedrec = "\n1 " . $fact . ' ' . $FACT;
- } else {
- $gedrec = "\n1 " . $fact;
- }
- if ($DATE) {
- $gedrec .= "\n2 DATE " . $DATE;
- }
- if ($PLAC) {
- $gedrec .= "\n2 PLAC " . $PLAC;
-
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $tag) {
- $TAG = Filter::post($fact . '_' . $tag);
- if ($TAG) {
- $gedrec .= "\n3 " . $tag . ' ' . $TAG;
- }
- }
- }
- $LATI = Filter::post($fact . '_LATI');
- $LONG = Filter::post($fact . '_LONG');
- if ($LATI || $LONG) {
- $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
- }
- }
- if (Filter::postBool('SOUR_' . $fact)) {
- return updateSOUR($gedrec, 2);
- } else {
- return $gedrec;
- }
- } elseif ($FACT === 'Y') {
- if (Filter::postBool('SOUR_' . $fact)) {
- return updateSOUR("\n1 " . $fact . ' Y', 2);
- } else {
- return "\n1 " . $fact . ' Y';
- }
- } else {
- return '';
- }
-}
-
-/**
- * This function splits the $glevels, $tag, $islink, and $text arrays so that the
- * entries associated with a SOUR record are separate from everything else.
- *
- * Input arrays:
- * - $glevels[] - an array of the gedcom level for each line that was edited
- * - $tag[] - an array of the tags for each gedcom line that was edited
- * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
- * - $text[] - an array of the text data for each line
- *
- * Output arrays:
- * ** For the SOUR record:
- * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
- * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
- * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
- * - $textSOUR[] - an array of the text data for each line
- * ** For the remaining records:
- * - $glevelsRest[] - an array of the gedcom level for each line that was edited
- * - $tagRest[] - an array of the tags for each gedcom line that was edited
- * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
- * - $textRest[] - an array of the text data for each line
- */
-function splitSOUR() {
- global $glevels, $tag, $islink, $text;
- global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
- global $glevelsRest, $tagRest, $islinkRest, $textRest;
-
- $glevelsSOUR = array();
- $tagSOUR = array();
- $islinkSOUR = array();
- $textSOUR = array();
-
- $glevelsRest = array();
- $tagRest = array();
- $islinkRest = array();
- $textRest = array();
-
- $inSOUR = false;
-
- for ($i = 0; $i < count($glevels); $i++) {
- if ($inSOUR) {
- if ($levelSOUR < $glevels[$i]) {
- $dest = 'S';
- } else {
- $inSOUR = false;
- $dest = 'R';
- }
- } else {
- if ($tag[$i] === 'SOUR') {
- $inSOUR = true;
- $levelSOUR = $glevels[$i];
- $dest = 'S';
- } else {
- $dest = 'R';
- }
- }
- if ($dest === 'S') {
- $glevelsSOUR[] = $glevels[$i];
- $tagSOUR[] = $tag[$i];
- $islinkSOUR[] = $islink[$i];
- $textSOUR[] = $text[$i];
- } else {
- $glevelsRest[] = $glevels[$i];
- $tagRest[] = $tag[$i];
- $islinkRest[] = $islink[$i];
- $textRest[] = $text[$i];
- }
- }
-}
-
-/**
- * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
- * were produced by the splitSOUR() function.
- * See the handle_updates() function for details.
- *
- * @param string $inputRec
- * @param string $levelOverride
- *
- * @return string
- */
-function updateSOUR($inputRec, $levelOverride = 'no') {
- global $glevels, $tag, $islink, $text;
- global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
-
- if (count($tagSOUR) === 0) {
- return $inputRec; // No update required
- }
-
- // Save original interface update arrays before replacing them with the xxxSOUR ones
- $glevelsSave = $glevels;
- $tagSave = $tag;
- $islinkSave = $islink;
- $textSave = $text;
-
- $glevels = $glevelsSOUR;
- $tag = $tagSOUR;
- $islink = $islinkSOUR;
- $text = $textSOUR;
-
- $myRecord = handle_updates($inputRec, $levelOverride); // Now do the update
-
- // Restore the original interface update arrays (just in case ...)
- $glevels = $glevelsSave;
- $tag = $tagSave;
- $islink = $islinkSave;
- $text = $textSave;
-
- return $myRecord;
-}
-
-/**
- * Add new GEDCOM lines from the $xxxRest interface update arrays, which
- * were produced by the splitSOUR() function.
- * See the handle_updates() function for details.
- *
- * @param string $inputRec
- * @param string $levelOverride
- *
- * @return string
- */
-function updateRest($inputRec, $levelOverride = 'no') {
- global $glevels, $tag, $islink, $text;
- global $glevelsRest, $tagRest, $islinkRest, $textRest;
-
- if (count($tagRest) === 0) {
- return $inputRec; // No update required
- }
-
- // Save original interface update arrays before replacing them with the xxxRest ones
- $glevelsSave = $glevels;
- $tagSave = $tag;
- $islinkSave = $islink;
- $textSave = $text;
-
- $glevels = $glevelsRest;
- $tag = $tagRest;
- $islink = $islinkRest;
- $text = $textRest;
-
- $myRecord = handle_updates($inputRec, $levelOverride); // Now do the update
-
- // Restore the original interface update arrays (just in case ...)
- $glevels = $glevelsSave;
- $tag = $tagSave;
- $islink = $islinkSave;
- $text = $textSave;
-
- return $myRecord;
-}
-
-/**
- * Add new gedcom lines from interface update arrays
- * The edit_interface and add_simple_tag function produce the following
- * arrays incoming from the $_POST form
- * - $glevels[] - an array of the gedcom level for each line that was edited
- * - $tag[] - an array of the tags for each gedcom line that was edited
- * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
- * - $text[] - an array of the text data for each line
- * With these arrays you can recreate the gedcom lines like this
- * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
- * There will be an index in each of these arrays for each line of the gedcom
- * fact that is being edited.
- * If the $text[] array is empty for the given line, then it means that the
- * user removed that line during editing or that the line is supposed to be
- * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
- * there is a section of code that looks ahead to the next lines to see if there
- * are sub lines. For example we don't want to remove the 1 DEAT line if it has
- * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
- * can be safely removed.
- *
- * @param string $newged the new gedcom record to add the lines to
- * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
- *
- * @return string The updated gedcom record
- */
-function handle_updates($newged, $levelOverride = 'no') {
- global $glevels, $islink, $tag, $uploaded_files, $text;
-
- if ($levelOverride === 'no' || count($glevels) === 0) {
- $levelAdjust = 0;
- } else {
- $levelAdjust = $levelOverride - $glevels[0];
- }
-
- for ($j = 0; $j < count($glevels); $j++) {
-
- // Look for empty SOUR reference with non-empty sub-records.
- // This can happen when the SOUR entry is deleted but its sub-records
- // were incorrectly left intact.
- // The sub-records should be deleted.
- if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) {
- $text[$j] = '';
- $k = $j + 1;
- while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
- $text[$k] = '';
- $k++;
- }
- }
-
- if (trim($text[$j]) !== '') {
- $pass = true;
- } else {
- //-- for facts with empty values they must have sub records
- //-- this section checks if they have subrecords
- $k = $j + 1;
- $pass = false;
- while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
- if ($text[$k] !== '') {
- if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) {
- $pass = true;
- break;
- }
- }
- if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) {
- $filename = array_shift($uploaded_files);
- if (!empty($filename)) {
- $text[$k] = $filename;
- $pass = true;
- break;
- }
- }
- $k++;
- }
- }
-
- //-- if the value is not empty or it has sub lines
- //--- then write the line to the gedcom record
- //if ((($text[trim($j)]!='')||($pass==true)) && (strlen($text[$j]) > 0)) {
- //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
- if ($pass) {
- $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j];
- //-- check and translate the incoming dates
- if ($tag[$j] === 'DATE' && $text[$j] !== '') {
- }
- if ($text[$j] !== '') {
- if ($islink[$j]) {
- $newline .= ' @' . $text[$j] . '@';
- } else {
- $newline .= ' ' . $text[$j];
- }
- }
- $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline);
- }
- }
-
- return $newged;
-}
-
-/**
- * builds the form for adding new facts
- *
- * @param string $fact the new fact we are adding
- */
-function create_add_form($fact) {
- global $tags, $WT_TREE;
-
- $tags = array();
-
- // handle MARRiage TYPE
- if (substr($fact, 0, 5) === 'MARR_') {
- $tags[0] = 'MARR';
- add_simple_tag('1 MARR');
- insert_missing_subtags($fact);
- } else {
- $tags[0] = $fact;
- if ($fact === '_UID') {
- $fact .= ' ' . GedcomTag::createUid();
- }
- // These new level 1 tags need to be turned into links
- if (in_array($fact, array('ALIA', 'ASSO'))) {
- $fact .= ' @';
- }
- if (in_array($fact, ConfigData::emptyFacts())) {
- add_simple_tag('1 ' . $fact . ' Y');
- } else {
- add_simple_tag('1 ' . $fact);
- }
- insert_missing_subtags($tags[0]);
- //-- handle the special SOURce case for level 1 sources [ 1759246 ]
- if ($fact === 'SOUR') {
- add_simple_tag('2 PAGE');
- add_simple_tag('3 TEXT');
- if ($WT_TREE->getPreference('FULL_SOURCES')) {
- add_simple_tag('3 DATE', '', GedcomTag::getLabel('DATA:DATE'));
- add_simple_tag('2 QUAY');
- }
- }
- }
-}
-
-/**
- * Create a form to edit a Fact object.
- *
- * @param GedcomRecord $record
- * @param Fact $fact
- *
- * @return string
- */
-function create_edit_form(GedcomRecord $record, Fact $fact) {
- global $tags, $WT_TREE;
-
- $pid = $record->getXref();
-
- $tags = array();
- $gedlines = explode("\n", $fact->getGedcom());
-
- $linenum = 0;
- $fields = explode(' ', $gedlines[$linenum]);
- $glevel = $fields[0];
- $level = $glevel;
-
- $type = $fact->getTag();
- $parent = $fact->getParent();
- $level0type = $parent::RECORD_TYPE;
- $level1type = $type;
-
- $i = $linenum;
- $inSource = false;
- $levelSource = 0;
- $add_date = true;
- // List of tags we would expect at the next level
- // NB add_missing_subtags() already takes care of the simple cases
- // where a level 1 tag is missing a level 2 tag. Here we only need to
- // handle the more complicated cases.
- $expected_subtags = array(
- 'SOUR' => array('PAGE', 'DATA'),
- 'DATA' => array('TEXT'),
- 'PLAC' => array('MAP'),
- 'MAP' => array('LATI', 'LONG'),
- );
- if ($record->getTree()->getPreference('FULL_SOURCES')) {
- $expected_subtags['SOUR'][] = 'QUAY';
- $expected_subtags['DATA'][] = 'DATE';
- }
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']);
- }
-
- $stack = array(0 => $level0type);
- // Loop on existing tags :
- while (true) {
- // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE
- $stack[(int) $level] = $type;
- // Merge them together, e.g. BIRT:PLAC:FONE
- $label = implode(':', array_slice($stack, 1, $level));
-
- $text = '';
- for ($j = 2; $j < count($fields); $j++) {
- if ($j > 2) {
- $text .= ' ';
- }
- $text .= $fields[$j];
- }
- $text = rtrim($text);
- while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) {
- $text .= "\n" . $cmatch[1];
- $i++;
- }
-
- if ($type === 'SOUR') {
- $inSource = true;
- $levelSource = $level;
- } elseif ($levelSource >= $level) {
- $inSource = false;
- }
-
- if ($type !== 'DATA' && $type !== 'CONT') {
- $tags[] = $type;
- $person = Individual::getInstance($pid, $WT_TREE);
- $subrecord = $level . ' ' . $type . ' ' . $text;
- if ($inSource && $type === 'DATE') {
- add_simple_tag($subrecord, '', GedcomTag::getLabel($label, $person));
- } elseif (!$inSource && $type === 'DATE') {
- add_simple_tag($subrecord, $level1type, GedcomTag::getLabel($label, $person));
- $add_date = false;
- } elseif ($type === 'STAT') {
- add_simple_tag($subrecord, $level1type, GedcomTag::getLabel($label, $person));
- } elseif ($level0type === 'REPO') {
- $repo = Repository::getInstance($pid, $WT_TREE);
- add_simple_tag($subrecord, $level0type, GedcomTag::getLabel($label, $repo));
- } else {
- add_simple_tag($subrecord, $level0type, GedcomTag::getLabel($label, $person));
- }
- }
-
- // Get a list of tags present at the next level
- $subtags = array();
- for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^\s*(\d+)\s+(\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) {
- if ($mm[1] == $level + 1) {
- $subtags[] = $mm[2];
- }
- }
-
- // Insert missing tags
- if (!empty($expected_subtags[$type])) {
- foreach ($expected_subtags[$type] as $subtag) {
- if (!in_array($subtag, $subtags)) {
- if (!$inSource || $subtag !== 'DATA') {
- add_simple_tag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag));
- }
- if (!empty($expected_subtags[$subtag])) {
- foreach ($expected_subtags[$subtag] as $subsubtag) {
- add_simple_tag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag));
- }
- }
- }
- }
- }
-
- // Awkward special cases
- if ($level == 2 && $type === 'DATE' && in_array($level1type, ConfigData::dateAndTime()) && !in_array('TIME', $subtags)) {
- add_simple_tag('3 TIME'); // TIME is NOT a valid 5.5.1 tag
- }
- if ($level == 2 && $type === 'STAT' && GedcomCodeTemp::isTagLDS($level1type) && !in_array('DATE', $subtags)) {
- add_simple_tag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
- }
-
- $i++;
- if (isset($gedlines[$i])) {
- $fields = explode(' ', $gedlines[$i]);
- $level = $fields[0];
- if (isset($fields[1])) {
- $type = trim($fields[1]);
- } else {
- $level = 0;
- }
- } else {
- $level = 0;
- }
- if ($level <= $glevel) {
- break;
- }
- }
-
- if ($level1type !== '_PRIM') {
- insert_missing_subtags($level1type, $add_date);
- }
-
- return $level1type;
-}
-
-/**
- * Populates the global $tags array with any missing sub-tags.
- *
- * @param string $level1tag the type of the level 1 gedcom record
- * @param bool $add_date
- */
-function insert_missing_subtags($level1tag, $add_date = false) {
- global $tags, $WT_TREE;
-
- // handle MARRiage TYPE
- $type_val = '';
- if (substr($level1tag, 0, 5) === 'MARR_') {
- $type_val = substr($level1tag, 5);
- $level1tag = 'MARR';
- }
-
- foreach (ConfigData::levelTwoTags() as $key => $value) {
- if ($key === 'DATE' && in_array($level1tag, ConfigData::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, ConfigData::nonPlaceFacts())) {
- continue;
- }
- if (in_array($level1tag, $value) && !in_array($key, $tags)) {
- if ($key === 'TYPE') {
- add_simple_tag('2 TYPE ' . $type_val, $level1tag);
- } elseif ($level1tag === '_TODO' && $key === 'DATE') {
- add_simple_tag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag);
- } elseif ($level1tag === '_TODO' && $key === '_WT_USER') {
- add_simple_tag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag);
- } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
- add_simple_tag('2 ' . $key, $level1tag);
- } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
- add_simple_tag('2 ' . $key, $level1tag);
- } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') {
- add_simple_tag('2 ' . $key, $level1tag);
- }
- // Add level 3/4 tags as appropriate
- switch ($key) {
- case 'PLAC':
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $tag) {
- add_simple_tag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
- }
- }
- add_simple_tag('3 MAP');
- add_simple_tag('4 LATI');
- add_simple_tag('4 LONG');
- break;
- case 'FILE':
- add_simple_tag('3 FORM');
- break;
- case 'EVEN':
- add_simple_tag('3 DATE');
- add_simple_tag('3 PLAC');
- break;
- case 'STAT':
- if (GedcomCodeTemp::isTagLDS($level1tag)) {
- add_simple_tag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
- }
- break;
- case 'DATE':
- // TIME is NOT a valid 5.5.1 tag
- if (in_array($level1tag, ConfigData::dateAndTime())) {
- add_simple_tag('3 TIME');
- }
- break;
- case 'HUSB':
- case 'WIFE':
- add_simple_tag('3 AGE');
- break;
- case 'FAMC':
- if ($level1tag === 'ADOP') {
- add_simple_tag('3 ADOP BOTH');
- }
- break;
- }
- } elseif ($key === 'DATE' && $add_date) {
- add_simple_tag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
- }
- }
- // Do something (anything!) with unrecognized custom tags
- if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') {
- foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) {
- if (!in_array($tag, $tags)) {
- add_simple_tag('2 ' . $tag);
- if ($tag === 'PLAC') {
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $ptag) {
- add_simple_tag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag));
- }
- }
- add_simple_tag('3 MAP');
- add_simple_tag('4 LATI');
- add_simple_tag('4 LONG');
- }
- }
- }
- }
-}
diff --git a/includes/functions/functions_export.php b/includes/functions/functions_export.php
deleted file mode 100644
index 5aad3fb595..0000000000
--- a/includes/functions/functions_export.php
+++ /dev/null
@@ -1,308 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Tree;
-
-/**
- * Tidy up a gedcom record on export, for compatibility/portability.
- *
- * @param string $rec
- *
- * @return string
- */
-function reformat_record_export($rec) {
- global $WT_TREE;
-
- $newrec = '';
- foreach (preg_split('/[\r\n]+/', $rec, -1, PREG_SPLIT_NO_EMPTY) as $line) {
- // Split long lines
- // The total length of a GEDCOM line, including level number, cross-reference number,
- // tag, value, delimiters, and terminator, must not exceed 255 (wide) characters.
- if (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH) {
- list($level, $tag) = explode(' ', $line, 3);
- if ($tag != 'CONT' && $tag != 'CONC') {
- $level++;
- }
- do {
- // Split after $pos chars
- $pos = WT_GEDCOM_LINE_LENGTH;
- if ($WT_TREE->getPreference('WORD_WRAPPED_NOTES')) {
- // Split on a space, and remove it (for compatibility with some desktop apps)
- while ($pos && mb_substr($line, $pos - 1, 1) != ' ') {
- --$pos;
- }
- if ($pos == strpos($line, ' ', 3) + 1) {
- // No spaces in the data! Can’t split it :-(
- break;
- } else {
- $newrec .= mb_substr($line, 0, $pos - 1) . WT_EOL;
- $line = $level . ' CONC ' . mb_substr($line, $pos);
- }
- } else {
- // Split on a non-space (standard gedcom behaviour)
- while ($pos && mb_substr($line, $pos - 1, 1) == ' ') {
- --$pos;
- }
- if ($pos == strpos($line, ' ', 3)) {
- // No non-spaces in the data! Can’t split it :-(
- break;
- }
- $newrec .= mb_substr($line, 0, $pos) . WT_EOL;
- $line = $level . ' CONC ' . mb_substr($line, $pos);
- }
- } while (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH);
- }
- $newrec .= $line . WT_EOL;
- }
-
- return $newrec;
-}
-
-/**
- * Create a header for a (newly-created or already-imported) gedcom file.
- *
- * @param Tree $tree
- *
- * @return string
- */
-function gedcom_header(Tree $tree) {
- // Default values for a new header
- $HEAD = "0 HEAD";
- $SOUR = "\n1 SOUR " . WT_WEBTREES . "\n2 NAME " . WT_WEBTREES . "\n2 VERS " . WT_VERSION;
- $DEST = "\n1 DEST DISKETTE";
- $DATE = "\n1 DATE " . strtoupper(date("d M Y")) . "\n2 TIME " . date("H:i:s");
- $GEDC = "\n1 GEDC\n2 VERS 5.5.1\n2 FORM Lineage-Linked";
- $CHAR = "\n1 CHAR UTF-8";
- $FILE = "\n1 FILE " . $tree->getName();
- $LANG = "";
- $PLAC = "\n1 PLAC\n2 FORM City, County, State/Province, Country";
- $COPR = "";
- $SUBN = "";
- $SUBM = "\n1 SUBM @SUBM@\n0 @SUBM@ SUBM\n1 NAME " . Auth::user()->getUserName(); // The SUBM record is mandatory
-
- // Preserve some values from the original header
- $record = GedcomRecord::getInstance('HEAD', $tree);
- if ($fact = $record->getFirstFact('PLAC')) {
- $PLAC = "\n1 PLAC\n2 FORM " . $fact->getAttribute('FORM');
- }
- if ($fact = $record->getFirstFact('LANG')) {
- $LANG = $fact->getValue();
- }
- if ($fact = $record->getFirstFact('SUBN')) {
- $SUBN = $fact->getValue();
- }
- if ($fact = $record->getFirstFact('COPR')) {
- $COPR = $fact->getValue();
- }
- // Link to actual SUBM/SUBN records, if they exist
- $subn =
- Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
- ->execute(array('SUBN', $tree->getTreeId()))
- ->fetchOne();
- if ($subn) {
- $SUBN = "\n1 SUBN @{$subn}@";
- }
- $subm =
- Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
- ->execute(array('SUBM', $tree->getTreeId()))
- ->fetchOne();
- if ($subm) {
- $SUBM = "\n1 SUBM @{$subm}@";
- }
-
- return $HEAD . $SOUR . $DEST . $DATE . $GEDC . $CHAR . $FILE . $COPR . $LANG . $PLAC . $SUBN . $SUBM . "\n";
-}
-
-/**
- * Prepend the GEDCOM_MEDIA_PATH to media filenames.
- *
- * @param string $rec
- * @param string $path
- *
- * @return string
- */
-function convert_media_path($rec, $path) {
- if ($path && preg_match('/\n1 FILE (.+)/', $rec, $match)) {
- $old_file_name = $match[1];
- // Don’t modify external links
- if (!preg_match('~^(https?|ftp):~', $old_file_name)) {
- // Adding a windows path? Convert the slashes.
- if (strpos($path, '\\') !== false) {
- $new_file_name = preg_replace('~/+~', '\\', $old_file_name);
- } else {
- $new_file_name = $old_file_name;
- }
- // Path not present - add it.
- if (strpos($new_file_name, $path) === false) {
- $new_file_name = $path . $new_file_name;
- }
- $rec = str_replace("\n1 FILE " . $old_file_name, "\n1 FILE " . $new_file_name, $rec);
- }
- }
-
- return $rec;
-}
-
-/**
- * Export the database in GEDCOM format
- *
- * @param Tree $tree Which tree to export
- * @param resource $gedout Handle to a writable stream
- * @param string[] $exportOptions Export options are as follows:
- * 'privatize': which Privacy rules apply? (none, visitor, user, manager)
- * 'toANSI': should the output be produced in ISO-8859-1 instead of UTF-8? (yes, no)
- * 'path': what constant should prefix all media file paths? (eg: media/ or c:\my pictures\my family
- * 'slashes': what folder separators apply to media file paths? (forward, backward)
- */
-function export_gedcom(Tree $tree, $gedout, $exportOptions) {
- switch ($exportOptions['privatize']) {
- case 'gedadmin':
- $access_level = Auth::PRIV_NONE;
- break;
- case 'user':
- $access_level = Auth::PRIV_USER;
- break;
- case 'visitor':
- $access_level = Auth::PRIV_PRIVATE;
- break;
- case 'none':
- $access_level = Auth::PRIV_HIDE;
- break;
- }
-
- $head = gedcom_header($tree);
- if ($exportOptions['toANSI'] == 'yes') {
- $head = str_replace('UTF-8', 'ANSI', $head);
- $head = utf8_decode($head);
- }
- $head = reformat_record_export($head);
- fwrite($gedout, $head);
-
- // Buffer the output. Lots of small fwrite() calls can be very slow when writing large gedcoms.
- $buffer = '';
-
- // Generate the OBJE/SOUR/REPO/NOTE records first, as their privacy calcualations involve
- // database queries, and we wish to avoid large gaps between queries due to MySQL connection timeouts.
- $tmp_gedcom = '';
- $rows = Database::prepare(
- "SELECT m_id AS xref, m_gedcom AS gedcom" .
- " FROM `##media` WHERE m_file = :tree_id ORDER BY m_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Media::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- $rec = convert_media_path($rec, $exportOptions['path']);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $tmp_gedcom .= reformat_record_export($rec);
- }
-
- $rows = Database::prepare(
- "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom" .
- " FROM `##sources` WHERE s_file = :tree_id ORDER BY s_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Source::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $tmp_gedcom .= reformat_record_export($rec);
- }
-
- $rows = Database::prepare(
- "SELECT o_type AS type, o_id AS xref, o_gedcom AS gedcom" .
- " FROM `##other` WHERE o_file = :tree_id AND o_type NOT IN ('HEAD', 'TRLR') ORDER BY o_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- switch ($row->type) {
- case 'NOTE':
- $record = Note::getInstance($row->xref, $tree, $row->gedcom);
- break;
- case 'REPO':
- $record = Repository::getInstance($row->xref, $tree, $row->gedcom);
- break;
- default:
- $record = GedcomRecord::getInstance($row->xref, $tree, $row->gedcom);
- break;
- }
-
- $rec = $record->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $tmp_gedcom .= reformat_record_export($rec);
- }
-
- $rows = Database::prepare(
- "SELECT i_id AS xref, i_gedcom AS gedcom" .
- " FROM `##individuals` WHERE i_file = :tree_id ORDER BY i_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Individual::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $buffer .= reformat_record_export($rec);
- if (strlen($buffer) > 65536) {
- fwrite($gedout, $buffer);
- $buffer = '';
- }
- }
-
- $rows = Database::prepare(
- "SELECT f_id AS xref, f_gedcom AS gedcom" .
- " FROM `##families` WHERE f_file = :tree_id ORDER BY f_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Family::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $buffer .= reformat_record_export($rec);
- if (strlen($buffer) > 65536) {
- fwrite($gedout, $buffer);
- $buffer = '';
- }
- }
-
- fwrite($gedout, $buffer);
- fwrite($gedout, $tmp_gedcom);
- fwrite($gedout, '0 TRLR' . WT_EOL);
-}
diff --git a/includes/functions/functions_import.php b/includes/functions/functions_import.php
deleted file mode 100644
index 2bb608e5d7..0000000000
--- a/includes/functions/functions_import.php
+++ /dev/null
@@ -1,1153 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Log;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Soundex;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Tree;
-
-/**
- * Tidy up a gedcom record on import, so that we can access it consistently/efficiently.
- *
- * @param string $rec
- * @param Tree $tree
- *
- * @return string
- */
-function reformat_record_import($rec, Tree $tree) {
- // Strip out UTF8 formatting characters
- $rec = str_replace(array(WT_UTF8_BOM, WT_UTF8_LRM, WT_UTF8_RLM), '', $rec);
-
- // Strip out mac/msdos line endings
- $rec = preg_replace("/[\r\n]+/", "\n", $rec);
-
- // Extract lines from the record; lines consist of: level + optional xref + tag + optional data
- $num_matches = preg_match_all('/^[ \t]*(\d+)[ \t]*(@[^@]*@)?[ \t]*(\w+)[ \t]?(.*)$/m', $rec, $matches, PREG_SET_ORDER);
-
- // Process the record line-by-line
- $newrec = '';
- foreach ($matches as $n => $match) {
- list(, $level, $xref, $tag, $data) = $match;
- $tag = strtoupper($tag); // Tags should always be upper case
- switch ($tag) {
- // Convert PhpGedView tags to WT
- case '_PGVU':
- $tag = '_WT_USER';
- break;
- case '_PGV_OBJS':
- $tag = '_WT_OBJE_SORT';
- break;
- // Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
- case 'ABBREVIATION':
- $tag = 'ABBR';
- break;
- case 'ADDRESS':
- $tag = 'ADDR';
- break;
- case 'ADDRESS1':
- $tag = 'ADR1';
- break;
- case 'ADDRESS2':
- $tag = 'ADR2';
- break;
- case 'ADDRESS3':
- $tag = 'ADR3';
- break;
- case 'ADOPTION':
- $tag = 'ADOP';
- break;
- case 'ADULT_CHRISTENING':
- $tag = 'CHRA';
- break;
- case 'AFN':
- // AFN values are upper case
- $data = strtoupper($data);
- break;
- case 'AGENCY':
- $tag = 'AGNC';
- break;
- case 'ALIAS':
- $tag = 'ALIA';
- break;
- case 'ANCESTORS':
- $tag = 'ANCE';
- break;
- case 'ANCES_INTEREST':
- $tag = 'ANCI';
- break;
- case 'ANNULMENT':
- $tag = 'ANUL';
- break;
- case 'ASSOCIATES':
- $tag = 'ASSO';
- break;
- case 'AUTHOR':
- $tag = 'AUTH';
- break;
- case 'BAPTISM':
- $tag = 'BAPM';
- break;
- case 'BAPTISM_LDS':
- $tag = 'BAPL';
- break;
- case 'BAR_MITZVAH':
- $tag = 'BARM';
- break;
- case 'BAS_MITZVAH':
- $tag = 'BASM';
- break;
- case 'BIRTH':
- $tag = 'BIRT';
- break;
- case 'BLESSING':
- $tag = 'BLES';
- break;
- case 'BURIAL':
- $tag = 'BURI';
- break;
- case 'CALL_NUMBER':
- $tag = 'CALN';
- break;
- case 'CASTE':
- $tag = 'CAST';
- break;
- case 'CAUSE':
- $tag = 'CAUS';
- break;
- case 'CENSUS':
- $tag = 'CENS';
- break;
- case 'CHANGE':
- $tag = 'CHAN';
- break;
- case 'CHARACTER':
- $tag = 'CHAR';
- break;
- case 'CHILD':
- $tag = 'CHIL';
- break;
- case 'CHILDREN_COUNT':
- $tag = 'NCHI';
- break;
- case 'CHRISTENING':
- $tag = 'CHR';
- break;
- case 'CONCATENATION':
- $tag = 'CONC';
- break;
- case 'CONFIRMATION':
- $tag = 'CONF';
- break;
- case 'CONFIRMATION_LDS':
- $tag = 'CONL';
- break;
- case 'CONTINUED':
- $tag = 'CONT';
- break;
- case 'COPYRIGHT':
- $tag = 'COPR';
- break;
- case 'CORPORATE':
- $tag = 'CORP';
- break;
- case 'COUNTRY':
- $tag = 'CTRY';
- break;
- case 'CREMATION':
- $tag = 'CREM';
- break;
- case 'DATE':
- // Preserve text from INT dates
- if (strpos($data, '(') !== false) {
- list($date, $text) = explode('(', $data, 2);
- $text = ' (' . $text;
- } else {
- $date = $data;
- $text = '';
- }
- // Capitals
- $date = strtoupper($date);
- // Temporarily add leading/trailing spaces, to allow efficient matching below
- $date = " {$date} ";
- // Ensure space digits and letters
- $date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
- $date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
- // Ensure space before/after calendar escapes
- $date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
- // "BET." => "BET"
- $date = preg_replace('/(\w\w)\./', '$1', $date);
- // "CIR" => "ABT"
- $date = str_replace(' CIR ', ' ABT ', $date);
- $date = str_replace(' APX ', ' ABT ', $date);
- // B.C. => BC (temporarily, to allow easier handling of ".")
- $date = str_replace(' B.C. ', ' BC ', $date);
- // "BET X - Y " => "BET X AND Y"
- $date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
- $date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
- // "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
- $date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
- $date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
- // "@#ESC@ AFT X" => "AFT @#ESC@ X"
- $date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
- // Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
- // (don't change "/" - it is used in NS/OS dates)
- $date = preg_replace('/[.,:;-]/', ' ', $date);
- // BC => B.C.
- $date = str_replace(' BC ', ' B.C. ', $date);
- // Append the "INT" text
- $data = $date . $text;
- break;
- case 'DEATH':
- $tag = 'DEAT';
- break;
- case '_DEATH_OF_SPOUSE':
- $tag = '_DETS';
- break;
- case '_DEGREE':
- $tag = '_DEG';
- break;
- case 'DESCENDANTS':
- $tag = 'DESC';
- break;
- case 'DESCENDANT_INT':
- $tag = 'DESI';
- break;
- case 'DESTINATION':
- $tag = 'DEST';
- break;
- case 'DIVORCE':
- $tag = 'DIV';
- break;
- case 'DIVORCE_FILED':
- $tag = 'DIVF';
- break;
- case 'EDUCATION':
- $tag = 'EDUC';
- break;
- case 'EMIGRATION':
- $tag = 'EMIG';
- break;
- case 'ENDOWMENT':
- $tag = 'ENDL';
- break;
- case 'ENGAGEMENT':
- $tag = 'ENGA';
- break;
- case 'EVENT':
- $tag = 'EVEN';
- break;
- case 'FACSIMILE':
- $tag = 'FAX';
- break;
- case 'FAMILY':
- $tag = 'FAM';
- break;
- case 'FAMILY_CHILD':
- $tag = 'FAMC';
- break;
- case 'FAMILY_FILE':
- $tag = 'FAMF';
- break;
- case 'FAMILY_SPOUSE':
- $tag = 'FAMS';
- break;
- case 'FIRST_COMMUNION':
- $tag = 'FCOM';
- break;
- case '_FILE':
- $tag = 'FILE';
- break;
- case 'FORMAT':
- $tag = 'FORM';
- case 'FORM':
- // Consistent commas
- $data = preg_replace('/ *, */', ', ', $data);
- break;
- case 'GEDCOM':
- $tag = 'GEDC';
- break;
- case 'GIVEN_NAME':
- $tag = 'GIVN';
- break;
- case 'GRADUATION':
- $tag = 'GRAD';
- break;
- case 'HEADER':
- $tag = 'HEAD';
- case 'HEAD':
- // HEAD records don't have an XREF or DATA
- if ($level == '0') {
- $xref = '';
- $data = '';
- }
- break;
- case 'HUSBAND':
- $tag = 'HUSB';
- break;
- case 'IDENT_NUMBER':
- $tag = 'IDNO';
- break;
- case 'IMMIGRATION':
- $tag = 'IMMI';
- break;
- case 'INDIVIDUAL':
- $tag = 'INDI';
- break;
- case 'LANGUAGE':
- $tag = 'LANG';
- break;
- case 'LATITUDE':
- $tag = 'LATI';
- break;
- case 'LONGITUDE':
- $tag = 'LONG';
- break;
- case 'MARRIAGE':
- $tag = 'MARR';
- break;
- case 'MARRIAGE_BANN':
- $tag = 'MARB';
- break;
- case 'MARRIAGE_COUNT':
- $tag = 'NMR';
- break;
- case 'MARRIAGE_CONTRACT':
- $tag = 'MARC';
- break;
- case 'MARRIAGE_LICENSE':
- $tag = 'MARL';
- break;
- case 'MARRIAGE_SETTLEMENT':
- $tag = 'MARS';
- break;
- case 'MEDIA':
- $tag = 'MEDI';
- break;
- case '_MEDICAL':
- $tag = '_MDCL';
- break;
- case '_MILITARY_SERVICE':
- $tag = '_MILT';
- break;
- case 'NAME':
- // Tidy up whitespace
- $data = preg_replace('/ +/', ' ', trim($data));
- break;
- case 'NAME_PREFIX':
- $tag = 'NPFX';
- break;
- case 'NAME_SUFFIX':
- $tag = 'NSFX';
- break;
- case 'NATIONALITY':
- $tag = 'NATI';
- break;
- case 'NATURALIZATION':
- $tag = 'NATU';
- break;
- case 'NICKNAME':
- $tag = 'NICK';
- break;
- case 'OBJECT':
- $tag = 'OBJE';
- break;
- case 'OCCUPATION':
- $tag = 'OCCU';
- break;
- case 'ORDINANCE':
- $tag = 'ORDI';
- break;
- case 'ORDINATION':
- $tag = 'ORDN';
- break;
- case 'PEDIGREE':
- $tag = 'PEDI';
- case 'PEDI':
- // PEDI values are lower case
- $data = strtolower($data);
- break;
- case 'PHONE':
- $tag = 'PHON';
- break;
- case 'PHONETIC':
- $tag = 'FONE';
- break;
- case 'PHY_DESCRIPTION':
- $tag = 'DSCR';
- break;
- case 'PLACE':
- $tag = 'PLAC';
- case 'PLAC':
- // Consistent commas
- $data = preg_replace('/ *(،|,) */', ', ', $data);
- // The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
- if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
- $data =
- $match[1] . "\n" .
- ($level + 1) . " MAP\n" .
- ($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
- ($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
- }
- break;
- case 'POSTAL_CODE':
- $tag = 'POST';
- break;
- case 'PROBATE':
- $tag = 'PROB';
- break;
- case 'PROPERTY':
- $tag = 'PROP';
- break;
- case 'PUBLICATION':
- $tag = 'PUBL';
- break;
- case 'QUALITY_OF_DATA':
- $tag = 'QUAL';
- break;
- case 'REC_FILE_NUMBER':
- $tag = 'RFN';
- break;
- case 'REC_ID_NUMBER':
- $tag = 'RIN';
- break;
- case 'REFERENCE':
- $tag = 'REFN';
- break;
- case 'RELATIONSHIP':
- $tag = 'RELA';
- break;
- case 'RELIGION':
- $tag = 'RELI';
- break;
- case 'REPOSITORY':
- $tag = 'REPO';
- break;
- case 'RESIDENCE':
- $tag = 'RESI';
- break;
- case 'RESTRICTION':
- $tag = 'RESN';
- case 'RESN':
- // RESN values are lower case (confidential, privacy, locked, none)
- $data = strtolower($data);
- if ($data == 'invisible') {
- $data = 'confidential'; // From old versions of Legacy.
- }
- break;
- case 'RETIREMENT':
- $tag = 'RETI';
- break;
- case 'ROMANIZED':
- $tag = 'ROMN';
- break;
- case 'SEALING_CHILD':
- $tag = 'SLGC';
- break;
- case 'SEALING_SPOUSE':
- $tag = 'SLGS';
- break;
- case 'SOC_SEC_NUMBER':
- $tag = 'SSN';
- break;
- case 'SEX':
- $data = strtoupper($data);
- break;
- case 'SOURCE':
- $tag = 'SOUR';
- break;
- case 'STATE':
- $tag = 'STAE';
- break;
- case 'STATUS':
- $tag = 'STAT';
- case 'STAT':
- if ($data == 'CANCELLED') {
- // PhpGedView mis-spells this tag - correct it.
- $data = 'CANCELED';
- }
- break;
- case 'SUBMISSION':
- $tag = 'SUBN';
- break;
- case 'SUBMITTER':
- $tag = 'SUBM';
- break;
- case 'SURNAME':
- $tag = 'SURN';
- break;
- case 'SURN_PREFIX':
- $tag = 'SPFX';
- break;
- case 'TEMPLE':
- $tag = 'TEMP';
- case 'TEMP':
- // Temple codes are upper case
- $data = strtoupper($data);
- break;
- case 'TITLE':
- $tag = 'TITL';
- break;
- case 'TRAILER':
- $tag = 'TRLR';
- case 'TRLR':
- // TRLR records don't have an XREF or DATA
- if ($level == '0') {
- $xref = '';
- $data = '';
- }
- break;
- case 'VERSION':
- $tag = 'VERS';
- break;
- case 'WEB':
- $tag = 'WWW';
- break;
- }
- // Suppress "Y", for facts/events with a DATE or PLAC
- if ($data == 'y') {
- $data = 'Y';
- }
- if ($level == '1' && $data == 'Y') {
- for ($i = $n + 1; $i < $num_matches - 1 && $matches[$i][1] != '1'; ++$i) {
- if ($matches[$i][3] == 'DATE' || $matches[$i][3] == 'PLAC') {
- $data = '';
- break;
- }
- }
- }
- // Reassemble components back into a single line
- switch ($tag) {
- default:
- // Remove tabs and multiple/leading/trailing spaces
- if (strpos($data, "\t") !== false) {
- $data = str_replace("\t", ' ', $data);
- }
- if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
- $data = trim($data);
- }
- while (strpos($data, ' ')) {
- $data = str_replace(' ', ' ', $data);
- }
- $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
- break;
- case 'NOTE':
- case 'TEXT':
- case 'DATA':
- case 'CONT':
- $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
- break;
- case 'FILE':
- // Strip off the user-defined path prefix
- $GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
- if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
- $data = substr($data, strlen($GEDCOM_MEDIA_PATH));
- }
- // convert backslashes in filenames to forward slashes
- $data = preg_replace("/\\\/", "/", $data);
-
- $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
- break;
- case 'CONC':
- // Merge CONC lines, to simplify access later on.
- $newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
- break;
- }
- }
-
- return $newrec;
-}
-
-/**
- * import record into database
- *
- * this function will parse the given gedcom record and add it to the database
- *
- * @param string $gedrec the raw gedcom record to parse
- * @param Tree $tree import the record into this tree
- * @param bool $update whether or not this is an updated record that has been accepted
- */
-function import_record($gedrec, Tree $tree, $update) {
- $tree_id = $tree->getTreeId();
-
- // Escaped @ signs (only if importing from file)
- if (!$update) {
- $gedrec = str_replace('@@', '@', $gedrec);
- }
-
- // Standardise gedcom format
- $gedrec = reformat_record_import($gedrec, $tree);
-
- // import different types of records
- if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
- list(, $xref, $type) = $match;
- // check for a _UID, if the record doesn't have one, add one
- if ($tree->getPreference('GENERATE_UIDS') && !strpos($gedrec, "\n1 _UID ")) {
- $gedrec .= "\n1 _UID " . GedcomTag::createUid();
- }
- } elseif (preg_match('/0 (HEAD|TRLR)/', $gedrec, $match)) {
- $type = $match[1];
- $xref = $type; // For HEAD/TRLR, use type as pseudo XREF.
- } else {
- echo I18N::translate('Invalid GEDCOM format'), '<br><pre>', $gedrec, '</pre>';
-
- return;
- }
-
- // If the user has downloaded their GEDCOM data (containing media objects) and edited it
- // using an application which does not support (and deletes) media objects, then add them
- // back in.
- if ($tree->getPreference('keep_media') && $xref) {
- $old_linked_media =
- Database::prepare("SELECT l_to FROM `##link` WHERE l_from=? AND l_file=? AND l_type='OBJE'")
- ->execute(array($xref, $tree_id))
- ->fetchOneColumn();
- foreach ($old_linked_media as $media_id) {
- $gedrec .= "\n1 OBJE @" . $media_id . "@";
- }
- }
-
- switch ($type) {
- case 'INDI':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- $record = new Individual($xref, $gedrec, null, $tree);
- if ($tree->getPreference('USE_RIN') && preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
- $rin = $match[1];
- } else {
- $rin = $xref;
- }
- Database::prepare(
- "INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $tree_id, $rin, $record->getSex(), $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_places($xref, $tree_id, $gedrec);
- update_dates($xref, $tree_id, $gedrec);
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'FAM':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
- $husb = $match[1];
- } else {
- $husb = '';
- }
- if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
- $wife = $match[1];
- } else {
- $wife = '';
- }
- $nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
- if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
- $nchi = max($nchi, $match[1]);
- }
- Database::prepare(
- "INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $tree_id, $husb, $wife, $gedrec, $nchi,
- ));
- // Update the cross-reference/index tables.
- update_places($xref, $tree_id, $gedrec);
- update_dates($xref, $tree_id, $gedrec);
- update_links($xref, $tree_id, $gedrec);
- break;
- case 'SOUR':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- $record = new Source($xref, $gedrec, null, $tree);
- if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
- $name = $match[1];
- } elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
- $name = $match[1];
- } else {
- $name = $xref;
- }
- Database::prepare(
- "INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
- )->execute(array(
- $xref, $tree_id, $name, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'REPO':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- $record = new Repository($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
- )->execute(array(
- $xref, $tree_id, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'NOTE':
- $record = new Note($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
- )->execute(array(
- $xref, $tree_id, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'OBJE':
- $record = new Media($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'HEAD':
- // Force HEAD records to have a creation date.
- if (!strpos($gedrec, "\n1 DATE ")) {
- $gedrec .= "\n1 DATE " . date('j M Y');
- }
- // No break;
- case 'TRLR':
- case 'SUBM':
- case 'SUBN':
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, ?, ?)"
- )->execute(array($xref, $tree_id, $type, $gedrec));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- break;
- default:
- $record = new GedcomRecord($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, ?, ?)"
- )->execute(array($xref, $tree_id, $type, $gedrec));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- }
-}
-
-/**
- * extract all places from the given record and insert them into the places table
- *
- * @param string $gid
- * @param int $ged_id
- * @param string $gedrec
- */
-function update_places($gid, $ged_id, $gedrec) {
- global $placecache;
-
- if (!isset($placecache)) {
- $placecache = array();
- }
- $personplace = array();
- // import all place locations, but not control info such as
- // 0 HEAD/1 PLAC or 0 _EVDEF/1 PLAC
- $pt = preg_match_all("/^[2-9] PLAC (.+)/m", $gedrec, $match, PREG_SET_ORDER);
- for ($i = 0; $i < $pt; $i++) {
- $place = trim($match[$i][1]);
- $lowplace = I18N::strtolower($place);
- //-- if we have already visited this place for this person then we don't need to again
- if (isset($personplace[$lowplace])) {
- continue;
- }
- $personplace[$lowplace] = 1;
- $places = explode(',', $place);
- //-- reverse the array to start at the highest level
- $secalp = array_reverse($places);
- $parent_id = 0;
- $search = true;
-
- foreach ($secalp as $place) {
- $place = trim($place);
- $key = strtolower(mb_substr($place, 0, 150) . "_" . $parent_id);
- //-- if this place has already been added then we don't need to add it again
- if (isset($placecache[$key])) {
- $parent_id = $placecache[$key];
- if (!isset($personplace[$key])) {
- $personplace[$key] = 1;
- // Use INSERT IGNORE as a (temporary) fix for https://bugs.launchpad.net/webtrees/+bug/582226
- // It ignores places that utf8_unicode_ci consider to be the same (i.e. accents).
- // For example Québec and Quebec
- // We need a better solution that attaches multiple names to single places
- Database::prepare(
- "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
- )->execute(array(
- $parent_id, $gid, $ged_id,
- ));
- }
- continue;
- }
-
- //-- only search the database while we are finding places in it
- if ($search) {
- //-- check if this place and level has already been added
- $tmp = Database::prepare(
- "SELECT p_id FROM `##places` WHERE p_file = ? AND p_parent_id = ? AND p_place = LEFT(?, 150)"
- )->execute(array(
- $ged_id, $parent_id, $place,
- ))->fetchOne();
- if ($tmp) {
- $p_id = $tmp;
- } else {
- $search = false;
- }
- }
-
- //-- if we are not searching then we have to insert the place into the db
- if (!$search) {
- $std_soundex = Soundex::russell($place);
- $dm_soundex = Soundex::daitchMokotoff($place);
- Database::prepare(
- "INSERT INTO `##places` (p_place, p_parent_id, p_file, p_std_soundex, p_dm_soundex) VALUES (LEFT(?, 150), ?, ?, ?, ?)"
- )->execute(array(
- $place, $parent_id, $ged_id, $std_soundex, $dm_soundex,
- ));
- $p_id = Database::getInstance()->lastInsertId();
- }
-
- Database::prepare(
- "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
- )->execute(array(
- $p_id, $gid, $ged_id,
- ));
- //-- increment the level and assign the parent id for the next place level
- $parent_id = $p_id;
- $placecache[$key] = $p_id;
- $personplace[$key] = 1;
- }
- }
-}
-
-/**
- * Extract all the dates from the given record and insert them into the database.
- *
- * @param string $xref
- * @param int $ged_id
- * @param string $gedrec
- */
-function update_dates($xref, $ged_id, $gedrec) {
- if (strpos($gedrec, '2 DATE ') && preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $fact = $match[1];
- if (($fact == 'FACT' || $fact == 'EVEN') && preg_match("/\n2 TYPE ([A-Z]{3,5})/", $match[0], $tmatch)) {
- $fact = $tmatch[1];
- }
- $date = new Date($match[2]);
- Database::prepare(
- "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
- )->execute(array(
- $date->minimumDate()->d,
- $date->minimumDate()->format('%O'),
- $date->minimumDate()->m,
- $date->minimumDate()->y,
- $date->minimumDate()->minJD,
- $date->minimumDate()->maxJD,
- $fact,
- $xref,
- $ged_id,
- $date->minimumDate()->format('%@'),
- ));
- if ($date->minimumDate() !== $date->maximumDate()) {
- Database::prepare(
- "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
- )->execute(array(
- $date->maximumDate()->d,
- $date->maximumDate()->format('%O'),
- $date->maximumDate()->m,
- $date->maximumDate()->y,
- $date->maximumDate()->minJD,
- $date->maximumDate()->maxJD,
- $fact,
- $xref,
- $ged_id,
- $date->maximumDate()->format('%@'),
- ));
- }
- }
- }
-}
-
-/**
- * Extract all the links from the given record and insert them into the database
- *
- * @param string $xref
- * @param int $ged_id
- * @param string $gedrec
- */
-function update_links($xref, $ged_id, $gedrec) {
- if (preg_match_all('/^\d+ (' . WT_REGEX_TAG . ') @(' . WT_REGEX_XREF . ')@/m', $gedrec, $matches, PREG_SET_ORDER)) {
- $data = array();
- foreach ($matches as $match) {
- // Include each link once only.
- if (!in_array($match[1] . $match[2], $data)) {
- $data[] = $match[1] . $match[2];
- // Ignore any errors, which may be caused by "duplicates" that differ on case/collation, e.g. "S1" and "s1"
- try {
- Database::prepare(
- "INSERT INTO `##link` (l_from, l_to, l_type, l_file) VALUES (?, ?, ?, ?)"
- )->execute(array(
- $xref, $match[2], $match[1], $ged_id,
- ));
- } catch (PDOException $e) {
- // We could display a warning here....
- }
- }
- }
- }
-}
-
-/**
- * Extract all the names from the given record and insert them into the database.
- *
- * @param string $xref
- * @param int $ged_id
- * @param GedcomRecord $record
- */
-function update_names($xref, $ged_id, GedcomRecord $record) {
- foreach ($record->getAllNames() as $n => $name) {
- if ($record instanceof Individual) {
- if ($name['givn'] === '@P.N.') {
- $soundex_givn_std = null;
- $soundex_givn_dm = null;
- } else {
- $soundex_givn_std = Soundex::russell($name['givn']);
- $soundex_givn_dm = Soundex::daitchMokotoff($name['givn']);
- }
- if ($name['surn'] === '@N.N.') {
- $soundex_surn_std = null;
- $soundex_surn_dm = null;
- } else {
- $soundex_surn_std = Soundex::russell($name['surname']);
- $soundex_surn_dm = Soundex::daitchMokotoff($name['surname']);
- }
- Database::prepare(
- "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full,n_surname,n_surn,n_givn,n_soundex_givn_std,n_soundex_surn_std,n_soundex_givn_dm,n_soundex_surn_dm) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), ?, ?, ?, ?, ?)"
- )->execute(array(
- $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'], $name['surname'], $name['surn'], $name['givn'], $soundex_givn_std, $soundex_surn_std, $soundex_givn_dm, $soundex_surn_dm,
- ));
- } else {
- Database::prepare(
- "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255))"
- )->execute(array(
- $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'],
- ));
- }
- }
-}
-
-/**
- * Extract inline media data, and convert to media objects.
- *
- * @param Tree $tree
- * @param string $gedrec
- *
- * @return string
- */
-function convert_inline_media(Tree $tree, $gedrec) {
- while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedrec, $match)) {
- $gedrec = str_replace($match[0], create_media_object(1, $match[0], $tree), $gedrec);
- }
- while (preg_match('/\n2 OBJE(?:\n[3-9].+)+/', $gedrec, $match)) {
- $gedrec = str_replace($match[0], create_media_object(2, $match[0], $tree), $gedrec);
- }
- while (preg_match('/\n3 OBJE(?:\n[4-9].+)+/', $gedrec, $match)) {
- $gedrec = str_replace($match[0], create_media_object(3, $match[0], $tree), $gedrec);
- }
-
- return $gedrec;
-}
-
-/**
- * Create a new media object, from inline media data.
- *
- * @param int $level
- * @param string $gedrec
- * @param Tree $tree
- *
- * @return string
- */
-function create_media_object($level, $gedrec, Tree $tree) {
- if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
- $file = $file_match[1];
- } else {
- $file = '';
- }
-
- if (preg_match('/\n\d TITL (.+)/', $gedrec, $file_match)) {
- $titl = $file_match[1];
- } else {
- $titl = $file;
- }
-
- // Have we already created a media object with the same title/filename?
- $xref = Database::prepare(
- "SELECT m_id FROM `##media` WHERE m_filename = ? AND m_titl = ? AND m_file = ?"
- )->execute(array(
- $file, $titl, $tree->getTreeId(),
- ))->fetchOne();
-
- if (!$xref) {
- $xref = $tree->getNewXref('OBJE');
- // renumber the lines
- $gedrec = preg_replace_callback('/\n(\d+)/', function ($m) use ($level) { return "\n" . ($m[1] - $level); }, $gedrec);
- // convert to an object
- $gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);
- // Fix Legacy GEDCOMS
- $gedrec = preg_replace('/\n1 FORM (.+)\n1 FILE (.+)\n1 TITL (.+)/', "\n1 FILE $2\n2 FORM $1\n2 TITL $3", $gedrec);
- // Create new record
- $record = new Media($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree->getTreeId(), $gedrec,
- ));
- }
-
- return "\n" . $level . ' OBJE @' . $xref . '@';
-}
-
-/**
- * Accept all pending changes for a specified record.
- *
- * @param string $xref
- * @param int $ged_id
- */
-function accept_all_changes($xref, $ged_id) {
- $changes = Database::prepare(
- "SELECT change_id, gedcom_name, old_gedcom, new_gedcom" .
- " FROM `##change` c" .
- " JOIN `##gedcom` g USING (gedcom_id)" .
- " WHERE c.status='pending' AND xref=? AND gedcom_id=?" .
- " ORDER BY change_id"
- )->execute(array($xref, $ged_id))->fetchAll();
- foreach ($changes as $change) {
- if (empty($change->new_gedcom)) {
- // delete
- update_record($change->old_gedcom, $ged_id, true);
- } else {
- // add/update
- update_record($change->new_gedcom, $ged_id, false);
- }
- Database::prepare(
- "UPDATE `##change`" .
- " SET status='accepted'" .
- " WHERE status='pending' AND xref=? AND gedcom_id=?"
- )->execute(array($xref, $ged_id));
- Log::addEditLog("Accepted change {$change->change_id} for {$xref} / {$change->gedcom_name} into database");
- }
-}
-
-/**
- * Accept all pending changes for a specified record.
- *
- * @param GedcomRecord $record
- */
-function reject_all_changes(GedcomRecord $record) {
- Database::prepare(
- "UPDATE `##change`" .
- " SET status = 'rejected'" .
- " WHERE status = 'pending' AND xref = :xref AND gedcom_id = :tree_id"
- )->execute(array(
- 'xref' => $record->getXref(),
- 'tree_id' => $record->getTree()->getTreeId(),
- ));
-}
-
-/**
- * update a record in the database
- *
- * @param string $gedrec
- * @param int $ged_id
- * @param bool $delete
- */
-function update_record($gedrec, $ged_id, $delete) {
- if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
- list(, $gid, $type) = $match;
- } else {
- echo "ERROR: Invalid gedcom record.";
-
- return;
- }
-
- // TODO deleting unlinked places can be done more efficiently in a single query
- $placeids =
- Database::prepare("SELECT pl_p_id FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")
- ->execute(array($gid, $ged_id))
- ->fetchOneColumn();
-
- Database::prepare("DELETE FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")->execute(array($gid, $ged_id));
- Database::prepare("DELETE FROM `##dates` WHERE d_gid =? AND d_file =?")->execute(array($gid, $ged_id));
-
- //-- delete any unlinked places
- foreach ($placeids as $p_id) {
- $num =
- Database::prepare("SELECT count(pl_p_id) FROM `##placelinks` WHERE pl_p_id=? AND pl_file=?")
- ->execute(array($p_id, $ged_id))
- ->fetchOne();
- if ($num == 0) {
- Database::prepare("DELETE FROM `##places` WHERE p_id=? AND p_file=?")->execute(array($p_id, $ged_id));
- }
- }
-
- Database::prepare("DELETE FROM `##name` WHERE n_id=? AND n_file=?")->execute(array($gid, $ged_id));
- Database::prepare("DELETE FROM `##link` WHERE l_from=? AND l_file=?")->execute(array($gid, $ged_id));
-
- switch ($type) {
- case 'INDI':
- Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
- break;
- case 'FAM':
- Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
- break;
- case 'SOUR':
- Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
- break;
- case 'OBJE':
- Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
- break;
- default:
- Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
- break;
- }
-
- if (!$delete) {
- import_record($gedrec, Tree::FindById($ged_id), true);
- }
-}
diff --git a/includes/functions/functions_mediadb.php b/includes/functions/functions_mediadb.php
deleted file mode 100644
index f2e40af6d8..0000000000
--- a/includes/functions/functions_mediadb.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-use Fisharebest\Webtrees\Log;
-
-/**
- * Convert raw values from php.ini file into bytes
- *
- * @param string $val
- *
- * @return int
- */
-function return_bytes($val) {
- if (!$val) {
- // no value was passed in, assume no limit and return -1
- $val = -1;
- }
- switch (substr($val, -1)) {
- case 'g':
- case 'G':
- return (int) $val * 1024 * 1024 * 1024;
- case 'm':
- case 'M':
- return (int) $val * 1024 * 1024;
- case 'k':
- case 'K':
- return (int) $val * 1024;
- default:
- return (int) $val;
- }
-}
-
-/**
- * Determine whether there is enough memory to load a particular image.
- *
- * @param string $serverFilename
- *
- * @return bool
- */
-function hasMemoryForImage($serverFilename) {
- // find out how much total memory this script can access
- $memoryAvailable = return_bytes(ini_get('memory_limit'));
- // if memory is unlimited, it will return -1 and we don’t need to worry about it
- if ($memoryAvailable == -1) {
- return true;
- }
-
- // find out how much memory we are already using
- $memoryUsed = memory_get_usage();
-
- try {
- $imgsize = getimagesize($serverFilename);
- } catch (\ErrorException $ex) {
- // Not an image, or not a valid image?
- $imgsize = false;
- }
-
- // find out how much memory this image needs for processing, probably only works for jpegs
- // from comments on http://www.php.net/imagecreatefromjpeg
- if ($imgsize && isset($imgsize['bits']) && (isset($imgsize['channels']))) {
- $memoryNeeded = round(($imgsize[0] * $imgsize[1] * $imgsize['bits'] * $imgsize['channels'] / 8 + Pow(2, 16)) * 1.65);
- $memorySpare = $memoryAvailable - $memoryUsed - $memoryNeeded;
- if ($memorySpare > 0) {
- // we have enough memory to load this file
- return true;
- } else {
- // not enough memory to load this file
- $image_info = sprintf('%.2fKB, %d × %d %d bits %d channels', filesize($serverFilename) / 1024, $imgsize[0], $imgsize[1], $imgsize['bits'], $imgsize['channels']);
- Log::addMediaLog('Cannot create thumbnail ' . $serverFilename . ' (' . $image_info . ') memory avail: ' . $memoryAvailable . ' used: ' . $memoryUsed . ' needed: ' . $memoryNeeded . ' spare: ' . $memorySpare);
-
- return false;
- }
- } else {
- // assume there is enough memory
- // TODO find out how to check memory needs for gif and png
- return true;
- }
-}
diff --git a/includes/functions/functions_print.php b/includes/functions/functions_print.php
deleted file mode 100644
index 336cf29481..0000000000
--- a/includes/functions/functions_print.php
+++ /dev/null
@@ -1,867 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Controller\SearchController;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\CensusAssistantModule;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Place;
-use Fisharebest\Webtrees\Session;
-use Fisharebest\Webtrees\Theme;
-use Fisharebest\Webtrees\Tree;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * print the information for an individual chart box
- *
- * find and print a given individuals information for a pedigree chart
- *
- * @param Individual $person The person to print
- * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes
- */
-function print_pedigree_person(Individual $person = null, $show_full = 1) {
-
- switch ($show_full) {
- case 0:
- if ($person) {
- echo Theme::theme()->individualBoxSmall($person);
- } else {
- echo Theme::theme()->individualBoxSmallEmpty();
- }
- break;
- case 1:
- if ($person) {
- echo Theme::theme()->individualBox($person);
- } else {
- echo Theme::theme()->individualBoxEmpty();
- }
- break;
- }
-}
-
-/**
- * print a note record
- *
- * @param string $text
- * @param int $nlevel the level of the note record
- * @param string $nrec the note record to print
- * @param bool $textOnly Don't print the "Note: " introduction
- *
- * @return string
- */
-function print_note_record($text, $nlevel, $nrec, $textOnly = false) {
- global $WT_TREE;
-
- $text .= get_cont($nlevel, $nrec);
-
- // Check if shared note (we have already checked that it exists)
- if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
- $note = Note::getInstance($match[1], $WT_TREE);
- $label = 'SHARED_NOTE';
- // If Census assistant installed, allow it to format the note
- if (Module::getModuleByName('GEDFact_assistant')) {
- $html = CensusAssistantModule::formatCensusNote($note);
- } else {
- $html = Filter::formatText($note->getNote(), $WT_TREE);
- }
- } else {
- $note = null;
- $label = 'NOTE';
- $html = Filter::formatText($text, $WT_TREE);
- }
-
- if ($textOnly) {
- return strip_tags($text);
- }
-
- if (strpos($text, "\n") === false) {
- // A one-line note? strip the block-level tags, so it displays inline
- return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
- } elseif ($WT_TREE->getPreference('EXPAND_NOTES')) {
- // A multi-line note, and we're expanding notes by default
- return GedcomTag::getLabelValue($label, $html);
- } else {
- // A multi-line note, with an expand/collapse option
- $element_id = Uuid::uuid4();
- // NOTE: class "note-details" is (currently) used only by some third-party themes
- if ($note) {
- $first_line = '<a href="' . $note->getHtmlUrl() . '">' . $note->getFullName() . '</a>';
- } else {
- list($text) = explode("\n", strip_tags($html));
- $first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text;
- }
-
- return
- '<div class="fact_NOTE"><span class="label">' .
- '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
- '</div>' .
- '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
- }
-}
-
-/**
- * Print all of the notes in this fact record
- *
- * @param string $factrec The factrecord to print the notes from
- * @param int $level The level of the factrecord
- * @param bool $textOnly Don't print the "Note: " introduction
- *
- * @return string HTML
- */
-function print_fact_notes($factrec, $level, $textOnly = false) {
- global $WT_TREE;
-
- $data = '';
- $previous_spos = 0;
- $nlevel = $level + 1;
- $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
- for ($j = 0; $j < $ct; $j++) {
- $spos1 = strpos($factrec, $match[$j][0], $previous_spos);
- $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
- if (!$spos2) {
- $spos2 = strlen($factrec);
- }
- $previous_spos = $spos2;
- $nrec = substr($factrec, $spos1, $spos2 - $spos1);
- if (!isset($match[$j][1])) {
- $match[$j][1] = '';
- }
- if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) {
- $data .= print_note_record($match[$j][1], $nlevel, $nrec, $textOnly);
- } else {
- $note = Note::getInstance($nmatch[1], $WT_TREE);
- if ($note) {
- if ($note->canShow()) {
- $noterec = $note->getGedcom();
- $nt = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
- $data .= print_note_record(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly);
- if (!$textOnly) {
- if (strpos($noterec, '1 SOUR') !== false) {
- $data .= print_fact_sources($noterec, 1);
- }
- }
- }
- } else {
- $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
- }
- }
- if (!$textOnly) {
- if (strpos($factrec, "$nlevel SOUR") !== false) {
- $data .= "<div class=\"indent\">";
- $data .= print_fact_sources($nrec, $nlevel);
- $data .= "</div>";
- }
- }
- }
-
- return $data;
-}
-
-/**
- * Print a link for a popup help window.
- *
- * @param string $help_topic
- * @param string $module
- *
- * @return string
- */
-function help_link($help_topic, $module = '') {
- return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;">&nbsp;</span>';
-}
-
-/**
- * Print an external help link to the wiki site, in a new window
- *
- * @param string $topic
- *
- * @return string
- */
-function wiki_help_link($topic) {
- return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '" target="_blank">&nbsp;</a>';
-}
-
-/**
- * When a user has searched for text, highlight any matches in
- * the displayed string.
- *
- * @param string $string
- *
- * @return string
- */
-function highlight_search_hits($string) {
- global $controller;
-
- if ($controller instanceof SearchController && $controller->query) {
- // TODO: when a search contains multiple words, we search independently.
- // e.g. searching for "FOO BAR" will find records containing both FOO and BAR.
- // However, we only highlight the original search string, not the search terms.
- // The controller needs to provide its "query_terms" array.
- $regex = array();
- foreach (array($controller->query) as $search_term) {
- $regex[] = preg_quote($search_term, '/');
- }
- // Match these strings, provided they do not occur inside HTML tags
- $regex = '(' . implode('|', $regex) . ')(?![^<]*>)';
-
- return preg_replace('/' . $regex . '/i', '<span class="search_hit">$1</span>', $string);
- } else {
- return $string;
- }
-}
-
-/**
- * Print the associations from the associated individuals in $event to the individuals in $record
- *
- * @param Fact $event
- *
- * @return string
- */
-function format_asso_rela_record(Fact $event) {
- $parent = $event->getParent();
- // To whom is this record an assocate?
- if ($parent instanceof Individual) {
- // On an individual page, we just show links to the person
- $associates = array($parent);
- } elseif ($parent instanceof Family) {
- // On a family page, we show links to both spouses
- $associates = $parent->getSpouses();
- } else {
- // On other pages, it does not make sense to show associates
- return '';
- }
-
- preg_match_all('/^1 ASSO @(' . WT_REGEX_XREF . ')@((\n[2-9].*)*)/', $event->getGedcom(), $amatches1, PREG_SET_ORDER);
- preg_match_all('/\n2 _?ASSO @(' . WT_REGEX_XREF . ')@((\n[3-9].*)*)/', $event->getGedcom(), $amatches2, PREG_SET_ORDER);
-
- $html = '';
- // For each ASSO record
- foreach (array_merge($amatches1, $amatches2) as $amatch) {
- $person = Individual::getInstance($amatch[1], $event->getParent()->getTree());
- if ($person && $person->canShowName()) {
- // Is there a "RELA" tag
- if (preg_match('/\n[23] RELA (.+)/', $amatch[2], $rmatch)) {
- // Use the supplied relationship as a label
- $label = GedcomCodeRela::getValue($rmatch[1], $person);
- } else {
- // Use a default label
- $label = GedcomTag::getLabel('ASSO', $person);
- }
-
- $values = array('<a href="' . $person->getHtmlUrl() . '">' . $person->getFullName() . '</a>');
- if (!Auth::isSearchEngine()) {
- foreach ($associates as $associate) {
- $relationship_name = get_associate_relationship_name($associate, $person);
- if (!$relationship_name) {
- $relationship_name = GedcomTag::getLabel('RELA');
- }
-
- if ($parent instanceof Family) {
- // For family ASSO records (e.g. MARR), identify the spouse with a sex icon
- $relationship_name .= $associate->getSexImage();
- }
-
- $values[] = '<a href="relationship.php?pid1=' . $associate->getXref() . '&amp;pid2=' . $person->getXref() . '&amp;ged=' . $associate->getTree()->getNameUrl() . '">' . $relationship_name . '</a>';
- }
- }
- $value = implode(' — ', $values);
-
- // Use same markup as GedcomTag::getLabelValue()
- $asso = I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $label, $value);
- } elseif (!$person && Auth::isEditor($event->getParent()->getTree())) {
- $asso = GedcomTag::getLabelValue('ASSO', '<span class="error">' . $amatch[1] . '</span>');
- } else {
- $asso = '';
- }
- $html .= '<div class="fact_ASSO">' . $asso . '</div>';
- }
-
- return $html;
-}
-
-/**
- * Format age of parents in HTML
- *
- * @param Individual $person child
- * @param Date $birth_date
- *
- * @return string HTML
- */
-function format_parents_age(Individual $person, Date $birth_date) {
- $html = '';
- $families = $person->getChildFamilies();
- // Multiple sets of parents (e.g. adoption) cause complications, so ignore.
- if ($birth_date->isOK() && count($families) == 1) {
- $family = current($families);
- foreach ($family->getSpouses() as $parent) {
- if ($parent->getBirthDate()->isOK()) {
- $sex = $parent->getSexImage();
- $age = Date::getAge($parent->getBirthDate(), $birth_date, 2);
- $deatdate = $parent->getDeathDate();
- switch ($parent->getSex()) {
- case 'F':
- // Highlight mothers who die in childbirth or shortly afterwards
- if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
- $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
- } else {
- $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
- }
- break;
- case 'M':
- // Highlight fathers who die before the birth
- if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
- $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
- } else {
- $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
- }
- break;
- default:
- $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
- break;
- }
- }
- }
- if ($html) {
- $html = '<span class="age">' . $html . '</span>';
- }
- }
-
- return $html;
-}
-
-/**
- * Print fact DATE/TIME
- *
- * @param Fact $event event containing the date/age
- * @param GedcomRecord $record the person (or couple) whose ages should be printed
- * @param bool $anchor option to print a link to calendar
- * @param bool $time option to print TIME value
- *
- * @return string
- */
-function format_fact_date(Fact $event, GedcomRecord $record, $anchor, $time) {
- global $pid;
-
- $factrec = $event->getGedcom();
- $html = '';
- // Recorded age
- if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
- $fact_age = $match[1];
- } else {
- $fact_age = '';
- }
- if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
- $husb_age = $match[1];
- } else {
- $husb_age = '';
- }
- if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
- $wife_age = $match[1];
- } else {
- $wife_age = '';
- }
-
- // Calculated age
- if (preg_match('/\n2 DATE (.+)/', $factrec, $match)) {
- $date = new Date($match[1]);
- $html .= ' ' . $date->display($anchor && !Auth::isSearchEngine());
- // time
- if ($time && preg_match('/\n3 TIME (.+)/', $factrec, $match)) {
- $html .= ' – <span class="date">' . $match[1] . '</span>';
- }
- $fact = $event->getTag();
- if ($record instanceof Individual) {
- if ($fact === 'BIRT' && $record->getTree()->getPreference('SHOW_PARENTS_AGE')) {
- // age of parents at child birth
- $html .= format_parents_age($record, $date);
- } elseif ($fact !== 'CHAN' && $fact !== '_TODO') {
- // age at event
- $birth_date = $record->getBirthDate();
- // Can't use getDeathDate(), as this also gives BURI/CREM events, which
- // wouldn't give the correct "days after death" result for people with
- // no DEAT.
- $death_event = $record->getFirstFact('DEAT');
- if ($death_event) {
- $death_date = $death_event->getDate();
- } else {
- $death_date = new Date('');
- }
- $ageText = '';
- if ((Date::compare($date, $death_date) <= 0 || !$record->isDead()) || $fact == 'DEAT') {
- // Before death, print age
- $age = Date::getAgeGedcom($birth_date, $date);
- // Only show calculated age if it differs from recorded age
- if ($age != '') {
- if (
- $fact_age != '' && $fact_age != $age ||
- $fact_age == '' && $husb_age == '' && $wife_age == '' ||
- $husb_age != '' && $record->getSex() == 'M' && $husb_age != $age ||
- $wife_age != '' && $record->getSex() == 'F' && $wife_age != $age
- ) {
- if ($age != "0d") {
- $ageText = '(' . I18N::translate('Age') . ' ' . get_age_at_event($age, false) . ')';
- }
- }
- }
- }
- if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) {
- // After death, print time since death
- $age = get_age_at_event(Date::getAgeGedcom($death_date, $date), true);
- if ($age != '') {
- if (Date::getAgeGedcom($death_date, $date) == "0d") {
- $ageText = '(' . I18N::translate('on the date of death') . ')';
- } else {
- $ageText = '(' . $age . ' ' . I18N::translate('after death') . ')';
- // Family events which occur after death are probably errors
- if ($event->getParent() instanceof Family) {
- $ageText .= '<i class="icon-warning"></i>';
- }
- }
- }
- }
- if ($ageText) {
- $html .= ' <span class="age">' . $ageText . '</span>';
- }
- }
- } elseif ($record instanceof Family) {
- $indi = Individual::getInstance($pid, $record->getTree());
- if ($indi) {
- $birth_date = $indi->getBirthDate();
- $death_date = $indi->getDeathDate();
- $ageText = '';
- if (Date::compare($date, $death_date) <= 0) {
- $age = Date::getAgeGedcom($birth_date, $date);
- // Only show calculated age if it differs from recorded age
- if ($age != '' && $age > 0) {
- if (
- $fact_age != '' && $fact_age != $age ||
- $fact_age == '' && $husb_age == '' && $wife_age == '' ||
- $husb_age != '' && $indi->getSex() == 'M' && $husb_age != $age ||
- $wife_age != '' && $indi->getSex() == 'F' && $wife_age != $age
- ) {
- $ageText = '(' . I18N::translate('Age') . ' ' . get_age_at_event($age, false) . ')';
- }
- }
- }
- if ($ageText) {
- $html .= ' <span class="age">' . $ageText . '</span>';
- }
- }
- }
- } else {
- // 1 DEAT Y with no DATE => print YES
- // 1 BIRT 2 SOUR @S1@ => print YES
- // 1 DEAT N is not allowed
- // It is not proper GEDCOM form to use a N(o) value with an event tag to infer that it did not happen.
- $factdetail = explode(' ', trim($factrec));
- if (isset($factdetail) && (count($factdetail) == 3 && strtoupper($factdetail[2]) == 'Y') || (count($factdetail) == 4 && $factdetail[2] == 'SOUR')) {
- $html .= I18N::translate('yes');
- }
- }
- // print gedcom ages
- foreach (array(GedcomTag::getLabel('AGE') => $fact_age, GedcomTag::getLabel('HUSB') => $husb_age, GedcomTag::getLabel('WIFE') => $wife_age) as $label => $age) {
- if ($age != '') {
- $html .= ' <span class="label">' . $label . ':</span> <span class="age">' . get_age_at_event($age, false) . '</span>';
- }
- }
-
- return $html;
-}
-
-/**
- * print fact PLACe TEMPle STATus
- *
- * @param Fact $event gedcom fact record
- * @param bool $anchor to print a link to placelist
- * @param bool $sub_records to print place subrecords
- * @param bool $lds to print LDS TEMPle and STATus
- *
- * @return string HTML
- */
-function format_fact_place(Fact $event, $anchor = false, $sub_records = false, $lds = false) {
- if ($anchor) {
- // Show the full place name, for facts/events tab
- $html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>';
- } else {
- // Abbreviate the place name, for chart boxes
- return ' - ' . $event->getPlace()->getShortName();
- }
-
- if ($sub_records) {
- $placerec = get_sub_record(2, '2 PLAC', $event->getGedcom());
- if (!empty($placerec)) {
- if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) {
- foreach ($matches[1] as $match) {
- $wt_place = new Place($match, $event->getParent()->getTree());
- $html .= ' - ' . $wt_place->getFullName();
- }
- }
- $map_lati = "";
- $cts = preg_match('/\d LATI (.*)/', $placerec, $match);
- if ($cts > 0) {
- $map_lati = $match[1];
- $html .= '<br><span class="label">' . GedcomTag::getLabel('LATI') . ': </span>' . $map_lati;
- }
- $map_long = '';
- $cts = preg_match('/\d LONG (.*)/', $placerec, $match);
- if ($cts > 0) {
- $map_long = $match[1];
- $html .= ' <span class="label">' . GedcomTag::getLabel('LONG') . ': </span>' . $map_long;
- }
- if ($map_lati && $map_long) {
- $map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789
- $map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456
- $html .= ' <a rel="nofollow" href="https://maps.google.com/maps?q=' . $map_lati . ',' . $map_long . '" class="icon-googlemaps" title="' . I18N::translate('Google Maps™') . '"></a>';
- $html .= ' <a rel="nofollow" href="https://www.bing.com/maps/?lvl=15&cp=' . $map_lati . '~' . $map_long . '" class="icon-bing" title="' . I18N::translate('Bing Maps™') . '"></a>';
- $html .= ' <a rel="nofollow" href="https://www.openstreetmap.org/#map=15/' . $map_lati . '/' . $map_long . '" class="icon-osm" title="' . I18N::translate('OpenStreetMap™') . '"></a>';
- }
- if (preg_match('/\d NOTE (.*)/', $placerec, $match)) {
- $html .= '<br>' . print_fact_notes($placerec, 3);
- }
- }
- }
- if ($lds) {
- if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) {
- $html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]);
- }
- if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) {
- $html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]);
- if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) {
- $date = new Date($match[1]);
- $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display();
- }
- }
- }
-
- return $html;
-}
-
-/**
- * Check for facts that may exist only once for a certain record type.
- * If the fact already exists in the second array, delete it from the first one.
- *
- * @param string[] $uniquefacts
- * @param Fact[] $recfacts
- * @param string $type
- *
- * @return string[]
- */
-function CheckFactUnique($uniquefacts, $recfacts, $type) {
- foreach ($recfacts as $factarray) {
- $fact = false;
- if (is_object($factarray)) {
- $fact = $factarray->getTag();
- } else {
- if ($type === 'SOUR' || $type === 'REPO') {
- $factrec = $factarray[0];
- }
- if ($type === 'FAM' || $type === 'INDI') {
- $factrec = $factarray[1];
- }
-
- $ft = preg_match("/1 (\w+)(.*)/", $factrec, $match);
- if ($ft > 0) {
- $fact = trim($match[1]);
- }
- }
- if ($fact !== false) {
- $key = array_search($fact, $uniquefacts);
- if ($key !== false) {
- unset($uniquefacts[$key]);
- }
- }
- }
-
- return $uniquefacts;
-}
-
-/**
- * Print a new fact box on details pages
- *
- * @param string $id the id of the person, family, source etc the fact will be added to
- * @param array $usedfacts an array of facts already used in this record
- * @param string $type the type of record INDI, FAM, SOUR etc
- */
-function print_add_new_fact($id, $usedfacts, $type) {
- global $WT_TREE;
-
- // -- Add from clipboard
- if (is_array(Session::get('clipboard'))) {
- $newRow = true;
- foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) {
- if ($fact["type"] == $type || $fact["type"] == 'all') {
- if ($newRow) {
- $newRow = false;
- echo '<tr><td class="descriptionbox">';
- echo I18N::translate('Add from clipboard'), '</td>';
- echo '<td class="optionbox wrap"><form method="get" name="newFromClipboard" action="?" onsubmit="return false;">';
- echo '<select id="newClipboardFact">';
- }
- echo '<option value="', Filter::escapeHtml($fact_id), '">', GedcomTag::getLabel($fact['fact']);
- // TODO use the event class to store/parse the clipboard events
- if (preg_match('/^2 DATE (.+)/m', $fact['factrec'], $match)) {
- $tmp = new Date($match[1]);
- echo '; ', $tmp->minimumDate()->format('%Y');
- }
- if (preg_match('/^2 PLAC ([^,\n]+)/m', $fact['factrec'], $match)) {
- echo '; ', $match[1];
- }
- echo '</option>';
- }
- }
- if (!$newRow) {
- echo '</select>';
- echo '&nbsp;&nbsp;<input type="button" value="', I18N::translate('Add'), "\" onclick=\"return paste_fact('$id', '#newClipboardFact');\"> ";
- echo '</form></td></tr>', "\n";
- }
- }
-
- // -- Add from pick list
- switch ($type) {
- case "INDI":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "FAM":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "SOUR":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "NOTE":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "REPO":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- default:
- return;
- }
- $addfacts = array_merge(CheckFactUnique($uniquefacts, $usedfacts, $type), $addfacts);
- $quickfacts = array_intersect($quickfacts, $addfacts);
- $translated_addfacts = array();
- foreach ($addfacts as $addfact) {
- $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact);
- }
- uasort($translated_addfacts, function ($x, $y) {
- return I18N::strcasecmp(I18N::translate($x), I18N::translate($y));
- });
- echo '<tr><td class="descriptionbox">';
- echo I18N::translate('Fact or event');
- echo '</td>';
- echo '<td class="optionbox wrap">';
- echo '<form method="get" name="newfactform" action="?" onsubmit="return false;">';
- echo '<select id="newfact" name="newfact">';
- echo '<option value="" disabled selected>' . I18N::translate('&lt;select&gt;') . '</option>';
- foreach ($translated_addfacts as $fact => $fact_name) {
- echo '<option value="', $fact, '">', $fact_name, '</option>';
- }
- if ($type == 'INDI' || $type == 'FAM') {
- echo '<option value="FACT">', I18N::translate('Custom fact'), '</option>';
- echo '<option value="EVEN">', I18N::translate('Custom event'), '</option>';
- }
- echo '</select>';
- echo '<input type="button" value="', I18N::translate('Add'), '" onclick="add_record(\'' . $id . '\', \'newfact\');">';
- echo '<span class="quickfacts">';
- foreach ($quickfacts as $fact) {
- echo '<a href="#" onclick="add_new_record(\'' . $id . '\', \'' . $fact . '\');return false;">', GedcomTag::getLabel($fact), '</a>';
- }
- echo '</span></form>';
- echo '</td></tr>';
-}
-
-/**
- * javascript declaration for calendar popup
- */
-function init_calendar_popup() {
- global $controller;
-
- $controller->addInlineJavascript('
- cal_setMonthNames(
- "' . I18N::translateContext('NOMINATIVE', 'January') . '",
- "' . I18N::translateContext('NOMINATIVE', 'February') . '",
- "' . I18N::translateContext('NOMINATIVE', 'March') . '",
- "' . I18N::translateContext('NOMINATIVE', 'April') . '",
- "' . I18N::translateContext('NOMINATIVE', 'May') . '",
- "' . I18N::translateContext('NOMINATIVE', 'June') . '",
- "' . I18N::translateContext('NOMINATIVE', 'July') . '",
- "' . I18N::translateContext('NOMINATIVE', 'August') . '",
- "' . I18N::translateContext('NOMINATIVE', 'September') . '",
- "' . I18N::translateContext('NOMINATIVE', 'October') . '",
- "' . I18N::translateContext('NOMINATIVE', 'November') . '",
- "' . I18N::translateContext('NOMINATIVE', 'December') . '"
- )
- cal_setDayHeaders(
- "' . I18N::translate('Sun') . '",
- "' . I18N::translate('Mon') . '",
- "' . I18N::translate('Tue') . '",
- "' . I18N::translate('Wed') . '",
- "' . I18N::translate('Thu') . '",
- "' . I18N::translate('Fri') . '",
- "' . I18N::translate('Sat') . '"
- )
- cal_setWeekStart(' . I18N::firstDay() . ');
- ');
-}
-
-/**
- * @param string $element_id
- * @param string $indiname
- * @param Tree $tree
- *
- * @return string
- */
-function print_findindi_link($element_id, $indiname = '', $tree = null) {
- global $WT_TREE;
-
- if ($tree === null) {
- $tree = $WT_TREE;
- }
-
- return '<a href="#" onclick="findIndi(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $indiname . '\'), \'' . $tree->getNameHtml() . '\'); return false;" class="icon-button_indi" title="' . I18N::translate('Find an individual') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findplace_link($element_id) {
- return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findfamily_link($element_id) {
- return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_specialchar_link($element_id) {
- return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>';
-}
-
-/**
- * @param string $element_id
- * @param string[] $choices
- */
-function print_autopaste_link($element_id, $choices) {
- echo '<small>';
- foreach ($choices as $choice) {
- echo '<span onclick="document.getElementById(\'', $element_id, '\').value=';
- echo '\'', $choice, '\';';
- echo " return false;\">", $choice, '</span> ';
- }
- echo '</small>';
-}
-
-/**
- * @param string $element_id
- * @param string $sourcename
- *
- * @return string
- */
-function print_findsource_link($element_id, $sourcename = '') {
- return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>';
-}
-
-/**
- * @param string $element_id
- * @param string $notename
- *
- * @return string
- */
-function print_findnote_link($element_id, $notename = '') {
- return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findrepository_link($element_id) {
- return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>';
-}
-
-/**
- * @param string $element_id
- * @param string $choose
- *
- * @return string
- */
-function print_findmedia_link($element_id, $choose = '') {
- return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findfact_link($element_id) {
- return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>';
-}
-
-/**
- * Summary of LDS ordinances.
- *
- * @param Individual $individual
- *
- * @return string
- */
-function get_lds_glance(Individual $individual) {
- $BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
- $ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
- $SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
- $SLGS = '_';
-
- foreach ($individual->getSpouseFamilies() as $family) {
- if ($family->getFacts('SLGS')) {
- $SLGS = '';
- }
- }
-
- return $BAPL . $ENDL . $SLGS . $SLGC;
-}
diff --git a/includes/functions/functions_print_facts.php b/includes/functions/functions_print_facts.php
deleted file mode 100644
index 6d66cbe772..0000000000
--- a/includes/functions/functions_print_facts.php
+++ /dev/null
@@ -1,1144 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\CensusAssistantModule;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Theme;
-use Fisharebest\Webtrees\User;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * Print a fact record, for the individual/family/source/repository/etc. pages.
- *
- * Although a Fact has a parent object, we also need to know
- * the GedcomRecord for which we are printing it. For example,
- * we can show the death of X on the page of Y, or the marriage
- * of X+Y on the page of Z. We need to know both records to
- * calculate ages, relationships, etc.
- *
- * @param Fact $fact
- * @param GedcomRecord $record
- */
-function print_fact(Fact $fact, GedcomRecord $record) {
- static $n_chil = 0, $n_gchi = 0;
-
- $parent = $fact->getParent();
-
- // Some facts don't get printed here ...
- switch ($fact->getTag()) {
- case 'NOTE':
- print_main_notes($fact, 1);
-
- return;
- case 'SOUR':
- print_main_sources($fact, 1);
-
- return;
- case 'OBJE':
- print_main_media($fact, 1);
-
- return;
- case 'FAMC':
- case 'FAMS':
- case 'CHIL':
- case 'HUSB':
- case 'WIFE':
- // These are internal links, not facts
- return;
- case '_WT_OBJE_SORT':
- // These links are used internally to record the sort order.
- return;
- default:
- // Hide unrecognized/custom tags?
- if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') && !GedcomTag::isTag($fact->getTag())) {
- return;
- }
- break;
- }
-
- // Who is this fact about? Need it to translate fact label correctly
- if ($parent instanceof Family && $record instanceof Individual) {
- // Family event
- $label_person = $fact->getParent()->getSpouse($record);
- } else {
- // Individual event
- $label_person = $parent;
- }
-
- // New or deleted facts need different styling
- $styleadd = '';
- if ($fact->isPendingAddition()) {
- $styleadd = 'new';
- }
- if ($fact->isPendingDeletion()) {
- $styleadd = 'old';
- }
-
- // Event of close relative
- if (preg_match('/^_[A-Z_]{3,5}_[A-Z0-9]{4}$/', $fact->getTag())) {
- $styleadd = trim($styleadd . ' rela');
- }
-
- // Event of close associates
- if ($fact->getFactId() == 'asso') {
- $styleadd = trim($styleadd . ' rela');
- }
-
- // historical facts
- if ($fact->getFactId() == 'histo') {
- $styleadd = trim($styleadd . ' histo');
- }
-
- // Does this fact have a type?
- if (preg_match('/\n2 TYPE (.+)/', $fact->getGedcom(), $match)) {
- $type = $match[1];
- } else {
- $type = '';
- }
-
- switch ($fact->getTag()) {
- case 'EVEN':
- case 'FACT':
- if (GedcomTag::isTag($type)) {
- // Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
- $label = GedcomTag::getLabel($type, $label_person);
- $type = ''; // Do not print this again
- } elseif ($type) {
- // We don't have a translation for $type - but a custom translation might exist.
- $label = I18N::translate(Filter::escapeHtml($type));
- $type = ''; // Do not print this again
- } else {
- // An unspecified fact/event
- $label = $fact->getLabel();
- }
- break;
- case 'MARR':
- // This is a hack for a proprietory extension. Is it still used/needed?
- $utype = strtoupper($type);
- if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
- $label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
- $type = ''; // Do not print this again
- } else {
- $label = $fact->getLabel();
- }
- break;
- default:
- // Normal fact/event
- $label = $fact->getLabel();
- break;
- }
-
- echo '<tr class="', $styleadd, '">';
- echo '<td class="descriptionbox width20">';
-
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- echo Theme::theme()->icon($fact), ' ';
- }
-
- if ($fact->getFactId() != 'histo' && $fact->canEdit()) {
- ?>
- <a
- href="#"
- title="<?php echo I18N::translate('Edit'); ?>"
- onclick="return edit_record('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><?php echo $label; ?></a>
- <div class="editfacts">
- <div class="editlink">
- <a
- href="#"
- title="<?php echo I18N::translate('Edit'); ?>"
- class="editicon"
- onclick="return edit_record('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><span class="link_text"><?php echo I18N::translate('Edit'); ?></span></a>
- </div>
- <div class="copylink">
- <a
- href="#"
- title="<?php echo I18N::translate('Copy'); ?>"
- class="copyicon"
- onclick="return copy_fact('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><span class="link_text"><?php echo I18N::translate('Copy'); ?></span></a>
- </div>
- <div class="deletelink">
- <a
- href="#"
- title="<?php echo I18N::translate('Delete'); ?>"
- class="deleteicon"
- onclick="return delete_fact('<?php echo I18N::translate('Are you sure you want to delete this fact?'); ?>', '<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><span class="link_text"><?php echo I18N::translate('Delete'); ?></span></a>
- </div>
- </div>
- <?php
- } else {
- echo $label;
- }
-
- switch ($fact->getTag()) {
- case '_BIRT_CHIL':
- echo '<br>', /* I18N: Abbreviation for "number %s" */
- I18N::translate('#%s', ++$n_chil);
- break;
- case '_BIRT_GCHI':
- case '_BIRT_GCH1':
- case '_BIRT_GCH2':
- echo '<br>', I18N::translate('#%s', ++$n_gchi);
- break;
- }
-
- echo '</td><td class="optionbox ', $styleadd, ' wrap">';
-
- // Event from another record?
- if ($parent !== $record) {
- if ($parent instanceof Family) {
- foreach ($parent->getSpouses() as $spouse) {
- if ($record !== $spouse) {
- echo '<a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a> — ';
- }
- }
- echo '<a href="', $parent->getHtmlUrl(), '">', I18N::translate('View family'), '</a><br>';
- } elseif ($parent instanceof Individual) {
- echo '<a href="', $parent->getHtmlUrl(), '">', $parent->getFullName(), '</a><br>';
- }
- }
-
- // Print the value of this fact/event
- switch ($fact->getTag()) {
- case 'ADDR':
- echo $fact->getValue();
- break;
- case 'AFN':
- echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', rawurlencode($fact->getValue()), '" target="new">', Filter::escapeHtml($fact->getValue()), '</a></div>';
- break;
- case 'ASSO':
- // we handle this later, in format_asso_rela_record()
- break;
- case 'EMAIL':
- case 'EMAI':
- case '_EMAIL':
- echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
- break;
- case 'FILE':
- if (Auth::isEditor($fact->getParent()->getTree())) {
- echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
- }
- break;
- case 'RESN':
- echo '<div class="field">';
- switch ($fact->getValue()) {
- case 'none':
- // Note: "1 RESN none" is not valid gedcom.
- // However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
- echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
- break;
- case 'privacy':
- echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
- break;
- case 'confidential':
- echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
- break;
- case 'locked':
- echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
- break;
- default:
- echo Filter::escapeHtml($fact->getValue());
- break;
- }
- echo '</div>';
- break;
- case 'PUBL': // Publication details might contain URLs.
- echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
- break;
- case 'REPO':
- if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
- print_repository_record($match[1]);
- } else {
- echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
- }
- break;
- case 'URL':
- case '_URL':
- case 'WWW':
- echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
- break;
- case 'TEXT': // 0 SOUR / 1 TEXT
- echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
- break;
- default:
- // Display the value for all other facts/events
- switch ($fact->getValue()) {
- case '':
- // Nothing to display
- break;
- case 'N':
- // Not valid GEDCOM
- echo '<div class="field">', I18N::translate('No'), '</div>';
- break;
- case 'Y':
- // Do not display "Yes".
- break;
- default:
- if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
- $target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
- if ($target) {
- echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
- } else {
- echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
- }
- } else {
- echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
- }
- break;
- }
- break;
- }
-
- // Print the type of this fact/event
- if ($type) {
- $utype = strtoupper($type);
- // Events of close relatives, e.g. _MARR_CHIL
- if (substr($fact->getTag(), 0, 6) == '_MARR_' && ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS')) {
- // Translate MARR/TYPE using the code that supports MARR_CIVIL, etc. tags
- $type = GedcomTag::getLabel('MARR_' . $utype);
- } else {
- // Allow (custom) translations for other types
- $type = I18N::translate($type);
- }
- echo GedcomTag::getLabelValue('TYPE', Filter::escapeHtml($type));
- }
-
- // Print the date of this fact/event
- echo format_fact_date($fact, $record, true, true);
-
- // Print the place of this fact/event
- echo '<div class="place">', format_fact_place($fact, true, true, true), '</div>';
- // A blank line between the primary attributes (value, date, place) and the secondary ones
- echo '<br>';
-
- $addr = $fact->getAttribute('ADDR');
- if ($addr) {
- echo GedcomTag::getLabelValue('ADDR', $addr);
- }
-
- // Print the associates of this fact/event
- echo format_asso_rela_record($fact);
-
- // Print any other "2 XXXX" attributes, in the order in which they appear.
- preg_match_all('/\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- switch ($match[1]) {
- case 'DATE':
- case 'TIME':
- case 'AGE':
- case 'PLAC':
- case 'ADDR':
- case 'ALIA':
- case 'ASSO':
- case '_ASSO':
- case 'DESC':
- case 'RELA':
- case 'STAT':
- case 'TEMP':
- case 'TYPE':
- case 'FAMS':
- case 'CONT':
- // These were already shown at the beginning
- break;
- case 'NOTE':
- case 'OBJE':
- case 'SOUR':
- // These will be shown at the end
- break;
- case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
- $events = array();
- foreach (preg_split('/ *, */', $match[2]) as $event) {
- $events[] = GedcomTag::getLabel($event);
- }
- if (count($events) == 1) {
- echo GedcomTag::getLabelValue('EVEN', $event);
- } else {
- echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
- }
- if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
- $date = new Date($date_match[1]);
- echo GedcomTag::getLabelValue('DATE', $date->display());
- }
- if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
- echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
- }
- break;
- case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
- $family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
- if ($family) {
- echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
- if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
- echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
- }
- } else {
- echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
- }
- break;
- case '_WT_USER':
- $user = User::findByIdentifier($match[2]); // may not exist
- if ($user) {
- echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
- } else {
- echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
- }
- break;
- case 'RESN':
- switch ($match[2]) {
- case 'none':
- // Note: "2 RESN none" is not valid gedcom.
- // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
- break;
- case 'privacy':
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
- break;
- case 'confidential':
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
- break;
- case 'locked':
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
- break;
- default:
- echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
- break;
- }
- break;
- case 'CALN':
- echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
- break;
- case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
- echo GedcomTag::getLabelValue('FORM', $match[2]);
- if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
- echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
- }
- break;
- case 'URL':
- case '_URL':
- case 'WWW':
- $link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
- break;
- default:
- if (!$fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') || GedcomTag::isTag($match[1])) {
- if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
- // Links
- $linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
- if ($linked_record) {
- $link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
- } else {
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
- }
- } else {
- // Non links
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
- }
- }
- break;
- }
- }
- echo print_fact_sources($fact->getGedcom(), 2);
- echo print_fact_notes($fact->getGedcom(), 2);
- print_media_links($fact->getGedcom(), 2);
- echo '</td></tr>';
-}
-
-/**
- * print a repository record
- *
- * find and print repository information attached to a source
- *
- * @param string $xref the Gedcom Xref ID of the repository to print
- */
-function print_repository_record($xref) {
- global $WT_TREE;
-
- $repository = Repository::getInstance($xref, $WT_TREE);
- if ($repository && $repository->canShow()) {
- echo '<a class="field" href="', $repository->getHtmlUrl(), '">', $repository->getFullName(), '</a><br>';
- echo '<br>';
- echo print_fact_notes($repository->getGedcom(), 1);
- }
-}
-
-/**
- * print a source linked to a fact (2 SOUR)
- *
- * this function is called by the print_fact function and other functions to
- * print any source information attached to the fact
- *
- * @param string $factrec The fact record to look for sources in
- * @param int $level The level to look for sources at
- *
- * @return string HTML text
- */
-function print_fact_sources($factrec, $level) {
- global $WT_TREE;
-
- $data = '';
- $nlevel = $level + 1;
-
- // -- Systems not using source records [ 1046971 ]
- $ct = preg_match_all("/$level SOUR (.*)/", $factrec, $match, PREG_SET_ORDER);
- for ($j = 0; $j < $ct; $j++) {
- if (strpos($match[$j][1], '@') === false) {
- $data .= '<div class="fact_SOUR"><span class="label">' . I18N::translate('Source') . ':</span> <span class="field" dir="auto">' . Filter::escapeHtml($match[$j][1]) . '</span></div>';
- }
- }
- // -- find source for each fact
- $ct = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
- $spos2 = 0;
- for ($j = 0; $j < $ct; $j++) {
- $sid = $match[$j][1];
- $source = Source::getInstance($sid, $WT_TREE);
- if ($source) {
- if ($source->canShow()) {
- $spos1 = strpos($factrec, "$level SOUR @" . $sid . "@", $spos2);
- $spos2 = strpos($factrec, "\n$level", $spos1);
- if (!$spos2) {
- $spos2 = strlen($factrec);
- }
- $srec = substr($factrec, $spos1, $spos2 - $spos1);
- $lt = preg_match_all("/$nlevel \w+/", $srec, $matches);
- $data .= '<div class="fact_SOUR">';
- $data .= '<span class="label">';
- $elementID = Uuid::uuid4();
- if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
- $plusminus = 'icon-minus';
- } else {
- $plusminus = 'icon-plus';
- }
- if ($lt > 0) {
- $data .= '<a href="#" onclick="return expand_layer(\'' . $elementID . '\');"><i id="' . $elementID . '_img" class="' . $plusminus . '"></i></a> ';
- }
- $data .= I18N::translate('Source') . ':</span> <span class="field">';
- $data .= '<a href="' . $source->getHtmlUrl() . '">' . $source->getFullName() . '</a>';
- $data .= '</span></div>';
-
- $data .= "<div id=\"$elementID\"";
- if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
- $data .= ' style="display:block"';
- }
- $data .= ' class="source_citations">';
- // PUBL
- $publ = $source->getFirstFact('PUBL');
- if ($publ) {
- $data .= GedcomTag::getLabelValue('PUBL', $publ->getValue());
- }
- $data .= printSourceStructure(getSourceStructure($srec));
- $data .= '<div class="indent">';
- ob_start();
- print_media_links($srec, $nlevel);
- $data .= ob_get_clean();
- $data .= print_fact_notes($srec, $nlevel, false);
- $data .= '</div>';
- $data .= '</div>';
- } else {
- // Here we could show that we do actually have sources for this data,
- // but not the details. For example “Sources: ”.
- // But not by default, based on user feedback.
- // http://webtrees.net/index.php/en/forum/3-help-for-beta-and-svn-versions/27002-source-media-privacy-issue
- }
- } else {
- $data .= GedcomTag::getLabelValue('SOUR', '<span class="error">' . $sid . '</span>');
- }
- }
-
- return $data;
-}
-
-/**
- * Print the links to media objects
- *
- * @param string $factrec
- * @param int $level
- */
-function print_media_links($factrec, $level) {
- global $WT_TREE;
-
- $nlevel = $level + 1;
- if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) == 0) {
- return;
- }
- $objectNum = 0;
- while ($objectNum < count($omatch)) {
- $media_id = $omatch[$objectNum][1];
- $media = Media::getInstance($media_id, $WT_TREE);
- if ($media) {
- if ($media->canShow()) {
- if ($objectNum > 0) {
- echo '<br class="media-separator" style="clear:both;">';
- }
- echo '<div class="media-display"><div class="media-display-image">';
- echo $media->displayImage();
- echo '</div>';
- echo '<div class="media-display-title">';
- echo '<a href="', $media->getHtmlUrl(), '">', $media->getFullName(), '</a>';
- // NOTE: echo the notes of the media
- echo '<p>';
- echo print_fact_notes($media->getGedcom(), 1);
- $ttype = preg_match("/" . ($nlevel + 1) . " TYPE (.*)/", $media->getGedcom(), $match);
- if ($ttype > 0) {
- $mediaType = GedcomTag::getFileFormTypeValue($match[1]);
- echo '<p class="label">', I18N::translate('Type'), ': </span> <span class="field">', $mediaType, '</p>';
- }
- echo '</p>';
- //-- print spouse name for marriage events
- $ct = preg_match("/WT_SPOUSE: (.*)/", $factrec, $match);
- if ($ct > 0) {
- $spouse = Individual::getInstance($match[1], $media->getTree());
- if ($spouse) {
- echo '<a href="', $spouse->getHtmlUrl(), '">';
- echo $spouse->getFullName();
- echo '</a>';
- }
- $ct = preg_match("/WT_FAMILY_ID: (.*)/", $factrec, $match);
- if ($ct > 0) {
- $famid = trim($match[1]);
- $family = Family::getInstance($famid, $spouse->getTree());
- if ($family) {
- if ($spouse) {
- echo " - ";
- }
- echo '<a href="', $family->getHtmlUrl(), '">', I18N::translate('View family'), '</a>';
- }
- }
- }
- echo print_fact_notes($media->getGedcom(), $nlevel);
- echo print_fact_sources($media->getGedcom(), $nlevel);
- echo '</div>'; //close div "media-display-title"
- echo '</div>'; //close div "media-display"
- }
- } elseif (!$WT_TREE->getPreference('HIDE_GEDCOM_ERRORS')) {
- echo '<p class="ui-state-error">', $media_id, '</p>';
- }
- $objectNum++;
- }
-}
-
-/**
- * Print a row for the sources tab on the individual page.
- *
- * @param Fact $fact
- * @param int $level
- */
-function print_main_sources(Fact $fact, $level) {
- $factrec = $fact->getGedcom();
- $fact_id = $fact->getFactId();
- $parent = $fact->getParent();
- $pid = $parent->getXref();
-
- $nlevel = $level + 1;
- if ($fact->isPendingAddition()) {
- $styleadd = 'new';
- $can_edit = $level == 1 && $fact->canEdit();
- } elseif ($fact->isPendingDeletion()) {
- $styleadd = 'old';
- $can_edit = false;
- } else {
- $styleadd = '';
- $can_edit = $level == 1 && $fact->canEdit();
- }
-
- // -- find source for each fact
- $ct = preg_match_all("/($level SOUR (.+))/", $factrec, $match, PREG_SET_ORDER);
- $spos2 = 0;
- for ($j = 0; $j < $ct; $j++) {
- $sid = trim($match[$j][2], '@');
- $spos1 = strpos($factrec, $match[$j][1], $spos2);
- $spos2 = strpos($factrec, "\n$level", $spos1);
- if (!$spos2) {
- $spos2 = strlen($factrec);
- }
- $srec = substr($factrec, $spos1, $spos2 - $spos1);
- $source = Source::getInstance($sid, $fact->getParent()->getTree());
- // Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
- if (!$source || $source->canShow()) {
- if ($level > 1) {
- echo '<tr class="row_sour2">';
- } else {
- echo '<tr>';
- }
- echo '<td class="descriptionbox';
- if ($level > 1) {
- echo ' rela';
- }
- echo ' ', $styleadd, ' width20">';
- $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
- $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
- $factname = $factwords[1]; // BIRT
- if ($factname == 'EVEN' || $factname == 'FACT') {
- // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
- $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
- if ($ct > 0) {
- $factname = trim($ematch[1]);
- echo $factname;
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- } elseif ($can_edit) {
- echo "<a onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"", I18N::translate('Edit'), '">';
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- if ($level == 1) {
- echo '<i class="icon-source"></i> ';
- }
- }
- echo GedcomTag::getLabel($factname, $parent), '</a>';
- echo '<div class="editfacts">';
- if (preg_match('/^@.+@$/', $match[$j][2])) {
- // Inline sources can't be edited. Attempting to save one will convert it
- // into a link, and delete it.
- // e.g. "1 SOUR my source" becomes "1 SOUR @my source@" which does not exist.
- echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
- echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
- }
- echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
- echo '</div>';
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- echo '</td>';
- echo '<td class="optionbox ', $styleadd, ' wrap">';
- if ($source) {
- echo '<a href="', $source->getHtmlUrl(), '">', $source->getFullName(), '</a>';
- // PUBL
- $publ = $source->getFirstFact('PUBL');
- if ($publ) {
- echo GedcomTag::getLabelValue('PUBL', $publ->getValue());
- }
- // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
- if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
- foreach ($rmatches[1] as $rmatch) {
- echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
- switch ($rmatch) {
- case 'none':
- // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
- // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
- echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
- break;
- case 'privacy':
- echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
- break;
- case 'confidential':
- echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
- break;
- case 'locked':
- echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
- break;
- default:
- echo $rmatch;
- break;
- }
- echo '</span>';
- }
- }
- $cs = preg_match("/$nlevel EVEN (.*)/", $srec, $cmatch);
- if ($cs > 0) {
- echo '<br><span class="label">', GedcomTag::getLabel('EVEN'), ' </span><span class="field">', $cmatch[1], '</span>';
- $cs = preg_match("/" . ($nlevel + 1) . " ROLE (.*)/", $srec, $cmatch);
- if ($cs > 0) {
- echo '<br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="label">', GedcomTag::getLabel('ROLE'), ' </span><span class="field">', $cmatch[1], '</span>';
- }
- }
- echo printSourceStructure(getSourceStructure($srec));
- echo '<div class="indent">';
- print_media_links($srec, $nlevel);
- if ($nlevel == 2) {
- print_media_links($source->getGedcom(), 1);
- }
- echo print_fact_notes($srec, $nlevel);
- if ($nlevel == 2) {
- echo print_fact_notes($source->getGedcom(), 1);
- }
- echo '</div>';
- } else {
- echo $sid;
- }
- echo '</td></tr>';
- }
- }
-}
-
-/**
- * Print SOUR structure
- * This function prints the input array of SOUR sub-records built by the
- * getSourceStructure() function.
- *
- * @param string[] $textSOUR
- *
- * @return string
- */
-function printSourceStructure($textSOUR) {
- global $WT_TREE;
- $html = '';
-
- if ($textSOUR['PAGE']) {
- $html .= GedcomTag::getLabelValue('PAGE', Filter::expandUrls($textSOUR['PAGE']));
- }
-
- if ($textSOUR['EVEN']) {
- $html .= GedcomTag::getLabelValue('EVEN', Filter::escapeHtml($textSOUR['EVEN']));
- if ($textSOUR['ROLE']) {
- $html .= GedcomTag::getLabelValue('ROLE', Filter::escapeHtml($textSOUR['ROLE']));
- }
- }
-
- if ($textSOUR['DATE'] || count($textSOUR['TEXT'])) {
- if ($textSOUR['DATE']) {
- $date = new Date($textSOUR['DATE']);
- $html .= GedcomTag::getLabelValue('DATA:DATE', $date->display());
- }
- foreach ($textSOUR['TEXT'] as $text) {
- $html .= GedcomTag::getLabelValue('TEXT', Filter::formatText($text, $WT_TREE));
- }
- }
-
- if ($textSOUR['QUAY'] != '') {
- $html .= GedcomTag::getLabelValue('QUAY', GedcomCodeQuay::getValue($textSOUR['QUAY']));
- }
-
- return '<div class="indent">' . $html . '</div>';
-}
-
-/**
- * Extract SOUR structure from the incoming Source sub-record
- * The output array is defined as follows:
- * $textSOUR['PAGE'] = Source citation
- * $textSOUR['EVEN'] = Event type
- * $textSOUR['ROLE'] = Role in event
- * $textSOUR['DATA'] = place holder (no text in this sub-record)
- * $textSOUR['DATE'] = Entry recording date
- * $textSOUR['TEXT'] = (array) Text from source
- * $textSOUR['QUAY'] = Certainty assessment
- *
- * @param string $srec
- *
- * @return string[]
- */
-function getSourceStructure($srec) {
- // Set up the output array
- $textSOUR = array(
- 'PAGE' => '',
- 'EVEN' => '',
- 'ROLE' => '',
- 'DATA' => '',
- 'DATE' => '',
- 'TEXT' => array(),
- 'QUAY' => '',
- );
-
- if ($srec) {
- $subrecords = explode("\n", $srec);
- for ($i = 0; $i < count($subrecords); $i++) {
- $tag = substr($subrecords[$i], 2, 4);
- $text = substr($subrecords[$i], 7);
- $i++;
- for (; $i < count($subrecords); $i++) {
- $nextTag = substr($subrecords[$i], 2, 4);
- if ($nextTag != 'CONT') {
- $i--;
- break;
- }
- if ($nextTag == 'CONT') {
- $text .= "\n";
- }
- $text .= rtrim(substr($subrecords[$i], 7));
- }
- if ($tag == 'TEXT') {
- $textSOUR[$tag][] = $text;
- } else {
- $textSOUR[$tag] = $text;
- }
- }
- }
-
- return $textSOUR;
-}
-
-/**
- * Print a row for the notes tab on the individual page.
- *
- * @param Fact $fact
- * @param int $level
- */
-function print_main_notes(Fact $fact, $level) {
- $factrec = $fact->getGedcom();
- $fact_id = $fact->getFactId();
- $parent = $fact->getParent();
- $pid = $parent->getXref();
-
- if ($fact->isPendingAddition()) {
- $styleadd = ' new';
- $can_edit = $level == 1 && $fact->canEdit();
- } elseif ($fact->isPendingDeletion()) {
- $styleadd = ' old';
- $can_edit = false;
- } else {
- $styleadd = '';
- $can_edit = $level == 1 && $fact->canEdit();
- }
-
- $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
- for ($j = 0; $j < $ct; $j++) {
- // Note object, or inline note?
- if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
- $note = Note::getInstance($nmatch[1], $fact->getParent()->getTree());
- if ($note && !$note->canShow()) {
- continue;
- }
- } else {
- $note = null;
- }
-
- if ($level >= 2) {
- echo '<tr class="row_note2"><td class="descriptionbox rela ', $styleadd, ' width20">';
- } else {
- echo '<tr><td class="descriptionbox ', $styleadd, ' width20">';
- }
- if ($can_edit) {
- echo '<a onclick="return edit_record(\'', $pid, '\', \'', $fact_id, '\');" href="#" title="', I18N::translate('Edit'), '">';
- if ($level < 2) {
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- echo '<i class="icon-note"></i> ';
- }
- if ($note) {
- echo GedcomTag::getLabel('SHARED_NOTE');
- } else {
- echo GedcomTag::getLabel('NOTE');
- }
- echo '</a>';
- echo '<div class="editfacts">';
- echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
- echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
- echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
- if ($note) {
- echo '<a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a>';
- }
- echo '</div>';
- }
- } else {
- if ($level < 2) {
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- echo '<i class="icon-note"></i> ';
- }
- if ($note) {
- echo GedcomTag::getLabel('SHARED_NOTE');
- } else {
- echo GedcomTag::getLabel('NOTE');
- }
- }
- $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
- $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
- $factname = $factwords[1]; // BIRT
- $parent = GedcomRecord::getInstance($pid, $fact->getParent()->getTree());
- if ($factname == 'EVEN' || $factname == 'FACT') {
- // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
- $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
- if ($ct > 0) {
- $factname = trim($ematch[1]);
- echo $factname;
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- } elseif ($factname != 'NOTE') {
- // Note is already printed
- echo GedcomTag::getLabel($factname, $parent);
- if ($note) {
- echo '<div class="editfacts"><a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a></div>';
-
- }
- }
- }
- echo '</td>';
- if ($note) {
- // Note objects
- if (Module::getModuleByName('GEDFact_assistant')) {
- // If Census assistant installed, allow it to format the note
- $text = CensusAssistantModule::formatCensusNote($note);
- } else {
- $text = Filter::formatText($note->getNote(), $fact->getParent()->getTree());
- }
- } else {
- // Inline notes
- $nrec = get_sub_record($level, "$level NOTE", $factrec, $j + 1);
- $text = $match[$j][1] . get_cont($level + 1, $nrec);
- $text = Filter::formatText($text, $fact->getParent()->getTree());
- }
-
- echo '<td class="optionbox', $styleadd, ' wrap">';
- echo $text;
-
- if (!empty($noterec)) {
- echo print_fact_sources($noterec, 1);
- }
-
- // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
- if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
- foreach ($matches[1] as $match) {
- echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
- switch ($match) {
- case 'none':
- // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
- // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
- echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
- break;
- case 'privacy':
- echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
- break;
- case 'confidential':
- echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
- break;
- case 'locked':
- echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
- break;
- default:
- echo $match;
- break;
- }
- echo '</span>';
- }
- }
- echo '</td></tr>';
- }
-}
-
-/**
- * Print a row for the media tab on the individual page.
- *
- * @param Fact $fact
- * @param int $level
- */
-function print_main_media(Fact $fact, $level) {
- $factrec = $fact->getGedcom();
- $parent = $fact->getParent();
-
- if ($fact->isPendingAddition()) {
- $styleadd = 'new';
- $can_edit = $level == 1 && $fact->canEdit();
- } elseif ($fact->isPendingDeletion()) {
- $styleadd = 'old';
- $can_edit = false;
- } else {
- $styleadd = '';
- $can_edit = $level == 1 && $fact->canEdit();
- }
-
- // -- find source for each fact
- preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $factrec, $matches);
- foreach ($matches[1] as $xref) {
- $media = Media::getInstance($xref, $fact->getParent()->getTree());
- // Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
- if (!$media || $media->canShow()) {
- if ($level > 1) {
- echo '<tr class="row_obje2">';
- } else {
- echo '<tr>';
- }
- echo '<td class="descriptionbox';
- if ($level > 1) {
- echo ' rela';
- }
- echo ' ', $styleadd, ' width20">';
- preg_match("/^\d (\w*)/", $factrec, $factname);
- $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
- $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
- $factname = $factwords[1]; // BIRT
- if ($factname == 'EVEN' || $factname == 'FACT') {
- // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
- $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
- if ($ct > 0) {
- $factname = $ematch[1];
- echo $factname;
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- } elseif ($can_edit) {
- echo '<a onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '">';
- echo GedcomTag::getLabel($factname, $parent), '</a>';
- echo '<div class="editfacts">';
- echo '<div class="editlink"><a class="editicon" onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '"><span class="link_text">', I18N::translate('Edit'), '</span></a></div>';
- echo '<div class="copylink"><a class="copyicon" href="#" onclick="jQuery.post(\'action.php\',{action:\'copy-fact\', type:\'\', factgedcom:\'' . rawurlencode($factrec) . '\'},function(){location.reload();})" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
- echo '<div class="deletelink"><a class="deleteicon" onclick="return delete_fact(\'', I18N::translate('Are you sure you want to delete this fact?'), '\', \'', $parent->getXref(), '\', \'', $fact->getFactId(), '\');" href="#" title="', I18N::translate('Delete'), '"><span class="link_text">', I18N::translate('Delete'), '</span></a></div>';
- echo '</div>';
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- echo '</td>';
- echo '<td class="optionbox ', $styleadd, ' wrap">';
- if ($media) {
- echo '<span class="field">';
- echo $media->displayImage();
- echo '<a href="' . $media->getHtmlUrl() . '">';
- echo '<em>';
- foreach ($media->getAllNames() as $name) {
- if ($name['type'] != 'TITL') {
- echo '<br>';
- }
- echo $name['full'];
- }
- echo '</em>';
- echo '</a>';
- echo '</span>';
-
- echo GedcomTag::getLabelValue('FORM', $media->mimeType());
- $imgsize = $media->getImageAttributes('main');
- if (!empty($imgsize['WxH'])) {
- echo GedcomTag::getLabelValue('__IMAGE_SIZE__', $imgsize['WxH']);
- }
- if ($media->getFilesizeraw() > 0) {
- echo GedcomTag::getLabelValue('__FILE_SIZE__', $media->getFilesize());
- }
- $mediatype = $media->getMediaType();
- if ($mediatype) {
- echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($mediatype));
- }
-
- switch ($media->isPrimary()) {
- case 'Y':
- echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
- break;
- case 'N':
- echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
- break;
- }
- echo print_fact_notes($media->getGedcom(), 1);
- echo print_fact_sources($media->getGedcom(), 1);
- } else {
- echo $xref;
- }
- echo '</td></tr>';
- }
- }
-}
diff --git a/includes/functions/functions_print_lists.php b/includes/functions/functions_print_lists.php
deleted file mode 100644
index 376e581b6e..0000000000
--- a/includes/functions/functions_print_lists.php
+++ /dev/null
@@ -1,2289 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Place;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Stats;
-use Fisharebest\Webtrees\Tree;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * Print a table of individuals
- *
- * @param Individual[] $datalist
- * @param string $option
- *
- * @return string
- */
-function format_indi_table($datalist, $option = '') {
- global $controller, $WT_TREE;
-
- $table_id = 'table-indi-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
-
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc" ]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["num-html-asc" ]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a<b) ? -1 : (a>b ? 1 : 0);};
- jQuery.fn.dataTableExt.oSort["num-html-desc"]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a>b) ? -1 : (a<b ? 1 : 0);};
- jQuery("#' . $table_id . '").dataTable( {
- dom: \'<"H"<"filtersH_' . $table_id . '">T<"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\',
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- autoWidth: false,
- processing: true,
- retrieve: true,
- columns: [
- /* 0 givn */ { dataSort: 2 },
- /* 1 surn */ { dataSort: 3 },
- /* 2 GIVN,SURN */ { type: "unicode", visible: false },
- /* 3 SURN,GIVN */ { type: "unicode", visible: false },
- /* 4 sosa */ { dataSort: 5, class: "center", visible: ' . ($option == 'sosa' ? 'true' : 'false') . ' },
- /* 5 SOSA */ { type: "num", visible: false },
- /* 6 birt date */ { dataSort: 7 },
- /* 7 BIRT:DATE */ { visible: false },
- /* 8 anniv */ { dataSort: 7, class: "center" },
- /* 9 birt plac */ { type: "unicode" },
- /* 10 children */ { dataSort: 11, class: "center" },
- /* 11 children */ { type: "num", visible: false },
- /* 12 deat date */ { dataSort: 13 },
- /* 13 DEAT:DATE */ { visible: false },
- /* 14 anniv */ { dataSort: 13, class: "center" },
- /* 15 age */ { dataSort: 16, class: "center" },
- /* 16 AGE */ { type: "num", visible: false },
- /* 17 deat plac */ { type: "unicode" },
- /* 18 CHAN */ { dataSort: 19, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
- /* 19 CHAN_sort */ { visible: false },
- /* 20 SEX */ { visible: false },
- /* 21 BIRT */ { visible: false },
- /* 22 DEAT */ { visible: false },
- /* 23 TREE */ { visible: false }
- ],
- sorting: [[' . ($option == 'sosa' ? '4, "asc"' : '1, "asc"') . ']],
- displayLength: 20,
- pagingType: "full_numbers"
- });
-
- jQuery("#' . $table_id . '")
- /* Hide/show parents */
- .on("click", ".btn-toggle-parents", function() {
- jQuery(this).toggleClass("ui-state-active");
- jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle();
- })
- /* Hide/show statistics */
- .on("click", ".btn-toggle-statistics", function() {
- jQuery(this).toggleClass("ui-state-active");
- jQuery("#indi_list_table-charts_' . $table_id . '").slideToggle();
- })
- /* Filter buttons in table header */
- .on("click", "button[data-filter-column]", function() {
- var btn = jQuery(this);
- // De-activate the other buttons in this button group
- btn.siblings().removeClass("ui-state-active");
- // Apply (or clear) this filter
- var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column"));
- if (btn.hasClass("ui-state-active")) {
- btn.removeClass("ui-state-active");
- col.search("").draw();
- } else {
- btn.addClass("ui-state-active");
- col.search(btn.data("filter-value")).draw();
- }
- });
-
- jQuery(".indi-list").css("visibility", "visible");
- jQuery(".loading-image").css("display", "none");
- ');
-
- $stats = new Stats($WT_TREE);
-
- // Bad data can cause "longest life" to be huge, blowing memory limits
- $max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->LongestLifeAge()) + 1;
-
- // Inititialise chart data
- $deat_by_age = array();
- for ($age = 0; $age <= $max_age; $age++) {
- $deat_by_age[$age] = '';
- }
- $birt_by_decade = array();
- $deat_by_decade = array();
- for ($year = 1550; $year < 2030; $year += 10) {
- $birt_by_decade[$year] = '';
- $deat_by_decade[$year] = '';
- }
-
- $html = '
- <div class="loading-image">&nbsp;</div>
- <div class="indi-list">
- <table id="' . $table_id . '">
- <thead>
- <tr>
- <th colspan="24">
- <div class="btn-toolbar">
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="20"
- data-filter-value="M"
- title="' . I18N::translate('Show only males.') . '"
- type="button"
- >
- ' . Individual::sexImage('M', 'large') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="20"
- data-filter-value="F"
- title="' . I18N::translate('Show only females.') . '"
- type="button"
- >
- ' . Individual::sexImage('F', 'large') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="20"
- data-filter-value="U"
- title="' . I18N::translate('Show only individuals for whom the gender is not known.') . '"
- type="button"
- >
- ' . Individual::sexImage('U', 'large') . '
- </button>
- </div>
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="N"
- title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
- type="button"
- >
- ' . I18N::translate('Alive') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="Y"
- title="' . I18N::translate('Show individuals who are dead or couples where both partners are deceased.') . '"
- type="button"
- >
- ' . I18N::translate('Dead') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="YES"
- title="' . I18N::translate('Show individuals who died more than 100 years ago.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('DEAT') . '&gt;100
- </button>
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="Y100"
- title="' . I18N::translate('Show individuals who died within the last 100 years.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('DEAT') . '&lt;=100
- </button>
- </div>
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="21"
- data-filter-value="YES"
- title="' . I18N::translate('Show individuals born more than 100 years ago.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('BIRT') . '&gt;100
- </button>
- <button
- class="ui-state-default"
- data-filter-column="21"
- data-filter-value="Y100"
- title="' . I18N::translate('Show individuals born within the last 100 years.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('BIRT') . '&lt;=100
- </button>
- </div>
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="23"
- data-filter-value="R"
- title="' . I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') . '"
- type="button"
- >
- ' . I18N::translate('Roots') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="23"
- data-filter-value="L"
- title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '"
- type="button"
- >
- ' . I18N::translate('Leaves') . '
- </button>
- </div>
- </div>
- </th>
- </tr>
- <tr>
- <th>' . GedcomTag::getLabel('GIVN') . '</th>
- <th>' . GedcomTag::getLabel('SURN') . '</th>
- <th>GIVN</th>
- <th>SURN</th>
- <th>' . /* I18N: Abbreviation for “Sosa-Stradonitz number”. This is an individual’s surname, so may need transliterating into non-latin alphabets. */ I18N::translate('Sosa') . '</th>
- <th>SOSA</th>
- <th>' . GedcomTag::getLabel('BIRT') . '</th>
- <th>SORT_BIRT</th>
- <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
- <th>' . GedcomTag::getLabel('PLAC') . '</th>
- <th><i class="icon-children" title="' . I18N::translate('Children') . '"></i></th>
- <th>NCHI</th>
- <th>' . GedcomTag::getLabel('DEAT') . '</th>
- <th>SORT_DEAT</th>
- <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
- <th>' . GedcomTag::getLabel('AGE') . '</th>
- <th>AGE</th>
- <th>' . GedcomTag::getLabel('PLAC') . '</th>
- <th>' . GedcomTag::getLabel('CHAN') . '</th>
- <th>CHAN</th>
- <th>SEX</th>
- <th>BIRT</th>
- <th>DEAT</th>
- <th>TREE</th>
- </tr>
- </thead>
- <tfoot>
- <tr>
- <th colspan="24">
- <div class="btn-toolbar">
- <div class="btn-group">
- <button type="button" class="ui-state-default btn-toggle-parents">
- ' . I18N::translate('Show parents') . '
- </button>
- <button type="button" class="ui-state-default btn-toggle-statistics">
- ' . I18N::translate('Show statistics charts') . '
- </button>
- </div>
- </div>
- </th>
- </tr>
- </tfoot>
- <tbody>';
-
- $d100y = new Date(date('Y') - 100); // 100 years ago
- $unique_indis = array(); // Don't double-count indis with multiple names.
- foreach ($datalist as $key => $person) {
- if (!$person->canShowName()) {
- continue;
- }
- if ($person->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($person->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Indi name(s)
- $html .= '<td colspan="2">';
- foreach ($person->getAllNames() as $num => $name) {
- if ($name['type'] == 'NAME') {
- $title = '';
- } else {
- $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $person)) . '"';
- }
- if ($num == $person->getPrimaryName()) {
- $class = ' class="name2"';
- $sex_image = $person->getSexImage();
- list($surn, $givn) = explode(',', $name['sort']);
- } else {
- $class = '';
- $sex_image = '';
- }
- $html .= '<a ' . $title . ' href="' . $person->getHtmlUrl() . '"' . $class . '>' . highlight_search_hits($name['full']) . '</a>' . $sex_image . '<br>';
- }
- // Indi parents
- $html .= $person->getPrimaryParentsNames('parents details1', 'none');
- $html .= '</td>';
- // Dummy column to match colspan in header
- $html .= '<td style="display:none;"></td>';
- //-- GIVN/SURN
- // Use "AAAA" as a separator (instead of ",") as Javascript.localeCompare() ignores
- // punctuation and "ANN,ROACH" would sort after "ANNE,ROACH", instead of before it.
- // Similarly, @N.N. would sort as NN.
- $html .= '<td>' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '</td>';
- $html .= '<td>' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '</td>';
- //-- SOSA
- if ($option == 'sosa') {
- $html .= '<td><a href="relationship.php?pid1=' . $datalist[1] . '&amp;pid2=' . $person->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a></td><td>' . $key . '</td>';
- } else {
- $html .= '<td></td><td>0</td>';
- }
- //-- Birth date
- $html .= '<td>';
- if ($birth_dates = $person->getAllBirthDates()) {
- foreach ($birth_dates as $num => $birth_date) {
- if ($num) {
- $html .= '<br>';
- }
- $html .= $birth_date->display(true);
- }
- if ($birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$person->getXref()])) {
- $birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex();
- }
- } else {
- $birth_date = $person->getEstimatedBirthDate();
- if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES')) {
- $html .= $birth_date->display(true);
- } else {
- $html .= '&nbsp;';
- }
- $birth_dates[0] = new Date('');
- }
- $html .= '</td>';
- //-- Event date (sortable)hidden by datatables code
- $html .= '<td>' . $birth_date->julianDay() . '</td>';
- //-- Birth anniversary
- $html .= '<td>' . Date::getAge($birth_dates[0], null, 2) . '</td>';
- //-- Birth place
- $html .= '<td>';
- foreach ($person->getAllBirthPlaces() as $n => $birth_place) {
- $tmp = new Place($birth_place, $person->getTree());
- if ($n) {
- $html .= '<br>';
- }
- $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
- $html .= highlight_search_hits($tmp->getShortName()) . '</a>';
- }
- $html .= '</td>';
- //-- Number of children
- $nchi = $person->getNumberOfChildren();
- $html .= '<td>' . I18N::number($nchi) . '</td><td>' . $nchi . '</td>';
- //-- Death date
- $html .= '<td>';
- if ($death_dates = $person->getAllDeathDates()) {
- foreach ($death_dates as $num => $death_date) {
- if ($num) {
- $html .= '<br>';
- }
- $html .= $death_date->display(true);
- }
- if ($death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$person->getXref()])) {
- $deat_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex();
- }
- } else {
- $death_date = $person->getEstimatedDeathDate();
- // Estimated death dates are a fixed number of years after the birth date.
- // Don't show estimates in the future.
- if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES') && $death_date->minimumJulianDay() < WT_CLIENT_JD) {
- $html .= $death_date->display(true);
- } elseif ($person->isDead()) {
- $html .= I18N::translate('yes');
- } else {
- $html .= '&nbsp;';
- }
- $death_dates[0] = new Date('');
- }
- $html .= '</td>';
- //-- Event date (sortable)hidden by datatables code
- $html .= '<td>' . $death_date->julianDay() . '</td>';
- //-- Death anniversary
- $html .= '<td>' . Date::getAge($death_dates[0], null, 2) . '</td>';
- //-- Age at death
- $age = Date::getAge($birth_dates[0], $death_dates[0], 0);
- if (!isset($unique_indis[$person->getXref()]) && $age >= 0 && $age <= $max_age) {
- $deat_by_age[$age] .= $person->getSex();
- }
- // Need both display and sortable age
- $html .= '<td>' . Date::getAge($birth_dates[0], $death_dates[0], 2) . '</td><td>' . Date::getAge($birth_dates[0], $death_dates[0], 1) . '</td>';
- //-- Death place
- $html .= '<td>';
- foreach ($person->getAllDeathPlaces() as $n => $death_place) {
- $tmp = new Place($death_place, $person->getTree());
- if ($n) {
- $html .= '<br>';
- }
- $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
- $html .= highlight_search_hits($tmp->getShortName()) . '</a>';
- }
- $html .= '</td>';
- //-- Last change
- $html .= '<td>' . $person->lastChangeTimestamp() . '</td>';
- $html .= '<td>' . $person->lastChangeTimestamp(true) . '</td>';
- //-- Sorting by gender
- $html .= '<td>' . $person->getSex() . '</td>';
- //-- Filtering by birth date
- $html .= '<td>';
- if (!$person->canShow() || Date::compare($birth_date, $d100y) > 0) {
- $html .= 'Y100';
- } else {
- $html .= 'YES';
- }
- $html .= '</td>';
- //-- Filtering by death date
- $html .= '<td>';
- // Died in last 100 years? Died? Not dead?
- if (Date::compare($death_dates[0], $d100y) > 0) {
- $html .= 'Y100';
- } elseif ($death_dates[0]->minimumJulianDay() || $person->isDead()) {
- $html .= 'YES';
- } else {
- $html .= 'N';
- }
- $html .= '</td>';
- //-- Roots or Leaves ?
- $html .= '<td>';
- if (!$person->getChildFamilies()) {
- $html .= 'R';
- } // roots
- elseif (!$person->isDead() && $person->getNumberOfChildren() < 1) {
- $html .= 'L';
- } // leaves
- else {
- $html .= '&nbsp;';
- }
- $html .= '</td>';
- $html .= '</tr>';
- $unique_indis[$person->getXref()] = true;
- }
- $html .= '
- </tbody>
- </table>
- <div id="indi_list_table-charts_' . $table_id . '" style="display:none">
- <table class="list-charts">
- <tr>
- <td>
- ' . print_chart_by_decade($birt_by_decade, I18N::translate('Decade of birth')) . '
- </td>
- <td>
- ' . print_chart_by_decade($deat_by_decade, I18N::translate('Decade of death')) . '
- </td>
- </tr>
- <tr>
- <td colspan="2">
- ' . print_chart_by_age($deat_by_age, I18N::translate('Age related to death year')) . '
- </td>
- </tr>
- </table>
- </div>
- </div>';
-
- return $html;
-}
-
-/**
- * Print a table of families
- *
- * @param Family[] $datalist
- *
- * @return string
- */
-function format_fam_table($datalist) {
- global $WT_TREE, $controller;
-
- $table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
-
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery("#' . $table_id . '").dataTable( {
- dom: \'<"H"<"filtersH_' . $table_id . '"><"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\',
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- autoWidth: false,
- processing: true,
- retrieve: true,
- columns: [
- /* 0 husb givn */ {dataSort: 2},
- /* 1 husb surn */ {dataSort: 3},
- /* 2 GIVN,SURN */ {type: "unicode", visible: false},
- /* 3 SURN,GIVN */ {type: "unicode", visible: false},
- /* 4 age */ {dataSort: 5, class: "center"},
- /* 5 AGE */ {type: "num", visible: false},
- /* 6 wife givn */ {dataSort: 8},
- /* 7 wife surn */ {dataSort: 9},
- /* 8 GIVN,SURN */ {type: "unicode", visible: false},
- /* 9 SURN,GIVN */ {type: "unicode", visible: false},
- /* 10 age */ {dataSort: 11, class: "center"},
- /* 11 AGE */ {type: "num", visible: false},
- /* 12 marr date */ {dataSort: 13},
- /* 13 MARR:DATE */ {visible: false},
- /* 14 anniv */ {dataSort: 13, class: "center"},
- /* 15 marr plac */ {type: "unicode"},
- /* 16 children */ {dataSort: 17, class: "center"},
- /* 17 NCHI */ {type: "num", visible: false},
- /* 18 CHAN */ {dataSort: 19, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . '},
- /* 19 CHAN_sort */ {visible: false},
- /* 20 MARR */ {visible: false},
- /* 21 DEAT */ {visible: false},
- /* 22 TREE */ {visible: false}
- ],
- sorting: [[1, "asc"]],
- displayLength: 20,
- pagingType: "full_numbers"
- });
-
- jQuery("#' . $table_id . '")
- /* Hide/show parents */
- .on("click", ".btn-toggle-parents", function() {
- jQuery(this).toggleClass("ui-state-active");
- jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle();
- })
- /* Hide/show statistics */
- .on("click", ".btn-toggle-statistics", function() {
- jQuery(this).toggleClass("ui-state-active");
- jQuery("#fam_list_table-charts_' . $table_id . '").slideToggle();
- })
- /* Filter buttons in table header */
- .on("click", "button[data-filter-column]", function() {
- var btn = $(this);
- // De-activate the other buttons in this button group
- btn.siblings().removeClass("ui-state-active");
- // Apply (or clear) this filter
- var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column"));
- if (btn.hasClass("ui-state-active")) {
- btn.removeClass("ui-state-active");
- col.search("").draw();
- } else {
- btn.addClass("ui-state-active");
- col.search(btn.data("filter-value")).draw();
- }
- });
-
- jQuery(".fam-list").css("visibility", "visible");
- jQuery(".loading-image").css("display", "none");
- ');
-
- $stats = new Stats($WT_TREE);
- $max_age = max($stats->oldestMarriageMaleAge(), $stats->oldestMarriageFemaleAge()) + 1;
-
- //-- init chart data
- $marr_by_age = array();
- for ($age = 0; $age <= $max_age; $age++) {
- $marr_by_age[$age] = '';
- }
- $birt_by_decade = array();
- $marr_by_decade = array();
- for ($year = 1550; $year < 2030; $year += 10) {
- $birt_by_decade[$year] = '';
- $marr_by_decade[$year] = '';
- }
-
- $html = '
- <div class="loading-image">&nbsp;</div>
- <div class="fam-list">
- <table id="' . $table_id . '">
- <thead>
- <tr>
- <th colspan="23">
- <div class="btn-toolbar">
- <div class="btn-group">
- <button
- type="button"
- data-filter-column="21"
- data-filter-value="N"
- class="ui-state-default"
- title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
- >
- ' . I18N::translate('Both alive') . '
- </button>
- <button
- type="button"
- data-filter-column="21"
- data-filter-value="W"
- class="ui-state-default"
- title="' . I18N::translate('Show couples where only the female partner is deceased.') . '"
- >
- ' . I18N::translate('Widower') . '
- </button>
- <button
- type="button"
- data-filter-column="21"
- data-filter-value="H"
- class="ui-state-default"
- title="' . I18N::translate('Show couples where only the male partner is deceased.') . '"
- >
- ' . I18N::translate('Widow') . '
- </button>
- <button
- type="button"
- data-filter-column="21"
- data-filter-value="Y"
- class="ui-state-default"
- title="' . I18N::translate('Show individuals who are dead or couples where both partners are deceased.') . '"
- >
- ' . I18N::translate('Both dead') . '
- </button>
- </div>
- <div class="btn-group">
- <button
- type="button"
- data-filter-column="22"
- data-filter-value="R"
- class="ui-state-default"
- title="' . I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') . '"
- >
- ' . I18N::translate('Roots') . '
- </button>
- <button
- type="button"
- data-filter-column="22"
- data-filter-value="L"
- class="ui-state-default"
- title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '"
- >
- ' . I18N::translate('Leaves') . '
- </button>
- </div>
- <div class="btn-group">
- <button
- type="button"
- data-filter-column="20"
- data-filter-value="U"
- class="ui-state-default"
- title="' . I18N::translate('Show couples with an unknown marriage date.') . '"
- >
- ' . GedcomTag::getLabel('MARR') . '
- </button>
- <button
- type="button"
- data-filter-column="20"
- data-filter-value="YES"
- class="ui-state-default"
- title="' . I18N::translate('Show couples who married more than 100 years ago.') . '"
- >
- ' . GedcomTag::getLabel('MARR') . '&gt;100
- </button>
- <button
- type="button"
- data-filter-column="20"
- data-filter-value="Y100"
- class="ui-state-default"
- title="' . I18N::translate('Show couples who married within the last 100 years.') . '"
- >
- ' . GedcomTag::getLabel('MARR') . '&lt;=100
- </button>
- <button
- type="button"
- data-filter-column="20"
- data-filter-value="D"
- class="ui-state-default"
- title="' . I18N::translate('Show divorced couples.') . '"
- >
- ' . GedcomTag::getLabel('DIV') . '
- </button>
- <button
- type="button"
- data-filter-column="20"
- data-filter-value="M"
- class="ui-state-default"
- title="' . I18N::translate('Show couples where either partner married more than once.') . '"
- >
- ' . I18N::translate('Multiple marriages') . '
- </button>
- </div>
- </div>
- </th>
- </tr>
- <tr>
- <th>' . GedcomTag::getLabel('GIVN') . '</th>
- <th>' . GedcomTag::getLabel('SURN') . '</th>
- <th>HUSB:GIVN_SURN</th>
- <th>HUSB:SURN_GIVN</th>
- <th>' . GedcomTag::getLabel('AGE') . '</th>
- <th>AGE</th>
- <th>' . GedcomTag::getLabel('GIVN') . '</th>
- <th>' . GedcomTag::getLabel('SURN') . '</th>
- <th>WIFE:GIVN_SURN</th>
- <th>WIFE:SURN_GIVN</th>
- <th>' . GedcomTag::getLabel('AGE') . '</th>
- <th>AGE</th>
- <th>' . GedcomTag::getLabel('MARR') . '</th>
- <th>MARR:DATE</th>
- <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
- <th>' . GedcomTag::getLabel('PLAC') . '</th>
- <th><i class="icon-children" title="' . I18N::translate('Children') . '"></i></th>
- <th>NCHI</th>
- <th>' . GedcomTag::getLabel('CHAN') . '</th>
- <th>CHAN</th>
- <th>MARR</th>
- <th>DEAT</th>
- <th>TREE</th>
- </tr>
- </thead>
- <tfoot>
- <tr>
- <th colspan="23">
- <div class="btn-toolbar">
- <div class="btn-group">
- <button type="button" class="ui-state-default btn-toggle-parents">
- ' . I18N::translate('Show parents') . '
- </button>
- <button type="button" class="ui-state-default btn-toggle-statistics">
- ' . I18N::translate('Show statistics charts') . '
- </button>
- </div>
- </div>
- </th>
- </tr>
- </tfoot>
- <tbody>';
-
- $d100y = new Date(date('Y') - 100); // 100 years ago
- foreach ($datalist as $family) {
- //-- Retrieve husband and wife
- $husb = $family->getHusband();
- if (is_null($husb)) {
- $husb = new Individual('H', '0 @H@ INDI', null, $family->getTree());
- }
- $wife = $family->getWife();
- if (is_null($wife)) {
- $wife = new Individual('W', '0 @W@ INDI', null, $family->getTree());
- }
- if (!$family->canShow()) {
- continue;
- }
- if ($family->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($family->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Husband name(s)
- $html .= '<td colspan="2">';
- foreach ($husb->getAllNames() as $num => $name) {
- if ($name['type'] == 'NAME') {
- $title = '';
- } else {
- $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"';
- }
- if ($num == $husb->getPrimaryName()) {
- $class = ' class="name2"';
- $sex_image = $husb->getSexImage();
- list($surn, $givn) = explode(',', $name['sort']);
- } else {
- $class = '';
- $sex_image = '';
- }
- // Only show married names if they are the name we are filtering by.
- if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) {
- $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . highlight_search_hits($name['full']) . '</a>' . $sex_image . '<br>';
- }
- }
- // Husband parents
- $html .= $husb->getPrimaryParentsNames('parents details1', 'none');
- $html .= '</td>';
- // Dummy column to match colspan in header
- $html .= '<td style="display:none;"></td>';
- //-- Husb GIVN
- // Use "AAAA" as a separator (instead of ",") as Javascript.localeCompare() ignores
- // punctuation and "ANN,ROACH" would sort after "ANNE,ROACH", instead of before it.
- // Similarly, @N.N. would sort as NN.
- $html .= '<td>' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '</td>';
- $html .= '<td>' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '</td>';
- $mdate = $family->getMarriageDate();
- //-- Husband age
- $hdate = $husb->getBirthDate();
- if ($hdate->isOK() && $mdate->isOK()) {
- if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) {
- $birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex();
- }
- $hage = Date::getAge($hdate, $mdate, 0);
- if ($hage >= 0 && $hage <= $max_age) {
- $marr_by_age[$hage] .= $husb->getSex();
- }
- }
- $html .= '<td>' . Date::getAge($hdate, $mdate, 2) . '</td><td>' . Date::getAge($hdate, $mdate, 1) . '</td>';
- //-- Wife name(s)
- $html .= '<td colspan="2">';
- foreach ($wife->getAllNames() as $num => $name) {
- if ($name['type'] == 'NAME') {
- $title = '';
- } else {
- $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"';
- }
- if ($num == $wife->getPrimaryName()) {
- $class = ' class="name2"';
- $sex_image = $wife->getSexImage();
- list($surn, $givn) = explode(',', $name['sort']);
- } else {
- $class = '';
- $sex_image = '';
- }
- // Only show married names if they are the name we are filtering by.
- if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) {
- $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . highlight_search_hits($name['full']) . '</a>' . $sex_image . '<br>';
- }
- }
- // Wife parents
- $html .= $wife->getPrimaryParentsNames('parents details1', 'none');
- $html .= '</td>';
- // Dummy column to match colspan in header
- $html .= '<td style="display:none;"></td>';
- //-- Wife GIVN
- //-- Husb GIVN
- // Use "AAAA" as a separator (instead of ",") as Javascript.localeCompare() ignores
- // punctuation and "ANN,ROACH" would sort after "ANNE,ROACH", instead of before it.
- // Similarly, @N.N. would sort as NN.
- $html .= '<td>' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '</td>';
- $html .= '<td>' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '</td>';
- $mdate = $family->getMarriageDate();
- //-- Wife age
- $wdate = $wife->getBirthDate();
- if ($wdate->isOK() && $mdate->isOK()) {
- if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) {
- $birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex();
- }
- $wage = Date::getAge($wdate, $mdate, 0);
- if ($wage >= 0 && $wage <= $max_age) {
- $marr_by_age[$wage] .= $wife->getSex();
- }
- }
- $html .= '<td>' . Date::getAge($wdate, $mdate, 2) . '</td><td>' . Date::getAge($wdate, $mdate, 1) . '</td>';
- //-- Marriage date
- $html .= '<td>';
- if ($marriage_dates = $family->getAllMarriageDates()) {
- foreach ($marriage_dates as $n => $marriage_date) {
- if ($n) {
- $html .= '<br>';
- }
- $html .= '<div>' . $marriage_date->display(true) . '</div>';
- }
- if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) {
- $marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex();
- }
- } elseif ($family->getFacts('_NMR')) {
- $html .= I18N::translate('no');
- } elseif ($family->getFacts('MARR')) {
- $html .= I18N::translate('yes');
- } else {
- $html .= '&nbsp;';
- }
- $html .= '</td>';
- //-- Event date (sortable)hidden by datatables code
- $html .= '<td>';
- if ($marriage_dates) {
- $html .= $marriage_date->julianDay();
- } else {
- $html .= 0;
- }
- $html .= '</td>';
- //-- Marriage anniversary
- $html .= '<td>' . Date::getAge($mdate, null, 2) . '</td>';
- //-- Marriage place
- $html .= '<td>';
- foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) {
- $tmp = new Place($marriage_place, $family->getTree());
- if ($n) {
- $html .= '<br>';
- }
- $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
- $html .= highlight_search_hits($tmp->getShortName()) . '</a>';
- }
- $html .= '</td>';
- //-- Number of children
- $nchi = $family->getNumberOfChildren();
- $html .= '<td>' . I18N::number($nchi) . '</td><td>' . $nchi . '</td>';
- //-- Last change
- $html .= '<td>' . $family->LastChangeTimestamp() . '</td>';
- $html .= '<td>' . $family->LastChangeTimestamp(true) . '</td>';
- //-- Sorting by marriage date
- $html .= '<td>';
- if (!$family->canShow() || !$mdate->isOK()) {
- $html .= 'U';
- } else {
- if (Date::compare($mdate, $d100y) > 0) {
- $html .= 'Y100';
- } else {
- $html .= 'YES';
- }
- }
- if ($family->getFacts(WT_EVENTS_DIV)) {
- $html .= 'D';
- }
- if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) {
- $html .= 'M';
- }
- $html .= '</td>';
- //-- Sorting alive/dead
- $html .= '<td>';
- if ($husb->isDead() && $wife->isDead()) {
- $html .= 'Y';
- }
- if ($husb->isDead() && !$wife->isDead()) {
- if ($wife->getSex() == 'F') {
- $html .= 'H';
- }
- if ($wife->getSex() == 'M') {
- $html .= 'W';
- } // male partners
- }
- if (!$husb->isDead() && $wife->isDead()) {
- if ($husb->getSex() == 'M') {
- $html .= 'W';
- }
- if ($husb->getSex() == 'F') {
- $html .= 'H';
- } // female partners
- }
- if (!$husb->isDead() && !$wife->isDead()) {
- $html .= 'N';
- }
- $html .= '</td>';
- //-- Roots or Leaves
- $html .= '<td>';
- if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) {
- $html .= 'R';
- } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() < 1) {
- $html .= 'L';
- } else {
- $html .= '&nbsp;';
- }
- $html .= '</td>
- </tr>';
- }
- $html .= '
- </tbody>
- </table>
- <div id="fam_list_table-charts_' . $table_id . '" style="display:none">
- <table class="list-charts">
- <tr>
- <td>
- ' . print_chart_by_decade($birt_by_decade, I18N::translate('Decade of birth')) . '
- </td>
- <td>
- ' . print_chart_by_decade($marr_by_decade, I18N::translate('Decade of marriage')) . '
- </td>
- </tr>
- <tr>
- <td colspan="2">
- ' . print_chart_by_age($marr_by_age, I18N::translate('Age in year of marriage')) . '
- </td>
- </tr>
- </table>
- </div>
- </div>';
-
- return $html;
-}
-
-/**
- * Print a table of sources
- *
- * @param Source[] $datalist
- *
- * @return string
- */
-function format_sour_table($datalist) {
- global $WT_TREE, $controller;
-
- // Count the number of linked records. These numbers include private records.
- // It is not good to bypass privacy, but many servers do not have the resources
- // to process privacy for every record in the tree
- $count_individuals = Database::prepare(
- "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
- )->fetchAssoc();
- $count_families = Database::prepare(
- "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
- )->fetchAssoc();
- $count_media = Database::prepare(
- "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
- )->fetchAssoc();
- $count_notes = Database::prepare(
- "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##other` JOIN `##link` ON l_from = o_id AND l_file = o_file AND o_type = 'NOTE' AND l_type = 'SOUR' GROUP BY l_to, l_file"
- )->fetchAssoc();
-
- $html = '';
- $table_id = 'table-sour-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery("#' . $table_id . '").dataTable( {
- dom: \'<"H"pf<"dt-clear">irl>t<"F"pl>\',
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- autoWidth: false,
- processing: true,
- columns: [
- /* 0 title */ { dataSort: 1 },
- /* 1 TITL */ { visible: false, type: "unicode" },
- /* 2 author */ { type: "unicode" },
- /* 3 #indi */ { dataSort: 4, class: "center" },
- /* 4 #INDI */ { type: "num", visible: false },
- /* 5 #fam */ { dataSort: 6, class: "center" },
- /* 6 #FAM */ { type: "num", visible: false },
- /* 7 #obje */ { dataSort: 8, class: "center" },
- /* 8 #OBJE */ { type: "num", visible: false },
- /* 9 #note */ { dataSort: 10, class: "center" },
- /* 10 #NOTE */ { type: "num", visible: false },
- /* 11 CHAN */ { dataSort: 12, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
- /* 12 CHAN_sort */ { visible: false },
- /* 13 DELETE */ { visible: ' . (Auth::isManager($WT_TREE) ? 'true' : 'false') . ', sortable: false }
- ],
- displayLength: 20,
- pagingType: "full_numbers"
- });
- jQuery(".source-list").css("visibility", "visible");
- jQuery(".loading-image").css("display", "none");
- ');
-
- //--table wrapper
- $html .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="source-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
- $html .= '<th>TITL</th>';
- $html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
- $html .= '<th>' . I18N::translate('Individuals') . '</th>';
- $html .= '<th>#INDI</th>';
- $html .= '<th>' . I18N::translate('Families') . '</th>';
- $html .= '<th>#FAM</th>';
- $html .= '<th>' . I18N::translate('Media objects') . '</th>';
- $html .= '<th>#OBJE</th>';
- $html .= '<th>' . I18N::translate('Shared notes') . '</th>';
- $html .= '<th>#NOTE</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '<th></th>'; //delete
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
-
- foreach ($datalist as $source) {
- if (!$source->canShow()) {
- continue;
- }
- if ($source->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($source->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Source name(s)
- $html .= '<td>';
- foreach ($source->getAllNames() as $n => $name) {
- if ($n) {
- $html .= '<br>';
- }
- if ($n == $source->getPrimaryName()) {
- $html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- } else {
- $html .= '<a href="' . $source->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- }
- }
- $html .= '</td>';
- // Sortable name
- $html .= '<td>' . strip_tags($source->getFullName()) . '</td>';
- //-- Author
- $auth = $source->getFirstFact('AUTH');
- if ($auth) {
- $author = $auth->getValue();
- } else {
- $author = '';
- }
- $html .= '<td>' . highlight_search_hits($author) . '</td>';
- $key = $source->getXref() . '@' . $source->getTree()->getTreeId();
- //-- Linked INDIs
- $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked FAMs
- $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked OBJEcts
- $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked NOTEs
- $num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $source->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $source->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Delete
- if (Auth::isManager($WT_TREE)) {
- $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_source(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- $html .= '</tbody></table></div>';
-
- return $html;
-}
-
-/**
- * Print a table of shared notes
- *
- * @param Note[] $datalist
- *
- * @return string
- */
-function format_note_table($datalist) {
- global $WT_TREE, $controller;
-
- $html = '';
- $table_id = 'table-note-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery("#' . $table_id . '").dataTable({
- dom: \'<"H"pf<"dt-clear">irl>t<"F"pl>\',
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- autoWidth: false,
- processing: true,
- columns: [
- /* 0 title */ { type: "unicode" },
- /* 1 #indi */ { dataSort: 2, class: "center" },
- /* 2 #INDI */ { type: "num", visible: false },
- /* 3 #fam */ { dataSort: 4, class: "center" },
- /* 4 #FAM */ { type: "num", visible: false },
- /* 5 #obje */ { dataSort: 6, class: "center" },
- /* 6 #OBJE */ { type: "num", visible: false },
- /* 7 #sour */ { dataSort: 8, class: "center" },
- /* 8 #SOUR */ { type: "num", visible: false },
- /* 9 CHAN */ { dataSort: 10, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
- /* 10 CHAN_sort */ { visible: false },
- /* 11 DELETE */ { visible: ' . (Auth::isManager($WT_TREE) ? 'true' : 'false') . ', sortable: false }
- ],
- displayLength: 20,
- pagingType: "full_numbers"
- });
- jQuery(".note-list").css("visibility", "visible");
- jQuery(".loading-image").css("display", "none");
- ');
-
- //--table wrapper
- $html .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="note-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
- $html .= '<th>' . I18N::translate('Individuals') . '</th>';
- $html .= '<th>#INDI</th>';
- $html .= '<th>' . I18N::translate('Families') . '</th>';
- $html .= '<th>#FAM</th>';
- $html .= '<th>' . I18N::translate('Media objects') . '</th>';
- $html .= '<th>#OBJE</th>';
- $html .= '<th>' . I18N::translate('Sources') . '</th>';
- $html .= '<th>#SOUR</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '<th></th>'; //delete
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
- foreach ($datalist as $note) {
- if (!$note->canShow()) {
- continue;
- }
- if ($note->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($note->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Shared Note name
- $html .= '<td><a class="name2" href="' . $note->getHtmlUrl() . '">' . highlight_search_hits($note->getFullName()) . '</a></td>';
- //-- Linked INDIs
- $num = count($note->linkedIndividuals('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked FAMs
- $num = count($note->linkedfamilies('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked OBJEcts
- $num = count($note->linkedMedia('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked SOURs
- $num = count($note->linkedSources('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $note->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $note->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Delete
- if (Auth::isManager($WT_TREE)) {
- $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_note(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- $html .= '</tbody></table></div>';
-
- return $html;
-}
-
-/**
- * Print a table of repositories
- *
- * @param Repository[] $repositories
- *
- * @return string
- */
-function format_repo_table($repositories) {
- global $WT_TREE, $controller;
-
- // Count the number of linked records. These numbers include private records.
- // It is not good to bypass privacy, but many servers do not have the resources
- // to process privacy for every record in the tree
- $count_sources = Database::prepare(
- "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'REPO' GROUP BY l_to, l_file"
- )->fetchAssoc();
-
- $html = '';
- $table_id = 'table-repo-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery("#' . $table_id . '").dataTable({
- dom: \'<"H"pf<"dt-clear">irl>t<"F"pl>\',
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- autoWidth: false,
- processing: true,
- columns: [
- /* 0 name */ { type: "unicode" },
- /* 1 #sour */ { dataSort: 2, class: "center" },
- /* 2 #SOUR */ { type: "num", visible: false },
- /* 3 CHAN */ { dataSort: 4, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
- /* 4 CHAN_sort */ { visible: false },
- /* 5 DELETE */ { visible: ' . (Auth::isManager($WT_TREE) ? 'true' : 'false') . ', sortable: false }
- ],
- displayLength: 20,
- pagingType: "full_numbers"
- });
- jQuery(".repo-list").css("visibility", "visible");
- jQuery(".loading-image").css("display", "none");
- ');
-
- //--table wrapper
- $html .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="repo-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . I18N::translate('Repository name') . '</th>';
- $html .= '<th>' . I18N::translate('Sources') . '</th>';
- $html .= '<th>#SOUR</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '<th></th>'; //delete
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
-
- foreach ($repositories as $repository) {
- if (!$repository->canShow()) {
- continue;
- }
- if ($repository->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($repository->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Repository name(s)
- $html .= '<td>';
- foreach ($repository->getAllNames() as $n => $name) {
- if ($n) {
- $html .= '<br>';
- }
- if ($n == $repository->getPrimaryName()) {
- $html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- } else {
- $html .= '<a href="' . $repository->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- }
- }
- $html .= '</td>';
- $key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
- //-- Linked SOURces
- $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $repository->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $repository->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Delete
- if (Auth::isManager($WT_TREE)) {
- $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_repository(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- $html .= '</tbody></table></div>';
-
- return $html;
-}
-
-/**
- * Print a table of media objects
- *
- * @param Media[] $media_objects
- *
- * @return string
- */
-function format_media_table($media_objects) {
- global $WT_TREE, $controller;
-
- $html = '';
- $table_id = 'table-obje-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery("#' . $table_id . '").dataTable({
- dom: \'<"H"pf<"dt-clear">irl>t<"F"pl>\',
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- autoWidth:false,
- processing: true,
- columns: [
- /* 0 media */ { sortable: false },
- /* 1 title */ { type: "unicode" },
- /* 2 #indi */ { dataSort: 3, class: "center" },
- /* 3 #INDI */ { type: "num", visible: false },
- /* 4 #fam */ { dataSort: 5, class: "center" },
- /* 5 #FAM */ { type: "num", visible: false },
- /* 6 #sour */ { dataSort: 7, class: "center" },
- /* 7 #SOUR */ { type: "num", visible: false },
- /* 8 CHAN */ { dataSort: 9, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
- /* 9 CHAN_sort */ { visible: false },
- ],
- displayLength: 20,
- pagingType: "full_numbers"
- });
- jQuery(".media-list").css("visibility", "visible");
- jQuery(".loading-image").css("display", "none");
- ');
-
- //--table wrapper
- $html .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="media-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . I18N::translate('Media') . '</th>';
- $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
- $html .= '<th>' . I18N::translate('Individuals') . '</th>';
- $html .= '<th>#INDI</th>';
- $html .= '<th>' . I18N::translate('Families') . '</th>';
- $html .= '<th>#FAM</th>';
- $html .= '<th>' . I18N::translate('Sources') . '</th>';
- $html .= '<th>#SOUR</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
-
- foreach ($media_objects as $media_object) {
- if ($media_object->canShow()) {
- $name = $media_object->getFullName();
- if ($media_object->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($media_object->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Object thumbnail
- $html .= '<td>' . $media_object->displayImage() . '</td>';
- //-- Object name(s)
- $html .= '<td>';
- $html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
- $html .= highlight_search_hits($name) . '</a>';
- if (Auth::isEditor($media_object->getTree())) {
- $html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
- }
- $html .= '</td>';
-
- //-- Linked INDIs
- $num = count($media_object->linkedIndividuals('OBJE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked FAMs
- $num = count($media_object->linkedfamilies('OBJE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked SOURces
- $num = count($media_object->linkedSources('OBJE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $media_object->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $media_object->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- }
- $html .= '</tbody></table></div>';
-
- return $html;
-}
-
-/**
- * Print a table of surnames, for the top surnames block, the indi/fam lists, etc.
- *
- * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
- * @param string $script "indilist.php" (counts of individuals) or "famlist.php" (counts of spouses)
- * @param Tree $tree generate links for this tree
- *
- * @return string
- */
-function format_surname_table($surnames, $script, Tree $tree) {
- global $controller;
-
- $html = '';
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["num-asc" ]=function(a,b) {a=parseFloat(a); b=parseFloat(b); return (a<b) ? -1 : (a>b ? 1 : 0);};
- jQuery.fn.dataTableExt.oSort["num-desc"]=function(a,b) {a=parseFloat(a); b=parseFloat(b); return (a>b) ? -1 : (a<b ? 1 : 0);};
- jQuery(".surname-list").dataTable( {
- dom: \'t\',
- jQueryUI: true,
- autoWidth: false,
- paging: false,
- sorting: [],
- columns: [
- /* 0 name */ { dataSort: 1 },
- /* 1 NAME */ { visible: false },
- /* 2 count */ { dataSort: 3, class: "center" },
- /* 3 COUNT */ { visible: false }
- ],
- });
- ');
-
- if ($script == 'famlist.php') {
- $col_heading = I18N::translate('Spouses');
- } else {
- $col_heading = I18N::translate('Individuals');
- }
-
- $html .= '<table class="surname-list">' .
- '<thead><tr>' .
- '<th>' . GedcomTag::getLabel('SURN') . '</th>' .
- '<th></th>' .
- '<th>' . $col_heading . '</th>' .
- '<th></th>' .
- '</tr></thead>';
-
- $html .= '<tbody>';
- foreach ($surnames as $surn => $surns) {
- // Each surname links back to the indi/fam surname list
- if ($surn) {
- $url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
- } else {
- $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
- }
- // Row counter
- $html .= '<tr>';
- // Surname
- $html .= '<td>';
- // Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
- foreach ($surns as $spfxsurn => $indis) {
- if ($spfxsurn) {
- $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
- } else {
- // No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
- $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
- }
- }
- $html .= '</td>';
- // Sort column for name
- $html .= '<td>' . $surn . '</td>';
- // Surname count
- $html .= '<td>';
- $subtotal = 0;
- foreach ($surns as $indis) {
- $subtotal += count($indis);
- $html .= I18N::number(count($indis)) . '<br>';
- }
- // More than one surname variant? Show a subtotal
- if (count($surns) > 1) {
- $html .= I18N::number($subtotal);
- }
- $html .= '</td>';
- // add hidden numeric sort column
- $html .= '<td>' . $subtotal . '</td></tr>';
- }
- $html .= '</tbody></table>';
-
- return $html;
-}
-
-/**
- * Print a tagcloud of surnames.
- *
- * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
- * @param string $script indilist or famlist
- * @param bool $totals show totals after each name
- * @param Tree $tree generate links to this tree
- *
- * @return string
- */
-function format_surname_tagcloud($surnames, $script, $totals, Tree $tree) {
- global $WT_TREE;
-
- $minimum = PHP_INT_MAX;
- $maximum = 1;
- foreach ($surnames as $surn => $surns) {
- foreach ($surns as $spfxsurn => $indis) {
- $maximum = max($maximum, count($indis));
- $minimum = min($minimum, count($indis));
- }
- }
-
- $html = '';
- foreach ($surnames as $surn => $surns) {
- foreach ($surns as $spfxsurn => $indis) {
- $size = 75.0 + 125.0 * (count($indis) - $minimum) / ($maximum - $minimum);
- $html .= '<a style="font-size:' . $size . '%" href="' . $script . '?';
- if ($surn) {
- $html .= 'surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
- } else {
- $html .= '?alpha=,&amp;ged=' . $WT_TREE->getNameUrl();
- }
- if ($totals) {
- $html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
- } else {
- $html .= $spfxsurn;
- }
- $html .= '</a> ';
- }
- }
-
- return '<div class="tag_cloud">' . $html . '</div>';
-}
-
-/**
- * Print a list of surnames.
- *
- * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
- * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns
- * @param bool $totals show totals after each name
- * @param string $script indilist or famlist
- * @param Tree $tree Link back to the individual list in this tree
- *
- * @return string
- */
-function format_surname_list($surnames, $style, $totals, $script, Tree $tree) {
- $html = array();
- foreach ($surnames as $surn => $surns) {
- // Each surname links back to the indilist
- if ($surn) {
- $url = $script . '?surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
- } else {
- $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
- }
- // If all the surnames are just case variants, then merge them into one
- // Comment out this block if you want SMITH listed separately from Smith
- $first_spfxsurn = null;
- foreach ($surns as $spfxsurn => $indis) {
- if ($first_spfxsurn) {
- if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) {
- $surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]);
- unset($surns[$spfxsurn]);
- }
- } else {
- $first_spfxsurn = $spfxsurn;
- }
- }
- $subhtml = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
-
- if ($totals) {
- $subtotal = 0;
- foreach ($surns as $indis) {
- $subtotal += count($indis);
- }
- $subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
- }
- $html[] = $subhtml;
-
- }
- switch ($style) {
- case 1:
- return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
- case 2:
- return implode(I18N::$list_separator, $html);
- case 3:
- $i = 0;
- $count = count($html);
- if ($count > 36) {
- $col = 4;
- } elseif ($count > 18) {
- $col = 3;
- } elseif ($count > 6) {
- $col = 2;
- } else {
- $col = 1;
- }
- $newcol = ceil($count / $col);
- $html2 = '<table class="list_table"><tr>';
- $html2 .= '<td class="list_value" style="padding: 14px;">';
-
- foreach ($html as $surns) {
- $html2 .= $surns . '<br>';
- $i++;
- if ($i == $newcol && $i < $count) {
- $html2 .= '</td><td class="list_value" style="padding: 14px;">';
- $newcol = $i + ceil($count / $col);
- }
- }
- $html2 .= '</td></tr></table>';
-
- return $html2;
- }
-}
-
-/**
- * Print a table of events
- *
- * @param string[] $change_ids
- * @param string $sort
- *
- * @return string
- */
-function print_changes_list($change_ids, $sort) {
- global $WT_TREE;
-
- $n = 0;
- $arr = array();
- foreach ($change_ids as $change_id) {
- $record = GedcomRecord::getInstance($change_id, $WT_TREE);
- if (!$record || !$record->canShow()) {
- continue;
- }
- // setup sorting parameters
- $arr[$n]['record'] = $record;
- $arr[$n]['jd'] = ($sort == 'name') ? 1 : $n;
- $arr[$n]['anniv'] = $record->lastChangeTimestamp(true);
- $arr[$n++]['fact'] = $record->getSortName(); // in case two changes have same timestamp
- }
-
- switch ($sort) {
- case 'name':
- uasort($arr, 'event_sort_name');
- break;
- case 'date_asc':
- uasort($arr, 'event_sort');
- $arr = array_reverse($arr);
- break;
- case 'date_desc':
- uasort($arr, 'event_sort');
- }
- $html = '';
- foreach ($arr as $value) {
- $html .= '<a href="' . $value['record']->getHtmlUrl() . '" class="list_item name2">' . $value['record']->getFullName() . '</a>';
- $html .= '<div class="indent" style="margin-bottom: 5px;">';
- if ($value['record'] instanceof Individual) {
- if ($value['record']->getAddName()) {
- $html .= '<a href="' . $value['record']->getHtmlUrl() . '" class="list_item">' . $value['record']->getAddName() . '</a>';
- }
- }
- $html .= /* I18N: [a record was] Changed on <date/time> by <user> */ I18N::translate('Changed on %1$s by %2$s', $value['record']->lastChangeTimestamp(), Filter::escapeHtml($value['record']->lastChangeUser()));
- $html .= '</div>';
- }
-
- return $html;
-}
-
-/**
- * Print a table of events
- *
- * @param string[] $change_ids
- * @param string $sort
- *
- * @return string
- */
-function print_changes_table($change_ids, $sort) {
- global $controller, $WT_TREE;
-
- $n = 0;
- $table_id = 'table-chan-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
- switch ($sort) {
- case 'name': //name
- $aaSorting = "[5,'asc'], [4,'desc']";
- break;
- case 'date_asc': //date ascending
- $aaSorting = "[4,'asc'], [5,'asc']";
- break;
- case 'date_desc': //date descending
- $aaSorting = "[4,'desc'], [5,'asc']";
- break;
- }
- $html = '';
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
- jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
- jQuery("#' . $table_id . '").dataTable({
- dom: \'t\',
- paging: false,
- autoWidth:false,
- lengthChange: false,
- filter: false,
- ' . I18N::datatablesI18N() . ',
- jQueryUI: true,
- sorting: [' . $aaSorting . '],
- columns: [
- /* 0-Type */ { sortable: false, class: "center" },
- /* 1-Record */ { dataSort: 5 },
- /* 2-Change */ { dataSort: 4 },
- /* 3-By */ null,
- /* 4-DATE */ { visible: false },
- /* 5-SORTNAME */{ type: "unicode", visible: false }
- ]
- });
- ');
-
- //-- table header
- $html .= '<table id="' . $table_id . '" class="width100">';
- $html .= '<thead><tr>';
- $html .= '<th></th>';
- $html .= '<th>' . I18N::translate('Record') . '</th>';
- $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th>' . GedcomTag::getLabel('_WT_USER') . '</th>';
- $html .= '<th>DATE</th>'; //hidden by datatables code
- $html .= '<th>SORTNAME</th>'; //hidden by datatables code
- $html .= '</tr></thead><tbody>';
-
- //-- table body
- foreach ($change_ids as $change_id) {
- $record = GedcomRecord::getInstance($change_id, $WT_TREE);
- if (!$record || !$record->canShow()) {
- continue;
- }
- $html .= '<tr><td>';
- switch ($record::RECORD_TYPE) {
- case 'INDI':
- $icon = $record->getSexImage('small');
- break;
- case 'FAM':
- $icon = '<i class="icon-button_family"></i>';
- break;
- case 'OBJE':
- $icon = '<i class="icon-button_media"></i>';
- break;
- case 'NOTE':
- $icon = '<i class="icon-button_note"></i>';
- break;
- case 'SOUR':
- $icon = '<i class="icon-button_source"></i>';
- break;
- case 'REPO':
- $icon = '<i class="icon-button_repository"></i>';
- break;
- default:
- $icon = '&nbsp;';
- break;
- }
- $html .= '<a href="' . $record->getHtmlUrl() . '">' . $icon . '</a>';
- $html .= '</td>';
- ++$n;
- //-- Record name(s)
- $name = $record->getFullName();
- $html .= '<td class="wrap">';
- $html .= '<a href="' . $record->getHtmlUrl() . '">' . $name . '</a>';
- if ($record instanceof Individual) {
- $addname = $record->getAddName();
- if ($addname) {
- $html .= '<div class="indent"><a href="' . $record->getHtmlUrl() . '">' . $addname . '</a></div>';
- }
- }
- $html .= "</td>";
- //-- Last change date/time
- $html .= '<td class="wrap">' . $record->lastChangeTimestamp() . '</td>';
- //-- Last change user
- $html .= '<td class="wrap">' . Filter::escapeHtml($record->lastChangeUser()) . '</td>';
- //-- change date (sortable) hidden by datatables code
- $html .= '<td>' . $record->lastChangeTimestamp(true) . '</td>';
- //-- names (sortable) hidden by datatables code
- $html .= '<td>' . $record->getSortName() . '</td></tr>';
- }
-
- $html .= '</tbody></table>';
-
- return $html;
-}
-
-/**
- * Print a table of events
- *
- * @param int $startjd
- * @param int $endjd
- * @param string $events
- * @param bool $only_living
- * @param string $sort_by
- *
- * @return string
- */
-function print_events_table($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
- global $controller, $WT_TREE;
-
- $html = '';
- $table_id = 'table-even-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
- $controller
- ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
- ->addInlineJavascript('
- jQuery("#' . $table_id . '").dataTable({
- dom: \'t\',
- ' . I18N::datatablesI18N() . ',
- autoWidth: false,
- paging: false,
- lengthChange: false,
- filter: false,
- info: true,
- jQueryUI: true,
- sorting: [[ ' . ($sort_by == 'alpha' ? 1 : 3) . ', "asc"]],
- columns: [
- /* 0-Record */ { dataSort: 1 },
- /* 1-NAME */ { visible: false },
- /* 2-Date */ { dataSort: 3 },
- /* 3-DATE */ { visible: false },
- /* 4-Anniv. */ { dataSort: 5, class: "center" },
- /* 5-ANNIV */ { type: "num", visible: false },
- /* 6-Event */ { class: "center" }
- ]
- });
- ');
-
- // Did we have any output? Did we skip anything?
- $output = 0;
- $filter = 0;
- $filtered_events = array();
-
- foreach (get_events_list($startjd, $endjd, $events, $WT_TREE) as $fact) {
- $record = $fact->getParent();
- //-- only living people ?
- if ($only_living) {
- if ($record instanceof Individual && $record->isDead()) {
- $filter++;
- continue;
- }
- if ($record instanceof Family) {
- $husb = $record->getHusband();
- if (is_null($husb) || $husb->isDead()) {
- $filter++;
- continue;
- }
- $wife = $record->getWife();
- if (is_null($wife) || $wife->isDead()) {
- $filter++;
- continue;
- }
- }
- }
-
- //-- Counter
- $output++;
-
- if ($output == 1) {
- //-- table body
- $html .= '<table id="' . $table_id . '" class="width100">';
- $html .= '<thead><tr>';
- $html .= '<th>' . I18N::translate('Record') . '</th>';
- $html .= '<th>NAME</th>'; //hidden by datatables code
- $html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
- $html .= '<th>DATE</th>'; //hidden by datatables code
- $html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
- $html .= '<th>ANNIV</th>';
- $html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
- $html .= '</tr></thead><tbody>';
- }
-
- $filtered_events[] = $fact;
- }
-
- foreach ($filtered_events as $n => $fact) {
- $record = $fact->getParent();
- $html .= '<tr>';
- $html .= '<td>';
- $html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
- if ($record instanceof Individual) {
- $html .= $record->getSexImage();
- }
- $html .= '</td>';
- $html .= '<td>' . $record->getSortName() . '</td>';
- $html .= '<td>' . $fact->getDate()->display(!Auth::isSearchEngine()) . '</td>';
- $html .= '<td>' . $n . '</td>';
- $html .= '<td>' . I18N::number($fact->anniv) . '</td>';
- $html .= '<td>' . $fact->anniv . '</td>';
- $html .= '<td>' . $fact->getLabel() . '</td>';
- $html .= '</tr>';
- }
-
- if ($output != 0) {
- $html .= '</tbody></table>';
- }
-
- // Print a final summary message about restricted/filtered facts
- $summary = '';
- if ($endjd == WT_CLIENT_JD) {
- // We're dealing with the Today’s Events block
- if ($output == 0) {
- if ($filter == 0) {
- $summary = I18N::translate('No events exist for today.');
- } else {
- $summary = I18N::translate('No events for living individuals exist for today.');
- }
- }
- } else {
- // We're dealing with the Upcoming Events block
- if ($output == 0) {
- if ($filter == 0) {
- if ($endjd == $startjd) {
- $summary = I18N::translate('No events exist for tomorrow.');
- } else {
- // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
- $summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
- }
- } else {
- if ($endjd == $startjd) {
- $summary = I18N::translate('No events for living individuals exist for tomorrow.');
- } else {
- // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
- $summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
- }
- }
- }
- }
- if ($summary != "") {
- $html .= '<strong>' . $summary . '</strong>';
- }
-
- return $html;
-}
-
-/**
- * Print a list of events
- *
- * This performs the same function as print_events_table(), but formats the output differently.
- *
- * @param int $startjd
- * @param int $endjd
- * @param string $events
- * @param bool $only_living
- * @param string $sort_by
- *
- * @return string
- */
-function print_events_list($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
- global $WT_TREE;
-
- // Did we have any output? Did we skip anything?
- $output = 0;
- $filter = 0;
- $filtered_events = array();
- $html = '';
- foreach (get_events_list($startjd, $endjd, $events, $WT_TREE) as $fact) {
- $record = $fact->getParent();
- //-- only living people ?
- if ($only_living) {
- if ($record instanceof Individual && $record->isDead()) {
- $filter++;
- continue;
- }
- if ($record instanceof Family) {
- $husb = $record->getHusband();
- if (is_null($husb) || $husb->isDead()) {
- $filter++;
- continue;
- }
- $wife = $record->getWife();
- if (is_null($wife) || $wife->isDead()) {
- $filter++;
- continue;
- }
- }
- }
-
- $output++;
-
- $filtered_events[] = $fact;
- }
-
- // Now we've filtered the list, we can sort by event, if required
- switch ($sort_by) {
- case 'anniv':
- // Data is already sorted by anniversary date
- break;
- case 'alpha':
- uasort($filtered_events, function (Fact $x, Fact $y) {
- return GedcomRecord::compare($x->getParent(), $y->getParent());
- });
- break;
- }
-
- foreach ($filtered_events as $fact) {
- $record = $fact->getParent();
- $html .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
- if ($record instanceof Individual) {
- $html .= $record->getSexImage();
- }
- $html .= '<br><div class="indent">';
- $html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
- if ($fact->anniv) {
- $html .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')';
- }
- if (!$fact->getPlace()->isEmpty()) {
- $html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
- }
- $html .= '</div>';
- }
-
- // Print a final summary message about restricted/filtered facts
- $summary = '';
- if ($endjd == WT_CLIENT_JD) {
- // We're dealing with the Today’s Events block
- if ($output == 0) {
- if ($filter == 0) {
- $summary = I18N::translate('No events exist for today.');
- } else {
- $summary = I18N::translate('No events for living individuals exist for today.');
- }
- }
- } else {
- // We're dealing with the Upcoming Events block
- if ($output == 0) {
- if ($filter == 0) {
- if ($endjd == $startjd) {
- $summary = I18N::translate('No events exist for tomorrow.');
- } else {
- // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
- $summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
- }
- } else {
- if ($endjd == $startjd) {
- $summary = I18N::translate('No events for living individuals exist for tomorrow.');
- } else {
- // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
- $summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
- }
- }
- }
- }
- if ($summary) {
- $html .= "<b>" . $summary . "</b>";
- }
-
- return $html;
-}
-
-/**
- * Print a chart by age using Google chart API
- *
- * @param integer[] $data
- * @param string $title
- *
- * @return string
- */
-function print_chart_by_age($data, $title) {
- $count = 0;
- $agemax = 0;
- $vmax = 0;
- $avg = 0;
- foreach ($data as $age => $v) {
- $n = strlen($v);
- $vmax = max($vmax, $n);
- $agemax = max($agemax, $age);
- $count += $n;
- $avg += $age * $n;
- }
- if ($count < 1) {
- return '';
- }
- $avg = round($avg / $count);
- $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
- $chart_url .= "&amp;chs=725x150"; // size
- $chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
- $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
- $chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
- $chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
- $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
- $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
- $chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
- $chart_url .= "&amp;chxl=0:|"; // label
- for ($age = 0; $age <= $agemax; $age += 5) {
- $chart_url .= $age . "|||||"; // x axis
- }
- $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
- $chart_url .= "|2:||";
- $step = $vmax;
- for ($d = $vmax; $d > 0; $d--) {
- if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
- $step = $d;
- }
- }
- if ($step == $vmax) {
- for ($d = $vmax - 1; $d > 0; $d--) {
- if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
- $step = $d;
- }
- }
- }
- for ($n = $step; $n < $vmax; $n += $step) {
- $chart_url .= $n . "|";
- }
- $chart_url .= rawurlencode($vmax . " / " . $count); // r axis
- $chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
- $chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
- $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- for ($age = 0; $age <= $agemax; $age++) {
- $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "M") * 61 / $vmax)];
- }
- $chart_url .= ",";
- for ($age = 0; $age <= $agemax; $age++) {
- $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "F") * 61 / $vmax)];
- }
- $html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
-
- return $html;
-}
-
-/**
- * Print a chart by decade using Google chart API
- *
- * @param integer[] $data
- * @param string $title
- *
- * @return string
- */
-function print_chart_by_decade($data, $title) {
- $count = 0;
- $vmax = 0;
- foreach ($data as $v) {
- $n = strlen($v);
- $vmax = max($vmax, $n);
- $count += $n;
- }
- if ($count < 1) {
- return '';
- }
- $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
- $chart_url .= "&amp;chs=360x150"; // size
- $chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
- $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
- $chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
- $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
- $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
- $chart_url .= "&amp;chxl=0:|&lt;|||"; // <1570
- for ($y = 1600; $y < 2030; $y += 50) {
- $chart_url .= $y . "|||||"; // x axis
- }
- $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
- $chart_url .= "|2:||";
- $step = $vmax;
- for ($d = $vmax; $d > 0; $d--) {
- if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
- $step = $d;
- }
- }
- if ($step == $vmax) {
- for ($d = $vmax - 1; $d > 0; $d--) {
- if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
- $step = $d;
- }
- }
- }
- for ($n = $step; $n < $vmax; $n += $step) {
- $chart_url .= $n . "|";
- }
- $chart_url .= rawurlencode($vmax . " / " . $count); // r axis
- $chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
- $chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
- $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- for ($y = 1570; $y < 2030; $y += 10) {
- $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "M") * 61 / $vmax)];
- }
- $chart_url .= ",";
- for ($y = 1570; $y < 2030; $y += 10) {
- $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "F") * 61 / $vmax)];
- }
- $html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
-
- return $html;
-}
diff --git a/includes/functions/functions_rtl.php b/includes/functions/functions_rtl.php
deleted file mode 100644
index 3c4f7bca8e..0000000000
--- a/includes/functions/functions_rtl.php
+++ /dev/null
@@ -1,1141 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 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/>.
- */
-
-// RTL Functions for use in the PDF/HTML reports
-
-use Fisharebest\Webtrees\I18N;
-
-$SpecialChar = array(' ', '.', ',', '"', '\'', '/', '\\', '|', ':', ';', '+', '&', '#', '@', '-', '=', '*', '%', '!', '?', '$', '<', '>', "\n");
-$SpecialPar = array('(', ')', '[', ']', '{', '}');
-$SpecialNum = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
-
-$openPar = '([{';
-$closePar = ')]}';
-$numbers = '0123456789';
-$numberPrefix = '+-'; // Treat these like numbers when at beginning or end of numeric strings
-$numberPunctuation = '- ,.:/'; // Treat these like numbers when inside numeric strings
-$punctuation = ',.:;?!';
-
-/**
- * This function strips &lrm; and &rlm; from the input string. It should be used for all
- * text that has been passed through the PrintReady() function before that text is stored
- * in the database. The database should NEVER contain these characters.
- *
- * @param string $inputText The string from which the &lrm; and &rlm; characters should be stripped
- *
- * @return string The input string, with &lrm; and &rlm; stripped
- */
-function stripLRMRLM($inputText) {
- return str_replace(array(WT_UTF8_LRM, WT_UTF8_RLM, WT_UTF8_LRO, WT_UTF8_RLO, WT_UTF8_LRE, WT_UTF8_RLE, WT_UTF8_PDF, "&lrm;", "&rlm;", "&LRM;", "&RLM;"), "", $inputText);
-}
-
-/**
- * This function encapsulates all texts in the input with <span dir='xxx'> and </span>
- * according to the directionality specified.
- *
- * @param string $inputText Raw input
- * @param string $direction Directionality (LTR, BOTH, RTL) default BOTH
- * @param string $class Additional text to insert into output <span dir="xxx"> (such as 'class="yyy"')
- *
- * @return string The string with all texts encapsulated as required
- */
-function spanLTRRTL($inputText, $direction = 'BOTH', $class = '') {
- global $openPar, $closePar, $punctuation;
- global $numbers, $numberPrefix, $numberPunctuation;
- global $previousState, $currentState, $waitingText;
- global $startLTR, $endLTR, $startRTL, $endRTL, $lenStart, $lenEnd;
- static $spanNumber = 0;
-
- if ($inputText == '') {
- // Nothing to do
- return '';
- }
- $spanNumber++;
-
- $workingText = str_replace("\n", '<br>', $inputText);
- $workingText = str_replace(array('<span class="starredname"><br>', '<span<br>class="starredname">'), '<br><span class="starredname">', $workingText); // Reposition some incorrectly placed line breaks
- $workingText = stripLRMRLM($workingText); // Get rid of any existing UTF8 control codes
-
- // $nothing = '&zwnj;'; // Zero Width Non-Joiner (not sure whether this is still needed to work around a TCPDF bug)
- $nothing = '';
-
- $startLTR = '<LTR>'; // This will become '<span dir="ltr">' at the end
- $endLTR = '</LTR>'; // This will become '</span>' at the end
- $startRTL = '<RTL>'; // This will become '<span dir="rtl">' at the end
- $endRTL = '</RTL>'; // This will become '</span>' at the end
- $lenStart = strlen($startLTR); // RTL version MUST have same length
- $lenEnd = strlen($endLTR); // RTL version MUST have same length
-
- $previousState = '';
- $currentState = strtoupper(I18N::direction());
- $numberState = false; // Set when we're inside a numeric string
- $result = '';
- $waitingText = '';
- $openParDirection = array();
-
- beginCurrentSpan($result);
-
- while ($workingText != '') {
- $charArray = getChar($workingText, 0); // Get the next ASCII or UTF-8 character
- $currentLetter = $charArray['letter'];
- $currentLen = $charArray['length'];
-
- $openParIndex = strpos($openPar, $currentLetter); // Which opening parenthesis is this?
- $closeParIndex = strpos($closePar, $currentLetter); // Which closing parenthesis is this?
-
- switch ($currentLetter) {
- case '<':
- // Assume this '<' starts an HTML element
- $endPos = strpos($workingText, '>'); // look for the terminating '>'
- if ($endPos === false) {
- $endPos = 0;
- }
- $currentLen += $endPos;
- $element = substr($workingText, 0, $currentLen);
- $temp = strtolower(substr($element, 0, 3));
- if (strlen($element) < 7 && $temp == '<br') {
- if ($numberState) {
- $numberState = false;
- if ($currentState == 'RTL') {
- $waitingText .= WT_UTF8_PDF;
- }
- }
- breakCurrentSpan($result);
- } elseif ($waitingText == '') {
- $result .= $element;
- } else {
- $waitingText .= $element;
- }
- $workingText = substr($workingText, $currentLen);
- break;
- case '&':
- // Assume this '&' starts an HTML entity
- $endPos = strpos($workingText, ';'); // look for the terminating ';'
- if ($endPos === false) {
- $endPos = 0;
- }
- $currentLen += $endPos;
- $entity = substr($workingText, 0, $currentLen);
- if (strtolower($entity) == '&nbsp;') {
- $entity .= '&nbsp;'; // Ensure consistent case for this entity
- }
- if ($waitingText == '') {
- $result .= $entity;
- } else {
- $waitingText .= $entity;
- }
- $workingText = substr($workingText, $currentLen);
- break;
- case '{':
- if (substr($workingText, 1, 1) == '{') {
- // Assume this '{{' starts a TCPDF directive
- $endPos = strpos($workingText, '}}'); // look for the terminating '}}'
- if ($endPos === false) {
- $endPos = 0;
- }
- $currentLen = $endPos + 2;
- $directive = substr($workingText, 0, $currentLen);
- $workingText = substr($workingText, $currentLen);
- $result = $result . $waitingText . $directive;
- $waitingText = '';
- break;
- }
- default:
- // Look for strings of numbers with optional leading or trailing + or -
- // and with optional embedded numeric punctuation
- if ($numberState) {
- // If we're inside a numeric string, look for reasons to end it
- $offset = 0; // Be sure to look at the current character first
- $charArray = getChar($workingText . "\n", $offset);
- if (strpos($numbers, $charArray['letter']) === false) {
- // This is not a digit. Is it numeric punctuation?
- if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
- $offset += 6; // This could be numeric punctuation
- } elseif (strpos($numberPunctuation, $charArray['letter']) !== false) {
- $offset += $charArray['length']; // This could be numeric punctuation
- }
- // If the next character is a digit, the current character is numeric punctuation
- $charArray = getChar($workingText . "\n", $offset);
- if (strpos($numbers, $charArray['letter']) === false) {
- // This is not a digit. End the run of digits and punctuation.
- $numberState = false;
- if ($currentState == 'RTL') {
- if (strpos($numberPrefix, $currentLetter) === false) {
- $currentLetter = WT_UTF8_PDF . $currentLetter;
- } else {
- $currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
- }
- }
- }
- }
- } else {
- // If we're outside a numeric string, look for reasons to start it
- if (strpos($numberPrefix, $currentLetter) !== false) {
- // This might be a number lead-in
- $offset = $currentLen;
- $nextChar = substr($workingText . "\n", $offset, 1);
- if (strpos($numbers, $nextChar) !== false) {
- $numberState = true; // We found a digit: the lead-in is therefore numeric
- if ($currentState == 'RTL') {
- $currentLetter = WT_UTF8_LRE . $currentLetter;
- }
- }
- } elseif (strpos($numbers, $currentLetter) !== false) {
- $numberState = true; // The current letter is a digit
- if ($currentState == 'RTL') {
- $currentLetter = WT_UTF8_LRE . $currentLetter;
- }
- }
- }
-
- // Determine the directionality of the current UTF-8 character
- $newState = $currentState;
- while (true) {
- if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
- if ($currentState == '') {
- $newState = 'RTL';
- break;
- }
-
- if ($currentState == 'RTL') {
- break;
- }
- // Switch to RTL only if this isn't a solitary RTL letter
- $tempText = substr($workingText, $currentLen);
- while ($tempText != '') {
- $nextCharArray = getChar($tempText, 0);
- $nextLetter = $nextCharArray['letter'];
- $nextLen = $nextCharArray['length'];
- $tempText = substr($tempText, $nextLen);
-
- if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
- $newState = 'RTL';
- break 2;
- }
-
- if (strpos($punctuation, $nextLetter) !== false || strpos($openPar, $nextLetter) !== false) {
- $newState = 'RTL';
- break 2;
- }
-
- if ($nextLetter === ' ') {
- break;
- }
- $nextLetter .= substr($tempText . "\n", 0, 5);
- if ($nextLetter === '&nbsp;') {
- break;
- }
- }
- // This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
- $currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
- $newState = 'LTR';
- break;
- }
- if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
- // Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
- $newState = 'LTR';
- break;
- }
- if ($closeParIndex !== false) {
- // This closing parenthesis has to inherit the matching opening parenthesis' directionality
- if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
- $newState = $openParDirection[$closeParIndex];
- }
- $openParDirection[$closeParIndex] = '';
- break;
- }
- if ($openParIndex !== false) {
- // Opening parentheses always inherit the following directionality
- $waitingText .= $currentLetter;
- $workingText = substr($workingText, $currentLen);
- while (true) {
- if ($workingText === '') {
- break;
- }
- if (substr($workingText, 0, 1) === ' ') {
- // Spaces following this left parenthesis inherit the following directionality too
- $waitingText .= ' ';
- $workingText = substr($workingText, 1);
- continue;
- }
- if (substr($workingText, 0, 6) === '&nbsp;') {
- // Spaces following this left parenthesis inherit the following directionality too
- $waitingText .= '&nbsp;';
- $workingText = substr($workingText, 6);
- continue;
- }
- break;
- }
- $openParDirection[$openParIndex] = '?';
- break 2; // double break because we're waiting for more information
- }
-
- // We have a digit or a "normal" special character.
- //
- // When this character is not at the start of the input string, it inherits the preceding directionality;
- // at the start of the input string, it assumes the following directionality.
- //
- // Exceptions to this rule will be handled later during final clean-up.
- //
- $waitingText .= $currentLetter;
- $workingText = substr($workingText, $currentLen);
- if ($currentState != '') {
- $result .= $waitingText;
- $waitingText = '';
- }
- break 2; // double break because we're waiting for more information
- }
- if ($newState != $currentState) {
- // A direction change has occurred
- finishCurrentSpan($result, false);
- $previousState = $currentState;
- $currentState = $newState;
- beginCurrentSpan($result);
- }
- $waitingText .= $currentLetter;
- $workingText = substr($workingText, $currentLen);
- $result .= $waitingText;
- $waitingText = '';
-
- foreach ($openParDirection as $index => $value) {
- // Since we now know the proper direction, remember it for all waiting opening parentheses
- if ($value === '?') {
- $openParDirection[$index] = $currentState;
- }
- }
-
- break;
- }
- }
-
- // We're done. Finish last <span> if necessary
- if ($numberState) {
- if ($waitingText === '') {
- if ($currentState === 'RTL') {
- $result .= WT_UTF8_PDF;
- }
- } else {
- if ($currentState === 'RTL') {
- $waitingText .= WT_UTF8_PDF;
- }
- }
- }
- finishCurrentSpan($result, true);
-
- // Get rid of any waiting text
- if ($waitingText != '') {
- if (I18N::direction() === 'rtl' && $currentState === 'LTR') {
- $result .= $startRTL;
- $result .= $waitingText;
- $result .= $endRTL;
- } else {
- $result .= $startLTR;
- $result .= $waitingText;
- $result .= $endLTR;
- }
- $waitingText = '';
- }
-
- // Lastly, do some more cleanups
-
- // Move leading RTL numeric strings to following LTR text
- // (this happens when the page direction is RTL and the original text begins with a number and is followed by LTR text)
- while (substr($result, 0, $lenStart + 3) === $startRTL . WT_UTF8_LRE) {
- $spanEnd = strpos($result, $endRTL . $startLTR);
- if ($spanEnd === false) {
- break;
- }
- $textSpan = stripLRMRLM(substr($result, $lenStart + 3, $spanEnd - $lenStart - 3));
- if (I18N::scriptDirection(I18N::textScript($textSpan)) === 'rtl') {
- break;
- }
- $result = $startLTR . substr($result, $lenStart, $spanEnd - $lenStart) . substr($result, $spanEnd + $lenStart + $lenEnd);
- break;
- }
-
- // On RTL pages, put trailing "." in RTL numeric strings into its own RTL span
- if (I18N::direction() === 'rtl') {
- $result = str_replace(WT_UTF8_PDF . '.' . $endRTL, WT_UTF8_PDF . $endRTL . $startRTL . '.' . $endRTL, $result);
- }
-
- // Trim trailing blanks preceding <br> in LTR text
- while ($previousState != 'RTL') {
- if (strpos($result, ' <LTRbr>') !== false) {
- $result = str_replace(' <LTRbr>', '<LTRbr>', $result);
- continue;
- }
- if (strpos($result, '&nbsp;<LTRbr>') !== false) {
- $result = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $result);
- continue;
- }
- if (strpos($result, ' <br>') !== false) {
- $result = str_replace(' <br>', '<br>', $result);
- continue;
- }
- if (strpos($result, '&nbsp;<br>') !== false) {
- $result = str_replace('&nbsp;<br>', '<br>', $result);
- continue;
- }
- break; // Neither space nor &nbsp; : we're done
- }
-
- // Trim trailing blanks preceding <br> in RTL text
- while (true) {
- if (strpos($result, ' <RTLbr>') !== false) {
- $result = str_replace(' <RTLbr>', '<RTLbr>', $result);
- continue;
- }
- if (strpos($result, '&nbsp;<RTLbr>') !== false) {
- $result = str_replace('&nbsp;<RTLbr>', '<RTLbr>', $result);
- continue;
- }
- break; // Neither space nor &nbsp; : we're done
- }
-
- // Convert '<LTRbr>' and '<RTLbr /'
- $result = str_replace(array('<LTRbr>', '<RTLbr>'), array($endLTR . '<br>' . $startLTR, $endRTL . '<br>' . $startRTL), $result);
-
- // Include leading indeterminate directional text in whatever follows
- if (substr($result . "\n", 0, $lenStart) != $startLTR && substr($result . "\n", 0, $lenStart) != $startRTL && substr($result . "\n", 0, 6) != '<br>') {
- $leadingText = '';
- while (true) {
- if ($result == '') {
- $result = $leadingText;
- break;
- }
- if (substr($result . "\n", 0, $lenStart) != $startLTR && substr($result . "\n", 0, $lenStart) != $startRTL) {
- $leadingText .= substr($result, 0, 1);
- $result = substr($result, 1);
- continue;
- }
- $result = substr($result, 0, $lenStart) . $leadingText . substr($result, $lenStart);
- break;
- }
- }
-
- // Include solitary "-" and "+" in surrounding RTL text
- $result = str_replace(array($endRTL . $startLTR . '-' . $endLTR . $startRTL, $endRTL . $startLTR . '-' . $endLTR . $startRTL), array('-', '+'), $result);
-
- // Remove empty spans
- $result = str_replace(array($startLTR . $endLTR, $startRTL . $endRTL), '', $result);
-
- // Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>'
- switch ($direction) {
- case 'BOTH':
- case 'both':
- // LTR text: <span dir="ltr"> text </span>
- // RTL text: <span dir="rtl"> text </span>
- $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
- $eLTR = $nothing . '</span>';
- $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
- $eRTL = $nothing . '</span>';
- break;
- case 'LTR':
- case 'ltr':
- // LTR text: <span dir="ltr"> text </span>
- // RTL text: text
- $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
- $eLTR = $nothing . '</span>';
- $sRTL = '';
- $eRTL = '';
- break;
- case 'RTL':
- case 'rtl':
- default:
- // LTR text: text
- // RTL text: <span dir="rtl"> text </span>
- $sLTR = '';
- $eLTR = '';
- $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
- $eRTL = $nothing . '</span>';
- break;
- }
- $result = str_replace(array($startLTR, $endLTR, $startRTL, $endRTL), array($sLTR, $eLTR, $sRTL, $eRTL), $result);
-
- return $result;
-}
-
-/**
- * Wrap words that have an asterisk suffix in <u> and </u> tags.
- * This should underline starred names to show the preferred name.
- *
- * @param string $textSpan
- * @param string $direction
- *
- * @return string
- */
-function starredName($textSpan, $direction) {
- // To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags
- // only when page and span directions are identical.
- if ($direction === strtoupper(I18N::direction())) {
- while (true) {
- $starPos = strpos($textSpan, '*');
- if ($starPos === false) {
- break;
- }
- $trailingText = substr($textSpan, $starPos + 1);
- $textSpan = substr($textSpan, 0, $starPos);
- $wordStart = strrpos($textSpan, ' '); // Find the start of the word
- if ($wordStart !== false) {
- $leadingText = substr($textSpan, 0, $wordStart + 1);
- $wordText = substr($textSpan, $wordStart + 1);
- } else {
- $leadingText = '';
- $wordText = $textSpan;
- }
- $textSpan = $leadingText . '<u>' . $wordText . '</u>' . $trailingText;
- }
- $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '<u>\1</u>', $textSpan);
- // The &nbsp; is a work-around for a TCPDF bug eating blanks.
- $textSpan = str_replace(array(' <u>', '</u> '), array('&nbsp;<u>', '</u>&nbsp;'), $textSpan);
- } else {
- // Text and page directions differ: remove the <span> and </span>
- $textSpan = preg_replace('~(.*)\*~', '\1', $textSpan);
- $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '\1', $textSpan);
- }
-
- return $textSpan;
-}
-
-/**
- * Get the next character from an input string
- *
- * @param string $text
- * @param string $offset
- *
- * @return array
- */
-function getChar($text, $offset) {
-
- if ($text == '') {
- return array('letter' => '', 'length' => 0);
- }
-
- $char = substr($text, $offset, 1);
- $length = 1;
- if ((ord($char) & 0xE0) == 0xC0) {
- $length = 2;
- }
- if ((ord($char) & 0xF0) == 0xE0) {
- $length = 3;
- }
- if ((ord($char) & 0xF8) == 0xF0) {
- $length = 4;
- }
- $letter = substr($text, $offset, $length);
-
- return array('letter' => $letter, 'length' => $length);
-}
-
-/**
- * Insert <br> into current span
- *
- * @param string $result
- */
-function breakCurrentSpan(&$result) {
- global $currentState, $waitingText;
-
- // Interrupt the current span, insert that <br>, and then continue the current span
- $result .= $waitingText;
- $waitingText = '';
-
- $breakString = '<' . $currentState . 'br>';
- $result .= $breakString;
-
- return;
-}
-
-/**
- * Begin current span
- *
- * @param string $result
- */
-function beginCurrentSpan(&$result) {
- global $currentState, $startLTR, $startRTL, $posSpanStart;
-
- if ($currentState == 'LTR') {
- $result .= $startLTR;
- }
- if ($currentState == 'RTL') {
- $result .= $startRTL;
- }
-
- $posSpanStart = strlen($result);
-
- return;
-}
-
-/**
- * Finish current span
- *
- * @param string $result
- * @param bool $theEnd
- */
-function finishCurrentSpan(&$result, $theEnd = false) {
- global $previousState, $currentState, $posSpanStart, $waitingText;
- global $startLTR, $endLTR, $startRTL, $endRTL;
- global $numbers, $punctuation;
-
- $textSpan = substr($result, $posSpanStart);
- $result = substr($result, 0, $posSpanStart);
-
- // Get rid of empty spans, so that our check for presence of RTL will work
- $result = str_replace(array($startLTR . $endLTR, $startRTL . $endRTL), '', $result);
-
- // Look for numeric strings that are times (hh:mm:ss). These have to be separated from surrounding numbers.
- $tempResult = '';
- while ($textSpan != '') {
- $posColon = strpos($textSpan, ':');
- if ($posColon === false) {
- break;
- } // No more possible time strings
- $posLRE = strpos($textSpan, WT_UTF8_LRE);
- if ($posLRE === false) {
- break;
- } // No more numeric strings
- $posPDF = strpos($textSpan, WT_UTF8_PDF, $posLRE);
- if ($posPDF === false) {
- break;
- } // No more numeric strings
-
- $tempResult .= substr($textSpan, 0, $posLRE + 3); // Copy everything preceding the numeric string
- $numericString = substr($textSpan, $posLRE + 3, $posPDF - $posLRE); // Separate the entire numeric string
- $textSpan = substr($textSpan, $posPDF + 3);
- $posColon = strpos($numericString, ':');
- if ($posColon === false) {
- // Nothing that looks like a time here
- $tempResult .= $numericString;
- continue;
- }
- $posBlank = strpos($numericString . ' ', ' ');
- $posNbsp = strpos($numericString . '&nbsp;', '&nbsp;');
- if ($posBlank < $posNbsp) {
- $posSeparator = $posBlank;
- $lengthSeparator = 1;
- } else {
- $posSeparator = $posNbsp;
- $lengthSeparator = 6;
- }
- if ($posColon > $posSeparator) {
- // We have a time string preceded by a blank: Exclude that blank from the numeric string
- $tempResult .= substr($numericString, 0, $posSeparator);
- $tempResult .= WT_UTF8_PDF;
- $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
- $tempResult .= WT_UTF8_LRE;
- $numericString = substr($numericString, $posSeparator + $lengthSeparator);
- }
-
- $posBlank = strpos($numericString, ' ');
- $posNbsp = strpos($numericString, '&nbsp;');
- if ($posBlank === false && $posNbsp === false) {
- // The time string isn't followed by a blank
- $textSpan = $numericString . $textSpan;
- continue;
- }
-
- // We have a time string followed by a blank: Exclude that blank from the numeric string
- if ($posBlank === false) {
- $posSeparator = $posNbsp;
- $lengthSeparator = 6;
- } elseif ($posNbsp === false) {
- $posSeparator = $posBlank;
- $lengthSeparator = 1;
- } elseif ($posBlank < $posNbsp) {
- $posSeparator = $posBlank;
- $lengthSeparator = 1;
- } else {
- $posSeparator = $posNbsp;
- $lengthSeparator = 6;
- }
- $tempResult .= substr($numericString, 0, $posSeparator);
- $tempResult .= WT_UTF8_PDF;
- $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
- $posSeparator += $lengthSeparator;
- $numericString = substr($numericString, $posSeparator);
- $textSpan = WT_UTF8_LRE . $numericString . $textSpan;
- }
- $textSpan = $tempResult . $textSpan;
- $trailingBlanks = '';
- $trailingBreaks = '';
-
- /* ****************************** LTR text handling ******************************** */
-
- if ($currentState === 'LTR') {
- // Move trailing numeric strings to the following RTL text. Include any blanks preceding or following the numeric text too.
- if (I18N::direction() === 'rtl' && $previousState === 'RTL' && !$theEnd) {
- $trailingString = '';
- $savedSpan = $textSpan;
- while ($textSpan !== '') {
- // Look for trailing spaces and tentatively move them
- if (substr($textSpan, -1) === ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) === '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -3) !== WT_UTF8_PDF) {
- // There is no trailing numeric string
- $textSpan = $savedSpan;
- break;
- }
-
- // We have a numeric string
- $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
- if ($posStartNumber === false) {
- $posStartNumber = 0;
- }
- $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
- $textSpan = substr($textSpan, 0, $posStartNumber);
-
- // Look for more spaces and move them too
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
-
- $waitingText = $trailingString . $waitingText;
- break;
- }
- }
-
- $savedSpan = $textSpan;
- // Move any trailing <br>, optionally preceded or followed by blanks, outside this LTR span
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingBlanks = ' ' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr('......' . $textSpan, -6) == '&nbsp;') {
- $trailingBlanks = '&nbsp;' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
- while (substr($textSpan, -9) == '<LTRbr>') {
- $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
- $textSpan = substr($textSpan, 0, -9);
- }
- if ($trailingBreaks != '') {
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingBreaks = ' ' . $trailingBreaks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr('......' . $textSpan, -6) == '&nbsp;') {
- $trailingBreaks = '&nbsp;' . $trailingBreaks;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
- $waitingText = $trailingBlanks . $waitingText; // Put those trailing blanks inside the following span
- } else {
- $textSpan = $savedSpan;
- }
-
- $trailingBlanks = '';
- $trailingPunctuation = '';
- $trailingID = '';
- $trailingSeparator = '';
- $leadingSeparator = '';
- while (I18N::direction() === 'rtl') {
- if (strpos($result, $startRTL) !== false) {
- // Remove trailing blanks for inclusion in a separate LTR span
- while ($textSpan != '') {
- if (substr($textSpan, -1) === ' ') {
- $trailingBlanks = ' ' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) === '&nbsp;') {
- $trailingBlanks = '&nbsp;' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
-
- // Remove trailing punctuation for inclusion in a separate LTR span
- if ($textSpan == '') {
- $trailingChar = "\n";
- } else {
- $trailingChar = substr($textSpan, -1);
- }
- if (strpos($punctuation, $trailingChar) !== false) {
- $trailingPunctuation = $trailingChar;
- $textSpan = substr($textSpan, 0, -1);
- }
- }
-
- // Remove trailing ID numbers that look like "(xnnn)" for inclusion in a separate LTR span
- while (true) {
- if (substr($textSpan, -1) != ')') {
- break;
- } // There is no trailing ')'
- $posLeftParen = strrpos($textSpan, '(');
- if ($posLeftParen === false) {
- break;
- } // There is no leading '('
- $temp = stripLRMRLM(substr($textSpan, $posLeftParen)); // Get rid of UTF8 control codes
-
- // If the parenthesized text doesn't look like an ID number,
- // we don't want to touch it.
- // This check won’t work if somebody uses ID numbers with an unusual format.
- $offset = 1;
- $charArray = getchar($temp, $offset); // Get 1st character of parenthesized text
- if (strpos($numbers, $charArray['letter']) !== false) {
- break;
- }
- $offset += $charArray['length']; // Point at 2nd character of parenthesized text
- if (strpos($numbers, substr($temp, $offset, 1)) === false) {
- break;
- }
- // 1st character of parenthesized text is alpha, 2nd character is a digit; last has to be a digit too
- if (strpos($numbers, substr($temp, -2, 1)) === false) {
- break;
- }
-
- $trailingID = substr($textSpan, $posLeftParen);
- $textSpan = substr($textSpan, 0, $posLeftParen);
- break;
- }
-
- // Look for " - " or blank preceding the ID number and remove it for inclusion in a separate LTR span
- if ($trailingID != '') {
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingSeparator = ' ' . $trailingSeparator;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $trailingSeparator = '&nbsp;' . $trailingSeparator;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- if (substr($textSpan, -1) == '-') {
- $trailingSeparator = '-' . $trailingSeparator;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
- }
-
- // Look for " - " preceding the text and remove it for inclusion in a separate LTR span
- $foundSeparator = false;
- $savedSpan = $textSpan;
- while ($textSpan != '') {
- if (substr($textSpan, 0, 1) == ' ') {
- $leadingSeparator = ' ' . $leadingSeparator;
- $textSpan = substr($textSpan, 1);
- continue;
- }
- if (substr($textSpan, 0, 6) == '&nbsp;') {
- $leadingSeparator = '&nbsp;' . $leadingSeparator;
- $textSpan = substr($textSpan, 6);
- continue;
- }
- if (substr($textSpan, 0, 1) == '-') {
- $leadingSeparator = '-' . $leadingSeparator;
- $textSpan = substr($textSpan, 1);
- $foundSeparator = true;
- continue;
- }
- break;
- }
- if (!$foundSeparator) {
- $textSpan = $savedSpan;
- $leadingSeparator = '';
- }
- break;
- }
-
- // We're done: finish the span
- $textSpan = starredName($textSpan, 'LTR'); // Wrap starred name in <u> and </u> tags
- while (true) {
- // Remove blanks that precede <LTRbr>
- if (strpos($textSpan, ' <LTRbr>') !== false) {
- $textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan);
- continue;
- }
- if (strpos($textSpan, '&nbsp;<LTRbr>') !== false) {
- $textSpan = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $textSpan);
- continue;
- }
- break;
- }
- if ($leadingSeparator != '') {
- $result = $result . $startLTR . $leadingSeparator . $endLTR;
- }
- $result = $result . $textSpan . $endLTR;
- if ($trailingSeparator != '') {
- $result = $result . $startLTR . $trailingSeparator . $endLTR;
- }
- if ($trailingID != '') {
- $result = $result . $startLTR . $trailingID . $endLTR;
- }
- if ($trailingPunctuation != '') {
- $result = $result . $startLTR . $trailingPunctuation . $endLTR;
- }
- if ($trailingBlanks != '') {
- $result = $result . $startLTR . $trailingBlanks . $endLTR;
- }
- }
-
- /* ****************************** RTL text handling ******************************** */
-
- if ($currentState == 'RTL') {
- $savedSpan = $textSpan;
-
- // Move any trailing <br>, optionally followed by blanks, outside this RTL span
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingBlanks = ' ' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr('......' . $textSpan, -6) == '&nbsp;') {
- $trailingBlanks = '&nbsp;' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
- while (substr($textSpan, -9) == '<RTLbr>') {
- $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
- $textSpan = substr($textSpan, 0, -9);
- }
- if ($trailingBreaks != '') {
- $waitingText = $trailingBlanks . $waitingText; // Put those trailing blanks inside the following span
- } else {
- $textSpan = $savedSpan;
- }
-
- // Move trailing numeric strings to the following LTR text. Include any blanks preceding or following the numeric text too.
- if (!$theEnd && I18N::direction() !== 'rtl') {
- $trailingString = '';
- $savedSpan = $textSpan;
- while ($textSpan != '') {
- // Look for trailing spaces and tentatively move them
- if (substr($textSpan, -1) === ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) === '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -3) !== WT_UTF8_PDF) {
- // There is no trailing numeric string
- $textSpan = $savedSpan;
- break;
- }
-
- // We have a numeric string
- $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
- if ($posStartNumber === false) {
- $posStartNumber = 0;
- }
- $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
- $textSpan = substr($textSpan, 0, $posStartNumber);
-
- // Look for more spaces and move them too
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
-
- $waitingText = $trailingString . $waitingText;
- break;
- }
- }
-
- // Trailing " - " needs to be prefixed to the following span
- if (!$theEnd && substr('...' . $textSpan, -3) == ' - ') {
- $textSpan = substr($textSpan, 0, -3);
- $waitingText = ' - ' . $waitingText;
- }
-
- while (I18N::direction() === 'rtl') {
- // Look for " - " preceding <RTLbr> and relocate it to the front of the string
- $posDashString = strpos($textSpan, ' - <RTLbr>');
- if ($posDashString === false) {
- break;
- }
- $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
- if ($posStringStart === false) {
- $posStringStart = 0;
- } else {
- $posStringStart += 9;
- } // Point to the first char following the last <RTLbr>
-
- $textSpan = substr($textSpan, 0, $posStringStart) . ' - ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 3);
- }
-
- // Strip leading spaces from the RTL text
- $countLeadingSpaces = 0;
- while ($textSpan != '') {
- if (substr($textSpan, 0, 1) == ' ') {
- $countLeadingSpaces++;
- $textSpan = substr($textSpan, 1);
- continue;
- }
- if (substr($textSpan, 0, 6) == '&nbsp;') {
- $countLeadingSpaces++;
- $textSpan = substr($textSpan, 6);
- continue;
- }
- break;
- }
-
- // Strip trailing spaces from the RTL text
- $countTrailingSpaces = 0;
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $countTrailingSpaces++;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $countTrailingSpaces++;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
-
- // Look for trailing " -", reverse it, and relocate it to the front of the string
- if (substr($textSpan, -2) === ' -') {
- $posDashString = strlen($textSpan) - 2;
- $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
- if ($posStringStart === false) {
- $posStringStart = 0;
- } else {
- $posStringStart += 9;
- } // Point to the first char following the last <RTLbr>
-
- $textSpan = substr($textSpan, 0, $posStringStart) . '- ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 2);
- }
-
- if ($countLeadingSpaces != 0) {
- $newLength = strlen($textSpan) + $countLeadingSpaces;
- $textSpan = str_pad($textSpan, $newLength, ' ', (I18N::direction() === 'rtl' ? STR_PAD_LEFT : STR_PAD_RIGHT));
- }
- if ($countTrailingSpaces != 0) {
- if (I18N::direction() === 'ltr') {
- if ($trailingBreaks === '') {
- // Move trailing RTL spaces to front of following LTR span
- $newLength = strlen($waitingText) + $countTrailingSpaces;
- $waitingText = str_pad($waitingText, $newLength, ' ', STR_PAD_LEFT);
- }
- } else {
- $newLength = strlen($textSpan) + $countTrailingSpaces;
- $textSpan = str_pad($textSpan, $newLength, ' ', STR_PAD_RIGHT);
- }
- }
-
- // We're done: finish the span
- $textSpan = starredName($textSpan, 'RTL'); // Wrap starred name in <u> and </u> tags
- $result = $result . $textSpan . $endRTL;
- }
-
- if ($currentState != 'LTR' && $currentState != 'RTL') {
- $result = $result . $textSpan;
- }
-
- $result .= $trailingBreaks; // Get rid of any waiting <br>
-
- return;
-}
-
-/**
- * @param string $string
- * @param int $width
- * @param string $sep
- * @param bool $cut
- *
- * @return string
- */
-function utf8_wordwrap($string, $width = 75, $sep = "\n", $cut = false) {
- $out = '';
- while ($string) {
- if (mb_strlen($string) <= $width) {
- // Do not wrap any text that is less than the output area.
- $out .= $string;
- $string = '';
- } else {
- $sub1 = mb_substr($string, 0, $width + 1);
- if (mb_substr($string, mb_strlen($sub1) - 1, 1) == ' ') {
- // include words that end by a space immediately after the area.
- $sub = $sub1;
- } else {
- $sub = mb_substr($string, 0, $width);
- }
- $spacepos = strrpos($sub, ' ');
- if ($spacepos === false) {
- // No space on line?
- if ($cut) {
- $out .= $sub . $sep;
- $string = mb_substr($string, mb_strlen($sub));
- } else {
- $spacepos = strpos($string, ' ');
- if ($spacepos === false) {
- $out .= $string;
- $string = '';
- } else {
- $out .= substr($string, 0, $spacepos) . $sep;
- $string = substr($string, $spacepos + 1);
- }
- }
- } else {
- // Split at space;
- $out .= substr($string, 0, $spacepos) . $sep;
- $string = substr($string, $spacepos + 1);
- }
- }
- }
-
- return $out;
-}