1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/webtrees_ynh.git synced 2024-09-03 18:26:37 +02:00
webtrees_ynh/sources/app/Functions/FunctionsDb.php

1050 lines
33 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* webtrees: online genealogy
* Copyright (C) 2016 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Fisharebest\Webtrees\Functions;
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;
/**
* Class FunctionsDb - common functions
*/
class FunctionsDb {
/**
* 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[]
*/
public static function fetchAllLinks($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('%@', ?, '@%') AND new_gedcom NOT LIKE CONCAT('0 @', ?, '@%')"
)->execute(array(
$gedcom_id,
$xref,
$gedcom_id,
$xref,
$xref,
))->fetchOneColumn();
}
/**
* Get a list of all the sources.
*
* @param Tree $tree
*
* @return Source[] array
*/
public static function getSourceList(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);
}
$list = array_filter($list, function (Source $x) { return $x->canShowName(); });
usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
return $list;
}
/**
* Get a list of all the repositories.
*
* @param Tree $tree
*
* @return Repository[] array
*/
public static function getRepositoryList(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);
}
$list = array_filter($list, function (Repository $x) { return $x->canShowName(); });
usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
return $list;
}
/**
* Get a list of all the shared notes.
*
* @param Tree $tree
*
* @return Note[] array
*/
public static function getNoteList(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);
}
$list = array_filter($list, function (Note $x) { return $x->canShowName(); });
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[]
*/
public static function searchIndividuals(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|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;
}
$list = array_filter($list, function (Individual $x) { return $x->canShowName(); });
return $list;
}
/**
* Search the names of individuals
*
* @param string[] $query Search terms
* @param Tree[] $trees The trees to search
*
* @return Individual[]
*/
public static function searchIndividualNames(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);
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;
}
}
}
$list = array_filter($list, function (Individual $x) { return $x->canShowName(); });
return $list;
}
/**
* Search for individuals names/places using phonetic matching
*
* @param string $soundex
* @param string $lastname
* @param string $firstname
* @param string $place
* @param Tree[] $trees
*
* @return Individual[]
*/
public static function searchIndividualsPhonetic($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) {
$list[] = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
}
$list = array_filter($list, function (Individual $x) { return $x->canShowName(); });
return $list;
}
/**
* Search family records
*
* @param string[] $query Search terms
* @param Tree[] $trees The trees to search
*
* @return Family[]
*/
public static function searchFamilies(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|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;
}
$list = array_filter($list, function (Family $x) { return $x->canShowName(); });
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[]
*/
public static function searchFamilyNames(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) {
$list[] = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
}
$list = array_filter($list, function (Family $x) use ($query) {
$name = I18N::strtolower(strip_tags($x->getFullName()));
foreach ($query as $q) {
if (stripos($name, I18N::strtolower($q)) === false) {
return false;
}
}
return true;
});
return $list;
}
/**
* Search the sources
*
* @param string[] $query Search terms
* @param Tree[] $trees The tree to search
*
* @return Source[]
*/
public static function searchSources($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|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;
}
$list = array_filter($list, function (Source $x) { return $x->canShowName(); });
return $list;
}
/**
* Search the shared notes
*
* @param string[] $query Search terms
* @param Tree[] $trees The tree to search
*
* @return Note[]
*/
public static function searchNotes(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|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;
}
$list = array_filter($list, function (Note $x) { return $x->canShowName(); });
return $list;
}
/**
* Search the repositories
*
* @param string[] $query Search terms
* @param Tree[] $trees The trees to search
*
* @return Repository[]
*/
public static function searchRepositories(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|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;
}
$list = array_filter($list, function (Repository $x) { return $x->canShowName(); });
return $list;
}
/**
* Find the record for the given rin.
*
* @param string $rin
*
* @return string
*/
public static function findRin($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.
*
* @deprecated
*
* @param int $min The number of times a surname must occur before it is added to the array
* @param Tree $tree
*
* @return int[]
*/
public static function getCommonSurnames($min, Tree $tree) {
return self::getTopSurnames($tree->getTreeId(), $min, 0);
}
/**
* 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 int[]
*/
public static function getTopSurnames($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.', '')" .
" 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.', '')" .
" 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[]
*/
public static function getAnniversaryEvents($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 didnt 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 didnt 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[]
*/
public static function getCalendarEvents($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";
$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 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 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[]
*/
public static function getEventsList($jd1, $jd2, $events, Tree $tree) {
$found_facts = array();
for ($jd = $jd1; $jd <= $jd2; ++$jd) {
$found_facts = array_merge($found_facts, self::getAnniversaryEvents($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
*/
public static function isMediaUsedInOtherTree($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();
}
/**
* Get the blocks for a specified user.
*
* @param int $user_id
*
* @return string[][]
*/
public static function getUserBlocks($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[][]
*/
public static function getTreeBlocks($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
*/
public static function updateFavorites($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();
}
}