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