1<?php 2namespace Fisharebest\Webtrees\Report; 3 4/** 5 * webtrees: online genealogy 6 * Copyright (C) 2015 webtrees development team 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18use Fisharebest\Webtrees\Auth; 19use Fisharebest\Webtrees\Database; 20use Fisharebest\Webtrees\Date; 21use Fisharebest\Webtrees\Family; 22use Fisharebest\Webtrees\GedcomRecord; 23use Fisharebest\Webtrees\GedcomTag; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Individual; 26use Fisharebest\Webtrees\Media; 27use Fisharebest\Webtrees\Place; 28 29/** 30 * Class ReportParserGenerate - parse a report.xml file and generate the report. 31 */ 32class ReportParserGenerate extends ReportParserBase { 33 /** @var bool Are we processing a <Description> element */ 34 private $report_description = false; 35 36 /** @var bool Are we processing a <Title> element */ 37 private $report_title = false; 38 39 /** @var bool Are we collecting data from <Footnote> elements */ 40 private $process_footnote = true; 41 42 /** @var bool Are we currently outputing data? */ 43 private $print_data = false; 44 45 /** @var bool[] Push-down stack of $print_data */ 46 private $print_data_stack = array(); 47 48 /** @var int */ 49 private $process_gedcoms = 0; 50 51 /** @var int */ 52 private $process_ifs = 0; 53 54 /** @var int */ 55 private $process_repeats = 0; 56 57 /** @var int Quantity of data to repeat during loops */ 58 private $repeat_bytes = 0; 59 60 /** @var array[] Repeated data when iterating over loops */ 61 private $repeats = array(); 62 63 /** @var array[] Nested repeating data */ 64 private $repeats_stack = array(); 65 66 /** @var string The current GEDCOM record */ 67 private $gedrec = ''; 68 69 /** @var string[] Nested GEDCOM records */ 70 private $gedrec_stack = array(); 71 72 /** @var ReportBaseElement The currently processed element */ 73 private $current_element; 74 75 /** @var ReportBaseElement The currently processed element */ 76 private $footnote_element; 77 78 /** @var string The GEDCOM fact currently being processed */ 79 private $fact = ''; 80 81 /** @var string The GEDCOM value currently being processed */ 82 private $desc = ''; 83 84 /** @var string The GEDCOM type currently being processed */ 85 private $type = ''; 86 87 /** @var int The current generational level */ 88 private $generation = 1; 89 90 /** @var array Source data for processing lists */ 91 private $list = array(); 92 93 /** @var int Number of items in lists */ 94 private $list_total = 0; 95 96 /** @var int Number of items filtered from lists */ 97 private $list_private = 0; 98 99 /** {@inheritDoc} */ 100 public function __construct($report) { 101 $this->current_element = new ReportBaseElement; 102 parent::__construct($report); 103 } 104 105 /** 106 *XML start element handler 107 * 108 * This function is called whenever a starting element is reached 109 * The element handler will be called if found, otherwise it must be HTML 110 * 111 * @param resource $parser the resource handler for the XML parser 112 * @param string $name the name of the XML element parsed 113 * @param array $attrs an array of key value pairs for the attributes 114 */ 115 protected function startElement($parser, $name, $attrs) { 116 global $vars; 117 118 $newattrs = array(); 119 120 foreach ($attrs as $key => $value) { 121 if (preg_match("/^\\$(\w+)$/", $value, $match)) { 122 if ((isset($vars[$match[1]]['id'])) && (!isset($vars[$match[1]]['gedcom']))) { 123 $value = $vars[$match[1]]['id']; 124 } 125 } 126 $newattrs[$key] = $value; 127 } 128 $attrs = $newattrs; 129 if ($this->process_footnote && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag")) { 130 $start_method = $name . 'StartHandler'; 131 $end_method = $name . 'EndHandler'; 132 if (method_exists($this, $start_method)) { 133 $this->$start_method($attrs); 134 } elseif (!method_exists($this, $end_method)) { 135 $this->htmlStartHandler($name, $attrs); 136 } 137 } 138 } 139 140 /** 141 * XML end element handler 142 * 143 * This function is called whenever an ending element is reached 144 * The element handler will be called if found, otherwise it must be HTML 145 * 146 * @param resource $parser the resource handler for the XML parser 147 * @param string $name the name of the XML element parsed 148 */ 149 protected function endElement($parser, $name) { 150 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")) { 151 $start_method = $name . 'StartHandler'; 152 $end_method = $name . 'EndHandler'; 153 if (method_exists($this, $end_method)) { 154 $this->$end_method(); 155 } elseif (!method_exists($this, $start_method)) { 156 $this->htmlEndHandler($name); 157 } 158 } 159 } 160 161 /** 162 * XML character data handler 163 * 164 * @param resource $parser the resource handler for the XML parser 165 * @param string $data the name of the XML element parsed 166 */ 167 protected function characterData($parser, $data) { 168 global $wt_report; 169 170 if ($this->print_data && ($this->process_gedcoms === 0) && ($this->process_ifs === 0) && ($this->process_repeats === 0)) { 171 $this->current_element->addText($data); 172 } elseif ($this->report_title) { 173 $wt_report->addTitle($data); 174 } elseif ($this->report_description) { 175 $wt_report->addDescription($data); 176 } 177 } 178 179 /** 180 * XML <style> start element handler 181 * 182 * @param array $attrs an array of key value pairs for the attributes 183 */ 184 private function styleStartHandler($attrs) { 185 global $wt_report; 186 187 if (empty($attrs['name'])) { 188 throw new \DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.'); 189 } 190 191 // array Style that will be passed on 192 $s = array(); 193 194 // string Name af the style 195 $s['name'] = $attrs['name']; 196 197 // string Name of the DEFAULT font 198 $s['font'] = $wt_report->defaultFont; 199 if (!empty($attrs['font'])) { 200 $s['font'] = $attrs['font']; 201 } 202 203 // int The size of the font in points 204 $s['size'] = $wt_report->defaultFontSize; 205 if (!empty($attrs['size'])) { 206 $s['size'] = (int) $attrs['size']; 207 } // Get it as int to ignore all decimal points or text (if any text then int(0)) 208 209 // string B: bold, I: italic, U: underline, D: line trough, The default value is regular. 210 $s['style'] = ""; 211 if (!empty($attrs['style'])) { 212 $s['style'] = $attrs['style']; 213 } 214 215 $wt_report->addStyle($s); 216 } 217 218 /** 219 * XML <Doc> start element handler 220 * 221 * Sets up the basics of the document proparties 222 * 223 * @param array $attrs an array of key value pairs for the attributes 224 */ 225 private function docStartHandler($attrs) { 226 global $parser, $wt_report; 227 228 $parser = $this->xml_parser; 229 230 // Custom page width 231 if (!empty($attrs['customwidth'])) { 232 $wt_report->pagew = (int) $attrs['customwidth']; 233 } // Get it as int to ignore all decimal points or text (if any text then int(0)) 234 // Custom Page height 235 if (!empty($attrs['customheight'])) { 236 $wt_report->pageh = (int) $attrs['customheight']; 237 } // Get it as int to ignore all decimal points or text (if any text then int(0)) 238 239 // Left Margin 240 if (isset($attrs['leftmargin'])) { 241 if ($attrs['leftmargin'] === "0") { 242 $wt_report->leftmargin = 0; 243 } elseif (!empty($attrs['leftmargin'])) { 244 $wt_report->leftmargin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 245 } 246 } 247 // Right Margin 248 if (isset($attrs['rightmargin'])) { 249 if ($attrs['rightmargin'] === "0") { 250 $wt_report->rightmargin = 0; 251 } elseif (!empty($attrs['rightmargin'])) { 252 $wt_report->rightmargin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 253 } 254 } 255 // Top Margin 256 if (isset($attrs['topmargin'])) { 257 if ($attrs['topmargin'] === "0") { 258 $wt_report->topmargin = 0; 259 } elseif (!empty($attrs['topmargin'])) { 260 $wt_report->topmargin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 261 } 262 } 263 // Bottom Margin 264 if (isset($attrs['bottommargin'])) { 265 if ($attrs['bottommargin'] === "0") { 266 $wt_report->bottommargin = 0; 267 } elseif (!empty($attrs['bottommargin'])) { 268 $wt_report->bottommargin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 269 } 270 } 271 // Header Margin 272 if (isset($attrs['headermargin'])) { 273 if ($attrs['headermargin'] === "0") { 274 $wt_report->headermargin = 0; 275 } elseif (!empty($attrs['headermargin'])) { 276 $wt_report->headermargin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 277 } 278 } 279 // Footer Margin 280 if (isset($attrs['footermargin'])) { 281 if ($attrs['footermargin'] === "0") { 282 $wt_report->footermargin = 0; 283 } elseif (!empty($attrs['footermargin'])) { 284 $wt_report->footermargin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 285 } 286 } 287 288 // Page Orientation 289 if (!empty($attrs['orientation'])) { 290 if ($attrs['orientation'] == "landscape") { 291 $wt_report->orientation = "landscape"; 292 } elseif ($attrs['orientation'] == "portrait") { 293 $wt_report->orientation = "portrait"; 294 } 295 } 296 // Page Size 297 if (!empty($attrs['pageSize'])) { 298 $wt_report->pageFormat = strtoupper($attrs['pageSize']); 299 } 300 301 // Show Generated By... 302 if (isset($attrs['showGeneratedBy'])) { 303 if ($attrs['showGeneratedBy'] === "0") { 304 $wt_report->showGenText = false; 305 } elseif ($attrs['showGeneratedBy'] === "1") { 306 $wt_report->showGenText = true; 307 } 308 } 309 310 $wt_report->setup(); 311 } 312 313 /** 314 * XML </Doc> end element handler 315 */ 316 private function docEndHandler() { 317 global $wt_report; 318 $wt_report->run(); 319 } 320 321 /** 322 * XML <Header> start element handler 323 */ 324 private function headerStartHandler() { 325 global $wt_report; 326 327 // Clear the Header before any new elements are added 328 $wt_report->clearHeader(); 329 $wt_report->setProcessing("H"); 330 } 331 332 /** 333 * XML <PageHeader> start element handler 334 */ 335 private function pageHeaderStartHandler() { 336 global $wt_reportStack, $wt_report, $ReportRoot; 337 338 array_push($this->print_data_stack, $this->print_data); 339 $this->print_data = false; 340 array_push($wt_reportStack, $wt_report); 341 $wt_report = $ReportRoot->createPageHeader(); 342 } 343 344 /** 345 * XML <pageHeaderEndHandler> end element handler 346 */ 347 private function pageHeaderEndHandler() { 348 global $wt_report, $wt_reportStack; 349 350 $this->print_data = array_pop($this->print_data_stack); 351 $this->current_element = $wt_report; 352 $wt_report = array_pop($wt_reportStack); 353 $wt_report->addElement($this->current_element); 354 } 355 356 /** 357 * XML <bodyStartHandler> start element handler 358 */ 359 private function bodyStartHandler() { 360 global $wt_report; 361 $wt_report->setProcessing("B"); 362 } 363 364 /** 365 * XML <footerStartHandler> start element handler 366 */ 367 private function footerStartHandler() { 368 global $wt_report; 369 $wt_report->setProcessing("F"); 370 } 371 372 /** 373 * XML <Cell> start element handler 374 * 375 * @param array $attrs an array of key value pairs for the attributes 376 */ 377 private function cellStartHandler($attrs) { 378 global $ReportRoot, $wt_report; 379 380 // string The text alignment of the text in this box. 381 $align = ""; 382 if (!empty($attrs['align'])) { 383 $align = $attrs['align']; 384 // RTL supported left/right alignment 385 if ($align == "rightrtl") { 386 if ($wt_report->rtl) { 387 $align = "left"; 388 } else { 389 $align = "right"; 390 } 391 } elseif ($align == "leftrtl") { 392 if ($wt_report->rtl) { 393 $align = "right"; 394 } else { 395 $align = "left"; 396 } 397 } 398 } 399 400 // string The color to fill the background of this cell 401 $bgcolor = ""; 402 if (!empty($attrs['bgcolor'])) { 403 $bgcolor = $attrs['bgcolor']; 404 } 405 406 // int Whether or not the background should be painted 407 $fill = 1; 408 if (isset($attrs['fill'])) { 409 if ($attrs['fill'] === "0") { 410 $fill = 0; 411 } elseif ($attrs['fill'] === "1") { 412 $fill = 1; 413 } 414 } 415 416 $reseth = true; 417 // boolean if true reset the last cell height (default true) 418 if (isset($attrs['reseth'])) { 419 if ($attrs['reseth'] === "0") { 420 $reseth = false; 421 } elseif ($attrs['reseth'] === "1") { 422 $reseth = true; 423 } 424 } 425 426 // mixed Whether or not a border should be printed around this box 427 $border = 0; 428 if (!empty($attrs['border'])) { 429 $border = $attrs['border']; 430 } 431 // string Border color in HTML code 432 $bocolor = ""; 433 if (!empty($attrs['bocolor'])) { 434 $bocolor = $attrs['bocolor']; 435 } 436 437 // int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted. 438 $height = 0; 439 if (!empty($attrs['height'])) { 440 $height = (int) $attrs['height']; 441 } 442 // int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin. 443 $width = 0; 444 if (!empty($attrs['width'])) { 445 $width = (int) $attrs['width']; 446 } 447 448 // int Stretch carachter mode 449 $stretch = 0; 450 if (!empty($attrs['stretch'])) { 451 $stretch = (int) $attrs['stretch']; 452 } 453 454 // mixed Position the left corner of this box on the page. The default is the current position. 455 $left = "."; 456 if (isset($attrs['left'])) { 457 if ($attrs['left'] === ".") { 458 $left = "."; 459 } elseif (!empty($attrs['left'])) { 460 $left = (int) $attrs['left']; 461 } elseif ($attrs['left'] === "0") { 462 $left = 0; 463 } 464 } 465 // mixed Position the top corner of this box on the page. the default is the current position 466 $top = "."; 467 if (isset($attrs['top'])) { 468 if ($attrs['top'] === ".") { 469 $top = "."; 470 } elseif (!empty($attrs['top'])) { 471 $top = (int) $attrs['top']; 472 } elseif ($attrs['top'] === "0") { 473 $top = 0; 474 } 475 } 476 477 // string The name of the Style that should be used to render the text. 478 $style = ""; 479 if (!empty($attrs['style'])) { 480 $style = $attrs['style']; 481 } 482 483 // string Text color in html code 484 $tcolor = ""; 485 if (!empty($attrs['tcolor'])) { 486 $tcolor = $attrs['tcolor']; 487 } 488 489 // int Indicates where the current position should go after the call. 490 $ln = 0; 491 if (isset($attrs['newline'])) { 492 if (!empty($attrs['newline'])) { 493 $ln = (int) $attrs['newline']; 494 } elseif ($attrs['newline'] === "0") { 495 $ln = 0; 496 } 497 } 498 499 if ($align == "left") { 500 $align = "L"; 501 } elseif ($align == "right") { 502 $align = "R"; 503 } elseif ($align == "center") { 504 $align = "C"; 505 } elseif ($align == "justify") { 506 $align = "J"; 507 } 508 509 array_push($this->print_data_stack, $this->print_data); 510 $this->print_data = true; 511 512 $this->current_element = $ReportRoot->createCell( 513 $width, 514 $height, 515 $border, 516 $align, 517 $bgcolor, 518 $style, 519 $ln, 520 $top, 521 $left, 522 $fill, 523 $stretch, 524 $bocolor, 525 $tcolor, 526 $reseth 527 ); 528 } 529 530 /** 531 * XML </Cell> end element handler 532 */ 533 private function cellEndHandler() { 534 global $wt_report; 535 536 $this->print_data = array_pop($this->print_data_stack); 537 $wt_report->addElement($this->current_element); 538 } 539 540 /** 541 * XML <Now /> element handler 542 */ 543 private function nowStartHandler() { 544 $g = timestamp_to_gedcom_date(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET); 545 $this->current_element->addText($g->display()); 546 } 547 548 /** 549 * XML <PageNum /> element handler 550 */ 551 private function pageNumStartHandler() { 552 $this->current_element->addText("#PAGENUM#"); 553 } 554 555 /** 556 * XML <TotalPages /> element handler 557 */ 558 private function totalPagesStartHandler() { 559 $this->current_element->addText("{{:ptp:}}"); 560 } 561 562 /** 563 * Called at the start of an element. 564 * 565 * @param array $attrs an array of key value pairs for the attributes 566 */ 567 private function gedcomStartHandler($attrs) { 568 global $vars, $WT_TREE; 569 570 if ($this->process_gedcoms > 0) { 571 $this->process_gedcoms++; 572 573 return; 574 } 575 576 $tag = $attrs['id']; 577 $tag = str_replace("@fact", $this->fact, $tag); 578 $tags = explode(":", $tag); 579 $newgedrec = ''; 580 if (count($tags) < 2) { 581 $tmp = GedcomRecord::getInstance($attrs['id'], $WT_TREE); 582 $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; 583 } 584 if (empty($newgedrec)) { 585 $tgedrec = $this->gedrec; 586 $newgedrec = ''; 587 foreach ($tags as $tag) { 588 if (preg_match("/\\$(.+)/", $tag, $match)) { 589 if (isset($vars[$match[1]]['gedcom'])) { 590 $newgedrec = $vars[$match[1]]['gedcom']; 591 } else { 592 $tmp = GedcomRecord::getInstance($match[1], $WT_TREE); 593 $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; 594 } 595 } else { 596 if (preg_match("/@(.+)/", $tag, $match)) { 597 $gmatch = array(); 598 if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) { 599 $tmp = GedcomRecord::getInstance($gmatch[1], $WT_TREE); 600 $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : ''; 601 $tgedrec = $newgedrec; 602 } else { 603 $newgedrec = ''; 604 break; 605 } 606 } else { 607 $temp = explode(" ", trim($tgedrec)); 608 $level = $temp[0] + 1; 609 $newgedrec = get_sub_record($level, "$level $tag", $tgedrec); 610 $tgedrec = $newgedrec; 611 } 612 } 613 } 614 } 615 if (!empty($newgedrec)) { 616 array_push($this->gedrec_stack, array($this->gedrec, $this->fact, $this->desc)); 617 $this->gedrec = $newgedrec; 618 if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $this->gedrec, $match)) { 619 $this->fact = $match[2]; 620 $this->desc = trim($match[3]); 621 } 622 } else { 623 $this->process_gedcoms++; 624 } 625 } 626 627 /** 628 * Called at the end of an element. 629 */ 630 private function gedcomEndHandler() { 631 if ($this->process_gedcoms > 0) { 632 $this->process_gedcoms--; 633 } else { 634 list($this->gedrec, $this->fact, $this->desc) = array_pop($this->gedrec_stack); 635 } 636 } 637 638 /** 639 * XML <textBoxStartHandler> start element handler 640 * 641 * @param array $attrs an array of key value pairs for the attributes 642 */ 643 private function textBoxStartHandler($attrs) { 644 global $wt_report, $wt_reportStack, $ReportRoot; 645 646 // string Background color code 647 $bgcolor = ""; 648 if (!empty($attrs['bgcolor'])) { 649 $bgcolor = $attrs['bgcolor']; 650 } 651 652 // boolean Wether or not fill the background color 653 $fill = true; 654 if (isset($attrs['fill'])) { 655 if ($attrs['fill'] === "0") { 656 $fill = false; 657 } elseif ($attrs['fill'] === "1") { 658 $fill = true; 659 } 660 } 661 662 // var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0 663 $border = false; 664 if (isset($attrs['border'])) { 665 if ($attrs['border'] === "1") { 666 $border = true; 667 } elseif ($attrs['border'] === "0") { 668 $border = false; 669 } 670 } 671 672 // int The starting height of this cell. If the text wraps the height will automatically be adjusted 673 $height = 0; 674 if (!empty($attrs['height'])) { 675 $height = (int) $attrs['height']; 676 } 677 // int Setting the width to 0 will make it the width from the current location to the margin 678 $width = 0; 679 if (!empty($attrs['width'])) { 680 $width = (int) $attrs['width']; 681 } 682 683 // mixed Position the left corner of this box on the page. The default is the current position. 684 $left = "."; 685 if (isset($attrs['left'])) { 686 if ($attrs['left'] === ".") { 687 $left = "."; 688 } elseif (!empty($attrs['left'])) { 689 $left = (int) $attrs['left']; 690 } elseif ($attrs['left'] === "0") { 691 $left = 0; 692 } 693 } 694 // mixed Position the top corner of this box on the page. the default is the current position 695 $top = "."; 696 if (isset($attrs['top'])) { 697 if ($attrs['top'] === ".") { 698 $top = "."; 699 } elseif (!empty($attrs['top'])) { 700 $top = (int) $attrs['top']; 701 } elseif ($attrs['top'] === "0") { 702 $top = 0; 703 } 704 } 705 // 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 706 $newline = false; 707 if (isset($attrs['newline'])) { 708 if ($attrs['newline'] === "1") { 709 $newline = true; 710 } elseif ($attrs['newline'] === "0") { 711 $newline = false; 712 } 713 } 714 // boolean 715 $pagecheck = true; 716 if (isset($attrs['pagecheck'])) { 717 if ($attrs['pagecheck'] === "0") { 718 $pagecheck = false; 719 } elseif ($attrs['pagecheck'] === "1") { 720 $pagecheck = true; 721 } 722 } 723 // boolean Cell padding 724 $padding = true; 725 if (isset($attrs['padding'])) { 726 if ($attrs['padding'] === "0") { 727 $padding = false; 728 } elseif ($attrs['padding'] === "1") { 729 $padding = true; 730 } 731 } 732 // boolean Reset this box Height 733 $reseth = false; 734 if (isset($attrs['reseth'])) { 735 if ($attrs['reseth'] === "1") { 736 $reseth = true; 737 } elseif ($attrs['reseth'] === "0") { 738 $reseth = false; 739 } 740 } 741 742 // string Style of rendering 743 $style = ""; 744 745 array_push($this->print_data_stack, $this->print_data); 746 $this->print_data = false; 747 748 array_push($wt_reportStack, $wt_report); 749 $wt_report = $ReportRoot->createTextBox( 750 $width, 751 $height, 752 $border, 753 $bgcolor, 754 $newline, 755 $left, 756 $top, 757 $pagecheck, 758 $style, 759 $fill, 760 $padding, 761 $reseth 762 ); 763 } 764 765 /** 766 * XML <textBoxEndHandler> end element handler 767 */ 768 private function textBoxEndHandler() { 769 global $wt_report, $wt_reportStack; 770 771 $this->print_data = array_pop($this->print_data_stack); 772 $this->current_element = $wt_report; 773 $wt_report = array_pop($wt_reportStack); 774 $wt_report->addElement($this->current_element); 775 } 776 777 /** 778 * @param array $attrs an array of key value pairs for the attributes 779 */ 780 private function textStartHandler($attrs) { 781 global $ReportRoot; 782 783 array_push($this->print_data_stack, $this->print_data); 784 $this->print_data = true; 785 786 // string The name of the Style that should be used to render the text. 787 $style = ""; 788 if (!empty($attrs['style'])) { 789 $style = $attrs['style']; 790 } 791 792 // string The color of the text - Keep the black color as default 793 $color = ""; 794 if (!empty($attrs['color'])) { 795 $color = $attrs['color']; 796 } 797 798 $this->current_element = $ReportRoot->createText($style, $color); 799 } 800 801 /** 802 * 803 */ 804 private function textEndHandler() { 805 global $wt_report; 806 807 $this->print_data = array_pop($this->print_data_stack); 808 $wt_report->addElement($this->current_element); 809 } 810 811 /** 812 * XML <GetPersonName> start element handler 813 * Get the name 814 * 1. id is empty - current GEDCOM record 815 * 2. id is set with a record id 816 * 817 * @param array $attrs an array of key value pairs for the attributes 818 */ 819 private function getPersonNameStartHandler($attrs) { 820 global $vars, $WT_TREE; 821 822 $id = ""; 823 $match = array(); 824 if (empty($attrs['id'])) { 825 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 826 $id = $match[1]; 827 } 828 } else { 829 if (preg_match("/\\$(.+)/", $attrs['id'], $match)) { 830 if (isset($vars[$match[1]]['id'])) { 831 $id = $vars[$match[1]]['id']; 832 } 833 } else { 834 if (preg_match("/@(.+)/", $attrs['id'], $match)) { 835 $gmatch = array(); 836 if (preg_match("/\d $match[1] @([^@]+)@/", $this->gedrec, $gmatch)) { 837 $id = $gmatch[1]; 838 } 839 } else { 840 $id = $attrs['id']; 841 } 842 } 843 } 844 if (!empty($id)) { 845 $record = GedcomRecord::getInstance($id, $WT_TREE); 846 if (is_null($record)) { 847 return; 848 } 849 if (!$record->canShowName()) { 850 $this->current_element->addText(I18N::translate('Private')); 851 } else { 852 $name = $record->getFullName(); 853 $name = preg_replace( 854 array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'), 855 array('«', '', '»'), 856 $name 857 ); 858 $name = strip_tags($name); 859 if (!empty($attrs['truncate'])) { 860 if (mb_strlen($name) > $attrs['truncate']) { 861 $name = preg_replace("/\(.*\) ?/", '', $name); //removes () and text inbetween - what about ", [ and { etc? 862 $words = preg_split('/[, -]+/', $name); // names separated with space, comma or hyphen - any others? 863 $name = $words[count($words) - 1]; 864 for ($i = count($words) - 2; $i >= 0; $i--) { 865 $len = mb_strlen($name); 866 for ($j = count($words) - 3; $j >= 0; $j--) { 867 $len += mb_strlen($words[$j]); 868 } 869 if ($len > $attrs['truncate']) { 870 $first_letter = mb_substr($words[$i], 0, 1); 871 // Do not show " of nick-names 872 if ($first_letter != "\"") { 873 $name = mb_substr($words[$i], 0, 1) . '. ' . $name; 874 } 875 } else { 876 $name = $words[$i] . ' ' . $name; 877 } 878 } 879 } 880 } else { 881 $addname = $record->getAddName(); 882 $addname = preg_replace( 883 array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'), 884 array('«', '', '»'), 885 $addname 886 ); 887 $addname = strip_tags($addname); 888 if (!empty($addname)) { 889 $name .= " " . $addname; 890 } 891 } 892 $this->current_element->addText(trim($name)); 893 } 894 } 895 } 896 897 /** 898 * XML <GedcomValue> start element handler 899 * 900 * @param array $attrs an array of key value pairs for the attributes 901 */ 902 private function gedcomValueStartHandler($attrs) { 903 global $WT_TREE; 904 905 $id = ""; 906 $match = array(); 907 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 908 $id = $match[1]; 909 } 910 911 if (isset($attrs['newline']) && $attrs['newline'] == "1") { 912 $useBreak = "1"; 913 } else { 914 $useBreak = "0"; 915 } 916 917 $tag = $attrs['tag']; 918 if (!empty($tag)) { 919 if ($tag == "@desc") { 920 $value = $this->desc; 921 $value = trim($value); 922 $this->current_element->addText($value); 923 } 924 if ($tag == "@id") { 925 $this->current_element->addText($id); 926 } else { 927 $tag = str_replace("@fact", $this->fact, $tag); 928 if (empty($attrs['level'])) { 929 $temp = explode(" ", trim($this->gedrec)); 930 $level = $temp[0]; 931 if ($level == 0) { 932 $level++; 933 } 934 } else { 935 $level = $attrs['level']; 936 } 937 $tags = preg_split('/[: ]/', $tag); 938 $value = get_gedcom_value($tag, $level, $this->gedrec); 939 switch (end($tags)) { 940 case 'DATE': 941 $tmp = new Date($value); 942 $value = $tmp->display(); 943 break; 944 case 'PLAC': 945 $tmp = new Place($value, $WT_TREE); 946 $value = $tmp->getShortName(); 947 break; 948 } 949 if ($useBreak == "1") { 950 // Insert <br> when multiple dates exist. 951 // This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages 952 $value = str_replace('(', '<br>(', $value); 953 $value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value); 954 $value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value); 955 if (substr($value, 0, 6) == '<br>') { 956 $value = substr($value, 6); 957 } 958 } 959 $this->current_element->addText($value); 960 } 961 } 962 } 963 964 /** 965 * XML <RepeatTag> start element handler 966 * 967 * @param array $attrs an array of key value pairs for the attributes 968 */ 969 private function repeatTagStartHandler($attrs) { 970 global $parser; 971 972 $this->process_repeats++; 973 if ($this->process_repeats > 1) { 974 return; 975 } 976 977 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 978 $this->repeats = array(); 979 $this->repeat_bytes = xml_get_current_line_number($parser); 980 981 $tag = ""; 982 if (isset($attrs['tag'])) { 983 $tag = $attrs['tag']; 984 } 985 if (!empty($tag)) { 986 if ($tag == "@desc") { 987 $value = $this->desc; 988 $value = trim($value); 989 $this->current_element->addText($value); 990 } else { 991 $tag = str_replace("@fact", $this->fact, $tag); 992 $tags = explode(":", $tag); 993 $temp = explode(" ", trim($this->gedrec)); 994 $level = $temp[0]; 995 if ($level == 0) { 996 $level++; 997 } 998 $subrec = $this->gedrec; 999 $t = $tag; 1000 $count = count($tags); 1001 $i = 0; 1002 while ($i < $count) { 1003 $t = $tags[$i]; 1004 if (!empty($t)) { 1005 if ($i < ($count - 1)) { 1006 $subrec = get_sub_record($level, "$level $t", $subrec); 1007 if (empty($subrec)) { 1008 $level--; 1009 $subrec = get_sub_record($level, "@ $t", $this->gedrec); 1010 if (empty($subrec)) { 1011 return; 1012 } 1013 } 1014 } 1015 $level++; 1016 } 1017 $i++; 1018 } 1019 $level--; 1020 $count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER); 1021 $i = 0; 1022 while ($i < $count) { 1023 $this->repeats[] = get_sub_record($level, "$level $t", $subrec, $i + 1); 1024 $i++; 1025 } 1026 } 1027 } 1028 } 1029 1030 /** 1031 * XML </ RepeatTag> end element handler 1032 */ 1033 private function repeatTagEndHandler() { 1034 global $parser, $parserStack, $report; 1035 1036 $this->process_repeats--; 1037 if ($this->process_repeats > 0) { 1038 return; 1039 } 1040 1041 // Check if there is anything to repeat 1042 if (count($this->repeats) > 0) { 1043 // No need to load them if not used... 1044 1045 $lineoffset = 0; 1046 foreach ($this->repeats_stack as $rep) { 1047 $lineoffset += $rep[1]; 1048 } 1049 //-- read the xml from the file 1050 $lines = file($report); 1051 while (strpos($lines[$lineoffset + $this->repeat_bytes], "<RepeatTag") === false) { 1052 $lineoffset--; 1053 } 1054 $lineoffset++; 1055 $reportxml = "<tempdoc>\n"; 1056 $line_nr = $lineoffset + $this->repeat_bytes; 1057 // RepeatTag Level counter 1058 $count = 1; 1059 while (0 < $count) { 1060 if (strstr($lines[$line_nr], "<RepeatTag") !== false) { 1061 $count++; 1062 } elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) { 1063 $count--; 1064 } 1065 if (0 < $count) { 1066 $reportxml .= $lines[$line_nr]; 1067 } 1068 $line_nr++; 1069 } 1070 // No need to drag this 1071 unset($lines); 1072 $reportxml .= "</tempdoc>\n"; 1073 // Save original values 1074 array_push($parserStack, $parser); 1075 $oldgedrec = $this->gedrec; 1076 foreach ($this->repeats as $gedrec) { 1077 $this->gedrec = $gedrec; 1078 $repeat_parser = xml_parser_create(); 1079 $parser = $repeat_parser; 1080 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 1081 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 1082 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 1083 if (!xml_parse($repeat_parser, $reportxml, true)) { 1084 throw new \DomainException(sprintf( 1085 'RepeatTagEHandler XML error: %s at line %d', 1086 xml_error_string(xml_get_error_code($repeat_parser)), 1087 xml_get_current_line_number($repeat_parser) 1088 )); 1089 } 1090 xml_parser_free($repeat_parser); 1091 } 1092 // Restore original values 1093 $this->gedrec = $oldgedrec; 1094 $parser = array_pop($parserStack); 1095 } 1096 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 1097 } 1098 1099 /** 1100 * Variable lookup 1101 * 1102 * Retrieve predefined variables : 1103 * 1104 * @ desc GEDCOM fact description, example: 1105 * 1 EVEN This is a description 1106 * @ fact GEDCOM fact tag, such as BIRT, DEAT etc. 1107 * $ I18N::translate('....') 1108 * $ language_settings[] 1109 * 1110 * @param array $attrs an array of key value pairs for the attributes 1111 */ 1112 private function varStartHandler($attrs) { 1113 global $parser, $vars; 1114 1115 if (empty($attrs['var'])) { 1116 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)); 1117 } 1118 1119 $var = $attrs['var']; 1120 // SetVar element preset variables 1121 if (!empty($vars[$var]['id'])) { 1122 $var = $vars[$var]['id']; 1123 } else { 1124 $tfact = $this->fact; 1125 if (($this->fact === "EVEN" || $this->fact === "FACT") && $this->type !== " ") { 1126 // Use : 1127 // n TYPE This text if string 1128 $tfact = $this->type; 1129 } 1130 $var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $this->desc), $var); 1131 if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) { 1132 $var = I18N::number($match[1]); 1133 } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) { 1134 $var = I18N::translate($match[1]); 1135 } elseif (preg_match('/^I18N::translate_c\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) { 1136 $var = I18N::translateContext($match[1], $match[2]); 1137 } 1138 } 1139 // Check if variable is set as a date and reformat the date 1140 if (isset($attrs['date'])) { 1141 if ($attrs['date'] === "1") { 1142 $g = new Date($var); 1143 $var = $g->display(); 1144 } 1145 } 1146 $this->current_element->addText($var); 1147 } 1148 1149 /** 1150 * @param array $attrs an array of key value pairs for the attributes 1151 */ 1152 private function factsStartHandler($attrs) { 1153 global $parser, $vars, $WT_TREE; 1154 1155 $this->process_repeats++; 1156 if ($this->process_repeats > 1) { 1157 return; 1158 } 1159 1160 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 1161 $this->repeats = array(); 1162 $this->repeat_bytes = xml_get_current_line_number($parser); 1163 1164 $id = ""; 1165 $match = array(); 1166 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1167 $id = $match[1]; 1168 } 1169 $tag = ""; 1170 if (isset($attrs['ignore'])) { 1171 $tag .= $attrs['ignore']; 1172 } 1173 if (preg_match("/\\$(.+)/", $tag, $match)) { 1174 $tag = $vars[$match[1]]['id']; 1175 } 1176 1177 $record = GedcomRecord::getInstance($id, $WT_TREE); 1178 if (empty($attrs['diff']) && !empty($id)) { 1179 $facts = $record->getFacts(); 1180 sort_facts($facts); 1181 $this->repeats = array(); 1182 $nonfacts = explode(',', $tag); 1183 foreach ($facts as $event) { 1184 if (!in_array($event->getTag(), $nonfacts)) { 1185 $this->repeats[] = $event->getGedcom(); 1186 } 1187 } 1188 } else { 1189 foreach ($record->getFacts() as $fact) { 1190 if ($fact->isPendingAddition() && $fact->getTag() !== 'CHAN') { 1191 $this->repeats[] = $fact->getGedcom(); 1192 } 1193 } 1194 } 1195 } 1196 1197 /** 1198 * XML </ Facts> end element handler 1199 */ 1200 private function factsEndHandler() { 1201 global $parser, $parserStack, $report; 1202 1203 $this->process_repeats--; 1204 if ($this->process_repeats > 0) { 1205 return; 1206 } 1207 1208 // Check if there is anything to repeat 1209 if (count($this->repeats) > 0) { 1210 1211 $line = xml_get_current_line_number($parser) - 1; 1212 $lineoffset = 0; 1213 foreach ($this->repeats_stack as $rep) { 1214 $lineoffset += $rep[1]; 1215 } 1216 1217 //-- read the xml from the file 1218 $lines = file($report); 1219 while ($lineoffset + $this->repeat_bytes > 0 && strpos($lines[$lineoffset + $this->repeat_bytes], '<Facts ') === false) { 1220 $lineoffset--; 1221 } 1222 $lineoffset++; 1223 $reportxml = "<tempdoc>\n"; 1224 $i = $line + $lineoffset; 1225 $line_nr = $this->repeat_bytes + $lineoffset; 1226 while ($line_nr < $i) { 1227 $reportxml .= $lines[$line_nr]; 1228 $line_nr++; 1229 } 1230 // No need to drag this 1231 unset($lines); 1232 $reportxml .= "</tempdoc>\n"; 1233 // Save original values 1234 array_push($parserStack, $parser); 1235 $oldgedrec = $this->gedrec; 1236 $count = count($this->repeats); 1237 $i = 0; 1238 while ($i < $count) { 1239 $this->gedrec = $this->repeats[$i]; 1240 $this->fact = ''; 1241 $this->desc = ''; 1242 if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) { 1243 $this->fact = $match[1]; 1244 if ($this->fact === 'EVEN' || $this->fact === 'FACT') { 1245 $tmatch = array(); 1246 if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) { 1247 $this->type = trim($tmatch[1]); 1248 } else { 1249 $this->type = ' '; 1250 } 1251 } 1252 $this->desc = trim($match[2]); 1253 $this->desc .= get_cont(2, $this->gedrec); 1254 } 1255 $repeat_parser = xml_parser_create(); 1256 $parser = $repeat_parser; 1257 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 1258 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 1259 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 1260 if (!xml_parse($repeat_parser, $reportxml, true)) { 1261 throw new \DomainException(sprintf( 1262 'FactsEHandler XML error: %s at line %d', 1263 xml_error_string(xml_get_error_code($repeat_parser)), 1264 xml_get_current_line_number($repeat_parser) 1265 )); 1266 } 1267 xml_parser_free($repeat_parser); 1268 $i++; 1269 } 1270 // Restore original values 1271 $parser = array_pop($parserStack); 1272 $this->gedrec = $oldgedrec; 1273 } 1274 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 1275 } 1276 1277 /** 1278 * Setting upp or changing variables in the XML 1279 * The XML variable name and value is stored in the global variable $vars 1280 * 1281 * @param array $attrs an array of key value pairs for the attributes 1282 */ 1283 private function setVarStartHandler($attrs) { 1284 global $vars; 1285 1286 if (empty($attrs['name'])) { 1287 throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file'); 1288 } 1289 1290 $name = $attrs['name']; 1291 $value = $attrs['value']; 1292 $match = array(); 1293 // Current GEDCOM record strings 1294 if ($value == "@ID") { 1295 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1296 $value = $match[1]; 1297 } 1298 } elseif ($value == "@fact") { 1299 $value = $this->fact; 1300 } elseif ($value == "@desc") { 1301 $value = $this->desc; 1302 } elseif ($value == "@generation") { 1303 $value = $this->generation; 1304 } elseif (preg_match("/@(\w+)/", $value, $match)) { 1305 $gmatch = array(); 1306 if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) { 1307 $value = str_replace("@", "", trim($gmatch[1])); 1308 } 1309 } 1310 if (preg_match("/\\$(\w+)/", $name, $match)) { 1311 $name = $vars["'" . $match[1] . "'"]['id']; 1312 } 1313 $count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER); 1314 $i = 0; 1315 while ($i < $count) { 1316 $t = $vars[$match[$i][1]]['id']; 1317 $value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1); 1318 $i++; 1319 } 1320 if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) { 1321 $value = I18N::number($match[1]); 1322 } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) { 1323 $value = I18N::translate($match[1]); 1324 } elseif (preg_match('/^I18N::translate_c\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) { 1325 $value = I18N::translateContext($match[1], $match[2]); 1326 } 1327 // Arithmetic functions 1328 if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) { 1329 switch ($match[2]) { 1330 case "+": 1331 $t = $match[1] + $match[3]; 1332 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1333 break; 1334 case "-": 1335 $t = $match[1] - $match[3]; 1336 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1337 break; 1338 case "*": 1339 $t = $match[1] * $match[3]; 1340 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1341 break; 1342 case "/": 1343 $t = $match[1] / $match[3]; 1344 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1345 break; 1346 } 1347 } 1348 if (strpos($value, "@") !== false) { 1349 $value = ""; 1350 } 1351 $vars[$name]['id'] = $value; 1352 } 1353 1354 /** 1355 * XML <if > start element 1356 * 1357 * @param array $attrs an array of key value pairs for the attributes 1358 */ 1359 private function ifStartHandler($attrs) { 1360 global $vars; 1361 1362 if ($this->process_ifs > 0) { 1363 $this->process_ifs++; 1364 1365 return; 1366 } 1367 1368 $condition = $attrs['condition']; 1369 $condition = preg_replace("/\\$(\w+)/", "\$vars[\"$1\"][\"id\"]", $condition); 1370 $condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition); 1371 // Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT 1372 $condition = str_replace("@fact", $this->fact, $condition); 1373 $match = array(); 1374 $count = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER); 1375 $i = 0; 1376 while ($i < $count) { 1377 $id = $match[$i][1]; 1378 $value = '""'; 1379 if ($id == "ID") { 1380 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1381 $value = "'" . $match[1] . "'"; 1382 } 1383 } elseif ($id === "fact") { 1384 $value = '"' . $this->fact . '"'; 1385 } elseif ($id === "desc") { 1386 $value = '"' . addslashes($this->desc) . '"'; 1387 } elseif ($id === "generation") { 1388 $value = '"' . $this->generation . '"'; 1389 } else { 1390 1391 $temp = explode(" ", trim($this->gedrec)); 1392 $level = $temp[0]; 1393 if ($level == 0) { 1394 $level++; 1395 } 1396 $value = get_gedcom_value($id, $level, $this->gedrec); 1397 if (empty($value)) { 1398 $level++; 1399 $value = get_gedcom_value($id, $level, $this->gedrec); 1400 } 1401 $value = preg_replace("/^@(" . WT_REGEX_XREF . ")@$/", "$1", $value); 1402 $value = "\"" . addslashes($value) . "\""; 1403 } 1404 $condition = str_replace("@$id", $value, $condition); 1405 $i++; 1406 } 1407 $condition = "return (bool) ($condition);"; 1408 $ret = @eval($condition); 1409 if (!$ret) { 1410 $this->process_ifs++; 1411 } 1412 } 1413 1414 /** 1415 * XML <if /> end element 1416 */ 1417 private function ifEndHandler() { 1418 if ($this->process_ifs > 0) { 1419 $this->process_ifs--; 1420 } 1421 } 1422 1423 /** 1424 * XML <Footnote > start element 1425 * Collect the Footnote links 1426 * GEDCOM Records that are protected by Privacy setting will be ignore 1427 * 1428 * @param array $attrs an array of key value pairs for the attributes 1429 */ 1430 private function footnoteStartHandler($attrs) { 1431 global $ReportRoot, $WT_TREE; 1432 1433 $id = ""; 1434 if (preg_match("/[0-9] (.+) @(.+)@/", $this->gedrec, $match)) { 1435 $id = $match[2]; 1436 } 1437 $record = GedcomRecord::GetInstance($id, $WT_TREE); 1438 if ($record && $record->canShow()) { 1439 array_push($this->print_data_stack, $this->print_data); 1440 $this->print_data = true; 1441 $style = ""; 1442 if (!empty($attrs['style'])) { 1443 $style = $attrs['style']; 1444 } 1445 $this->footnote_element = $this->current_element; 1446 $this->current_element = $ReportRoot->createFootnote($style); 1447 } else { 1448 $this->print_data = false; 1449 $this->process_footnote = false; 1450 } 1451 } 1452 1453 /** 1454 * XML <Footnote /> end element 1455 * Print the collected Footnote data 1456 */ 1457 private function footnoteEndHandler() { 1458 global $wt_report; 1459 1460 if ($this->process_footnote) { 1461 $this->print_data = array_pop($this->print_data_stack); 1462 $temp = trim($this->current_element->getValue()); 1463 if (strlen($temp) > 3) { 1464 $wt_report->addElement($this->current_element); 1465 } 1466 $this->current_element = $this->footnote_element; 1467 } else { 1468 $this->process_footnote = true; 1469 } 1470 } 1471 1472 /** 1473 * XML <FootnoteTexts /> element 1474 */ 1475 private function footnoteTextsStartHandler() { 1476 global $wt_report; 1477 1478 $temp = "footnotetexts"; 1479 $wt_report->addElement($temp); 1480 } 1481 1482 /** 1483 * XML <AgeAtDeath /> element handler 1484 */ 1485 private function ageAtDeathStartHandler() { 1486 // This duplicates functionality in format_fact_date() 1487 global $factrec, $WT_TREE; 1488 1489 $match = array(); 1490 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1491 $person = Individual::getInstance($match[1], $WT_TREE); 1492 // Recorded age 1493 if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { 1494 $fact_age = $match[1]; 1495 } else { 1496 $fact_age = ''; 1497 } 1498 if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { 1499 $husb_age = $match[1]; 1500 } else { 1501 $husb_age = ''; 1502 } 1503 if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { 1504 $wife_age = $match[1]; 1505 } else { 1506 $wife_age = ''; 1507 } 1508 1509 // Calculated age 1510 $birth_date = $person->getBirthDate(); 1511 // Can't use getDeathDate(), as this also gives BURI/CREM events, which 1512 // wouldn't give the correct "days after death" result for people with 1513 // no DEAT. 1514 $death_event = $person->getFirstFact('DEAT'); 1515 if ($death_event) { 1516 $death_date = $death_event->getDate(); 1517 } else { 1518 $death_date = new Date(''); 1519 } 1520 $value = ''; 1521 if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) { 1522 $age = Date::getAgeGedcom($birth_date, $death_date); 1523 // Only show calculated age if it differs from recorded age 1524 if ($age != '' && $age != "0d") { 1525 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 1526 ) { 1527 $value = get_age_at_event($age, false); 1528 $abbrev = substr($value, 0, strpos($value, ' ') + 5); 1529 if ($value !== $abbrev) { 1530 $value = $abbrev . '.'; 1531 } 1532 } 1533 } 1534 } 1535 $this->current_element->addText($value); 1536 } 1537 } 1538 1539 /** 1540 * XML element Forced line break handler - HTML code 1541 */ 1542 private function brStartHandler() { 1543 if ($this->print_data && $this->process_gedcoms === 0) { 1544 $this->current_element->addText('<br>'); 1545 } 1546 } 1547 1548 /** 1549 * XML <sp />element Forced space handler 1550 */ 1551 private function spStartHandler() { 1552 if ($this->print_data && $this->process_gedcoms === 0) { 1553 $this->current_element->addText(' '); 1554 } 1555 } 1556 1557 /** 1558 * @param array $attrs an array of key value pairs for the attributes 1559 */ 1560 private function highlightedImageStartHandler($attrs) { 1561 global $wt_report, $ReportRoot, $WT_TREE; 1562 1563 $id = ''; 1564 $match = array(); 1565 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1566 $id = $match[1]; 1567 } 1568 1569 // mixed Position the top corner of this box on the page. the default is the current position 1570 $top = '.'; 1571 if (isset($attrs['top'])) { 1572 if ($attrs['top'] === '0') { 1573 $top = 0; 1574 } elseif ($attrs['top'] === '.') { 1575 $top = '.'; 1576 } elseif (!empty($attrs['top'])) { 1577 $top = (int) $attrs['top']; 1578 } 1579 } 1580 1581 // mixed Position the left corner of this box on the page. the default is the current position 1582 $left = '.'; 1583 if (isset($attrs['left'])) { 1584 if ($attrs['left'] === '0') { 1585 $left = 0; 1586 } elseif ($attrs['left'] === '.') { 1587 $left = '.'; 1588 } elseif (!empty($attrs['left'])) { 1589 $left = (int) $attrs['left']; 1590 } 1591 } 1592 1593 // string Align the image in left, center, right 1594 $align = ''; 1595 if (!empty($attrs['align'])) { 1596 $align = $attrs['align']; 1597 } 1598 1599 // string Next Line should be T:next to the image, N:next line 1600 $ln = ''; 1601 if (!empty($attrs['ln'])) { 1602 $ln = $attrs['ln']; 1603 } 1604 1605 $width = 0; 1606 $height = 0; 1607 if (!empty($attrs['width'])) { 1608 $width = (int) $attrs['width']; 1609 } 1610 if (!empty($attrs['height'])) { 1611 $height = (int) $attrs['height']; 1612 } 1613 1614 $person = Individual::getInstance($id, $WT_TREE); 1615 $mediaobject = $person->findHighlightedMedia(); 1616 if ($mediaobject) { 1617 $attributes = $mediaobject->getImageAttributes('thumb'); 1618 if (in_array( 1619 $attributes['ext'], 1620 array( 1621 'GIF', 1622 'JPG', 1623 'PNG', 1624 'SWF', 1625 'PSD', 1626 'BMP', 1627 'TIFF', 1628 'TIFF', 1629 'JPC', 1630 'JP2', 1631 'JPX', 1632 'JB2', 1633 'SWC', 1634 'IFF', 1635 'WBMP', 1636 'XBM', 1637 ) 1638 ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') 1639 ) { 1640 if ($width > 0 && $height == 0) { 1641 $perc = $width / $attributes['adjW']; 1642 $height = round($attributes['adjH'] * $perc); 1643 } elseif ($height > 0 && $width == 0) { 1644 $perc = $height / $attributes['adjH']; 1645 $width = round($attributes['adjW'] * $perc); 1646 } else { 1647 $width = $attributes['adjW']; 1648 $height = $attributes['adjH']; 1649 } 1650 $image = $ReportRoot->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); 1651 $wt_report->addElement($image); 1652 } 1653 } 1654 } 1655 1656 /** 1657 * @param array $attrs an array of key value pairs for the attributes 1658 */ 1659 private function imageStartHandler($attrs) { 1660 global $wt_report, $ReportRoot, $WT_TREE; 1661 1662 // mixed Position the top corner of this box on the page. the default is the current position 1663 $top = '.'; 1664 if (isset($attrs['top'])) { 1665 if ($attrs['top'] === "0") { 1666 $top = 0; 1667 } elseif ($attrs['top'] === '.') { 1668 $top = '.'; 1669 } elseif (!empty($attrs['top'])) { 1670 $top = (int) $attrs['top']; 1671 } 1672 } 1673 1674 // mixed Position the left corner of this box on the page. the default is the current position 1675 $left = '.'; 1676 if (isset($attrs['left'])) { 1677 if ($attrs['left'] === '0') { 1678 $left = 0; 1679 } elseif ($attrs['left'] === '.') { 1680 $left = '.'; 1681 } elseif (!empty($attrs['left'])) { 1682 $left = (int) $attrs['left']; 1683 } 1684 } 1685 1686 // string Align the image in left, center, right 1687 $align = ''; 1688 if (!empty($attrs['align'])) { 1689 $align = $attrs['align']; 1690 } 1691 1692 // string Next Line should be T:next to the image, N:next line 1693 $ln = 'T'; 1694 if (!empty($attrs['ln'])) { 1695 $ln = $attrs['ln']; 1696 } 1697 1698 $width = 0; 1699 $height = 0; 1700 if (!empty($attrs['width'])) { 1701 $width = (int) $attrs['width']; 1702 } 1703 if (!empty($attrs['height'])) { 1704 $height = (int) $attrs['height']; 1705 } 1706 1707 $file = ''; 1708 if (!empty($attrs['file'])) { 1709 $file = $attrs['file']; 1710 } 1711 if ($file == "@FILE") { 1712 $match = array(); 1713 if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) { 1714 $mediaobject = Media::getInstance($match[1], $WT_TREE); 1715 $attributes = $mediaobject->getImageAttributes('thumb'); 1716 if (in_array( 1717 $attributes['ext'], 1718 array( 1719 'GIF', 1720 'JPG', 1721 'PNG', 1722 'SWF', 1723 'PSD', 1724 'BMP', 1725 'TIFF', 1726 'TIFF', 1727 'JPC', 1728 'JP2', 1729 'JPX', 1730 'JB2', 1731 'SWC', 1732 'IFF', 1733 'WBMP', 1734 'XBM', 1735 ) 1736 ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') 1737 ) { 1738 if ($width > 0 && $height == 0) { 1739 $perc = $width / $attributes['adjW']; 1740 $height = round($attributes['adjH'] * $perc); 1741 } elseif ($height > 0 && $width == 0) { 1742 $perc = $height / $attributes['adjH']; 1743 $width = round($attributes['adjW'] * $perc); 1744 } else { 1745 $width = $attributes['adjW']; 1746 $height = $attributes['adjH']; 1747 } 1748 $image = $ReportRoot->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); 1749 $wt_report->addElement($image); 1750 } 1751 } 1752 } else { 1753 if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) { 1754 $size = getimagesize($file); 1755 if ($width > 0 && $height == 0) { 1756 $perc = $width / $size[0]; 1757 $height = round($size[1] * $perc); 1758 } elseif ($height > 0 && $width == 0) { 1759 $perc = $height / $size[1]; 1760 $width = round($size[0] * $perc); 1761 } else { 1762 $width = $size[0]; 1763 $height = $size[1]; 1764 } 1765 $image = $ReportRoot->createImage($file, $left, $top, $width, $height, $align, $ln); 1766 $wt_report->addElement($image); 1767 } 1768 } 1769 } 1770 1771 /** 1772 * XML <Line> element handler 1773 * 1774 * @param array $attrs an array of key value pairs for the attributes 1775 */ 1776 private function lineStartHandler($attrs) { 1777 global $wt_report, $ReportRoot; 1778 1779 // Start horizontal position, current position (default) 1780 $x1 = "."; 1781 if (isset($attrs['x1'])) { 1782 if ($attrs['x1'] === "0") { 1783 $x1 = 0; 1784 } elseif ($attrs['x1'] === ".") { 1785 $x1 = "."; 1786 } elseif (!empty($attrs['x1'])) { 1787 $x1 = (int) $attrs['x1']; 1788 } 1789 } 1790 // Start vertical position, current position (default) 1791 $y1 = "."; 1792 if (isset($attrs['y1'])) { 1793 if ($attrs['y1'] === "0") { 1794 $y1 = 0; 1795 } elseif ($attrs['y1'] === ".") { 1796 $y1 = "."; 1797 } elseif (!empty($attrs['y1'])) { 1798 $y1 = (int) $attrs['y1']; 1799 } 1800 } 1801 // End horizontal position, maximum width (default) 1802 $x2 = "."; 1803 if (isset($attrs['x2'])) { 1804 if ($attrs['x2'] === "0") { 1805 $x2 = 0; 1806 } elseif ($attrs['x2'] === ".") { 1807 $x2 = "."; 1808 } elseif (!empty($attrs['x2'])) { 1809 $x2 = (int) $attrs['x2']; 1810 } 1811 } 1812 // End vertical position 1813 $y2 = "."; 1814 if (isset($attrs['y2'])) { 1815 if ($attrs['y2'] === "0") { 1816 $y2 = 0; 1817 } elseif ($attrs['y2'] === ".") { 1818 $y2 = "."; 1819 } elseif (!empty($attrs['y2'])) { 1820 $y2 = (int) $attrs['y2']; 1821 } 1822 } 1823 1824 $line = $ReportRoot->createLine($x1, $y1, $x2, $y2); 1825 $wt_report->addElement($line); 1826 } 1827 1828 /** 1829 * XML <List> start element handler 1830 * 1831 * @param array $attrs an array of key value pairs for the attributes 1832 */ 1833 private function listStartHandler($attrs) { 1834 global $parser, $vars, $WT_TREE; 1835 1836 $this->process_repeats++; 1837 if ($this->process_repeats > 1) { 1838 return; 1839 } 1840 1841 $match = array(); 1842 if (isset($attrs['sortby'])) { 1843 $sortby = $attrs['sortby']; 1844 if (preg_match("/\\$(\w+)/", $sortby, $match)) { 1845 $sortby = $vars[$match[1]]['id']; 1846 $sortby = trim($sortby); 1847 } 1848 } else { 1849 $sortby = "NAME"; 1850 } 1851 1852 if (isset($attrs['list'])) { 1853 $listname = $attrs['list']; 1854 } else { 1855 $listname = "individual"; 1856 } 1857 // Some filters/sorts can be applied using SQL, while others require PHP 1858 switch ($listname) { 1859 case "pending": 1860 $rows = Database::prepare( 1861 "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" . " )" 1862 )->execute(array($WT_TREE->getTreeId()))->fetchAll(); 1863 $this->list = array(); 1864 foreach ($rows as $row) { 1865 $this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom); 1866 } 1867 break; 1868 case 'individual': 1869 $sql_select = "SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` "; 1870 $sql_join = ""; 1871 $sql_where = " WHERE i_file = " . $WT_TREE->getTreeId(); 1872 $sql_order_by = ""; 1873 foreach ($attrs as $attr => $value) { 1874 if (strpos($attr, 'filter') === 0 && $value) { 1875 // Substitute global vars 1876 $value = preg_replace_callback( 1877 '/\$(\w+)/', 1878 function ($matches) use ($vars) { 1879 return $vars[$matches[1]]['id']; 1880 }, 1881 $value 1882 ); 1883 // Convert the various filters into SQL 1884 if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { 1885 $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)"; 1886 $sql_where .= " AND {$attr}.d_fact='{$match[1]}'"; 1887 $date = new Date($match[3]); 1888 if ($match[2] == "LTE") { 1889 $sql_where .= " AND {$attr}.d_julianday2<=" . $date->minimumJulianDay(); 1890 } else { 1891 $sql_where .= " AND {$attr}.d_julianday1>=" . $date->minimumJulianDay(); 1892 } 1893 if ($sortby == $match[1]) { 1894 $sortby = ""; 1895 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1"; 1896 } 1897 unset($attrs[$attr]); // This filter has been fully processed 1898 } elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) { 1899 // Do nothing, unless you have to 1900 if ($match[1] != '' || $sortby == 'NAME') { 1901 $sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)"; 1902 // Search the DB only if there is any name supplied 1903 if ($match[1] != "") { 1904 $names = explode(" ", $match[1]); 1905 foreach ($names as $name) { 1906 $sql_where .= " AND {$attr}.n_full LIKE " . Database::quote("%{$name}%"); 1907 } 1908 } 1909 // Let the DB do the name sorting even when no name was entered 1910 if ($sortby == "NAME") { 1911 $sortby = ""; 1912 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort"; 1913 } 1914 } 1915 unset($attrs[$attr]); // This filter has been fully processed 1916 } elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) { 1917 $sql_where .= " AND i_gedcom REGEXP '" . $match[1] . "'"; 1918 unset($attrs[$attr]); // This filter has been fully processed 1919 } elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) { 1920 $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=i_file)"; 1921 $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)"; 1922 $sql_where .= " AND {$attr}a.p_place LIKE " . Database::quote("%{$match[1]}%"); 1923 // Don't unset this filter. This is just initial filtering 1924 } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { 1925 $query = ""; 1926 // Level 1 tag 1927 if ($match[1] !== '') { 1928 $query .= "%1 {$match[1]}%"; 1929 } 1930 // Level 2 tag 1931 if ($match[2] !== '') { 1932 $query .= "%2 {$match[2]}%"; 1933 } 1934 // Contains what? 1935 if ($match[3] !== '') { 1936 $query .= "%{$match[3]}%"; 1937 } 1938 $sql_where .= " AND i_gedcom LIKE " . Database::quote($query); 1939 // Don't unset this filter. This is just initial filtering 1940 } 1941 } 1942 } 1943 1944 $this->list = array(); 1945 $rows = Database::prepare( 1946 $sql_select . $sql_join . $sql_where . $sql_order_by 1947 )->fetchAll(); 1948 1949 foreach ($rows as $row) { 1950 $this->list[] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom); 1951 } 1952 break; 1953 1954 case 'family': 1955 $sql_select = "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom FROM `##families`"; 1956 $sql_join = ""; 1957 $sql_where = " WHERE f_file=" . $WT_TREE->getTreeId(); 1958 $sql_order_by = ""; 1959 foreach ($attrs as $attr => $value) { 1960 if (strpos($attr, 'filter') === 0 && $value) { 1961 // Substitute global vars 1962 $value = preg_replace_callback( 1963 '/\$(\w+)/', 1964 function ($matches) use ($vars) { 1965 return $vars[$matches[1]]['id']; 1966 }, 1967 $value 1968 ); 1969 // Convert the various filters into SQL 1970 if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { 1971 $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)"; 1972 $sql_where .= " AND {$attr}.d_fact='{$match[1]}'"; 1973 $date = new Date($match[3]); 1974 if ($match[2] == "LTE") { 1975 $sql_where .= " AND {$attr}.d_julianday2<=" . $date->minimumJulianDay(); 1976 } else { 1977 $sql_where .= " AND {$attr}.d_julianday1>=" . $date->minimumJulianDay(); 1978 } 1979 if ($sortby == $match[1]) { 1980 $sortby = ""; 1981 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1"; 1982 } 1983 unset($attrs[$attr]); // This filter has been fully processed 1984 } elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) { 1985 $sql_where .= " AND f_gedcom REGEXP '" . $match[1] . "'"; 1986 unset($attrs[$attr]); // This filter has been fully processed 1987 } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) { 1988 $sql_join .= " JOIN `##link` AS {$attr}a ON ({$attr}a.l_file=f_file AND {$attr}a.l_from=f_id)"; 1989 $sql_join .= " JOIN `##name` AS {$attr}b ON ({$attr}b.n_file=f_file AND n_id=f_id)"; 1990 $sql_where .= " AND {$attr}a.l_type=IN ('HUSB, 'WIFE')"; 1991 $sql_where .= " AND {$attr}.n_full LIKE " . Database::quote("%{$match[1]}%"); 1992 if ($sortby == "NAME") { 1993 $sortby = ""; 1994 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort"; 1995 } 1996 unset($attrs[$attr]); // This filter has been fully processed 1997 } elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) { 1998 $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)"; 1999 $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)"; 2000 $sql_where .= " AND {$attr}a.p_place LIKE " . Database::quote("%{$match[1]}%"); 2001 // Don't unset this filter. This is just initial filtering 2002 } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { 2003 $query = ""; 2004 // Level 1 tag 2005 if ($match[1] !== '') { 2006 $query .= "%1 {$match[1]}%"; 2007 } 2008 // Level 2 tag 2009 if ($match[2] !== '') { 2010 $query .= "%2 {$match[2]}%"; 2011 } 2012 // Contains what? 2013 if ($match[3] !== '') { 2014 $query .= "%{$match[3]}%"; 2015 } 2016 $sql_where .= " AND f_gedcom LIKE " . Database::quote($query); 2017 // Don't unset this filter. This is just initial filtering 2018 } 2019 } 2020 } 2021 2022 $this->list = array(); 2023 $rows = Database::prepare( 2024 $sql_select . $sql_join . $sql_where . $sql_order_by 2025 )->fetchAll(); 2026 2027 foreach ($rows as $row) { 2028 $this->list[] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom); 2029 } 2030 break; 2031 2032 default: 2033 throw new \DomainException('Invalid list name: ' . $listname); 2034 } 2035 2036 $filters = array(); 2037 $filters2 = array(); 2038 if (isset($attrs['filter1']) && count($this->list) > 0) { 2039 foreach ($attrs as $key => $value) { 2040 if (preg_match("/filter(\d)/", $key)) { 2041 $condition = $value; 2042 if (preg_match("/@(\w+)/", $condition, $match)) { 2043 $id = $match[1]; 2044 $value = "''"; 2045 if ($id == "ID") { 2046 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 2047 $value = "'" . $match[1] . "'"; 2048 } 2049 } elseif ($id == "fact") { 2050 $value = "'" . $this->fact . "'"; 2051 } elseif ($id == "desc") { 2052 $value = "'" . $this->desc . "'"; 2053 } else { 2054 if (preg_match("/\d $id (.+)/", $this->gedrec, $match)) { 2055 $value = "'" . str_replace("@", "", trim($match[1])) . "'"; 2056 } 2057 } 2058 $condition = preg_replace("/@$id/", $value, $condition); 2059 } 2060 //-- handle regular expressions 2061 if (preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match)) { 2062 $tag = trim($match[1]); 2063 $expr = trim($match[2]); 2064 $val = trim($match[3]); 2065 if (preg_match("/\\$(\w+)/", $val, $match)) { 2066 $val = $vars[$match[1]]['id']; 2067 $val = trim($val); 2068 } 2069 if ($val) { 2070 $searchstr = ""; 2071 $tags = explode(":", $tag); 2072 //-- only limit to a level number if we are specifically looking at a level 2073 if (count($tags) > 1) { 2074 $level = 1; 2075 foreach ($tags as $t) { 2076 if (!empty($searchstr)) { 2077 $searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n"; 2078 } 2079 //-- search for both EMAIL and _EMAIL... silly double gedcom standard 2080 if ($t == "EMAIL" || $t == "_EMAIL") { 2081 $t = "_?EMAIL"; 2082 } 2083 $searchstr .= $level . " " . $t; 2084 $level++; 2085 } 2086 } else { 2087 if ($tag == "EMAIL" || $tag == "_EMAIL") { 2088 $tag = "_?EMAIL"; 2089 } 2090 $t = $tag; 2091 $searchstr = "1 " . $tag; 2092 } 2093 switch ($expr) { 2094 case "CONTAINS": 2095 if ($t == "PLAC") { 2096 $searchstr .= "[^\n]*[, ]*" . $val; 2097 } else { 2098 $searchstr .= "[^\n]*" . $val; 2099 } 2100 $filters[] = $searchstr; 2101 break; 2102 default: 2103 $filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val); 2104 break; 2105 } 2106 } 2107 } 2108 } 2109 } 2110 } 2111 //-- apply other filters to the list that could not be added to the search string 2112 if ($filters) { 2113 foreach ($this->list as $key => $record) { 2114 foreach ($filters as $filter) { 2115 if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) { 2116 unset($this->list[$key]); 2117 break; 2118 } 2119 } 2120 } 2121 } 2122 if ($filters2) { 2123 $mylist = array(); 2124 foreach ($this->list as $indi) { 2125 $key = $indi->getXref(); 2126 $grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE)); 2127 $keep = true; 2128 foreach ($filters2 as $filter) { 2129 if ($keep) { 2130 $tag = $filter['tag']; 2131 $expr = $filter['expr']; 2132 $val = $filter['val']; 2133 if ($val == "''") { 2134 $val = ""; 2135 } 2136 $tags = explode(":", $tag); 2137 $t = end($tags); 2138 $v = get_gedcom_value($tag, 1, $grec); 2139 //-- check for EMAIL and _EMAIL (silly double gedcom standard :P) 2140 if ($t == "EMAIL" && empty($v)) { 2141 $tag = str_replace("EMAIL", "_EMAIL", $tag); 2142 $tags = explode(":", $tag); 2143 $t = end($tags); 2144 $v = get_sub_record(1, $tag, $grec); 2145 } 2146 2147 switch ($expr) { 2148 case "GTE": 2149 if ($t == "DATE") { 2150 $date1 = new Date($v); 2151 $date2 = new Date($val); 2152 $keep = (Date::compare($date1, $date2) >= 0); 2153 } elseif ($val >= $v) { 2154 $keep = true; 2155 } 2156 break; 2157 case "LTE": 2158 if ($t == "DATE") { 2159 $date1 = new Date($v); 2160 $date2 = new Date($val); 2161 $keep = (Date::compare($date1, $date2) <= 0); 2162 } elseif ($val >= $v) { 2163 $keep = true; 2164 } 2165 break; 2166 default: 2167 if ($v == $val) { 2168 $keep = true; 2169 } else { 2170 $keep = false; 2171 } 2172 break; 2173 } 2174 } 2175 } 2176 if ($keep) { 2177 $mylist[$key] = $indi; 2178 } 2179 } 2180 $this->list = $mylist; 2181 } 2182 2183 switch ($sortby) { 2184 case 'NAME': 2185 uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare'); 2186 break; 2187 case 'CHAN': 2188 uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) { 2189 return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true); 2190 }); 2191 break; 2192 case 'BIRT:DATE': 2193 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); 2194 break; 2195 case 'DEAT:DATE': 2196 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); 2197 break; 2198 case 'MARR:DATE': 2199 uasort($this->list, '\Fisharebest\Webtrees\\Family::compareMarrDate'); 2200 break; 2201 default: 2202 // unsorted or already sorted by SQL 2203 break; 2204 } 2205 2206 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 2207 $this->repeat_bytes = xml_get_current_line_number($parser) + 1; 2208 } 2209 2210 /** 2211 * XML <List> end element handler 2212 */ 2213 private function listEndHandler() { 2214 global $parser, $parserStack, $report; 2215 2216 $this->process_repeats--; 2217 if ($this->process_repeats > 0) { 2218 return; 2219 } 2220 2221 // Check if there is any list 2222 if (count($this->list) > 0) { 2223 $lineoffset = 0; 2224 foreach ($this->repeats_stack as $rep) { 2225 $lineoffset += $rep[1]; 2226 } 2227 //-- read the xml from the file 2228 $lines = file($report); 2229 while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<List") === false) && (($lineoffset + $this->repeat_bytes) > 0)) { 2230 $lineoffset--; 2231 } 2232 $lineoffset++; 2233 $reportxml = "<tempdoc>\n"; 2234 $line_nr = $lineoffset + $this->repeat_bytes; 2235 // List Level counter 2236 $count = 1; 2237 while (0 < $count) { 2238 if (strpos($lines[$line_nr], "<List") !== false) { 2239 $count++; 2240 } elseif (strpos($lines[$line_nr], "</List") !== false) { 2241 $count--; 2242 } 2243 if (0 < $count) { 2244 $reportxml .= $lines[$line_nr]; 2245 } 2246 $line_nr++; 2247 } 2248 // No need to drag this 2249 unset($lines); 2250 $reportxml .= "</tempdoc>"; 2251 // Save original values 2252 array_push($parserStack, $parser); 2253 $oldgedrec = $this->gedrec; 2254 2255 $this->list_total = count($this->list); 2256 $this->list_private = 0; 2257 foreach ($this->list as $record) { 2258 if ($record->canShow()) { 2259 $this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree())); 2260 //-- start the sax parser 2261 $repeat_parser = xml_parser_create(); 2262 $parser = $repeat_parser; 2263 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 2264 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 2265 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 2266 if (!xml_parse($repeat_parser, $reportxml, true)) { 2267 throw new \DomainException(sprintf( 2268 'ListEHandler XML error: %s at line %d', 2269 xml_error_string(xml_get_error_code($repeat_parser)), 2270 xml_get_current_line_number($repeat_parser) 2271 )); 2272 } 2273 xml_parser_free($repeat_parser); 2274 } else { 2275 $this->list_private++; 2276 } 2277 } 2278 $this->list = array(); 2279 $parser = array_pop($parserStack); 2280 $this->gedrec = $oldgedrec; 2281 } 2282 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 2283 } 2284 2285 /** 2286 * XML <ListTotal> element handler 2287 * 2288 * Prints the total number of records in a list 2289 * The total number is collected from 2290 * List and Relatives 2291 */ 2292 private function listTotalStartHandler() { 2293 if ($this->list_private == 0) { 2294 $this->current_element->addText($this->list_total); 2295 } else { 2296 $this->current_element->addText(($this->list_total - $this->list_private) . " / " . $this->list_total); 2297 } 2298 } 2299 2300 /** 2301 * @param array $attrs an array of key value pairs for the attributes 2302 */ 2303 private function relativesStartHandler($attrs) { 2304 global $parser, $vars, $WT_TREE; 2305 2306 $this->process_repeats++; 2307 if ($this->process_repeats > 1) { 2308 return; 2309 } 2310 2311 $sortby = "NAME"; 2312 if (isset($attrs['sortby'])) { 2313 $sortby = $attrs['sortby']; 2314 } 2315 $match = array(); 2316 if (preg_match("/\\$(\w+)/", $sortby, $match)) { 2317 $sortby = $vars[$match[1]]['id']; 2318 $sortby = trim($sortby); 2319 } 2320 2321 $maxgen = -1; 2322 if (isset($attrs['maxgen'])) { 2323 $maxgen = $attrs['maxgen']; 2324 } 2325 if ($maxgen == "*") { 2326 $maxgen = -1; 2327 } 2328 2329 $group = "child-family"; 2330 if (isset($attrs['group'])) { 2331 $group = $attrs['group']; 2332 } 2333 if (preg_match("/\\$(\w+)/", $group, $match)) { 2334 $group = $vars[$match[1]]['id']; 2335 $group = trim($group); 2336 } 2337 2338 $id = ""; 2339 if (isset($attrs['id'])) { 2340 $id = $attrs['id']; 2341 } 2342 if (preg_match("/\\$(\w+)/", $id, $match)) { 2343 $id = $vars[$match[1]]['id']; 2344 $id = trim($id); 2345 } 2346 2347 $this->list = array(); 2348 $person = Individual::getInstance($id, $WT_TREE); 2349 if (!empty($person)) { 2350 $this->list[$id] = $person; 2351 switch ($group) { 2352 case "child-family": 2353 foreach ($person->getChildFamilies() as $family) { 2354 $husband = $family->getHusband(); 2355 $wife = $family->getWife(); 2356 if (!empty($husband)) { 2357 $this->list[$husband->getXref()] = $husband; 2358 } 2359 if (!empty($wife)) { 2360 $this->list[$wife->getXref()] = $wife; 2361 } 2362 $children = $family->getChildren(); 2363 foreach ($children as $child) { 2364 if (!empty($child)) { 2365 $this->list[$child->getXref()] = $child; 2366 } 2367 } 2368 } 2369 break; 2370 case "spouse-family": 2371 foreach ($person->getSpouseFamilies() as $family) { 2372 $husband = $family->getHusband(); 2373 $wife = $family->getWife(); 2374 if (!empty($husband)) { 2375 $this->list[$husband->getXref()] = $husband; 2376 } 2377 if (!empty($wife)) { 2378 $this->list[$wife->getXref()] = $wife; 2379 } 2380 $children = $family->getChildren(); 2381 foreach ($children as $child) { 2382 if (!empty($child)) { 2383 $this->list[$child->getXref()] = $child; 2384 } 2385 } 2386 } 2387 break; 2388 case "direct-ancestors": 2389 add_ancestors($this->list, $id, false, $maxgen); 2390 break; 2391 case "ancestors": 2392 add_ancestors($this->list, $id, true, $maxgen); 2393 break; 2394 case "descendants": 2395 $this->list[$id]->generation = 1; 2396 add_descendancy($this->list, $id, false, $maxgen); 2397 break; 2398 case "all": 2399 add_ancestors($this->list, $id, true, $maxgen); 2400 add_descendancy($this->list, $id, true, $maxgen); 2401 break; 2402 } 2403 } 2404 2405 switch ($sortby) { 2406 case 'NAME': 2407 uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare'); 2408 break; 2409 case 'BIRT:DATE': 2410 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); 2411 break; 2412 case 'DEAT:DATE': 2413 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); 2414 break; 2415 case 'generation': 2416 $newarray = array(); 2417 reset($this->list); 2418 $genCounter = 1; 2419 while (count($newarray) < count($this->list)) { 2420 foreach ($this->list as $key => $value) { 2421 $this->generation = $value->generation; 2422 if ($this->generation == $genCounter) { 2423 $newarray[$key] = new \stdClass; 2424 $newarray[$key]->generation = $this->generation; 2425 } 2426 } 2427 $genCounter++; 2428 } 2429 $this->list = $newarray; 2430 break; 2431 default: 2432 // unsorted 2433 break; 2434 } 2435 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 2436 $this->repeat_bytes = xml_get_current_line_number($parser) + 1; 2437 } 2438 2439 /** 2440 * XML </ Relatives> end element handler 2441 */ 2442 private function relativesEndHandler() { 2443 global $parser, $parserStack, $report, $WT_TREE; 2444 2445 $this->process_repeats--; 2446 if ($this->process_repeats > 0) { 2447 return; 2448 } 2449 2450 // Check if there is any relatives 2451 if (count($this->list) > 0) { 2452 $lineoffset = 0; 2453 foreach ($this->repeats_stack as $rep) { 2454 $lineoffset += $rep[1]; 2455 } 2456 //-- read the xml from the file 2457 $lines = file($report); 2458 while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<Relatives") === false) && (($lineoffset + $this->repeat_bytes) > 0)) { 2459 $lineoffset--; 2460 } 2461 $lineoffset++; 2462 $reportxml = "<tempdoc>\n"; 2463 $line_nr = $lineoffset + $this->repeat_bytes; 2464 // Relatives Level counter 2465 $count = 1; 2466 while (0 < $count) { 2467 if (strpos($lines[$line_nr], "<Relatives") !== false) { 2468 $count++; 2469 } elseif (strpos($lines[$line_nr], "</Relatives") !== false) { 2470 $count--; 2471 } 2472 if (0 < $count) { 2473 $reportxml .= $lines[$line_nr]; 2474 } 2475 $line_nr++; 2476 } 2477 // No need to drag this 2478 unset($lines); 2479 $reportxml .= "</tempdoc>\n"; 2480 // Save original values 2481 array_push($parserStack, $parser); 2482 $oldgedrec = $this->gedrec; 2483 2484 $this->list_total = count($this->list); 2485 $this->list_private = 0; 2486 foreach ($this->list as $key => $value) { 2487 if (isset($value->generation)) { 2488 $this->generation = $value->generation; 2489 } 2490 $tmp = GedcomRecord::getInstance($key, $WT_TREE); 2491 $this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)); 2492 2493 $repeat_parser = xml_parser_create(); 2494 $parser = $repeat_parser; 2495 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 2496 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 2497 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 2498 2499 if (!xml_parse($repeat_parser, $reportxml, true)) { 2500 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))); 2501 } 2502 xml_parser_free($repeat_parser); 2503 } 2504 // Clean up the list array 2505 $this->list = array(); 2506 $parser = array_pop($parserStack); 2507 $this->gedrec = $oldgedrec; 2508 } 2509 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 2510 } 2511 2512 /** 2513 * XML <Generation /> element handler 2514 * 2515 * Prints the number of generations 2516 */ 2517 private function generationStartHandler() { 2518 $this->current_element->addText($this->generation); 2519 } 2520 2521 /** 2522 * XML <NewPage /> element handler 2523 * 2524 * Has to be placed in an element (header, pageheader, body or footer) 2525 */ 2526 private function newPageStartHandler() { 2527 global $wt_report; 2528 2529 $temp = "addpage"; 2530 $wt_report->addElement($temp); 2531 } 2532 2533 /** 2534 * @param array $attrs an array of key value pairs for the attributes 2535 * @param string $tag HTML tag name 2536 */ 2537 private function htmlStartHandler($tag, $attrs) { 2538 global $wt_reportStack, $wt_report, $ReportRoot; 2539 2540 if ($tag === "tempdoc") { 2541 return; 2542 } 2543 array_push($wt_reportStack, $wt_report); 2544 $wt_report = $ReportRoot->createHTML($tag, $attrs); 2545 $this->current_element = $wt_report; 2546 2547 array_push($this->print_data_stack, $this->print_data); 2548 $this->print_data = true; 2549 } 2550 2551 /** 2552 * @param string $tag 2553 */ 2554 private function htmlEndHandler($tag) { 2555 global $wt_report, $wt_reportStack; 2556 if ($tag === "tempdoc") { 2557 return; 2558 } 2559 2560 $this->print_data = array_pop($this->print_data_stack); 2561 $this->current_element = $wt_report; 2562 $wt_report = array_pop($wt_reportStack); 2563 if (!is_null($wt_report)) { 2564 $wt_report->addElement($this->current_element); 2565 } else { 2566 $wt_report = $this->current_element; 2567 } 2568 } 2569 2570 /** 2571 * Handle <Input> 2572 */ 2573 private function inputStartHandler() { 2574 // Dummy function, to prevent the default HtmlStartHandler() being called 2575 } 2576 2577 /** 2578 * Handle </Input> 2579 */ 2580 private function inputEndHandler() { 2581 // Dummy function, to prevent the default HtmlEndHandler() being called 2582 } 2583 2584 /** 2585 * Handle <Report> 2586 */ 2587 private function reportStartHandler() { 2588 // Dummy function, to prevent the default HtmlStartHandler() being called 2589 } 2590 2591 /** 2592 * Handle </Report> 2593 */ 2594 private function reportEndHandler() { 2595 // Dummy function, to prevent the default HtmlEndHandler() being called 2596 } 2597 2598 /** 2599 * XML <titleStartHandler> start element handler 2600 */ 2601 private function titleStartHandler() { 2602 $this->report_title = true; 2603 } 2604 2605 /** 2606 * XML </titleEndHandler> end element handler 2607 */ 2608 private function titleEndHandler() { 2609 $this->report_title = false; 2610 } 2611 2612 /** 2613 * XML <descriptionStartHandler> start element handler 2614 */ 2615 private function descriptionStartHandler() { 2616 $this->report_description = true; 2617 } 2618 2619 /** 2620 * XML </descriptionEndHandler> end element handler 2621 */ 2622 private function descriptionEndHandler() { 2623 $this->report_description = false; 2624 } 2625} 2626