Passed
Push — 1.7 ( 420789...d84c0a )
by
unknown
07:14
created
app/Database.php 2 patches
Spacing   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -85,8 +85,7 @@
 block discarded – undo
85 85
 		// Create the underlying PDO object
86 86
 		self::$pdo = new PDO(
87 87
 			(substr($DBHOST, 0, 1) === '/' ?
88
-				"mysql:unix_socket={$DBHOST};dbname={$DBNAME}" :
89
-				"mysql:host={$DBHOST};dbname={$DBNAME};port={$DBPORT}"
88
+				"mysql:unix_socket={$DBHOST};dbname={$DBNAME}" : "mysql:host={$DBHOST};dbname={$DBNAME};port={$DBPORT}"
90 89
 			),
91 90
 			$DBUSER, $DBPASS,
92 91
 			array(
Please login to merge, or discard this patch.
Indentation   +268 added lines, -268 removed lines patch added patch discarded remove patch
@@ -23,299 +23,299 @@
 block discarded – undo
23 23
  * Extend PHP's native PDO class.
24 24
  */
25 25
 class Database {
26
-	/** @var Database Implement the singleton pattern */
27
-	private static $instance;
26
+    /** @var Database Implement the singleton pattern */
27
+    private static $instance;
28 28
 
29
-	/** @var PDO Native PHP database driver */
30
-	private static $pdo;
29
+    /** @var PDO Native PHP database driver */
30
+    private static $pdo;
31 31
 
32
-	/** @var array Keep a log of all the SQL statements that we execute */
33
-	private static $log;
32
+    /** @var array Keep a log of all the SQL statements that we execute */
33
+    private static $log;
34 34
 
35
-	/** @var Statement[] Cache of prepared statements */
36
-	private static $prepared = array();
35
+    /** @var Statement[] Cache of prepared statements */
36
+    private static $prepared = array();
37 37
 
38
-	/**
39
-	 * Prevent instantiation via new Database
40
-	 */
41
-	private function __construct() {
42
-		self::$log = array();
43
-	}
38
+    /**
39
+     * Prevent instantiation via new Database
40
+     */
41
+    private function __construct() {
42
+        self::$log = array();
43
+    }
44 44
 
45
-	/**
46
-	 * Begin a transaction.
47
-	 *
48
-	 * @return bool
49
-	 */
50
-	public static function beginTransaction() {
51
-		return self::$pdo->beginTransaction();
52
-	}
45
+    /**
46
+     * Begin a transaction.
47
+     *
48
+     * @return bool
49
+     */
50
+    public static function beginTransaction() {
51
+        return self::$pdo->beginTransaction();
52
+    }
53 53
 
54
-	/**
55
-	 * Commit this transaction.
56
-	 *
57
-	 * @return bool
58
-	 */
59
-	public static function commit() {
60
-		return self::$pdo->commit();
61
-	}
54
+    /**
55
+     * Commit this transaction.
56
+     *
57
+     * @return bool
58
+     */
59
+    public static function commit() {
60
+        return self::$pdo->commit();
61
+    }
62 62
 
63
-	/**
64
-	 * Disconnect from the server, so we can connect to another one
65
-	 */
66
-	public static function disconnect() {
67
-		self::$pdo = null;
68
-	}
63
+    /**
64
+     * Disconnect from the server, so we can connect to another one
65
+     */
66
+    public static function disconnect() {
67
+        self::$pdo = null;
68
+    }
69 69
 
70
-	/**
71
-	 * Implement the singleton pattern, using a static accessor.
72
-	 *
73
-	 * @param string $DBHOST
74
-	 * @param string $DBPORT
75
-	 * @param string $DBNAME
76
-	 * @param string $DBUSER
77
-	 * @param string $DBPASS
78
-	 *
79
-	 * @throws \Exception
80
-	 */
81
-	public static function createInstance($DBHOST, $DBPORT, $DBNAME, $DBUSER, $DBPASS) {
82
-		if (self::$pdo instanceof PDO) {
83
-			throw new \Exception('Database::createInstance() can only be called once.');
84
-		}
85
-		// Create the underlying PDO object
86
-		self::$pdo = new PDO(
87
-			(substr($DBHOST, 0, 1) === '/' ?
88
-				"mysql:unix_socket={$DBHOST};dbname={$DBNAME}" :
89
-				"mysql:host={$DBHOST};dbname={$DBNAME};port={$DBPORT}"
90
-			),
91
-			$DBUSER, $DBPASS,
92
-			array(
93
-				PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
94
-				PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
95
-				PDO::ATTR_CASE               => PDO::CASE_LOWER,
96
-				PDO::ATTR_AUTOCOMMIT         => true,
97
-			)
98
-		);
99
-		self::$pdo->exec("SET NAMES UTF8");
100
-		self::$pdo->prepare("SET time_zone = :time_zone")->execute(array('time_zone' => date('P')));
70
+    /**
71
+     * Implement the singleton pattern, using a static accessor.
72
+     *
73
+     * @param string $DBHOST
74
+     * @param string $DBPORT
75
+     * @param string $DBNAME
76
+     * @param string $DBUSER
77
+     * @param string $DBPASS
78
+     *
79
+     * @throws \Exception
80
+     */
81
+    public static function createInstance($DBHOST, $DBPORT, $DBNAME, $DBUSER, $DBPASS) {
82
+        if (self::$pdo instanceof PDO) {
83
+            throw new \Exception('Database::createInstance() can only be called once.');
84
+        }
85
+        // Create the underlying PDO object
86
+        self::$pdo = new PDO(
87
+            (substr($DBHOST, 0, 1) === '/' ?
88
+                "mysql:unix_socket={$DBHOST};dbname={$DBNAME}" :
89
+                "mysql:host={$DBHOST};dbname={$DBNAME};port={$DBPORT}"
90
+            ),
91
+            $DBUSER, $DBPASS,
92
+            array(
93
+                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
94
+                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
95
+                PDO::ATTR_CASE               => PDO::CASE_LOWER,
96
+                PDO::ATTR_AUTOCOMMIT         => true,
97
+            )
98
+        );
99
+        self::$pdo->exec("SET NAMES UTF8");
100
+        self::$pdo->prepare("SET time_zone = :time_zone")->execute(array('time_zone' => date('P')));
101 101
 
102
-		self::$instance = new self;
103
-	}
102
+        self::$instance = new self;
103
+    }
104 104
 
105
-	/**
106
-	 * We don't access $instance directly, only via query(), exec() and prepare()
107
-	 *
108
-	 * @throws \Exception
109
-	 *
110
-	 * @return Database
111
-	 */
112
-	public static function getInstance() {
113
-		if (self::$pdo instanceof PDO) {
114
-			return self::$instance;
115
-		} else {
116
-			throw new \Exception('createInstance() must be called before getInstance().');
117
-		}
118
-	}
105
+    /**
106
+     * We don't access $instance directly, only via query(), exec() and prepare()
107
+     *
108
+     * @throws \Exception
109
+     *
110
+     * @return Database
111
+     */
112
+    public static function getInstance() {
113
+        if (self::$pdo instanceof PDO) {
114
+            return self::$instance;
115
+        } else {
116
+            throw new \Exception('createInstance() must be called before getInstance().');
117
+        }
118
+    }
119 119
 
120
-	/**
121
-	 * Are we currently connected to a database?
122
-	 *
123
-	 * @return bool
124
-	 */
125
-	public static function isConnected() {
126
-		return self::$pdo instanceof PDO;
127
-	}
120
+    /**
121
+     * Are we currently connected to a database?
122
+     *
123
+     * @return bool
124
+     */
125
+    public static function isConnected() {
126
+        return self::$pdo instanceof PDO;
127
+    }
128 128
 
129
-	/**
130
-	 * Log the details of a query, for debugging and analysis.
131
-	 *
132
-	 * @param string   $query
133
-	 * @param int      $rows
134
-	 * @param float    $microtime
135
-	 * @param string[] $bind_variables
136
-	 */
137
-	public static function logQuery($query, $rows, $microtime, $bind_variables) {
138
-		if (WT_DEBUG_SQL) {
139
-			// Full logging
140
-			// Trace
141
-			$trace = debug_backtrace();
142
-			array_shift($trace);
143
-			array_shift($trace);
144
-			foreach ($trace as $n => $frame) {
145
-				if (isset($frame['file']) && isset($frame['line'])) {
146
-					$trace[$n] = basename($frame['file']) . ':' . $frame['line'] . ' ' . $frame['function'];
147
-				} else {
148
-					unset($trace[$n]);
149
-				}
150
-			}
151
-			$stack = '<abbr title="' . Filter::escapeHtml(implode(" / ", $trace)) . '">' . (count(self::$log) + 1) . '</abbr>';
152
-			// Bind variables
153
-			foreach ($bind_variables as $key => $value) {
154
-				if (is_null($value)) {
155
-					$value = 'NULL';
156
-				} elseif (!is_integer($value)) {
157
-					$value = '\'' . $value . '\'';
158
-				}
159
-				if (is_integer($key)) {
160
-					$query = preg_replace('/\?/', $value, $query, 1);
161
-				} else {
162
-					$query = str_replace(':' . $key, $value, $query);
163
-				}
164
-			}
165
-			// Highlight slow queries
166
-			$microtime *= 1000; // convert to milliseconds
167
-			if ($microtime > 1000) {
168
-				$microtime = sprintf('<span style="background-color: #ff0000;">%.3f</span>', $microtime);
169
-			} elseif ($microtime > 100) {
170
-				$microtime = sprintf('<span style="background-color: #ffa500;">%.3f</span>', $microtime);
171
-			} elseif ($microtime > 1) {
172
-				$microtime = sprintf('<span style="background-color: #ffff00;">%.3f</span>', $microtime);
173
-			} else {
174
-				$microtime = sprintf('%.3f', $microtime);
175
-			}
176
-			self::$log[] = "<tr><td>{$stack}</td><td>{$query}</td><td>{$rows}</td><td>{$microtime}</td></tr>";
177
-		} else {
178
-			// Just log query count for statistics
179
-			self::$log[] = true;
180
-		}
181
-	}
129
+    /**
130
+     * Log the details of a query, for debugging and analysis.
131
+     *
132
+     * @param string   $query
133
+     * @param int      $rows
134
+     * @param float    $microtime
135
+     * @param string[] $bind_variables
136
+     */
137
+    public static function logQuery($query, $rows, $microtime, $bind_variables) {
138
+        if (WT_DEBUG_SQL) {
139
+            // Full logging
140
+            // Trace
141
+            $trace = debug_backtrace();
142
+            array_shift($trace);
143
+            array_shift($trace);
144
+            foreach ($trace as $n => $frame) {
145
+                if (isset($frame['file']) && isset($frame['line'])) {
146
+                    $trace[$n] = basename($frame['file']) . ':' . $frame['line'] . ' ' . $frame['function'];
147
+                } else {
148
+                    unset($trace[$n]);
149
+                }
150
+            }
151
+            $stack = '<abbr title="' . Filter::escapeHtml(implode(" / ", $trace)) . '">' . (count(self::$log) + 1) . '</abbr>';
152
+            // Bind variables
153
+            foreach ($bind_variables as $key => $value) {
154
+                if (is_null($value)) {
155
+                    $value = 'NULL';
156
+                } elseif (!is_integer($value)) {
157
+                    $value = '\'' . $value . '\'';
158
+                }
159
+                if (is_integer($key)) {
160
+                    $query = preg_replace('/\?/', $value, $query, 1);
161
+                } else {
162
+                    $query = str_replace(':' . $key, $value, $query);
163
+                }
164
+            }
165
+            // Highlight slow queries
166
+            $microtime *= 1000; // convert to milliseconds
167
+            if ($microtime > 1000) {
168
+                $microtime = sprintf('<span style="background-color: #ff0000;">%.3f</span>', $microtime);
169
+            } elseif ($microtime > 100) {
170
+                $microtime = sprintf('<span style="background-color: #ffa500;">%.3f</span>', $microtime);
171
+            } elseif ($microtime > 1) {
172
+                $microtime = sprintf('<span style="background-color: #ffff00;">%.3f</span>', $microtime);
173
+            } else {
174
+                $microtime = sprintf('%.3f', $microtime);
175
+            }
176
+            self::$log[] = "<tr><td>{$stack}</td><td>{$query}</td><td>{$rows}</td><td>{$microtime}</td></tr>";
177
+        } else {
178
+            // Just log query count for statistics
179
+            self::$log[] = true;
180
+        }
181
+    }
182 182
 
183
-	/**
184
-	 * Determine the number of queries executed, for the page statistics.
185
-	 *
186
-	 * @return int
187
-	 */
188
-	public static function getQueryCount() {
189
-		return count(self::$log);
190
-	}
183
+    /**
184
+     * Determine the number of queries executed, for the page statistics.
185
+     *
186
+     * @return int
187
+     */
188
+    public static function getQueryCount() {
189
+        return count(self::$log);
190
+    }
191 191
 
192
-	/**
193
-	 * Convert the query log into an HTML table.
194
-	 *
195
-	 * @return string
196
-	 */
197
-	public static function getQueryLog() {
198
-		$html      = '<table border="1" style="table-layout: fixed; width: 960px;word-wrap: break-word;"><col span="3"><col align="char"><thead><tr><th>#</th><th style="width: 800px;">Query</th><th>Rows</th><th>Time (ms)</th></tr></thead><tbody>' . implode('', self::$log) . '</tbody></table>';
199
-		self::$log = array();
192
+    /**
193
+     * Convert the query log into an HTML table.
194
+     *
195
+     * @return string
196
+     */
197
+    public static function getQueryLog() {
198
+        $html      = '<table border="1" style="table-layout: fixed; width: 960px;word-wrap: break-word;"><col span="3"><col align="char"><thead><tr><th>#</th><th style="width: 800px;">Query</th><th>Rows</th><th>Time (ms)</th></tr></thead><tbody>' . implode('', self::$log) . '</tbody></table>';
199
+        self::$log = array();
200 200
 
201
-		return $html;
202
-	}
201
+        return $html;
202
+    }
203 203
 
204
-	/**
205
-	 * Determine the most recently created value of an AUTO_INCREMENT field.
206
-	 *
207
-	 * @return string
208
-	 */
209
-	public static function lastInsertId() {
210
-		return self::$pdo->lastInsertId();
211
-	}
204
+    /**
205
+     * Determine the most recently created value of an AUTO_INCREMENT field.
206
+     *
207
+     * @return string
208
+     */
209
+    public static function lastInsertId() {
210
+        return self::$pdo->lastInsertId();
211
+    }
212 212
 
213
-	/**
214
-	 * Quote a string for embedding in a MySQL statement.
215
-	 *
216
-	 * The native quote() function does not convert PHP nulls to DB nulls
217
-	 *
218
-	 * @param  string $string
219
-	 *
220
-	 * @return string
221
-	 *
222
-	 * @deprecated We should use bind-variables instead.
223
-	 */
224
-	public static function quote($string) {
225
-		if (is_null($string)) {
226
-			return 'NULL';
227
-		} else {
228
-			return self::$pdo->quote($string, PDO::PARAM_STR);
229
-		}
230
-	}
213
+    /**
214
+     * Quote a string for embedding in a MySQL statement.
215
+     *
216
+     * The native quote() function does not convert PHP nulls to DB nulls
217
+     *
218
+     * @param  string $string
219
+     *
220
+     * @return string
221
+     *
222
+     * @deprecated We should use bind-variables instead.
223
+     */
224
+    public static function quote($string) {
225
+        if (is_null($string)) {
226
+            return 'NULL';
227
+        } else {
228
+            return self::$pdo->quote($string, PDO::PARAM_STR);
229
+        }
230
+    }
231 231
 
232
-	/**
233
-	 * Execute an SQL statement, and log the result.
234
-	 *
235
-	 * @param string $sql The SQL statement to execute
236
-	 *
237
-	 * @return int The number of rows affected by this SQL query
238
-	 */
239
-	public static function exec($sql) {
240
-		$sql   = str_replace('##', WT_TBLPREFIX, $sql);
241
-		$start = microtime(true);
242
-		$rows  = self::$pdo->exec($sql);
243
-		$end   = microtime(true);
244
-		self::logQuery($sql, $rows, $end - $start, array());
232
+    /**
233
+     * Execute an SQL statement, and log the result.
234
+     *
235
+     * @param string $sql The SQL statement to execute
236
+     *
237
+     * @return int The number of rows affected by this SQL query
238
+     */
239
+    public static function exec($sql) {
240
+        $sql   = str_replace('##', WT_TBLPREFIX, $sql);
241
+        $start = microtime(true);
242
+        $rows  = self::$pdo->exec($sql);
243
+        $end   = microtime(true);
244
+        self::logQuery($sql, $rows, $end - $start, array());
245 245
 
246
-		return $rows;
247
-	}
246
+        return $rows;
247
+    }
248 248
 
249
-	/**
250
-	 * Prepare an SQL statement for execution.
251
-	 *
252
-	 * @param $sql
253
-	 *
254
-	 * @throws \Exception
255
-	 *
256
-	 * @return Statement
257
-	 */
258
-	public static function prepare($sql) {
259
-		if (!self::$pdo instanceof PDO) {
260
-			throw new \Exception("No Connection Established");
261
-		}
262
-		$sql = str_replace('##', WT_TBLPREFIX, $sql);
249
+    /**
250
+     * Prepare an SQL statement for execution.
251
+     *
252
+     * @param $sql
253
+     *
254
+     * @throws \Exception
255
+     *
256
+     * @return Statement
257
+     */
258
+    public static function prepare($sql) {
259
+        if (!self::$pdo instanceof PDO) {
260
+            throw new \Exception("No Connection Established");
261
+        }
262
+        $sql = str_replace('##', WT_TBLPREFIX, $sql);
263 263
 
264
-		$hash = md5($sql);
265
-		if (!array_key_exists($hash, self::$prepared)) {
266
-			self::$prepared[$hash] = new Statement(self::$pdo->prepare($sql));
267
-		}
264
+        $hash = md5($sql);
265
+        if (!array_key_exists($hash, self::$prepared)) {
266
+            self::$prepared[$hash] = new Statement(self::$pdo->prepare($sql));
267
+        }
268 268
 
269
-		return self::$prepared[$hash];
270
-	}
269
+        return self::$prepared[$hash];
270
+    }
271 271
 
272
-	/**
273
-	 * Roll back this transaction.
274
-	 *
275
-	 * @return bool
276
-	 */
277
-	public static function rollBack() {
278
-		return self::$pdo->rollBack();
279
-	}
272
+    /**
273
+     * Roll back this transaction.
274
+     *
275
+     * @return bool
276
+     */
277
+    public static function rollBack() {
278
+        return self::$pdo->rollBack();
279
+    }
280 280
 
281
-	/**
282
-	 * Run a series of scripts to bring the database schema up to date.
283
-	 *
284
-	 * @param string $namespace      Where to find our MigrationXXX classes
285
-	 * @param string $schema_name    Where to find our MigrationXXX classes
286
-	 * @param int    $target_version updade/downgrade to this version
287
-	 *
288
-	 * @throws PDOException
289
-	 *
290
-	 * @return bool  Were any updates applied
291
-	 */
292
-	public static function updateSchema($namespace, $schema_name, $target_version) {
293
-		try {
294
-			$current_version = (int) Site::getPreference($schema_name);
295
-		} catch (PDOException $e) {
296
-			// During initial installation, the site_preference table won’t exist.
297
-			$current_version = 0;
298
-		}
281
+    /**
282
+     * Run a series of scripts to bring the database schema up to date.
283
+     *
284
+     * @param string $namespace      Where to find our MigrationXXX classes
285
+     * @param string $schema_name    Where to find our MigrationXXX classes
286
+     * @param int    $target_version updade/downgrade to this version
287
+     *
288
+     * @throws PDOException
289
+     *
290
+     * @return bool  Were any updates applied
291
+     */
292
+    public static function updateSchema($namespace, $schema_name, $target_version) {
293
+        try {
294
+            $current_version = (int) Site::getPreference($schema_name);
295
+        } catch (PDOException $e) {
296
+            // During initial installation, the site_preference table won’t exist.
297
+            $current_version = 0;
298
+        }
299 299
 
300
-		$updates_applied = false;
300
+        $updates_applied = false;
301 301
 
302
-		try {
303
-			// Update the schema, one version at a time.
304
-			while ($current_version < $target_version) {
305
-				$class = $namespace . '\\Migration' . $current_version;
306
-				/** @var MigrationInterface $migration */
307
-				$migration = new $class;
308
-				$migration->upgrade();
309
-				Site::setPreference($schema_name, ++$current_version);
310
-				$updates_applied = true;
311
-			}
312
-		} catch (PDOException $ex) {
313
-			// The schema update scripts should never fail. If they do, there is no clean recovery.
314
-			FlashMessages::addMessage($ex->getMessage(), 'danger');
315
-			header('Location: ' . WT_BASE_URL . 'site-unavailable.php');
316
-			throw $ex;
317
-		}
302
+        try {
303
+            // Update the schema, one version at a time.
304
+            while ($current_version < $target_version) {
305
+                $class = $namespace . '\\Migration' . $current_version;
306
+                /** @var MigrationInterface $migration */
307
+                $migration = new $class;
308
+                $migration->upgrade();
309
+                Site::setPreference($schema_name, ++$current_version);
310
+                $updates_applied = true;
311
+            }
312
+        } catch (PDOException $ex) {
313
+            // The schema update scripts should never fail. If they do, there is no clean recovery.
314
+            FlashMessages::addMessage($ex->getMessage(), 'danger');
315
+            header('Location: ' . WT_BASE_URL . 'site-unavailable.php');
316
+            throw $ex;
317
+        }
318 318
 
319
-		return $updates_applied;
320
-	}
319
+        return $updates_applied;
320
+    }
321 321
 }
Please login to merge, or discard this patch.
app/Report/ReportParserGenerate.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2743,7 +2743,7 @@
 block discarded – undo
2743 2743
 		$tags      = explode(':', $tag);
2744 2744
 		$origlevel = $level;
2745 2745
 		if ($level == 0) {
2746
-			$level = $gedrec{0} + 1;
2746
+			$level = $gedrec{0} +1;
2747 2747
 		}
2748 2748
 
2749 2749
 		$subrec = $gedrec;
Please login to merge, or discard this patch.
Indentation   +2790 added lines, -2790 removed lines patch added patch discarded remove patch
@@ -35,2799 +35,2799 @@
 block discarded – undo
35 35
  * Class ReportParserGenerate - parse a report.xml file and generate the report.
36 36
  */
37 37
 class ReportParserGenerate extends ReportParserBase {
38
-	/** @var bool Are we collecting data from <Footnote> elements  */
39
-	private $process_footnote = true;
38
+    /** @var bool Are we collecting data from <Footnote> elements  */
39
+    private $process_footnote = true;
40 40
 
41
-	/** @var bool Are we currently outputing data? */
42
-	private $print_data = false;
41
+    /** @var bool Are we currently outputing data? */
42
+    private $print_data = false;
43 43
 
44
-	/** @var bool[] Push-down stack of $print_data */
45
-	private $print_data_stack = array();
44
+    /** @var bool[] Push-down stack of $print_data */
45
+    private $print_data_stack = array();
46 46
 
47
-	/** @var int Are we processing GEDCOM data */
48
-	private $process_gedcoms = 0;
47
+    /** @var int Are we processing GEDCOM data */
48
+    private $process_gedcoms = 0;
49 49
 
50
-	/** @var int Are we processing conditionals */
51
-	private $process_ifs = 0;
50
+    /** @var int Are we processing conditionals */
51
+    private $process_ifs = 0;
52 52
 
53
-	/** @var int Are we processing repeats*/
54
-	private $process_repeats = 0;
55
-
56
-	/** @var int Quantity of data to repeat during loops */
57
-	private $repeat_bytes = 0;
58
-
59
-	/** @var array[] Repeated data when iterating over loops */
60
-	private $repeats = array();
61
-
62
-	/** @var array[] Nested repeating data */
63
-	private $repeats_stack = array();
64
-
65
-	/** @var ReportBase[] Nested repeating data */
66
-	private $wt_report_stack = array();
67
-
68
-	/** @var resource Nested repeating data */
69
-	private $parser;
70
-
71
-	/** @var resource[] Nested repeating data */
72
-	private $parser_stack = array();
73
-
74
-	/** @var string The current GEDCOM record */
75
-	private $gedrec = '';
76
-
77
-	/** @var string[] Nested GEDCOM records */
78
-	private $gedrec_stack = array();
79
-
80
-	/** @var ReportBaseElement The currently processed element */
81
-	private $current_element;
82
-
83
-	/** @var ReportBaseElement The currently processed element */
84
-	private $footnote_element;
85
-
86
-	/** @var string The GEDCOM fact currently being processed */
87
-	private $fact = '';
88
-
89
-	/** @var string The GEDCOM value currently being processed */
90
-	private $desc = '';
91
-
92
-	/** @var string The GEDCOM type currently being processed */
93
-	private $type = '';
94
-
95
-	/** @var int The current generational level */
96
-	private $generation = 1;
97
-
98
-	/** @var array Source data for processing lists */
99
-	private $list = array();
100
-
101
-	/** @var int Number of items in lists */
102
-	private $list_total = 0;
103
-
104
-	/** @var int Number of items filtered from lists */
105
-	private $list_private = 0;
106
-
107
-	/** @var ReportBase A factory for creating report elements */
108
-	private $report_root;
109
-
110
-	/** @var ReportBase Nested report elements */
111
-	private $wt_report;
112
-
113
-	/** @todo This attribute is public to support the PHP5.3 closure workaround. */
114
-	/** @var string[][] Variables defined in the report at run-time */
115
-	public $vars;
116
-
117
-	/**
118
-	 * Create a parser for a report
119
-	 *
120
-	 * @param string     $report     The XML filename
121
-	 * @param ReportBase $report_root
122
-	 * @param string[][] $vars
123
-	 */
124
-	public function __construct($report, ReportBase $report_root = null, array $vars = array()) {
125
-		$this->report_root     = $report_root;
126
-		$this->wt_report       = $report_root;
127
-		$this->current_element = new ReportBaseElement;
128
-		$this->vars            = $vars;
129
-		parent::__construct($report);
130
-	}
131
-
132
-	/**
133
-	 * XML start element handler
134
-	 *
135
-	 * This function is called whenever a starting element is reached
136
-	 * The element handler will be called if found, otherwise it must be HTML
137
-	 *
138
-	 * @param resource $parser the resource handler for the XML parser
139
-	 * @param string   $name   the name of the XML element parsed
140
-	 * @param array    $attrs  an array of key value pairs for the attributes
141
-	 */
142
-	protected function startElement($parser, $name, $attrs) {
143
-		$newattrs = array();
144
-
145
-		foreach ($attrs as $key => $value) {
146
-			if (preg_match("/^\\$(\w+)$/", $value, $match)) {
147
-				if ((isset($this->vars[$match[1]]['id'])) && (!isset($this->vars[$match[1]]['gedcom']))) {
148
-					$value = $this->vars[$match[1]]['id'];
149
-				}
150
-			}
151
-			$newattrs[$key] = $value;
152
-		}
153
-		$attrs = $newattrs;
154
-		if ($this->process_footnote && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag")) {
155
-			$start_method = $name . 'StartHandler';
156
-			$end_method   = $name . 'EndHandler';
157
-			if (method_exists($this, $start_method)) {
158
-				$this->$start_method($attrs);
159
-			} elseif (!method_exists($this, $end_method)) {
160
-				$this->htmlStartHandler($name, $attrs);
161
-			}
162
-		}
163
-	}
164
-
165
-	/**
166
-	 * XML end element handler
167
-	 *
168
-	 * This function is called whenever an ending element is reached
169
-	 * The element handler will be called if found, otherwise it must be HTML
170
-	 *
171
-	 * @param resource $parser the resource handler for the XML parser
172
-	 * @param string   $name   the name of the XML element parsed
173
-	 */
174
-	protected function endElement($parser, $name) {
175
-		if (($this->process_footnote || $name === "Footnote") && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag" || $name === "List" || $name === "Relatives")) {
176
-			$start_method = $name . 'StartHandler';
177
-			$end_method   = $name . 'EndHandler';
178
-			if (method_exists($this, $end_method)) {
179
-				$this->$end_method();
180
-			} elseif (!method_exists($this, $start_method)) {
181
-				$this->htmlEndHandler($name);
182
-			}
183
-		}
184
-	}
185
-
186
-	/**
187
-	 * XML character data handler
188
-	 *
189
-	 * @param resource $parser the resource handler for the XML parser
190
-	 * @param string   $data   the name of the XML element parsed
191
-	 */
192
-	protected function characterData($parser, $data) {
193
-		if ($this->print_data && $this->process_gedcoms === 0 && $this->process_ifs === 0 && $this->process_repeats === 0) {
194
-			$this->current_element->addText($data);
195
-		}
196
-	}
197
-
198
-	/**
199
-	 * XML <style>
200
-	 *
201
-	 * @param array $attrs an array of key value pairs for the attributes
202
-	 */
203
-	private function styleStartHandler($attrs) {
204
-		if (empty($attrs['name'])) {
205
-			throw new \DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.');
206
-		}
207
-
208
-		// array Style that will be passed on
209
-		$s = array();
210
-
211
-		// string Name af the style
212
-		$s['name'] = $attrs['name'];
213
-
214
-		// string Name of the DEFAULT font
215
-		$s['font'] = $this->wt_report->defaultFont;
216
-		if (!empty($attrs['font'])) {
217
-			$s['font'] = $attrs['font'];
218
-		}
219
-
220
-		// int The size of the font in points
221
-		$s['size'] = $this->wt_report->defaultFontSize;
222
-		if (!empty($attrs['size'])) {
223
-			$s['size'] = (int) $attrs['size'];
224
-		} // Get it as int to ignore all decimal points or text (if any text then int(0))
225
-
226
-		// string B: bold, I: italic, U: underline, D: line trough, The default value is regular.
227
-		$s['style'] = "";
228
-		if (!empty($attrs['style'])) {
229
-			$s['style'] = $attrs['style'];
230
-		}
231
-
232
-		$this->wt_report->addStyle($s);
233
-	}
234
-
235
-	/**
236
-	 * XML <Doc>
237
-	 *
238
-	 * Sets up the basics of the document proparties
239
-	 *
240
-	 * @param array $attrs an array of key value pairs for the attributes
241
-	 */
242
-	private function docStartHandler($attrs) {
243
-		$this->parser = $this->xml_parser;
244
-
245
-		// Custom page width
246
-		if (!empty($attrs['customwidth'])) {
247
-			$this->wt_report->pagew = (int) $attrs['customwidth'];
248
-		} // Get it as int to ignore all decimal points or text (if any text then int(0))
249
-		// Custom Page height
250
-		if (!empty($attrs['customheight'])) {
251
-			$this->wt_report->pageh = (int) $attrs['customheight'];
252
-		} // Get it as int to ignore all decimal points or text (if any text then int(0))
253
-
254
-		// Left Margin
255
-		if (isset($attrs['leftmargin'])) {
256
-			if ($attrs['leftmargin'] === "0") {
257
-				$this->wt_report->leftmargin = 0;
258
-			} elseif (!empty($attrs['leftmargin'])) {
259
-				$this->wt_report->leftmargin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
260
-			}
261
-		}
262
-		// Right Margin
263
-		if (isset($attrs['rightmargin'])) {
264
-			if ($attrs['rightmargin'] === "0") {
265
-				$this->wt_report->rightmargin = 0;
266
-			} elseif (!empty($attrs['rightmargin'])) {
267
-				$this->wt_report->rightmargin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
268
-			}
269
-		}
270
-		// Top Margin
271
-		if (isset($attrs['topmargin'])) {
272
-			if ($attrs['topmargin'] === "0") {
273
-				$this->wt_report->topmargin = 0;
274
-			} elseif (!empty($attrs['topmargin'])) {
275
-				$this->wt_report->topmargin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
276
-			}
277
-		}
278
-		// Bottom Margin
279
-		if (isset($attrs['bottommargin'])) {
280
-			if ($attrs['bottommargin'] === "0") {
281
-				$this->wt_report->bottommargin = 0;
282
-			} elseif (!empty($attrs['bottommargin'])) {
283
-				$this->wt_report->bottommargin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
284
-			}
285
-		}
286
-		// Header Margin
287
-		if (isset($attrs['headermargin'])) {
288
-			if ($attrs['headermargin'] === "0") {
289
-				$this->wt_report->headermargin = 0;
290
-			} elseif (!empty($attrs['headermargin'])) {
291
-				$this->wt_report->headermargin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
292
-			}
293
-		}
294
-		// Footer Margin
295
-		if (isset($attrs['footermargin'])) {
296
-			if ($attrs['footermargin'] === "0") {
297
-				$this->wt_report->footermargin = 0;
298
-			} elseif (!empty($attrs['footermargin'])) {
299
-				$this->wt_report->footermargin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
300
-			}
301
-		}
302
-
303
-		// Page Orientation
304
-		if (!empty($attrs['orientation'])) {
305
-			if ($attrs['orientation'] == "landscape") {
306
-				$this->wt_report->orientation = "landscape";
307
-			} elseif ($attrs['orientation'] == "portrait") {
308
-				$this->wt_report->orientation = "portrait";
309
-			}
310
-		}
311
-		// Page Size
312
-		if (!empty($attrs['pageSize'])) {
313
-			$this->wt_report->pageFormat = strtoupper($attrs['pageSize']);
314
-		}
315
-
316
-		// Show Generated By...
317
-		if (isset($attrs['showGeneratedBy'])) {
318
-			if ($attrs['showGeneratedBy'] === "0") {
319
-				$this->wt_report->showGenText = false;
320
-			} elseif ($attrs['showGeneratedBy'] === "1") {
321
-				$this->wt_report->showGenText = true;
322
-			}
323
-		}
324
-
325
-		$this->wt_report->setup();
326
-	}
327
-
328
-	/**
329
-	 * XML </Doc>
330
-	 */
331
-	private function docEndHandler() {
332
-		$this->wt_report->run();
333
-	}
334
-
335
-	/**
336
-	 * XML <Header>
337
-	 */
338
-	private function headerStartHandler() {
339
-		// Clear the Header before any new elements are added
340
-		$this->wt_report->clearHeader();
341
-		$this->wt_report->setProcessing("H");
342
-	}
343
-
344
-	/**
345
-	 * XML <PageHeader>
346
-	 */
347
-	private function pageHeaderStartHandler() {
348
-		array_push($this->print_data_stack, $this->print_data);
349
-		$this->print_data = false;
350
-		array_push($this->wt_report_stack, $this->wt_report);
351
-		$this->wt_report = $this->report_root->createPageHeader();
352
-	}
353
-
354
-	/**
355
-	 * XML <pageHeaderEndHandler>
356
-	 */
357
-	private function pageHeaderEndHandler() {
358
-		$this->print_data        = array_pop($this->print_data_stack);
359
-		$this->current_element   = $this->wt_report;
360
-		$this->wt_report         = array_pop($this->wt_report_stack);
361
-		$this->wt_report->addElement($this->current_element);
362
-	}
363
-
364
-	/**
365
-	 * XML <bodyStartHandler>
366
-	 */
367
-	private function bodyStartHandler() {
368
-		$this->wt_report->setProcessing("B");
369
-	}
370
-
371
-	/**
372
-	 * XML <footerStartHandler>
373
-	 */
374
-	private function footerStartHandler() {
375
-		$this->wt_report->setProcessing("F");
376
-	}
377
-
378
-	/**
379
-	 * XML <Cell>
380
-	 *
381
-	 * @param array $attrs an array of key value pairs for the attributes
382
-	 */
383
-	private function cellStartHandler($attrs) {
384
-		// string The text alignment of the text in this box.
385
-		$align = "";
386
-		if (!empty($attrs['align'])) {
387
-			$align = $attrs['align'];
388
-			// RTL supported left/right alignment
389
-			if ($align == "rightrtl") {
390
-				if ($this->wt_report->rtl) {
391
-					$align = "left";
392
-				} else {
393
-					$align = "right";
394
-				}
395
-			} elseif ($align == "leftrtl") {
396
-				if ($this->wt_report->rtl) {
397
-					$align = "right";
398
-				} else {
399
-					$align = "left";
400
-				}
401
-			}
402
-		}
403
-
404
-		// string The color to fill the background of this cell
405
-		$bgcolor = "";
406
-		if (!empty($attrs['bgcolor'])) {
407
-			$bgcolor = $attrs['bgcolor'];
408
-		}
409
-
410
-		// int Whether or not the background should be painted
411
-		$fill = 1;
412
-		if (isset($attrs['fill'])) {
413
-			if ($attrs['fill'] === "0") {
414
-				$fill = 0;
415
-			} elseif ($attrs['fill'] === "1") {
416
-				$fill = 1;
417
-			}
418
-		}
419
-
420
-		$reseth = true;
421
-		// boolean   if true reset the last cell height (default true)
422
-		if (isset($attrs['reseth'])) {
423
-			if ($attrs['reseth'] === "0") {
424
-				$reseth = false;
425
-			} elseif ($attrs['reseth'] === "1") {
426
-				$reseth = true;
427
-			}
428
-		}
429
-
430
-		// mixed Whether or not a border should be printed around this box
431
-		$border = 0;
432
-		if (!empty($attrs['border'])) {
433
-			$border = $attrs['border'];
434
-		}
435
-		// string Border color in HTML code
436
-		$bocolor = "";
437
-		if (!empty($attrs['bocolor'])) {
438
-			$bocolor = $attrs['bocolor'];
439
-		}
440
-
441
-		// int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted.
442
-		$height = 0;
443
-		if (!empty($attrs['height'])) {
444
-			$height = (int) $attrs['height'];
445
-		}
446
-		// int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin.
447
-		$width = 0;
448
-		if (!empty($attrs['width'])) {
449
-			$width = (int) $attrs['width'];
450
-		}
451
-
452
-		// int Stretch carachter mode
453
-		$stretch = 0;
454
-		if (!empty($attrs['stretch'])) {
455
-			$stretch = (int) $attrs['stretch'];
456
-		}
457
-
458
-		// mixed Position the left corner of this box on the page. The default is the current position.
459
-		$left = ".";
460
-		if (isset($attrs['left'])) {
461
-			if ($attrs['left'] === ".") {
462
-				$left = ".";
463
-			} elseif (!empty($attrs['left'])) {
464
-				$left = (int) $attrs['left'];
465
-			} elseif ($attrs['left'] === "0") {
466
-				$left = 0;
467
-			}
468
-		}
469
-		// mixed Position the top corner of this box on the page. the default is the current position
470
-		$top = ".";
471
-		if (isset($attrs['top'])) {
472
-			if ($attrs['top'] === ".") {
473
-				$top = ".";
474
-			} elseif (!empty($attrs['top'])) {
475
-				$top = (int) $attrs['top'];
476
-			} elseif ($attrs['top'] === "0") {
477
-				$top = 0;
478
-			}
479
-		}
480
-
481
-		// string The name of the Style that should be used to render the text.
482
-		$style = "";
483
-		if (!empty($attrs['style'])) {
484
-			$style = $attrs['style'];
485
-		}
486
-
487
-		// string Text color in html code
488
-		$tcolor = "";
489
-		if (!empty($attrs['tcolor'])) {
490
-			$tcolor = $attrs['tcolor'];
491
-		}
492
-
493
-		// int Indicates where the current position should go after the call.
494
-		$ln = 0;
495
-		if (isset($attrs['newline'])) {
496
-			if (!empty($attrs['newline'])) {
497
-				$ln = (int) $attrs['newline'];
498
-			} elseif ($attrs['newline'] === "0") {
499
-				$ln = 0;
500
-			}
501
-		}
502
-
503
-		if ($align == "left") {
504
-			$align = "L";
505
-		} elseif ($align == "right") {
506
-			$align = "R";
507
-		} elseif ($align == "center") {
508
-			$align = "C";
509
-		} elseif ($align == "justify") {
510
-			$align = "J";
511
-		}
512
-
513
-		array_push($this->print_data_stack, $this->print_data);
514
-		$this->print_data = true;
515
-
516
-		$this->current_element = $this->report_root->createCell(
517
-			$width,
518
-			$height,
519
-			$border,
520
-			$align,
521
-			$bgcolor,
522
-			$style,
523
-			$ln,
524
-			$top,
525
-			$left,
526
-			$fill,
527
-			$stretch,
528
-			$bocolor,
529
-			$tcolor,
530
-			$reseth
531
-		);
532
-	}
533
-
534
-	/**
535
-	 * XML </Cell>
536
-	 */
537
-	private function cellEndHandler() {
538
-		$this->print_data = array_pop($this->print_data_stack);
539
-		$this->wt_report->addElement($this->current_element);
540
-	}
541
-
542
-	/**
543
-	 * XML <Now /> element handler
544
-	 */
545
-	private function nowStartHandler() {
546
-		$g = FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET);
547
-		$this->current_element->addText($g->display());
548
-	}
549
-
550
-	/**
551
-	 * XML <PageNum /> element handler
552
-	 */
553
-	private function pageNumStartHandler() {
554
-		$this->current_element->addText("#PAGENUM#");
555
-	}
556
-
557
-	/**
558
-	 * XML <TotalPages /> element handler
559
-	 */
560
-	private function totalPagesStartHandler() {
561
-		$this->current_element->addText("{{:ptp:}}");
562
-	}
563
-
564
-	/**
565
-	 * Called at the start of an element.
566
-	 *
567
-	 * @param array $attrs an array of key value pairs for the attributes
568
-	 */
569
-	private function gedcomStartHandler($attrs) {
570
-		global $WT_TREE;
571
-
572
-		if ($this->process_gedcoms > 0) {
573
-			$this->process_gedcoms++;
574
-
575
-			return;
576
-		}
577
-
578
-		$tag       = $attrs['id'];
579
-		$tag       = str_replace("@fact", $this->fact, $tag);
580
-		$tags      = explode(":", $tag);
581
-		$newgedrec = '';
582
-		if (count($tags) < 2) {
583
-			$tmp       = GedcomRecord::getInstance($attrs['id'], $WT_TREE);
584
-			$newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
585
-		}
586
-		if (empty($newgedrec)) {
587
-			$tgedrec   = $this->gedrec;
588
-			$newgedrec = '';
589
-			foreach ($tags as $tag) {
590
-				if (preg_match("/\\$(.+)/", $tag, $match)) {
591
-					if (isset($this->vars[$match[1]]['gedcom'])) {
592
-						$newgedrec = $this->vars[$match[1]]['gedcom'];
593
-					} else {
594
-						$tmp       = GedcomRecord::getInstance($match[1], $WT_TREE);
595
-						$newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
596
-					}
597
-				} else {
598
-					if (preg_match("/@(.+)/", $tag, $match)) {
599
-						$gmatch = array();
600
-						if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) {
601
-							$tmp       = GedcomRecord::getInstance($gmatch[1], $WT_TREE);
602
-							$newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
603
-							$tgedrec   = $newgedrec;
604
-						} else {
605
-							$newgedrec = '';
606
-							break;
607
-						}
608
-					} else {
609
-						$temp      = explode(" ", trim($tgedrec));
610
-						$level     = $temp[0] + 1;
611
-						$newgedrec = Functions::getSubRecord($level, "$level $tag", $tgedrec);
612
-						$tgedrec   = $newgedrec;
613
-					}
614
-				}
615
-			}
616
-		}
617
-		if (!empty($newgedrec)) {
618
-			array_push($this->gedrec_stack, array($this->gedrec, $this->fact, $this->desc));
619
-			$this->gedrec = $newgedrec;
620
-			if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $this->gedrec, $match)) {
621
-				$this->fact = $match[2];
622
-				$this->desc = trim($match[3]);
623
-			}
624
-		} else {
625
-			$this->process_gedcoms++;
626
-		}
627
-	}
628
-
629
-	/**
630
-	 * Called at the end of an element.
631
-	 */
632
-	private function gedcomEndHandler() {
633
-		if ($this->process_gedcoms > 0) {
634
-			$this->process_gedcoms--;
635
-		} else {
636
-			list($this->gedrec, $this->fact, $this->desc) = array_pop($this->gedrec_stack);
637
-		}
638
-	}
639
-
640
-	/**
641
-	 * XML <textBoxStartHandler>
642
-	 *
643
-	 * @param array $attrs an array of key value pairs for the attributes
644
-	 */
645
-	private function textBoxStartHandler($attrs) {
646
-		// string Background color code
647
-		$bgcolor = "";
648
-		if (!empty($attrs['bgcolor'])) {
649
-			$bgcolor = $attrs['bgcolor'];
650
-		}
651
-
652
-		// boolean Wether or not fill the background color
653
-		$fill = true;
654
-		if (isset($attrs['fill'])) {
655
-			if ($attrs['fill'] === "0") {
656
-				$fill = false;
657
-			} elseif ($attrs['fill'] === "1") {
658
-				$fill = true;
659
-			}
660
-		}
661
-
662
-		// var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0
663
-		$border = false;
664
-		if (isset($attrs['border'])) {
665
-			if ($attrs['border'] === "1") {
666
-				$border = true;
667
-			} elseif ($attrs['border'] === "0") {
668
-				$border = false;
669
-			}
670
-		}
671
-
672
-		// int The starting height of this cell. If the text wraps the height will automatically be adjusted
673
-		$height = 0;
674
-		if (!empty($attrs['height'])) {
675
-			$height = (int) $attrs['height'];
676
-		}
677
-		// int Setting the width to 0 will make it the width from the current location to the margin
678
-		$width = 0;
679
-		if (!empty($attrs['width'])) {
680
-			$width = (int) $attrs['width'];
681
-		}
682
-
683
-		// mixed Position the left corner of this box on the page. The default is the current position.
684
-		$left = ".";
685
-		if (isset($attrs['left'])) {
686
-			if ($attrs['left'] === ".") {
687
-				$left = ".";
688
-			} elseif (!empty($attrs['left'])) {
689
-				$left = (int) $attrs['left'];
690
-			} elseif ($attrs['left'] === "0") {
691
-				$left = 0;
692
-			}
693
-		}
694
-		// mixed Position the top corner of this box on the page. the default is the current position
695
-		$top = ".";
696
-		if (isset($attrs['top'])) {
697
-			if ($attrs['top'] === ".") {
698
-				$top = ".";
699
-			} elseif (!empty($attrs['top'])) {
700
-				$top = (int) $attrs['top'];
701
-			} elseif ($attrs['top'] === "0") {
702
-				$top = 0;
703
-			}
704
-		}
705
-		// boolean After this box is finished rendering, should the next section of text start immediately after the this box or should it start on a new line under this box. 0 = no new line, 1 = force new line. Default is 0
706
-		$newline = false;
707
-		if (isset($attrs['newline'])) {
708
-			if ($attrs['newline'] === "1") {
709
-				$newline = true;
710
-			} elseif ($attrs['newline'] === "0") {
711
-				$newline = false;
712
-			}
713
-		}
714
-		// boolean
715
-		$pagecheck = true;
716
-		if (isset($attrs['pagecheck'])) {
717
-			if ($attrs['pagecheck'] === "0") {
718
-				$pagecheck = false;
719
-			} elseif ($attrs['pagecheck'] === "1") {
720
-				$pagecheck = true;
721
-			}
722
-		}
723
-		// boolean Cell padding
724
-		$padding = true;
725
-		if (isset($attrs['padding'])) {
726
-			if ($attrs['padding'] === "0") {
727
-				$padding = false;
728
-			} elseif ($attrs['padding'] === "1") {
729
-				$padding = true;
730
-			}
731
-		}
732
-		// boolean Reset this box Height
733
-		$reseth = false;
734
-		if (isset($attrs['reseth'])) {
735
-			if ($attrs['reseth'] === "1") {
736
-				$reseth = true;
737
-			} elseif ($attrs['reseth'] === "0") {
738
-				$reseth = false;
739
-			}
740
-		}
741
-
742
-		// string Style of rendering
743
-		$style = "";
744
-
745
-		array_push($this->print_data_stack, $this->print_data);
746
-		$this->print_data = false;
747
-
748
-		array_push($this->wt_report_stack, $this->wt_report);
749
-		$this->wt_report = $this->report_root->createTextBox(
750
-			$width,
751
-			$height,
752
-			$border,
753
-			$bgcolor,
754
-			$newline,
755
-			$left,
756
-			$top,
757
-			$pagecheck,
758
-			$style,
759
-			$fill,
760
-			$padding,
761
-			$reseth
762
-		);
763
-	}
764
-
765
-	/**
766
-	 * XML <textBoxEndHandler>
767
-	 */
768
-	private function textBoxEndHandler() {
769
-		$this->print_data      = array_pop($this->print_data_stack);
770
-		$this->current_element = $this->wt_report;
771
-		$this->wt_report       = array_pop($this->wt_report_stack);
772
-		$this->wt_report->addElement($this->current_element);
773
-	}
774
-
775
-	/**
776
-	 * XLM <Text>.
777
-	 *
778
-	 * @param array $attrs an array of key value pairs for the attributes
779
-	 */
780
-	private function textStartHandler($attrs) {
781
-		array_push($this->print_data_stack, $this->print_data);
782
-		$this->print_data = true;
783
-
784
-		// string The name of the Style that should be used to render the text.
785
-		$style = "";
786
-		if (!empty($attrs['style'])) {
787
-			$style = $attrs['style'];
788
-		}
789
-
790
-		// string  The color of the text - Keep the black color as default
791
-		$color = "";
792
-		if (!empty($attrs['color'])) {
793
-			$color = $attrs['color'];
794
-		}
795
-
796
-		$this->current_element = $this->report_root->createText($style, $color);
797
-	}
798
-
799
-	/**
800
-	 * XML </Text>
801
-	 */
802
-	private function textEndHandler() {
803
-		$this->print_data = array_pop($this->print_data_stack);
804
-		$this->wt_report->addElement($this->current_element);
805
-	}
806
-
807
-	/**
808
-	 * XML <GetPersonName/>
809
-	 *
810
-	 * Get the name
811
-	 * 1. id is empty - current GEDCOM record
812
-	 * 2. id is set with a record id
813
-	 *
814
-	 * @param array $attrs an array of key value pairs for the attributes
815
-	 */
816
-	private function getPersonNameStartHandler($attrs) {
817
-		global $WT_TREE;
818
-
819
-		$id    = "";
820
-		$match = array();
821
-		if (empty($attrs['id'])) {
822
-			if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
823
-				$id = $match[1];
824
-			}
825
-		} else {
826
-			if (preg_match("/\\$(.+)/", $attrs['id'], $match)) {
827
-				if (isset($this->vars[$match[1]]['id'])) {
828
-					$id = $this->vars[$match[1]]['id'];
829
-				}
830
-			} else {
831
-				if (preg_match("/@(.+)/", $attrs['id'], $match)) {
832
-					$gmatch = array();
833
-					if (preg_match("/\d $match[1] @([^@]+)@/", $this->gedrec, $gmatch)) {
834
-						$id = $gmatch[1];
835
-					}
836
-				} else {
837
-					$id = $attrs['id'];
838
-				}
839
-			}
840
-		}
841
-		if (!empty($id)) {
842
-			$record = GedcomRecord::getInstance($id, $WT_TREE);
843
-			if (is_null($record)) {
844
-				return;
845
-			}
846
-			if (!$record->canShowName()) {
847
-				$this->current_element->addText(I18N::translate('Private'));
848
-			} else {
849
-				$name = $record->getFullName();
850
-				$name = preg_replace(
851
-					array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'),
852
-					array('«', '', '»'),
853
-					$name
854
-				);
855
-				$name = strip_tags($name);
856
-				if (!empty($attrs['truncate'])) {
857
-					if (mb_strlen($name) > $attrs['truncate']) {
858
-						$name  = preg_replace("/\(.*\) ?/", '', $name); //removes () and text inbetween - what about ", [ and { etc?
859
-						$words = preg_split('/[, -]+/', $name); // names separated with space, comma or hyphen - any others?
860
-						$name  = $words[count($words) - 1];
861
-						for ($i = count($words) - 2; $i >= 0; $i--) {
862
-							$len = mb_strlen($name);
863
-							for ($j = count($words) - 3; $j >= 0; $j--) {
864
-								$len += mb_strlen($words[$j]);
865
-							}
866
-							if ($len > $attrs['truncate']) {
867
-								$first_letter = mb_substr($words[$i], 0, 1);
868
-								// Do not show " of nick-names
869
-								if ($first_letter != "\"") {
870
-									$name = mb_substr($words[$i], 0, 1) . '. ' . $name;
871
-								}
872
-							} else {
873
-								$name = $words[$i] . ' ' . $name;
874
-							}
875
-						}
876
-					}
877
-				} else {
878
-					$addname = $record->getAddName();
879
-					$addname = preg_replace(
880
-						array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'),
881
-						array('«', '', '»'),
882
-						$addname
883
-					);
884
-					$addname = strip_tags($addname);
885
-					if (!empty($addname)) {
886
-						$name .= " " . $addname;
887
-					}
888
-				}
889
-				$this->current_element->addText(trim($name));
890
-			}
891
-		}
892
-	}
893
-
894
-	/**
895
-	 * XML <GedcomValue/>
896
-	 *
897
-	 * @param array $attrs an array of key value pairs for the attributes
898
-	 */
899
-	private function gedcomValueStartHandler($attrs) {
900
-		global $WT_TREE;
901
-
902
-		$id    = "";
903
-		$match = array();
904
-		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
905
-			$id = $match[1];
906
-		}
907
-
908
-		if (isset($attrs['newline']) && $attrs['newline'] == "1") {
909
-			$useBreak = "1";
910
-		} else {
911
-			$useBreak = "0";
912
-		}
913
-
914
-		$tag = $attrs['tag'];
915
-		if (!empty($tag)) {
916
-			if ($tag == "@desc") {
917
-				$value = $this->desc;
918
-				$value = trim($value);
919
-				$this->current_element->addText($value);
920
-			}
921
-			if ($tag == "@id") {
922
-				$this->current_element->addText($id);
923
-			} else {
924
-				$tag = str_replace("@fact", $this->fact, $tag);
925
-				if (empty($attrs['level'])) {
926
-					$temp  = explode(" ", trim($this->gedrec));
927
-					$level = $temp[0];
928
-					if ($level == 0) {
929
-						$level++;
930
-					}
931
-				} else {
932
-					$level = $attrs['level'];
933
-				}
934
-				$tags  = preg_split('/[: ]/', $tag);
935
-				$value = $this->getGedcomValue($tag, $level, $this->gedrec);
936
-				switch (end($tags)) {
937
-				case 'DATE':
938
-					$tmp   = new Date($value);
939
-					$value = $tmp->display();
940
-					break;
941
-				case 'PLAC':
942
-					$tmp   = new Place($value, $WT_TREE);
943
-					$value = $tmp->getShortName();
944
-					break;
945
-				}
946
-				if ($useBreak == "1") {
947
-					// Insert <br> when multiple dates exist.
948
-					// This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages
949
-					$value = str_replace('(', '<br>(', $value);
950
-					$value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value);
951
-					$value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value);
952
-					if (substr($value, 0, 6) == '<br>') {
953
-						$value = substr($value, 6);
954
-					}
955
-				}
956
-				$tmp = explode(':', $tag);
957
-				if (in_array(end($tmp), array('NOTE', 'TEXT'))) {
958
-					$value = Filter::formatText($value, $WT_TREE); // We'll strip HTML in addText()
959
-				}
960
-				$this->current_element->addText($value);
961
-			}
962
-		}
963
-	}
964
-
965
-	/**
966
-	 * XML <RepeatTag>
967
-	 *
968
-	 * @param array $attrs an array of key value pairs for the attributes
969
-	 */
970
-	private function repeatTagStartHandler($attrs) {
971
-		global $WT_TREE;
972
-
973
-		$this->process_repeats++;
974
-		if ($this->process_repeats > 1) {
975
-			return;
976
-		}
977
-
978
-		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
979
-		$this->repeats      = array();
980
-		$this->repeat_bytes = xml_get_current_line_number($this->parser);
981
-
982
-		$tag = "";
983
-		if (isset($attrs['tag'])) {
984
-			$tag = $attrs['tag'];
985
-		}
986
-		if (!empty($tag)) {
987
-			if ($tag == "@desc") {
988
-				$value = $this->desc;
989
-				$value = trim($value);
990
-				$this->current_element->addText($value);
991
-			} else {
992
-				$tag   = str_replace("@fact", $this->fact, $tag);
993
-				$tags  = explode(":", $tag);
994
-				$temp  = explode(" ", trim($this->gedrec));
995
-				$level = $temp[0];
996
-				if ($level == 0) {
997
-					$level++;
998
-				}
999
-				$subrec = $this->gedrec;
1000
-				$t      = $tag;
1001
-				$count  = count($tags);
1002
-				$i      = 0;
1003
-				while ($i < $count) {
1004
-					$t = $tags[$i];
1005
-					if (!empty($t)) {
1006
-						if ($i < ($count - 1)) {
1007
-							$subrec = Functions::getSubRecord($level, "$level $t", $subrec);
1008
-							if (empty($subrec)) {
1009
-								$level--;
1010
-								$subrec = Functions::getSubRecord($level, "@ $t", $this->gedrec);
1011
-								if (empty($subrec)) {
1012
-									return;
1013
-								}
1014
-							}
1015
-						}
1016
-						$level++;
1017
-					}
1018
-					$i++;
1019
-				}
1020
-				$level--;
1021
-				$count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER);
1022
-				$i     = 0;
1023
-				while ($i < $count) {
1024
-					$i++;
1025
-					// Privacy check - is this a link, and are we allowed to view the linked object?
1026
-					$subrecord = Functions::getSubRecord($level, "$level $t", $subrec, $i);
1027
-					if (preg_match('/^\d ' . WT_REGEX_TAG . ' @(' . WT_REGEX_XREF . ')@/', $subrecord, $xref_match)) {
1028
-						$linked_object = GedcomRecord::getInstance($xref_match[1], $WT_TREE);
1029
-						if ($linked_object && !$linked_object->canShow()) {
1030
-							continue;
1031
-						}
1032
-					}
1033
-					$this->repeats[] = $subrecord;
1034
-				}
1035
-			}
1036
-		}
1037
-	}
1038
-
1039
-	/**
1040
-	 * XML </ RepeatTag>
1041
-	 */
1042
-	private function repeatTagEndHandler() {
1043
-		global $report;
1044
-
1045
-		$this->process_repeats--;
1046
-		if ($this->process_repeats > 0) {
1047
-			return;
1048
-		}
1049
-
1050
-		// Check if there is anything to repeat
1051
-		if (count($this->repeats) > 0) {
1052
-			// No need to load them if not used...
1053
-
1054
-			$lineoffset = 0;
1055
-			foreach ($this->repeats_stack as $rep) {
1056
-				$lineoffset += $rep[1];
1057
-			}
1058
-			//-- read the xml from the file
1059
-			$lines = file($report);
1060
-			while (strpos($lines[$lineoffset + $this->repeat_bytes], "<RepeatTag") === false) {
1061
-				$lineoffset--;
1062
-			}
1063
-			$lineoffset++;
1064
-			$reportxml = "<tempdoc>\n";
1065
-			$line_nr   = $lineoffset + $this->repeat_bytes;
1066
-			// RepeatTag Level counter
1067
-			$count = 1;
1068
-			while (0 < $count) {
1069
-				if (strstr($lines[$line_nr], "<RepeatTag") !== false) {
1070
-					$count++;
1071
-				} elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) {
1072
-					$count--;
1073
-				}
1074
-				if (0 < $count) {
1075
-					$reportxml .= $lines[$line_nr];
1076
-				}
1077
-				$line_nr++;
1078
-			}
1079
-			// No need to drag this
1080
-			unset($lines);
1081
-			$reportxml .= "</tempdoc>\n";
1082
-			// Save original values
1083
-			array_push($this->parser_stack, $this->parser);
1084
-			$oldgedrec = $this->gedrec;
1085
-			foreach ($this->repeats as $gedrec) {
1086
-				$this->gedrec  = $gedrec;
1087
-				$repeat_parser = xml_parser_create();
1088
-				$this->parser  = $repeat_parser;
1089
-				xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
1090
-				xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
1091
-				xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
1092
-				if (!xml_parse($repeat_parser, $reportxml, true)) {
1093
-					throw new \DomainException(sprintf(
1094
-						'RepeatTagEHandler XML error: %s at line %d',
1095
-						xml_error_string(xml_get_error_code($repeat_parser)),
1096
-						xml_get_current_line_number($repeat_parser)
1097
-					));
1098
-				}
1099
-				xml_parser_free($repeat_parser);
1100
-			}
1101
-			// Restore original values
1102
-			$this->gedrec = $oldgedrec;
1103
-			$this->parser = array_pop($this->parser_stack);
1104
-		}
1105
-		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
1106
-	}
1107
-
1108
-	/**
1109
-	 * Variable lookup
1110
-	 *
1111
-	 * Retrieve predefined variables :
1112
-	 *
1113
-	 * @ desc GEDCOM fact description, example:
1114
-	 *        1 EVEN This is a description
1115
-	 * @ fact GEDCOM fact tag, such as BIRT, DEAT etc.
1116
-	 * $ I18N::translate('....')
1117
-	 * $ language_settings[]
1118
-	 *
1119
-	 * @param array $attrs an array of key value pairs for the attributes
1120
-	 */
1121
-	private function varStartHandler($attrs) {
1122
-		if (empty($attrs['var'])) {
1123
-			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));
1124
-		}
1125
-
1126
-		$var = $attrs['var'];
1127
-		// SetVar element preset variables
1128
-		if (!empty($this->vars[$var]['id'])) {
1129
-			$var = $this->vars[$var]['id'];
1130
-		} else {
1131
-			$tfact = $this->fact;
1132
-			if (($this->fact === "EVEN" || $this->fact === "FACT") && $this->type !== " ") {
1133
-				// Use :
1134
-				// n TYPE This text if string
1135
-				$tfact = $this->type;
1136
-			}
1137
-			$var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $this->desc), $var);
1138
-			if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) {
1139
-				$var = I18N::number($match[1]);
1140
-			} elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) {
1141
-				$var = I18N::translate($match[1]);
1142
-			} elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) {
1143
-				$var = I18N::translateContext($match[1], $match[2]);
1144
-			}
1145
-		}
1146
-		// Check if variable is set as a date and reformat the date
1147
-		if (isset($attrs['date'])) {
1148
-			if ($attrs['date'] === "1") {
1149
-				$g   = new Date($var);
1150
-				$var = $g->display();
1151
-			}
1152
-		}
1153
-		$this->current_element->addText($var);
1154
-		$this->text = $var; // Used for title/descriptio
1155
-	}
1156
-
1157
-	/**
1158
-	 * XML <Facts>
1159
-	 *
1160
-	 * @param array $attrs an array of key value pairs for the attributes
1161
-	 */
1162
-	private function factsStartHandler($attrs) {
1163
-		global $WT_TREE;
1164
-
1165
-		$this->process_repeats++;
1166
-		if ($this->process_repeats > 1) {
1167
-			return;
1168
-		}
1169
-
1170
-		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
1171
-		$this->repeats      = array();
1172
-		$this->repeat_bytes = xml_get_current_line_number($this->parser);
1173
-
1174
-		$id    = "";
1175
-		$match = array();
1176
-		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1177
-			$id = $match[1];
1178
-		}
1179
-		$tag = "";
1180
-		if (isset($attrs['ignore'])) {
1181
-			$tag .= $attrs['ignore'];
1182
-		}
1183
-		if (preg_match("/\\$(.+)/", $tag, $match)) {
1184
-			$tag = $this->vars[$match[1]]['id'];
1185
-		}
1186
-
1187
-		$record = GedcomRecord::getInstance($id, $WT_TREE);
1188
-		if (empty($attrs['diff']) && !empty($id)) {
1189
-			$facts = $record->getFacts();
1190
-			Functions::sortFacts($facts);
1191
-			$this->repeats  = array();
1192
-			$nonfacts       = explode(',', $tag);
1193
-			foreach ($facts as $event) {
1194
-				if (!in_array($event->getTag(), $nonfacts)) {
1195
-					$this->repeats[] = $event->getGedcom();
1196
-				}
1197
-			}
1198
-		} else {
1199
-			foreach ($record->getFacts() as $fact) {
1200
-				if ($fact->isPendingAddition() && $fact->getTag() !== 'CHAN') {
1201
-					$this->repeats[] = $fact->getGedcom();
1202
-				}
1203
-			}
1204
-		}
1205
-	}
1206
-
1207
-	/**
1208
-	 * XML </Facts>
1209
-	 */
1210
-	private function factsEndHandler() {
1211
-		global $report;
1212
-
1213
-		$this->process_repeats--;
1214
-		if ($this->process_repeats > 0) {
1215
-			return;
1216
-		}
1217
-
1218
-		// Check if there is anything to repeat
1219
-		if (count($this->repeats) > 0) {
1220
-
1221
-			$line       = xml_get_current_line_number($this->parser) - 1;
1222
-			$lineoffset = 0;
1223
-			foreach ($this->repeats_stack as $rep) {
1224
-				$lineoffset += $rep[1];
1225
-			}
1226
-
1227
-			//-- read the xml from the file
1228
-			$lines = file($report);
1229
-			while ($lineoffset + $this->repeat_bytes > 0 && strpos($lines[$lineoffset + $this->repeat_bytes], '<Facts ') === false) {
1230
-				$lineoffset--;
1231
-			}
1232
-			$lineoffset++;
1233
-			$reportxml = "<tempdoc>\n";
1234
-			$i         = $line + $lineoffset;
1235
-			$line_nr   = $this->repeat_bytes + $lineoffset;
1236
-			while ($line_nr < $i) {
1237
-				$reportxml .= $lines[$line_nr];
1238
-				$line_nr++;
1239
-			}
1240
-			// No need to drag this
1241
-			unset($lines);
1242
-			$reportxml .= "</tempdoc>\n";
1243
-			// Save original values
1244
-			array_push($this->parser_stack, $this->parser);
1245
-			$oldgedrec = $this->gedrec;
1246
-			$count     = count($this->repeats);
1247
-			$i         = 0;
1248
-			while ($i < $count) {
1249
-				$this->gedrec = $this->repeats[$i];
1250
-				$this->fact   = '';
1251
-				$this->desc   = '';
1252
-				if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) {
1253
-					$this->fact = $match[1];
1254
-					if ($this->fact === 'EVEN' || $this->fact === 'FACT') {
1255
-						$tmatch = array();
1256
-						if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) {
1257
-							$this->type = trim($tmatch[1]);
1258
-						} else {
1259
-							$this->type = ' ';
1260
-						}
1261
-					}
1262
-					$this->desc = trim($match[2]);
1263
-					$this->desc .= Functions::getCont(2, $this->gedrec);
1264
-				}
1265
-				$repeat_parser = xml_parser_create();
1266
-				$this->parser  = $repeat_parser;
1267
-				xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
1268
-				xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
1269
-				xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
1270
-				if (!xml_parse($repeat_parser, $reportxml, true)) {
1271
-					throw new \DomainException(sprintf(
1272
-						'FactsEHandler XML error: %s at line %d',
1273
-						xml_error_string(xml_get_error_code($repeat_parser)),
1274
-						xml_get_current_line_number($repeat_parser)
1275
-					));
1276
-				}
1277
-				xml_parser_free($repeat_parser);
1278
-				$i++;
1279
-			}
1280
-			// Restore original values
1281
-			$this->parser = array_pop($this->parser_stack);
1282
-			$this->gedrec = $oldgedrec;
1283
-		}
1284
-		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
1285
-	}
1286
-
1287
-	/**
1288
-	 * Setting upp or changing variables in the XML
1289
-	 * The XML variable name and value is stored in $this->vars
1290
-	 *
1291
-	 * @param array $attrs an array of key value pairs for the attributes
1292
-	 */
1293
-	private function setVarStartHandler($attrs) {
1294
-		if (empty($attrs['name'])) {
1295
-			throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file');
1296
-		}
1297
-
1298
-		$name  = $attrs['name'];
1299
-		$value = $attrs['value'];
1300
-		$match = array();
1301
-		// Current GEDCOM record strings
1302
-		if ($value == "@ID") {
1303
-			if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1304
-				$value = $match[1];
1305
-			}
1306
-		} elseif ($value == "@fact") {
1307
-			$value = $this->fact;
1308
-		} elseif ($value == "@desc") {
1309
-			$value = $this->desc;
1310
-		} elseif ($value == "@generation") {
1311
-			$value = $this->generation;
1312
-		} elseif (preg_match("/@(\w+)/", $value, $match)) {
1313
-			$gmatch = array();
1314
-			if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) {
1315
-				$value = str_replace("@", "", trim($gmatch[1]));
1316
-			}
1317
-		}
1318
-		if (preg_match("/\\$(\w+)/", $name, $match)) {
1319
-			$name = $this->vars["'" . $match[1] . "'"]['id'];
1320
-		}
1321
-		$count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER);
1322
-		$i     = 0;
1323
-		while ($i < $count) {
1324
-			$t     = $this->vars[$match[$i][1]]['id'];
1325
-			$value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1);
1326
-			$i++;
1327
-		}
1328
-		if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) {
1329
-			$value = I18N::number($match[1]);
1330
-		} elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) {
1331
-			$value = I18N::translate($match[1]);
1332
-		} elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) {
1333
-			$value = I18N::translateContext($match[1], $match[2]);
1334
-		}
1335
-		// Arithmetic functions
1336
-		if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) {
1337
-			switch ($match[2]) {
1338
-			case "+":
1339
-				$t     = $match[1] + $match[3];
1340
-				$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1341
-				break;
1342
-			case "-":
1343
-				$t     = $match[1] - $match[3];
1344
-				$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1345
-				break;
1346
-			case "*":
1347
-				$t     = $match[1] * $match[3];
1348
-				$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1349
-				break;
1350
-			case "/":
1351
-				$t     = $match[1] / $match[3];
1352
-				$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1353
-				break;
1354
-			}
1355
-		}
1356
-		if (strpos($value, "@") !== false) {
1357
-			$value = "";
1358
-		}
1359
-		$this->vars[$name]['id'] = $value;
1360
-	}
1361
-
1362
-	/**
1363
-	 * XML <if > start element
1364
-	 *
1365
-	 * @param array $attrs an array of key value pairs for the attributes
1366
-	 */
1367
-	private function ifStartHandler($attrs) {
1368
-		if ($this->process_ifs > 0) {
1369
-			$this->process_ifs++;
1370
-
1371
-			return;
1372
-		}
1373
-
1374
-		$condition = $attrs['condition'];
1375
-		$condition = $this->substituteVars($condition, true);
1376
-		$condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition);
1377
-		// Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT
1378
-		$condition = str_replace("@fact:", $this->fact . ':', $condition);
1379
-		$match     = array();
1380
-		$count     = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER);
1381
-		$i         = 0;
1382
-		while ($i < $count) {
1383
-			$id    = $match[$i][1];
1384
-			$value = '""';
1385
-			if ($id == "ID") {
1386
-				if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1387
-					$value = "'" . $match[1] . "'";
1388
-				}
1389
-			} elseif ($id === "fact") {
1390
-				$value = '"' . $this->fact . '"';
1391
-			} elseif ($id === "desc") {
1392
-				$value = '"' . addslashes($this->desc) . '"';
1393
-			} elseif ($id === "generation") {
1394
-				$value = '"' . $this->generation . '"';
1395
-			} else {
1396
-
1397
-				$temp  = explode(" ", trim($this->gedrec));
1398
-				$level = $temp[0];
1399
-				if ($level == 0) {
1400
-					$level++;
1401
-				}
1402
-				$value = $this->getGedcomValue($id, $level, $this->gedrec);
1403
-				if (empty($value)) {
1404
-					$level++;
1405
-					$value = $this->getGedcomValue($id, $level, $this->gedrec);
1406
-				}
1407
-				$value = preg_replace('/^@(' . WT_REGEX_XREF . ')@$/', '$1', $value);
1408
-				$value = '"' . addslashes($value) . '"';
1409
-			}
1410
-			$condition = str_replace("@$id", $value, $condition);
1411
-			$i++;
1412
-		}
1413
-		$ret = eval("return (bool) ($condition);");
1414
-		if (!$ret) {
1415
-			$this->process_ifs++;
1416
-		}
1417
-	}
1418
-
1419
-	/**
1420
-	 * XML <if /> end element
1421
-	 */
1422
-	private function ifEndHandler() {
1423
-		if ($this->process_ifs > 0) {
1424
-			$this->process_ifs--;
1425
-		}
1426
-	}
1427
-
1428
-	/**
1429
-	 * XML <Footnote > start element
1430
-	 * Collect the Footnote links
1431
-	 * GEDCOM Records that are protected by Privacy setting will be ignore
1432
-	 *
1433
-	 * @param array $attrs an array of key value pairs for the attributes
1434
-	 */
1435
-	private function footnoteStartHandler($attrs) {
1436
-		global $WT_TREE;
1437
-
1438
-		$id = "";
1439
-		if (preg_match("/[0-9] (.+) @(.+)@/", $this->gedrec, $match)) {
1440
-			$id = $match[2];
1441
-		}
1442
-		$record = GedcomRecord::getInstance($id, $WT_TREE);
1443
-		if ($record && $record->canShow()) {
1444
-			array_push($this->print_data_stack, $this->print_data);
1445
-			$this->print_data = true;
1446
-			$style            = "";
1447
-			if (!empty($attrs['style'])) {
1448
-				$style = $attrs['style'];
1449
-			}
1450
-			$this->footnote_element = $this->current_element;
1451
-			$this->current_element  = $this->report_root->createFootnote($style);
1452
-		} else {
1453
-			$this->print_data       = false;
1454
-			$this->process_footnote = false;
1455
-		}
1456
-	}
1457
-
1458
-	/**
1459
-	 * XML <Footnote /> end element
1460
-	 * Print the collected Footnote data
1461
-	 */
1462
-	private function footnoteEndHandler() {
1463
-		if ($this->process_footnote) {
1464
-			$this->print_data = array_pop($this->print_data_stack);
1465
-			$temp             = trim($this->current_element->getValue());
1466
-			if (strlen($temp) > 3) {
1467
-				$this->wt_report->addElement($this->current_element);
1468
-			}
1469
-			$this->current_element = $this->footnote_element;
1470
-		} else {
1471
-			$this->process_footnote = true;
1472
-		}
1473
-	}
1474
-
1475
-	/**
1476
-	 * XML <FootnoteTexts /> element
1477
-	 */
1478
-	private function footnoteTextsStartHandler() {
1479
-		$temp = "footnotetexts";
1480
-		$this->wt_report->addElement($temp);
1481
-	}
1482
-
1483
-	/**
1484
-	 * XML <AgeAtDeath /> element handler
1485
-	 */
1486
-	private function ageAtDeathStartHandler() {
1487
-		// This duplicates functionality in FunctionsPrint::format_fact_date()
1488
-		global $factrec, $WT_TREE;
1489
-
1490
-		$match = array();
1491
-		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1492
-			$person = Individual::getInstance($match[1], $WT_TREE);
1493
-			// Recorded age
1494
-			if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
1495
-				$fact_age = $match[1];
1496
-			} else {
1497
-				$fact_age = '';
1498
-			}
1499
-			if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
1500
-				$husb_age = $match[1];
1501
-			} else {
1502
-				$husb_age = '';
1503
-			}
1504
-			if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
1505
-				$wife_age = $match[1];
1506
-			} else {
1507
-				$wife_age = '';
1508
-			}
1509
-
1510
-			// Calculated age
1511
-			$birth_date = $person->getBirthDate();
1512
-			// Can't use getDeathDate(), as this also gives BURI/CREM events, which
1513
-			// wouldn't give the correct "days after death" result for people with
1514
-			// no DEAT.
1515
-			$death_event = $person->getFirstFact('DEAT');
1516
-			if ($death_event) {
1517
-				$death_date = $death_event->getDate();
1518
-			} else {
1519
-				$death_date = new Date('');
1520
-			}
1521
-			$value = '';
1522
-			if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) {
1523
-				$age = Date::getAgeGedcom($birth_date, $death_date);
1524
-				// Only show calculated age if it differs from recorded age
1525
-				if ($age != '' && $age != "0d") {
1526
-					if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $person->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $person->getSex() == 'F' && $wife_age != $age
1527
-					) {
1528
-						$value  = FunctionsDate::getAgeAtEvent($age);
1529
-						$abbrev = substr($value, 0, strpos($value, ' ') + 5);
1530
-						if ($value !== $abbrev) {
1531
-							$value = $abbrev . '.';
1532
-						}
1533
-					}
1534
-				}
1535
-			}
1536
-			$this->current_element->addText($value);
1537
-		}
1538
-	}
1539
-
1540
-	/**
1541
-	 * XML element Forced line break handler - HTML code
1542
-	 */
1543
-	private function brStartHandler() {
1544
-		if ($this->print_data && $this->process_gedcoms === 0) {
1545
-			$this->current_element->addText('<br>');
1546
-		}
1547
-	}
1548
-
1549
-	/**
1550
-	 * XML <sp />element Forced space handler
1551
-	 */
1552
-	private function spStartHandler() {
1553
-		if ($this->print_data && $this->process_gedcoms === 0) {
1554
-			$this->current_element->addText(' ');
1555
-		}
1556
-	}
1557
-
1558
-	/**
1559
-	 * XML <HighlightedImage/>
1560
-	 *
1561
-	 * @param array $attrs an array of key value pairs for the attributes
1562
-	 */
1563
-	private function highlightedImageStartHandler($attrs) {
1564
-		global $WT_TREE;
1565
-
1566
-		$id    = '';
1567
-		$match = array();
1568
-		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1569
-			$id = $match[1];
1570
-		}
1571
-
1572
-		// mixed Position the top corner of this box on the page. the default is the current position
1573
-		$top = '.';
1574
-		if (isset($attrs['top'])) {
1575
-			if ($attrs['top'] === '0') {
1576
-				$top = 0;
1577
-			} elseif ($attrs['top'] === '.') {
1578
-				$top = '.';
1579
-			} elseif (!empty($attrs['top'])) {
1580
-				$top = (int) $attrs['top'];
1581
-			}
1582
-		}
1583
-
1584
-		// mixed Position the left corner of this box on the page. the default is the current position
1585
-		$left = '.';
1586
-		if (isset($attrs['left'])) {
1587
-			if ($attrs['left'] === '0') {
1588
-				$left = 0;
1589
-			} elseif ($attrs['left'] === '.') {
1590
-				$left = '.';
1591
-			} elseif (!empty($attrs['left'])) {
1592
-				$left = (int) $attrs['left'];
1593
-			}
1594
-		}
1595
-
1596
-		// string Align the image in left, center, right
1597
-		$align = '';
1598
-		if (!empty($attrs['align'])) {
1599
-			$align = $attrs['align'];
1600
-		}
1601
-
1602
-		// string Next Line should be T:next to the image, N:next line
1603
-		$ln = '';
1604
-		if (!empty($attrs['ln'])) {
1605
-			$ln = $attrs['ln'];
1606
-		}
1607
-
1608
-		$width  = 0;
1609
-		$height = 0;
1610
-		if (!empty($attrs['width'])) {
1611
-			$width = (int) $attrs['width'];
1612
-		}
1613
-		if (!empty($attrs['height'])) {
1614
-			$height = (int) $attrs['height'];
1615
-		}
1616
-
1617
-		$person      = Individual::getInstance($id, $WT_TREE);
1618
-		$mediaobject = $person->findHighlightedMedia();
1619
-		if ($mediaobject) {
1620
-			$attributes = $mediaobject->getImageAttributes('thumb');
1621
-			if (in_array(
1622
-					$attributes['ext'],
1623
-					array(
1624
-						'GIF',
1625
-						'JPG',
1626
-						'PNG',
1627
-						'SWF',
1628
-						'PSD',
1629
-						'BMP',
1630
-						'TIFF',
1631
-						'TIFF',
1632
-						'JPC',
1633
-						'JP2',
1634
-						'JPX',
1635
-						'JB2',
1636
-						'SWC',
1637
-						'IFF',
1638
-						'WBMP',
1639
-						'XBM',
1640
-					)
1641
-				) && $mediaobject->canShow() && $mediaobject->fileExists('thumb')
1642
-			) {
1643
-				if ($width > 0 && $height == 0) {
1644
-					$perc   = $width / $attributes['adjW'];
1645
-					$height = round($attributes['adjH'] * $perc);
1646
-				} elseif ($height > 0 && $width == 0) {
1647
-					$perc  = $height / $attributes['adjH'];
1648
-					$width = round($attributes['adjW'] * $perc);
1649
-				} else {
1650
-					$width  = $attributes['adjW'];
1651
-					$height = $attributes['adjH'];
1652
-				}
1653
-				$image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln);
1654
-				$this->wt_report->addElement($image);
1655
-			}
1656
-		}
1657
-	}
1658
-
1659
-	/**
1660
-	 * XML <Image/>
1661
-	 *
1662
-	 * @param array $attrs an array of key value pairs for the attributes
1663
-	 */
1664
-	private function imageStartHandler($attrs) {
1665
-		global $WT_TREE;
1666
-
1667
-		// mixed Position the top corner of this box on the page. the default is the current position
1668
-		$top = '.';
1669
-		if (isset($attrs['top'])) {
1670
-			if ($attrs['top'] === "0") {
1671
-				$top = 0;
1672
-			} elseif ($attrs['top'] === '.') {
1673
-				$top = '.';
1674
-			} elseif (!empty($attrs['top'])) {
1675
-				$top = (int) $attrs['top'];
1676
-			}
1677
-		}
1678
-
1679
-		// mixed Position the left corner of this box on the page. the default is the current position
1680
-		$left = '.';
1681
-		if (isset($attrs['left'])) {
1682
-			if ($attrs['left'] === '0') {
1683
-				$left = 0;
1684
-			} elseif ($attrs['left'] === '.') {
1685
-				$left = '.';
1686
-			} elseif (!empty($attrs['left'])) {
1687
-				$left = (int) $attrs['left'];
1688
-			}
1689
-		}
1690
-
1691
-		// string Align the image in left, center, right
1692
-		$align = '';
1693
-		if (!empty($attrs['align'])) {
1694
-			$align = $attrs['align'];
1695
-		}
1696
-
1697
-		// string Next Line should be T:next to the image, N:next line
1698
-		$ln = 'T';
1699
-		if (!empty($attrs['ln'])) {
1700
-			$ln = $attrs['ln'];
1701
-		}
1702
-
1703
-		$width  = 0;
1704
-		$height = 0;
1705
-		if (!empty($attrs['width'])) {
1706
-			$width = (int) $attrs['width'];
1707
-		}
1708
-		if (!empty($attrs['height'])) {
1709
-			$height = (int) $attrs['height'];
1710
-		}
1711
-
1712
-		$file = '';
1713
-		if (!empty($attrs['file'])) {
1714
-			$file = $attrs['file'];
1715
-		}
1716
-		if ($file == "@FILE") {
1717
-			$match = array();
1718
-			if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) {
1719
-				$mediaobject = Media::getInstance($match[1], $WT_TREE);
1720
-				$attributes  = $mediaobject->getImageAttributes('thumb');
1721
-				if (in_array(
1722
-						$attributes['ext'],
1723
-						array(
1724
-							'GIF',
1725
-							'JPG',
1726
-							'PNG',
1727
-							'SWF',
1728
-							'PSD',
1729
-							'BMP',
1730
-							'TIFF',
1731
-							'TIFF',
1732
-							'JPC',
1733
-							'JP2',
1734
-							'JPX',
1735
-							'JB2',
1736
-							'SWC',
1737
-							'IFF',
1738
-							'WBMP',
1739
-							'XBM',
1740
-						)
1741
-					) && $mediaobject->canShow() && $mediaobject->fileExists('thumb')
1742
-				) {
1743
-					if ($width > 0 && $height == 0) {
1744
-						$perc   = $width / $attributes['adjW'];
1745
-						$height = round($attributes['adjH'] * $perc);
1746
-					} elseif ($height > 0 && $width == 0) {
1747
-						$perc  = $height / $attributes['adjH'];
1748
-						$width = round($attributes['adjW'] * $perc);
1749
-					} else {
1750
-						$width  = $attributes['adjW'];
1751
-						$height = $attributes['adjH'];
1752
-					}
1753
-					$image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln);
1754
-					$this->wt_report->addElement($image);
1755
-				}
1756
-			}
1757
-		} else {
1758
-			if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) {
1759
-				$size = getimagesize($file);
1760
-				if ($width > 0 && $height == 0) {
1761
-					$perc   = $width / $size[0];
1762
-					$height = round($size[1] * $perc);
1763
-				} elseif ($height > 0 && $width == 0) {
1764
-					$perc  = $height / $size[1];
1765
-					$width = round($size[0] * $perc);
1766
-				} else {
1767
-					$width  = $size[0];
1768
-					$height = $size[1];
1769
-				}
1770
-				$image = $this->report_root->createImage($file, $left, $top, $width, $height, $align, $ln);
1771
-				$this->wt_report->addElement($image);
1772
-			}
1773
-		}
1774
-	}
1775
-
1776
-	/**
1777
-	 * XML <Line> element handler
1778
-	 *
1779
-	 * @param array $attrs an array of key value pairs for the attributes
1780
-	 */
1781
-	private function lineStartHandler($attrs) {
1782
-		// Start horizontal position, current position (default)
1783
-		$x1 = ".";
1784
-		if (isset($attrs['x1'])) {
1785
-			if ($attrs['x1'] === "0") {
1786
-				$x1 = 0;
1787
-			} elseif ($attrs['x1'] === ".") {
1788
-				$x1 = ".";
1789
-			} elseif (!empty($attrs['x1'])) {
1790
-				$x1 = (int) $attrs['x1'];
1791
-			}
1792
-		}
1793
-		// Start vertical position, current position (default)
1794
-		$y1 = ".";
1795
-		if (isset($attrs['y1'])) {
1796
-			if ($attrs['y1'] === "0") {
1797
-				$y1 = 0;
1798
-			} elseif ($attrs['y1'] === ".") {
1799
-				$y1 = ".";
1800
-			} elseif (!empty($attrs['y1'])) {
1801
-				$y1 = (int) $attrs['y1'];
1802
-			}
1803
-		}
1804
-		// End horizontal position, maximum width (default)
1805
-		$x2 = ".";
1806
-		if (isset($attrs['x2'])) {
1807
-			if ($attrs['x2'] === "0") {
1808
-				$x2 = 0;
1809
-			} elseif ($attrs['x2'] === ".") {
1810
-				$x2 = ".";
1811
-			} elseif (!empty($attrs['x2'])) {
1812
-				$x2 = (int) $attrs['x2'];
1813
-			}
1814
-		}
1815
-		// End vertical position
1816
-		$y2 = ".";
1817
-		if (isset($attrs['y2'])) {
1818
-			if ($attrs['y2'] === "0") {
1819
-				$y2 = 0;
1820
-			} elseif ($attrs['y2'] === ".") {
1821
-				$y2 = ".";
1822
-			} elseif (!empty($attrs['y2'])) {
1823
-				$y2 = (int) $attrs['y2'];
1824
-			}
1825
-		}
1826
-
1827
-		$line = $this->report_root->createLine($x1, $y1, $x2, $y2);
1828
-		$this->wt_report->addElement($line);
1829
-	}
1830
-
1831
-	/**
1832
-	 * XML <List>
1833
-	 *
1834
-	 * @param array $attrs an array of key value pairs for the attributes
1835
-	 */
1836
-	private function listStartHandler($attrs) {
1837
-		global $WT_TREE;
1838
-
1839
-		$this->process_repeats++;
1840
-		if ($this->process_repeats > 1) {
1841
-			return;
1842
-		}
1843
-
1844
-		$match = array();
1845
-		if (isset($attrs['sortby'])) {
1846
-			$sortby = $attrs['sortby'];
1847
-			if (preg_match("/\\$(\w+)/", $sortby, $match)) {
1848
-				$sortby = $this->vars[$match[1]]['id'];
1849
-				$sortby = trim($sortby);
1850
-			}
1851
-		} else {
1852
-			$sortby = "NAME";
1853
-		}
1854
-
1855
-		if (isset($attrs['list'])) {
1856
-			$listname = $attrs['list'];
1857
-		} else {
1858
-			$listname = "individual";
1859
-		}
1860
-		// Some filters/sorts can be applied using SQL, while others require PHP
1861
-		switch ($listname) {
1862
-		case "pending":
1863
-			$rows = Database::prepare(
1864
-				"SELECT xref, CASE new_gedcom WHEN '' THEN old_gedcom ELSE new_gedcom END AS gedcom" .
1865
-				" FROM `##change`" . " WHERE (xref, change_id) IN (" .
1866
-				"  SELECT xref, MAX(change_id)" .
1867
-				"  FROM `##change`" .
1868
-				"  WHERE status = 'pending' AND gedcom_id = :tree_id" .
1869
-				"  GROUP BY xref" .
1870
-				" )"
1871
-			)->execute(array(
1872
-				'tree_id' => $WT_TREE->getTreeId(),
1873
-			))->fetchAll();
1874
-			$this->list = array();
1875
-			foreach ($rows as $row) {
1876
-				$this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom);
1877
-			}
1878
-			break;
1879
-		case 'individual':
1880
-			$sql_select   = "SELECT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` ";
1881
-			$sql_join     = "";
1882
-			$sql_where    = " WHERE i_file = :tree_id";
1883
-			$sql_order_by = "";
1884
-			$sql_params   = array('tree_id' => $WT_TREE->getTreeId());
1885
-			foreach ($attrs as $attr => $value) {
1886
-				if (strpos($attr, 'filter') === 0 && $value) {
1887
-					$value = $this->substituteVars($value, false);
1888
-					// Convert the various filters into SQL
1889
-					if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
1890
-						$sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)";
1891
-						$sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
1892
-						$sql_params[$attr . 'fact'] = $match[1];
1893
-						$date                       = new Date($match[3]);
1894
-						if ($match[2] == "LTE") {
1895
-							$sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
1896
-							$sql_params[$attr . 'date'] = $date->maximumJulianDay();
1897
-						} else {
1898
-							$sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
1899
-							$sql_params[$attr . 'date'] = $date->minimumJulianDay();
1900
-						}
1901
-						if ($sortby == $match[1]) {
1902
-							$sortby = "";
1903
-							$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
1904
-						}
1905
-						unset($attrs[$attr]); // This filter has been fully processed
1906
-					} elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) {
1907
-						// Do nothing, unless you have to
1908
-						if ($match[1] != '' || $sortby == 'NAME') {
1909
-							$sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)";
1910
-							// Search the DB only if there is any name supplied
1911
-							if ($match[1] != "") {
1912
-								$names = explode(" ", $match[1]);
1913
-								foreach ($names as $n => $name) {
1914
-									$sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
1915
-									$sql_params[$attr . 'name' . $n] = $name;
1916
-								}
1917
-							}
1918
-							// Let the DB do the name sorting even when no name was entered
1919
-							if ($sortby == "NAME") {
1920
-								$sortby = "";
1921
-								$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
1922
-							}
1923
-						}
1924
-						unset($attrs[$attr]); // This filter has been fully processed
1925
-					} elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) {
1926
-						$sql_where .= " AND i_gedcom REGEXP :{$attr}gedcom";
1927
-						// PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
1928
-						$sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]);
1929
-						unset($attrs[$attr]); // This filter has been fully processed
1930
-					} elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
1931
-						$sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file = i_file)";
1932
-						$sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file = {$attr}b.pl_file AND {$attr}b.pl_p_id = {$attr}a.p_id AND {$attr}b.pl_gid = i_id)";
1933
-						$sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
1934
-						$sql_params[$attr . 'place'] = $match[1];
1935
-						// Don't unset this filter. This is just initial filtering
1936
-					} elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) {
1937
-						$sql_where .= " AND i_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
1938
-						$sql_params[$attr . 'contains1'] = $match[1];
1939
-						$sql_params[$attr . 'contains2'] = $match[2];
1940
-						$sql_params[$attr . 'contains3'] = $match[3];
1941
-						// Don't unset this filter. This is just initial filtering
1942
-					}
1943
-				}
1944
-			}
1945
-
1946
-			$this->list = array();
1947
-			$rows       = Database::prepare(
1948
-				$sql_select . $sql_join . $sql_where . $sql_order_by
1949
-			)->execute($sql_params)->fetchAll();
1950
-
1951
-			foreach ($rows as $row) {
1952
-				$this->list[$row->xref] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
1953
-			}
1954
-			break;
1955
-
1956
-		case 'family':
1957
-			$sql_select   = "SELECT f_id AS xref, f_gedcom AS gedcom FROM `##families`";
1958
-			$sql_join     = "";
1959
-			$sql_where    = " WHERE f_file = :tree_id";
1960
-			$sql_order_by = "";
1961
-			$sql_params   = array('tree_id' => $WT_TREE->getTreeId());
1962
-			foreach ($attrs as $attr => $value) {
1963
-				if (strpos($attr, 'filter') === 0 && $value) {
1964
-					$value = $this->substituteVars($value, false);
1965
-					// Convert the various filters into SQL
1966
-					if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
1967
-						$sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)";
1968
-						$sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
1969
-						$sql_params[$attr . 'fact'] = $match[1];
1970
-						$date                       = new Date($match[3]);
1971
-						if ($match[2] == "LTE") {
1972
-							$sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
1973
-							$sql_params[$attr . 'date'] = $date->maximumJulianDay();
1974
-						} else {
1975
-							$sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
1976
-							$sql_params[$attr . 'date'] = $date->minimumJulianDay();
1977
-						}
1978
-						if ($sortby == $match[1]) {
1979
-							$sortby = "";
1980
-							$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
1981
-						}
1982
-						unset($attrs[$attr]); // This filter has been fully processed
1983
-					} elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) {
1984
-						$sql_where .= " AND f_gedcom REGEXP :{$attr}gedcom";
1985
-						// PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
1986
-						$sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]);
1987
-						unset($attrs[$attr]); // This filter has been fully processed
1988
-					} elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) {
1989
-						// Do nothing, unless you have to
1990
-						if ($match[1] != '' || $sortby == 'NAME') {
1991
-							$sql_join .= " JOIN `##name` AS {$attr} ON n_file = f_file AND n_id IN (f_husb, f_wife)";
1992
-							// Search the DB only if there is any name supplied
1993
-							if ($match[1] != "") {
1994
-								$names = explode(" ", $match[1]);
1995
-								foreach ($names as $n => $name) {
1996
-									$sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
1997
-									$sql_params[$attr . 'name' . $n] = $name;
1998
-								}
1999
-							}
2000
-							// Let the DB do the name sorting even when no name was entered
2001
-							if ($sortby == "NAME") {
2002
-								$sortby = "";
2003
-								$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
2004
-							}
2005
-						}
2006
-						unset($attrs[$attr]); // This filter has been fully processed
2007
-
2008
-					} elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
2009
-						$sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)";
2010
-						$sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file={$attr}b.pl_file AND {$attr}b.pl_p_id={$attr}a.p_id AND {$attr}b.pl_gid=f_id)";
2011
-						$sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
2012
-						$sql_params[$attr . 'place'] = $match[1];
2013
-						// Don't unset this filter. This is just initial filtering
2014
-					} elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) {
2015
-						$sql_where .= " AND f_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
2016
-						$sql_params[$attr . 'contains1'] = $match[1];
2017
-						$sql_params[$attr . 'contains2'] = $match[2];
2018
-						$sql_params[$attr . 'contains3'] = $match[3];
2019
-						// Don't unset this filter. This is just initial filtering
2020
-					}
2021
-				}
2022
-			}
2023
-
2024
-			$this->list = array();
2025
-			$rows       = Database::prepare(
2026
-				$sql_select . $sql_join . $sql_where . $sql_order_by
2027
-			)->execute($sql_params)->fetchAll();
2028
-
2029
-			foreach ($rows as $row) {
2030
-				$this->list[$row->xref] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom);
2031
-			}
2032
-			break;
2033
-
2034
-		default:
2035
-			throw new \DomainException('Invalid list name: ' . $listname);
2036
-		}
2037
-
2038
-		$filters  = array();
2039
-		$filters2 = array();
2040
-		if (isset($attrs['filter1']) && count($this->list) > 0) {
2041
-			foreach ($attrs as $key => $value) {
2042
-				if (preg_match("/filter(\d)/", $key)) {
2043
-					$condition = $value;
2044
-					if (preg_match("/@(\w+)/", $condition, $match)) {
2045
-						$id    = $match[1];
2046
-						$value = "''";
2047
-						if ($id == "ID") {
2048
-							if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
2049
-								$value = "'" . $match[1] . "'";
2050
-							}
2051
-						} elseif ($id == "fact") {
2052
-							$value = "'" . $this->fact . "'";
2053
-						} elseif ($id == "desc") {
2054
-							$value = "'" . $this->desc . "'";
2055
-						} else {
2056
-							if (preg_match("/\d $id (.+)/", $this->gedrec, $match)) {
2057
-								$value = "'" . str_replace("@", "", trim($match[1])) . "'";
2058
-							}
2059
-						}
2060
-						$condition = preg_replace("/@$id/", $value, $condition);
2061
-					}
2062
-					//-- handle regular expressions
2063
-					if (preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match)) {
2064
-						$tag  = trim($match[1]);
2065
-						$expr = trim($match[2]);
2066
-						$val  = trim($match[3]);
2067
-						if (preg_match("/\\$(\w+)/", $val, $match)) {
2068
-							$val = $this->vars[$match[1]]['id'];
2069
-							$val = trim($val);
2070
-						}
2071
-						if ($val) {
2072
-							$searchstr = "";
2073
-							$tags      = explode(":", $tag);
2074
-							//-- only limit to a level number if we are specifically looking at a level
2075
-							if (count($tags) > 1) {
2076
-								$level = 1;
2077
-								foreach ($tags as $t) {
2078
-									if (!empty($searchstr)) {
2079
-										$searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n";
2080
-									}
2081
-									//-- search for both EMAIL and _EMAIL... silly double gedcom standard
2082
-									if ($t == "EMAIL" || $t == "_EMAIL") {
2083
-										$t = "_?EMAIL";
2084
-									}
2085
-									$searchstr .= $level . " " . $t;
2086
-									$level++;
2087
-								}
2088
-							} else {
2089
-								if ($tag == "EMAIL" || $tag == "_EMAIL") {
2090
-									$tag = "_?EMAIL";
2091
-								}
2092
-								$t         = $tag;
2093
-								$searchstr = "1 " . $tag;
2094
-							}
2095
-							switch ($expr) {
2096
-							case "CONTAINS":
2097
-								if ($t == "PLAC") {
2098
-									$searchstr .= "[^\n]*[, ]*" . $val;
2099
-								} else {
2100
-									$searchstr .= "[^\n]*" . $val;
2101
-								}
2102
-								$filters[] = $searchstr;
2103
-								break;
2104
-							default:
2105
-								$filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val);
2106
-								break;
2107
-							}
2108
-						}
2109
-					}
2110
-				}
2111
-			}
2112
-		}
2113
-		//-- apply other filters to the list that could not be added to the search string
2114
-		if ($filters) {
2115
-			foreach ($this->list as $key => $record) {
2116
-				foreach ($filters as $filter) {
2117
-					if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) {
2118
-						unset($this->list[$key]);
2119
-						break;
2120
-					}
2121
-				}
2122
-			}
2123
-		}
2124
-		if ($filters2) {
2125
-			$mylist = array();
2126
-			foreach ($this->list as $indi) {
2127
-				$key  = $indi->getXref();
2128
-				$grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE));
2129
-				$keep = true;
2130
-				foreach ($filters2 as $filter) {
2131
-					if ($keep) {
2132
-						$tag  = $filter['tag'];
2133
-						$expr = $filter['expr'];
2134
-						$val  = $filter['val'];
2135
-						if ($val == "''") {
2136
-							$val = "";
2137
-						}
2138
-						$tags = explode(":", $tag);
2139
-						$t    = end($tags);
2140
-						$v    = $this->getGedcomValue($tag, 1, $grec);
2141
-						//-- check for EMAIL and _EMAIL (silly double gedcom standard :P)
2142
-						if ($t == "EMAIL" && empty($v)) {
2143
-							$tag  = str_replace("EMAIL", "_EMAIL", $tag);
2144
-							$tags = explode(":", $tag);
2145
-							$t    = end($tags);
2146
-							$v    = Functions::getSubRecord(1, $tag, $grec);
2147
-						}
2148
-
2149
-						switch ($expr) {
2150
-						case "GTE":
2151
-							if ($t == "DATE") {
2152
-								$date1 = new Date($v);
2153
-								$date2 = new Date($val);
2154
-								$keep  = (Date::compare($date1, $date2) >= 0);
2155
-							} elseif ($val >= $v) {
2156
-								$keep = true;
2157
-							}
2158
-							break;
2159
-						case "LTE":
2160
-							if ($t == "DATE") {
2161
-								$date1 = new Date($v);
2162
-								$date2 = new Date($val);
2163
-								$keep  = (Date::compare($date1, $date2) <= 0);
2164
-							} elseif ($val >= $v) {
2165
-								$keep = true;
2166
-							}
2167
-							break;
2168
-						default:
2169
-							if ($v == $val) {
2170
-								$keep = true;
2171
-							} else {
2172
-								$keep = false;
2173
-							}
2174
-							break;
2175
-						}
2176
-					}
2177
-				}
2178
-				if ($keep) {
2179
-					$mylist[$key] = $indi;
2180
-				}
2181
-			}
2182
-			$this->list = $mylist;
2183
-		}
2184
-
2185
-		switch ($sortby) {
2186
-		case 'NAME':
2187
-			uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare');
2188
-			break;
2189
-		case 'CHAN':
2190
-			uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) {
2191
-				return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true);
2192
-			});
2193
-			break;
2194
-		case 'BIRT:DATE':
2195
-			uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate');
2196
-			break;
2197
-		case 'DEAT:DATE':
2198
-			uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate');
2199
-			break;
2200
-		case 'MARR:DATE':
2201
-			uasort($this->list, '\Fisharebest\Webtrees\Family::compareMarrDate');
2202
-			break;
2203
-		default:
2204
-			// unsorted or already sorted by SQL
2205
-			break;
2206
-		}
2207
-
2208
-		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
2209
-		$this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
2210
-	}
2211
-
2212
-	/**
2213
-	 * XML <List>
2214
-	 */
2215
-	private function listEndHandler() {
2216
-		global $report;
2217
-
2218
-		$this->process_repeats--;
2219
-		if ($this->process_repeats > 0) {
2220
-			return;
2221
-		}
2222
-
2223
-		// Check if there is any list
2224
-		if (count($this->list) > 0) {
2225
-			$lineoffset = 0;
2226
-			foreach ($this->repeats_stack as $rep) {
2227
-				$lineoffset += $rep[1];
2228
-			}
2229
-			//-- read the xml from the file
2230
-			$lines = file($report);
2231
-			while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<List") === false) && (($lineoffset + $this->repeat_bytes) > 0)) {
2232
-				$lineoffset--;
2233
-			}
2234
-			$lineoffset++;
2235
-			$reportxml = "<tempdoc>\n";
2236
-			$line_nr   = $lineoffset + $this->repeat_bytes;
2237
-			// List Level counter
2238
-			$count = 1;
2239
-			while (0 < $count) {
2240
-				if (strpos($lines[$line_nr], "<List") !== false) {
2241
-					$count++;
2242
-				} elseif (strpos($lines[$line_nr], "</List") !== false) {
2243
-					$count--;
2244
-				}
2245
-				if (0 < $count) {
2246
-					$reportxml .= $lines[$line_nr];
2247
-				}
2248
-				$line_nr++;
2249
-			}
2250
-			// No need to drag this
2251
-			unset($lines);
2252
-			$reportxml .= "</tempdoc>";
2253
-			// Save original values
2254
-			array_push($this->parser_stack, $this->parser);
2255
-			$oldgedrec = $this->gedrec;
2256
-
2257
-			$this->list_total   = count($this->list);
2258
-			$this->list_private = 0;
2259
-			foreach ($this->list as $record) {
2260
-				if ($record->canShow()) {
2261
-					$this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree()));
2262
-					//-- start the sax parser
2263
-					$repeat_parser = xml_parser_create();
2264
-					$this->parser  = $repeat_parser;
2265
-					xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
2266
-					xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
2267
-					xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
2268
-					if (!xml_parse($repeat_parser, $reportxml, true)) {
2269
-						throw new \DomainException(sprintf(
2270
-							'ListEHandler XML error: %s at line %d',
2271
-							xml_error_string(xml_get_error_code($repeat_parser)),
2272
-							xml_get_current_line_number($repeat_parser)
2273
-						));
2274
-					}
2275
-					xml_parser_free($repeat_parser);
2276
-				} else {
2277
-					$this->list_private++;
2278
-				}
2279
-			}
2280
-			$this->list   = array();
2281
-			$this->parser = array_pop($this->parser_stack);
2282
-			$this->gedrec = $oldgedrec;
2283
-		}
2284
-		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
2285
-	}
2286
-
2287
-	/**
2288
-	 * XML <ListTotal> element handler
2289
-	 *
2290
-	 * Prints the total number of records in a list
2291
-	 * The total number is collected from
2292
-	 * List and Relatives
2293
-	 */
2294
-	private function listTotalStartHandler() {
2295
-		if ($this->list_private == 0) {
2296
-			$this->current_element->addText($this->list_total);
2297
-		} else {
2298
-			$this->current_element->addText(($this->list_total - $this->list_private) . " / " . $this->list_total);
2299
-		}
2300
-	}
2301
-
2302
-	/**
2303
-	 * XML <Relatives>
2304
-	 *
2305
-	 * @param array $attrs an array of key value pairs for the attributes
2306
-	 */
2307
-	private function relativesStartHandler($attrs) {
2308
-		global $WT_TREE;
2309
-
2310
-		$this->process_repeats++;
2311
-		if ($this->process_repeats > 1) {
2312
-			return;
2313
-		}
2314
-
2315
-		$sortby = "NAME";
2316
-		if (isset($attrs['sortby'])) {
2317
-			$sortby = $attrs['sortby'];
2318
-		}
2319
-		$match = array();
2320
-		if (preg_match("/\\$(\w+)/", $sortby, $match)) {
2321
-			$sortby = $this->vars[$match[1]]['id'];
2322
-			$sortby = trim($sortby);
2323
-		}
2324
-
2325
-		$maxgen = -1;
2326
-		if (isset($attrs['maxgen'])) {
2327
-			$maxgen = $attrs['maxgen'];
2328
-		}
2329
-		if ($maxgen == "*") {
2330
-			$maxgen = -1;
2331
-		}
2332
-
2333
-		$group = "child-family";
2334
-		if (isset($attrs['group'])) {
2335
-			$group = $attrs['group'];
2336
-		}
2337
-		if (preg_match("/\\$(\w+)/", $group, $match)) {
2338
-			$group = $this->vars[$match[1]]['id'];
2339
-			$group = trim($group);
2340
-		}
2341
-
2342
-		$id = "";
2343
-		if (isset($attrs['id'])) {
2344
-			$id = $attrs['id'];
2345
-		}
2346
-		if (preg_match("/\\$(\w+)/", $id, $match)) {
2347
-			$id = $this->vars[$match[1]]['id'];
2348
-			$id = trim($id);
2349
-		}
2350
-
2351
-		$this->list = array();
2352
-		$person     = Individual::getInstance($id, $WT_TREE);
2353
-		if (!empty($person)) {
2354
-			$this->list[$id] = $person;
2355
-			switch ($group) {
2356
-			case "child-family":
2357
-				foreach ($person->getChildFamilies() as $family) {
2358
-					$husband = $family->getHusband();
2359
-					$wife    = $family->getWife();
2360
-					if (!empty($husband)) {
2361
-						$this->list[$husband->getXref()] = $husband;
2362
-					}
2363
-					if (!empty($wife)) {
2364
-						$this->list[$wife->getXref()] = $wife;
2365
-					}
2366
-					$children = $family->getChildren();
2367
-					foreach ($children as $child) {
2368
-						if (!empty($child)) {
2369
-							$this->list[$child->getXref()] = $child;
2370
-						}
2371
-					}
2372
-				}
2373
-				break;
2374
-			case "spouse-family":
2375
-				foreach ($person->getSpouseFamilies() as $family) {
2376
-					$husband = $family->getHusband();
2377
-					$wife    = $family->getWife();
2378
-					if (!empty($husband)) {
2379
-						$this->list[$husband->getXref()] = $husband;
2380
-					}
2381
-					if (!empty($wife)) {
2382
-						$this->list[$wife->getXref()] = $wife;
2383
-					}
2384
-					$children = $family->getChildren();
2385
-					foreach ($children as $child) {
2386
-						if (!empty($child)) {
2387
-							$this->list[$child->getXref()] = $child;
2388
-						}
2389
-					}
2390
-				}
2391
-				break;
2392
-			case "direct-ancestors":
2393
-				$this->addAncestors($this->list, $id, false, $maxgen);
2394
-				break;
2395
-			case "ancestors":
2396
-				$this->addAncestors($this->list, $id, true, $maxgen);
2397
-				break;
2398
-			case "descendants":
2399
-				$this->list[$id]->generation = 1;
2400
-				$this->addDescendancy($this->list, $id, false, $maxgen);
2401
-				break;
2402
-			case "all":
2403
-				$this->addAncestors($this->list, $id, true, $maxgen);
2404
-				$this->addDescendancy($this->list, $id, true, $maxgen);
2405
-				break;
2406
-			}
2407
-		}
2408
-
2409
-		switch ($sortby) {
2410
-		case 'NAME':
2411
-			uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare');
2412
-			break;
2413
-		case 'BIRT:DATE':
2414
-			uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate');
2415
-			break;
2416
-		case 'DEAT:DATE':
2417
-			uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate');
2418
-			break;
2419
-		case 'generation':
2420
-			$newarray = array();
2421
-			reset($this->list);
2422
-			$genCounter = 1;
2423
-			while (count($newarray) < count($this->list)) {
2424
-				foreach ($this->list as $key => $value) {
2425
-					$this->generation = $value->generation;
2426
-					if ($this->generation == $genCounter) {
2427
-						$newarray[$key]             = new \stdClass;
2428
-						$newarray[$key]->generation = $this->generation;
2429
-					}
2430
-				}
2431
-				$genCounter++;
2432
-			}
2433
-			$this->list = $newarray;
2434
-			break;
2435
-		default:
2436
-			// unsorted
2437
-			break;
2438
-		}
2439
-		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
2440
-		$this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
2441
-	}
2442
-
2443
-	/**
2444
-	 * XML </ Relatives>
2445
-	 */
2446
-	private function relativesEndHandler() {
2447
-		global $report, $WT_TREE;
2448
-
2449
-		$this->process_repeats--;
2450
-		if ($this->process_repeats > 0) {
2451
-			return;
2452
-		}
2453
-
2454
-		// Check if there is any relatives
2455
-		if (count($this->list) > 0) {
2456
-			$lineoffset = 0;
2457
-			foreach ($this->repeats_stack as $rep) {
2458
-				$lineoffset += $rep[1];
2459
-			}
2460
-			//-- read the xml from the file
2461
-			$lines = file($report);
2462
-			while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<Relatives") === false) && (($lineoffset + $this->repeat_bytes) > 0)) {
2463
-				$lineoffset--;
2464
-			}
2465
-			$lineoffset++;
2466
-			$reportxml = "<tempdoc>\n";
2467
-			$line_nr   = $lineoffset + $this->repeat_bytes;
2468
-			// Relatives Level counter
2469
-			$count = 1;
2470
-			while (0 < $count) {
2471
-				if (strpos($lines[$line_nr], "<Relatives") !== false) {
2472
-					$count++;
2473
-				} elseif (strpos($lines[$line_nr], "</Relatives") !== false) {
2474
-					$count--;
2475
-				}
2476
-				if (0 < $count) {
2477
-					$reportxml .= $lines[$line_nr];
2478
-				}
2479
-				$line_nr++;
2480
-			}
2481
-			// No need to drag this
2482
-			unset($lines);
2483
-			$reportxml .= "</tempdoc>\n";
2484
-			// Save original values
2485
-			array_push($this->parser_stack, $this->parser);
2486
-			$oldgedrec = $this->gedrec;
2487
-
2488
-			$this->list_total   = count($this->list);
2489
-			$this->list_private = 0;
2490
-			foreach ($this->list as $key => $value) {
2491
-				if (isset($value->generation)) {
2492
-					$this->generation = $value->generation;
2493
-				}
2494
-				$tmp          = GedcomRecord::getInstance($key, $WT_TREE);
2495
-				$this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE));
2496
-
2497
-				$repeat_parser = xml_parser_create();
2498
-				$this->parser  = $repeat_parser;
2499
-				xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
2500
-				xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
2501
-				xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
2502
-
2503
-				if (!xml_parse($repeat_parser, $reportxml, true)) {
2504
-					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)));
2505
-				}
2506
-				xml_parser_free($repeat_parser);
2507
-			}
2508
-			// Clean up the list array
2509
-			$this->list   = array();
2510
-			$this->parser = array_pop($this->parser_stack);
2511
-			$this->gedrec = $oldgedrec;
2512
-		}
2513
-		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
2514
-	}
2515
-
2516
-	/**
2517
-	 * XML <Generation /> element handler
2518
-	 *
2519
-	 * Prints the number of generations
2520
-	 */
2521
-	private function generationStartHandler() {
2522
-		$this->current_element->addText($this->generation);
2523
-	}
2524
-
2525
-	/**
2526
-	 * XML <NewPage /> element handler
2527
-	 *
2528
-	 * Has to be placed in an element (header, pageheader, body or footer)
2529
-	 */
2530
-	private function newPageStartHandler() {
2531
-		$temp = "addpage";
2532
-		$this->wt_report->addElement($temp);
2533
-	}
2534
-
2535
-	/**
2536
-	 * XML <html>
2537
-	 *
2538
-	 * @param string  $tag   HTML tag name
2539
-	 * @param array[] $attrs an array of key value pairs for the attributes
2540
-	 */
2541
-	private function htmlStartHandler($tag, $attrs) {
2542
-		if ($tag === "tempdoc") {
2543
-			return;
2544
-		}
2545
-		array_push($this->wt_report_stack, $this->wt_report);
2546
-		$this->wt_report       = $this->report_root->createHTML($tag, $attrs);
2547
-		$this->current_element = $this->wt_report;
2548
-
2549
-		array_push($this->print_data_stack, $this->print_data);
2550
-		$this->print_data = true;
2551
-	}
2552
-
2553
-	/**
2554
-	 * XML </html>
2555
-	 *
2556
-	 * @param string $tag
2557
-	 */
2558
-	private function htmlEndHandler($tag) {
2559
-		if ($tag === "tempdoc") {
2560
-			return;
2561
-		}
2562
-
2563
-		$this->print_data      = array_pop($this->print_data_stack);
2564
-		$this->current_element = $this->wt_report;
2565
-		$this->wt_report       = array_pop($this->wt_report_stack);
2566
-		if (!is_null($this->wt_report)) {
2567
-			$this->wt_report->addElement($this->current_element);
2568
-		} else {
2569
-			$this->wt_report = $this->current_element;
2570
-		}
2571
-	}
2572
-
2573
-	/**
2574
-	 * Handle <Input>
2575
-	 */
2576
-	private function inputStartHandler() {
2577
-		// Dummy function, to prevent the default HtmlStartHandler() being called
2578
-	}
2579
-
2580
-	/**
2581
-	 * Handle </Input>
2582
-	 */
2583
-	private function inputEndHandler() {
2584
-		// Dummy function, to prevent the default HtmlEndHandler() being called
2585
-	}
2586
-
2587
-	/**
2588
-	 * Handle <Report>
2589
-	 */
2590
-	private function reportStartHandler() {
2591
-		// Dummy function, to prevent the default HtmlStartHandler() being called
2592
-	}
2593
-
2594
-	/**
2595
-	 * Handle </Report>
2596
-	 */
2597
-	private function reportEndHandler() {
2598
-		// Dummy function, to prevent the default HtmlEndHandler() being called
2599
-	}
2600
-
2601
-	/**
2602
-	 * XML </titleEndHandler>
2603
-	 */
2604
-	private function titleEndHandler() {
2605
-		$this->report_root->addTitle($this->text);
2606
-	}
2607
-
2608
-	/**
2609
-	 * XML </descriptionEndHandler>
2610
-	 */
2611
-	private function descriptionEndHandler() {
2612
-		$this->report_root->addDescription($this->text);
2613
-	}
2614
-
2615
-	/**
2616
-	 * Create a list of all descendants.
2617
-	 *
2618
-	 * @param string[] $list
2619
-	 * @param string   $pid
2620
-	 * @param bool  $parents
2621
-	 * @param int  $generations
2622
-	 */
2623
-	private function addDescendancy(&$list, $pid, $parents = false, $generations = -1) {
2624
-		global $WT_TREE;
2625
-
2626
-		$person = Individual::getInstance($pid, $WT_TREE);
2627
-		if ($person === null) {
2628
-			return;
2629
-		}
2630
-		if (!isset($list[$pid])) {
2631
-			$list[$pid] = $person;
2632
-		}
2633
-		if (!isset($list[$pid]->generation)) {
2634
-			$list[$pid]->generation = 0;
2635
-		}
2636
-		foreach ($person->getSpouseFamilies() as $family) {
2637
-			if ($parents) {
2638
-				$husband = $family->getHusband();
2639
-				$wife    = $family->getWife();
2640
-				if ($husband) {
2641
-					$list[$husband->getXref()] = $husband;
2642
-					if (isset($list[$pid]->generation)) {
2643
-						$list[$husband->getXref()]->generation = $list[$pid]->generation - 1;
2644
-					} else {
2645
-						$list[$husband->getXref()]->generation = 1;
2646
-					}
2647
-				}
2648
-				if ($wife) {
2649
-					$list[$wife->getXref()] = $wife;
2650
-					if (isset($list[$pid]->generation)) {
2651
-						$list[$wife->getXref()]->generation = $list[$pid]->generation - 1;
2652
-					} else {
2653
-						$list[$wife->getXref()]->generation = 1;
2654
-					}
2655
-				}
2656
-			}
2657
-			$children = $family->getChildren();
2658
-			foreach ($children as $child) {
2659
-				if ($child) {
2660
-					$list[$child->getXref()] = $child;
2661
-					if (isset($list[$pid]->generation)) {
2662
-						$list[$child->getXref()]->generation = $list[$pid]->generation + 1;
2663
-					} else {
2664
-						$list[$child->getXref()]->generation = 2;
2665
-					}
2666
-				}
2667
-			}
2668
-			if ($generations == -1 || $list[$pid]->generation + 1 < $generations) {
2669
-				foreach ($children as $child) {
2670
-					$this->addDescendancy($list, $child->getXref(), $parents, $generations); // recurse on the childs family
2671
-				}
2672
-			}
2673
-		}
2674
-	}
2675
-
2676
-	/**
2677
-	 * Create a list of all ancestors.
2678
-	 *
2679
-	 * @param string[] $list
2680
-	 * @param string   $pid
2681
-	 * @param bool  $children
2682
-	 * @param int  $generations
2683
-	 */
2684
-	private function addAncestors(&$list, $pid, $children = false, $generations = -1) {
2685
-		global $WT_TREE;
2686
-
2687
-		$genlist                = array($pid);
2688
-		$list[$pid]->generation = 1;
2689
-		while (count($genlist) > 0) {
2690
-			$id = array_shift($genlist);
2691
-			if (strpos($id, 'empty') === 0) {
2692
-				continue; // id can be something like “empty7”
2693
-			}
2694
-			$person = Individual::getInstance($id, $WT_TREE);
2695
-			foreach ($person->getChildFamilies() as $family) {
2696
-				$husband = $family->getHusband();
2697
-				$wife    = $family->getWife();
2698
-				if ($husband) {
2699
-					$list[$husband->getXref()]             = $husband;
2700
-					$list[$husband->getXref()]->generation = $list[$id]->generation + 1;
2701
-				}
2702
-				if ($wife) {
2703
-					$list[$wife->getXref()]             = $wife;
2704
-					$list[$wife->getXref()]->generation = $list[$id]->generation + 1;
2705
-				}
2706
-				if ($generations == -1 || $list[$id]->generation + 1 < $generations) {
2707
-					if ($husband) {
2708
-						array_push($genlist, $husband->getXref());
2709
-					}
2710
-					if ($wife) {
2711
-						array_push($genlist, $wife->getXref());
2712
-					}
2713
-				}
2714
-				if ($children) {
2715
-					foreach ($family->getChildren() as $child) {
2716
-						$list[$child->getXref()] = $child;
2717
-						if (isset($list[$id]->generation)) {
2718
-							$list[$child->getXref()]->generation = $list[$id]->generation;
2719
-						} else {
2720
-							$list[$child->getXref()]->generation = 1;
2721
-						}
2722
-					}
2723
-				}
2724
-			}
2725
-		}
2726
-	}
2727
-
2728
-	/**
2729
-	 * get gedcom tag value
2730
-	 *
2731
-	 * @param string  $tag    The tag to find, use : to delineate subtags
2732
-	 * @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
2733
-	 * @param string  $gedrec The gedcom record to get the value from
2734
-	 *
2735
-	 * @return string the value of a gedcom tag from the given gedcom record
2736
-	 */
2737
-	private function getGedcomValue($tag, $level, $gedrec) {
2738
-		global $WT_TREE;
2739
-
2740
-		if (empty($gedrec)) {
2741
-			return '';
2742
-		}
2743
-		$tags      = explode(':', $tag);
2744
-		$origlevel = $level;
2745
-		if ($level == 0) {
2746
-			$level = $gedrec{0} + 1;
2747
-		}
2748
-
2749
-		$subrec = $gedrec;
2750
-		foreach ($tags as $t) {
2751
-			$lastsubrec = $subrec;
2752
-			$subrec     = Functions::getSubRecord($level, "$level $t", $subrec);
2753
-			if (empty($subrec) && $origlevel == 0) {
2754
-				$level--;
2755
-				$subrec = Functions::getSubRecord($level, "$level $t", $lastsubrec);
2756
-			}
2757
-			if (empty($subrec)) {
2758
-				if ($t == "TITL") {
2759
-					$subrec = Functions::getSubRecord($level, "$level ABBR", $lastsubrec);
2760
-					if (!empty($subrec)) {
2761
-						$t = "ABBR";
2762
-					}
2763
-				}
2764
-				if (empty($subrec)) {
2765
-					if ($level > 0) {
2766
-						$level--;
2767
-					}
2768
-					$subrec = Functions::getSubRecord($level, "@ $t", $gedrec);
2769
-					if (empty($subrec)) {
2770
-						return '';
2771
-					}
2772
-				}
2773
-			}
2774
-			$level++;
2775
-		}
2776
-		$level--;
2777
-		$ct = preg_match("/$level $t(.*)/", $subrec, $match);
2778
-		if ($ct == 0) {
2779
-			$ct = preg_match("/$level @.+@ (.+)/", $subrec, $match);
2780
-		}
2781
-		if ($ct == 0) {
2782
-			$ct = preg_match("/@ $t (.+)/", $subrec, $match);
2783
-		}
2784
-		if ($ct > 0) {
2785
-			$value = trim($match[1]);
2786
-			if ($t == 'NOTE' && preg_match('/^@(.+)@$/', $value, $match)) {
2787
-				$note = Note::getInstance($match[1], $WT_TREE);
2788
-				if ($note) {
2789
-					$value = $note->getNote();
2790
-				} else {
2791
-					//-- set the value to the id without the @
2792
-					$value = $match[1];
2793
-				}
2794
-			}
2795
-			if ($level != 0 || $t != "NOTE") {
2796
-				$value .= Functions::getCont($level + 1, $subrec);
2797
-			}
2798
-
2799
-			return $value;
2800
-		}
2801
-
2802
-		return "";
2803
-	}
2804
-
2805
-	/**
2806
-	 * Replace variable identifiers with their values.
2807
-	 *
2808
-	 * @param string $expression An expression such as "$foo == 123"
2809
-	 * @param bool   $quote      Whether to add quotation marks
2810
-	 *
2811
-	 * @return string
2812
-	 */
2813
-	private function substituteVars($expression, $quote) {
2814
-		$that = $this; // PHP5.3 cannot access $this inside a closure
2815
-		return preg_replace_callback(
2816
-			'/\$(\w+)/',
2817
-			function ($matches) use ($that, $quote) {
2818
-				if (isset($that->vars[$matches[1]]['id'])) {
2819
-					if ($quote) {
2820
-						return "'" . addcslashes($that->vars[$matches[1]]['id'], "'") . "'";
2821
-					} else {
2822
-						return $that->vars[$matches[1]]['id'];
2823
-					}
2824
-				} else {
2825
-					Log::addErrorLog(sprintf('Undefined variable $%s in report', $matches[1]));
2826
-
2827
-					return '$' . $matches[1];
2828
-				}
2829
-			},
2830
-			$expression
2831
-		);
2832
-	}
53
+    /** @var int Are we processing repeats*/
54
+    private $process_repeats = 0;
55
+
56
+    /** @var int Quantity of data to repeat during loops */
57
+    private $repeat_bytes = 0;
58
+
59
+    /** @var array[] Repeated data when iterating over loops */
60
+    private $repeats = array();
61
+
62
+    /** @var array[] Nested repeating data */
63
+    private $repeats_stack = array();
64
+
65
+    /** @var ReportBase[] Nested repeating data */
66
+    private $wt_report_stack = array();
67
+
68
+    /** @var resource Nested repeating data */
69
+    private $parser;
70
+
71
+    /** @var resource[] Nested repeating data */
72
+    private $parser_stack = array();
73
+
74
+    /** @var string The current GEDCOM record */
75
+    private $gedrec = '';
76
+
77
+    /** @var string[] Nested GEDCOM records */
78
+    private $gedrec_stack = array();
79
+
80
+    /** @var ReportBaseElement The currently processed element */
81
+    private $current_element;
82
+
83
+    /** @var ReportBaseElement The currently processed element */
84
+    private $footnote_element;
85
+
86
+    /** @var string The GEDCOM fact currently being processed */
87
+    private $fact = '';
88
+
89
+    /** @var string The GEDCOM value currently being processed */
90
+    private $desc = '';
91
+
92
+    /** @var string The GEDCOM type currently being processed */
93
+    private $type = '';
94
+
95
+    /** @var int The current generational level */
96
+    private $generation = 1;
97
+
98
+    /** @var array Source data for processing lists */
99
+    private $list = array();
100
+
101
+    /** @var int Number of items in lists */
102
+    private $list_total = 0;
103
+
104
+    /** @var int Number of items filtered from lists */
105
+    private $list_private = 0;
106
+
107
+    /** @var ReportBase A factory for creating report elements */
108
+    private $report_root;
109
+
110
+    /** @var ReportBase Nested report elements */
111
+    private $wt_report;
112
+
113
+    /** @todo This attribute is public to support the PHP5.3 closure workaround. */
114
+    /** @var string[][] Variables defined in the report at run-time */
115
+    public $vars;
116
+
117
+    /**
118
+     * Create a parser for a report
119
+     *
120
+     * @param string     $report     The XML filename
121
+     * @param ReportBase $report_root
122
+     * @param string[][] $vars
123
+     */
124
+    public function __construct($report, ReportBase $report_root = null, array $vars = array()) {
125
+        $this->report_root     = $report_root;
126
+        $this->wt_report       = $report_root;
127
+        $this->current_element = new ReportBaseElement;
128
+        $this->vars            = $vars;
129
+        parent::__construct($report);
130
+    }
131
+
132
+    /**
133
+     * XML start element handler
134
+     *
135
+     * This function is called whenever a starting element is reached
136
+     * The element handler will be called if found, otherwise it must be HTML
137
+     *
138
+     * @param resource $parser the resource handler for the XML parser
139
+     * @param string   $name   the name of the XML element parsed
140
+     * @param array    $attrs  an array of key value pairs for the attributes
141
+     */
142
+    protected function startElement($parser, $name, $attrs) {
143
+        $newattrs = array();
144
+
145
+        foreach ($attrs as $key => $value) {
146
+            if (preg_match("/^\\$(\w+)$/", $value, $match)) {
147
+                if ((isset($this->vars[$match[1]]['id'])) && (!isset($this->vars[$match[1]]['gedcom']))) {
148
+                    $value = $this->vars[$match[1]]['id'];
149
+                }
150
+            }
151
+            $newattrs[$key] = $value;
152
+        }
153
+        $attrs = $newattrs;
154
+        if ($this->process_footnote && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag")) {
155
+            $start_method = $name . 'StartHandler';
156
+            $end_method   = $name . 'EndHandler';
157
+            if (method_exists($this, $start_method)) {
158
+                $this->$start_method($attrs);
159
+            } elseif (!method_exists($this, $end_method)) {
160
+                $this->htmlStartHandler($name, $attrs);
161
+            }
162
+        }
163
+    }
164
+
165
+    /**
166
+     * XML end element handler
167
+     *
168
+     * This function is called whenever an ending element is reached
169
+     * The element handler will be called if found, otherwise it must be HTML
170
+     *
171
+     * @param resource $parser the resource handler for the XML parser
172
+     * @param string   $name   the name of the XML element parsed
173
+     */
174
+    protected function endElement($parser, $name) {
175
+        if (($this->process_footnote || $name === "Footnote") && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag" || $name === "List" || $name === "Relatives")) {
176
+            $start_method = $name . 'StartHandler';
177
+            $end_method   = $name . 'EndHandler';
178
+            if (method_exists($this, $end_method)) {
179
+                $this->$end_method();
180
+            } elseif (!method_exists($this, $start_method)) {
181
+                $this->htmlEndHandler($name);
182
+            }
183
+        }
184
+    }
185
+
186
+    /**
187
+     * XML character data handler
188
+     *
189
+     * @param resource $parser the resource handler for the XML parser
190
+     * @param string   $data   the name of the XML element parsed
191
+     */
192
+    protected function characterData($parser, $data) {
193
+        if ($this->print_data && $this->process_gedcoms === 0 && $this->process_ifs === 0 && $this->process_repeats === 0) {
194
+            $this->current_element->addText($data);
195
+        }
196
+    }
197
+
198
+    /**
199
+     * XML <style>
200
+     *
201
+     * @param array $attrs an array of key value pairs for the attributes
202
+     */
203
+    private function styleStartHandler($attrs) {
204
+        if (empty($attrs['name'])) {
205
+            throw new \DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.');
206
+        }
207
+
208
+        // array Style that will be passed on
209
+        $s = array();
210
+
211
+        // string Name af the style
212
+        $s['name'] = $attrs['name'];
213
+
214
+        // string Name of the DEFAULT font
215
+        $s['font'] = $this->wt_report->defaultFont;
216
+        if (!empty($attrs['font'])) {
217
+            $s['font'] = $attrs['font'];
218
+        }
219
+
220
+        // int The size of the font in points
221
+        $s['size'] = $this->wt_report->defaultFontSize;
222
+        if (!empty($attrs['size'])) {
223
+            $s['size'] = (int) $attrs['size'];
224
+        } // Get it as int to ignore all decimal points or text (if any text then int(0))
225
+
226
+        // string B: bold, I: italic, U: underline, D: line trough, The default value is regular.
227
+        $s['style'] = "";
228
+        if (!empty($attrs['style'])) {
229
+            $s['style'] = $attrs['style'];
230
+        }
231
+
232
+        $this->wt_report->addStyle($s);
233
+    }
234
+
235
+    /**
236
+     * XML <Doc>
237
+     *
238
+     * Sets up the basics of the document proparties
239
+     *
240
+     * @param array $attrs an array of key value pairs for the attributes
241
+     */
242
+    private function docStartHandler($attrs) {
243
+        $this->parser = $this->xml_parser;
244
+
245
+        // Custom page width
246
+        if (!empty($attrs['customwidth'])) {
247
+            $this->wt_report->pagew = (int) $attrs['customwidth'];
248
+        } // Get it as int to ignore all decimal points or text (if any text then int(0))
249
+        // Custom Page height
250
+        if (!empty($attrs['customheight'])) {
251
+            $this->wt_report->pageh = (int) $attrs['customheight'];
252
+        } // Get it as int to ignore all decimal points or text (if any text then int(0))
253
+
254
+        // Left Margin
255
+        if (isset($attrs['leftmargin'])) {
256
+            if ($attrs['leftmargin'] === "0") {
257
+                $this->wt_report->leftmargin = 0;
258
+            } elseif (!empty($attrs['leftmargin'])) {
259
+                $this->wt_report->leftmargin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
260
+            }
261
+        }
262
+        // Right Margin
263
+        if (isset($attrs['rightmargin'])) {
264
+            if ($attrs['rightmargin'] === "0") {
265
+                $this->wt_report->rightmargin = 0;
266
+            } elseif (!empty($attrs['rightmargin'])) {
267
+                $this->wt_report->rightmargin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
268
+            }
269
+        }
270
+        // Top Margin
271
+        if (isset($attrs['topmargin'])) {
272
+            if ($attrs['topmargin'] === "0") {
273
+                $this->wt_report->topmargin = 0;
274
+            } elseif (!empty($attrs['topmargin'])) {
275
+                $this->wt_report->topmargin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
276
+            }
277
+        }
278
+        // Bottom Margin
279
+        if (isset($attrs['bottommargin'])) {
280
+            if ($attrs['bottommargin'] === "0") {
281
+                $this->wt_report->bottommargin = 0;
282
+            } elseif (!empty($attrs['bottommargin'])) {
283
+                $this->wt_report->bottommargin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
284
+            }
285
+        }
286
+        // Header Margin
287
+        if (isset($attrs['headermargin'])) {
288
+            if ($attrs['headermargin'] === "0") {
289
+                $this->wt_report->headermargin = 0;
290
+            } elseif (!empty($attrs['headermargin'])) {
291
+                $this->wt_report->headermargin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
292
+            }
293
+        }
294
+        // Footer Margin
295
+        if (isset($attrs['footermargin'])) {
296
+            if ($attrs['footermargin'] === "0") {
297
+                $this->wt_report->footermargin = 0;
298
+            } elseif (!empty($attrs['footermargin'])) {
299
+                $this->wt_report->footermargin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
300
+            }
301
+        }
302
+
303
+        // Page Orientation
304
+        if (!empty($attrs['orientation'])) {
305
+            if ($attrs['orientation'] == "landscape") {
306
+                $this->wt_report->orientation = "landscape";
307
+            } elseif ($attrs['orientation'] == "portrait") {
308
+                $this->wt_report->orientation = "portrait";
309
+            }
310
+        }
311
+        // Page Size
312
+        if (!empty($attrs['pageSize'])) {
313
+            $this->wt_report->pageFormat = strtoupper($attrs['pageSize']);
314
+        }
315
+
316
+        // Show Generated By...
317
+        if (isset($attrs['showGeneratedBy'])) {
318
+            if ($attrs['showGeneratedBy'] === "0") {
319
+                $this->wt_report->showGenText = false;
320
+            } elseif ($attrs['showGeneratedBy'] === "1") {
321
+                $this->wt_report->showGenText = true;
322
+            }
323
+        }
324
+
325
+        $this->wt_report->setup();
326
+    }
327
+
328
+    /**
329
+     * XML </Doc>
330
+     */
331
+    private function docEndHandler() {
332
+        $this->wt_report->run();
333
+    }
334
+
335
+    /**
336
+     * XML <Header>
337
+     */
338
+    private function headerStartHandler() {
339
+        // Clear the Header before any new elements are added
340
+        $this->wt_report->clearHeader();
341
+        $this->wt_report->setProcessing("H");
342
+    }
343
+
344
+    /**
345
+     * XML <PageHeader>
346
+     */
347
+    private function pageHeaderStartHandler() {
348
+        array_push($this->print_data_stack, $this->print_data);
349
+        $this->print_data = false;
350
+        array_push($this->wt_report_stack, $this->wt_report);
351
+        $this->wt_report = $this->report_root->createPageHeader();
352
+    }
353
+
354
+    /**
355
+     * XML <pageHeaderEndHandler>
356
+     */
357
+    private function pageHeaderEndHandler() {
358
+        $this->print_data        = array_pop($this->print_data_stack);
359
+        $this->current_element   = $this->wt_report;
360
+        $this->wt_report         = array_pop($this->wt_report_stack);
361
+        $this->wt_report->addElement($this->current_element);
362
+    }
363
+
364
+    /**
365
+     * XML <bodyStartHandler>
366
+     */
367
+    private function bodyStartHandler() {
368
+        $this->wt_report->setProcessing("B");
369
+    }
370
+
371
+    /**
372
+     * XML <footerStartHandler>
373
+     */
374
+    private function footerStartHandler() {
375
+        $this->wt_report->setProcessing("F");
376
+    }
377
+
378
+    /**
379
+     * XML <Cell>
380
+     *
381
+     * @param array $attrs an array of key value pairs for the attributes
382
+     */
383
+    private function cellStartHandler($attrs) {
384
+        // string The text alignment of the text in this box.
385
+        $align = "";
386
+        if (!empty($attrs['align'])) {
387
+            $align = $attrs['align'];
388
+            // RTL supported left/right alignment
389
+            if ($align == "rightrtl") {
390
+                if ($this->wt_report->rtl) {
391
+                    $align = "left";
392
+                } else {
393
+                    $align = "right";
394
+                }
395
+            } elseif ($align == "leftrtl") {
396
+                if ($this->wt_report->rtl) {
397
+                    $align = "right";
398
+                } else {
399
+                    $align = "left";
400
+                }
401
+            }
402
+        }
403
+
404
+        // string The color to fill the background of this cell
405
+        $bgcolor = "";
406
+        if (!empty($attrs['bgcolor'])) {
407
+            $bgcolor = $attrs['bgcolor'];
408
+        }
409
+
410
+        // int Whether or not the background should be painted
411
+        $fill = 1;
412
+        if (isset($attrs['fill'])) {
413
+            if ($attrs['fill'] === "0") {
414
+                $fill = 0;
415
+            } elseif ($attrs['fill'] === "1") {
416
+                $fill = 1;
417
+            }
418
+        }
419
+
420
+        $reseth = true;
421
+        // boolean   if true reset the last cell height (default true)
422
+        if (isset($attrs['reseth'])) {
423
+            if ($attrs['reseth'] === "0") {
424
+                $reseth = false;
425
+            } elseif ($attrs['reseth'] === "1") {
426
+                $reseth = true;
427
+            }
428
+        }
429
+
430
+        // mixed Whether or not a border should be printed around this box
431
+        $border = 0;
432
+        if (!empty($attrs['border'])) {
433
+            $border = $attrs['border'];
434
+        }
435
+        // string Border color in HTML code
436
+        $bocolor = "";
437
+        if (!empty($attrs['bocolor'])) {
438
+            $bocolor = $attrs['bocolor'];
439
+        }
440
+
441
+        // int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted.
442
+        $height = 0;
443
+        if (!empty($attrs['height'])) {
444
+            $height = (int) $attrs['height'];
445
+        }
446
+        // int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin.
447
+        $width = 0;
448
+        if (!empty($attrs['width'])) {
449
+            $width = (int) $attrs['width'];
450
+        }
451
+
452
+        // int Stretch carachter mode
453
+        $stretch = 0;
454
+        if (!empty($attrs['stretch'])) {
455
+            $stretch = (int) $attrs['stretch'];
456
+        }
457
+
458
+        // mixed Position the left corner of this box on the page. The default is the current position.
459
+        $left = ".";
460
+        if (isset($attrs['left'])) {
461
+            if ($attrs['left'] === ".") {
462
+                $left = ".";
463
+            } elseif (!empty($attrs['left'])) {
464
+                $left = (int) $attrs['left'];
465
+            } elseif ($attrs['left'] === "0") {
466
+                $left = 0;
467
+            }
468
+        }
469
+        // mixed Position the top corner of this box on the page. the default is the current position
470
+        $top = ".";
471
+        if (isset($attrs['top'])) {
472
+            if ($attrs['top'] === ".") {
473
+                $top = ".";
474
+            } elseif (!empty($attrs['top'])) {
475
+                $top = (int) $attrs['top'];
476
+            } elseif ($attrs['top'] === "0") {
477
+                $top = 0;
478
+            }
479
+        }
480
+
481
+        // string The name of the Style that should be used to render the text.
482
+        $style = "";
483
+        if (!empty($attrs['style'])) {
484
+            $style = $attrs['style'];
485
+        }
486
+
487
+        // string Text color in html code
488
+        $tcolor = "";
489
+        if (!empty($attrs['tcolor'])) {
490
+            $tcolor = $attrs['tcolor'];
491
+        }
492
+
493
+        // int Indicates where the current position should go after the call.
494
+        $ln = 0;
495
+        if (isset($attrs['newline'])) {
496
+            if (!empty($attrs['newline'])) {
497
+                $ln = (int) $attrs['newline'];
498
+            } elseif ($attrs['newline'] === "0") {
499
+                $ln = 0;
500
+            }
501
+        }
502
+
503
+        if ($align == "left") {
504
+            $align = "L";
505
+        } elseif ($align == "right") {
506
+            $align = "R";
507
+        } elseif ($align == "center") {
508
+            $align = "C";
509
+        } elseif ($align == "justify") {
510
+            $align = "J";
511
+        }
512
+
513
+        array_push($this->print_data_stack, $this->print_data);
514
+        $this->print_data = true;
515
+
516
+        $this->current_element = $this->report_root->createCell(
517
+            $width,
518
+            $height,
519
+            $border,
520
+            $align,
521
+            $bgcolor,
522
+            $style,
523
+            $ln,
524
+            $top,
525
+            $left,
526
+            $fill,
527
+            $stretch,
528
+            $bocolor,
529
+            $tcolor,
530
+            $reseth
531
+        );
532
+    }
533
+
534
+    /**
535
+     * XML </Cell>
536
+     */
537
+    private function cellEndHandler() {
538
+        $this->print_data = array_pop($this->print_data_stack);
539
+        $this->wt_report->addElement($this->current_element);
540
+    }
541
+
542
+    /**
543
+     * XML <Now /> element handler
544
+     */
545
+    private function nowStartHandler() {
546
+        $g = FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET);
547
+        $this->current_element->addText($g->display());
548
+    }
549
+
550
+    /**
551
+     * XML <PageNum /> element handler
552
+     */
553
+    private function pageNumStartHandler() {
554
+        $this->current_element->addText("#PAGENUM#");
555
+    }
556
+
557
+    /**
558
+     * XML <TotalPages /> element handler
559
+     */
560
+    private function totalPagesStartHandler() {
561
+        $this->current_element->addText("{{:ptp:}}");
562
+    }
563
+
564
+    /**
565
+     * Called at the start of an element.
566
+     *
567
+     * @param array $attrs an array of key value pairs for the attributes
568
+     */
569
+    private function gedcomStartHandler($attrs) {
570
+        global $WT_TREE;
571
+
572
+        if ($this->process_gedcoms > 0) {
573
+            $this->process_gedcoms++;
574
+
575
+            return;
576
+        }
577
+
578
+        $tag       = $attrs['id'];
579
+        $tag       = str_replace("@fact", $this->fact, $tag);
580
+        $tags      = explode(":", $tag);
581
+        $newgedrec = '';
582
+        if (count($tags) < 2) {
583
+            $tmp       = GedcomRecord::getInstance($attrs['id'], $WT_TREE);
584
+            $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
585
+        }
586
+        if (empty($newgedrec)) {
587
+            $tgedrec   = $this->gedrec;
588
+            $newgedrec = '';
589
+            foreach ($tags as $tag) {
590
+                if (preg_match("/\\$(.+)/", $tag, $match)) {
591
+                    if (isset($this->vars[$match[1]]['gedcom'])) {
592
+                        $newgedrec = $this->vars[$match[1]]['gedcom'];
593
+                    } else {
594
+                        $tmp       = GedcomRecord::getInstance($match[1], $WT_TREE);
595
+                        $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
596
+                    }
597
+                } else {
598
+                    if (preg_match("/@(.+)/", $tag, $match)) {
599
+                        $gmatch = array();
600
+                        if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) {
601
+                            $tmp       = GedcomRecord::getInstance($gmatch[1], $WT_TREE);
602
+                            $newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
603
+                            $tgedrec   = $newgedrec;
604
+                        } else {
605
+                            $newgedrec = '';
606
+                            break;
607
+                        }
608
+                    } else {
609
+                        $temp      = explode(" ", trim($tgedrec));
610
+                        $level     = $temp[0] + 1;
611
+                        $newgedrec = Functions::getSubRecord($level, "$level $tag", $tgedrec);
612
+                        $tgedrec   = $newgedrec;
613
+                    }
614
+                }
615
+            }
616
+        }
617
+        if (!empty($newgedrec)) {
618
+            array_push($this->gedrec_stack, array($this->gedrec, $this->fact, $this->desc));
619
+            $this->gedrec = $newgedrec;
620
+            if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $this->gedrec, $match)) {
621
+                $this->fact = $match[2];
622
+                $this->desc = trim($match[3]);
623
+            }
624
+        } else {
625
+            $this->process_gedcoms++;
626
+        }
627
+    }
628
+
629
+    /**
630
+     * Called at the end of an element.
631
+     */
632
+    private function gedcomEndHandler() {
633
+        if ($this->process_gedcoms > 0) {
634
+            $this->process_gedcoms--;
635
+        } else {
636
+            list($this->gedrec, $this->fact, $this->desc) = array_pop($this->gedrec_stack);
637
+        }
638
+    }
639
+
640
+    /**
641
+     * XML <textBoxStartHandler>
642
+     *
643
+     * @param array $attrs an array of key value pairs for the attributes
644
+     */
645
+    private function textBoxStartHandler($attrs) {
646
+        // string Background color code
647
+        $bgcolor = "";
648
+        if (!empty($attrs['bgcolor'])) {
649
+            $bgcolor = $attrs['bgcolor'];
650
+        }
651
+
652
+        // boolean Wether or not fill the background color
653
+        $fill = true;
654
+        if (isset($attrs['fill'])) {
655
+            if ($attrs['fill'] === "0") {
656
+                $fill = false;
657
+            } elseif ($attrs['fill'] === "1") {
658
+                $fill = true;
659
+            }
660
+        }
661
+
662
+        // var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0
663
+        $border = false;
664
+        if (isset($attrs['border'])) {
665
+            if ($attrs['border'] === "1") {
666
+                $border = true;
667
+            } elseif ($attrs['border'] === "0") {
668
+                $border = false;
669
+            }
670
+        }
671
+
672
+        // int The starting height of this cell. If the text wraps the height will automatically be adjusted
673
+        $height = 0;
674
+        if (!empty($attrs['height'])) {
675
+            $height = (int) $attrs['height'];
676
+        }
677
+        // int Setting the width to 0 will make it the width from the current location to the margin
678
+        $width = 0;
679
+        if (!empty($attrs['width'])) {
680
+            $width = (int) $attrs['width'];
681
+        }
682
+
683
+        // mixed Position the left corner of this box on the page. The default is the current position.
684
+        $left = ".";
685
+        if (isset($attrs['left'])) {
686
+            if ($attrs['left'] === ".") {
687
+                $left = ".";
688
+            } elseif (!empty($attrs['left'])) {
689
+                $left = (int) $attrs['left'];
690
+            } elseif ($attrs['left'] === "0") {
691
+                $left = 0;
692
+            }
693
+        }
694
+        // mixed Position the top corner of this box on the page. the default is the current position
695
+        $top = ".";
696
+        if (isset($attrs['top'])) {
697
+            if ($attrs['top'] === ".") {
698
+                $top = ".";
699
+            } elseif (!empty($attrs['top'])) {
700
+                $top = (int) $attrs['top'];
701
+            } elseif ($attrs['top'] === "0") {
702
+                $top = 0;
703
+            }
704
+        }
705
+        // boolean After this box is finished rendering, should the next section of text start immediately after the this box or should it start on a new line under this box. 0 = no new line, 1 = force new line. Default is 0
706
+        $newline = false;
707
+        if (isset($attrs['newline'])) {
708
+            if ($attrs['newline'] === "1") {
709
+                $newline = true;
710
+            } elseif ($attrs['newline'] === "0") {
711
+                $newline = false;
712
+            }
713
+        }
714
+        // boolean
715
+        $pagecheck = true;
716
+        if (isset($attrs['pagecheck'])) {
717
+            if ($attrs['pagecheck'] === "0") {
718
+                $pagecheck = false;
719
+            } elseif ($attrs['pagecheck'] === "1") {
720
+                $pagecheck = true;
721
+            }
722
+        }
723
+        // boolean Cell padding
724
+        $padding = true;
725
+        if (isset($attrs['padding'])) {
726
+            if ($attrs['padding'] === "0") {
727
+                $padding = false;
728
+            } elseif ($attrs['padding'] === "1") {
729
+                $padding = true;
730
+            }
731
+        }
732
+        // boolean Reset this box Height
733
+        $reseth = false;
734
+        if (isset($attrs['reseth'])) {
735
+            if ($attrs['reseth'] === "1") {
736
+                $reseth = true;
737
+            } elseif ($attrs['reseth'] === "0") {
738
+                $reseth = false;
739
+            }
740
+        }
741
+
742
+        // string Style of rendering
743
+        $style = "";
744
+
745
+        array_push($this->print_data_stack, $this->print_data);
746
+        $this->print_data = false;
747
+
748
+        array_push($this->wt_report_stack, $this->wt_report);
749
+        $this->wt_report = $this->report_root->createTextBox(
750
+            $width,
751
+            $height,
752
+            $border,
753
+            $bgcolor,
754
+            $newline,
755
+            $left,
756
+            $top,
757
+            $pagecheck,
758
+            $style,
759
+            $fill,
760
+            $padding,
761
+            $reseth
762
+        );
763
+    }
764
+
765
+    /**
766
+     * XML <textBoxEndHandler>
767
+     */
768
+    private function textBoxEndHandler() {
769
+        $this->print_data      = array_pop($this->print_data_stack);
770
+        $this->current_element = $this->wt_report;
771
+        $this->wt_report       = array_pop($this->wt_report_stack);
772
+        $this->wt_report->addElement($this->current_element);
773
+    }
774
+
775
+    /**
776
+     * XLM <Text>.
777
+     *
778
+     * @param array $attrs an array of key value pairs for the attributes
779
+     */
780
+    private function textStartHandler($attrs) {
781
+        array_push($this->print_data_stack, $this->print_data);
782
+        $this->print_data = true;
783
+
784
+        // string The name of the Style that should be used to render the text.
785
+        $style = "";
786
+        if (!empty($attrs['style'])) {
787
+            $style = $attrs['style'];
788
+        }
789
+
790
+        // string  The color of the text - Keep the black color as default
791
+        $color = "";
792
+        if (!empty($attrs['color'])) {
793
+            $color = $attrs['color'];
794
+        }
795
+
796
+        $this->current_element = $this->report_root->createText($style, $color);
797
+    }
798
+
799
+    /**
800
+     * XML </Text>
801
+     */
802
+    private function textEndHandler() {
803
+        $this->print_data = array_pop($this->print_data_stack);
804
+        $this->wt_report->addElement($this->current_element);
805
+    }
806
+
807
+    /**
808
+     * XML <GetPersonName/>
809
+     *
810
+     * Get the name
811
+     * 1. id is empty - current GEDCOM record
812
+     * 2. id is set with a record id
813
+     *
814
+     * @param array $attrs an array of key value pairs for the attributes
815
+     */
816
+    private function getPersonNameStartHandler($attrs) {
817
+        global $WT_TREE;
818
+
819
+        $id    = "";
820
+        $match = array();
821
+        if (empty($attrs['id'])) {
822
+            if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
823
+                $id = $match[1];
824
+            }
825
+        } else {
826
+            if (preg_match("/\\$(.+)/", $attrs['id'], $match)) {
827
+                if (isset($this->vars[$match[1]]['id'])) {
828
+                    $id = $this->vars[$match[1]]['id'];
829
+                }
830
+            } else {
831
+                if (preg_match("/@(.+)/", $attrs['id'], $match)) {
832
+                    $gmatch = array();
833
+                    if (preg_match("/\d $match[1] @([^@]+)@/", $this->gedrec, $gmatch)) {
834
+                        $id = $gmatch[1];
835
+                    }
836
+                } else {
837
+                    $id = $attrs['id'];
838
+                }
839
+            }
840
+        }
841
+        if (!empty($id)) {
842
+            $record = GedcomRecord::getInstance($id, $WT_TREE);
843
+            if (is_null($record)) {
844
+                return;
845
+            }
846
+            if (!$record->canShowName()) {
847
+                $this->current_element->addText(I18N::translate('Private'));
848
+            } else {
849
+                $name = $record->getFullName();
850
+                $name = preg_replace(
851
+                    array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'),
852
+                    array('«', '', '»'),
853
+                    $name
854
+                );
855
+                $name = strip_tags($name);
856
+                if (!empty($attrs['truncate'])) {
857
+                    if (mb_strlen($name) > $attrs['truncate']) {
858
+                        $name  = preg_replace("/\(.*\) ?/", '', $name); //removes () and text inbetween - what about ", [ and { etc?
859
+                        $words = preg_split('/[, -]+/', $name); // names separated with space, comma or hyphen - any others?
860
+                        $name  = $words[count($words) - 1];
861
+                        for ($i = count($words) - 2; $i >= 0; $i--) {
862
+                            $len = mb_strlen($name);
863
+                            for ($j = count($words) - 3; $j >= 0; $j--) {
864
+                                $len += mb_strlen($words[$j]);
865
+                            }
866
+                            if ($len > $attrs['truncate']) {
867
+                                $first_letter = mb_substr($words[$i], 0, 1);
868
+                                // Do not show " of nick-names
869
+                                if ($first_letter != "\"") {
870
+                                    $name = mb_substr($words[$i], 0, 1) . '. ' . $name;
871
+                                }
872
+                            } else {
873
+                                $name = $words[$i] . ' ' . $name;
874
+                            }
875
+                        }
876
+                    }
877
+                } else {
878
+                    $addname = $record->getAddName();
879
+                    $addname = preg_replace(
880
+                        array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'),
881
+                        array('«', '', '»'),
882
+                        $addname
883
+                    );
884
+                    $addname = strip_tags($addname);
885
+                    if (!empty($addname)) {
886
+                        $name .= " " . $addname;
887
+                    }
888
+                }
889
+                $this->current_element->addText(trim($name));
890
+            }
891
+        }
892
+    }
893
+
894
+    /**
895
+     * XML <GedcomValue/>
896
+     *
897
+     * @param array $attrs an array of key value pairs for the attributes
898
+     */
899
+    private function gedcomValueStartHandler($attrs) {
900
+        global $WT_TREE;
901
+
902
+        $id    = "";
903
+        $match = array();
904
+        if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
905
+            $id = $match[1];
906
+        }
907
+
908
+        if (isset($attrs['newline']) && $attrs['newline'] == "1") {
909
+            $useBreak = "1";
910
+        } else {
911
+            $useBreak = "0";
912
+        }
913
+
914
+        $tag = $attrs['tag'];
915
+        if (!empty($tag)) {
916
+            if ($tag == "@desc") {
917
+                $value = $this->desc;
918
+                $value = trim($value);
919
+                $this->current_element->addText($value);
920
+            }
921
+            if ($tag == "@id") {
922
+                $this->current_element->addText($id);
923
+            } else {
924
+                $tag = str_replace("@fact", $this->fact, $tag);
925
+                if (empty($attrs['level'])) {
926
+                    $temp  = explode(" ", trim($this->gedrec));
927
+                    $level = $temp[0];
928
+                    if ($level == 0) {
929
+                        $level++;
930
+                    }
931
+                } else {
932
+                    $level = $attrs['level'];
933
+                }
934
+                $tags  = preg_split('/[: ]/', $tag);
935
+                $value = $this->getGedcomValue($tag, $level, $this->gedrec);
936
+                switch (end($tags)) {
937
+                case 'DATE':
938
+                    $tmp   = new Date($value);
939
+                    $value = $tmp->display();
940
+                    break;
941
+                case 'PLAC':
942
+                    $tmp   = new Place($value, $WT_TREE);
943
+                    $value = $tmp->getShortName();
944
+                    break;
945
+                }
946
+                if ($useBreak == "1") {
947
+                    // Insert <br> when multiple dates exist.
948
+                    // This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages
949
+                    $value = str_replace('(', '<br>(', $value);
950
+                    $value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value);
951
+                    $value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value);
952
+                    if (substr($value, 0, 6) == '<br>') {
953
+                        $value = substr($value, 6);
954
+                    }
955
+                }
956
+                $tmp = explode(':', $tag);
957
+                if (in_array(end($tmp), array('NOTE', 'TEXT'))) {
958
+                    $value = Filter::formatText($value, $WT_TREE); // We'll strip HTML in addText()
959
+                }
960
+                $this->current_element->addText($value);
961
+            }
962
+        }
963
+    }
964
+
965
+    /**
966
+     * XML <RepeatTag>
967
+     *
968
+     * @param array $attrs an array of key value pairs for the attributes
969
+     */
970
+    private function repeatTagStartHandler($attrs) {
971
+        global $WT_TREE;
972
+
973
+        $this->process_repeats++;
974
+        if ($this->process_repeats > 1) {
975
+            return;
976
+        }
977
+
978
+        array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
979
+        $this->repeats      = array();
980
+        $this->repeat_bytes = xml_get_current_line_number($this->parser);
981
+
982
+        $tag = "";
983
+        if (isset($attrs['tag'])) {
984
+            $tag = $attrs['tag'];
985
+        }
986
+        if (!empty($tag)) {
987
+            if ($tag == "@desc") {
988
+                $value = $this->desc;
989
+                $value = trim($value);
990
+                $this->current_element->addText($value);
991
+            } else {
992
+                $tag   = str_replace("@fact", $this->fact, $tag);
993
+                $tags  = explode(":", $tag);
994
+                $temp  = explode(" ", trim($this->gedrec));
995
+                $level = $temp[0];
996
+                if ($level == 0) {
997
+                    $level++;
998
+                }
999
+                $subrec = $this->gedrec;
1000
+                $t      = $tag;
1001
+                $count  = count($tags);
1002
+                $i      = 0;
1003
+                while ($i < $count) {
1004
+                    $t = $tags[$i];
1005
+                    if (!empty($t)) {
1006
+                        if ($i < ($count - 1)) {
1007
+                            $subrec = Functions::getSubRecord($level, "$level $t", $subrec);
1008
+                            if (empty($subrec)) {
1009
+                                $level--;
1010
+                                $subrec = Functions::getSubRecord($level, "@ $t", $this->gedrec);
1011
+                                if (empty($subrec)) {
1012
+                                    return;
1013
+                                }
1014
+                            }
1015
+                        }
1016
+                        $level++;
1017
+                    }
1018
+                    $i++;
1019
+                }
1020
+                $level--;
1021
+                $count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER);
1022
+                $i     = 0;
1023
+                while ($i < $count) {
1024
+                    $i++;
1025
+                    // Privacy check - is this a link, and are we allowed to view the linked object?
1026
+                    $subrecord = Functions::getSubRecord($level, "$level $t", $subrec, $i);
1027
+                    if (preg_match('/^\d ' . WT_REGEX_TAG . ' @(' . WT_REGEX_XREF . ')@/', $subrecord, $xref_match)) {
1028
+                        $linked_object = GedcomRecord::getInstance($xref_match[1], $WT_TREE);
1029
+                        if ($linked_object && !$linked_object->canShow()) {
1030
+                            continue;
1031
+                        }
1032
+                    }
1033
+                    $this->repeats[] = $subrecord;
1034
+                }
1035
+            }
1036
+        }
1037
+    }
1038
+
1039
+    /**
1040
+     * XML </ RepeatTag>
1041
+     */
1042
+    private function repeatTagEndHandler() {
1043
+        global $report;
1044
+
1045
+        $this->process_repeats--;
1046
+        if ($this->process_repeats > 0) {
1047
+            return;
1048
+        }
1049
+
1050
+        // Check if there is anything to repeat
1051
+        if (count($this->repeats) > 0) {
1052
+            // No need to load them if not used...
1053
+
1054
+            $lineoffset = 0;
1055
+            foreach ($this->repeats_stack as $rep) {
1056
+                $lineoffset += $rep[1];
1057
+            }
1058
+            //-- read the xml from the file
1059
+            $lines = file($report);
1060
+            while (strpos($lines[$lineoffset + $this->repeat_bytes], "<RepeatTag") === false) {
1061
+                $lineoffset--;
1062
+            }
1063
+            $lineoffset++;
1064
+            $reportxml = "<tempdoc>\n";
1065
+            $line_nr   = $lineoffset + $this->repeat_bytes;
1066
+            // RepeatTag Level counter
1067
+            $count = 1;
1068
+            while (0 < $count) {
1069
+                if (strstr($lines[$line_nr], "<RepeatTag") !== false) {
1070
+                    $count++;
1071
+                } elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) {
1072
+                    $count--;
1073
+                }
1074
+                if (0 < $count) {
1075
+                    $reportxml .= $lines[$line_nr];
1076
+                }
1077
+                $line_nr++;
1078
+            }
1079
+            // No need to drag this
1080
+            unset($lines);
1081
+            $reportxml .= "</tempdoc>\n";
1082
+            // Save original values
1083
+            array_push($this->parser_stack, $this->parser);
1084
+            $oldgedrec = $this->gedrec;
1085
+            foreach ($this->repeats as $gedrec) {
1086
+                $this->gedrec  = $gedrec;
1087
+                $repeat_parser = xml_parser_create();
1088
+                $this->parser  = $repeat_parser;
1089
+                xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
1090
+                xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
1091
+                xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
1092
+                if (!xml_parse($repeat_parser, $reportxml, true)) {
1093
+                    throw new \DomainException(sprintf(
1094
+                        'RepeatTagEHandler XML error: %s at line %d',
1095
+                        xml_error_string(xml_get_error_code($repeat_parser)),
1096
+                        xml_get_current_line_number($repeat_parser)
1097
+                    ));
1098
+                }
1099
+                xml_parser_free($repeat_parser);
1100
+            }
1101
+            // Restore original values
1102
+            $this->gedrec = $oldgedrec;
1103
+            $this->parser = array_pop($this->parser_stack);
1104
+        }
1105
+        list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
1106
+    }
1107
+
1108
+    /**
1109
+     * Variable lookup
1110
+     *
1111
+     * Retrieve predefined variables :
1112
+     *
1113
+     * @ desc GEDCOM fact description, example:
1114
+     *        1 EVEN This is a description
1115
+     * @ fact GEDCOM fact tag, such as BIRT, DEAT etc.
1116
+     * $ I18N::translate('....')
1117
+     * $ language_settings[]
1118
+     *
1119
+     * @param array $attrs an array of key value pairs for the attributes
1120
+     */
1121
+    private function varStartHandler($attrs) {
1122
+        if (empty($attrs['var'])) {
1123
+            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));
1124
+        }
1125
+
1126
+        $var = $attrs['var'];
1127
+        // SetVar element preset variables
1128
+        if (!empty($this->vars[$var]['id'])) {
1129
+            $var = $this->vars[$var]['id'];
1130
+        } else {
1131
+            $tfact = $this->fact;
1132
+            if (($this->fact === "EVEN" || $this->fact === "FACT") && $this->type !== " ") {
1133
+                // Use :
1134
+                // n TYPE This text if string
1135
+                $tfact = $this->type;
1136
+            }
1137
+            $var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $this->desc), $var);
1138
+            if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) {
1139
+                $var = I18N::number($match[1]);
1140
+            } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) {
1141
+                $var = I18N::translate($match[1]);
1142
+            } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) {
1143
+                $var = I18N::translateContext($match[1], $match[2]);
1144
+            }
1145
+        }
1146
+        // Check if variable is set as a date and reformat the date
1147
+        if (isset($attrs['date'])) {
1148
+            if ($attrs['date'] === "1") {
1149
+                $g   = new Date($var);
1150
+                $var = $g->display();
1151
+            }
1152
+        }
1153
+        $this->current_element->addText($var);
1154
+        $this->text = $var; // Used for title/descriptio
1155
+    }
1156
+
1157
+    /**
1158
+     * XML <Facts>
1159
+     *
1160
+     * @param array $attrs an array of key value pairs for the attributes
1161
+     */
1162
+    private function factsStartHandler($attrs) {
1163
+        global $WT_TREE;
1164
+
1165
+        $this->process_repeats++;
1166
+        if ($this->process_repeats > 1) {
1167
+            return;
1168
+        }
1169
+
1170
+        array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
1171
+        $this->repeats      = array();
1172
+        $this->repeat_bytes = xml_get_current_line_number($this->parser);
1173
+
1174
+        $id    = "";
1175
+        $match = array();
1176
+        if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1177
+            $id = $match[1];
1178
+        }
1179
+        $tag = "";
1180
+        if (isset($attrs['ignore'])) {
1181
+            $tag .= $attrs['ignore'];
1182
+        }
1183
+        if (preg_match("/\\$(.+)/", $tag, $match)) {
1184
+            $tag = $this->vars[$match[1]]['id'];
1185
+        }
1186
+
1187
+        $record = GedcomRecord::getInstance($id, $WT_TREE);
1188
+        if (empty($attrs['diff']) && !empty($id)) {
1189
+            $facts = $record->getFacts();
1190
+            Functions::sortFacts($facts);
1191
+            $this->repeats  = array();
1192
+            $nonfacts       = explode(',', $tag);
1193
+            foreach ($facts as $event) {
1194
+                if (!in_array($event->getTag(), $nonfacts)) {
1195
+                    $this->repeats[] = $event->getGedcom();
1196
+                }
1197
+            }
1198
+        } else {
1199
+            foreach ($record->getFacts() as $fact) {
1200
+                if ($fact->isPendingAddition() && $fact->getTag() !== 'CHAN') {
1201
+                    $this->repeats[] = $fact->getGedcom();
1202
+                }
1203
+            }
1204
+        }
1205
+    }
1206
+
1207
+    /**
1208
+     * XML </Facts>
1209
+     */
1210
+    private function factsEndHandler() {
1211
+        global $report;
1212
+
1213
+        $this->process_repeats--;
1214
+        if ($this->process_repeats > 0) {
1215
+            return;
1216
+        }
1217
+
1218
+        // Check if there is anything to repeat
1219
+        if (count($this->repeats) > 0) {
1220
+
1221
+            $line       = xml_get_current_line_number($this->parser) - 1;
1222
+            $lineoffset = 0;
1223
+            foreach ($this->repeats_stack as $rep) {
1224
+                $lineoffset += $rep[1];
1225
+            }
1226
+
1227
+            //-- read the xml from the file
1228
+            $lines = file($report);
1229
+            while ($lineoffset + $this->repeat_bytes > 0 && strpos($lines[$lineoffset + $this->repeat_bytes], '<Facts ') === false) {
1230
+                $lineoffset--;
1231
+            }
1232
+            $lineoffset++;
1233
+            $reportxml = "<tempdoc>\n";
1234
+            $i         = $line + $lineoffset;
1235
+            $line_nr   = $this->repeat_bytes + $lineoffset;
1236
+            while ($line_nr < $i) {
1237
+                $reportxml .= $lines[$line_nr];
1238
+                $line_nr++;
1239
+            }
1240
+            // No need to drag this
1241
+            unset($lines);
1242
+            $reportxml .= "</tempdoc>\n";
1243
+            // Save original values
1244
+            array_push($this->parser_stack, $this->parser);
1245
+            $oldgedrec = $this->gedrec;
1246
+            $count     = count($this->repeats);
1247
+            $i         = 0;
1248
+            while ($i < $count) {
1249
+                $this->gedrec = $this->repeats[$i];
1250
+                $this->fact   = '';
1251
+                $this->desc   = '';
1252
+                if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) {
1253
+                    $this->fact = $match[1];
1254
+                    if ($this->fact === 'EVEN' || $this->fact === 'FACT') {
1255
+                        $tmatch = array();
1256
+                        if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) {
1257
+                            $this->type = trim($tmatch[1]);
1258
+                        } else {
1259
+                            $this->type = ' ';
1260
+                        }
1261
+                    }
1262
+                    $this->desc = trim($match[2]);
1263
+                    $this->desc .= Functions::getCont(2, $this->gedrec);
1264
+                }
1265
+                $repeat_parser = xml_parser_create();
1266
+                $this->parser  = $repeat_parser;
1267
+                xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
1268
+                xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
1269
+                xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
1270
+                if (!xml_parse($repeat_parser, $reportxml, true)) {
1271
+                    throw new \DomainException(sprintf(
1272
+                        'FactsEHandler XML error: %s at line %d',
1273
+                        xml_error_string(xml_get_error_code($repeat_parser)),
1274
+                        xml_get_current_line_number($repeat_parser)
1275
+                    ));
1276
+                }
1277
+                xml_parser_free($repeat_parser);
1278
+                $i++;
1279
+            }
1280
+            // Restore original values
1281
+            $this->parser = array_pop($this->parser_stack);
1282
+            $this->gedrec = $oldgedrec;
1283
+        }
1284
+        list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
1285
+    }
1286
+
1287
+    /**
1288
+     * Setting upp or changing variables in the XML
1289
+     * The XML variable name and value is stored in $this->vars
1290
+     *
1291
+     * @param array $attrs an array of key value pairs for the attributes
1292
+     */
1293
+    private function setVarStartHandler($attrs) {
1294
+        if (empty($attrs['name'])) {
1295
+            throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file');
1296
+        }
1297
+
1298
+        $name  = $attrs['name'];
1299
+        $value = $attrs['value'];
1300
+        $match = array();
1301
+        // Current GEDCOM record strings
1302
+        if ($value == "@ID") {
1303
+            if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1304
+                $value = $match[1];
1305
+            }
1306
+        } elseif ($value == "@fact") {
1307
+            $value = $this->fact;
1308
+        } elseif ($value == "@desc") {
1309
+            $value = $this->desc;
1310
+        } elseif ($value == "@generation") {
1311
+            $value = $this->generation;
1312
+        } elseif (preg_match("/@(\w+)/", $value, $match)) {
1313
+            $gmatch = array();
1314
+            if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) {
1315
+                $value = str_replace("@", "", trim($gmatch[1]));
1316
+            }
1317
+        }
1318
+        if (preg_match("/\\$(\w+)/", $name, $match)) {
1319
+            $name = $this->vars["'" . $match[1] . "'"]['id'];
1320
+        }
1321
+        $count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER);
1322
+        $i     = 0;
1323
+        while ($i < $count) {
1324
+            $t     = $this->vars[$match[$i][1]]['id'];
1325
+            $value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1);
1326
+            $i++;
1327
+        }
1328
+        if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) {
1329
+            $value = I18N::number($match[1]);
1330
+        } elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) {
1331
+            $value = I18N::translate($match[1]);
1332
+        } elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) {
1333
+            $value = I18N::translateContext($match[1], $match[2]);
1334
+        }
1335
+        // Arithmetic functions
1336
+        if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) {
1337
+            switch ($match[2]) {
1338
+            case "+":
1339
+                $t     = $match[1] + $match[3];
1340
+                $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1341
+                break;
1342
+            case "-":
1343
+                $t     = $match[1] - $match[3];
1344
+                $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1345
+                break;
1346
+            case "*":
1347
+                $t     = $match[1] * $match[3];
1348
+                $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1349
+                break;
1350
+            case "/":
1351
+                $t     = $match[1] / $match[3];
1352
+                $value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1353
+                break;
1354
+            }
1355
+        }
1356
+        if (strpos($value, "@") !== false) {
1357
+            $value = "";
1358
+        }
1359
+        $this->vars[$name]['id'] = $value;
1360
+    }
1361
+
1362
+    /**
1363
+     * XML <if > start element
1364
+     *
1365
+     * @param array $attrs an array of key value pairs for the attributes
1366
+     */
1367
+    private function ifStartHandler($attrs) {
1368
+        if ($this->process_ifs > 0) {
1369
+            $this->process_ifs++;
1370
+
1371
+            return;
1372
+        }
1373
+
1374
+        $condition = $attrs['condition'];
1375
+        $condition = $this->substituteVars($condition, true);
1376
+        $condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition);
1377
+        // Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT
1378
+        $condition = str_replace("@fact:", $this->fact . ':', $condition);
1379
+        $match     = array();
1380
+        $count     = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER);
1381
+        $i         = 0;
1382
+        while ($i < $count) {
1383
+            $id    = $match[$i][1];
1384
+            $value = '""';
1385
+            if ($id == "ID") {
1386
+                if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1387
+                    $value = "'" . $match[1] . "'";
1388
+                }
1389
+            } elseif ($id === "fact") {
1390
+                $value = '"' . $this->fact . '"';
1391
+            } elseif ($id === "desc") {
1392
+                $value = '"' . addslashes($this->desc) . '"';
1393
+            } elseif ($id === "generation") {
1394
+                $value = '"' . $this->generation . '"';
1395
+            } else {
1396
+
1397
+                $temp  = explode(" ", trim($this->gedrec));
1398
+                $level = $temp[0];
1399
+                if ($level == 0) {
1400
+                    $level++;
1401
+                }
1402
+                $value = $this->getGedcomValue($id, $level, $this->gedrec);
1403
+                if (empty($value)) {
1404
+                    $level++;
1405
+                    $value = $this->getGedcomValue($id, $level, $this->gedrec);
1406
+                }
1407
+                $value = preg_replace('/^@(' . WT_REGEX_XREF . ')@$/', '$1', $value);
1408
+                $value = '"' . addslashes($value) . '"';
1409
+            }
1410
+            $condition = str_replace("@$id", $value, $condition);
1411
+            $i++;
1412
+        }
1413
+        $ret = eval("return (bool) ($condition);");
1414
+        if (!$ret) {
1415
+            $this->process_ifs++;
1416
+        }
1417
+    }
1418
+
1419
+    /**
1420
+     * XML <if /> end element
1421
+     */
1422
+    private function ifEndHandler() {
1423
+        if ($this->process_ifs > 0) {
1424
+            $this->process_ifs--;
1425
+        }
1426
+    }
1427
+
1428
+    /**
1429
+     * XML <Footnote > start element
1430
+     * Collect the Footnote links
1431
+     * GEDCOM Records that are protected by Privacy setting will be ignore
1432
+     *
1433
+     * @param array $attrs an array of key value pairs for the attributes
1434
+     */
1435
+    private function footnoteStartHandler($attrs) {
1436
+        global $WT_TREE;
1437
+
1438
+        $id = "";
1439
+        if (preg_match("/[0-9] (.+) @(.+)@/", $this->gedrec, $match)) {
1440
+            $id = $match[2];
1441
+        }
1442
+        $record = GedcomRecord::getInstance($id, $WT_TREE);
1443
+        if ($record && $record->canShow()) {
1444
+            array_push($this->print_data_stack, $this->print_data);
1445
+            $this->print_data = true;
1446
+            $style            = "";
1447
+            if (!empty($attrs['style'])) {
1448
+                $style = $attrs['style'];
1449
+            }
1450
+            $this->footnote_element = $this->current_element;
1451
+            $this->current_element  = $this->report_root->createFootnote($style);
1452
+        } else {
1453
+            $this->print_data       = false;
1454
+            $this->process_footnote = false;
1455
+        }
1456
+    }
1457
+
1458
+    /**
1459
+     * XML <Footnote /> end element
1460
+     * Print the collected Footnote data
1461
+     */
1462
+    private function footnoteEndHandler() {
1463
+        if ($this->process_footnote) {
1464
+            $this->print_data = array_pop($this->print_data_stack);
1465
+            $temp             = trim($this->current_element->getValue());
1466
+            if (strlen($temp) > 3) {
1467
+                $this->wt_report->addElement($this->current_element);
1468
+            }
1469
+            $this->current_element = $this->footnote_element;
1470
+        } else {
1471
+            $this->process_footnote = true;
1472
+        }
1473
+    }
1474
+
1475
+    /**
1476
+     * XML <FootnoteTexts /> element
1477
+     */
1478
+    private function footnoteTextsStartHandler() {
1479
+        $temp = "footnotetexts";
1480
+        $this->wt_report->addElement($temp);
1481
+    }
1482
+
1483
+    /**
1484
+     * XML <AgeAtDeath /> element handler
1485
+     */
1486
+    private function ageAtDeathStartHandler() {
1487
+        // This duplicates functionality in FunctionsPrint::format_fact_date()
1488
+        global $factrec, $WT_TREE;
1489
+
1490
+        $match = array();
1491
+        if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1492
+            $person = Individual::getInstance($match[1], $WT_TREE);
1493
+            // Recorded age
1494
+            if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
1495
+                $fact_age = $match[1];
1496
+            } else {
1497
+                $fact_age = '';
1498
+            }
1499
+            if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
1500
+                $husb_age = $match[1];
1501
+            } else {
1502
+                $husb_age = '';
1503
+            }
1504
+            if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
1505
+                $wife_age = $match[1];
1506
+            } else {
1507
+                $wife_age = '';
1508
+            }
1509
+
1510
+            // Calculated age
1511
+            $birth_date = $person->getBirthDate();
1512
+            // Can't use getDeathDate(), as this also gives BURI/CREM events, which
1513
+            // wouldn't give the correct "days after death" result for people with
1514
+            // no DEAT.
1515
+            $death_event = $person->getFirstFact('DEAT');
1516
+            if ($death_event) {
1517
+                $death_date = $death_event->getDate();
1518
+            } else {
1519
+                $death_date = new Date('');
1520
+            }
1521
+            $value = '';
1522
+            if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) {
1523
+                $age = Date::getAgeGedcom($birth_date, $death_date);
1524
+                // Only show calculated age if it differs from recorded age
1525
+                if ($age != '' && $age != "0d") {
1526
+                    if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $person->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $person->getSex() == 'F' && $wife_age != $age
1527
+                    ) {
1528
+                        $value  = FunctionsDate::getAgeAtEvent($age);
1529
+                        $abbrev = substr($value, 0, strpos($value, ' ') + 5);
1530
+                        if ($value !== $abbrev) {
1531
+                            $value = $abbrev . '.';
1532
+                        }
1533
+                    }
1534
+                }
1535
+            }
1536
+            $this->current_element->addText($value);
1537
+        }
1538
+    }
1539
+
1540
+    /**
1541
+     * XML element Forced line break handler - HTML code
1542
+     */
1543
+    private function brStartHandler() {
1544
+        if ($this->print_data && $this->process_gedcoms === 0) {
1545
+            $this->current_element->addText('<br>');
1546
+        }
1547
+    }
1548
+
1549
+    /**
1550
+     * XML <sp />element Forced space handler
1551
+     */
1552
+    private function spStartHandler() {
1553
+        if ($this->print_data && $this->process_gedcoms === 0) {
1554
+            $this->current_element->addText(' ');
1555
+        }
1556
+    }
1557
+
1558
+    /**
1559
+     * XML <HighlightedImage/>
1560
+     *
1561
+     * @param array $attrs an array of key value pairs for the attributes
1562
+     */
1563
+    private function highlightedImageStartHandler($attrs) {
1564
+        global $WT_TREE;
1565
+
1566
+        $id    = '';
1567
+        $match = array();
1568
+        if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1569
+            $id = $match[1];
1570
+        }
1571
+
1572
+        // mixed Position the top corner of this box on the page. the default is the current position
1573
+        $top = '.';
1574
+        if (isset($attrs['top'])) {
1575
+            if ($attrs['top'] === '0') {
1576
+                $top = 0;
1577
+            } elseif ($attrs['top'] === '.') {
1578
+                $top = '.';
1579
+            } elseif (!empty($attrs['top'])) {
1580
+                $top = (int) $attrs['top'];
1581
+            }
1582
+        }
1583
+
1584
+        // mixed Position the left corner of this box on the page. the default is the current position
1585
+        $left = '.';
1586
+        if (isset($attrs['left'])) {
1587
+            if ($attrs['left'] === '0') {
1588
+                $left = 0;
1589
+            } elseif ($attrs['left'] === '.') {
1590
+                $left = '.';
1591
+            } elseif (!empty($attrs['left'])) {
1592
+                $left = (int) $attrs['left'];
1593
+            }
1594
+        }
1595
+
1596
+        // string Align the image in left, center, right
1597
+        $align = '';
1598
+        if (!empty($attrs['align'])) {
1599
+            $align = $attrs['align'];
1600
+        }
1601
+
1602
+        // string Next Line should be T:next to the image, N:next line
1603
+        $ln = '';
1604
+        if (!empty($attrs['ln'])) {
1605
+            $ln = $attrs['ln'];
1606
+        }
1607
+
1608
+        $width  = 0;
1609
+        $height = 0;
1610
+        if (!empty($attrs['width'])) {
1611
+            $width = (int) $attrs['width'];
1612
+        }
1613
+        if (!empty($attrs['height'])) {
1614
+            $height = (int) $attrs['height'];
1615
+        }
1616
+
1617
+        $person      = Individual::getInstance($id, $WT_TREE);
1618
+        $mediaobject = $person->findHighlightedMedia();
1619
+        if ($mediaobject) {
1620
+            $attributes = $mediaobject->getImageAttributes('thumb');
1621
+            if (in_array(
1622
+                    $attributes['ext'],
1623
+                    array(
1624
+                        'GIF',
1625
+                        'JPG',
1626
+                        'PNG',
1627
+                        'SWF',
1628
+                        'PSD',
1629
+                        'BMP',
1630
+                        'TIFF',
1631
+                        'TIFF',
1632
+                        'JPC',
1633
+                        'JP2',
1634
+                        'JPX',
1635
+                        'JB2',
1636
+                        'SWC',
1637
+                        'IFF',
1638
+                        'WBMP',
1639
+                        'XBM',
1640
+                    )
1641
+                ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb')
1642
+            ) {
1643
+                if ($width > 0 && $height == 0) {
1644
+                    $perc   = $width / $attributes['adjW'];
1645
+                    $height = round($attributes['adjH'] * $perc);
1646
+                } elseif ($height > 0 && $width == 0) {
1647
+                    $perc  = $height / $attributes['adjH'];
1648
+                    $width = round($attributes['adjW'] * $perc);
1649
+                } else {
1650
+                    $width  = $attributes['adjW'];
1651
+                    $height = $attributes['adjH'];
1652
+                }
1653
+                $image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln);
1654
+                $this->wt_report->addElement($image);
1655
+            }
1656
+        }
1657
+    }
1658
+
1659
+    /**
1660
+     * XML <Image/>
1661
+     *
1662
+     * @param array $attrs an array of key value pairs for the attributes
1663
+     */
1664
+    private function imageStartHandler($attrs) {
1665
+        global $WT_TREE;
1666
+
1667
+        // mixed Position the top corner of this box on the page. the default is the current position
1668
+        $top = '.';
1669
+        if (isset($attrs['top'])) {
1670
+            if ($attrs['top'] === "0") {
1671
+                $top = 0;
1672
+            } elseif ($attrs['top'] === '.') {
1673
+                $top = '.';
1674
+            } elseif (!empty($attrs['top'])) {
1675
+                $top = (int) $attrs['top'];
1676
+            }
1677
+        }
1678
+
1679
+        // mixed Position the left corner of this box on the page. the default is the current position
1680
+        $left = '.';
1681
+        if (isset($attrs['left'])) {
1682
+            if ($attrs['left'] === '0') {
1683
+                $left = 0;
1684
+            } elseif ($attrs['left'] === '.') {
1685
+                $left = '.';
1686
+            } elseif (!empty($attrs['left'])) {
1687
+                $left = (int) $attrs['left'];
1688
+            }
1689
+        }
1690
+
1691
+        // string Align the image in left, center, right
1692
+        $align = '';
1693
+        if (!empty($attrs['align'])) {
1694
+            $align = $attrs['align'];
1695
+        }
1696
+
1697
+        // string Next Line should be T:next to the image, N:next line
1698
+        $ln = 'T';
1699
+        if (!empty($attrs['ln'])) {
1700
+            $ln = $attrs['ln'];
1701
+        }
1702
+
1703
+        $width  = 0;
1704
+        $height = 0;
1705
+        if (!empty($attrs['width'])) {
1706
+            $width = (int) $attrs['width'];
1707
+        }
1708
+        if (!empty($attrs['height'])) {
1709
+            $height = (int) $attrs['height'];
1710
+        }
1711
+
1712
+        $file = '';
1713
+        if (!empty($attrs['file'])) {
1714
+            $file = $attrs['file'];
1715
+        }
1716
+        if ($file == "@FILE") {
1717
+            $match = array();
1718
+            if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) {
1719
+                $mediaobject = Media::getInstance($match[1], $WT_TREE);
1720
+                $attributes  = $mediaobject->getImageAttributes('thumb');
1721
+                if (in_array(
1722
+                        $attributes['ext'],
1723
+                        array(
1724
+                            'GIF',
1725
+                            'JPG',
1726
+                            'PNG',
1727
+                            'SWF',
1728
+                            'PSD',
1729
+                            'BMP',
1730
+                            'TIFF',
1731
+                            'TIFF',
1732
+                            'JPC',
1733
+                            'JP2',
1734
+                            'JPX',
1735
+                            'JB2',
1736
+                            'SWC',
1737
+                            'IFF',
1738
+                            'WBMP',
1739
+                            'XBM',
1740
+                        )
1741
+                    ) && $mediaobject->canShow() && $mediaobject->fileExists('thumb')
1742
+                ) {
1743
+                    if ($width > 0 && $height == 0) {
1744
+                        $perc   = $width / $attributes['adjW'];
1745
+                        $height = round($attributes['adjH'] * $perc);
1746
+                    } elseif ($height > 0 && $width == 0) {
1747
+                        $perc  = $height / $attributes['adjH'];
1748
+                        $width = round($attributes['adjW'] * $perc);
1749
+                    } else {
1750
+                        $width  = $attributes['adjW'];
1751
+                        $height = $attributes['adjH'];
1752
+                    }
1753
+                    $image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln);
1754
+                    $this->wt_report->addElement($image);
1755
+                }
1756
+            }
1757
+        } else {
1758
+            if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) {
1759
+                $size = getimagesize($file);
1760
+                if ($width > 0 && $height == 0) {
1761
+                    $perc   = $width / $size[0];
1762
+                    $height = round($size[1] * $perc);
1763
+                } elseif ($height > 0 && $width == 0) {
1764
+                    $perc  = $height / $size[1];
1765
+                    $width = round($size[0] * $perc);
1766
+                } else {
1767
+                    $width  = $size[0];
1768
+                    $height = $size[1];
1769
+                }
1770
+                $image = $this->report_root->createImage($file, $left, $top, $width, $height, $align, $ln);
1771
+                $this->wt_report->addElement($image);
1772
+            }
1773
+        }
1774
+    }
1775
+
1776
+    /**
1777
+     * XML <Line> element handler
1778
+     *
1779
+     * @param array $attrs an array of key value pairs for the attributes
1780
+     */
1781
+    private function lineStartHandler($attrs) {
1782
+        // Start horizontal position, current position (default)
1783
+        $x1 = ".";
1784
+        if (isset($attrs['x1'])) {
1785
+            if ($attrs['x1'] === "0") {
1786
+                $x1 = 0;
1787
+            } elseif ($attrs['x1'] === ".") {
1788
+                $x1 = ".";
1789
+            } elseif (!empty($attrs['x1'])) {
1790
+                $x1 = (int) $attrs['x1'];
1791
+            }
1792
+        }
1793
+        // Start vertical position, current position (default)
1794
+        $y1 = ".";
1795
+        if (isset($attrs['y1'])) {
1796
+            if ($attrs['y1'] === "0") {
1797
+                $y1 = 0;
1798
+            } elseif ($attrs['y1'] === ".") {
1799
+                $y1 = ".";
1800
+            } elseif (!empty($attrs['y1'])) {
1801
+                $y1 = (int) $attrs['y1'];
1802
+            }
1803
+        }
1804
+        // End horizontal position, maximum width (default)
1805
+        $x2 = ".";
1806
+        if (isset($attrs['x2'])) {
1807
+            if ($attrs['x2'] === "0") {
1808
+                $x2 = 0;
1809
+            } elseif ($attrs['x2'] === ".") {
1810
+                $x2 = ".";
1811
+            } elseif (!empty($attrs['x2'])) {
1812
+                $x2 = (int) $attrs['x2'];
1813
+            }
1814
+        }
1815
+        // End vertical position
1816
+        $y2 = ".";
1817
+        if (isset($attrs['y2'])) {
1818
+            if ($attrs['y2'] === "0") {
1819
+                $y2 = 0;
1820
+            } elseif ($attrs['y2'] === ".") {
1821
+                $y2 = ".";
1822
+            } elseif (!empty($attrs['y2'])) {
1823
+                $y2 = (int) $attrs['y2'];
1824
+            }
1825
+        }
1826
+
1827
+        $line = $this->report_root->createLine($x1, $y1, $x2, $y2);
1828
+        $this->wt_report->addElement($line);
1829
+    }
1830
+
1831
+    /**
1832
+     * XML <List>
1833
+     *
1834
+     * @param array $attrs an array of key value pairs for the attributes
1835
+     */
1836
+    private function listStartHandler($attrs) {
1837
+        global $WT_TREE;
1838
+
1839
+        $this->process_repeats++;
1840
+        if ($this->process_repeats > 1) {
1841
+            return;
1842
+        }
1843
+
1844
+        $match = array();
1845
+        if (isset($attrs['sortby'])) {
1846
+            $sortby = $attrs['sortby'];
1847
+            if (preg_match("/\\$(\w+)/", $sortby, $match)) {
1848
+                $sortby = $this->vars[$match[1]]['id'];
1849
+                $sortby = trim($sortby);
1850
+            }
1851
+        } else {
1852
+            $sortby = "NAME";
1853
+        }
1854
+
1855
+        if (isset($attrs['list'])) {
1856
+            $listname = $attrs['list'];
1857
+        } else {
1858
+            $listname = "individual";
1859
+        }
1860
+        // Some filters/sorts can be applied using SQL, while others require PHP
1861
+        switch ($listname) {
1862
+        case "pending":
1863
+            $rows = Database::prepare(
1864
+                "SELECT xref, CASE new_gedcom WHEN '' THEN old_gedcom ELSE new_gedcom END AS gedcom" .
1865
+                " FROM `##change`" . " WHERE (xref, change_id) IN (" .
1866
+                "  SELECT xref, MAX(change_id)" .
1867
+                "  FROM `##change`" .
1868
+                "  WHERE status = 'pending' AND gedcom_id = :tree_id" .
1869
+                "  GROUP BY xref" .
1870
+                " )"
1871
+            )->execute(array(
1872
+                'tree_id' => $WT_TREE->getTreeId(),
1873
+            ))->fetchAll();
1874
+            $this->list = array();
1875
+            foreach ($rows as $row) {
1876
+                $this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom);
1877
+            }
1878
+            break;
1879
+        case 'individual':
1880
+            $sql_select   = "SELECT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` ";
1881
+            $sql_join     = "";
1882
+            $sql_where    = " WHERE i_file = :tree_id";
1883
+            $sql_order_by = "";
1884
+            $sql_params   = array('tree_id' => $WT_TREE->getTreeId());
1885
+            foreach ($attrs as $attr => $value) {
1886
+                if (strpos($attr, 'filter') === 0 && $value) {
1887
+                    $value = $this->substituteVars($value, false);
1888
+                    // Convert the various filters into SQL
1889
+                    if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
1890
+                        $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)";
1891
+                        $sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
1892
+                        $sql_params[$attr . 'fact'] = $match[1];
1893
+                        $date                       = new Date($match[3]);
1894
+                        if ($match[2] == "LTE") {
1895
+                            $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
1896
+                            $sql_params[$attr . 'date'] = $date->maximumJulianDay();
1897
+                        } else {
1898
+                            $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
1899
+                            $sql_params[$attr . 'date'] = $date->minimumJulianDay();
1900
+                        }
1901
+                        if ($sortby == $match[1]) {
1902
+                            $sortby = "";
1903
+                            $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
1904
+                        }
1905
+                        unset($attrs[$attr]); // This filter has been fully processed
1906
+                    } elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) {
1907
+                        // Do nothing, unless you have to
1908
+                        if ($match[1] != '' || $sortby == 'NAME') {
1909
+                            $sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)";
1910
+                            // Search the DB only if there is any name supplied
1911
+                            if ($match[1] != "") {
1912
+                                $names = explode(" ", $match[1]);
1913
+                                foreach ($names as $n => $name) {
1914
+                                    $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
1915
+                                    $sql_params[$attr . 'name' . $n] = $name;
1916
+                                }
1917
+                            }
1918
+                            // Let the DB do the name sorting even when no name was entered
1919
+                            if ($sortby == "NAME") {
1920
+                                $sortby = "";
1921
+                                $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
1922
+                            }
1923
+                        }
1924
+                        unset($attrs[$attr]); // This filter has been fully processed
1925
+                    } elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) {
1926
+                        $sql_where .= " AND i_gedcom REGEXP :{$attr}gedcom";
1927
+                        // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
1928
+                        $sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]);
1929
+                        unset($attrs[$attr]); // This filter has been fully processed
1930
+                    } elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
1931
+                        $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file = i_file)";
1932
+                        $sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file = {$attr}b.pl_file AND {$attr}b.pl_p_id = {$attr}a.p_id AND {$attr}b.pl_gid = i_id)";
1933
+                        $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
1934
+                        $sql_params[$attr . 'place'] = $match[1];
1935
+                        // Don't unset this filter. This is just initial filtering
1936
+                    } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) {
1937
+                        $sql_where .= " AND i_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
1938
+                        $sql_params[$attr . 'contains1'] = $match[1];
1939
+                        $sql_params[$attr . 'contains2'] = $match[2];
1940
+                        $sql_params[$attr . 'contains3'] = $match[3];
1941
+                        // Don't unset this filter. This is just initial filtering
1942
+                    }
1943
+                }
1944
+            }
1945
+
1946
+            $this->list = array();
1947
+            $rows       = Database::prepare(
1948
+                $sql_select . $sql_join . $sql_where . $sql_order_by
1949
+            )->execute($sql_params)->fetchAll();
1950
+
1951
+            foreach ($rows as $row) {
1952
+                $this->list[$row->xref] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
1953
+            }
1954
+            break;
1955
+
1956
+        case 'family':
1957
+            $sql_select   = "SELECT f_id AS xref, f_gedcom AS gedcom FROM `##families`";
1958
+            $sql_join     = "";
1959
+            $sql_where    = " WHERE f_file = :tree_id";
1960
+            $sql_order_by = "";
1961
+            $sql_params   = array('tree_id' => $WT_TREE->getTreeId());
1962
+            foreach ($attrs as $attr => $value) {
1963
+                if (strpos($attr, 'filter') === 0 && $value) {
1964
+                    $value = $this->substituteVars($value, false);
1965
+                    // Convert the various filters into SQL
1966
+                    if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
1967
+                        $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)";
1968
+                        $sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
1969
+                        $sql_params[$attr . 'fact'] = $match[1];
1970
+                        $date                       = new Date($match[3]);
1971
+                        if ($match[2] == "LTE") {
1972
+                            $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
1973
+                            $sql_params[$attr . 'date'] = $date->maximumJulianDay();
1974
+                        } else {
1975
+                            $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
1976
+                            $sql_params[$attr . 'date'] = $date->minimumJulianDay();
1977
+                        }
1978
+                        if ($sortby == $match[1]) {
1979
+                            $sortby = "";
1980
+                            $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
1981
+                        }
1982
+                        unset($attrs[$attr]); // This filter has been fully processed
1983
+                    } elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) {
1984
+                        $sql_where .= " AND f_gedcom REGEXP :{$attr}gedcom";
1985
+                        // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
1986
+                        $sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]);
1987
+                        unset($attrs[$attr]); // This filter has been fully processed
1988
+                    } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) {
1989
+                        // Do nothing, unless you have to
1990
+                        if ($match[1] != '' || $sortby == 'NAME') {
1991
+                            $sql_join .= " JOIN `##name` AS {$attr} ON n_file = f_file AND n_id IN (f_husb, f_wife)";
1992
+                            // Search the DB only if there is any name supplied
1993
+                            if ($match[1] != "") {
1994
+                                $names = explode(" ", $match[1]);
1995
+                                foreach ($names as $n => $name) {
1996
+                                    $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
1997
+                                    $sql_params[$attr . 'name' . $n] = $name;
1998
+                                }
1999
+                            }
2000
+                            // Let the DB do the name sorting even when no name was entered
2001
+                            if ($sortby == "NAME") {
2002
+                                $sortby = "";
2003
+                                $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
2004
+                            }
2005
+                        }
2006
+                        unset($attrs[$attr]); // This filter has been fully processed
2007
+
2008
+                    } elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
2009
+                        $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)";
2010
+                        $sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file={$attr}b.pl_file AND {$attr}b.pl_p_id={$attr}a.p_id AND {$attr}b.pl_gid=f_id)";
2011
+                        $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
2012
+                        $sql_params[$attr . 'place'] = $match[1];
2013
+                        // Don't unset this filter. This is just initial filtering
2014
+                    } elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) {
2015
+                        $sql_where .= " AND f_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
2016
+                        $sql_params[$attr . 'contains1'] = $match[1];
2017
+                        $sql_params[$attr . 'contains2'] = $match[2];
2018
+                        $sql_params[$attr . 'contains3'] = $match[3];
2019
+                        // Don't unset this filter. This is just initial filtering
2020
+                    }
2021
+                }
2022
+            }
2023
+
2024
+            $this->list = array();
2025
+            $rows       = Database::prepare(
2026
+                $sql_select . $sql_join . $sql_where . $sql_order_by
2027
+            )->execute($sql_params)->fetchAll();
2028
+
2029
+            foreach ($rows as $row) {
2030
+                $this->list[$row->xref] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom);
2031
+            }
2032
+            break;
2033
+
2034
+        default:
2035
+            throw new \DomainException('Invalid list name: ' . $listname);
2036
+        }
2037
+
2038
+        $filters  = array();
2039
+        $filters2 = array();
2040
+        if (isset($attrs['filter1']) && count($this->list) > 0) {
2041
+            foreach ($attrs as $key => $value) {
2042
+                if (preg_match("/filter(\d)/", $key)) {
2043
+                    $condition = $value;
2044
+                    if (preg_match("/@(\w+)/", $condition, $match)) {
2045
+                        $id    = $match[1];
2046
+                        $value = "''";
2047
+                        if ($id == "ID") {
2048
+                            if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
2049
+                                $value = "'" . $match[1] . "'";
2050
+                            }
2051
+                        } elseif ($id == "fact") {
2052
+                            $value = "'" . $this->fact . "'";
2053
+                        } elseif ($id == "desc") {
2054
+                            $value = "'" . $this->desc . "'";
2055
+                        } else {
2056
+                            if (preg_match("/\d $id (.+)/", $this->gedrec, $match)) {
2057
+                                $value = "'" . str_replace("@", "", trim($match[1])) . "'";
2058
+                            }
2059
+                        }
2060
+                        $condition = preg_replace("/@$id/", $value, $condition);
2061
+                    }
2062
+                    //-- handle regular expressions
2063
+                    if (preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match)) {
2064
+                        $tag  = trim($match[1]);
2065
+                        $expr = trim($match[2]);
2066
+                        $val  = trim($match[3]);
2067
+                        if (preg_match("/\\$(\w+)/", $val, $match)) {
2068
+                            $val = $this->vars[$match[1]]['id'];
2069
+                            $val = trim($val);
2070
+                        }
2071
+                        if ($val) {
2072
+                            $searchstr = "";
2073
+                            $tags      = explode(":", $tag);
2074
+                            //-- only limit to a level number if we are specifically looking at a level
2075
+                            if (count($tags) > 1) {
2076
+                                $level = 1;
2077
+                                foreach ($tags as $t) {
2078
+                                    if (!empty($searchstr)) {
2079
+                                        $searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n";
2080
+                                    }
2081
+                                    //-- search for both EMAIL and _EMAIL... silly double gedcom standard
2082
+                                    if ($t == "EMAIL" || $t == "_EMAIL") {
2083
+                                        $t = "_?EMAIL";
2084
+                                    }
2085
+                                    $searchstr .= $level . " " . $t;
2086
+                                    $level++;
2087
+                                }
2088
+                            } else {
2089
+                                if ($tag == "EMAIL" || $tag == "_EMAIL") {
2090
+                                    $tag = "_?EMAIL";
2091
+                                }
2092
+                                $t         = $tag;
2093
+                                $searchstr = "1 " . $tag;
2094
+                            }
2095
+                            switch ($expr) {
2096
+                            case "CONTAINS":
2097
+                                if ($t == "PLAC") {
2098
+                                    $searchstr .= "[^\n]*[, ]*" . $val;
2099
+                                } else {
2100
+                                    $searchstr .= "[^\n]*" . $val;
2101
+                                }
2102
+                                $filters[] = $searchstr;
2103
+                                break;
2104
+                            default:
2105
+                                $filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val);
2106
+                                break;
2107
+                            }
2108
+                        }
2109
+                    }
2110
+                }
2111
+            }
2112
+        }
2113
+        //-- apply other filters to the list that could not be added to the search string
2114
+        if ($filters) {
2115
+            foreach ($this->list as $key => $record) {
2116
+                foreach ($filters as $filter) {
2117
+                    if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) {
2118
+                        unset($this->list[$key]);
2119
+                        break;
2120
+                    }
2121
+                }
2122
+            }
2123
+        }
2124
+        if ($filters2) {
2125
+            $mylist = array();
2126
+            foreach ($this->list as $indi) {
2127
+                $key  = $indi->getXref();
2128
+                $grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE));
2129
+                $keep = true;
2130
+                foreach ($filters2 as $filter) {
2131
+                    if ($keep) {
2132
+                        $tag  = $filter['tag'];
2133
+                        $expr = $filter['expr'];
2134
+                        $val  = $filter['val'];
2135
+                        if ($val == "''") {
2136
+                            $val = "";
2137
+                        }
2138
+                        $tags = explode(":", $tag);
2139
+                        $t    = end($tags);
2140
+                        $v    = $this->getGedcomValue($tag, 1, $grec);
2141
+                        //-- check for EMAIL and _EMAIL (silly double gedcom standard :P)
2142
+                        if ($t == "EMAIL" && empty($v)) {
2143
+                            $tag  = str_replace("EMAIL", "_EMAIL", $tag);
2144
+                            $tags = explode(":", $tag);
2145
+                            $t    = end($tags);
2146
+                            $v    = Functions::getSubRecord(1, $tag, $grec);
2147
+                        }
2148
+
2149
+                        switch ($expr) {
2150
+                        case "GTE":
2151
+                            if ($t == "DATE") {
2152
+                                $date1 = new Date($v);
2153
+                                $date2 = new Date($val);
2154
+                                $keep  = (Date::compare($date1, $date2) >= 0);
2155
+                            } elseif ($val >= $v) {
2156
+                                $keep = true;
2157
+                            }
2158
+                            break;
2159
+                        case "LTE":
2160
+                            if ($t == "DATE") {
2161
+                                $date1 = new Date($v);
2162
+                                $date2 = new Date($val);
2163
+                                $keep  = (Date::compare($date1, $date2) <= 0);
2164
+                            } elseif ($val >= $v) {
2165
+                                $keep = true;
2166
+                            }
2167
+                            break;
2168
+                        default:
2169
+                            if ($v == $val) {
2170
+                                $keep = true;
2171
+                            } else {
2172
+                                $keep = false;
2173
+                            }
2174
+                            break;
2175
+                        }
2176
+                    }
2177
+                }
2178
+                if ($keep) {
2179
+                    $mylist[$key] = $indi;
2180
+                }
2181
+            }
2182
+            $this->list = $mylist;
2183
+        }
2184
+
2185
+        switch ($sortby) {
2186
+        case 'NAME':
2187
+            uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare');
2188
+            break;
2189
+        case 'CHAN':
2190
+            uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) {
2191
+                return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true);
2192
+            });
2193
+            break;
2194
+        case 'BIRT:DATE':
2195
+            uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate');
2196
+            break;
2197
+        case 'DEAT:DATE':
2198
+            uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate');
2199
+            break;
2200
+        case 'MARR:DATE':
2201
+            uasort($this->list, '\Fisharebest\Webtrees\Family::compareMarrDate');
2202
+            break;
2203
+        default:
2204
+            // unsorted or already sorted by SQL
2205
+            break;
2206
+        }
2207
+
2208
+        array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
2209
+        $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
2210
+    }
2211
+
2212
+    /**
2213
+     * XML <List>
2214
+     */
2215
+    private function listEndHandler() {
2216
+        global $report;
2217
+
2218
+        $this->process_repeats--;
2219
+        if ($this->process_repeats > 0) {
2220
+            return;
2221
+        }
2222
+
2223
+        // Check if there is any list
2224
+        if (count($this->list) > 0) {
2225
+            $lineoffset = 0;
2226
+            foreach ($this->repeats_stack as $rep) {
2227
+                $lineoffset += $rep[1];
2228
+            }
2229
+            //-- read the xml from the file
2230
+            $lines = file($report);
2231
+            while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<List") === false) && (($lineoffset + $this->repeat_bytes) > 0)) {
2232
+                $lineoffset--;
2233
+            }
2234
+            $lineoffset++;
2235
+            $reportxml = "<tempdoc>\n";
2236
+            $line_nr   = $lineoffset + $this->repeat_bytes;
2237
+            // List Level counter
2238
+            $count = 1;
2239
+            while (0 < $count) {
2240
+                if (strpos($lines[$line_nr], "<List") !== false) {
2241
+                    $count++;
2242
+                } elseif (strpos($lines[$line_nr], "</List") !== false) {
2243
+                    $count--;
2244
+                }
2245
+                if (0 < $count) {
2246
+                    $reportxml .= $lines[$line_nr];
2247
+                }
2248
+                $line_nr++;
2249
+            }
2250
+            // No need to drag this
2251
+            unset($lines);
2252
+            $reportxml .= "</tempdoc>";
2253
+            // Save original values
2254
+            array_push($this->parser_stack, $this->parser);
2255
+            $oldgedrec = $this->gedrec;
2256
+
2257
+            $this->list_total   = count($this->list);
2258
+            $this->list_private = 0;
2259
+            foreach ($this->list as $record) {
2260
+                if ($record->canShow()) {
2261
+                    $this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree()));
2262
+                    //-- start the sax parser
2263
+                    $repeat_parser = xml_parser_create();
2264
+                    $this->parser  = $repeat_parser;
2265
+                    xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
2266
+                    xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
2267
+                    xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
2268
+                    if (!xml_parse($repeat_parser, $reportxml, true)) {
2269
+                        throw new \DomainException(sprintf(
2270
+                            'ListEHandler XML error: %s at line %d',
2271
+                            xml_error_string(xml_get_error_code($repeat_parser)),
2272
+                            xml_get_current_line_number($repeat_parser)
2273
+                        ));
2274
+                    }
2275
+                    xml_parser_free($repeat_parser);
2276
+                } else {
2277
+                    $this->list_private++;
2278
+                }
2279
+            }
2280
+            $this->list   = array();
2281
+            $this->parser = array_pop($this->parser_stack);
2282
+            $this->gedrec = $oldgedrec;
2283
+        }
2284
+        list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
2285
+    }
2286
+
2287
+    /**
2288
+     * XML <ListTotal> element handler
2289
+     *
2290
+     * Prints the total number of records in a list
2291
+     * The total number is collected from
2292
+     * List and Relatives
2293
+     */
2294
+    private function listTotalStartHandler() {
2295
+        if ($this->list_private == 0) {
2296
+            $this->current_element->addText($this->list_total);
2297
+        } else {
2298
+            $this->current_element->addText(($this->list_total - $this->list_private) . " / " . $this->list_total);
2299
+        }
2300
+    }
2301
+
2302
+    /**
2303
+     * XML <Relatives>
2304
+     *
2305
+     * @param array $attrs an array of key value pairs for the attributes
2306
+     */
2307
+    private function relativesStartHandler($attrs) {
2308
+        global $WT_TREE;
2309
+
2310
+        $this->process_repeats++;
2311
+        if ($this->process_repeats > 1) {
2312
+            return;
2313
+        }
2314
+
2315
+        $sortby = "NAME";
2316
+        if (isset($attrs['sortby'])) {
2317
+            $sortby = $attrs['sortby'];
2318
+        }
2319
+        $match = array();
2320
+        if (preg_match("/\\$(\w+)/", $sortby, $match)) {
2321
+            $sortby = $this->vars[$match[1]]['id'];
2322
+            $sortby = trim($sortby);
2323
+        }
2324
+
2325
+        $maxgen = -1;
2326
+        if (isset($attrs['maxgen'])) {
2327
+            $maxgen = $attrs['maxgen'];
2328
+        }
2329
+        if ($maxgen == "*") {
2330
+            $maxgen = -1;
2331
+        }
2332
+
2333
+        $group = "child-family";
2334
+        if (isset($attrs['group'])) {
2335
+            $group = $attrs['group'];
2336
+        }
2337
+        if (preg_match("/\\$(\w+)/", $group, $match)) {
2338
+            $group = $this->vars[$match[1]]['id'];
2339
+            $group = trim($group);
2340
+        }
2341
+
2342
+        $id = "";
2343
+        if (isset($attrs['id'])) {
2344
+            $id = $attrs['id'];
2345
+        }
2346
+        if (preg_match("/\\$(\w+)/", $id, $match)) {
2347
+            $id = $this->vars[$match[1]]['id'];
2348
+            $id = trim($id);
2349
+        }
2350
+
2351
+        $this->list = array();
2352
+        $person     = Individual::getInstance($id, $WT_TREE);
2353
+        if (!empty($person)) {
2354
+            $this->list[$id] = $person;
2355
+            switch ($group) {
2356
+            case "child-family":
2357
+                foreach ($person->getChildFamilies() as $family) {
2358
+                    $husband = $family->getHusband();
2359
+                    $wife    = $family->getWife();
2360
+                    if (!empty($husband)) {
2361
+                        $this->list[$husband->getXref()] = $husband;
2362
+                    }
2363
+                    if (!empty($wife)) {
2364
+                        $this->list[$wife->getXref()] = $wife;
2365
+                    }
2366
+                    $children = $family->getChildren();
2367
+                    foreach ($children as $child) {
2368
+                        if (!empty($child)) {
2369
+                            $this->list[$child->getXref()] = $child;
2370
+                        }
2371
+                    }
2372
+                }
2373
+                break;
2374
+            case "spouse-family":
2375
+                foreach ($person->getSpouseFamilies() as $family) {
2376
+                    $husband = $family->getHusband();
2377
+                    $wife    = $family->getWife();
2378
+                    if (!empty($husband)) {
2379
+                        $this->list[$husband->getXref()] = $husband;
2380
+                    }
2381
+                    if (!empty($wife)) {
2382
+                        $this->list[$wife->getXref()] = $wife;
2383
+                    }
2384
+                    $children = $family->getChildren();
2385
+                    foreach ($children as $child) {
2386
+                        if (!empty($child)) {
2387
+                            $this->list[$child->getXref()] = $child;
2388
+                        }
2389
+                    }
2390
+                }
2391
+                break;
2392
+            case "direct-ancestors":
2393
+                $this->addAncestors($this->list, $id, false, $maxgen);
2394
+                break;
2395
+            case "ancestors":
2396
+                $this->addAncestors($this->list, $id, true, $maxgen);
2397
+                break;
2398
+            case "descendants":
2399
+                $this->list[$id]->generation = 1;
2400
+                $this->addDescendancy($this->list, $id, false, $maxgen);
2401
+                break;
2402
+            case "all":
2403
+                $this->addAncestors($this->list, $id, true, $maxgen);
2404
+                $this->addDescendancy($this->list, $id, true, $maxgen);
2405
+                break;
2406
+            }
2407
+        }
2408
+
2409
+        switch ($sortby) {
2410
+        case 'NAME':
2411
+            uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare');
2412
+            break;
2413
+        case 'BIRT:DATE':
2414
+            uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate');
2415
+            break;
2416
+        case 'DEAT:DATE':
2417
+            uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate');
2418
+            break;
2419
+        case 'generation':
2420
+            $newarray = array();
2421
+            reset($this->list);
2422
+            $genCounter = 1;
2423
+            while (count($newarray) < count($this->list)) {
2424
+                foreach ($this->list as $key => $value) {
2425
+                    $this->generation = $value->generation;
2426
+                    if ($this->generation == $genCounter) {
2427
+                        $newarray[$key]             = new \stdClass;
2428
+                        $newarray[$key]->generation = $this->generation;
2429
+                    }
2430
+                }
2431
+                $genCounter++;
2432
+            }
2433
+            $this->list = $newarray;
2434
+            break;
2435
+        default:
2436
+            // unsorted
2437
+            break;
2438
+        }
2439
+        array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
2440
+        $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
2441
+    }
2442
+
2443
+    /**
2444
+     * XML </ Relatives>
2445
+     */
2446
+    private function relativesEndHandler() {
2447
+        global $report, $WT_TREE;
2448
+
2449
+        $this->process_repeats--;
2450
+        if ($this->process_repeats > 0) {
2451
+            return;
2452
+        }
2453
+
2454
+        // Check if there is any relatives
2455
+        if (count($this->list) > 0) {
2456
+            $lineoffset = 0;
2457
+            foreach ($this->repeats_stack as $rep) {
2458
+                $lineoffset += $rep[1];
2459
+            }
2460
+            //-- read the xml from the file
2461
+            $lines = file($report);
2462
+            while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<Relatives") === false) && (($lineoffset + $this->repeat_bytes) > 0)) {
2463
+                $lineoffset--;
2464
+            }
2465
+            $lineoffset++;
2466
+            $reportxml = "<tempdoc>\n";
2467
+            $line_nr   = $lineoffset + $this->repeat_bytes;
2468
+            // Relatives Level counter
2469
+            $count = 1;
2470
+            while (0 < $count) {
2471
+                if (strpos($lines[$line_nr], "<Relatives") !== false) {
2472
+                    $count++;
2473
+                } elseif (strpos($lines[$line_nr], "</Relatives") !== false) {
2474
+                    $count--;
2475
+                }
2476
+                if (0 < $count) {
2477
+                    $reportxml .= $lines[$line_nr];
2478
+                }
2479
+                $line_nr++;
2480
+            }
2481
+            // No need to drag this
2482
+            unset($lines);
2483
+            $reportxml .= "</tempdoc>\n";
2484
+            // Save original values
2485
+            array_push($this->parser_stack, $this->parser);
2486
+            $oldgedrec = $this->gedrec;
2487
+
2488
+            $this->list_total   = count($this->list);
2489
+            $this->list_private = 0;
2490
+            foreach ($this->list as $key => $value) {
2491
+                if (isset($value->generation)) {
2492
+                    $this->generation = $value->generation;
2493
+                }
2494
+                $tmp          = GedcomRecord::getInstance($key, $WT_TREE);
2495
+                $this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE));
2496
+
2497
+                $repeat_parser = xml_parser_create();
2498
+                $this->parser  = $repeat_parser;
2499
+                xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
2500
+                xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
2501
+                xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
2502
+
2503
+                if (!xml_parse($repeat_parser, $reportxml, true)) {
2504
+                    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)));
2505
+                }
2506
+                xml_parser_free($repeat_parser);
2507
+            }
2508
+            // Clean up the list array
2509
+            $this->list   = array();
2510
+            $this->parser = array_pop($this->parser_stack);
2511
+            $this->gedrec = $oldgedrec;
2512
+        }
2513
+        list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
2514
+    }
2515
+
2516
+    /**
2517
+     * XML <Generation /> element handler
2518
+     *
2519
+     * Prints the number of generations
2520
+     */
2521
+    private function generationStartHandler() {
2522
+        $this->current_element->addText($this->generation);
2523
+    }
2524
+
2525
+    /**
2526
+     * XML <NewPage /> element handler
2527
+     *
2528
+     * Has to be placed in an element (header, pageheader, body or footer)
2529
+     */
2530
+    private function newPageStartHandler() {
2531
+        $temp = "addpage";
2532
+        $this->wt_report->addElement($temp);
2533
+    }
2534
+
2535
+    /**
2536
+     * XML <html>
2537
+     *
2538
+     * @param string  $tag   HTML tag name
2539
+     * @param array[] $attrs an array of key value pairs for the attributes
2540
+     */
2541
+    private function htmlStartHandler($tag, $attrs) {
2542
+        if ($tag === "tempdoc") {
2543
+            return;
2544
+        }
2545
+        array_push($this->wt_report_stack, $this->wt_report);
2546
+        $this->wt_report       = $this->report_root->createHTML($tag, $attrs);
2547
+        $this->current_element = $this->wt_report;
2548
+
2549
+        array_push($this->print_data_stack, $this->print_data);
2550
+        $this->print_data = true;
2551
+    }
2552
+
2553
+    /**
2554
+     * XML </html>
2555
+     *
2556
+     * @param string $tag
2557
+     */
2558
+    private function htmlEndHandler($tag) {
2559
+        if ($tag === "tempdoc") {
2560
+            return;
2561
+        }
2562
+
2563
+        $this->print_data      = array_pop($this->print_data_stack);
2564
+        $this->current_element = $this->wt_report;
2565
+        $this->wt_report       = array_pop($this->wt_report_stack);
2566
+        if (!is_null($this->wt_report)) {
2567
+            $this->wt_report->addElement($this->current_element);
2568
+        } else {
2569
+            $this->wt_report = $this->current_element;
2570
+        }
2571
+    }
2572
+
2573
+    /**
2574
+     * Handle <Input>
2575
+     */
2576
+    private function inputStartHandler() {
2577
+        // Dummy function, to prevent the default HtmlStartHandler() being called
2578
+    }
2579
+
2580
+    /**
2581
+     * Handle </Input>
2582
+     */
2583
+    private function inputEndHandler() {
2584
+        // Dummy function, to prevent the default HtmlEndHandler() being called
2585
+    }
2586
+
2587
+    /**
2588
+     * Handle <Report>
2589
+     */
2590
+    private function reportStartHandler() {
2591
+        // Dummy function, to prevent the default HtmlStartHandler() being called
2592
+    }
2593
+
2594
+    /**
2595
+     * Handle </Report>
2596
+     */
2597
+    private function reportEndHandler() {
2598
+        // Dummy function, to prevent the default HtmlEndHandler() being called
2599
+    }
2600
+
2601
+    /**
2602
+     * XML </titleEndHandler>
2603
+     */
2604
+    private function titleEndHandler() {
2605
+        $this->report_root->addTitle($this->text);
2606
+    }
2607
+
2608
+    /**
2609
+     * XML </descriptionEndHandler>
2610
+     */
2611
+    private function descriptionEndHandler() {
2612
+        $this->report_root->addDescription($this->text);
2613
+    }
2614
+
2615
+    /**
2616
+     * Create a list of all descendants.
2617
+     *
2618
+     * @param string[] $list
2619
+     * @param string   $pid
2620
+     * @param bool  $parents
2621
+     * @param int  $generations
2622
+     */
2623
+    private function addDescendancy(&$list, $pid, $parents = false, $generations = -1) {
2624
+        global $WT_TREE;
2625
+
2626
+        $person = Individual::getInstance($pid, $WT_TREE);
2627
+        if ($person === null) {
2628
+            return;
2629
+        }
2630
+        if (!isset($list[$pid])) {
2631
+            $list[$pid] = $person;
2632
+        }
2633
+        if (!isset($list[$pid]->generation)) {
2634
+            $list[$pid]->generation = 0;
2635
+        }
2636
+        foreach ($person->getSpouseFamilies() as $family) {
2637
+            if ($parents) {
2638
+                $husband = $family->getHusband();
2639
+                $wife    = $family->getWife();
2640
+                if ($husband) {
2641
+                    $list[$husband->getXref()] = $husband;
2642
+                    if (isset($list[$pid]->generation)) {
2643
+                        $list[$husband->getXref()]->generation = $list[$pid]->generation - 1;
2644
+                    } else {
2645
+                        $list[$husband->getXref()]->generation = 1;
2646
+                    }
2647
+                }
2648
+                if ($wife) {
2649
+                    $list[$wife->getXref()] = $wife;
2650
+                    if (isset($list[$pid]->generation)) {
2651
+                        $list[$wife->getXref()]->generation = $list[$pid]->generation - 1;
2652
+                    } else {
2653
+                        $list[$wife->getXref()]->generation = 1;
2654
+                    }
2655
+                }
2656
+            }
2657
+            $children = $family->getChildren();
2658
+            foreach ($children as $child) {
2659
+                if ($child) {
2660
+                    $list[$child->getXref()] = $child;
2661
+                    if (isset($list[$pid]->generation)) {
2662
+                        $list[$child->getXref()]->generation = $list[$pid]->generation + 1;
2663
+                    } else {
2664
+                        $list[$child->getXref()]->generation = 2;
2665
+                    }
2666
+                }
2667
+            }
2668
+            if ($generations == -1 || $list[$pid]->generation + 1 < $generations) {
2669
+                foreach ($children as $child) {
2670
+                    $this->addDescendancy($list, $child->getXref(), $parents, $generations); // recurse on the childs family
2671
+                }
2672
+            }
2673
+        }
2674
+    }
2675
+
2676
+    /**
2677
+     * Create a list of all ancestors.
2678
+     *
2679
+     * @param string[] $list
2680
+     * @param string   $pid
2681
+     * @param bool  $children
2682
+     * @param int  $generations
2683
+     */
2684
+    private function addAncestors(&$list, $pid, $children = false, $generations = -1) {
2685
+        global $WT_TREE;
2686
+
2687
+        $genlist                = array($pid);
2688
+        $list[$pid]->generation = 1;
2689
+        while (count($genlist) > 0) {
2690
+            $id = array_shift($genlist);
2691
+            if (strpos($id, 'empty') === 0) {
2692
+                continue; // id can be something like “empty7”
2693
+            }
2694
+            $person = Individual::getInstance($id, $WT_TREE);
2695
+            foreach ($person->getChildFamilies() as $family) {
2696
+                $husband = $family->getHusband();
2697
+                $wife    = $family->getWife();
2698
+                if ($husband) {
2699
+                    $list[$husband->getXref()]             = $husband;
2700
+                    $list[$husband->getXref()]->generation = $list[$id]->generation + 1;
2701
+                }
2702
+                if ($wife) {
2703
+                    $list[$wife->getXref()]             = $wife;
2704
+                    $list[$wife->getXref()]->generation = $list[$id]->generation + 1;
2705
+                }
2706
+                if ($generations == -1 || $list[$id]->generation + 1 < $generations) {
2707
+                    if ($husband) {
2708
+                        array_push($genlist, $husband->getXref());
2709
+                    }
2710
+                    if ($wife) {
2711
+                        array_push($genlist, $wife->getXref());
2712
+                    }
2713
+                }
2714
+                if ($children) {
2715
+                    foreach ($family->getChildren() as $child) {
2716
+                        $list[$child->getXref()] = $child;
2717
+                        if (isset($list[$id]->generation)) {
2718
+                            $list[$child->getXref()]->generation = $list[$id]->generation;
2719
+                        } else {
2720
+                            $list[$child->getXref()]->generation = 1;
2721
+                        }
2722
+                    }
2723
+                }
2724
+            }
2725
+        }
2726
+    }
2727
+
2728
+    /**
2729
+     * get gedcom tag value
2730
+     *
2731
+     * @param string  $tag    The tag to find, use : to delineate subtags
2732
+     * @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
2733
+     * @param string  $gedrec The gedcom record to get the value from
2734
+     *
2735
+     * @return string the value of a gedcom tag from the given gedcom record
2736
+     */
2737
+    private function getGedcomValue($tag, $level, $gedrec) {
2738
+        global $WT_TREE;
2739
+
2740
+        if (empty($gedrec)) {
2741
+            return '';
2742
+        }
2743
+        $tags      = explode(':', $tag);
2744
+        $origlevel = $level;
2745
+        if ($level == 0) {
2746
+            $level = $gedrec{0} + 1;
2747
+        }
2748
+
2749
+        $subrec = $gedrec;
2750
+        foreach ($tags as $t) {
2751
+            $lastsubrec = $subrec;
2752
+            $subrec     = Functions::getSubRecord($level, "$level $t", $subrec);
2753
+            if (empty($subrec) && $origlevel == 0) {
2754
+                $level--;
2755
+                $subrec = Functions::getSubRecord($level, "$level $t", $lastsubrec);
2756
+            }
2757
+            if (empty($subrec)) {
2758
+                if ($t == "TITL") {
2759
+                    $subrec = Functions::getSubRecord($level, "$level ABBR", $lastsubrec);
2760
+                    if (!empty($subrec)) {
2761
+                        $t = "ABBR";
2762
+                    }
2763
+                }
2764
+                if (empty($subrec)) {
2765
+                    if ($level > 0) {
2766
+                        $level--;
2767
+                    }
2768
+                    $subrec = Functions::getSubRecord($level, "@ $t", $gedrec);
2769
+                    if (empty($subrec)) {
2770
+                        return '';
2771
+                    }
2772
+                }
2773
+            }
2774
+            $level++;
2775
+        }
2776
+        $level--;
2777
+        $ct = preg_match("/$level $t(.*)/", $subrec, $match);
2778
+        if ($ct == 0) {
2779
+            $ct = preg_match("/$level @.+@ (.+)/", $subrec, $match);
2780
+        }
2781
+        if ($ct == 0) {
2782
+            $ct = preg_match("/@ $t (.+)/", $subrec, $match);
2783
+        }
2784
+        if ($ct > 0) {
2785
+            $value = trim($match[1]);
2786
+            if ($t == 'NOTE' && preg_match('/^@(.+)@$/', $value, $match)) {
2787
+                $note = Note::getInstance($match[1], $WT_TREE);
2788
+                if ($note) {
2789
+                    $value = $note->getNote();
2790
+                } else {
2791
+                    //-- set the value to the id without the @
2792
+                    $value = $match[1];
2793
+                }
2794
+            }
2795
+            if ($level != 0 || $t != "NOTE") {
2796
+                $value .= Functions::getCont($level + 1, $subrec);
2797
+            }
2798
+
2799
+            return $value;
2800
+        }
2801
+
2802
+        return "";
2803
+    }
2804
+
2805
+    /**
2806
+     * Replace variable identifiers with their values.
2807
+     *
2808
+     * @param string $expression An expression such as "$foo == 123"
2809
+     * @param bool   $quote      Whether to add quotation marks
2810
+     *
2811
+     * @return string
2812
+     */
2813
+    private function substituteVars($expression, $quote) {
2814
+        $that = $this; // PHP5.3 cannot access $this inside a closure
2815
+        return preg_replace_callback(
2816
+            '/\$(\w+)/',
2817
+            function ($matches) use ($that, $quote) {
2818
+                if (isset($that->vars[$matches[1]]['id'])) {
2819
+                    if ($quote) {
2820
+                        return "'" . addcslashes($that->vars[$matches[1]]['id'], "'") . "'";
2821
+                    } else {
2822
+                        return $that->vars[$matches[1]]['id'];
2823
+                    }
2824
+                } else {
2825
+                    Log::addErrorLog(sprintf('Undefined variable $%s in report', $matches[1]));
2826
+
2827
+                    return '$' . $matches[1];
2828
+                }
2829
+            },
2830
+            $expression
2831
+        );
2832
+    }
2833 2833
 }
Please login to merge, or discard this patch.
app/GedcomCode/GedcomCodeTemp.php 2 patches
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -57,14 +57,14 @@
 block discarded – undo
57 57
 			'MADRI', 'MANAU', 'MANHA', 'MANIL', 'MANTI', 'MEDFO', 'MELBO', 'MEMPH',
58 58
 			'MERID', 'MEXIC', 'MNTVD', 'MONTE', 'MONTI', 'MONTR', 'MTIMP', 'NASHV',
59 59
 			'NAUV2', 'NAUVO', 'NBEAC', 'NUKUA', 'NYORK', 'NZEAL', 'OAKLA', 'OAXAC',
60
-			'OGDEN', 'OKLAH',	'OQUIR', 'ORLAN', 'PALEG', 'PALMY', 'PANAM', 'PAPEE',
61
-			'PAYSO', 'PERTH',	'PHOEN', 'POFFI', 'PORTL', 'PREST', 'PROCC', 'PROVO',
60
+			'OGDEN', 'OKLAH', 'OQUIR', 'ORLAN', 'PALEG', 'PALMY', 'PANAM', 'PAPEE',
61
+			'PAYSO', 'PERTH', 'PHOEN', 'POFFI', 'PORTL', 'PREST', 'PROCC', 'PROVO',
62 62
 			'QUETZ', 'RALEI', 'RECIF', 'REDLA', 'REGIN', 'RENO', 'REXBU', 'SACRA',
63
-			'SAMOA', 'SANTI',	'SANSA', 'SANTO', 'SDIEG', 'SDOMI', 'SEATT', 'SEOUL',
64
-			'SGEOR', 'SJOSE',	'SLAKE', 'SLOUI', 'SNOWF','SPAUL', 'SPMIN', 'SPOKA',
63
+			'SAMOA', 'SANTI', 'SANSA', 'SANTO', 'SDIEG', 'SDOMI', 'SEATT', 'SEOUL',
64
+			'SGEOR', 'SJOSE', 'SLAKE', 'SLOUI', 'SNOWF', 'SPAUL', 'SPMIN', 'SPOKA',
65 65
 			'STOCK', 'SUVA', 'SWISS', 'SYDNE', 'TAIPE', 'TAMPI', 'TEGUC', 'TGUTI',
66
-			'TIHUA', 'TOKYO',	'TORNO', 'TRUJI', 'TWINF', 'VANCO', 'VERAC', 'VERNA',
67
-			'VILLA', 'WASHI',	'WINTE',
66
+			'TIHUA', 'TOKYO', 'TORNO', 'TRUJI', 'TWINF', 'VANCO', 'VERAC', 'VERNA',
67
+			'VILLA', 'WASHI', 'WINTE',
68 68
 		);
69 69
 	}
70 70
 
Please login to merge, or discard this patch.
Indentation   +381 added lines, -381 removed lines patch added patch discarded remove patch
@@ -21,389 +21,389 @@
 block discarded – undo
21 21
  * Class GedcomCodeTemp - Functions and logic for GEDCOM "TEMP" codes
22 22
  */
23 23
 class GedcomCodeTemp {
24
-	/**
25
-	 * A list of GEDCOM tags that require a TEMP subtag
26
-	 *
27
-	 * @param string $tag
28
-	 *
29
-	 * @return bool
30
-	 */
31
-	public static function isTagLDS($tag) {
32
-		return $tag === 'BAPL' || $tag === 'CONL' || $tag === 'ENDL' || $tag === 'SLGC' || $tag === 'SLGS';
33
-	}
24
+    /**
25
+     * A list of GEDCOM tags that require a TEMP subtag
26
+     *
27
+     * @param string $tag
28
+     *
29
+     * @return bool
30
+     */
31
+    public static function isTagLDS($tag) {
32
+        return $tag === 'BAPL' || $tag === 'CONL' || $tag === 'ENDL' || $tag === 'SLGC' || $tag === 'SLGS';
33
+    }
34 34
 
35
-	/**
36
-	 * A list of all temple codes, from the GEDCOM 5.5.1 specification
37
-	 *
38
-	 * Note that this list is out-of-date. We could add recently built
39
-	 * temples, but what codes would we use?
40
-	 *
41
-	 * @link http://en.wikipedia.org/wiki/List_of_temples_of_The_Church_of_Jesus_Christ_of_Latter-day_Saints
42
-	 * @link http://www.ldschurchtemples.com/codes/
43
-	 *
44
-	 * @return string[]
45
-	 */
46
-	public static function templeCodes() {
47
-		return array(
48
-			'ABA', 'ACCRA', 'ADELA', 'ALBER', 'ALBUQ', 'ANCHO', 'ARIZO', 'ASUNC',
49
-			'ATLAN', 'BAIRE', 'BILLI', 'BIRMI', 'BISMA', 'BOGOT', 'BOISE', 'BOSTO',
50
-			'BOUNT', 'BRIGH', 'BRISB', 'BROUG', 'CALGA', 'CAMPI', 'CARAC', 'CEBUP',
51
-			'CHICA', 'CIUJU', 'COCHA', 'COLJU', 'COLSC', 'COLUM', 'COPEN', 'CORDO',
52
-			'CRIVE', 'CURIT', 'DALLA', 'DENVE', 'DETRO', 'DRAPE', 'EDMON', 'EHOUS',
53
-			'FORTL', 'FRANK', 'FREIB', 'FRESN', 'FUKUO', 'GILAV', 'GILBE', 'GUADA',
54
-			'GUATE', 'GUAYA', 'HAGUE', 'HALIF', 'HARTF', 'HAWAI', 'HELSI', 'HERMO',
55
-			'HKONG', 'HOUST', 'IFALL', 'INDIA', 'JOHAN', 'JRIVE', 'KANSA', 'KONA',
56
-			'KYIV', 'LANGE', 'LIMA', 'LOGAN', 'LONDO', 'LOUIS', 'LUBBO', 'LVEGA',
57
-			'MADRI', 'MANAU', 'MANHA', 'MANIL', 'MANTI', 'MEDFO', 'MELBO', 'MEMPH',
58
-			'MERID', 'MEXIC', 'MNTVD', 'MONTE', 'MONTI', 'MONTR', 'MTIMP', 'NASHV',
59
-			'NAUV2', 'NAUVO', 'NBEAC', 'NUKUA', 'NYORK', 'NZEAL', 'OAKLA', 'OAXAC',
60
-			'OGDEN', 'OKLAH',	'OQUIR', 'ORLAN', 'PALEG', 'PALMY', 'PANAM', 'PAPEE',
61
-			'PAYSO', 'PERTH',	'PHOEN', 'POFFI', 'PORTL', 'PREST', 'PROCC', 'PROVO',
62
-			'QUETZ', 'RALEI', 'RECIF', 'REDLA', 'REGIN', 'RENO', 'REXBU', 'SACRA',
63
-			'SAMOA', 'SANTI',	'SANSA', 'SANTO', 'SDIEG', 'SDOMI', 'SEATT', 'SEOUL',
64
-			'SGEOR', 'SJOSE',	'SLAKE', 'SLOUI', 'SNOWF','SPAUL', 'SPMIN', 'SPOKA',
65
-			'STOCK', 'SUVA', 'SWISS', 'SYDNE', 'TAIPE', 'TAMPI', 'TEGUC', 'TGUTI',
66
-			'TIHUA', 'TOKYO',	'TORNO', 'TRUJI', 'TWINF', 'VANCO', 'VERAC', 'VERNA',
67
-			'VILLA', 'WASHI',	'WINTE',
68
-		);
69
-	}
35
+    /**
36
+     * A list of all temple codes, from the GEDCOM 5.5.1 specification
37
+     *
38
+     * Note that this list is out-of-date. We could add recently built
39
+     * temples, but what codes would we use?
40
+     *
41
+     * @link http://en.wikipedia.org/wiki/List_of_temples_of_The_Church_of_Jesus_Christ_of_Latter-day_Saints
42
+     * @link http://www.ldschurchtemples.com/codes/
43
+     *
44
+     * @return string[]
45
+     */
46
+    public static function templeCodes() {
47
+        return array(
48
+            'ABA', 'ACCRA', 'ADELA', 'ALBER', 'ALBUQ', 'ANCHO', 'ARIZO', 'ASUNC',
49
+            'ATLAN', 'BAIRE', 'BILLI', 'BIRMI', 'BISMA', 'BOGOT', 'BOISE', 'BOSTO',
50
+            'BOUNT', 'BRIGH', 'BRISB', 'BROUG', 'CALGA', 'CAMPI', 'CARAC', 'CEBUP',
51
+            'CHICA', 'CIUJU', 'COCHA', 'COLJU', 'COLSC', 'COLUM', 'COPEN', 'CORDO',
52
+            'CRIVE', 'CURIT', 'DALLA', 'DENVE', 'DETRO', 'DRAPE', 'EDMON', 'EHOUS',
53
+            'FORTL', 'FRANK', 'FREIB', 'FRESN', 'FUKUO', 'GILAV', 'GILBE', 'GUADA',
54
+            'GUATE', 'GUAYA', 'HAGUE', 'HALIF', 'HARTF', 'HAWAI', 'HELSI', 'HERMO',
55
+            'HKONG', 'HOUST', 'IFALL', 'INDIA', 'JOHAN', 'JRIVE', 'KANSA', 'KONA',
56
+            'KYIV', 'LANGE', 'LIMA', 'LOGAN', 'LONDO', 'LOUIS', 'LUBBO', 'LVEGA',
57
+            'MADRI', 'MANAU', 'MANHA', 'MANIL', 'MANTI', 'MEDFO', 'MELBO', 'MEMPH',
58
+            'MERID', 'MEXIC', 'MNTVD', 'MONTE', 'MONTI', 'MONTR', 'MTIMP', 'NASHV',
59
+            'NAUV2', 'NAUVO', 'NBEAC', 'NUKUA', 'NYORK', 'NZEAL', 'OAKLA', 'OAXAC',
60
+            'OGDEN', 'OKLAH',	'OQUIR', 'ORLAN', 'PALEG', 'PALMY', 'PANAM', 'PAPEE',
61
+            'PAYSO', 'PERTH',	'PHOEN', 'POFFI', 'PORTL', 'PREST', 'PROCC', 'PROVO',
62
+            'QUETZ', 'RALEI', 'RECIF', 'REDLA', 'REGIN', 'RENO', 'REXBU', 'SACRA',
63
+            'SAMOA', 'SANTI',	'SANSA', 'SANTO', 'SDIEG', 'SDOMI', 'SEATT', 'SEOUL',
64
+            'SGEOR', 'SJOSE',	'SLAKE', 'SLOUI', 'SNOWF','SPAUL', 'SPMIN', 'SPOKA',
65
+            'STOCK', 'SUVA', 'SWISS', 'SYDNE', 'TAIPE', 'TAMPI', 'TEGUC', 'TGUTI',
66
+            'TIHUA', 'TOKYO',	'TORNO', 'TRUJI', 'TWINF', 'VANCO', 'VERAC', 'VERNA',
67
+            'VILLA', 'WASHI',	'WINTE',
68
+        );
69
+    }
70 70
 
71
-	/**
72
-	 * Get the localized name for a temple code
73
-	 *
74
-	 * @param string $temple_code
75
-	 *
76
-	 * @return string
77
-	 */
78
-	public static function templeName($temple_code) {
79
-		switch ($temple_code) {
80
-		case 'ABA':
81
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Aba, Nigeria');
82
-		case 'ACCRA':
83
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Accra, Ghana');
84
-		case 'ADELA':
85
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Adelaide, Australia');
86
-		case 'ALBER':
87
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Cardston, Alberta, Canada');
88
-		case 'ALBUQ':
89
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Albuquerque, New Mexico, United States');
90
-		case 'ANCHO':
91
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Anchorage, Alaska, United States');
92
-		case 'APIA':
93
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Apia, Samoa');
94
-		case 'ARIZO':
95
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Mesa, Arizona, United States');
96
-		case 'ASUNC':
97
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Asuncion, Paraguay');
98
-		case 'ATLAN':
99
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Atlanta, Georgia, United States');
100
-		case 'BAIRE':
101
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Buenos Aires, Argentina');
102
-		case 'BILLI':
103
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Billings, Montana, United States');
104
-		case 'BIRMI':
105
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Birmingham, Alabama, United States');
106
-		case 'BISMA':
107
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Bismarck, North Dakota, United States');
108
-		case 'BOGOT':
109
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Bogota, Colombia');
110
-		case 'BOISE':
111
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Boise, Idaho, United States');
112
-		case 'BOSTO':
113
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Boston, Massachusetts, United States');
114
-		case 'BOUNT':
115
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Bountiful, Utah, United States');
116
-		case 'BRIGH':
117
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Brigham City, Utah, United States');
118
-		case 'BRISB':
119
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Brisbane, Australia');
120
-		case 'BROUG':
121
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Baton Rouge, Louisiana, United States');
122
-		case 'CALGA':
123
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Calgary, Alberta, Canada');
124
-		case 'CAMPI':
125
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Campinas, Brazil');
126
-		case 'CARAC':
127
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Caracas, Venezuela');
128
-		case 'CEBUP':
129
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Cebu City, Philippines');
130
-		case 'CHICA':
131
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Chicago, Illinois, United States');
132
-		case 'CIUJU':
133
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Ciudad Juarez, Mexico');
134
-		case 'COCHA':
135
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Cochabamba, Bolivia');
136
-		case 'COLJU':
137
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Colonia Juarez, Mexico');
138
-		case 'COLSC':
139
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Columbia, South Carolina, United States');
140
-		case 'COLUM':
141
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Columbus, Ohio, United States');
142
-		case 'COPEN':
143
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Copenhagen, Denmark');
144
-		case 'CORDO':
145
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Cordoba, Argentina');
146
-		case 'CRIVE':
147
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Columbia River, Washington, United States');
148
-		case 'CURIT':
149
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Curitiba, Brazil');
150
-		case 'DALLA':
151
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Dallas, Texas, United States');
152
-		case 'DENVE':
153
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Denver, Colorado, United States');
154
-		case 'DETRO':
155
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Detroit, Michigan, United States');
156
-		case 'DRAPE':
157
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Draper, Utah, United States');
158
-		case 'EDMON':
159
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Edmonton, Alberta, Canada');
160
-		case 'EHOUS':
161
-			return /* I18N: Location of an historic LDS church temple - http://en.wikipedia.org/wiki/Endowment_house */ I18N::translate('Endowment House');
162
-		case 'FORTL':
163
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Fort Lauderdale, Florida, United States');
164
-		case 'FRANK':
165
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Frankfurt am Main, Germany');
166
-		case 'FREIB':
167
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Freiburg, Germany');
168
-		case 'FRESN':
169
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Fresno, California, United States');
170
-		case 'FUKUO':
171
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Fukuoka, Japan');
172
-		case 'GILAV':
173
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Gila Valley, Arizona, United States');
174
-		case 'GILBE':
175
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Gilbert, Arizona, United States');
176
-		case 'GUADA':
177
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Guadalajara, Mexico');
178
-		case 'GUATE':
179
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Guatemala City, Guatemala');
180
-		case 'GUAYA':
181
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Guayaquil, Ecuador');
182
-		case 'HAGUE':
183
-			return /* I18N: Location of an LDS church temple */ I18N::translate('The Hague, Netherlands');
184
-		case 'HALIF':
185
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Halifax, Nova Scotia, Canada');
186
-		case 'HARTF':
187
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Hartford, Connecticut, United States');
188
-		case 'HAWAI':
189
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Laie, Hawaii, United States');
190
-		case 'HELSI':
191
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Helsinki, Finland');
192
-		case 'HERMO':
193
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Hermosillo, Mexico');
194
-		case 'HKONG':
195
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Hong Kong');
196
-		case 'HOUST':
197
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Houston, Texas, United States');
198
-		case 'IFALL':
199
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Idaho Falls, Idaho, United States');
200
-		case 'INDIA':
201
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Indianapolis, Indiana, United States');
202
-		case 'JOHAN':
203
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Johannesburg, South Africa');
204
-		case 'JRIVE':
205
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Jordan River, Utah, United States');
206
-		case 'KANSA':
207
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Kansas City, Missouri, United States');
208
-		case 'KONA':
209
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Kona, Hawaii, United States');
210
-		case 'KYIV':
211
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Kiev, Ukraine');
212
-		case 'LANGE':
213
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Los Angeles, California, United States');
214
-		case 'LIMA':
215
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Lima, Peru');
216
-		case 'LOGAN':
217
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Logan, Utah, United States');
218
-		case 'LONDO':
219
-			return /* I18N: Location of an LDS church temple */ I18N::translate('London, England');
220
-		case 'LOUIS':
221
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Louisville, Kentucky, United States');
222
-		case 'LUBBO':
223
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Lubbock, Texas, United States');
224
-		case 'LVEGA':
225
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Las Vegas, Nevada, United States');
226
-		case 'MADRI':
227
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Madrid, Spain');
228
-		case 'MANAU':
229
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Manaus, Brazil');
230
-		case 'MANHA':
231
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Manhattan, New York, United States');
232
-		case 'MANIL':
233
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Manila, Philippines');
234
-		case 'MANTI':
235
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Manti, Utah, United States');
236
-		case 'MEDFO':
237
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Medford, Oregon, United States');
238
-		case 'MELBO':
239
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Melbourne, Australia');
240
-		case 'MEMPH':
241
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Memphis, Tennessee, United States');
242
-		case 'MERID':
243
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Merida, Mexico');
244
-		case 'MEXIC':
245
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Mexico City, Mexico');
246
-		case 'MNTVD':
247
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Montevideo, Uruguay');
248
-		case 'MONTE':
249
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Monterrey, Mexico');
250
-		case 'MONTI':
251
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Monticello, Utah, United States');
252
-		case 'MONTR':
253
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Montreal, Quebec, Canada');
254
-		case 'MTIMP':
255
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Mount Timpanogos, Utah, United States');
256
-		case 'NASHV':
257
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Nashville, Tennessee, United States');
258
-		case 'NAUV2':
259
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Nauvoo (new), Illinois, United States');
260
-		case 'NAUVO':
261
-			return /* I18N: Location of an historic LDS church temple */ I18N::translate('Nauvoo (original), Illinois, United States');
262
-		case 'NBEAC':
263
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Newport Beach, California, United States');
264
-		case 'NUKUA':
265
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Nuku’Alofa, Tonga');
266
-		case 'NYORK':
267
-			return /* I18N: Location of an historic LDS church temple */ I18N::translate('New York, New York, United States');
268
-		case 'NZEAL':
269
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Hamilton, New Zealand');
270
-		case 'OAKLA':
271
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Oakland, California, United States');
272
-		case 'OAXAC':
273
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Oaxaca, Mexico');
274
-		case 'OGDEN':
275
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Ogden, Utah, United States');
276
-		case 'OKLAH':
277
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Oklahoma City, Oklahoma, United States');
278
-		case 'OQUIR':
279
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Oquirrh Mountain, Utah, United States');
280
-		case 'ORLAN':
281
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Orlando, Florida, United States');
282
-		case 'PALEG':
283
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Porto Alegre, Brazil');
284
-		case 'PALMY':
285
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Palmyra, New York, United States');
286
-		case 'PANAM':
287
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Panama City, Panama');
288
-		case 'PAPEE':
289
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Papeete, Tahiti');
290
-		case 'PAYSO':
291
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Payson, Utah, United States');
292
-		case 'PERTH':
293
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Perth, Australia');
294
-		case 'PHOEN':
295
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Phoenix, Arizona, United States');
296
-		case 'POFFI':
297
-			return /* I18N: I18N: Location of an historic LDS church temple - http://en.wikipedia.org/wiki/President_of_the_Church */ I18N::translate('President’s Office');
298
-		case 'PORTL':
299
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Portland, Oregon, United States');
300
-		case 'PREST':
301
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Preston, England');
302
-		case 'PROCC':
303
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Provo City Center, Utah, United States');
304
-		case 'PROVO':
305
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Provo, Utah, United States');
306
-		case 'QUETZ':
307
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Quetzaltenango, Guatemala');
308
-		case 'RALEI':
309
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Raleigh, North Carolina, United States');
310
-		case 'RECIF':
311
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Recife, Brazil');
312
-		case 'REDLA':
313
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Redlands, California, United States');
314
-		case 'REGIN':
315
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Regina, Saskatchewan, Canada');
316
-		case 'RENO':
317
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Reno, Nevada, United States');
318
-		case 'REXBU':
319
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Rexburg, Idaho, United States');
320
-		case 'SACRA':
321
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Sacramento, California, United States');
322
-		case 'SANSA':
323
-			return /* I18N: Location of an LDS church temple */ I18N::translate('San Salvador, El Salvador');
324
-		case 'SANTI':
325
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Santiago, Chile');
326
-		case 'SANTO':
327
-			return /* I18N: Location of an LDS church temple */ I18N::translate('San Antonio, Texas, United States');
328
-		case 'SDIEG':
329
-			return /* I18N: Location of an LDS church temple */ I18N::translate('San Diego, California, United States');
330
-		case 'SDOMI':
331
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Santo Domingo, Dominican Republic');
332
-		case 'SEATT':
333
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Seattle, Washington, United States');
334
-		case 'SEOUL':
335
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Seoul, Korea');
336
-		case 'SGEOR':
337
-			return /* I18N: Location of an LDS church temple */ I18N::translate('St. George, Utah, United States');
338
-		case 'SJOSE':
339
-			return /* I18N: Location of an LDS church temple */ I18N::translate('San Jose, Costa Rica');
340
-		case 'SLAKE':
341
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Salt Lake City, Utah, United States');
342
-		case 'SLOUI':
343
-			return /* I18N: Location of an LDS church temple */ I18N::translate('St. Louis, Missouri, United States');
344
-		case 'SNOWF':
345
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Snowflake, Arizona, United States');
346
-		case 'SPAUL':
347
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Sao Paulo, Brazil');
348
-		case 'SPMIN':
349
-			return /* I18N: Location of an LDS church temple */ I18N::translate('St. Paul, Minnesota, United States');
350
-		case 'SPOKA':
351
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Spokane, Washington, United States');
352
-		case 'STOCK':
353
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Stockholm, Sweden');
354
-		case 'SUVA':
355
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Suva, Fiji');
356
-		case 'SWISS':
357
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Bern, Switzerland');
358
-		case 'SYDNE':
359
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Sydney, Australia');
360
-		case 'TAIPE':
361
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Taipei, Taiwan');
362
-		case 'TAMPI':
363
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Tampico, Mexico');
364
-		case 'TEGUC':
365
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Tegucigalpa, Honduras');
366
-		case 'TGUTI':
367
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Tuxtla Gutierrez, Mexico');
368
-		case 'TIJUA':
369
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Tijuana, Mexico');
370
-		case 'TOKYO':
371
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Tokyo, Japan');
372
-		case 'TORNO':
373
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Toronto, Ontario, Canada');
374
-		case 'TRUJI':
375
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Trujillo, Peru');
376
-		case 'TWINF':
377
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Twin Falls, Idaho, United States');
378
-		case 'VANCO':
379
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Vancouver, British Columbia, Canada');
380
-		case 'VERAC':
381
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Veracruz, Mexico');
382
-		case 'VERNA':
383
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Vernal, Utah, United States');
384
-		case 'VILLA':
385
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Villa Hermosa, Mexico');
386
-		case 'WASHI':
387
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Washington, District of Columbia, United States');
388
-		case 'WINTE':
389
-			return /* I18N: Location of an LDS church temple */ I18N::translate('Winter Quarters, Nebraska, United States');
390
-		default:
391
-			return $temple_code;
392
-		}
393
-	}
71
+    /**
72
+     * Get the localized name for a temple code
73
+     *
74
+     * @param string $temple_code
75
+     *
76
+     * @return string
77
+     */
78
+    public static function templeName($temple_code) {
79
+        switch ($temple_code) {
80
+        case 'ABA':
81
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Aba, Nigeria');
82
+        case 'ACCRA':
83
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Accra, Ghana');
84
+        case 'ADELA':
85
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Adelaide, Australia');
86
+        case 'ALBER':
87
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Cardston, Alberta, Canada');
88
+        case 'ALBUQ':
89
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Albuquerque, New Mexico, United States');
90
+        case 'ANCHO':
91
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Anchorage, Alaska, United States');
92
+        case 'APIA':
93
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Apia, Samoa');
94
+        case 'ARIZO':
95
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Mesa, Arizona, United States');
96
+        case 'ASUNC':
97
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Asuncion, Paraguay');
98
+        case 'ATLAN':
99
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Atlanta, Georgia, United States');
100
+        case 'BAIRE':
101
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Buenos Aires, Argentina');
102
+        case 'BILLI':
103
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Billings, Montana, United States');
104
+        case 'BIRMI':
105
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Birmingham, Alabama, United States');
106
+        case 'BISMA':
107
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Bismarck, North Dakota, United States');
108
+        case 'BOGOT':
109
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Bogota, Colombia');
110
+        case 'BOISE':
111
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Boise, Idaho, United States');
112
+        case 'BOSTO':
113
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Boston, Massachusetts, United States');
114
+        case 'BOUNT':
115
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Bountiful, Utah, United States');
116
+        case 'BRIGH':
117
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Brigham City, Utah, United States');
118
+        case 'BRISB':
119
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Brisbane, Australia');
120
+        case 'BROUG':
121
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Baton Rouge, Louisiana, United States');
122
+        case 'CALGA':
123
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Calgary, Alberta, Canada');
124
+        case 'CAMPI':
125
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Campinas, Brazil');
126
+        case 'CARAC':
127
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Caracas, Venezuela');
128
+        case 'CEBUP':
129
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Cebu City, Philippines');
130
+        case 'CHICA':
131
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Chicago, Illinois, United States');
132
+        case 'CIUJU':
133
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Ciudad Juarez, Mexico');
134
+        case 'COCHA':
135
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Cochabamba, Bolivia');
136
+        case 'COLJU':
137
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Colonia Juarez, Mexico');
138
+        case 'COLSC':
139
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Columbia, South Carolina, United States');
140
+        case 'COLUM':
141
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Columbus, Ohio, United States');
142
+        case 'COPEN':
143
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Copenhagen, Denmark');
144
+        case 'CORDO':
145
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Cordoba, Argentina');
146
+        case 'CRIVE':
147
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Columbia River, Washington, United States');
148
+        case 'CURIT':
149
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Curitiba, Brazil');
150
+        case 'DALLA':
151
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Dallas, Texas, United States');
152
+        case 'DENVE':
153
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Denver, Colorado, United States');
154
+        case 'DETRO':
155
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Detroit, Michigan, United States');
156
+        case 'DRAPE':
157
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Draper, Utah, United States');
158
+        case 'EDMON':
159
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Edmonton, Alberta, Canada');
160
+        case 'EHOUS':
161
+            return /* I18N: Location of an historic LDS church temple - http://en.wikipedia.org/wiki/Endowment_house */ I18N::translate('Endowment House');
162
+        case 'FORTL':
163
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Fort Lauderdale, Florida, United States');
164
+        case 'FRANK':
165
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Frankfurt am Main, Germany');
166
+        case 'FREIB':
167
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Freiburg, Germany');
168
+        case 'FRESN':
169
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Fresno, California, United States');
170
+        case 'FUKUO':
171
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Fukuoka, Japan');
172
+        case 'GILAV':
173
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Gila Valley, Arizona, United States');
174
+        case 'GILBE':
175
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Gilbert, Arizona, United States');
176
+        case 'GUADA':
177
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Guadalajara, Mexico');
178
+        case 'GUATE':
179
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Guatemala City, Guatemala');
180
+        case 'GUAYA':
181
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Guayaquil, Ecuador');
182
+        case 'HAGUE':
183
+            return /* I18N: Location of an LDS church temple */ I18N::translate('The Hague, Netherlands');
184
+        case 'HALIF':
185
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Halifax, Nova Scotia, Canada');
186
+        case 'HARTF':
187
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Hartford, Connecticut, United States');
188
+        case 'HAWAI':
189
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Laie, Hawaii, United States');
190
+        case 'HELSI':
191
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Helsinki, Finland');
192
+        case 'HERMO':
193
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Hermosillo, Mexico');
194
+        case 'HKONG':
195
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Hong Kong');
196
+        case 'HOUST':
197
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Houston, Texas, United States');
198
+        case 'IFALL':
199
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Idaho Falls, Idaho, United States');
200
+        case 'INDIA':
201
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Indianapolis, Indiana, United States');
202
+        case 'JOHAN':
203
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Johannesburg, South Africa');
204
+        case 'JRIVE':
205
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Jordan River, Utah, United States');
206
+        case 'KANSA':
207
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Kansas City, Missouri, United States');
208
+        case 'KONA':
209
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Kona, Hawaii, United States');
210
+        case 'KYIV':
211
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Kiev, Ukraine');
212
+        case 'LANGE':
213
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Los Angeles, California, United States');
214
+        case 'LIMA':
215
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Lima, Peru');
216
+        case 'LOGAN':
217
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Logan, Utah, United States');
218
+        case 'LONDO':
219
+            return /* I18N: Location of an LDS church temple */ I18N::translate('London, England');
220
+        case 'LOUIS':
221
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Louisville, Kentucky, United States');
222
+        case 'LUBBO':
223
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Lubbock, Texas, United States');
224
+        case 'LVEGA':
225
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Las Vegas, Nevada, United States');
226
+        case 'MADRI':
227
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Madrid, Spain');
228
+        case 'MANAU':
229
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Manaus, Brazil');
230
+        case 'MANHA':
231
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Manhattan, New York, United States');
232
+        case 'MANIL':
233
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Manila, Philippines');
234
+        case 'MANTI':
235
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Manti, Utah, United States');
236
+        case 'MEDFO':
237
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Medford, Oregon, United States');
238
+        case 'MELBO':
239
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Melbourne, Australia');
240
+        case 'MEMPH':
241
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Memphis, Tennessee, United States');
242
+        case 'MERID':
243
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Merida, Mexico');
244
+        case 'MEXIC':
245
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Mexico City, Mexico');
246
+        case 'MNTVD':
247
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Montevideo, Uruguay');
248
+        case 'MONTE':
249
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Monterrey, Mexico');
250
+        case 'MONTI':
251
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Monticello, Utah, United States');
252
+        case 'MONTR':
253
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Montreal, Quebec, Canada');
254
+        case 'MTIMP':
255
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Mount Timpanogos, Utah, United States');
256
+        case 'NASHV':
257
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Nashville, Tennessee, United States');
258
+        case 'NAUV2':
259
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Nauvoo (new), Illinois, United States');
260
+        case 'NAUVO':
261
+            return /* I18N: Location of an historic LDS church temple */ I18N::translate('Nauvoo (original), Illinois, United States');
262
+        case 'NBEAC':
263
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Newport Beach, California, United States');
264
+        case 'NUKUA':
265
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Nuku’Alofa, Tonga');
266
+        case 'NYORK':
267
+            return /* I18N: Location of an historic LDS church temple */ I18N::translate('New York, New York, United States');
268
+        case 'NZEAL':
269
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Hamilton, New Zealand');
270
+        case 'OAKLA':
271
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Oakland, California, United States');
272
+        case 'OAXAC':
273
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Oaxaca, Mexico');
274
+        case 'OGDEN':
275
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Ogden, Utah, United States');
276
+        case 'OKLAH':
277
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Oklahoma City, Oklahoma, United States');
278
+        case 'OQUIR':
279
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Oquirrh Mountain, Utah, United States');
280
+        case 'ORLAN':
281
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Orlando, Florida, United States');
282
+        case 'PALEG':
283
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Porto Alegre, Brazil');
284
+        case 'PALMY':
285
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Palmyra, New York, United States');
286
+        case 'PANAM':
287
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Panama City, Panama');
288
+        case 'PAPEE':
289
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Papeete, Tahiti');
290
+        case 'PAYSO':
291
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Payson, Utah, United States');
292
+        case 'PERTH':
293
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Perth, Australia');
294
+        case 'PHOEN':
295
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Phoenix, Arizona, United States');
296
+        case 'POFFI':
297
+            return /* I18N: I18N: Location of an historic LDS church temple - http://en.wikipedia.org/wiki/President_of_the_Church */ I18N::translate('President’s Office');
298
+        case 'PORTL':
299
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Portland, Oregon, United States');
300
+        case 'PREST':
301
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Preston, England');
302
+        case 'PROCC':
303
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Provo City Center, Utah, United States');
304
+        case 'PROVO':
305
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Provo, Utah, United States');
306
+        case 'QUETZ':
307
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Quetzaltenango, Guatemala');
308
+        case 'RALEI':
309
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Raleigh, North Carolina, United States');
310
+        case 'RECIF':
311
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Recife, Brazil');
312
+        case 'REDLA':
313
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Redlands, California, United States');
314
+        case 'REGIN':
315
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Regina, Saskatchewan, Canada');
316
+        case 'RENO':
317
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Reno, Nevada, United States');
318
+        case 'REXBU':
319
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Rexburg, Idaho, United States');
320
+        case 'SACRA':
321
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Sacramento, California, United States');
322
+        case 'SANSA':
323
+            return /* I18N: Location of an LDS church temple */ I18N::translate('San Salvador, El Salvador');
324
+        case 'SANTI':
325
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Santiago, Chile');
326
+        case 'SANTO':
327
+            return /* I18N: Location of an LDS church temple */ I18N::translate('San Antonio, Texas, United States');
328
+        case 'SDIEG':
329
+            return /* I18N: Location of an LDS church temple */ I18N::translate('San Diego, California, United States');
330
+        case 'SDOMI':
331
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Santo Domingo, Dominican Republic');
332
+        case 'SEATT':
333
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Seattle, Washington, United States');
334
+        case 'SEOUL':
335
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Seoul, Korea');
336
+        case 'SGEOR':
337
+            return /* I18N: Location of an LDS church temple */ I18N::translate('St. George, Utah, United States');
338
+        case 'SJOSE':
339
+            return /* I18N: Location of an LDS church temple */ I18N::translate('San Jose, Costa Rica');
340
+        case 'SLAKE':
341
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Salt Lake City, Utah, United States');
342
+        case 'SLOUI':
343
+            return /* I18N: Location of an LDS church temple */ I18N::translate('St. Louis, Missouri, United States');
344
+        case 'SNOWF':
345
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Snowflake, Arizona, United States');
346
+        case 'SPAUL':
347
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Sao Paulo, Brazil');
348
+        case 'SPMIN':
349
+            return /* I18N: Location of an LDS church temple */ I18N::translate('St. Paul, Minnesota, United States');
350
+        case 'SPOKA':
351
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Spokane, Washington, United States');
352
+        case 'STOCK':
353
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Stockholm, Sweden');
354
+        case 'SUVA':
355
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Suva, Fiji');
356
+        case 'SWISS':
357
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Bern, Switzerland');
358
+        case 'SYDNE':
359
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Sydney, Australia');
360
+        case 'TAIPE':
361
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Taipei, Taiwan');
362
+        case 'TAMPI':
363
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Tampico, Mexico');
364
+        case 'TEGUC':
365
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Tegucigalpa, Honduras');
366
+        case 'TGUTI':
367
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Tuxtla Gutierrez, Mexico');
368
+        case 'TIJUA':
369
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Tijuana, Mexico');
370
+        case 'TOKYO':
371
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Tokyo, Japan');
372
+        case 'TORNO':
373
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Toronto, Ontario, Canada');
374
+        case 'TRUJI':
375
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Trujillo, Peru');
376
+        case 'TWINF':
377
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Twin Falls, Idaho, United States');
378
+        case 'VANCO':
379
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Vancouver, British Columbia, Canada');
380
+        case 'VERAC':
381
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Veracruz, Mexico');
382
+        case 'VERNA':
383
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Vernal, Utah, United States');
384
+        case 'VILLA':
385
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Villa Hermosa, Mexico');
386
+        case 'WASHI':
387
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Washington, District of Columbia, United States');
388
+        case 'WINTE':
389
+            return /* I18N: Location of an LDS church temple */ I18N::translate('Winter Quarters, Nebraska, United States');
390
+        default:
391
+            return $temple_code;
392
+        }
393
+    }
394 394
 
395
-	/**
396
-	 * A sorted list of all temple names
397
-	 *
398
-	 * @return string[]
399
-	 */
400
-	public static function templeNames() {
401
-		$temple_names = array();
402
-		foreach (self::templeCodes() as $temple_code) {
403
-			$temple_names[$temple_code] = self::templeName($temple_code);
404
-		}
405
-		uasort($temple_names, '\Fisharebest\Webtrees\I18N::strcasecmp');
395
+    /**
396
+     * A sorted list of all temple names
397
+     *
398
+     * @return string[]
399
+     */
400
+    public static function templeNames() {
401
+        $temple_names = array();
402
+        foreach (self::templeCodes() as $temple_code) {
403
+            $temple_names[$temple_code] = self::templeName($temple_code);
404
+        }
405
+        uasort($temple_names, '\Fisharebest\Webtrees\I18N::strcasecmp');
406 406
 
407
-		return $temple_names;
408
-	}
407
+        return $temple_names;
408
+    }
409 409
 }
Please login to merge, or discard this patch.
edituser.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -44,7 +44,7 @@
 block discarded – undo
44 44
 $form_pass2          = Filter::post('form_pass2', WT_REGEX_PASSWORD);
45 45
 $form_email          = Filter::postEmail('form_email');
46 46
 $form_rootid         = Filter::post('form_rootid', WT_REGEX_XREF);
47
-$form_theme          = Filter::post('form_theme', implode('|', array_keys(Theme::themeNames())), '');;
47
+$form_theme          = Filter::post('form_theme', implode('|', array_keys(Theme::themeNames())), ''); ;
48 48
 $form_language       = Filter::post('form_language');
49 49
 $form_timezone       = Filter::post('form_timezone');
50 50
 $form_contact_method = Filter::post('form_contact_method');
Please login to merge, or discard this patch.
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -31,9 +31,9 @@  discard block
 block discarded – undo
31 31
 
32 32
 // Need to be logged in
33 33
 if (!Auth::check()) {
34
-	header('Location: ' . WT_BASE_URL);
34
+    header('Location: ' . WT_BASE_URL);
35 35
 
36
-	return;
36
+    return;
37 37
 }
38 38
 
39 39
 // Extract form variables
@@ -52,66 +52,66 @@  discard block
 block discarded – undo
52 52
 
53 53
 // Respond to form action
54 54
 if ($form_action && Filter::checkCsrf()) {
55
-	switch ($form_action) {
56
-	case 'update':
57
-		if ($form_username !== Auth::user()->getUserName() && User::findByUserName($form_username)) {
58
-			FlashMessages::addMessage(I18N::translate('Duplicate username. A user with that username already exists. Please choose another username.'));
59
-		} elseif ($form_email !== Auth::user()->getEmail() && User::findByEmail($form_email)) {
60
-			FlashMessages::addMessage(I18N::translate('Duplicate email address. A user with that email already exists.'));
61
-		} else {
62
-			// Change username
63
-			if ($form_username !== Auth::user()->getUserName()) {
64
-				Log::addAuthenticationLog('User ' . Auth::user()->getUserName() . ' renamed to ' . $form_username);
65
-				Auth::user()->setUserName($form_username);
66
-			}
67
-
68
-			// Change password
69
-			if ($form_pass1 && $form_pass1 === $form_pass2) {
70
-				Auth::user()->setPassword($form_pass1);
71
-			}
72
-
73
-			// Change other settings
74
-			Auth::user()
75
-				->setRealName($form_realname)
76
-				->setEmail($form_email)
77
-				->setPreference('language', $form_language)
78
-				->setPreference('TIMEZONE', $form_timezone)
79
-				->setPreference('contactmethod', $form_contact_method)
80
-				->setPreference('visibleonline', $form_visible_online ? '1' : '0');
81
-
82
-			if ($form_theme === null) {
83
-				Auth::user()->deletePreference('theme');
84
-			} else {
85
-				Auth::user()->setPreference('theme', $form_theme);
86
-			}
87
-
88
-			$WT_TREE->setUserPreference(Auth::user(), 'rootid', $form_rootid);
89
-		}
90
-		break;
91
-
92
-	case 'delete':
93
-		// An administrator can only be deleted by another administrator
94
-		if (!Auth::user()->getPreference('canadmin')) {
95
-			// Keep a reference to the currently logged in user because after logging out this user,
96
-			// a call to Auth::user() will not return this user anymore
97
-			$currentUser = Auth::user();
98
-			Auth::logout();
99
-			$currentUser->delete();
100
-		}
101
-		break;
102
-	}
103
-
104
-	header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME);
105
-
106
-	return;
55
+    switch ($form_action) {
56
+    case 'update':
57
+        if ($form_username !== Auth::user()->getUserName() && User::findByUserName($form_username)) {
58
+            FlashMessages::addMessage(I18N::translate('Duplicate username. A user with that username already exists. Please choose another username.'));
59
+        } elseif ($form_email !== Auth::user()->getEmail() && User::findByEmail($form_email)) {
60
+            FlashMessages::addMessage(I18N::translate('Duplicate email address. A user with that email already exists.'));
61
+        } else {
62
+            // Change username
63
+            if ($form_username !== Auth::user()->getUserName()) {
64
+                Log::addAuthenticationLog('User ' . Auth::user()->getUserName() . ' renamed to ' . $form_username);
65
+                Auth::user()->setUserName($form_username);
66
+            }
67
+
68
+            // Change password
69
+            if ($form_pass1 && $form_pass1 === $form_pass2) {
70
+                Auth::user()->setPassword($form_pass1);
71
+            }
72
+
73
+            // Change other settings
74
+            Auth::user()
75
+                ->setRealName($form_realname)
76
+                ->setEmail($form_email)
77
+                ->setPreference('language', $form_language)
78
+                ->setPreference('TIMEZONE', $form_timezone)
79
+                ->setPreference('contactmethod', $form_contact_method)
80
+                ->setPreference('visibleonline', $form_visible_online ? '1' : '0');
81
+
82
+            if ($form_theme === null) {
83
+                Auth::user()->deletePreference('theme');
84
+            } else {
85
+                Auth::user()->setPreference('theme', $form_theme);
86
+            }
87
+
88
+            $WT_TREE->setUserPreference(Auth::user(), 'rootid', $form_rootid);
89
+        }
90
+        break;
91
+
92
+    case 'delete':
93
+        // An administrator can only be deleted by another administrator
94
+        if (!Auth::user()->getPreference('canadmin')) {
95
+            // Keep a reference to the currently logged in user because after logging out this user,
96
+            // a call to Auth::user() will not return this user anymore
97
+            $currentUser = Auth::user();
98
+            Auth::logout();
99
+            $currentUser->delete();
100
+        }
101
+        break;
102
+    }
103
+
104
+    header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME);
105
+
106
+    return;
107 107
 }
108 108
 
109 109
 $controller = new PageController;
110 110
 $controller
111
-	->setPageTitle(I18N::translate('My account'))
112
-	->pageHeader()
113
-	->addExternalJavascript(WT_AUTOCOMPLETE_JS_URL)
114
-	->addInlineJavascript('autocomplete();');
111
+    ->setPageTitle(I18N::translate('My account'))
112
+    ->pageHeader()
113
+    ->addExternalJavascript(WT_AUTOCOMPLETE_JS_URL)
114
+    ->addInlineJavascript('autocomplete();');
115 115
 
116 116
 $my_individual_record = Individual::getInstance($WT_TREE->getUserPreference(Auth::user(), 'gedcomid'), $WT_TREE);
117 117
 $default_individual   = Individual::getInstance($WT_TREE->getUserPreference(Auth::user(), 'rootid'), $WT_TREE);
Please login to merge, or discard this patch.
app/Functions/FunctionsPrintLists.php 2 patches
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -1820,24 +1820,24 @@
 block discarded – undo
1820 1820
 			if ($endjd === WT_CLIENT_JD) {
1821 1821
 				// We're dealing with the Today’s Events block
1822 1822
 				if ($filter === 0) {
1823
-					$html .=  I18N::translate('No events exist for today.');
1823
+					$html .= I18N::translate('No events exist for today.');
1824 1824
 				} else {
1825
-					$html .=  I18N::translate('No events for living individuals exist for today.');
1825
+					$html .= I18N::translate('No events for living individuals exist for today.');
1826 1826
 				}
1827 1827
 			} else {
1828 1828
 				// We're dealing with the Upcoming Events block
1829 1829
 				if ($filter === 0) {
1830 1830
 					if ($endjd === $startjd) {
1831
-						$html .=  I18N::translate('No events exist for tomorrow.');
1831
+						$html .= I18N::translate('No events exist for tomorrow.');
1832 1832
 					} else {
1833
-						$html .=  /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1833
+						$html .= /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1834 1834
 					}
1835 1835
 				} else {
1836 1836
 					if ($endjd === $startjd) {
1837
-						$html .=  I18N::translate('No events for living individuals exist for tomorrow.');
1837
+						$html .= I18N::translate('No events for living individuals exist for tomorrow.');
1838 1838
 					} else {
1839 1839
 						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1840
-						$html .=  I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1840
+						$html .= I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1841 1841
 					}
1842 1842
 				}
1843 1843
 			}
Please login to merge, or discard this patch.
Indentation   +1342 added lines, -1342 removed lines patch added patch discarded remove patch
@@ -38,49 +38,49 @@  discard block
 block discarded – undo
38 38
  * Class FunctionsPrintLists - create sortable lists using datatables.net
39 39
  */
40 40
 class FunctionsPrintLists {
41
-	/**
42
-	 * Generate a SURN,GIVN and GIVN,SURN sortable name for an individual.
43
-	 * This allows table data to sort by surname or given names.
44
-	 *
45
-	 * Use AAAA as a separator (instead of ","), as Javascript localeCompare()
46
-	 * ignores punctuation and "ANN,ROACH" would sort after "ANNE,ROACH",
47
-	 * instead of before it.
48
-	 *
49
-	 * @param Individual $individual
50
-	 *
51
-	 * @return string[]
52
-	 */
53
-	private static function sortableNames(Individual $individual) {
54
-		$names   = $individual->getAllNames();
55
-		$primary = $individual->getPrimaryName();
56
-
57
-		list($surn, $givn) = explode(',', $names[$primary]['sort']);
58
-
59
-		$givn = str_replace('@P.N.', 'AAAA', $givn);
60
-		$surn = str_replace('@N.N.', 'AAAA', $surn);
61
-
62
-		return array(
63
-			$surn . 'AAAA' . $givn,
64
-			$givn . 'AAAA' . $surn,
65
-		);
66
-	}
67
-
68
-	/**
69
-	 * Print a table of individuals
70
-	 *
71
-	 * @param Individual[] $indiviudals
72
-	 * @param string       $option
73
-	 *
74
-	 * @return string
75
-	 */
76
-	public static function individualTable($indiviudals, $option = '') {
77
-		global $controller, $WT_TREE;
78
-
79
-		$table_id = 'table-indi-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
80
-
81
-		$controller
82
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
83
-			->addInlineJavascript('
41
+    /**
42
+     * Generate a SURN,GIVN and GIVN,SURN sortable name for an individual.
43
+     * This allows table data to sort by surname or given names.
44
+     *
45
+     * Use AAAA as a separator (instead of ","), as Javascript localeCompare()
46
+     * ignores punctuation and "ANN,ROACH" would sort after "ANNE,ROACH",
47
+     * instead of before it.
48
+     *
49
+     * @param Individual $individual
50
+     *
51
+     * @return string[]
52
+     */
53
+    private static function sortableNames(Individual $individual) {
54
+        $names   = $individual->getAllNames();
55
+        $primary = $individual->getPrimaryName();
56
+
57
+        list($surn, $givn) = explode(',', $names[$primary]['sort']);
58
+
59
+        $givn = str_replace('@P.N.', 'AAAA', $givn);
60
+        $surn = str_replace('@N.N.', 'AAAA', $surn);
61
+
62
+        return array(
63
+            $surn . 'AAAA' . $givn,
64
+            $givn . 'AAAA' . $surn,
65
+        );
66
+    }
67
+
68
+    /**
69
+     * Print a table of individuals
70
+     *
71
+     * @param Individual[] $indiviudals
72
+     * @param string       $option
73
+     *
74
+     * @return string
75
+     */
76
+    public static function individualTable($indiviudals, $option = '') {
77
+        global $controller, $WT_TREE;
78
+
79
+        $table_id = 'table-indi-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
80
+
81
+        $controller
82
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
83
+            ->addInlineJavascript('
84 84
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
85 85
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
86 86
 				jQuery("#' . $table_id . '").dataTable( {
@@ -144,24 +144,24 @@  discard block
 block discarded – undo
144 144
 				jQuery(".loading-image").css("display", "none");
145 145
 			');
146 146
 
147
-		$stats = new Stats($WT_TREE);
147
+        $stats = new Stats($WT_TREE);
148 148
 
149
-		// Bad data can cause "longest life" to be huge, blowing memory limits
150
-		$max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->longestLifeAge()) + 1;
149
+        // Bad data can cause "longest life" to be huge, blowing memory limits
150
+        $max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->longestLifeAge()) + 1;
151 151
 
152
-		// Inititialise chart data
153
-		$deat_by_age = array();
154
-		for ($age = 0; $age <= $max_age; $age++) {
155
-			$deat_by_age[$age] = '';
156
-		}
157
-		$birt_by_decade = array();
158
-		$deat_by_decade = array();
159
-		for ($year = 1550; $year < 2030; $year += 10) {
160
-			$birt_by_decade[$year] = '';
161
-			$deat_by_decade[$year] = '';
162
-		}
152
+        // Inititialise chart data
153
+        $deat_by_age = array();
154
+        for ($age = 0; $age <= $max_age; $age++) {
155
+            $deat_by_age[$age] = '';
156
+        }
157
+        $birt_by_decade = array();
158
+        $deat_by_decade = array();
159
+        for ($year = 1550; $year < 2030; $year += 10) {
160
+            $birt_by_decade[$year] = '';
161
+            $deat_by_decade[$year] = '';
162
+        }
163 163
 
164
-		$html = '
164
+        $html = '
165 165
 			<div class="loading-image"></div>
166 166
 			<div class="indi-list">
167 167
 				<table id="' . $table_id . '">
@@ -316,175 +316,175 @@  discard block
 block discarded – undo
316 316
 					</tfoot>
317 317
 					<tbody>';
318 318
 
319
-		$hundred_years_ago = new Date(date('Y') - 100);
320
-		$unique_indis      = array(); // Don't double-count indis with multiple names.
321
-
322
-		foreach ($indiviudals as $key => $individual) {
323
-			if (!$individual->canShowName()) {
324
-				continue;
325
-			}
326
-			if ($individual->isPendingAddtion()) {
327
-				$class = ' class="new"';
328
-			} elseif ($individual->isPendingDeletion()) {
329
-				$class = ' class="old"';
330
-			} else {
331
-				$class = '';
332
-			}
333
-			$html .= '<tr' . $class . '>';
334
-			// Extract Given names and Surnames for sorting
335
-			list($surn_givn, $givn_surn) = self::sortableNames($individual);
336
-
337
-			$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
338
-			foreach ($individual->getAllNames() as $num => $name) {
339
-				if ($name['type'] == 'NAME') {
340
-					$title = '';
341
-				} else {
342
-					$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $individual)) . '"';
343
-				}
344
-				if ($num == $individual->getPrimaryName()) {
345
-					$class             = ' class="name2"';
346
-					$sex_image         = $individual->getSexImage();
347
-				} else {
348
-					$class     = '';
349
-					$sex_image = '';
350
-				}
351
-				$html .= '<a ' . $title . ' href="' . $individual->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
352
-			}
353
-			$html .= $individual->getPrimaryParentsNames('parents details1', 'none');
354
-			$html .= '</td>';
355
-
356
-			// Hidden column for sortable name
357
-			$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
358
-
359
-			// SOSA
360
-			$html .= '<td class="center" data-sort="' . $key . '">';
361
-			if ($option === 'sosa') {
362
-				$html .= '<a href="relationship.php?pid1=' . $indiviudals[1] . '&amp;pid2=' . $individual->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a>';
363
-			}
364
-			$html .= '</td>';
365
-
366
-			// Birth date
367
-			$birth_dates = $individual->getAllBirthDates();
368
-			$html .= '<td data-sort="' . $individual->getEstimatedBirthDate()->julianDay() . '">';
369
-			foreach ($birth_dates as $n => $birth_date) {
370
-				if ($n > 0) {
371
-					$html .= '<br>';
372
-				}
373
-				$html .= $birth_date->display(true);
374
-			}
375
-			$html .= '</td>';
376
-
377
-			// Birth anniversary
378
-			if (isset($birth_dates[0]) && $birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
379
-				$birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
380
-				$anniversary = Date::getAge($birth_dates[0], null, 2);
381
-			} else {
382
-				$anniversary = '';
383
-			}
384
-			$html .= '<td class="center" data-sort="' . -$individual->getEstimatedBirthDate()->julianDay() . '">' . $anniversary . '</td>';
385
-
386
-			// Birth place
387
-			$html .= '<td>';
388
-			foreach ($individual->getAllBirthPlaces() as $n => $birth_place) {
389
-				$tmp = new Place($birth_place, $individual->getTree());
390
-				if ($n > 0) {
391
-					$html .= '<br>';
392
-				}
393
-				$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
394
-				$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
395
-			}
396
-			$html .= '</td>';
397
-
398
-			// Number of children
399
-			$number_of_children = $individual->getNumberOfChildren();
400
-			$html .= '<td class="center" data-sort="' . $number_of_children . '">' . I18N::number($number_of_children) . '</td>';
401
-
402
-			// Death date
403
-			$death_dates = $individual->getAllDeathDates();
404
-			$html .= '<td data-sort="' . $individual->getEstimatedDeathDate()->julianDay() . '">';
405
-			foreach ($death_dates as $num => $death_date) {
406
-				if ($num) {
407
-					$html .= '<br>';
408
-				}
409
-				$html .= $death_date->display(true);
410
-			}
411
-			$html .= '</td>';
412
-
413
-			// Death anniversary
414
-			if (isset($death_dates[0]) && $death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
415
-				$birt_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
416
-				$anniversary = Date::getAge($death_dates[0], null, 2);
417
-			} else {
418
-				$anniversary = '';
419
-			}
420
-			$html .= '<td class="center" data-sort="' . -$individual->getEstimatedDeathDate()->julianDay() . '">' . $anniversary . '</td>';
421
-
422
-			// Age at death
423
-			if (isset($birth_dates[0]) && isset($death_dates[0])) {
424
-				$age_at_death      = Date::getAge($birth_dates[0], $death_dates[0], 0);
425
-				$age_at_death_sort = Date::getAge($birth_dates[0], $death_dates[0], 2);
426
-				if (!isset($unique_indis[$individual->getXref()]) && $age >= 0 && $age <= $max_age) {
427
-					$deat_by_age[$age_at_death] .= $individual->getSex();
428
-				}
429
-			} else {
430
-				$age_at_death      = '';
431
-				$age_at_death_sort = PHP_INT_MAX;
432
-			}
433
-			$html .= '<td class="center" data-sort="' . $age_at_death_sort . '">' . $age_at_death . '</td>';
434
-
435
-			// Death place
436
-			$html .= '<td>';
437
-			foreach ($individual->getAllDeathPlaces() as $n => $death_place) {
438
-				$tmp = new Place($death_place, $individual->getTree());
439
-				if ($n > 0) {
440
-					$html .= '<br>';
441
-				}
442
-				$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
443
-				$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
444
-			}
445
-			$html .= '</td>';
446
-
447
-			// Last change
448
-			$html .= '<td data-sort="' . $individual->lastChangeTimestamp(true) . '">' . $individual->lastChangeTimestamp() . '</td>';
449
-
450
-			// Filter by sex
451
-			$html .= '<td hidden>' . $individual->getSex() . '</td>';
452
-
453
-			// Filter by birth date
454
-			$html .= '<td hidden>';
455
-			if (!$individual->canShow() || Date::compare($individual->getEstimatedBirthDate(), $hundred_years_ago) > 0) {
456
-				$html .= 'Y100';
457
-			} else {
458
-				$html .= 'YES';
459
-			}
460
-			$html .= '</td>';
461
-
462
-			// Filter by death date
463
-			$html .= '<td hidden>';
464
-			// Died in last 100 years? Died? Not dead?
465
-			if (isset($death_dates[0]) && Date::compare($death_dates[0], $hundred_years_ago) > 0) {
466
-				$html .= 'Y100';
467
-			} elseif ($individual->isDead()) {
468
-				$html .= 'YES';
469
-			} else {
470
-				$html .= 'N';
471
-			}
472
-			$html .= '</td>';
473
-
474
-			// Filter by roots/leaves
475
-			$html .= '<td hidden>';
476
-			if (!$individual->getChildFamilies()) {
477
-				$html .= 'R';
478
-			} elseif (!$individual->isDead() && $individual->getNumberOfChildren() < 1) {
479
-				$html .= 'L';
480
-				$html .= '&nbsp;';
481
-			}
482
-			$html .= '</td>';
483
-			$html .= '</tr>';
484
-
485
-			$unique_indis[$individual->getXref()] = true;
486
-		}
487
-		$html .= '
319
+        $hundred_years_ago = new Date(date('Y') - 100);
320
+        $unique_indis      = array(); // Don't double-count indis with multiple names.
321
+
322
+        foreach ($indiviudals as $key => $individual) {
323
+            if (!$individual->canShowName()) {
324
+                continue;
325
+            }
326
+            if ($individual->isPendingAddtion()) {
327
+                $class = ' class="new"';
328
+            } elseif ($individual->isPendingDeletion()) {
329
+                $class = ' class="old"';
330
+            } else {
331
+                $class = '';
332
+            }
333
+            $html .= '<tr' . $class . '>';
334
+            // Extract Given names and Surnames for sorting
335
+            list($surn_givn, $givn_surn) = self::sortableNames($individual);
336
+
337
+            $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
338
+            foreach ($individual->getAllNames() as $num => $name) {
339
+                if ($name['type'] == 'NAME') {
340
+                    $title = '';
341
+                } else {
342
+                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $individual)) . '"';
343
+                }
344
+                if ($num == $individual->getPrimaryName()) {
345
+                    $class             = ' class="name2"';
346
+                    $sex_image         = $individual->getSexImage();
347
+                } else {
348
+                    $class     = '';
349
+                    $sex_image = '';
350
+                }
351
+                $html .= '<a ' . $title . ' href="' . $individual->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
352
+            }
353
+            $html .= $individual->getPrimaryParentsNames('parents details1', 'none');
354
+            $html .= '</td>';
355
+
356
+            // Hidden column for sortable name
357
+            $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
358
+
359
+            // SOSA
360
+            $html .= '<td class="center" data-sort="' . $key . '">';
361
+            if ($option === 'sosa') {
362
+                $html .= '<a href="relationship.php?pid1=' . $indiviudals[1] . '&amp;pid2=' . $individual->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a>';
363
+            }
364
+            $html .= '</td>';
365
+
366
+            // Birth date
367
+            $birth_dates = $individual->getAllBirthDates();
368
+            $html .= '<td data-sort="' . $individual->getEstimatedBirthDate()->julianDay() . '">';
369
+            foreach ($birth_dates as $n => $birth_date) {
370
+                if ($n > 0) {
371
+                    $html .= '<br>';
372
+                }
373
+                $html .= $birth_date->display(true);
374
+            }
375
+            $html .= '</td>';
376
+
377
+            // Birth anniversary
378
+            if (isset($birth_dates[0]) && $birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
379
+                $birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
380
+                $anniversary = Date::getAge($birth_dates[0], null, 2);
381
+            } else {
382
+                $anniversary = '';
383
+            }
384
+            $html .= '<td class="center" data-sort="' . -$individual->getEstimatedBirthDate()->julianDay() . '">' . $anniversary . '</td>';
385
+
386
+            // Birth place
387
+            $html .= '<td>';
388
+            foreach ($individual->getAllBirthPlaces() as $n => $birth_place) {
389
+                $tmp = new Place($birth_place, $individual->getTree());
390
+                if ($n > 0) {
391
+                    $html .= '<br>';
392
+                }
393
+                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
394
+                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
395
+            }
396
+            $html .= '</td>';
397
+
398
+            // Number of children
399
+            $number_of_children = $individual->getNumberOfChildren();
400
+            $html .= '<td class="center" data-sort="' . $number_of_children . '">' . I18N::number($number_of_children) . '</td>';
401
+
402
+            // Death date
403
+            $death_dates = $individual->getAllDeathDates();
404
+            $html .= '<td data-sort="' . $individual->getEstimatedDeathDate()->julianDay() . '">';
405
+            foreach ($death_dates as $num => $death_date) {
406
+                if ($num) {
407
+                    $html .= '<br>';
408
+                }
409
+                $html .= $death_date->display(true);
410
+            }
411
+            $html .= '</td>';
412
+
413
+            // Death anniversary
414
+            if (isset($death_dates[0]) && $death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
415
+                $birt_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
416
+                $anniversary = Date::getAge($death_dates[0], null, 2);
417
+            } else {
418
+                $anniversary = '';
419
+            }
420
+            $html .= '<td class="center" data-sort="' . -$individual->getEstimatedDeathDate()->julianDay() . '">' . $anniversary . '</td>';
421
+
422
+            // Age at death
423
+            if (isset($birth_dates[0]) && isset($death_dates[0])) {
424
+                $age_at_death      = Date::getAge($birth_dates[0], $death_dates[0], 0);
425
+                $age_at_death_sort = Date::getAge($birth_dates[0], $death_dates[0], 2);
426
+                if (!isset($unique_indis[$individual->getXref()]) && $age >= 0 && $age <= $max_age) {
427
+                    $deat_by_age[$age_at_death] .= $individual->getSex();
428
+                }
429
+            } else {
430
+                $age_at_death      = '';
431
+                $age_at_death_sort = PHP_INT_MAX;
432
+            }
433
+            $html .= '<td class="center" data-sort="' . $age_at_death_sort . '">' . $age_at_death . '</td>';
434
+
435
+            // Death place
436
+            $html .= '<td>';
437
+            foreach ($individual->getAllDeathPlaces() as $n => $death_place) {
438
+                $tmp = new Place($death_place, $individual->getTree());
439
+                if ($n > 0) {
440
+                    $html .= '<br>';
441
+                }
442
+                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
443
+                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
444
+            }
445
+            $html .= '</td>';
446
+
447
+            // Last change
448
+            $html .= '<td data-sort="' . $individual->lastChangeTimestamp(true) . '">' . $individual->lastChangeTimestamp() . '</td>';
449
+
450
+            // Filter by sex
451
+            $html .= '<td hidden>' . $individual->getSex() . '</td>';
452
+
453
+            // Filter by birth date
454
+            $html .= '<td hidden>';
455
+            if (!$individual->canShow() || Date::compare($individual->getEstimatedBirthDate(), $hundred_years_ago) > 0) {
456
+                $html .= 'Y100';
457
+            } else {
458
+                $html .= 'YES';
459
+            }
460
+            $html .= '</td>';
461
+
462
+            // Filter by death date
463
+            $html .= '<td hidden>';
464
+            // Died in last 100 years? Died? Not dead?
465
+            if (isset($death_dates[0]) && Date::compare($death_dates[0], $hundred_years_ago) > 0) {
466
+                $html .= 'Y100';
467
+            } elseif ($individual->isDead()) {
468
+                $html .= 'YES';
469
+            } else {
470
+                $html .= 'N';
471
+            }
472
+            $html .= '</td>';
473
+
474
+            // Filter by roots/leaves
475
+            $html .= '<td hidden>';
476
+            if (!$individual->getChildFamilies()) {
477
+                $html .= 'R';
478
+            } elseif (!$individual->isDead() && $individual->getNumberOfChildren() < 1) {
479
+                $html .= 'L';
480
+                $html .= '&nbsp;';
481
+            }
482
+            $html .= '</td>';
483
+            $html .= '</tr>';
484
+
485
+            $unique_indis[$individual->getXref()] = true;
486
+        }
487
+        $html .= '
488 488
 					</tbody>
489 489
 				</table>
490 490
 				<div id="indi_list_table-charts_' . $table_id . '" style="display:none">
@@ -506,24 +506,24 @@  discard block
 block discarded – undo
506 506
 				</div>
507 507
 			</div>';
508 508
 
509
-		return $html;
510
-	}
509
+        return $html;
510
+    }
511 511
 
512
-	/**
513
-	 * Print a table of families
514
-	 *
515
-	 * @param Family[] $families
516
-	 *
517
-	 * @return string
518
-	 */
519
-	public static function familyTable($families) {
520
-		global $WT_TREE, $controller;
512
+    /**
513
+     * Print a table of families
514
+     *
515
+     * @param Family[] $families
516
+     *
517
+     * @return string
518
+     */
519
+    public static function familyTable($families) {
520
+        global $WT_TREE, $controller;
521 521
 
522
-		$table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
522
+        $table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
523 523
 
524
-		$controller
525
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
526
-			->addInlineJavascript('
524
+        $controller
525
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
526
+            ->addInlineJavascript('
527 527
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
528 528
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
529 529
 				jQuery("#' . $table_id . '").dataTable( {
@@ -585,22 +585,22 @@  discard block
 block discarded – undo
585 585
 				jQuery(".loading-image").css("display", "none");
586 586
 		');
587 587
 
588
-		$stats   = new Stats($WT_TREE);
589
-		$max_age = max($stats->oldestMarriageMaleAge(), $stats->oldestMarriageFemaleAge()) + 1;
590
-
591
-		// init chart data
592
-		$marr_by_age = array();
593
-		for ($age = 0; $age <= $max_age; $age++) {
594
-			$marr_by_age[$age] = '';
595
-		}
596
-		$birt_by_decade = array();
597
-		$marr_by_decade = array();
598
-		for ($year = 1550; $year < 2030; $year += 10) {
599
-			$birt_by_decade[$year] = '';
600
-			$marr_by_decade[$year] = '';
601
-		}
602
-
603
-		$html = '
588
+        $stats   = new Stats($WT_TREE);
589
+        $max_age = max($stats->oldestMarriageMaleAge(), $stats->oldestMarriageFemaleAge()) + 1;
590
+
591
+        // init chart data
592
+        $marr_by_age = array();
593
+        for ($age = 0; $age <= $max_age; $age++) {
594
+            $marr_by_age[$age] = '';
595
+        }
596
+        $birt_by_decade = array();
597
+        $marr_by_decade = array();
598
+        for ($year = 1550; $year < 2030; $year += 10) {
599
+            $birt_by_decade[$year] = '';
600
+            $marr_by_decade[$year] = '';
601
+        }
602
+
603
+        $html = '
604 604
 			<div class="loading-image"></div>
605 605
 			<div class="fam-list">
606 606
 				<table id="' . $table_id . '">
@@ -751,215 +751,215 @@  discard block
 block discarded – undo
751 751
 					</tfoot>
752 752
 					<tbody>';
753 753
 
754
-		$hundred_years_ago = new Date(date('Y') - 100);
755
-
756
-		foreach ($families as $family) {
757
-			// Retrieve husband and wife
758
-			$husb = $family->getHusband();
759
-			if (is_null($husb)) {
760
-				$husb = new Individual('H', '0 @H@ INDI', null, $family->getTree());
761
-			}
762
-			$wife = $family->getWife();
763
-			if (is_null($wife)) {
764
-				$wife = new Individual('W', '0 @W@ INDI', null, $family->getTree());
765
-			}
766
-			if (!$family->canShow()) {
767
-				continue;
768
-			}
769
-			if ($family->isPendingAddtion()) {
770
-				$class = ' class="new"';
771
-			} elseif ($family->isPendingDeletion()) {
772
-				$class = ' class="old"';
773
-			} else {
774
-				$class = '';
775
-			}
776
-			$html .= '<tr' . $class . '>';
777
-			// Husband name(s)
778
-			// Extract Given names and Surnames for sorting
779
-			list($surn_givn, $givn_surn) = self::sortableNames($husb);
780
-
781
-			$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
782
-			foreach ($husb->getAllNames() as $num => $name) {
783
-				if ($name['type'] == 'NAME') {
784
-					$title = '';
785
-				} else {
786
-					$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"';
787
-				}
788
-				if ($num == $husb->getPrimaryName()) {
789
-					$class             = ' class="name2"';
790
-					$sex_image         = $husb->getSexImage();
791
-				} else {
792
-					$class     = '';
793
-					$sex_image = '';
794
-				}
795
-				// Only show married names if they are the name we are filtering by.
796
-				if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) {
797
-					$html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
798
-				}
799
-			}
800
-			// Husband parents
801
-			$html .= $husb->getPrimaryParentsNames('parents details1', 'none');
802
-			$html .= '</td>';
803
-
804
-			// Hidden column for sortable name
805
-			$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
806
-
807
-			// Husband age
808
-			$mdate = $family->getMarriageDate();
809
-			$hdate = $husb->getBirthDate();
810
-			if ($hdate->isOK() && $mdate->isOK()) {
811
-				if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) {
812
-					$birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex();
813
-				}
814
-				$hage = Date::getAge($hdate, $mdate, 0);
815
-				if ($hage >= 0 && $hage <= $max_age) {
816
-					$marr_by_age[$hage] .= $husb->getSex();
817
-				}
818
-			}
819
-			$html .= '<td class="center" data=-sort="' . Date::getAge($hdate, $mdate, 1) . '">' . Date::getAge($hdate, $mdate, 2) . '</td>';
820
-
821
-			// Wife name(s)
822
-			// Extract Given names and Surnames for sorting
823
-			list($surn_givn, $givn_surn) = self::sortableNames($wife);
824
-			$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
825
-			foreach ($wife->getAllNames() as $num => $name) {
826
-				if ($name['type'] == 'NAME') {
827
-					$title = '';
828
-				} else {
829
-					$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"';
830
-				}
831
-				if ($num == $wife->getPrimaryName()) {
832
-					$class             = ' class="name2"';
833
-					$sex_image         = $wife->getSexImage();
834
-				} else {
835
-					$class     = '';
836
-					$sex_image = '';
837
-				}
838
-				// Only show married names if they are the name we are filtering by.
839
-				if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) {
840
-					$html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
841
-				}
842
-			}
843
-			// Wife parents
844
-			$html .= $wife->getPrimaryParentsNames('parents details1', 'none');
845
-			$html .= '</td>';
846
-
847
-			// Hidden column for sortable name
848
-			$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
849
-
850
-			// Wife age
851
-			$mdate = $family->getMarriageDate();
852
-			$wdate = $wife->getBirthDate();
853
-			if ($wdate->isOK() && $mdate->isOK()) {
854
-				if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) {
855
-					$birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex();
856
-				}
857
-				$wage = Date::getAge($wdate, $mdate, 0);
858
-				if ($wage >= 0 && $wage <= $max_age) {
859
-					$marr_by_age[$wage] .= $wife->getSex();
860
-				}
861
-			}
862
-			$html .= '<td class="center" data-sort="' . Date::getAge($wdate, $mdate, 1) . '">' . Date::getAge($wdate, $mdate, 2) . '</td>';
863
-
864
-			// Marriage date
865
-			$html .= '<td data-sort="' . $family->getMarriageDate()->julianDay() . '">';
866
-			if ($marriage_dates = $family->getAllMarriageDates()) {
867
-				foreach ($marriage_dates as $n => $marriage_date) {
868
-					if ($n) {
869
-						$html .= '<br>';
870
-					}
871
-					$html .= '<div>' . $marriage_date->display(true) . '</div>';
872
-				}
873
-				if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) {
874
-					$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex();
875
-				}
876
-			} elseif ($family->getFacts('_NMR')) {
877
-				$html .= I18N::translate('no');
878
-			} elseif ($family->getFacts('MARR')) {
879
-				$html .= I18N::translate('yes');
880
-			} else {
881
-				$html .= '&nbsp;';
882
-			}
883
-			$html .= '</td>';
884
-
885
-			// Marriage anniversary
886
-			$html .= '<td class="center" data-sort="' . -$family->getMarriageDate()->julianDay() . '">' . Date::getAge($family->getMarriageDate(), null, 2) . '</td>';
887
-
888
-			// Marriage place
889
-			$html .= '<td>';
890
-			foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) {
891
-				$tmp = new Place($marriage_place, $family->getTree());
892
-				if ($n) {
893
-					$html .= '<br>';
894
-				}
895
-				$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
896
-				$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
897
-			}
898
-			$html .= '</td>';
899
-
900
-			// Number of children
901
-			$html .= '<td class="center" data-sort="' . $family->getNumberOfChildren() . '">' . I18N::number($family->getNumberOfChildren()) . '</td>';
902
-
903
-			// Last change
904
-			$html .= '<td data-sort="' . $family->lastChangeTimestamp(true) . '">' . $family->lastChangeTimestamp() . '</td>';
905
-
906
-			// Filter by marriage date
907
-			$html .= '<td hidden>';
908
-			if (!$family->canShow() || !$mdate->isOK()) {
909
-				$html .= 'U';
910
-			} else {
911
-				if (Date::compare($mdate, $hundred_years_ago) > 0) {
912
-					$html .= 'Y100';
913
-				} else {
914
-					$html .= 'YES';
915
-				}
916
-			}
917
-			if ($family->getFacts(WT_EVENTS_DIV)) {
918
-				$html .= 'D';
919
-			}
920
-			if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) {
921
-				$html .= 'M';
922
-			}
923
-			$html .= '</td>';
924
-
925
-			// Filter by alive/dead
926
-			$html .= '<td hidden>';
927
-			if ($husb->isDead() && $wife->isDead()) {
928
-				$html .= 'Y';
929
-			}
930
-			if ($husb->isDead() && !$wife->isDead()) {
931
-				if ($wife->getSex() == 'F') {
932
-					$html .= 'H';
933
-				}
934
-				if ($wife->getSex() == 'M') {
935
-					$html .= 'W';
936
-				} // male partners
937
-			}
938
-			if (!$husb->isDead() && $wife->isDead()) {
939
-				if ($husb->getSex() == 'M') {
940
-					$html .= 'W';
941
-				}
942
-				if ($husb->getSex() == 'F') {
943
-					$html .= 'H';
944
-				} // female partners
945
-			}
946
-			if (!$husb->isDead() && !$wife->isDead()) {
947
-				$html .= 'N';
948
-			}
949
-			$html .= '</td>';
950
-
951
-			// Filter by roots/leaves
952
-			$html .= '<td hidden>';
953
-			if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) {
954
-				$html .= 'R';
955
-			} elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() === 0) {
956
-				$html .= 'L';
957
-			}
958
-			$html .= '</td>
754
+        $hundred_years_ago = new Date(date('Y') - 100);
755
+
756
+        foreach ($families as $family) {
757
+            // Retrieve husband and wife
758
+            $husb = $family->getHusband();
759
+            if (is_null($husb)) {
760
+                $husb = new Individual('H', '0 @H@ INDI', null, $family->getTree());
761
+            }
762
+            $wife = $family->getWife();
763
+            if (is_null($wife)) {
764
+                $wife = new Individual('W', '0 @W@ INDI', null, $family->getTree());
765
+            }
766
+            if (!$family->canShow()) {
767
+                continue;
768
+            }
769
+            if ($family->isPendingAddtion()) {
770
+                $class = ' class="new"';
771
+            } elseif ($family->isPendingDeletion()) {
772
+                $class = ' class="old"';
773
+            } else {
774
+                $class = '';
775
+            }
776
+            $html .= '<tr' . $class . '>';
777
+            // Husband name(s)
778
+            // Extract Given names and Surnames for sorting
779
+            list($surn_givn, $givn_surn) = self::sortableNames($husb);
780
+
781
+            $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
782
+            foreach ($husb->getAllNames() as $num => $name) {
783
+                if ($name['type'] == 'NAME') {
784
+                    $title = '';
785
+                } else {
786
+                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"';
787
+                }
788
+                if ($num == $husb->getPrimaryName()) {
789
+                    $class             = ' class="name2"';
790
+                    $sex_image         = $husb->getSexImage();
791
+                } else {
792
+                    $class     = '';
793
+                    $sex_image = '';
794
+                }
795
+                // Only show married names if they are the name we are filtering by.
796
+                if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) {
797
+                    $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
798
+                }
799
+            }
800
+            // Husband parents
801
+            $html .= $husb->getPrimaryParentsNames('parents details1', 'none');
802
+            $html .= '</td>';
803
+
804
+            // Hidden column for sortable name
805
+            $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
806
+
807
+            // Husband age
808
+            $mdate = $family->getMarriageDate();
809
+            $hdate = $husb->getBirthDate();
810
+            if ($hdate->isOK() && $mdate->isOK()) {
811
+                if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) {
812
+                    $birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex();
813
+                }
814
+                $hage = Date::getAge($hdate, $mdate, 0);
815
+                if ($hage >= 0 && $hage <= $max_age) {
816
+                    $marr_by_age[$hage] .= $husb->getSex();
817
+                }
818
+            }
819
+            $html .= '<td class="center" data=-sort="' . Date::getAge($hdate, $mdate, 1) . '">' . Date::getAge($hdate, $mdate, 2) . '</td>';
820
+
821
+            // Wife name(s)
822
+            // Extract Given names and Surnames for sorting
823
+            list($surn_givn, $givn_surn) = self::sortableNames($wife);
824
+            $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
825
+            foreach ($wife->getAllNames() as $num => $name) {
826
+                if ($name['type'] == 'NAME') {
827
+                    $title = '';
828
+                } else {
829
+                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"';
830
+                }
831
+                if ($num == $wife->getPrimaryName()) {
832
+                    $class             = ' class="name2"';
833
+                    $sex_image         = $wife->getSexImage();
834
+                } else {
835
+                    $class     = '';
836
+                    $sex_image = '';
837
+                }
838
+                // Only show married names if they are the name we are filtering by.
839
+                if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) {
840
+                    $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
841
+                }
842
+            }
843
+            // Wife parents
844
+            $html .= $wife->getPrimaryParentsNames('parents details1', 'none');
845
+            $html .= '</td>';
846
+
847
+            // Hidden column for sortable name
848
+            $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
849
+
850
+            // Wife age
851
+            $mdate = $family->getMarriageDate();
852
+            $wdate = $wife->getBirthDate();
853
+            if ($wdate->isOK() && $mdate->isOK()) {
854
+                if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) {
855
+                    $birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex();
856
+                }
857
+                $wage = Date::getAge($wdate, $mdate, 0);
858
+                if ($wage >= 0 && $wage <= $max_age) {
859
+                    $marr_by_age[$wage] .= $wife->getSex();
860
+                }
861
+            }
862
+            $html .= '<td class="center" data-sort="' . Date::getAge($wdate, $mdate, 1) . '">' . Date::getAge($wdate, $mdate, 2) . '</td>';
863
+
864
+            // Marriage date
865
+            $html .= '<td data-sort="' . $family->getMarriageDate()->julianDay() . '">';
866
+            if ($marriage_dates = $family->getAllMarriageDates()) {
867
+                foreach ($marriage_dates as $n => $marriage_date) {
868
+                    if ($n) {
869
+                        $html .= '<br>';
870
+                    }
871
+                    $html .= '<div>' . $marriage_date->display(true) . '</div>';
872
+                }
873
+                if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) {
874
+                    $marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex();
875
+                }
876
+            } elseif ($family->getFacts('_NMR')) {
877
+                $html .= I18N::translate('no');
878
+            } elseif ($family->getFacts('MARR')) {
879
+                $html .= I18N::translate('yes');
880
+            } else {
881
+                $html .= '&nbsp;';
882
+            }
883
+            $html .= '</td>';
884
+
885
+            // Marriage anniversary
886
+            $html .= '<td class="center" data-sort="' . -$family->getMarriageDate()->julianDay() . '">' . Date::getAge($family->getMarriageDate(), null, 2) . '</td>';
887
+
888
+            // Marriage place
889
+            $html .= '<td>';
890
+            foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) {
891
+                $tmp = new Place($marriage_place, $family->getTree());
892
+                if ($n) {
893
+                    $html .= '<br>';
894
+                }
895
+                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
896
+                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
897
+            }
898
+            $html .= '</td>';
899
+
900
+            // Number of children
901
+            $html .= '<td class="center" data-sort="' . $family->getNumberOfChildren() . '">' . I18N::number($family->getNumberOfChildren()) . '</td>';
902
+
903
+            // Last change
904
+            $html .= '<td data-sort="' . $family->lastChangeTimestamp(true) . '">' . $family->lastChangeTimestamp() . '</td>';
905
+
906
+            // Filter by marriage date
907
+            $html .= '<td hidden>';
908
+            if (!$family->canShow() || !$mdate->isOK()) {
909
+                $html .= 'U';
910
+            } else {
911
+                if (Date::compare($mdate, $hundred_years_ago) > 0) {
912
+                    $html .= 'Y100';
913
+                } else {
914
+                    $html .= 'YES';
915
+                }
916
+            }
917
+            if ($family->getFacts(WT_EVENTS_DIV)) {
918
+                $html .= 'D';
919
+            }
920
+            if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) {
921
+                $html .= 'M';
922
+            }
923
+            $html .= '</td>';
924
+
925
+            // Filter by alive/dead
926
+            $html .= '<td hidden>';
927
+            if ($husb->isDead() && $wife->isDead()) {
928
+                $html .= 'Y';
929
+            }
930
+            if ($husb->isDead() && !$wife->isDead()) {
931
+                if ($wife->getSex() == 'F') {
932
+                    $html .= 'H';
933
+                }
934
+                if ($wife->getSex() == 'M') {
935
+                    $html .= 'W';
936
+                } // male partners
937
+            }
938
+            if (!$husb->isDead() && $wife->isDead()) {
939
+                if ($husb->getSex() == 'M') {
940
+                    $html .= 'W';
941
+                }
942
+                if ($husb->getSex() == 'F') {
943
+                    $html .= 'H';
944
+                } // female partners
945
+            }
946
+            if (!$husb->isDead() && !$wife->isDead()) {
947
+                $html .= 'N';
948
+            }
949
+            $html .= '</td>';
950
+
951
+            // Filter by roots/leaves
952
+            $html .= '<td hidden>';
953
+            if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) {
954
+                $html .= 'R';
955
+            } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() === 0) {
956
+                $html .= 'L';
957
+            }
958
+            $html .= '</td>
959 959
 			</tr>';
960
-		}
960
+        }
961 961
 
962
-		$html .= '
962
+        $html .= '
963 963
 					</tbody>
964 964
 				</table>
965 965
 				<div id="fam_list_table-charts_' . $table_id . '" style="display:none">
@@ -975,40 +975,40 @@  discard block
 block discarded – undo
975 975
 				</div>
976 976
 			</div>';
977 977
 
978
-		return $html;
979
-	}
980
-
981
-	/**
982
-	 * Print a table of sources
983
-	 *
984
-	 * @param Source[] $sources
985
-	 *
986
-	 * @return string
987
-	 */
988
-	public static function sourceTable($sources) {
989
-		global $WT_TREE, $controller;
990
-
991
-		// Count the number of linked records. These numbers include private records.
992
-		// It is not good to bypass privacy, but many servers do not have the resources
993
-		// to process privacy for every record in the tree
994
-		$count_individuals = Database::prepare(
995
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
996
-		)->fetchAssoc();
997
-		$count_families = Database::prepare(
998
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
999
-		)->fetchAssoc();
1000
-		$count_media = Database::prepare(
1001
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
1002
-		)->fetchAssoc();
1003
-		$count_notes = Database::prepare(
1004
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##other` JOIN `##link` ON l_from = o_id AND l_file = o_file AND o_type = 'NOTE' AND l_type = 'SOUR' GROUP BY l_to, l_file"
1005
-		)->fetchAssoc();
1006
-
1007
-		$html     = '';
1008
-		$table_id = 'table-sour-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1009
-		$controller
1010
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1011
-			->addInlineJavascript('
978
+        return $html;
979
+    }
980
+
981
+    /**
982
+     * Print a table of sources
983
+     *
984
+     * @param Source[] $sources
985
+     *
986
+     * @return string
987
+     */
988
+    public static function sourceTable($sources) {
989
+        global $WT_TREE, $controller;
990
+
991
+        // Count the number of linked records. These numbers include private records.
992
+        // It is not good to bypass privacy, but many servers do not have the resources
993
+        // to process privacy for every record in the tree
994
+        $count_individuals = Database::prepare(
995
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
996
+        )->fetchAssoc();
997
+        $count_families = Database::prepare(
998
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
999
+        )->fetchAssoc();
1000
+        $count_media = Database::prepare(
1001
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'SOUR' GROUP BY l_to, l_file"
1002
+        )->fetchAssoc();
1003
+        $count_notes = Database::prepare(
1004
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##other` JOIN `##link` ON l_from = o_id AND l_file = o_file AND o_type = 'NOTE' AND l_type = 'SOUR' GROUP BY l_to, l_file"
1005
+        )->fetchAssoc();
1006
+
1007
+        $html     = '';
1008
+        $table_id = 'table-sour-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1009
+        $controller
1010
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1011
+            ->addInlineJavascript('
1012 1012
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1013 1013
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1014 1014
 				jQuery("#' . $table_id . '").dataTable( {
@@ -1034,108 +1034,108 @@  discard block
 block discarded – undo
1034 1034
 				jQuery(".loading-image").css("display", "none");
1035 1035
 			');
1036 1036
 
1037
-		$html .= '<div class="loading-image"></div>';
1038
-		$html .= '<div class="source-list">';
1039
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1040
-		$html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1041
-		$html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
1042
-		$html .= '<th>' . I18N::translate('Individuals') . '</th>';
1043
-		$html .= '<th>' . I18N::translate('Families') . '</th>';
1044
-		$html .= '<th>' . I18N::translate('Media objects') . '</th>';
1045
-		$html .= '<th>' . I18N::translate('Shared notes') . '</th>';
1046
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1047
-		$html .= '<th>' . I18N::translate('Delete') . '</th>';
1048
-		$html .= '</tr></thead>';
1049
-		$html .= '<tbody>';
1050
-
1051
-		foreach ($sources as $source) {
1052
-			if (!$source->canShow()) {
1053
-				continue;
1054
-			}
1055
-			if ($source->isPendingAddtion()) {
1056
-				$class = ' class="new"';
1057
-			} elseif ($source->isPendingDeletion()) {
1058
-				$class = ' class="old"';
1059
-			} else {
1060
-				$class = '';
1061
-			}
1062
-			$html .= '<tr' . $class . '>';
1063
-			// Source name(s)
1064
-			$html .= '<td data-sort="' . Filter::escapeHtml($source->getSortName()) . '">';
1065
-			foreach ($source->getAllNames() as $n => $name) {
1066
-				if ($n) {
1067
-					$html .= '<br>';
1068
-				}
1069
-				if ($n == $source->getPrimaryName()) {
1070
-					$html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1071
-				} else {
1072
-					$html .= '<a href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1073
-				}
1074
-			}
1075
-			$html .= '</td>';
1076
-			// Author
1077
-			$auth = $source->getFirstFact('AUTH');
1078
-			if ($auth) {
1079
-				$author = $auth->getValue();
1080
-			} else {
1081
-				$author = '';
1082
-			}
1083
-			$html .= '<td data-sort="' . Filter::escapeHtml($author) . '">' . FunctionsPrint::highlightSearchHits($author) . '</td>';
1084
-			$key = $source->getXref() . '@' . $source->getTree()->getTreeId();
1085
-			// Count of linked individuals
1086
-			$num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1087
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1088
-			// Count of linked families
1089
-			$num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1090
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1091
-			// Count of linked media objects
1092
-			$num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1093
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1094
-			// Count of linked notes
1095
-			$num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
1096
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1097
-			// Last change
1098
-			$html .= '<td data-sort="' . $source->lastChangeTimestamp(true) . '">' . $source->lastChangeTimestamp() . '</td>';
1099
-			// Delete
1100
-			$html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1101
-			$html .= '</tr>';
1102
-		}
1103
-		$html .= '</tbody></table></div>';
1104
-
1105
-		return $html;
1106
-	}
1107
-
1108
-	/**
1109
-	 * Print a table of shared notes
1110
-	 *
1111
-	 * @param Note[] $notes
1112
-	 *
1113
-	 * @return string
1114
-	 */
1115
-	public static function noteTable($notes) {
1116
-		global $WT_TREE, $controller;
1117
-
1118
-		// Count the number of linked records. These numbers include private records.
1119
-		// It is not good to bypass privacy, but many servers do not have the resources
1120
-		// to process privacy for every record in the tree
1121
-		$count_individuals = Database::prepare(
1122
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1123
-		)->fetchAssoc();
1124
-		$count_families = Database::prepare(
1125
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1126
-		)->fetchAssoc();
1127
-		$count_media = Database::prepare(
1128
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1129
-		)->fetchAssoc();
1130
-		$count_sources = Database::prepare(
1131
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1132
-		)->fetchAssoc();
1133
-
1134
-		$html     = '';
1135
-		$table_id = 'table-note-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1136
-		$controller
1137
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1138
-			->addInlineJavascript('
1037
+        $html .= '<div class="loading-image"></div>';
1038
+        $html .= '<div class="source-list">';
1039
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1040
+        $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1041
+        $html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
1042
+        $html .= '<th>' . I18N::translate('Individuals') . '</th>';
1043
+        $html .= '<th>' . I18N::translate('Families') . '</th>';
1044
+        $html .= '<th>' . I18N::translate('Media objects') . '</th>';
1045
+        $html .= '<th>' . I18N::translate('Shared notes') . '</th>';
1046
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1047
+        $html .= '<th>' . I18N::translate('Delete') . '</th>';
1048
+        $html .= '</tr></thead>';
1049
+        $html .= '<tbody>';
1050
+
1051
+        foreach ($sources as $source) {
1052
+            if (!$source->canShow()) {
1053
+                continue;
1054
+            }
1055
+            if ($source->isPendingAddtion()) {
1056
+                $class = ' class="new"';
1057
+            } elseif ($source->isPendingDeletion()) {
1058
+                $class = ' class="old"';
1059
+            } else {
1060
+                $class = '';
1061
+            }
1062
+            $html .= '<tr' . $class . '>';
1063
+            // Source name(s)
1064
+            $html .= '<td data-sort="' . Filter::escapeHtml($source->getSortName()) . '">';
1065
+            foreach ($source->getAllNames() as $n => $name) {
1066
+                if ($n) {
1067
+                    $html .= '<br>';
1068
+                }
1069
+                if ($n == $source->getPrimaryName()) {
1070
+                    $html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1071
+                } else {
1072
+                    $html .= '<a href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1073
+                }
1074
+            }
1075
+            $html .= '</td>';
1076
+            // Author
1077
+            $auth = $source->getFirstFact('AUTH');
1078
+            if ($auth) {
1079
+                $author = $auth->getValue();
1080
+            } else {
1081
+                $author = '';
1082
+            }
1083
+            $html .= '<td data-sort="' . Filter::escapeHtml($author) . '">' . FunctionsPrint::highlightSearchHits($author) . '</td>';
1084
+            $key = $source->getXref() . '@' . $source->getTree()->getTreeId();
1085
+            // Count of linked individuals
1086
+            $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1087
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1088
+            // Count of linked families
1089
+            $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1090
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1091
+            // Count of linked media objects
1092
+            $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1093
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1094
+            // Count of linked notes
1095
+            $num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
1096
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1097
+            // Last change
1098
+            $html .= '<td data-sort="' . $source->lastChangeTimestamp(true) . '">' . $source->lastChangeTimestamp() . '</td>';
1099
+            // Delete
1100
+            $html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1101
+            $html .= '</tr>';
1102
+        }
1103
+        $html .= '</tbody></table></div>';
1104
+
1105
+        return $html;
1106
+    }
1107
+
1108
+    /**
1109
+     * Print a table of shared notes
1110
+     *
1111
+     * @param Note[] $notes
1112
+     *
1113
+     * @return string
1114
+     */
1115
+    public static function noteTable($notes) {
1116
+        global $WT_TREE, $controller;
1117
+
1118
+        // Count the number of linked records. These numbers include private records.
1119
+        // It is not good to bypass privacy, but many servers do not have the resources
1120
+        // to process privacy for every record in the tree
1121
+        $count_individuals = Database::prepare(
1122
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1123
+        )->fetchAssoc();
1124
+        $count_families = Database::prepare(
1125
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1126
+        )->fetchAssoc();
1127
+        $count_media = Database::prepare(
1128
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1129
+        )->fetchAssoc();
1130
+        $count_sources = Database::prepare(
1131
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'NOTE' GROUP BY l_to, l_file"
1132
+        )->fetchAssoc();
1133
+
1134
+        $html     = '';
1135
+        $table_id = 'table-note-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1136
+        $controller
1137
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1138
+            ->addInlineJavascript('
1139 1139
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1140 1140
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1141 1141
 				jQuery("#' . $table_id . '").dataTable({
@@ -1160,79 +1160,79 @@  discard block
 block discarded – undo
1160 1160
 				jQuery(".loading-image").css("display", "none");
1161 1161
 			');
1162 1162
 
1163
-		$html .= '<div class="loading-image"></div>';
1164
-		$html .= '<div class="note-list">';
1165
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1166
-		$html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1167
-		$html .= '<th>' . I18N::translate('Individuals') . '</th>';
1168
-		$html .= '<th>' . I18N::translate('Families') . '</th>';
1169
-		$html .= '<th>' . I18N::translate('Media objects') . '</th>';
1170
-		$html .= '<th>' . I18N::translate('Sources') . '</th>';
1171
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1172
-		$html .= '<th>' . I18N::translate('Delete') . '</th>';
1173
-		$html .= '</tr></thead>';
1174
-		$html .= '<tbody>';
1175
-
1176
-		foreach ($notes as $note) {
1177
-			if (!$note->canShow()) {
1178
-				continue;
1179
-			}
1180
-			if ($note->isPendingAddtion()) {
1181
-				$class = ' class="new"';
1182
-			} elseif ($note->isPendingDeletion()) {
1183
-				$class = ' class="old"';
1184
-			} else {
1185
-				$class = '';
1186
-			}
1187
-			$html .= '<tr' . $class . '>';
1188
-			// Count of linked notes
1189
-			$html .= '<td data-sort="' . Filter::escapeHtml($note->getSortName()) . '"><a class="name2" href="' . $note->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($note->getFullName()) . '</a></td>';
1190
-			$key = $note->getXref() . '@' . $note->getTree()->getTreeId();
1191
-			// Count of linked individuals
1192
-			$num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1193
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1194
-			// Count of linked families
1195
-			$num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1196
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1197
-			// Count of linked media objects
1198
-			$num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1199
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1200
-			// Count of linked sources
1201
-			$num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1202
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1203
-			// Last change
1204
-			$html .= '<td data-sort="' . $note->lastChangeTimestamp(true) . '">' . $note->lastChangeTimestamp() . '</td>';
1205
-			// Delete
1206
-			$html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1207
-			$html .= '</tr>';
1208
-		}
1209
-		$html .= '</tbody></table></div>';
1210
-
1211
-		return $html;
1212
-	}
1213
-
1214
-	/**
1215
-	 * Print a table of repositories
1216
-	 *
1217
-	 * @param Repository[] $repositories
1218
-	 *
1219
-	 * @return string
1220
-	 */
1221
-	public static function repositoryTable($repositories) {
1222
-		global $WT_TREE, $controller;
1223
-
1224
-		// Count the number of linked records. These numbers include private records.
1225
-		// It is not good to bypass privacy, but many servers do not have the resources
1226
-		// to process privacy for every record in the tree
1227
-		$count_sources = Database::prepare(
1228
-			"SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'REPO' GROUP BY l_to, l_file"
1229
-		)->fetchAssoc();
1230
-
1231
-		$html     = '';
1232
-		$table_id = 'table-repo-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1233
-		$controller
1234
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1235
-			->addInlineJavascript('
1163
+        $html .= '<div class="loading-image"></div>';
1164
+        $html .= '<div class="note-list">';
1165
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1166
+        $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1167
+        $html .= '<th>' . I18N::translate('Individuals') . '</th>';
1168
+        $html .= '<th>' . I18N::translate('Families') . '</th>';
1169
+        $html .= '<th>' . I18N::translate('Media objects') . '</th>';
1170
+        $html .= '<th>' . I18N::translate('Sources') . '</th>';
1171
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1172
+        $html .= '<th>' . I18N::translate('Delete') . '</th>';
1173
+        $html .= '</tr></thead>';
1174
+        $html .= '<tbody>';
1175
+
1176
+        foreach ($notes as $note) {
1177
+            if (!$note->canShow()) {
1178
+                continue;
1179
+            }
1180
+            if ($note->isPendingAddtion()) {
1181
+                $class = ' class="new"';
1182
+            } elseif ($note->isPendingDeletion()) {
1183
+                $class = ' class="old"';
1184
+            } else {
1185
+                $class = '';
1186
+            }
1187
+            $html .= '<tr' . $class . '>';
1188
+            // Count of linked notes
1189
+            $html .= '<td data-sort="' . Filter::escapeHtml($note->getSortName()) . '"><a class="name2" href="' . $note->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($note->getFullName()) . '</a></td>';
1190
+            $key = $note->getXref() . '@' . $note->getTree()->getTreeId();
1191
+            // Count of linked individuals
1192
+            $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
1193
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1194
+            // Count of linked families
1195
+            $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
1196
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1197
+            // Count of linked media objects
1198
+            $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
1199
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1200
+            // Count of linked sources
1201
+            $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1202
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1203
+            // Last change
1204
+            $html .= '<td data-sort="' . $note->lastChangeTimestamp(true) . '">' . $note->lastChangeTimestamp() . '</td>';
1205
+            // Delete
1206
+            $html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1207
+            $html .= '</tr>';
1208
+        }
1209
+        $html .= '</tbody></table></div>';
1210
+
1211
+        return $html;
1212
+    }
1213
+
1214
+    /**
1215
+     * Print a table of repositories
1216
+     *
1217
+     * @param Repository[] $repositories
1218
+     *
1219
+     * @return string
1220
+     */
1221
+    public static function repositoryTable($repositories) {
1222
+        global $WT_TREE, $controller;
1223
+
1224
+        // Count the number of linked records. These numbers include private records.
1225
+        // It is not good to bypass privacy, but many servers do not have the resources
1226
+        // to process privacy for every record in the tree
1227
+        $count_sources = Database::prepare(
1228
+            "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'REPO' GROUP BY l_to, l_file"
1229
+        )->fetchAssoc();
1230
+
1231
+        $html     = '';
1232
+        $table_id = 'table-repo-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1233
+        $controller
1234
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1235
+            ->addInlineJavascript('
1236 1236
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1237 1237
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1238 1238
 				jQuery("#' . $table_id . '").dataTable({
@@ -1254,71 +1254,71 @@  discard block
 block discarded – undo
1254 1254
 				jQuery(".loading-image").css("display", "none");
1255 1255
 			');
1256 1256
 
1257
-		$html .= '<div class="loading-image"></div>';
1258
-		$html .= '<div class="repo-list">';
1259
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1260
-		$html .= '<th>' . I18N::translate('Repository name') . '</th>';
1261
-		$html .= '<th>' . I18N::translate('Sources') . '</th>';
1262
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1263
-		$html .= '<th>' . I18N::translate('Delete') . '</th>';
1264
-		$html .= '</tr></thead>';
1265
-		$html .= '<tbody>';
1266
-
1267
-		foreach ($repositories as $repository) {
1268
-			if (!$repository->canShow()) {
1269
-				continue;
1270
-			}
1271
-			if ($repository->isPendingAddtion()) {
1272
-				$class = ' class="new"';
1273
-			} elseif ($repository->isPendingDeletion()) {
1274
-				$class = ' class="old"';
1275
-			} else {
1276
-				$class = '';
1277
-			}
1278
-			$html .= '<tr' . $class . '>';
1279
-			// Repository name(s)
1280
-			$html .= '<td data-sort="' . Filter::escapeHtml($repository->getSortName()) . '">';
1281
-			foreach ($repository->getAllNames() as $n => $name) {
1282
-				if ($n) {
1283
-					$html .= '<br>';
1284
-				}
1285
-				if ($n == $repository->getPrimaryName()) {
1286
-					$html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1287
-				} else {
1288
-					$html .= '<a href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1289
-				}
1290
-			}
1291
-			$html .= '</td>';
1292
-			$key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
1293
-			// Count of linked sources
1294
-			$num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1295
-			$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1296
-			// Last change
1297
-			$html .= '<td data-sort="' . $repository->lastChangeTimestamp(true) . '">' . $repository->lastChangeTimestamp() . '</td>';
1298
-			// Delete
1299
-			$html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1300
-			$html .= '</tr>';
1301
-		}
1302
-		$html .= '</tbody></table></div>';
1303
-
1304
-		return $html;
1305
-	}
1306
-
1307
-	/**
1308
-	 * Print a table of media objects
1309
-	 *
1310
-	 * @param Media[] $media_objects
1311
-	 *
1312
-	 * @return string
1313
-	 */
1314
-	public static function mediaTable($media_objects) {
1315
-		global $WT_TREE, $controller;
1316
-
1317
-		$html     = '';
1318
-		$table_id = 'table-obje-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1319
-		$controller
1320
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1321
-			->addInlineJavascript('
1257
+        $html .= '<div class="loading-image"></div>';
1258
+        $html .= '<div class="repo-list">';
1259
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1260
+        $html .= '<th>' . I18N::translate('Repository name') . '</th>';
1261
+        $html .= '<th>' . I18N::translate('Sources') . '</th>';
1262
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1263
+        $html .= '<th>' . I18N::translate('Delete') . '</th>';
1264
+        $html .= '</tr></thead>';
1265
+        $html .= '<tbody>';
1266
+
1267
+        foreach ($repositories as $repository) {
1268
+            if (!$repository->canShow()) {
1269
+                continue;
1270
+            }
1271
+            if ($repository->isPendingAddtion()) {
1272
+                $class = ' class="new"';
1273
+            } elseif ($repository->isPendingDeletion()) {
1274
+                $class = ' class="old"';
1275
+            } else {
1276
+                $class = '';
1277
+            }
1278
+            $html .= '<tr' . $class . '>';
1279
+            // Repository name(s)
1280
+            $html .= '<td data-sort="' . Filter::escapeHtml($repository->getSortName()) . '">';
1281
+            foreach ($repository->getAllNames() as $n => $name) {
1282
+                if ($n) {
1283
+                    $html .= '<br>';
1284
+                }
1285
+                if ($n == $repository->getPrimaryName()) {
1286
+                    $html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1287
+                } else {
1288
+                    $html .= '<a href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
1289
+                }
1290
+            }
1291
+            $html .= '</td>';
1292
+            $key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
1293
+            // Count of linked sources
1294
+            $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
1295
+            $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1296
+            // Last change
1297
+            $html .= '<td data-sort="' . $repository->lastChangeTimestamp(true) . '">' . $repository->lastChangeTimestamp() . '</td>';
1298
+            // Delete
1299
+            $html .= '<td><a href="#" title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_record(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></a></td>';
1300
+            $html .= '</tr>';
1301
+        }
1302
+        $html .= '</tbody></table></div>';
1303
+
1304
+        return $html;
1305
+    }
1306
+
1307
+    /**
1308
+     * Print a table of media objects
1309
+     *
1310
+     * @param Media[] $media_objects
1311
+     *
1312
+     * @return string
1313
+     */
1314
+    public static function mediaTable($media_objects) {
1315
+        global $WT_TREE, $controller;
1316
+
1317
+        $html     = '';
1318
+        $table_id = 'table-obje-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1319
+        $controller
1320
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1321
+            ->addInlineJavascript('
1322 1322
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1323 1323
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1324 1324
 				jQuery("#' . $table_id . '").dataTable({
@@ -1342,75 +1342,75 @@  discard block
 block discarded – undo
1342 1342
 				jQuery(".loading-image").css("display", "none");
1343 1343
 			');
1344 1344
 
1345
-		$html .= '<div class="loading-image"></div>';
1346
-		$html .= '<div class="media-list">';
1347
-		$html .= '<table id="' . $table_id . '"><thead><tr>';
1348
-		$html .= '<th>' . I18N::translate('Media') . '</th>';
1349
-		$html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1350
-		$html .= '<th>' . I18N::translate('Individuals') . '</th>';
1351
-		$html .= '<th>' . I18N::translate('Families') . '</th>';
1352
-		$html .= '<th>' . I18N::translate('Sources') . '</th>';
1353
-		$html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1354
-		$html .= '</tr></thead>';
1355
-		$html .= '<tbody>';
1356
-
1357
-		foreach ($media_objects as $media_object) {
1358
-			if ($media_object->canShow()) {
1359
-				$name = $media_object->getFullName();
1360
-				if ($media_object->isPendingAddtion()) {
1361
-					$class = ' class="new"';
1362
-				} elseif ($media_object->isPendingDeletion()) {
1363
-					$class = ' class="old"';
1364
-				} else {
1365
-					$class = '';
1366
-				}
1367
-				$html .= '<tr' . $class . '>';
1368
-				// Media object thumbnail
1369
-				$html .= '<td>' . $media_object->displayImage() . '</td>';
1370
-				// Media object name(s)
1371
-				$html .= '<td data-sort="' . Filter::escapeHtml($media_object->getSortName()) . '">';
1372
-				$html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
1373
-				$html .= FunctionsPrint::highlightSearchHits($name) . '</a>';
1374
-				if (Auth::isEditor($media_object->getTree())) {
1375
-					$html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
1376
-				}
1377
-				$html .= '</td>';
1378
-
1379
-				// Count of linked individuals
1380
-				$num = count($media_object->linkedIndividuals('OBJE'));
1381
-				$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1382
-				// Count of linked families
1383
-				$num = count($media_object->linkedFamilies('OBJE'));
1384
-				$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1385
-				// Count of linked sources
1386
-				$num = count($media_object->linkedSources('OBJE'));
1387
-				$html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1388
-				// Last change
1389
-				$html .= '<td data-sort="' . $media_object->lastChangeTimestamp(true) . '">' . $media_object->lastChangeTimestamp() . '</td>';
1390
-				$html .= '</tr>';
1391
-			}
1392
-		}
1393
-		$html .= '</tbody></table></div>';
1394
-
1395
-		return $html;
1396
-	}
1397
-
1398
-	/**
1399
-	 * Print a table of surnames, for the top surnames block, the indi/fam lists, etc.
1400
-	 *
1401
-	 * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1402
-	 * @param string $script "indilist.php" (counts of individuals) or "famlist.php" (counts of spouses)
1403
-	 * @param Tree $tree generate links for this tree
1404
-	 *
1405
-	 * @return string
1406
-	 */
1407
-	public static function surnameTable($surnames, $script, Tree $tree) {
1408
-		global $controller;
1409
-
1410
-		$html = '';
1411
-		$controller
1412
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1413
-			->addInlineJavascript('
1345
+        $html .= '<div class="loading-image"></div>';
1346
+        $html .= '<div class="media-list">';
1347
+        $html .= '<table id="' . $table_id . '"><thead><tr>';
1348
+        $html .= '<th>' . I18N::translate('Media') . '</th>';
1349
+        $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
1350
+        $html .= '<th>' . I18N::translate('Individuals') . '</th>';
1351
+        $html .= '<th>' . I18N::translate('Families') . '</th>';
1352
+        $html .= '<th>' . I18N::translate('Sources') . '</th>';
1353
+        $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
1354
+        $html .= '</tr></thead>';
1355
+        $html .= '<tbody>';
1356
+
1357
+        foreach ($media_objects as $media_object) {
1358
+            if ($media_object->canShow()) {
1359
+                $name = $media_object->getFullName();
1360
+                if ($media_object->isPendingAddtion()) {
1361
+                    $class = ' class="new"';
1362
+                } elseif ($media_object->isPendingDeletion()) {
1363
+                    $class = ' class="old"';
1364
+                } else {
1365
+                    $class = '';
1366
+                }
1367
+                $html .= '<tr' . $class . '>';
1368
+                // Media object thumbnail
1369
+                $html .= '<td>' . $media_object->displayImage() . '</td>';
1370
+                // Media object name(s)
1371
+                $html .= '<td data-sort="' . Filter::escapeHtml($media_object->getSortName()) . '">';
1372
+                $html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
1373
+                $html .= FunctionsPrint::highlightSearchHits($name) . '</a>';
1374
+                if (Auth::isEditor($media_object->getTree())) {
1375
+                    $html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
1376
+                }
1377
+                $html .= '</td>';
1378
+
1379
+                // Count of linked individuals
1380
+                $num = count($media_object->linkedIndividuals('OBJE'));
1381
+                $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1382
+                // Count of linked families
1383
+                $num = count($media_object->linkedFamilies('OBJE'));
1384
+                $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1385
+                // Count of linked sources
1386
+                $num = count($media_object->linkedSources('OBJE'));
1387
+                $html .= '<td class="center" data-sort="' . $num . '">' . I18N::number($num) . '</td>';
1388
+                // Last change
1389
+                $html .= '<td data-sort="' . $media_object->lastChangeTimestamp(true) . '">' . $media_object->lastChangeTimestamp() . '</td>';
1390
+                $html .= '</tr>';
1391
+            }
1392
+        }
1393
+        $html .= '</tbody></table></div>';
1394
+
1395
+        return $html;
1396
+    }
1397
+
1398
+    /**
1399
+     * Print a table of surnames, for the top surnames block, the indi/fam lists, etc.
1400
+     *
1401
+     * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1402
+     * @param string $script "indilist.php" (counts of individuals) or "famlist.php" (counts of spouses)
1403
+     * @param Tree $tree generate links for this tree
1404
+     *
1405
+     * @return string
1406
+     */
1407
+    public static function surnameTable($surnames, $script, Tree $tree) {
1408
+        global $controller;
1409
+
1410
+        $html = '';
1411
+        $controller
1412
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1413
+            ->addInlineJavascript('
1414 1414
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1415 1415
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1416 1416
 				jQuery(".surname-list").dataTable({
@@ -1427,203 +1427,203 @@  discard block
 block discarded – undo
1427 1427
 				});
1428 1428
 			');
1429 1429
 
1430
-		if ($script == 'famlist.php') {
1431
-			$col_heading = I18N::translate('Spouses');
1432
-		} else {
1433
-			$col_heading = I18N::translate('Individuals');
1434
-		}
1435
-
1436
-		$html .=
1437
-			'<table class="surname-list">' .
1438
-			'<thead>' .
1439
-			'<tr>' .
1440
-			'<th>' . GedcomTag::getLabel('SURN') . '</th>' .
1441
-			'<th>' . $col_heading . '</th>' .
1442
-			'</tr>' .
1443
-			'</thead>';
1444
-
1445
-		$html .= '<tbody>';
1446
-		foreach ($surnames as $surn => $surns) {
1447
-			// Each surname links back to the indi/fam surname list
1448
-			if ($surn) {
1449
-				$url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1450
-			} else {
1451
-				$url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1452
-			}
1453
-			$html .= '<tr>';
1454
-			// Surname
1455
-			$html .= '<td data-sort="' . Filter::escapeHtml($surn) . '">';
1456
-			// Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
1457
-			foreach ($surns as $spfxsurn => $indis) {
1458
-				if ($spfxsurn) {
1459
-					$html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
1460
-				} else {
1461
-					// No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
1462
-					$html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
1463
-				}
1464
-			}
1465
-			$html .= '</td>';
1466
-			// Surname count
1467
-			$subtotal = 0;
1468
-			foreach ($surns as $indis) {
1469
-				$subtotal += count($indis);
1470
-			}
1471
-			$html .= '<td class="center" data-sort="' . $subtotal . '">';
1472
-			foreach ($surns as $indis) {
1473
-				$html .= I18N::number(count($indis)) . '<br>';
1474
-			}
1475
-			if (count($surns) > 1) {
1476
-				// More than one surname variant? Show a subtotal
1477
-				$html .= I18N::number($subtotal);
1478
-			}
1479
-			$html .= '</td>';
1480
-			$html .= '</tr>';
1481
-		}
1482
-		$html .= '</tbody></table>';
1483
-
1484
-		return $html;
1485
-	}
1486
-
1487
-	/**
1488
-	 * Print a tagcloud of surnames.
1489
-	 *
1490
-	 * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1491
-	 * @param string $script indilist or famlist
1492
-	 * @param bool $totals show totals after each name
1493
-	 * @param Tree $tree generate links to this tree
1494
-	 *
1495
-	 * @return string
1496
-	 */
1497
-	public static function surnameTagCloud($surnames, $script, $totals, Tree $tree) {
1498
-		$minimum = PHP_INT_MAX;
1499
-		$maximum = 1;
1500
-		foreach ($surnames as $surn => $surns) {
1501
-			foreach ($surns as $spfxsurn => $indis) {
1502
-				$maximum = max($maximum, count($indis));
1503
-				$minimum = min($minimum, count($indis));
1504
-			}
1505
-		}
1506
-
1507
-		$html = '';
1508
-		foreach ($surnames as $surn => $surns) {
1509
-			foreach ($surns as $spfxsurn => $indis) {
1510
-				if ($maximum === $minimum) {
1511
-					// All surnames occur the same number of times
1512
-					$size = 150.0;
1513
-				} else {
1514
-					$size = 75.0 + 125.0 * (count($indis) - $minimum) / ($maximum - $minimum);
1515
-				}
1516
-				$html .= '<a style="font-size:' . $size . '%" href="' . $script . '?surname=' . Filter::escapeUrl($surn) . '&amp;ged=' . $tree->getNameUrl() . '">';
1517
-				if ($totals) {
1518
-					$html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
1519
-				} else {
1520
-					$html .= $spfxsurn;
1521
-				}
1522
-				$html .= '</a> ';
1523
-			}
1524
-		}
1525
-
1526
-		return '<div class="tag_cloud">' . $html . '</div>';
1527
-	}
1528
-
1529
-	/**
1530
-	 * Print a list of surnames.
1531
-	 *
1532
-	 * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1533
-	 * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns
1534
-	 * @param bool $totals show totals after each name
1535
-	 * @param string $script indilist or famlist
1536
-	 * @param Tree $tree Link back to the individual list in this tree
1537
-	 *
1538
-	 * @return string
1539
-	 */
1540
-	public static function surnameList($surnames, $style, $totals, $script, Tree $tree) {
1541
-		$html = array();
1542
-		foreach ($surnames as $surn => $surns) {
1543
-			// Each surname links back to the indilist
1544
-			if ($surn) {
1545
-				$url = $script . '?surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1546
-			} else {
1547
-				$url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1548
-			}
1549
-			// If all the surnames are just case variants, then merge them into one
1550
-			// Comment out this block if you want SMITH listed separately from Smith
1551
-			$first_spfxsurn = null;
1552
-			foreach ($surns as $spfxsurn => $indis) {
1553
-				if ($first_spfxsurn) {
1554
-					if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) {
1555
-						$surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]);
1556
-						unset($surns[$spfxsurn]);
1557
-					}
1558
-				} else {
1559
-					$first_spfxsurn = $spfxsurn;
1560
-				}
1561
-			}
1562
-			$subhtml = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
1563
-
1564
-			if ($totals) {
1565
-				$subtotal = 0;
1566
-				foreach ($surns as $indis) {
1567
-					$subtotal += count($indis);
1568
-				}
1569
-				$subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
1570
-			}
1571
-			$html[] = $subhtml;
1572
-
1573
-		}
1574
-		switch ($style) {
1575
-		case 1:
1576
-			return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
1577
-		case 2:
1578
-			return implode(I18N::$list_separator, $html);
1579
-		case 3:
1580
-			$i     = 0;
1581
-			$count = count($html);
1582
-			if ($count > 36) {
1583
-				$col = 4;
1584
-			} elseif ($count > 18) {
1585
-				$col = 3;
1586
-			} elseif ($count > 6) {
1587
-				$col = 2;
1588
-			} else {
1589
-				$col = 1;
1590
-			}
1591
-			$newcol = ceil($count / $col);
1592
-			$html2  = '<table class="list_table"><tr>';
1593
-			$html2 .= '<td class="list_value" style="padding: 14px;">';
1594
-
1595
-			foreach ($html as $surns) {
1596
-				$html2 .= $surns . '<br>';
1597
-				$i++;
1598
-				if ($i == $newcol && $i < $count) {
1599
-					$html2 .= '</td><td class="list_value" style="padding: 14px;">';
1600
-					$newcol = $i + ceil($count / $col);
1601
-				}
1602
-			}
1603
-			$html2 .= '</td></tr></table>';
1604
-
1605
-			return $html2;
1606
-		}
1607
-	}
1608
-	/**
1609
-	 * Print a table of events
1610
-	 *
1611
-	 * @param int $startjd
1612
-	 * @param int $endjd
1613
-	 * @param string $events
1614
-	 * @param bool $only_living
1615
-	 * @param string $sort_by
1616
-	 *
1617
-	 * @return string
1618
-	 */
1619
-	public static function eventsTable($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1620
-		global $controller, $WT_TREE;
1621
-
1622
-		$html     = '';
1623
-		$table_id = 'table-even-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1624
-		$controller
1625
-			->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1626
-			->addInlineJavascript('
1430
+        if ($script == 'famlist.php') {
1431
+            $col_heading = I18N::translate('Spouses');
1432
+        } else {
1433
+            $col_heading = I18N::translate('Individuals');
1434
+        }
1435
+
1436
+        $html .=
1437
+            '<table class="surname-list">' .
1438
+            '<thead>' .
1439
+            '<tr>' .
1440
+            '<th>' . GedcomTag::getLabel('SURN') . '</th>' .
1441
+            '<th>' . $col_heading . '</th>' .
1442
+            '</tr>' .
1443
+            '</thead>';
1444
+
1445
+        $html .= '<tbody>';
1446
+        foreach ($surnames as $surn => $surns) {
1447
+            // Each surname links back to the indi/fam surname list
1448
+            if ($surn) {
1449
+                $url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1450
+            } else {
1451
+                $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1452
+            }
1453
+            $html .= '<tr>';
1454
+            // Surname
1455
+            $html .= '<td data-sort="' . Filter::escapeHtml($surn) . '">';
1456
+            // Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
1457
+            foreach ($surns as $spfxsurn => $indis) {
1458
+                if ($spfxsurn) {
1459
+                    $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
1460
+                } else {
1461
+                    // No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
1462
+                    $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
1463
+                }
1464
+            }
1465
+            $html .= '</td>';
1466
+            // Surname count
1467
+            $subtotal = 0;
1468
+            foreach ($surns as $indis) {
1469
+                $subtotal += count($indis);
1470
+            }
1471
+            $html .= '<td class="center" data-sort="' . $subtotal . '">';
1472
+            foreach ($surns as $indis) {
1473
+                $html .= I18N::number(count($indis)) . '<br>';
1474
+            }
1475
+            if (count($surns) > 1) {
1476
+                // More than one surname variant? Show a subtotal
1477
+                $html .= I18N::number($subtotal);
1478
+            }
1479
+            $html .= '</td>';
1480
+            $html .= '</tr>';
1481
+        }
1482
+        $html .= '</tbody></table>';
1483
+
1484
+        return $html;
1485
+    }
1486
+
1487
+    /**
1488
+     * Print a tagcloud of surnames.
1489
+     *
1490
+     * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1491
+     * @param string $script indilist or famlist
1492
+     * @param bool $totals show totals after each name
1493
+     * @param Tree $tree generate links to this tree
1494
+     *
1495
+     * @return string
1496
+     */
1497
+    public static function surnameTagCloud($surnames, $script, $totals, Tree $tree) {
1498
+        $minimum = PHP_INT_MAX;
1499
+        $maximum = 1;
1500
+        foreach ($surnames as $surn => $surns) {
1501
+            foreach ($surns as $spfxsurn => $indis) {
1502
+                $maximum = max($maximum, count($indis));
1503
+                $minimum = min($minimum, count($indis));
1504
+            }
1505
+        }
1506
+
1507
+        $html = '';
1508
+        foreach ($surnames as $surn => $surns) {
1509
+            foreach ($surns as $spfxsurn => $indis) {
1510
+                if ($maximum === $minimum) {
1511
+                    // All surnames occur the same number of times
1512
+                    $size = 150.0;
1513
+                } else {
1514
+                    $size = 75.0 + 125.0 * (count($indis) - $minimum) / ($maximum - $minimum);
1515
+                }
1516
+                $html .= '<a style="font-size:' . $size . '%" href="' . $script . '?surname=' . Filter::escapeUrl($surn) . '&amp;ged=' . $tree->getNameUrl() . '">';
1517
+                if ($totals) {
1518
+                    $html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
1519
+                } else {
1520
+                    $html .= $spfxsurn;
1521
+                }
1522
+                $html .= '</a> ';
1523
+            }
1524
+        }
1525
+
1526
+        return '<div class="tag_cloud">' . $html . '</div>';
1527
+    }
1528
+
1529
+    /**
1530
+     * Print a list of surnames.
1531
+     *
1532
+     * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
1533
+     * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns
1534
+     * @param bool $totals show totals after each name
1535
+     * @param string $script indilist or famlist
1536
+     * @param Tree $tree Link back to the individual list in this tree
1537
+     *
1538
+     * @return string
1539
+     */
1540
+    public static function surnameList($surnames, $style, $totals, $script, Tree $tree) {
1541
+        $html = array();
1542
+        foreach ($surnames as $surn => $surns) {
1543
+            // Each surname links back to the indilist
1544
+            if ($surn) {
1545
+                $url = $script . '?surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
1546
+            } else {
1547
+                $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
1548
+            }
1549
+            // If all the surnames are just case variants, then merge them into one
1550
+            // Comment out this block if you want SMITH listed separately from Smith
1551
+            $first_spfxsurn = null;
1552
+            foreach ($surns as $spfxsurn => $indis) {
1553
+                if ($first_spfxsurn) {
1554
+                    if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) {
1555
+                        $surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]);
1556
+                        unset($surns[$spfxsurn]);
1557
+                    }
1558
+                } else {
1559
+                    $first_spfxsurn = $spfxsurn;
1560
+                }
1561
+            }
1562
+            $subhtml = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
1563
+
1564
+            if ($totals) {
1565
+                $subtotal = 0;
1566
+                foreach ($surns as $indis) {
1567
+                    $subtotal += count($indis);
1568
+                }
1569
+                $subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
1570
+            }
1571
+            $html[] = $subhtml;
1572
+
1573
+        }
1574
+        switch ($style) {
1575
+        case 1:
1576
+            return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
1577
+        case 2:
1578
+            return implode(I18N::$list_separator, $html);
1579
+        case 3:
1580
+            $i     = 0;
1581
+            $count = count($html);
1582
+            if ($count > 36) {
1583
+                $col = 4;
1584
+            } elseif ($count > 18) {
1585
+                $col = 3;
1586
+            } elseif ($count > 6) {
1587
+                $col = 2;
1588
+            } else {
1589
+                $col = 1;
1590
+            }
1591
+            $newcol = ceil($count / $col);
1592
+            $html2  = '<table class="list_table"><tr>';
1593
+            $html2 .= '<td class="list_value" style="padding: 14px;">';
1594
+
1595
+            foreach ($html as $surns) {
1596
+                $html2 .= $surns . '<br>';
1597
+                $i++;
1598
+                if ($i == $newcol && $i < $count) {
1599
+                    $html2 .= '</td><td class="list_value" style="padding: 14px;">';
1600
+                    $newcol = $i + ceil($count / $col);
1601
+                }
1602
+            }
1603
+            $html2 .= '</td></tr></table>';
1604
+
1605
+            return $html2;
1606
+        }
1607
+    }
1608
+    /**
1609
+     * Print a table of events
1610
+     *
1611
+     * @param int $startjd
1612
+     * @param int $endjd
1613
+     * @param string $events
1614
+     * @param bool $only_living
1615
+     * @param string $sort_by
1616
+     *
1617
+     * @return string
1618
+     */
1619
+    public static function eventsTable($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1620
+        global $controller, $WT_TREE;
1621
+
1622
+        $html     = '';
1623
+        $table_id = 'table-even-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page
1624
+        $controller
1625
+            ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
1626
+            ->addInlineJavascript('
1627 1627
 				jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
1628 1628
 				jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
1629 1629
 				jQuery("#' . $table_id . '").dataTable({
@@ -1645,341 +1645,341 @@  discard block
 block discarded – undo
1645 1645
 				});
1646 1646
 			');
1647 1647
 
1648
-		// Did we have any output? Did we skip anything?
1649
-		$filter          = 0;
1650
-		$filtered_events = array();
1651
-
1652
-		foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1653
-			$record = $fact->getParent();
1654
-			// Only living people ?
1655
-			if ($only_living) {
1656
-				if ($record instanceof Individual && $record->isDead()) {
1657
-					$filter++;
1658
-					continue;
1659
-				}
1660
-				if ($record instanceof Family) {
1661
-					$husb = $record->getHusband();
1662
-					if (is_null($husb) || $husb->isDead()) {
1663
-						$filter++;
1664
-						continue;
1665
-					}
1666
-					$wife = $record->getWife();
1667
-					if (is_null($wife) || $wife->isDead()) {
1668
-						$filter++;
1669
-						continue;
1670
-					}
1671
-				}
1672
-			}
1673
-
1674
-			$filtered_events[] = $fact;
1675
-		}
1676
-
1677
-		if (!empty($filtered_events)) {
1678
-			$html .= '<table id="' . $table_id . '" class="width100">';
1679
-			$html .= '<thead><tr>';
1680
-			$html .= '<th>' . I18N::translate('Record') . '</th>';
1681
-			$html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
1682
-			$html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
1683
-			$html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
1684
-			$html .= '</tr></thead><tbody>';
1685
-
1686
-			foreach ($filtered_events as $n => $fact) {
1687
-				$record = $fact->getParent();
1688
-				$html .= '<tr>';
1689
-				$html .= '<td data-sort="' . Filter::escapeHtml($record->getSortName()) . '">';
1690
-				$html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
1691
-				if ($record instanceof Individual) {
1692
-					$html .= $record->getSexImage();
1693
-				}
1694
-				$html .= '</td>';
1695
-				$html .= '<td data-sort="' . $fact->getDate()->minimumJulianDay() . '">';
1696
-				$html .= $fact->getDate()->display();
1697
-				$html .= '</td>';
1698
-				$html .= '<td class="center" data-sort="' . $fact->anniv . '">';
1699
-				$html .= ($fact->anniv ? I18N::number($fact->anniv) : '');
1700
-				$html .= '</td>';
1701
-				$html .= '<td class="center">' . $fact->getLabel() . '</td>';
1702
-				$html .= '</tr>';
1703
-			}
1704
-
1705
-			$html .= '</tbody></table>';
1706
-		} else {
1707
-			if ($endjd === WT_CLIENT_JD) {
1708
-				// We're dealing with the Today’s Events block
1709
-				if ($filter === 0) {
1710
-					$html .=  I18N::translate('No events exist for today.');
1711
-				} else {
1712
-					$html .=  I18N::translate('No events for living individuals exist for today.');
1713
-				}
1714
-			} else {
1715
-				// We're dealing with the Upcoming Events block
1716
-				if ($filter === 0) {
1717
-					if ($endjd === $startjd) {
1718
-						$html .=  I18N::translate('No events exist for tomorrow.');
1719
-					} else {
1720
-						$html .=  /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1721
-					}
1722
-				} else {
1723
-					if ($endjd === $startjd) {
1724
-						$html .=  I18N::translate('No events for living individuals exist for tomorrow.');
1725
-					} else {
1726
-						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1727
-						$html .=  I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1728
-					}
1729
-				}
1730
-			}
1731
-		}
1732
-
1733
-		return $html;
1734
-	}
1735
-
1736
-	/**
1737
-	 * Print a list of events
1738
-	 *
1739
-	 * This performs the same function as print_events_table(), but formats the output differently.
1740
-	 *
1741
-	 * @param int $startjd
1742
-	 * @param int $endjd
1743
-	 * @param string $events
1744
-	 * @param bool $only_living
1745
-	 * @param string $sort_by
1746
-	 *
1747
-	 * @return string
1748
-	 */
1749
-	public static function eventsList($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1750
-		global $WT_TREE;
1751
-
1752
-		// Did we have any output? Did we skip anything?
1753
-		$output          = 0;
1754
-		$filter          = 0;
1755
-		$filtered_events = array();
1756
-		$html            = '';
1757
-		foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1758
-			$record = $fact->getParent();
1759
-			// only living people ?
1760
-			if ($only_living) {
1761
-				if ($record instanceof Individual && $record->isDead()) {
1762
-					$filter++;
1763
-					continue;
1764
-				}
1765
-				if ($record instanceof Family) {
1766
-					$husb = $record->getHusband();
1767
-					if (is_null($husb) || $husb->isDead()) {
1768
-						$filter++;
1769
-						continue;
1770
-					}
1771
-					$wife = $record->getWife();
1772
-					if (is_null($wife) || $wife->isDead()) {
1773
-						$filter++;
1774
-						continue;
1775
-					}
1776
-				}
1777
-			}
1778
-
1779
-			$output++;
1780
-
1781
-			$filtered_events[] = $fact;
1782
-		}
1783
-
1784
-		// Now we've filtered the list, we can sort by event, if required
1785
-		switch ($sort_by) {
1786
-		case 'anniv':
1787
-			// Data is already sorted by anniversary date
1788
-			break;
1789
-		case 'alpha':
1790
-			uasort($filtered_events, function (Fact $x, Fact $y) {
1791
-				return GedcomRecord::compare($x->getParent(), $y->getParent());
1792
-			});
1793
-			break;
1794
-		}
1795
-
1796
-		foreach ($filtered_events as $fact) {
1797
-			$record = $fact->getParent();
1798
-			$html .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
1799
-			if ($record instanceof Individual) {
1800
-				$html .= $record->getSexImage();
1801
-			}
1802
-			$html .= '<br><div class="indent">';
1803
-			$html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
1804
-			if ($fact->anniv) {
1805
-				$html .= ' (' . I18N::translate('%s year anniversary', I18N::number($fact->anniv)) . ')';
1806
-			}
1807
-			if (!$fact->getPlace()->isEmpty()) {
1808
-				$html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
1809
-			}
1810
-			$html .= '</div>';
1811
-		}
1812
-
1813
-		// Print a final summary message about restricted/filtered facts
1814
-		$summary = '';
1815
-		if ($endjd == WT_CLIENT_JD) {
1816
-			// We're dealing with the Today’s Events block
1817
-			if ($output == 0) {
1818
-				if ($filter == 0) {
1819
-					$summary = I18N::translate('No events exist for today.');
1820
-				} else {
1821
-					$summary = I18N::translate('No events for living individuals exist for today.');
1822
-				}
1823
-			}
1824
-		} else {
1825
-			// We're dealing with the Upcoming Events block
1826
-			if ($output == 0) {
1827
-				if ($filter == 0) {
1828
-					if ($endjd == $startjd) {
1829
-						$summary = I18N::translate('No events exist for tomorrow.');
1830
-					} else {
1831
-						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1832
-						$summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1833
-					}
1834
-				} else {
1835
-					if ($endjd == $startjd) {
1836
-						$summary = I18N::translate('No events for living individuals exist for tomorrow.');
1837
-					} else {
1838
-						// I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1839
-						$summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1840
-					}
1841
-				}
1842
-			}
1843
-		}
1844
-		if ($summary) {
1845
-			$html .= '<b>' . $summary . '</b>';
1846
-		}
1847
-
1848
-		return $html;
1849
-	}
1850
-
1851
-	/**
1852
-	 * Print a chart by age using Google chart API
1853
-	 *
1854
-	 * @param int[] $data
1855
-	 * @param string $title
1856
-	 *
1857
-	 * @return string
1858
-	 */
1859
-	public static function chartByAge($data, $title) {
1860
-		$count  = 0;
1861
-		$agemax = 0;
1862
-		$vmax   = 0;
1863
-		$avg    = 0;
1864
-		foreach ($data as $age => $v) {
1865
-			$n      = strlen($v);
1866
-			$vmax   = max($vmax, $n);
1867
-			$agemax = max($agemax, $age);
1868
-			$count += $n;
1869
-			$avg += $age * $n;
1870
-		}
1871
-		if ($count < 1) {
1872
-			return '';
1873
-		}
1874
-		$avg       = round($avg / $count);
1875
-		$chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1876
-		$chart_url .= "&amp;chs=725x150"; // size
1877
-		$chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
1878
-		$chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1879
-		$chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
1880
-		$chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
1881
-		$chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1882
-		$chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1883
-		$chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
1884
-		$chart_url .= "&amp;chxl=0:|"; // label
1885
-		for ($age = 0; $age <= $agemax; $age += 5) {
1886
-			$chart_url .= $age . "|||||"; // x axis
1887
-		}
1888
-		$chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1889
-		$chart_url .= "|2:||";
1890
-		$step = $vmax;
1891
-		for ($d = $vmax; $d > 0; $d--) {
1892
-			if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1893
-				$step = $d;
1894
-			}
1895
-		}
1896
-		if ($step == $vmax) {
1897
-			for ($d = $vmax - 1; $d > 0; $d--) {
1898
-				if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1899
-					$step = $d;
1900
-				}
1901
-			}
1902
-		}
1903
-		for ($n = $step; $n < $vmax; $n += $step) {
1904
-			$chart_url .= $n . "|";
1905
-		}
1906
-		$chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1907
-		$chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1908
-		$chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1909
-		$CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1910
-		for ($age = 0; $age <= $agemax; $age++) {
1911
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "M") * 61 / $vmax)];
1912
-		}
1913
-		$chart_url .= ",";
1914
-		for ($age = 0; $age <= $agemax; $age++) {
1915
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "F") * 61 / $vmax)];
1916
-		}
1917
-		$html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1918
-
1919
-		return $html;
1920
-	}
1921
-
1922
-	/**
1923
-	 * Print a chart by decade using Google chart API
1924
-	 *
1925
-	 * @param int[] $data
1926
-	 * @param string $title
1927
-	 *
1928
-	 * @return string
1929
-	 */
1930
-	public static function chartByDecade($data, $title) {
1931
-		$count = 0;
1932
-		$vmax  = 0;
1933
-		foreach ($data as $v) {
1934
-			$n    = strlen($v);
1935
-			$vmax = max($vmax, $n);
1936
-			$count += $n;
1937
-		}
1938
-		if ($count < 1) {
1939
-			return '';
1940
-		}
1941
-		$chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1942
-		$chart_url .= "&amp;chs=360x150"; // size
1943
-		$chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
1944
-		$chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1945
-		$chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
1946
-		$chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1947
-		$chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1948
-		$chart_url .= "&amp;chxl=0:|&lt;|||"; // <1570
1949
-		for ($y = 1600; $y < 2030; $y += 50) {
1950
-			$chart_url .= $y . "|||||"; // x axis
1951
-		}
1952
-		$chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1953
-		$chart_url .= "|2:||";
1954
-		$step = $vmax;
1955
-		for ($d = $vmax; $d > 0; $d--) {
1956
-			if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1957
-				$step = $d;
1958
-			}
1959
-		}
1960
-		if ($step == $vmax) {
1961
-			for ($d = $vmax - 1; $d > 0; $d--) {
1962
-				if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1963
-					$step = $d;
1964
-				}
1965
-			}
1966
-		}
1967
-		for ($n = $step; $n < $vmax; $n += $step) {
1968
-			$chart_url .= $n . "|";
1969
-		}
1970
-		$chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1971
-		$chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1972
-		$chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1973
-		$CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1974
-		for ($y = 1570; $y < 2030; $y += 10) {
1975
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "M") * 61 / $vmax)];
1976
-		}
1977
-		$chart_url .= ",";
1978
-		for ($y = 1570; $y < 2030; $y += 10) {
1979
-			$chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "F") * 61 / $vmax)];
1980
-		}
1981
-		$html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1982
-
1983
-		return $html;
1984
-	}
1648
+        // Did we have any output? Did we skip anything?
1649
+        $filter          = 0;
1650
+        $filtered_events = array();
1651
+
1652
+        foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1653
+            $record = $fact->getParent();
1654
+            // Only living people ?
1655
+            if ($only_living) {
1656
+                if ($record instanceof Individual && $record->isDead()) {
1657
+                    $filter++;
1658
+                    continue;
1659
+                }
1660
+                if ($record instanceof Family) {
1661
+                    $husb = $record->getHusband();
1662
+                    if (is_null($husb) || $husb->isDead()) {
1663
+                        $filter++;
1664
+                        continue;
1665
+                    }
1666
+                    $wife = $record->getWife();
1667
+                    if (is_null($wife) || $wife->isDead()) {
1668
+                        $filter++;
1669
+                        continue;
1670
+                    }
1671
+                }
1672
+            }
1673
+
1674
+            $filtered_events[] = $fact;
1675
+        }
1676
+
1677
+        if (!empty($filtered_events)) {
1678
+            $html .= '<table id="' . $table_id . '" class="width100">';
1679
+            $html .= '<thead><tr>';
1680
+            $html .= '<th>' . I18N::translate('Record') . '</th>';
1681
+            $html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
1682
+            $html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
1683
+            $html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
1684
+            $html .= '</tr></thead><tbody>';
1685
+
1686
+            foreach ($filtered_events as $n => $fact) {
1687
+                $record = $fact->getParent();
1688
+                $html .= '<tr>';
1689
+                $html .= '<td data-sort="' . Filter::escapeHtml($record->getSortName()) . '">';
1690
+                $html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
1691
+                if ($record instanceof Individual) {
1692
+                    $html .= $record->getSexImage();
1693
+                }
1694
+                $html .= '</td>';
1695
+                $html .= '<td data-sort="' . $fact->getDate()->minimumJulianDay() . '">';
1696
+                $html .= $fact->getDate()->display();
1697
+                $html .= '</td>';
1698
+                $html .= '<td class="center" data-sort="' . $fact->anniv . '">';
1699
+                $html .= ($fact->anniv ? I18N::number($fact->anniv) : '');
1700
+                $html .= '</td>';
1701
+                $html .= '<td class="center">' . $fact->getLabel() . '</td>';
1702
+                $html .= '</tr>';
1703
+            }
1704
+
1705
+            $html .= '</tbody></table>';
1706
+        } else {
1707
+            if ($endjd === WT_CLIENT_JD) {
1708
+                // We're dealing with the Today’s Events block
1709
+                if ($filter === 0) {
1710
+                    $html .=  I18N::translate('No events exist for today.');
1711
+                } else {
1712
+                    $html .=  I18N::translate('No events for living individuals exist for today.');
1713
+                }
1714
+            } else {
1715
+                // We're dealing with the Upcoming Events block
1716
+                if ($filter === 0) {
1717
+                    if ($endjd === $startjd) {
1718
+                        $html .=  I18N::translate('No events exist for tomorrow.');
1719
+                    } else {
1720
+                        $html .=  /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1721
+                    }
1722
+                } else {
1723
+                    if ($endjd === $startjd) {
1724
+                        $html .=  I18N::translate('No events for living individuals exist for tomorrow.');
1725
+                    } else {
1726
+                        // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1727
+                        $html .=  I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1728
+                    }
1729
+                }
1730
+            }
1731
+        }
1732
+
1733
+        return $html;
1734
+    }
1735
+
1736
+    /**
1737
+     * Print a list of events
1738
+     *
1739
+     * This performs the same function as print_events_table(), but formats the output differently.
1740
+     *
1741
+     * @param int $startjd
1742
+     * @param int $endjd
1743
+     * @param string $events
1744
+     * @param bool $only_living
1745
+     * @param string $sort_by
1746
+     *
1747
+     * @return string
1748
+     */
1749
+    public static function eventsList($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') {
1750
+        global $WT_TREE;
1751
+
1752
+        // Did we have any output? Did we skip anything?
1753
+        $output          = 0;
1754
+        $filter          = 0;
1755
+        $filtered_events = array();
1756
+        $html            = '';
1757
+        foreach (FunctionsDb::getEventsList($startjd, $endjd, $events, $WT_TREE) as $fact) {
1758
+            $record = $fact->getParent();
1759
+            // only living people ?
1760
+            if ($only_living) {
1761
+                if ($record instanceof Individual && $record->isDead()) {
1762
+                    $filter++;
1763
+                    continue;
1764
+                }
1765
+                if ($record instanceof Family) {
1766
+                    $husb = $record->getHusband();
1767
+                    if (is_null($husb) || $husb->isDead()) {
1768
+                        $filter++;
1769
+                        continue;
1770
+                    }
1771
+                    $wife = $record->getWife();
1772
+                    if (is_null($wife) || $wife->isDead()) {
1773
+                        $filter++;
1774
+                        continue;
1775
+                    }
1776
+                }
1777
+            }
1778
+
1779
+            $output++;
1780
+
1781
+            $filtered_events[] = $fact;
1782
+        }
1783
+
1784
+        // Now we've filtered the list, we can sort by event, if required
1785
+        switch ($sort_by) {
1786
+        case 'anniv':
1787
+            // Data is already sorted by anniversary date
1788
+            break;
1789
+        case 'alpha':
1790
+            uasort($filtered_events, function (Fact $x, Fact $y) {
1791
+                return GedcomRecord::compare($x->getParent(), $y->getParent());
1792
+            });
1793
+            break;
1794
+        }
1795
+
1796
+        foreach ($filtered_events as $fact) {
1797
+            $record = $fact->getParent();
1798
+            $html .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
1799
+            if ($record instanceof Individual) {
1800
+                $html .= $record->getSexImage();
1801
+            }
1802
+            $html .= '<br><div class="indent">';
1803
+            $html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
1804
+            if ($fact->anniv) {
1805
+                $html .= ' (' . I18N::translate('%s year anniversary', I18N::number($fact->anniv)) . ')';
1806
+            }
1807
+            if (!$fact->getPlace()->isEmpty()) {
1808
+                $html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
1809
+            }
1810
+            $html .= '</div>';
1811
+        }
1812
+
1813
+        // Print a final summary message about restricted/filtered facts
1814
+        $summary = '';
1815
+        if ($endjd == WT_CLIENT_JD) {
1816
+            // We're dealing with the Today’s Events block
1817
+            if ($output == 0) {
1818
+                if ($filter == 0) {
1819
+                    $summary = I18N::translate('No events exist for today.');
1820
+                } else {
1821
+                    $summary = I18N::translate('No events for living individuals exist for today.');
1822
+                }
1823
+            }
1824
+        } else {
1825
+            // We're dealing with the Upcoming Events block
1826
+            if ($output == 0) {
1827
+                if ($filter == 0) {
1828
+                    if ($endjd == $startjd) {
1829
+                        $summary = I18N::translate('No events exist for tomorrow.');
1830
+                    } else {
1831
+                        // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1832
+                        $summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1833
+                    }
1834
+                } else {
1835
+                    if ($endjd == $startjd) {
1836
+                        $summary = I18N::translate('No events for living individuals exist for tomorrow.');
1837
+                    } else {
1838
+                        // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow”
1839
+                        $summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1));
1840
+                    }
1841
+                }
1842
+            }
1843
+        }
1844
+        if ($summary) {
1845
+            $html .= '<b>' . $summary . '</b>';
1846
+        }
1847
+
1848
+        return $html;
1849
+    }
1850
+
1851
+    /**
1852
+     * Print a chart by age using Google chart API
1853
+     *
1854
+     * @param int[] $data
1855
+     * @param string $title
1856
+     *
1857
+     * @return string
1858
+     */
1859
+    public static function chartByAge($data, $title) {
1860
+        $count  = 0;
1861
+        $agemax = 0;
1862
+        $vmax   = 0;
1863
+        $avg    = 0;
1864
+        foreach ($data as $age => $v) {
1865
+            $n      = strlen($v);
1866
+            $vmax   = max($vmax, $n);
1867
+            $agemax = max($agemax, $age);
1868
+            $count += $n;
1869
+            $avg += $age * $n;
1870
+        }
1871
+        if ($count < 1) {
1872
+            return '';
1873
+        }
1874
+        $avg       = round($avg / $count);
1875
+        $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1876
+        $chart_url .= "&amp;chs=725x150"; // size
1877
+        $chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
1878
+        $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1879
+        $chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
1880
+        $chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
1881
+        $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1882
+        $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1883
+        $chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
1884
+        $chart_url .= "&amp;chxl=0:|"; // label
1885
+        for ($age = 0; $age <= $agemax; $age += 5) {
1886
+            $chart_url .= $age . "|||||"; // x axis
1887
+        }
1888
+        $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1889
+        $chart_url .= "|2:||";
1890
+        $step = $vmax;
1891
+        for ($d = $vmax; $d > 0; $d--) {
1892
+            if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1893
+                $step = $d;
1894
+            }
1895
+        }
1896
+        if ($step == $vmax) {
1897
+            for ($d = $vmax - 1; $d > 0; $d--) {
1898
+                if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1899
+                    $step = $d;
1900
+                }
1901
+            }
1902
+        }
1903
+        for ($n = $step; $n < $vmax; $n += $step) {
1904
+            $chart_url .= $n . "|";
1905
+        }
1906
+        $chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1907
+        $chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1908
+        $chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1909
+        $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1910
+        for ($age = 0; $age <= $agemax; $age++) {
1911
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "M") * 61 / $vmax)];
1912
+        }
1913
+        $chart_url .= ",";
1914
+        for ($age = 0; $age <= $agemax; $age++) {
1915
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "F") * 61 / $vmax)];
1916
+        }
1917
+        $html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1918
+
1919
+        return $html;
1920
+    }
1921
+
1922
+    /**
1923
+     * Print a chart by decade using Google chart API
1924
+     *
1925
+     * @param int[] $data
1926
+     * @param string $title
1927
+     *
1928
+     * @return string
1929
+     */
1930
+    public static function chartByDecade($data, $title) {
1931
+        $count = 0;
1932
+        $vmax  = 0;
1933
+        foreach ($data as $v) {
1934
+            $n    = strlen($v);
1935
+            $vmax = max($vmax, $n);
1936
+            $count += $n;
1937
+        }
1938
+        if ($count < 1) {
1939
+            return '';
1940
+        }
1941
+        $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type
1942
+        $chart_url .= "&amp;chs=360x150"; // size
1943
+        $chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
1944
+        $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
1945
+        $chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
1946
+        $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
1947
+        $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
1948
+        $chart_url .= "&amp;chxl=0:|&lt;|||"; // <1570
1949
+        for ($y = 1600; $y < 2030; $y += 50) {
1950
+            $chart_url .= $y . "|||||"; // x axis
1951
+        }
1952
+        $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis
1953
+        $chart_url .= "|2:||";
1954
+        $step = $vmax;
1955
+        for ($d = $vmax; $d > 0; $d--) {
1956
+            if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) {
1957
+                $step = $d;
1958
+            }
1959
+        }
1960
+        if ($step == $vmax) {
1961
+            for ($d = $vmax - 1; $d > 0; $d--) {
1962
+                if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) {
1963
+                    $step = $d;
1964
+                }
1965
+            }
1966
+        }
1967
+        for ($n = $step; $n < $vmax; $n += $step) {
1968
+            $chart_url .= $n . "|";
1969
+        }
1970
+        $chart_url .= rawurlencode($vmax . " / " . $count); // r axis
1971
+        $chart_url .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
1972
+        $chart_url .= "&amp;chd=s:"; // data : simple encoding from A=0 to 9=61
1973
+        $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1974
+        for ($y = 1570; $y < 2030; $y += 10) {
1975
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "M") * 61 / $vmax)];
1976
+        }
1977
+        $chart_url .= ",";
1978
+        for ($y = 1570; $y < 2030; $y += 10) {
1979
+            $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "F") * 61 / $vmax)];
1980
+        }
1981
+        $html = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
1982
+
1983
+        return $html;
1984
+    }
1985 1985
 }
Please login to merge, or discard this patch.
app/Family.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -343,7 +343,7 @@  discard block
 block discarded – undo
343 343
 			// Check the script used by each name, so we can match cyrillic with cyrillic, greek with greek, etc.
344 344
 			$husb_names = array();
345 345
 			if ($this->husb) {
346
-				$husb_names = array_filter($this->husb->getAllNames(), function(array $x) { return $x['type'] !== '_MARNM'; } );
346
+				$husb_names = array_filter($this->husb->getAllNames(), function (array $x) { return $x['type'] !== '_MARNM'; } );
347 347
 			}
348 348
 			// If the individual only has married names, create a dummy birth name.
349 349
 			if (empty($husb_names)) {
@@ -359,7 +359,7 @@  discard block
 block discarded – undo
359 359
 
360 360
 			$wife_names = array();
361 361
 			if ($this->wife) {
362
-				$wife_names = array_filter($this->wife->getAllNames(), function(array $x) { return $x['type'] !== '_MARNM'; } );
362
+				$wife_names = array_filter($this->wife->getAllNames(), function (array $x) { return $x['type'] !== '_MARNM'; } );
363 363
 			}
364 364
 			// If the individual only has married names, create a dummy birth name.
365 365
 			if (empty($wife_names)) {
Please login to merge, or discard this patch.
Indentation   +397 added lines, -397 removed lines patch added patch discarded remove patch
@@ -19,401 +19,401 @@
 block discarded – undo
19 19
  * A GEDCOM family (FAM) object.
20 20
  */
21 21
 class Family extends GedcomRecord {
22
-	const RECORD_TYPE = 'FAM';
23
-	const URL_PREFIX  = 'family.php?famid=';
24
-
25
-	/** @var Individual|null The husband (or first spouse for same-sex couples) */
26
-	private $husb;
27
-
28
-	/** @var Individual|null The wife (or second spouse for same-sex couples) */
29
-	private $wife;
30
-
31
-	/**
32
-	 * Create a GedcomRecord object from raw GEDCOM data.
33
-	 *
34
-	 * @param string      $xref
35
-	 * @param string      $gedcom  an empty string for new/pending records
36
-	 * @param string|null $pending null for a record with no pending edits,
37
-	 *                             empty string for records with pending deletions
38
-	 * @param Tree        $tree
39
-	 */
40
-	public function __construct($xref, $gedcom, $pending, $tree) {
41
-		parent::__construct($xref, $gedcom, $pending, $tree);
42
-
43
-		// Fetch family members
44
-		if (preg_match_all('/^1 (?:HUSB|WIFE|CHIL) @(.+)@/m', $gedcom . $pending, $match)) {
45
-			Individual::load($tree, $match[1]);
46
-		}
47
-
48
-		if (preg_match('/^1 HUSB @(.+)@/m', $gedcom . $pending, $match)) {
49
-			$this->husb = Individual::getInstance($match[1], $tree);
50
-		}
51
-		if (preg_match('/^1 WIFE @(.+)@/m', $gedcom . $pending, $match)) {
52
-			$this->wife = Individual::getInstance($match[1], $tree);
53
-		}
54
-
55
-		// Make sure husb/wife are the right way round.
56
-		if ($this->husb && $this->husb->getSex() === 'F' || $this->wife && $this->wife->getSex() === 'M') {
57
-			list($this->husb, $this->wife) = array($this->wife, $this->husb);
58
-		}
59
-	}
60
-
61
-	/**
62
-	 * Generate a private version of this record
63
-	 *
64
-	 * @param int $access_level
65
-	 *
66
-	 * @return string
67
-	 */
68
-	protected function createPrivateGedcomRecord($access_level) {
69
-		$SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
70
-
71
-		$rec = '0 @' . $this->xref . '@ FAM';
72
-		// Just show the 1 CHIL/HUSB/WIFE tag, not any subtags, which may contain private data
73
-		preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches, PREG_SET_ORDER);
74
-		foreach ($matches as $match) {
75
-			$rela = Individual::getInstance($match[1], $this->tree);
76
-			if ($rela && ($SHOW_PRIVATE_RELATIONSHIPS || $rela->canShow($access_level))) {
77
-				$rec .= $match[0];
78
-			}
79
-		}
80
-
81
-		return $rec;
82
-	}
83
-
84
-	/**
85
-	 * Fetch data from the database
86
-	 *
87
-	 * @param string $xref
88
-	 * @param int    $tree_id
89
-	 *
90
-	 * @return null|string
91
-	 */
92
-	protected static function fetchGedcomRecord($xref, $tree_id) {
93
-		return Database::prepare(
94
-			"SELECT f_gedcom FROM `##families` WHERE f_id = :xref AND f_file = :tree_id"
95
-		)->execute(array(
96
-			'xref'    => $xref,
97
-			'tree_id' => $tree_id,
98
-		))->fetchOne();
99
-	}
100
-
101
-	/**
102
-	 * Get the male (or first female) partner of the family
103
-	 *
104
-	 * @param $access_level int|null
105
-	 *
106
-	 * @return Individual|null
107
-	 */
108
-	public function getHusband($access_level = null) {
109
-		$SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
110
-
111
-		if ($this->husb && ($SHOW_PRIVATE_RELATIONSHIPS || $this->husb->canShowName($access_level))) {
112
-			return $this->husb;
113
-		} else {
114
-			return null;
115
-		}
116
-	}
117
-
118
-	/**
119
-	 * Get the female (or second male) partner of the family
120
-	 *
121
-	 * @param $access_level int|null
122
-	 *
123
-	 * @return Individual|null
124
-	 */
125
-	public function getWife($access_level = null) {
126
-		$SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
127
-
128
-		if ($this->wife && ($SHOW_PRIVATE_RELATIONSHIPS || $this->wife->canShowName($access_level))) {
129
-			return $this->wife;
130
-		} else {
131
-			return null;
132
-		}
133
-	}
134
-
135
-	/**
136
-	 * Each object type may have its own special rules, and re-implement this function.
137
-	 *
138
-	 * @param int $access_level
139
-	 *
140
-	 * @return bool
141
-	 */
142
-	protected function canShowByType($access_level) {
143
-		// Hide a family if any member is private
144
-		preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches);
145
-		foreach ($matches[1] as $match) {
146
-			$person = Individual::getInstance($match, $this->tree);
147
-			if ($person && !$person->canShow($access_level)) {
148
-				return false;
149
-			}
150
-		}
151
-
152
-		return true;
153
-	}
154
-
155
-	/**
156
-	 * Can the name of this record be shown?
157
-	 *
158
-	 * @param int|null $access_level
159
-	 *
160
-	 * @return bool
161
-	 */
162
-	public function canShowName($access_level = null) {
163
-		// We can always see the name (Husband-name + Wife-name), however,
164
-		// the name will often be "private + private"
165
-		return true;
166
-	}
167
-
168
-	/**
169
-	 * Find the spouse of a person.
170
-	 *
171
-	 * @param Individual $person
172
-	 * @param int|null   $access_level
173
-	 *
174
-	 * @return Individual|null
175
-	 */
176
-	public function getSpouse(Individual $person, $access_level = null) {
177
-		if ($person === $this->wife) {
178
-			return $this->getHusband($access_level);
179
-		} else {
180
-			return $this->getWife($access_level);
181
-		}
182
-	}
183
-
184
-	/**
185
-	 * Get the (zero, one or two) spouses from this family.
186
-	 *
187
-	 * @param int|null $access_level
188
-	 *
189
-	 * @return Individual[]
190
-	 */
191
-	public function getSpouses($access_level = null) {
192
-		return array_filter(array(
193
-			$this->getHusband($access_level),
194
-			$this->getWife($access_level),
195
-		));
196
-	}
197
-
198
-	/**
199
-	 * Get a list of this family’s children.
200
-	 *
201
-	 * @param int|null $access_level
202
-	 *
203
-	 * @return Individual[]
204
-	 */
205
-	public function getChildren($access_level = null) {
206
-		if ($access_level === null) {
207
-			$access_level = Auth::accessLevel($this->tree);
208
-		}
209
-
210
-		$SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
211
-
212
-		$children = array();
213
-		foreach ($this->getFacts('CHIL', false, $access_level, $SHOW_PRIVATE_RELATIONSHIPS) as $fact) {
214
-			$child = $fact->getTarget();
215
-			if ($child && ($SHOW_PRIVATE_RELATIONSHIPS || $child->canShowName($access_level))) {
216
-				$children[] = $child;
217
-			}
218
-		}
219
-
220
-		return $children;
221
-	}
222
-
223
-	/**
224
-	 * Static helper function to sort an array of families by marriage date
225
-	 *
226
-	 * @param Family $x
227
-	 * @param Family $y
228
-	 *
229
-	 * @return int
230
-	 */
231
-	public static function compareMarrDate(Family $x, Family $y) {
232
-		return Date::compare($x->getMarriageDate(), $y->getMarriageDate());
233
-	}
234
-
235
-	/**
236
-	 * Number of children - for the individual list
237
-	 *
238
-	 * @return int
239
-	 */
240
-	public function getNumberOfChildren() {
241
-		$nchi = count($this->getChildren());
242
-		foreach ($this->getFacts('NCHI') as $fact) {
243
-			$nchi = max($nchi, (int) $fact->getValue());
244
-		}
245
-
246
-		return $nchi;
247
-	}
248
-
249
-	/**
250
-	 * get the marriage event
251
-	 *
252
-	 * @return Fact
253
-	 */
254
-	public function getMarriage() {
255
-		return $this->getFirstFact('MARR');
256
-	}
257
-
258
-	/**
259
-	 * Get marriage date
260
-	 *
261
-	 * @return Date
262
-	 */
263
-	public function getMarriageDate() {
264
-		$marriage = $this->getMarriage();
265
-		if ($marriage) {
266
-			return $marriage->getDate();
267
-		} else {
268
-			return new Date('');
269
-		}
270
-	}
271
-
272
-	/**
273
-	 * Get the marriage year - displayed on lists of families
274
-	 *
275
-	 * @return int
276
-	 */
277
-	public function getMarriageYear() {
278
-		return $this->getMarriageDate()->minimumDate()->y;
279
-	}
280
-
281
-	/**
282
-	 * Get the type for this marriage
283
-	 *
284
-	 * @return string|null
285
-	 */
286
-	public function getMarriageType() {
287
-		$marriage = $this->getMarriage();
288
-		if ($marriage) {
289
-			return $marriage->getAttribute('TYPE');
290
-		} else {
291
-			return null;
292
-		}
293
-	}
294
-
295
-	/**
296
-	 * Get the marriage place
297
-	 *
298
-	 * @return Place
299
-	 */
300
-	public function getMarriagePlace() {
301
-		$marriage = $this->getMarriage();
302
-
303
-		return $marriage->getPlace();
304
-	}
305
-
306
-	/**
307
-	 * Get a list of all marriage dates - for the family lists.
308
-	 *
309
-	 * @return Date[]
310
-	 */
311
-	public function getAllMarriageDates() {
312
-		foreach (explode('|', WT_EVENTS_MARR) as $event) {
313
-			if ($array = $this->getAllEventDates($event)) {
314
-				return $array;
315
-			}
316
-		}
317
-
318
-		return array();
319
-	}
320
-
321
-	/**
322
-	 * Get a list of all marriage places - for the family lists.
323
-	 *
324
-	 * @return string[]
325
-	 */
326
-	public function getAllMarriagePlaces() {
327
-		foreach (explode('|', WT_EVENTS_MARR) as $event) {
328
-			if ($array = $this->getAllEventPlaces($event)) {
329
-				return $array;
330
-			}
331
-		}
332
-
333
-		return array();
334
-	}
335
-
336
-	/**
337
-	 * Derived classes should redefine this function, otherwise the object will have no name
338
-	 *
339
-	 * @return string[][]
340
-	 */
341
-	public function getAllNames() {
342
-		if (is_null($this->_getAllNames)) {
343
-			// Check the script used by each name, so we can match cyrillic with cyrillic, greek with greek, etc.
344
-			$husb_names = array();
345
-			if ($this->husb) {
346
-				$husb_names = array_filter($this->husb->getAllNames(), function(array $x) { return $x['type'] !== '_MARNM'; } );
347
-			}
348
-			// If the individual only has married names, create a dummy birth name.
349
-			if (empty($husb_names)) {
350
-				$husb_names[] = array(
351
-					'type' => 'BIRT',
352
-					'sort' => '@N.N.',
353
-					'full' => I18N::translateContext('Unknown given name', '…') . ' ' . I18N::translateContext('Unknown surname', '…'),
354
-				);
355
-			}
356
-			foreach ($husb_names as $n => $husb_name) {
357
-				$husb_names[$n]['script'] = I18N::textScript($husb_name['full']);
358
-			}
359
-
360
-			$wife_names = array();
361
-			if ($this->wife) {
362
-				$wife_names = array_filter($this->wife->getAllNames(), function(array $x) { return $x['type'] !== '_MARNM'; } );
363
-			}
364
-			// If the individual only has married names, create a dummy birth name.
365
-			if (empty($wife_names)) {
366
-				$wife_names[] = array(
367
-					'type' => 'BIRT',
368
-					'sort' => '@N.N.',
369
-					'full' => I18N::translateContext('Unknown given name', '…') . ' ' . I18N::translateContext('Unknown surname', '…'),
370
-				);
371
-			}
372
-			foreach ($wife_names as $n => $wife_name) {
373
-				$wife_names[$n]['script'] = I18N::textScript($wife_name['full']);
374
-			}
375
-
376
-			// Add the matched names first
377
-			foreach ($husb_names as $husb_name) {
378
-				foreach ($wife_names as $wife_name) {
379
-					if ($husb_name['script'] == $wife_name['script']) {
380
-						$this->_getAllNames[] = array(
381
-							'type' => $husb_name['type'],
382
-							'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'],
383
-							'full' => $husb_name['full'] . ' + ' . $wife_name['full'],
384
-							// No need for a fullNN entry - we do not currently store FAM names in the database
385
-						);
386
-					}
387
-				}
388
-			}
389
-
390
-			// Add the unmatched names second (there may be no matched names)
391
-			foreach ($husb_names as $husb_name) {
392
-				foreach ($wife_names as $wife_name) {
393
-					if ($husb_name['script'] != $wife_name['script']) {
394
-						$this->_getAllNames[] = array(
395
-							'type' => $husb_name['type'],
396
-							'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'],
397
-							'full' => $husb_name['full'] . ' + ' . $wife_name['full'],
398
-							// No need for a fullNN entry - we do not currently store FAM names in the database
399
-						);
400
-					}
401
-				}
402
-			}
403
-		}
404
-
405
-		return $this->_getAllNames;
406
-	}
407
-
408
-	/**
409
-	 * This function should be redefined in derived classes to show any major
410
-	 * identifying characteristics of this record.
411
-	 *
412
-	 * @return string
413
-	 */
414
-	public function formatListDetails() {
415
-		return
416
-			$this->formatFirstMajorFact(WT_EVENTS_MARR, 1) .
417
-			$this->formatFirstMajorFact(WT_EVENTS_DIV, 1);
418
-	}
22
+    const RECORD_TYPE = 'FAM';
23
+    const URL_PREFIX  = 'family.php?famid=';
24
+
25
+    /** @var Individual|null The husband (or first spouse for same-sex couples) */
26
+    private $husb;
27
+
28
+    /** @var Individual|null The wife (or second spouse for same-sex couples) */
29
+    private $wife;
30
+
31
+    /**
32
+     * Create a GedcomRecord object from raw GEDCOM data.
33
+     *
34
+     * @param string      $xref
35
+     * @param string      $gedcom  an empty string for new/pending records
36
+     * @param string|null $pending null for a record with no pending edits,
37
+     *                             empty string for records with pending deletions
38
+     * @param Tree        $tree
39
+     */
40
+    public function __construct($xref, $gedcom, $pending, $tree) {
41
+        parent::__construct($xref, $gedcom, $pending, $tree);
42
+
43
+        // Fetch family members
44
+        if (preg_match_all('/^1 (?:HUSB|WIFE|CHIL) @(.+)@/m', $gedcom . $pending, $match)) {
45
+            Individual::load($tree, $match[1]);
46
+        }
47
+
48
+        if (preg_match('/^1 HUSB @(.+)@/m', $gedcom . $pending, $match)) {
49
+            $this->husb = Individual::getInstance($match[1], $tree);
50
+        }
51
+        if (preg_match('/^1 WIFE @(.+)@/m', $gedcom . $pending, $match)) {
52
+            $this->wife = Individual::getInstance($match[1], $tree);
53
+        }
54
+
55
+        // Make sure husb/wife are the right way round.
56
+        if ($this->husb && $this->husb->getSex() === 'F' || $this->wife && $this->wife->getSex() === 'M') {
57
+            list($this->husb, $this->wife) = array($this->wife, $this->husb);
58
+        }
59
+    }
60
+
61
+    /**
62
+     * Generate a private version of this record
63
+     *
64
+     * @param int $access_level
65
+     *
66
+     * @return string
67
+     */
68
+    protected function createPrivateGedcomRecord($access_level) {
69
+        $SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
70
+
71
+        $rec = '0 @' . $this->xref . '@ FAM';
72
+        // Just show the 1 CHIL/HUSB/WIFE tag, not any subtags, which may contain private data
73
+        preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches, PREG_SET_ORDER);
74
+        foreach ($matches as $match) {
75
+            $rela = Individual::getInstance($match[1], $this->tree);
76
+            if ($rela && ($SHOW_PRIVATE_RELATIONSHIPS || $rela->canShow($access_level))) {
77
+                $rec .= $match[0];
78
+            }
79
+        }
80
+
81
+        return $rec;
82
+    }
83
+
84
+    /**
85
+     * Fetch data from the database
86
+     *
87
+     * @param string $xref
88
+     * @param int    $tree_id
89
+     *
90
+     * @return null|string
91
+     */
92
+    protected static function fetchGedcomRecord($xref, $tree_id) {
93
+        return Database::prepare(
94
+            "SELECT f_gedcom FROM `##families` WHERE f_id = :xref AND f_file = :tree_id"
95
+        )->execute(array(
96
+            'xref'    => $xref,
97
+            'tree_id' => $tree_id,
98
+        ))->fetchOne();
99
+    }
100
+
101
+    /**
102
+     * Get the male (or first female) partner of the family
103
+     *
104
+     * @param $access_level int|null
105
+     *
106
+     * @return Individual|null
107
+     */
108
+    public function getHusband($access_level = null) {
109
+        $SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
110
+
111
+        if ($this->husb && ($SHOW_PRIVATE_RELATIONSHIPS || $this->husb->canShowName($access_level))) {
112
+            return $this->husb;
113
+        } else {
114
+            return null;
115
+        }
116
+    }
117
+
118
+    /**
119
+     * Get the female (or second male) partner of the family
120
+     *
121
+     * @param $access_level int|null
122
+     *
123
+     * @return Individual|null
124
+     */
125
+    public function getWife($access_level = null) {
126
+        $SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
127
+
128
+        if ($this->wife && ($SHOW_PRIVATE_RELATIONSHIPS || $this->wife->canShowName($access_level))) {
129
+            return $this->wife;
130
+        } else {
131
+            return null;
132
+        }
133
+    }
134
+
135
+    /**
136
+     * Each object type may have its own special rules, and re-implement this function.
137
+     *
138
+     * @param int $access_level
139
+     *
140
+     * @return bool
141
+     */
142
+    protected function canShowByType($access_level) {
143
+        // Hide a family if any member is private
144
+        preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches);
145
+        foreach ($matches[1] as $match) {
146
+            $person = Individual::getInstance($match, $this->tree);
147
+            if ($person && !$person->canShow($access_level)) {
148
+                return false;
149
+            }
150
+        }
151
+
152
+        return true;
153
+    }
154
+
155
+    /**
156
+     * Can the name of this record be shown?
157
+     *
158
+     * @param int|null $access_level
159
+     *
160
+     * @return bool
161
+     */
162
+    public function canShowName($access_level = null) {
163
+        // We can always see the name (Husband-name + Wife-name), however,
164
+        // the name will often be "private + private"
165
+        return true;
166
+    }
167
+
168
+    /**
169
+     * Find the spouse of a person.
170
+     *
171
+     * @param Individual $person
172
+     * @param int|null   $access_level
173
+     *
174
+     * @return Individual|null
175
+     */
176
+    public function getSpouse(Individual $person, $access_level = null) {
177
+        if ($person === $this->wife) {
178
+            return $this->getHusband($access_level);
179
+        } else {
180
+            return $this->getWife($access_level);
181
+        }
182
+    }
183
+
184
+    /**
185
+     * Get the (zero, one or two) spouses from this family.
186
+     *
187
+     * @param int|null $access_level
188
+     *
189
+     * @return Individual[]
190
+     */
191
+    public function getSpouses($access_level = null) {
192
+        return array_filter(array(
193
+            $this->getHusband($access_level),
194
+            $this->getWife($access_level),
195
+        ));
196
+    }
197
+
198
+    /**
199
+     * Get a list of this family’s children.
200
+     *
201
+     * @param int|null $access_level
202
+     *
203
+     * @return Individual[]
204
+     */
205
+    public function getChildren($access_level = null) {
206
+        if ($access_level === null) {
207
+            $access_level = Auth::accessLevel($this->tree);
208
+        }
209
+
210
+        $SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
211
+
212
+        $children = array();
213
+        foreach ($this->getFacts('CHIL', false, $access_level, $SHOW_PRIVATE_RELATIONSHIPS) as $fact) {
214
+            $child = $fact->getTarget();
215
+            if ($child && ($SHOW_PRIVATE_RELATIONSHIPS || $child->canShowName($access_level))) {
216
+                $children[] = $child;
217
+            }
218
+        }
219
+
220
+        return $children;
221
+    }
222
+
223
+    /**
224
+     * Static helper function to sort an array of families by marriage date
225
+     *
226
+     * @param Family $x
227
+     * @param Family $y
228
+     *
229
+     * @return int
230
+     */
231
+    public static function compareMarrDate(Family $x, Family $y) {
232
+        return Date::compare($x->getMarriageDate(), $y->getMarriageDate());
233
+    }
234
+
235
+    /**
236
+     * Number of children - for the individual list
237
+     *
238
+     * @return int
239
+     */
240
+    public function getNumberOfChildren() {
241
+        $nchi = count($this->getChildren());
242
+        foreach ($this->getFacts('NCHI') as $fact) {
243
+            $nchi = max($nchi, (int) $fact->getValue());
244
+        }
245
+
246
+        return $nchi;
247
+    }
248
+
249
+    /**
250
+     * get the marriage event
251
+     *
252
+     * @return Fact
253
+     */
254
+    public function getMarriage() {
255
+        return $this->getFirstFact('MARR');
256
+    }
257
+
258
+    /**
259
+     * Get marriage date
260
+     *
261
+     * @return Date
262
+     */
263
+    public function getMarriageDate() {
264
+        $marriage = $this->getMarriage();
265
+        if ($marriage) {
266
+            return $marriage->getDate();
267
+        } else {
268
+            return new Date('');
269
+        }
270
+    }
271
+
272
+    /**
273
+     * Get the marriage year - displayed on lists of families
274
+     *
275
+     * @return int
276
+     */
277
+    public function getMarriageYear() {
278
+        return $this->getMarriageDate()->minimumDate()->y;
279
+    }
280
+
281
+    /**
282
+     * Get the type for this marriage
283
+     *
284
+     * @return string|null
285
+     */
286
+    public function getMarriageType() {
287
+        $marriage = $this->getMarriage();
288
+        if ($marriage) {
289
+            return $marriage->getAttribute('TYPE');
290
+        } else {
291
+            return null;
292
+        }
293
+    }
294
+
295
+    /**
296
+     * Get the marriage place
297
+     *
298
+     * @return Place
299
+     */
300
+    public function getMarriagePlace() {
301
+        $marriage = $this->getMarriage();
302
+
303
+        return $marriage->getPlace();
304
+    }
305
+
306
+    /**
307
+     * Get a list of all marriage dates - for the family lists.
308
+     *
309
+     * @return Date[]
310
+     */
311
+    public function getAllMarriageDates() {
312
+        foreach (explode('|', WT_EVENTS_MARR) as $event) {
313
+            if ($array = $this->getAllEventDates($event)) {
314
+                return $array;
315
+            }
316
+        }
317
+
318
+        return array();
319
+    }
320
+
321
+    /**
322
+     * Get a list of all marriage places - for the family lists.
323
+     *
324
+     * @return string[]
325
+     */
326
+    public function getAllMarriagePlaces() {
327
+        foreach (explode('|', WT_EVENTS_MARR) as $event) {
328
+            if ($array = $this->getAllEventPlaces($event)) {
329
+                return $array;
330
+            }
331
+        }
332
+
333
+        return array();
334
+    }
335
+
336
+    /**
337
+     * Derived classes should redefine this function, otherwise the object will have no name
338
+     *
339
+     * @return string[][]
340
+     */
341
+    public function getAllNames() {
342
+        if (is_null($this->_getAllNames)) {
343
+            // Check the script used by each name, so we can match cyrillic with cyrillic, greek with greek, etc.
344
+            $husb_names = array();
345
+            if ($this->husb) {
346
+                $husb_names = array_filter($this->husb->getAllNames(), function(array $x) { return $x['type'] !== '_MARNM'; } );
347
+            }
348
+            // If the individual only has married names, create a dummy birth name.
349
+            if (empty($husb_names)) {
350
+                $husb_names[] = array(
351
+                    'type' => 'BIRT',
352
+                    'sort' => '@N.N.',
353
+                    'full' => I18N::translateContext('Unknown given name', '…') . ' ' . I18N::translateContext('Unknown surname', '…'),
354
+                );
355
+            }
356
+            foreach ($husb_names as $n => $husb_name) {
357
+                $husb_names[$n]['script'] = I18N::textScript($husb_name['full']);
358
+            }
359
+
360
+            $wife_names = array();
361
+            if ($this->wife) {
362
+                $wife_names = array_filter($this->wife->getAllNames(), function(array $x) { return $x['type'] !== '_MARNM'; } );
363
+            }
364
+            // If the individual only has married names, create a dummy birth name.
365
+            if (empty($wife_names)) {
366
+                $wife_names[] = array(
367
+                    'type' => 'BIRT',
368
+                    'sort' => '@N.N.',
369
+                    'full' => I18N::translateContext('Unknown given name', '…') . ' ' . I18N::translateContext('Unknown surname', '…'),
370
+                );
371
+            }
372
+            foreach ($wife_names as $n => $wife_name) {
373
+                $wife_names[$n]['script'] = I18N::textScript($wife_name['full']);
374
+            }
375
+
376
+            // Add the matched names first
377
+            foreach ($husb_names as $husb_name) {
378
+                foreach ($wife_names as $wife_name) {
379
+                    if ($husb_name['script'] == $wife_name['script']) {
380
+                        $this->_getAllNames[] = array(
381
+                            'type' => $husb_name['type'],
382
+                            'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'],
383
+                            'full' => $husb_name['full'] . ' + ' . $wife_name['full'],
384
+                            // No need for a fullNN entry - we do not currently store FAM names in the database
385
+                        );
386
+                    }
387
+                }
388
+            }
389
+
390
+            // Add the unmatched names second (there may be no matched names)
391
+            foreach ($husb_names as $husb_name) {
392
+                foreach ($wife_names as $wife_name) {
393
+                    if ($husb_name['script'] != $wife_name['script']) {
394
+                        $this->_getAllNames[] = array(
395
+                            'type' => $husb_name['type'],
396
+                            'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'],
397
+                            'full' => $husb_name['full'] . ' + ' . $wife_name['full'],
398
+                            // No need for a fullNN entry - we do not currently store FAM names in the database
399
+                        );
400
+                    }
401
+                }
402
+            }
403
+        }
404
+
405
+        return $this->_getAllNames;
406
+    }
407
+
408
+    /**
409
+     * This function should be redefined in derived classes to show any major
410
+     * identifying characteristics of this record.
411
+     *
412
+     * @return string
413
+     */
414
+    public function formatListDetails() {
415
+        return
416
+            $this->formatFirstMajorFact(WT_EVENTS_MARR, 1) .
417
+            $this->formatFirstMajorFact(WT_EVENTS_DIV, 1);
418
+    }
419 419
 }
Please login to merge, or discard this patch.
admin_trees_renumber.php 1 patch
Indentation   +236 added lines, -236 removed lines patch added patch discarded remove patch
@@ -29,259 +29,259 @@
 block discarded – undo
29 29
 
30 30
 $controller = new PageController;
31 31
 $controller
32
-	->restrictAccess(Auth::isManager($WT_TREE))
33
-	->setPageTitle(I18N::translate(/* I18N: Renumber the records in a family tree */ 'Renumber family tree') . ' — ' . $WT_TREE->getTitleHtml())
34
-	->pageHeader();
32
+    ->restrictAccess(Auth::isManager($WT_TREE))
33
+    ->setPageTitle(I18N::translate(/* I18N: Renumber the records in a family tree */ 'Renumber family tree') . ' — ' . $WT_TREE->getTitleHtml())
34
+    ->pageHeader();
35 35
 
36 36
 // Every XREF used by this tree and also used by some other tree
37 37
 $xrefs = Database::prepare(
38
-	"SELECT xref, type FROM (" .
39
-	" SELECT i_id AS xref, 'INDI' AS type FROM `##individuals` WHERE i_file = ?" .
40
-	"  UNION " .
41
-	" SELECT f_id AS xref, 'FAM' AS type FROM `##families` WHERE f_file = ?" .
42
-	"  UNION " .
43
-	" SELECT s_id AS xref, 'SOUR' AS type FROM `##sources` WHERE s_file = ?" .
44
-	"  UNION " .
45
-	" SELECT m_id AS xref, 'OBJE' AS type FROM `##media` WHERE m_file = ?" .
46
-	"  UNION " .
47
-	" SELECT o_id AS xref, o_type AS type FROM `##other` WHERE o_file = ? AND o_type NOT IN ('HEAD', 'TRLR')" .
48
-	") AS this_tree JOIN (" .
49
-	" SELECT xref FROM `##change` WHERE gedcom_id <> ?" .
50
-	"  UNION " .
51
-	" SELECT i_id AS xref FROM `##individuals` WHERE i_file <> ?" .
52
-	"  UNION " .
53
-	" SELECT f_id AS xref FROM `##families` WHERE f_file <> ?" .
54
-	"  UNION " .
55
-	" SELECT s_id AS xref FROM `##sources` WHERE s_file <> ?" .
56
-	"  UNION " .
57
-	" SELECT m_id AS xref FROM `##media` WHERE m_file <> ?" .
58
-	"  UNION " .
59
-	" SELECT o_id AS xref FROM `##other` WHERE o_file <> ? AND o_type NOT IN ('HEAD', 'TRLR')" .
60
-	") AS other_trees USING (xref)"
38
+    "SELECT xref, type FROM (" .
39
+    " SELECT i_id AS xref, 'INDI' AS type FROM `##individuals` WHERE i_file = ?" .
40
+    "  UNION " .
41
+    " SELECT f_id AS xref, 'FAM' AS type FROM `##families` WHERE f_file = ?" .
42
+    "  UNION " .
43
+    " SELECT s_id AS xref, 'SOUR' AS type FROM `##sources` WHERE s_file = ?" .
44
+    "  UNION " .
45
+    " SELECT m_id AS xref, 'OBJE' AS type FROM `##media` WHERE m_file = ?" .
46
+    "  UNION " .
47
+    " SELECT o_id AS xref, o_type AS type FROM `##other` WHERE o_file = ? AND o_type NOT IN ('HEAD', 'TRLR')" .
48
+    ") AS this_tree JOIN (" .
49
+    " SELECT xref FROM `##change` WHERE gedcom_id <> ?" .
50
+    "  UNION " .
51
+    " SELECT i_id AS xref FROM `##individuals` WHERE i_file <> ?" .
52
+    "  UNION " .
53
+    " SELECT f_id AS xref FROM `##families` WHERE f_file <> ?" .
54
+    "  UNION " .
55
+    " SELECT s_id AS xref FROM `##sources` WHERE s_file <> ?" .
56
+    "  UNION " .
57
+    " SELECT m_id AS xref FROM `##media` WHERE m_file <> ?" .
58
+    "  UNION " .
59
+    " SELECT o_id AS xref FROM `##other` WHERE o_file <> ? AND o_type NOT IN ('HEAD', 'TRLR')" .
60
+    ") AS other_trees USING (xref)"
61 61
 )->execute(array(
62
-	$WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(),
63
-	$WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(),
62
+    $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(),
63
+    $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(),
64 64
 ))->fetchAssoc();
65 65
 
66 66
 echo '<h1>', $controller->getPageTitle(), '</h1>';
67 67
 
68 68
 if (Filter::get('action') === 'renumber') {
69
-	foreach ($xrefs as $old_xref => $type) {
70
-		Database::beginTransaction();
71
-		Database::exec(
72
-			"LOCK TABLE `##individuals` WRITE," .
73
-			" `##families` WRITE," .
74
-			" `##sources` WRITE," .
75
-			" `##media` WRITE," .
76
-			" `##other` WRITE," .
77
-			" `##name` WRITE," .
78
-			" `##placelinks` WRITE," .
79
-			" `##change` WRITE," .
80
-			" `##next_id` WRITE," .
81
-			" `##dates` WRITE," .
82
-			" `##default_resn` WRITE," .
83
-			" `##hit_counter` WRITE," .
84
-			" `##link` WRITE," .
85
-			" `##user_gedcom_setting` WRITE"
86
-		);
87
-		$new_xref = $WT_TREE->getNewXref($type);
88
-		switch ($type) {
89
-		case 'INDI':
90
-			Database::prepare(
91
-				"UPDATE `##individuals` SET i_id = ?, i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_id = ? AND i_file = ?"
92
-			)->execute(array($new_xref, "0 @$old_xref@ INDI\n", "0 @$new_xref@ INDI\n", $old_xref, $WT_TREE->getTreeId()));
93
-			Database::prepare(
94
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'HUSB') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
95
-			)->execute(array($old_xref, " HUSB @$old_xref@", " HUSB @$new_xref@", $WT_TREE->getTreeId()));
96
-			Database::prepare(
97
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'WIFE') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
98
-			)->execute(array($old_xref, " WIFE @$old_xref@", " WIFE @$new_xref@", $WT_TREE->getTreeId()));
99
-			Database::prepare(
100
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'CHIL') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
101
-			)->execute(array($old_xref, " CHIL @$old_xref@", " CHIL @$new_xref@", $WT_TREE->getTreeId()));
102
-			Database::prepare(
103
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'ASSO') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
104
-			)->execute(array($old_xref, " ASSO @$old_xref@", " ASSO @$new_xref@", $WT_TREE->getTreeId()));
105
-			Database::prepare(
106
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = '_ASSO') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
107
-			)->execute(array($old_xref, " _ASSO @$old_xref@", " _ASSO @$new_xref@", $WT_TREE->getTreeId()));
108
-			Database::prepare(
109
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'ASSO') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
110
-			)->execute(array($old_xref, " ASSO @$old_xref@", " ASSO @$new_xref@", $WT_TREE->getTreeId()));
111
-			Database::prepare(
112
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = '_ASSO') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
113
-			)->execute(array($old_xref, " _ASSO @$old_xref@", " _ASSO @$new_xref@", $WT_TREE->getTreeId()));
114
-			Database::prepare(
115
-				"UPDATE `##placelinks` SET pl_gid = ? WHERE pl_gid = ? AND pl_file = ?"
116
-			)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
117
-			Database::prepare(
118
-				"UPDATE `##dates` SET d_gid = ? WHERE d_gid = ? AND d_file = ?"
119
-			)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
120
-			Database::prepare(
121
-				"UPDATE `##user_gedcom_setting` SET setting_value = ? WHERE setting_value = ? AND gedcom_id = ? AND setting_name IN ('gedcomid', 'rootid')"
122
-			)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
123
-			break;
124
-		case 'FAM':
125
-			Database::prepare(
126
-				"UPDATE `##families` SET f_id = ?, f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_id = ? AND f_file = ?"
127
-			)->execute(array($new_xref, "0 @$old_xref@ FAM\n", "0 @$new_xref@ FAM\n", $old_xref, $WT_TREE->getTreeId()));
128
-			Database::prepare(
129
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'FAMC') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
130
-			)->execute(array($old_xref, " FAMC @$old_xref@", " FAMC @$new_xref@", $WT_TREE->getTreeId()));
131
-			Database::prepare(
132
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'FAMS') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
133
-			)->execute(array($old_xref, " FAMS @$old_xref@", " FAMS @$new_xref@", $WT_TREE->getTreeId()));
134
-			Database::prepare(
135
-				"UPDATE `##placelinks` SET pl_gid = ? WHERE pl_gid = ? AND pl_file = ?"
136
-			)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
137
-			Database::prepare(
138
-				"UPDATE `##dates` SET d_gid = ? WHERE d_gid = ? AND d_file = ?"
139
-			)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
140
-			break;
141
-		case 'SOUR':
142
-			Database::prepare(
143
-				"UPDATE `##sources` SET s_id = ?, s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_id = ? AND s_file = ?"
144
-			)->execute(array($new_xref, "0 @$old_xref@ SOUR\n", "0 @$new_xref@ SOUR\n", $old_xref, $WT_TREE->getTreeId()));
145
-			Database::prepare(
146
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'SOUR') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
147
-			)->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
148
-			Database::prepare(
149
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'SOUR') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
150
-			)->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
151
-			Database::prepare(
152
-				"UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ? AND l_type = 'SOUR') SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
153
-			)->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
154
-			Database::prepare(
155
-				"UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ? AND l_type = 'SOUR') SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
156
-			)->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
157
-			break;
158
-		case 'REPO':
159
-			Database::prepare(
160
-				"UPDATE `##other` SET o_id = ?, o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_id = ? AND o_file = ?"
161
-			)->execute(array($new_xref, "0 @$old_xref@ REPO\n", "0 @$new_xref@ REPO\n", $old_xref, $WT_TREE->getTreeId()));
162
-			Database::prepare(
163
-				"UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ? AND l_type = 'REPO') SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
164
-			)->execute(array($old_xref, " REPO @$old_xref@", " REPO @$new_xref@", $WT_TREE->getTreeId()));
165
-			break;
166
-		case 'NOTE':
167
-			Database::prepare(
168
-				"UPDATE `##other` SET o_id = ?, o_gedcom = REPLACE(REPLACE(o_gedcom, ?, ?), ?, ?) WHERE o_id = ? AND o_file = ?"
169
-			)->execute(array($new_xref, "0 @$old_xref@ NOTE\n", "0 @$new_xref@ NOTE\n", "0 @$old_xref@ NOTE ", "0 @$new_xref@ NOTE ", $old_xref, $WT_TREE->getTreeId()));
170
-			Database::prepare(
171
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'NOTE') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
172
-			)->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
173
-			Database::prepare(
174
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'NOTE') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
175
-			)->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
176
-			Database::prepare(
177
-				"UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ? AND l_type = 'NOTE') SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
178
-			)->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
179
-			Database::prepare(
180
-				"UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ? AND l_type = 'NOTE') SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
181
-			)->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
182
-			Database::prepare(
183
-				"UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ? AND l_type = 'NOTE') SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
184
-			)->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
185
-			break;
186
-		case 'OBJE':
187
-			Database::prepare(
188
-				"UPDATE `##media` SET m_id = ?, m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_id = ? AND m_file = ?"
189
-			)->execute(array($new_xref, "0 @$old_xref@ OBJE\n", "0 @$new_xref@ OBJE\n", $old_xref, $WT_TREE->getTreeId()));
190
-			Database::prepare(
191
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'OBJE') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
192
-			)->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
193
-			Database::prepare(
194
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'OBJE') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
195
-			)->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
196
-			Database::prepare(
197
-				"UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ? AND l_type = 'OBJE') SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
198
-			)->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
199
-			Database::prepare(
200
-				"UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ? AND l_type = 'OBJE') SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
201
-			)->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
202
-			Database::prepare(
203
-				"UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ? AND l_type = 'OBJE') SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
204
-			)->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
205
-			break;
206
-		default:
207
-			Database::prepare(
208
-				"UPDATE `##other` SET o_id = ?, o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_id = ? AND o_file = ?"
209
-			)->execute(array($new_xref, "0 @$old_xref@ $type\n", "0 @$new_xref@ $type\n", $old_xref, $WT_TREE->getTreeId()));
210
-			Database::prepare(
211
-				"UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ?) SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
212
-			)->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
213
-			Database::prepare(
214
-				"UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ?) SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
215
-			)->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
216
-			Database::prepare(
217
-				"UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ?) SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
218
-			)->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
219
-			Database::prepare(
220
-				"UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ?) SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
221
-			)->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
222
-			Database::prepare(
223
-				"UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ?) SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
224
-			)->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
225
-			break;
226
-		}
227
-		Database::prepare(
228
-			"UPDATE `##name` SET n_id = ? WHERE n_id = ? AND n_file = ?"
229
-		)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
230
-		Database::prepare(
231
-			"UPDATE `##default_resn` SET xref = ? WHERE xref = ? AND gedcom_id = ?"
232
-		)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
233
-		Database::prepare(
234
-			"UPDATE `##hit_counter` SET page_parameter = ? WHERE page_parameter = ? AND gedcom_id = ?"
235
-		)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
236
-		Database::prepare(
237
-			"UPDATE `##link` SET l_from = ? WHERE l_from = ? AND l_file = ?"
238
-		)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
239
-		Database::prepare(
240
-			"UPDATE `##link` SET l_to = ? WHERE l_to = ? AND l_file = ?"
241
-		)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
242
-		echo '<p>', I18N::translate('The record %1$s has been renamed to %2$s.', $old_xref, $new_xref), '</p>';
243
-		unset($xrefs[$old_xref]);
244
-		Database::exec("UNLOCK TABLES");
245
-		Database::commit();
69
+    foreach ($xrefs as $old_xref => $type) {
70
+        Database::beginTransaction();
71
+        Database::exec(
72
+            "LOCK TABLE `##individuals` WRITE," .
73
+            " `##families` WRITE," .
74
+            " `##sources` WRITE," .
75
+            " `##media` WRITE," .
76
+            " `##other` WRITE," .
77
+            " `##name` WRITE," .
78
+            " `##placelinks` WRITE," .
79
+            " `##change` WRITE," .
80
+            " `##next_id` WRITE," .
81
+            " `##dates` WRITE," .
82
+            " `##default_resn` WRITE," .
83
+            " `##hit_counter` WRITE," .
84
+            " `##link` WRITE," .
85
+            " `##user_gedcom_setting` WRITE"
86
+        );
87
+        $new_xref = $WT_TREE->getNewXref($type);
88
+        switch ($type) {
89
+        case 'INDI':
90
+            Database::prepare(
91
+                "UPDATE `##individuals` SET i_id = ?, i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_id = ? AND i_file = ?"
92
+            )->execute(array($new_xref, "0 @$old_xref@ INDI\n", "0 @$new_xref@ INDI\n", $old_xref, $WT_TREE->getTreeId()));
93
+            Database::prepare(
94
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'HUSB') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
95
+            )->execute(array($old_xref, " HUSB @$old_xref@", " HUSB @$new_xref@", $WT_TREE->getTreeId()));
96
+            Database::prepare(
97
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'WIFE') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
98
+            )->execute(array($old_xref, " WIFE @$old_xref@", " WIFE @$new_xref@", $WT_TREE->getTreeId()));
99
+            Database::prepare(
100
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'CHIL') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
101
+            )->execute(array($old_xref, " CHIL @$old_xref@", " CHIL @$new_xref@", $WT_TREE->getTreeId()));
102
+            Database::prepare(
103
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'ASSO') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
104
+            )->execute(array($old_xref, " ASSO @$old_xref@", " ASSO @$new_xref@", $WT_TREE->getTreeId()));
105
+            Database::prepare(
106
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = '_ASSO') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
107
+            )->execute(array($old_xref, " _ASSO @$old_xref@", " _ASSO @$new_xref@", $WT_TREE->getTreeId()));
108
+            Database::prepare(
109
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'ASSO') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
110
+            )->execute(array($old_xref, " ASSO @$old_xref@", " ASSO @$new_xref@", $WT_TREE->getTreeId()));
111
+            Database::prepare(
112
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = '_ASSO') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
113
+            )->execute(array($old_xref, " _ASSO @$old_xref@", " _ASSO @$new_xref@", $WT_TREE->getTreeId()));
114
+            Database::prepare(
115
+                "UPDATE `##placelinks` SET pl_gid = ? WHERE pl_gid = ? AND pl_file = ?"
116
+            )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
117
+            Database::prepare(
118
+                "UPDATE `##dates` SET d_gid = ? WHERE d_gid = ? AND d_file = ?"
119
+            )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
120
+            Database::prepare(
121
+                "UPDATE `##user_gedcom_setting` SET setting_value = ? WHERE setting_value = ? AND gedcom_id = ? AND setting_name IN ('gedcomid', 'rootid')"
122
+            )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
123
+            break;
124
+        case 'FAM':
125
+            Database::prepare(
126
+                "UPDATE `##families` SET f_id = ?, f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_id = ? AND f_file = ?"
127
+            )->execute(array($new_xref, "0 @$old_xref@ FAM\n", "0 @$new_xref@ FAM\n", $old_xref, $WT_TREE->getTreeId()));
128
+            Database::prepare(
129
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'FAMC') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
130
+            )->execute(array($old_xref, " FAMC @$old_xref@", " FAMC @$new_xref@", $WT_TREE->getTreeId()));
131
+            Database::prepare(
132
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'FAMS') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
133
+            )->execute(array($old_xref, " FAMS @$old_xref@", " FAMS @$new_xref@", $WT_TREE->getTreeId()));
134
+            Database::prepare(
135
+                "UPDATE `##placelinks` SET pl_gid = ? WHERE pl_gid = ? AND pl_file = ?"
136
+            )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
137
+            Database::prepare(
138
+                "UPDATE `##dates` SET d_gid = ? WHERE d_gid = ? AND d_file = ?"
139
+            )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
140
+            break;
141
+        case 'SOUR':
142
+            Database::prepare(
143
+                "UPDATE `##sources` SET s_id = ?, s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_id = ? AND s_file = ?"
144
+            )->execute(array($new_xref, "0 @$old_xref@ SOUR\n", "0 @$new_xref@ SOUR\n", $old_xref, $WT_TREE->getTreeId()));
145
+            Database::prepare(
146
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'SOUR') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
147
+            )->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
148
+            Database::prepare(
149
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'SOUR') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
150
+            )->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
151
+            Database::prepare(
152
+                "UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ? AND l_type = 'SOUR') SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
153
+            )->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
154
+            Database::prepare(
155
+                "UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ? AND l_type = 'SOUR') SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
156
+            )->execute(array($old_xref, " SOUR @$old_xref@", " SOUR @$new_xref@", $WT_TREE->getTreeId()));
157
+            break;
158
+        case 'REPO':
159
+            Database::prepare(
160
+                "UPDATE `##other` SET o_id = ?, o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_id = ? AND o_file = ?"
161
+            )->execute(array($new_xref, "0 @$old_xref@ REPO\n", "0 @$new_xref@ REPO\n", $old_xref, $WT_TREE->getTreeId()));
162
+            Database::prepare(
163
+                "UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ? AND l_type = 'REPO') SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
164
+            )->execute(array($old_xref, " REPO @$old_xref@", " REPO @$new_xref@", $WT_TREE->getTreeId()));
165
+            break;
166
+        case 'NOTE':
167
+            Database::prepare(
168
+                "UPDATE `##other` SET o_id = ?, o_gedcom = REPLACE(REPLACE(o_gedcom, ?, ?), ?, ?) WHERE o_id = ? AND o_file = ?"
169
+            )->execute(array($new_xref, "0 @$old_xref@ NOTE\n", "0 @$new_xref@ NOTE\n", "0 @$old_xref@ NOTE ", "0 @$new_xref@ NOTE ", $old_xref, $WT_TREE->getTreeId()));
170
+            Database::prepare(
171
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'NOTE') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
172
+            )->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
173
+            Database::prepare(
174
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'NOTE') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
175
+            )->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
176
+            Database::prepare(
177
+                "UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ? AND l_type = 'NOTE') SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
178
+            )->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
179
+            Database::prepare(
180
+                "UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ? AND l_type = 'NOTE') SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
181
+            )->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
182
+            Database::prepare(
183
+                "UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ? AND l_type = 'NOTE') SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
184
+            )->execute(array($old_xref, " NOTE @$old_xref@", " NOTE @$new_xref@", $WT_TREE->getTreeId()));
185
+            break;
186
+        case 'OBJE':
187
+            Database::prepare(
188
+                "UPDATE `##media` SET m_id = ?, m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_id = ? AND m_file = ?"
189
+            )->execute(array($new_xref, "0 @$old_xref@ OBJE\n", "0 @$new_xref@ OBJE\n", $old_xref, $WT_TREE->getTreeId()));
190
+            Database::prepare(
191
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ? AND l_type = 'OBJE') SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
192
+            )->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
193
+            Database::prepare(
194
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ? AND l_type = 'OBJE') SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
195
+            )->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
196
+            Database::prepare(
197
+                "UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ? AND l_type = 'OBJE') SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
198
+            )->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
199
+            Database::prepare(
200
+                "UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ? AND l_type = 'OBJE') SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
201
+            )->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
202
+            Database::prepare(
203
+                "UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ? AND l_type = 'OBJE') SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
204
+            )->execute(array($old_xref, " OBJE @$old_xref@", " OBJE @$new_xref@", $WT_TREE->getTreeId()));
205
+            break;
206
+        default:
207
+            Database::prepare(
208
+                "UPDATE `##other` SET o_id = ?, o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_id = ? AND o_file = ?"
209
+            )->execute(array($new_xref, "0 @$old_xref@ $type\n", "0 @$new_xref@ $type\n", $old_xref, $WT_TREE->getTreeId()));
210
+            Database::prepare(
211
+                "UPDATE `##individuals` JOIN `##link` ON (l_file = i_file AND l_to = ?) SET i_gedcom = REPLACE(i_gedcom, ?, ?) WHERE i_file = ?"
212
+            )->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
213
+            Database::prepare(
214
+                "UPDATE `##families` JOIN `##link` ON (l_file = f_file AND l_to = ?) SET f_gedcom = REPLACE(f_gedcom, ?, ?) WHERE f_file = ?"
215
+            )->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
216
+            Database::prepare(
217
+                "UPDATE `##media` JOIN `##link` ON (l_file = m_file AND l_to = ?) SET m_gedcom = REPLACE(m_gedcom, ?, ?) WHERE m_file = ?"
218
+            )->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
219
+            Database::prepare(
220
+                "UPDATE `##sources` JOIN `##link` ON (l_file = s_file AND l_to = ?) SET s_gedcom = REPLACE(s_gedcom, ?, ?) WHERE s_file = ?"
221
+            )->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
222
+            Database::prepare(
223
+                "UPDATE `##other` JOIN `##link` ON (l_file = o_file AND l_to = ?) SET o_gedcom = REPLACE(o_gedcom, ?, ?) WHERE o_file = ?"
224
+            )->execute(array($old_xref, " @$old_xref@", " @$new_xref@", $WT_TREE->getTreeId()));
225
+            break;
226
+        }
227
+        Database::prepare(
228
+            "UPDATE `##name` SET n_id = ? WHERE n_id = ? AND n_file = ?"
229
+        )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
230
+        Database::prepare(
231
+            "UPDATE `##default_resn` SET xref = ? WHERE xref = ? AND gedcom_id = ?"
232
+        )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
233
+        Database::prepare(
234
+            "UPDATE `##hit_counter` SET page_parameter = ? WHERE page_parameter = ? AND gedcom_id = ?"
235
+        )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
236
+        Database::prepare(
237
+            "UPDATE `##link` SET l_from = ? WHERE l_from = ? AND l_file = ?"
238
+        )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
239
+        Database::prepare(
240
+            "UPDATE `##link` SET l_to = ? WHERE l_to = ? AND l_file = ?"
241
+        )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
242
+        echo '<p>', I18N::translate('The record %1$s has been renamed to %2$s.', $old_xref, $new_xref), '</p>';
243
+        unset($xrefs[$old_xref]);
244
+        Database::exec("UNLOCK TABLES");
245
+        Database::commit();
246 246
 
247
-		try {
248
-			Database::prepare(
249
-				"UPDATE `##favorite` SET xref = ? WHERE xref = ? AND gedcom_id = ?"
250
-			)->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
251
-		} catch (\Exception $ex) {
252
-			// Perhaps the favorites module was not installed?
253
-		}
247
+        try {
248
+            Database::prepare(
249
+                "UPDATE `##favorite` SET xref = ? WHERE xref = ? AND gedcom_id = ?"
250
+            )->execute(array($new_xref, $old_xref, $WT_TREE->getTreeId()));
251
+        } catch (\Exception $ex) {
252
+            // Perhaps the favorites module was not installed?
253
+        }
254 254
 
255
-		// How much time do we have left?
256
-		if (microtime(true) - WT_START_TIME > ini_get('max_execution_time') - 2) {
257
-			echo '<p>', I18N::translate('The server’s time limit has been reached.'), '</p>';
258
-			break;
259
-		}
260
-	}
261
-	if ($xrefs) {
255
+        // How much time do we have left?
256
+        if (microtime(true) - WT_START_TIME > ini_get('max_execution_time') - 2) {
257
+            echo '<p>', I18N::translate('The server’s time limit has been reached.'), '</p>';
258
+            break;
259
+        }
260
+    }
261
+    if ($xrefs) {
262 262
 
263
-	}
263
+    }
264 264
 } else {
265
-	echo '<p>', I18N::translate('In a family tree, each record has an internal reference number (called an “XREF”) such as “F123” or “R14”.'), '</p>';
266
-	echo '<p>', I18N::translate('You can renumber the records in a family tree, so that these internal reference numbers are not duplicated in any other family tree.'), '</p>';
265
+    echo '<p>', I18N::translate('In a family tree, each record has an internal reference number (called an “XREF”) such as “F123” or “R14”.'), '</p>';
266
+    echo '<p>', I18N::translate('You can renumber the records in a family tree, so that these internal reference numbers are not duplicated in any other family tree.'), '</p>';
267 267
 }
268 268
 
269 269
 echo '<p>', I18N::plural(
270
-	'This family tree has %s record which uses the same “XREF” as another family tree.',
271
-	'This family tree has %s records which use the same “XREF” as another family tree.',
272
-	count($xrefs), count($xrefs)
270
+    'This family tree has %s record which uses the same “XREF” as another family tree.',
271
+    'This family tree has %s records which use the same “XREF” as another family tree.',
272
+    count($xrefs), count($xrefs)
273 273
 ), '</p>';
274 274
 
275 275
 if ($xrefs) {
276
-	// We use GET (not POST) for this update operation - because we want the user to
277
-	// be able to press F5 to continue after a timeout.
278
-	echo '<form>';
279
-	echo '<p>', I18N::translate('You can renumber this family tree.'), '</p>';
280
-	echo '<button type="submit" class="btn btn-primary">';
281
-	echo '<i class="fa fa-check"></i> ', /* I18N: A button label. */ I18N::translate('continue');
282
-	echo '</button>';
283
-	echo '<input type="hidden" name="action" value="renumber">';
284
-	echo '<input type="hidden" name="ged" value="', $WT_TREE->getNameHtml(), '">';
285
-	echo '</form>';
286
-	echo '<p>', I18N::translate('Caution! This may take a long time. Be patient.'), '</p>';
276
+    // We use GET (not POST) for this update operation - because we want the user to
277
+    // be able to press F5 to continue after a timeout.
278
+    echo '<form>';
279
+    echo '<p>', I18N::translate('You can renumber this family tree.'), '</p>';
280
+    echo '<button type="submit" class="btn btn-primary">';
281
+    echo '<i class="fa fa-check"></i> ', /* I18N: A button label. */ I18N::translate('continue');
282
+    echo '</button>';
283
+    echo '<input type="hidden" name="action" value="renumber">';
284
+    echo '<input type="hidden" name="ged" value="', $WT_TREE->getNameHtml(), '">';
285
+    echo '</form>';
286
+    echo '<p>', I18N::translate('Caution! This may take a long time. Be patient.'), '</p>';
287 287
 }
Please login to merge, or discard this patch.
admin_module_reports.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -24,29 +24,29 @@
 block discarded – undo
24 24
 
25 25
 $controller = new PageController;
26 26
 $controller
27
-	->restrictAccess(Auth::isAdmin())
28
-	->setPageTitle(I18N::translate('Reports'));
27
+    ->restrictAccess(Auth::isAdmin())
28
+    ->setPageTitle(I18N::translate('Reports'));
29 29
 
30 30
 $action  = Filter::post('action');
31 31
 $modules = Module::getAllModulesByComponent('report');
32 32
 
33 33
 if ($action === 'update_mods' && Filter::checkCsrf()) {
34
-	foreach ($modules as $module) {
35
-		foreach (Tree::getAll() as $tree) {
36
-			$access_level = Filter::post('access-' . $module->getName() . '-' . $tree->getTreeId(), WT_REGEX_INTEGER, $module->defaultAccessLevel());
37
-			Database::prepare(
38
-				"REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'report', ?)"
39
-			)->execute(array($module->getName(), $tree->getTreeId(), $access_level));
40
-		}
41
-	}
34
+    foreach ($modules as $module) {
35
+        foreach (Tree::getAll() as $tree) {
36
+            $access_level = Filter::post('access-' . $module->getName() . '-' . $tree->getTreeId(), WT_REGEX_INTEGER, $module->defaultAccessLevel());
37
+            Database::prepare(
38
+                "REPLACE INTO `##module_privacy` (module_name, gedcom_id, component, access_level) VALUES (?, ?, 'report', ?)"
39
+            )->execute(array($module->getName(), $tree->getTreeId(), $access_level));
40
+        }
41
+    }
42 42
 
43
-	header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME);
43
+    header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME);
44 44
 
45
-	return;
45
+    return;
46 46
 }
47 47
 
48 48
 $controller
49
-	->pageHeader();
49
+    ->pageHeader();
50 50
 
51 51
 ?>
52 52
 <ol class="breadcrumb small">
Please login to merge, or discard this patch.
editnews.php 1 patch
Indentation   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -31,9 +31,9 @@  discard block
 block discarded – undo
31 31
 
32 32
 $controller = new SimpleController;
33 33
 $controller
34
-	->setPageTitle(I18N::translate('Add/edit a journal/news entry'))
35
-	->restrictAccess(Auth::isMember($WT_TREE))
36
-	->pageHeader();
34
+    ->setPageTitle(I18N::translate('Add/edit a journal/news entry'))
35
+    ->restrictAccess(Auth::isMember($WT_TREE))
36
+    ->pageHeader();
37 37
 
38 38
 $action    = Filter::get('action', 'compose|save', 'compose');
39 39
 $news_id   = Filter::getInteger('news_id');
@@ -45,43 +45,43 @@  discard block
 block discarded – undo
45 45
 
46 46
 switch ($action) {
47 47
 case 'compose':
48
-	if (Module::getModuleByName('ckeditor')) {
49
-		CkeditorModule::enableEditor($controller);
50
-	}
48
+    if (Module::getModuleByName('ckeditor')) {
49
+        CkeditorModule::enableEditor($controller);
50
+    }
51 51
 
52
-	echo '<h3>' . I18N::translate('Add/edit a journal/news entry') . '</h3>';
53
-	echo '<form style="overflow: hidden;" name="messageform" method="post" action="editnews.php?action=save&news_id=' . $news_id . '">';
54
-	if ($news_id) {
55
-		$news = Database::prepare("SELECT news_id AS id, user_id, gedcom_id, UNIX_TIMESTAMP(updated) AS date, subject, body FROM `##news` WHERE news_id=?")->execute(array($news_id))->fetchOneRow(PDO::FETCH_ASSOC);
56
-	} else {
57
-		$news              = array();
58
-		$news['user_id']   = $user_id;
59
-		$news['gedcom_id'] = $gedcom_id;
60
-		$news['date']      = WT_TIMESTAMP;
61
-		$news['subject']   = '';
62
-		$news['body']      = '';
63
-	}
64
-	echo '<input type="hidden" name="user_id" value="' . $news['user_id'] . '">';
65
-	echo '<input type="hidden" name="gedcom_id" value="' . $news['gedcom_id'] . '">';
66
-	echo '<input type="hidden" name="date" value="' . $news['date'] . '">';
67
-	echo '<table>';
68
-	echo '<tr><th style="text-align:start;">' . I18N::translate('Title') . '</th><tr>';
69
-	echo '<tr><td><input type="text" name="title" size="50" dir="auto" autofocus value="' . $news['subject'] . '"></td></tr>';
70
-	echo '<tr><th style="text-align:start;">' . I18N::translate('Content') . '</th></tr>';
71
-	echo '<tr><td>';
72
-	echo '<textarea name="text" class="html-edit" cols="80" rows="10" dir="auto">' . Filter::escapeHtml($news['body']) . '</textarea>';
73
-	echo '</td></tr>';
74
-	echo '<tr><td><input type="submit" value="' . I18N::translate('save') . '"></td></tr>';
75
-	echo '</table>';
76
-	echo '</form>';
77
-	break;
52
+    echo '<h3>' . I18N::translate('Add/edit a journal/news entry') . '</h3>';
53
+    echo '<form style="overflow: hidden;" name="messageform" method="post" action="editnews.php?action=save&news_id=' . $news_id . '">';
54
+    if ($news_id) {
55
+        $news = Database::prepare("SELECT news_id AS id, user_id, gedcom_id, UNIX_TIMESTAMP(updated) AS date, subject, body FROM `##news` WHERE news_id=?")->execute(array($news_id))->fetchOneRow(PDO::FETCH_ASSOC);
56
+    } else {
57
+        $news              = array();
58
+        $news['user_id']   = $user_id;
59
+        $news['gedcom_id'] = $gedcom_id;
60
+        $news['date']      = WT_TIMESTAMP;
61
+        $news['subject']   = '';
62
+        $news['body']      = '';
63
+    }
64
+    echo '<input type="hidden" name="user_id" value="' . $news['user_id'] . '">';
65
+    echo '<input type="hidden" name="gedcom_id" value="' . $news['gedcom_id'] . '">';
66
+    echo '<input type="hidden" name="date" value="' . $news['date'] . '">';
67
+    echo '<table>';
68
+    echo '<tr><th style="text-align:start;">' . I18N::translate('Title') . '</th><tr>';
69
+    echo '<tr><td><input type="text" name="title" size="50" dir="auto" autofocus value="' . $news['subject'] . '"></td></tr>';
70
+    echo '<tr><th style="text-align:start;">' . I18N::translate('Content') . '</th></tr>';
71
+    echo '<tr><td>';
72
+    echo '<textarea name="text" class="html-edit" cols="80" rows="10" dir="auto">' . Filter::escapeHtml($news['body']) . '</textarea>';
73
+    echo '</td></tr>';
74
+    echo '<tr><td><input type="submit" value="' . I18N::translate('save') . '"></td></tr>';
75
+    echo '</table>';
76
+    echo '</form>';
77
+    break;
78 78
 case 'save':
79
-	if ($news_id) {
80
-		Database::prepare("UPDATE `##news` SET subject=?, body=?, updated=FROM_UNIXTIME(?) WHERE news_id=?")->execute(array($title, $text, $date, $news_id));
81
-	} else {
82
-		Database::prepare("INSERT INTO `##news` (user_id, gedcom_id, subject, body, updated) VALUES (NULLIF(?, ''), NULLIF(?, '') ,? ,?, CURRENT_TIMESTAMP)")->execute(array($user_id, $gedcom_id, $title, $text));
83
-	}
79
+    if ($news_id) {
80
+        Database::prepare("UPDATE `##news` SET subject=?, body=?, updated=FROM_UNIXTIME(?) WHERE news_id=?")->execute(array($title, $text, $date, $news_id));
81
+    } else {
82
+        Database::prepare("INSERT INTO `##news` (user_id, gedcom_id, subject, body, updated) VALUES (NULLIF(?, ''), NULLIF(?, '') ,? ,?, CURRENT_TIMESTAMP)")->execute(array($user_id, $gedcom_id, $title, $text));
83
+    }
84 84
 
85
-	$controller->addInlineJavascript('window.opener.location.reload();window.close();');
86
-	break;
85
+    $controller->addInlineJavascript('window.opener.location.reload();window.close();');
86
+    break;
87 87
 }
Please login to merge, or discard this patch.