/**
  * Returns an array of the place hierarchy, based on a random example of place within the GEDCOM.
  * It will look for the longest hierarchy in the tree.
  * The places are reversed compared to normal GEDCOM structure.
  * 
  * @return array
  */
 protected function getPlacesHierarchyFromData()
 {
     $nb_levels = 0;
     //Select all '2 PLAC ' tags in the file and create array
     $places_list = array();
     $ged_data = Database::prepare('SELECT i_gedcom AS gedcom' . ' FROM `##individuals`' . ' WHERE i_gedcom LIKE :gedcom AND i_file = :gedcom_id' . ' UNION ALL' . 'SELECT f_gedcom AS gedcom' . ' FROM `##families`' . ' WHERE f_gedcom LIKE :gedcom AND f_file = :gedcom_id')->execute(array('gedcom' => '%\\n2 PLAC %', 'gedcom_id' => $this->tree->getTreeId()))->fetchOneColumn();
     foreach ($ged_data as $ged_datum) {
         $matches = null;
         preg_match_all('/\\n2 PLAC (.+)/', $ged_datum, $matches);
         foreach ($matches[1] as $match) {
             $places_list[$match] = true;
         }
     }
     // Unique list of places
     $places_list = array_keys($places_list);
     //sort the array, limit to unique values, and count them
     usort($places_list, array('I18N', 'strcasecmp'));
     //calculate maximum no. of levels to display
     $has_found_good_example = false;
     foreach ($places_list as $place) {
         $levels = explode(",", $place);
         $parts = count($levels);
         if ($parts >= $nb_levels) {
             $nb_levels = $parts;
             if (!$has_found_good_example) {
                 $random_place = $place;
                 if (min(array_map('strlen', $levels)) > 0) {
                     $has_found_good_example = true;
                 }
             }
         }
     }
     return array_reverse(array_map('trim', explode(',', $random_place)));
 }
Exemple #2
0
 /**
  * Sometimes, we'll know in advance that we need to load a set of records.
  * Typically when we load families and their members.
  *
  * @param Tree  $tree
  * @param string[] $xrefs
  */
 public static function load(Tree $tree, array $xrefs)
 {
     $args = array('tree_id' => $tree->getTreeId());
     $placeholders = array();
     foreach (array_unique($xrefs) as $n => $xref) {
         if (!isset(self::$gedcom_record_cache[$tree->getTreeId()][$xref])) {
             $placeholders[] = ':x' . $n;
             $args['x' . $n] = $xref;
         }
     }
     if (!empty($placeholders)) {
         $rows = Database::prepare("SELECT i_id AS xref, i_gedcom AS gedcom" . " FROM `##individuals`" . " WHERE i_file = :tree_id AND i_id IN (" . implode(',', $placeholders) . ")")->execute($args)->fetchAll();
         foreach ($rows as $row) {
             self::getInstance($row->xref, $tree, $row->gedcom);
         }
     }
 }
Exemple #3
0
 /**
  * Return a computed array of statistics about the dispersion of ancestors across the ancestors
  * at a specified generation.
  * This statistics cannot be used for generations above 11, as it would cause a out of range in MySQL
  * 
  * Format: 
  *  - key : a base-2 representation of the ancestor at generation G for which exclusive ancestors have been found,
  *          -1 is used for shared ancestors
  *          For instance base2(0100) = base10(4) represent the maternal grand father
  *  - values: number of ancestors exclusively in the ancestors of the ancestor in key
  *  
  *  For instance a result at generation 3 could be :
  *      array (   -1        =>  12      -> 12 ancestors are shared by the grand-parents
  *                base10(1) =>  32      -> 32 ancestors are exclusive to the paternal grand-father
  *                base10(2) =>  25      -> 25 ancestors are exclusive to the paternal grand-mother
  *                base10(4) =>  12      -> 12 ancestors are exclusive to the maternal grand-father
  *                base10(8) =>  30      -> 30 ancestors are exclusive to the maternal grand-mother
  *            )
  *  
  * @param int $gen Reference generation
  * @return array
  */
 public function getAncestorDispersionForGen($gen)
 {
     if (!$this->is_setup || $gen > 11) {
         return array();
     }
     // Going further than 11 gen will be out of range in the query
     return Database::prepare('SELECT branches, count(i_id)' . ' FROM (' . '   SELECT i_id,' . '       CASE' . '           WHEN CEIL(LOG2(SUM(branch))) = LOG2(SUM(branch)) THEN SUM(branch)' . '           ELSE -1' . '       END branches' . '   FROM (' . '       SELECT DISTINCT majs_i_id i_id,' . '           POW(2, FLOOR(majs_sosa / POW(2, (majs_gen - :gen))) - POW(2, :gen -1)) branch' . '       FROM `##maj_sosa`' . '       WHERE majs_gedcom_id = :tree_id AND majs_user_id = :user_id' . '           AND majs_gen >= :gen' . '   ) indistat' . '   GROUP BY i_id' . ') grouped' . ' GROUP BY branches')->execute(array('tree_id' => $this->tree->getTreeId(), 'user_id' => $this->user->getUserId(), 'gen' => $gen))->fetchAssoc() ?: array();
 }
