1a6f13a4aSGreg Roach<?php 23976b470SGreg Roach 3a6f13a4aSGreg Roach/** 4a6f13a4aSGreg Roach * webtrees: online genealogy 589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team 6a6f13a4aSGreg Roach * This program is free software: you can redistribute it and/or modify 7a6f13a4aSGreg Roach * it under the terms of the GNU General Public License as published by 8a6f13a4aSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9a6f13a4aSGreg Roach * (at your option) any later version. 10a6f13a4aSGreg Roach * This program is distributed in the hope that it will be useful, 11a6f13a4aSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12a6f13a4aSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13a6f13a4aSGreg Roach * GNU General Public License for more details. 14a6f13a4aSGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16a6f13a4aSGreg Roach */ 17fcfa147eSGreg Roach 18e7f56f2aSGreg Roachdeclare(strict_types=1); 19e7f56f2aSGreg Roach 2076692c8bSGreg Roachnamespace Fisharebest\Webtrees\Report; 2176692c8bSGreg Roach 226ccdf4f0SGreg Roachuse DomainException; 23a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Auth; 244459dc9aSGreg Roachuse Fisharebest\Webtrees\Carbon; 25a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Date; 2697def6bcSGreg Roachuse Fisharebest\Webtrees\Elements\UnknownElement; 27a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Family; 283d7a8a4cSGreg Roachuse Fisharebest\Webtrees\Functions\Functions; 299a27f660SGreg Roachuse Fisharebest\Webtrees\Gedcom; 30a6f13a4aSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 31a6f13a4aSGreg Roachuse Fisharebest\Webtrees\I18N; 32a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Individual; 33d1286247SGreg Roachuse Fisharebest\Webtrees\Log; 34a04bb9a2SGreg Roachuse Fisharebest\Webtrees\MediaFile; 35729ce104SGreg Roachuse Fisharebest\Webtrees\Note; 36a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Place; 37d0889c63SGreg Roachuse Fisharebest\Webtrees\Registry; 38299d100dSGreg Roachuse Fisharebest\Webtrees\Tree; 39195b5e75SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 40195b5e75SGreg Roachuse Illuminate\Database\Query\Builder; 41a69f5655SGreg Roachuse Illuminate\Database\Query\Expression; 425985adfbSGreg Roachuse Illuminate\Database\Query\JoinClause; 439a9e551aSGreg Roachuse Illuminate\Support\Str; 44f7cf8a15SGreg Roachuse League\Flysystem\FilesystemOperator; 4531d9777aSGreg Roachuse LogicException; 4679529c87SGreg Roachuse stdClass; 47c0fe75acSGreg Roachuse Symfony\Component\Cache\Adapter\NullAdapter; 485809450fSGreg Roachuse Symfony\Component\ExpressionLanguage\ExpressionLanguage; 494e69366aSGreg Roachuse XMLParser; 5059597b37SGreg Roach 51b6f35a76SGreg Roachuse function addcslashes; 52b6f35a76SGreg Roachuse function addslashes; 53b6f35a76SGreg Roachuse function array_pop; 54b6f35a76SGreg Roachuse function array_shift; 55b6f35a76SGreg Roachuse function assert; 56b19e047dSGreg Roachuse function call_user_func; 57b6f35a76SGreg Roachuse function count; 58b6f35a76SGreg Roachuse function end; 59b2448a1bSGreg Roachuse function explode; 60b6f35a76SGreg Roachuse function file; 61b6f35a76SGreg Roachuse function file_exists; 62b6f35a76SGreg Roachuse function getimagesize; 6329518ad2SGreg Roachuse function imagecreatefromstring; 6429518ad2SGreg Roachuse function imagesx; 6529518ad2SGreg Roachuse function imagesy; 66b6f35a76SGreg Roachuse function in_array; 67b19e047dSGreg Roachuse function method_exists; 68b6f35a76SGreg Roachuse function preg_match; 69b6f35a76SGreg Roachuse function preg_match_all; 70b6f35a76SGreg Roachuse function preg_replace; 71b6f35a76SGreg Roachuse function preg_replace_callback; 72b6f35a76SGreg Roachuse function preg_split; 73b6f35a76SGreg Roachuse function reset; 74b6f35a76SGreg Roachuse function round; 75b6f35a76SGreg Roachuse function sprintf; 76dec352c1SGreg Roachuse function str_contains; 77b6f35a76SGreg Roachuse function str_replace; 78dec352c1SGreg Roachuse function str_starts_with; 79b6f35a76SGreg Roachuse function strip_tags; 80b6f35a76SGreg Roachuse function strlen; 81b6f35a76SGreg Roachuse function strtoupper; 82b6f35a76SGreg Roachuse function substr; 83b2448a1bSGreg Roachuse function trim; 84b6f35a76SGreg Roachuse function uasort; 85b6f35a76SGreg Roachuse function xml_error_string; 86b6f35a76SGreg Roachuse function xml_get_current_line_number; 87b6f35a76SGreg Roachuse function xml_get_error_code; 88b6f35a76SGreg Roachuse function xml_parse; 89b6f35a76SGreg Roachuse function xml_parser_create; 90b6f35a76SGreg Roachuse function xml_parser_free; 91b6f35a76SGreg Roachuse function xml_parser_set_option; 92b6f35a76SGreg Roachuse function xml_set_character_data_handler; 93b6f35a76SGreg Roachuse function xml_set_element_handler; 94b6f35a76SGreg Roach 95b6f35a76SGreg Roachuse const PREG_SET_ORDER; 96b6f35a76SGreg Roachuse const XML_OPTION_CASE_FOLDING; 9729518ad2SGreg Roach 98a6f13a4aSGreg Roach/** 99a6f13a4aSGreg Roach * Class ReportParserGenerate - parse a report.xml file and generate the report. 100a6f13a4aSGreg Roach */ 101c1010edaSGreg Roachclass ReportParserGenerate extends ReportParserBase 102c1010edaSGreg Roach{ 103a6f13a4aSGreg Roach /** @var bool Are we collecting data from <Footnote> elements */ 104a6f13a4aSGreg Roach private $process_footnote = true; 105a6f13a4aSGreg Roach 106a6f13a4aSGreg Roach /** @var bool Are we currently outputing data? */ 107a6f13a4aSGreg Roach private $print_data = false; 108a6f13a4aSGreg Roach 109a6f13a4aSGreg Roach /** @var bool[] Push-down stack of $print_data */ 11013abd6f3SGreg Roach private $print_data_stack = []; 111a6f13a4aSGreg Roach 11276692c8bSGreg Roach /** @var int Are we processing GEDCOM data */ 113a6f13a4aSGreg Roach private $process_gedcoms = 0; 114a6f13a4aSGreg Roach 11576692c8bSGreg Roach /** @var int Are we processing conditionals */ 116a6f13a4aSGreg Roach private $process_ifs = 0; 117a6f13a4aSGreg Roach 11876692c8bSGreg Roach /** @var int Are we processing repeats */ 119a6f13a4aSGreg Roach private $process_repeats = 0; 120a6f13a4aSGreg Roach 121a6f13a4aSGreg Roach /** @var int Quantity of data to repeat during loops */ 122a6f13a4aSGreg Roach private $repeat_bytes = 0; 123a6f13a4aSGreg Roach 124*09482a55SGreg Roach /** @var array<string> Repeated data when iterating over loops */ 125*09482a55SGreg Roach private array $repeats = []; 126a6f13a4aSGreg Roach 127*09482a55SGreg Roach /** @var array<int,array<int,array<string>|int>> Nested repeating data */ 128*09482a55SGreg Roach private array $repeats_stack = []; 129a6f13a4aSGreg Roach 130*09482a55SGreg Roach /** @var array<AbstractRenderer> Nested repeating data */ 131*09482a55SGreg Roach private array $wt_report_stack = []; 132e8e7866bSGreg Roach 1334e69366aSGreg Roach /** @var XMLParser (resource before PHP 8.0) Nested repeating data */ 134e8e7866bSGreg Roach private $parser; 135e8e7866bSGreg Roach 1364e69366aSGreg Roach /** @var XMLParser[] (resource[] before PHP 8.0) Nested repeating data */ 1374e69366aSGreg Roach private array $parser_stack = []; 138e8e7866bSGreg Roach 139a6f13a4aSGreg Roach /** @var string The current GEDCOM record */ 140a6f13a4aSGreg Roach private $gedrec = ''; 141a6f13a4aSGreg Roach 142*09482a55SGreg Roach /** @var array<string> Nested GEDCOM records */ 1434e69366aSGreg Roach private array $gedrec_stack = []; 144a6f13a4aSGreg Roach 145a6f13a4aSGreg Roach /** @var ReportBaseElement The currently processed element */ 146a6f13a4aSGreg Roach private $current_element; 147a6f13a4aSGreg Roach 148a6f13a4aSGreg Roach /** @var ReportBaseElement The currently processed element */ 149a6f13a4aSGreg Roach private $footnote_element; 150a6f13a4aSGreg Roach 151a6f13a4aSGreg Roach /** @var string The GEDCOM fact currently being processed */ 152a6f13a4aSGreg Roach private $fact = ''; 153a6f13a4aSGreg Roach 154a6f13a4aSGreg Roach /** @var string The GEDCOM value currently being processed */ 155a6f13a4aSGreg Roach private $desc = ''; 156a6f13a4aSGreg Roach 157a6f13a4aSGreg Roach /** @var string The GEDCOM type currently being processed */ 158a6f13a4aSGreg Roach private $type = ''; 159a6f13a4aSGreg Roach 160a6f13a4aSGreg Roach /** @var int The current generational level */ 161a6f13a4aSGreg Roach private $generation = 1; 162a6f13a4aSGreg Roach 163a6f13a4aSGreg Roach /** @var array Source data for processing lists */ 1644e69366aSGreg Roach private array $list = []; 165a6f13a4aSGreg Roach 166a6f13a4aSGreg Roach /** @var int Number of items in lists */ 167a6f13a4aSGreg Roach private $list_total = 0; 168a6f13a4aSGreg Roach 169a6f13a4aSGreg Roach /** @var int Number of items filtered from lists */ 170a6f13a4aSGreg Roach private $list_private = 0; 171a6f13a4aSGreg Roach 172299d100dSGreg Roach /** @var string The filename of the XML report */ 173299d100dSGreg Roach protected $report; 174299d100dSGreg Roach 175b6f35a76SGreg Roach /** @var AbstractRenderer A factory for creating report elements */ 176e8e7866bSGreg Roach private $report_root; 177e8e7866bSGreg Roach 178b6f35a76SGreg Roach /** @var AbstractRenderer Nested report elements */ 179e8e7866bSGreg Roach private $wt_report; 180e8e7866bSGreg Roach 181*09482a55SGreg Roach /** @var array<array<string>> Variables defined in the report at run-time */ 1824e69366aSGreg Roach private array $vars; 183d1286247SGreg Roach 1844e69366aSGreg Roach private Tree $tree; 185299d100dSGreg Roach 1864e69366aSGreg Roach private FilesystemOperator $data_filesystem; 187a04bb9a2SGreg Roach 18876692c8bSGreg Roach /** 18976692c8bSGreg Roach * Create a parser for a report 19076692c8bSGreg Roach * 19176692c8bSGreg Roach * @param string $report The XML filename 192b6f35a76SGreg Roach * @param AbstractRenderer $report_root 193*09482a55SGreg Roach * @param array<array<string>> $vars 194299d100dSGreg Roach * @param Tree $tree 195f7cf8a15SGreg Roach * @param FilesystemOperator $data_filesystem 19676692c8bSGreg Roach */ 197a04bb9a2SGreg Roach public function __construct( 198a04bb9a2SGreg Roach string $report, 199b6f35a76SGreg Roach AbstractRenderer $report_root, 200a04bb9a2SGreg Roach array $vars, 201a04bb9a2SGreg Roach Tree $tree, 202f7cf8a15SGreg Roach FilesystemOperator $data_filesystem 203a04bb9a2SGreg Roach ) { 204299d100dSGreg Roach $this->report = $report; 205e8e7866bSGreg Roach $this->report_root = $report_root; 206e8e7866bSGreg Roach $this->wt_report = $report_root; 20759f2f229SGreg Roach $this->current_element = new ReportBaseElement(); 208d1286247SGreg Roach $this->vars = $vars; 209299d100dSGreg Roach $this->tree = $tree; 210a04bb9a2SGreg Roach $this->data_filesystem = $data_filesystem; 211299d100dSGreg Roach 21276f666f4SGreg Roach parent::__construct($report); 213a6f13a4aSGreg Roach } 214a6f13a4aSGreg Roach 215a6f13a4aSGreg Roach /** 216a6f13a4aSGreg Roach * XML start element handler 217a6f13a4aSGreg Roach * This function is called whenever a starting element is reached 218a6f13a4aSGreg Roach * The element handler will be called if found, otherwise it must be HTML 219a6f13a4aSGreg Roach * 220a6f13a4aSGreg Roach * @param resource $parser the resource handler for the XML parser 221a6f13a4aSGreg Roach * @param string $name the name of the XML element parsed 222*09482a55SGreg Roach * @param array<string> $attrs an array of key value pairs for the attributes 22318d7a90dSGreg Roach * 22418d7a90dSGreg Roach * @return void 225a6f13a4aSGreg Roach */ 226af14d238SGreg Roach protected function startElement($parser, string $name, array $attrs): void 227c1010edaSGreg Roach { 22813abd6f3SGreg Roach $newattrs = []; 229a6f13a4aSGreg Roach 230a6f13a4aSGreg Roach foreach ($attrs as $key => $value) { 231a6f13a4aSGreg Roach if (preg_match("/^\\$(\w+)$/", $value, $match)) { 232e364afe4SGreg Roach if (isset($this->vars[$match[1]]['id']) && !isset($this->vars[$match[1]]['gedcom'])) { 233d1286247SGreg Roach $value = $this->vars[$match[1]]['id']; 234a6f13a4aSGreg Roach } 235a6f13a4aSGreg Roach } 236a6f13a4aSGreg Roach $newattrs[$key] = $value; 237a6f13a4aSGreg Roach } 238a6f13a4aSGreg Roach $attrs = $newattrs; 2397a6ee1acSGreg Roach if ($this->process_footnote && ($this->process_ifs === 0 || $name === 'if') && ($this->process_gedcoms === 0 || $name === 'Gedcom') && ($this->process_repeats === 0 || $name === 'Facts' || $name === 'RepeatTag')) { 240a0e2939aSGreg Roach $method = $name . 'StartHandler'; 241208e9f76SGreg Roach 242a0e2939aSGreg Roach if (method_exists($this, $method)) { 243b19e047dSGreg Roach call_user_func([$this, $method], $attrs); 244a6f13a4aSGreg Roach } 245a6f13a4aSGreg Roach } 246a6f13a4aSGreg Roach } 247a6f13a4aSGreg Roach 248a6f13a4aSGreg Roach /** 249a6f13a4aSGreg Roach * XML end element handler 250a6f13a4aSGreg Roach * This function is called whenever an ending element is reached 251a6f13a4aSGreg Roach * The element handler will be called if found, otherwise it must be HTML 252a6f13a4aSGreg Roach * 253a6f13a4aSGreg Roach * @param resource $parser the resource handler for the XML parser 254a6f13a4aSGreg Roach * @param string $name the name of the XML element parsed 25518d7a90dSGreg Roach * 25618d7a90dSGreg Roach * @return void 257a6f13a4aSGreg Roach */ 258af14d238SGreg Roach protected function endElement($parser, string $name): void 259c1010edaSGreg Roach { 2607a6ee1acSGreg Roach 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')) { 261a0e2939aSGreg Roach $method = $name . 'EndHandler'; 262a0e2939aSGreg Roach 263a0e2939aSGreg Roach if (method_exists($this, $method)) { 264b19e047dSGreg Roach call_user_func([$this, $method]); 265a6f13a4aSGreg Roach } 266a6f13a4aSGreg Roach } 267a6f13a4aSGreg Roach } 268a6f13a4aSGreg Roach 269a6f13a4aSGreg Roach /** 270a6f13a4aSGreg Roach * XML character data handler 271a6f13a4aSGreg Roach * 272a6f13a4aSGreg Roach * @param resource $parser the resource handler for the XML parser 273a6f13a4aSGreg Roach * @param string $data the name of the XML element parsed 27418d7a90dSGreg Roach * 27518d7a90dSGreg Roach * @return void 276a6f13a4aSGreg Roach */ 27724f2a3afSGreg Roach protected function characterData($parser, string $data): void 278c1010edaSGreg Roach { 279e8e7866bSGreg Roach if ($this->print_data && $this->process_gedcoms === 0 && $this->process_ifs === 0 && $this->process_repeats === 0) { 280a6f13a4aSGreg Roach $this->current_element->addText($data); 281a6f13a4aSGreg Roach } 282a6f13a4aSGreg Roach } 283a6f13a4aSGreg Roach 284a6f13a4aSGreg Roach /** 285fab8f067SGreg Roach * Handle <style> 286a6f13a4aSGreg Roach * 287*09482a55SGreg Roach * @param array<string> $attrs 2888ba2e626SGreg Roach * 2898ba2e626SGreg Roach * @return void 290a6f13a4aSGreg Roach */ 291b702978eSGreg Roach protected function styleStartHandler(array $attrs): void 292c1010edaSGreg Roach { 293a6f13a4aSGreg Roach if (empty($attrs['name'])) { 2946ccdf4f0SGreg Roach throw new DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.'); 295a6f13a4aSGreg Roach } 296a6f13a4aSGreg Roach 297a6f13a4aSGreg Roach // array Style that will be passed on 29813abd6f3SGreg Roach $s = []; 299a6f13a4aSGreg Roach 300a6f13a4aSGreg Roach // string Name af the style 301a6f13a4aSGreg Roach $s['name'] = $attrs['name']; 302a6f13a4aSGreg Roach 303a6f13a4aSGreg Roach // string Name of the DEFAULT font 304208e9f76SGreg Roach $s['font'] = $this->wt_report->default_font; 305a6f13a4aSGreg Roach if (!empty($attrs['font'])) { 306a6f13a4aSGreg Roach $s['font'] = $attrs['font']; 307a6f13a4aSGreg Roach } 308a6f13a4aSGreg Roach 309a6f13a4aSGreg Roach // int The size of the font in points 310208e9f76SGreg Roach $s['size'] = $this->wt_report->default_font_size; 311a6f13a4aSGreg Roach if (!empty($attrs['size'])) { 31259597b37SGreg Roach // Get it as int to ignore all decimal points or text (if no text then 0) 31359597b37SGreg Roach $s['size'] = (string) (int) $attrs['size']; 31459597b37SGreg Roach } 315a6f13a4aSGreg Roach 316a6f13a4aSGreg Roach // string B: bold, I: italic, U: underline, D: line trough, The default value is regular. 3177a6ee1acSGreg Roach $s['style'] = ''; 318a6f13a4aSGreg Roach if (!empty($attrs['style'])) { 319a6f13a4aSGreg Roach $s['style'] = $attrs['style']; 320a6f13a4aSGreg Roach } 321a6f13a4aSGreg Roach 322e8e7866bSGreg Roach $this->wt_report->addStyle($s); 323a6f13a4aSGreg Roach } 324a6f13a4aSGreg Roach 325a6f13a4aSGreg Roach /** 326fab8f067SGreg Roach * Handle <doc> 327a6f13a4aSGreg Roach * Sets up the basics of the document proparties 328a6f13a4aSGreg Roach * 329*09482a55SGreg Roach * @param array<string> $attrs 3308ba2e626SGreg Roach * 3318ba2e626SGreg Roach * @return void 332a6f13a4aSGreg Roach */ 333b702978eSGreg Roach protected function docStartHandler(array $attrs): void 334c1010edaSGreg Roach { 335e8e7866bSGreg Roach $this->parser = $this->xml_parser; 336a6f13a4aSGreg Roach 337a6f13a4aSGreg Roach // Custom page width 338a6f13a4aSGreg Roach if (!empty($attrs['customwidth'])) { 339208e9f76SGreg Roach $this->wt_report->page_width = (int) $attrs['customwidth']; 340a6f13a4aSGreg Roach } // Get it as int to ignore all decimal points or text (if any text then int(0)) 341a6f13a4aSGreg Roach // Custom Page height 342a6f13a4aSGreg Roach if (!empty($attrs['customheight'])) { 343208e9f76SGreg Roach $this->wt_report->page_height = (int) $attrs['customheight']; 344a6f13a4aSGreg Roach } // Get it as int to ignore all decimal points or text (if any text then int(0)) 345a6f13a4aSGreg Roach 346a6f13a4aSGreg Roach // Left Margin 347a6f13a4aSGreg Roach if (isset($attrs['leftmargin'])) { 3487a6ee1acSGreg Roach if ($attrs['leftmargin'] === '0') { 349208e9f76SGreg Roach $this->wt_report->left_margin = 0; 350a6f13a4aSGreg Roach } elseif (!empty($attrs['leftmargin'])) { 351208e9f76SGreg Roach $this->wt_report->left_margin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 352a6f13a4aSGreg Roach } 353a6f13a4aSGreg Roach } 354a6f13a4aSGreg Roach // Right Margin 355a6f13a4aSGreg Roach if (isset($attrs['rightmargin'])) { 3567a6ee1acSGreg Roach if ($attrs['rightmargin'] === '0') { 357208e9f76SGreg Roach $this->wt_report->right_margin = 0; 358a6f13a4aSGreg Roach } elseif (!empty($attrs['rightmargin'])) { 359208e9f76SGreg Roach $this->wt_report->right_margin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 360a6f13a4aSGreg Roach } 361a6f13a4aSGreg Roach } 362a6f13a4aSGreg Roach // Top Margin 363a6f13a4aSGreg Roach if (isset($attrs['topmargin'])) { 3647a6ee1acSGreg Roach if ($attrs['topmargin'] === '0') { 365208e9f76SGreg Roach $this->wt_report->top_margin = 0; 366a6f13a4aSGreg Roach } elseif (!empty($attrs['topmargin'])) { 367208e9f76SGreg Roach $this->wt_report->top_margin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 368a6f13a4aSGreg Roach } 369a6f13a4aSGreg Roach } 370a6f13a4aSGreg Roach // Bottom Margin 371a6f13a4aSGreg Roach if (isset($attrs['bottommargin'])) { 3727a6ee1acSGreg Roach if ($attrs['bottommargin'] === '0') { 373208e9f76SGreg Roach $this->wt_report->bottom_margin = 0; 374a6f13a4aSGreg Roach } elseif (!empty($attrs['bottommargin'])) { 375208e9f76SGreg Roach $this->wt_report->bottom_margin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 376a6f13a4aSGreg Roach } 377a6f13a4aSGreg Roach } 378a6f13a4aSGreg Roach // Header Margin 379a6f13a4aSGreg Roach if (isset($attrs['headermargin'])) { 3807a6ee1acSGreg Roach if ($attrs['headermargin'] === '0') { 381208e9f76SGreg Roach $this->wt_report->header_margin = 0; 382a6f13a4aSGreg Roach } elseif (!empty($attrs['headermargin'])) { 383208e9f76SGreg Roach $this->wt_report->header_margin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 384a6f13a4aSGreg Roach } 385a6f13a4aSGreg Roach } 386a6f13a4aSGreg Roach // Footer Margin 387a6f13a4aSGreg Roach if (isset($attrs['footermargin'])) { 3887a6ee1acSGreg Roach if ($attrs['footermargin'] === '0') { 389208e9f76SGreg Roach $this->wt_report->footer_margin = 0; 390a6f13a4aSGreg Roach } elseif (!empty($attrs['footermargin'])) { 391208e9f76SGreg Roach $this->wt_report->footer_margin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0)) 392a6f13a4aSGreg Roach } 393a6f13a4aSGreg Roach } 394a6f13a4aSGreg Roach 395a6f13a4aSGreg Roach // Page Orientation 396a6f13a4aSGreg Roach if (!empty($attrs['orientation'])) { 397044416d2SGreg Roach if ($attrs['orientation'] === 'landscape') { 3987a6ee1acSGreg Roach $this->wt_report->orientation = 'landscape'; 399044416d2SGreg Roach } elseif ($attrs['orientation'] === 'portrait') { 4007a6ee1acSGreg Roach $this->wt_report->orientation = 'portrait'; 401a6f13a4aSGreg Roach } 402a6f13a4aSGreg Roach } 403a6f13a4aSGreg Roach // Page Size 404a6f13a4aSGreg Roach if (!empty($attrs['pageSize'])) { 405208e9f76SGreg Roach $this->wt_report->page_format = strtoupper($attrs['pageSize']); 406a6f13a4aSGreg Roach } 407a6f13a4aSGreg Roach 408a6f13a4aSGreg Roach // Show Generated By... 409a6f13a4aSGreg Roach if (isset($attrs['showGeneratedBy'])) { 4107a6ee1acSGreg Roach if ($attrs['showGeneratedBy'] === '0') { 411208e9f76SGreg Roach $this->wt_report->show_generated_by = false; 4127a6ee1acSGreg Roach } elseif ($attrs['showGeneratedBy'] === '1') { 413208e9f76SGreg Roach $this->wt_report->show_generated_by = true; 414a6f13a4aSGreg Roach } 415a6f13a4aSGreg Roach } 416a6f13a4aSGreg Roach 417e8e7866bSGreg Roach $this->wt_report->setup(); 418a6f13a4aSGreg Roach } 419a6f13a4aSGreg Roach 420a6f13a4aSGreg Roach /** 421fab8f067SGreg Roach * Handle </doc> 4228ba2e626SGreg Roach * 4238ba2e626SGreg Roach * @return void 424a6f13a4aSGreg Roach */ 425b702978eSGreg Roach protected function docEndHandler(): void 426c1010edaSGreg Roach { 427e8e7866bSGreg Roach $this->wt_report->run(); 428a6f13a4aSGreg Roach } 429a6f13a4aSGreg Roach 430a6f13a4aSGreg Roach /** 431fab8f067SGreg Roach * Handle <header> 4328ba2e626SGreg Roach * 4338ba2e626SGreg Roach * @return void 434a6f13a4aSGreg Roach */ 435b702978eSGreg Roach protected function headerStartHandler(): void 436c1010edaSGreg Roach { 437a6f13a4aSGreg Roach // Clear the Header before any new elements are added 438e8e7866bSGreg Roach $this->wt_report->clearHeader(); 4397a6ee1acSGreg Roach $this->wt_report->setProcessing('H'); 440a6f13a4aSGreg Roach } 441a6f13a4aSGreg Roach 442a6f13a4aSGreg Roach /** 443fab8f067SGreg Roach * Handle <body> 4448ba2e626SGreg Roach * 4458ba2e626SGreg Roach * @return void 446a6f13a4aSGreg Roach */ 447b702978eSGreg Roach protected function bodyStartHandler(): void 448c1010edaSGreg Roach { 4497a6ee1acSGreg Roach $this->wt_report->setProcessing('B'); 450a6f13a4aSGreg Roach } 451a6f13a4aSGreg Roach 452a6f13a4aSGreg Roach /** 453fab8f067SGreg Roach * Handle <footer> 4548ba2e626SGreg Roach * 4558ba2e626SGreg Roach * @return void 456a6f13a4aSGreg Roach */ 457b702978eSGreg Roach protected function footerStartHandler(): void 458c1010edaSGreg Roach { 4597a6ee1acSGreg Roach $this->wt_report->setProcessing('F'); 460a6f13a4aSGreg Roach } 461a6f13a4aSGreg Roach 462a6f13a4aSGreg Roach /** 463fab8f067SGreg Roach * Handle <cell> 464a6f13a4aSGreg Roach * 46577bab461SGreg Roach * @param array<string,string> $attrs 4668ba2e626SGreg Roach * 4678ba2e626SGreg Roach * @return void 468a6f13a4aSGreg Roach */ 469b702978eSGreg Roach protected function cellStartHandler(array $attrs): void 470c1010edaSGreg Roach { 471a6f13a4aSGreg Roach // string The text alignment of the text in this box. 47277bab461SGreg Roach $align = $attrs['align'] ?? ''; 473a6f13a4aSGreg Roach // RTL supported left/right alignment 474044416d2SGreg Roach if ($align === 'rightrtl') { 475e8e7866bSGreg Roach if ($this->wt_report->rtl) { 4767a6ee1acSGreg Roach $align = 'left'; 477a6f13a4aSGreg Roach } else { 4787a6ee1acSGreg Roach $align = 'right'; 479a6f13a4aSGreg Roach } 480044416d2SGreg Roach } elseif ($align === 'leftrtl') { 481e8e7866bSGreg Roach if ($this->wt_report->rtl) { 4827a6ee1acSGreg Roach $align = 'right'; 483a6f13a4aSGreg Roach } else { 4847a6ee1acSGreg Roach $align = 'left'; 485a6f13a4aSGreg Roach } 486a6f13a4aSGreg Roach } 487a6f13a4aSGreg Roach 48877bab461SGreg Roach // The color to fill the background of this cell 48977bab461SGreg Roach $bgcolor = $attrs['bgcolor'] ?? ''; 490a6f13a4aSGreg Roach 49177bab461SGreg Roach // Whether the background should be painted 49277bab461SGreg Roach $fill = (int) ($attrs['fill'] ?? '0'); 493a6f13a4aSGreg Roach 49477bab461SGreg Roach // If true reset the last cell height 49577bab461SGreg Roach $reseth = (bool) ($attrs['reseth'] ?? '1'); 496a6f13a4aSGreg Roach 49777bab461SGreg Roach // Whether a border should be printed around this box 49877bab461SGreg Roach $border = $attrs['border'] ?? ''; 49977bab461SGreg Roach 500a6f13a4aSGreg Roach // string Border color in HTML code 50177bab461SGreg Roach $bocolor = $attrs['bocolor'] ?? ''; 502a6f13a4aSGreg Roach 50377bab461SGreg Roach // Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted. 50477bab461SGreg Roach $height = (int) ($attrs['height'] ?? '0'); 50577bab461SGreg Roach 506a6f13a4aSGreg Roach // int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin. 50777bab461SGreg Roach $width = (int) ($attrs['width'] ?? '0'); 508a6f13a4aSGreg Roach 50977bab461SGreg Roach // Stretch character mode 51077bab461SGreg Roach $stretch = (int) ($attrs['stretch'] ?? '0'); 511a6f13a4aSGreg Roach 512a6f13a4aSGreg Roach // mixed Position the left corner of this box on the page. The default is the current position. 513c21bdddcSGreg Roach $left = ReportBaseElement::CURRENT_POSITION; 514a6f13a4aSGreg Roach if (isset($attrs['left'])) { 5157a6ee1acSGreg Roach if ($attrs['left'] === '.') { 516c21bdddcSGreg Roach $left = ReportBaseElement::CURRENT_POSITION; 517a6f13a4aSGreg Roach } elseif (!empty($attrs['left'])) { 518a6f13a4aSGreg Roach $left = (int) $attrs['left']; 5197a6ee1acSGreg Roach } elseif ($attrs['left'] === '0') { 520a6f13a4aSGreg Roach $left = 0; 521a6f13a4aSGreg Roach } 522a6f13a4aSGreg Roach } 523a6f13a4aSGreg Roach // mixed Position the top corner of this box on the page. the default is the current position 524c21bdddcSGreg Roach $top = ReportBaseElement::CURRENT_POSITION; 525a6f13a4aSGreg Roach if (isset($attrs['top'])) { 5267a6ee1acSGreg Roach if ($attrs['top'] === '.') { 527c21bdddcSGreg Roach $top = ReportBaseElement::CURRENT_POSITION; 528a6f13a4aSGreg Roach } elseif (!empty($attrs['top'])) { 529a6f13a4aSGreg Roach $top = (int) $attrs['top']; 5307a6ee1acSGreg Roach } elseif ($attrs['top'] === '0') { 531a6f13a4aSGreg Roach $top = 0; 532a6f13a4aSGreg Roach } 533a6f13a4aSGreg Roach } 534a6f13a4aSGreg Roach 53577bab461SGreg Roach // The name of the Style that should be used to render the text. 53677bab461SGreg Roach $style = $attrs['style'] ?? ''; 537a6f13a4aSGreg Roach 538a6f13a4aSGreg Roach // string Text color in html code 53977bab461SGreg Roach $tcolor = $attrs['tcolor'] ?? ''; 540a6f13a4aSGreg Roach 541a6f13a4aSGreg Roach // int Indicates where the current position should go after the call. 542a6f13a4aSGreg Roach $ln = 0; 543a6f13a4aSGreg Roach if (isset($attrs['newline'])) { 544a6f13a4aSGreg Roach if (!empty($attrs['newline'])) { 545a6f13a4aSGreg Roach $ln = (int) $attrs['newline']; 5467a6ee1acSGreg Roach } elseif ($attrs['newline'] === '0') { 547a6f13a4aSGreg Roach $ln = 0; 548a6f13a4aSGreg Roach } 549a6f13a4aSGreg Roach } 550a6f13a4aSGreg Roach 551044416d2SGreg Roach if ($align === 'left') { 5527a6ee1acSGreg Roach $align = 'L'; 553044416d2SGreg Roach } elseif ($align === 'right') { 5547a6ee1acSGreg Roach $align = 'R'; 555044416d2SGreg Roach } elseif ($align === 'center') { 5567a6ee1acSGreg Roach $align = 'C'; 557044416d2SGreg Roach } elseif ($align === 'justify') { 5587a6ee1acSGreg Roach $align = 'J'; 559a6f13a4aSGreg Roach } 560a6f13a4aSGreg Roach 5619b3dd960SGreg Roach $this->print_data_stack[] = $this->print_data; 562a6f13a4aSGreg Roach $this->print_data = true; 563a6f13a4aSGreg Roach 564e8e7866bSGreg Roach $this->current_element = $this->report_root->createCell( 56524f2a3afSGreg Roach (int) $width, 56624f2a3afSGreg Roach (int) $height, 567a6f13a4aSGreg Roach $border, 568a6f13a4aSGreg Roach $align, 569a6f13a4aSGreg Roach $bgcolor, 570a6f13a4aSGreg Roach $style, 571a6f13a4aSGreg Roach $ln, 572a6f13a4aSGreg Roach $top, 573a6f13a4aSGreg Roach $left, 574a6f13a4aSGreg Roach $fill, 575a6f13a4aSGreg Roach $stretch, 576a6f13a4aSGreg Roach $bocolor, 577a6f13a4aSGreg Roach $tcolor, 578a6f13a4aSGreg Roach $reseth 579a6f13a4aSGreg Roach ); 580a6f13a4aSGreg Roach } 581a6f13a4aSGreg Roach 582a6f13a4aSGreg Roach /** 583fab8f067SGreg Roach * Handle </cell> 5848ba2e626SGreg Roach * 5858ba2e626SGreg Roach * @return void 586a6f13a4aSGreg Roach */ 587b702978eSGreg Roach protected function cellEndHandler(): void 588c1010edaSGreg Roach { 589a6f13a4aSGreg Roach $this->print_data = array_pop($this->print_data_stack); 590e8e7866bSGreg Roach $this->wt_report->addElement($this->current_element); 591a6f13a4aSGreg Roach } 592a6f13a4aSGreg Roach 593a6f13a4aSGreg Roach /** 594fab8f067SGreg Roach * Handle <now /> 5958ba2e626SGreg Roach * 5968ba2e626SGreg Roach * @return void 597a6f13a4aSGreg Roach */ 598b702978eSGreg Roach protected function nowStartHandler(): void 599c1010edaSGreg Roach { 6004459dc9aSGreg Roach $this->current_element->addText(Carbon::now()->local()->isoFormat('LLLL')); 601a6f13a4aSGreg Roach } 602a6f13a4aSGreg Roach 603a6f13a4aSGreg Roach /** 604fab8f067SGreg Roach * Handle <pageNum /> 6058ba2e626SGreg Roach * 6068ba2e626SGreg Roach * @return void 607a6f13a4aSGreg Roach */ 608b702978eSGreg Roach protected function pageNumStartHandler(): void 609c1010edaSGreg Roach { 6107a6ee1acSGreg Roach $this->current_element->addText('#PAGENUM#'); 611a6f13a4aSGreg Roach } 612a6f13a4aSGreg Roach 613a6f13a4aSGreg Roach /** 614fab8f067SGreg Roach * Handle <totalPages /> 6158ba2e626SGreg Roach * 6168ba2e626SGreg Roach * @return void 617a6f13a4aSGreg Roach */ 618b702978eSGreg Roach protected function totalPagesStartHandler(): void 619c1010edaSGreg Roach { 6207a6ee1acSGreg Roach $this->current_element->addText('{{:ptp:}}'); 621a6f13a4aSGreg Roach } 622a6f13a4aSGreg Roach 623a6f13a4aSGreg Roach /** 624a6f13a4aSGreg Roach * Called at the start of an element. 625a6f13a4aSGreg Roach * 626*09482a55SGreg Roach * @param array<string> $attrs an array of key value pairs for the attributes 6278ba2e626SGreg Roach * 6288ba2e626SGreg Roach * @return void 629a6f13a4aSGreg Roach */ 630b702978eSGreg Roach protected function gedcomStartHandler(array $attrs): void 631c1010edaSGreg Roach { 632a6f13a4aSGreg Roach if ($this->process_gedcoms > 0) { 633a6f13a4aSGreg Roach $this->process_gedcoms++; 634a6f13a4aSGreg Roach 635a6f13a4aSGreg Roach return; 636a6f13a4aSGreg Roach } 637a6f13a4aSGreg Roach 638a6f13a4aSGreg Roach $tag = $attrs['id']; 6397a6ee1acSGreg Roach $tag = str_replace('@fact', $this->fact, $tag); 6407a6ee1acSGreg Roach $tags = explode(':', $tag); 641a6f13a4aSGreg Roach $newgedrec = ''; 642a6f13a4aSGreg Roach if (count($tags) < 2) { 6436b9cb339SGreg Roach $tmp = Registry::gedcomRecordFactory()->make($attrs['id'], $this->tree); 644299d100dSGreg Roach $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($this->tree)) : ''; 645a6f13a4aSGreg Roach } 646a6f13a4aSGreg Roach if (empty($newgedrec)) { 647a6f13a4aSGreg Roach $tgedrec = $this->gedrec; 648a6f13a4aSGreg Roach $newgedrec = ''; 649a6f13a4aSGreg Roach foreach ($tags as $tag) { 6507a6ee1acSGreg Roach if (preg_match('/\$(.+)/', $tag, $match)) { 651d1286247SGreg Roach if (isset($this->vars[$match[1]]['gedcom'])) { 652d1286247SGreg Roach $newgedrec = $this->vars[$match[1]]['gedcom']; 653a6f13a4aSGreg Roach } else { 6546b9cb339SGreg Roach $tmp = Registry::gedcomRecordFactory()->make($match[1], $this->tree); 655299d100dSGreg Roach $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($this->tree)) : ''; 656a6f13a4aSGreg Roach } 657a6f13a4aSGreg Roach } else { 6587a6ee1acSGreg Roach if (preg_match('/@(.+)/', $tag, $match)) { 65913abd6f3SGreg Roach $gmatch = []; 660a6f13a4aSGreg Roach if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) { 6616b9cb339SGreg Roach $tmp = Registry::gedcomRecordFactory()->make($gmatch[1], $this->tree); 662299d100dSGreg Roach $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($this->tree)) : ''; 663a6f13a4aSGreg Roach $tgedrec = $newgedrec; 664a6f13a4aSGreg Roach } else { 665a6f13a4aSGreg Roach $newgedrec = ''; 666a6f13a4aSGreg Roach break; 667a6f13a4aSGreg Roach } 668a6f13a4aSGreg Roach } else { 669b2448a1bSGreg Roach $level = 1 + (int) explode(' ', trim($tgedrec))[0]; 6703d7a8a4cSGreg Roach $newgedrec = Functions::getSubRecord($level, "$level $tag", $tgedrec); 671a6f13a4aSGreg Roach $tgedrec = $newgedrec; 672a6f13a4aSGreg Roach } 673a6f13a4aSGreg Roach } 674a6f13a4aSGreg Roach } 675a6f13a4aSGreg Roach } 676a6f13a4aSGreg Roach if (!empty($newgedrec)) { 6779b3dd960SGreg Roach $this->gedrec_stack[] = [$this->gedrec, $this->fact, $this->desc]; 678a6f13a4aSGreg Roach $this->gedrec = $newgedrec; 679a6f13a4aSGreg Roach if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $this->gedrec, $match)) { 680a6f13a4aSGreg Roach $this->fact = $match[2]; 681a6f13a4aSGreg Roach $this->desc = trim($match[3]); 682a6f13a4aSGreg Roach } 683a6f13a4aSGreg Roach } else { 684a6f13a4aSGreg Roach $this->process_gedcoms++; 685a6f13a4aSGreg Roach } 686a6f13a4aSGreg Roach } 687a6f13a4aSGreg Roach 688a6f13a4aSGreg Roach /** 689a6f13a4aSGreg Roach * Called at the end of an element. 6908ba2e626SGreg Roach * 6918ba2e626SGreg Roach * @return void 692a6f13a4aSGreg Roach */ 693b702978eSGreg Roach protected function gedcomEndHandler(): void 694c1010edaSGreg Roach { 695a6f13a4aSGreg Roach if ($this->process_gedcoms > 0) { 696a6f13a4aSGreg Roach $this->process_gedcoms--; 697a6f13a4aSGreg Roach } else { 69865e02381SGreg Roach [$this->gedrec, $this->fact, $this->desc] = array_pop($this->gedrec_stack); 699a6f13a4aSGreg Roach } 700a6f13a4aSGreg Roach } 701a6f13a4aSGreg Roach 702a6f13a4aSGreg Roach /** 703fab8f067SGreg Roach * Handle <textBox> 704a6f13a4aSGreg Roach * 705*09482a55SGreg Roach * @param array<string> $attrs 7068ba2e626SGreg Roach * 7078ba2e626SGreg Roach * @return void 708a6f13a4aSGreg Roach */ 709b702978eSGreg Roach protected function textBoxStartHandler(array $attrs): void 710c1010edaSGreg Roach { 711a6f13a4aSGreg Roach // string Background color code 7127a6ee1acSGreg Roach $bgcolor = ''; 713a6f13a4aSGreg Roach if (!empty($attrs['bgcolor'])) { 714a6f13a4aSGreg Roach $bgcolor = $attrs['bgcolor']; 715a6f13a4aSGreg Roach } 716a6f13a4aSGreg Roach 717a6f13a4aSGreg Roach // boolean Wether or not fill the background color 718a6f13a4aSGreg Roach $fill = true; 719a6f13a4aSGreg Roach if (isset($attrs['fill'])) { 7207a6ee1acSGreg Roach if ($attrs['fill'] === '0') { 721a6f13a4aSGreg Roach $fill = false; 7227a6ee1acSGreg Roach } elseif ($attrs['fill'] === '1') { 723a6f13a4aSGreg Roach $fill = true; 724a6f13a4aSGreg Roach } 725a6f13a4aSGreg Roach } 726a6f13a4aSGreg Roach 727a6f13a4aSGreg Roach // var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0 728a6f13a4aSGreg Roach $border = false; 729a6f13a4aSGreg Roach if (isset($attrs['border'])) { 7307a6ee1acSGreg Roach if ($attrs['border'] === '1') { 731a6f13a4aSGreg Roach $border = true; 7327a6ee1acSGreg Roach } elseif ($attrs['border'] === '0') { 733a6f13a4aSGreg Roach $border = false; 734a6f13a4aSGreg Roach } 735a6f13a4aSGreg Roach } 736a6f13a4aSGreg Roach 737a6f13a4aSGreg Roach // int The starting height of this cell. If the text wraps the height will automatically be adjusted 738a6f13a4aSGreg Roach $height = 0; 739a6f13a4aSGreg Roach if (!empty($attrs['height'])) { 740a6f13a4aSGreg Roach $height = (int) $attrs['height']; 741a6f13a4aSGreg Roach } 742a6f13a4aSGreg Roach // int Setting the width to 0 will make it the width from the current location to the margin 743a6f13a4aSGreg Roach $width = 0; 744a6f13a4aSGreg Roach if (!empty($attrs['width'])) { 745a6f13a4aSGreg Roach $width = (int) $attrs['width']; 746a6f13a4aSGreg Roach } 747a6f13a4aSGreg Roach 748a6f13a4aSGreg Roach // mixed Position the left corner of this box on the page. The default is the current position. 749c21bdddcSGreg Roach $left = ReportBaseElement::CURRENT_POSITION; 750a6f13a4aSGreg Roach if (isset($attrs['left'])) { 7517a6ee1acSGreg Roach if ($attrs['left'] === '.') { 752c21bdddcSGreg Roach $left = ReportBaseElement::CURRENT_POSITION; 753a6f13a4aSGreg Roach } elseif (!empty($attrs['left'])) { 754a6f13a4aSGreg Roach $left = (int) $attrs['left']; 7557a6ee1acSGreg Roach } elseif ($attrs['left'] === '0') { 756a6f13a4aSGreg Roach $left = 0; 757a6f13a4aSGreg Roach } 758a6f13a4aSGreg Roach } 759a6f13a4aSGreg Roach // mixed Position the top corner of this box on the page. the default is the current position 760c21bdddcSGreg Roach $top = ReportBaseElement::CURRENT_POSITION; 761a6f13a4aSGreg Roach if (isset($attrs['top'])) { 7627a6ee1acSGreg Roach if ($attrs['top'] === '.') { 763c21bdddcSGreg Roach $top = ReportBaseElement::CURRENT_POSITION; 764a6f13a4aSGreg Roach } elseif (!empty($attrs['top'])) { 765a6f13a4aSGreg Roach $top = (int) $attrs['top']; 7667a6ee1acSGreg Roach } elseif ($attrs['top'] === '0') { 767a6f13a4aSGreg Roach $top = 0; 768a6f13a4aSGreg Roach } 769a6f13a4aSGreg Roach } 770a6f13a4aSGreg Roach // 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 771a6f13a4aSGreg Roach $newline = false; 772a6f13a4aSGreg Roach if (isset($attrs['newline'])) { 7737a6ee1acSGreg Roach if ($attrs['newline'] === '1') { 774a6f13a4aSGreg Roach $newline = true; 7757a6ee1acSGreg Roach } elseif ($attrs['newline'] === '0') { 776a6f13a4aSGreg Roach $newline = false; 777a6f13a4aSGreg Roach } 778a6f13a4aSGreg Roach } 779a6f13a4aSGreg Roach // boolean 780a6f13a4aSGreg Roach $pagecheck = true; 781a6f13a4aSGreg Roach if (isset($attrs['pagecheck'])) { 7827a6ee1acSGreg Roach if ($attrs['pagecheck'] === '0') { 783a6f13a4aSGreg Roach $pagecheck = false; 7847a6ee1acSGreg Roach } elseif ($attrs['pagecheck'] === '1') { 785a6f13a4aSGreg Roach $pagecheck = true; 786a6f13a4aSGreg Roach } 787a6f13a4aSGreg Roach } 788a6f13a4aSGreg Roach // boolean Cell padding 789a6f13a4aSGreg Roach $padding = true; 790a6f13a4aSGreg Roach if (isset($attrs['padding'])) { 7917a6ee1acSGreg Roach if ($attrs['padding'] === '0') { 792a6f13a4aSGreg Roach $padding = false; 7937a6ee1acSGreg Roach } elseif ($attrs['padding'] === '1') { 794a6f13a4aSGreg Roach $padding = true; 795a6f13a4aSGreg Roach } 796a6f13a4aSGreg Roach } 797a6f13a4aSGreg Roach // boolean Reset this box Height 798a6f13a4aSGreg Roach $reseth = false; 799a6f13a4aSGreg Roach if (isset($attrs['reseth'])) { 8007a6ee1acSGreg Roach if ($attrs['reseth'] === '1') { 801a6f13a4aSGreg Roach $reseth = true; 8027a6ee1acSGreg Roach } elseif ($attrs['reseth'] === '0') { 803a6f13a4aSGreg Roach $reseth = false; 804a6f13a4aSGreg Roach } 805a6f13a4aSGreg Roach } 806a6f13a4aSGreg Roach 807a6f13a4aSGreg Roach // string Style of rendering 8087a6ee1acSGreg Roach $style = ''; 809a6f13a4aSGreg Roach 8109b3dd960SGreg Roach $this->print_data_stack[] = $this->print_data; 811a6f13a4aSGreg Roach $this->print_data = false; 812a6f13a4aSGreg Roach 8139b3dd960SGreg Roach $this->wt_report_stack[] = $this->wt_report; 814e8e7866bSGreg Roach $this->wt_report = $this->report_root->createTextBox( 815a6f13a4aSGreg Roach $width, 816a6f13a4aSGreg Roach $height, 817a6f13a4aSGreg Roach $border, 818a6f13a4aSGreg Roach $bgcolor, 819a6f13a4aSGreg Roach $newline, 820a6f13a4aSGreg Roach $left, 821a6f13a4aSGreg Roach $top, 822a6f13a4aSGreg Roach $pagecheck, 823a6f13a4aSGreg Roach $style, 824a6f13a4aSGreg Roach $fill, 825a6f13a4aSGreg Roach $padding, 826a6f13a4aSGreg Roach $reseth 827a6f13a4aSGreg Roach ); 828a6f13a4aSGreg Roach } 829a6f13a4aSGreg Roach 830a6f13a4aSGreg Roach /** 831fab8f067SGreg Roach * Handle <textBox> 8328ba2e626SGreg Roach * 8338ba2e626SGreg Roach * @return void 834a6f13a4aSGreg Roach */ 835b702978eSGreg Roach protected function textBoxEndHandler(): void 836c1010edaSGreg Roach { 837a6f13a4aSGreg Roach $this->print_data = array_pop($this->print_data_stack); 838e8e7866bSGreg Roach $this->current_element = $this->wt_report; 83931d9777aSGreg Roach 84031d9777aSGreg Roach // The TextBox handler is mis-using the wt_report attribute to store an element. 84131d9777aSGreg Roach // Until this can be re-designed, we need this assertion to help static analysis tools. 84231d9777aSGreg Roach assert($this->current_element instanceof ReportBaseElement, new LogicException()); 84331d9777aSGreg Roach 844e8e7866bSGreg Roach $this->wt_report = array_pop($this->wt_report_stack); 845e8e7866bSGreg Roach $this->wt_report->addElement($this->current_element); 846a6f13a4aSGreg Roach } 847a6f13a4aSGreg Roach 848a6f13a4aSGreg Roach /** 84976692c8bSGreg Roach * XLM <Text>. 85076692c8bSGreg Roach * 851*09482a55SGreg Roach * @param array<string> $attrs an array of key value pairs for the attributes 8528ba2e626SGreg Roach * 8538ba2e626SGreg Roach * @return void 854a6f13a4aSGreg Roach */ 855b702978eSGreg Roach protected function textStartHandler(array $attrs): void 856c1010edaSGreg Roach { 8579b3dd960SGreg Roach $this->print_data_stack[] = $this->print_data; 858a6f13a4aSGreg Roach $this->print_data = true; 859a6f13a4aSGreg Roach 860a6f13a4aSGreg Roach // string The name of the Style that should be used to render the text. 8617a6ee1acSGreg Roach $style = ''; 862a6f13a4aSGreg Roach if (!empty($attrs['style'])) { 863a6f13a4aSGreg Roach $style = $attrs['style']; 864a6f13a4aSGreg Roach } 865a6f13a4aSGreg Roach 866a6f13a4aSGreg Roach // string The color of the text - Keep the black color as default 8677a6ee1acSGreg Roach $color = ''; 868a6f13a4aSGreg Roach if (!empty($attrs['color'])) { 869a6f13a4aSGreg Roach $color = $attrs['color']; 870a6f13a4aSGreg Roach } 871a6f13a4aSGreg Roach 872e8e7866bSGreg Roach $this->current_element = $this->report_root->createText($style, $color); 873a6f13a4aSGreg Roach } 874a6f13a4aSGreg Roach 875a6f13a4aSGreg Roach /** 876fab8f067SGreg Roach * Handle </text> 8778ba2e626SGreg Roach * 8788ba2e626SGreg Roach * @return void 879a6f13a4aSGreg Roach */ 880b702978eSGreg Roach protected function textEndHandler(): void 881c1010edaSGreg Roach { 882a6f13a4aSGreg Roach $this->print_data = array_pop($this->print_data_stack); 883e8e7866bSGreg Roach $this->wt_report->addElement($this->current_element); 884a6f13a4aSGreg Roach } 885a6f13a4aSGreg Roach 886a6f13a4aSGreg Roach /** 887fab8f067SGreg Roach * Handle <getPersonName /> 888a6f13a4aSGreg Roach * Get the name 889a6f13a4aSGreg Roach * 1. id is empty - current GEDCOM record 890a6f13a4aSGreg Roach * 2. id is set with a record id 891a6f13a4aSGreg Roach * 892*09482a55SGreg Roach * @param array<string> $attrs an array of key value pairs for the attributes 8938ba2e626SGreg Roach * 8948ba2e626SGreg Roach * @return void 895a6f13a4aSGreg Roach */ 896b702978eSGreg Roach protected function getPersonNameStartHandler(array $attrs): void 897c1010edaSGreg Roach { 8987a6ee1acSGreg Roach $id = ''; 89913abd6f3SGreg Roach $match = []; 900a6f13a4aSGreg Roach if (empty($attrs['id'])) { 9017a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 902a6f13a4aSGreg Roach $id = $match[1]; 903a6f13a4aSGreg Roach } 904a6f13a4aSGreg Roach } else { 9057a6ee1acSGreg Roach if (preg_match('/\$(.+)/', $attrs['id'], $match)) { 906d1286247SGreg Roach if (isset($this->vars[$match[1]]['id'])) { 907d1286247SGreg Roach $id = $this->vars[$match[1]]['id']; 908a6f13a4aSGreg Roach } 909a6f13a4aSGreg Roach } else { 9107a6ee1acSGreg Roach if (preg_match('/@(.+)/', $attrs['id'], $match)) { 91113abd6f3SGreg Roach $gmatch = []; 912a6f13a4aSGreg Roach if (preg_match("/\d $match[1] @([^@]+)@/", $this->gedrec, $gmatch)) { 913a6f13a4aSGreg Roach $id = $gmatch[1]; 914a6f13a4aSGreg Roach } 915a6f13a4aSGreg Roach } else { 916a6f13a4aSGreg Roach $id = $attrs['id']; 917a6f13a4aSGreg Roach } 918a6f13a4aSGreg Roach } 919a6f13a4aSGreg Roach } 920a6f13a4aSGreg Roach if (!empty($id)) { 9216b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->make($id, $this->tree); 9228f038c36SRico Sonntag if ($record === null) { 923a6f13a4aSGreg Roach return; 924a6f13a4aSGreg Roach } 925a6f13a4aSGreg Roach if (!$record->canShowName()) { 926a6f13a4aSGreg Roach $this->current_element->addText(I18N::translate('Private')); 927a6f13a4aSGreg Roach } else { 92839ca88baSGreg Roach $name = $record->fullName(); 929a6f13a4aSGreg Roach $name = strip_tags($name); 930a6f13a4aSGreg Roach if (!empty($attrs['truncate'])) { 931381e28f4SGreg Roach $name = Str::limit($name, (int) $attrs['truncate'], I18N::translate('…')); 932a6f13a4aSGreg Roach } else { 933911124f5SGreg Roach $addname = (string) $record->alternateName(); 934a6f13a4aSGreg Roach $addname = strip_tags($addname); 935a6f13a4aSGreg Roach if (!empty($addname)) { 9367a6ee1acSGreg Roach $name .= ' ' . $addname; 937a6f13a4aSGreg Roach } 938a6f13a4aSGreg Roach } 939a6f13a4aSGreg Roach $this->current_element->addText(trim($name)); 940a6f13a4aSGreg Roach } 941a6f13a4aSGreg Roach } 942a6f13a4aSGreg Roach } 943a6f13a4aSGreg Roach 944a6f13a4aSGreg Roach /** 945fab8f067SGreg Roach * Handle <gedcomValue /> 946a6f13a4aSGreg Roach * 947*09482a55SGreg Roach * @param array<string> $attrs 9488ba2e626SGreg Roach * 9498ba2e626SGreg Roach * @return void 950a6f13a4aSGreg Roach */ 951b702978eSGreg Roach protected function gedcomValueStartHandler(array $attrs): void 952c1010edaSGreg Roach { 9537a6ee1acSGreg Roach $id = ''; 95413abd6f3SGreg Roach $match = []; 9557a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 956a6f13a4aSGreg Roach $id = $match[1]; 957a6f13a4aSGreg Roach } 958a6f13a4aSGreg Roach 959044416d2SGreg Roach if (isset($attrs['newline']) && $attrs['newline'] === '1') { 9607a6ee1acSGreg Roach $useBreak = '1'; 961a6f13a4aSGreg Roach } else { 9627a6ee1acSGreg Roach $useBreak = '0'; 963a6f13a4aSGreg Roach } 964a6f13a4aSGreg Roach 965a6f13a4aSGreg Roach $tag = $attrs['tag']; 966a6f13a4aSGreg Roach if (!empty($tag)) { 967044416d2SGreg Roach if ($tag === '@desc') { 968a6f13a4aSGreg Roach $value = $this->desc; 969a6f13a4aSGreg Roach $value = trim($value); 970a6f13a4aSGreg Roach $this->current_element->addText($value); 971a6f13a4aSGreg Roach } 972044416d2SGreg Roach if ($tag === '@id') { 973a6f13a4aSGreg Roach $this->current_element->addText($id); 974a6f13a4aSGreg Roach } else { 9757a6ee1acSGreg Roach $tag = str_replace('@fact', $this->fact, $tag); 976a6f13a4aSGreg Roach if (empty($attrs['level'])) { 977b2448a1bSGreg Roach $level = (int) explode(' ', trim($this->gedrec))[0]; 978b2448a1bSGreg Roach if ($level === 0) { 979a6f13a4aSGreg Roach $level++; 980a6f13a4aSGreg Roach } 981a6f13a4aSGreg Roach } else { 982b2448a1bSGreg Roach $level = (int) $attrs['level']; 983a6f13a4aSGreg Roach } 984a6f13a4aSGreg Roach $tags = preg_split('/[: ]/', $tag); 9853d7a8a4cSGreg Roach $value = $this->getGedcomValue($tag, $level, $this->gedrec); 986a6f13a4aSGreg Roach switch (end($tags)) { 987a6f13a4aSGreg Roach case 'DATE': 988a6f13a4aSGreg Roach $tmp = new Date($value); 989326dd299SGreg Roach $value = strip_tags($tmp->display()); 990a6f13a4aSGreg Roach break; 991a6f13a4aSGreg Roach case 'PLAC': 992299d100dSGreg Roach $tmp = new Place($value, $this->tree); 993392561bbSGreg Roach $value = $tmp->shortName(); 994a6f13a4aSGreg Roach break; 995a6f13a4aSGreg Roach } 996044416d2SGreg Roach if ($useBreak === '1') { 997a6f13a4aSGreg Roach // Insert <br> when multiple dates exist. 998a6f13a4aSGreg Roach // This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages 999a6f13a4aSGreg Roach $value = str_replace('(', '<br>(', $value); 1000a6f13a4aSGreg Roach $value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value); 1001a6f13a4aSGreg Roach $value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value); 10023b3cfeeaSGreg Roach if (substr($value, 0, 4) === '<br>') { 10033b3cfeeaSGreg Roach $value = substr($value, 4); 1004a6f13a4aSGreg Roach } 1005a6f13a4aSGreg Roach } 1006d4d660b7SGreg Roach $tmp = explode(':', $tag); 10073cfcc809SGreg Roach if (in_array(end($tmp), ['NOTE', 'TEXT'], true)) { 10084d35caa7SGreg Roach if ($this->tree->getPreference('FORMAT_TEXT') === 'markdown') { 100908b2f88aSGreg Roach $value = strip_tags(Registry::markdownFactory()->markdown($this->tree)->convertToHtml($value)); 10104d35caa7SGreg Roach } else { 101108b2f88aSGreg Roach $value = strip_tags(Registry::markdownFactory()->autolink($this->tree)->convertToHtml($value)); 10124d35caa7SGreg Roach } 1013a4d703aeSGreg Roach } 1014a2a485c3SGreg Roach 1015a2a485c3SGreg Roach if (!empty($attrs['truncate'])) { 1016381e28f4SGreg Roach $value = Str::limit($value, (int) $attrs['truncate'], I18N::translate('…')); 1017a2a485c3SGreg Roach } 1018a6f13a4aSGreg Roach $this->current_element->addText($value); 1019a6f13a4aSGreg Roach } 1020a6f13a4aSGreg Roach } 1021a6f13a4aSGreg Roach } 1022a6f13a4aSGreg Roach 1023a6f13a4aSGreg Roach /** 1024fab8f067SGreg Roach * Handle <repeatTag> 1025a6f13a4aSGreg Roach * 1026*09482a55SGreg Roach * @param array<string> $attrs 10278ba2e626SGreg Roach * 10288ba2e626SGreg Roach * @return void 1029a6f13a4aSGreg Roach */ 1030b702978eSGreg Roach protected function repeatTagStartHandler(array $attrs): void 1031c1010edaSGreg Roach { 1032a6f13a4aSGreg Roach $this->process_repeats++; 1033a6f13a4aSGreg Roach if ($this->process_repeats > 1) { 1034a6f13a4aSGreg Roach return; 1035a6f13a4aSGreg Roach } 1036a6f13a4aSGreg Roach 10379b3dd960SGreg Roach $this->repeats_stack[] = [$this->repeats, $this->repeat_bytes]; 103813abd6f3SGreg Roach $this->repeats = []; 1039e8e7866bSGreg Roach $this->repeat_bytes = xml_get_current_line_number($this->parser); 1040a6f13a4aSGreg Roach 1041e364afe4SGreg Roach $tag = $attrs['tag'] ?? ''; 1042a6f13a4aSGreg Roach if (!empty($tag)) { 1043044416d2SGreg Roach if ($tag === '@desc') { 1044a6f13a4aSGreg Roach $value = $this->desc; 1045a6f13a4aSGreg Roach $value = trim($value); 1046a6f13a4aSGreg Roach $this->current_element->addText($value); 1047a6f13a4aSGreg Roach } else { 10487a6ee1acSGreg Roach $tag = str_replace('@fact', $this->fact, $tag); 10497a6ee1acSGreg Roach $tags = explode(':', $tag); 1050b2448a1bSGreg Roach $level = (int) explode(' ', trim($this->gedrec))[0]; 1051b2448a1bSGreg Roach if ($level === 0) { 1052a6f13a4aSGreg Roach $level++; 1053a6f13a4aSGreg Roach } 1054a6f13a4aSGreg Roach $subrec = $this->gedrec; 1055a6f13a4aSGreg Roach $t = $tag; 1056a6f13a4aSGreg Roach $count = count($tags); 1057a6f13a4aSGreg Roach $i = 0; 1058a6f13a4aSGreg Roach while ($i < $count) { 1059a6f13a4aSGreg Roach $t = $tags[$i]; 1060a6f13a4aSGreg Roach if (!empty($t)) { 1061a6f13a4aSGreg Roach if ($i < ($count - 1)) { 10623d7a8a4cSGreg Roach $subrec = Functions::getSubRecord($level, "$level $t", $subrec); 1063a6f13a4aSGreg Roach if (empty($subrec)) { 1064a6f13a4aSGreg Roach $level--; 10653d7a8a4cSGreg Roach $subrec = Functions::getSubRecord($level, "@ $t", $this->gedrec); 1066a6f13a4aSGreg Roach if (empty($subrec)) { 1067a6f13a4aSGreg Roach return; 1068a6f13a4aSGreg Roach } 1069a6f13a4aSGreg Roach } 1070a6f13a4aSGreg Roach } 1071a6f13a4aSGreg Roach $level++; 1072a6f13a4aSGreg Roach } 1073a6f13a4aSGreg Roach $i++; 1074a6f13a4aSGreg Roach } 1075a6f13a4aSGreg Roach $level--; 1076a6f13a4aSGreg Roach $count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER); 1077a6f13a4aSGreg Roach $i = 0; 1078a6f13a4aSGreg Roach while ($i < $count) { 1079a6f13a4aSGreg Roach $i++; 1080a9007102SGreg Roach // Privacy check - is this a link, and are we allowed to view the linked object? 1081a9007102SGreg Roach $subrecord = Functions::getSubRecord($level, "$level $t", $subrec, $i); 10828d0ebef0SGreg Roach if (preg_match('/^\d ' . Gedcom::REGEX_TAG . ' @(' . Gedcom::REGEX_XREF . ')@/', $subrecord, $xref_match)) { 10836b9cb339SGreg Roach $linked_object = Registry::gedcomRecordFactory()->make($xref_match[1], $this->tree); 1084a9007102SGreg Roach if ($linked_object && !$linked_object->canShow()) { 1085a9007102SGreg Roach continue; 1086a9007102SGreg Roach } 1087a9007102SGreg Roach } 1088a9007102SGreg Roach $this->repeats[] = $subrecord; 1089a6f13a4aSGreg Roach } 1090a6f13a4aSGreg Roach } 1091a6f13a4aSGreg Roach } 1092a6f13a4aSGreg Roach } 1093a6f13a4aSGreg Roach 1094a6f13a4aSGreg Roach /** 1095fab8f067SGreg Roach * Handle </repeatTag> 10968ba2e626SGreg Roach * 10978ba2e626SGreg Roach * @return void 1098a6f13a4aSGreg Roach */ 1099b702978eSGreg Roach protected function repeatTagEndHandler(): void 1100c1010edaSGreg Roach { 1101a6f13a4aSGreg Roach $this->process_repeats--; 1102a6f13a4aSGreg Roach if ($this->process_repeats > 0) { 1103a6f13a4aSGreg Roach return; 1104a6f13a4aSGreg Roach } 1105a6f13a4aSGreg Roach 1106a6f13a4aSGreg Roach // Check if there is anything to repeat 1107a6f13a4aSGreg Roach if (count($this->repeats) > 0) { 1108a6f13a4aSGreg Roach // No need to load them if not used... 1109a6f13a4aSGreg Roach 1110a6f13a4aSGreg Roach $lineoffset = 0; 1111a6f13a4aSGreg Roach foreach ($this->repeats_stack as $rep) { 1112a6f13a4aSGreg Roach $lineoffset += $rep[1]; 1113a6f13a4aSGreg Roach } 1114a6f13a4aSGreg Roach //-- read the xml from the file 1115299d100dSGreg Roach $lines = file($this->report); 1116dec352c1SGreg Roach while (!str_contains($lines[$lineoffset + $this->repeat_bytes], '<RepeatTag')) { 1117a6f13a4aSGreg Roach $lineoffset--; 1118a6f13a4aSGreg Roach } 1119a6f13a4aSGreg Roach $lineoffset++; 1120a6f13a4aSGreg Roach $reportxml = "<tempdoc>\n"; 1121a6f13a4aSGreg Roach $line_nr = $lineoffset + $this->repeat_bytes; 1122a6f13a4aSGreg Roach // RepeatTag Level counter 1123a6f13a4aSGreg Roach $count = 1; 1124a6f13a4aSGreg Roach while (0 < $count) { 1125dec352c1SGreg Roach if (str_contains($lines[$line_nr], '<RepeatTag')) { 1126a6f13a4aSGreg Roach $count++; 1127dec352c1SGreg Roach } elseif (str_contains($lines[$line_nr], '</RepeatTag')) { 1128a6f13a4aSGreg Roach $count--; 1129a6f13a4aSGreg Roach } 1130a6f13a4aSGreg Roach if (0 < $count) { 1131a6f13a4aSGreg Roach $reportxml .= $lines[$line_nr]; 1132a6f13a4aSGreg Roach } 1133a6f13a4aSGreg Roach $line_nr++; 1134a6f13a4aSGreg Roach } 1135a6f13a4aSGreg Roach // No need to drag this 1136a6f13a4aSGreg Roach unset($lines); 1137a6f13a4aSGreg Roach $reportxml .= "</tempdoc>\n"; 1138a6f13a4aSGreg Roach // Save original values 11399b3dd960SGreg Roach $this->parser_stack[] = $this->parser; 1140a6f13a4aSGreg Roach $oldgedrec = $this->gedrec; 1141a6f13a4aSGreg Roach foreach ($this->repeats as $gedrec) { 1142a6f13a4aSGreg Roach $this->gedrec = $gedrec; 1143a6f13a4aSGreg Roach $repeat_parser = xml_parser_create(); 1144e8e7866bSGreg Roach $this->parser = $repeat_parser; 1145a6f13a4aSGreg Roach xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 11461aa04befSGreg Roach 11471aa04befSGreg Roach xml_set_element_handler( 11481aa04befSGreg Roach $repeat_parser, 11499d454b6bSGreg Roach function ($parser, string $name, array $attrs): void { 11501aa04befSGreg Roach $this->startElement($parser, $name, $attrs); 11511aa04befSGreg Roach }, 11529d454b6bSGreg Roach function ($parser, string $name): void { 11531aa04befSGreg Roach $this->endElement($parser, $name); 11541aa04befSGreg Roach } 11551aa04befSGreg Roach ); 11561aa04befSGreg Roach 11571aa04befSGreg Roach xml_set_character_data_handler( 11581aa04befSGreg Roach $repeat_parser, 11599d454b6bSGreg Roach function ($parser, string $data): void { 11601aa04befSGreg Roach $this->characterData($parser, $data); 11611aa04befSGreg Roach } 11621aa04befSGreg Roach ); 11631aa04befSGreg Roach 1164a6f13a4aSGreg Roach if (!xml_parse($repeat_parser, $reportxml, true)) { 11656ccdf4f0SGreg Roach throw new DomainException(sprintf( 1166a6f13a4aSGreg Roach 'RepeatTagEHandler XML error: %s at line %d', 1167a6f13a4aSGreg Roach xml_error_string(xml_get_error_code($repeat_parser)), 1168a6f13a4aSGreg Roach xml_get_current_line_number($repeat_parser) 1169a6f13a4aSGreg Roach )); 1170a6f13a4aSGreg Roach } 1171a6f13a4aSGreg Roach xml_parser_free($repeat_parser); 1172a6f13a4aSGreg Roach } 1173a6f13a4aSGreg Roach // Restore original values 1174a6f13a4aSGreg Roach $this->gedrec = $oldgedrec; 1175e8e7866bSGreg Roach $this->parser = array_pop($this->parser_stack); 1176a6f13a4aSGreg Roach } 117765e02381SGreg Roach [$this->repeats, $this->repeat_bytes] = array_pop($this->repeats_stack); 1178a6f13a4aSGreg Roach } 1179a6f13a4aSGreg Roach 1180a6f13a4aSGreg Roach /** 1181a6f13a4aSGreg Roach * Variable lookup 1182a6f13a4aSGreg Roach * Retrieve predefined variables : 1183a6f13a4aSGreg Roach * @ desc GEDCOM fact description, example: 1184a6f13a4aSGreg Roach * 1 EVEN This is a description 1185a6f13a4aSGreg Roach * @ fact GEDCOM fact tag, such as BIRT, DEAT etc. 1186a6f13a4aSGreg Roach * $ I18N::translate('....') 1187a6f13a4aSGreg Roach * $ language_settings[] 1188a6f13a4aSGreg Roach * 1189*09482a55SGreg Roach * @param array<string> $attrs an array of key value pairs for the attributes 11908ba2e626SGreg Roach * 11918ba2e626SGreg Roach * @return void 1192a6f13a4aSGreg Roach */ 1193b702978eSGreg Roach protected function varStartHandler(array $attrs): void 1194c1010edaSGreg Roach { 1195a6f13a4aSGreg Roach if (empty($attrs['var'])) { 11966ccdf4f0SGreg Roach 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)); 1197a6f13a4aSGreg Roach } 1198a6f13a4aSGreg Roach 1199a6f13a4aSGreg Roach $var = $attrs['var']; 1200a6f13a4aSGreg Roach // SetVar element preset variables 1201d1286247SGreg Roach if (!empty($this->vars[$var]['id'])) { 1202d1286247SGreg Roach $var = $this->vars[$var]['id']; 1203a6f13a4aSGreg Roach } else { 1204a6f13a4aSGreg Roach $tfact = $this->fact; 12057a6ee1acSGreg Roach if (($this->fact === 'EVEN' || $this->fact === 'FACT') && $this->type !== '') { 1206a6f13a4aSGreg Roach // Use : 1207a6f13a4aSGreg Roach // n TYPE This text if string 1208a6f13a4aSGreg Roach $tfact = $this->type; 120997def6bcSGreg Roach } else { 121097def6bcSGreg Roach foreach ([Individual::RECORD_TYPE, Family::RECORD_TYPE] as $record_type) { 121197def6bcSGreg Roach $element = Registry::elementFactory()->make($record_type . ':' . $this->fact); 121297def6bcSGreg Roach 121397def6bcSGreg Roach if (!$element instanceof UnknownElement) { 121497def6bcSGreg Roach $tfact = $element->label(); 121597def6bcSGreg Roach break; 1216a6f13a4aSGreg Roach } 121797def6bcSGreg Roach } 121897def6bcSGreg Roach } 121997def6bcSGreg Roach 122097def6bcSGreg Roach $var = strtr($var, ['@desc' => $this->desc, '@fact' => $tfact]); 122197def6bcSGreg Roach 1222a6f13a4aSGreg Roach if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) { 1223da46f7cdSGreg Roach $var = I18N::number((int) $match[1]); 1224a6f13a4aSGreg Roach } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) { 1225a6f13a4aSGreg Roach $var = I18N::translate($match[1]); 1226a4956c0eSGreg Roach } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) { 1227a6f13a4aSGreg Roach $var = I18N::translateContext($match[1], $match[2]); 1228a6f13a4aSGreg Roach } 1229a6f13a4aSGreg Roach } 1230a6f13a4aSGreg Roach // Check if variable is set as a date and reformat the date 1231a6f13a4aSGreg Roach if (isset($attrs['date'])) { 12327a6ee1acSGreg Roach if ($attrs['date'] === '1') { 1233a6f13a4aSGreg Roach $g = new Date($var); 1234a6f13a4aSGreg Roach $var = $g->display(); 1235a6f13a4aSGreg Roach } 1236a6f13a4aSGreg Roach } 1237a6f13a4aSGreg Roach $this->current_element->addText($var); 12382836aa05SGreg Roach $this->text = $var; // Used for title/descriptio 1239a6f13a4aSGreg Roach } 1240a6f13a4aSGreg Roach 1241a6f13a4aSGreg Roach /** 1242fab8f067SGreg Roach * Handle <facts> 124376692c8bSGreg Roach * 1244*09482a55SGreg Roach * @param array<string> $attrs 12458ba2e626SGreg Roach * 12468ba2e626SGreg Roach * @return void 1247a6f13a4aSGreg Roach */ 1248b702978eSGreg Roach protected function factsStartHandler(array $attrs): void 1249c1010edaSGreg Roach { 1250a6f13a4aSGreg Roach $this->process_repeats++; 1251a6f13a4aSGreg Roach if ($this->process_repeats > 1) { 1252a6f13a4aSGreg Roach return; 1253a6f13a4aSGreg Roach } 1254a6f13a4aSGreg Roach 12559b3dd960SGreg Roach $this->repeats_stack[] = [$this->repeats, $this->repeat_bytes]; 125613abd6f3SGreg Roach $this->repeats = []; 1257e8e7866bSGreg Roach $this->repeat_bytes = xml_get_current_line_number($this->parser); 1258a6f13a4aSGreg Roach 12597a6ee1acSGreg Roach $id = ''; 126013abd6f3SGreg Roach $match = []; 12617a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 1262a6f13a4aSGreg Roach $id = $match[1]; 1263a6f13a4aSGreg Roach } 12647a6ee1acSGreg Roach $tag = ''; 1265a6f13a4aSGreg Roach if (isset($attrs['ignore'])) { 1266a6f13a4aSGreg Roach $tag .= $attrs['ignore']; 1267a6f13a4aSGreg Roach } 12687a6ee1acSGreg Roach if (preg_match('/\$(.+)/', $tag, $match)) { 1269d1286247SGreg Roach $tag = $this->vars[$match[1]]['id']; 1270a6f13a4aSGreg Roach } 1271a6f13a4aSGreg Roach 12726b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->make($id, $this->tree); 1273a6f13a4aSGreg Roach if (empty($attrs['diff']) && !empty($id)) { 1274820b62dfSGreg Roach $facts = $record->facts([], true); 127513abd6f3SGreg Roach $this->repeats = []; 1276a6f13a4aSGreg Roach $nonfacts = explode(',', $tag); 1277195b5e75SGreg Roach foreach ($facts as $fact) { 1278d0889c63SGreg Roach $tag = explode(':', $fact->tag())[1]; 1279d0889c63SGreg Roach 1280d0889c63SGreg Roach if (!in_array($tag, $nonfacts, true)) { 1281195b5e75SGreg Roach $this->repeats[] = $fact->gedcom(); 1282a6f13a4aSGreg Roach } 1283a6f13a4aSGreg Roach } 1284a6f13a4aSGreg Roach } else { 128530158ae7SGreg Roach foreach ($record->facts() as $fact) { 1286d0889c63SGreg Roach if (($fact->isPendingAddition() || $fact->isPendingDeletion()) && !str_ends_with($fact->tag(), ':CHAN')) { 1287138ca96cSGreg Roach $this->repeats[] = $fact->gedcom(); 1288a6f13a4aSGreg Roach } 1289a6f13a4aSGreg Roach } 1290a6f13a4aSGreg Roach } 1291a6f13a4aSGreg Roach } 1292a6f13a4aSGreg Roach 1293a6f13a4aSGreg Roach /** 1294fab8f067SGreg Roach * Handle </facts> 12958ba2e626SGreg Roach * 12968ba2e626SGreg Roach * @return void 1297a6f13a4aSGreg Roach */ 1298b702978eSGreg Roach protected function factsEndHandler(): void 1299c1010edaSGreg Roach { 1300a6f13a4aSGreg Roach $this->process_repeats--; 1301a6f13a4aSGreg Roach if ($this->process_repeats > 0) { 1302a6f13a4aSGreg Roach return; 1303a6f13a4aSGreg Roach } 1304a6f13a4aSGreg Roach 1305a6f13a4aSGreg Roach // Check if there is anything to repeat 1306a6f13a4aSGreg Roach if (count($this->repeats) > 0) { 1307e8e7866bSGreg Roach $line = xml_get_current_line_number($this->parser) - 1; 1308a6f13a4aSGreg Roach $lineoffset = 0; 1309a6f13a4aSGreg Roach foreach ($this->repeats_stack as $rep) { 1310a6f13a4aSGreg Roach $lineoffset += $rep[1]; 1311a6f13a4aSGreg Roach } 1312a6f13a4aSGreg Roach 1313a6f13a4aSGreg Roach //-- read the xml from the file 1314299d100dSGreg Roach $lines = file($this->report); 1315dec352c1SGreg Roach while ($lineoffset + $this->repeat_bytes > 0 && !str_contains($lines[$lineoffset + $this->repeat_bytes], '<Facts ')) { 1316a6f13a4aSGreg Roach $lineoffset--; 1317a6f13a4aSGreg Roach } 1318a6f13a4aSGreg Roach $lineoffset++; 1319a6f13a4aSGreg Roach $reportxml = "<tempdoc>\n"; 1320a6f13a4aSGreg Roach $i = $line + $lineoffset; 1321a6f13a4aSGreg Roach $line_nr = $this->repeat_bytes + $lineoffset; 1322a6f13a4aSGreg Roach while ($line_nr < $i) { 1323a6f13a4aSGreg Roach $reportxml .= $lines[$line_nr]; 1324a6f13a4aSGreg Roach $line_nr++; 1325a6f13a4aSGreg Roach } 1326a6f13a4aSGreg Roach // No need to drag this 1327a6f13a4aSGreg Roach unset($lines); 1328a6f13a4aSGreg Roach $reportxml .= "</tempdoc>\n"; 1329a6f13a4aSGreg Roach // Save original values 13309b3dd960SGreg Roach $this->parser_stack[] = $this->parser; 1331a6f13a4aSGreg Roach $oldgedrec = $this->gedrec; 1332a6f13a4aSGreg Roach $count = count($this->repeats); 1333a6f13a4aSGreg Roach $i = 0; 1334a6f13a4aSGreg Roach while ($i < $count) { 1335a6f13a4aSGreg Roach $this->gedrec = $this->repeats[$i]; 1336a6f13a4aSGreg Roach $this->fact = ''; 1337a6f13a4aSGreg Roach $this->desc = ''; 1338a6f13a4aSGreg Roach if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) { 1339a6f13a4aSGreg Roach $this->fact = $match[1]; 1340a6f13a4aSGreg Roach if ($this->fact === 'EVEN' || $this->fact === 'FACT') { 134113abd6f3SGreg Roach $tmatch = []; 1342a6f13a4aSGreg Roach if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) { 1343a6f13a4aSGreg Roach $this->type = trim($tmatch[1]); 1344a6f13a4aSGreg Roach } else { 1345a6f13a4aSGreg Roach $this->type = ' '; 1346a6f13a4aSGreg Roach } 1347a6f13a4aSGreg Roach } 1348a6f13a4aSGreg Roach $this->desc = trim($match[2]); 13493d7a8a4cSGreg Roach $this->desc .= Functions::getCont(2, $this->gedrec); 1350a6f13a4aSGreg Roach } 1351a6f13a4aSGreg Roach $repeat_parser = xml_parser_create(); 1352e8e7866bSGreg Roach $this->parser = $repeat_parser; 1353a6f13a4aSGreg Roach xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 13541aa04befSGreg Roach 13551aa04befSGreg Roach xml_set_element_handler( 13561aa04befSGreg Roach $repeat_parser, 13579d454b6bSGreg Roach function ($parser, string $name, array $attrs): void { 13581aa04befSGreg Roach $this->startElement($parser, $name, $attrs); 13591aa04befSGreg Roach }, 13609d454b6bSGreg Roach function ($parser, string $name): void { 13611aa04befSGreg Roach $this->endElement($parser, $name); 13621aa04befSGreg Roach } 13631aa04befSGreg Roach ); 13641aa04befSGreg Roach 13651aa04befSGreg Roach xml_set_character_data_handler( 13661aa04befSGreg Roach $repeat_parser, 13679d454b6bSGreg Roach function ($parser, string $data): void { 13681aa04befSGreg Roach $this->characterData($parser, $data); 13691aa04befSGreg Roach } 13701aa04befSGreg Roach ); 13711aa04befSGreg Roach 1372a6f13a4aSGreg Roach if (!xml_parse($repeat_parser, $reportxml, true)) { 13736ccdf4f0SGreg Roach throw new DomainException(sprintf( 1374a6f13a4aSGreg Roach 'FactsEHandler XML error: %s at line %d', 1375a6f13a4aSGreg Roach xml_error_string(xml_get_error_code($repeat_parser)), 1376a6f13a4aSGreg Roach xml_get_current_line_number($repeat_parser) 1377a6f13a4aSGreg Roach )); 1378a6f13a4aSGreg Roach } 1379a6f13a4aSGreg Roach xml_parser_free($repeat_parser); 1380a6f13a4aSGreg Roach $i++; 1381a6f13a4aSGreg Roach } 1382a6f13a4aSGreg Roach // Restore original values 1383e8e7866bSGreg Roach $this->parser = array_pop($this->parser_stack); 1384a6f13a4aSGreg Roach $this->gedrec = $oldgedrec; 1385a6f13a4aSGreg Roach } 138665e02381SGreg Roach [$this->repeats, $this->repeat_bytes] = array_pop($this->repeats_stack); 1387a6f13a4aSGreg Roach } 1388a6f13a4aSGreg Roach 1389a6f13a4aSGreg Roach /** 1390a6f13a4aSGreg Roach * Setting upp or changing variables in the XML 1391d1286247SGreg Roach * The XML variable name and value is stored in $this->vars 1392a6f13a4aSGreg Roach * 1393*09482a55SGreg Roach * @param array<string> $attrs an array of key value pairs for the attributes 13948ba2e626SGreg Roach * 13958ba2e626SGreg Roach * @return void 1396a6f13a4aSGreg Roach */ 1397b702978eSGreg Roach protected function setVarStartHandler(array $attrs): void 1398c1010edaSGreg Roach { 1399a6f13a4aSGreg Roach if (empty($attrs['name'])) { 14006ccdf4f0SGreg Roach throw new DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file'); 1401a6f13a4aSGreg Roach } 1402a6f13a4aSGreg Roach 1403a6f13a4aSGreg Roach $name = $attrs['name']; 1404a6f13a4aSGreg Roach $value = $attrs['value']; 140513abd6f3SGreg Roach $match = []; 1406a6f13a4aSGreg Roach // Current GEDCOM record strings 1407044416d2SGreg Roach if ($value === '@ID') { 14087a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 1409a6f13a4aSGreg Roach $value = $match[1]; 1410a6f13a4aSGreg Roach } 1411044416d2SGreg Roach } elseif ($value === '@fact') { 1412a6f13a4aSGreg Roach $value = $this->fact; 1413044416d2SGreg Roach } elseif ($value === '@desc') { 1414a6f13a4aSGreg Roach $value = $this->desc; 1415044416d2SGreg Roach } elseif ($value === '@generation') { 1416589feda3SGreg Roach $value = (string) $this->generation; 1417a6f13a4aSGreg Roach } elseif (preg_match("/@(\w+)/", $value, $match)) { 141813abd6f3SGreg Roach $gmatch = []; 1419a6f13a4aSGreg Roach if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) { 14207a6ee1acSGreg Roach $value = str_replace('@', '', trim($gmatch[1])); 1421a6f13a4aSGreg Roach } 1422a6f13a4aSGreg Roach } 1423a6f13a4aSGreg Roach if (preg_match("/\\$(\w+)/", $name, $match)) { 1424d1286247SGreg Roach $name = $this->vars["'" . $match[1] . "'"]['id']; 1425a6f13a4aSGreg Roach } 1426a6f13a4aSGreg Roach $count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER); 1427a6f13a4aSGreg Roach $i = 0; 1428a6f13a4aSGreg Roach while ($i < $count) { 1429d1286247SGreg Roach $t = $this->vars[$match[$i][1]]['id']; 14307a6ee1acSGreg Roach $value = preg_replace('/\$' . $match[$i][1] . '/', $t, $value, 1); 1431a6f13a4aSGreg Roach $i++; 1432a6f13a4aSGreg Roach } 1433a6f13a4aSGreg Roach if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) { 1434da46f7cdSGreg Roach $value = I18N::number((int) $match[1]); 1435a6f13a4aSGreg Roach } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) { 1436a6f13a4aSGreg Roach $value = I18N::translate($match[1]); 1437a4956c0eSGreg Roach } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) { 1438a6f13a4aSGreg Roach $value = I18N::translateContext($match[1], $match[2]); 1439a6f13a4aSGreg Roach } 144052868398SGreg Roach 1441a6f13a4aSGreg Roach // Arithmetic functions 14423cfcc809SGreg Roach if (preg_match("/(\d+)\s*([-+*\/])\s*(\d+)/", $value, $match)) { 144352868398SGreg Roach // Create an expression language with the functions used by our reports. 144452868398SGreg Roach $expression_provider = new ReportExpressionLanguageProvider(); 1445c0fe75acSGreg Roach $expression_cache = new NullAdapter(); 1446c0fe75acSGreg Roach $expression_language = new ExpressionLanguage($expression_cache, [$expression_provider]); 144752868398SGreg Roach 144852868398SGreg Roach $value = (string) $expression_language->evaluate($value); 1449a6f13a4aSGreg Roach } 145052868398SGreg Roach 1451dec352c1SGreg Roach if (str_contains($value, '@')) { 14527a6ee1acSGreg Roach $value = ''; 1453a6f13a4aSGreg Roach } 1454d1286247SGreg Roach $this->vars[$name]['id'] = $value; 1455a6f13a4aSGreg Roach } 1456a6f13a4aSGreg Roach 1457a6f13a4aSGreg Roach /** 1458fab8f067SGreg Roach * Handle <if> 1459a6f13a4aSGreg Roach * 1460*09482a55SGreg Roach * @param array<string> $attrs 14618ba2e626SGreg Roach * 14628ba2e626SGreg Roach * @return void 1463a6f13a4aSGreg Roach */ 1464b702978eSGreg Roach protected function ifStartHandler(array $attrs): void 1465c1010edaSGreg Roach { 1466a6f13a4aSGreg Roach if ($this->process_ifs > 0) { 1467a6f13a4aSGreg Roach $this->process_ifs++; 1468a6f13a4aSGreg Roach 1469a6f13a4aSGreg Roach return; 1470a6f13a4aSGreg Roach } 1471a6f13a4aSGreg Roach 1472a6f13a4aSGreg Roach $condition = $attrs['condition']; 147382759250SGreg Roach $condition = $this->substituteVars($condition, true); 1474c1010edaSGreg Roach $condition = str_replace([ 1475c1010edaSGreg Roach ' LT ', 1476c1010edaSGreg Roach ' GT ', 1477c1010edaSGreg Roach ], [ 1478c1010edaSGreg Roach '<', 1479c1010edaSGreg Roach '>', 1480c1010edaSGreg Roach ], $condition); 14813cfcc809SGreg Roach // Replace the first occurrence only once of @fact:DATE or in any other combinations to the current fact, such as BIRT 14827a6ee1acSGreg Roach $condition = str_replace('@fact:', $this->fact . ':', $condition); 148313abd6f3SGreg Roach $match = []; 14843cfcc809SGreg Roach $count = preg_match_all("/@([\w:.]+)/", $condition, $match, PREG_SET_ORDER); 1485a6f13a4aSGreg Roach $i = 0; 1486a6f13a4aSGreg Roach while ($i < $count) { 1487a6f13a4aSGreg Roach $id = $match[$i][1]; 1488a6f13a4aSGreg Roach $value = '""'; 1489044416d2SGreg Roach if ($id === 'ID') { 14907a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 1491a6f13a4aSGreg Roach $value = "'" . $match[1] . "'"; 1492a6f13a4aSGreg Roach } 14937a6ee1acSGreg Roach } elseif ($id === 'fact') { 1494a6f13a4aSGreg Roach $value = '"' . $this->fact . '"'; 14957a6ee1acSGreg Roach } elseif ($id === 'desc') { 1496a6f13a4aSGreg Roach $value = '"' . addslashes($this->desc) . '"'; 14977a6ee1acSGreg Roach } elseif ($id === 'generation') { 1498a6f13a4aSGreg Roach $value = '"' . $this->generation . '"'; 1499a6f13a4aSGreg Roach } else { 1500b2448a1bSGreg Roach $level = (int) explode(' ', trim($this->gedrec))[0]; 1501b2448a1bSGreg Roach if ($level === 0) { 1502a6f13a4aSGreg Roach $level++; 1503a6f13a4aSGreg Roach } 15043d7a8a4cSGreg Roach $value = $this->getGedcomValue($id, $level, $this->gedrec); 1505a6f13a4aSGreg Roach if (empty($value)) { 1506a6f13a4aSGreg Roach $level++; 15073d7a8a4cSGreg Roach $value = $this->getGedcomValue($id, $level, $this->gedrec); 1508a6f13a4aSGreg Roach } 15098d0ebef0SGreg Roach $value = preg_replace('/^@(' . Gedcom::REGEX_XREF . ')@$/', '$1', $value); 15105e8c88c1SGreg Roach $value = '"' . addslashes($value) . '"'; 1511a6f13a4aSGreg Roach } 1512a6f13a4aSGreg Roach $condition = str_replace("@$id", $value, $condition); 1513a6f13a4aSGreg Roach $i++; 1514a6f13a4aSGreg Roach } 15155809450fSGreg Roach 1516cb63a60eSGreg Roach // Create an expression language with the functions used by our reports. 1517cb63a60eSGreg Roach $expression_provider = new ReportExpressionLanguageProvider(); 1518c0fe75acSGreg Roach $expression_cache = new NullAdapter(); 1519c0fe75acSGreg Roach $expression_language = new ExpressionLanguage($expression_cache, [$expression_provider]); 1520cb63a60eSGreg Roach 1521cb63a60eSGreg Roach $ret = $expression_language->evaluate($condition); 15225809450fSGreg Roach 1523a6f13a4aSGreg Roach if (!$ret) { 1524a6f13a4aSGreg Roach $this->process_ifs++; 1525a6f13a4aSGreg Roach } 1526a6f13a4aSGreg Roach } 1527a6f13a4aSGreg Roach 1528a6f13a4aSGreg Roach /** 1529fab8f067SGreg Roach * Handle </if> 15308ba2e626SGreg Roach * 15318ba2e626SGreg Roach * @return void 1532a6f13a4aSGreg Roach */ 1533b702978eSGreg Roach protected function ifEndHandler(): void 1534c1010edaSGreg Roach { 1535a6f13a4aSGreg Roach if ($this->process_ifs > 0) { 1536a6f13a4aSGreg Roach $this->process_ifs--; 1537a6f13a4aSGreg Roach } 1538a6f13a4aSGreg Roach } 1539a6f13a4aSGreg Roach 1540a6f13a4aSGreg Roach /** 1541fab8f067SGreg Roach * Handle <footnote> 1542a6f13a4aSGreg Roach * Collect the Footnote links 1543fab8f067SGreg Roach * GEDCOM Records that are protected by Privacy setting will be ignored 1544a6f13a4aSGreg Roach * 1545*09482a55SGreg Roach * @param array<string> $attrs 15468ba2e626SGreg Roach * 15478ba2e626SGreg Roach * @return void 1548a6f13a4aSGreg Roach */ 1549b702978eSGreg Roach protected function footnoteStartHandler(array $attrs): void 1550c1010edaSGreg Roach { 15517a6ee1acSGreg Roach $id = ''; 15527a6ee1acSGreg Roach if (preg_match('/[0-9] (.+) @(.+)@/', $this->gedrec, $match)) { 1553a6f13a4aSGreg Roach $id = $match[2]; 1554a6f13a4aSGreg Roach } 15556b9cb339SGreg Roach $record = Registry::gedcomRecordFactory()->make($id, $this->tree); 1556a6f13a4aSGreg Roach if ($record && $record->canShow()) { 15579b3dd960SGreg Roach $this->print_data_stack[] = $this->print_data; 1558a6f13a4aSGreg Roach $this->print_data = true; 15597a6ee1acSGreg Roach $style = ''; 1560a6f13a4aSGreg Roach if (!empty($attrs['style'])) { 1561a6f13a4aSGreg Roach $style = $attrs['style']; 1562a6f13a4aSGreg Roach } 1563a6f13a4aSGreg Roach $this->footnote_element = $this->current_element; 1564e8e7866bSGreg Roach $this->current_element = $this->report_root->createFootnote($style); 1565a6f13a4aSGreg Roach } else { 1566a6f13a4aSGreg Roach $this->print_data = false; 1567a6f13a4aSGreg Roach $this->process_footnote = false; 1568a6f13a4aSGreg Roach } 1569a6f13a4aSGreg Roach } 1570a6f13a4aSGreg Roach 1571a6f13a4aSGreg Roach /** 1572fab8f067SGreg Roach * Handle </footnote> 1573a6f13a4aSGreg Roach * Print the collected Footnote data 15748ba2e626SGreg Roach * 15758ba2e626SGreg Roach * @return void 1576a6f13a4aSGreg Roach */ 1577b702978eSGreg Roach protected function footnoteEndHandler(): void 1578c1010edaSGreg Roach { 1579a6f13a4aSGreg Roach if ($this->process_footnote) { 1580a6f13a4aSGreg Roach $this->print_data = array_pop($this->print_data_stack); 1581a6f13a4aSGreg Roach $temp = trim($this->current_element->getValue()); 1582a6f13a4aSGreg Roach if (strlen($temp) > 3) { 1583e8e7866bSGreg Roach $this->wt_report->addElement($this->current_element); 1584a6f13a4aSGreg Roach } 1585a6f13a4aSGreg Roach $this->current_element = $this->footnote_element; 1586a6f13a4aSGreg Roach } else { 1587a6f13a4aSGreg Roach $this->process_footnote = true; 1588a6f13a4aSGreg Roach } 1589a6f13a4aSGreg Roach } 1590a6f13a4aSGreg Roach 1591a6f13a4aSGreg Roach /** 1592fab8f067SGreg Roach * Handle <footnoteTexts /> 15938ba2e626SGreg Roach * 15948ba2e626SGreg Roach * @return void 1595a6f13a4aSGreg Roach */ 1596b702978eSGreg Roach protected function footnoteTextsStartHandler(): void 1597c1010edaSGreg Roach { 15987a6ee1acSGreg Roach $temp = 'footnotetexts'; 1599e8e7866bSGreg Roach $this->wt_report->addElement($temp); 1600a6f13a4aSGreg Roach } 1601a6f13a4aSGreg Roach 1602a6f13a4aSGreg Roach /** 1603a6f13a4aSGreg Roach * XML element Forced line break handler - HTML code 16048ba2e626SGreg Roach * 16058ba2e626SGreg Roach * @return void 1606a6f13a4aSGreg Roach */ 1607b702978eSGreg Roach protected function brStartHandler(): void 1608c1010edaSGreg Roach { 1609a6f13a4aSGreg Roach if ($this->print_data && $this->process_gedcoms === 0) { 1610a6f13a4aSGreg Roach $this->current_element->addText('<br>'); 1611a6f13a4aSGreg Roach } 1612a6f13a4aSGreg Roach } 1613a6f13a4aSGreg Roach 1614a6f13a4aSGreg Roach /** 1615fab8f067SGreg Roach * Handle <sp /> 1616fab8f067SGreg Roach * Forced space 16178ba2e626SGreg Roach * 16188ba2e626SGreg Roach * @return void 1619a6f13a4aSGreg Roach */ 1620b702978eSGreg Roach protected function spStartHandler(): void 1621c1010edaSGreg Roach { 1622a6f13a4aSGreg Roach if ($this->print_data && $this->process_gedcoms === 0) { 1623a6f13a4aSGreg Roach $this->current_element->addText(' '); 1624a6f13a4aSGreg Roach } 1625a6f13a4aSGreg Roach } 1626a6f13a4aSGreg Roach 1627a6f13a4aSGreg Roach /** 1628fab8f067SGreg Roach * Handle <highlightedImage /> 162976692c8bSGreg Roach * 1630*09482a55SGreg Roach * @param array<string> $attrs 16318ba2e626SGreg Roach * 16328ba2e626SGreg Roach * @return void 1633a6f13a4aSGreg Roach */ 1634b702978eSGreg Roach protected function highlightedImageStartHandler(array $attrs): void 1635c1010edaSGreg Roach { 1636a6f13a4aSGreg Roach $id = ''; 16377a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 1638a6f13a4aSGreg Roach $id = $match[1]; 1639a6f13a4aSGreg Roach } 1640a6f13a4aSGreg Roach 1641c21bdddcSGreg Roach // Position the top corner of this box on the page 1642c21bdddcSGreg Roach $top = (float) ($attrs['top'] ?? ReportBaseElement::CURRENT_POSITION); 1643a6f13a4aSGreg Roach 1644c21bdddcSGreg Roach // Position the left corner of this box on the page 1645c21bdddcSGreg Roach $left = (float) ($attrs['left'] ?? ReportBaseElement::CURRENT_POSITION); 1646a6f13a4aSGreg Roach 164783cdc021SGreg Roach // string Align the image in left, center, right (or empty to use x/y position). 164883cdc021SGreg Roach $align = $attrs['align'] ?? ''; 1649a6f13a4aSGreg Roach 1650a6f13a4aSGreg Roach // string Next Line should be T:next to the image, N:next line 165183cdc021SGreg Roach $ln = $attrs['ln'] ?? 'T'; 1652a6f13a4aSGreg Roach 165383cdc021SGreg Roach // Width, height (or both). 1654c21bdddcSGreg Roach $width = (float) ($attrs['width'] ?? 0.0); 1655c21bdddcSGreg Roach $height = (float) ($attrs['height'] ?? 0.0); 1656a6f13a4aSGreg Roach 16576b9cb339SGreg Roach $person = Registry::individualFactory()->make($id, $this->tree); 16584a9f750fSGreg Roach $media_file = $person->findHighlightedMediaFile(); 165986a63f51SGreg Roach 1660a04bb9a2SGreg Roach if ($media_file instanceof MediaFile && $media_file->fileExists($this->data_filesystem)) { 1661a04bb9a2SGreg Roach $image = imagecreatefromstring($media_file->fileContents($this->data_filesystem)); 1662b6f35a76SGreg Roach $attributes = [imagesx($image), imagesy($image)]; 166329518ad2SGreg Roach 1664a6f13a4aSGreg Roach if ($width > 0 && $height == 0) { 16653c3b90deSGreg Roach $perc = $width / $attributes[0]; 16663c3b90deSGreg Roach $height = round($attributes[1] * $perc); 1667a6f13a4aSGreg Roach } elseif ($height > 0 && $width == 0) { 16683c3b90deSGreg Roach $perc = $height / $attributes[1]; 16693c3b90deSGreg Roach $width = round($attributes[0] * $perc); 1670a6f13a4aSGreg Roach } else { 16713c3b90deSGreg Roach $width = $attributes[0]; 16723c3b90deSGreg Roach $height = $attributes[1]; 1673a6f13a4aSGreg Roach } 1674a04bb9a2SGreg Roach $image = $this->report_root->createImageFromObject($media_file, $left, $top, $width, $height, $align, $ln, $this->data_filesystem); 1675e8e7866bSGreg Roach $this->wt_report->addElement($image); 1676a6f13a4aSGreg Roach } 1677a6f13a4aSGreg Roach } 1678a6f13a4aSGreg Roach 1679a6f13a4aSGreg Roach /** 1680fab8f067SGreg Roach * Handle <image/> 168176692c8bSGreg Roach * 1682*09482a55SGreg Roach * @param array<string> $attrs 16838ba2e626SGreg Roach * 16848ba2e626SGreg Roach * @return void 1685a6f13a4aSGreg Roach */ 1686b702978eSGreg Roach protected function imageStartHandler(array $attrs): void 1687c1010edaSGreg Roach { 168883cdc021SGreg Roach // Position the top corner of this box on the page. the default is the current position 1689c21bdddcSGreg Roach $top = (float) ($attrs['top'] ?? ReportBaseElement::CURRENT_POSITION); 1690a6f13a4aSGreg Roach 1691a6f13a4aSGreg Roach // mixed Position the left corner of this box on the page. the default is the current position 1692c21bdddcSGreg Roach $left = (float) ($attrs['left'] ?? ReportBaseElement::CURRENT_POSITION); 1693a6f13a4aSGreg Roach 169483cdc021SGreg Roach // string Align the image in left, center, right (or empty to use x/y position). 169583cdc021SGreg Roach $align = $attrs['align'] ?? ''; 1696a6f13a4aSGreg Roach 1697a6f13a4aSGreg Roach // string Next Line should be T:next to the image, N:next line 169883cdc021SGreg Roach $ln = $attrs['ln'] ?? 'T'; 1699a6f13a4aSGreg Roach 170083cdc021SGreg Roach // Width, height (or both). 1701c21bdddcSGreg Roach $width = (float) ($attrs['width'] ?? 0.0); 1702c21bdddcSGreg Roach $height = (float) ($attrs['height'] ?? 0.0); 1703a6f13a4aSGreg Roach 170483cdc021SGreg Roach $file = $attrs['file'] ?? ''; 170583cdc021SGreg Roach 1706044416d2SGreg Roach if ($file === '@FILE') { 170713abd6f3SGreg Roach $match = []; 1708a6f13a4aSGreg Roach if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) { 17096b9cb339SGreg Roach $mediaobject = Registry::mediaFactory()->make($match[1], $this->tree); 17104a9f750fSGreg Roach $media_file = $mediaobject->firstImageFile(); 1711cdf416fbSGreg Roach 1712a04bb9a2SGreg Roach if ($media_file instanceof MediaFile && $media_file->fileExists($this->data_filesystem)) { 1713a04bb9a2SGreg Roach $image = imagecreatefromstring($media_file->fileContents($this->data_filesystem)); 1714b6f35a76SGreg Roach $attributes = [imagesx($image), imagesy($image)]; 171529518ad2SGreg Roach 1716a6f13a4aSGreg Roach if ($width > 0 && $height == 0) { 17173c3b90deSGreg Roach $perc = $width / $attributes[0]; 17183c3b90deSGreg Roach $height = round($attributes[1] * $perc); 1719a6f13a4aSGreg Roach } elseif ($height > 0 && $width == 0) { 17203c3b90deSGreg Roach $perc = $height / $attributes[1]; 17213c3b90deSGreg Roach $width = round($attributes[0] * $perc); 1722a6f13a4aSGreg Roach } else { 17233c3b90deSGreg Roach $width = $attributes[0]; 17243c3b90deSGreg Roach $height = $attributes[1]; 1725a6f13a4aSGreg Roach } 1726a04bb9a2SGreg Roach $image = $this->report_root->createImageFromObject($media_file, $left, $top, $width, $height, $align, $ln, $this->data_filesystem); 1727e8e7866bSGreg Roach $this->wt_report->addElement($image); 1728a6f13a4aSGreg Roach } 1729a6f13a4aSGreg Roach } 1730a6f13a4aSGreg Roach } else { 17317a6ee1acSGreg Roach if (file_exists($file) && preg_match('/(jpg|jpeg|png|gif)$/i', $file)) { 1732a6f13a4aSGreg Roach $size = getimagesize($file); 1733a6f13a4aSGreg Roach if ($width > 0 && $height == 0) { 1734a6f13a4aSGreg Roach $perc = $width / $size[0]; 1735a6f13a4aSGreg Roach $height = round($size[1] * $perc); 1736a6f13a4aSGreg Roach } elseif ($height > 0 && $width == 0) { 1737a6f13a4aSGreg Roach $perc = $height / $size[1]; 1738a6f13a4aSGreg Roach $width = round($size[0] * $perc); 1739a6f13a4aSGreg Roach } else { 1740a6f13a4aSGreg Roach $width = $size[0]; 1741a6f13a4aSGreg Roach $height = $size[1]; 1742a6f13a4aSGreg Roach } 1743e8e7866bSGreg Roach $image = $this->report_root->createImage($file, $left, $top, $width, $height, $align, $ln); 1744e8e7866bSGreg Roach $this->wt_report->addElement($image); 1745a6f13a4aSGreg Roach } 1746a6f13a4aSGreg Roach } 1747a6f13a4aSGreg Roach } 1748a6f13a4aSGreg Roach 1749a6f13a4aSGreg Roach /** 1750fab8f067SGreg Roach * Handle <line> 1751a6f13a4aSGreg Roach * 1752*09482a55SGreg Roach * @param array<string> $attrs 17538ba2e626SGreg Roach * 17548ba2e626SGreg Roach * @return void 1755a6f13a4aSGreg Roach */ 1756b702978eSGreg Roach protected function lineStartHandler(array $attrs): void 1757c1010edaSGreg Roach { 1758a6f13a4aSGreg Roach // Start horizontal position, current position (default) 1759c21bdddcSGreg Roach $x1 = ReportBaseElement::CURRENT_POSITION; 1760a6f13a4aSGreg Roach if (isset($attrs['x1'])) { 17617a6ee1acSGreg Roach if ($attrs['x1'] === '0') { 1762a6f13a4aSGreg Roach $x1 = 0; 17637a6ee1acSGreg Roach } elseif ($attrs['x1'] === '.') { 1764c21bdddcSGreg Roach $x1 = ReportBaseElement::CURRENT_POSITION; 1765a6f13a4aSGreg Roach } elseif (!empty($attrs['x1'])) { 1766c21bdddcSGreg Roach $x1 = (float) $attrs['x1']; 1767a6f13a4aSGreg Roach } 1768a6f13a4aSGreg Roach } 1769a6f13a4aSGreg Roach // Start vertical position, current position (default) 1770c21bdddcSGreg Roach $y1 = ReportBaseElement::CURRENT_POSITION; 1771a6f13a4aSGreg Roach if (isset($attrs['y1'])) { 17727a6ee1acSGreg Roach if ($attrs['y1'] === '0') { 1773a6f13a4aSGreg Roach $y1 = 0; 17747a6ee1acSGreg Roach } elseif ($attrs['y1'] === '.') { 1775c21bdddcSGreg Roach $y1 = ReportBaseElement::CURRENT_POSITION; 1776a6f13a4aSGreg Roach } elseif (!empty($attrs['y1'])) { 1777c21bdddcSGreg Roach $y1 = (float) $attrs['y1']; 1778a6f13a4aSGreg Roach } 1779a6f13a4aSGreg Roach } 1780a6f13a4aSGreg Roach // End horizontal position, maximum width (default) 1781c21bdddcSGreg Roach $x2 = ReportBaseElement::CURRENT_POSITION; 1782a6f13a4aSGreg Roach if (isset($attrs['x2'])) { 17837a6ee1acSGreg Roach if ($attrs['x2'] === '0') { 1784a6f13a4aSGreg Roach $x2 = 0; 17857a6ee1acSGreg Roach } elseif ($attrs['x2'] === '.') { 1786c21bdddcSGreg Roach $x2 = ReportBaseElement::CURRENT_POSITION; 1787a6f13a4aSGreg Roach } elseif (!empty($attrs['x2'])) { 1788c21bdddcSGreg Roach $x2 = (float) $attrs['x2']; 1789a6f13a4aSGreg Roach } 1790a6f13a4aSGreg Roach } 1791a6f13a4aSGreg Roach // End vertical position 1792c21bdddcSGreg Roach $y2 = ReportBaseElement::CURRENT_POSITION; 1793a6f13a4aSGreg Roach if (isset($attrs['y2'])) { 17947a6ee1acSGreg Roach if ($attrs['y2'] === '0') { 1795a6f13a4aSGreg Roach $y2 = 0; 17967a6ee1acSGreg Roach } elseif ($attrs['y2'] === '.') { 1797c21bdddcSGreg Roach $y2 = ReportBaseElement::CURRENT_POSITION; 1798a6f13a4aSGreg Roach } elseif (!empty($attrs['y2'])) { 1799c21bdddcSGreg Roach $y2 = (float) $attrs['y2']; 1800a6f13a4aSGreg Roach } 1801a6f13a4aSGreg Roach } 1802a6f13a4aSGreg Roach 1803e8e7866bSGreg Roach $line = $this->report_root->createLine($x1, $y1, $x2, $y2); 1804e8e7866bSGreg Roach $this->wt_report->addElement($line); 1805a6f13a4aSGreg Roach } 1806a6f13a4aSGreg Roach 1807a6f13a4aSGreg Roach /** 1808fab8f067SGreg Roach * Handle <list> 1809a6f13a4aSGreg Roach * 1810*09482a55SGreg Roach * @param array<string> $attrs 18118ba2e626SGreg Roach * 18128ba2e626SGreg Roach * @return void 1813a6f13a4aSGreg Roach */ 1814b702978eSGreg Roach protected function listStartHandler(array $attrs): void 1815c1010edaSGreg Roach { 1816a6f13a4aSGreg Roach $this->process_repeats++; 1817a6f13a4aSGreg Roach if ($this->process_repeats > 1) { 1818a6f13a4aSGreg Roach return; 1819a6f13a4aSGreg Roach } 1820a6f13a4aSGreg Roach 182113abd6f3SGreg Roach $match = []; 1822a6f13a4aSGreg Roach if (isset($attrs['sortby'])) { 1823a6f13a4aSGreg Roach $sortby = $attrs['sortby']; 1824a6f13a4aSGreg Roach if (preg_match("/\\$(\w+)/", $sortby, $match)) { 1825d1286247SGreg Roach $sortby = $this->vars[$match[1]]['id']; 1826a6f13a4aSGreg Roach $sortby = trim($sortby); 1827a6f13a4aSGreg Roach } 1828a6f13a4aSGreg Roach } else { 18297a6ee1acSGreg Roach $sortby = 'NAME'; 1830a6f13a4aSGreg Roach } 1831a6f13a4aSGreg Roach 1832e364afe4SGreg Roach $listname = $attrs['list'] ?? 'individual'; 1833195b5e75SGreg Roach 1834a6f13a4aSGreg Roach // Some filters/sorts can be applied using SQL, while others require PHP 1835a6f13a4aSGreg Roach switch ($listname) { 18367a6ee1acSGreg Roach case 'pending': 18376c0bef3aSGreg Roach $xrefs = DB::table('change') 1838195b5e75SGreg Roach ->whereIn('change_id', function (Builder $query): void { 1839a69f5655SGreg Roach $query->select(new Expression('MAX(change_id)')) 1840195b5e75SGreg Roach ->from('change') 1841195b5e75SGreg Roach ->where('gedcom_id', '=', $this->tree->id()) 1842195b5e75SGreg Roach ->where('status', '=', 'pending') 18437f5c2944SGreg Roach ->groupBy(['xref']); 1844195b5e75SGreg Roach }) 18456c0bef3aSGreg Roach ->pluck('xref'); 1846195b5e75SGreg Roach 184713abd6f3SGreg Roach $this->list = []; 18486c0bef3aSGreg Roach foreach ($xrefs as $xref) { 18496b9cb339SGreg Roach $this->list[] = Registry::gedcomRecordFactory()->make($xref, $this->tree); 1850a6f13a4aSGreg Roach } 1851a6f13a4aSGreg Roach break; 1852a6f13a4aSGreg Roach case 'individual': 18535985adfbSGreg Roach $query = DB::table('individuals') 18545985adfbSGreg Roach ->where('i_file', '=', $this->tree->id()) 18555985adfbSGreg Roach ->select(['i_id AS xref', 'i_gedcom AS gedcom']) 18565985adfbSGreg Roach ->distinct(); 18575985adfbSGreg Roach 1858a6f13a4aSGreg Roach foreach ($attrs as $attr => $value) { 1859dec352c1SGreg Roach if (str_starts_with($attr, 'filter') && $value !== '') { 186082759250SGreg Roach $value = $this->substituteVars($value, false); 1861a6f13a4aSGreg Roach // Convert the various filters into SQL 1862a6f13a4aSGreg Roach if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { 18630b5fd0a6SGreg Roach $query->join('dates AS ' . $attr, static function (JoinClause $join) use ($attr): void { 18645985adfbSGreg Roach $join 18655985adfbSGreg Roach ->on($attr . '.d_gid', '=', 'i_id') 18665985adfbSGreg Roach ->on($attr . '.d_file', '=', 'i_file'); 18675985adfbSGreg Roach }); 18685985adfbSGreg Roach 18695985adfbSGreg Roach $query->where($attr . '.d_fact', '=', $match[1]); 18705985adfbSGreg Roach 1871a6f13a4aSGreg Roach $date = new Date($match[3]); 18725985adfbSGreg Roach 1873044416d2SGreg Roach if ($match[2] === 'LTE') { 18745985adfbSGreg Roach $query->where($attr . '.d_julianday2', '<=', $date->maximumJulianDay()); 1875a6f13a4aSGreg Roach } else { 18765985adfbSGreg Roach $query->where($attr . '.d_julianday1', '>=', $date->minimumJulianDay()); 1877a6f13a4aSGreg Roach } 18785985adfbSGreg Roach 18795985adfbSGreg Roach // This filter has been fully processed 18805985adfbSGreg Roach unset($attrs[$attr]); 18817ee4bfadSGreg Roach } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) { 18820b5fd0a6SGreg Roach $query->join('name AS ' . $attr, static function (JoinClause $join) use ($attr): void { 18835985adfbSGreg Roach $join 18845985adfbSGreg Roach ->on($attr . '.n_id', '=', 'i_id') 18855985adfbSGreg Roach ->on($attr . '.n_file', '=', 'i_file'); 18865985adfbSGreg Roach }); 1887a6f13a4aSGreg Roach // Search the DB only if there is any name supplied 18887a6ee1acSGreg Roach $names = explode(' ', $match[1]); 18895d0bc43dSGreg Roach foreach ($names as $n => $name) { 1890b5961194SGreg Roach $query->where($attr . '.n_full', 'LIKE', '%' . addcslashes($name, '\\%_') . '%'); 1891a6f13a4aSGreg Roach } 18925985adfbSGreg Roach 18935985adfbSGreg Roach // This filter has been fully processed 18945985adfbSGreg Roach unset($attrs[$attr]); 1895a1afa4f8SGreg Roach } elseif (preg_match('/^LIKE \/(.+)\/$/', $value, $match)) { 18965985adfbSGreg Roach // Convert newline escape sequences to actual new lines 1897a1afa4f8SGreg Roach $match[1] = str_replace('\n', "\n", $match[1]); 18985985adfbSGreg Roach 1899a1afa4f8SGreg Roach $query->where('i_gedcom', 'LIKE', $match[1]); 19005985adfbSGreg Roach 19015985adfbSGreg Roach // This filter has been fully processed 19025985adfbSGreg Roach unset($attrs[$attr]); 19032fac69aeSGreg Roach } elseif (preg_match('/^(?:\w*):PLAC CONTAINS (.+)$/', $value, $match)) { 19049dc7e9e3SGreg Roach // Don't unset this filter. This is just initial filtering for performance 19055985adfbSGreg Roach $query 19060b5fd0a6SGreg Roach ->join('placelinks AS ' . $attr . 'a', static function (JoinClause $join) use ($attr): void { 19075985adfbSGreg Roach $join 19087ee4bfadSGreg Roach ->on($attr . 'a.pl_file', '=', 'i_file') 19097ee4bfadSGreg Roach ->on($attr . 'a.pl_gid', '=', 'i_id'); 19105985adfbSGreg Roach }) 19110b5fd0a6SGreg Roach ->join('places AS ' . $attr . 'b', static function (JoinClause $join) use ($attr): void { 19127ee4bfadSGreg Roach $join 19137ee4bfadSGreg Roach ->on($attr . 'b.p_file', '=', $attr . 'a.pl_file') 19147ee4bfadSGreg Roach ->on($attr . 'b.p_id', '=', $attr . 'a.pl_p_id'); 19157ee4bfadSGreg Roach }) 19169b44b7f5SGreg Roach ->where($attr . 'b.p_place', 'LIKE', '%' . addcslashes($match[1], '\\%_') . '%'); 19176e5c0963SGreg Roach } elseif (preg_match('/^(\w*):(\w+) CONTAINS (.+)$/', $value, $match)) { 19185985adfbSGreg Roach // Don't unset this filter. This is just initial filtering for performance 19197ee4bfadSGreg Roach $match[3] = strtr($match[3], ['\\' => '\\\\', '%' => '\\%', '_' => '\\_', ' ' => '%']); 19206e5c0963SGreg Roach $like = "%\n1 " . $match[1] . "%\n2 " . $match[2] . '%' . $match[3] . '%'; 19216e5c0963SGreg Roach $query->where('i_gedcom', 'LIKE', $like); 192247a2bfb7SGreg Roach } elseif (preg_match('/^(\w+) CONTAINS (.*)$/', $value, $match)) { 19236e5c0963SGreg Roach // Don't unset this filter. This is just initial filtering for performance 19246e5c0963SGreg Roach $match[2] = strtr($match[2], ['\\' => '\\\\', '%' => '\\%', '_' => '\\_', ' ' => '%']); 1925e364afe4SGreg Roach $like = "%\n1 " . $match[1] . '%' . $match[2] . '%'; 19267ee4bfadSGreg Roach $query->where('i_gedcom', 'LIKE', $like); 1927a6f13a4aSGreg Roach } 1928a6f13a4aSGreg Roach } 1929a6f13a4aSGreg Roach } 1930a6f13a4aSGreg Roach 193113abd6f3SGreg Roach $this->list = []; 1932a6f13a4aSGreg Roach 19335985adfbSGreg Roach foreach ($query->get() as $row) { 19346b9cb339SGreg Roach $this->list[$row->xref] = Registry::individualFactory()->make($row->xref, $this->tree, $row->gedcom); 1935a6f13a4aSGreg Roach } 1936a6f13a4aSGreg Roach break; 1937a6f13a4aSGreg Roach 1938a6f13a4aSGreg Roach case 'family': 193930fc2b1eSGreg Roach $query = DB::table('families') 194030fc2b1eSGreg Roach ->where('f_file', '=', $this->tree->id()) 194130fc2b1eSGreg Roach ->select(['f_id AS xref', 'f_gedcom AS gedcom']) 194230fc2b1eSGreg Roach ->distinct(); 194330fc2b1eSGreg Roach 1944a6f13a4aSGreg Roach foreach ($attrs as $attr => $value) { 1945dec352c1SGreg Roach if (str_starts_with($attr, 'filter') && $value !== '') { 194682759250SGreg Roach $value = $this->substituteVars($value, false); 1947a6f13a4aSGreg Roach // Convert the various filters into SQL 1948a6f13a4aSGreg Roach if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { 19490b5fd0a6SGreg Roach $query->join('dates AS ' . $attr, static function (JoinClause $join) use ($attr): void { 195030fc2b1eSGreg Roach $join 195130fc2b1eSGreg Roach ->on($attr . '.d_gid', '=', 'f_id') 195230fc2b1eSGreg Roach ->on($attr . '.d_file', '=', 'f_file'); 195330fc2b1eSGreg Roach }); 195430fc2b1eSGreg Roach 195530fc2b1eSGreg Roach $query->where($attr . '.d_fact', '=', $match[1]); 195630fc2b1eSGreg Roach 1957a6f13a4aSGreg Roach $date = new Date($match[3]); 195830fc2b1eSGreg Roach 1959044416d2SGreg Roach if ($match[2] === 'LTE') { 196030fc2b1eSGreg Roach $query->where($attr . '.d_julianday2', '<=', $date->maximumJulianDay()); 1961a6f13a4aSGreg Roach } else { 196230fc2b1eSGreg Roach $query->where($attr . '.d_julianday1', '>=', $date->minimumJulianDay()); 1963a6f13a4aSGreg Roach } 196430fc2b1eSGreg Roach 196530fc2b1eSGreg Roach // This filter has been fully processed 196630fc2b1eSGreg Roach unset($attrs[$attr]); 1967a1afa4f8SGreg Roach } elseif (preg_match('/^LIKE \/(.+)\/$/', $value, $match)) { 196830fc2b1eSGreg Roach // Convert newline escape sequences to actual new lines 1969a1afa4f8SGreg Roach $match[1] = str_replace('\n', "\n", $match[1]); 197030fc2b1eSGreg Roach 1971a1afa4f8SGreg Roach $query->where('f_gedcom', 'LIKE', $match[1]); 197230fc2b1eSGreg Roach 197330fc2b1eSGreg Roach // This filter has been fully processed 197430fc2b1eSGreg Roach unset($attrs[$attr]); 197530fc2b1eSGreg Roach } elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) { 19763b3cfeeaSGreg Roach if ($sortby === 'NAME' || $match[1] !== '') { 19770b5fd0a6SGreg Roach $query->join('name AS ' . $attr, static function (JoinClause $join) use ($attr): void { 197830fc2b1eSGreg Roach $join 197930fc2b1eSGreg Roach ->on($attr . '.n_file', '=', 'f_file') 19803b3cfeeaSGreg Roach ->where(static function (Builder $query): void { 198130fc2b1eSGreg Roach $query 198230fc2b1eSGreg Roach ->whereColumn('n_id', '=', 'f_husb') 198330fc2b1eSGreg Roach ->orWhereColumn('n_id', '=', 'f_wife'); 198430fc2b1eSGreg Roach }); 198530fc2b1eSGreg Roach }); 19865d0bc43dSGreg Roach // Search the DB only if there is any name supplied 19877a6ee1acSGreg Roach if ($match[1] != '') { 19887a6ee1acSGreg Roach $names = explode(' ', $match[1]); 19895d0bc43dSGreg Roach foreach ($names as $n => $name) { 1990b5961194SGreg Roach $query->where($attr . '.n_full', 'LIKE', '%' . addcslashes($name, '\\%_') . '%'); 19915d0bc43dSGreg Roach } 19925d0bc43dSGreg Roach } 1993a6f13a4aSGreg Roach } 199430fc2b1eSGreg Roach 199530fc2b1eSGreg Roach // This filter has been fully processed 199630fc2b1eSGreg Roach unset($attrs[$attr]); 19976e5c0963SGreg Roach } elseif (preg_match('/^(?:\w*):PLAC CONTAINS (.+)$/', $value, $match)) { 19989dc7e9e3SGreg Roach // Don't unset this filter. This is just initial filtering for performance 199930fc2b1eSGreg Roach $query 20000b5fd0a6SGreg Roach ->join('placelinks AS ' . $attr . 'a', static function (JoinClause $join) use ($attr): void { 200130fc2b1eSGreg Roach $join 20026e5c0963SGreg Roach ->on($attr . 'a.pl_file', '=', 'f_file') 20036e5c0963SGreg Roach ->on($attr . 'a.pl_gid', '=', 'f_id'); 200430fc2b1eSGreg Roach }) 20050b5fd0a6SGreg Roach ->join('places AS ' . $attr . 'b', static function (JoinClause $join) use ($attr): void { 20066e5c0963SGreg Roach $join 20076e5c0963SGreg Roach ->on($attr . 'b.p_file', '=', $attr . 'a.pl_file') 20086e5c0963SGreg Roach ->on($attr . 'b.p_id', '=', $attr . 'a.pl_p_id'); 20096e5c0963SGreg Roach }) 2010b5961194SGreg Roach ->where($attr . 'b.p_place', 'LIKE', '%' . addcslashes($match[1], '\\%_') . '%'); 20116e5c0963SGreg Roach } elseif (preg_match('/^(\w*):(\w+) CONTAINS (.+)$/', $value, $match)) { 201230fc2b1eSGreg Roach // Don't unset this filter. This is just initial filtering for performance 20136e5c0963SGreg Roach $match[3] = strtr($match[3], ['\\' => '\\\\', '%' => '\\%', '_' => '\\_', ' ' => '%']); 20146e5c0963SGreg Roach $like = "%\n1 " . $match[1] . "%\n2 " . $match[2] . '%' . $match[3] . '%'; 20156e5c0963SGreg Roach $query->where('f_gedcom', 'LIKE', $like); 20166e5c0963SGreg Roach } elseif (preg_match('/^(\w+) CONTAINS (.+)$/', $value, $match)) { 20176e5c0963SGreg Roach // Don't unset this filter. This is just initial filtering for performance 20186e5c0963SGreg Roach $match[2] = strtr($match[2], ['\\' => '\\\\', '%' => '\\%', '_' => '\\_', ' ' => '%']); 2019e364afe4SGreg Roach $like = "%\n1 " . $match[1] . '%' . $match[2] . '%'; 20206e5c0963SGreg Roach $query->where('f_gedcom', 'LIKE', $like); 2021a6f13a4aSGreg Roach } 2022a6f13a4aSGreg Roach } 2023a6f13a4aSGreg Roach } 2024a6f13a4aSGreg Roach 202513abd6f3SGreg Roach $this->list = []; 2026a6f13a4aSGreg Roach 202730fc2b1eSGreg Roach foreach ($query->get() as $row) { 20286b9cb339SGreg Roach $this->list[$row->xref] = Registry::familyFactory()->make($row->xref, $this->tree, $row->gedcom); 2029a6f13a4aSGreg Roach } 2030a6f13a4aSGreg Roach break; 2031a6f13a4aSGreg Roach 2032a6f13a4aSGreg Roach default: 20336ccdf4f0SGreg Roach throw new DomainException('Invalid list name: ' . $listname); 2034a6f13a4aSGreg Roach } 2035a6f13a4aSGreg Roach 203613abd6f3SGreg Roach $filters = []; 203713abd6f3SGreg Roach $filters2 = []; 2038a6f13a4aSGreg Roach if (isset($attrs['filter1']) && count($this->list) > 0) { 2039a6f13a4aSGreg Roach foreach ($attrs as $key => $value) { 2040a6f13a4aSGreg Roach if (preg_match("/filter(\d)/", $key)) { 2041a6f13a4aSGreg Roach $condition = $value; 2042a6f13a4aSGreg Roach if (preg_match("/@(\w+)/", $condition, $match)) { 2043a6f13a4aSGreg Roach $id = $match[1]; 2044a6f13a4aSGreg Roach $value = "''"; 2045044416d2SGreg Roach if ($id === 'ID') { 20467a6ee1acSGreg Roach if (preg_match('/0 @(.+)@/', $this->gedrec, $match)) { 2047a6f13a4aSGreg Roach $value = "'" . $match[1] . "'"; 2048a6f13a4aSGreg Roach } 2049044416d2SGreg Roach } elseif ($id === 'fact') { 2050a6f13a4aSGreg Roach $value = "'" . $this->fact . "'"; 2051044416d2SGreg Roach } elseif ($id === 'desc') { 2052a6f13a4aSGreg Roach $value = "'" . $this->desc . "'"; 2053a6f13a4aSGreg Roach } else { 2054a6f13a4aSGreg Roach if (preg_match("/\d $id (.+)/", $this->gedrec, $match)) { 20557a6ee1acSGreg Roach $value = "'" . str_replace('@', '', trim($match[1])) . "'"; 2056a6f13a4aSGreg Roach } 2057a6f13a4aSGreg Roach } 2058a6f13a4aSGreg Roach $condition = preg_replace("/@$id/", $value, $condition); 2059a6f13a4aSGreg Roach } 2060a6f13a4aSGreg Roach //-- handle regular expressions 2061a6f13a4aSGreg Roach if (preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match)) { 2062a6f13a4aSGreg Roach $tag = trim($match[1]); 2063a6f13a4aSGreg Roach $expr = trim($match[2]); 2064a6f13a4aSGreg Roach $val = trim($match[3]); 2065a6f13a4aSGreg Roach if (preg_match("/\\$(\w+)/", $val, $match)) { 2066d1286247SGreg Roach $val = $this->vars[$match[1]]['id']; 2067a6f13a4aSGreg Roach $val = trim($val); 2068a6f13a4aSGreg Roach } 2069a6f13a4aSGreg Roach if ($val) { 20707a6ee1acSGreg Roach $searchstr = ''; 20717a6ee1acSGreg Roach $tags = explode(':', $tag); 2072a6f13a4aSGreg Roach //-- only limit to a level number if we are specifically looking at a level 2073a6f13a4aSGreg Roach if (count($tags) > 1) { 2074a6f13a4aSGreg Roach $level = 1; 2075f71a7dedSGreg Roach $t = 'XXXX'; 2076a6f13a4aSGreg Roach foreach ($tags as $t) { 2077a6f13a4aSGreg Roach if (!empty($searchstr)) { 2078a6f13a4aSGreg Roach $searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n"; 2079a6f13a4aSGreg Roach } 2080a6f13a4aSGreg Roach //-- search for both EMAIL and _EMAIL... silly double gedcom standard 2081044416d2SGreg Roach if ($t === 'EMAIL' || $t === '_EMAIL') { 20827a6ee1acSGreg Roach $t = '_?EMAIL'; 2083a6f13a4aSGreg Roach } 20847a6ee1acSGreg Roach $searchstr .= $level . ' ' . $t; 2085a6f13a4aSGreg Roach $level++; 2086a6f13a4aSGreg Roach } 2087a6f13a4aSGreg Roach } else { 2088044416d2SGreg Roach if ($tag === 'EMAIL' || $tag === '_EMAIL') { 20897a6ee1acSGreg Roach $tag = '_?EMAIL'; 2090a6f13a4aSGreg Roach } 2091a6f13a4aSGreg Roach $t = $tag; 20927a6ee1acSGreg Roach $searchstr = '1 ' . $tag; 2093a6f13a4aSGreg Roach } 2094a6f13a4aSGreg Roach switch ($expr) { 20957a6ee1acSGreg Roach case 'CONTAINS': 2096044416d2SGreg Roach if ($t === 'PLAC') { 2097a6f13a4aSGreg Roach $searchstr .= "[^\n]*[, ]*" . $val; 2098a6f13a4aSGreg Roach } else { 2099a6f13a4aSGreg Roach $searchstr .= "[^\n]*" . $val; 2100a6f13a4aSGreg Roach } 2101a6f13a4aSGreg Roach $filters[] = $searchstr; 2102a6f13a4aSGreg Roach break; 2103a6f13a4aSGreg Roach default: 2104c1010edaSGreg Roach $filters2[] = [ 2105c1010edaSGreg Roach 'tag' => $tag, 2106c1010edaSGreg Roach 'expr' => $expr, 2107c1010edaSGreg Roach 'val' => $val, 2108c1010edaSGreg Roach ]; 2109a6f13a4aSGreg Roach break; 2110a6f13a4aSGreg Roach } 2111a6f13a4aSGreg Roach } 2112a6f13a4aSGreg Roach } 2113a6f13a4aSGreg Roach } 2114a6f13a4aSGreg Roach } 2115a6f13a4aSGreg Roach } 2116a6f13a4aSGreg Roach //-- apply other filters to the list that could not be added to the search string 2117a6f13a4aSGreg Roach if ($filters) { 2118a6f13a4aSGreg Roach foreach ($this->list as $key => $record) { 2119a6f13a4aSGreg Roach foreach ($filters as $filter) { 2120299d100dSGreg Roach if (!preg_match('/' . $filter . '/i', $record->privatizeGedcom(Auth::accessLevel($this->tree)))) { 2121a6f13a4aSGreg Roach unset($this->list[$key]); 2122a6f13a4aSGreg Roach break; 2123a6f13a4aSGreg Roach } 2124a6f13a4aSGreg Roach } 2125a6f13a4aSGreg Roach } 2126a6f13a4aSGreg Roach } 2127a6f13a4aSGreg Roach if ($filters2) { 212813abd6f3SGreg Roach $mylist = []; 2129a6f13a4aSGreg Roach foreach ($this->list as $indi) { 2130c0935879SGreg Roach $key = $indi->xref(); 2131299d100dSGreg Roach $grec = $indi->privatizeGedcom(Auth::accessLevel($this->tree)); 2132a6f13a4aSGreg Roach $keep = true; 2133a6f13a4aSGreg Roach foreach ($filters2 as $filter) { 2134a6f13a4aSGreg Roach if ($keep) { 2135a6f13a4aSGreg Roach $tag = $filter['tag']; 2136a6f13a4aSGreg Roach $expr = $filter['expr']; 2137a6f13a4aSGreg Roach $val = $filter['val']; 2138b2448a1bSGreg Roach if ($val === "''") { 21397a6ee1acSGreg Roach $val = ''; 2140a6f13a4aSGreg Roach } 21417a6ee1acSGreg Roach $tags = explode(':', $tag); 2142a6f13a4aSGreg Roach $t = end($tags); 21433d7a8a4cSGreg Roach $v = $this->getGedcomValue($tag, 1, $grec); 2144a6f13a4aSGreg Roach //-- check for EMAIL and _EMAIL (silly double gedcom standard :P) 2145044416d2SGreg Roach if ($t === 'EMAIL' && empty($v)) { 21467a6ee1acSGreg Roach $tag = str_replace('EMAIL', '_EMAIL', $tag); 21477a6ee1acSGreg Roach $tags = explode(':', $tag); 2148a6f13a4aSGreg Roach $t = end($tags); 21493d7a8a4cSGreg Roach $v = Functions::getSubRecord(1, $tag, $grec); 2150a6f13a4aSGreg Roach } 2151a6f13a4aSGreg Roach 2152a6f13a4aSGreg Roach switch ($expr) { 21537a6ee1acSGreg Roach case 'GTE': 2154044416d2SGreg Roach if ($t === 'DATE') { 2155a6f13a4aSGreg Roach $date1 = new Date($v); 2156a6f13a4aSGreg Roach $date2 = new Date($val); 2157a6f13a4aSGreg Roach $keep = (Date::compare($date1, $date2) >= 0); 2158a6f13a4aSGreg Roach } elseif ($val >= $v) { 2159a6f13a4aSGreg Roach $keep = true; 2160a6f13a4aSGreg Roach } 2161a6f13a4aSGreg Roach break; 21627a6ee1acSGreg Roach case 'LTE': 2163044416d2SGreg Roach if ($t === 'DATE') { 2164a6f13a4aSGreg Roach $date1 = new Date($v); 2165a6f13a4aSGreg Roach $date2 = new Date($val); 2166a6f13a4aSGreg Roach $keep = (Date::compare($date1, $date2) <= 0); 2167a6f13a4aSGreg Roach } elseif ($val >= $v) { 2168a6f13a4aSGreg Roach $keep = true; 2169a6f13a4aSGreg Roach } 2170a6f13a4aSGreg Roach break; 2171a6f13a4aSGreg Roach default: 2172a6f13a4aSGreg Roach if ($v == $val) { 2173a6f13a4aSGreg Roach $keep = true; 2174a6f13a4aSGreg Roach } else { 2175a6f13a4aSGreg Roach $keep = false; 2176a6f13a4aSGreg Roach } 2177a6f13a4aSGreg Roach break; 2178a6f13a4aSGreg Roach } 2179a6f13a4aSGreg Roach } 2180a6f13a4aSGreg Roach } 2181a6f13a4aSGreg Roach if ($keep) { 2182a6f13a4aSGreg Roach $mylist[$key] = $indi; 2183a6f13a4aSGreg Roach } 2184a6f13a4aSGreg Roach } 2185a6f13a4aSGreg Roach $this->list = $mylist; 2186a6f13a4aSGreg Roach } 2187a6f13a4aSGreg Roach 2188a6f13a4aSGreg Roach switch ($sortby) { 2189a6f13a4aSGreg Roach case 'NAME': 2190c156e8f5SGreg Roach uasort($this->list, GedcomRecord::nameComparator()); 2191a6f13a4aSGreg Roach break; 2192a6f13a4aSGreg Roach case 'CHAN': 2193c156e8f5SGreg Roach uasort($this->list, GedcomRecord::lastChangeComparator()); 2194a6f13a4aSGreg Roach break; 2195a6f13a4aSGreg Roach case 'BIRT:DATE': 2196c156e8f5SGreg Roach uasort($this->list, Individual::birthDateComparator()); 2197a6f13a4aSGreg Roach break; 2198a6f13a4aSGreg Roach case 'DEAT:DATE': 2199c156e8f5SGreg Roach uasort($this->list, Individual::deathDateComparator()); 2200a6f13a4aSGreg Roach break; 2201a6f13a4aSGreg Roach case 'MARR:DATE': 2202c156e8f5SGreg Roach uasort($this->list, Family::marriageDateComparator()); 2203a6f13a4aSGreg Roach break; 2204a6f13a4aSGreg Roach default: 2205a6f13a4aSGreg Roach // unsorted or already sorted by SQL 2206a6f13a4aSGreg Roach break; 2207a6f13a4aSGreg Roach } 2208a6f13a4aSGreg Roach 22099b3dd960SGreg Roach $this->repeats_stack[] = [$this->repeats, $this->repeat_bytes]; 2210e8e7866bSGreg Roach $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1; 2211a6f13a4aSGreg Roach } 2212a6f13a4aSGreg Roach 2213a6f13a4aSGreg Roach /** 2214fab8f067SGreg Roach * Handle </list> 22158ba2e626SGreg Roach * 22168ba2e626SGreg Roach * @return void 2217a6f13a4aSGreg Roach */ 2218b702978eSGreg Roach protected function listEndHandler(): void 2219c1010edaSGreg Roach { 2220a6f13a4aSGreg Roach $this->process_repeats--; 2221a6f13a4aSGreg Roach if ($this->process_repeats > 0) { 2222a6f13a4aSGreg Roach return; 2223a6f13a4aSGreg Roach } 2224a6f13a4aSGreg Roach 2225a6f13a4aSGreg Roach // Check if there is any list 2226a6f13a4aSGreg Roach if (count($this->list) > 0) { 2227a6f13a4aSGreg Roach $lineoffset = 0; 2228a6f13a4aSGreg Roach foreach ($this->repeats_stack as $rep) { 2229a6f13a4aSGreg Roach $lineoffset += $rep[1]; 2230a6f13a4aSGreg Roach } 2231a6f13a4aSGreg Roach //-- read the xml from the file 2232299d100dSGreg Roach $lines = file($this->report); 2233dec352c1SGreg Roach while ((!str_contains($lines[$lineoffset + $this->repeat_bytes], '<List')) && (($lineoffset + $this->repeat_bytes) > 0)) { 2234a6f13a4aSGreg Roach $lineoffset--; 2235a6f13a4aSGreg Roach } 2236a6f13a4aSGreg Roach $lineoffset++; 2237a6f13a4aSGreg Roach $reportxml = "<tempdoc>\n"; 2238a6f13a4aSGreg Roach $line_nr = $lineoffset + $this->repeat_bytes; 2239a6f13a4aSGreg Roach // List Level counter 2240a6f13a4aSGreg Roach $count = 1; 2241a6f13a4aSGreg Roach while (0 < $count) { 2242dec352c1SGreg Roach if (str_contains($lines[$line_nr], '<List')) { 2243a6f13a4aSGreg Roach $count++; 2244dec352c1SGreg Roach } elseif (str_contains($lines[$line_nr], '</List')) { 2245a6f13a4aSGreg Roach $count--; 2246a6f13a4aSGreg Roach } 2247a6f13a4aSGreg Roach if (0 < $count) { 2248a6f13a4aSGreg Roach $reportxml .= $lines[$line_nr]; 2249a6f13a4aSGreg Roach } 2250a6f13a4aSGreg Roach $line_nr++; 2251a6f13a4aSGreg Roach } 2252a6f13a4aSGreg Roach // No need to drag this 2253a6f13a4aSGreg Roach unset($lines); 22547a6ee1acSGreg Roach $reportxml .= '</tempdoc>'; 2255a6f13a4aSGreg Roach // Save original values 22569b3dd960SGreg Roach $this->parser_stack[] = $this->parser; 2257a6f13a4aSGreg Roach $oldgedrec = $this->gedrec; 2258a6f13a4aSGreg Roach 2259a6f13a4aSGreg Roach $this->list_total = count($this->list); 2260a6f13a4aSGreg Roach $this->list_private = 0; 2261a6f13a4aSGreg Roach foreach ($this->list as $record) { 2262a6f13a4aSGreg Roach if ($record->canShow()) { 2263f4afa648SGreg Roach $this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->tree())); 2264a6f13a4aSGreg Roach //-- start the sax parser 2265a6f13a4aSGreg Roach $repeat_parser = xml_parser_create(); 2266e8e7866bSGreg Roach $this->parser = $repeat_parser; 2267a6f13a4aSGreg Roach xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 22681aa04befSGreg Roach 22691aa04befSGreg Roach xml_set_element_handler( 22701aa04befSGreg Roach $repeat_parser, 22719d454b6bSGreg Roach function ($parser, string $name, array $attrs): void { 22721aa04befSGreg Roach $this->startElement($parser, $name, $attrs); 22731aa04befSGreg Roach }, 22749d454b6bSGreg Roach function ($parser, string $name): void { 22751aa04befSGreg Roach $this->endElement($parser, $name); 22761aa04befSGreg Roach } 22771aa04befSGreg Roach ); 22781aa04befSGreg Roach 22791aa04befSGreg Roach xml_set_character_data_handler( 22801aa04befSGreg Roach $repeat_parser, 22819d454b6bSGreg Roach function ($parser, string $data): void { 22821aa04befSGreg Roach $this->characterData($parser, $data); 22831aa04befSGreg Roach } 22841aa04befSGreg Roach ); 22851aa04befSGreg Roach 2286a6f13a4aSGreg Roach if (!xml_parse($repeat_parser, $reportxml, true)) { 22876ccdf4f0SGreg Roach throw new DomainException(sprintf( 2288a6f13a4aSGreg Roach 'ListEHandler XML error: %s at line %d', 2289a6f13a4aSGreg Roach xml_error_string(xml_get_error_code($repeat_parser)), 2290a6f13a4aSGreg Roach xml_get_current_line_number($repeat_parser) 2291a6f13a4aSGreg Roach )); 2292a6f13a4aSGreg Roach } 2293a6f13a4aSGreg Roach xml_parser_free($repeat_parser); 2294a6f13a4aSGreg Roach } else { 2295a6f13a4aSGreg Roach $this->list_private++; 2296a6f13a4aSGreg Roach } 2297a6f13a4aSGreg Roach } 229813abd6f3SGreg Roach $this->list = []; 2299e8e7866bSGreg Roach $this->parser = array_pop($this->parser_stack); 2300a6f13a4aSGreg Roach $this->gedrec = $oldgedrec; 2301a6f13a4aSGreg Roach } 230265e02381SGreg Roach [$this->repeats, $this->repeat_bytes] = array_pop($this->repeats_stack); 2303a6f13a4aSGreg Roach } 2304a6f13a4aSGreg Roach 2305a6f13a4aSGreg Roach /** 2306fab8f067SGreg Roach * Handle <listTotal> 2307a6f13a4aSGreg Roach * Prints the total number of records in a list 2308fab8f067SGreg Roach * The total number is collected from <list> and <relatives> 23098ba2e626SGreg Roach * 23108ba2e626SGreg Roach * @return void 2311a6f13a4aSGreg Roach */ 2312b702978eSGreg Roach protected function listTotalStartHandler(): void 2313c1010edaSGreg Roach { 2314a6f13a4aSGreg Roach if ($this->list_private == 0) { 2315589feda3SGreg Roach $this->current_element->addText((string) $this->list_total); 2316a6f13a4aSGreg Roach } else { 23177a6ee1acSGreg Roach $this->current_element->addText(($this->list_total - $this->list_private) . ' / ' . $this->list_total); 2318a6f13a4aSGreg Roach } 2319a6f13a4aSGreg Roach } 2320a6f13a4aSGreg Roach 2321a6f13a4aSGreg Roach /** 2322fab8f067SGreg Roach * Handle <relatives> 232376692c8bSGreg Roach * 2324*09482a55SGreg Roach * @param array<string> $attrs 23258ba2e626SGreg Roach * 23268ba2e626SGreg Roach * @return void 2327a6f13a4aSGreg Roach */ 2328b702978eSGreg Roach protected function relativesStartHandler(array $attrs): void 2329c1010edaSGreg Roach { 2330a6f13a4aSGreg Roach $this->process_repeats++; 2331a6f13a4aSGreg Roach if ($this->process_repeats > 1) { 2332a6f13a4aSGreg Roach return; 2333a6f13a4aSGreg Roach } 2334a6f13a4aSGreg Roach 2335e364afe4SGreg Roach $sortby = $attrs['sortby'] ?? 'NAME'; 2336e364afe4SGreg Roach 233713abd6f3SGreg Roach $match = []; 2338a6f13a4aSGreg Roach if (preg_match("/\\$(\w+)/", $sortby, $match)) { 2339d1286247SGreg Roach $sortby = $this->vars[$match[1]]['id']; 2340a6f13a4aSGreg Roach $sortby = trim($sortby); 2341a6f13a4aSGreg Roach } 2342a6f13a4aSGreg Roach 2343a6f13a4aSGreg Roach $maxgen = -1; 2344a6f13a4aSGreg Roach if (isset($attrs['maxgen'])) { 2345c0624077SGreg Roach $maxgen = (int) $attrs['maxgen']; 2346a6f13a4aSGreg Roach } 2347a6f13a4aSGreg Roach 2348e364afe4SGreg Roach $group = $attrs['group'] ?? 'child-family'; 2349e364afe4SGreg Roach 2350a6f13a4aSGreg Roach if (preg_match("/\\$(\w+)/", $group, $match)) { 2351d1286247SGreg Roach $group = $this->vars[$match[1]]['id']; 2352a6f13a4aSGreg Roach $group = trim($group); 2353a6f13a4aSGreg Roach } 2354a6f13a4aSGreg Roach 2355e364afe4SGreg Roach $id = $attrs['id'] ?? ''; 2356e364afe4SGreg Roach 2357a6f13a4aSGreg Roach if (preg_match("/\\$(\w+)/", $id, $match)) { 2358d1286247SGreg Roach $id = $this->vars[$match[1]]['id']; 2359a6f13a4aSGreg Roach $id = trim($id); 2360a6f13a4aSGreg Roach } 2361a6f13a4aSGreg Roach 236213abd6f3SGreg Roach $this->list = []; 23636b9cb339SGreg Roach $person = Registry::individualFactory()->make($id, $this->tree); 2364d965cc1aSGreg Roach if ($person instanceof Individual) { 2365a6f13a4aSGreg Roach $this->list[$id] = $person; 2366a6f13a4aSGreg Roach switch ($group) { 23677a6ee1acSGreg Roach case 'child-family': 236839ca88baSGreg Roach foreach ($person->childFamilies() as $family) { 2369820b62dfSGreg Roach foreach ($family->spouses() as $spouse) { 2370820b62dfSGreg Roach $this->list[$spouse->xref()] = $spouse; 2371a6f13a4aSGreg Roach } 2372820b62dfSGreg Roach 2373820b62dfSGreg Roach foreach ($family->children() as $child) { 2374c0935879SGreg Roach $this->list[$child->xref()] = $child; 2375a6f13a4aSGreg Roach } 2376a6f13a4aSGreg Roach } 2377a6f13a4aSGreg Roach break; 23787a6ee1acSGreg Roach case 'spouse-family': 237939ca88baSGreg Roach foreach ($person->spouseFamilies() as $family) { 2380820b62dfSGreg Roach foreach ($family->spouses() as $spouse) { 2381820b62dfSGreg Roach $this->list[$spouse->xref()] = $spouse; 2382a6f13a4aSGreg Roach } 2383820b62dfSGreg Roach 2384820b62dfSGreg Roach foreach ($family->children() as $child) { 2385c0935879SGreg Roach $this->list[$child->xref()] = $child; 2386a6f13a4aSGreg Roach } 2387a6f13a4aSGreg Roach } 2388a6f13a4aSGreg Roach break; 23897a6ee1acSGreg Roach case 'direct-ancestors': 23903d7a8a4cSGreg Roach $this->addAncestors($this->list, $id, false, $maxgen); 2391a6f13a4aSGreg Roach break; 23927a6ee1acSGreg Roach case 'ancestors': 23933d7a8a4cSGreg Roach $this->addAncestors($this->list, $id, true, $maxgen); 2394a6f13a4aSGreg Roach break; 23957a6ee1acSGreg Roach case 'descendants': 2396a6f13a4aSGreg Roach $this->list[$id]->generation = 1; 23973d7a8a4cSGreg Roach $this->addDescendancy($this->list, $id, false, $maxgen); 2398a6f13a4aSGreg Roach break; 23997a6ee1acSGreg Roach case 'all': 24003d7a8a4cSGreg Roach $this->addAncestors($this->list, $id, true, $maxgen); 24013d7a8a4cSGreg Roach $this->addDescendancy($this->list, $id, true, $maxgen); 2402a6f13a4aSGreg Roach break; 2403a6f13a4aSGreg Roach } 2404a6f13a4aSGreg Roach } 2405a6f13a4aSGreg Roach 2406a6f13a4aSGreg Roach switch ($sortby) { 2407a6f13a4aSGreg Roach case 'NAME': 2408c156e8f5SGreg Roach uasort($this->list, GedcomRecord::nameComparator()); 2409a6f13a4aSGreg Roach break; 2410a6f13a4aSGreg Roach case 'BIRT:DATE': 2411c156e8f5SGreg Roach uasort($this->list, Individual::birthDateComparator()); 2412a6f13a4aSGreg Roach break; 2413a6f13a4aSGreg Roach case 'DEAT:DATE': 2414c156e8f5SGreg Roach uasort($this->list, Individual::deathDateComparator()); 2415a6f13a4aSGreg Roach break; 2416a6f13a4aSGreg Roach case 'generation': 241713abd6f3SGreg Roach $newarray = []; 2418a6f13a4aSGreg Roach reset($this->list); 2419a6f13a4aSGreg Roach $genCounter = 1; 2420a6f13a4aSGreg Roach while (count($newarray) < count($this->list)) { 2421a6f13a4aSGreg Roach foreach ($this->list as $key => $value) { 2422a6f13a4aSGreg Roach $this->generation = $value->generation; 2423a6f13a4aSGreg Roach if ($this->generation == $genCounter) { 242479529c87SGreg Roach $newarray[$key] = new stdClass(); 2425a6f13a4aSGreg Roach $newarray[$key]->generation = $this->generation; 2426a6f13a4aSGreg Roach } 2427a6f13a4aSGreg Roach } 2428a6f13a4aSGreg Roach $genCounter++; 2429a6f13a4aSGreg Roach } 2430a6f13a4aSGreg Roach $this->list = $newarray; 2431a6f13a4aSGreg Roach break; 2432a6f13a4aSGreg Roach default: 2433a6f13a4aSGreg Roach // unsorted 2434a6f13a4aSGreg Roach break; 2435a6f13a4aSGreg Roach } 24369b3dd960SGreg Roach $this->repeats_stack[] = [$this->repeats, $this->repeat_bytes]; 2437e8e7866bSGreg Roach $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1; 2438a6f13a4aSGreg Roach } 2439a6f13a4aSGreg Roach 2440a6f13a4aSGreg Roach /** 2441fab8f067SGreg Roach * Handle </relatives> 24428ba2e626SGreg Roach * 24438ba2e626SGreg Roach * @return void 2444a6f13a4aSGreg Roach */ 2445b702978eSGreg Roach protected function relativesEndHandler(): void 2446c1010edaSGreg Roach { 2447a6f13a4aSGreg Roach $this->process_repeats--; 2448a6f13a4aSGreg Roach if ($this->process_repeats > 0) { 2449a6f13a4aSGreg Roach return; 2450a6f13a4aSGreg Roach } 2451a6f13a4aSGreg Roach 2452a6f13a4aSGreg Roach // Check if there is any relatives 2453a6f13a4aSGreg Roach if (count($this->list) > 0) { 2454a6f13a4aSGreg Roach $lineoffset = 0; 2455a6f13a4aSGreg Roach foreach ($this->repeats_stack as $rep) { 2456a6f13a4aSGreg Roach $lineoffset += $rep[1]; 2457a6f13a4aSGreg Roach } 2458a6f13a4aSGreg Roach //-- read the xml from the file 2459299d100dSGreg Roach $lines = file($this->report); 2460dec352c1SGreg Roach while (!str_contains($lines[$lineoffset + $this->repeat_bytes], '<Relatives') && $lineoffset + $this->repeat_bytes > 0) { 2461a6f13a4aSGreg Roach $lineoffset--; 2462a6f13a4aSGreg Roach } 2463a6f13a4aSGreg Roach $lineoffset++; 2464a6f13a4aSGreg Roach $reportxml = "<tempdoc>\n"; 2465a6f13a4aSGreg Roach $line_nr = $lineoffset + $this->repeat_bytes; 2466a6f13a4aSGreg Roach // Relatives Level counter 2467a6f13a4aSGreg Roach $count = 1; 2468a6f13a4aSGreg Roach while (0 < $count) { 2469dec352c1SGreg Roach if (str_contains($lines[$line_nr], '<Relatives')) { 2470a6f13a4aSGreg Roach $count++; 2471dec352c1SGreg Roach } elseif (str_contains($lines[$line_nr], '</Relatives')) { 2472a6f13a4aSGreg Roach $count--; 2473a6f13a4aSGreg Roach } 2474a6f13a4aSGreg Roach if (0 < $count) { 2475a6f13a4aSGreg Roach $reportxml .= $lines[$line_nr]; 2476a6f13a4aSGreg Roach } 2477a6f13a4aSGreg Roach $line_nr++; 2478a6f13a4aSGreg Roach } 2479a6f13a4aSGreg Roach // No need to drag this 2480a6f13a4aSGreg Roach unset($lines); 2481a6f13a4aSGreg Roach $reportxml .= "</tempdoc>\n"; 2482a6f13a4aSGreg Roach // Save original values 24839b3dd960SGreg Roach $this->parser_stack[] = $this->parser; 2484a6f13a4aSGreg Roach $oldgedrec = $this->gedrec; 2485a6f13a4aSGreg Roach 2486a6f13a4aSGreg Roach $this->list_total = count($this->list); 2487a6f13a4aSGreg Roach $this->list_private = 0; 2488b092a991SGreg Roach foreach ($this->list as $xref => $value) { 2489a6f13a4aSGreg Roach if (isset($value->generation)) { 2490a6f13a4aSGreg Roach $this->generation = $value->generation; 2491a6f13a4aSGreg Roach } 24926b9cb339SGreg Roach $tmp = Registry::gedcomRecordFactory()->make((string) $xref, $this->tree); 2493299d100dSGreg Roach $this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($this->tree)); 2494a6f13a4aSGreg Roach 2495a6f13a4aSGreg Roach $repeat_parser = xml_parser_create(); 2496e8e7866bSGreg Roach $this->parser = $repeat_parser; 2497a6f13a4aSGreg Roach xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false); 24981aa04befSGreg Roach 24991aa04befSGreg Roach xml_set_element_handler( 25001aa04befSGreg Roach $repeat_parser, 25019d454b6bSGreg Roach function ($parser, string $name, array $attrs): void { 25021aa04befSGreg Roach $this->startElement($parser, $name, $attrs); 25031aa04befSGreg Roach }, 25049d454b6bSGreg Roach function ($parser, string $name): void { 25051aa04befSGreg Roach $this->endElement($parser, $name); 25061aa04befSGreg Roach } 25071aa04befSGreg Roach ); 25081aa04befSGreg Roach 25091aa04befSGreg Roach xml_set_character_data_handler( 25101aa04befSGreg Roach $repeat_parser, 25119d454b6bSGreg Roach function ($parser, string $data): void { 25121aa04befSGreg Roach $this->characterData($parser, $data); 25131aa04befSGreg Roach } 25141aa04befSGreg Roach ); 2515a6f13a4aSGreg Roach 2516a6f13a4aSGreg Roach if (!xml_parse($repeat_parser, $reportxml, true)) { 25176ccdf4f0SGreg Roach 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))); 2518a6f13a4aSGreg Roach } 2519a6f13a4aSGreg Roach xml_parser_free($repeat_parser); 2520a6f13a4aSGreg Roach } 2521a6f13a4aSGreg Roach // Clean up the list array 252213abd6f3SGreg Roach $this->list = []; 2523e8e7866bSGreg Roach $this->parser = array_pop($this->parser_stack); 2524a6f13a4aSGreg Roach $this->gedrec = $oldgedrec; 2525a6f13a4aSGreg Roach } 252665e02381SGreg Roach [$this->repeats, $this->repeat_bytes] = array_pop($this->repeats_stack); 2527a6f13a4aSGreg Roach } 2528a6f13a4aSGreg Roach 2529a6f13a4aSGreg Roach /** 2530fab8f067SGreg Roach * Handle <generation /> 2531a6f13a4aSGreg Roach * Prints the number of generations 25328ba2e626SGreg Roach * 25338ba2e626SGreg Roach * @return void 2534a6f13a4aSGreg Roach */ 2535b702978eSGreg Roach protected function generationStartHandler(): void 2536c1010edaSGreg Roach { 2537589feda3SGreg Roach $this->current_element->addText((string) $this->generation); 2538a6f13a4aSGreg Roach } 2539a6f13a4aSGreg Roach 2540a6f13a4aSGreg Roach /** 2541fab8f067SGreg Roach * Handle <newPage /> 2542a393a2a1SGreg Roach * Has to be placed in an element (header, body or footer) 25438ba2e626SGreg Roach * 25448ba2e626SGreg Roach * @return void 2545a6f13a4aSGreg Roach */ 2546b702978eSGreg Roach protected function newPageStartHandler(): void 2547c1010edaSGreg Roach { 25487a6ee1acSGreg Roach $temp = 'addpage'; 2549e8e7866bSGreg Roach $this->wt_report->addElement($temp); 2550a6f13a4aSGreg Roach } 2551a6f13a4aSGreg Roach 2552a6f13a4aSGreg Roach /** 2553fab8f067SGreg Roach * Handle </title> 25548ba2e626SGreg Roach * 25558ba2e626SGreg Roach * @return void 2556a6f13a4aSGreg Roach */ 2557b702978eSGreg Roach protected function titleEndHandler(): void 2558c1010edaSGreg Roach { 25592836aa05SGreg Roach $this->report_root->addTitle($this->text); 2560a6f13a4aSGreg Roach } 2561a6f13a4aSGreg Roach 2562a6f13a4aSGreg Roach /** 2563fab8f067SGreg Roach * Handle </description> 25648ba2e626SGreg Roach * 25658ba2e626SGreg Roach * @return void 2566a6f13a4aSGreg Roach */ 2567b702978eSGreg Roach protected function descriptionEndHandler(): void 2568c1010edaSGreg Roach { 25692836aa05SGreg Roach $this->report_root->addDescription($this->text); 2570a6f13a4aSGreg Roach } 2571729ce104SGreg Roach 2572729ce104SGreg Roach /** 257376692c8bSGreg Roach * Create a list of all descendants. 257476692c8bSGreg Roach * 2575*09482a55SGreg Roach * @param array<string> $list 2576729ce104SGreg Roach * @param string $pid 2577729ce104SGreg Roach * @param bool $parents 2578729ce104SGreg Roach * @param int $generations 25798ba2e626SGreg Roach * 25808ba2e626SGreg Roach * @return void 2581729ce104SGreg Roach */ 25823b3cfeeaSGreg Roach private function addDescendancy(&$list, $pid, $parents = false, $generations = -1): void 2583c1010edaSGreg Roach { 25846b9cb339SGreg Roach $person = Registry::individualFactory()->make($pid, $this->tree); 2585729ce104SGreg Roach if ($person === null) { 2586729ce104SGreg Roach return; 2587729ce104SGreg Roach } 2588729ce104SGreg Roach if (!isset($list[$pid])) { 2589729ce104SGreg Roach $list[$pid] = $person; 2590729ce104SGreg Roach } 2591729ce104SGreg Roach if (!isset($list[$pid]->generation)) { 2592729ce104SGreg Roach $list[$pid]->generation = 0; 2593729ce104SGreg Roach } 259439ca88baSGreg Roach foreach ($person->spouseFamilies() as $family) { 2595729ce104SGreg Roach if ($parents) { 259639ca88baSGreg Roach $husband = $family->husband(); 259739ca88baSGreg Roach $wife = $family->wife(); 2598729ce104SGreg Roach if ($husband) { 2599c0935879SGreg Roach $list[$husband->xref()] = $husband; 2600729ce104SGreg Roach if (isset($list[$pid]->generation)) { 2601c0935879SGreg Roach $list[$husband->xref()]->generation = $list[$pid]->generation - 1; 2602729ce104SGreg Roach } else { 2603c0935879SGreg Roach $list[$husband->xref()]->generation = 1; 2604729ce104SGreg Roach } 2605729ce104SGreg Roach } 2606729ce104SGreg Roach if ($wife) { 2607c0935879SGreg Roach $list[$wife->xref()] = $wife; 2608729ce104SGreg Roach if (isset($list[$pid]->generation)) { 2609c0935879SGreg Roach $list[$wife->xref()]->generation = $list[$pid]->generation - 1; 2610729ce104SGreg Roach } else { 2611c0935879SGreg Roach $list[$wife->xref()]->generation = 1; 2612729ce104SGreg Roach } 2613729ce104SGreg Roach } 2614729ce104SGreg Roach } 2615820b62dfSGreg Roach 261639ca88baSGreg Roach $children = $family->children(); 2617820b62dfSGreg Roach 2618729ce104SGreg Roach foreach ($children as $child) { 2619729ce104SGreg Roach if ($child) { 2620c0935879SGreg Roach $list[$child->xref()] = $child; 2621820b62dfSGreg Roach 2622729ce104SGreg Roach if (isset($list[$pid]->generation)) { 2623c0935879SGreg Roach $list[$child->xref()]->generation = $list[$pid]->generation + 1; 2624729ce104SGreg Roach } else { 2625c0935879SGreg Roach $list[$child->xref()]->generation = 2; 2626729ce104SGreg Roach } 2627729ce104SGreg Roach } 2628729ce104SGreg Roach } 2629729ce104SGreg Roach if ($generations == -1 || $list[$pid]->generation + 1 < $generations) { 2630729ce104SGreg Roach foreach ($children as $child) { 2631c0935879SGreg Roach $this->addDescendancy($list, $child->xref(), $parents, $generations); // recurse on the childs family 2632729ce104SGreg Roach } 2633729ce104SGreg Roach } 2634729ce104SGreg Roach } 2635729ce104SGreg Roach } 2636729ce104SGreg Roach 2637729ce104SGreg Roach /** 263876692c8bSGreg Roach * Create a list of all ancestors. 263976692c8bSGreg Roach * 2640870ec5a3SGreg Roach * @param array<stdClass> $list 2641729ce104SGreg Roach * @param string $pid 2642729ce104SGreg Roach * @param bool $children 2643729ce104SGreg Roach * @param int $generations 26448ba2e626SGreg Roach * 26458ba2e626SGreg Roach * @return void 2646729ce104SGreg Roach */ 26473b3cfeeaSGreg Roach private function addAncestors(array &$list, string $pid, bool $children = false, int $generations = -1): void 2648c1010edaSGreg Roach { 264913abd6f3SGreg Roach $genlist = [$pid]; 2650729ce104SGreg Roach $list[$pid]->generation = 1; 2651729ce104SGreg Roach while (count($genlist) > 0) { 2652729ce104SGreg Roach $id = array_shift($genlist); 2653dec352c1SGreg Roach if (str_starts_with($id, 'empty')) { 2654729ce104SGreg Roach continue; // id can be something like “empty7” 2655729ce104SGreg Roach } 26566b9cb339SGreg Roach $person = Registry::individualFactory()->make($id, $this->tree); 265739ca88baSGreg Roach foreach ($person->childFamilies() as $family) { 265839ca88baSGreg Roach $husband = $family->husband(); 265939ca88baSGreg Roach $wife = $family->wife(); 2660729ce104SGreg Roach if ($husband) { 2661c0935879SGreg Roach $list[$husband->xref()] = $husband; 2662c0935879SGreg Roach $list[$husband->xref()]->generation = $list[$id]->generation + 1; 2663729ce104SGreg Roach } 2664729ce104SGreg Roach if ($wife) { 2665c0935879SGreg Roach $list[$wife->xref()] = $wife; 2666c0935879SGreg Roach $list[$wife->xref()]->generation = $list[$id]->generation + 1; 2667729ce104SGreg Roach } 2668729ce104SGreg Roach if ($generations == -1 || $list[$id]->generation + 1 < $generations) { 2669729ce104SGreg Roach if ($husband) { 2670c0935879SGreg Roach $genlist[] = $husband->xref(); 2671729ce104SGreg Roach } 2672729ce104SGreg Roach if ($wife) { 2673c0935879SGreg Roach $genlist[] = $wife->xref(); 2674729ce104SGreg Roach } 2675729ce104SGreg Roach } 2676729ce104SGreg Roach if ($children) { 267739ca88baSGreg Roach foreach ($family->children() as $child) { 2678c0935879SGreg Roach $list[$child->xref()] = $child; 2679e364afe4SGreg Roach $list[$child->xref()]->generation = $list[$id]->generation ?? 1; 2680729ce104SGreg Roach } 2681729ce104SGreg Roach } 2682729ce104SGreg Roach } 2683729ce104SGreg Roach } 2684729ce104SGreg Roach } 2685729ce104SGreg Roach 2686729ce104SGreg Roach /** 2687729ce104SGreg Roach * get gedcom tag value 2688729ce104SGreg Roach * 2689729ce104SGreg Roach * @param string $tag The tag to find, use : to delineate subtags 2690729ce104SGreg Roach * @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 2691729ce104SGreg Roach * @param string $gedrec The gedcom record to get the value from 2692729ce104SGreg Roach * 2693729ce104SGreg Roach * @return string the value of a gedcom tag from the given gedcom record 2694729ce104SGreg Roach */ 2695b2448a1bSGreg Roach private function getGedcomValue(string $tag, int $level, string $gedrec): string 2696c1010edaSGreg Roach { 2697b2448a1bSGreg Roach if ($gedrec === '') { 2698729ce104SGreg Roach return ''; 2699729ce104SGreg Roach } 2700729ce104SGreg Roach $tags = explode(':', $tag); 2701729ce104SGreg Roach $origlevel = $level; 2702b2448a1bSGreg Roach if ($level === 0) { 27033c12f3e5SGreg Roach $level = $gedrec[0] + 1; 2704729ce104SGreg Roach } 2705729ce104SGreg Roach 2706729ce104SGreg Roach $subrec = $gedrec; 2707f71a7dedSGreg Roach $t = 'XXXX'; 2708729ce104SGreg Roach foreach ($tags as $t) { 2709729ce104SGreg Roach $lastsubrec = $subrec; 27103d7a8a4cSGreg Roach $subrec = Functions::getSubRecord($level, "$level $t", $subrec); 2711729ce104SGreg Roach if (empty($subrec) && $origlevel == 0) { 2712729ce104SGreg Roach $level--; 27133d7a8a4cSGreg Roach $subrec = Functions::getSubRecord($level, "$level $t", $lastsubrec); 2714729ce104SGreg Roach } 2715729ce104SGreg Roach if (empty($subrec)) { 2716044416d2SGreg Roach if ($t === 'TITL') { 27173d7a8a4cSGreg Roach $subrec = Functions::getSubRecord($level, "$level ABBR", $lastsubrec); 2718729ce104SGreg Roach if (!empty($subrec)) { 27197a6ee1acSGreg Roach $t = 'ABBR'; 2720729ce104SGreg Roach } 2721729ce104SGreg Roach } 2722b2448a1bSGreg Roach if ($subrec === '') { 2723729ce104SGreg Roach if ($level > 0) { 2724729ce104SGreg Roach $level--; 2725729ce104SGreg Roach } 27263d7a8a4cSGreg Roach $subrec = Functions::getSubRecord($level, "@ $t", $gedrec); 2727b2448a1bSGreg Roach if ($subrec === '') { 2728729ce104SGreg Roach return ''; 2729729ce104SGreg Roach } 2730729ce104SGreg Roach } 2731729ce104SGreg Roach } 2732729ce104SGreg Roach $level++; 2733729ce104SGreg Roach } 2734729ce104SGreg Roach $level--; 2735729ce104SGreg Roach $ct = preg_match("/$level $t(.*)/", $subrec, $match); 2736f71a7dedSGreg Roach if ($ct === 0) { 2737729ce104SGreg Roach $ct = preg_match("/$level @.+@ (.+)/", $subrec, $match); 2738729ce104SGreg Roach } 2739f71a7dedSGreg Roach if ($ct === 0) { 2740729ce104SGreg Roach $ct = preg_match("/@ $t (.+)/", $subrec, $match); 2741729ce104SGreg Roach } 2742729ce104SGreg Roach if ($ct > 0) { 2743729ce104SGreg Roach $value = trim($match[1]); 2744044416d2SGreg Roach if ($t === 'NOTE' && preg_match('/^@(.+)@$/', $value, $match)) { 27456b9cb339SGreg Roach $note = Registry::noteFactory()->make($match[1], $this->tree); 2746ff166e64SGreg Roach if ($note instanceof Note) { 2747729ce104SGreg Roach $value = $note->getNote(); 2748729ce104SGreg Roach } else { 2749729ce104SGreg Roach //-- set the value to the id without the @ 2750729ce104SGreg Roach $value = $match[1]; 2751729ce104SGreg Roach } 2752729ce104SGreg Roach } 2753f71a7dedSGreg Roach if ($level !== 0 || $t !== 'NOTE') { 27543d7a8a4cSGreg Roach $value .= Functions::getCont($level + 1, $subrec); 2755729ce104SGreg Roach } 2756729ce104SGreg Roach 2757729ce104SGreg Roach return $value; 2758729ce104SGreg Roach } 2759729ce104SGreg Roach 27607a6ee1acSGreg Roach return ''; 2761729ce104SGreg Roach } 2762d1286247SGreg Roach 2763d1286247SGreg Roach /** 2764d1286247SGreg Roach * Replace variable identifiers with their values. 2765d1286247SGreg Roach * 2766d1286247SGreg Roach * @param string $expression An expression such as "$foo == 123" 276782759250SGreg Roach * @param bool $quote Whether to add quotation marks 2768d1286247SGreg Roach * 2769d1286247SGreg Roach * @return string 2770d1286247SGreg Roach */ 27718f53f488SRico Sonntag private function substituteVars($expression, $quote): string 2772c1010edaSGreg Roach { 2773d1286247SGreg Roach return preg_replace_callback( 2774d1286247SGreg Roach '/\$(\w+)/', 277518d7a90dSGreg Roach function (array $matches) use ($quote): string { 27762118c0e3SGreg Roach if (isset($this->vars[$matches[1]]['id'])) { 277782759250SGreg Roach if ($quote) { 27782118c0e3SGreg Roach return "'" . addcslashes($this->vars[$matches[1]]['id'], "'") . "'"; 2779b2ce94c6SRico Sonntag } 2780b2ce94c6SRico Sonntag 27812118c0e3SGreg Roach return $this->vars[$matches[1]]['id']; 278282759250SGreg Roach } 2783b2ce94c6SRico Sonntag 2784d1286247SGreg Roach Log::addErrorLog(sprintf('Undefined variable $%s in report', $matches[1])); 27853d7a8a4cSGreg Roach 2786d1286247SGreg Roach return '$' . $matches[1]; 2787d1286247SGreg Roach }, 2788d1286247SGreg Roach $expression 2789d1286247SGreg Roach ); 2790d1286247SGreg Roach } 2791a6f13a4aSGreg Roach} 2792