summaryrefslogtreecommitdiff
path: root/app/Module/BatchUpdate
diff options
context:
space:
mode:
Diffstat (limited to 'app/Module/BatchUpdate')
-rw-r--r--app/Module/BatchUpdate/BatchUpdateBasePlugin.php190
-rw-r--r--app/Module/BatchUpdate/BatchUpdateDuplicateLinksPlugin.php84
-rw-r--r--app/Module/BatchUpdate/BatchUpdateMarriedNamesPlugin.php152
-rw-r--r--app/Module/BatchUpdate/BatchUpdateMissingDeathPlugin.php64
-rw-r--r--app/Module/BatchUpdate/BatchUpdateNameFormatPlugin.php76
-rw-r--r--app/Module/BatchUpdate/BatchUpdateSearchReplacePlugin.php153
-rw-r--r--app/Module/BatchUpdate/BirthDeathMarriageReportModule.php52
7 files changed, 771 insertions, 0 deletions
diff --git a/app/Module/BatchUpdate/BatchUpdateBasePlugin.php b/app/Module/BatchUpdate/BatchUpdateBasePlugin.php
new file mode 100644
index 0000000000..3d9d034920
--- /dev/null
+++ b/app/Module/BatchUpdate/BatchUpdateBasePlugin.php
@@ -0,0 +1,190 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BatchUpdateBasePlugin
+ *
+ * Each plugin should extend this class, and implement these two functions:
+ *
+ * bool doesRecordNeedUpdate($xref, $gedrec)
+ * string updateRecord($xref, $gedrec)
+ */
+class BatchUpdateBasePlugin {
+ public $chan = false; // User option; update change record
+
+ /**
+ * Default is to operate on INDI records
+ *
+ * @return string[]
+ */
+ function getRecordTypesToUpdate() {
+ return array('INDI');
+ }
+
+ /**
+ * Default option is just the "don't update CHAN record"
+ */
+ function getOptions() {
+ $this->chan = Filter::getBool('chan');
+ }
+
+ /**
+ * Default option is just the "don't update CHAN record"
+ *
+ * @return string
+ */
+ function getOptionsForm() {
+ return
+ '<tr><th>' . I18N::translate('Do not update the “last change” record') . '</th>' .
+ '<td><select name="chan" onchange="this.form.submit();">' .
+ '<option value="0" ' . ($this->chan ? '' : 'selected') . '>' . I18N::translate('yes') . '</option>' .
+ '<option value="1" ' . ($this->chan ? 'selected' : '') . '>' . I18N::translate('no') . '</option>' .
+ '</select></td></tr>';
+ }
+
+ /**
+ * Default buttons are update and update_all
+ *
+ * @param string $xref
+ *
+ * @return string[]
+ */
+ function getActionButtons($xref) {
+ if (Auth::user()->getPreference('auto_accept')) {
+ return array(
+ BatchUpdateModule::createSubmitButton(I18N::translate('Update'), $xref, 'update'),
+ BatchUpdateModule::createSubmitButton(I18N::translate('Update all'), $xref, 'update_all')
+ );
+ } else {
+ return array(
+ BatchUpdateModule::createSubmitButton(I18N::translate('Update'), $xref, 'update')
+ );
+ }
+ }
+
+ /**
+ * Default previewer for plugins with no custom preview.
+ *
+ * @param GedcomRecord $record
+ *
+ * @return string
+ */
+ function getActionPreview(GedcomRecord $record) {
+ $old_lines = preg_split('/[\n]+/', $record->getGedcom());
+ $new_lines = preg_split('/[\n]+/', $this->updateRecord($record->getXref(), $record->getGedcom()));
+ // Find matching lines using longest-common-subsequence algorithm.
+ $lcs = self::LongestCommonSubsequence($old_lines, $new_lines, 0, count($old_lines) - 1, 0, count($new_lines) - 1);
+
+ $diff_lines = array();
+ $last_old = -1;
+ $last_new = -1;
+ while ($lcs) {
+ list($old, $new) = array_shift($lcs);
+ while ($last_old < $old - 1) {
+ $diff_lines[] = self::decorateDeletedText($old_lines[++$last_old]);
+ }
+ while ($last_new < $new - 1) {
+ $diff_lines[] = self::decorateInsertedText($new_lines[++$last_new]);
+ }
+ $diff_lines[] = $new_lines[$new];
+ $last_old = $old;
+ $last_new = $new;
+ }
+ while ($last_old < count($old_lines) - 1) {
+ $diff_lines[] = self::decorateDeletedText($old_lines[++$last_old]);
+ }
+ while ($last_new < count($new_lines) - 1) {
+ $diff_lines[] = self::decorateInsertedText($new_lines[++$last_new]);
+ }
+
+ return '<pre>' . self::createEditLinks(implode("\n", $diff_lines)) . '</pre>';
+ }
+
+ /**
+ * Longest Common Subsequence.
+ *
+ * @param string[] $X
+ * @param string[] $Y
+ * @param integer $x1
+ * @param integer $x2
+ * @param integer $y1
+ * @param integer $y2
+ *
+ * @return array
+ */
+ private static function LongestCommonSubsequence($X, $Y, $x1, $x2, $y1, $y2) {
+ if ($x2 - $x1 >= 0 && $y2 - $y1 >= 0) {
+ if ($X[$x1] == $Y[$y1]) {
+ // Match at start of sequence
+ $tmp = self::LongestCommonSubsequence($X, $Y, $x1 + 1, $x2, $y1 + 1, $y2);
+ array_unshift($tmp, array($x1, $y1));
+ return $tmp;
+ } elseif ($X[$x2] == $Y[$y2]) {
+ // Match at end of sequence
+ $tmp = self::LongestCommonSubsequence($X, $Y, $x1, $x2 - 1, $y1, $y2 - 1);
+ array_push($tmp, array($x2, $y2));
+ return $tmp;
+ } else {
+ // No match. Look for subsequences
+ $tmp1 = self::LongestCommonSubsequence($X, $Y, $x1, $x2, $y1, $y2 - 1);
+ $tmp2 = self::LongestCommonSubsequence($X, $Y, $x1, $x2 - 1, $y1, $y2);
+ return count($tmp1) > count($tmp2) ? $tmp1 : $tmp2;
+ }
+ } else {
+ // One array is empty - end recursion
+ return array();
+ }
+ }
+
+ /**
+ * Decorate inserted text
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ static function decorateInsertedText($text) {
+ return '<span class="added_text">' . $text . '</span>';
+ }
+
+ /**
+ * Decorate deleted text
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ static function decorateDeletedText($text) {
+ return '<span class="deleted_text">' . $text . '</span>';
+ }
+
+ /**
+ * Converted gedcom links into editable links
+ *
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ static function createEditLinks($gedrec) {
+ return preg_replace(
+ "/@([^#@\n]+)@/m",
+ '<a href="#" onclick="return edit_raw(\'\\1\');">@\\1@</a>',
+ $gedrec
+ );
+ }
+}
diff --git a/app/Module/BatchUpdate/BatchUpdateDuplicateLinksPlugin.php b/app/Module/BatchUpdate/BatchUpdateDuplicateLinksPlugin.php
new file mode 100644
index 0000000000..16a7542c8d
--- /dev/null
+++ b/app/Module/BatchUpdate/BatchUpdateDuplicateLinksPlugin.php
@@ -0,0 +1,84 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BatchUpdateDuplicateLinksPlugin Batch Update plugin: remove duplicate links in records
+ */
+class BatchUpdateDuplicateLinksPlugin extends BatchUpdateBasePlugin {
+ /**
+ * User-friendly name for this plugin.
+ *
+ * @return string
+ */
+ public function getName() {
+ return I18N::translate('Remove duplicate links');
+ }
+
+ /**
+ * Description / help-text for this plugin.
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return I18N::translate('A common error is to have multiple links to the same record, for example listing the same child more than once in a family record.');
+ }
+
+ /**
+ * This plugin will update all types of record.
+ *
+ * @return string[]
+ */
+ public function getRecordTypesToUpdate() {
+ return array('INDI', 'FAM', 'SOUR', 'REPO', 'NOTE', 'OBJE');
+ }
+
+ /**
+ * Does this record need updating?
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return boolean
+ */
+ public function doesRecordNeedUpdate($xref, $gedrec) {
+ return
+ preg_match('/(\n1.*@.+@.*(?:(?:\n[2-9].*)*))(?:\n1.*(?:\n[2-9].*)*)*\1/', $gedrec) ||
+ preg_match('/(\n2.*@.+@.*(?:(?:\n[3-9].*)*))(?:\n2.*(?:\n[3-9].*)*)*\1/', $gedrec) ||
+ preg_match('/(\n3.*@.+@.*(?:(?:\n[4-9].*)*))(?:\n3.*(?:\n[4-9].*)*)*\1/', $gedrec);
+ }
+
+ /**
+ * Apply any updates to this record
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ public function updateRecord($xref, $gedrec) {
+ return preg_replace(
+ array(
+ '/(\n1.*@.+@.*(?:(?:\n[2-9].*)*))((?:\n1.*(?:\n[2-9].*)*)*\1)/',
+ '/(\n2.*@.+@.*(?:(?:\n[3-9].*)*))((?:\n2.*(?:\n[3-9].*)*)*\1)/',
+ '/(\n3.*@.+@.*(?:(?:\n[4-9].*)*))((?:\n3.*(?:\n[4-9].*)*)*\1)/'
+ ),
+ '$2',
+ $gedrec
+ );
+ }
+}
diff --git a/app/Module/BatchUpdate/BatchUpdateMarriedNamesPlugin.php b/app/Module/BatchUpdate/BatchUpdateMarriedNamesPlugin.php
new file mode 100644
index 0000000000..371a11705f
--- /dev/null
+++ b/app/Module/BatchUpdate/BatchUpdateMarriedNamesPlugin.php
@@ -0,0 +1,152 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BatchUpdateMarriedNamesPlugin Batch Update plugin: add missing 2 _MARNM records
+ */
+class BatchUpdateMarriedNamesPlugin extends BatchUpdateBasePlugin {
+ /** @var string User option: add or replace husband’s surname */
+ private $surname;
+
+ /**
+ * User-friendly name for this plugin.
+ *
+ * @return string
+ */
+ public function getName() {
+ return I18N::translate('Add missing married names');
+ }
+
+ /**
+ * Description / help-text for this plugin.
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return I18N::translate('You can make it easier to search for married women by recording their married name.<br>However not all women take their husband’s surname, so beware of introducing incorrect information into your database.');
+ }
+
+ /**
+ * Does this record need updating?
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return boolean
+ */
+ public function doesRecordNeedUpdate($xref, $gedrec) {
+ return preg_match('/^1 SEX F/m', $gedrec) && preg_match('/^1 NAME /m', $gedrec) && self::surnamesToAdd($xref, $gedrec);
+ }
+
+ /**
+ * Apply any updates to this record
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ public function updateRecord($xref, $gedrec) {
+ global $WT_TREE;
+
+ $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
+
+ preg_match('/^1 NAME (.*)/m', $gedrec, $match);
+ $wife_name = $match[1];
+ $married_names = array();
+ foreach (self::surnamesToAdd($xref, $gedrec) as $surname) {
+ switch ($this->surname) {
+ case 'add':
+ $married_names[] = "\n2 _MARNM " . str_replace('/', '', $wife_name) . ' /' . $surname . '/';
+ break;
+ case 'replace':
+ if ($SURNAME_TRADITION === 'polish') {
+ $surname = preg_replace(array('/ski$/', '/cki$/', '/dzki$/'), array('ska', 'cka', 'dzka'), $surname);
+ }
+ $married_names[] = "\n2 _MARNM " . preg_replace('!/.*/!', '/' . $surname . '/', $wife_name);
+ break;
+ }
+ }
+ return preg_replace('/(^1 NAME .*([\r\n]+[2-9].*)*)/m', '\\1' . implode('', $married_names), $gedrec, 1);
+ }
+
+ /**
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string[]
+ */
+ private function surnamesToAdd($xref, $gedrec) {
+ $wife_surnames = self::surnames($xref, $gedrec);
+ $husb_surnames = array();
+ $missing_surnames = array();
+ preg_match_all('/^1 FAMS @(.+)@/m', $gedrec, $fmatch);
+ foreach ($fmatch[1] as $famid) {
+ $famrec = BatchUpdateModule::getLatestRecord($famid, 'FAM');
+ if (preg_match('/^1 MARR/m', $famrec) && preg_match('/^1 HUSB @(.+)@/m', $famrec, $hmatch)) {
+ $husbrec = BatchUpdateModule::getLatestRecord($hmatch[1], 'INDI');
+ $husb_surnames = array_unique(array_merge($husb_surnames, self::surnames($hmatch[1], $husbrec)));
+ }
+ }
+ foreach ($husb_surnames as $husb_surname) {
+ if (!in_array($husb_surname, $wife_surnames)) {
+ $missing_surnames[] = $husb_surname;
+ }
+ }
+
+ return $missing_surnames;
+ }
+
+ /**
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string[]
+ */
+ private function surnames($xref, $gedrec) {
+ if (preg_match_all('/^(?:1 NAME|2 _MARNM) .*\/(.+)\//m', $gedrec, $match)) {
+ return $match[1];
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Process the user-supplied options.
+ */
+ public function getOptions() {
+ parent::getOptions();
+ $this->surname = Filter::get('surname', 'add|replace', 'replace');
+ }
+
+ /**
+ * Generate a form to ask the user for options.
+ *
+ * @return string
+ */
+ public function getOptionsForm() {
+ return
+ parent::getOptionsForm() .
+ '<tr valign="top"><th>' . I18N::translate('Surname option') . '</th>' .
+ '<td class="optionbox"><select name="surname" onchange="reset_reload();"><option value="replace" ' .
+ ($this->surname == 'replace' ? 'selected' : '') .
+ '">' . I18N::translate('Wife’s surname replaced by husband’s surname') . '</option><option value="add" ' .
+ ($this->surname == 'add' ? 'selected' : '') .
+ '">' . I18N::translate('Wife’s maiden surname becomes new given name') . '</option></select></td></tr>';
+ }
+}
diff --git a/app/Module/BatchUpdate/BatchUpdateMissingDeathPlugin.php b/app/Module/BatchUpdate/BatchUpdateMissingDeathPlugin.php
new file mode 100644
index 0000000000..50ff0a56f4
--- /dev/null
+++ b/app/Module/BatchUpdate/BatchUpdateMissingDeathPlugin.php
@@ -0,0 +1,64 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BatchUpdateMissingDeathPlugin Batch Update plugin: add missing 1 BIRT/DEAT Y
+ */
+class BatchUpdateMissingDeathPlugin extends BatchUpdateBasePlugin {
+ /**
+ * User-friendly name for this plugin.
+ *
+ * @return string
+ */
+ public function getName() {
+ return I18N::translate('Add missing death records');
+ }
+
+ /**
+ * Description / help-text for this plugin.
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return I18N::translate('You can speed up the privacy calculations by adding a death record to individuals whose death can be inferred from other dates, but who do not have a record of death, burial, cremation, etc.');
+ }
+
+ /**
+ * Does this record need updating?
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return boolean
+ */
+ public function doesRecordNeedUpdate($xref, $gedrec) {
+ return !preg_match('/\n1 (' . WT_EVENTS_DEAT . ')/', $gedrec) && Individual::getInstance($xref)->isDead();
+ }
+
+ /**
+ * Apply any updates to this record
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ public function updateRecord($xref, $gedrec) {
+ return $gedrec . "\n1 DEAT Y";
+ }
+}
diff --git a/app/Module/BatchUpdate/BatchUpdateNameFormatPlugin.php b/app/Module/BatchUpdate/BatchUpdateNameFormatPlugin.php
new file mode 100644
index 0000000000..c3728070d0
--- /dev/null
+++ b/app/Module/BatchUpdate/BatchUpdateNameFormatPlugin.php
@@ -0,0 +1,76 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BatchUpdateNameFormatPlugin Batch Update plugin: fix spacing in names, particularly that before/after the surname slashes
+ */
+class BatchUpdateNameFormatPlugin extends BatchUpdateBasePlugin {
+ /**
+ * User-friendly name for this plugin.
+ *
+ * @return string
+ */
+ public function getName() {
+ return I18N::translate('Fix name slashes and spaces');
+ }
+
+ /**
+ * Description / help-text for this plugin.
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return I18N::translate('Correct NAME records of the form “John/DOE/” or “John /DOE”, as produced by older genealogy programs.');
+ }
+
+ /**
+ * Does this record need updating?
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return boolean
+ */
+ public function doesRecordNeedUpdate($xref, $gedrec) {
+ return
+ preg_match('/^(?:1 NAME|2 (?:FONE|ROMN|_MARNM|_AKA|_HEB)) [^\/\n]*\/[^\/\n]*$/m', $gedrec) ||
+ preg_match('/^(?:1 NAME|2 (?:FONE|ROMN|_MARNM|_AKA|_HEB)) [^\/\n]*[^\/ ]\//m', $gedrec);
+ }
+
+ /**
+ * Apply any updates to this record
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ public function updateRecord($xref, $gedrec) {
+ return preg_replace(
+ array(
+ '/^((?:1 NAME|2 (?:FONE|ROMN|_MARNM|_AKA|_HEB)) [^\/\n]*\/[^\/\n]*)$/m',
+ '/^((?:1 NAME|2 (?:FONE|ROMN|_MARNM|_AKA|_HEB)) [^\/\n]*[^\/ ])(\/)/m',
+ ),
+ array(
+ '$1/',
+ '$1 $2',
+ ),
+ $gedrec
+ );
+ }
+}
diff --git a/app/Module/BatchUpdate/BatchUpdateSearchReplacePlugin.php b/app/Module/BatchUpdate/BatchUpdateSearchReplacePlugin.php
new file mode 100644
index 0000000000..2489d1b132
--- /dev/null
+++ b/app/Module/BatchUpdate/BatchUpdateSearchReplacePlugin.php
@@ -0,0 +1,153 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BatchUpdateSearchReplacePlugin Batch Update plugin: search/replace
+ */
+class BatchUpdateSearchReplacePlugin extends BatchUpdateBasePlugin {
+ var $search = null; // Search string
+ var $replace = null; // Replace string
+ var $method = null; // simple/wildcards/regex
+ var $regex = null; // Search string, converted to a regex
+ var $case = null; // "i" for case insensitive, "" for case sensitive
+ var $error = null; // Message for bad user parameters
+
+ /**
+ * User-friendly name for this plugin.
+ *
+ * @return string
+ */
+ public function getName() {
+ return I18N::translate('Search and replace');
+ }
+
+ /**
+ * Description / help-text for this plugin.
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return /* I18N: Description of the “Search and replace” option of the batch update module */ I18N::translate('Search and replace text, using simple searches or advanced pattern matching.');
+ }
+
+ /**
+ * This plugin will update all types of record.
+ *
+ * @return string[]
+ */
+ public function getRecordTypesToUpdate() {
+ return array('INDI', 'FAM', 'SOUR', 'REPO', 'NOTE', 'OBJE');
+ }
+
+ /**
+ * Does this record need updating?
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return boolean
+ */
+ public function doesRecordNeedUpdate($xref, $gedrec) {
+ return !$this->error && preg_match('/(?:' . $this->regex . ')/mu' . $this->case, $gedrec);
+ }
+
+ /**
+ * Apply any updates to this record
+ *
+ * @param string $xref
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ public function updateRecord($xref, $gedrec) {
+ // Allow "\n" to indicate a line-feed in replacement text.
+ // Back-references such as $1, $2 are handled automatically.
+ return preg_replace('/' . $this->regex . '/mu' . $this->case, str_replace('\n', "\n", $this->replace), $gedrec);
+ }
+
+ /**
+ * Process the user-supplied options.
+ */
+ public function getOptions() {
+ parent::getOptions();
+ $this->search = Filter::get('search');
+ $this->replace = Filter::get('replace');
+ $this->method = Filter::get('method', 'exact|words|wildcards|regex', 'exact');
+ $this->case = Filter::get('case', 'i');
+
+ $this->error = '';
+ switch ($this->method) {
+ case 'exact':
+ $this->regex = preg_quote($this->search, '/');
+ break;
+ case 'words':
+ $this->regex = '\b' . preg_quote($this->search, '/') . '\b';
+ break;
+ case 'wildcards':
+ $this->regex = '\b' . str_replace(array('\*', '\?'), array('.*', '.'), preg_quote($this->search, '/')) . '\b';
+ break;
+ case 'regex':
+ $this->regex = $this->search;
+ // Check for invalid regular expressions.
+ // A valid regex on a null string returns zero.
+ // An invalid regex on a null string returns false and throws a warning.
+ try {
+ preg_match('/' . $this->search . '/', null);
+ } catch (\ErrorException $ex) {
+ $this->error = '<div class="alert alert-danger">' . I18N::translate('The regex appears to contain an error. It can’t be used.') . '</div>';
+ }
+ break;
+ }
+ }
+
+ /**
+ * Generate a form to ask the user for options.
+ *
+ * @return string
+ */
+ public function getOptionsForm() {
+ $descriptions = array(
+ 'exact' => I18N::translate('Match the exact text, even if it occurs in the middle of a word.'),
+ 'words' => I18N::translate('Match the exact text, unless it occurs in the middle of a word.'),
+ 'wildcards' => I18N::translate('Use a “?” to match a single character, use “*” to match zero or more characters.'),
+ 'regex' => /* I18N: http://en.wikipedia.org/wiki/Regular_expression */ I18N::translate('Regular expressions are an advanced pattern matching technique.') . '<br>' . /* I18N: %s is a URL */ I18N::translate('See %s for more information.', '<a href="http://php.net/manual/regexp.reference.php" target="_blank">php.net/manual/regexp.reference.php</a>'),
+ );
+
+ return
+ '<tr><th>' . I18N::translate('Search text/pattern') . '</th>' .
+ '<td>' .
+ '<input name="search" size="40" value="' . Filter::escapeHtml($this->search) .
+ '" onchange="this.form.submit();"></td></tr>' .
+ '<tr><th>' . I18N::translate('Replacement text') . '</th>' .
+ '<td>' .
+ '<input name="replace" size="40" value="' . Filter::escapeHtml($this->replace) .
+ '" onchange="this.form.submit();"></td></tr>' .
+ '<tr><th>' . I18N::translate('Search method') . '</th>' .
+ '<td><select name="method" onchange="this.form.submit();">' .
+ '<option value="exact" ' . ($this->method == 'exact' ? 'selected' : '') . '>' . I18N::translate('Exact text') . '</option>' .
+ '<option value="words" ' . ($this->method == 'words' ? 'selected' : '') . '>' . I18N::translate('Whole words only') . '</option>' .
+ '<option value="wildcards" ' . ($this->method == 'wildcards' ? 'selected' : '') . '>' . I18N::translate('Wildcards') . '</option>' .
+ '<option value="regex" ' . ($this->method == 'regex' ? 'selected' : '') . '>' . I18N::translate('Regular expression') . '</option>' .
+ '</select><br><em>' . $descriptions[$this->method] . '</em>' . $this->error . '</td></tr>' .
+ '<tr><th>' . I18N::translate('Case insensitive') . '</th>' .
+ '<td>' .
+ '<input type="checkbox" name="case" value="i" ' . ($this->case == 'i' ? 'checked' : '') . '" onchange="this.form.submit();">' .
+ '<br><em>' . I18N::translate('Tick this box to match both upper and lower case letters.') . '</em></td></tr>' .
+ parent::getOptionsForm();
+ }
+}
diff --git a/app/Module/BatchUpdate/BirthDeathMarriageReportModule.php b/app/Module/BatchUpdate/BirthDeathMarriageReportModule.php
new file mode 100644
index 0000000000..4364a60a01
--- /dev/null
+++ b/app/Module/BatchUpdate/BirthDeathMarriageReportModule.php
@@ -0,0 +1,52 @@
+<?php
+namespace Fisharebest\Webtrees;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Class BirthDeathMarriageReportModule
+ */
+class BirthDeathMarriageReportModule extends Module implements ModuleReportInterface {
+ /** {@inheritdoc} */
+ public function getTitle() {
+ // This text also appears in the .XML file - update both together
+ return /* I18N: Name of a module/report. “Vital records” are life events - birth/marriage/death */ I18N::translate('Vital records');
+ }
+
+ /** {@inheritdoc} */
+ public function getDescription() {
+ // This text also appears in the .XML file - update both together
+ return /* I18N: Description of the “Vital records” module. “Vital records” are life events - birth/marriage/death */ I18N::translate('A report of vital records for a given date or place.');
+ }
+
+ /** {@inheritdoc} */
+ public function defaultAccessLevel() {
+ return WT_PRIV_PUBLIC;
+ }
+
+ /** {@inheritdoc} */
+ public function getReportMenus() {
+ $menus = array();
+ $menu = new Menu(
+ $this->getTitle(),
+ 'reportengine.php?ged=' . WT_GEDURL . '&amp;action=setup&amp;report=' . WT_MODULES_DIR . $this->getName() . '/report.xml',
+ 'menu-report-' . $this->getName()
+ );
+ $menus[] = $menu;
+
+ return $menus;
+ }
+}