Exemple #4
0
 /**
  * Sometimes, we'll know in advance that we need to load a set of records.
  * Typically when we load families and their members.
  *
  * @param Tree  $tree
  * @param string[] $xrefs
  */
 public static function load(Tree $tree, array $xrefs)
 {
     $sql = '';
     $args = array('tree_id' => $tree->getTreeId());
     foreach (array_unique($xrefs) as $n => $xref) {
         if (!isset(self::$gedcom_record_cache[$tree->getTreeId()][$xref])) {
             $sql .= ($n ? ',:x' : ':x') . $n;
             $args['x' . $n] = $xref;
         }
     }
     if (count($args) > 1) {
         $rows = Database::prepare("SELECT i_id AS xref, i_gedcom AS gedcom" . " FROM `##individuals`" . " WHERE i_file = :tree_id AND i_id IN (" . $sql . ")")->execute($args)->fetchAll();
         foreach ($rows as $row) {
             self::getInstance($row->xref, $tree, $row->gedcom);
         }
     }
 }
Exemple #5
0
 /**
  * Get a list of modules which (a) provide a specific function and (b) we have permission to see.
  *
  * We cannot currently use auto-loading for modules, as there may be user-defined
  * modules about which the auto-loader knows nothing.
  *
  * @param Tree   $tree
  * @param string $component The type of module, such as "tab", "report" or "menu"
  *
  * @return AbstractModule[]
  */
 private static function getActiveModulesByComponent(Tree $tree, $component)
 {
     $module_names = Database::prepare("SELECT SQL_CACHE module_name" . " FROM `##module`" . " JOIN `##module_privacy` USING (module_name)" . " WHERE gedcom_id = :tree_id AND component = :component AND status = 'enabled' AND access_level >= :access_level" . " ORDER BY CASE component WHEN 'menu' THEN menu_order WHEN 'sidebar' THEN sidebar_order WHEN 'tab' THEN tab_order ELSE 0 END, module_name")->execute(array('tree_id' => $tree->getTreeId(), 'component' => $component, 'access_level' => Auth::accessLevel($tree)))->fetchOneColumn();
     $array = array();
     foreach ($module_names as $module_name) {
         $interface = '\\Fisharebest\\Webtrees\\Module\\Module' . ucfirst($component) . 'Interface';
         $module = self::getModuleByName($module_name);
         if ($module instanceof $interface) {
             $array[$module_name] = $module;
         }
     }
     // The order of menus/sidebars/tabs is defined in the database. Others are sorted by name.
     if ($component !== 'menu' && $component !== 'sidebar' && $component !== 'tab') {
         uasort($array, function (AbstractModule $x, AbstractModule $y) {
             return I18N::strcasecmp($x->getTitle(), $y->getTitle());
         });
     }
     return $array;
 }
