Ejemplo n.º 1
0
 /**
  * Display the age difference between marriages and the births of children.
  *
  * @param Date $prev
  * @param Date $next
  * @param int  $child_number
  *
  * @return string
  */
 private static function ageDifference(Date $prev, Date $next, $child_number = 0)
 {
     if ($prev->isOK() && $next->isOK()) {
         $days = $next->maximumJulianDay() - $prev->minimumJulianDay();
         if ($days < 0) {
             // Show warning triangle if dates in reverse order
             $diff = '<i class="icon-warning"></i> ';
         } elseif ($child_number > 1 && $days > 1 && $days < 240) {
             // Show warning triangle if children born too close together
             $diff = '<i class="icon-warning"></i> ';
         } else {
             $diff = '';
         }
         $months = round($days * 12 / 365.25);
         // Approximate - we do not know the calendar
         if (abs($months) == 12 || abs($months) >= 24) {
             $diff .= I18N::plural('%s year', '%s years', round($months / 12), I18N::number(round($months / 12)));
         } elseif ($months != 0) {
             $diff .= I18N::plural('%s month', '%s months', $months, I18N::number($months));
         }
         return '<div class="elderdate age">' . $diff . '</div>';
     } else {
         return '';
     }
 }
Ejemplo n.º 2
0
                $tmp = $fact->getDate()->minimumDate();
                if ($tmp->d >= 1 && $tmp->d <= $tmp->daysInMonth()) {
                    // If the day is valid (for its own calendar), display it in the
                    // anniversary day (for the display calendar).
                    $found_facts[$jd - $cal_date->minJD + 1][] = $fact;
                } else {
                    // Otherwise, display it in the "Day not set" box.
                    $found_facts[0][] = $fact;
                }
            }
        }
        break;
    case 'year':
        $cal_date->m = 0;
        $cal_date->setJdFromYmd();
        $found_facts = apply_filter(FunctionsDb::getCalendarEvents($ged_date->minimumJulianDay(), $ged_date->maximumJulianDay(), $filterev, $WT_TREE), $filterof, $filtersx);
        // Eliminate duplicates (e.g. BET JUL 1900 AND SEP 1900 will appear twice in 1900)
        $found_facts = array_unique($found_facts);
        break;
}
// Group the facts by family/individual
$indis = array();
$fams = array();
$cal_facts = array();
switch ($view) {
    case 'year':
    case 'day':
        foreach ($found_facts as $fact) {
            $record = $fact->getParent();
            $xref = $record->getXref();
            if ($record instanceof Individual) {
 /**
  * Perform the search
  */
 private function advancedSearch()
 {
     global $WT_TREE;
     $this->myindilist = array();
     $fct = count($this->fields);
     if (!array_filter($this->values)) {
         return;
     }
     // Dynamic SQL query, plus bind variables
     $sql = 'SELECT DISTINCT ind.i_id AS xref, ind.i_gedcom AS gedcom FROM `##individuals` ind';
     $bind = array();
     // Join the following tables
     $father_name = false;
     $mother_name = false;
     $spouse_family = false;
     $indi_name = false;
     $indi_date = false;
     $fam_date = false;
     $indi_plac = false;
     $fam_plac = false;
     foreach ($this->fields as $n => $field) {
         if ($this->values[$n]) {
             if (substr($field, 0, 14) == 'FAMC:HUSB:NAME') {
                 $father_name = true;
             } elseif (substr($field, 0, 14) == 'FAMC:WIFE:NAME') {
                 $mother_name = true;
             } elseif (substr($field, 0, 4) == 'NAME') {
                 $indi_name = true;
             } elseif (strpos($field, ':DATE') !== false) {
                 if (substr($field, 0, 4) == 'FAMS') {
                     $fam_date = true;
                     $spouse_family = true;
                 } else {
                     $indi_date = true;
                 }
             } elseif (strpos($field, ':PLAC') !== false) {
                 if (substr($field, 0, 4) == 'FAMS') {
                     $fam_plac = true;
                     $spouse_family = true;
                 } else {
                     $indi_plac = true;
                 }
             } elseif ($field == 'FAMS:NOTE') {
                 $spouse_family = true;
             }
         }
     }
     if ($father_name || $mother_name) {
         $sql .= " JOIN `##link`   l_1 ON (l_1.l_file=ind.i_file AND l_1.l_from=ind.i_id AND l_1.l_type='FAMC')";
     }
     if ($father_name) {
         $sql .= " JOIN `##link`   l_2 ON (l_2.l_file=ind.i_file AND l_2.l_from=l_1.l_to AND l_2.l_type='HUSB')";
         $sql .= " JOIN `##name`   f_n ON (f_n.n_file=ind.i_file AND f_n.n_id  =l_2.l_to)";
     }
     if ($mother_name) {
         $sql .= " JOIN `##link`   l_3 ON (l_3.l_file=ind.i_file AND l_3.l_from=l_1.l_to AND l_3.l_type='WIFE')";
         $sql .= " JOIN `##name`   m_n ON (m_n.n_file=ind.i_file AND m_n.n_id  =l_3.l_to)";
     }
     if ($spouse_family) {
         $sql .= " JOIN `##link`     l_4 ON (l_4.l_file=ind.i_file AND l_4.l_from=ind.i_id AND l_4.l_type='FAMS')";
         $sql .= " JOIN `##families` fam ON (fam.f_file=ind.i_file AND fam.f_id  =l_4.l_to)";
     }
     if ($indi_name) {
         $sql .= " JOIN `##name`   i_n ON (i_n.n_file=ind.i_file AND i_n.n_id=ind.i_id)";
     }
     if ($indi_date) {
         $sql .= " JOIN `##dates`  i_d ON (i_d.d_file=ind.i_file AND i_d.d_gid=ind.i_id)";
     }
     if ($fam_date) {
         $sql .= " JOIN `##dates`  f_d ON (f_d.d_file=ind.i_file AND f_d.d_gid=fam.f_id)";
     }
     if ($indi_plac) {
         $sql .= " JOIN `##placelinks`   i_pl ON (i_pl.pl_file=ind.i_file AND i_pl.pl_gid =ind.i_id)";
         $sql .= " JOIN (" . "SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) AS place, p1.p_id AS id, p1.p_file AS file" . " FROM      `##places` AS p1" . " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id=p2.p_id)" . " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id=p3.p_id)" . " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id=p4.p_id)" . " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id=p5.p_id)" . " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id=p6.p_id)" . " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id=p7.p_id)" . " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id=p8.p_id)" . " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id=p9.p_id)" . ") AS i_p ON (i_p.file  =ind.i_file AND i_pl.pl_p_id= i_p.id)";
     }
     if ($fam_plac) {
         $sql .= " JOIN `##placelinks`   f_pl ON (f_pl.pl_file=ind.i_file AND f_pl.pl_gid =fam.f_id)";
         $sql .= " JOIN (" . "SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) AS place, p1.p_id AS id, p1.p_file AS file" . " FROM      `##places` AS p1" . " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id=p2.p_id)" . " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id=p3.p_id)" . " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id=p4.p_id)" . " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id=p5.p_id)" . " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id=p6.p_id)" . " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id=p7.p_id)" . " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id=p8.p_id)" . " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id=p9.p_id)" . ") AS f_p ON (f_p.file  =ind.i_file AND f_pl.pl_p_id= f_p.id)";
     }
     // Add the where clause
     $sql .= " WHERE ind.i_file=?";
     $bind[] = $WT_TREE->getTreeId();
     for ($i = 0; $i < $fct; $i++) {
         $field = $this->fields[$i];
         $value = $this->values[$i];
         if ($value === '') {
             continue;
         }
         $parts = preg_split("/:/", $field . '::::');
         if ($parts[0] == 'NAME') {
             // NAME:*
             switch ($parts[1]) {
                 case 'GIVN':
                     switch ($parts[2]) {
                         case 'EXACT':
                             $sql .= " AND i_n.n_givn=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND i_n.n_givn LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_givn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_givn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                     }
                     break;
                 case 'SURN':
                     switch ($parts[2]) {
                         case 'EXACT':
                             $sql .= " AND i_n.n_surname=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND i_n.n_surname LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND i_n.n_surname LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= " AND (" . implode(' OR ', $sdx) . ")";
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_surn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= " AND (" . implode(' OR ', $sdx) . ")";
                                 break;
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_surn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                     }
                     break;
                 case 'NICK':
                 case '_MARNM':
                 case '_HEB':
                 case '_AKA':
                     $sql .= " AND i_n.n_type=? AND i_n.n_full LIKE CONCAT('%', ?, '%')";
                     $bind[] = $parts[1];
                     $bind[] = $value;
                     break;
             }
         } elseif ($parts[1] == 'DATE') {
             // *:DATE
             $date = new Date($value);
             if ($date->isOK()) {
                 $jd1 = $date->minimumJulianDay();
                 $jd2 = $date->maximumJulianDay();
                 if (!empty($this->plusminus[$i])) {
                     $adjd = $this->plusminus[$i] * 365;
                     $jd1 -= $adjd;
                     $jd2 += $adjd;
                 }
                 $sql .= " AND i_d.d_fact=? AND i_d.d_julianday1>=? AND i_d.d_julianday2<=?";
                 $bind[] = $parts[0];
                 $bind[] = $jd1;
                 $bind[] = $jd2;
             }
         } elseif ($parts[0] == 'FAMS' && $parts[2] == 'DATE') {
             // FAMS:*:DATE
             $date = new Date($value);
             if ($date->isOK()) {
                 $jd1 = $date->minimumJulianDay();
                 $jd2 = $date->maximumJulianDay();
                 if (!empty($this->plusminus[$i])) {
                     $adjd = $this->plusminus[$i] * 365;
                     $jd1 -= $adjd;
                     $jd2 += $adjd;
                 }
                 $sql .= " AND f_d.d_fact=? AND f_d.d_julianday1>=? AND f_d.d_julianday2<=?";
                 $bind[] = $parts[1];
                 $bind[] = $jd1;
                 $bind[] = $jd2;
             }
         } elseif ($parts[1] == 'PLAC') {
             // *:PLAC
             // SQL can only link a place to a person/family, not to an event.
             $sql .= " AND i_p.place LIKE CONCAT('%', ?, '%')";
             $bind[] = $value;
         } elseif ($parts[0] == 'FAMS' && $parts[2] == 'PLAC') {
             // FAMS:*:PLAC
             // SQL can only link a place to a person/family, not to an event.
             $sql .= " AND f_p.place LIKE CONCAT('%', ?, '%')";
             $bind[] = $value;
         } elseif ($parts[0] == 'FAMC' && $parts[2] == 'NAME') {
             $table = $parts[1] == 'HUSB' ? 'f_n' : 'm_n';
             // NAME:*
             switch ($parts[3]) {
                 case 'GIVN':
                     switch ($parts[4]) {
                         case 'EXACT':
                             $sql .= " AND {$table}.n_givn=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND {$table}.n_givn LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND {$table}.n_givn LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_givn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_givn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_givn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                                 break;
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_givn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                     }
                     break;
                 case 'SURN':
                     switch ($parts[4]) {
                         case 'EXACT':
                             $sql .= " AND {$table}.n_surname=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND {$table}.n_surname LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND {$table}.n_surname LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_surn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_surn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                     }
                     break;
             }
         } elseif ($parts[0] == 'FAMS') {
             // e.g. searches for occupation, religion, note, etc.
             $sql .= " AND fam.f_gedcom REGEXP CONCAT('\n[0-9] ', ?, '(.*\n[0-9] CONT)* [^\n]*', ?)";
             $bind[] = $parts[1];
             $bind[] = $value;
         } else {
             // e.g. searches for occupation, religion, note, etc.
             $sql .= " AND ind.i_gedcom REGEXP CONCAT('\n[0-9] ', ?, '(.*\n[0-9] CONT)* [^\n]*', ?)";
             $bind[] = $parts[0];
             $bind[] = $value;
         }
     }
     $rows = Database::prepare($sql)->execute($bind)->fetchAll();
     foreach ($rows as $row) {
         $person = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
         // Check for XXXX:PLAC fields, which were only partially matched by SQL
         foreach ($this->fields as $n => $field) {
             if ($this->values[$n] && preg_match('/^(' . WT_REGEX_TAG . '):PLAC$/', $field, $match)) {
                 if (!preg_match('/\\n1 ' . $match[1] . '(\\n[2-9].*)*\\n2 PLAC .*' . preg_quote($this->values[$n], '/') . '/i', $person->getGedcom())) {
                     continue 2;
                 }
             }
         }
         $this->myindilist[] = $person;
     }
 }
Ejemplo n.º 4
0
 /**
  * Compare two dates, so they can be sorted.
  *
  * return <0 if $a<$b
  * return >0 if $b>$a
  * return  0 if dates same/overlap
  * BEF/AFT sort as the day before/after
  *
  * @param Date $a
  * @param Date $b
  *
  * @return int
  */
 public static function compare(Date $a, Date $b)
 {
     // Get min/max JD for each date.
     switch ($a->qual1) {
         case 'BEF':
             $amin = $a->minimumJulianDay() - 1;
             $amax = $amin;
             break;
         case 'AFT':
             $amax = $a->maximumJulianDay() + 1;
             $amin = $amax;
             break;
         default:
             $amin = $a->minimumJulianDay();
             $amax = $a->maximumJulianDay();
             break;
     }
     switch ($b->qual1) {
         case 'BEF':
             $bmin = $b->minimumJulianDay() - 1;
             $bmax = $bmin;
             break;
         case 'AFT':
             $bmax = $b->maximumJulianDay() + 1;
             $bmin = $bmax;
             break;
         default:
             $bmin = $b->minimumJulianDay();
             $bmax = $b->maximumJulianDay();
             break;
     }
     if ($amax < $bmin) {
         return -1;
     } elseif ($amin > $bmax && $bmax > 0) {
         return 1;
     } elseif ($amin < $bmin && $amax <= $bmax) {
         return -1;
     } elseif ($amin > $bmin && $amax >= $bmax && $bmax > 0) {
         return 1;
     } else {
         return 0;
     }
 }
Ejemplo n.º 5
0
 /**
  * XML <List>
  *
  * @param array $attrs an array of key value pairs for the attributes
  */
 private function listStartHandler($attrs)
 {
     global $WT_TREE;
     $this->process_repeats++;
     if ($this->process_repeats > 1) {
         return;
     }
     $match = array();
     if (isset($attrs['sortby'])) {
         $sortby = $attrs['sortby'];
         if (preg_match("/\\\$(\\w+)/", $sortby, $match)) {
             $sortby = $this->vars[$match[1]]['id'];
             $sortby = trim($sortby);
         }
     } else {
         $sortby = "NAME";
     }
     if (isset($attrs['list'])) {
         $listname = $attrs['list'];
     } else {
         $listname = "individual";
     }
     // Some filters/sorts can be applied using SQL, while others require PHP
     switch ($listname) {
         case "pending":
             $rows = Database::prepare("SELECT xref, CASE new_gedcom WHEN '' THEN old_gedcom ELSE new_gedcom END AS gedcom" . " FROM `##change`" . " WHERE (xref, change_id) IN (" . "  SELECT xref, MAX(change_id)" . "  FROM `##change`" . "  WHERE status = 'pending' AND gedcom_id = :tree_id" . "  GROUP BY xref" . " )")->execute(array('tree_id' => $WT_TREE->getTreeId()))->fetchAll();
             $this->list = array();
             foreach ($rows as $row) {
                 $this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom);
             }
             break;
         case 'individual':
             $sql_select = "SELECT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` ";
             $sql_join = "";
             $sql_where = " WHERE i_file = :tree_id";
             $sql_order_by = "";
             $sql_params = array('tree_id' => $WT_TREE->getTreeId());
             foreach ($attrs as $attr => $value) {
                 if (strpos($attr, 'filter') === 0 && $value) {
                     $value = $this->substituteVars($value, false);
                     // Convert the various filters into SQL
                     if (preg_match('/^(\\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
                         $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)";
                         $sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
                         $sql_params[$attr . 'fact'] = $match[1];
                         $date = new Date($match[3]);
                         if ($match[2] == "LTE") {
                             $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
                             $sql_params[$attr . 'date'] = $date->maximumJulianDay();
                         } else {
                             $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
                             $sql_params[$attr . 'date'] = $date->minimumJulianDay();
                         }
                         if ($sortby == $match[1]) {
                             $sortby = "";
                             $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
                         }
                         unset($attrs[$attr]);
                         // This filter has been fully processed
                     } elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) {
                         // Do nothing, unless you have to
                         if ($match[1] != '' || $sortby == 'NAME') {
                             $sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)";
                             // Search the DB only if there is any name supplied
                             if ($match[1] != "") {
                                 $names = explode(" ", $match[1]);
                                 foreach ($names as $n => $name) {
                                     $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
                                     $sql_params[$attr . 'name' . $n] = $name;
                                 }
                             }
                             // Let the DB do the name sorting even when no name was entered
                             if ($sortby == "NAME") {
                                 $sortby = "";
                                 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
                             }
                         }
                         unset($attrs[$attr]);
                         // This filter has been fully processed
                     } elseif (preg_match('/^REGEXP \\/(.+)\\//', $value, $match)) {
                         $sql_where .= " AND i_gedcom REGEXP :{$attr}gedcom";
                         // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
                         $sql_params[$attr . 'gedcom'] = str_replace('\\n', "\n", $match[1]);
                         unset($attrs[$attr]);
                         // This filter has been fully processed
                     } elseif (preg_match('/^(?:\\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
                         $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file = i_file)";
                         $sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file = {$attr}b.pl_file AND {$attr}b.pl_p_id = {$attr}a.p_id AND {$attr}b.pl_gid = i_id)";
                         $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
                         $sql_params[$attr . 'place'] = $match[1];
                         // Don't unset this filter. This is just initial filtering
                     } elseif (preg_match('/^(\\w*):*(\\w*) CONTAINS (.+)$/', $value, $match)) {
                         $sql_where .= " AND i_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
                         $sql_params[$attr . 'contains1'] = $match[1];
                         $sql_params[$attr . 'contains2'] = $match[2];
                         $sql_params[$attr . 'contains3'] = $match[3];
                         // Don't unset this filter. This is just initial filtering
                     }
                 }
             }
             $this->list = array();
             $rows = Database::prepare($sql_select . $sql_join . $sql_where . $sql_order_by)->execute($sql_params)->fetchAll();
             foreach ($rows as $row) {
                 $this->list[$row->xref] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
             }
             break;
         case 'family':
             $sql_select = "SELECT f_id AS xref, f_gedcom AS gedcom FROM `##families`";
             $sql_join = "";
             $sql_where = " WHERE f_file = :tree_id";
             $sql_order_by = "";
             $sql_params = array('tree_id' => $WT_TREE->getTreeId());
             foreach ($attrs as $attr => $value) {
                 if (strpos($attr, 'filter') === 0 && $value) {
                     $value = $this->substituteVars($value, false);
                     // Convert the various filters into SQL
                     if (preg_match('/^(\\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
                         $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)";
                         $sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
                         $sql_params[$attr . 'fact'] = $match[1];
                         $date = new Date($match[3]);
                         if ($match[2] == "LTE") {
                             $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
                             $sql_params[$attr . 'date'] = $date->maximumJulianDay();
                         } else {
                             $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
                             $sql_params[$attr . 'date'] = $date->minimumJulianDay();
                         }
                         if ($sortby == $match[1]) {
                             $sortby = "";
                             $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
                         }
                         unset($attrs[$attr]);
                         // This filter has been fully processed
                     } elseif (preg_match('/^REGEXP \\/(.+)\\//', $value, $match)) {
                         $sql_where .= " AND f_gedcom REGEXP :{$attr}gedcom";
                         // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
                         $sql_params[$attr . 'gedcom'] = str_replace('\\n', "\n", $match[1]);
                         unset($attrs[$attr]);
                         // This filter has been fully processed
                     } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) {
                         // Do nothing, unless you have to
                         if ($match[1] != '' || $sortby == 'NAME') {
                             $sql_join .= " JOIN `##name` AS {$attr} ON n_file = f_file AND n_id IN (f_husb, f_wife)";
                             // Search the DB only if there is any name supplied
                             if ($match[1] != "") {
                                 $names = explode(" ", $match[1]);
                                 foreach ($names as $n => $name) {
                                     $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
                                     $sql_params[$attr . 'name' . $n] = $name;
                                 }
                             }
                             // Let the DB do the name sorting even when no name was entered
                             if ($sortby == "NAME") {
                                 $sortby = "";
                                 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
                             }
                         }
                         unset($attrs[$attr]);
                         // This filter has been fully processed
                     } elseif (preg_match('/^(?:\\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
                         $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)";
                         $sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file={$attr}b.pl_file AND {$attr}b.pl_p_id={$attr}a.p_id AND {$attr}b.pl_gid=f_id)";
                         $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
                         $sql_params[$attr . 'place'] = $match[1];
                         // Don't unset this filter. This is just initial filtering
                     } elseif (preg_match('/^(\\w*):*(\\w*) CONTAINS (.+)$/', $value, $match)) {
                         $sql_where .= " AND f_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
                         $sql_params[$attr . 'contains1'] = $match[1];
                         $sql_params[$attr . 'contains2'] = $match[2];
                         $sql_params[$attr . 'contains3'] = $match[3];
                         // Don't unset this filter. This is just initial filtering
                     }
                 }
             }
             $this->list = array();
             $rows = Database::prepare($sql_select . $sql_join . $sql_where . $sql_order_by)->execute($sql_params)->fetchAll();
             foreach ($rows as $row) {
                 $this->list[$row->xref] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom);
             }
             break;
         default:
             throw new \DomainException('Invalid list name: ' . $listname);
     }
     $filters = array();
     $filters2 = array();
     if (isset($attrs['filter1']) && count($this->list) > 0) {
         foreach ($attrs as $key => $value) {
             if (preg_match("/filter(\\d)/", $key)) {
                 $condition = $value;
                 if (preg_match("/@(\\w+)/", $condition, $match)) {
                     $id = $match[1];
                     $value = "''";
                     if ($id == "ID") {
                         if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
                             $value = "'" . $match[1] . "'";
                         }
                     } elseif ($id == "fact") {
                         $value = "'" . $this->fact . "'";
                     } elseif ($id == "desc") {
                         $value = "'" . $this->desc . "'";
                     } else {
                         if (preg_match("/\\d {$id} (.+)/", $this->gedrec, $match)) {
                             $value = "'" . str_replace("@", "", trim($match[1])) . "'";
                         }
                     }
                     $condition = preg_replace("/@{$id}/", $value, $condition);
                 }
                 //-- handle regular expressions
                 if (preg_match("/([A-Z:]+)\\s*([^\\s]+)\\s*(.+)/", $condition, $match)) {
                     $tag = trim($match[1]);
                     $expr = trim($match[2]);
                     $val = trim($match[3]);
                     if (preg_match("/\\\$(\\w+)/", $val, $match)) {
                         $val = $this->vars[$match[1]]['id'];
                         $val = trim($val);
                     }
                     if ($val) {
                         $searchstr = "";
                         $tags = explode(":", $tag);
                         //-- only limit to a level number if we are specifically looking at a level
                         if (count($tags) > 1) {
                             $level = 1;
                             foreach ($tags as $t) {
                                 if (!empty($searchstr)) {
                                     $searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n";
                                 }
                                 //-- search for both EMAIL and _EMAIL... silly double gedcom standard
                                 if ($t == "EMAIL" || $t == "_EMAIL") {
                                     $t = "_?EMAIL";
                                 }
                                 $searchstr .= $level . " " . $t;
                                 $level++;
                             }
                         } else {
                             if ($tag == "EMAIL" || $tag == "_EMAIL") {
                                 $tag = "_?EMAIL";
                             }
                             $t = $tag;
                             $searchstr = "1 " . $tag;
                         }
                         switch ($expr) {
                             case "CONTAINS":
                                 if ($t == "PLAC") {
                                     $searchstr .= "[^\n]*[, ]*" . $val;
                                 } else {
                                     $searchstr .= "[^\n]*" . $val;
                                 }
                                 $filters[] = $searchstr;
                                 break;
                             default:
                                 $filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val);
                                 break;
                         }
                     }
                 }
             }
         }
     }
     //-- apply other filters to the list that could not be added to the search string
     if ($filters) {
         foreach ($this->list as $key => $record) {
             foreach ($filters as $filter) {
                 if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) {
                     unset($this->list[$key]);
                     break;
                 }
             }
         }
     }
     if ($filters2) {
         $mylist = array();
         foreach ($this->list as $indi) {
             $key = $indi->getXref();
             $grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE));
             $keep = true;
             foreach ($filters2 as $filter) {
                 if ($keep) {
                     $tag = $filter['tag'];
                     $expr = $filter['expr'];
                     $val = $filter['val'];
                     if ($val == "''") {
                         $val = "";
                     }
                     $tags = explode(":", $tag);
                     $t = end($tags);
                     $v = $this->getGedcomValue($tag, 1, $grec);
                     //-- check for EMAIL and _EMAIL (silly double gedcom standard :P)
                     if ($t == "EMAIL" && empty($v)) {
                         $tag = str_replace("EMAIL", "_EMAIL", $tag);
                         $tags = explode(":", $tag);
                         $t = end($tags);
                         $v = Functions::getSubRecord(1, $tag, $grec);
                     }
                     switch ($expr) {
                         case "GTE":
                             if ($t == "DATE") {
                                 $date1 = new Date($v);
                                 $date2 = new Date($val);
                                 $keep = Date::compare($date1, $date2) >= 0;
                             } elseif ($val >= $v) {
                                 $keep = true;
                             }
                             break;
                         case "LTE":
                             if ($t == "DATE") {
                                 $date1 = new Date($v);
                                 $date2 = new Date($val);
                                 $keep = Date::compare($date1, $date2) <= 0;
                             } elseif ($val >= $v) {
                                 $keep = true;
                             }
                             break;
                         default:
                             if ($v == $val) {
                                 $keep = true;
                             } else {
                                 $keep = false;
                             }
                             break;
                     }
                 }
             }
             if ($keep) {
                 $mylist[$key] = $indi;
             }
         }
         $this->list = $mylist;
     }
     switch ($sortby) {
         case 'NAME':
             uasort($this->list, '\\Fisharebest\\Webtrees\\GedcomRecord::compare');
             break;
         case 'CHAN':
             uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) {
                 return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true);
             });
             break;
         case 'BIRT:DATE':
             uasort($this->list, '\\Fisharebest\\Webtrees\\Individual::compareBirthDate');
             break;
         case 'DEAT:DATE':
             uasort($this->list, '\\Fisharebest\\Webtrees\\Individual::compareDeathDate');
             break;
         case 'MARR:DATE':
             uasort($this->list, '\\Fisharebest\\Webtrees\\Family::compareMarrDate');
             break;
         default:
             // unsorted or already sorted by SQL
             break;
     }
     array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
     $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
 }