summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Functions/FunctionsEdit.php9
-rw-r--r--app/Http/RequestHandlers/CreateRepositoryAction.php79
-rw-r--r--app/Http/RequestHandlers/CreateRepositoryModal.php (renamed from app/Http/Controllers/EditRepositoryController.php)18
-rw-r--r--app/Http/RequestHandlers/CreateSourceAction.php121
-rw-r--r--app/Http/RequestHandlers/CreateSourceModal.php (renamed from app/Http/Controllers/EditSourceController.php)20
-rw-r--r--app/Http/RequestHandlers/CreateSubmitterAction.php (renamed from app/Http/Controllers/EditSubmitterController.php)23
-rw-r--r--app/Http/RequestHandlers/CreateSubmitterModal.php48
-rw-r--r--app/Http/RequestHandlers/IndividualPage.php395
-rw-r--r--app/Individual.php3
-rw-r--r--app/Module/ModuleTabInterface.php11
-rw-r--r--app/Module/ModuleTabTrait.php57
-rw-r--r--resources/views/admin/trees.phtml9
-rw-r--r--resources/views/individual-page.phtml6
-rw-r--r--resources/views/modals/create-repository.phtml9
-rw-r--r--resources/views/modals/create-source.phtml9
-rw-r--r--resources/views/modals/create-submitter.phtml9
-rw-r--r--routes/web.php22
17 files changed, 790 insertions, 58 deletions
diff --git a/app/Functions/FunctionsEdit.php b/app/Functions/FunctionsEdit.php
index 1d9e5c17c9..68e65076dd 100644
--- a/app/Functions/FunctionsEdit.php
+++ b/app/Functions/FunctionsEdit.php
@@ -37,6 +37,9 @@ use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\Html;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterModal;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Media;
@@ -524,7 +527,7 @@ class FunctionsEdit
} elseif ($fact === 'REPO') {
$html .=
'<div class="input-group">' .
- '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route('create-repository', ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a repository') . '">' . view('icons/add') . '</button></div>' .
+ '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route(CreateRepositoryModal::class, ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a repository') . '">' . view('icons/add') . '</button></div>' .
view('components/select-repository', ['id' => $id, 'name' => $name, 'repository' => Repository::getInstance($value, $tree), 'tree' => $tree]) .
'</div>';
} elseif ($fact === 'RESN') {
@@ -541,7 +544,7 @@ class FunctionsEdit
} elseif ($fact === 'SOUR') {
$html .=
'<div class="input-group">' .
- '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route('create-source', ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a source') . '">' . view('icons/add') . '</button></div>' .
+ '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route(CreateSourceModal::class, ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a source') . '">' . view('icons/add') . '</button></div>' .
view('components/select-source', ['id' => $id, 'name' => $name, 'source' => Source::getInstance($value, $tree), 'tree' => $tree]) .
'</div>';
} elseif ($fact === 'STAT') {
@@ -549,7 +552,7 @@ class FunctionsEdit
} elseif ($fact === 'SUBM') {
$html .=
'<div class="input-group">' .
- '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route('create-submitter', ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a submitter') . '">' . view('icons/add') . '</button></div>' .
+ '<div class="input-group-prepend"><button class="btn btn-secondary" type="button" data-toggle="modal" data-href="' . e(route(CreateSubmitterModal::class, ['tree' => $tree->name()])) . '" data-target="#wt-ajax-modal" data-select-id="' . $id . '" title="' . I18N::translate('Create a submitter') . '">' . view('icons/add') . '</button></div>' .
view('components/select-submitter', ['id' => $id, 'name' => $name, 'submitter' => GedcomRecord::getInstance($value, $tree), 'tree' => $tree]) .
'</div>';
} elseif ($fact === 'TEMP') {
diff --git a/app/Http/RequestHandlers/CreateRepositoryAction.php b/app/Http/RequestHandlers/CreateRepositoryAction.php
new file mode 100644
index 0000000000..41526e6540
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateRepositoryAction.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2019 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/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+
+/**
+ * Process a form to create a new repository.
+ */
+class CreateRepositoryAction implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $params = $request->getParsedBody();
+ $name = $params['repository-name'];
+ $privacy_restriction = $params['privacy-restriction'];
+ $edit_restriction = $params['edit-restriction'];
+
+ // Fix whitespace
+ $name = trim(preg_replace('/\s+/', ' ', $name));
+
+ $gedcom = "0 @@ REPO\n1 NAME " . $name;
+
+ if (in_array($privacy_restriction, ['none', 'privacy', 'confidential'], true)) {
+ $gedcom .= "\n1 RESN " . $privacy_restriction;
+ }
+
+ if ($edit_restriction === 'locked') {
+ $gedcom .= "\n1 RESN " . $edit_restriction;
+ }
+
+ $record = $tree->createRecord($gedcom);
+
+ // id and text are for select2 / autocomplete
+ // html is for interactive modals
+ return response([
+ 'id' => $record->xref(),
+ 'text' => view('selects/repository', [
+ 'repository' => $record,
+ ]),
+ 'html' => view('modals/record-created', [
+ 'title' => I18N::translate('The repository has been created'),
+ 'name' => $record->fullName(),
+ 'url' => $record->url(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/EditRepositoryController.php b/app/Http/RequestHandlers/CreateRepositoryModal.php
index 16a392d729..def6c0b3f3 100644
--- a/app/Http/Controllers/EditRepositoryController.php
+++ b/app/Http/RequestHandlers/CreateRepositoryModal.php
@@ -17,30 +17,34 @@
declare(strict_types=1);
-namespace Fisharebest\Webtrees\Http\Controllers;
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Tree;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
use function assert;
/**
- * Controller for edit forms and responses.
+ * Show a form to create a new repository.
*/
-class EditRepositoryController extends AbstractEditController
+class CreateRepositoryModal implements RequestHandlerInterface
{
/**
- * Show a form to create a new repository.
- *
* @param ServerRequestInterface $request
*
* @return ResponseInterface
*/
- public function createRepository(ServerRequestInterface $request): ResponseInterface
+ public function handle(ServerRequestInterface $request): ResponseInterface
{
- return response(view('modals/create-repository'));
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ return response(view('modals/create-repository', [
+ 'tree' => $tree,
+ ]));
}
/**
diff --git a/app/Http/RequestHandlers/CreateSourceAction.php b/app/Http/RequestHandlers/CreateSourceAction.php
new file mode 100644
index 0000000000..e76fbfceaf
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateSourceAction.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2019 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/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+
+/**
+ * Process a form to create a new source.
+ */
+class CreateSourceAction implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $params = $request->getParsedBody();
+ $title = $params['source-title'];
+ $abbreviation = $params['source-abbreviation'];
+ $author = $params['source-author'];
+ $publication = $params['source-publication'];
+ $repository = $params['source-repository'];
+ $call_number = $params['source-call-number'];
+ $text = $params['source-text'];
+ $privacy_restriction = $params['privacy-restriction'];
+ $edit_restriction = $params['edit-restriction'];
+
+ // Fix whitespace
+ $title = trim(preg_replace('/\s+/', ' ', $title));
+ $abbreviation = trim(preg_replace('/\s+/', ' ', $abbreviation));
+ $author = trim(preg_replace('/\s+/', ' ', $author));
+ $publication = trim(preg_replace('/\s+/', ' ', $publication));
+ $repository = trim(preg_replace('/\s+/', ' ', $repository));
+ $call_number = trim(preg_replace('/\s+/', ' ', $call_number));
+
+ // Convert line endings to GEDDCOM continuations
+ $text = str_replace([
+ "\r\n",
+ "\r",
+ "\n",
+ ], "\n1 CONT ", $text);
+
+ $gedcom = "0 @@ SOUR\n\n1 TITL " . $title;
+
+ if ($abbreviation !== '') {
+ $gedcom .= "\n1 ABBR " . $abbreviation;
+ }
+
+ if ($author !== '') {
+ $gedcom .= "\n1 AUTH " . $author;
+ }
+
+ if ($publication !== '') {
+ $gedcom .= "\n1 PUBL " . $publication;
+ }
+
+ if ($text !== '') {
+ $gedcom .= "\n1 TEXT " . $text;
+ }
+
+ if ($repository !== '') {
+ $gedcom .= "\n1 REPO @" . $repository . '@';
+
+ if ($call_number !== '') {
+ $gedcom .= "\n2 CALN " . $call_number;
+ }
+ }
+
+ if (in_array($privacy_restriction, ['none', 'privacy', 'confidential'], true)) {
+ $gedcom .= "\n1 RESN " . $privacy_restriction;
+ }
+
+ if ($edit_restriction === 'locked') {
+ $gedcom .= "\n1 RESN " . $edit_restriction;
+ }
+
+ $record = $tree->createRecord($gedcom);
+
+ // id and text are for select2 / autocomplete
+ // html is for interactive modals
+ return response([
+ 'id' => $record->xref(),
+ 'text' => view('selects/source', [
+ 'source' => $record,
+ ]),
+ 'html' => view('modals/record-created', [
+ 'title' => I18N::translate('The source has been created'),
+ 'name' => $record->fullName(),
+ 'url' => $record->url(),
+ ]),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/EditSourceController.php b/app/Http/RequestHandlers/CreateSourceModal.php
index e300e37fa3..2e29b15431 100644
--- a/app/Http/Controllers/EditSourceController.php
+++ b/app/Http/RequestHandlers/CreateSourceModal.php
@@ -17,35 +17,37 @@
declare(strict_types=1);
-namespace Fisharebest\Webtrees\Http\Controllers;
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Tree;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
use function assert;
/**
- * Controller for edit forms and responses.
+ * Process a form to create a new source.
*/
-class EditSourceController extends AbstractEditController
+class CreateSourceModal implements RequestHandlerInterface
{
/**
- * Show a form to create a new source.
- *
* @param ServerRequestInterface $request
*
* @return ResponseInterface
*/
- public function createSource(ServerRequestInterface $request): ResponseInterface
+ public function handle(ServerRequestInterface $request): ResponseInterface
{
- return response(view('modals/create-source'));
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ return response(view('modals/create-source', [
+ 'tree' => $tree,
+ ]));
}
/**
- * Process a form to create a new source.
- *
* @param ServerRequestInterface $request
*
* @return ResponseInterface
diff --git a/app/Http/Controllers/EditSubmitterController.php b/app/Http/RequestHandlers/CreateSubmitterAction.php
index 4241d29f6c..ee76278d13 100644
--- a/app/Http/Controllers/EditSubmitterController.php
+++ b/app/Http/RequestHandlers/CreateSubmitterAction.php
@@ -17,40 +17,27 @@
declare(strict_types=1);
-namespace Fisharebest\Webtrees\Http\Controllers;
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Tree;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
use function assert;
/**
- * Controller for edit forms and responses.
+ * Process a form to create a new submitter.
*/
-class EditSubmitterController extends AbstractEditController
+class CreateSubmitterAction implements RequestHandlerInterface
{
/**
- * Show a form to create a new submitter.
- *
- * @param ServerRequestInterface $request
- *
- * @return ResponseInterface
- */
- public function createSubmitter(ServerRequestInterface $request): ResponseInterface
- {
- return response(view('modals/create-submitter'));
- }
-
- /**
- * Process a form to create a new submitter.
- *
* @param ServerRequestInterface $request
*
* @return ResponseInterface
*/
- public function createSubmitterAction(ServerRequestInterface $request): ResponseInterface
+ public function handle(ServerRequestInterface $request): ResponseInterface
{
$tree = $request->getAttribute('tree');
assert($tree instanceof Tree);
diff --git a/app/Http/RequestHandlers/CreateSubmitterModal.php b/app/Http/RequestHandlers/CreateSubmitterModal.php
new file mode 100644
index 0000000000..a5bc552cab
--- /dev/null
+++ b/app/Http/RequestHandlers/CreateSubmitterModal.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2019 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/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Tree;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function assert;
+
+/**
+ * Show a form to create a new submitter.
+ */
+class CreateSubmitterModal implements RequestHandlerInterface
+{
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ return response(view('modals/create-submitter', [
+ 'tree' => $tree,
+ ]));
+ }
+}
diff --git a/app/Http/RequestHandlers/IndividualPage.php b/app/Http/RequestHandlers/IndividualPage.php
new file mode 100644
index 0000000000..f46da95f4b
--- /dev/null
+++ b/app/Http/RequestHandlers/IndividualPage.php
@@ -0,0 +1,395 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2019 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/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
+use Fisharebest\Webtrees\GedcomTag;
+use Fisharebest\Webtrees\Http\ViewResponseTrait;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Media;
+use Fisharebest\Webtrees\MediaFile;
+use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
+use Fisharebest\Webtrees\Module\ModuleTabInterface;
+use Fisharebest\Webtrees\Services\ClipboardService;
+use Fisharebest\Webtrees\Services\ModuleService;
+use Fisharebest\Webtrees\Services\UserService;
+use Fisharebest\Webtrees\Tree;
+use Illuminate\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use stdClass;
+
+use function assert;
+use function e;
+use function explode;
+use function is_string;
+use function ob_get_clean;
+use function ob_start;
+use function preg_match_all;
+use function preg_replace;
+use function redirect;
+use function route;
+use function str_replace;
+use function strpos;
+
+/**
+ * Show an individual's page.
+ */
+class IndividualPage implements RequestHandlerInterface
+{
+ use ViewResponseTrait;
+
+ /** @var ClipboardService */
+ private $clipboard_service;
+
+ /** @var ModuleService */
+ private $module_service;
+
+ /** @var UserService */
+ private $user_service;
+
+ /**
+ * IndividualController constructor.
+ *
+ * @param ClipboardService $clipboard_service
+ * @param ModuleService $module_service
+ * @param UserService $user_service
+ */
+ public function __construct(ClipboardService $clipboard_service, ModuleService $module_service, UserService $user_service)
+ {
+ $this->clipboard_service = $clipboard_service;
+ $this->module_service = $module_service;
+ $this->user_service = $user_service;
+ }
+
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $xref = $request->getAttribute('xref');
+ assert(is_string($xref));
+
+ $individual = Individual::getInstance($xref, $tree);
+ $individual = Auth::checkIndividualAccess($individual);
+
+ // Redirect to correct xref/slug
+ if ($individual->xref() !== $xref || $request->getAttribute('slug') !== $individual->slug()) {
+ return redirect($individual->url());
+ }
+
+ // What is (was) the age of the individual
+ $bdate = $individual->getBirthDate();
+ $ddate = $individual->getDeathDate();
+ if ($bdate->isOK() && !$individual->isDead()) {
+ // If living display age
+ $age = ' (' . I18N::translate('age') . ' ' . FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($bdate, new Date(strtoupper(date('d M Y'))))) . ')';
+ } elseif ($bdate->isOK() && $ddate->isOK()) {
+ // If dead, show age at death
+ $age = ' (' . I18N::translate('age') . ' ' . FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($bdate, $ddate)) . ')';
+ } else {
+ $age = '';
+ }
+
+ // What images are linked to this individual
+ $individual_media = new Collection();
+ foreach ($individual->facts(['OBJE']) as $fact) {
+ $media_object = $fact->target();
+ if ($media_object instanceof Media) {
+ $media_file = $media_object->firstImageFile();
+ if ($media_file instanceof MediaFile) {
+ $individual_media->add($media_file);
+ }
+ }
+ }
+
+ $name_records = new Collection();
+ foreach ($individual->facts(['NAME']) as $n => $name_fact) {
+ $name_records->add($this->formatNameRecord($tree, $n, $name_fact));
+ }
+
+ $sex_records = new Collection();
+ foreach ($individual->facts(['SEX']) as $n => $sex_fact) {
+ $sex_records->add($this->formatSexRecord($sex_fact));
+ }
+
+ // If this individual is linked to a user account, show the link
+ $user_link = '';
+ if (Auth::isAdmin()) {
+ $users = $this->user_service->findByIndividual($individual);
+ foreach ($users as $user) {
+ $user_link = ' — <a href="' . e(route('admin-users', ['filter' => $user->email()])) . '">' . e($user->userName()) . '</a>';
+ }
+ }
+
+ return $this->viewResponse('individual-page', [
+ 'age' => $age,
+ 'clipboard_facts' => $this->clipboard_service->pastableFacts($individual, new Collection()),
+ 'count_media' => $this->countFacts($individual, ['OBJE']),
+ 'count_names' => $this->countFacts($individual, ['NAME']),
+ 'count_sex' => $this->countFacts($individual, ['SEX']),
+ 'individual' => $individual,
+ 'individual_media' => $individual_media,
+ 'meta_robots' => 'index,follow',
+ 'name_records' => $name_records,
+ 'sex_records' => $sex_records,
+ 'sidebars' => $this->getSidebars($individual),
+ 'tabs' => $this->getTabs($individual),
+ 'significant' => $this->significant($individual),
+ 'title' => $individual->fullName() . ' ' . $individual->getLifeSpan(),
+ 'tree' => $tree,
+ 'user_link' => $user_link,
+ ]);
+ }
+
+ /**
+ * Count the (non-pending-delete) name records for an individual.
+ *
+ * @param Individual $individual
+ * @param string[] $tags
+ *
+ * @return int
+ */
+ private function countFacts(Individual $individual, array $tags): int
+ {
+ $count = 0;
+
+ foreach ($individual->facts($tags) as $fact) {
+ if (!$fact->isPendingDeletion()) {
+ $count++;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Format a name record
+ *
+ * @param Tree $tree
+ * @param int $n
+ * @param Fact $fact
+ *
+ * @return string
+ */
+ private function formatNameRecord(Tree $tree, $n, Fact $fact): string
+ {
+ $individual = $fact->record();
+
+ // Create a dummy record, so we can extract the formatted NAME value from it.
+ $dummy = new Individual(
+ 'xref',
+ "0 @xref@ INDI\n1 DEAT Y\n" . $fact->gedcom(),
+ null,
+ $individual->tree()
+ );
+ $dummy->setPrimaryName(0); // Make sure we use the name from "1 NAME"
+
+ $container_class = 'card';
+ $content_class = 'collapse';
+ $aria = 'false';
+
+ if ($n === 0) {
+ $content_class = 'collapse show';
+ $aria = 'true';
+ }
+ if ($fact->isPendingDeletion()) {
+ $container_class .= ' wt-old';
+ } elseif ($fact->isPendingAddition()) {
+ $container_class .= ' wt-new';
+ }
+
+ ob_start();
+ echo '<dl><dt class="label">', I18N::translate('Name'), '</dt>';
+ echo '<dd class="field">', $dummy->fullName(), '</dd>';
+ $ct = preg_match_all('/\n2 (\w+) (.*)/', $fact->gedcom(), $nmatch, PREG_SET_ORDER);
+ for ($i = 0; $i < $ct; $i++) {
+ $tag = $nmatch[$i][1];
+ if ($tag !== 'SOUR' && $tag !== 'NOTE' && $tag !== 'SPFX') {
+ echo '<dt class="label">', GedcomTag::getLabel($tag, $individual), '</dt>';
+ echo '<dd class="field">'; // Before using dir="auto" on this field, note that Gecko treats this as an inline element but WebKit treats it as a block element
+ if (isset($nmatch[$i][2])) {
+ $name = e($nmatch[$i][2]);
+ $name = str_replace('/', '', $name);
+ $name = preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $name);
+ switch ($tag) {
+ case 'TYPE':
+ echo GedcomCodeName::getValue($name, $individual);
+ break;
+ case 'SURN':
+ // The SURN field is not necessarily the surname.
+ // Where it is not a substring of the real surname, show it after the real surname.
+ $surname = e($dummy->getAllNames()[0]['surname']);
+ $surns = preg_replace('/, */', ' ', $nmatch[$i][2]);
+ if (strpos($dummy->getAllNames()[0]['surname'], $surns) !== false) {
+ echo '<span dir="auto">' . $surname . '</span>';
+ } else {
+ echo I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $surname . '</span>', '<span dir="auto">' . $name . '</span>');
+ }
+ break;
+ default:
+ echo '<span dir="auto">' . $name . '</span>';
+ break;
+ }
+ }
+ echo '</dd>';
+ }
+ }
+ echo '</dl>';
+ if (strpos($fact->gedcom(), "\n2 SOUR") !== false) {
+ echo '<div id="indi_sour" class="clearfix">', FunctionsPrintFacts::printFactSources($tree, $fact->gedcom(), 2), '</div>';
+ }
+ if (strpos($fact->gedcom(), "\n2 NOTE") !== false) {
+ echo '<div id="indi_note" class="clearfix">', FunctionsPrint::printFactNotes($tree, $fact->gedcom(), 2), '</div>';
+ }
+ $content = ob_get_clean();
+
+ if ($fact->canEdit()) {
+ $edit_links =
+ '<a class="btn btn-link" href="#" data-confirm="' . I18N::translate('Are you sure you want to delete this fact?') . '" data-post-url="' . e(route(DeleteFact::class, ['tree' => $individual->tree()->name(), 'xref' => $individual->xref(), 'fact_id' => $fact->id()])) . '" title="' . I18N::translate('Delete this name') . '">' . view('icons/delete') . '<span class="sr-only">' . I18N::translate('Delete this name') . '</span></a>' .
+ '<a class="btn btn-link" href="' . e(route('edit-name', ['xref' => $individual->xref(), 'fact_id' => $fact->id(), 'tree' => $individual->tree()->name()])) . '" title="' . I18N::translate('Edit the name') . '">' . view('icons/edit') . '<span class="sr-only">' . I18N::translate('Edit the name') . '</span></a>';
+ } else {
+ $edit_links = '';
+ }
+
+ return '
+ <div class="' . $container_class . '">
+ <div class="card-header" role="tab" id="name-header-' . $n . '">
+ <a data-toggle="collapse" data-parent="#individual-names" href="#name-content-' . $n . '" aria-expanded="' . $aria . '" aria-controls="name-content-' . $n . '">' . $dummy->fullName() . '</a>
+ ' . $edit_links . '
+ </div>
+ <div id="name-content-' . $n . '" class="' . $content_class . '" role="tabpanel" aria-labelledby="name-header-' . $n . '">
+ <div class="card-body">' . $content . '</div>
+ </div>
+ </div>';
+ }
+
+ /**
+ * print information for a sex record
+ *
+ * @param Fact $fact
+ *
+ * @return string
+ */
+ private function formatSexRecord(Fact $fact): string
+ {
+ $individual = $fact->record();
+
+ switch ($fact->value()) {
+ case 'M':
+ $sex = I18N::translate('Male');
+ break;
+ case 'F':
+ $sex = I18N::translate('Female');
+ break;
+ default:
+ $sex = I18N::translateContext('unknown gender', 'Unknown');
+ break;
+ }
+
+ $container_class = 'card';
+ if ($fact->isPendingDeletion()) {
+ $container_class .= ' wt-old';
+ } elseif ($fact->isPendingAddition()) {
+ $container_class .= ' wt-new';
+ }
+
+ if ($individual->canEdit()) {
+ $edit_links = '<a class="btn btn-link" href="' . e(route(EditFact::class, ['xref' => $individual->xref(), 'fact_id' => $fact->id(), 'tree' => $individual->tree()->name()])) . '" title="' . I18N::translate('Edit the gender') . '">' . view('icons/edit') . '<span class="sr-only">' . I18N::translate('Edit the gender') . '</span></a>';
+ } else {
+ $edit_links = '';
+ }
+
+ return '
+ <div class="' . $container_class . '">
+ <div class="card-header" role="tab" id="name-header-add">
+ <div class="card-title mb-0">
+ <b>' . I18N::translate('Gender') . '</b> ' . $sex . $edit_links . '
+ </div>
+ </div>
+ </div>';
+ }
+
+ /**
+ * Which tabs should we show on this individual's page.
+ * We don't show empty tabs.
+ *
+ * @param Individual $individual
+ *
+ * @return Collection
+ */
+ public function getSidebars(Individual $individual): Collection
+ {
+ return $this->module_service->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
+ ->filter(static function (ModuleSidebarInterface $sidebar) use ($individual): bool {
+ return $sidebar->hasSidebarContent($individual);
+ });
+ }
+
+ /**
+ * Which tabs should we show on this individual's page.
+ * We don't show empty tabs.
+ *
+ * @param Individual $individual
+ *
+ * @return Collection
+ */
+ public function getTabs(Individual $individual): Collection
+ {
+ return $this->module_service->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
+ ->filter(static function (ModuleTabInterface $tab) use ($individual): bool {
+ return $tab->hasTabContent($individual);
+ });
+ }
+
+ /**
+ * What are the significant elements of this page?
+ * The layout will need them to generate URLs for charts and reports.
+ *
+ * @param Individual $individual
+ *
+ * @return stdClass
+ */
+ private function significant(Individual $individual): stdClass
+ {
+ [$surname] = explode(',', $individual->sortName());
+
+ $family = $individual->childFamilies()->merge($individual->spouseFamilies())->first();
+
+ return (object) [
+ 'family' => $family,
+ 'individual' => $individual,
+ 'surname' => $surname,
+ ];
+ }
+}
diff --git a/app/Individual.php b/app/Individual.php
index e61a0d3131..c1085fbafa 100644
--- a/app/Individual.php
+++ b/app/Individual.php
@@ -23,6 +23,7 @@ use Closure;
use Exception;
use Fisharebest\ExtCalendar\GregorianCalendar;
use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
+use Fisharebest\Webtrees\Http\RequestHandlers\IndividualPage;
use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Support\Collection;
use stdClass;
@@ -34,7 +35,7 @@ class Individual extends GedcomRecord
{
public const RECORD_TYPE = 'INDI';
- protected const ROUTE_NAME = 'individual';
+ protected const ROUTE_NAME = IndividualPage::class;
/** @var int used in some lists to keep track of this individual’s generation in that list */
public $generation;
diff --git a/app/Module/ModuleTabInterface.php b/app/Module/ModuleTabInterface.php
index f44fd5b17d..333862d518 100644
--- a/app/Module/ModuleTabInterface.php
+++ b/app/Module/ModuleTabInterface.php
@@ -21,6 +21,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Individual;
use Illuminate\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
/**
* Interface ModuleTabInterface - Classes and libraries for module system
@@ -98,4 +100,13 @@ interface ModuleTabInterface extends ModuleInterface
* @return Collection
*/
public function supportedFacts(): Collection;
+
+ /**
+ * Generate an AJAX response for a tab.
+ *
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function getTabAction(ServerRequestInterface $request): ResponseInterface;
}
diff --git a/app/Module/ModuleTabTrait.php b/app/Module/ModuleTabTrait.php
index d9b334dfda..6e18368be0 100644
--- a/app/Module/ModuleTabTrait.php
+++ b/app/Module/ModuleTabTrait.php
@@ -19,7 +19,17 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Tree;
use Illuminate\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+use function assert;
+use function response;
+use function view;
/**
* Trait ModuleTabTrait - default implementation of ModuleTabInterface
@@ -35,6 +45,25 @@ trait ModuleTabTrait
abstract public function title(): string;
/**
+ * Get a the current access level for a module
+ *
+ * @param Tree $tree
+ * @param string $interface
+ *
+ * @return int
+ */
+ abstract public function accessLevel(Tree $tree, string $interface): int;
+
+ /**
+ * Generate the HTML content of this tab.
+ *
+ * @param Individual $individual
+ *
+ * @return string
+ */
+ abstract public function getTabContent(Individual $individual): string;
+
+ /**
* The text that appears on the tab.
*
* @return string
@@ -85,4 +114,32 @@ trait ModuleTabTrait
{
return new Collection([]);
}
+
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function getTabAction(ServerRequestInterface $request): ResponseInterface
+ {
+ $tree = $request->getAttribute('tree');
+ assert($tree instanceof Tree);
+
+ $xref = $request->getQueryParams()['xref'];
+
+ $record = Individual::getInstance($xref, $tree);
+ $record = Auth::checkIndividualAccess($record);
+
+ $user = $request->getAttribute('user');
+
+ if ($this->accessLevel($tree, 'tab') < Auth::accessLevel($tree, $user)) {
+ throw new AccessDeniedHttpException('Access denied');
+ }
+
+ $layout = view('layouts/ajax', [
+ 'content' => $this->getTabContent($record),
+ ]);
+
+ return response($layout);
+ }
}
diff --git a/resources/views/admin/trees.phtml b/resources/views/admin/trees.phtml
index 1ec9170616..62fefd2a65 100644
--- a/resources/views/admin/trees.phtml
+++ b/resources/views/admin/trees.phtml
@@ -2,6 +2,9 @@
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterModal;
use Fisharebest\Webtrees\Http\RequestHandlers\DeleteTreeAction;
use Fisharebest\Webtrees\Http\RequestHandlers\PendingChangesLogPage;
use Fisharebest\Webtrees\Http\RequestHandlers\SelectDefaultTree;
@@ -215,7 +218,7 @@ use Illuminate\Database\Capsule\Manager as DB;
<!-- UNLINKED SOURCE -->
<li>
<span class="fa-li"><?= view('icons/source') ?></span>
- <a href="#" data-href="<?= e(route('create-source', ['tree' => $managed_tree->name()])) ?>" data-target="#wt-ajax-modal" data-toggle="modal">
+ <a href="#" data-href="<?= e(route(CreateSourceModal::class, ['tree' => $managed_tree->name()])) ?>" data-target="#wt-ajax-modal" data-toggle="modal">
<?= I18N::translate('Source') ?>
<span class="sr-only">
<?= e($managed_tree->title()) ?>
@@ -225,7 +228,7 @@ use Illuminate\Database\Capsule\Manager as DB;
<!-- UNLINKED REPOSITORY -->
<li>
<span class="fa-li"><?= view('icons/repository') ?></span>
- <a href="#" data-href="<?= e(route('create-repository', ['tree' => $managed_tree->name()])) ?>" data-target="#wt-ajax-modal" data-toggle="modal">
+ <a href="#" data-href="<?= e(route(CreateRepositoryModal::class, ['tree' => $managed_tree->name()])) ?>" data-target="#wt-ajax-modal" data-toggle="modal">
<?= I18N::translate('Repository') ?>
<span class="sr-only">
<?= e($managed_tree->title()) ?>
@@ -256,7 +259,7 @@ use Illuminate\Database\Capsule\Manager as DB;
<!-- UNLINKED SUBMITTER -->
<li>
<span class="fa-li"><?= view('icons/submitter') ?></span>
- <a href="#" data-href="<?= e(route('create-submitter', ['tree' => $managed_tree->name()])) ?>" data-target="#wt-ajax-modal" data-toggle="modal">
+ <a href="#" data-href="<?= e(route(CreateSubmitterModal::class, ['tree' => $managed_tree->name()])) ?>" data-target="#wt-ajax-modal" data-toggle="modal">
<span class="sr-only">
<?= e($managed_tree->title()) ?>
</span>
diff --git a/resources/views/individual-page.phtml b/resources/views/individual-page.phtml
index cd65e15b3f..8c63eb2b1b 100644
--- a/resources/views/individual-page.phtml
+++ b/resources/views/individual-page.phtml
@@ -1,4 +1,5 @@
<?php
+
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\Http\RequestHandlers\AddNewFact;
@@ -108,7 +109,7 @@ use Illuminate\Support\Collection;
<ul class="nav nav-tabs flex-wrap">
<?php foreach ($tabs as $tab) : ?>
<li class="nav-item">
- <a class="nav-link<?= $tab->isGrayedOut($individual) ? ' text-muted' : '' ?>" data-toggle="tab" role="tab" data-href="<?= e(route('individual-tab', ['xref' => $individual->xref(), 'tree' => $individual->tree()->name(), 'module' => $tab->name()])) ?>" href="#<?= $tab->name() ?>">
+ <a class="nav-link<?= $tab->isGrayedOut($individual) ? ' text-muted' : '' ?>" data-toggle="tab" role="tab" data-href="<?= e(route('module', ['module' => $tab->name(), 'action' => 'Tab', 'tree' => $individual->tree()->name(), 'xref' => $individual->xref()])) ?>" href="#<?= $tab->name() ?>">
<?= $tab->tabTitle() ?>
</a>
</li>
@@ -117,8 +118,7 @@ use Illuminate\Support\Collection;
<div class="tab-content">
<?php foreach ($tabs as $tab) : ?>
<div id="<?= $tab->name() ?>" class="tab-pane fade wt-ajax-load" role="tabpanel"><?php if (!$tab->canLoadAjax()) :
- ?><?= $tab->getTabContent($individual) ?><?php
- endif ?></div>
+ ?><?= $tab->getTabContent($individual) ?><?php endif ?></div>
<?php endforeach ?>
</div>
</div>
diff --git a/resources/views/modals/create-repository.phtml b/resources/views/modals/create-repository.phtml
index 093b7885df..997dc75dd7 100644
--- a/resources/views/modals/create-repository.phtml
+++ b/resources/views/modals/create-repository.phtml
@@ -1,6 +1,11 @@
-<?php use Fisharebest\Webtrees\I18N; ?>
+<?php
-<form method="post" action="<?= e(route('create-repository', ['tree' => $tree->name()])) ?>" id="wt-modal-form">
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryAction;
+use Fisharebest\Webtrees\I18N;
+
+?>
+
+<form method="post" action="<?= e(route(CreateRepositoryAction::class, ['tree' => $tree->name()])) ?>" id="wt-modal-form">
<?= csrf_field() ?>
<?= view('modals/header', ['title' => I18N::translate('Create a repository')]) ?>
diff --git a/resources/views/modals/create-source.phtml b/resources/views/modals/create-source.phtml
index 147c9fae27..90a0d9ecbe 100644
--- a/resources/views/modals/create-source.phtml
+++ b/resources/views/modals/create-source.phtml
@@ -1,6 +1,11 @@
-<?php use Fisharebest\Webtrees\I18N; ?>
+<?php
-<form method="post" action="<?= e(route('create-source', ['tree' => $tree->name()])) ?>" id="wt-modal-form">
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceAction;
+use Fisharebest\Webtrees\I18N;
+
+?>
+
+<form method="post" action="<?= e(route(CreateSourceAction::class, ['tree' => $tree->name()])) ?>" id="wt-modal-form">
<?= csrf_field() ?>
<?= view('modals/header', ['title' => I18N::translate('Create a source')]) ?>
diff --git a/resources/views/modals/create-submitter.phtml b/resources/views/modals/create-submitter.phtml
index ff9f519c4f..0200ee2d1a 100644
--- a/resources/views/modals/create-submitter.phtml
+++ b/resources/views/modals/create-submitter.phtml
@@ -1,6 +1,11 @@
-<?php use Fisharebest\Webtrees\I18N; ?>
+<?php
-<form method="post" action="<?= e(route('create-submitter', ['tree' => $tree->name()])) ?>" id="wt-modal-form">
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterAction;
+use Fisharebest\Webtrees\I18N;
+
+?>
+
+<form method="post" action="<?= e(route(CreateSubmitterAction::class, ['tree' => $tree->name()])) ?>" id="wt-modal-form">
<?= csrf_field() ?>
<?= view('modals/header', ['title' => I18N::translate('Create a submitter')]) ?>
diff --git a/routes/web.php b/routes/web.php
index ed956d4151..9b6471939a 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -31,6 +31,12 @@ use Fisharebest\Webtrees\Http\Middleware\AuthMember;
use Fisharebest\Webtrees\Http\Middleware\AuthModerator;
use Fisharebest\Webtrees\Http\Middleware\AuthVisitor;
use Fisharebest\Webtrees\Http\RequestHandlers\AddNewFact;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryAction;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateRepositoryModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceAction;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSourceModal;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterAction;
+use Fisharebest\Webtrees\Http\RequestHandlers\CreateSubmitterModal;
use Fisharebest\Webtrees\Http\RequestHandlers\EditRawRecordAction;
use Fisharebest\Webtrees\Http\RequestHandlers\AccountDelete;
use Fisharebest\Webtrees\Http\RequestHandlers\AccountEdit;
@@ -53,6 +59,7 @@ use Fisharebest\Webtrees\Http\RequestHandlers\FamilyPage;
use Fisharebest\Webtrees\Http\RequestHandlers\GedcomRecordPage;
use Fisharebest\Webtrees\Http\RequestHandlers\HelpText;
use Fisharebest\Webtrees\Http\RequestHandlers\HomePage;
+use Fisharebest\Webtrees\Http\RequestHandlers\IndividualPage;
use Fisharebest\Webtrees\Http\RequestHandlers\MediaPage;
use Fisharebest\Webtrees\Http\RequestHandlers\NotePage;
use Fisharebest\Webtrees\Http\RequestHandlers\PasteFact;
@@ -320,12 +327,12 @@ $router->attach('', '/tree/{tree}', static function (Map $router) {
$router->post(CopyFact::class, '/copy/{xref}/{fact_id}', CopyFact::class);
$router->get('create-note-object', '/create-note-object', 'EditNoteController::createNoteObject');
$router->post('create-note-object-action', '/create-note-object', 'EditNoteController::createNoteObjectAction');
- $router->get('create-repository', '/create-repository', 'EditRepositoryController::createRepository');
- $router->post('create-repository-action', '/create-repository', 'EditRepositoryController::createRepositoryAction');
- $router->get('create-source', '/create-source', 'EditSourceController::createSource');
- $router->post('create-source-action', '/create-source', 'EditSourceController::createSourceAction');
- $router->get('create-submitter', '/create-submitter', 'EditSubmitterController::createSubmitter');
- $router->post('create-submitter-action', '/create-submitter', 'EditSubmitterController::createSubmitterAction');
+ $router->get(CreateRepositoryModal::class, '/create-repository', CreateRepositoryModal::class);
+ $router->post(CreateRepositoryAction::class, '/create-repository', CreateRepositoryAction::class);
+ $router->get(CreateSourceModal::class, '/create-source', CreateSourceModal::class);
+ $router->post(CreateSourceAction::class, '/create-source', CreateSourceAction::class);
+ $router->get(CreateSubmitterModal::class, '/create-submitter', CreateSubmitterModal::class);
+ $router->post(CreateSubmitterAction::class, '/create-submitter', CreateSubmitterAction::class);
$router->post(DeleteRecord::class, '/delete/{xref}', DeleteRecord::class);
$router->post(DeleteFact::class, '/delete/{xref}/{fact_id}', DeleteFact::class);
$router->get(EditFact::class, '/edit-fact', EditFact::class);
@@ -417,7 +424,7 @@ $router->attach('', '/tree/{tree}', static function (Map $router) {
$router->get('contact', '/contact', 'MessageController::contactPage');
$router->post('contact-action', '/contact', 'MessageController::contactAction');
$router->get(FamilyPage::class, '/family/{xref}{/slug}', FamilyPage::class);
- $router->get('individual', '/individual/{xref}{/slug}', 'IndividualController::show');
+ $router->get(IndividualPage::class, '/individual/{xref}{/slug}', IndividualPage::class);
$router->get('media-thumbnail', '/media-thumbnail', 'MediaFileController::mediaThumbnail');
$router->get('media-download', '/media-download', 'MediaFileController::mediaDownload');
$router->get(MediaPage::class, '/media/{xref}{/slug}', MediaPage::class);
@@ -446,7 +453,6 @@ $router->attach('', '/tree/{tree}', static function (Map $router) {
$router->post(Select2Submitter::class, '/select2-submitter', Select2Submitter::class);
$router->post(Select2Repository::class, '/select2-repository', Select2Repository::class);
$router->get(SourcePage::class, '/source/{xref}{/slug}', SourcePage::class);
- $router->get('individual-tab', '/tab-{module}/{xref}', 'IndividualController::tab');
$router->get('tree-page-block', '/tree-page-block', 'HomePageController::treePageBlock');
$router->get('example', '/…');
});