Exemple #6
0
 /**
  * Fetch a list of individuals with specified names
  *
  * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@"
  * To search for names with no surnames, use $salpha=","
  *
  * @param Tree   $tree   only fetch individuals from this tree
  * @param string $surn   if set, only fetch people with this surname
  * @param string $salpha if set, only fetch surnames starting with this letter
  * @param string  $galpha if set, only fetch given names starting with this letter
  * @param bool   $marnm  if set, include married names
  * @param bool   $fams   if set, only fetch individuals with FAMS records
  *
  * @return Individual[]
  */
 public static function individuals(Tree $tree, $surn, $salpha, $galpha, $marnm, $fams)
 {
     $sql = "SELECT i_id AS xref, i_gedcom AS gedcom, n_full " . "FROM `##individuals` " . "JOIN `##name` ON n_id = i_id AND n_file = i_file " . ($fams ? "JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . "WHERE n_file = :tree_id " . ($marnm ? "" : "AND n_type != '_MARNM'");
     $args = array('tree_id' => $tree->getTreeId());
     if ($surn) {
         $sql .= " AND n_surn COLLATE :collate_1 = :surn";
         $args['collate_1'] = I18N::collation();
         $args['surn'] = $surn;
     } elseif ($salpha === ',') {
         $sql .= " AND n_surn = ''";
     } elseif ($salpha === '@') {
         $sql .= " AND n_surn = '@N.N.'";
     } elseif ($salpha) {
         $sql .= " AND " . self::getInitialSql('n_surn', $salpha);
     } else {
         // All surnames
         $sql .= " AND n_surn NOT IN ('', '@N.N.')";
     }
     if ($galpha) {
         $sql .= " AND " . self::getInitialSql('n_givn', $galpha);
     }
     $sql .= " ORDER BY CASE n_surn WHEN '@N.N.' THEN 1 ELSE 0 END, n_surn COLLATE :collate_2, CASE n_givn WHEN '@P.N.' THEN 1 ELSE 0 END, n_givn COLLATE :collate_3";
     $args['collate_2'] = I18N::collation();
     $args['collate_3'] = I18N::collation();
     $list = array();
     $rows = Database::prepare($sql)->execute($args)->fetchAll();
     foreach ($rows as $row) {
         $person = Individual::getInstance($row->xref, $tree, $row->gedcom);
         // The name from the database may be private - check the filtered list...
         foreach ($person->getAllNames() as $n => $name) {
             if ($name['fullNN'] == $row->n_full) {
                 $person->setPrimaryName($n);
                 // 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.
                 $list[] = clone $person;
                 break;
             }
         }
     }
     return $list;
 }
 /**
  * Search for individuals in a tree.
  *
  * @param Tree   $tree  Search this tree
  * @param string $query Search for this text
  *
  * @return string
  */
 private function search(Tree $tree, $query)
 {
     if (strlen($query) < 2) {
         return '';
     }
     $rows = Database::prepare("SELECT i_id AS xref, i_gedcom AS gedcom" . " FROM `##individuals`, `##name`" . " WHERE (i_id LIKE CONCAT('%', :query_1, '%') OR n_sort LIKE CONCAT('%', :query_2, '%'))" . " AND i_id = n_id AND i_file = n_file AND i_file = :tree_id" . " ORDER BY n_sort COLLATE :collation" . " LIMIT 50")->execute(array('query_1' => $query, 'query_2' => $query, 'tree_id' => $tree->getTreeId(), 'collation' => I18N::collation()))->fetchAll();
     $out = '<ul>';
     foreach ($rows as $row) {
         $person = Individual::getInstance($row->xref, $tree, $row->gedcom);
         if ($person->canShowName()) {
             $out .= '<li><a href="' . $person->getHtmlUrl() . '">' . $person->getSexImage() . ' ' . $person->getFullName() . ' ';
             if ($person->canShow()) {
                 $bd = $person->getLifeSpan();
                 if (!empty($bd)) {
                     $out .= ' (' . $bd . ')';
                 }
             }
             $out .= '</a></li>';
         }
     }
     $out .= '</ul>';
     return $out;
 }
 /**
  * Find records that have changed since a given julian day
  *
  * @param Tree $tree Changes for which tree
  * @param int  $jd   Julian day
  *
  * @return GedcomRecord[] List of records with changes
  */
 private function getRecentChanges(Tree $tree, $jd)
 {
     $sql = "SELECT d_gid FROM `##dates`" . " WHERE d_fact='CHAN' AND d_julianday1 >= :jd AND d_file = :tree_id";
     $vars = array('jd' => $jd, 'tree_id' => $tree->getTreeId());
     $xrefs = Database::prepare($sql)->execute($vars)->fetchOneColumn();
     $records = array();
     foreach ($xrefs as $xref) {
         $record = GedcomRecord::getInstance($xref, $tree);
         if ($record->canShow()) {
             $records[] = $record;
         }
     }
     return $records;
 }
 /**
  * Autocomplete search for families.
  *
  * @param Tree   $tree  Search this tree
  * @param string $query Search for this text
  *
  * @return string
  */
 private function search(Tree $tree, $query)
 {
     if (strlen($query) < 2) {
         return '';
     }
     $rows = Database::prepare("SELECT i_id AS xref" . " FROM `##individuals`, `##name`" . " WHERE (i_id LIKE CONCAT('%', :query_1, '%') OR n_sort LIKE CONCAT('%', :query_2, '%'))" . " AND i_id = n_id AND i_file = n_file AND i_file = :tree_id" . " ORDER BY n_sort COLLATE :collation" . " LIMIT 50")->execute(array('query_1' => $query, 'query_2' => $query, 'tree_id' => $tree->getTreeId(), 'collation' => I18N::collation()))->fetchAll();
     $ids = array();
     foreach ($rows as $row) {
         $ids[] = $row->xref;
     }
     $vars = array();
     if (empty($ids)) {
         //-- no match : search for FAM id
         $where = "f_id LIKE CONCAT('%', ?, '%')";
         $vars[] = $query;
     } else {
         //-- search for spouses
         $qs = implode(',', array_fill(0, count($ids), '?'));
         $where = "(f_husb IN ({$qs}) OR f_wife IN ({$qs}))";
         $vars = array_merge($vars, $ids, $ids);
     }
     $vars[] = $tree->getTreeId();
     $rows = Database::prepare("SELECT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom FROM `##families` WHERE {$where} AND f_file=?")->execute($vars)->fetchAll();
     $out = '<ul>';
     foreach ($rows as $row) {
         $family = Family::getInstance($row->xref, $tree, $row->gedcom);
         if ($family->canShowName()) {
             $out .= '<li><a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . ' ';
             if ($family->canShow()) {
                 $marriage_year = $family->getMarriageYear();
                 if ($marriage_year) {
                     $out .= ' (' . $marriage_year . ')';
                 }
             }
             $out .= '</a></li>';
         }
     }
     $out .= '</ul>';
     return $out;
 }
Exemple #10
0
 /**
  * Get a the current access level for a module
  *
  * @param Tree   $tree
  * @param string $component tab, block, menu, etc
  *
  * @return int
  */
 public function getAccessLevel(Tree $tree, $component)
 {
     $access_level = Database::prepare("SELECT access_level FROM `##module_privacy` WHERE gedcom_id = :gedcom_id AND module_name = :module_name AND component = :component")->execute(array('gedcom_id' => $tree->getTreeId(), 'module_name' => $this->getName(), 'component' => $component))->fetchOne();
     if ($access_level === null) {
         return $this->defaultAccessLevel();
     } else {
         return (int) $access_level;
     }
 }
Exemple #11
0
 /**
  * Get an instance of a GedcomRecord object.  For single records,
  * we just receive the XREF.  For bulk records (such as lists
  * and search results) we can receive the GEDCOM data as well.
  *
  * @param string      $xref
  * @param Tree        $tree
  * @param string|null $gedcom
  *
  * @throws \Exception
  *
  * @return GedcomRecord|Individual|Family|Source|Repository|Media|Note|null
  */
 public static function getInstance($xref, Tree $tree, $gedcom = null)
 {
     $tree_id = $tree->getTreeId();
     // Is this record already in the cache, and of the correct type?
     if (isset(self::$gedcom_record_cache[$xref][$tree_id])) {
         $record = self::$gedcom_record_cache[$xref][$tree_id];
         if ($record instanceof static) {
             return $record;
         } else {
             null;
         }
     }
     // Do we need to fetch the record from the database?
     if ($gedcom === null) {
         $gedcom = static::fetchGedcomRecord($xref, $tree_id);
     }
     // If we can edit, then we also need to be able to see pending records.
     if (Auth::isEditor($tree)) {
         if (!isset(self::$pending_record_cache[$tree_id])) {
             // Fetch all pending records in one database query
             self::$pending_record_cache[$tree_id] = array();
             $rows = Database::prepare("SELECT xref, new_gedcom FROM `##change` WHERE status = 'pending' AND gedcom_id = :tree_id ORDER BY change_id")->execute(array('tree_id' => $tree_id))->fetchAll();
             foreach ($rows as $row) {
                 self::$pending_record_cache[$tree_id][$row->xref] = $row->new_gedcom;
             }
         }
         if (isset(self::$pending_record_cache[$tree_id][$xref])) {
             // A pending edit exists for this record
             $pending = self::$pending_record_cache[$tree_id][$xref];
         } else {
             $pending = null;
         }
     } else {
         // There are no pending changes for this record
         $pending = null;
     }
     // No such record exists
     if ($gedcom === null && $pending === null) {
         return null;
     }
     // Create the object
     if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedcom . $pending, $match)) {
         $xref = $match[1];
         // Collation - we may have requested I123 and found i123
         $type = $match[2];
     } elseif (preg_match('/^0 (HEAD|TRLR)/', $gedcom . $pending, $match)) {
         $xref = $match[1];
         $type = $match[1];
     } elseif ($gedcom . $pending) {
         throw new \Exception('Unrecognized GEDCOM record: ' . $gedcom);
     } else {
         // A record with both pending creation and pending deletion
         $type = static::RECORD_TYPE;
     }
     switch ($type) {
         case 'INDI':
             $record = new Individual($xref, $gedcom, $pending, $tree);
             break;
         case 'FAM':
             $record = new Family($xref, $gedcom, $pending, $tree);
             break;
         case 'SOUR':
             $record = new Source($xref, $gedcom, $pending, $tree);
             break;
         case 'OBJE':
             $record = new Media($xref, $gedcom, $pending, $tree);
             break;
         case 'REPO':
             $record = new Repository($xref, $gedcom, $pending, $tree);
             break;
         case 'NOTE':
             $record = new Note($xref, $gedcom, $pending, $tree);
             break;
         default:
             $record = new self($xref, $gedcom, $pending, $tree);
             break;
     }
     // Store it in the cache
     self::$gedcom_record_cache[$xref][$tree_id] = $record;
     return $record;
 }
