/** * get edit menu */ public function getEditMenu() { if (!$this->record || $this->record->isPendingDeletion()) { return null; } // edit menu $menu = new Menu(I18N::translate('Edit'), '#', 'menu-record'); // edit raw if (Auth::isAdmin() || Auth::isEditor($this->record->getTree()) && $this->record->getTree()->getPreference('SHOW_GEDCOM_RECORD')) { $menu->addSubmenu(new Menu(I18N::translate('Edit raw GEDCOM'), '#', 'menu-record-editraw', array('onclick' => 'return edit_raw("' . $this->record->getXref() . '");'))); } // delete if (Auth::isEditor($this->record->getTree())) { $menu->addSubmenu(new Menu(I18N::translate('Delete'), '#', 'menu-record-del', array('onclick' => 'return delete_record("' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($this->record->getFullName()))) . '", "' . $this->record->getXref() . '");'))); } // add to favorites if (Module::getModuleByName('user_favorites')) { $menu->addSubmenu(new Menu(I18N::translate('Add to favorites'), '#', 'menu-record-addfav', array('onclick' => 'jQuery.post("module.php?mod=user_favorites&mod_action=menu-add-favorite" ,{xref:"' . $this->record->getXref() . '"},function(){location.reload();})'))); } // Get the link for the first submenu and set it as the link for the main menu if ($menu->getSubmenus()) { $submenus = $menu->getSubmenus(); $menu->setLink($submenus[0]->getLink()); $menu->setAttrs($submenus[0]->getAttrs()); } return $menu; }
/** * Startup activity */ public function __construct() { global $WT_TREE; $xref = Filter::get('rid', WT_REGEX_XREF); $this->record = Repository::getInstance($xref, $WT_TREE); parent::__construct(); }
/** * get edit menu */ public function getEditMenu() { if (!$this->record || $this->record->isPendingDeletion()) { return null; } // edit menu $menu = new Menu(I18N::translate('Edit'), '#', 'menu-record'); // edit raw if (Auth::isAdmin() || Auth::isEditor($this->record->getTree()) && $this->record->getTree()->getPreference('SHOW_GEDCOM_RECORD')) { $menu->addSubmenu(new Menu(I18N::translate('Edit the raw GEDCOM'), '#', 'menu-record-editraw', array('onclick' => 'return edit_raw("' . $this->record->getXref() . '");'))); } // delete if (Auth::isEditor($this->record->getTree())) { $menu->addSubmenu(new Menu(I18N::translate('Delete'), '#', 'menu-record-del', array('onclick' => 'return delete_record("' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($this->record->getFullName()))) . '", "' . $this->record->getXref() . '");'))); } return $menu; }
/** * Export the database in GEDCOM format * * @param Tree $tree Which tree to export * @param resource $gedout Handle to a writable stream * @param string[] $exportOptions Export options are as follows: * 'privatize': which Privacy rules apply? (none, visitor, user, manager) * 'toANSI': should the output be produced in ISO-8859-1 instead of UTF-8? (yes, no) * 'path': what constant should prefix all media file paths? (eg: media/ or c:\my pictures\my family * 'slashes': what folder separators apply to media file paths? (forward, backward) */ public static function exportGedcom(Tree $tree, $gedout, $exportOptions) { switch ($exportOptions['privatize']) { case 'gedadmin': $access_level = Auth::PRIV_NONE; break; case 'user': $access_level = Auth::PRIV_USER; break; case 'visitor': $access_level = Auth::PRIV_PRIVATE; break; case 'none': $access_level = Auth::PRIV_HIDE; break; } $head = self::gedcomHeader($tree); if ($exportOptions['toANSI'] == 'yes') { $head = str_replace('UTF-8', 'ANSI', $head); $head = utf8_decode($head); } $head = self::reformatRecord($head); fwrite($gedout, $head); // Buffer the output. Lots of small fwrite() calls can be very slow when writing large gedcoms. $buffer = ''; // Generate the OBJE/SOUR/REPO/NOTE records first, as their privacy calcualations involve // database queries, and we wish to avoid large gaps between queries due to MySQL connection timeouts. $tmp_gedcom = ''; $rows = Database::prepare("SELECT m_id AS xref, m_gedcom AS gedcom" . " FROM `##media` WHERE m_file = :tree_id ORDER BY m_id")->execute(array('tree_id' => $tree->getTreeId()))->fetchAll(); foreach ($rows as $row) { $rec = Media::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level); $rec = self::convertMediaPath($rec, $exportOptions['path']); if ($exportOptions['toANSI'] === 'yes') { $rec = utf8_decode($rec); } $tmp_gedcom .= self::reformatRecord($rec); } $rows = Database::prepare("SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom" . " FROM `##sources` WHERE s_file = :tree_id ORDER BY s_id")->execute(array('tree_id' => $tree->getTreeId()))->fetchAll(); foreach ($rows as $row) { $rec = Source::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level); if ($exportOptions['toANSI'] === 'yes') { $rec = utf8_decode($rec); } $tmp_gedcom .= self::reformatRecord($rec); } $rows = Database::prepare("SELECT o_type AS type, o_id AS xref, o_gedcom AS gedcom" . " FROM `##other` WHERE o_file = :tree_id AND o_type NOT IN ('HEAD', 'TRLR') ORDER BY o_id")->execute(array('tree_id' => $tree->getTreeId()))->fetchAll(); foreach ($rows as $row) { switch ($row->type) { case 'NOTE': $record = Note::getInstance($row->xref, $tree, $row->gedcom); break; case 'REPO': $record = Repository::getInstance($row->xref, $tree, $row->gedcom); break; default: $record = GedcomRecord::getInstance($row->xref, $tree, $row->gedcom); break; } $rec = $record->privatizeGedcom($access_level); if ($exportOptions['toANSI'] === 'yes') { $rec = utf8_decode($rec); } $tmp_gedcom .= self::reformatRecord($rec); } $rows = Database::prepare("SELECT i_id AS xref, i_gedcom AS gedcom" . " FROM `##individuals` WHERE i_file = :tree_id ORDER BY i_id")->execute(array('tree_id' => $tree->getTreeId()))->fetchAll(); foreach ($rows as $row) { $rec = Individual::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level); if ($exportOptions['toANSI'] === 'yes') { $rec = utf8_decode($rec); } $buffer .= self::reformatRecord($rec); if (strlen($buffer) > 65536) { fwrite($gedout, $buffer); $buffer = ''; } } $rows = Database::prepare("SELECT f_id AS xref, f_gedcom AS gedcom" . " FROM `##families` WHERE f_file = :tree_id ORDER BY f_id")->execute(array('tree_id' => $tree->getTreeId()))->fetchAll(); foreach ($rows as $row) { $rec = Family::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level); if ($exportOptions['toANSI'] === 'yes') { $rec = utf8_decode($rec); } $buffer .= self::reformatRecord($rec); if (strlen($buffer) > 65536) { fwrite($gedout, $buffer); $buffer = ''; } } fwrite($gedout, $buffer); fwrite($gedout, $tmp_gedcom); fwrite($gedout, '0 TRLR' . WT_EOL); }
/** * print a repository record * * find and print repository information attached to a source * * @param string $xref the Gedcom Xref ID of the repository to print */ public static function printRepositoryRecord($xref) { global $WT_TREE; $repository = Repository::getInstance($xref, $WT_TREE); if ($repository && $repository->canShow()) { echo '<a class="field" href="', $repository->getHtmlUrl(), '">', $repository->getFullName(), '</a><br>'; echo '<br>'; echo FunctionsPrint::printFactNotes($repository->getGedcom(), 1); } }
/** * Get the record to which this fact links * * @return Individual|Family|Source|Repository|Media|Note|null */ public function getTarget() { $xref = trim($this->getValue(), '@'); switch ($this->tag) { case 'FAMC': case 'FAMS': return Family::getInstance($xref, $this->getParent()->getTree()); case 'HUSB': case 'WIFE': case 'CHIL': return Individual::getInstance($xref, $this->getParent()->getTree()); case 'SOUR': return Source::getInstance($xref, $this->getParent()->getTree()); case 'OBJE': return Media::getInstance($xref, $this->getParent()->getTree()); case 'REPO': return Repository::getInstance($xref, $this->getParent()->getTree()); case 'NOTE': return Note::getInstance($xref, $this->getParent()->getTree()); default: return GedcomRecord::getInstance($xref, $this->getParent()->getTree()); } }
/** * Get the current view of a record, allowing for pending changes * * @param string $xref * @param string $type * * @return string */ public static function getLatestRecord($xref, $type) { global $WT_TREE; switch ($type) { case 'INDI': return Individual::getInstance($xref, $WT_TREE)->getGedcom(); case 'FAM': return Family::getInstance($xref, $WT_TREE)->getGedcom(); case 'SOUR': return Source::getInstance($xref, $WT_TREE)->getGedcom(); case 'REPO': return Repository::getInstance($xref, $WT_TREE)->getGedcom(); case 'OBJE': return Media::getInstance($xref, $WT_TREE)->getGedcom(); case 'NOTE': return Note::getInstance($xref, $WT_TREE)->getGedcom(); default: return GedcomRecord::getInstance($xref, $WT_TREE)->getGedcom(); } }
/** * Defined in session.php * * @global Tree $WT_TREE */ global $WT_TREE; use Fisharebest\Webtrees\Controller\PageController; define('WT_SCRIPT_NAME', 'admin_trees_duplicates.php'); require './includes/session.php'; $controller = new PageController(); $controller->restrictAccess(Auth::isManager($WT_TREE))->setPageTitle(I18N::translate('Find duplicates') . ' — ' . $WT_TREE->getTitleHtml())->pageHeader(); $repositories = Database::prepare("SELECT GROUP_CONCAT(n_id) AS xrefs " . " FROM `##other`" . " JOIN `##name` ON o_id = n_id AND o_file = n_file" . " WHERE o_file = :tree_id AND o_type = 'REPO'" . " GROUP BY n_full" . " HAVING COUNT(n_id) > 1")->execute(array('tree_id' => $WT_TREE->getTreeId()))->fetchAll(); $repositories = array_map(function (\stdClass $x) use($WT_TREE) { $tmp = explode(',', $x->xrefs); return array_map(function ($y) use($WT_TREE) { return Repository::getInstance($y, $WT_TREE); }, $tmp); }, $repositories); $sources = Database::prepare("SELECT GROUP_CONCAT(n_id) AS xrefs " . " FROM `##sources`" . " JOIN `##name` ON s_id = n_id AND s_file = n_file" . " WHERE s_file = :tree_id" . " GROUP BY n_full" . " HAVING COUNT(n_id) > 1")->execute(array('tree_id' => $WT_TREE->getTreeId()))->fetchAll(); $sources = array_map(function (\stdClass $x) use($WT_TREE) { $tmp = explode(',', $x->xrefs); return array_map(function ($y) use($WT_TREE) { return Source::getInstance($y, $WT_TREE); }, $tmp); }, $sources); $individuals = Database::prepare("SELECT DISTINCT GROUP_CONCAT(d_gid ORDER BY d_gid) AS xrefs" . " FROM `##dates` AS d" . " JOIN `##name` ON d_file = n_file AND d_gid = n_id" . " WHERE d_file = :tree_id AND d_fact IN ('BIRT', 'CHR', 'BAPM', 'DEAT', 'BURI')" . " GROUP BY d_day, d_month, d_year, d_type, d_fact, n_type, n_full" . " HAVING COUNT(DISTINCT d_gid) > 1")->execute(array('tree_id' => $WT_TREE->getTreeId()))->fetchAll(); $individuals = array_map(function (\stdClass $x) use($WT_TREE) { $tmp = explode(',', $x->xrefs); return array_map(function ($y) use($WT_TREE) { return Individual::getInstance($y, $WT_TREE); }, $tmp);
/** * Find repositories linked to this record. * * @param string $link * * @return Repository[] */ public function linkedRepositories($link) { $rows = Database::prepare("SELECT o_id AS xref, o_gedcom AS gedcom" . " 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 = :tree_id AND o_type = 'REPO' AND l_type = :link AND l_to = :xref" . " ORDER BY n_sort COLLATE :collation")->execute(array('tree_id' => $this->tree->getTreeId(), 'link' => $link, 'xref' => $this->xref, 'collation' => I18N::collation()))->fetchAll(); $list = array(); foreach ($rows as $row) { $record = Repository::getInstance($row->xref, $this->tree, $row->gedcom); if ($record->canShowName()) { $list[] = $record; } } return $list; }
echo '<td class="optionbox wrap">'; if ($linktoid == "") { echo '<input class="pedigree_form" type="text" name="linktoid" id="linktosid" size="3" value="', $linktoid, '"> '; echo FunctionsPrint::printFindSourceLink('linktosid'); } else { $record = Source::getInstance($linktoid, $WT_TREE); echo $record->formatList('span', false, $record->getFullName()); } } if ($linkto == "repository") { echo I18N::translate('Repository'), "</td>"; echo '<td class="optionbox wrap">'; if ($linktoid == "") { echo '<input class="pedigree_form" type="text" name="linktoid" id="linktorid" size="3" value="', $linktoid, '">'; } else { $record = Repository::getInstance($linktoid, $WT_TREE); echo $record->formatList('span', false, $record->getFullName()); } } if ($linkto == "note") { echo I18N::translate('Shared note'), "</td>"; echo '<td class="optionbox wrap">'; if ($linktoid == "") { echo '<input class="pedigree_form" type="text" name="linktoid" id="linktonid" size="3" value="', $linktoid, '">'; } else { $record = Note::getInstance($linktoid, $WT_TREE); echo $record->formatList('span', false, $record->getFullName()); } } echo '</td></tr>'; echo '<tr><td class="topbottombar" colspan="2"><input type="submit" value="', I18N::translate('Set link'), '"></td></tr>';
/** * A separate file for each family tree and each record type. * These files depend on access levels, so only cache for visitors. * * @param int $ged_id * @param string $rec_type * @param string $volume */ private function generateFile($ged_id, $rec_type, $volume) { $tree = Tree::findById($ged_id); // Check the cache $timestamp = $this->getSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp'); if ($timestamp > WT_TIMESTAMP - self::CACHE_LIFE && !Auth::check()) { $data = $this->getSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml'); } else { $data = '<url><loc>' . WT_BASE_URL . 'index.php?ctype=gedcom&ged=' . $tree->getNameUrl() . '</loc></url>' . PHP_EOL; $records = array(); switch ($rec_type) { case 'i': $rows = Database::prepare("SELECT i_id AS xref, i_gedcom AS gedcom" . " FROM `##individuals`" . " WHERE i_file = :tree_id" . " ORDER BY i_id" . " LIMIT :limit OFFSET :offset")->execute(array('tree_id' => $ged_id, 'limit' => self::RECORDS_PER_VOLUME, 'offset' => self::RECORDS_PER_VOLUME * $volume))->fetchAll(); foreach ($rows as $row) { $records[] = Individual::getInstance($row->xref, $tree, $row->gedcom); } break; case 's': $rows = Database::prepare("SELECT s_id AS xref, s_gedcom AS gedcom" . " FROM `##sources`" . " WHERE s_file = :tree_id" . " ORDER BY s_id" . " LIMIT :limit OFFSET :offset")->execute(array('tree_id' => $ged_id, 'limit' => self::RECORDS_PER_VOLUME, 'offset' => self::RECORDS_PER_VOLUME * $volume))->fetchAll(); foreach ($rows as $row) { $records[] = Source::getInstance($row->xref, $tree, $row->gedcom); } break; case 'r': $rows = Database::prepare("SELECT o_id AS xref, o_gedcom AS gedcom" . " FROM `##other`" . " WHERE o_file = :tree_id AND o_type = 'REPO'" . " ORDER BY o_id" . " LIMIT :limit OFFSET :offset")->execute(array('tree_id' => $ged_id, 'limit' => self::RECORDS_PER_VOLUME, 'offset' => self::RECORDS_PER_VOLUME * $volume))->fetchAll(); foreach ($rows as $row) { $records[] = Repository::getInstance($row->xref, $tree, $row->gedcom); } break; case 'n': $rows = Database::prepare("SELECT o_id AS xref, o_gedcom AS gedcom" . " FROM `##other`" . " WHERE o_file = :tree_id AND o_type = 'NOTE'" . " ORDER BY o_id" . " LIMIT :limit OFFSET :offset")->execute(array('tree_id' => $ged_id, 'limit' => self::RECORDS_PER_VOLUME, 'offset' => self::RECORDS_PER_VOLUME * $volume))->fetchAll(); foreach ($rows as $row) { $records[] = Note::getInstance($row->xref, $tree, $row->gedcom); } break; case 'm': $rows = Database::prepare("SELECT m_id AS xref, m_gedcom AS gedcom" . " FROM `##media`" . " WHERE m_file = :tree_id" . " ORDER BY m_id" . " LIMIT :limit OFFSET :offset")->execute(array('tree_id' => $ged_id, 'limit' => self::RECORDS_PER_VOLUME, 'offset' => self::RECORDS_PER_VOLUME * $volume))->fetchAll(); foreach ($rows as $row) { $records[] = Media::getInstance($row->xref, $tree, $row->gedcom); } break; } foreach ($records as $record) { if ($record->canShowName()) { $data .= '<url>'; $data .= '<loc>' . WT_BASE_URL . $record->getHtmlUrl() . '</loc>'; $chan = $record->getFirstFact('CHAN'); if ($chan) { $date = $chan->getDate(); if ($date->isOK()) { $data .= '<lastmod>' . $date->minimumDate()->Format('%Y-%m-%d') . '</lastmod>'; } } $data .= '</url>' . PHP_EOL; } } $data = '<' . '?xml version="1.0" encoding="UTF-8" ?' . '>' . PHP_EOL . '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">' . PHP_EOL . $data . '</urlset>' . PHP_EOL; // Cache this data - but only for visitors, as we don’t want // visitors to see data created by signed-in users. if (!Auth::check()) { $this->setSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.xml', $data); $this->setSetting('sitemap-' . $ged_id . '-' . $rec_type . '-' . $volume . '.timestamp', WT_TIMESTAMP); } } header('Content-Type: application/xml'); header('Content-Length: ' . strlen($data)); echo $data; }
/** * Create a form to edit a Fact object. * * @param GedcomRecord $record * @param Fact $fact * * @return string */ public static function createEditForm(GedcomRecord $record, Fact $fact) { global $tags, $WT_TREE; $pid = $record->getXref(); $tags = array(); $gedlines = explode("\n", $fact->getGedcom()); $linenum = 0; $fields = explode(' ', $gedlines[$linenum]); $glevel = $fields[0]; $level = $glevel; $type = $fact->getTag(); $parent = $fact->getParent(); $level0type = $parent::RECORD_TYPE; $level1type = $type; $i = $linenum; $inSource = false; $levelSource = 0; $add_date = true; // List of tags we would expect at the next level // NB add_missing_subtags() already takes care of the simple cases // where a level 1 tag is missing a level 2 tag. Here we only need to // handle the more complicated cases. $expected_subtags = array('SOUR' => array('PAGE', 'DATA'), 'DATA' => array('TEXT'), 'PLAC' => array('MAP'), 'MAP' => array('LATI', 'LONG')); if ($record->getTree()->getPreference('FULL_SOURCES')) { $expected_subtags['SOUR'][] = 'QUAY'; $expected_subtags['DATA'][] = 'DATE'; } if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) { $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']); } $stack = array(0 => $level0type); // Loop on existing tags : while (true) { // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE $stack[(int) $level] = $type; // Merge them together, e.g. BIRT:PLAC:FONE $label = implode(':', array_slice($stack, 1, $level)); $text = ''; for ($j = 2; $j < count($fields); $j++) { if ($j > 2) { $text .= ' '; } $text .= $fields[$j]; } $text = rtrim($text); while ($i + 1 < count($gedlines) && preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0) { $text .= "\n" . $cmatch[1]; $i++; } if ($type === 'SOUR') { $inSource = true; $levelSource = $level; } elseif ($levelSource >= $level) { $inSource = false; } if ($type !== 'DATA' && $type !== 'CONT') { $tags[] = $type; $person = Individual::getInstance($pid, $WT_TREE); $subrecord = $level . ' ' . $type . ' ' . $text; if ($inSource && $type === 'DATE') { self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $person)); } elseif (!$inSource && $type === 'DATE') { self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $person)); if ($level === '2') { // We already have a date - no need to add one. $add_date = false; } } elseif ($type === 'STAT') { self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $person)); } elseif ($level0type === 'REPO') { $repo = Repository::getInstance($pid, $WT_TREE); self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $repo)); } else { self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $person)); } } // Get a list of tags present at the next level $subtags = array(); for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^\\s*(\\d+)\\s+(\\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) { if ($mm[1] == $level + 1) { $subtags[] = $mm[2]; } } // Insert missing tags if (!empty($expected_subtags[$type])) { foreach ($expected_subtags[$type] as $subtag) { if (!in_array($subtag, $subtags)) { if (!$inSource || $subtag !== 'DATA') { self::addSimpleTag($level + 1 . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag)); } if (!empty($expected_subtags[$subtag])) { foreach ($expected_subtags[$subtag] as $subsubtag) { self::addSimpleTag($level + 2 . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag)); } } } } } // Awkward special cases if ($level == 2 && $type === 'DATE' && in_array($level1type, Config::dateAndTime()) && !in_array('TIME', $subtags)) { self::addSimpleTag('3 TIME'); // TIME is NOT a valid 5.5.1 tag } if ($level == 2 && $type === 'STAT' && GedcomCodeTemp::isTagLDS($level1type) && !in_array('DATE', $subtags)) { self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE')); } $i++; if (isset($gedlines[$i])) { $fields = explode(' ', $gedlines[$i]); $level = $fields[0]; if (isset($fields[1])) { $type = trim($fields[1]); } else { $level = 0; } } else { $level = 0; } if ($level <= $glevel) { break; } } if ($level1type !== '_PRIM') { self::insertMissingSubtags($level1type, $add_date); } return $level1type; }
/** * Startup activity */ public function __construct() { // Automatically fix broken links if ($this->record && $this->record->canEdit()) { $broken_links = 0; foreach ($this->record->getFacts('HUSB|WIFE|CHIL|FAMS|FAMC|REPO') as $fact) { if (!$fact->isPendingDeletion() && $fact->getTarget() === null) { $this->record->deleteFact($fact->getFactId(), false); FlashMessages::addMessage(I18N::translate('The link from “%1$s” to “%2$s” has been deleted.', $this->record->getFullName(), $fact->getValue())); $broken_links = true; } } foreach ($this->record->getFacts('NOTE|SOUR|OBJE') as $fact) { // These can be links or inline. Only delete links. if (!$fact->isPendingDeletion() && $fact->getTarget() === null && preg_match('/^@.*@$/', $fact->getValue())) { $this->record->deleteFact($fact->getFactId(), false); FlashMessages::addMessage(I18N::translate('The link from “%1$s” to “%2$s” has been deleted.', $this->record->getFullName(), $fact->getValue())); $broken_links = true; } } if ($broken_links) { // Reload the updated family $this->record = GedcomRecord::getInstance($this->record->getXref(), $this->record->getTree()); } } parent::__construct(); // We want robots to index this page $this->setMetaRobots('index,follow'); // Set a page title if ($this->record) { if ($this->record->canShowName()) { // e.g. "John Doe" or "1881 Census of Wales" $this->setPageTitle($this->record->getFullName()); } else { // e.g. "Individual" or "Source" $record = $this->record; $this->setPageTitle(GedcomTag::getLabel($record::RECORD_TYPE)); } } else { // No such record $this->setPageTitle(I18N::translate('Private')); } }
/** * add a new tag input field * * called for each fact to be edited on a form. * Fact level=0 means a new empty form : data are POSTed by name * else data are POSTed using arrays : * glevels[] : tag level * islink[] : tag is a link * tag[] : tag name * text[] : tag value * * @param string $tag fact record to edit (eg 2 DATE xxxxx) * @param string $upperlevel optional upper level tag (eg BIRT) * @param string $label An optional label to echo instead of the default * @param string $extra optional text to display after the input field * @param Individual $person For male/female translations * * @return string */ public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) { global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE; // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields. static $source_element_id; $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN'); preg_match('/^(?:(\\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match); list(, $level, $fact, $value) = $match; $level = (int) $level; // element name : used to POST data if ($level === 0) { if ($upperlevel) { $element_name = $upperlevel . '_' . $fact; } else { $element_name = $fact; } } else { $element_name = 'text[]'; } if ($level === 1) { $main_fact = $fact; } // element id : used by javascript functions if ($level === 0) { $element_id = $fact; } else { $element_id = $fact . Uuid::uuid4(); } if ($upperlevel) { $element_id = $upperlevel . '_' . $fact . Uuid::uuid4(); } // field value $islink = substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#'; if ($islink) { $value = trim(substr($tag, strlen($fact) + 3), ' @\\r'); } else { $value = (string) substr($tag, strlen($fact) + 3); } if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') { $islink = true; } if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') { $islink = true; $fact = 'NOTE'; } // label echo '<tr id="', $element_id, '_tr"'; if ($fact === 'DATA' || $fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') { echo ' style="display:none;"'; } echo '>'; if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') { echo '<td class="optionbox wrap width25">'; } else { echo '<td class="descriptionbox wrap width25">'; } // tag name if ($label) { echo $label; } elseif ($upperlevel) { echo GedcomTag::getLabel($upperlevel . ':' . $fact); } else { echo GedcomTag::getLabel($fact); } // If using GEDFact-assistant window if ($action === 'addnewnote_assisted') { // Do not print on GEDFact Assistant window } else { // Not all facts have help text. switch ($fact) { case 'NAME': if ($upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN') { echo FunctionsPrint::helpLink($fact); } break; case 'DATE': case 'PLAC': case 'RESN': case 'ROMN': case 'SURN': case '_HEB': echo FunctionsPrint::helpLink($fact); break; } } // tag level if ($level > 0) { echo '<input type="hidden" name="glevels[]" value="', $level, '">'; echo '<input type="hidden" name="islink[]" value="', $islink, '">'; echo '<input type="hidden" name="tag[]" value="', $fact, '">'; } echo '</td>'; // value echo '<td class="optionbox wrap">'; // retrieve linked NOTE if ($fact === 'NOTE' && $islink) { $note1 = Note::getInstance($value, $WT_TREE); if ($note1) { $noterec = $note1->getGedcom(); preg_match('/' . $value . '/i', $noterec, $notematch); $value = $notematch[0]; } } // Show names for spouses in MARR/HUSB/AGE and MARR/WIFE/AGE if ($fact === 'HUSB' || $fact === 'WIFE') { $family = Family::getInstance($xref, $WT_TREE); if ($family) { $spouse_link = $family->getFirstFact($fact); if ($spouse_link) { $spouse = $spouse_link->getTarget(); if ($spouse) { echo $spouse->getFullName(); } } } } if (in_array($fact, Config::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) { echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">'; if ($level <= 1) { echo '<input type="checkbox" '; if ($value) { echo 'checked'; } echo ' onclick="document.getElementById(\'' . $element_id . '\').value = (this.checked) ? \'Y\' : \'\';">'; echo I18N::translate('yes'); } if ($fact === 'CENS' && $value === 'Y') { echo self::censusDateSelector(WT_LOCALE, $xref); if (Module::getModuleByName('GEDFact_assistant') && GedcomRecord::getInstance($xref, $WT_TREE) instanceof Individual) { echo '<div></div><a href="#" style="display: none;" id="assistant-link" onclick="return activateCensusAssistant();">' . I18N::translate('Create a shared note using the census assistant') . '</a></div>'; } } } elseif ($fact === 'TEMP') { echo self::selectEditControl($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value); } elseif ($fact === 'ADOP') { echo self::editFieldAdoption($element_name, $value, '', $person); } elseif ($fact === 'PEDI') { echo self::editFieldPedigree($element_name, $value, '', $person); } elseif ($fact === 'STAT') { echo self::selectEditControl($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value); } elseif ($fact === 'RELA') { echo self::editFieldRelationship($element_name, strtolower($value)); } elseif ($fact === 'QUAY') { echo self::selectEditControl($element_name, GedcomCodeQuay::getValues(), '', $value); } elseif ($fact === '_WT_USER') { echo self::editFieldUsername($element_name, $value); } elseif ($fact === 'RESN') { echo self::editFieldRestriction($element_name, $value); } elseif ($fact === '_PRIM') { echo '<select id="', $element_id, '" name="', $element_name, '" >'; echo '<option value=""></option>'; echo '<option value="Y" '; if ($value === 'Y') { echo ' selected'; } echo '>', I18N::translate('always'), '</option>'; echo '<option value="N" '; if ($value === 'N') { echo 'selected'; } echo '>', I18N::translate('never'), '</option>'; echo '</select>'; echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>'; } elseif ($fact === 'SEX') { echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" '; if ($value === 'M') { echo 'selected'; } echo '>', I18N::translate('Male'), '</option><option value="F" '; if ($value === 'F') { echo 'selected'; } echo '>', I18N::translate('Female'), '</option><option value="U" '; if ($value === 'U' || empty($value)) { echo 'selected'; } echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>'; } elseif ($fact === 'TYPE' && $level === 3) { //-- Build the selector for the Media 'TYPE' Fact echo '<select name="text[]"><option selected value="" ></option>'; $selectedValue = strtolower($value); if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) { echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>'; } foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) { echo '<option value="', $typeName, '" '; if ($selectedValue === $typeName) { echo 'selected'; } echo '>', $typeValue, '</option>'; } echo '</select>'; } elseif ($fact === 'NAME' && $upperlevel !== 'REPO' && $upperlevel !== 'UNKNOWN' || $fact === '_MARNM') { // Populated in javascript from sub-tags echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">'; echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>'; echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit the name') . '"></a>'; } else { // textarea if ($fact === 'TEXT' || $fact === 'ADDR' || $fact === 'NOTE' && !$islink) { echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>'; } else { // text // If using GEDFact-assistant window if ($action === 'addnewnote_assisted') { echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"'; } else { echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"'; } echo ' class="', $fact, '"'; if (in_array($fact, $subnamefacts)) { echo ' onblur="updatewholename();" onkeyup="updatewholename();"'; } // Extra markup for specific fact types switch ($fact) { case 'ALIA': case 'ASSO': case '_ASSO': echo ' data-autocomplete-type="ASSO" data-autocomplete-extra="input.DATE"'; break; case 'DATE': echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"'; break; case 'GIVN': echo ' autofocus data-autocomplete-type="GIVN"'; break; case 'LATI': echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"'; break; case 'LONG': echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"'; break; case 'NOTE': // Shared notes. Inline notes are handled elsewhere. echo ' data-autocomplete-type="NOTE"'; break; case 'OBJE': echo ' data-autocomplete-type="OBJE"'; break; case 'PAGE': echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="#' . $source_element_id . '"'; break; case 'PLAC': echo ' data-autocomplete-type="PLAC"'; break; case 'REPO': echo ' data-autocomplete-type="REPO"'; break; case 'SOUR': $source_element_id = $element_id; echo ' data-autocomplete-type="SOUR"'; break; case 'SURN': case '_MARNM_SURN': echo ' data-autocomplete-type="SURN"'; break; case 'TIME': echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?" dir="ltr" placeholder="' . I18N::translate('hh:mm or hh:mm:ss') . '"'; break; } echo '>'; } $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE'); // split PLAC if ($fact === 'PLAC') { echo '<div id="', $element_id, '_pop" style="display: inline;">'; echo FunctionsPrint::printSpecialCharacterLink($element_id), ' ', FunctionsPrint::printFindPlaceLink($element_id); echo '<span onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>'; echo '</div>'; if (Module::getModuleByName('places_assistant')) { \PlacesAssistantModule::setup_place_subfields($element_id); \PlacesAssistantModule::print_place_subfields($element_id); } } elseif (!in_array($fact, $tmp_array)) { echo FunctionsPrint::printSpecialCharacterLink($element_id); } } // MARRiage TYPE : hide text field and show a selection list if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') { echo '<script>'; echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\''; echo '</script>'; echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >'; foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) { if ($key === 'Unknown') { echo '<option value="" '; } else { echo '<option value="', $key, '" '; } $a = strtolower($key); $b = strtolower($value); if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) { echo 'selected'; } echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>'; } echo '</select>'; } elseif ($fact === 'TYPE' && $level === 0) { // NAME TYPE : hide text field and show a selection list $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"'; echo self::editFieldNameType($element_name, $value, $onchange, $person); echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>'; } // popup links switch ($fact) { case 'DATE': echo self::printCalendarPopup($element_id); break; case 'FAMC': case 'FAMS': echo FunctionsPrint::printFindFamilyLink($element_id); break; case 'ALIA': case 'ASSO': case '_ASSO': echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description'); break; case 'FILE': FunctionsPrint::printFindMediaLink($element_id, '0file'); break; case 'SOUR': echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id); //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR' if ($level === 1) { echo '<br>'; switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) { case '2': // records $level1_checked = 'checked'; $level2_checked = ''; break; case '1': // facts $level1_checked = ''; $level2_checked = 'checked'; break; case '0': // none // none default: $level1_checked = ''; $level2_checked = ''; break; } if (strpos($bdm, 'B') !== false) { echo ' <label><input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">', I18N::translate('Individual'), '</label>'; if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { foreach ($matches[1] as $match) { if (!in_array($match, explode('|', WT_EVENTS_DEAT))) { echo ' <label><input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>'; } } } } if (strpos($bdm, 'D') !== false) { if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) { foreach ($matches[1] as $match) { if (in_array($match, explode('|', WT_EVENTS_DEAT))) { echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>'; } } } } if (strpos($bdm, 'M') !== false) { echo ' <label><input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">', I18N::translate('Family'), '</label>'; if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) { foreach ($matches[1] as $match) { echo ' <label><input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">', GedcomTag::getLabel($match), '</label>'; } } } } break; case 'REPO': echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id); break; case 'NOTE': // Shared Notes Icons ======================================== if ($islink) { // Print regular Shared Note icons --------------------------- echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id); if ($value) { echo ' ', self::printEditNoteLink($value); } } break; case 'OBJE': echo FunctionsPrint::printFindMediaLink($element_id, '1media'); if (!$value) { echo ' ', self::printAddNewMediaLink($element_id); $value = 'new'; } break; } echo '<div id="' . $element_id . '_description">'; // current value if ($fact === 'DATE') { $date = new Date($value); echo $date->display(); } if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') { if ($level === 1) { echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>'; } else { echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>'; } } if ($value && $value !== 'new' && $islink) { switch ($fact) { case 'ALIA': case 'ASSO': case '_ASSO': $tmp = Individual::getInstance($value, $WT_TREE); if ($tmp) { echo ' ', $tmp->getFullName(); } break; case 'SOUR': $tmp = Source::getInstance($value, $WT_TREE); if ($tmp) { echo ' ', $tmp->getFullName(); } break; case 'NOTE': $tmp = Note::getInstance($value, $WT_TREE); if ($tmp) { echo ' ', $tmp->getFullName(); } break; case 'OBJE': $tmp = Media::getInstance($value, $WT_TREE); if ($tmp) { echo ' ', $tmp->getFullName(); } break; case 'REPO': $tmp = Repository::getInstance($value, $WT_TREE); if ($tmp) { echo ' ', $tmp->getFullName(); } break; } } // pastable values if ($fact === 'FORM' && $upperlevel === 'OBJE') { FunctionsPrint::printAutoPasteLink($element_id, Config::fileFormats()); } echo '</div>', $extra, '</td></tr>'; return $element_id; }
/** * Search the repositories * * @param string[] $query Search terms * @param Tree[] $trees The trees to search * * @return Repository[] */ public static function searchRepositories(array $query, array $trees) { // Convert the query into a regular expression $queryregex = array(); $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO'"; $args = array(); foreach ($query as $n => $q) { $queryregex[] = preg_quote(I18N::strtoupper($q), '/'); $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')"; $args['collate_' . $n] = I18N::collation(); $args['query_' . $n] = Filter::escapeLike($q); } $sql .= " AND o_file IN ("; foreach ($trees as $n => $tree) { $sql .= $n ? ", " : ""; $sql .= ":tree_id_" . $n; $args['tree_id_' . $n] = $tree->getTreeId(); } $sql .= ")"; $list = array(); $rows = Database::prepare($sql)->execute($args)->fetchAll(); foreach ($rows as $row) { // SQL may have matched on private data or gedcom tags, so check again against privatized data. $record = Repository::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom); // Ignore non-genealogy data $gedrec = preg_replace('/\\n\\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom()); // Ignore links and tags $gedrec = preg_replace('/\\n\\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec); // Ignore tags $gedrec = preg_replace('/\\n\\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec); // Re-apply the filtering $gedrec = I18N::strtoupper($gedrec); foreach ($queryregex as $regex) { if (!preg_match('/' . $regex . '/', $gedrec)) { continue 2; } } $list[] = $record; } $list = array_filter($list, function (Repository $x) { return $x->canShowName(); }); return $list; }
*/ namespace Fisharebest\Webtrees; /** * Defined in session.php * * @global Tree $WT_TREE */ global $WT_TREE; use Fisharebest\Webtrees\Controller\RepositoryController; use Fisharebest\Webtrees\Functions\FunctionsPrint; use Fisharebest\Webtrees\Functions\FunctionsPrintFacts; use Fisharebest\Webtrees\Functions\FunctionsPrintLists; define('WT_SCRIPT_NAME', 'repo.php'); require './includes/session.php'; $record = Repository::getInstance(Filter::get('rid', WT_REGEX_XREF), $WT_TREE); $controller = new RepositoryController($record); if ($controller->record && $controller->record->canShow()) { $controller->pageHeader(); if ($controller->record->isPendingDeletion()) { if (Auth::isModerator($controller->record->getTree())) { echo '<p class="ui-state-highlight">', I18N::translate('This repository has been deleted. You should review the deletion and then %1$s or %2$s it.', '<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>', '<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'), ' ', FunctionsPrint::helpLink('pending_changes'), '</p>'; } elseif (Auth::isEditor($controller->record->getTree())) { echo '<p class="ui-state-highlight">', I18N::translate('This repository has been deleted. The deletion will need to be reviewed by a moderator.'), ' ', FunctionsPrint::helpLink('pending_changes'), '</p>'; } } elseif ($controller->record->isPendingAddtion()) { if (Auth::isModerator($controller->record->getTree())) { echo '<p class="ui-state-highlight">', I18N::translate('This repository has been edited. You should review the changes and then %1$s or %2$s them.', '<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>', '<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'), ' ', FunctionsPrint::helpLink('pending_changes'), '</p>'; } elseif (Auth::isEditor($controller->record->getTree())) { echo '<p class="ui-state-highlight">', I18N::translate('This repository has been edited. The changes need to be reviewed by a moderator.'), ' ', FunctionsPrint::helpLink('pending_changes'), '</p>'; }