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 $tmp = explode(':', $tag); 957 if (in_array(end($tmp), array('NOTE', 'TEXT'))) { 958 $value = Filter::formatText($value, $WT_TREE); // We'll strip HTML in addText() 959 } 960 $this->current_element->addText($value); 961 } 962 } 963 } 964 965 /** 966 * XML <RepeatTag> 967 * 968 * @param array $attrs an array of key value pairs for the attributes 969 */ 970 private function repeatTagStartHandler($attrs) { 971 $this->process_repeats++; 972 if ($this->process_repeats > 1) { 973 return; 974 } 975 976 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 977 $this->repeats = array(); 978 $this->repeat_bytes = xml_get_current_line_number($this->parser); 979 980 $tag = ""; 981 if (isset($attrs['tag'])) { 982 $tag = $attrs['tag']; 983 } 984 if (!empty($tag)) { 985 if ($tag == "@desc") { 986 $value = $this->desc; 987 $value = trim($value); 988 $this->current_element->addText($value); 989 } else { 990 $tag = str_replace("@fact", $this->fact, $tag); 991 $tags = explode(":", $tag); 992 $temp = explode(" ", trim($this->gedrec)); 993 $level = $temp[0]; 994 if ($level == 0) { 995 $level++; 996 } 997 $subrec = $this->gedrec; 998 $t = $tag; 999 $count = count($tags); 1000 $i = 0; 1001 while ($i < $count) { 1002 $t = $tags[$i]; 1003 if (!empty($t)) { 1004 if ($i < ($count - 1)) { 1005 $subrec = Functions::getSubRecord($level, "$level $t", $subrec); 1006 if (empty($subrec)) { 1007 $level--; 1008 $subrec = Functions::getSubRecord($level, "@ $t", $this->gedrec); 1009 if (empty($subrec)) { 1010 return; 1011 } 1012 } 1013 } 1014 $level++; 1015 } 1016 $i++; 1017 } 1018 $level--; 1019 $count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER); 1020 $i = 0; 1021 while ($i < $count) { 1022 $this->repeats[] = Functions::getSubRecord($level, "$level $t", $subrec, $i + 1); 1023 $i++; 1024 } 1025 } 1026 } 1027 } 1028 1029 /** 1030 * XML </ RepeatTag> 1031 */ 1032 private function repeatTagEndHandler() { 1033 global $report; 1034 1035 $this->process_repeats--; 1036 if ($this->process_repeats > 0) { 1037 return; 1038 } 1039 1040 // Check if there is anything to repeat 1041 if (count($this->repeats) > 0) { 1042 // No need to load them if not used... 1043 1044 $lineoffset = 0; 1045 foreach ($this->repeats_stack as $rep) { 1046 $lineoffset += $rep[1]; 1047 } 1048 //-- read the xml from the file 1049 $lines = file($report); 1050 while (strpos($lines[$lineoffset + $this->repeat_bytes], "<RepeatTag") === false) { 1051 $lineoffset--; 1052 } 1053 $lineoffset++; 1054 $reportxml = "<tempdoc>\n"; 1055 $line_nr = $lineoffset + $this->repeat_bytes; 1056 // RepeatTag Level counter 1057 $count = 1; 1058 while (0 < $count) { 1059 if (strstr($lines[$line_nr], "<RepeatTag") !== false) { 1060 $count++; 1061 } elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) { 1062 $count--; 1063 } 1064 if (0 < $count) { 1065 $reportxml .= $lines[$line_nr]; 1066 } 1067 $line_nr++; 1068 } 1069 // No need to drag this 1070 unset($lines); 1071 $reportxml .= "</tempdoc>\n"; 1072 // Save original values 1073 array_push($this->parser_stack, $this->parser); 1074 $oldgedrec = $this->gedrec; 1075 foreach ($this->repeats as $gedrec) { 1076 $this->gedrec = $gedrec; 1077 $repeat_parser = xml_parser_create(); 1078 $this->parser = $repeat_parser; 1079 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 1080 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 1081 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 1082 if (!xml_parse($repeat_parser, $reportxml, true)) { 1083 throw new \DomainException(sprintf( 1084 'RepeatTagEHandler XML error: %s at line %d', 1085 xml_error_string(xml_get_error_code($repeat_parser)), 1086 xml_get_current_line_number($repeat_parser) 1087 )); 1088 } 1089 xml_parser_free($repeat_parser); 1090 } 1091 // Restore original values 1092 $this->gedrec = $oldgedrec; 1093 $this->parser = array_pop($this->parser_stack); 1094 } 1095 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 1096 } 1097 1098 /** 1099 * Variable lookup 1100 * 1101 * Retrieve predefined variables : 1102 * 1103 * @ desc GEDCOM fact description, example: 1104 * 1 EVEN This is a description 1105 * @ fact GEDCOM fact tag, such as BIRT, DEAT etc. 1106 * $ I18N::translate('....') 1107 * $ language_settings[] 1108 * 1109 * @param array $attrs an array of key value pairs for the attributes 1110 */ 1111 private function varStartHandler($attrs) { 1112 if (empty($attrs['var'])) { 1113 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)); 1114 } 1115 1116 $var = $attrs['var']; 1117 // SetVar element preset variables 1118 if (!empty($this->vars[$var]['id'])) { 1119 $var = $this->vars[$var]['id']; 1120 } else { 1121 $tfact = $this->fact; 1122 if (($this->fact === "EVEN" || $this->fact === "FACT") && $this->type !== " ") { 1123 // Use : 1124 // n TYPE This text if string 1125 $tfact = $this->type; 1126 } 1127 $var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $this->desc), $var); 1128 if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) { 1129 $var = I18N::number($match[1]); 1130 } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) { 1131 $var = I18N::translate($match[1]); 1132 } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) { 1133 $var = I18N::translateContext($match[1], $match[2]); 1134 } 1135 } 1136 // Check if variable is set as a date and reformat the date 1137 if (isset($attrs['date'])) { 1138 if ($attrs['date'] === "1") { 1139 $g = new Date($var); 1140 $var = $g->display(); 1141 } 1142 } 1143 $this->current_element->addText($var); 1144 $this->text = $var; // Used for title/descriptio 1145 } 1146 1147 /** 1148 * XML <Facts> 1149 * 1150 * @param array $attrs an array of key value pairs for the attributes 1151 */ 1152 private function factsStartHandler($attrs) { 1153 global $WT_TREE; 1154 1155 $this->process_repeats++; 1156 if ($this->process_repeats > 1) { 1157 return; 1158 } 1159 1160 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 1161 $this->repeats = array(); 1162 $this->repeat_bytes = xml_get_current_line_number($this->parser); 1163 1164 $id = ""; 1165 $match = array(); 1166 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1167 $id = $match[1]; 1168 } 1169 $tag = ""; 1170 if (isset($attrs['ignore'])) { 1171 $tag .= $attrs['ignore']; 1172 } 1173 if (preg_match("/\\$(.+)/", $tag, $match)) { 1174 $tag = $this->vars[$match[1]]['id']; 1175 } 1176 1177 $record = GedcomRecord::getInstance($id, $WT_TREE); 1178 if (empty($attrs['diff']) && !empty($id)) { 1179 $facts = $record->getFacts(); 1180 Functions::sortFacts($facts); 1181 $this->repeats = array(); 1182 $nonfacts = explode(',', $tag); 1183 foreach ($facts as $event) { 1184 if (!in_array($event->getTag(), $nonfacts)) { 1185 $this->repeats[] = $event->getGedcom(); 1186 } 1187 } 1188 } else { 1189 foreach ($record->getFacts() as $fact) { 1190 if ($fact->isPendingAddition() && $fact->getTag() !== 'CHAN') { 1191 $this->repeats[] = $fact->getGedcom(); 1192 } 1193 } 1194 } 1195 } 1196 1197 /** 1198 * XML </Facts> 1199 */ 1200 private function factsEndHandler() { 1201 global $report; 1202 1203 $this->process_repeats--; 1204 if ($this->process_repeats > 0) { 1205 return; 1206 } 1207 1208 // Check if there is anything to repeat 1209 if (count($this->repeats) > 0) { 1210 1211 $line = xml_get_current_line_number($this->parser) - 1; 1212 $lineoffset = 0; 1213 foreach ($this->repeats_stack as $rep) { 1214 $lineoffset += $rep[1]; 1215 } 1216 1217 //-- read the xml from the file 1218 $lines = file($report); 1219 while ($lineoffset + $this->repeat_bytes > 0 && strpos($lines[$lineoffset + $this->repeat_bytes], '<Facts ') === false) { 1220 $lineoffset--; 1221 } 1222 $lineoffset++; 1223 $reportxml = "<tempdoc>\n"; 1224 $i = $line + $lineoffset; 1225 $line_nr = $this->repeat_bytes + $lineoffset; 1226 while ($line_nr < $i) { 1227 $reportxml .= $lines[$line_nr]; 1228 $line_nr++; 1229 } 1230 // No need to drag this 1231 unset($lines); 1232 $reportxml .= "</tempdoc>\n"; 1233 // Save original values 1234 array_push($this->parser_stack, $this->parser); 1235 $oldgedrec = $this->gedrec; 1236 $count = count($this->repeats); 1237 $i = 0; 1238 while ($i < $count) { 1239 $this->gedrec = $this->repeats[$i]; 1240 $this->fact = ''; 1241 $this->desc = ''; 1242 if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) { 1243 $this->fact = $match[1]; 1244 if ($this->fact === 'EVEN' || $this->fact === 'FACT') { 1245 $tmatch = array(); 1246 if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) { 1247 $this->type = trim($tmatch[1]); 1248 } else { 1249 $this->type = ' '; 1250 } 1251 } 1252 $this->desc = trim($match[2]); 1253 $this->desc .= Functions::getCont(2, $this->gedrec); 1254 } 1255 $repeat_parser = xml_parser_create(); 1256 $this->parser = $repeat_parser; 1257 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 1258 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 1259 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 1260 if (!xml_parse($repeat_parser, $reportxml, true)) { 1261 throw new \DomainException(sprintf( 1262 'FactsEHandler XML error: %s at line %d', 1263 xml_error_string(xml_get_error_code($repeat_parser)), 1264 xml_get_current_line_number($repeat_parser) 1265 )); 1266 } 1267 xml_parser_free($repeat_parser); 1268 $i++; 1269 } 1270 // Restore original values 1271 $this->parser = array_pop($this->parser_stack); 1272 $this->gedrec = $oldgedrec; 1273 } 1274 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 1275 } 1276 1277 /** 1278 * Setting upp or changing variables in the XML 1279 * The XML variable name and value is stored in $this->vars 1280 * 1281 * @param array $attrs an array of key value pairs for the attributes 1282 */ 1283 private function setVarStartHandler($attrs) { 1284 if (empty($attrs['name'])) { 1285 throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file'); 1286 } 1287 1288 $name = $attrs['name']; 1289 $value = $attrs['value']; 1290 $match = array(); 1291 // Current GEDCOM record strings 1292 if ($value == "@ID") { 1293 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1294 $value = $match[1]; 1295 } 1296 } elseif ($value == "@fact") { 1297 $value = $this->fact; 1298 } elseif ($value == "@desc") { 1299 $value = $this->desc; 1300 } elseif ($value == "@generation") { 1301 $value = $this->generation; 1302 } elseif (preg_match("/@(\w+)/", $value, $match)) { 1303 $gmatch = array(); 1304 if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) { 1305 $value = str_replace("@", "", trim($gmatch[1])); 1306 } 1307 } 1308 if (preg_match("/\\$(\w+)/", $name, $match)) { 1309 $name = $this->vars["'" . $match[1] . "'"]['id']; 1310 } 1311 $count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER); 1312 $i = 0; 1313 while ($i < $count) { 1314 $t = $this->vars[$match[$i][1]]['id']; 1315 $value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1); 1316 $i++; 1317 } 1318 if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) { 1319 $value = I18N::number($match[1]); 1320 } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) { 1321 $value = I18N::translate($match[1]); 1322 } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) { 1323 $value = I18N::translateContext($match[1], $match[2]); 1324 } 1325 // Arithmetic functions 1326 if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) { 1327 switch ($match[2]) { 1328 case "+": 1329 $t = $match[1] + $match[3]; 1330 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1331 break; 1332 case "-": 1333 $t = $match[1] - $match[3]; 1334 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1335 break; 1336 case "*": 1337 $t = $match[1] * $match[3]; 1338 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1339 break; 1340 case "/": 1341 $t = $match[1] / $match[3]; 1342 $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value); 1343 break; 1344 } 1345 } 1346 if (strpos($value, "@") !== false) { 1347 $value = ""; 1348 } 1349 $this->vars[$name]['id'] = $value; 1350 } 1351 1352 /** 1353 * XML <if > start element 1354 * 1355 * @param array $attrs an array of key value pairs for the attributes 1356 */ 1357 private function ifStartHandler($attrs) { 1358 if ($this->process_ifs > 0) { 1359 $this->process_ifs++; 1360 1361 return; 1362 } 1363 1364 $condition = $attrs['condition']; 1365 $condition = $this->substituteVars($condition, true); 1366 $condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition); 1367 // Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT 1368 $condition = str_replace("@fact", $this->fact, $condition); 1369 $match = array(); 1370 $count = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER); 1371 $i = 0; 1372 while ($i < $count) { 1373 $id = $match[$i][1]; 1374 $value = '""'; 1375 if ($id == "ID") { 1376 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1377 $value = "'" . $match[1] . "'"; 1378 } 1379 } elseif ($id === "fact") { 1380 $value = '"' . $this->fact . '"'; 1381 } elseif ($id === "desc") { 1382 $value = '"' . addslashes($this->desc) . '"'; 1383 } elseif ($id === "generation") { 1384 $value = '"' . $this->generation . '"'; 1385 } else { 1386 1387 $temp = explode(" ", trim($this->gedrec)); 1388 $level = $temp[0]; 1389 if ($level == 0) { 1390 $level++; 1391 } 1392 $value = $this->getGedcomValue($id, $level, $this->gedrec); 1393 if (empty($value)) { 1394 $level++; 1395 $value = $this->getGedcomValue($id, $level, $this->gedrec); 1396 } 1397 $value = preg_replace("/^@(" . WT_REGEX_XREF . ")@$/", "$1", $value); 1398 $value = "\"" . addslashes($value) . "\""; 1399 } 1400 $condition = str_replace("@$id", $value, $condition); 1401 $i++; 1402 } 1403 $condition = "return (bool) ($condition);"; 1404 $ret = @eval($condition); 1405 if (!$ret) { 1406 $this->process_ifs++; 1407 } 1408 } 1409 1410 /** 1411 * XML <if /> end element 1412 */ 1413 private function ifEndHandler() { 1414 if ($this->process_ifs > 0) { 1415 $this->process_ifs--; 1416 } 1417 } 1418 1419 /** 1420 * XML <Footnote > start element 1421 * Collect the Footnote links 1422 * GEDCOM Records that are protected by Privacy setting will be ignore 1423 * 1424 * @param array $attrs an array of key value pairs for the attributes 1425 */ 1426 private function footnoteStartHandler($attrs) { 1427 global $WT_TREE; 1428 1429 $id = ""; 1430 if (preg_match("/[0-9] (.+) @(.+)@/", $this->gedrec, $match)) { 1431 $id = $match[2]; 1432 } 1433 $record = GedcomRecord::GetInstance($id, $WT_TREE); 1434 if ($record && $record->canShow()) { 1435 array_push($this->print_data_stack, $this->print_data); 1436 $this->print_data = true; 1437 $style = ""; 1438 if (!empty($attrs['style'])) { 1439 $style = $attrs['style']; 1440 } 1441 $this->footnote_element = $this->current_element; 1442 $this->current_element = $this->report_root->createFootnote($style); 1443 } else { 1444 $this->print_data = false; 1445 $this->process_footnote = false; 1446 } 1447 } 1448 1449 /** 1450 * XML <Footnote /> end element 1451 * Print the collected Footnote data 1452 */ 1453 private function footnoteEndHandler() { 1454 if ($this->process_footnote) { 1455 $this->print_data = array_pop($this->print_data_stack); 1456 $temp = trim($this->current_element->getValue()); 1457 if (strlen($temp) > 3) { 1458 $this->wt_report->addElement($this->current_element); 1459 } 1460 $this->current_element = $this->footnote_element; 1461 } else { 1462 $this->process_footnote = true; 1463 } 1464 } 1465 1466 /** 1467 * XML <FootnoteTexts /> element 1468 */ 1469 private function footnoteTextsStartHandler() { 1470 $temp = "footnotetexts"; 1471 $this->wt_report->addElement($temp); 1472 } 1473 1474 /** 1475 * XML <AgeAtDeath /> element handler 1476 */ 1477 private function ageAtDeathStartHandler() { 1478 // This duplicates functionality in FunctionsPrint::format_fact_date() 1479 global $factrec, $WT_TREE; 1480 1481 $match = array(); 1482 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1483 $person = Individual::getInstance($match[1], $WT_TREE); 1484 // Recorded age 1485 if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) { 1486 $fact_age = $match[1]; 1487 } else { 1488 $fact_age = ''; 1489 } 1490 if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) { 1491 $husb_age = $match[1]; 1492 } else { 1493 $husb_age = ''; 1494 } 1495 if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) { 1496 $wife_age = $match[1]; 1497 } else { 1498 $wife_age = ''; 1499 } 1500 1501 // Calculated age 1502 $birth_date = $person->getBirthDate(); 1503 // Can't use getDeathDate(), as this also gives BURI/CREM events, which 1504 // wouldn't give the correct "days after death" result for people with 1505 // no DEAT. 1506 $death_event = $person->getFirstFact('DEAT'); 1507 if ($death_event) { 1508 $death_date = $death_event->getDate(); 1509 } else { 1510 $death_date = new Date(''); 1511 } 1512 $value = ''; 1513 if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) { 1514 $age = Date::getAgeGedcom($birth_date, $death_date); 1515 // Only show calculated age if it differs from recorded age 1516 if ($age != '' && $age != "0d") { 1517 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 1518 ) { 1519 $value = FunctionsDate::getAgeAtEvent($age, false); 1520 $abbrev = substr($value, 0, strpos($value, ' ') + 5); 1521 if ($value !== $abbrev) { 1522 $value = $abbrev . '.'; 1523 } 1524 } 1525 } 1526 } 1527 $this->current_element->addText($value); 1528 } 1529 } 1530 1531 /** 1532 * XML element Forced line break handler - HTML code 1533 */ 1534 private function brStartHandler() { 1535 if ($this->print_data && $this->process_gedcoms === 0) { 1536 $this->current_element->addText('<br>'); 1537 } 1538 } 1539 1540 /** 1541 * XML <sp />element Forced space handler 1542 */ 1543 private function spStartHandler() { 1544 if ($this->print_data && $this->process_gedcoms === 0) { 1545 $this->current_element->addText(' '); 1546 } 1547 } 1548 1549 /** 1550 * XML <HighlightedImage/> 1551 * 1552 * @param array $attrs an array of key value pairs for the attributes 1553 */ 1554 private function highlightedImageStartHandler($attrs) { 1555 global $WT_TREE; 1556 1557 $id = ''; 1558 $match = array(); 1559 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 1560 $id = $match[1]; 1561 } 1562 1563 // mixed Position the top corner of this box on the page. the default is the current position 1564 $top = '.'; 1565 if (isset($attrs['top'])) { 1566 if ($attrs['top'] === '0') { 1567 $top = 0; 1568 } elseif ($attrs['top'] === '.') { 1569 $top = '.'; 1570 } elseif (!empty($attrs['top'])) { 1571 $top = (int) $attrs['top']; 1572 } 1573 } 1574 1575 // mixed Position the left corner of this box on the page. the default is the current position 1576 $left = '.'; 1577 if (isset($attrs['left'])) { 1578 if ($attrs['left'] === '0') { 1579 $left = 0; 1580 } elseif ($attrs['left'] === '.') { 1581 $left = '.'; 1582 } elseif (!empty($attrs['left'])) { 1583 $left = (int) $attrs['left']; 1584 } 1585 } 1586 1587 // string Align the image in left, center, right 1588 $align = ''; 1589 if (!empty($attrs['align'])) { 1590 $align = $attrs['align']; 1591 } 1592 1593 // string Next Line should be T:next to the image, N:next line 1594 $ln = ''; 1595 if (!empty($attrs['ln'])) { 1596 $ln = $attrs['ln']; 1597 } 1598 1599 $width = 0; 1600 $height = 0; 1601 if (!empty($attrs['width'])) { 1602 $width = (int) $attrs['width']; 1603 } 1604 if (!empty($attrs['height'])) { 1605 $height = (int) $attrs['height']; 1606 } 1607 1608 $person = Individual::getInstance($id, $WT_TREE); 1609 $mediaobject = $person->findHighlightedMedia(); 1610 if ($mediaobject) { 1611 $attributes = $mediaobject->getImageAttributes('thumb'); 1612 if (in_array( 1613 $attributes['ext'], 1614 array( 1615 'GIF', 1616 'JPG', 1617 'PNG', 1618 'SWF', 1619 'PSD', 1620 'BMP', 1621 'TIFF', 1622 'TIFF', 1623 'JPC', 1624 'JP2', 1625 'JPX', 1626 'JB2', 1627 'SWC', 1628 'IFF', 1629 'WBMP', 1630 'XBM', 1631 ) 1632 ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') 1633 ) { 1634 if ($width > 0 && $height == 0) { 1635 $perc = $width / $attributes['adjW']; 1636 $height = round($attributes['adjH'] * $perc); 1637 } elseif ($height > 0 && $width == 0) { 1638 $perc = $height / $attributes['adjH']; 1639 $width = round($attributes['adjW'] * $perc); 1640 } else { 1641 $width = $attributes['adjW']; 1642 $height = $attributes['adjH']; 1643 } 1644 $image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); 1645 $this->wt_report->addElement($image); 1646 } 1647 } 1648 } 1649 1650 /** 1651 * XML <Image/> 1652 * 1653 * @param array $attrs an array of key value pairs for the attributes 1654 */ 1655 private function imageStartHandler($attrs) { 1656 global $WT_TREE; 1657 1658 // mixed Position the top corner of this box on the page. the default is the current position 1659 $top = '.'; 1660 if (isset($attrs['top'])) { 1661 if ($attrs['top'] === "0") { 1662 $top = 0; 1663 } elseif ($attrs['top'] === '.') { 1664 $top = '.'; 1665 } elseif (!empty($attrs['top'])) { 1666 $top = (int) $attrs['top']; 1667 } 1668 } 1669 1670 // mixed Position the left corner of this box on the page. the default is the current position 1671 $left = '.'; 1672 if (isset($attrs['left'])) { 1673 if ($attrs['left'] === '0') { 1674 $left = 0; 1675 } elseif ($attrs['left'] === '.') { 1676 $left = '.'; 1677 } elseif (!empty($attrs['left'])) { 1678 $left = (int) $attrs['left']; 1679 } 1680 } 1681 1682 // string Align the image in left, center, right 1683 $align = ''; 1684 if (!empty($attrs['align'])) { 1685 $align = $attrs['align']; 1686 } 1687 1688 // string Next Line should be T:next to the image, N:next line 1689 $ln = 'T'; 1690 if (!empty($attrs['ln'])) { 1691 $ln = $attrs['ln']; 1692 } 1693 1694 $width = 0; 1695 $height = 0; 1696 if (!empty($attrs['width'])) { 1697 $width = (int) $attrs['width']; 1698 } 1699 if (!empty($attrs['height'])) { 1700 $height = (int) $attrs['height']; 1701 } 1702 1703 $file = ''; 1704 if (!empty($attrs['file'])) { 1705 $file = $attrs['file']; 1706 } 1707 if ($file == "@FILE") { 1708 $match = array(); 1709 if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) { 1710 $mediaobject = Media::getInstance($match[1], $WT_TREE); 1711 $attributes = $mediaobject->getImageAttributes('thumb'); 1712 if (in_array( 1713 $attributes['ext'], 1714 array( 1715 'GIF', 1716 'JPG', 1717 'PNG', 1718 'SWF', 1719 'PSD', 1720 'BMP', 1721 'TIFF', 1722 'TIFF', 1723 'JPC', 1724 'JP2', 1725 'JPX', 1726 'JB2', 1727 'SWC', 1728 'IFF', 1729 'WBMP', 1730 'XBM', 1731 ) 1732 ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb') 1733 ) { 1734 if ($width > 0 && $height == 0) { 1735 $perc = $width / $attributes['adjW']; 1736 $height = round($attributes['adjH'] * $perc); 1737 } elseif ($height > 0 && $width == 0) { 1738 $perc = $height / $attributes['adjH']; 1739 $width = round($attributes['adjW'] * $perc); 1740 } else { 1741 $width = $attributes['adjW']; 1742 $height = $attributes['adjH']; 1743 } 1744 $image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln); 1745 $this->wt_report->addElement($image); 1746 } 1747 } 1748 } else { 1749 if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) { 1750 $size = getimagesize($file); 1751 if ($width > 0 && $height == 0) { 1752 $perc = $width / $size[0]; 1753 $height = round($size[1] * $perc); 1754 } elseif ($height > 0 && $width == 0) { 1755 $perc = $height / $size[1]; 1756 $width = round($size[0] * $perc); 1757 } else { 1758 $width = $size[0]; 1759 $height = $size[1]; 1760 } 1761 $image = $this->report_root->createImage($file, $left, $top, $width, $height, $align, $ln); 1762 $this->wt_report->addElement($image); 1763 } 1764 } 1765 } 1766 1767 /** 1768 * XML <Line> element handler 1769 * 1770 * @param array $attrs an array of key value pairs for the attributes 1771 */ 1772 private function lineStartHandler($attrs) { 1773 // Start horizontal position, current position (default) 1774 $x1 = "."; 1775 if (isset($attrs['x1'])) { 1776 if ($attrs['x1'] === "0") { 1777 $x1 = 0; 1778 } elseif ($attrs['x1'] === ".") { 1779 $x1 = "."; 1780 } elseif (!empty($attrs['x1'])) { 1781 $x1 = (int) $attrs['x1']; 1782 } 1783 } 1784 // Start vertical position, current position (default) 1785 $y1 = "."; 1786 if (isset($attrs['y1'])) { 1787 if ($attrs['y1'] === "0") { 1788 $y1 = 0; 1789 } elseif ($attrs['y1'] === ".") { 1790 $y1 = "."; 1791 } elseif (!empty($attrs['y1'])) { 1792 $y1 = (int) $attrs['y1']; 1793 } 1794 } 1795 // End horizontal position, maximum width (default) 1796 $x2 = "."; 1797 if (isset($attrs['x2'])) { 1798 if ($attrs['x2'] === "0") { 1799 $x2 = 0; 1800 } elseif ($attrs['x2'] === ".") { 1801 $x2 = "."; 1802 } elseif (!empty($attrs['x2'])) { 1803 $x2 = (int) $attrs['x2']; 1804 } 1805 } 1806 // End vertical position 1807 $y2 = "."; 1808 if (isset($attrs['y2'])) { 1809 if ($attrs['y2'] === "0") { 1810 $y2 = 0; 1811 } elseif ($attrs['y2'] === ".") { 1812 $y2 = "."; 1813 } elseif (!empty($attrs['y2'])) { 1814 $y2 = (int) $attrs['y2']; 1815 } 1816 } 1817 1818 $line = $this->report_root->createLine($x1, $y1, $x2, $y2); 1819 $this->wt_report->addElement($line); 1820 } 1821 1822 /** 1823 * XML <List> 1824 * 1825 * @param array $attrs an array of key value pairs for the attributes 1826 */ 1827 private function listStartHandler($attrs) { 1828 global $WT_TREE; 1829 1830 $this->process_repeats++; 1831 if ($this->process_repeats > 1) { 1832 return; 1833 } 1834 1835 $match = array(); 1836 if (isset($attrs['sortby'])) { 1837 $sortby = $attrs['sortby']; 1838 if (preg_match("/\\$(\w+)/", $sortby, $match)) { 1839 $sortby = $this->vars[$match[1]]['id']; 1840 $sortby = trim($sortby); 1841 } 1842 } else { 1843 $sortby = "NAME"; 1844 } 1845 1846 if (isset($attrs['list'])) { 1847 $listname = $attrs['list']; 1848 } else { 1849 $listname = "individual"; 1850 } 1851 // Some filters/sorts can be applied using SQL, while others require PHP 1852 switch ($listname) { 1853 case "pending": 1854 $rows = Database::prepare( 1855 "SELECT xref, CASE new_gedcom WHEN '' THEN old_gedcom ELSE new_gedcom END AS gedcom" . 1856 " FROM `##change`" . " WHERE (xref, change_id) IN (" . 1857 " SELECT xref, MAX(change_id)" . 1858 " FROM `##change`" . 1859 " WHERE status = 'pending' AND gedcom_id = :tree_id" . 1860 " GROUP BY xref" . 1861 " )" 1862 )->execute(array( 1863 'tree_id' => $WT_TREE->getTreeId(), 1864 ))->fetchAll(); 1865 $this->list = array(); 1866 foreach ($rows as $row) { 1867 $this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom); 1868 } 1869 break; 1870 case 'individual': 1871 $sql_select = "SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` "; 1872 $sql_join = ""; 1873 $sql_where = " WHERE i_file = :tree_id"; 1874 $sql_order_by = ""; 1875 $sql_params = array('tree_id' => $WT_TREE->getTreeId()); 1876 foreach ($attrs as $attr => $value) { 1877 if (strpos($attr, 'filter') === 0 && $value) { 1878 $value = $this->substituteVars($value, false); 1879 // Convert the various filters into SQL 1880 if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { 1881 $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)"; 1882 $sql_where .= " AND {$attr}.d_fact = :{$attr}fact"; 1883 $sql_params[$attr . 'fact'] = $match[1]; 1884 $date = new Date($match[3]); 1885 if ($match[2] == "LTE") { 1886 $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date"; 1887 $sql_params[$attr . 'date'] = $date->maximumJulianDay(); 1888 } else { 1889 $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date"; 1890 $sql_params[$attr . 'date'] = $date->minimumJulianDay(); 1891 } 1892 if ($sortby == $match[1]) { 1893 $sortby = ""; 1894 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1"; 1895 } 1896 unset($attrs[$attr]); // This filter has been fully processed 1897 } elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) { 1898 // Do nothing, unless you have to 1899 if ($match[1] != '' || $sortby == 'NAME') { 1900 $sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)"; 1901 // Search the DB only if there is any name supplied 1902 if ($match[1] != "") { 1903 $names = explode(" ", $match[1]); 1904 foreach ($names as $n => $name) { 1905 $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')"; 1906 $sql_params[$attr . 'name' . $n] = $name; 1907 } 1908 } 1909 // Let the DB do the name sorting even when no name was entered 1910 if ($sortby == "NAME") { 1911 $sortby = ""; 1912 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort"; 1913 } 1914 } 1915 unset($attrs[$attr]); // This filter has been fully processed 1916 } elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) { 1917 $sql_where .= " AND i_gedcom REGEXP :{$attr}gedcom"; 1918 // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT" 1919 $sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]); 1920 unset($attrs[$attr]); // This filter has been fully processed 1921 } elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) { 1922 $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file = i_file)"; 1923 $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)"; 1924 $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')"; 1925 $sql_params[$attr . 'place'] = $match[1]; 1926 // Don't unset this filter. This is just initial filtering 1927 } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { 1928 $sql_where .= " AND i_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')"; 1929 $sql_params[$attr . 'contains1'] = $match[1]; 1930 $sql_params[$attr . 'contains2'] = $match[2]; 1931 $sql_params[$attr . 'contains3'] = $match[3]; 1932 // Don't unset this filter. This is just initial filtering 1933 } 1934 } 1935 } 1936 1937 $this->list = array(); 1938 $rows = Database::prepare( 1939 $sql_select . $sql_join . $sql_where . $sql_order_by 1940 )->execute($sql_params)->fetchAll(); 1941 1942 foreach ($rows as $row) { 1943 $this->list[] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom); 1944 } 1945 break; 1946 1947 case 'family': 1948 $sql_select = "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom FROM `##families`"; 1949 $sql_join = ""; 1950 $sql_where = " WHERE f_file = :tree_id"; 1951 $sql_order_by = ""; 1952 $sql_params = array('tree_id' => $WT_TREE->getTreeId()); 1953 foreach ($attrs as $attr => $value) { 1954 if (strpos($attr, 'filter') === 0 && $value) { 1955 $value = $this->substituteVars($value, false); 1956 // Convert the various filters into SQL 1957 if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { 1958 $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)"; 1959 $sql_where .= " AND {$attr}.d_fact = :{$attr}fact"; 1960 $sql_params[$attr . 'fact'] = $match[1]; 1961 $date = new Date($match[3]); 1962 if ($match[2] == "LTE") { 1963 $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date"; 1964 $sql_params[$attr . 'date'] = $date->maximumJulianDay(); 1965 } else { 1966 $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date"; 1967 $sql_params[$attr . 'date'] = $date->minimumJulianDay(); 1968 } 1969 if ($sortby == $match[1]) { 1970 $sortby = ""; 1971 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1"; 1972 } 1973 unset($attrs[$attr]); // This filter has been fully processed 1974 } elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) { 1975 $sql_where .= " AND f_gedcom REGEXP :{$attr}gedcom"; 1976 // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT" 1977 $sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]); 1978 unset($attrs[$attr]); // This filter has been fully processed 1979 } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) { 1980 // Do nothing, unless you have to 1981 if ($match[1] != '' || $sortby == 'NAME') { 1982 $sql_join .= " JOIN `##name` AS {$attr} ON n_file = f_file AND n_id IN (f_husb, f_wife)"; 1983 // Search the DB only if there is any name supplied 1984 if ($match[1] != "") { 1985 $names = explode(" ", $match[1]); 1986 foreach ($names as $n => $name) { 1987 $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')"; 1988 $sql_params[$attr . 'name' . $n] = $name; 1989 } 1990 } 1991 // Let the DB do the name sorting even when no name was entered 1992 if ($sortby == "NAME") { 1993 $sortby = ""; 1994 $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort"; 1995 } 1996 } 1997 unset($attrs[$attr]); // This filter has been fully processed 1998 1999 } elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) { 2000 $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)"; 2001 $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)"; 2002 $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')"; 2003 $sql_params[$attr . 'place'] = $match[1]; 2004 // Don't unset this filter. This is just initial filtering 2005 } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) { 2006 $sql_where .= " AND f_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')"; 2007 $sql_params[$attr . 'contains1'] = $match[1]; 2008 $sql_params[$attr . 'contains2'] = $match[2]; 2009 $sql_params[$attr . 'contains3'] = $match[3]; 2010 // Don't unset this filter. This is just initial filtering 2011 } 2012 } 2013 } 2014 2015 $this->list = array(); 2016 $rows = Database::prepare( 2017 $sql_select . $sql_join . $sql_where . $sql_order_by 2018 )->execute($sql_params)->fetchAll(); 2019 2020 foreach ($rows as $row) { 2021 $this->list[] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom); 2022 } 2023 break; 2024 2025 default: 2026 throw new \DomainException('Invalid list name: ' . $listname); 2027 } 2028 2029 $filters = array(); 2030 $filters2 = array(); 2031 if (isset($attrs['filter1']) && count($this->list) > 0) { 2032 foreach ($attrs as $key => $value) { 2033 if (preg_match("/filter(\d)/", $key)) { 2034 $condition = $value; 2035 if (preg_match("/@(\w+)/", $condition, $match)) { 2036 $id = $match[1]; 2037 $value = "''"; 2038 if ($id == "ID") { 2039 if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { 2040 $value = "'" . $match[1] . "'"; 2041 } 2042 } elseif ($id == "fact") { 2043 $value = "'" . $this->fact . "'"; 2044 } elseif ($id == "desc") { 2045 $value = "'" . $this->desc . "'"; 2046 } else { 2047 if (preg_match("/\d $id (.+)/", $this->gedrec, $match)) { 2048 $value = "'" . str_replace("@", "", trim($match[1])) . "'"; 2049 } 2050 } 2051 $condition = preg_replace("/@$id/", $value, $condition); 2052 } 2053 //-- handle regular expressions 2054 if (preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match)) { 2055 $tag = trim($match[1]); 2056 $expr = trim($match[2]); 2057 $val = trim($match[3]); 2058 if (preg_match("/\\$(\w+)/", $val, $match)) { 2059 $val = $this->vars[$match[1]]['id']; 2060 $val = trim($val); 2061 } 2062 if ($val) { 2063 $searchstr = ""; 2064 $tags = explode(":", $tag); 2065 //-- only limit to a level number if we are specifically looking at a level 2066 if (count($tags) > 1) { 2067 $level = 1; 2068 foreach ($tags as $t) { 2069 if (!empty($searchstr)) { 2070 $searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n"; 2071 } 2072 //-- search for both EMAIL and _EMAIL... silly double gedcom standard 2073 if ($t == "EMAIL" || $t == "_EMAIL") { 2074 $t = "_?EMAIL"; 2075 } 2076 $searchstr .= $level . " " . $t; 2077 $level++; 2078 } 2079 } else { 2080 if ($tag == "EMAIL" || $tag == "_EMAIL") { 2081 $tag = "_?EMAIL"; 2082 } 2083 $t = $tag; 2084 $searchstr = "1 " . $tag; 2085 } 2086 switch ($expr) { 2087 case "CONTAINS": 2088 if ($t == "PLAC") { 2089 $searchstr .= "[^\n]*[, ]*" . $val; 2090 } else { 2091 $searchstr .= "[^\n]*" . $val; 2092 } 2093 $filters[] = $searchstr; 2094 break; 2095 default: 2096 $filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val); 2097 break; 2098 } 2099 } 2100 } 2101 } 2102 } 2103 } 2104 //-- apply other filters to the list that could not be added to the search string 2105 if ($filters) { 2106 foreach ($this->list as $key => $record) { 2107 foreach ($filters as $filter) { 2108 if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) { 2109 unset($this->list[$key]); 2110 break; 2111 } 2112 } 2113 } 2114 } 2115 if ($filters2) { 2116 $mylist = array(); 2117 foreach ($this->list as $indi) { 2118 $key = $indi->getXref(); 2119 $grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE)); 2120 $keep = true; 2121 foreach ($filters2 as $filter) { 2122 if ($keep) { 2123 $tag = $filter['tag']; 2124 $expr = $filter['expr']; 2125 $val = $filter['val']; 2126 if ($val == "''") { 2127 $val = ""; 2128 } 2129 $tags = explode(":", $tag); 2130 $t = end($tags); 2131 $v = $this->getGedcomValue($tag, 1, $grec); 2132 //-- check for EMAIL and _EMAIL (silly double gedcom standard :P) 2133 if ($t == "EMAIL" && empty($v)) { 2134 $tag = str_replace("EMAIL", "_EMAIL", $tag); 2135 $tags = explode(":", $tag); 2136 $t = end($tags); 2137 $v = Functions::getSubRecord(1, $tag, $grec); 2138 } 2139 2140 switch ($expr) { 2141 case "GTE": 2142 if ($t == "DATE") { 2143 $date1 = new Date($v); 2144 $date2 = new Date($val); 2145 $keep = (Date::compare($date1, $date2) >= 0); 2146 } elseif ($val >= $v) { 2147 $keep = true; 2148 } 2149 break; 2150 case "LTE": 2151 if ($t == "DATE") { 2152 $date1 = new Date($v); 2153 $date2 = new Date($val); 2154 $keep = (Date::compare($date1, $date2) <= 0); 2155 } elseif ($val >= $v) { 2156 $keep = true; 2157 } 2158 break; 2159 default: 2160 if ($v == $val) { 2161 $keep = true; 2162 } else { 2163 $keep = false; 2164 } 2165 break; 2166 } 2167 } 2168 } 2169 if ($keep) { 2170 $mylist[$key] = $indi; 2171 } 2172 } 2173 $this->list = $mylist; 2174 } 2175 2176 switch ($sortby) { 2177 case 'NAME': 2178 uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare'); 2179 break; 2180 case 'CHAN': 2181 uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) { 2182 return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true); 2183 }); 2184 break; 2185 case 'BIRT:DATE': 2186 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); 2187 break; 2188 case 'DEAT:DATE': 2189 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); 2190 break; 2191 case 'MARR:DATE': 2192 uasort($this->list, '\Fisharebest\Webtrees\Family::compareMarrDate'); 2193 break; 2194 default: 2195 // unsorted or already sorted by SQL 2196 break; 2197 } 2198 2199 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 2200 $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1; 2201 } 2202 2203 /** 2204 * XML <List> 2205 */ 2206 private function listEndHandler() { 2207 global $report; 2208 2209 $this->process_repeats--; 2210 if ($this->process_repeats > 0) { 2211 return; 2212 } 2213 2214 // Check if there is any list 2215 if (count($this->list) > 0) { 2216 $lineoffset = 0; 2217 foreach ($this->repeats_stack as $rep) { 2218 $lineoffset += $rep[1]; 2219 } 2220 //-- read the xml from the file 2221 $lines = file($report); 2222 while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<List") === false) && (($lineoffset + $this->repeat_bytes) > 0)) { 2223 $lineoffset--; 2224 } 2225 $lineoffset++; 2226 $reportxml = "<tempdoc>\n"; 2227 $line_nr = $lineoffset + $this->repeat_bytes; 2228 // List Level counter 2229 $count = 1; 2230 while (0 < $count) { 2231 if (strpos($lines[$line_nr], "<List") !== false) { 2232 $count++; 2233 } elseif (strpos($lines[$line_nr], "</List") !== false) { 2234 $count--; 2235 } 2236 if (0 < $count) { 2237 $reportxml .= $lines[$line_nr]; 2238 } 2239 $line_nr++; 2240 } 2241 // No need to drag this 2242 unset($lines); 2243 $reportxml .= "</tempdoc>"; 2244 // Save original values 2245 array_push($this->parser_stack, $this->parser); 2246 $oldgedrec = $this->gedrec; 2247 2248 $this->list_total = count($this->list); 2249 $this->list_private = 0; 2250 foreach ($this->list as $record) { 2251 if ($record->canShow()) { 2252 $this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree())); 2253 //-- start the sax parser 2254 $repeat_parser = xml_parser_create(); 2255 $this->parser = $repeat_parser; 2256 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 2257 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 2258 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 2259 if (!xml_parse($repeat_parser, $reportxml, true)) { 2260 throw new \DomainException(sprintf( 2261 'ListEHandler XML error: %s at line %d', 2262 xml_error_string(xml_get_error_code($repeat_parser)), 2263 xml_get_current_line_number($repeat_parser) 2264 )); 2265 } 2266 xml_parser_free($repeat_parser); 2267 } else { 2268 $this->list_private++; 2269 } 2270 } 2271 $this->list = array(); 2272 $this->parser = array_pop($this->parser_stack); 2273 $this->gedrec = $oldgedrec; 2274 } 2275 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 2276 } 2277 2278 /** 2279 * XML <ListTotal> element handler 2280 * 2281 * Prints the total number of records in a list 2282 * The total number is collected from 2283 * List and Relatives 2284 */ 2285 private function listTotalStartHandler() { 2286 if ($this->list_private == 0) { 2287 $this->current_element->addText($this->list_total); 2288 } else { 2289 $this->current_element->addText(($this->list_total - $this->list_private) . " / " . $this->list_total); 2290 } 2291 } 2292 2293 /** 2294 * XML <Relatives> 2295 * 2296 * @param array $attrs an array of key value pairs for the attributes 2297 */ 2298 private function relativesStartHandler($attrs) { 2299 global $WT_TREE; 2300 2301 $this->process_repeats++; 2302 if ($this->process_repeats > 1) { 2303 return; 2304 } 2305 2306 $sortby = "NAME"; 2307 if (isset($attrs['sortby'])) { 2308 $sortby = $attrs['sortby']; 2309 } 2310 $match = array(); 2311 if (preg_match("/\\$(\w+)/", $sortby, $match)) { 2312 $sortby = $this->vars[$match[1]]['id']; 2313 $sortby = trim($sortby); 2314 } 2315 2316 $maxgen = -1; 2317 if (isset($attrs['maxgen'])) { 2318 $maxgen = $attrs['maxgen']; 2319 } 2320 if ($maxgen == "*") { 2321 $maxgen = -1; 2322 } 2323 2324 $group = "child-family"; 2325 if (isset($attrs['group'])) { 2326 $group = $attrs['group']; 2327 } 2328 if (preg_match("/\\$(\w+)/", $group, $match)) { 2329 $group = $this->vars[$match[1]]['id']; 2330 $group = trim($group); 2331 } 2332 2333 $id = ""; 2334 if (isset($attrs['id'])) { 2335 $id = $attrs['id']; 2336 } 2337 if (preg_match("/\\$(\w+)/", $id, $match)) { 2338 $id = $this->vars[$match[1]]['id']; 2339 $id = trim($id); 2340 } 2341 2342 $this->list = array(); 2343 $person = Individual::getInstance($id, $WT_TREE); 2344 if (!empty($person)) { 2345 $this->list[$id] = $person; 2346 switch ($group) { 2347 case "child-family": 2348 foreach ($person->getChildFamilies() as $family) { 2349 $husband = $family->getHusband(); 2350 $wife = $family->getWife(); 2351 if (!empty($husband)) { 2352 $this->list[$husband->getXref()] = $husband; 2353 } 2354 if (!empty($wife)) { 2355 $this->list[$wife->getXref()] = $wife; 2356 } 2357 $children = $family->getChildren(); 2358 foreach ($children as $child) { 2359 if (!empty($child)) { 2360 $this->list[$child->getXref()] = $child; 2361 } 2362 } 2363 } 2364 break; 2365 case "spouse-family": 2366 foreach ($person->getSpouseFamilies() as $family) { 2367 $husband = $family->getHusband(); 2368 $wife = $family->getWife(); 2369 if (!empty($husband)) { 2370 $this->list[$husband->getXref()] = $husband; 2371 } 2372 if (!empty($wife)) { 2373 $this->list[$wife->getXref()] = $wife; 2374 } 2375 $children = $family->getChildren(); 2376 foreach ($children as $child) { 2377 if (!empty($child)) { 2378 $this->list[$child->getXref()] = $child; 2379 } 2380 } 2381 } 2382 break; 2383 case "direct-ancestors": 2384 $this->addAncestors($this->list, $id, false, $maxgen); 2385 break; 2386 case "ancestors": 2387 $this->addAncestors($this->list, $id, true, $maxgen); 2388 break; 2389 case "descendants": 2390 $this->list[$id]->generation = 1; 2391 $this->addDescendancy($this->list, $id, false, $maxgen); 2392 break; 2393 case "all": 2394 $this->addAncestors($this->list, $id, true, $maxgen); 2395 $this->addDescendancy($this->list, $id, true, $maxgen); 2396 break; 2397 } 2398 } 2399 2400 switch ($sortby) { 2401 case 'NAME': 2402 uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare'); 2403 break; 2404 case 'BIRT:DATE': 2405 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate'); 2406 break; 2407 case 'DEAT:DATE': 2408 uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate'); 2409 break; 2410 case 'generation': 2411 $newarray = array(); 2412 reset($this->list); 2413 $genCounter = 1; 2414 while (count($newarray) < count($this->list)) { 2415 foreach ($this->list as $key => $value) { 2416 $this->generation = $value->generation; 2417 if ($this->generation == $genCounter) { 2418 $newarray[$key] = new \stdClass; 2419 $newarray[$key]->generation = $this->generation; 2420 } 2421 } 2422 $genCounter++; 2423 } 2424 $this->list = $newarray; 2425 break; 2426 default: 2427 // unsorted 2428 break; 2429 } 2430 array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); 2431 $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1; 2432 } 2433 2434 /** 2435 * XML </ Relatives> 2436 */ 2437 private function relativesEndHandler() { 2438 global $report, $WT_TREE; 2439 2440 $this->process_repeats--; 2441 if ($this->process_repeats > 0) { 2442 return; 2443 } 2444 2445 // Check if there is any relatives 2446 if (count($this->list) > 0) { 2447 $lineoffset = 0; 2448 foreach ($this->repeats_stack as $rep) { 2449 $lineoffset += $rep[1]; 2450 } 2451 //-- read the xml from the file 2452 $lines = file($report); 2453 while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<Relatives") === false) && (($lineoffset + $this->repeat_bytes) > 0)) { 2454 $lineoffset--; 2455 } 2456 $lineoffset++; 2457 $reportxml = "<tempdoc>\n"; 2458 $line_nr = $lineoffset + $this->repeat_bytes; 2459 // Relatives Level counter 2460 $count = 1; 2461 while (0 < $count) { 2462 if (strpos($lines[$line_nr], "<Relatives") !== false) { 2463 $count++; 2464 } elseif (strpos($lines[$line_nr], "</Relatives") !== false) { 2465 $count--; 2466 } 2467 if (0 < $count) { 2468 $reportxml .= $lines[$line_nr]; 2469 } 2470 $line_nr++; 2471 } 2472 // No need to drag this 2473 unset($lines); 2474 $reportxml .= "</tempdoc>\n"; 2475 // Save original values 2476 array_push($this->parser_stack, $this->parser); 2477 $oldgedrec = $this->gedrec; 2478 2479 $this->list_total = count($this->list); 2480 $this->list_private = 0; 2481 foreach ($this->list as $key => $value) { 2482 if (isset($value->generation)) { 2483 $this->generation = $value->generation; 2484 } 2485 $tmp = GedcomRecord::getInstance($key, $WT_TREE); 2486 $this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)); 2487 2488 $repeat_parser = xml_parser_create(); 2489 $this->parser = $repeat_parser; 2490 xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 2491 xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement')); 2492 xml_set_character_data_handler($repeat_parser, array($this, 'characterData')); 2493 2494 if (!xml_parse($repeat_parser, $reportxml, true)) { 2495 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))); 2496 } 2497 xml_parser_free($repeat_parser); 2498 } 2499 // Clean up the list array 2500 $this->list = array(); 2501 $this->parser = array_pop($this->parser_stack); 2502 $this->gedrec = $oldgedrec; 2503 } 2504 list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack); 2505 } 2506 2507 /** 2508 * XML <Generation /> element handler 2509 * 2510 * Prints the number of generations 2511 */ 2512 private function generationStartHandler() { 2513 $this->current_element->addText($this->generation); 2514 } 2515 2516 /** 2517 * XML <NewPage /> element handler 2518 * 2519 * Has to be placed in an element (header, pageheader, body or footer) 2520 */ 2521 private function newPageStartHandler() { 2522 $temp = "addpage"; 2523 $this->wt_report->addElement($temp); 2524 } 2525 2526 /** 2527 * XML <html> 2528 * 2529 * @param string $tag HTML tag name 2530 * @param array[] $attrs an array of key value pairs for the attributes 2531 */ 2532 private function htmlStartHandler($tag, $attrs) { 2533 if ($tag === "tempdoc") { 2534 return; 2535 } 2536 array_push($this->wt_report_stack, $this->wt_report); 2537 $this->wt_report = $this->report_root->createHTML($tag, $attrs); 2538 $this->current_element = $this->wt_report; 2539 2540 array_push($this->print_data_stack, $this->print_data); 2541 $this->print_data = true; 2542 } 2543 2544 /** 2545 * XML </html> 2546 * 2547 * @param string $tag 2548 */ 2549 private function htmlEndHandler($tag) { 2550 if ($tag === "tempdoc") { 2551 return; 2552 } 2553 2554 $this->print_data = array_pop($this->print_data_stack); 2555 $this->current_element = $this->wt_report; 2556 $this->wt_report = array_pop($this->wt_report_stack); 2557 if (!is_null($this->wt_report)) { 2558 $this->wt_report->addElement($this->current_element); 2559 } else { 2560 $this->wt_report = $this->current_element; 2561 } 2562 } 2563 2564 /** 2565 * Handle <Input> 2566 */ 2567 private function inputStartHandler() { 2568 // Dummy function, to prevent the default HtmlStartHandler() being called 2569 } 2570 2571 /** 2572 * Handle </Input> 2573 */ 2574 private function inputEndHandler() { 2575 // Dummy function, to prevent the default HtmlEndHandler() being called 2576 } 2577 2578 /** 2579 * Handle <Report> 2580 */ 2581 private function reportStartHandler() { 2582 // Dummy function, to prevent the default HtmlStartHandler() being called 2583 } 2584 2585 /** 2586 * Handle </Report> 2587 */ 2588 private function reportEndHandler() { 2589 // Dummy function, to prevent the default HtmlEndHandler() being called 2590 } 2591 2592 /** 2593 * XML </titleEndHandler> 2594 */ 2595 private function titleEndHandler() { 2596 $this->report_root->addTitle($this->text); 2597 } 2598 2599 /** 2600 * XML </descriptionEndHandler> 2601 */ 2602 private function descriptionEndHandler() { 2603 $this->report_root->addDescription($this->text); 2604 } 2605 2606 /** 2607 * Create a list of all descendants. 2608 * 2609 * @param string[] $list 2610 * @param string $pid 2611 * @param bool $parents 2612 * @param int $generations 2613 */ 2614 private function addDescendancy(&$list, $pid, $parents = false, $generations = -1) { 2615 global $WT_TREE; 2616 2617 $person = Individual::getInstance($pid, $WT_TREE); 2618 if ($person === null) { 2619 return; 2620 } 2621 if (!isset($list[$pid])) { 2622 $list[$pid] = $person; 2623 } 2624 if (!isset($list[$pid]->generation)) { 2625 $list[$pid]->generation = 0; 2626 } 2627 foreach ($person->getSpouseFamilies() as $family) { 2628 if ($parents) { 2629 $husband = $family->getHusband(); 2630 $wife = $family->getWife(); 2631 if ($husband) { 2632 $list[$husband->getXref()] = $husband; 2633 if (isset($list[$pid]->generation)) { 2634 $list[$husband->getXref()]->generation = $list[$pid]->generation - 1; 2635 } else { 2636 $list[$husband->getXref()]->generation = 1; 2637 } 2638 } 2639 if ($wife) { 2640 $list[$wife->getXref()] = $wife; 2641 if (isset($list[$pid]->generation)) { 2642 $list[$wife->getXref()]->generation = $list[$pid]->generation - 1; 2643 } else { 2644 $list[$wife->getXref()]->generation = 1; 2645 } 2646 } 2647 } 2648 $children = $family->getChildren(); 2649 foreach ($children as $child) { 2650 if ($child) { 2651 $list[$child->getXref()] = $child; 2652 if (isset($list[$pid]->generation)) { 2653 $list[$child->getXref()]->generation = $list[$pid]->generation + 1; 2654 } else { 2655 $list[$child->getXref()]->generation = 2; 2656 } 2657 } 2658 } 2659 if ($generations == -1 || $list[$pid]->generation + 1 < $generations) { 2660 foreach ($children as $child) { 2661 $this->addDescendancy($list, $child->getXref(), $parents, $generations); // recurse on the childs family 2662 } 2663 } 2664 } 2665 } 2666 2667 /** 2668 * Create a list of all ancestors. 2669 * 2670 * @param string[] $list 2671 * @param string $pid 2672 * @param bool $children 2673 * @param int $generations 2674 */ 2675 private function addAncestors(&$list, $pid, $children = false, $generations = -1) { 2676 global $WT_TREE; 2677 2678 $genlist = array($pid); 2679 $list[$pid]->generation = 1; 2680 while (count($genlist) > 0) { 2681 $id = array_shift($genlist); 2682 if (strpos($id, 'empty') === 0) { 2683 continue; // id can be something like “empty7” 2684 } 2685 $person = Individual::getInstance($id, $WT_TREE); 2686 foreach ($person->getChildFamilies() as $family) { 2687 $husband = $family->getHusband(); 2688 $wife = $family->getWife(); 2689 if ($husband) { 2690 $list[$husband->getXref()] = $husband; 2691 $list[$husband->getXref()]->generation = $list[$id]->generation + 1; 2692 } 2693 if ($wife) { 2694 $list[$wife->getXref()] = $wife; 2695 $list[$wife->getXref()]->generation = $list[$id]->generation + 1; 2696 } 2697 if ($generations == -1 || $list[$id]->generation + 1 < $generations) { 2698 if ($husband) { 2699 array_push($genlist, $husband->getXref()); 2700 } 2701 if ($wife) { 2702 array_push($genlist, $wife->getXref()); 2703 } 2704 } 2705 if ($children) { 2706 foreach ($family->getChildren() as $child) { 2707 $list[$child->getXref()] = $child; 2708 if (isset($list[$id]->generation)) { 2709 $list[$child->getXref()]->generation = $list[$id]->generation; 2710 } else { 2711 $list[$child->getXref()]->generation = 1; 2712 } 2713 } 2714 } 2715 } 2716 } 2717 } 2718 2719 /** 2720 * get gedcom tag value 2721 * 2722 * @param string $tag The tag to find, use : to delineate subtags 2723 * @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 2724 * @param string $gedrec The gedcom record to get the value from 2725 * 2726 * @return string the value of a gedcom tag from the given gedcom record 2727 */ 2728 private function getGedcomValue($tag, $level, $gedrec) { 2729 global $WT_TREE; 2730 2731 if (empty($gedrec)) { 2732 return ''; 2733 } 2734 $tags = explode(':', $tag); 2735 $origlevel = $level; 2736 if ($level == 0) { 2737 $level = $gedrec{0} + 1; 2738 } 2739 2740 $subrec = $gedrec; 2741 foreach ($tags as $t) { 2742 $lastsubrec = $subrec; 2743 $subrec = Functions::getSubRecord($level, "$level $t", $subrec); 2744 if (empty($subrec) && $origlevel == 0) { 2745 $level--; 2746 $subrec = Functions::getSubRecord($level, "$level $t", $lastsubrec); 2747 } 2748 if (empty($subrec)) { 2749 if ($t == "TITL") { 2750 $subrec = Functions::getSubRecord($level, "$level ABBR", $lastsubrec); 2751 if (!empty($subrec)) { 2752 $t = "ABBR"; 2753 } 2754 } 2755 if (empty($subrec)) { 2756 if ($level > 0) { 2757 $level--; 2758 } 2759 $subrec = Functions::getSubRecord($level, "@ $t", $gedrec); 2760 if (empty($subrec)) { 2761 return ''; 2762 } 2763 } 2764 } 2765 $level++; 2766 } 2767 $level--; 2768 $ct = preg_match("/$level $t(.*)/", $subrec, $match); 2769 if ($ct == 0) { 2770 $ct = preg_match("/$level @.+@ (.+)/", $subrec, $match); 2771 } 2772 if ($ct == 0) { 2773 $ct = preg_match("/@ $t (.+)/", $subrec, $match); 2774 } 2775 if ($ct > 0) { 2776 $value = trim($match[1]); 2777 if ($t == 'NOTE' && preg_match('/^@(.+)@$/', $value, $match)) { 2778 $note = Note::getInstance($match[1], $WT_TREE); 2779 if ($note) { 2780 $value = $note->getNote(); 2781 } else { 2782 //-- set the value to the id without the @ 2783 $value = $match[1]; 2784 } 2785 } 2786 if ($level != 0 || $t != "NOTE") { 2787 $value .= Functions::getCont($level + 1, $subrec); 2788 } 2789 2790 return $value; 2791 } 2792 2793 return ""; 2794 } 2795 2796 /** 2797 * Replace variable identifiers with their values. 2798 * 2799 * @param string $expression An expression such as "$foo == 123" 2800 * @param bool $quote Whether to add quotation marks 2801 * 2802 * @return string 2803 */ 2804 private function substituteVars($expression, $quote) { 2805 $that = $this; // PHP5.3 cannot access $this inside a closure 2806 return preg_replace_callback( 2807 '/\$(\w+)/', 2808 function ($matches) use ($that, $quote) { 2809 if (isset($that->vars[$matches[1]]['id'])) { 2810 if ($quote) { 2811 return "'" . addcslashes($that->vars[$matches[1]]['id'], "'") . "'"; 2812 } else { 2813 return $that->vars[$matches[1]]['id']; 2814 } 2815 } else { 2816 Log::addErrorLog(sprintf('Undefined variable $%s in report', $matches[1])); 2817 2818 return '$' . $matches[1]; 2819 } 2820 }, 2821 $expression 2822 ); 2823 } 2824} 2825