Exemple #12
0
 /**
  * Create a new media object, from inline media data.
  *
  * @param int    $level
  * @param string $gedrec
  * @param Tree   $tree
  *
  * @return string
  */
 public static function createMediaObject($level, $gedrec, Tree $tree)
 {
     if (preg_match('/\\n\\d FILE (.+)/', $gedrec, $file_match)) {
         $file = $file_match[1];
     } else {
         $file = '';
     }
     if (preg_match('/\\n\\d TITL (.+)/', $gedrec, $file_match)) {
         $titl = $file_match[1];
     } else {
         $titl = '';
     }
     // Have we already created a media object with the same title/filename?
     $xref = Database::prepare("SELECT m_id FROM `##media` WHERE m_filename = ? AND m_titl = ? AND m_file = ?")->execute(array($file, $titl, $tree->getTreeId()))->fetchOne();
     if (!$xref) {
         $xref = $tree->getNewXref('OBJE');
         // renumber the lines
         $gedrec = preg_replace_callback('/\\n(\\d+)/', function ($m) use($level) {
             return "\n" . ($m[1] - $level);
         }, $gedrec);
         // convert to an object
         $gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);
         // Fix Legacy GEDCOMS
         $gedrec = preg_replace('/\\n1 FORM (.+)\\n1 FILE (.+)\\n1 TITL (.+)/', "\n1 FILE \$2\n2 FORM \$1\n2 TITL \$3", $gedrec);
         // Fix FTB GEDCOMS
         $gedrec = preg_replace('/\\n1 FORM (.+)\\n1 TITL (.+)\\n1 FILE (.+)/', "\n1 FILE \$3\n2 FORM \$1\n2 TITL \$2", $gedrec);
         // Create new record
         $record = new Media($xref, $gedrec, null, $tree);
         Database::prepare("INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute(array($xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree->getTreeId(), $gedrec));
     }
     return "\n" . $level . ' OBJE @' . $xref . '@';
 }
 /**
  * Respond to an autocomplete search request.
  *
  * @param string $query Search for this term
  * @param Tree   $tree  Search in this tree
  *
  * @return string
  */
 public function search($query, Tree $tree)
 {
     if (strlen($query) < 2) {
         return '';
     }
     $rows = Database::prepare("SELECT i_id AS xref" . " FROM `##individuals`" . " JOIN `##name` ON i_id = n_id AND i_file = n_file" . " WHERE n_sort LIKE CONCAT('%', :query, '%') AND i_file = :tree_id" . " ORDER BY n_sort")->execute(array('query' => $query, 'tree_id' => $tree->getTreeId()))->fetchAll();
     $out = '';
     foreach ($rows as $row) {
         $person = Individual::getInstance($row->xref, $tree);
         if ($person && $person->canShowName()) {
             $out .= $this->getPersonLi($person);
         }
     }
     if ($out) {
         return '<ul>' . $out . '</ul>';
     } else {
         return '';
     }
 }
Exemple #14
0
 /**
  * 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();
 }