diff options
| -rw-r--r-- | admin.php | 1 | ||||
| -rw-r--r-- | app/Report/ReportBase.php | 2560 | ||||
| -rw-r--r-- | app/Report/ReportParserBase.php | 14 | ||||
| -rw-r--r-- | app/Report/ReportParserGenerate.php | 2625 | ||||
| -rw-r--r-- | app/Report/ReportParserSetup.php | 2 | ||||
| -rw-r--r-- | reportengine.php | 247 |
6 files changed, 2635 insertions, 2814 deletions
@@ -356,6 +356,7 @@ $old_files = array( // Removed in 1.7.0 WT_ROOT . 'admin_site_other.php', WT_ROOT . 'includes/hitcount.php', + WT_ROOT . 'includes/reportheader.php', WT_ROOT . 'js', WT_ROOT . 'language/en_GB.mo', // Replaced with en-GB.mo WT_ROOT . 'language/en_US.mo', // Replaced with en-US.mo diff --git a/app/Report/ReportBase.php b/app/Report/ReportBase.php index 71fb78df33..11d3678547 100644 --- a/app/Report/ReportBase.php +++ b/app/Report/ReportBase.php @@ -485,2566 +485,6 @@ class ReportBase { } /** - *XML start element handler - * - * This function is called whenever a starting element is reached - * The element handler will be called if found, otherwise it must be HTML - * - * @param resource $parser the resource handler for the XML parser - * @param string $name the name of the XML element parsed - * @param array $attrs an array of key value pairs for the attributes - */ -function startElement($parser, $name, $attrs) { - global $elementHandler, $processIfs, $processGedcoms, $processRepeats, $vars; - global $processFootnote; - - $newattrs = array(); - - foreach ($attrs as $key => $value) { - if (preg_match("/^\\$(\w+)$/", $value, $match)) { - if ((isset($vars[$match[1]]['id'])) && (!isset($vars[$match[1]]['gedcom']))) { - $value = $vars[$match[1]]['id']; - } - } - $newattrs[$key] = $value; - } - $attrs = $newattrs; - if (($processFootnote) && ($processIfs == 0 || $name == "if") && ($processGedcoms == 0 || $name == "Gedcom") && ($processRepeats == 0 || $name == "Facts" || $name == "RepeatTag")) { - if (isset($elementHandler[$name]['start'])) { - if ($elementHandler[$name]['start'] != "") { - call_user_func($elementHandler[$name]['start'], $attrs); - } - } elseif (!isset($elementHandler[$name]['end'])) { - htmlStartHandler($name, $attrs); - } - } -} - -/** - * XML end element handler - * - * This function is called whenever an ending element is reached - * The element handler will be called if found, otherwise it must be HTML - * - * @param resource $parser the resource handler for the XML parser - * @param string $name the name of the XML element parsed - */ -function endElement($parser, $name) { - global $elementHandler, $processIfs, $processGedcoms, $processRepeats; - global $processFootnote; - - if (($processFootnote || $name == "Footnote") && ($processIfs == 0 || $name == "if") && ($processGedcoms == 0 || $name == "Gedcom") && ($processRepeats == 0 || $name == "Facts" || $name == "RepeatTag" || $name == "List" || $name == "Relatives")) { - if (isset($elementHandler[$name]['end'])) { - if ($elementHandler[$name]['end'] != "") { - call_user_func($elementHandler[$name]['end']); - } - } elseif (!isset($elementHandler[$name]['start'])) { - htmlEndHandler($name); - } - } -} - -/** - * XML character data handler - * - * This function is called whenever raw character data is reached - * just print it to the screen - * - * @param resource $parser the resource handler for the XML parser - * @param string $data the name of the XML element parsed - */ -function characterData($parser, $data) { - global $printData, $currentElement, $processGedcoms, $processIfs, $processRepeats, $reportTitle, $wt_report, $reportDescription; - - if ($printData && ($processGedcoms == 0) && ($processIfs == 0) && ($processRepeats == 0)) { - $currentElement->addText($data); - } elseif ($reportTitle) { - $wt_report->addTitle($data); - } elseif ($reportDescription) { - $wt_report->addDescription($data); - } -} - -/** - * XML <style> start element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function styleStartHandler($attrs) { - global $wt_report; - - if (empty($attrs['name'])) { - throw new \DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.'); - } - - // array Style that will be passed on - $s = array(); - - // string Name af the style - $s['name'] = $attrs['name']; - - // string Name of the DEFAULT font - $s['font'] = $wt_report->defaultFont; - if (!empty($attrs['font'])) { - $s['font'] = $attrs['font']; - } - - // int The size of the font in points - $s['size'] = $wt_report->defaultFontSize; - if (!empty($attrs['size'])) { - $s['size'] = (int) $attrs['size']; - } // Get it as int to ignore all decimal points or text (if any text then int(0)) - - // string B: bold, I: italic, U: underline, D: line trough, The default value is regular. - $s['style'] = ""; - if (!empty($attrs['style'])) { - $s['style'] = $attrs['style']; - } - - $wt_report->addStyle($s); -} - -/** - * XML <Doc> start element handler - * - * Sets up the basics of the document proparties - * - * @param array $attrs an array of key value pairs for the attributes - */ -function docStartHandler($attrs) { - global $parser, $xml_parser, $wt_report; - - $parser = $xml_parser; - - // Custom page width - if (!empty($attrs['customwidth'])) { - $wt_report->pagew = (int) $attrs['customwidth']; - } // Get it as int to ignore all decimal points or text (if any text then int(0)) - // Custom Page height - if (!empty($attrs['customheight'])) { - $wt_report->pageh = (int) $attrs['customheight']; - } // Get it as int to ignore all decimal points or text (if any text then int(0)) - - // Left Margin - if (isset($attrs['leftmargin'])) { - if ($attrs['leftmargin'] === "0") { - $wt_report->leftmargin = 0; - } elseif (!empty($attrs['leftmargin'])) { - $wt_report->leftmargin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) - } - } - // Right Margin - if (isset($attrs['rightmargin'])) { - if ($attrs['rightmargin'] === "0") { - $wt_report->rightmargin = 0; - } elseif (!empty($attrs['rightmargin'])) { - $wt_report->rightmargin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) - } - } - // Top Margin - if (isset($attrs['topmargin'])) { - if ($attrs['topmargin'] === "0") { - $wt_report->topmargin = 0; - } elseif (!empty($attrs['topmargin'])) { - $wt_report->topmargin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) - } - } - // Bottom Margin - if (isset($attrs['bottommargin'])) { - if ($attrs['bottommargin'] === "0") { - $wt_report->bottommargin = 0; - } elseif (!empty($attrs['bottommargin'])) { - $wt_report->bottommargin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) - } - } - // Header Margin - if (isset($attrs['headermargin'])) { - if ($attrs['headermargin'] === "0") { - $wt_report->headermargin = 0; - } elseif (!empty($attrs['headermargin'])) { - $wt_report->headermargin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) - } - } - // Footer Margin - if (isset($attrs['footermargin'])) { - if ($attrs['footermargin'] === "0") { - $wt_report->footermargin = 0; - } elseif (!empty($attrs['footermargin'])) { - $wt_report->footermargin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) - } - } - - // Page Orientation - if (!empty($attrs['orientation'])) { - if ($attrs['orientation'] == "landscape") { - $wt_report->orientation = "landscape"; - } elseif ($attrs['orientation'] == "portrait") { - $wt_report->orientation = "portrait"; - } - } - // Page Size - if (!empty($attrs['pageSize'])) { - $wt_report->pageFormat = strtoupper($attrs['pageSize']); - } - - // Show Generated By... - if (isset($attrs['showGeneratedBy'])) { - if ($attrs['showGeneratedBy'] === "0") { - $wt_report->showGenText = false; - } elseif ($attrs['showGeneratedBy'] === "1") { - $wt_report->showGenText = true; - } - } - - $wt_report->setup(); -} - -/** - * XML </Doc> end element handler - */ -function docEndHandler() { - global $wt_report; - $wt_report->run(); -} - -/** - * XML <Header> start element handler - */ -function headerStartHandler() { - global $wt_report; - - // Clear the Header before any new elements are added - $wt_report->clearHeader(); - $wt_report->setProcessing("H"); -} - -/** - * XML <PageHeader> start element handler - */ -function pageHeaderStartHandler() { - global $printDataStack, $printData, $wt_reportStack, $wt_report, $ReportRoot; - - array_push($printDataStack, $printData); - $printData = false; - array_push($wt_reportStack, $wt_report); - $wt_report = $ReportRoot->createPageHeader(); -} - -/** - * XML <pageHeaderEndHandler> end element handler - */ -function pageHeaderEndHandler() { - global $printData, $printDataStack, $wt_report, $currentElement, $wt_reportStack; - - $printData = array_pop($printDataStack); - $currentElement = $wt_report; - $wt_report = array_pop($wt_reportStack); - $wt_report->addElement($currentElement); -} - -/** - * XML <bodyStartHandler> start element handler - */ -function bodyStartHandler() { - global $wt_report; - $wt_report->setProcessing("B"); -} - -/** - * XML <footerStartHandler> start element handler - */ -function footerStartHandler() { - global $wt_report; - $wt_report->setProcessing("F"); -} - -/** - * XML <Cell> start element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function cellStartHandler($attrs) { - global $printData, $printDataStack, $currentElement, $ReportRoot, $wt_report; - - // string The text alignment of the text in this box. - $align = ""; - if (!empty($attrs['align'])) { - $align = $attrs['align']; - // RTL supported left/right alignment - if ($align == "rightrtl") { - if ($wt_report->rtl) { - $align = "left"; - } else { - $align = "right"; - } - } elseif ($align == "leftrtl") { - if ($wt_report->rtl) { - $align = "right"; - } else { - $align = "left"; - } - } - } - - // string The color to fill the background of this cell - $bgcolor = ""; - if (!empty($attrs['bgcolor'])) { - $bgcolor = $attrs['bgcolor']; - } - - // int Whether or not the background should be painted - $fill = 1; - if (isset($attrs['fill'])) { - if ($attrs['fill'] === "0") { - $fill = 0; - } elseif ($attrs['fill'] === "1") { - $fill = 1; - } - } - - $reseth = true; - // boolean if true reset the last cell height (default true) - if (isset($attrs['reseth'])) { - if ($attrs['reseth'] === "0") { - $reseth = false; - } elseif ($attrs['reseth'] === "1") { - $reseth = true; - } - } - - // mixed Whether or not a border should be printed around this box - $border = 0; - if (!empty($attrs['border'])) { - $border = $attrs['border']; - } - // string Border color in HTML code - $bocolor = ""; - if (!empty($attrs['bocolor'])) { - $bocolor = $attrs['bocolor']; - } - - // int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted. - $height = 0; - if (!empty($attrs['height'])) { - $height = (int) $attrs['height']; - } - // int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin. - $width = 0; - if (!empty($attrs['width'])) { - $width = (int) $attrs['width']; - } - - // int Stretch carachter mode - $stretch = 0; - if (!empty($attrs['stretch'])) { - $stretch = (int) $attrs['stretch']; - } - - // mixed Position the left corner of this box on the page. The default is the current position. - $left = "."; - if (isset($attrs['left'])) { - if ($attrs['left'] === ".") { - $left = "."; - } elseif (!empty($attrs['left'])) { - $left = (int) $attrs['left']; - } elseif ($attrs['left'] === "0") { - $left = 0; - } - } - // mixed Position the top corner of this box on the page. the default is the current position - $top = "."; - if (isset($attrs['top'])) { - if ($attrs['top'] === ".") { - $top = "."; - } elseif (!empty($attrs['top'])) { - $top = (int) $attrs['top']; - } elseif ($attrs['top'] === "0") { - $top = 0; - } - } - - // string The name of the Style that should be used to render the text. - $style = ""; - if (!empty($attrs['style'])) { - $style = $attrs['style']; - } - - // string Text color in html code - $tcolor = ""; - if (!empty($attrs['tcolor'])) { - $tcolor = $attrs['tcolor']; - } - - // int Indicates where the current position should go after the call. - $ln = 0; - if (isset($attrs['newline'])) { - if (!empty($attrs['newline'])) { - $ln = (int) $attrs['newline']; - } elseif ($attrs['newline'] === "0") { - $ln = 0; - } - } - - if ($align == "left") { - $align = "L"; - } elseif ($align == "right") { - $align = "R"; - } elseif ($align == "center") { - $align = "C"; - } elseif ($align == "justify") { - $align = "J"; - } - - array_push($printDataStack, $printData); - $printData = true; - - $currentElement = $ReportRoot->createCell( - $width, - $height, - $border, - $align, - $bgcolor, - $style, - $ln, - $top, - $left, - $fill, - $stretch, - $bocolor, - $tcolor, - $reseth - ); -} - -/** - * XML </Cell> end element handler - */ -function cellEndHandler() { - global $printData, $printDataStack, $currentElement, $wt_report; - - $printData = array_pop($printDataStack); - $wt_report->addElement($currentElement); -} - -/** - * XML <Now /> element handler - */ -function nowStartHandler() { - global $currentElement; - - $g = timestamp_to_gedcom_date(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET); - $currentElement->addText($g->display()); -} - -/** - * XML <PageNum /> element handler - */ -function pageNumStartHandler() { - global $currentElement; - $currentElement->addText("#PAGENUM#"); -} - -/** - * XML <TotalPages /> element handler - */ -function totalPagesStartHandler() { - global $currentElement; - $currentElement->addText("{{:ptp:}}"); -} - -/** - * Called at the start of an element. - * - * @param array $attrs an array of key value pairs for the attributes - */ -function gedcomStartHandler($attrs) { - global $vars, $gedrec, $gedrecStack, $processGedcoms, $fact, $desc, $ged_level, $WT_TREE; - - if ($processGedcoms > 0) { - $processGedcoms++; - - return; - } - - $tag = $attrs['id']; - $tag = str_replace("@fact", $fact, $tag); - $tags = explode(":", $tag); - $newgedrec = ''; - if (count($tags) < 2) { - $tmp = GedcomRecord::getInstance($attrs['id'], $WT_TREE); - $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; - } - if (empty($newgedrec)) { - $tgedrec = $gedrec; - $newgedrec = ''; - foreach ($tags as $tag) { - if (preg_match("/\\$(.+)/", $tag, $match)) { - if (isset($vars[$match[1]]['gedcom'])) { - $newgedrec = $vars[$match[1]]['gedcom']; - } else { - $tmp = GedcomRecord::getInstance($match[1], $WT_TREE); - $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; - } - } else { - if (preg_match("/@(.+)/", $tag, $match)) { - $gmatch = array(); - if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) { - $tmp = GedcomRecord::getInstance($gmatch[1], $WT_TREE); - $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; - $tgedrec = $newgedrec; - } else { - $newgedrec = ''; - break; - } - } else { - $temp = explode(" ", trim($tgedrec)); - $level = $temp[0] + 1; - $newgedrec = get_sub_record($level, "$level $tag", $tgedrec); - $tgedrec = $newgedrec; - } - } - } - } - if (!empty($newgedrec)) { - array_push($gedrecStack, array($gedrec, $fact, $desc)); - $gedrec = $newgedrec; - if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $gedrec, $match)) { - $ged_level = $match[1]; - $fact = $match[2]; - $desc = trim($match[3]); - } - } else { - $processGedcoms++; - } -} - -/** - * Called at the end of an element. - */ -function gedcomEndHandler() { - global $gedrec, $gedrecStack, $processGedcoms, $fact, $desc; - - if ($processGedcoms > 0) { - $processGedcoms--; - } else { - $temp = array_pop($gedrecStack); - $gedrec = $temp[0]; - $fact = $temp[1]; - $desc = $temp[2]; - } -} - -/** - * XML <textBoxStartHandler> start element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function textBoxStartHandler($attrs) { - global $printData, $printDataStack, $wt_report, $wt_reportStack, $ReportRoot; - - // string Background color code - $bgcolor = ""; - if (!empty($attrs['bgcolor'])) { - $bgcolor = $attrs['bgcolor']; - } - - // boolean Wether or not fill the background color - $fill = true; - if (isset($attrs['fill'])) { - if ($attrs['fill'] === "0") { - $fill = false; - } elseif ($attrs['fill'] === "1") { - $fill = true; - } - } - - // var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0 - $border = false; - if (isset($attrs['border'])) { - if ($attrs['border'] === "1") { - $border = true; - } elseif ($attrs['border'] === "0") { - $border = false; - } - } - - // int The starting height of this cell. If the text wraps the height will automatically be adjusted - $height = 0; - if (!empty($attrs['height'])) { - $height = (int) $attrs['height']; - } - // int Setting the width to 0 will make it the width from the current location to the margin - $width = 0; - if (!empty($attrs['width'])) { - $width = (int) $attrs['width']; - } - - // mixed Position the left corner of this box on the page. The default is the current position. - $left = "."; - if (isset($attrs['left'])) { - if ($attrs['left'] === ".") { - $left = "."; - } elseif (!empty($attrs['left'])) { - $left = (int) $attrs['left']; - } elseif ($attrs['left'] === "0") { - $left = 0; - } - } - // mixed Position the top corner of this box on the page. the default is the current position - $top = "."; - if (isset($attrs['top'])) { - if ($attrs['top'] === ".") { - $top = "."; - } elseif (!empty($attrs['top'])) { - $top = (int) $attrs['top']; - } elseif ($attrs['top'] === "0") { - $top = 0; - } - } - // boolean After this box is finished rendering, should the next section of text start immediately after the this box or should it start on a new line under this box. 0 = no new line, 1 = force new line. Default is 0 - $newline = false; - if (isset($attrs['newline'])) { - if ($attrs['newline'] === "1") { - $newline = true; - } elseif ($attrs['newline'] === "0") { - $newline = false; - } - } - // boolean - $pagecheck = true; - if (isset($attrs['pagecheck'])) { - if ($attrs['pagecheck'] === "0") { - $pagecheck = false; - } elseif ($attrs['pagecheck'] === "1") { - $pagecheck = true; - } - } - // boolean Cell padding - $padding = true; - if (isset($attrs['padding'])) { - if ($attrs['padding'] === "0") { - $padding = false; - } elseif ($attrs['padding'] === "1") { - $padding = true; - } - } - // boolean Reset this box Height - $reseth = false; - if (isset($attrs['reseth'])) { - if ($attrs['reseth'] === "1") { - $reseth = true; - } elseif ($attrs['reseth'] === "0") { - $reseth = false; - } - } - - // string Style of rendering - $style = ""; - - array_push($printDataStack, $printData); - $printData = false; - - array_push($wt_reportStack, $wt_report); - $wt_report = $ReportRoot->createTextBox( - $width, - $height, - $border, - $bgcolor, - $newline, - $left, - $top, - $pagecheck, - $style, - $fill, - $padding, - $reseth - ); -} - -/** - * XML <textBoxEndHandler> end element handler - */ -function textBoxEndHandler() { - global $printData, $printDataStack, $wt_report, $currentElement, $wt_reportStack; - - $printData = array_pop($printDataStack); - $currentElement = $wt_report; - $wt_report = array_pop($wt_reportStack); - $wt_report->addElement($currentElement); -} - -/** - * @param array $attrs an array of key value pairs for the attributes - */ -function textStartHandler($attrs) { - global $printData, $printDataStack, $currentElement, $ReportRoot; - - array_push($printDataStack, $printData); - $printData = true; - - // string The name of the Style that should be used to render the text. - $style = ""; - if (!empty($attrs['style'])) { - $style = $attrs['style']; - } - - // string The color of the text - Keep the black color as default - $color = ""; - if (!empty($attrs['color'])) { - $color = $attrs['color']; - } - - $currentElement = $ReportRoot->createText($style, $color); -} - -/** - * - */ -function textEndHandler() { - global $printData, $printDataStack, $wt_report, $currentElement; - - $printData = array_pop($printDataStack); - $wt_report->addElement($currentElement); -} - -/** - * XML <GetPersonName> start element handler - * Get the name - * 1. id is empty - current GEDCOM record - * 2. id is set with a record id - * - * @param array $attrs an array of key value pairs for the attributes - */ -function getPersonNameStartHandler($attrs) { - // @deprecated - global $currentElement, $vars, $gedrec, $WT_TREE; - - $id = ""; - $match = array(); - if (empty($attrs['id'])) { - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $id = $match[1]; - } - } else { - if (preg_match("/\\$(.+)/", $attrs['id'], $match)) { - if (isset($vars[$match[1]]['id'])) { - $id = $vars[$match[1]]['id']; - } - } else { - if (preg_match("/@(.+)/", $attrs['id'], $match)) { - $gmatch = array(); - if (preg_match("/\d $match[1] @([^@]+)@/", $gedrec, $gmatch)) { - $id = $gmatch[1]; - } - } else { - $id = $attrs['id']; - } - } - } - if (!empty($id)) { - $record = GedcomRecord::getInstance($id, $WT_TREE); - if (is_null($record)) { - return; - } - if (!$record->canShowName()) { - $currentElement->addText(I18N::translate('Private')); - } else { - $name = $record->getFullName(); - $name = preg_replace( - array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'), - array('«', '', '»'), - $name - ); - $name = strip_tags($name); - if (!empty($attrs['truncate'])) { - if (mb_strlen($name) > $attrs['truncate']) { - $name = preg_replace("/\(.*\) ?/", '', $name); //removes () and text inbetween - what about ", [ and { etc? - $words = preg_split('/[, -]+/', $name); // names separated with space, comma or hyphen - any others? - $name = $words[count($words) - 1]; - for ($i = count($words) - 2; $i >= 0; $i--) { - $len = mb_strlen($name); - for ($j = count($words) - 3; $j >= 0; $j--) { - $len += mb_strlen($words[$j]); - } - if ($len > $attrs['truncate']) { - $first_letter = mb_substr($words[$i], 0, 1); - // Do not show " of nick-names - if ($first_letter != "\"") { - $name = mb_substr($words[$i], 0, 1) . '. ' . $name; - } - } else { - $name = $words[$i] . ' ' . $name; - } - } - } - } else { - $addname = $record->getAddName(); - $addname = preg_replace( - array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'), - array('«', '', '»'), - $addname - ); - $addname = strip_tags($addname); - if (!empty($addname)) { - $name .= " " . $addname; - } - } - $currentElement->addText(trim($name)); - } - } -} - -/** - * XML <GedcomValue> start element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function gedcomValueStartHandler($attrs) { - // @deprecated - global $currentElement, $gedrec, $fact, $desc, $WT_TREE; - - $id = ""; - $match = array(); - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $id = $match[1]; - } - - if (isset($attrs['newline']) && $attrs['newline'] == "1") { - $useBreak = "1"; - } else { - $useBreak = "0"; - } - - $tag = $attrs['tag']; - if (!empty($tag)) { - if ($tag == "@desc") { - $value = $desc; - $value = trim($value); - $currentElement->addText($value); - } - if ($tag == "@id") { - $currentElement->addText($id); - } else { - $tag = str_replace("@fact", $fact, $tag); - if (empty($attrs['level'])) { - $temp = explode(" ", trim($gedrec)); - $level = $temp[0]; - if ($level == 0) { - $level++; - } - } else { - $level = $attrs['level']; - } - $tags = preg_split('/[: ]/', $tag); - $value = get_gedcom_value($tag, $level, $gedrec); - switch (end($tags)) { - case 'DATE': - $tmp = new Date($value); - $value = $tmp->display(); - break; - case 'PLAC': - $tmp = new Place($value, $WT_TREE); - $value = $tmp->getShortName(); - break; - } - if ($useBreak == "1") { - // Insert <br> when multiple dates exist. - // This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages - $value = str_replace('(', '<br>(', $value); - $value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value); - $value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value); - if (substr($value, 0, 6) == '<br>') { - $value = substr($value, 6); - } - } - $currentElement->addText($value); - } - } -} - -/** - * XML <RepeatTag> start element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function repeatTagStartHandler($attrs) { - // @deprecated - global $repeats, $repeatsStack, $gedrec, $repeatBytes, $parser, $processRepeats, $fact, $desc, $currentElement; - - $processRepeats++; - if ($processRepeats > 1) { - return; - } - - array_push($repeatsStack, array($repeats, $repeatBytes)); - $repeats = array(); - $repeatBytes = xml_get_current_line_number($parser); - - $tag = ""; - if (isset($attrs['tag'])) { - $tag = $attrs['tag']; - } - if (!empty($tag)) { - if ($tag == "@desc") { - $value = $desc; - $value = trim($value); - $currentElement->addText($value); - } else { - $tag = str_replace("@fact", $fact, $tag); - $tags = explode(":", $tag); - $temp = explode(" ", trim($gedrec)); - $level = $temp[0]; - if ($level == 0) { - $level++; - } - $subrec = $gedrec; - $t = $tag; - $count = count($tags); - $i = 0; - while ($i < $count) { - $t = $tags[$i]; - if (!empty($t)) { - if ($i < ($count - 1)) { - $subrec = get_sub_record($level, "$level $t", $subrec); - if (empty($subrec)) { - $level--; - $subrec = get_sub_record($level, "@ $t", $gedrec); - if (empty($subrec)) { - return; - } - } - } - $level++; - } - $i++; - } - $level--; - $count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER); - $i = 0; - while ($i < $count) { - $repeats[] = get_sub_record($level, "$level $t", $subrec, $i + 1); - $i++; - } - } - } -} - -/** - * XML </ RepeatTag> end element handler - */ -function repeatTagEndHandler() { - global $processRepeats, $repeats, $repeatsStack, $repeatBytes; - - $processRepeats--; - if ($processRepeats > 0) { - return; - } - - // Check if there is anything to repeat - if (count($repeats) > 0) { - // No need to load them if not used... - global $parser, $parserStack, $report, $gedrec; - - $lineoffset = 0; - foreach ($repeatsStack as $rep) { - $lineoffset += $rep[1]; - } - //-- read the xml from the file - $lines = file($report); - while (strpos($lines[$lineoffset + $repeatBytes], "<RepeatTag") === false) { - $lineoffset--; - } - $lineoffset++; - $reportxml = "<tempdoc>\n"; - $line_nr = $lineoffset + $repeatBytes; - // RepeatTag Level counter - $count = 1; - while (0 < $count) { - if (strstr($lines[$line_nr], "<RepeatTag") !== false) { - $count++; - } elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) { - $count--; - } - if (0 < $count) { - $reportxml .= $lines[$line_nr]; - } - $line_nr++; - } - // No need to drag this - unset($lines); - $reportxml .= "</tempdoc>\n"; - // Save original values - array_push($parserStack, $parser); - $oldgedrec = $gedrec; - foreach ($repeats as $gedrec) { - //-- start the sax parser - $repeat_parser = xml_parser_create(); - $parser = $repeat_parser; - //-- make sure everything is case sensitive - xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); - //-- set the main element handler functions - xml_set_element_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\startElement', '\Fisharebest\Webtrees\Report\\endElement'); - //-- set the character data handler - xml_set_character_data_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\characterData'); - if (!xml_parse($repeat_parser, $reportxml, true)) { - throw new \DomainException(sprintf( - 'RepeatTagEHandler XML error: %s at line %d', - xml_error_string(xml_get_error_code($repeat_parser)), - xml_get_current_line_number($repeat_parser) - )); - } - xml_parser_free($repeat_parser); - } - // Restore original values - $gedrec = $oldgedrec; - $parser = array_pop($parserStack); - } - $temp = array_pop($repeatsStack); - $repeats = $temp[0]; - $repeatBytes = $temp[1]; -} - -/** - * Variable lookup - * - * Retrieve predefined variables : - * - * @ desc GEDCOM fact description, example: - * 1 EVEN This is a description - * @ fact GEDCOM fact tag, such as BIRT, DEAT etc. - * $ I18N::translate('....') - * $ language_settings[] - * - * @param array $attrs an array of key value pairs for the attributes - */ -function varStartHandler($attrs) { - // @deprecated - global $currentElement, $type, $parser; - // Retrievable variables - global $desc, $fact, $vars; - - if (empty($attrs['var'])) { - throw new \DomainException('REPORT ERROR var: The attribute "var=" is missing or not set in the XML file on line: ' . xml_get_current_line_number($parser)); - } - - $var = $attrs['var']; - // SetVar element preset variables - if (!empty($vars[$var]['id'])) { - $var = $vars[$var]['id']; - } else { - $tfact = $fact; - if (($fact == "EVEN" || $fact == "FACT") && is_string($type) && $type != " ") { - // Use : - // n TYPE This text if string - $tfact = $type; - } - $var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $desc), $var); - if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) { - $var = I18N::number($match[1]); - } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) { - $var = I18N::translate($match[1]); - } elseif (preg_match('/^I18N::translate_c\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) { - $var = I18N::translateContext($match[1], $match[2]); - } - } - // Check if variable is set as a date and reformat the date - if (isset($attrs['date'])) { - if ($attrs['date'] === "1") { - $g = new Date($var); - $var = $g->display(); - } - } - $currentElement->addText($var); -} - -/** - * @param array $attrs an array of key value pairs for the attributes - */ -function factsStartHandler($attrs) { - // @deprecated - global $repeats, $repeatsStack, $gedrec, $parser, $repeatBytes, $processRepeats, $vars, $WT_TREE; - - $processRepeats++; - if ($processRepeats > 1) { - return; - } - - array_push($repeatsStack, array($repeats, $repeatBytes)); - $repeats = array(); - $repeatBytes = xml_get_current_line_number($parser); - - $id = ""; - $match = array(); - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $id = $match[1]; - } - $tag = ""; - if (isset($attrs['ignore'])) { - $tag .= $attrs['ignore']; - } - if (preg_match("/\\$(.+)/", $tag, $match)) { - $tag = $vars[$match[1]]['id']; - } - - $record = GedcomRecord::getInstance($id, $WT_TREE); - if (empty($attrs['diff']) && !empty($id)) { - $facts = $record->getFacts(); - sort_facts($facts); - $repeats = array(); - $nonfacts = explode(',', $tag); - foreach ($facts as $event) { - if (!in_array($event->getTag(), $nonfacts)) { - $repeats[] = $event->getGedcom(); - } - } - } else { - foreach ($record->getFacts() as $fact) { - if ($fact->isPendingAddition() && $fact->getTag() != 'CHAN') { - $repeats[] = $fact->getGedcom(); - } - } - } -} - -/** - * XML </ Facts> end element handler - */ -function factsEndHandler() { - global $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $gedrec, $fact, $desc, $type, $processRepeats; - - $processRepeats--; - if ($processRepeats > 0) { - return; - } - - // Check if there is anything to repeat - if (count($repeats) > 0) { - - $line = xml_get_current_line_number($parser) - 1; - $lineoffset = 0; - foreach ($repeatsStack as $rep) { - $lineoffset += $rep[1]; - } - - //-- read the xml from the file - $lines = file($report); - while ($lineoffset + $repeatBytes > 0 && strpos($lines[$lineoffset + $repeatBytes], '<Facts ') === false) { - $lineoffset--; - } - $lineoffset++; - $reportxml = "<tempdoc>\n"; - $i = $line + $lineoffset; - $line_nr = $repeatBytes + $lineoffset; - while ($line_nr < $i) { - $reportxml .= $lines[$line_nr]; - $line_nr++; - } - // No need to drag this - unset($lines); - $reportxml .= "</tempdoc>\n"; - // Save original values - array_push($parserStack, $parser); - $oldgedrec = $gedrec; - $count = count($repeats); - $i = 0; - while ($i < $count) { - $gedrec = $repeats[$i]; - $fact = ''; - $desc = ''; - if (preg_match('/1 (\w+)(.*)/', $gedrec, $match)) { - $fact = $match[1]; - if ($fact === 'EVEN' || $fact === 'FACT') { - $tmatch = array(); - if (preg_match('/2 TYPE (.+)/', $gedrec, $tmatch)) { - $type = trim($tmatch[1]); - } else { - $type = ' '; - } - } - $desc = trim($match[2]); - $desc .= get_cont(2, $gedrec); - } - //-- start the sax parser - $repeat_parser = xml_parser_create(); - $parser = $repeat_parser; - //-- make sure everything is case sensitive - xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); - //-- set the main element handler functions - xml_set_element_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\startElement', '\Fisharebest\Webtrees\Report\\endElement'); - //-- set the character data handler - xml_set_character_data_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\characterData'); - if (!xml_parse($repeat_parser, $reportxml, true)) { - throw new \DomainException(sprintf( - 'FactsEHandler XML error: %s at line %d', - xml_error_string(xml_get_error_code($repeat_parser)), - xml_get_current_line_number($repeat_parser) - )); - } - xml_parser_free($repeat_parser); - $i++; - } - // Restore original values - $parser = array_pop($parserStack); - $gedrec = $oldgedrec; - } - $temp = array_pop($repeatsStack); - $repeats = $temp[0]; - $repeatBytes = $temp[1]; -} - -/** - * Setting upp or changing variables in the XML - * The XML variable name and value is stored in the global variable $vars - * - * @param array $attrs an array of key value pairs for the attributes - */ -function setVarStartHandler($attrs) { - global $vars, $gedrec, $fact, $desc, $generation; - - if (empty($attrs['name'])) { - throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file'); - } - - $name = $attrs['name']; - $value = $attrs['value']; - $match = array(); - // Current GEDCOM record strings - if ($value == "@ID") { - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $value = $match[1]; - } - } elseif ($value == "@fact") { - $value = $fact; - } elseif ($value == "@desc") { - $value = $desc; - } elseif ($value == "@generation") { - $value = $generation; - } elseif (preg_match("/@(\w+)/", $value, $match)) { - $gmatch = array(); - if (preg_match("/\d $match[1] (.+)/", $gedrec, $gmatch)) { - $value = str_replace("@", "", trim($gmatch[1])); - } - } - if (preg_match("/\\$(\w+)/", $name, $match)) { - $name = $vars["'" . $match[1] . "'"]['id']; - } - $count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER); - $i = 0; - while ($i < $count) { - $t = $vars[$match[$i][1]]['id']; - $value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1); - $i++; - } - if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) { - $value = I18N::number($match[1]); - } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) { - $value = I18N::translate($match[1]); - } elseif (preg_match('/^I18N::translate_c\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) { - $value = I18N::translateContext($match[1], $match[2]); - } - // Arithmetic functions - if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) { - switch ($match[2]) { - case "+": - $t = $match[1] + $match[3]; - $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); - break; - case "-": - $t = $match[1] - $match[3]; - $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); - break; - case "*": - $t = $match[1] * $match[3]; - $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); - break; - case "/": - $t = $match[1] / $match[3]; - $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); - break; - } - } - if (strpos($value, "@") !== false) { - $value = ""; - } - $vars[$name]['id'] = $value; -} - -/** - * XML <if > start element - * - * @param array $attrs an array of key value pairs for the attributes - */ -function ifStartHandler($attrs) { - global $vars, $gedrec, $processIfs, $fact, $desc, $generation; - - if ($processIfs > 0) { - $processIfs++; - - return; - } - - $condition = $attrs['condition']; - $condition = preg_replace("/\\$(\w+)/", "\$vars[\"$1\"][\"id\"]", $condition); - $condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition); - // Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT - $condition = str_replace("@fact", $fact, $condition); - $match = array(); - $count = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER); - $i = 0; - while ($i < $count) { - $id = $match[$i][1]; - $value = "\"\""; - if ($id == "ID") { - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $value = "'" . $match[1] . "'"; - } - } elseif ($id == "fact") { - $value = "\"$fact\""; - } elseif ($id == "desc") { - $value = "\"" . addslashes($desc) . "\""; - } elseif ($id == "generation") { - $value = "\"$generation\""; - } else { - - $temp = explode(" ", trim($gedrec)); - $level = $temp[0]; - if ($level == 0) { - $level++; - } - $value = get_gedcom_value($id, $level, $gedrec); - if (empty($value)) { - $level++; - $value = get_gedcom_value($id, $level, $gedrec); - } - $value = preg_replace("/^@(" . WT_REGEX_XREF . ")@$/", "$1", $value); - $value = "\"" . addslashes($value) . "\""; - } - $condition = str_replace("@$id", $value, $condition); - $i++; - } - $condition = "return (bool) ($condition);"; - $ret = @eval($condition); - if (!$ret) { - $processIfs++; - } -} - -/** - * XML <if /> end element - */ -function ifEndHandler() { - global $processIfs; - if ($processIfs > 0) { - $processIfs--; - } -} - -/** - * XML <Footnote > start element - * Collect the Footnote links - * GEDCOM Records that are protected by Privacy setting will be ignore - * - * @param array $attrs an array of key value pairs for the attributes - */ -function footnoteStartHandler($attrs) { - global $printData, $printDataStack, $currentElement, $footnoteElement, $processFootnote, $gedrec, $ReportRoot, $WT_TREE; - - $id = ""; - if (preg_match("/[0-9] (.+) @(.+)@/", $gedrec, $match)) { - $id = $match[2]; - } - $record = GedcomRecord::GetInstance($id, $WT_TREE); - if ($record && $record->canShow()) { - array_push($printDataStack, $printData); - $printData = true; - $style = ""; - if (!empty($attrs['style'])) { - $style = $attrs['style']; - } - $footnoteElement = $currentElement; - $currentElement = $ReportRoot->createFootnote($style); - } else { - $printData = false; - $processFootnote = false; - } -} - -/** - * XML <Footnote /> end element - * Print the collected Footnote data - */ -function footnoteEndHandler() { - // @deprecated - global $printData, $printDataStack, $currentElement, $footnoteElement, $processFootnote, $wt_report; - - if ($processFootnote) { - $printData = array_pop($printDataStack); - $temp = trim($currentElement->getValue()); - if (strlen($temp) > 3) { - $wt_report->addElement($currentElement); - } - $currentElement = $footnoteElement; - } else { - $processFootnote = true; - } -} - -/** - * XML <FootnoteTexts /> element - */ -function footnoteTextsStartHandler() { - global $wt_report; - - $temp = "footnotetexts"; - $wt_report->addElement($temp); -} - -/** - * XML <AgeAtDeath /> element handler - */ -function ageAtDeathStartHandler() { - // This duplicates functionality in format_fact_date() - global $currentElement, $gedrec, $factrec, $WT_TREE; - - $match = array(); - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $person = Individual::getInstance($match[1], $WT_TREE); - // Recorded age - if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { - $fact_age = $match[1]; - } else { - $fact_age = ''; - } - if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { - $husb_age = $match[1]; - } else { - $husb_age = ''; - } - if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { - $wife_age = $match[1]; - } else { - $wife_age = ''; - } - - // Calculated age - $birth_date = $person->getBirthDate(); - // Can't use getDeathDate(), as this also gives BURI/CREM events, which - // wouldn't give the correct "days after death" result for people with - // no DEAT. - $death_event = $person->getFirstFact('DEAT'); - if ($death_event) { - $death_date = $death_event->getDate(); - } else { - $death_date = new Date(''); - } - $value = ''; - if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) { - $age = Date::getAgeGedcom($birth_date, $death_date); - // Only show calculated age if it differs from recorded age - if ($age != '' && $age != "0d") { - if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $person->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $person->getSex() == 'F' && $wife_age != $age - ) { - $value = get_age_at_event($age, false); - $abbrev = substr($value, 0, strpos($value, ' ') + 5); - if ($value !== $abbrev) { - $value = $abbrev . '.'; - } - } - } - } - $currentElement->addText($value); - } -} - -/** - * XML element Forced line break handler - HTML code - */ -function brStartHandler() { - global $printData, $currentElement, $processGedcoms; - if ($printData && ($processGedcoms == 0)) { - $currentElement->addText('<br>'); - } -} - -/** - * XML <sp />element Forced space handler - */ -function spStartHandler() { - global $printData, $currentElement, $processGedcoms; - if ($printData && ($processGedcoms == 0)) { - $currentElement->addText(' '); - } -} - -/** - * @param array $attrs an array of key value pairs for the attributes - */ -function highlightedImageStartHandler($attrs) { - global $gedrec, $wt_report, $ReportRoot, $WT_TREE; - - $id = ''; - $match = array(); - if (preg_match("/0 @(.+)@/", $gedrec, $match)) { - $id = $match[1]; - } - - // mixed Position the top corner of this box on the page. the default is the current position - $top = '.'; - if (isset($attrs['top'])) { - if ($attrs['top'] === '0') { - $top = 0; - } elseif ($attrs['top'] === '.') { - $top = '.'; - } elseif (!empty($attrs['top'])) { - $top = (int) $attrs['top']; - } - } - - // mixed Position the left corner of this box on the page. the default is the current position - $left = '.'; - if (isset($attrs['left'])) { - if ($attrs['left'] === '0') { - $left = 0; - } elseif ($attrs['left'] === '.') { - $left = '.'; - } elseif (!empty($attrs['left'])) { - $left = (int) $attrs['left']; - } - } - - // string Align the image in left, center, right - $align = ''; - if (!empty($attrs['align'])) { - $align = $attrs['align']; - } - - // string Next Line should be T:next to the image, N:next line - $ln = ''; - if (!empty($attrs['ln'])) { - $ln = $attrs['ln']; - } - - $width = 0; - $height = 0; - if (!empty($attrs['width'])) { - $width = (int) $attrs['width']; - } - if (!empty($attrs['height'])) { - $height = (int) $attrs['height']; - } - - $person = Individual::getInstance($id, $WT_TREE); - $mediaobject = $person->findHighlightedMedia(); - if ($mediaobject) { - $attributes = $mediaobject->getImageAttributes('thumb'); - if (in_array( - $attributes['ext'], - array( - 'GIF', - 'JPG', - 'PNG', - 'SWF', - 'PSD', - 'BMP', - 'TIFF', - 'TIFF', - 'JPC', - 'JP2', - 'JPX', - 'JB2', - 'SWC', - 'IFF', - 'WBMP', - 'XBM', - ) - ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') - ) { - if ($width > 0 && $height == 0) { - $perc = $width / $attributes['adjW']; - $height = round($attributes['adjH'] * $perc); - } elseif ($height > 0 && $width == 0) { - $perc = $height / $attributes['adjH']; - $width = round($attributes['adjW'] * $perc); - } else { - $width = $attributes['adjW']; - $height = $attributes['adjH']; - } - $image = $ReportRoot->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); - $wt_report->addElement($image); - } - } -} - -/** - * @param array $attrs an array of key value pairs for the attributes - */ -function imageStartHandler($attrs) { - global $gedrec, $wt_report, $ReportRoot, $WT_TREE; - - // mixed Position the top corner of this box on the page. the default is the current position - $top = '.'; - if (isset($attrs['top'])) { - if ($attrs['top'] === "0") { - $top = 0; - } elseif ($attrs['top'] === '.') { - $top = '.'; - } elseif (!empty($attrs['top'])) { - $top = (int) $attrs['top']; - } - } - - // mixed Position the left corner of this box on the page. the default is the current position - $left = '.'; - if (isset($attrs['left'])) { - if ($attrs['left'] === '0') { - $left = 0; - } elseif ($attrs['left'] === '.') { - $left = '.'; - } elseif (!empty($attrs['left'])) { - $left = (int) $attrs['left']; - } - } - - // string Align the image in left, center, right - $align = ''; - if (!empty($attrs['align'])) { - $align = $attrs['align']; - } - - // string Next Line should be T:next to the image, N:next line - $ln = 'T'; - if (!empty($attrs['ln'])) { - $ln = $attrs['ln']; - } - - $width = 0; - $height = 0; - if (!empty($attrs['width'])) { - $width = (int) $attrs['width']; - } - if (!empty($attrs['height'])) { - $height = (int) $attrs['height']; - } - - $file = ''; - if (!empty($attrs['file'])) { - $file = $attrs['file']; - } - if ($file == "@FILE") { - $match = array(); - if (preg_match("/\d OBJE @(.+)@/", $gedrec, $match)) { - $mediaobject = Media::getInstance($match[1], $WT_TREE); - $attributes = $mediaobject->getImageAttributes('thumb'); - if (in_array( - $attributes['ext'], - array( - 'GIF', - 'JPG', - 'PNG', - 'SWF', - 'PSD', - 'BMP', - 'TIFF', - 'TIFF', - 'JPC', - 'JP2', - 'JPX', - 'JB2', - 'SWC', - 'IFF', - 'WBMP', - 'XBM', - ) - ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') - ) { - if ($width > 0 && $height == 0) { - $perc = $width / $attributes['adjW']; - $height = round($attributes['adjH'] * $perc); - } elseif ($height > 0 && $width == 0) { - $perc = $height / $attributes['adjH']; - $width = round($attributes['adjW'] * $perc); - } else { - $width = $attributes['adjW']; - $height = $attributes['adjH']; - } - $image = $ReportRoot->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); - $wt_report->addElement($image); - } - } - } else { - if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) { - $size = getimagesize($file); - if ($width > 0 && $height == 0) { - $perc = $width / $size[0]; - $height = round($size[1] * $perc); - } elseif ($height > 0 && $width == 0) { - $perc = $height / $size[1]; - $width = round($size[0] * $perc); - } else { - $width = $size[0]; - $height = $size[1]; - } - $image = $ReportRoot->createImage($file, $left, $top, $width, $height, $align, $ln); - $wt_report->addElement($image); - } - } -} - -/** - * XML <Line> element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function lineStartHandler($attrs) { - global $wt_report, $ReportRoot; - - // Start horizontal position, current position (default) - $x1 = "."; - if (isset($attrs['x1'])) { - if ($attrs['x1'] === "0") { - $x1 = 0; - } elseif ($attrs['x1'] === ".") { - $x1 = "."; - } elseif (!empty($attrs['x1'])) { - $x1 = (int) $attrs['x1']; - } - } - // Start vertical position, current position (default) - $y1 = "."; - if (isset($attrs['y1'])) { - if ($attrs['y1'] === "0") { - $y1 = 0; - } elseif ($attrs['y1'] === ".") { - $y1 = "."; - } elseif (!empty($attrs['y1'])) { - $y1 = (int) $attrs['y1']; - } - } - // End horizontal position, maximum width (default) - $x2 = "."; - if (isset($attrs['x2'])) { - if ($attrs['x2'] === "0") { - $x2 = 0; - } elseif ($attrs['x2'] === ".") { - $x2 = "."; - } elseif (!empty($attrs['x2'])) { - $x2 = (int) $attrs['x2']; - } - } - // End vertical position - $y2 = "."; - if (isset($attrs['y2'])) { - if ($attrs['y2'] === "0") { - $y2 = 0; - } elseif ($attrs['y2'] === ".") { - $y2 = "."; - } elseif (!empty($attrs['y2'])) { - $y2 = (int) $attrs['y2']; - } - } - - $line = $ReportRoot->createLine($x1, $y1, $x2, $y2); - $wt_report->addElement($line); -} - -/** - * XML <List> start element handler - * - * @param array $attrs an array of key value pairs for the attributes - */ -function listStartHandler($attrs) { - global $gedrec, $repeats, $repeatBytes, $list, $fact, $desc, $repeatsStack, $processRepeats, $parser, $vars, $sortby, $WT_TREE; - - $processRepeats++; - if ($processRepeats > 1) { - return; - } - - $match = array(); - if (isset($attrs['sortby'])) { - $sortby = $attrs['sortby']; - if (preg_match("/\\$(\w+)/", $sortby, $match)) { - $sortby = $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=?" . " GROUP BY xref" . " )" - )->execute(array($WT_TREE->getTreeId()))->fetchAll(); - $list = array(); - foreach ($rows as $row) { - $list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom); - } - break; - case 'individual': - $sql_select = "SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` "; - $sql_join = ""; - $sql_where = " WHERE i_file = " . $WT_TREE->getTreeId(); - $sql_order_by = ""; - foreach ($attrs as $attr => $value) { - if (strpos($attr, 'filter') === 0 && $value) { - // Substitute global vars - $value = preg_replace_callback( - '/\$(\w+)/', - function ($matches) use ($vars) { - return $vars[$matches[1]]['id']; - }, - $value - ); - // 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='{$match[1]}'"; - $date = new Date($match[3]); - if ($match[2] == "LTE") { - $sql_where .= " AND {$attr}.d_julianday2<=" . $date->minimumJulianDay(); - } else { - $sql_where .= " AND {$attr}.d_julianday1>=" . $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 $name) { - $sql_where .= " AND {$attr}.n_full LIKE " . Database::quote("%{$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 '" . $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 " . Database::quote("%{$match[1]}%"); - // Don't unset this filter. This is just initial filtering - } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { - $query = ""; - // Level 1 tag - if ($match[1] !== '') { - $query .= "%1 {$match[1]}%"; - } - // Level 2 tag - if ($match[2] !== '') { - $query .= "%2 {$match[2]}%"; - } - // Contains what? - if ($match[3] !== '') { - $query .= "%{$match[3]}%"; - } - $sql_where .= " AND i_gedcom LIKE " . Database::quote($query); - // Don't unset this filter. This is just initial filtering - } - } - } - - $list = array(); - $rows = Database::prepare( - $sql_select . $sql_join . $sql_where . $sql_order_by - )->fetchAll(); - - foreach ($rows as $row) { - $list[] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom); - } - break; - - case 'family': - $sql_select = "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom FROM `##families`"; - $sql_join = ""; - $sql_where = " WHERE f_file=" . $WT_TREE->getTreeId(); - $sql_order_by = ""; - foreach ($attrs as $attr => $value) { - if (strpos($attr, 'filter') === 0 && $value) { - // Substitute global vars - $value = preg_replace_callback( - '/\$(\w+)/', - function ($matches) use ($vars) { - return $vars[$matches[1]]['id']; - }, - $value - ); - // 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='{$match[1]}'"; - $date = new Date($match[3]); - if ($match[2] == "LTE") { - $sql_where .= " AND {$attr}.d_julianday2<=" . $date->minimumJulianDay(); - } else { - $sql_where .= " AND {$attr}.d_julianday1>=" . $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 '" . $match[1] . "'"; - unset($attrs[$attr]); // This filter has been fully processed - } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) { - $sql_join .= " JOIN `##link` AS {$attr}a ON ({$attr}a.l_file=f_file AND {$attr}a.l_from=f_id)"; - $sql_join .= " JOIN `##name` AS {$attr}b ON ({$attr}b.n_file=f_file AND n_id=f_id)"; - $sql_where .= " AND {$attr}a.l_type=IN ('HUSB, 'WIFE')"; - $sql_where .= " AND {$attr}.n_full LIKE " . Database::quote("%{$match[1]}%"); - 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 " . Database::quote("%{$match[1]}%"); - // Don't unset this filter. This is just initial filtering - } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { - $query = ""; - // Level 1 tag - if ($match[1] !== '') { - $query .= "%1 {$match[1]}%"; - } - // Level 2 tag - if ($match[2] !== '') { - $query .= "%2 {$match[2]}%"; - } - // Contains what? - if ($match[3] !== '') { - $query .= "%{$match[3]}%"; - } - $sql_where .= " AND f_gedcom LIKE " . Database::quote($query); - // Don't unset this filter. This is just initial filtering - } - } - } - - $list = array(); - $rows = Database::prepare( - $sql_select . $sql_join . $sql_where . $sql_order_by - )->fetchAll(); - - foreach ($rows as $row) { - $list[] = 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($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 @(.+)@/", $gedrec, $match)) { - $value = "'" . $match[1] . "'"; - } - } elseif ($id == "fact") { - $value = "'" . $fact . "'"; - } elseif ($id == "desc") { - $value = "'" . $desc . "'"; - } else { - if (preg_match("/\d $id (.+)/", $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 = $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 ($list as $key => $record) { - foreach ($filters as $filter) { - if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) { - unset($list[$key]); - break; - } - } - } - } - if ($filters2) { - $mylist = array(); - foreach ($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 = get_gedcom_value($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 = get_sub_record(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; - } - } - $list = $mylist; - } - - switch ($sortby) { - case 'NAME': - uasort($list, '\Fisharebest\Webtrees\GedcomRecord::compare'); - break; - case 'CHAN': - uasort($list, function (GedcomRecord $x, GedcomRecord $y) { - return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true); - }); - break; - case 'BIRT:DATE': - uasort($list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); - break; - case 'DEAT:DATE': - uasort($list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); - break; - case 'MARR:DATE': - uasort($list, '\Fisharebest\Webtrees\\Family::compareMarrDate'); - break; - default: - // unsorted or already sorted by SQL - break; - } - - array_push($repeatsStack, array($repeats, $repeatBytes)); - $repeatBytes = xml_get_current_line_number($parser) + 1; -} - -/** - * XML <List> end element handler - */ -function listEndHandler() { - global $list, $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $gedrec, $processRepeats, $list_total, $list_private; - - $processRepeats--; - if ($processRepeats > 0) { - return; - } - - // Check if there is any list - if (count($list) > 0) { - $lineoffset = 0; - foreach ($repeatsStack as $rep) { - $lineoffset += $rep[1]; - } - //-- read the xml from the file - $lines = file($report); - while ((strpos($lines[$lineoffset + $repeatBytes], "<List") === false) && (($lineoffset + $repeatBytes) > 0)) { - $lineoffset--; - } - $lineoffset++; - $reportxml = "<tempdoc>\n"; - $line_nr = $lineoffset + $repeatBytes; - // List Level counter - $count = 1; - while (0 < $count) { - if (strpos($lines[$line_nr], "<List") !== false) { - $count++; - } elseif (strpos($lines[$line_nr], "</List") !== false) { - $count--; - } - if (0 < $count) { - $reportxml .= $lines[$line_nr]; - } - $line_nr++; - } - // No need to drag this - unset($lines); - $reportxml .= "</tempdoc>"; - // Save original values - array_push($parserStack, $parser); - $oldgedrec = $gedrec; - - $list_total = count($list); - $list_private = 0; - foreach ($list as $record) { - if ($record->canShow()) { - $gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree())); - //-- start the sax parser - $repeat_parser = xml_parser_create(); - $parser = $repeat_parser; - //-- make sure everything is case sensitive - xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); - //-- set the main element handler functions - xml_set_element_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\startElement', '\Fisharebest\Webtrees\Report\\endElement'); - //-- set the character data handler - xml_set_character_data_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\characterData'); - if (!xml_parse($repeat_parser, $reportxml, true)) { - throw new \DomainException(sprintf( - 'ListEHandler XML error: %s at line %d', - xml_error_string(xml_get_error_code($repeat_parser)), - xml_get_current_line_number($repeat_parser) - )); - } - xml_parser_free($repeat_parser); - } else { - $list_private++; - } - } - // Clean up the GLOBAL list array - unset($list); - $parser = array_pop($parserStack); - $gedrec = $oldgedrec; - } - $temp = array_pop($repeatsStack); - $repeats = $temp[0]; - $repeatBytes = $temp[1]; -} - -/** - * XML <ListTotal> element handler - * - * Prints the total number of records in a list - * The total number is collected from - * List and Relatives - */ -function listTotalStartHandler() { - global $list_total, $list_private, $currentElement; - - if (empty($list_total)) { - $list_total = 0; - } - - if ($list_private == 0) { - $currentElement->addText($list_total); - } else { - $currentElement->addText(($list_total - $list_private) . " / " . $list_total); - } -} - -/** - * @param array $attrs an array of key value pairs for the attributes - */ -function relativesStartHandler($attrs) { - global $repeats, $repeatBytes, $list, $repeatsStack, $processRepeats, $parser, $vars, $sortby, $WT_TREE; - - $processRepeats++; - if ($processRepeats > 1) { - return; - } - - $sortby = "NAME"; - if (isset($attrs['sortby'])) { - $sortby = $attrs['sortby']; - } - $match = array(); - if (preg_match("/\\$(\w+)/", $sortby, $match)) { - $sortby = $vars[$match[1]]['id']; - $sortby = trim($sortby); - } - - $maxgen = -1; - if (isset($attrs['maxgen'])) { - $maxgen = $attrs['maxgen']; - } - if ($maxgen == "*") { - $maxgen = -1; - } - - $group = "child-family"; - if (isset($attrs['group'])) { - $group = $attrs['group']; - } - if (preg_match("/\\$(\w+)/", $group, $match)) { - $group = $vars[$match[1]]['id']; - $group = trim($group); - } - - $id = ""; - if (isset($attrs['id'])) { - $id = $attrs['id']; - } - if (preg_match("/\\$(\w+)/", $id, $match)) { - $id = $vars[$match[1]]['id']; - $id = trim($id); - } - - $list = array(); - $person = Individual::getInstance($id, $WT_TREE); - if (!empty($person)) { - $list[$id] = $person; - switch ($group) { - case "child-family": - foreach ($person->getChildFamilies() as $family) { - $husband = $family->getHusband(); - $wife = $family->getWife(); - if (!empty($husband)) { - $list[$husband->getXref()] = $husband; - } - if (!empty($wife)) { - $list[$wife->getXref()] = $wife; - } - $children = $family->getChildren(); - foreach ($children as $child) { - if (!empty($child)) { - $list[$child->getXref()] = $child; - } - } - } - break; - case "spouse-family": - foreach ($person->getSpouseFamilies() as $family) { - $husband = $family->getHusband(); - $wife = $family->getWife(); - if (!empty($husband)) { - $list[$husband->getXref()] = $husband; - } - if (!empty($wife)) { - $list[$wife->getXref()] = $wife; - } - $children = $family->getChildren(); - foreach ($children as $child) { - if (!empty($child)) { - $list[$child->getXref()] = $child; - } - } - } - break; - case "direct-ancestors": - add_ancestors($list, $id, false, $maxgen); - break; - case "ancestors": - add_ancestors($list, $id, true, $maxgen); - break; - case "descendants": - $list[$id]->generation = 1; - add_descendancy($list, $id, false, $maxgen); - break; - case "all": - add_ancestors($list, $id, true, $maxgen); - add_descendancy($list, $id, true, $maxgen); - break; - } - } - - switch ($sortby) { - case 'NAME': - uasort($list, '\Fisharebest\Webtrees\GedcomRecord::compare'); - break; - case 'BIRT:DATE': - uasort($list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); - break; - case 'DEAT:DATE': - uasort($list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); - break; - case 'generation': - $newarray = array(); - reset($list); - $genCounter = 1; - while (count($newarray) < count($list)) { - foreach ($list as $key => $value) { - $generation = $value->generation; - if ($generation == $genCounter) { - $newarray[$key] = new \stdClass; - $newarray[$key]->generation = $generation; - } - } - $genCounter++; - } - $list = $newarray; - break; - default: - // unsorted - break; - } - array_push($repeatsStack, array($repeats, $repeatBytes)); - $repeatBytes = xml_get_current_line_number($parser) + 1; -} - -/** - * XML </ Relatives> end element handler - */ -function relativesEndHandler() { - global $list, $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $gedrec, $processRepeats, $list_total, $list_private, $generation, $WT_TREE; - - $processRepeats--; - if ($processRepeats > 0) { - return; - } - - // Check if there is any relatives - if (count($list) > 0) { - $lineoffset = 0; - foreach ($repeatsStack as $rep) { - $lineoffset += $rep[1]; - } - //-- read the xml from the file - $lines = file($report); - while ((strpos($lines[$lineoffset + $repeatBytes], "<Relatives") === false) && (($lineoffset + $repeatBytes) > 0)) { - $lineoffset--; - } - $lineoffset++; - $reportxml = "<tempdoc>\n"; - $line_nr = $lineoffset + $repeatBytes; - // Relatives Level counter - $count = 1; - while (0 < $count) { - if (strpos($lines[$line_nr], "<Relatives") !== false) { - $count++; - } elseif (strpos($lines[$line_nr], "</Relatives") !== false) { - $count--; - } - if (0 < $count) { - $reportxml .= $lines[$line_nr]; - } - $line_nr++; - } - // No need to drag this - unset($lines); - $reportxml .= "</tempdoc>\n"; - // Save original values - array_push($parserStack, $parser); - $oldgedrec = $gedrec; - - $list_total = count($list); - $list_private = 0; - foreach ($list as $key => $value) { - if (isset($value->generation)) { - $generation = $value->generation; - } - $tmp = GedcomRecord::getInstance($key, $WT_TREE); - $gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)); - //-- start the sax parser - $repeat_parser = xml_parser_create(); - $parser = $repeat_parser; - //-- make sure everything is case sensitive - xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); - //-- set the main element handler functions - xml_set_element_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\startElement', '\Fisharebest\Webtrees\Report\\endElement'); - //-- set the character data handler - xml_set_character_data_handler($repeat_parser, '\Fisharebest\Webtrees\Report\\characterData'); - - if (!xml_parse($repeat_parser, $reportxml, true)) { - throw new \DomainException(sprintf("RelativesEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser))); - } - xml_parser_free($repeat_parser); - } - // Clean up the GLOBAL list array - unset($list); - $parser = array_pop($parserStack); - $gedrec = $oldgedrec; - } - $temp = array_pop($repeatsStack); - $repeats = $temp[0]; - $repeatBytes = $temp[1]; -} - -/** - * XML <Generation /> element handler - * - * Prints the number of generations - */ -function generationStartHandler() { - global $generation, $currentElement; - - if (empty($generation)) { - $generation = 1; - } - - $currentElement->addText($generation); -} - -/** - * XML <NewPage /> element handler - * - * Has to be placed in an element (header, pageheader, body or footer) - */ -function newPageStartHandler() { - global $wt_report; - - $temp = "addpage"; - $wt_report->addElement($temp); -} - -/** - * @param array $attrs an array of key value pairs for the attributes - * @param string $tag HTML tag name - */ -function htmlStartHandler($tag, $attrs) { - global $printData, $printDataStack, $wt_reportStack, $wt_report, $currentElement, $ReportRoot; - - if ($tag == "tempdoc") { - return; - } - array_push($wt_reportStack, $wt_report); - $wt_report = $ReportRoot->createHTML($tag, $attrs); - $currentElement = $wt_report; - - array_push($printDataStack, $printData); - $printData = true; -} - -/** - * @param string $tag - */ -function htmlEndHandler($tag) { - global $printData, $printDataStack, $wt_report, $currentElement, $wt_reportStack; - if ($tag == "tempdoc") { - return; - } - - $printData = array_pop($printDataStack); - $currentElement = $wt_report; - $wt_report = array_pop($wt_reportStack); - if (!is_null($wt_report)) { - $wt_report->addElement($currentElement); - } else { - $wt_report = $currentElement; - } -} - -/** - * XML <titleStartHandler> start element handler - */ -function titleStartHandler() { - global $reportTitle; - $reportTitle = true; -} - -/** - * XML </titleEndHandler> end element handler - */ -function titleEndHandler() { - global $reportTitle; - $reportTitle = false; -} - -/** - * XML <descriptionStartHandler> start element handler - */ -function descriptionStartHandler() { - global $reportDescription; - $reportDescription = true; -} - -/** - * XML </descriptionEndHandler> end element handler - */ -function descriptionEndHandler() { - global $reportDescription; - $reportDescription = false; -} - -/** * get gedcom tag value * * @param string $tag The tag to find, use : to delineate subtags diff --git a/app/Report/ReportParserBase.php b/app/Report/ReportParserBase.php index 36e4b16557..3312566ffa 100644 --- a/app/Report/ReportParserBase.php +++ b/app/Report/ReportParserBase.php @@ -15,16 +15,14 @@ namespace Fisharebest\Webtrees\Report; * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Date; -use Fisharebest\Webtrees\I18N; /** * Class ReportParserBase */ class ReportParserBase { /** @var resource The XML parser */ - private $xml_parser; + protected $xml_parser; /** @var string Text contents of tags */ protected $text = ''; @@ -61,8 +59,8 @@ class ReportParserBase { * @param string $name The name of the xml element parsed * @param string[] $attrs An array of key value pairs for the attributes */ - private function startElement($parser, $name, $attrs) { - $method = strtolower($name) . 'StartHandler'; + public function startElement($parser, $name, $attrs) { + $method = $name . 'StartHandler'; if (method_exists($this, $method)) { $this->$method($attrs); } @@ -74,8 +72,8 @@ class ReportParserBase { * @param resource $parser the resource handler for the xml parser * @param string $name the name of the xml element parsed */ - private function endElement($parser, $name) { - $method = strtolower($name) . 'EndHandler'; + public function endElement($parser, $name) { + $method = $name . 'EndHandler'; if (method_exists($this, $method)) { $this->$method(); } @@ -87,7 +85,7 @@ class ReportParserBase { * @param resource $parser The resource handler for the xml parser * @param string $data The name of the xml element parsed */ - private function characterData($parser, $data) { + public function characterData($parser, $data) { $this->text .= $data; } } diff --git a/app/Report/ReportParserGenerate.php b/app/Report/ReportParserGenerate.php new file mode 100644 index 0000000000..98763dd449 --- /dev/null +++ b/app/Report/ReportParserGenerate.php @@ -0,0 +1,2625 @@ +<?php +namespace Fisharebest\Webtrees\Report; + +/** + * webtrees: online genealogy + * Copyright (C) 2015 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +use Fisharebest\Webtrees\Auth; +use Fisharebest\Webtrees\Database; +use Fisharebest\Webtrees\Date; +use Fisharebest\Webtrees\Family; +use Fisharebest\Webtrees\GedcomRecord; +use Fisharebest\Webtrees\GedcomTag; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Media; +use Fisharebest\Webtrees\Place; + +/** + * Class ReportParserGenerate - parse a report.xml file and generate the report. + */ +class ReportParserGenerate extends ReportParserBase { + /** @var bool Are we processing a <Description> element */ + private $report_description = false; + + /** @var bool Are we processing a <Title> element */ + private $report_title = false; + + /** @var bool Are we collecting data from <Footnote> elements */ + private $process_footnote = true; + + /** @var bool Are we currently outputing data? */ + private $print_data = false; + + /** @var bool[] Push-down stack of $print_data */ + private $print_data_stack = array(); + + /** @var int */ + private $process_gedcoms = 0; + + /** @var int */ + private $process_ifs = 0; + + /** @var int */ + private $process_repeats = 0; + + /** @var int Quantity of data to repeat during loops */ + private $repeat_bytes = 0; + + /** @var array[] Repeated data when iterating over loops */ + private $repeats = array(); + + /** @var array[] Nested repeating data */ + private $repeats_stack = array(); + + /** @var string The current GEDCOM record */ + private $gedrec = ''; + + /** @var string[] Nested GEDCOM records */ + private $gedrec_stack = array(); + + /** @var ReportBaseElement The currently processed element */ + private $current_element; + + /** @var ReportBaseElement The currently processed element */ + private $footnote_element; + + /** @var string The GEDCOM fact currently being processed */ + private $fact = ''; + + /** @var string The GEDCOM value currently being processed */ + private $desc = ''; + + /** @var string The GEDCOM type currently being processed */ + private $type = ''; + + /** @var int The current generational level */ + private $generation = 1; + + /** @var array Source data for processing lists */ + private $list = array(); + + /** @var int Number of items in lists */ + private $list_total = 0; + + /** @var int Number of items filtered from lists */ + private $list_private = 0; + + /** {@inheritDoc} */ + public function __construct($report) { + $this->current_element = new ReportBaseElement; + parent::__construct($report); + } + + /** + *XML start element handler + * + * This function is called whenever a starting element is reached + * The element handler will be called if found, otherwise it must be HTML + * + * @param resource $parser the resource handler for the XML parser + * @param string $name the name of the XML element parsed + * @param array $attrs an array of key value pairs for the attributes + */ + function startElement($parser, $name, $attrs) { + global $vars; + + $newattrs = array(); + + foreach ($attrs as $key => $value) { + if (preg_match("/^\\$(\w+)$/", $value, $match)) { + if ((isset($vars[$match[1]]['id'])) && (!isset($vars[$match[1]]['gedcom']))) { + $value = $vars[$match[1]]['id']; + } + } + $newattrs[$key] = $value; + } + $attrs = $newattrs; + if ($this->process_footnote && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag")) { + $start_method = $name . 'StartHandler'; + $end_method = $name . 'EndHandler'; + if (method_exists($this, $start_method)) { + $this->$start_method($attrs); + } elseif (!method_exists($this, $end_method)) { + $this->htmlStartHandler($name, $attrs); + } + } + } + + /** + * XML end element handler + * + * This function is called whenever an ending element is reached + * The element handler will be called if found, otherwise it must be HTML + * + * @param resource $parser the resource handler for the XML parser + * @param string $name the name of the XML element parsed + */ + function endElement($parser, $name) { + if (($this->process_footnote || $name === "Footnote") && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag" || $name === "List" || $name === "Relatives")) { + $start_method = $name . 'StartHandler'; + $end_method = $name . 'EndHandler'; + if (method_exists($this, $end_method)) { + $this->$end_method(); + } elseif (!method_exists($this, $start_method)) { + $this->htmlEndHandler($name); + } + } + } + + /** + * XML character data handler + * + * @param resource $parser the resource handler for the XML parser + * @param string $data the name of the XML element parsed + */ + function characterData($parser, $data) { + global $wt_report; + + if ($this->print_data && ($this->process_gedcoms === 0) && ($this->process_ifs === 0) && ($this->process_repeats === 0)) { + $this->current_element->addText($data); + } elseif ($this->report_title) { + $wt_report->addTitle($data); + } elseif ($this->report_description) { + $wt_report->addDescription($data); + } + } + + /** + * XML <style> start element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function styleStartHandler($attrs) { + global $wt_report; + + if (empty($attrs['name'])) { + throw new \DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.'); + } + + // array Style that will be passed on + $s = array(); + + // string Name af the style + $s['name'] = $attrs['name']; + + // string Name of the DEFAULT font + $s['font'] = $wt_report->defaultFont; + if (!empty($attrs['font'])) { + $s['font'] = $attrs['font']; + } + + // int The size of the font in points + $s['size'] = $wt_report->defaultFontSize; + if (!empty($attrs['size'])) { + $s['size'] = (int) $attrs['size']; + } // Get it as int to ignore all decimal points or text (if any text then int(0)) + + // string B: bold, I: italic, U: underline, D: line trough, The default value is regular. + $s['style'] = ""; + if (!empty($attrs['style'])) { + $s['style'] = $attrs['style']; + } + + $wt_report->addStyle($s); + } + + /** + * XML <Doc> start element handler + * + * Sets up the basics of the document proparties + * + * @param array $attrs an array of key value pairs for the attributes + */ + function docStartHandler($attrs) { + global $parser, $wt_report; + + $parser = $this->xml_parser; + + // Custom page width + if (!empty($attrs['customwidth'])) { + $wt_report->pagew = (int) $attrs['customwidth']; + } // Get it as int to ignore all decimal points or text (if any text then int(0)) + // Custom Page height + if (!empty($attrs['customheight'])) { + $wt_report->pageh = (int) $attrs['customheight']; + } // Get it as int to ignore all decimal points or text (if any text then int(0)) + + // Left Margin + if (isset($attrs['leftmargin'])) { + if ($attrs['leftmargin'] === "0") { + $wt_report->leftmargin = 0; + } elseif (!empty($attrs['leftmargin'])) { + $wt_report->leftmargin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) + } + } + // Right Margin + if (isset($attrs['rightmargin'])) { + if ($attrs['rightmargin'] === "0") { + $wt_report->rightmargin = 0; + } elseif (!empty($attrs['rightmargin'])) { + $wt_report->rightmargin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) + } + } + // Top Margin + if (isset($attrs['topmargin'])) { + if ($attrs['topmargin'] === "0") { + $wt_report->topmargin = 0; + } elseif (!empty($attrs['topmargin'])) { + $wt_report->topmargin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) + } + } + // Bottom Margin + if (isset($attrs['bottommargin'])) { + if ($attrs['bottommargin'] === "0") { + $wt_report->bottommargin = 0; + } elseif (!empty($attrs['bottommargin'])) { + $wt_report->bottommargin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) + } + } + // Header Margin + if (isset($attrs['headermargin'])) { + if ($attrs['headermargin'] === "0") { + $wt_report->headermargin = 0; + } elseif (!empty($attrs['headermargin'])) { + $wt_report->headermargin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) + } + } + // Footer Margin + if (isset($attrs['footermargin'])) { + if ($attrs['footermargin'] === "0") { + $wt_report->footermargin = 0; + } elseif (!empty($attrs['footermargin'])) { + $wt_report->footermargin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) + } + } + + // Page Orientation + if (!empty($attrs['orientation'])) { + if ($attrs['orientation'] == "landscape") { + $wt_report->orientation = "landscape"; + } elseif ($attrs['orientation'] == "portrait") { + $wt_report->orientation = "portrait"; + } + } + // Page Size + if (!empty($attrs['pageSize'])) { + $wt_report->pageFormat = strtoupper($attrs['pageSize']); + } + + // Show Generated By... + if (isset($attrs['showGeneratedBy'])) { + if ($attrs['showGeneratedBy'] === "0") { + $wt_report->showGenText = false; + } elseif ($attrs['showGeneratedBy'] === "1") { + $wt_report->showGenText = true; + } + } + + $wt_report->setup(); + } + + /** + * XML </Doc> end element handler + */ + function docEndHandler() { + global $wt_report; + $wt_report->run(); + } + + /** + * XML <Header> start element handler + */ + function headerStartHandler() { + global $wt_report; + + // Clear the Header before any new elements are added + $wt_report->clearHeader(); + $wt_report->setProcessing("H"); + } + + /** + * XML <PageHeader> start element handler + */ + function pageHeaderStartHandler() { + global $wt_reportStack, $wt_report, $ReportRoot; + + array_push($this->print_data_stack, $this->print_data); + $this->print_data = false; + array_push($wt_reportStack, $wt_report); + $wt_report = $ReportRoot->createPageHeader(); + } + + /** + * XML <pageHeaderEndHandler> end element handler + */ + function pageHeaderEndHandler() { + global $wt_report, $wt_reportStack; + + $this->print_data = array_pop($this->print_data_stack); + $this->current_element = $wt_report; + $wt_report = array_pop($wt_reportStack); + $wt_report->addElement($this->current_element); + } + + /** + * XML <bodyStartHandler> start element handler + */ + function bodyStartHandler() { + global $wt_report; + $wt_report->setProcessing("B"); + } + + /** + * XML <footerStartHandler> start element handler + */ + function footerStartHandler() { + global $wt_report; + $wt_report->setProcessing("F"); + } + + /** + * XML <Cell> start element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function cellStartHandler($attrs) { + global $ReportRoot, $wt_report; + + // string The text alignment of the text in this box. + $align = ""; + if (!empty($attrs['align'])) { + $align = $attrs['align']; + // RTL supported left/right alignment + if ($align == "rightrtl") { + if ($wt_report->rtl) { + $align = "left"; + } else { + $align = "right"; + } + } elseif ($align == "leftrtl") { + if ($wt_report->rtl) { + $align = "right"; + } else { + $align = "left"; + } + } + } + + // string The color to fill the background of this cell + $bgcolor = ""; + if (!empty($attrs['bgcolor'])) { + $bgcolor = $attrs['bgcolor']; + } + + // int Whether or not the background should be painted + $fill = 1; + if (isset($attrs['fill'])) { + if ($attrs['fill'] === "0") { + $fill = 0; + } elseif ($attrs['fill'] === "1") { + $fill = 1; + } + } + + $reseth = true; + // boolean if true reset the last cell height (default true) + if (isset($attrs['reseth'])) { + if ($attrs['reseth'] === "0") { + $reseth = false; + } elseif ($attrs['reseth'] === "1") { + $reseth = true; + } + } + + // mixed Whether or not a border should be printed around this box + $border = 0; + if (!empty($attrs['border'])) { + $border = $attrs['border']; + } + // string Border color in HTML code + $bocolor = ""; + if (!empty($attrs['bocolor'])) { + $bocolor = $attrs['bocolor']; + } + + // int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted. + $height = 0; + if (!empty($attrs['height'])) { + $height = (int) $attrs['height']; + } + // int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin. + $width = 0; + if (!empty($attrs['width'])) { + $width = (int) $attrs['width']; + } + + // int Stretch carachter mode + $stretch = 0; + if (!empty($attrs['stretch'])) { + $stretch = (int) $attrs['stretch']; + } + + // mixed Position the left corner of this box on the page. The default is the current position. + $left = "."; + if (isset($attrs['left'])) { + if ($attrs['left'] === ".") { + $left = "."; + } elseif (!empty($attrs['left'])) { + $left = (int) $attrs['left']; + } elseif ($attrs['left'] === "0") { + $left = 0; + } + } + // mixed Position the top corner of this box on the page. the default is the current position + $top = "."; + if (isset($attrs['top'])) { + if ($attrs['top'] === ".") { + $top = "."; + } elseif (!empty($attrs['top'])) { + $top = (int) $attrs['top']; + } elseif ($attrs['top'] === "0") { + $top = 0; + } + } + + // string The name of the Style that should be used to render the text. + $style = ""; + if (!empty($attrs['style'])) { + $style = $attrs['style']; + } + + // string Text color in html code + $tcolor = ""; + if (!empty($attrs['tcolor'])) { + $tcolor = $attrs['tcolor']; + } + + // int Indicates where the current position should go after the call. + $ln = 0; + if (isset($attrs['newline'])) { + if (!empty($attrs['newline'])) { + $ln = (int) $attrs['newline']; + } elseif ($attrs['newline'] === "0") { + $ln = 0; + } + } + + if ($align == "left") { + $align = "L"; + } elseif ($align == "right") { + $align = "R"; + } elseif ($align == "center") { + $align = "C"; + } elseif ($align == "justify") { + $align = "J"; + } + + array_push($this->print_data_stack, $this->print_data); + $this->print_data = true; + + $this->current_element = $ReportRoot->createCell( + $width, + $height, + $border, + $align, + $bgcolor, + $style, + $ln, + $top, + $left, + $fill, + $stretch, + $bocolor, + $tcolor, + $reseth + ); + } + + /** + * XML </Cell> end element handler + */ + function cellEndHandler() { + global $wt_report; + + $this->print_data = array_pop($this->print_data_stack); + $wt_report->addElement($this->current_element); + } + + /** + * XML <Now /> element handler + */ + function nowStartHandler() { + $g = timestamp_to_gedcom_date(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET); + $this->current_element->addText($g->display()); + } + + /** + * XML <PageNum /> element handler + */ + function pageNumStartHandler() { + $this->current_element->addText("#PAGENUM#"); + } + + /** + * XML <TotalPages /> element handler + */ + function totalPagesStartHandler() { + $this->current_element->addText("{{:ptp:}}"); + } + + /** + * Called at the start of an element. + * + * @param array $attrs an array of key value pairs for the attributes + */ + function gedcomStartHandler($attrs) { + global $vars, $WT_TREE; + + if ($this->process_gedcoms > 0) { + $this->process_gedcoms++; + + return; + } + + $tag = $attrs['id']; + $tag = str_replace("@fact", $this->fact, $tag); + $tags = explode(":", $tag); + $newgedrec = ''; + if (count($tags) < 2) { + $tmp = GedcomRecord::getInstance($attrs['id'], $WT_TREE); + $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; + } + if (empty($newgedrec)) { + $tgedrec = $this->gedrec; + $newgedrec = ''; + foreach ($tags as $tag) { + if (preg_match("/\\$(.+)/", $tag, $match)) { + if (isset($vars[$match[1]]['gedcom'])) { + $newgedrec = $vars[$match[1]]['gedcom']; + } else { + $tmp = GedcomRecord::getInstance($match[1], $WT_TREE); + $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; + } + } else { + if (preg_match("/@(.+)/", $tag, $match)) { + $gmatch = array(); + if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) { + $tmp = GedcomRecord::getInstance($gmatch[1], $WT_TREE); + $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; + $tgedrec = $newgedrec; + } else { + $newgedrec = ''; + break; + } + } else { + $temp = explode(" ", trim($tgedrec)); + $level = $temp[0] + 1; + $newgedrec = get_sub_record($level, "$level $tag", $tgedrec); + $tgedrec = $newgedrec; + } + } + } + } + if (!empty($newgedrec)) { + array_push($this->gedrec_stack, array($this->gedrec, $this->fact, $this->desc)); + $this->gedrec = $newgedrec; + if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $this->gedrec, $match)) { + $this->fact = $match[2]; + $this->desc = trim($match[3]); + } + } else { + $this->process_gedcoms++; + } + } + + /** + * Called at the end of an element. + */ + function gedcomEndHandler() { + if ($this->process_gedcoms > 0) { + $this->process_gedcoms--; + } else { + list($this->gedrec, $this->fact, $this->desc) = array_pop($this->gedrec_stack); + } + } + + /** + * XML <textBoxStartHandler> start element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function textBoxStartHandler($attrs) { + global $wt_report, $wt_reportStack, $ReportRoot; + + // string Background color code + $bgcolor = ""; + if (!empty($attrs['bgcolor'])) { + $bgcolor = $attrs['bgcolor']; + } + + // boolean Wether or not fill the background color + $fill = true; + if (isset($attrs['fill'])) { + if ($attrs['fill'] === "0") { + $fill = false; + } elseif ($attrs['fill'] === "1") { + $fill = true; + } + } + + // var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0 + $border = false; + if (isset($attrs['border'])) { + if ($attrs['border'] === "1") { + $border = true; + } elseif ($attrs['border'] === "0") { + $border = false; + } + } + + // int The starting height of this cell. If the text wraps the height will automatically be adjusted + $height = 0; + if (!empty($attrs['height'])) { + $height = (int) $attrs['height']; + } + // int Setting the width to 0 will make it the width from the current location to the margin + $width = 0; + if (!empty($attrs['width'])) { + $width = (int) $attrs['width']; + } + + // mixed Position the left corner of this box on the page. The default is the current position. + $left = "."; + if (isset($attrs['left'])) { + if ($attrs['left'] === ".") { + $left = "."; + } elseif (!empty($attrs['left'])) { + $left = (int) $attrs['left']; + } elseif ($attrs['left'] === "0") { + $left = 0; + } + } + // mixed Position the top corner of this box on the page. the default is the current position + $top = "."; + if (isset($attrs['top'])) { + if ($attrs['top'] === ".") { + $top = "."; + } elseif (!empty($attrs['top'])) { + $top = (int) $attrs['top']; + } elseif ($attrs['top'] === "0") { + $top = 0; + } + } + // boolean After this box is finished rendering, should the next section of text start immediately after the this box or should it start on a new line under this box. 0 = no new line, 1 = force new line. Default is 0 + $newline = false; + if (isset($attrs['newline'])) { + if ($attrs['newline'] === "1") { + $newline = true; + } elseif ($attrs['newline'] === "0") { + $newline = false; + } + } + // boolean + $pagecheck = true; + if (isset($attrs['pagecheck'])) { + if ($attrs['pagecheck'] === "0") { + $pagecheck = false; + } elseif ($attrs['pagecheck'] === "1") { + $pagecheck = true; + } + } + // boolean Cell padding + $padding = true; + if (isset($attrs['padding'])) { + if ($attrs['padding'] === "0") { + $padding = false; + } elseif ($attrs['padding'] === "1") { + $padding = true; + } + } + // boolean Reset this box Height + $reseth = false; + if (isset($attrs['reseth'])) { + if ($attrs['reseth'] === "1") { + $reseth = true; + } elseif ($attrs['reseth'] === "0") { + $reseth = false; + } + } + + // string Style of rendering + $style = ""; + + array_push($this->print_data_stack, $this->print_data); + $this->print_data = false; + + array_push($wt_reportStack, $wt_report); + $wt_report = $ReportRoot->createTextBox( + $width, + $height, + $border, + $bgcolor, + $newline, + $left, + $top, + $pagecheck, + $style, + $fill, + $padding, + $reseth + ); + } + + /** + * XML <textBoxEndHandler> end element handler + */ + function textBoxEndHandler() { + global $wt_report, $wt_reportStack; + + $this->print_data = array_pop($this->print_data_stack); + $this->current_element = $wt_report; + $wt_report = array_pop($wt_reportStack); + $wt_report->addElement($this->current_element); + } + + /** + * @param array $attrs an array of key value pairs for the attributes + */ + function textStartHandler($attrs) { + global $ReportRoot; + + array_push($this->print_data_stack, $this->print_data); + $this->print_data = true; + + // string The name of the Style that should be used to render the text. + $style = ""; + if (!empty($attrs['style'])) { + $style = $attrs['style']; + } + + // string The color of the text - Keep the black color as default + $color = ""; + if (!empty($attrs['color'])) { + $color = $attrs['color']; + } + + $this->current_element = $ReportRoot->createText($style, $color); + } + + /** + * + */ + function textEndHandler() { + global $wt_report; + + $this->print_data = array_pop($this->print_data_stack); + $wt_report->addElement($this->current_element); + } + + /** + * XML <GetPersonName> start element handler + * Get the name + * 1. id is empty - current GEDCOM record + * 2. id is set with a record id + * + * @param array $attrs an array of key value pairs for the attributes + */ + function getPersonNameStartHandler($attrs) { + global $vars, $WT_TREE; + + $id = ""; + $match = array(); + if (empty($attrs['id'])) { + if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { + $id = $match[1]; + } + } else { + if (preg_match("/\\$(.+)/", $attrs['id'], $match)) { + if (isset($vars[$match[1]]['id'])) { + $id = $vars[$match[1]]['id']; + } + } else { + if (preg_match("/@(.+)/", $attrs['id'], $match)) { + $gmatch = array(); + if (preg_match("/\d $match[1] @([^@]+)@/", $this->gedrec, $gmatch)) { + $id = $gmatch[1]; + } + } else { + $id = $attrs['id']; + } + } + } + if (!empty($id)) { + $record = GedcomRecord::getInstance($id, $WT_TREE); + if (is_null($record)) { + return; + } + if (!$record->canShowName()) { + $this->current_element->addText(I18N::translate('Private')); + } else { + $name = $record->getFullName(); + $name = preg_replace( + array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'), + array('«', '', '»'), + $name + ); + $name = strip_tags($name); + if (!empty($attrs['truncate'])) { + if (mb_strlen($name) > $attrs['truncate']) { + $name = preg_replace("/\(.*\) ?/", '', $name); //removes () and text inbetween - what about ", [ and { etc? + $words = preg_split('/[, -]+/', $name); // names separated with space, comma or hyphen - any others? + $name = $words[count($words) - 1]; + for ($i = count($words) - 2; $i >= 0; $i--) { + $len = mb_strlen($name); + for ($j = count($words) - 3; $j >= 0; $j--) { + $len += mb_strlen($words[$j]); + } + if ($len > $attrs['truncate']) { + $first_letter = mb_substr($words[$i], 0, 1); + // Do not show " of nick-names + if ($first_letter != "\"") { + $name = mb_substr($words[$i], 0, 1) . '. ' . $name; + } + } else { + $name = $words[$i] . ' ' . $name; + } + } + } + } else { + $addname = $record->getAddName(); + $addname = preg_replace( + array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'), + array('«', '', '»'), + $addname + ); + $addname = strip_tags($addname); + if (!empty($addname)) { + $name .= " " . $addname; + } + } + $this->current_element->addText(trim($name)); + } + } + } + + /** + * XML <GedcomValue> start element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function gedcomValueStartHandler($attrs) { + global $WT_TREE; + + $id = ""; + $match = array(); + if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { + $id = $match[1]; + } + + if (isset($attrs['newline']) && $attrs['newline'] == "1") { + $useBreak = "1"; + } else { + $useBreak = "0"; + } + + $tag = $attrs['tag']; + if (!empty($tag)) { + if ($tag == "@desc") { + $value = $this->desc; + $value = trim($value); + $this->current_element->addText($value); + } + if ($tag == "@id") { + $this->current_element->addText($id); + } else { + $tag = str_replace("@fact", $this->fact, $tag); + if (empty($attrs['level'])) { + $temp = explode(" ", trim($this->gedrec)); + $level = $temp[0]; + if ($level == 0) { + $level++; + } + } else { + $level = $attrs['level']; + } + $tags = preg_split('/[: ]/', $tag); + $value = get_gedcom_value($tag, $level, $this->gedrec); + switch (end($tags)) { + case 'DATE': + $tmp = new Date($value); + $value = $tmp->display(); + break; + case 'PLAC': + $tmp = new Place($value, $WT_TREE); + $value = $tmp->getShortName(); + break; + } + if ($useBreak == "1") { + // Insert <br> when multiple dates exist. + // This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages + $value = str_replace('(', '<br>(', $value); + $value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value); + $value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value); + if (substr($value, 0, 6) == '<br>') { + $value = substr($value, 6); + } + } + $this->current_element->addText($value); + } + } + } + + /** + * XML <RepeatTag> start element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function repeatTagStartHandler($attrs) { + global $parser; + + $this->process_repeats++; + if ($this->process_repeats > 1) { + return; + } + + array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); + $this->repeats = array(); + $this->repeat_bytes = xml_get_current_line_number($parser); + + $tag = ""; + if (isset($attrs['tag'])) { + $tag = $attrs['tag']; + } + if (!empty($tag)) { + if ($tag == "@desc") { + $value = $this->desc; + $value = trim($value); + $this->current_element->addText($value); + } else { + $tag = str_replace("@fact", $this->fact, $tag); + $tags = explode(":", $tag); + $temp = explode(" ", trim($this->gedrec)); + $level = $temp[0]; + if ($level == 0) { + $level++; + } + $subrec = $this->gedrec; + $t = $tag; + $count = count($tags); + $i = 0; + while ($i < $count) { + $t = $tags[$i]; + if (!empty($t)) { + if ($i < ($count - 1)) { + $subrec = get_sub_record($level, "$level $t", $subrec); + if (empty($subrec)) { + $level--; + $subrec = get_sub_record($level, "@ $t", $this->gedrec); + if (empty($subrec)) { + return; + } + } + } + $level++; + } + $i++; + } + $level--; + $count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER); + $i = 0; + while ($i < $count) { + $this->repeats[] = get_sub_record($level, "$level $t", $subrec, $i + 1); + $i++; + } + } + } + } + + /** + * XML </ RepeatTag> end element handler + */ + function repeatTagEndHandler() { + global $parser, $parserStack, $report; + + $this->process_repeats--; + if ($this->process_repeats > 0) { + return; + } + + // Check if there is anything to repeat + if (count($this->repeats) > 0) { + // No need to load them if not used... + + $lineoffset = 0; + foreach ($this->repeats_stack as $rep) { + $lineoffset += $rep[1]; + } + //-- read the xml from the file + $lines = file($report); + while (strpos($lines[$lineoffset + $this->repeat_bytes], "<RepeatTag") === false) { + $lineoffset--; + } + $lineoffset++; + $reportxml = "<tempdoc>\n"; + $line_nr = $lineoffset + $this->repeat_bytes; + // RepeatTag Level counter + $count = 1; + while (0 < $count) { + if (strstr($lines[$line_nr], "<RepeatTag") !== false) { + $count++; + } elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) { + $count--; + } + if (0 < $count) { + $reportxml .= $lines[$line_nr]; + } + $line_nr++; + } + // No need to drag this + unset($lines); + $reportxml .= "</tempdoc>\n"; + // Save original values + array_push($parserStack, $parser); + $oldgedrec = $this->gedrec; + foreach ($this->repeats as $gedrec) { + $this->gedrec = $gedrec; + $repeat_parser = xml_parser_create(); + $parser = $repeat_parser; + xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); + xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); + if (!xml_parse($repeat_parser, $reportxml, true)) { + throw new \DomainException(sprintf( + 'RepeatTagEHandler XML error: %s at line %d', + xml_error_string(xml_get_error_code($repeat_parser)), + xml_get_current_line_number($repeat_parser) + )); + } + xml_parser_free($repeat_parser); + } + // Restore original values + $this->gedrec = $oldgedrec; + $parser = array_pop($parserStack); + } + list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); + } + + /** + * Variable lookup + * + * Retrieve predefined variables : + * + * @ desc GEDCOM fact description, example: + * 1 EVEN This is a description + * @ fact GEDCOM fact tag, such as BIRT, DEAT etc. + * $ I18N::translate('....') + * $ language_settings[] + * + * @param array $attrs an array of key value pairs for the attributes + */ + function varStartHandler($attrs) { + global $parser, $vars; + + if (empty($attrs['var'])) { + throw new \DomainException('REPORT ERROR var: The attribute "var=" is missing or not set in the XML file on line: ' . xml_get_current_line_number($parser)); + } + + $var = $attrs['var']; + // SetVar element preset variables + if (!empty($vars[$var]['id'])) { + $var = $vars[$var]['id']; + } else { + $tfact = $this->fact; + if (($this->fact === "EVEN" || $this->fact === "FACT") && $this->type !== " ") { + // Use : + // n TYPE This text if string + $tfact = $this->type; + } + $var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $this->desc), $var); + if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) { + $var = I18N::number($match[1]); + } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) { + $var = I18N::translate($match[1]); + } elseif (preg_match('/^I18N::translate_c\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) { + $var = I18N::translateContext($match[1], $match[2]); + } + } + // Check if variable is set as a date and reformat the date + if (isset($attrs['date'])) { + if ($attrs['date'] === "1") { + $g = new Date($var); + $var = $g->display(); + } + } + $this->current_element->addText($var); + } + + /** + * @param array $attrs an array of key value pairs for the attributes + */ + function factsStartHandler($attrs) { + global $parser, $vars, $WT_TREE; + + $this->process_repeats++; + if ($this->process_repeats > 1) { + return; + } + + array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); + $this->repeats = array(); + $this->repeat_bytes = xml_get_current_line_number($parser); + + $id = ""; + $match = array(); + if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { + $id = $match[1]; + } + $tag = ""; + if (isset($attrs['ignore'])) { + $tag .= $attrs['ignore']; + } + if (preg_match("/\\$(.+)/", $tag, $match)) { + $tag = $vars[$match[1]]['id']; + } + + $record = GedcomRecord::getInstance($id, $WT_TREE); + if (empty($attrs['diff']) && !empty($id)) { + $facts = $record->getFacts(); + sort_facts($facts); + $this->repeats = array(); + $nonfacts = explode(',', $tag); + foreach ($facts as $event) { + if (!in_array($event->getTag(), $nonfacts)) { + $this->repeats[] = $event->getGedcom(); + } + } + } else { + foreach ($record->getFacts() as $fact) { + if ($fact->isPendingAddition() && $fact->getTag() !== 'CHAN') { + $this->repeats[] = $fact->getGedcom(); + } + } + } + } + + /** + * XML </ Facts> end element handler + */ + function factsEndHandler() { + global $parser, $parserStack, $report; + + $this->process_repeats--; + if ($this->process_repeats > 0) { + return; + } + + // Check if there is anything to repeat + if (count($this->repeats) > 0) { + + $line = xml_get_current_line_number($parser) - 1; + $lineoffset = 0; + foreach ($this->repeats_stack as $rep) { + $lineoffset += $rep[1]; + } + + //-- read the xml from the file + $lines = file($report); + while ($lineoffset + $this->repeat_bytes > 0 && strpos($lines[$lineoffset + $this->repeat_bytes], '<Facts ') === false) { + $lineoffset--; + } + $lineoffset++; + $reportxml = "<tempdoc>\n"; + $i = $line + $lineoffset; + $line_nr = $this->repeat_bytes + $lineoffset; + while ($line_nr < $i) { + $reportxml .= $lines[$line_nr]; + $line_nr++; + } + // No need to drag this + unset($lines); + $reportxml .= "</tempdoc>\n"; + // Save original values + array_push($parserStack, $parser); + $oldgedrec = $this->gedrec; + $count = count($this->repeats); + $i = 0; + while ($i < $count) { + $this->gedrec = $this->repeats[$i]; + $this->fact = ''; + $this->desc = ''; + if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) { + $this->fact = $match[1]; + if ($this->fact === 'EVEN' || $this->fact === 'FACT') { + $tmatch = array(); + if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) { + $this->type = trim($tmatch[1]); + } else { + $this->type = ' '; + } + } + $this->desc = trim($match[2]); + $this->desc .= get_cont(2, $this->gedrec); + } + $repeat_parser = xml_parser_create(); + $parser = $repeat_parser; + xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); + xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); + if (!xml_parse($repeat_parser, $reportxml, true)) { + throw new \DomainException(sprintf( + 'FactsEHandler XML error: %s at line %d', + xml_error_string(xml_get_error_code($repeat_parser)), + xml_get_current_line_number($repeat_parser) + )); + } + xml_parser_free($repeat_parser); + $i++; + } + // Restore original values + $parser = array_pop($parserStack); + $this->gedrec = $oldgedrec; + } + list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); + } + + /** + * Setting upp or changing variables in the XML + * The XML variable name and value is stored in the global variable $vars + * + * @param array $attrs an array of key value pairs for the attributes + */ + function setVarStartHandler($attrs) { + global $vars; + + if (empty($attrs['name'])) { + throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file'); + } + + $name = $attrs['name']; + $value = $attrs['value']; + $match = array(); + // Current GEDCOM record strings + if ($value == "@ID") { + if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { + $value = $match[1]; + } + } elseif ($value == "@fact") { + $value = $this->fact; + } elseif ($value == "@desc") { + $value = $this->desc; + } elseif ($value == "@generation") { + $value = $this->generation; + } elseif (preg_match("/@(\w+)/", $value, $match)) { + $gmatch = array(); + if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) { + $value = str_replace("@", "", trim($gmatch[1])); + } + } + if (preg_match("/\\$(\w+)/", $name, $match)) { + $name = $vars["'" . $match[1] . "'"]['id']; + } + $count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER); + $i = 0; + while ($i < $count) { + $t = $vars[$match[$i][1]]['id']; + $value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1); + $i++; + } + if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) { + $value = I18N::number($match[1]); + } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) { + $value = I18N::translate($match[1]); + } elseif (preg_match('/^I18N::translate_c\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) { + $value = I18N::translateContext($match[1], $match[2]); + } + // Arithmetic functions + if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) { + switch ($match[2]) { + case "+": + $t = $match[1] + $match[3]; + $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); + break; + case "-": + $t = $match[1] - $match[3]; + $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); + break; + case "*": + $t = $match[1] * $match[3]; + $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); + break; + case "/": + $t = $match[1] / $match[3]; + $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); + break; + } + } + if (strpos($value, "@") !== false) { + $value = ""; + } + $vars[$name]['id'] = $value; + } + + /** + * XML <if > start element + * + * @param array $attrs an array of key value pairs for the attributes + */ + function ifStartHandler($attrs) { + global $vars; + + if ($this->process_ifs > 0) { + $this->process_ifs++; + + return; + } + + $condition = $attrs['condition']; + $condition = preg_replace("/\\$(\w+)/", "\$vars[\"$1\"][\"id\"]", $condition); + $condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition); + // Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT + $condition = str_replace("@fact", $this->fact, $condition); + $match = array(); + $count = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER); + $i = 0; + while ($i < $count) { + $id = $match[$i][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 = '"' . addslashes($this->desc) . '"'; + } elseif ($id === "generation") { + $value = '"' . $this->generation . '"'; + } else { + + $temp = explode(" ", trim($this->gedrec)); + $level = $temp[0]; + if ($level == 0) { + $level++; + } + $value = get_gedcom_value($id, $level, $this->gedrec); + if (empty($value)) { + $level++; + $value = get_gedcom_value($id, $level, $this->gedrec); + } + $value = preg_replace("/^@(" . WT_REGEX_XREF . ")@$/", "$1", $value); + $value = "\"" . addslashes($value) . "\""; + } + $condition = str_replace("@$id", $value, $condition); + $i++; + } + $condition = "return (bool) ($condition);"; + $ret = @eval($condition); + if (!$ret) { + $this->process_ifs++; + } + } + + /** + * XML <if /> end element + */ + function ifEndHandler() { + if ($this->process_ifs > 0) { + $this->process_ifs--; + } + } + + /** + * XML <Footnote > start element + * Collect the Footnote links + * GEDCOM Records that are protected by Privacy setting will be ignore + * + * @param array $attrs an array of key value pairs for the attributes + */ + function footnoteStartHandler($attrs) { + global $ReportRoot, $WT_TREE; + + $id = ""; + if (preg_match("/[0-9] (.+) @(.+)@/", $this->gedrec, $match)) { + $id = $match[2]; + } + $record = GedcomRecord::GetInstance($id, $WT_TREE); + if ($record && $record->canShow()) { + array_push($this->print_data_stack, $this->print_data); + $this->print_data = true; + $style = ""; + if (!empty($attrs['style'])) { + $style = $attrs['style']; + } + $this->footnote_element = $this->current_element; + $this->current_element = $ReportRoot->createFootnote($style); + } else { + $this->print_data = false; + $this->process_footnote = false; + } + } + + /** + * XML <Footnote /> end element + * Print the collected Footnote data + */ + function footnoteEndHandler() { + global $wt_report; + + if ($this->process_footnote) { + $this->print_data = array_pop($this->print_data_stack); + $temp = trim($this->current_element->getValue()); + if (strlen($temp) > 3) { + $wt_report->addElement($this->current_element); + } + $this->current_element = $this->footnote_element; + } else { + $this->process_footnote = true; + } + } + + /** + * XML <FootnoteTexts /> element + */ + function footnoteTextsStartHandler() { + global $wt_report; + + $temp = "footnotetexts"; + $wt_report->addElement($temp); + } + + /** + * XML <AgeAtDeath /> element handler + */ + function ageAtDeathStartHandler() { + // This duplicates functionality in format_fact_date() + global $factrec, $WT_TREE; + + $match = array(); + if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { + $person = Individual::getInstance($match[1], $WT_TREE); + // Recorded age + if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { + $fact_age = $match[1]; + } else { + $fact_age = ''; + } + if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { + $husb_age = $match[1]; + } else { + $husb_age = ''; + } + if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { + $wife_age = $match[1]; + } else { + $wife_age = ''; + } + + // Calculated age + $birth_date = $person->getBirthDate(); + // Can't use getDeathDate(), as this also gives BURI/CREM events, which + // wouldn't give the correct "days after death" result for people with + // no DEAT. + $death_event = $person->getFirstFact('DEAT'); + if ($death_event) { + $death_date = $death_event->getDate(); + } else { + $death_date = new Date(''); + } + $value = ''; + if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) { + $age = Date::getAgeGedcom($birth_date, $death_date); + // Only show calculated age if it differs from recorded age + if ($age != '' && $age != "0d") { + if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $person->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $person->getSex() == 'F' && $wife_age != $age + ) { + $value = get_age_at_event($age, false); + $abbrev = substr($value, 0, strpos($value, ' ') + 5); + if ($value !== $abbrev) { + $value = $abbrev . '.'; + } + } + } + } + $this->current_element->addText($value); + } + } + + /** + * XML element Forced line break handler - HTML code + */ + function brStartHandler() { + if ($this->print_data && $this->process_gedcoms === 0) { + $this->current_element->addText('<br>'); + } + } + + /** + * XML <sp />element Forced space handler + */ + function spStartHandler() { + if ($this->print_data && $this->process_gedcoms === 0) { + $this->current_element->addText(' '); + } + } + + /** + * @param array $attrs an array of key value pairs for the attributes + */ + function highlightedImageStartHandler($attrs) { + global $wt_report, $ReportRoot, $WT_TREE; + + $id = ''; + $match = array(); + if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { + $id = $match[1]; + } + + // mixed Position the top corner of this box on the page. the default is the current position + $top = '.'; + if (isset($attrs['top'])) { + if ($attrs['top'] === '0') { + $top = 0; + } elseif ($attrs['top'] === '.') { + $top = '.'; + } elseif (!empty($attrs['top'])) { + $top = (int) $attrs['top']; + } + } + + // mixed Position the left corner of this box on the page. the default is the current position + $left = '.'; + if (isset($attrs['left'])) { + if ($attrs['left'] === '0') { + $left = 0; + } elseif ($attrs['left'] === '.') { + $left = '.'; + } elseif (!empty($attrs['left'])) { + $left = (int) $attrs['left']; + } + } + + // string Align the image in left, center, right + $align = ''; + if (!empty($attrs['align'])) { + $align = $attrs['align']; + } + + // string Next Line should be T:next to the image, N:next line + $ln = ''; + if (!empty($attrs['ln'])) { + $ln = $attrs['ln']; + } + + $width = 0; + $height = 0; + if (!empty($attrs['width'])) { + $width = (int) $attrs['width']; + } + if (!empty($attrs['height'])) { + $height = (int) $attrs['height']; + } + + $person = Individual::getInstance($id, $WT_TREE); + $mediaobject = $person->findHighlightedMedia(); + if ($mediaobject) { + $attributes = $mediaobject->getImageAttributes('thumb'); + if (in_array( + $attributes['ext'], + array( + 'GIF', + 'JPG', + 'PNG', + 'SWF', + 'PSD', + 'BMP', + 'TIFF', + 'TIFF', + 'JPC', + 'JP2', + 'JPX', + 'JB2', + 'SWC', + 'IFF', + 'WBMP', + 'XBM', + ) + ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') + ) { + if ($width > 0 && $height == 0) { + $perc = $width / $attributes['adjW']; + $height = round($attributes['adjH'] * $perc); + } elseif ($height > 0 && $width == 0) { + $perc = $height / $attributes['adjH']; + $width = round($attributes['adjW'] * $perc); + } else { + $width = $attributes['adjW']; + $height = $attributes['adjH']; + } + $image = $ReportRoot->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); + $wt_report->addElement($image); + } + } + } + + /** + * @param array $attrs an array of key value pairs for the attributes + */ + function imageStartHandler($attrs) { + global $wt_report, $ReportRoot, $WT_TREE; + + // mixed Position the top corner of this box on the page. the default is the current position + $top = '.'; + if (isset($attrs['top'])) { + if ($attrs['top'] === "0") { + $top = 0; + } elseif ($attrs['top'] === '.') { + $top = '.'; + } elseif (!empty($attrs['top'])) { + $top = (int) $attrs['top']; + } + } + + // mixed Position the left corner of this box on the page. the default is the current position + $left = '.'; + if (isset($attrs['left'])) { + if ($attrs['left'] === '0') { + $left = 0; + } elseif ($attrs['left'] === '.') { + $left = '.'; + } elseif (!empty($attrs['left'])) { + $left = (int) $attrs['left']; + } + } + + // string Align the image in left, center, right + $align = ''; + if (!empty($attrs['align'])) { + $align = $attrs['align']; + } + + // string Next Line should be T:next to the image, N:next line + $ln = 'T'; + if (!empty($attrs['ln'])) { + $ln = $attrs['ln']; + } + + $width = 0; + $height = 0; + if (!empty($attrs['width'])) { + $width = (int) $attrs['width']; + } + if (!empty($attrs['height'])) { + $height = (int) $attrs['height']; + } + + $file = ''; + if (!empty($attrs['file'])) { + $file = $attrs['file']; + } + if ($file == "@FILE") { + $match = array(); + if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) { + $mediaobject = Media::getInstance($match[1], $WT_TREE); + $attributes = $mediaobject->getImageAttributes('thumb'); + if (in_array( + $attributes['ext'], + array( + 'GIF', + 'JPG', + 'PNG', + 'SWF', + 'PSD', + 'BMP', + 'TIFF', + 'TIFF', + 'JPC', + 'JP2', + 'JPX', + 'JB2', + 'SWC', + 'IFF', + 'WBMP', + 'XBM', + ) + ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') + ) { + if ($width > 0 && $height == 0) { + $perc = $width / $attributes['adjW']; + $height = round($attributes['adjH'] * $perc); + } elseif ($height > 0 && $width == 0) { + $perc = $height / $attributes['adjH']; + $width = round($attributes['adjW'] * $perc); + } else { + $width = $attributes['adjW']; + $height = $attributes['adjH']; + } + $image = $ReportRoot->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); + $wt_report->addElement($image); + } + } + } else { + if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) { + $size = getimagesize($file); + if ($width > 0 && $height == 0) { + $perc = $width / $size[0]; + $height = round($size[1] * $perc); + } elseif ($height > 0 && $width == 0) { + $perc = $height / $size[1]; + $width = round($size[0] * $perc); + } else { + $width = $size[0]; + $height = $size[1]; + } + $image = $ReportRoot->createImage($file, $left, $top, $width, $height, $align, $ln); + $wt_report->addElement($image); + } + } + } + + /** + * XML <Line> element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function lineStartHandler($attrs) { + global $wt_report, $ReportRoot; + + // Start horizontal position, current position (default) + $x1 = "."; + if (isset($attrs['x1'])) { + if ($attrs['x1'] === "0") { + $x1 = 0; + } elseif ($attrs['x1'] === ".") { + $x1 = "."; + } elseif (!empty($attrs['x1'])) { + $x1 = (int) $attrs['x1']; + } + } + // Start vertical position, current position (default) + $y1 = "."; + if (isset($attrs['y1'])) { + if ($attrs['y1'] === "0") { + $y1 = 0; + } elseif ($attrs['y1'] === ".") { + $y1 = "."; + } elseif (!empty($attrs['y1'])) { + $y1 = (int) $attrs['y1']; + } + } + // End horizontal position, maximum width (default) + $x2 = "."; + if (isset($attrs['x2'])) { + if ($attrs['x2'] === "0") { + $x2 = 0; + } elseif ($attrs['x2'] === ".") { + $x2 = "."; + } elseif (!empty($attrs['x2'])) { + $x2 = (int) $attrs['x2']; + } + } + // End vertical position + $y2 = "."; + if (isset($attrs['y2'])) { + if ($attrs['y2'] === "0") { + $y2 = 0; + } elseif ($attrs['y2'] === ".") { + $y2 = "."; + } elseif (!empty($attrs['y2'])) { + $y2 = (int) $attrs['y2']; + } + } + + $line = $ReportRoot->createLine($x1, $y1, $x2, $y2); + $wt_report->addElement($line); + } + + /** + * XML <List> start element handler + * + * @param array $attrs an array of key value pairs for the attributes + */ + function listStartHandler($attrs) { + global $parser, $vars, $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 = $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=?" . " GROUP BY xref" . " )" + )->execute(array($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 DISTINCT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` "; + $sql_join = ""; + $sql_where = " WHERE i_file = " . $WT_TREE->getTreeId(); + $sql_order_by = ""; + foreach ($attrs as $attr => $value) { + if (strpos($attr, 'filter') === 0 && $value) { + // Substitute global vars + $value = preg_replace_callback( + '/\$(\w+)/', + function ($matches) use ($vars) { + return $vars[$matches[1]]['id']; + }, + $value + ); + // 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='{$match[1]}'"; + $date = new Date($match[3]); + if ($match[2] == "LTE") { + $sql_where .= " AND {$attr}.d_julianday2<=" . $date->minimumJulianDay(); + } else { + $sql_where .= " AND {$attr}.d_julianday1>=" . $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 $name) { + $sql_where .= " AND {$attr}.n_full LIKE " . Database::quote("%{$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 '" . $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 " . Database::quote("%{$match[1]}%"); + // Don't unset this filter. This is just initial filtering + } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { + $query = ""; + // Level 1 tag + if ($match[1] !== '') { + $query .= "%1 {$match[1]}%"; + } + // Level 2 tag + if ($match[2] !== '') { + $query .= "%2 {$match[2]}%"; + } + // Contains what? + if ($match[3] !== '') { + $query .= "%{$match[3]}%"; + } + $sql_where .= " AND i_gedcom LIKE " . Database::quote($query); + // 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 + )->fetchAll(); + + foreach ($rows as $row) { + $this->list[] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom); + } + break; + + case 'family': + $sql_select = "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom FROM `##families`"; + $sql_join = ""; + $sql_where = " WHERE f_file=" . $WT_TREE->getTreeId(); + $sql_order_by = ""; + foreach ($attrs as $attr => $value) { + if (strpos($attr, 'filter') === 0 && $value) { + // Substitute global vars + $value = preg_replace_callback( + '/\$(\w+)/', + function ($matches) use ($vars) { + return $vars[$matches[1]]['id']; + }, + $value + ); + // 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='{$match[1]}'"; + $date = new Date($match[3]); + if ($match[2] == "LTE") { + $sql_where .= " AND {$attr}.d_julianday2<=" . $date->minimumJulianDay(); + } else { + $sql_where .= " AND {$attr}.d_julianday1>=" . $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 '" . $match[1] . "'"; + unset($attrs[$attr]); // This filter has been fully processed + } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) { + $sql_join .= " JOIN `##link` AS {$attr}a ON ({$attr}a.l_file=f_file AND {$attr}a.l_from=f_id)"; + $sql_join .= " JOIN `##name` AS {$attr}b ON ({$attr}b.n_file=f_file AND n_id=f_id)"; + $sql_where .= " AND {$attr}a.l_type=IN ('HUSB, 'WIFE')"; + $sql_where .= " AND {$attr}.n_full LIKE " . Database::quote("%{$match[1]}%"); + 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 " . Database::quote("%{$match[1]}%"); + // Don't unset this filter. This is just initial filtering + } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { + $query = ""; + // Level 1 tag + if ($match[1] !== '') { + $query .= "%1 {$match[1]}%"; + } + // Level 2 tag + if ($match[2] !== '') { + $query .= "%2 {$match[2]}%"; + } + // Contains what? + if ($match[3] !== '') { + $query .= "%{$match[3]}%"; + } + $sql_where .= " AND f_gedcom LIKE " . Database::quote($query); + // 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 + )->fetchAll(); + + foreach ($rows as $row) { + $this->list[] = 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 = $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 = get_gedcom_value($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 = get_sub_record(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($parser) + 1; + } + + /** + * XML <List> end element handler + */ + function listEndHandler() { + global $parser, $parserStack, $report; + + $this->process_repeats--; + if ($this->process_repeats > 0) { + return; + } + + // Check if there is any list + if (count($this->list) > 0) { + $lineoffset = 0; + foreach ($this->repeats_stack as $rep) { + $lineoffset += $rep[1]; + } + //-- read the xml from the file + $lines = file($report); + while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<List") === false) && (($lineoffset + $this->repeat_bytes) > 0)) { + $lineoffset--; + } + $lineoffset++; + $reportxml = "<tempdoc>\n"; + $line_nr = $lineoffset + $this->repeat_bytes; + // List Level counter + $count = 1; + while (0 < $count) { + if (strpos($lines[$line_nr], "<List") !== false) { + $count++; + } elseif (strpos($lines[$line_nr], "</List") !== false) { + $count--; + } + if (0 < $count) { + $reportxml .= $lines[$line_nr]; + } + $line_nr++; + } + // No need to drag this + unset($lines); + $reportxml .= "</tempdoc>"; + // Save original values + array_push($parserStack, $parser); + $oldgedrec = $this->gedrec; + + $this->list_total = count($this->list); + $this->list_private = 0; + foreach ($this->list as $record) { + if ($record->canShow()) { + $this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree())); + //-- start the sax parser + $repeat_parser = xml_parser_create(); + $parser = $repeat_parser; + xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); + xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); + if (!xml_parse($repeat_parser, $reportxml, true)) { + throw new \DomainException(sprintf( + 'ListEHandler XML error: %s at line %d', + xml_error_string(xml_get_error_code($repeat_parser)), + xml_get_current_line_number($repeat_parser) + )); + } + xml_parser_free($repeat_parser); + } else { + $this->list_private++; + } + } + $this->list = array(); + $parser = array_pop($parserStack); + $this->gedrec = $oldgedrec; + } + list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); + } + + /** + * XML <ListTotal> element handler + * + * Prints the total number of records in a list + * The total number is collected from + * List and Relatives + */ + function listTotalStartHandler() { + if ($this->list_private == 0) { + $this->current_element->addText($this->list_total); + } else { + $this->current_element->addText(($this->list_total - $this->list_private) . " / " . $this->list_total); + } + } + + /** + * @param array $attrs an array of key value pairs for the attributes + */ + function relativesStartHandler($attrs) { + global $parser, $vars, $WT_TREE; + + $this->process_repeats++; + if ($this->process_repeats > 1) { + return; + } + + $sortby = "NAME"; + if (isset($attrs['sortby'])) { + $sortby = $attrs['sortby']; + } + $match = array(); + if (preg_match("/\\$(\w+)/", $sortby, $match)) { + $sortby = $vars[$match[1]]['id']; + $sortby = trim($sortby); + } + + $maxgen = -1; + if (isset($attrs['maxgen'])) { + $maxgen = $attrs['maxgen']; + } + if ($maxgen == "*") { + $maxgen = -1; + } + + $group = "child-family"; + if (isset($attrs['group'])) { + $group = $attrs['group']; + } + if (preg_match("/\\$(\w+)/", $group, $match)) { + $group = $vars[$match[1]]['id']; + $group = trim($group); + } + + $id = ""; + if (isset($attrs['id'])) { + $id = $attrs['id']; + } + if (preg_match("/\\$(\w+)/", $id, $match)) { + $id = $vars[$match[1]]['id']; + $id = trim($id); + } + + $this->list = array(); + $person = Individual::getInstance($id, $WT_TREE); + if (!empty($person)) { + $this->list[$id] = $person; + switch ($group) { + case "child-family": + foreach ($person->getChildFamilies() as $family) { + $husband = $family->getHusband(); + $wife = $family->getWife(); + if (!empty($husband)) { + $this->list[$husband->getXref()] = $husband; + } + if (!empty($wife)) { + $this->list[$wife->getXref()] = $wife; + } + $children = $family->getChildren(); + foreach ($children as $child) { + if (!empty($child)) { + $this->list[$child->getXref()] = $child; + } + } + } + break; + case "spouse-family": + foreach ($person->getSpouseFamilies() as $family) { + $husband = $family->getHusband(); + $wife = $family->getWife(); + if (!empty($husband)) { + $this->list[$husband->getXref()] = $husband; + } + if (!empty($wife)) { + $this->list[$wife->getXref()] = $wife; + } + $children = $family->getChildren(); + foreach ($children as $child) { + if (!empty($child)) { + $this->list[$child->getXref()] = $child; + } + } + } + break; + case "direct-ancestors": + add_ancestors($this->list, $id, false, $maxgen); + break; + case "ancestors": + add_ancestors($this->list, $id, true, $maxgen); + break; + case "descendants": + $this->list[$id]->generation = 1; + add_descendancy($this->list, $id, false, $maxgen); + break; + case "all": + add_ancestors($this->list, $id, true, $maxgen); + add_descendancy($this->list, $id, true, $maxgen); + break; + } + } + + switch ($sortby) { + case 'NAME': + uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare'); + break; + case 'BIRT:DATE': + uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); + break; + case 'DEAT:DATE': + uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); + break; + case 'generation': + $newarray = array(); + reset($this->list); + $genCounter = 1; + while (count($newarray) < count($this->list)) { + foreach ($this->list as $key => $value) { + $this->generation = $value->generation; + if ($this->generation == $genCounter) { + $newarray[$key] = new \stdClass; + $newarray[$key]->generation = $this->generation; + } + } + $genCounter++; + } + $this->list = $newarray; + break; + default: + // unsorted + break; + } + array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); + $this->repeat_bytes = xml_get_current_line_number($parser) + 1; + } + + /** + * XML </ Relatives> end element handler + */ + function relativesEndHandler() { + global $parser, $parserStack, $report, $WT_TREE; + + $this->process_repeats--; + if ($this->process_repeats > 0) { + return; + } + + // Check if there is any relatives + if (count($this->list) > 0) { + $lineoffset = 0; + foreach ($this->repeats_stack as $rep) { + $lineoffset += $rep[1]; + } + //-- read the xml from the file + $lines = file($report); + while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<Relatives") === false) && (($lineoffset + $this->repeat_bytes) > 0)) { + $lineoffset--; + } + $lineoffset++; + $reportxml = "<tempdoc>\n"; + $line_nr = $lineoffset + $this->repeat_bytes; + // Relatives Level counter + $count = 1; + while (0 < $count) { + if (strpos($lines[$line_nr], "<Relatives") !== false) { + $count++; + } elseif (strpos($lines[$line_nr], "</Relatives") !== false) { + $count--; + } + if (0 < $count) { + $reportxml .= $lines[$line_nr]; + } + $line_nr++; + } + // No need to drag this + unset($lines); + $reportxml .= "</tempdoc>\n"; + // Save original values + array_push($parserStack, $parser); + $oldgedrec = $this->gedrec; + + $this->list_total = count($this->list); + $this->list_private = 0; + foreach ($this->list as $key => $value) { + if (isset($value->generation)) { + $this->generation = $value->generation; + } + $tmp = GedcomRecord::getInstance($key, $WT_TREE); + $this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)); + + $repeat_parser = xml_parser_create(); + $parser = $repeat_parser; + xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); + xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); + + if (!xml_parse($repeat_parser, $reportxml, true)) { + throw new \DomainException(sprintf("RelativesEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser))); + } + xml_parser_free($repeat_parser); + } + // Clean up the list array + $this->list = array(); + $parser = array_pop($parserStack); + $this->gedrec = $oldgedrec; + } + list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); + } + + /** + * XML <Generation /> element handler + * + * Prints the number of generations + */ + function generationStartHandler() { + $this->current_element->addText($this->generation); + } + + /** + * XML <NewPage /> element handler + * + * Has to be placed in an element (header, pageheader, body or footer) + */ + function newPageStartHandler() { + global $wt_report; + + $temp = "addpage"; + $wt_report->addElement($temp); + } + + /** + * @param array $attrs an array of key value pairs for the attributes + * @param string $tag HTML tag name + */ + function htmlStartHandler($tag, $attrs) { + global $wt_reportStack, $wt_report, $ReportRoot; + + if ($tag === "tempdoc") { + return; + } + array_push($wt_reportStack, $wt_report); + $wt_report = $ReportRoot->createHTML($tag, $attrs); + $this->current_element = $wt_report; + + array_push($this->print_data_stack, $this->print_data); + $this->print_data = true; + } + + /** + * @param string $tag + */ + function htmlEndHandler($tag) { + global $wt_report, $wt_reportStack; + if ($tag === "tempdoc") { + return; + } + + $this->print_data = array_pop($this->print_data_stack); + $this->current_element = $wt_report; + $wt_report = array_pop($wt_reportStack); + if (!is_null($wt_report)) { + $wt_report->addElement($this->current_element); + } else { + $wt_report = $this->current_element; + } + } + + /** + * Handle <Input> + */ + function inputStartHandler() { + // Dummy function, to prevent the default HtmlStartHandler() being called + } + + /** + * Handle </Input> + */ + function inputEndHandler() { + // Dummy function, to prevent the default HtmlEndHandler() being called + } + + /** + * Handle <Report> + */ + function reportStartHandler() { + // Dummy function, to prevent the default HtmlStartHandler() being called + } + + /** + * Handle </Report> + */ + function reportEndHandler() { + // Dummy function, to prevent the default HtmlEndHandler() being called + } + + /** + * XML <titleStartHandler> start element handler + */ + function titleStartHandler() { + $this->report_title = true; + } + + /** + * XML </titleEndHandler> end element handler + */ + function titleEndHandler() { + $this->report_title = false; + } + + /** + * XML <descriptionStartHandler> start element handler + */ + function descriptionStartHandler() { + $this->report_description = true; + } + + /** + * XML </descriptionEndHandler> end element handler + */ + function descriptionEndHandler() { + $this->report_description = false; + } +} diff --git a/app/Report/ReportParserSetup.php b/app/Report/ReportParserSetup.php index 9add49857a..e9180d3433 100644 --- a/app/Report/ReportParserSetup.php +++ b/app/Report/ReportParserSetup.php @@ -20,7 +20,7 @@ use Fisharebest\Webtrees\Date; use Fisharebest\Webtrees\I18N; /** - * Class ReportParserSetup + * Class ReportParserSetup - parse a report.xml file and extract the setup options. */ class ReportParserSetup extends ReportParserBase { /** @var array An array of report options/parameters */ diff --git a/reportengine.php b/reportengine.php index 32882bb363..d58f3665a8 100644 --- a/reportengine.php +++ b/reportengine.php @@ -18,6 +18,7 @@ namespace Fisharebest\Webtrees; use Fisharebest\Webtrees\Controller\PageController; use Fisharebest\Webtrees\Report\ReportBaseElement; use Fisharebest\Webtrees\Report\ReportHtml; +use Fisharebest\Webtrees\Report\ReportParserGenerate; use Fisharebest\Webtrees\Report\ReportPdf; use Fisharebest\Webtrees\Report\ReportParserSetup; @@ -288,264 +289,20 @@ case 'run': } /** - * element handlers array - * - * Converts XML element names into functions - * - * @global string[][] $elementHandler - */ - $elementHandler = array( - 'AgeAtDeath' => array( - 'start' => '\Fisharebest\Webtrees\Report\\ageAtDeathStartHandler', - ), - 'br' => array( - 'start' => '\Fisharebest\Webtrees\Report\\brStartHandler', - ), - 'Body' => array( - 'start' => '\Fisharebest\Webtrees\Report\\bodyStartHandler', - ), - 'Cell' => array( - 'end' => '\Fisharebest\Webtrees\Report\\cellEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\cellStartHandler', - ), - 'Description' => array( - 'end' => '\Fisharebest\Webtrees\Report\\descriptionEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\descriptionStartHandler', - ), - 'Doc' => array( - 'end' => '\Fisharebest\Webtrees\Report\\docEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\docStartHandler', - ), - 'Report' => array( - 'end' => '', - 'start' => '', - ), - 'Facts' => array( - 'end' => '\Fisharebest\Webtrees\Report\\factsEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\factsStartHandler', - ), - 'Footer' => array( - 'start' => '\Fisharebest\Webtrees\Report\\footerStartHandler', - ), - 'Footnote' => array( - 'end' => '\Fisharebest\Webtrees\Report\\footnoteEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\footnoteStartHandler', - ), - 'FootnoteTexts' => array( - 'start' => '\Fisharebest\Webtrees\Report\\footnoteTextsStartHandler', - ), - 'Gedcom' => array( - 'end' => '\Fisharebest\Webtrees\Report\\gedcomEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\gedcomStartHandler', - ), - 'GedcomValue' => array( - 'start' => '\Fisharebest\Webtrees\Report\\gedcomValueStartHandler', - ), - 'Generation' => array( - 'start' => '\Fisharebest\Webtrees\Report\\generationStartHandler', - ), - 'GetPersonName' => array( - 'start' => '\Fisharebest\Webtrees\Report\\getPersonNameStartHandler', - ), - 'Header' => array( - 'start' => '\Fisharebest\Webtrees\Report\\headerStartHandler', - ), - 'HighlightedImage' => array( - 'start' => '\Fisharebest\Webtrees\Report\\highlightedImageStartHandler', - ), - 'if' => array( - 'end' => '\Fisharebest\Webtrees\Report\\ifEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\ifStartHandler', - ), - 'Image' => array( - 'start' => '\Fisharebest\Webtrees\Report\\imageStartHandler', - ), - 'Input' => array( - 'end' => '', - 'start'=> '', - ), - 'Line' => array( - 'start' => '\Fisharebest\Webtrees\Report\\lineStartHandler', - ), - 'List' => array( - 'end' => '\Fisharebest\Webtrees\Report\\listEndHandler', - 'start'=> '\Fisharebest\Webtrees\Report\\listStartHandler', - ), - 'ListTotal' => array( - 'start' => '\Fisharebest\Webtrees\Report\\listTotalStartHandler', - ), - 'NewPage' => array( - 'start' => '\Fisharebest\Webtrees\Report\\newPageStartHandler', - ), - 'Now' => array( - 'start' => '\Fisharebest\Webtrees\Report\\nowStartHandler', - ), - 'PageHeader' => array( - 'end' => '\Fisharebest\Webtrees\Report\\pageHeaderEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\pageHeaderStartHandler', - ), - 'PageNum' => array( - 'start' => '\Fisharebest\Webtrees\Report\\pageNumStartHandler', - ), - 'Relatives' => array( - 'end' => '\Fisharebest\Webtrees\Report\\relativesEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\relativesStartHandler', - ), - 'RepeatTag' => array( - 'end' => '\Fisharebest\Webtrees\Report\\repeatTagEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\repeatTagStartHandler', - ), - 'SetVar' => array( - 'start' => '\Fisharebest\Webtrees\Report\\setVarStartHandler', - ), - 'Style' => array( - 'start' => '\Fisharebest\Webtrees\Report\\styleStartHandler', - ), - 'Text' => array( - 'end' => '\Fisharebest\Webtrees\Report\\textEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\textStartHandler', - ), - 'TextBox' => array( - 'end' => '\Fisharebest\Webtrees\Report\\textBoxEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\textBoxStartHandler', - ), - 'Title' => array( - 'end' => '\Fisharebest\Webtrees\Report\\titleEndHandler', - 'start' => '\Fisharebest\Webtrees\Report\\titleStartHandler', - ), - 'TotalPages' => array( - 'start' => '\Fisharebest\Webtrees\Report\\totalPagesStartHandler', - ), - 'var' => array( - 'start' => '\Fisharebest\Webtrees\Report\\varStartHandler', - ), - 'sp' => array( - 'start' => '\Fisharebest\Webtrees\Report\\spStartHandler', - ), - ); - - /** - * A new object of the currently used element class - * - * @global ReportBaseElement $currentElement - */ - $currentElement = new ReportBaseElement; - - /** - * Should character data be printed - * - * This variable is turned on or off by the element handlers to tell whether the inner character - * Data should be printed - * - * @global boolean $printData - */ - $printData = false; - - /** - * Title collector. Mark it if it has already been used - * - * @global boolean $reportTitle - */ - $reportTitle = false; - - /** - * Description collector. Mark it if it has already been used - * - * @global boolean $reportDescription - */ - $reportDescription = false; - - /** - * Print data stack - * - * As the XML is being processed there will be times when we need to turn on and off the - * <var>$printData</var> variable as we encounter entinties in the XML. The stack allows us to - * keep track of when to turn <var>$printData</var> on and off. - * - * @global array $printDataStack - */ - $printDataStack = array(); - - /** * @global array $wt_reportStack */ $wt_reportStack = array(); /** - * @global array $gedrecStack - */ - $gedrecStack = array(); - - /** - * @global array $repeatsStack - */ - $repeatsStack = array(); - - /** * @global array $parserStack */ $parserStack = array(); /** - * @global array $repeats - */ - $repeats = array(); - - /** - * @global string $gedrec - */ - $gedrec = ''; - - /** - * @global ???? $repeatBytes - */ - $repeatBytes = 0; - - /** * @global resource $parser */ $parser = ''; - /** - * @global int $processRepeats - */ - $processRepeats = 0; - - /** - * @global ???? $processIfs - */ - $processIfs = 0; - - /** - * @global ???? $processGedcoms - */ - $processGedcoms = 0; - - /** - * Wether or not to print footnote - * true = print, false = don't print - */ - $processFootnote = true; - - //-- start the sax parser - $xml_parser = xml_parser_create(); - //-- make sure everything is case sensitive - xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); - //-- set the main element handler functions - xml_set_element_handler($xml_parser, '\Fisharebest\Webtrees\Report\\startElement', '\Fisharebest\Webtrees\Report\\endElement'); - //-- set the character data handler - xml_set_character_data_handler($xml_parser, '\Fisharebest\Webtrees\Report\\characterData'); - - $fp = fopen($report, 'r'); - while (($data = fread($fp, 4096))) { - if (!xml_parse($xml_parser, $data, feof($fp))) { - throw new \DomainException(sprintf( - 'XML error: %s at line %d', - xml_error_string(xml_get_error_code($xml_parser)), - xml_get_current_line_number($xml_parser) - )); - } - } - xml_parser_free($xml_parser); + new ReportParserGenerate($report); } |
