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