execute(array($ged_id)) ->fetchOne(); break; case "FAM": return WT_DB::prepare("SELECT MIN(f_id) FROM `##families` WHERE f_file=?") ->execute(array($ged_id)) ->fetchOne(); case "SOUR": return WT_DB::prepare("SELECT MIN(s_id) FROM `##sources` WHERE s_file=?") ->execute(array($ged_id)) ->fetchOne(); case "OBJE": return WT_DB::prepare("SELECT MIN(m_media) FROM `##media` WHERE m_gedfile=?") ->execute(array($ged_id)) ->fetchOne(); default: return WT_DB::prepare("SELECT MIN(o_id) FROM `##other` WHERE o_file=? AND o_type=?") ->execute(array($ged_id, $type)) ->fetchOne(); } } //-- gets the last record in the gedcom function get_last_xref($type, $ged_id=WT_GED_ID) { switch ($type) { case "INDI": return WT_DB::prepare("SELECT MAX(i_id) FROM `##individuals` WHERE i_file=?") ->execute(array($ged_id)) ->fetchOne(); break; case "FAM": return WT_DB::prepare("SELECT MAX(f_id) FROM `##families` WHERE f_file=?") ->execute(array($ged_id)) ->fetchOne(); case "SOUR": return WT_DB::prepare("SELECT MAX(s_id) FROM `##sources` WHERE s_file=?") ->execute(array($ged_id)) ->fetchOne(); case "OBJE": return WT_DB::prepare("SELECT MAX(m_media) FROM `##media` WHERE m_gedfile=?") ->execute(array($ged_id)) ->fetchOne(); default: return WT_DB::prepare("SELECT MAX(o_id) FROM `##other` WHERE o_file=? AND o_type=?") ->execute(array($ged_id, $type)) ->fetchOne(); } } //-- gets the next person in the gedcom function get_next_xref($pid, $ged_id=WT_GED_ID) { $type=gedcom_record_type($pid, $ged_id); switch ($type) { case "INDI": return WT_DB::prepare("SELECT MIN(i_id) FROM `##individuals` WHERE i_file=? AND i_id>?") ->execute(array($ged_id, $pid)) ->fetchOne(); break; case "FAM": return WT_DB::prepare("SELECT MIN(f_id) FROM `##families` WHERE f_file=? AND f_id>?") ->execute(array($ged_id, $pid)) ->fetchOne(); case "SOUR": return WT_DB::prepare("SELECT MIN(s_id) FROM `##sources` WHERE s_file=? AND s_id>?") ->execute(array($ged_id, $pid)) ->fetchOne(); case "OBJE": return WT_DB::prepare("SELECT MIN(m_media) FROM `##media` WHERE m_gedfile=? AND m_media>?") ->execute(array($ged_id, $pid)) ->fetchOne(); default: return WT_DB::prepare("SELECT MIN(o_id) FROM `##other` WHERE o_file=? AND o_type=? AND o_id>?") ->execute(array($ged_id, $type, $pid)) ->fetchOne(); } } //-- gets the previous person in the gedcom function get_prev_xref($pid, $ged_id=WT_GED_ID) { $type=gedcom_record_type($pid, $ged_id); switch ($type) { case "INDI": return WT_DB::prepare("SELECT MAX(i_id) FROM `##individuals` WHERE i_file=? AND i_idexecute(array($ged_id, $pid)) ->fetchOne(); break; case "FAM": return WT_DB::prepare("SELECT MAX(f_id) FROM `##families` WHERE f_file=? AND f_idexecute(array($ged_id, $pid)) ->fetchOne(); case "SOUR": return WT_DB::prepare("SELECT MAX(s_id) FROM `##sources` WHERE s_file=? AND s_idexecute(array($ged_id, $pid)) ->fetchOne(); case "OBJE": return WT_DB::prepare("SELECT MAX(m_media) FROM `##media` WHERE m_gedfile=? AND m_mediaexecute(array($ged_id, $pid)) ->fetchOne(); default: return WT_DB::prepare("SELECT MAX(o_id) FROM `##other` WHERE o_file=? AND o_type=? AND o_idexecute(array($ged_id, $type, $pid)) ->fetchOne(); } } //////////////////////////////////////////////////////////////////////////////// // Get a list of initial surname letters for indilist.php and famlist.php // $marnm - if set, include married names // $fams - if set, only consider individuals with FAMS records // $ged_id - only consider individuals from this gedcom //////////////////////////////////////////////////////////////////////////////// function get_indilist_salpha($marnm, $fams, $ged_id) { $alphas=array(); // This logic relies on the database's collation rules to ensure that accented letters // and digraphs appear in the correct listing. foreach (explode(' ', i18n::$alphabet) as $letter) { $query="SELECT COUNT(DISTINCT i_id) FROM `##individuals`"; if ($marnm) { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file)"; } else { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file AND n_type!='_MARNM')"; } if ($fams) { $query.=" JOIN `##link` ON (i_id=l_from AND i_file=l_file AND l_type='FAMS')"; } $query.=" WHERE n_file=? AND n_sort LIKE '{$letter}%' COLLATE '".i18n::$collation."'"; foreach (explode(' ', i18n::$alphabet) as $letter2) { if ($letter!=$letter2 && strpos($letter, $letter2)!==0) { $query.=" AND n_sort NOT LIKE '{$letter2}%' COLLATE '".i18n::$collation."'"; } } $alphas[$letter]=WT_DB::prepare($query)->execute(array(WT_GED_ID))->fetchOne(); } // Now repeat for all letters not in our alphabet. // This includes "@" (unknown) and "," (none) $query= "SELECT LEFT(n_sort, 1), COUNT(DISTINCT i_id)". " FROM `##individuals`"; if ($marnm) { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file)"; } else { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file AND n_type!='_MARNM')"; } if ($fams) { $query.=" JOIN `##link` ON (i_id=l_from AND i_file=l_file AND l_type='FAMS')"; } $query.=" WHERE n_file=?"; foreach (explode(' ', i18n::$alphabet) as $letter) { $query.=" AND n_surn NOT LIKE '{$letter}%' COLLATE '".i18n::$collation."'"; } $query.=" GROUP BY LEFT(n_surn, 1)"; foreach (WT_DB::prepare($query)->execute(array(WT_GED_ID))->fetchAssoc() as $letter=>$count) { $alphas[$letter]=$count; } // Force "," and "@" first to the end of the list if (array_key_exists(',', $alphas)) { $count=$alphas[',']; unset($alphas[',']); $alphas[',']=$count; } if (array_key_exists('@', $alphas)) { $count=$alphas['@']; unset($alphas['@']); $alphas['@']=$count; } return $alphas; } //////////////////////////////////////////////////////////////////////////////// // Get a list of initial given name letters for indilist.php and famlist.php // $surn - if set, only consider people with this surname // $salpha - if set, only consider surnames starting with this letter // $marnm - if set, include married names // $fams - if set, only consider individuals with FAMS records // $ged_id - only consider individuals from this gedcom //////////////////////////////////////////////////////////////////////////////// function get_indilist_galpha($surn, $salpha, $marnm, $fams, $ged_id) { $alphas=array(); // This logic relies on the database's collation rules to ensure that accented letters // and digraphs appear in the correct listing. foreach (explode(' ', i18n::$alphabet) as $letter) { $query="SELECT COUNT(DISTINCT i_id) FROM `##individuals`"; if ($marnm) { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file)"; } else { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file AND n_type!='_MARNM')"; } if ($fams) { $query.=" JOIN `##link` ON (i_id=l_from AND i_file=l_file AND l_type='FAMS')"; } $query.=" WHERE n_file=?"; if ($surn) { $query.=" AND n_sort LIKE ".WT_DB::quote("{$surn},%")." COLLATE '".i18n::$collation."'"; } elseif ($salpha) { $query.=" AND n_sort LIKE ".WT_DB::quote("{$salpha}%,%")." COLLATE '".i18n::$collation."'"; } $query.=" AND n_givn LIKE '".$letter."%' COLLATE '".i18n::$collation."'"; foreach (explode(' ', i18n::$alphabet) as $letter2) { if ($letter!=$letter2 && strpos($letter, $letter2)!==0) { $query.=" AND n_givn NOT LIKE '{$letter2}%' COLLATE '".i18n::$collation."'"; } } $alphas[$letter]=WT_DB::prepare($query)->execute(array(WT_GED_ID))->fetchOne(); } // Now repeat for all letters not in our alphabet. // This includes "@" (unknown) and "," (none) $query= "SELECT LEFT(n_givn, 1), COUNT(DISTINCT i_id)". " FROM `##individuals`"; if ($marnm) { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file)"; } else { $query.=" JOIN `##name` ON (i_id=n_id AND i_file=n_file AND n_type!='_MARNM')"; } if ($fams) { $query.=" JOIN `##link` ON (i_id=l_from AND i_file=l_file AND l_type='FAMS')"; } $query.=" WHERE n_file=?"; if ($surn) { $query.=" AND n_sort LIKE ".WT_DB::quote("{$surn},%")." COLLATE '".i18n::$collation."'"; } elseif ($salpha) { $query.=" AND n_sort LIKE ".WT_DB::quote("{$salpha}%,%")." COLLATE '".i18n::$collation."'"; } $query.=" AND n_givn LIKE '".$letter."%' COLLATE '".i18n::$collation."'"; foreach (explode(' ', i18n::$alphabet) as $letter) { $query.=" AND n_givn NOT LIKE '{$letter}%' COLLATE '".i18n::$collation."'"; } $query.=" GROUP BY LEFT(n_givn, 1)"; foreach (WT_DB::prepare($query)->execute(array(WT_GED_ID))->fetchAssoc() as $letter=>$count) { $alphas[$letter]=$count; } // Force "," and "@" first to the end of the list if (array_key_exists(',', $alphas)) { $count=$alphas[',']; unset($alphas[',']); $alphas[',']=$count; } if (array_key_exists('@', $alphas)) { $count=$alphas['@']; unset($alphas['@']); $alphas['@']=$count; } return $alphas; } //////////////////////////////////////////////////////////////////////////////// // Get a list of surnames for indilist.php // $surn - if set, only fetch people with this surname // $salpha - if set, only consider surnames starting with this letter // $marnm - if set, include married names // $fams - if set, only consider individuals with FAMS records // $ged_id - only consider individuals from this gedcom //////////////////////////////////////////////////////////////////////////////// function get_indilist_surns($surn, $salpha, $marnm, $fams, $ged_id) { $sql="SELECT DISTINCT n_surn, n_surname, n_id FROM `##individuals` JOIN `##name` ON (i_id=n_id AND i_file=n_file)"; if ($fams) { $sql.=" JOIN `##link` ON (i_id=l_from AND i_file=l_file AND l_type='FAMS')"; } $where=array("n_file={$ged_id}"); if (!$marnm) { $where[]="n_type!='_MARNM'"; } if ($surn) { // Match a surname $where[]="n_surn LIKE ".WT_DB::quote("{$surn}")." COLLATE '".i18n::$collation."'"; } elseif ($salpha==',') { // Match a surname-less name $where[]="n_surn = ''"; } elseif ($salpha) { // Match a surname initial $where[]="n_surn LIKE ".WT_DB::quote("{$salpha}%")." COLLATE '".i18n::$collation."'"; } else { // Match all individuals $where[]="n_surn <>'@N.N.'"; $where[]="n_surn <> ''"; } $sql.=" WHERE ".implode(' AND ', $where)." ORDER BY n_surn"; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(); foreach ($rows as $row) { $list[utf8_strtoupper($row->n_surn)][$row->n_surname][$row->n_id]=true; } return $list; } //////////////////////////////////////////////////////////////////////////////// // Get a list of surnames for indilist.php // $surn - if set, only fetch people with this surname // $salpha - if set, only consider surnames starting with this letter // $marnm - if set, include married names // $ged_id - only consider individuals from this gedcom //////////////////////////////////////////////////////////////////////////////// function get_famlist_surns($surn, $salpha, $marnm, $ged_id) { $sql="SELECT DISTINCT n_surn, n_surname, l_to FROM `##individuals` JOIN `##name` ON (i_id=n_id AND i_file=n_file) JOIN `##link` ON (i_id=l_from AND i_file=l_file AND l_type='FAMS')"; $where=array("n_file={$ged_id}"); if (!$marnm) { $where[]="n_type!='_MARNM'"; } if ($surn) { // Match a surname $where[]="n_surn LIKE ".WT_DB::quote("{$surn}")." COLLATE '".i18n::$collation."'"; } elseif ($salpha==',') { // Match a surname-less name $where[]="n_surn = ''"; } elseif ($salpha) { // Match a surname initial $where[]="n_surn LIKE ".WT_DB::quote("{$salpha}%")." COLLATE '".i18n::$collation."'"; } else { // Match all individuals $where[]="n_surn <> '@N.N.'"; $where[]="n_surn <> ''"; } $sql.=" WHERE ".implode(' AND ', $where)." ORDER BY n_surn"; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(); foreach ($rows as $row) { $list[utf8_strtoupper($row->n_surn)][$row->n_surname][$row->l_to]=true; } return $list; } //////////////////////////////////////////////////////////////////////////////// // Get a list of individuals for indilist.php // $surn - if set, only fetch people with this surname // $salpha - if set, only fetch surnames starting with this letter // $galpha - if set, only fetch given names starting with this letter // $marnm - if set, include married names // $fams - if set, only fetch individuals with FAMS records // $ged_id - if set, only fetch individuals from this gedcom // // All parameters must be in upper case. We search against a database column // that contains uppercase values. This will allow non utf8-aware database // to match diacritics. // // To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@" // To search for names with no surnames, use $salpha="," //////////////////////////////////////////////////////////////////////////////// function get_indilist_indis($surn='', $salpha='', $galpha='', $marnm=false, $fams=false, $ged_id=null) { $sql="SELECT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex, n_surn, n_surname, n_num FROM `##individuals` JOIN `##name` ON (i_id=n_id AND i_file=n_file)"; if ($fams) { $sql.=" JOIN `##link` ON (i_id=l_from AND i_file=l_file)"; } $where=array(); if ($ged_id) { $where[]="n_file={$ged_id}"; } if (!$marnm) { $where[]="n_type!='_MARNM'"; } if ($surn) { // Match a surname, with or without a given initial if ($galpha) { $where[]="n_sort LIKE ".WT_DB::quote("{$surn},{$galpha}%")." COLLATE '".i18n::$collation."'"; } else { $where[]="n_sort LIKE ".WT_DB::quote("{$surn},%")." COLLATE '".i18n::$collation."'"; } } elseif ($salpha==',') { // Match a surname-less name, with or without a given initial if ($galpha) { $where[]="n_sort LIKE ".WT_DB::quote(",{$galpha}%")." COLLATE '".i18n::$collation."'"; } else { $where[]="n_sort LIKE ".WT_DB::quote(",%")." COLLATE '".i18n::$collation."'"; } } elseif ($salpha) { // Match a surname initial, with or without a given initial if ($galpha) { $where[]="n_sort LIKE ".WT_DB::quote("{$salpha}%,{$galpha}%")." COLLATE '".i18n::$collation."'"; } else { $where[]="n_sort LIKE ".WT_DB::quote("{$salpha}%")." COLLATE '".i18n::$collation."'"; } } elseif ($galpha) { // Match all surnames with a given initial $where[]="n_sort LIKE ".WT_DB::quote("%,{$galpha}%")." COLLATE '".i18n::$collation."'"; } else { // Match all individuals } $sql.=" WHERE ".implode(' AND ', $where)." ORDER BY CASE n_surn WHEN '@N.N.' THEN 1 ELSE 0 END, n_surn, CASE n_givn WHEN '@P.N.' THEN 1 ELSE 0 END, n_givn"; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { $person=Person::getInstance($row); $person->setPrimaryName($row['n_num']); // We need to clone $person, 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. This has some performance/memory // implications, and there is probably a better way. This, however, // is clean, easy and works. $list[]=clone $person; } return $list; } //////////////////////////////////////////////////////////////////////////////// // Get a list of families for famlist.php // $surn - if set, only fetch people with this surname // $salpha - if set, only fetch surnames starting with this letter // $galpha - if set, only fetch given names starting with this letter // $marnm - if set, include married names // $ged_id - if set, only fetch individuals from this gedcom // // All parameters must be in upper case. We search against a database column // that contains uppercase values. This will allow non utf8-aware database // to match diacritics. // // To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@" // To search for names with no surnames, use $salpha="," //////////////////////////////////////////////////////////////////////////////// function get_famlist_fams($surn='', $salpha='', $galpha='', $marnm, $ged_id=null) { $list=array(); foreach (get_indilist_indis($surn, $salpha, $galpha, $marnm, true, $ged_id) as $indi) { foreach ($indi->getSpouseFamilies() as $family) { $list[$family->getXref()]=$family; } } // If we're searching for "Unknown surname", we also need to include families // with missing spouses if ($surn=='@N.N.' || $salpha=='@') { $rows= WT_DB::prepare("SELECT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil FROM `##families` f WHERE f_file={$ged_id} AND (f_husb='' OR f_wife='')") ->execute(array($ged_id)) ->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { $list[]=Family::getInstance($row); } } usort($list, array('GedcomRecord', 'Compare')); return $list; } //////////////////////////////////////////////////////////////////////////////// // Fetch a list of children for an individual, from all their partners. //////////////////////////////////////////////////////////////////////////////// function fetch_child_ids($parent_id, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare("SELECT DISTINCT child.l_from AS xref FROM `##link` child, `##link` spouse WHERE child.l_type=? AND spouse.l_type=? AND child.l_file=spouse.l_file AND child.l_to=spouse.l_to AND spouse.l_from=? AND child.l_file=?"); } return $statement->execute(array('FAMC', 'FAMS', $parent_id, $ged_id))->fetchOneColumn(); } //////////////////////////////////////////////////////////////////////////////// // Count the number of records linked to a given record //////////////////////////////////////////////////////////////////////////////// function count_linked_indi($xref, $link, $ged_id) { return WT_DB::prepare("SELECT COUNT(*) FROM `##link`, `##individuals` WHERE i_file=l_file AND i_id=l_from AND l_file=? AND l_type=? AND l_to=?") ->execute(array($ged_id, $link, $xref)) ->fetchOne(); } function count_linked_fam($xref, $link, $ged_id) { return WT_DB::prepare("SELECT COUNT(*) FROM `##link`, `##families` WHERE f_file=l_file AND f_id=l_from AND l_file=? AND l_type=? AND l_to=?") ->execute(array($ged_id, $link, $xref)) ->fetchOne(); } function count_linked_note($xref, $link, $ged_id) { return WT_DB::prepare("SELECT COUNT(*) FROM `##link`, `##other` WHERE o_file=l_file AND o_id=l_from AND o_type=? AND l_file=? AND l_type=? AND l_to=?") ->execute(array('NOTE', $ged_id, $link, $xref)) ->fetchOne(); } function count_linked_sour($xref, $link, $ged_id) { return WT_DB::prepare("SELECT COUNT(*) FROM `##link`, `##sources` WHERE s_file=l_file AND s_id=l_from AND l_file=? AND l_type=? AND l_to=?") ->execute(array($ged_id, $link, $xref)) ->fetchOne(); } function count_linked_obje($xref, $link, $ged_id) { return WT_DB::prepare("SELECT COUNT(*) FROM `##link`, `##media` WHERE m_gedfile=l_file AND m_media=l_from AND l_file=? AND l_type=? AND l_to=?") ->execute(array($ged_id, $link, $xref)) ->fetchOne(); } //////////////////////////////////////////////////////////////////////////////// // Fetch records linked to a given record //////////////////////////////////////////////////////////////////////////////// function fetch_linked_indi($xref, $link, $ged_id) { $rows=WT_DB::prepare( "SELECT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex". " FROM `##individuals`". " JOIN `##link` ON (i_file=l_file AND i_id=l_from)". " LEFT JOIN `##name` ON (i_file=n_file AND i_id=n_id AND n_num=0)". " WHERE i_file=? AND l_type=? AND l_to=?". " ORDER BY n_sort" )->execute(array($ged_id, $link, $xref))->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Person::getInstance($row); } return $list; } function fetch_linked_fam($xref, $link, $ged_id) { $rows=WT_DB::prepare( "SELECT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil". " FROM `##families`". " JOIN `##link` ON (f_file=l_file AND f_id=l_from)". " LEFT JOIN `##name` ON (f_file=n_file AND f_id=n_id AND n_num=0)". " WHERE f_file=? AND l_type=? AND l_to=?". " ORDER BY n_sort" )->execute(array($ged_id, $link, $xref))->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Family::getInstance($row); } return $list; } function fetch_linked_note($xref, $link, $ged_id) { $rows=WT_DB::prepare( "SELECT 'NOTE' AS type, o_id AS xref, o_file AS ged_id, o_gedcom AS gedrec". " FROM `##other`". " JOIN `##link` ON (o_file=l_file AND o_id=l_from)". " LEFT JOIN `##name` ON (o_file=n_file AND o_id=n_id AND n_num=0)". " WHERE o_file=? AND o_type='NOTE' AND l_type=? AND l_to=?". " ORDER BY n_sort" )->execute(array($ged_id, $link, $xref))->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Note::getInstance($row); } return $list; } function fetch_linked_sour($xref, $link, $ged_id) { $rows=WT_DB::prepare( "SELECT 'SOUR' AS type, s_id AS xref, s_file AS ged_id, s_gedcom AS gedrec". " FROM `##sources`". " JOIN `##link` ON (s_file=l_file AND s_id=l_from)". " LEFT JOIN `##name` ON (s_file=n_file AND s_id=n_id AND n_num=0)". " WHERE s_file=? AND l_type=? AND l_to=?". " ORDER BY n_sort" )->execute(array($ged_id, $link, $xref))->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Source::getInstance($row); } return $list; } function fetch_linked_obje($xref, $link, $ged_id) { $rows=WT_DB::prepare( "SELECT 'OBJE' AS type, m_media AS xref, m_gedfile AS ged_id, m_gedrec AS gedrec, m_titl, m_file". " FROM `##media`". " JOIN `##link` ON (m_gedfile=l_file AND m_media=l_from)". " LEFT JOIN `##name` ON (m_gedfile=n_file AND m_media=n_id AND n_num=0)". " WHERE m_gedfile=? AND l_type=? AND l_to=?". " ORDER BY n_sort" )->execute(array($ged_id, $link, $xref))->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Media::getInstance($row); } return $list; } //////////////////////////////////////////////////////////////////////////////// // Fetch all records linked to a record - when deleting an object, we must // also delete all links to it. //////////////////////////////////////////////////////////////////////////////// function fetch_all_links($xref, $ged_id) { return WT_DB::prepare("SELECT l_from FROM `##link` WHERE l_file=? AND l_to=?") ->execute(array($ged_id, $xref)) ->fetchOneColumn(); } //////////////////////////////////////////////////////////////////////////////// // Fetch a row from the database, corresponding to a gedcom record. // These functions are used to create gedcom objects. // To simplify common processing, the xref, gedcom id and gedcom record are // renamed consistently. The other columns are fetched as they are. //////////////////////////////////////////////////////////////////////////////// function fetch_person_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex ". "FROM `##individuals` WHERE i_id=? AND i_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOneRow(PDO::FETCH_ASSOC); } function fetch_family_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil ". "FROM `##families` WHERE f_id=? AND f_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOneRow(PDO::FETCH_ASSOC); } function fetch_source_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT 'SOUR' AS type, s_id AS xref, s_file AS ged_id, s_gedcom AS gedrec ". "FROM `##sources` WHERE s_id=? AND s_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOneRow(PDO::FETCH_ASSOC); } function fetch_note_record($xref, $ged_id) { // Notes are (currently) stored in the other table return fetch_other_record($xref, $ged_id); } function fetch_media_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT 'OBJE' AS type, m_media AS xref, m_gedfile AS ged_id, m_gedrec AS gedrec, m_titl, m_file ". "FROM `##media` WHERE m_media=? AND m_gedfile=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOneRow(PDO::FETCH_ASSOC); } function fetch_other_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT o_type AS type, o_id AS xref, o_file AS ged_id, o_gedcom AS gedrec ". "FROM `##other` WHERE o_id=? AND o_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOneRow(PDO::FETCH_ASSOC); } function fetch_gedcom_record($xref, $ged_id) { // We don't know the type of the record, so use the prefix to suggest the likely type. global $GEDCOM_ID_PREFIX, $FAM_ID_PREFIX, $SOURCE_ID_PREFIX, $MEDIA_ID_PREFIX; if (strpos($xref, $GEDCOM_ID_PREFIX)===0) { $row=fetch_person_record($xref, $ged_id); } elseif (strpos($xref, $FAM_ID_PREFIX )===0) { $row=fetch_family_record($xref, $ged_id); } elseif (strpos($xref, $SOURCE_ID_PREFIX)===0) { $row=fetch_source_record($xref, $ged_id); } elseif (strpos($xref, $MEDIA_ID_PREFIX )===0) { $row=fetch_media_record ($xref, $ged_id); } else { $row=fetch_other_record ($xref, $ged_id); } if ($row) { // If we found it, good return $row; } else { // Otherwise, try the other types if (strpos($xref, $GEDCOM_ID_PREFIX)!==0 && $row=fetch_person_record($xref, $ged_id)) { return $row; } elseif (strpos($xref, $FAM_ID_PREFIX )!==0 && $row=fetch_family_record($xref, $ged_id)) { return $row; } elseif (strpos($xref, $SOURCE_ID_PREFIX)!==0 && $row=fetch_source_record($xref, $ged_id)) { return $row; } elseif (strpos($xref, $MEDIA_ID_PREFIX )!==0 && $row=fetch_media_record ($xref, $ged_id)) { return $row; } else { return fetch_other_record($xref, $ged_id); } } } /** * find the gedcom record for a family * * @link http://phpgedview.sourceforge.net/devdocs/arrays.php#family * @param string $famid the unique gedcom xref id of the family record to retrieve * @return string the raw gedcom record is returned */ function find_family_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT f_gedcom FROM `##families` WHERE f_id=? AND f_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOne(); } /** * find the gedcom record for an individual * * @link http://phpgedview.sourceforge.net/devdocs/arrays.php#indi * @param string $pid the unique gedcom xref id of the individual record to retrieve * @return string the raw gedcom record is returned */ function find_person_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT i_gedcom FROM `##individuals` WHERE i_id=? AND i_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOne(); } /** * find the gedcom record for a source * * @link http://phpgedview.sourceforge.net/devdocs/arrays.php#source * @param string $sid the unique gedcom xref id of the source record to retrieve * @return string the raw gedcom record is returned */ function find_source_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT s_gedcom FROM `##sources` WHERE s_id=? AND s_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOne(); } /** * Find a repository record by its ID * @param string $rid the record id * @param string $gedfile the gedcom file id */ function find_other_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT o_gedcom FROM `##other` WHERE o_id=? AND o_file=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOne(); } /** * Find a media record by its ID * @param string $rid the record id */ function find_media_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT m_gedrec FROM `##media` WHERE m_media=? AND m_gedfile=?" ); } return $statement->execute(array($xref, $ged_id))->fetchOne(); } // Find the gedcom data for a record. Optionally include pending changes. function find_gedcom_record($xref, $ged_id, $pending=false) { if ($pending) { // This will return NULL if no record exists, or an empty string if the record has been deleted. $gedcom=find_updated_record($xref, $ged_id); } else { $gedcom=null; } if (is_null($gedcom)) { $gedcom=find_person_record($xref, $ged_id); } if (is_null($gedcom)) { $gedcom=find_family_record($xref, $ged_id); } if (is_null($gedcom)) { $gedcom=find_source_record($xref, $ged_id); } if (is_null($gedcom)) { $gedcom=find_media_record($xref, $ged_id); } if (is_null($gedcom)) { $gedcom=find_other_record($xref, $ged_id); } return $gedcom; } /** * find and return an updated gedcom record * @param string $gid the id of the record to find * @param string $gedfile the gedcom file to get the record from.. defaults to currently active gedcom */ function find_updated_record($xref, $ged_id) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT new_gedcom FROM `##change` WHERE gedcom_id=? AND xref=? AND status='pending' ". "ORDER BY change_id DESC LIMIT 1" ); } // This will return NULL if no record exists, or an empty string if the record has been deleted. return $gedcom=$statement->execute(array($ged_id, $xref))->fetchOne(); } // Find the type of a gedcom record. Check the cache before querying the database. // Returns 'INDI', 'FAM', etc., or null if the record does not exist. function gedcom_record_type($xref, $ged_id) { global $gedcom_record_cache; static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare( "SELECT 'INDI' FROM `##individuals` WHERE i_id =? AND i_file =? UNION ALL ". "SELECT 'FAM' FROM `##families` WHERE f_id =? AND f_file =? UNION ALL ". "SELECT 'SOUR' FROM `##sources` WHERE s_id =? AND s_file =? UNION ALL ". "SELECT 'OBJE' FROM `##media` WHERE m_media=? AND m_gedfile=? UNION ALL ". "SELECT o_type FROM `##other` WHERE o_id =? AND o_file =?" ); } if (isset($gedcom_record_cache[$xref][$ged_id])) { return $gedcom_record_cache[$xref][$ged_id]->getType(); } else { return $statement->execute(array($xref, $ged_id, $xref, $ged_id, $xref, $ged_id, $xref, $ged_id, $xref, $ged_id))->fetchOne(); } } // Find out if there are any pending changes that a given user may accept function exists_pending_change($user_id=WT_USER_ID, $ged_id=WT_GED_ID) { if (userCanAccept($user_id, $ged_id)) { return WT_DB::prepare( "SELECT 1". " FROM `##change`". " WHERE status='pending' AND gedcom_id=?" )->execute(array($ged_id))->fetchOne(); } else { return false; } } /** * get a list of all the sources * * returns an array of all of the sources in the database. * @link http://phpgedview.sourceforge.net/devdocs/arrays.php#sources * @return array the array of sources */ function get_source_list($ged_id) { $rows= WT_DB::prepare("SELECT 'SOUR' AS type, s_id AS xref, s_file AS ged_id, s_gedcom AS gedrec FROM `##sources` s WHERE s_file=?") ->execute(array($ged_id)) ->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Source::getInstance($row); } usort($list, array('GedcomRecord', 'Compare')); return $list; } // Get a list of repositories from the database // $ged_id - the gedcom to search function get_repo_list($ged_id) { $rows= WT_DB::prepare("SELECT 'REPO' AS type, o_id AS xref, o_file AS ged_id, o_gedcom AS gedrec FROM `##other` WHERE o_type='REPO' AND o_file=?") ->execute(array($ged_id)) ->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Repository::getInstance($row); } usort($list, array('GedcomRecord', 'Compare')); return $list; } //-- get the shared note list from the datastore function get_note_list($ged_id) { $rows= WT_DB::prepare("SELECT 'NOTE' AS type, o_id AS xref, {$ged_id} AS ged_id, o_gedcom AS gedrec FROM `##other` WHERE o_type=? AND o_file=?") ->execute(array('NOTE', $ged_id)) ->fetchAll(PDO::FETCH_ASSOC); $list=array(); foreach ($rows as $row) { $list[]=Note::getInstance($row); } usort($list, array('GedcomRecord', 'Compare')); return $list; } // Search for INDIs using custom SQL generated by the report engine function search_indis_custom($join, $where, $order) { $sql="SELECT DISTINCT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex FROM `##individuals` ".implode(' ', $join).' WHERE '.implode(' AND ', $where); if ($order) { $sql.=' ORDER BY '.implode(' ', $order); } $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $list[]=Person::getInstance($row); } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search for FAMs using custom SQL generated by the report engine function search_fams_custom($join, $where, $order) { $sql="SELECT DISTINCT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil FROM `##families` ".implode(' ', $join).' WHERE '.implode(' AND ', $where); if ($order) { $sql.=' ORDER BY '.implode(' ', $order); } $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $list[]=Family::getInstance($row); } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search the gedcom records of indis // $query - array of search terms // $geds - array of gedcoms to search // $match - AND or OR // $skip - ignore data in certain tags function search_indis($query, $geds, $match, $skip) { global $GEDCOM; // No query => no results if (!$query) { return array(); } // Convert the query into a SQL expression $querysql=array(); // Convert the query into a regular expression $queryregex=array(); foreach ($query as $q) { $queryregex[]=preg_quote(utf8_strtoupper($q), '/'); $querysql[]="i_gedcom LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."'"; } $sql="SELECT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex FROM `##individuals` WHERE (".implode(" {$match} ", $querysql).') AND i_file IN ('.implode(',', $geds).')'; // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $record=Person::getInstance($row); // SQL may have matched on private data or gedcom tags, so check again against privatized data. $gedrec=utf8_strtoupper($record->getGedcomRecord()); if ($skip) { $gedrec=preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|SUBM|REFN|RESN) .*/', '', $gedrec); } foreach ($queryregex as $regex) { if (!preg_match('/\n\d '.WT_REGEX_TAG.' .*'.$regex.'/', $gedrec)) { continue 2; } } $list[]=$record; } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search the names of indis // $query - array of search terms // $geds - array of gedcoms to search // $match - AND or OR function search_indis_names($query, $geds, $match) { global $GEDCOM; // No query => no results if (!$query) { return array(); } // Convert the query into a SQL expression $querysql=array(); foreach ($query as $q) { $querysql[]="n_full LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."'"; } $sql="SELECT DISTINCT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex, n_num FROM `##individuals` JOIN `##name` ON i_id=n_id AND i_file=n_file WHERE (".implode(" {$match} ", $querysql).') AND i_file IN ('.implode(',', $geds).')'; // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $indi=Person::getInstance($row); if ($indi->canDisplayName()) { $indi->setPrimaryName($row['n_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. This has some performance/memory // implications, and there is probably a better way. This, however, // is clean, easy and works. $list[]=clone $indi; } } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search for individuals names/places using soundex // $soundex - standard or dm // $lastname, $firstname, $place - search terms // $geds - array of gedcoms to search function search_indis_soundex($soundex, $lastname, $firstname, $place, $geds) { $sql="SELECT DISTINCT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex FROM `##individuals`"; 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.=' WHERE i_file IN ('.implode(',', $geds).')'; switch ($soundex) { case 'Russell': $givn_sdx=explode(':', soundex_std($firstname)); $surn_sdx=explode(':', soundex_std($lastname)); $plac_sdx=explode(':', soundex_std($place)); $field='std'; break; default: case 'DaitchM': $givn_sdx=explode(':', soundex_dm($firstname)); $surn_sdx=explode(':', soundex_dm($lastname)); $plac_sdx=explode(':', soundex_dm($place)); $field='dm'; break; } if ($firstname && $givn_sdx) { foreach ($givn_sdx as $k=>$v) { $givn_sdx[$k]="n_soundex_givn_{$field} LIKE ".WT_DB::quote("%{$v}%"); } $sql.=' AND ('.implode(' OR ', $givn_sdx).')'; } if ($lastname && $surn_sdx) { foreach ($surn_sdx as $k=>$v) { $surn_sdx[$k]="n_soundex_surn_{$field} LIKE ".WT_DB::quote("%{$v}%"); } $sql.=' AND ('.implode(' OR ', $surn_sdx).')'; } if ($place && $plac_sdx) { foreach ($plac_sdx as $k=>$v) { $plac_sdx[$k]="p_{$field}_soundex LIKE ".WT_DB::quote("%{$v}%"); } $sql.=' AND ('.implode(' OR ', $plac_sdx).')'; } // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $indi=Person::getInstance($row); if ($indi->canDisplayName()) { $list[]=$indi; } } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } /** * get recent changes since the given julian day inclusive * @author yalnifj * @param int $jd, leave empty to include all */ function get_recent_changes($jd=0, $allgeds=false) { $sql="SELECT d_gid FROM `##dates` WHERE d_fact='CHAN' AND d_julianday1>=? AND d_gid NOT LIKE ?"; $vars=array($jd, '%:%'); if (!$allgeds) { $sql.=" AND d_file=?"; $vars[]=WT_GED_ID; } $sql.=" ORDER BY d_julianday1 DESC"; return WT_DB::prepare($sql)->execute($vars)->fetchOneColumn(); } // Seach for individuals with events on a given day function search_indis_dates($day, $month, $year, $facts) { $sql="SELECT DISTINCT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex FROM `##individuals` JOIN `##dates` ON i_id=d_gid AND i_file=d_file WHERE i_file=?"; $vars=array(WT_GED_ID); if ($day) { $sql.=" AND d_day=?"; $vars[]=$day; } if ($month) { $sql.=" AND d_month=?"; $vars[]=$month; } if ($year) { $sql.=" AND d_year=?"; $vars[]=$year; } if ($facts) { $facts=preg_split('/[, ;]+/', $facts); foreach ($facts as $key=>$value) { if ($value[0]=='!') { $facts[$key]="d_fact!=?"; $vars[]=substr($value,1); } else { $facts[$key]="d_fact=?"; $vars[]=$value; } } $sql.=' AND '.implode(' AND ', $facts); } $list=array(); $rows=WT_DB::prepare($sql)->execute($vars)->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { $list[]=Person::getInstance($row); } return $list; } // Seach for individuals with events in a given date range function search_indis_daterange($start, $end, $facts) { $sql="SELECT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex FROM `##individuals` JOIN `##dates` ON i_id=d_gid AND i_file=d_file WHERE i_file=? AND d_julianday1 BETWEEN ? AND ?"; $vars=array(WT_GED_ID, $start, $end); if ($facts) { $facts=explode(',', $facts); foreach ($facts as $key=>$value) { $facts[$key]="?"; $vars[]=$value; } $sql.=' AND d_fact IN ('.implode(',', $facts).')'; } $list=array(); $rows=WT_DB::prepare($sql)->execute($vars)->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { $list[]=Person::getInstance($row); } return $list; } // Search for people who had events in a given year range function search_indis_year_range($startyear, $endyear) { // TODO: We should use Julian-days, rather than gregorian years, // to allow // the lifespan chart, etc., to use other calendars. $startjd=GregorianDate::YMDtoJD($startyear, 1, 1); $endjd =GregorianDate::YMDtoJD($endyear+1, 1, 1)-1; return search_indis_daterange($startjd, $endjd, ''); } // Search the gedcom records of families // $query - array of search terms // $geds - array of gedcoms to search // $match - AND or OR // $skip - ignore data in certain tags function search_fams($query, $geds, $match, $skip) { global $GEDCOM; // No query => no results if (!$query) { return array(); } // Convert the query into a SQL expression $querysql=array(); // Convert the query into a regular expression $queryregex=array(); foreach ($query as $q) { $queryregex[]=preg_quote(utf8_strtoupper($q), '/'); $querysql[]="f_gedcom LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."'"; } $sql="SELECT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil FROM `##families` WHERE (".implode(" {$match} ", $querysql).') AND f_file IN ('.implode(',', $geds).')'; // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $record=Family::getInstance($row); // SQL may have matched on private data or gedcom tags, so check again against privatized data. $gedrec=utf8_strtoupper($record->getGedcomRecord()); if ($skip) { $gedrec=preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|SUBM|REFN|RESN) .*/', '', $gedrec); } foreach ($queryregex as $regex) { if (!preg_match('/\n\d '.WT_REGEX_TAG.' .*'.$regex.'/', $gedrec)) { continue 2; } } $list[]=$record; } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search the names of the husb/wife in a family // $query - array of search terms // $geds - array of gedcoms to search // $match - AND or OR function search_fams_names($query, $geds, $match) { global $GEDCOM; // No query => no results if (!$query) { return array(); } // Convert the query into a SQL expression $querysql=array(); foreach ($query as $q) { $querysql[]="(husb.n_full LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."' OR wife.n_full LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."')"; } $sql="SELECT DISTINCT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil FROM `##families` LEFT OUTER JOIN `##name` husb ON f_husb=husb.n_id AND f_file=husb.n_file LEFT OUTER JOIN `##name` wife ON f_wife=wife.n_id AND f_file=wife.n_file WHERE (".implode(" {$match} ", $querysql).') AND f_file IN ('.implode(',', $geds).')'; // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $indi=Family::getInstance($row); if ($indi->canDisplayName()) { $list[]=$indi; } } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search the gedcom records of sources // $query - array of search terms // $geds - array of gedcoms to search // $match - AND or OR // $skip - ignore data in certain tags function search_sources($query, $geds, $match, $skip) { global $GEDCOM; // No query => no results if (!$query) { return array(); } // Convert the query into a SQL expression $querysql=array(); // Convert the query into a regular expression $queryregex=array(); foreach ($query as $q) { $queryregex[]=preg_quote(utf8_strtoupper($q), '/'); $querysql[]="s_gedcom LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."'"; } $sql="SELECT 'SOUR' AS type, s_id AS xref, s_file AS ged_id, s_gedcom AS gedrec FROM `##sources` WHERE (".implode(" {$match} ", $querysql).') AND s_file IN ('.implode(',', $geds).')'; // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $record=Source::getInstance($row); // SQL may have matched on private data or gedcom tags, so check again against privatized data. $gedrec=utf8_strtoupper($record->getGedcomRecord()); if ($skip) { $gedrec=preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|SUBM|REFN|RESN) .*/', '', $gedrec); } foreach ($queryregex as $regex) { if (!preg_match('/\n\d '.WT_REGEX_TAG.' .*'.$regex.'/', $gedrec)) { continue 2; } } $list[]=$record; } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } // Search the gedcom records of shared notes // $query - array of search terms // $geds - array of gedcoms to search // $match - AND or OR // $skip - ignore data in certain tags function search_notes($query, $geds, $match, $skip) { global $GEDCOM; // No query => no results if (!$query) { return array(); } // Convert the query into a SQL expression $querysql=array(); // Convert the query into a regular expression $queryregex=array(); foreach ($query as $q) { $queryregex[]=preg_quote(utf8_strtoupper($q), '/'); $querysql[]="o_gedcom LIKE ".WT_DB::quote("%{$q}%")." COLLATE '".i18n::$collation."'"; } $sql="SELECT 'NOTE' AS type, o_id AS xref, o_file AS ged_id, o_gedcom AS gedrec FROM `##other` WHERE (".implode(" {$match} ", $querysql).") AND o_type='NOTE' AND o_file IN (".implode(',', $geds).')'; // Group results by gedcom, to minimise switching between privacy files $sql.=' ORDER BY ged_id'; $list=array(); $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); $GED_ID=WT_GED_ID; foreach ($rows as $row) { // Switch privacy file if necessary if ($row['ged_id']!=$GED_ID) { $GEDCOM=get_gedcom_from_id($row['ged_id']); load_gedcom_settings($row['ged_id']); $GED_ID=$row['ged_id']; } $record=Note::getInstance($row); // SQL may have matched on private data or gedcom tags, so check again against privatized data. $gedrec=utf8_strtoupper($record->getGedcomRecord()); if ($skip) { $gedrec=preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|SUBM|REFN|RESN) .*/', '', $gedrec); } foreach ($queryregex as $regex) { if (!preg_match('/\n\d '.WT_REGEX_TAG.' .*'.$regex.'/', $gedrec)) { continue 2; } } $list[]=$record; } // Switch privacy file if necessary if ($GED_ID!=WT_GED_ID) { $GEDCOM=WT_GEDCOM; load_gedcom_settings(WT_GED_ID); } return $list; } /** * get place parent ID * @param array $parent * @param int $level * @return int */ function get_place_parent_id($parent, $level) { static $statement=null; if (is_null($statement)) { $statement=WT_DB::prepare("SELECT p_id FROM `##places` WHERE p_level=? AND p_parent_id=? AND p_place LIKE ? AND p_file=?"); } $parent_id=0; for ($i=0; $i<$level; $i++) { $p_id=$statement->execute(array($i, $parent_id, $parent[$i], WT_GED_ID))->fetchOne(); if (is_null($p_id)) { break; } $parent_id = $p_id; } return $parent_id; } /** * find all of the places in the hierarchy * The $parent array holds the parent hierarchy of the places * we want to get. The level holds the level in the hierarchy that * we are at. */ function get_place_list($parent, $level) { // --- find all of the place in the file if ($level==0) { return WT_DB::prepare("SELECT p_place FROM `##places` WHERE p_level=? AND p_file=? ORDER BY p_place") ->execute(array(0, WT_GED_ID)) ->fetchOneColumn(); } else { return WT_DB::prepare("SELECT p_place FROM `##places` WHERE p_level=? AND p_parent_id=? AND p_file=? ORDER BY p_place") ->execute(array($level, get_place_parent_id($parent, $level), WT_GED_ID)) ->fetchOneColumn(); } } /** * get all of the place connections * @param array $parent * @param int $level * @return array */ function get_place_positions($parent, $level='') { // TODO: this function needs splitting into two if ($level!=='') { return WT_DB::prepare("SELECT DISTINCT pl_gid FROM `##placelinks` WHERE pl_p_id=? AND pl_file=?") ->execute(array(get_place_parent_id($parent, $level), WT_GED_ID)) ->fetchOneColumn(); } else { //-- we don't know the level so get the any matching place return WT_DB::prepare("SELECT DISTINCT pl_gid FROM `##placelinks`, `##places` WHERE p_place LIKE ? AND p_file=pl_file AND p_id=pl_p_id AND p_file=?") ->execute(array($parent, WT_GED_ID)) ->fetchOneColumn(); } } //-- find all of the places function find_place_list($place) { $rows= WT_DB::prepare("SELECT p_id, p_place, p_parent_id FROM `##places` WHERE p_file=? ORDER BY p_parent_id, p_id") ->execute(array(WT_GED_ID)) ->fetchAll(); $placelist=array(); foreach ($rows as $row) { if ($row->p_parent_id==0) { $placelist[$row->p_id] = $row->p_place; } else { $placelist[$row->p_id] = $placelist[$row->p_parent_id].", ".$row->p_place; } } if (!empty($place)) { $found = array(); foreach ($placelist as $indexval => $pplace) { if (stripos($pplace, $place)!==false) { $upperplace = utf8_strtoupper($pplace); if (!isset($found[$upperplace])) { $found[$upperplace] = $pplace; } } } $placelist = array_values($found); } return $placelist; } //-- function to find the gedcom id for the given rin function find_rin_id($rin) { $xref= WT_DB::prepare("SELECT i_id FROM `##individuals` WHERE i_rin=? AND i_file=?") ->execute(array($rin, WT_GED_ID)) ->fetchOne(); return $xref ? $xref : $rin; } /** * Delete a gedcom from the database and the system * Does not delete the file from the file system * @param string $ged the filename of the gedcom to delete */ function delete_gedcom($ged_id) { // If this is the current default, then unset it if (get_site_setting('DEFAULT_GEDCOM')==get_gedcom_from_id($ged_id)) { set_site_setting('DEFAULT_GEDCOM', ''); } // Don't delete the logs. WT_DB::prepare("UPDATE `##log` SET gedcom_id=NULL WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE gedcom_id=?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##block` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##dates` WHERE d_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##families` WHERE f_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##user_gedcom_setting` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##gedcom_setting` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##individuals` WHERE i_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##link` WHERE l_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##media` WHERE m_gedfile =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##media_mapping` WHERE mm_gedfile=?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##module_privacy` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##name` WHERE n_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##next_id` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##other` WHERE o_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##placelinks` WHERE pl_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##places` WHERE p_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##sources` WHERE s_file =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##hit_counter` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##change` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##default_resn` WHERE gedcom_id =?")->execute(array($ged_id)); WT_DB::prepare("DELETE FROM `##gedcom` WHERE gedcom_id =?")->execute(array($ged_id)); } /** * 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 array */ function get_top_surnames($ged_id, $min, $max) { // Use n_surn, rather than n_surname, as it is used to generate url's for // the inid-list, etc. return WT_DB::prepareLimit("SELECT n_surn, COUNT(n_surn) FROM `##name` WHERE n_file=? AND n_type!=? AND n_surn NOT IN (?, ?, ?, ?) GROUP BY n_surn HAVING COUNT(n_surn)>=".$min." ORDER BY 2 DESC", $max) ->execute(array($ged_id, '_MARNM', '@N.N.', '', '?', 'UNKNOWN')) ->fetchAssoc(); } /** * get a list of remote servers */ function get_server_list($ged_id=WT_GED_ID){ $sitelist = array(); $rows=WT_DB::prepare("SELECT s_id, s_name, s_gedcom, s_file FROM `##sources` WHERE s_file=? AND s_dbid=? ORDER BY s_name") ->execute(array($ged_id, 'Y')) ->fetchAll(); foreach ($rows as $row) { $source = array(); $source["name"] = $row->s_name; $source["gedcom"] = $row->s_gedcom; $source["gedfile"] = $row->s_file; $source["url"] = get_gedcom_value("URL", 1, $row->s_gedcom); $sitelist[$row->s_id] = $source; } return $sitelist; } function delete_fact($linenum, $pid, $gedrec) { global $linefix; if (!empty($linenum)) { if ($linenum==0) { delete_gedrec($pid, WT_GED_ID); print i18n::translate('GEDCOM record successfully deleted.'); } else { $gedlines = explode("\n", $gedrec); // NOTE: The array_pop is used to kick off the last empty element on the array // NOTE: To prevent empty lines in the GEDCOM // DEBUG: Records without line breaks are imported as 1 big string if ($linefix > 0) { array_pop($gedlines); } $newged = ""; // NOTE: Add all lines that are before the fact to be deleted for ($i=0; $i<$linenum; $i++) { $newged .= trim($gedlines[$i])."\n"; } if (isset($gedlines[$linenum])) { $fields = explode(' ', $gedlines[$linenum]); $glevel = $fields[0]; $ctlines = count($gedlines); $i++; if ($i<$ctlines) { // Remove the fact while ((isset($gedlines[$i]))&&($gedlines[$i]{0}>$glevel)) { $i++; } // Add the remaining lines while ($i<$ctlines) { $newged .= $gedlines[$i]."\n"; $i++; } } } if ($newged != "") { return $newged; } } } } /** * get_remote_id Recieves a RFN key and returns a Stub ID if the RFN exists * * @param mixed $rfn RFN number to see if it exists * @access public * @return gid Stub ID that contains the RFN number. Returns false if it didn't find anything */ function get_remote_id($rfn) { return WT_DB::prepare("SELECT r_gid FROM `##remotelinks` WHERE r_linkid=? AND r_file=?") ->execute(array($rfn, WT_GED_ID)) ->fetchOne(); } //////////////////////////////////////////////////////////////////////////////// // 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. // $jd - the julian day // $facts - restrict the search to just these facts or leave blank for all // $ged_id - the id of the gedcom to search //////////////////////////////////////////////////////////////////////////////// function get_anniversary_events($jd, $facts='', $ged_id=WT_GED_ID) { // 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(); foreach (array(new GregorianDate($jd), new JulianDate($jd), new FrenchRDate($jd), new JewishDate($jd), new HijriDate($jd)) as $anniv) { // Build a SQL where clause to match anniversaries in the appropriate calendar. $where="WHERE d_type='".$anniv->CALENDAR_ESCAPE()."'"; // SIMPLE CASES: // a) Non-hebrew anniversaries // b) Hebrew months TVT, SHV, IYR, SVN, TMZ, AAV, ELL if ($anniv->CALENDAR_ESCAPE()!='@#DHEBREW@' || in_array($anniv->m, array(1, 5, 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"; } else if ($anniv->d==$anniv->DaysInMonth()) { $where.=" AND d_day>={$anniv->d}"; } else { $where.=" AND d_day={$anniv->d}"; } $where.=" AND d_mon={$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={$anniv->d} AND d_mon=2"; } 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"; } } else if ($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={$anniv->d} AND d_mon=3"; } break; case 4: // 1 TVT includes 30 KSL (if this year didn't have 30 KSL) if ($anniv->d==1) { $tmp=new JewishDate($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"; } } else if ($anniv->d==$anniv->DaysInMonth()) { $where.=" AND d_day>={$anniv->d} AND d_mon=4"; } else { $where.=" AND d_day={$anniv->d} AND d_mon=4"; } break; case 6: // ADR (non-leap) includes ADS (leap) if ($anniv->d==1) { $where.=" AND d_day<=1"; } elseif ($anniv->d==$anniv->DaysInMonth()) { $where.=" AND d_day>={$anniv->d}"; } else { $where.=" AND d_day={$anniv->d}"; } if ($anniv->IsLeapYear()) { $where.=" AND (d_mon=6 AND MOD(7*d_year+1, 19)<7)"; } else { $where.=" AND (d_mon=6 OR d_mon=7)"; } 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>={$anniv->d}"; } else { $where.=" AND d_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>={$anniv->d} AND d_mon=8"; } else { $where.=" AND d_day={$anniv->d} AND d_mon=8"; } break; } } // Only events in the past (includes dates without a year) $where.=" AND d_year<={$anniv->y}"; // 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=".$ged_id; // Now fetch these anniversaries $ind_sql="SELECT DISTINCT 'INDI' AS type, i_id AS xref, i_file AS ged_id, i_gedcom AS gedrec, i_isdead, i_sex, 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_day ASC, d_year DESC"; $fam_sql="SELECT DISTINCT 'FAM' AS type, f_id AS xref, f_file AS ged_id, f_gedcom AS gedrec, f_husb, f_wife, f_numchil, 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_day ASC, d_year DESC"; foreach (array($ind_sql, $fam_sql) as $sql) { $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { if ($row['type']=='INDI') { $record=Person::getInstance($row); } else { $record=Family::getInstance($row); } // Generate a regex to match the retrieved date - so we can find it in the original gedcom record. // TODO having to go back to the original gedcom is lame. This is why it is so slow, and needs // to be cached. We should store the level1 fact here (or somewhere) if ($row['d_type']=='@#DJULIAN@') { if ($row['d_year']<0) { $year_regex=$row['d_year'].' ?[Bb]\.? ?[Cc]\.\ ?'; } else { $year_regex="({$row['d_year']}|".($row['d_year']-1)."\/".($row['d_year']%100).")"; } } else $year_regex="0*".$row['d_year']; $ged_date_regex="/2 DATE.*(".($row['d_day']>0 ? "0?{$row['d_day']}\s*" : "").$row['d_month']."\s*".($row['d_year']!=0 ? $year_regex : "").")/i"; foreach (get_all_subrecords($row['gedrec'], $skipfacts, false, false) as $factrec) { if (preg_match("/(^1 {$row['d_fact']}|^1 (FACT|EVEN).*\n2 TYPE {$row['d_fact']})/s", $factrec) && preg_match($ged_date_regex, $factrec) && preg_match('/2 DATE (.+)/', $factrec, $match)) { $date=new GedcomDate($match[1]); if (preg_match('/2 PLAC (.+)/', $factrec, $match)) { $plac=$match[1]; } else { $plac=''; } if (canDisplayFact($row['xref'], $ged_id, $factrec)) { $found_facts[]=array( 'record'=>$record, 'id'=>$row['xref'], 'objtype'=>$row['type'], 'fact'=>$row['d_fact'], 'factrec'=>$factrec, 'jd'=>$jd, 'anniv'=>($row['d_year']==0?0:$anniv->y-$row['d_year']), 'date'=>$date, 'plac'=>$plac ); } } } } } } return $found_facts; } //////////////////////////////////////////////////////////////////////////////// // Get a list of events which occured during a given date range. // TODO: Used by the recent-changes block and the calendar year view. // $jd1, $jd2 - the range of julian day // $facts - restrict the search to just these facts or leave blank for all // $ged_id - the id of the gedcom to search //////////////////////////////////////////////////////////////////////////////// function get_calendar_events($jd1, $jd2, $facts='', $ged_id=WT_GED_ID) { // 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(); // This where clause gives events that start/end/overlap the period // e.g. 1914-1918 would show up on 1916 //$where="WHERE d_julianday1 <={$jd2} AND d_julianday2>={$jd1}"; // This where clause gives only events that start/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=".$ged_id; // Now fetch these events $ind_sql="SELECT d_gid, i_gedcom, 'INDI', 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, f_gedcom, 'FAM', 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($ind_sql, $fam_sql) as $sql) { $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_NUM); foreach ($rows as $row) { // Generate a regex to match the retrieved date - so we can find it in the original gedcom record. // TODO having to go back to the original gedcom is lame. This is why it is so slow, and needs // to be cached. We should store the level1 fact here (or somewhere) if ($row[8]=='@#DJULIAN@') { if ($row[6]<0) { $year_regex=$row[6].' ?[Bb]\.? ?[Cc]\.\ ?'; } else { $year_regex="({$row[6]}|".($row[6]-1)."\/".($row[6]%100).")"; } } else { $year_regex="0*".$row[6]; } $ged_date_regex="/2 DATE.*(".($row[4]>0 ? "0?{$row[4]}\s*" : "").$row[5]."\s*".($row[6]!=0 ? $year_regex : "").")/i"; foreach (get_all_subrecords($row[1], $skipfacts, false, false) as $factrec) { if (preg_match("/(^1 {$row[7]}|^1 (FACT|EVEN).*\n2 TYPE {$row[7]})/s", $factrec) && preg_match($ged_date_regex, $factrec) && preg_match('/2 DATE (.+)/', $factrec, $match)) { $date=new GedcomDate($match[1]); if (preg_match('/2 PLAC (.+)/', $factrec, $match)) { $plac=$match[1]; } else { $plac=''; } if (canDisplayFact($row[0], $ged_id, $factrec)) { $found_facts[]=array( 'id'=>$row[0], 'objtype'=>$row[2], 'fact'=>$row[7], 'factrec'=>$factrec, 'jd'=>$jd1, 'anniv'=>0, 'date'=>$date, 'plac'=>$plac ); } } } } } return $found_facts; } /** * Get the list of current and upcoming events, sorted by anniversary date * * This function is used by the Todays and Upcoming blocks on the Index and Portal * pages. * * Special note on unknown day-of-month: * When the anniversary date is imprecise, the sort will pretend that the day-of-month * is either tomorrow or the first day of next month. These imprecise anniversaries * will sort to the head of the chosen day. * * Special note on Privacy: * This routine does not check the Privacy of the events in the list. That check has * to be done by the routine that makes use of the event list. */ function get_events_list($jd1, $jd2, $events='') { $found_facts=array(); for ($jd=$jd1; $jd<=$jd2; ++$jd) { $found_facts=array_merge($found_facts, get_anniversary_events($jd, $events)); } return $found_facts; } //////////////////////////////////////////////////////////////////////////////// // Check if a media file is shared (i.e. used by another gedcom) //////////////////////////////////////////////////////////////////////////////// function is_media_used_in_other_gedcom($file_name, $ged_id) { return (bool)WT_DB::prepare("SELECT COUNT(*) FROM `##media` WHERE m_file LIKE ? AND m_gedfile<>?") ->execute(array("%{$file_name}", $ged_id)) ->fetchOne(); } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_SITE_SETTING table // We can't cache/reuse prepared statements here, as we need to call these // functions after performing DDL statements, and these invalidate any // existing prepared statement handles in some databases. //////////////////////////////////////////////////////////////////////////////// function get_site_setting($setting_name, $default_value=null) { return WT_DB::prepare( "SELECT SQL_CACHE setting_value FROM `##site_setting` WHERE setting_name=?" )->execute(array($setting_name))->fetchOne($default_value); } function set_site_setting($setting_name, $setting_value) { if (get_site_setting($setting_name)!=$setting_value) { AddToLog('Site setting "'.$setting_name.'" set to "'.$setting_value.'"', 'config'); } if (is_null($setting_value)) { WT_DB::prepare("DELETE FROM `##site_setting` WHERE setting_name=?") ->execute(array($setting_name)); } else { $rowcount=WT_DB::prepare("REPLACE INTO `##site_setting` (setting_name, setting_value) VALUES (?, ?)") ->execute(array($setting_name, $setting_value)); } } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_GEDCOM table //////////////////////////////////////////////////////////////////////////////// function get_all_gedcoms() { return WT_DB::prepare("SELECT gedcom_id, gedcom_name FROM `##gedcom`") ->fetchAssoc(); } function get_gedcom_titles() { return WT_DB::prepare( "SELECT SQL_CACHE g.gedcom_id, g.gedcom_name, COALESCE(gs.setting_value, g.gedcom_name) AS gedcom_title". " FROM `##gedcom` g". " LEFT JOIN `##gedcom_setting` gs ON (g.gedcom_id=gs.gedcom_id AND gs.setting_name=?)". " ORDER BY 3" ) ->execute(array('title')) ->fetchAll(); } function get_gedcom_from_id($ged_id) { // No need to look up the default gedcom if (defined('WT_GED_ID') && defined('WT_GEDCOM') && $ged_id==WT_GED_ID) { return WT_GEDCOM; } return WT_DB::prepare("SELECT gedcom_name FROM `##gedcom` WHERE gedcom_id=?") ->execute(array($ged_id)) ->fetchOne(); } // Convert an (external) gedcom name to an (internal) gedcom ID. // Optionally create an entry for it, if it does not exist. function get_id_from_gedcom($ged_name, $create=false) { // No need to look up the default gedcom if (defined('WT_GED_ID') && defined('WT_GEDCOM') && $ged_name==WT_GEDCOM) { return WT_GED_ID; } if ($create) { try { WT_DB::prepare("INSERT INTO `##gedcom` (gedcom_name, import_gedcom, import_offset) VALUES (?, '', 0)") ->execute(array($ged_name)); $ged_id=WT_DB::getInstance()->lastInsertId(); require WT_ROOT.'includes/set_gedcom_defaults.php'; return $ged_id; } catch (PDOException $ex) { // The gedcom already exists - can't create } } return WT_DB::prepare("SELECT gedcom_id FROM `##gedcom` WHERE gedcom_name=?") ->execute(array($ged_name)) ->fetchOne(); } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_GEDCOM_SETTING table //////////////////////////////////////////////////////////////////////////////// function get_gedcom_setting($gedcom_id, $setting_name, $default_value=null) { return WT_DB::prepare( "SELECT SQL_CACHE setting_value FROM `##gedcom_setting` WHERE gedcom_id=? AND setting_name=?" )->execute(array($gedcom_id, $setting_name))->fetchOne($default_value); } function set_gedcom_setting($ged_id, $setting_name, $setting_value) { if (get_gedcom_setting($ged_id, $setting_name)!=$setting_value) { AddToLog('Gedcom setting "'.$setting_name.'" set to "'.$setting_value.'"', 'config'); } if (is_null($setting_value)) { WT_DB::prepare("DELETE FROM `##gedcom_setting` WHERE gedcom_id=? AND setting_name=?") ->execute(array($ged_id, $setting_name)); } else { WT_DB::prepare("REPLACE INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value) VALUES (?, ?, ?)") ->execute(array($ged_id, $setting_name, $setting_value)); } } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_USER table //////////////////////////////////////////////////////////////////////////////// function create_user($username, $realname, $email, $password) { try { WT_DB::prepare("INSERT INTO `##user` (user_name, real_name, email, password) VALUES (?, ?, ?, ?)") ->execute(array($username, $realname, $email, $password)); } catch (PDOException $ex) { // User already exists? } return WT_DB::prepare("SELECT user_id FROM `##user` WHERE user_name=?") ->execute(array($username))->fetchOne(); } function rename_user($old_username, $new_username) { WT_DB::prepare("UPDATE `##user` SET user_name=? WHERE user_name =?")->execute(array($new_username, $old_username)); } function delete_user($user_id) { // Don't delete the logs. WT_DB::prepare("UPDATE `##log` SET user_id=NULL WHERE user_id =?")->execute(array($user_id)); // Take over the user's pending changes. // TODO: perhaps we should prevent deletion of users with pending changes? WT_DB::prepare("DELETE FROM `##change` WHERE user_id=? AND status='accepted'")->execute(array($user_id)); WT_DB::prepare("UPDATE `##change` SET user_id=? WHERE user_id=?")->execute(array(WT_USER_ID, $user_id)); WT_DB::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE user_id=?")->execute(array($user_id)); WT_DB::prepare("DELETE FROM `##block` WHERE user_id=?" )->execute(array($user_id)); WT_DB::prepare("DELETE FROM `##user_gedcom_setting` WHERE user_id=?" )->execute(array($user_id)); WT_DB::prepare("DELETE FROM `##user_setting` WHERE user_id=?" )->execute(array($user_id)); WT_DB::prepare("DELETE FROM `##message` WHERE user_id=?" )->execute(array($user_id)); WT_DB::prepare("DELETE FROM `##user` WHERE user_id=?" )->execute(array($user_id)); } function get_all_users($order='ASC', $key='realname') { if ($key=='username') { return WT_DB::prepare("SELECT user_id, user_name FROM `##user` ORDER BY user_name") ->fetchAssoc(); } elseif ($key=='realname') { return WT_DB::prepare("SELECT user_id, user_name FROM `##user` ORDER BY real_name") ->fetchAssoc(); } else { return WT_DB::prepare( "SELECT u.user_id, user_name". " FROM `##user` u". " LEFT JOIN `##user_setting` us1 ON (u.user_id=us1.user_id AND us1.setting_name=?)". " ORDER BY us1.setting_value {$order}" )->execute(array($key)) ->fetchAssoc(); } } function get_user_count() { return WT_DB::prepare("SELECT COUNT(*) FROM `##user`") ->fetchOne(); } function get_user_by_email($email) { return WT_DB::prepare("SELECT user_id FROM `##user` WHERE email=?") ->execute(array($email)) ->fetchOne(); } function get_admin_user_count() { return WT_DB::prepare("SELECT SQL_CACHE COUNT(*) FROM `##user_setting` WHERE setting_name=? AND setting_value=?") ->execute(array('canadmin', '1')) ->fetchOne(); } function get_non_admin_user_count() { return WT_DB::prepare("SELECT SQL_CACHE COUNT(*) FROM `##user_setting` WHERE setting_name=? AND setting_value<>?") ->execute(array('canadmin', '1')) ->fetchOne(); } // Get a list of logged-in users function get_logged_in_users() { return WT_DB::prepare( "SELECT u.user_id, user_name". " FROM `##user` u". " JOIN `##user_setting` us USING (user_id)". " WHERE setting_name=? AND setting_value=?" ) ->execute(array('loggedin', '1')) ->fetchAssoc(); } // Get a list of logged-in users who haven't been active recently function get_idle_users($time) { return WT_DB::prepare( "SELECT u.user_id, user_name". " FROM `##user` u". " JOIN `##user_setting` us1 USING (user_id)". " JOIN `##user_setting` us2 USING (user_id)". " WHERE us1.setting_name=? AND us1.setting_value=? AND us2.setting_name=?". " AND CAST(us2.setting_value AS UNSIGNED) BETWEEN 1 AND ?" ) ->execute(array('loggedin', '1', 'sessiontime', $time)) ->fetchAssoc(); } // Get the ID for a username function get_user_id($username) { return WT_DB::prepare("SELECT user_id FROM `##user` WHERE user_name=?") ->execute(array($username)) ->fetchOne(); } // Get the username for a user ID function get_user_name($user_id) { return WT_DB::prepare("SELECT user_name FROM `##user` WHERE user_id=?") ->execute(array($user_id)) ->fetchOne(); } function get_newest_registered_user() { return WT_DB::prepareLimit( "SELECT u.user_id". " FROM `##user` u". " LEFT JOIN `##user_setting` us ON (u.user_id=us.user_id AND us.setting_name=?) ". " ORDER BY us.setting_value DESC", 1 )->execute(array('reg_timestamp')) ->fetchOne(); } function set_user_password($user_id, $password) { WT_DB::prepare("UPDATE `##user` SET password=? WHERE user_id=?") ->execute(array($password, $user_id)); } function get_user_password($user_id) { return WT_DB::prepare("SELECT password FROM `##user` WHERE user_id=?") ->execute(array($user_id)) ->fetchOne(); } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_USER_SETTING table //////////////////////////////////////////////////////////////////////////////// function get_user_setting($user_id, $setting_name, $default_value=null) { return WT_DB::prepare( "SELECT SQL_CACHE setting_value FROM `##user_setting` WHERE user_id=? AND setting_name=?" )->execute(array($user_id, $setting_name))->fetchOne($default_value); } function set_user_setting($user_id, $setting_name, $setting_value) { if (is_null($setting_value)) { WT_DB::prepare("DELETE FROM `##user_setting` WHERE user_id=? AND setting_name=?") ->execute(array($user_id, $setting_name)); } else { WT_DB::prepare("REPLACE INTO `##user_setting` (user_id, setting_name, setting_value) VALUES (?, ?, ?)") ->execute(array($user_id, $setting_name, $setting_value)); } } function admin_user_exists() { return get_admin_user_count()>0; } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_USER_GEDCOM_SETTING table //////////////////////////////////////////////////////////////////////////////// function get_user_gedcom_setting($user_id, $gedcom_id, $setting_name, $default_value=null) { return WT_DB::prepare( "SELECT SQL_CACHE setting_value FROM `##user_gedcom_setting` WHERE user_id=? AND gedcom_id=? AND setting_name=?" )->execute(array($user_id, $gedcom_id, $setting_name))->fetchOne($default_value); } function set_user_gedcom_setting($user_id, $ged_id, $setting_name, $setting_value) { if (is_null($setting_value)) { WT_DB::prepare("DELETE FROM `##user_gedcom_setting` WHERE user_id=? AND gedcom_id=? AND setting_name=?") ->execute(array($user_id, $ged_id, $setting_name)); } else { WT_DB::prepare("REPLACE INTO `##user_gedcom_setting` (user_id, gedcom_id, setting_name, setting_value) VALUES (?, ?, ?, ?)") ->execute(array($user_id, $ged_id, $setting_name, $setting_value)); } } function get_user_from_gedcom_xref($ged_id, $xref) { return WT_DB::prepare( "SELECT SQL_CACHE user_id FROM `##user_gedcom_setting`". " WHERE gedcom_id=? AND setting_name=? AND setting_value=?" )->execute(array($ged_id, 'gedcomid', $xref))->fetchOne(); } //////////////////////////////////////////////////////////////////////////////// // Functions to access the WT_BLOCK table //////////////////////////////////////////////////////////////////////////////// function get_user_blocks($user_id) { $blocks=array('main'=>array(), 'side'=>array()); $rows=WT_DB::prepare( "SELECT location, block_id, module_name". " FROM `##block`". " WHERE user_id=?". " ORDER BY location, block_order" )->execute(array($user_id))->fetchAll(); foreach ($rows as $row) { $blocks[$row->location][$row->block_id]=$row->module_name; } if ($rows) { return $blocks; } else { $active_blocks=WT_Module::getActiveBlocks(); foreach (array('todays_events', 'user_messages', 'user_favorites') as $n=>$block) { if (array_key_exists($block, $active_blocks)) { WT_DB::prepare( "INSERT INTO `##block` (user_id, location, block_order, module_name) VALUES ". "(?, 'main', ?, ?)" )->execute(array($user_id, $n, $block)); } } foreach (array('user_welcome', 'random_media', 'upcoming_events', 'logged_in') as $n=>$block) { if (array_key_exists($block, $active_blocks)) { WT_DB::prepare( "INSERT INTO `##block` (user_id, location, block_order, module_name) VALUES ". "(?, 'side', ?, ?)" )->execute(array($user_id, $n, $block)); } } return get_user_blocks($user_id); } } function get_gedcom_blocks($gedcom_id) { $blocks=array('main'=>array(), 'side'=>array()); $rows=WT_DB::prepare( "SELECT location, block_id, module_name". " FROM `##block`". " WHERE gedcom_id=?". " ORDER BY location, block_order" )->execute(array($gedcom_id))->fetchAll(); foreach ($rows as $row) { $blocks[$row->location][$row->block_id]=$row->module_name; } if ($rows) { return $blocks; } else { $active_blocks=WT_Module::getActiveBlocks(); foreach (array('gedcom_stats', 'gedcom_news', 'gedcom_favorites', 'review_changes') as $n=>$block) { if (array_key_exists($block, $active_blocks)) { WT_DB::prepare( "INSERT INTO `##block` (gedcom_id, location, block_order, module_name) VALUES ". "(?, 'main', ?, ?)" )->execute(array($gedcom_id, $n, $block)); } } foreach (array('gedcom_block', 'random_media', 'todays_events', 'logged_in') as $n=>$block) { if (array_key_exists($block, $active_blocks)) { WT_DB::prepare( "INSERT INTO `##block` (gedcom_id, location, block_order, module_name) VALUES ". "(?, 'side', ?, ?)" )->execute(array($gedcom_id, $n, $block)); } } return get_gedcom_blocks($gedcom_id); } } function get_block_setting($block_id, $setting_name, $default_value=null) { return WT_DB::prepare( "SELECT SQL_CACHE setting_value FROM `##block_setting` WHERE block_id=? AND setting_name=?" )->execute(array($block_id, $setting_name))->fetchOne($default_value); } function set_block_setting($block_id, $setting_name, $setting_value) { if (is_null($setting_value)) { WT_DB::prepare("DELETE FROM `##block_setting` WHERE block_id=? AND setting_name=?") ->execute(array($block_id, $setting_name)); } else { WT_DB::prepare("REPLACE INTO `##block_setting` (block_id, setting_name, setting_value) VALUES (?, ?, ?)") ->execute(array($block_id, $setting_name, $setting_value)); } } function get_module_setting($module_name, $setting_name, $default_value=null) { return WT_DB::prepare( "SELECT SQL_CACHE setting_value FROM `##module_setting` WHERE module_name=? AND setting_name=?" )->execute(array($module_name, $setting_name))->fetchOne($default_value); } function set_module_setting($module_name, $setting_name, $setting_value) { if (is_null($setting_value)) { WT_DB::prepare("DELETE FROM `##module_setting` WHERE module_name=? AND setting_name=?") ->execute(array($module_name, $setting_name)); } else { WT_DB::prepare("REPLACE INTO `##module_setting` (module_name, setting_name, setting_value) VALUES (?, ?, ?)") ->execute(array($module_name, $setting_name, $setting_value)); } } /** * update favorites regarding a merge of records * * @param string $xref_from id to update * @param string $xref_to id to update to * @param string $ged_id gedcom to update */ function update_favorites($xref_from, $xref_to, $ged_id=WT_GED_ID) { $ged_name=get_gedcom_from_id($ged_id); return WT_DB::prepare("UPDATE `##favorites` SET fv_gid=? WHERE fv_gid=? AND fv_file=?") ->execute(array($xref_to, $xref_from, $ged_name)) ->rowCount(); }