diff options
34 files changed, 1315 insertions, 163 deletions
diff --git a/app/Functions/FunctionsPrintLists.php b/app/Functions/FunctionsPrintLists.php index 2c6456b1da..79fd1a082a 100644 --- a/app/Functions/FunctionsPrintLists.php +++ b/app/Functions/FunctionsPrintLists.php @@ -19,6 +19,7 @@ namespace Fisharebest\Webtrees\Functions; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Module\ModuleListInterface; /** * Class FunctionsPrintLists - create sortable lists using datatables.net @@ -28,14 +29,15 @@ class FunctionsPrintLists /** * Print a tagcloud of surnames. * - * @param int[][] $surnames array (of SURN, of array of SPFX_SURN, of counts) - * @param string $route individual-list or family-listlist - * @param bool $totals show totals after each name - * @param Tree $tree generate links to this tree + * @param int[][] $surnames array (of SURN, of array of SPFX_SURN, of counts) + * @param Tree $tree + * @param ?ModuleListInterface $module + * @param bool $totals show totals after each name + * @param Tree $tree generate links to this tree * * @return string */ - public static function surnameTagCloud(array $surnames, string $route, bool $totals, Tree $tree): string + public static function surnameTagCloud(array $surnames, ?ModuleListInterface $module, bool $totals, Tree $tree): string { $minimum = PHP_INT_MAX; $maximum = 1; @@ -55,17 +57,20 @@ class FunctionsPrintLists } else { $size = 75.0 + 125.0 * ($count - $minimum) / ($maximum - $minimum); } - $url = route($route, [ - 'surname' => $surn, - 'ged' => $tree->name(), - ]); - $html .= '<a style="font-size:' . $size . '%" href="' . e($url) . '">'; + + $tag = ($module instanceof ModuleListInterface)?'a':'span'; + $html .= '<'.$tag.' style="font-size:' . $size . '%"'; + if ($module instanceof ModuleListInterface) { + $url = $module->listUrl($tree, ['surname' => $surn]); + $html .= ' href="' . e($url) . '"'; + } + $html .= '>'; if ($totals) { $html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number($count)); } else { $html .= $spfxsurn; } - $html .= '</a> '; + $html .= '</'.$tag.'> '; } } @@ -75,34 +80,35 @@ class FunctionsPrintLists /** * Print a list of surnames. * - * @param string[][][] $surnames array (of SURN, of array of SPFX_SURN, of array of XREF) - * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns - * @param bool $totals show totals after each name - * @param string $route individual-list or family-list - * @param Tree $tree Link back to the individual list in this tree + * @param string[][][] $surnames array (of SURN, of array of SPFX_SURN, of array of XREF) + * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns + * @param bool $totals show totals after each name + * @param ?ModuleListInterface $module + * @param Tree $tree Link back to the individual list in this tree * * @return string */ - public static function surnameList($surnames, $style, $totals, $route, Tree $tree) + public static function surnameList($surnames, $style, $totals, ?ModuleListInterface $module, Tree $tree) { $html = []; foreach ($surnames as $surn => $surns) { // Each surname links back to the indilist - if ($surn) { - $url = route($route, [ - 'surname' => $surn, - 'ged' => $tree->name(), - ]); - } else { - $url = route($route, [ - 'alpha' => ',', - 'ged' => $tree->name(), - ]); + if ($module instanceof ModuleListInterface) { + if ($surn) { + $url = $module->listUrl($tree, ['surname' => $surn]); + } else { + $url = $module->listUrl($tree, ['alpha' => ',']); + } } // If all the surnames are just case variants, then merge them into one // Comment out this block if you want SMITH listed separately from Smith - $subhtml = '<a href="' . e($url) . '" dir="auto">' . e(implode(I18N::$list_separator, array_keys($surns))) . '</a>'; - + $tag = ($module instanceof ModuleListInterface)?'a':'span'; + $subhtml = '<'.$tag; + if ($url !== null) { + $subhtml .= ' href="' . e($url) . '"'; + } + $subhtml .= ' dir="auto">' . e(implode(I18N::$list_separator, array_keys($surns))) . '</'.$tag.'>'; + if ($totals) { $subtotal = 0; foreach ($surns as $count) { diff --git a/app/Http/Controllers/Admin/ControlPanelController.php b/app/Http/Controllers/Admin/ControlPanelController.php index 4ae1dc9d65..d804140d2e 100644 --- a/app/Http/Controllers/Admin/ControlPanelController.php +++ b/app/Http/Controllers/Admin/ControlPanelController.php @@ -25,6 +25,7 @@ use Fisharebest\Webtrees\Module\ModuleChartInterface; use Fisharebest\Webtrees\Module\ModuleFooterInterface; use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface; use Fisharebest\Webtrees\Module\ModuleLanguageInterface; +use Fisharebest\Webtrees\Module\ModuleListInterface; use Fisharebest\Webtrees\Module\ModuleMenuInterface; use Fisharebest\Webtrees\Module\ModuleReportInterface; use Fisharebest\Webtrees\Module\ModuleSidebarInterface; @@ -94,6 +95,7 @@ class ControlPanelController extends AbstractAdminController 'footer_modules' => $module_service->findByInterface(ModuleFooterInterface::class, true), 'history_modules' => $module_service->findByInterface(ModuleHistoricEventsInterface::class, true), 'language_modules' => $module_service->findByInterface(ModuleLanguageInterface::class, true), + 'list_modules' => $module_service->findByInterface(ModuleListInterface::class, true), 'menu_modules' => $module_service->findByInterface(ModuleMenuInterface::class, true), 'report_modules' => $module_service->findByInterface(ModuleReportInterface::class, true), 'sidebar_modules' => $module_service->findByInterface(ModuleSidebarInterface::class, true), diff --git a/app/Http/Controllers/Admin/ModuleController.php b/app/Http/Controllers/Admin/ModuleController.php index 767665de91..68fe692677 100644 --- a/app/Http/Controllers/Admin/ModuleController.php +++ b/app/Http/Controllers/Admin/ModuleController.php @@ -25,6 +25,7 @@ use Fisharebest\Webtrees\Module\ModuleChartInterface; use Fisharebest\Webtrees\Module\ModuleFooterInterface; use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface; use Fisharebest\Webtrees\Module\ModuleLanguageInterface; +use Fisharebest\Webtrees\Module\ModuleListInterface; use Fisharebest\Webtrees\Module\ModuleMenuInterface; use Fisharebest\Webtrees\Module\ModuleReportInterface; use Fisharebest\Webtrees\Module\ModuleSidebarInterface; @@ -46,6 +47,7 @@ class ModuleController extends AbstractAdminController private const COMPONENTS_WITH_ACCESS = [ 'block', 'chart', + 'list', 'menu', 'report', 'sidebar', @@ -165,6 +167,19 @@ class ModuleController extends AbstractAdminController '' ); } + + /** + * @return Response + */ + public function listLists(): Response + { + return $this->listComponents( + ModuleListInterface::class, + 'list', + I18N::translate('Lists'), + '' + ); + } /** * @return Response @@ -372,7 +387,22 @@ class ModuleController extends AbstractAdminController return new RedirectResponse(route('language')); } + + /** + * @param Request $request + * + * @return RedirectResponse + */ + public function updateLists(Request $request): RedirectResponse + { + $modules = $this->module_service->findByInterface(ModuleListInterface::class, true); + + $this->updateStatus($modules, $request); + $this->updateAccessLevel($modules, 'list', $request); + return new RedirectResponse(route('lists')); + } + /** * @param Request $request * diff --git a/app/Http/Controllers/BranchesController.php b/app/Http/Controllers/BranchesController.php index 50b72474f1..e93bd61f11 100644 --- a/app/Http/Controllers/BranchesController.php +++ b/app/Http/Controllers/BranchesController.php @@ -62,6 +62,10 @@ class BranchesController extends AbstractBaseController */ public function page(Request $request): Response { + //route is assumed to be 'module' + $module = $request->get('module'); + $action = $request->get('action'); + $surname = $request->get('surname', ''); $soundex_std = (bool) $request->get('soundex_std'); $soundex_dm = (bool) $request->get('soundex_dm'); @@ -79,6 +83,8 @@ class BranchesController extends AbstractBaseController 'soundex_std' => $soundex_std, 'surname' => $surname, 'title' => $title, + 'module' => $module, + 'action' => $action, ]); } diff --git a/app/Http/Controllers/ListController.php b/app/Http/Controllers/ListController.php index d0eeebeff9..676efc9232 100644 --- a/app/Http/Controllers/ListController.php +++ b/app/Http/Controllers/ListController.php @@ -25,6 +25,7 @@ use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Repository; +use Fisharebest\Webtrees\Module\ModuleListInterface; use Fisharebest\Webtrees\Services\IndividualListService; use Fisharebest\Webtrees\Services\LocalizationService; use Fisharebest\Webtrees\Session; @@ -47,7 +48,8 @@ class ListController extends AbstractBaseController /** @var LocalizationService */ private $localization_service; - + + /** * ListController constructor. * @@ -63,32 +65,40 @@ class ListController extends AbstractBaseController /** * Show a list of all individual or family records. * - * @param Request $request - * @param Tree $tree - * @param UserInterface $user + * @param ?ModuleListInterface $module + * @param Request $request + * @param Tree $tree + * @param UserInterface $user * * @return Response */ - public function familyList(Request $request, Tree $tree, UserInterface $user): Response + public function familyList(?ModuleListInterface $moduleListInterface, Request $request, Tree $tree, UserInterface $user): Response { - return $this->individualList($request, $tree, $user); + return $this->individualOrFamilyList($moduleListInterface, true, $request, $tree, $user); } /** * Show a list of all individual or family records. - * - * @param Request $request - * @param Tree $tree - * @param UserInterface $user + * + * @param ?ModuleListInterface $module + * @param Request $request + * @param Tree $tree + * @param UserInterface $user * * @return Response */ - public function individualList(Request $request, Tree $tree, UserInterface $user): Response + public function individualList(?ModuleListInterface $moduleListInterface, Request $request, Tree $tree, UserInterface $user): Response + { + return $this->individualOrFamilyList($moduleListInterface, false, $request, $tree, $user); + } + + public function individualOrFamilyList(?ModuleListInterface $moduleListInterface, bool $families, Request $request, Tree $tree, UserInterface $user): Response { // This action can show lists of both families and individuals. - $route = $request->get('route'); - $families = $route === 'family-list'; - + //route is assumed to be 'module' + $module = $request->get('module'); + $action = $request->get('action'); + ob_start(); // We show three different lists: initials, surnames and individuals @@ -114,10 +124,10 @@ class ListController extends AbstractBaseController switch ($show_marnm) { case 'no': case 'yes': - $user->setPreference($route . '-marnm', $show_marnm); + $user->setPreference($families?'family-list-marnm':'individual-list-marnm', $show_marnm); break; default: - $show_marnm = $user->getPreference($route . '-marnm'); + $show_marnm = $user->getPreference($families?'family-list-marnm':'individual-list-marnm'); } // Make sure selections are consistent. @@ -222,7 +232,7 @@ class ListController extends AbstractBaseController <?php foreach ($this->individual_list_service->surnameAlpha($show_marnm === 'yes', $families, WT_LOCALE, I18N::collation()) as $letter => $count) : ?> <li class="wt-initials-list-item d-flex"> <?php if ($count > 0) : ?> - <a href="<?= e(route($route, ['alpha' => $letter, 'ged' => $tree->name()])) ?>" class="wt-initial px-1<?= $letter === $alpha ? ' active' : '' ?> '" title="<?= I18N::number($count) ?>"><?= $this->surnameInitial((string) $letter) ?></a> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'alpha' => $letter, 'ged' => $tree->name()])) ?>" class="wt-initial px-1<?= $letter === $alpha ? ' active' : '' ?> '" title="<?= I18N::number($count) ?>"><?= $this->surnameInitial((string) $letter) ?></a> <?php else : ?> <span class="wt-initial px-1 text-muted"><?= $this->surnameInitial((string) $letter) ?></span> @@ -233,7 +243,7 @@ class ListController extends AbstractBaseController <?php if (Session::has('initiated')) : ?> <!-- Search spiders don't get the "show all" option as the other links give them everything. --> <li class="wt-initials-list-item d-flex"> - <a class="wt-initial px-1<?= $show_all === 'yes' ? ' active' : '' ?>" href="<?= e(route($route, ['show_all' => 'yes'] + $params)) ?>"><?= I18N::translate('All') ?></a> + <a class="wt-initial px-1<?= $show_all === 'yes' ? ' active' : '' ?>" href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show_all' => 'yes'] + $params)) ?>"><?= I18N::translate('All') ?></a> </li> <?php endif ?> </ul> @@ -242,13 +252,13 @@ class ListController extends AbstractBaseController <?php if (Session::has('initiated') && $show !== 'none') : ?> <?php if ($show_marnm === 'yes') : ?> <p> - <a href="<?= e(route($route, ['show' => $show, 'show_marnm' => 'no'] + $params)) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => $show, 'show_marnm' => 'no'] + $params)) ?>"> <?= I18N::translate('Exclude individuals with “%s” as a married name', $legend) ?> </a> </p> <?php else : ?> <p> - <a href="<?= e(route($route, ['show' => $show, 'show_marnm' => 'yes'] + $params)) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => $show, 'show_marnm' => 'yes'] + $params)) ?>"> <?= I18N::translate('Include individuals with “%s” as a married name', $legend) ?> </a> </p> @@ -257,13 +267,13 @@ class ListController extends AbstractBaseController <?php if ($alpha !== '@' && $alpha !== ',' && !$surname) : ?> <?php if ($show === 'surn') : ?> <p> - <a href="<?= e(route($route, ['show' => 'indi', 'show_marnm' => 'no'] + $params)) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => 'indi', 'show_marnm' => 'no'] + $params)) ?>"> <?= I18N::translate('Show the list of individuals') ?> </a> </p> <?php else : ?> <p> - <a href="<?= e(route($route, ['show' => 'surn', 'show_marnm' => 'no'] + $params)) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => 'surn', 'show_marnm' => 'no'] + $params)) ?>"> <?= I18N::translate('Show the list of surnames') ?> </a> </p> @@ -281,16 +291,17 @@ class ListController extends AbstractBaseController // Show the surname list switch ($tree->getPreference('SURNAME_LIST_STYLE')) { case 'style1': - echo FunctionsPrintLists::surnameList($surns, 3, true, $route, $tree); + echo FunctionsPrintLists::surnameList($surns, 3, true, $moduleListInterface, $tree); break; - case 'style3': - echo FunctionsPrintLists::surnameTagCloud($surns, $route, true, $tree); + case 'style3': + echo FunctionsPrintLists::surnameTagCloud($surns, $moduleListInterface, true, $tree); break; case 'style2': default: echo view('lists/surnames-table', [ 'surnames' => $surns, - 'route' => $route, + 'families' => $families, + 'router' => $moduleListInterface, ]); break; } @@ -320,9 +331,9 @@ class ListController extends AbstractBaseController echo '<li class="wt-initials-list-item d-flex">'; if ($count > 0) { if ($show === 'indi' && $givn_initial === $falpha && $show_all_firstnames === 'no') { - echo '<a class="wt-initial px-1 active" href="' . e(route($route, ['falpha' => $givn_initial] + $params)) . '" title="' . I18N::number($count) . '">' . $this->givenNameInitial((string) $givn_initial) . '</a>'; + echo '<a class="wt-initial px-1 active" href="' . e(route('module', ['module' => $module, 'action' => $action, 'falpha' => $givn_initial] + $params)) . '" title="' . I18N::number($count) . '">' . $this->givenNameInitial((string) $givn_initial) . '</a>'; } else { - echo '<a class="wt-initial px-1" href="' . e(route($route, ['falpha' => $givn_initial] + $params)) . '" title="' . I18N::number($count) . '">' . $this->givenNameInitial((string) $givn_initial) . '</a>'; + echo '<a class="wt-initial px-1" href="' . e(route('module', ['module' => $module, 'action' => $action, 'falpha' => $givn_initial] + $params)) . '" title="' . I18N::number($count) . '">' . $this->givenNameInitial((string) $givn_initial) . '</a>'; } } else { echo '<span class="wt-initial px-1 text-muted">' . $this->givenNameInitial((string) $givn_initial) . '</span>'; @@ -335,7 +346,7 @@ class ListController extends AbstractBaseController if ($show_all_firstnames === 'yes') { echo '<span class="wt-initial px-1 warning">' . I18N::translate('All') . '</span>'; } else { - echo '<a class="wt-initial px-1" href="' . e(route($route, ['show_all_firstnames' => 'yes'] + $params)) . '" title="' . I18N::number($count) . '">' . I18N::translate('All') . '</a>'; + echo '<a class="wt-initial px-1" href="' . e(route('module', ['module' => $module, 'action' => $action, 'show_all_firstnames' => 'yes'] + $params)) . '" title="' . I18N::number($count) . '">' . I18N::translate('All') . '</a>'; } echo '</li>'; } @@ -344,7 +355,7 @@ class ListController extends AbstractBaseController } } if ($show === 'indi') { - if ($route === 'individual-list') { + if (!$families) { echo view('lists/individuals-table', [ 'individuals' => $this->individual_list_service->individuals($surname, $alpha, $falpha, $show_marnm === 'yes', false, I18N::collation()), 'sosa' => false, @@ -381,9 +392,13 @@ class ListController extends AbstractBaseController */ public function mediaList(Request $request, Tree $tree): Response { + //route is assumed to be 'module' + $module = $request->get('module'); + $action = $request->get('action'); + $formats = GedcomTag::getFileFormTypes(); - $action = $request->get('action'); + $action2 = $request->get('action2'); $page = (int) $request->get('page'); $max = (int) $request->get('max', 20); $folder = $request->get('folder', ''); @@ -393,7 +408,7 @@ class ListController extends AbstractBaseController $folders = $this->allFolders($tree); - if ($action === '1') { + if ($action2 === '1') { $media_objects = $this->allMedia( $tree, $folder, @@ -426,6 +441,8 @@ class ListController extends AbstractBaseController 'pages' => $pages, 'subdirs' => $subdirs, 'title' => I18N::translate('Media'), + 'module' => $module, + 'action' => $action, ]); } diff --git a/app/Http/Controllers/PlaceHierarchyController.php b/app/Http/Controllers/PlaceHierarchyController.php index 91b647efaf..e8ca99daec 100644 --- a/app/Http/Controllers/PlaceHierarchyController.php +++ b/app/Http/Controllers/PlaceHierarchyController.php @@ -66,8 +66,8 @@ class PlaceHierarchyController extends AbstractBaseController * @return Response */ public function show(Request $request, Tree $tree, SearchService $search_service): Response - { - $action = $request->query->get('action', 'hierarchy'); + { + $action2 = $request->query->get('action2', 'hierarchy'); $parent = $request->query->get('parent', []); $fqpn = implode(Gedcom::PLACE_SEPARATOR, array_reverse($parent)); $place = new Place($fqpn, $tree); @@ -86,7 +86,7 @@ class PlaceHierarchyController extends AbstractBaseController ]); } - switch ($action) { + switch ($action2) { case 'list': $nextaction = ['hierarchy' => I18N::translate('Show place hierarchy')]; $content .= view('place-list', $this->getList($tree, $search_service)); @@ -96,7 +96,7 @@ class PlaceHierarchyController extends AbstractBaseController $nextaction = ['list' => I18N::translate('Show all places in a list')]; $data = $this->getHierarchy($tree, $place, $parent); $content .= (null === $data || $showmap) ? '' : view('place-hierarchy', $data); - if (null === $data || $action === 'hierarchy-e') { + if (null === $data || $action2 === 'hierarchy-e') { $content .= view('place-events', $this->getEvents($tree, $place)); } break; @@ -106,6 +106,10 @@ class PlaceHierarchyController extends AbstractBaseController $breadcrumbs = $this->breadcrumbs($place); + //route is assumed to be 'module' + $module = $request->get('module'); + $action = $request->get('action'); + return $this->viewResponse( 'places-page', [ @@ -117,8 +121,10 @@ class PlaceHierarchyController extends AbstractBaseController 'parent' => $parent, 'place' => $fqpn, 'content' => $content, - 'showeventslink' => null !== $data && $place->gedcomName() !== '' && $action !== 'hierarchy-e', + 'showeventslink' => null !== $data && $place->gedcomName() !== '' && $action2 !== 'hierarchy-e', 'nextaction' => $nextaction, + 'module' => $module, + 'action' => $action, ] ); } diff --git a/app/Module/BranchesListModule.php b/app/Module/BranchesListModule.php new file mode 100644 index 0000000000..76245532ae --- /dev/null +++ b/app/Module/BranchesListModule.php @@ -0,0 +1,97 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\BranchesController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\ModuleService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class BranchesListModule + */ +class BranchesListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Branches'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “BranchesListModule” module */ + return I18N::translate('A list of branches of a family.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-branches'; + } + + public function listUrl(Tree $tree, array $parameters = []): string + { + return route('module', [ + 'module' => $this->name(), + 'action' => 'Page', + 'ged' => $tree->name(), + ] + $parameters); + } + + public function getPageAction(Request $request, Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new BranchesController(app(ModuleService::class)); + return $listController->page($request); + } + + public function getListAction(Request $request, Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new BranchesController(app(ModuleService::class)); + return $listController->list($request, $tree, $user); + } + + public function listUrlAttributes(): array + { + return []; + } +} diff --git a/app/Module/FamilyListModule.php b/app/Module/FamilyListModule.php new file mode 100644 index 0000000000..06d9e309b8 --- /dev/null +++ b/app/Module/FamilyListModule.php @@ -0,0 +1,81 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\ListController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\IndividualListService; +use Fisharebest\Webtrees\Services\LocalizationService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class FamilyListModule + */ +class FamilyListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Families'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “FamilyListModule” module */ + return I18N::translate('A list of families.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-fam'; + } + + public function getListAction(Request $request, Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new ListController(app(IndividualListService::class), app(LocalizationService::class)); + return $listController->familyList($this, $request, $tree, $user); + } + + public function listUrlAttributes(): array + { + return []; + } +} diff --git a/app/Module/FamilyTreeStatisticsModule.php b/app/Module/FamilyTreeStatisticsModule.php index 47af90942d..4e02f15fb1 100644 --- a/app/Module/FamilyTreeStatisticsModule.php +++ b/app/Module/FamilyTreeStatisticsModule.php @@ -20,6 +20,9 @@ namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Functions\FunctionsPrintLists; use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Module\IndividualListModule; +use Fisharebest\Webtrees\Module\ModuleInterface; +use Fisharebest\Webtrees\Services\ModuleService; use Fisharebest\Webtrees\Statistics; use Fisharebest\Webtrees\Tree; use Illuminate\Database\Capsule\Manager as DB; @@ -120,7 +123,12 @@ class FamilyTreeStatisticsModule extends AbstractModule implements ModuleBlockIn uksort($all_surnames, [I18N::class, 'strcasecmp']); - $surnames = FunctionsPrintLists::surnameList($all_surnames, 2, false, 'individual-list', $tree); + //find a module providing individual lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof IndividualListModule; + }); + + $surnames = FunctionsPrintLists::surnameList($all_surnames, 2, false, $module, $tree); } else { $surnames = ''; } diff --git a/app/Module/IndividualListModule.php b/app/Module/IndividualListModule.php new file mode 100644 index 0000000000..3ee79c6296 --- /dev/null +++ b/app/Module/IndividualListModule.php @@ -0,0 +1,81 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\ListController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\IndividualListService; +use Fisharebest\Webtrees\Services\LocalizationService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class IndividualListModule + */ +class IndividualListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Individuals'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “IndividualListModule” module */ + return I18N::translate('A list of individuals.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-indi'; + } + + public function getListAction(Request $request, Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new ListController(app(IndividualListService::class), app(LocalizationService::class)); + return $listController->individualList($this, $request, $tree, $user); + } + + public function listUrlAttributes(): array + { + return []; + } +} diff --git a/app/Module/ListsMenuModule.php b/app/Module/ListsMenuModule.php index 8ea66673e1..683c1d4456 100644 --- a/app/Module/ListsMenuModule.php +++ b/app/Module/ListsMenuModule.php @@ -17,10 +17,11 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; +use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Menu; +use Fisharebest\Webtrees\Services\ModuleService; use Fisharebest\Webtrees\Tree; -use Illuminate\Database\Capsule\Manager as DB; /** * Class ListsMenuModule - provide a menu option for the lists @@ -30,6 +31,21 @@ class ListsMenuModule extends AbstractModule implements ModuleMenuInterface use ModuleMenuTrait; /** + * @var ModuleService + */ + private $module_service; + + /** + * ListsMenuModule constructor. + * + * @param ModuleService $module_service + */ + public function __construct(ModuleService $module_service) + { + $this->module_service = $module_service; + } + + /** * How should this module be labelled on tabs, menus, etc.? * * @return string @@ -70,45 +86,19 @@ class ListsMenuModule extends AbstractModule implements ModuleMenuInterface */ public function getMenu(Tree $tree): ?Menu { - // Do not show empty lists - $sources_exist = DB::table('sources') - ->where('s_file', '=', $tree->id()) - ->exists(); - - $repositories_exist = DB::table('other') - ->where('o_file', '=', $tree->id()) - ->where('o_type', '=', 'REPO') - ->exists(); - - $notes_exist = DB::table('other') - ->where('o_file', '=', $tree->id()) - ->where('o_type', '=', 'NOTE') - ->exists(); - - $media_exist = DB::table('media') - ->where('m_file', '=', $tree->id()) - ->exists(); + $submenusCollection = $this->module_service->findByComponent('list', $tree, Auth::user()) + ->map(function (ModuleListInterface $module) use ($tree): Menu { + return $module->listMenu($tree); + }) + ->filter(function (Menu $menu): bool { + return ($menu !== null); + }); - $submenus = [ - new Menu(I18N::translate('Individuals'), route('individual-list', ['ged' => $tree->name()]), 'menu-list-indi'), - new Menu(I18N::translate('Families'), route('family-list', ['ged' => $tree->name()]), 'menu-list-fam'), - new Menu(I18N::translate('Branches'), route('branches', ['ged' => $tree->name()]), 'menu-branches', ['rel' => 'nofollow']), - new Menu(I18N::translate('Place hierarchy'), route('place-hierarchy', ['ged' => $tree->name()]), 'menu-list-plac', ['rel' => 'nofollow']), - ]; - - if ($media_exist) { - $submenus[] = new Menu(I18N::translate('Media objects'), route('media-list', ['ged' => $tree->name()]), 'menu-list-obje', ['rel' => 'nofollow']); - } - if ($repositories_exist) { - $submenus[] = new Menu(I18N::translate('Repositories'), route('repository-list', ['ged' => $tree->name()]), 'menu-list-repo', ['rel' => 'nofollow']); - } - if ($sources_exist) { - $submenus[] = new Menu(I18N::translate('Sources'), route('source-list', ['ged' => $tree->name()]), 'menu-list-sour', ['rel' => 'nofollow']); - } - if ($notes_exist) { - $submenus[] = new Menu(I18N::translate('Shared notes'), route('note-list', ['ged' => $tree->name()]), 'menu-list-note', ['rel' => 'nofollow']); + if ($submenusCollection->isEmpty()) { + return null; } + $submenus = $submenusCollection->toArray(); uasort($submenus, function (Menu $x, Menu $y) { return I18N::strcasecmp($x->getLabel(), $y->getLabel()); }); diff --git a/app/Module/MediaListModule.php b/app/Module/MediaListModule.php new file mode 100644 index 0000000000..acb11fd00d --- /dev/null +++ b/app/Module/MediaListModule.php @@ -0,0 +1,89 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\ListController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\IndividualListService; +use Fisharebest\Webtrees\Services\LocalizationService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Illuminate\Database\Capsule\Manager as DB; + +/** + * Class MediaListModule + */ +class MediaListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Media objects'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “MediaListModule” module */ + return I18N::translate('A list of media objects.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-obje'; + } + + public function getListAction(Request $request, Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new ListController(app(IndividualListService::class), app(LocalizationService::class)); + return $listController->mediaList($request, $tree); + } + + public function listUrlAttributes(): array + { + return []; + } + + public function listIsEmpty(Tree $tree): bool + { + return !DB::table('media') + ->where('m_file', '=', $tree->id()) + ->exists(); + } +} diff --git a/app/Module/ModuleListInterface.php b/app/Module/ModuleListInterface.php new file mode 100644 index 0000000000..19feb8e84b --- /dev/null +++ b/app/Module/ModuleListInterface.php @@ -0,0 +1,70 @@ +<?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\Module; + +use Fisharebest\Webtrees\Menu; +use Fisharebest\Webtrees\Tree; + +/** + * Interface ModuleListInterface - Classes and libraries for module system + */ +interface ModuleListInterface extends ModuleInterface +{ + + /** + * A main menu item for this list, or null if the list is empty. + * + * @param Tree $tree + * + * @return Menu|null + */ + public function listMenu(Tree $tree): ?Menu; + + /** + * CSS class for the menu. + * + * @return string + */ + public function listMenuClass(): string; + + /** + * The title for a specific instance of this list. + * + * @return string + */ + public function listTitle(): string; + + /** + * The URL for a page showing list options. + * + * @param Tree $tree + * @param string[] $parameters + * + * @return string + */ + public function listUrl(Tree $tree, array $parameters = []): string; + + /** + * Attributes for the URL. + * + * @return string[] + */ + public function listUrlAttributes(): array; + + public function listIsEmpty(Tree $tree): bool; +} diff --git a/app/Module/ModuleListTrait.php b/app/Module/ModuleListTrait.php new file mode 100644 index 0000000000..9ee2f3c1fc --- /dev/null +++ b/app/Module/ModuleListTrait.php @@ -0,0 +1,100 @@ +<?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\Module; + +use Fisharebest\Webtrees\Menu; +use Fisharebest\Webtrees\Tree; + +/** + * Trait ModuleListTrait - default implementation of ModuleListInterface + */ +trait ModuleListTrait +{ + /** + * A main menu item for this list, or null if the list is empty. + * + * @param Tree $tree + * + * @return Menu|null + */ + public function listMenu(Tree $tree): ?Menu + { + if ($this->listIsEmpty($tree)) { + return null; + } + + return new Menu( + $this->title(), + $this->listUrl($tree), + $this->listMenuClass(), + $this->listUrlAttributes() + ); + } + + /** + * CSS class for the menu. + * + * @return string + */ + public function listMenuClass(): string + { + return ''; + } + + /** + * The title for a specific instance of this list. + * + * @return string + */ + public function listTitle(): string + { + return $this->title(); + } + + /** + * The URL for a page showing list options. + * + * @param Tree $tree + * @param string[] $parameters + * + * @return string + */ + public function listUrl(Tree $tree, array $parameters = []): string + { + return route('module', [ + 'module' => $this->name(), + 'action' => 'List', + 'ged' => $tree->name(), + ] + $parameters); + } + + /** + * Attributes for the URL. + * + * @return string[] + */ + public function listUrlAttributes(): array + { + return ['rel' => 'nofollow']; + } + + public function listIsEmpty(Tree $tree): bool + { + return false; + } +} diff --git a/app/Module/NoteListModule.php b/app/Module/NoteListModule.php new file mode 100644 index 0000000000..32a5450449 --- /dev/null +++ b/app/Module/NoteListModule.php @@ -0,0 +1,89 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\ListController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\IndividualListService; +use Fisharebest\Webtrees\Services\LocalizationService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Response; +use Illuminate\Database\Capsule\Manager as DB; + +/** + * Class IndividualListModule + */ +class NoteListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Shared notes'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “NoteListModule” module */ + return I18N::translate('A list of shared notes.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-note'; + } + + public function getListAction(Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new ListController(app(IndividualListService::class), app(LocalizationService::class)); + return $listController->noteList($tree); + } + + public function listUrlAttributes(): array + { + return []; + } + + public function listIsEmpty(Tree $tree): bool + { + return !DB::table('other') + ->where('o_file', '=', $tree->id()) + ->where('o_type', '=', 'NOTE') + ->exists(); + } +} diff --git a/app/Module/PlaceHierarchyListModule.php b/app/Module/PlaceHierarchyListModule.php new file mode 100644 index 0000000000..82789464ad --- /dev/null +++ b/app/Module/PlaceHierarchyListModule.php @@ -0,0 +1,81 @@ +<?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\Module; + +use Fisharebest\Webtrees\Auth; +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\PlaceHierarchyController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\SearchService; +use Fisharebest\Webtrees\Statistics; +use Fisharebest\Webtrees\Tree; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class IndividualListModule + */ +class PlaceHierarchyListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Place hierarchy'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “PlaceHierarchyListModule” module */ + return I18N::translate('The place hierarchy.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-plac'; + } + + public function getListAction(Request $request, Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new PlaceHierarchyController(app(Statistics::class)); + return $listController->show($request, $tree, app(SearchService::class)); + } + + public function listUrlAttributes(): array + { + return []; + } +} diff --git a/app/Module/RepositoryListModule.php b/app/Module/RepositoryListModule.php new file mode 100644 index 0000000000..cfaa8b72cf --- /dev/null +++ b/app/Module/RepositoryListModule.php @@ -0,0 +1,89 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\ListController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\IndividualListService; +use Fisharebest\Webtrees\Services\LocalizationService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Response; +use Illuminate\Database\Capsule\Manager as DB; + +/** + * Class RepositoryListModule + */ +class RepositoryListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Repositories'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “RepositoryListModule” module */ + return I18N::translate('A list of repositories.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-repo'; + } + + public function getListAction(Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new ListController(app(IndividualListService::class), app(LocalizationService::class)); + return $listController->repositoryList($tree); + } + + public function listUrlAttributes(): array + { + return []; + } + + public function listIsEmpty(Tree $tree): bool + { + return !DB::table('other') + ->where('o_file', '=', $tree->id()) + ->where('o_type', '=', 'REPO') + ->exists(); + } +} diff --git a/app/Module/SourceListModule.php b/app/Module/SourceListModule.php new file mode 100644 index 0000000000..f5750b857e --- /dev/null +++ b/app/Module/SourceListModule.php @@ -0,0 +1,88 @@ +<?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\Module; + +use Fisharebest\Webtrees\Contracts\UserInterface; +use Fisharebest\Webtrees\Http\Controllers\ListController; +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Services\IndividualListService; +use Fisharebest\Webtrees\Services\LocalizationService; +use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Auth; +use Symfony\Component\HttpFoundation\Response; +use Illuminate\Database\Capsule\Manager as DB; + +/** + * Class RepositoryListModule + */ +class SourceListModule extends AbstractModule implements ModuleListInterface +{ + use ModuleListTrait; + + /** + * How should this module be labelled on tabs, menus, etc.? + * + * @return string + */ + public function title(): string + { + /* I18N: Name of a module/list */ + return I18N::translate('Sources'); + } + + /** + * A sentence describing what this module does. + * + * @return string + */ + public function description(): string + { + /* I18N: Description of the “SourceListModule” module */ + return I18N::translate('A list of sources.'); + } + + /** + * CSS class for the URL. + * + * @return string + */ + public function listMenuClass(): string + { + return 'menu-list-sour'; + } + + public function getListAction(Tree $tree, UserInterface $user): Response + { + Auth::checkComponentAccess($this, 'list', $tree, $user); + + $listController = new ListController(app(IndividualListService::class), app(LocalizationService::class)); + return $listController->sourceList($tree); + } + + public function listUrlAttributes(): array + { + return []; + } + + public function listIsEmpty(Tree $tree): bool + { + return !DB::table('sources') + ->where('s_file', '=', $tree->id()) + ->exists(); + } +} diff --git a/app/Module/TopSurnamesModule.php b/app/Module/TopSurnamesModule.php index 1b52c65913..8f8d34f07e 100644 --- a/app/Module/TopSurnamesModule.php +++ b/app/Module/TopSurnamesModule.php @@ -21,6 +21,9 @@ use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Functions\FunctionsPrintLists; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Tree; +use Fisharebest\Webtrees\Module\ModuleInterface; +use Fisharebest\Webtrees\Module\IndividualListModule; +use Fisharebest\Webtrees\Services\ModuleService; use Illuminate\Database\Capsule\Manager as DB; use Symfony\Component\HttpFoundation\Request; @@ -97,25 +100,31 @@ class TopSurnamesModule extends AbstractModule implements ModuleBlockInterface $all_surnames[$top_surname] = $variants; } - + + //find a module providing individual lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof IndividualListModule; + }); + switch ($infoStyle) { case 'tagcloud': uksort($all_surnames, [I18N::class, 'strcasecmp']); - $content = FunctionsPrintLists::surnameTagCloud($all_surnames, 'individual-list', true, $tree); + $content = FunctionsPrintLists::surnameTagCloud($all_surnames, $module, true, $tree); break; case 'list': uasort($all_surnames, [$this, 'surnameCountSort']); - $content = FunctionsPrintLists::surnameList($all_surnames, 1, true, 'individual-list', $tree); + $content = FunctionsPrintLists::surnameList($all_surnames, 1, true, $module, $tree); break; case 'array': uasort($all_surnames, [$this, 'surnameCountSort']); - $content = FunctionsPrintLists::surnameList($all_surnames, 2, true, 'individual-list', $tree); + $content = FunctionsPrintLists::surnameList($all_surnames, 2, true, $module, $tree); break; case 'table': default: $content = view('lists/surnames-table', [ 'surnames' => $all_surnames, - 'route' => 'individual-list', + 'module' => $module, + 'families' => false, 'tree' => $tree, ]); break; diff --git a/app/Place.php b/app/Place.php index 70e2b42e13..a6dcba2073 100644 --- a/app/Place.php +++ b/app/Place.php @@ -17,6 +17,10 @@ declare(strict_types=1); namespace Fisharebest\Webtrees; +use Fisharebest\Webtrees\Auth; +use Fisharebest\Webtrees\Module\ModuleInterface; +use Fisharebest\Webtrees\Module\PlaceHierarchyListModule; +use Fisharebest\Webtrees\Services\ModuleService; use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Support\Collection; @@ -157,10 +161,20 @@ class Place */ public function url(): string { - return route('place-hierarchy', [ - 'parent' => $this->parts->reverse()->all(), - 'ged' => $this->tree->name(), - ]); + //find a module providing the place hierarchy + $module = app(ModuleService::class)->findByComponent('list', $this->tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof PlaceHierarchyListModule; + }); + + if ($module instanceof PlaceHierarchyListModule) { + return $module->listUrl($this->tree, [ + 'parent' => $this->parts->reverse()->all(), + 'ged' => $this->tree->name(), + ]); + } else { + //TODO: should we be allowed to return null here? + return \Fisharebest\Webtrees\Html::url('index.php', []); + } } /** diff --git a/app/Schema/Migration42.php b/app/Schema/Migration42.php new file mode 100644 index 0000000000..7635a75880 --- /dev/null +++ b/app/Schema/Migration42.php @@ -0,0 +1,62 @@ +<?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\Schema; + +use Illuminate\Database\Capsule\Manager as DB; +use Illuminate\Database\Schema\Blueprint; + +/** + * Upgrade the database schema from version 42 to version 43. + */ +class Migration42 implements MigrationInterface +{ + /** + * Upgrade to to the next version + * + * @return void + */ + public function upgrade(): void + { + //apparently not possible to change enum column directly via laravel + $data = DB::table('module_privacy')->get(); + DB::schema()->drop('module_privacy'); + + DB::schema()->create('module_privacy', function (Blueprint $table): void { + $table->string('module_name', 32); + $table->integer('gedcom_id'); + $table->enum('component', ['block', 'chart', 'list', 'menu', 'report', 'sidebar', 'tab', 'theme']); + $table->tinyInteger('access_level'); + + $table->primary(['module_name', 'gedcom_id', 'component']); + $table->unique(['gedcom_id', 'module_name', 'component']); + + $table->foreign('module_name')->references('module_name')->on('module'); + $table->foreign('gedcom_id')->references('gedcom_id')->on('gedcom'); + }); + + $rows = $data->toArray(); + foreach ($rows as $row) { + DB::table('module_privacy')->insert([ + 'module_name' => $row->module_name, + 'gedcom_id' => $row->gedcom_id, + 'component' => $row->component, + 'access_level' => $row->access_level, + ]); + } + } +} diff --git a/app/Services/ModuleService.php b/app/Services/ModuleService.php index 7e71841dd5..49baf50e5d 100644 --- a/app/Services/ModuleService.php +++ b/app/Services/ModuleService.php @@ -29,6 +29,7 @@ use Fisharebest\Webtrees\Module\BatchUpdateModule; use Fisharebest\Webtrees\Module\BingWebmasterToolsModule; use Fisharebest\Webtrees\Module\BirthDeathMarriageReportModule; use Fisharebest\Webtrees\Module\BirthReportModule; +use Fisharebest\Webtrees\Module\BranchesListModule; use Fisharebest\Webtrees\Module\CalendarMenuModule; use Fisharebest\Webtrees\Module\CemeteryReportModule; use Fisharebest\Webtrees\Module\CensusAssistantModule; @@ -51,6 +52,7 @@ use Fisharebest\Webtrees\Module\FabTheme; use Fisharebest\Webtrees\Module\FactSourcesReportModule; use Fisharebest\Webtrees\Module\FamilyBookChartModule; use Fisharebest\Webtrees\Module\FamilyGroupReportModule; +use Fisharebest\Webtrees\Module\FamilyListModule; use Fisharebest\Webtrees\Module\FamilyNavigatorModule; use Fisharebest\Webtrees\Module\FamilyTreeFavoritesModule; use Fisharebest\Webtrees\Module\FamilyTreeNewsModule; @@ -64,6 +66,7 @@ use Fisharebest\Webtrees\Module\HourglassChartModule; use Fisharebest\Webtrees\Module\HtmlBlockModule; use Fisharebest\Webtrees\Module\IndividualFactsTabModule; use Fisharebest\Webtrees\Module\IndividualFamiliesReportModule; +use Fisharebest\Webtrees\Module\IndividualListModule; use Fisharebest\Webtrees\Module\IndividualReportModule; use Fisharebest\Webtrees\Module\InteractiveTreeModule; use Fisharebest\Webtrees\Module\LifespansChartModule; @@ -72,6 +75,7 @@ use Fisharebest\Webtrees\Module\LoggedInUsersModule; use Fisharebest\Webtrees\Module\LoginBlockModule; use Fisharebest\Webtrees\Module\MarriageReportModule; use Fisharebest\Webtrees\Module\MatomoAnalyticsModule; +use Fisharebest\Webtrees\Module\MediaListModule; use Fisharebest\Webtrees\Module\MediaTabModule; use Fisharebest\Webtrees\Module\MinimalTheme; use Fisharebest\Webtrees\Module\MissingFactsReportModule; @@ -84,17 +88,20 @@ use Fisharebest\Webtrees\Module\ModuleFooterInterface; use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface; use Fisharebest\Webtrees\Module\ModuleInterface; use Fisharebest\Webtrees\Module\ModuleLanguageInterface; +use Fisharebest\Webtrees\Module\ModuleListInterface; use Fisharebest\Webtrees\Module\ModuleMenuInterface; use Fisharebest\Webtrees\Module\ModuleReportInterface; use Fisharebest\Webtrees\Module\ModuleSidebarInterface; use Fisharebest\Webtrees\Module\ModuleTabInterface; use Fisharebest\Webtrees\Module\ModuleThemeInterface; +use Fisharebest\Webtrees\Module\NoteListModule; use Fisharebest\Webtrees\Module\NotesTabModule; use Fisharebest\Webtrees\Module\OccupationReportModule; use Fisharebest\Webtrees\Module\OnThisDayModule; use Fisharebest\Webtrees\Module\PedigreeChartModule; use Fisharebest\Webtrees\Module\PedigreeMapModule; use Fisharebest\Webtrees\Module\PedigreeReportModule; +use Fisharebest\Webtrees\Module\PlaceHierarchyListModule; use Fisharebest\Webtrees\Module\PlacesModule; use Fisharebest\Webtrees\Module\PoweredByWebtreesModule; use Fisharebest\Webtrees\Module\RecentChangesModule; @@ -102,11 +109,13 @@ use Fisharebest\Webtrees\Module\RelatedIndividualsReportModule; use Fisharebest\Webtrees\Module\RelationshipsChartModule; use Fisharebest\Webtrees\Module\RelativesTabModule; use Fisharebest\Webtrees\Module\ReportsMenuModule; +use Fisharebest\Webtrees\Module\RepositoryListModule; use Fisharebest\Webtrees\Module\ResearchTaskModule; use Fisharebest\Webtrees\Module\ReviewChangesModule; use Fisharebest\Webtrees\Module\SearchMenuModule; use Fisharebest\Webtrees\Module\SiteMapModule; use Fisharebest\Webtrees\Module\SlideShowModule; +use Fisharebest\Webtrees\Module\SourceListModule; use Fisharebest\Webtrees\Module\SourcesTabModule; use Fisharebest\Webtrees\Module\StatcounterModule; use Fisharebest\Webtrees\Module\StatisticsChartModule; @@ -147,6 +156,7 @@ class ModuleService 'footer' => ModuleFooterInterface::class, 'history' => ModuleHistoricEventsInterface::class, 'language' => ModuleLanguageInterface::class, + 'list' => ModuleListInterface::class, 'menu' => ModuleMenuInterface::class, 'report' => ModuleReportInterface::class, 'sidebar' => ModuleSidebarInterface::class, @@ -163,6 +173,7 @@ class ModuleService 'bdm_report' => BirthDeathMarriageReportModule::class, 'bing-webmaster-tools' => BingWebmasterToolsModule::class, 'birth_report' => BirthReportModule::class, + 'branches_list' => BranchesListModule::class, 'calendar-menu' => CalendarMenuModule::class, 'cemetery_report' => CemeteryReportModule::class, 'change_report' => ChangeReportModule::class, @@ -184,6 +195,7 @@ class ModuleService 'fact_sources' => FactSourcesReportModule::class, 'family_book_chart' => FamilyBookChartModule::class, 'family_group_report' => FamilyGroupReportModule::class, + 'family_list' => FamilyListModule::class, 'family_nav' => FamilyNavigatorModule::class, 'fan_chart' => FanChartModule::class, 'faq' => FrequentlyAskedQuestionsModule::class, @@ -197,6 +209,7 @@ class ModuleService 'hourglass_chart' => HourglassChartModule::class, 'html' => HtmlBlockModule::class, 'individual_ext_report' => IndividualFamiliesReportModule::class, + 'individual_list' => IndividualListModule::class, 'individual_report' => IndividualReportModule::class, 'lifespans_chart' => LifespansChartModule::class, 'lightbox' => AlbumModule::class, @@ -206,15 +219,18 @@ class ModuleService 'marriage_report' => MarriageReportModule::class, 'matomo-analytics' => MatomoAnalyticsModule::class, 'media' => MediaTabModule::class, + 'media_list' => MediaListModule::class, 'minimal' => MinimalTheme::class, 'missing_facts_report' => MissingFactsReportModule::class, 'notes' => NotesTabModule::class, + 'note_list' => NoteListModule::class, 'occupation_report' => OccupationReportModule::class, 'pedigree-map' => PedigreeMapModule::class, 'pedigree_chart' => PedigreeChartModule::class, 'pedigree_report' => PedigreeReportModule::class, 'personal_facts' => IndividualFactsTabModule::class, 'places' => PlacesModule::class, + 'places_list' => PlaceHierarchyListModule::class, 'powered-by-webtrees' => PoweredByWebtreesModule::class, 'random_media' => SlideShowModule::class, 'recent_changes' => RecentChangesModule::class, @@ -222,9 +238,11 @@ class ModuleService 'relative_ext_report' => RelatedIndividualsReportModule::class, 'relatives' => RelativesTabModule::class, 'reports-menu' => ReportsMenuModule::class, + 'repository_list' => RepositoryListModule::class, 'review_changes' => ReviewChangesModule::class, 'search-menu' => SearchMenuModule::class, 'sitemap' => SiteMapModule::class, + 'source_list' => SourceListModule::class, 'sources_tab' => SourcesTabModule::class, 'statcounter' => StatcounterModule::class, 'statistics_chart' => StatisticsChartModule::class, diff --git a/app/Statistics/Repository/IndividualRepository.php b/app/Statistics/Repository/IndividualRepository.php index 1c366e4945..1433a55b6c 100644 --- a/app/Statistics/Repository/IndividualRepository.php +++ b/app/Statistics/Repository/IndividualRepository.php @@ -24,6 +24,9 @@ use Fisharebest\Webtrees\Gedcom; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Module\IndividualListModule; +use Fisharebest\Webtrees\Module\ModuleInterface; +use Fisharebest\Webtrees\Services\ModuleService; use Fisharebest\Webtrees\Statistics\Google\ChartAge; use Fisharebest\Webtrees\Statistics\Google\ChartBirth; use Fisharebest\Webtrees\Statistics\Google\ChartCommonGiven; @@ -554,11 +557,16 @@ class IndividualRepository implements IndividualRepositoryInterface break; } + //find a module providing individual lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof IndividualListModule; + }); + return FunctionsPrintLists::surnameList( $surnames, ($type === 'list' ? 1 : 2), $show_tot, - 'individual-list', + $module, $this->tree ); } diff --git a/app/Webtrees.php b/app/Webtrees.php index ef47e5a076..b9079aeb81 100644 --- a/app/Webtrees.php +++ b/app/Webtrees.php @@ -40,7 +40,7 @@ class Webtrees public const NAME = 'webtrees'; // Required version of database tables/columns/indexes/etc. - public const SCHEMA_VERSION = 42; + public const SCHEMA_VERSION = 43; // e.g. "dev", "alpha", "beta.3", etc. public const STABILITY = 'alpha.5'; diff --git a/resources/views/admin/control-panel-tree-list.phtml b/resources/views/admin/control-panel-tree-list.phtml index 25b73a4144..6d684affda 100644 --- a/resources/views/admin/control-panel-tree-list.phtml +++ b/resources/views/admin/control-panel-tree-list.phtml @@ -1,4 +1,13 @@ <?php use Fisharebest\Webtrees\I18N; ?> +<?php use Fisharebest\Webtrees\Auth; ?> +<?php use Fisharebest\Webtrees\Module\IndividualListModule; ?> +<?php use Fisharebest\Webtrees\Module\FamilyListModule; ?> +<?php use Fisharebest\Webtrees\Module\MediaListModule; ?> +<?php use Fisharebest\Webtrees\Module\ModuleInterface; ?> +<?php use Fisharebest\Webtrees\Module\NoteListModule; ?> +<?php use Fisharebest\Webtrees\Module\RepositoryListModule; ?> +<?php use Fisharebest\Webtrees\Module\SourceListModule; ?> +<?php use Fisharebest\Webtrees\Services\ModuleService; ?> <?php foreach ($all_trees as $tree) : ?> <tr class="<?= $changes[$tree->id()] ? 'danger' : '' ?>"> @@ -26,7 +35,18 @@ </td> <td class="d-none d-sm-table-cell text-right"> <?php if ($individuals[$tree->id()]) : ?> - <a href="<?= e(route('individual-list', ['ged' => $tree->name()])) ?>"> + <?php + //find a module providing individual lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof IndividualListModule; + }); + + if ($module instanceof IndividualListModule) { + echo '<a href="'.e($module->listUrl($tree)).'">'; + } else { + echo '<a>'; + } + ?> <?= I18N::number($individuals[$tree->id()]) ?> </a> <?php else : ?> @@ -35,7 +55,18 @@ </td> <td class="d-none d-lg-table-cell text-right"> <?php if ($families[$tree->id()]) : ?> - <a href="<?= e(route('family-list', ['ged' => $tree->name()])) ?>"> + <?php + //find a module providing family lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof FamilyListModule; + }); + + if ($module instanceof FamilyListModule) { + echo '<a href="'.e($module->listUrl($tree)).'">'; + } else { + echo '<a>'; + } + ?> <?= I18N::number($families[$tree->id()]) ?> </a> <?php else : ?> @@ -44,7 +75,18 @@ </td> <td class="d-none d-sm-table-cell text-right"> <?php if ($sources[$tree->id()]) : ?> - <a href="<?= e(route('source-list', ['ged' => $tree->name()])) ?>"> + <?php + //find a module providing source lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof SourceListModule; + }); + + if ($module instanceof SourceListModule) { + echo '<a href="'.e($module->listUrl($tree)).'">'; + } else { + echo '<a>'; + } + ?> <?= I18N::number($sources[$tree->id()]) ?> </a> <?php else : ?> @@ -53,7 +95,18 @@ </td> <td class="d-none d-lg-table-cell text-right"> <?php if ($repositories[$tree->id()]) : ?> - <a href="<?= e(route('repository-list', ['ged' => $tree->name()])) ?>"> + <?php + //find a module providing repository lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof RepositoryListModule; + }); + + if ($module instanceof RepositoryListModule) { + echo '<a href="'.e($module->listUrl($tree)).'">'; + } else { + echo '<a>'; + } + ?> <?= I18N::number($repositories[$tree->id()]) ?> </a> <?php else : ?> @@ -62,7 +115,18 @@ </td> <td class="d-none d-sm-table-cell text-right"> <?php if ($media[$tree->id()]) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name()])) ?>"> + <?php + //find a module providing media lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof MediaListModule; + }); + + if ($module instanceof MediaListModule) { + echo '<a href="'.e($module->listUrl($tree)).'">'; + } else { + echo '<a>'; + } + ?> <?= I18N::number($media[$tree->id()]) ?> </a> <?php else : ?> @@ -71,7 +135,18 @@ </td> <td class="d-none d-lg-table-cell text-right"> <?php if ($notes[$tree->id()]) : ?> - <a href="<?= e(route('note-list', ['ged' => $tree->name()])) ?>"> + <?php + //find a module providing note lists + $module = app(ModuleService::class)->findByComponent('list', $tree, Auth::user())->first(function (ModuleInterface $module) { + return $module instanceof NoteListModule; + }); + + if ($module instanceof NoteListModule) { + echo '<a href="'.e($module->listUrl($tree)).'">'; + } else { + echo '<a>'; + } + ?> <?= I18N::number($media[$tree->id()]) ?> </a> <?php else : ?> diff --git a/resources/views/admin/control-panel.phtml b/resources/views/admin/control-panel.phtml index 9f9c09e6ab..20bc5d4103 100644 --- a/resources/views/admin/control-panel.phtml +++ b/resources/views/admin/control-panel.phtml @@ -331,6 +331,13 @@ <?= view('components/badge', ['count' => $chart_modules->count(), 'context' => 'primary']) ?> </li> <li> + <span class="fa-li"><?= view('icons/list') ?></span> + <a href="<?= e(route('lists')) ?>"> + <?= I18N::translate('Lists') ?> + </a> + <?= view('components/badge', ['count' => $list_modules->count(), 'context' => 'primary']) ?> + </li> + <li> <span class="fa-li"><?= view('icons/report') ?></span> <a href="<?= e(route('reports')) ?>"> <?= I18N::translate('Reports') ?> diff --git a/resources/views/admin/modules.phtml b/resources/views/admin/modules.phtml index 0f168bc6e8..b969fe4948 100644 --- a/resources/views/admin/modules.phtml +++ b/resources/views/admin/modules.phtml @@ -9,6 +9,7 @@ <?php use Fisharebest\Webtrees\Module\ModuleFooterInterface; ?> <?php use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface; ?> <?php use Fisharebest\Webtrees\Module\ModuleLanguageInterface; ?> +<?php use Fisharebest\Webtrees\Module\ModuleListInterface; ?> <?php use Fisharebest\Webtrees\Module\ModuleMenuInterface; ?> <?php use Fisharebest\Webtrees\Module\ModuleReportInterface; ?> <?php use Fisharebest\Webtrees\Module\ModuleSidebarInterface; ?> @@ -75,6 +76,10 @@ <?= view('icons/chart') ?> <span class="sr-only"><?= I18N::translate('Charts') ?></span> </th> + <th title="<?= I18N::translate('Lists') ?>"> + <?= view('icons/list') ?> + <span class="sr-only"><?= I18N::translate('Lists') ?></span> + </th> <th title="<?= I18N::translate('Reports') ?>"> <?= view('icons/report') ?> <span class="sr-only"><?= I18N::translate('Reports') ?></span> @@ -198,6 +203,14 @@ - <?php endif ?> </td> + <td class="text-center text-muted" title="<?= I18N::translate('List') ?>"> + <?php if ($module instanceof ModuleListInterface) : ?> + <?= view('icons/list') ?> + <span class="sr-only"><?= I18N::translate('List') ?></span> + <?php else : ?> + - + <?php endif ?> + </td> <td class="text-center text-muted" title="<?= I18N::translate('Report') ?>"> <?php if ($module instanceof ModuleReportInterface) : ?> <?= view('icons/report') ?> diff --git a/resources/views/branches-page.phtml b/resources/views/branches-page.phtml index da0f6e5c83..2235d19b2c 100644 --- a/resources/views/branches-page.phtml +++ b/resources/views/branches-page.phtml @@ -6,7 +6,9 @@ </h2> <form class="wt-page-options wt-page-options-branches d-print-none"> - <input type="hidden" name="route" value="branches"> + <input type="hidden" name="route" value="module"> + <input type="hidden" name="module" value="<?= e($module) ?>"> + <input type="hidden" name="action" value="<?= e($action) ?>"> <input type="hidden" name="ged" value="<?= e($tree->name()) ?>"> <div class="form-group row"> @@ -42,5 +44,5 @@ </form> <?php if ($surname !== '') : ?> - <div class="wt-ajax-load wt-page-content wt-chart wt-branches" data-ajax-url="<?= e(route('branches-list', ['surname' => $surname, 'soundex_std' => $soundex_std, 'soundex_dm' => $soundex_dm, 'ged' => $tree->name()])) ?>"></div> + <div class="wt-ajax-load wt-page-content wt-chart wt-branches" data-ajax-url="<?= e(route('module', ['module' => $module, 'action' => 'List', 'surname' => $surname, 'soundex_std' => $soundex_std, 'soundex_dm' => $soundex_dm, 'ged' => $tree->name()])) ?>"></div> <?php endif ?> diff --git a/resources/views/icons/list.phtml b/resources/views/icons/list.phtml new file mode 100644 index 0000000000..2d0344f114 --- /dev/null +++ b/resources/views/icons/list.phtml @@ -0,0 +1 @@ +<i class="fas fa-list fa-fw wt-icon-list" aria-hidden="true"></i> diff --git a/resources/views/lists/surnames-table.phtml b/resources/views/lists/surnames-table.phtml index 19bac44961..8e6a30c654 100644 --- a/resources/views/lists/surnames-table.phtml +++ b/resources/views/lists/surnames-table.phtml @@ -10,7 +10,7 @@ <?= I18N::translate('Surname') ?> </th> <th> - <?php if ($route == 'family-list') :?> + <?php if ($families) :?> <?= I18N::translate('Spouses') ?> <?php else : ?> <?= I18N::translate('Individuals') ?> @@ -25,19 +25,28 @@ <td data-sort="<?= e($surn) ?>"> <!-- Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc. --> <?php foreach ($surns as $spfxsurn => $indis) : ?> - <?php if ($spfxsurn) : ?> - <?php if ($surn !== '') : ?> - <a href="<?= route($route, ['surname' => $surn, 'ged' => $tree->name()]) ?>" dir="auto"> - <?= e($spfxsurn) ?> - </a> + <?php if ($module instanceof IndividualListModule) : ?> + <?php if ($spfxsurn) : ?> + <?php if ($surn !== '') : ?> + <a href="<?= $module->listUrl($tree, ['surname' => $surn]) ?>" dir="auto"> + <?= e($spfxsurn) ?> + </a> + <?php else : ?> + <a href="<?= $module->listUrl($tree, ['alpha' => ',']) ?>" dir="auto"> + <?= e($spfxsurn) ?> + </a> + <?php endif ?> <?php else : ?> - <a href="<?= route($route, ['alpha' => ',', 'ged' => $tree->name()]) ?>" dir="auto"> - <?= e($spfxsurn) ?> - </a> + <!-- No surname, but a value from "2 SURN"? A common workaround for toponyms, etc. --> + <a href="<?= $module->listUrl($tree, ['surname' => $surn]) ?>" dir="auto"><?= e($surn) ?></a> <?php endif ?> <?php else : ?> - <!-- No surname, but a value from "2 SURN"? A common workaround for toponyms, etc. --> - <a href="<?= route($route, ['surname' => $surn, 'ged' => $tree->name()]) ?>" dir="auto"><?= e($surn) ?></a> + <?php if ($spfxsurn) : ?> + <span dir="auto"><?= e($spfxsurn) ?></span> + <?php else : ?> + <!-- No surname, but a value from "2 SURN"? A common workaround for toponyms, etc. --> + <span dir="auto"><?= e($surn) ?></span> + <?php endif ?> <?php endif ?> <br> <?php endforeach ?> diff --git a/resources/views/media-list-page.phtml b/resources/views/media-list-page.phtml index 9a216e78ff..8090ed4a9c 100644 --- a/resources/views/media-list-page.phtml +++ b/resources/views/media-list-page.phtml @@ -12,8 +12,10 @@ <form class="wt-page-options wt-page-options-media-list d-print-none"> <input type="hidden" name="ged" value="<?= e($tree->name()) ?>"> - <input type="hidden" name="route" value="media-list"> - <input type="hidden" name="action" value="1"> + <input type="hidden" name="route" value="module"> + <input type="hidden" name="module" value="<?= e($module) ?>"> + <input type="hidden" name="action" value="<?= e($action) ?>"> + <input type="hidden" name="action2" value="1"> <input type="hidden" name="search" value="yes"> <div class="row form-group"> @@ -58,10 +60,10 @@ <div class="col-sm-3 col-form-label wt-page-options-label"> </div> <div class="col-sm-3 wt-page-options-value"> - <button type="submit" name="action" value="1" class="btn btn-primary"> + <button type="submit" name="action2" value="1" class="btn btn-primary"> <?= /* I18N: A button label. */ I18N::translate('search') ?> </button> - <a class="btn btn-secondary" href="<?= e(route('media-list', ['ged' => $tree->name()])) ?>"> + <a class="btn btn-secondary" href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name()])) ?>"> <?= /* I18N: A button label. */ I18N::translate('reset') ?> </a> </div> @@ -75,14 +77,14 @@ <div class="row text-center"> <div class="col"> <?php if ($page > 1) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => 1])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => 1])) ?>"> <?= I18N::translate('first') ?> </a> <?php endif ?> </div> <div class="col"> <?php if ($page > 1) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page - 1])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page - 1])) ?>"> <?= I18N::translate('previous') ?> </a> <?php endif ?> @@ -92,14 +94,14 @@ </div> <div class="col"> <?php if ($page < $pages) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page + 1])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page + 1])) ?>"> <?= I18N::translate('next') ?> </a> <?php endif ?> </div> <div class="col"> <?php if ($page < $pages) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $pages])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $pages])) ?>"> <?= I18N::translate('last') ?> </a> <?php endif ?> @@ -181,14 +183,14 @@ <div class="row text-center"> <div class="col"> <?php if ($page > 1) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => 1])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => 1])) ?>"> <?= I18N::translate('first') ?> </a> <?php endif ?> </div> <div class="col"> <?php if ($page > 1) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page - 1])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page - 1])) ?>"> <?= I18N::translate('previous') ?> </a> <?php endif ?> @@ -198,14 +200,14 @@ </div> <div class="col"> <?php if ($page < $pages) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page + 1])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $page + 1])) ?>"> <?= I18N::translate('next') ?> </a> <?php endif ?> </div> <div class="col"> <?php if ($page < $pages) : ?> - <a href="<?= e(route('media-list', ['ged' => $tree->name(), 'action' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $pages])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => '1', 'folder' => $folder, 'subdirs' => $subdirs, 'filter' => $filter, 'form_type' => $form_type, 'max' => $max, 'page' => $pages])) ?>"> <?= I18N::translate('last') ?> </a> <?php endif ?> diff --git a/resources/views/places-page.phtml b/resources/views/places-page.phtml index 0f5a26ff18..2648f77fe5 100644 --- a/resources/views/places-page.phtml +++ b/resources/views/places-page.phtml @@ -5,7 +5,7 @@ <h4><?= $title ?></h4> <h5 class="text-center"> <?php if ($current) : ?> - <a href="<?= e(route('place-hierarchy', ['ged' => $tree->name()])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name()])) ?>"> <?= I18N::translate('World') ?> </a> <?php else : ?> @@ -29,13 +29,13 @@ <?= $content ?> <div class="text-center"> <?php if ($showeventslink) : ?> - <a class="formField" href= <?= e(route('place-hierarchy', ['ged' => $tree->name(), 'parent' => $parent, 'action' => 'hierarchy-e'])) ?>> + <a class="formField" href= <?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'parent' => $parent, 'action2' => 'hierarchy-e'])) ?>> <?= I18N::translate('View table of events occurring in %s', $place) ?> </a> | <?php endif ?> - <a href="<?= e(route('place-hierarchy', ['ged' => $tree->name(), 'action' => key($nextaction)])) ?>"> + <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'ged' => $tree->name(), 'action2' => key($nextaction)])) ?>"> <?= current($nextaction) ?> </a> </div> diff --git a/routes/web.php b/routes/web.php index 6bee6539a5..4e862f7031 100644 --- a/routes/web.php +++ b/routes/web.php @@ -41,6 +41,8 @@ if (Auth::isAdmin()) { 'POST:blocks' => 'Admin\\ModuleController@updateBlocks', 'GET:charts' => 'Admin\\ModuleController@listCharts', 'POST:charts' => 'Admin\\ModuleController@updateCharts', + 'GET:lists' => 'Admin\\ModuleController@listLists', + 'POST:lists' => 'Admin\\ModuleController@updateLists', 'GET:footers' => 'Admin\\ModuleController@listFooters', 'POST:footers' => 'Admin\\ModuleController@updateFooters', 'GET:history' => 'Admin\\ModuleController@listHistory', @@ -255,8 +257,8 @@ if ($tree instanceof Tree && $tree->getPreference('imported') === '1') { 'GET:autocomplete-folder' => 'AutocompleteController@folder', 'GET:autocomplete-page' => 'AutocompleteController@page', 'GET:autocomplete-place' => 'AutocompleteController@place', - 'GET:branches' => 'BranchesController@page', - 'GET:branches-list' => 'BranchesController@list', + //'GET:branches' => 'BranchesController@page', + //'GET:branches-list' => 'BranchesController@list', 'GET:calendar' => 'CalendarController@page', 'GET:calendar-events' => 'CalendarController@calendar', 'GET:help-text' => 'HelpTextController@helpText', @@ -279,13 +281,13 @@ if ($tree instanceof Tree && $tree->getPreference('imported') === '1') { 'GET:report-list' => 'ReportEngineController@reportList', 'GET:report-setup' => 'ReportEngineController@reportSetup', 'GET:report-run' => 'ReportEngineController@reportRun', - 'GET:family-list' => 'ListController@familyList', - 'GET:individual-list' => 'ListController@individualList', - 'GET:media-list' => 'ListController@mediaList', - 'GET:note-list' => 'ListController@noteList', - 'GET:place-hierarchy' => 'PlaceHierarchyController@show', - 'GET:repository-list' => 'ListController@repositoryList', - 'GET:source-list' => 'ListController@sourceList', + //'GET:family-list' => 'ListController@familyList', + //'GET:individual-list' => 'ListController@individualList', + //'GET:media-list' => 'ListController@mediaList', + //'GET:note-list' => 'ListController@noteList', + //'GET:place-hierarchy' => 'PlaceHierarchyController@show', + //'GET:repository-list' => 'ListController@repositoryList', + //'GET:source-list' => 'ListController@sourceList', 'POST:accept-changes' => 'PendingChangesController@acceptChanges', 'POST:reject-changes' => 'PendingChangesController@rejectChanges', 'POST:accept-all-changes' => 'PendingChangesController@acceptAllChanges', diff --git a/tests/feature/IndividualListTest.php b/tests/feature/IndividualListTest.php index d454034cf7..ac17907d6d 100644 --- a/tests/feature/IndividualListTest.php +++ b/tests/feature/IndividualListTest.php @@ -51,23 +51,23 @@ class IndividualListTest extends TestCase $individual_list_service = new IndividualListService($localization_service, $tree); $controller = new ListController($individual_list_service, $localization_service); - $request = new Request(['route' => 'individual-list']); + $request = new Request(['route' => 'module', 'module' => 'individual_list', 'action' => 'List']); $response = $controller->individualList($request, $tree, $user); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); - $request = new Request(['route' => 'individual-list', 'alpha' => 'B']); + $request = new Request(['route' => 'module', 'module' => 'individual_list', 'action' => 'List', 'alpha' => 'B']); $response = $controller->individualList($request, $tree, $user); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); - $request = new Request(['route' => 'individual-list', 'alpha' => ',']); + $request = new Request(['route' => 'module', 'module' => 'individual_list', 'action' => 'List', 'alpha' => ',']); $response = $controller->individualList($request, $tree, $user); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); - $request = new Request(['route' => 'individual-list', 'alpha' => '@']); + $request = new Request(['route' => 'module', 'module' => 'individual_list', 'action' => 'List', 'alpha' => '@']); $response = $controller->individualList($request, $tree, $user); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); - $request = new Request(['route' => 'individual-list', 'surname' => 'BRAUN']); + $request = new Request(['route' => 'module', 'module' => 'individual_list', 'action' => 'List', 'surname' => 'BRAUN']); $response = $controller->individualList($request, $tree, $user); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); } |
