summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Roach <fisharebest@webtrees.net>2019-01-26 16:55:54 +0000
committerGreg Roach <fisharebest@webtrees.net>2019-01-26 16:55:54 +0000
commit0cb66f863d2cdb6916511594d43e7ac87a6b59bb (patch)
tree5b081c32157a00b5bf7af423a9d2ca9050182d9d
parent17c50b57b14adeaa197eed2530436177c4db8f6b (diff)
downloadwebtrees-0cb66f863d2cdb6916511594d43e7ac87a6b59bb.tar.gz
webtrees-0cb66f863d2cdb6916511594d43e7ac87a6b59bb.tar.bz2
webtrees-0cb66f863d2cdb6916511594d43e7ac87a6b59bb.zip
Fix: #2129 - module management
-rw-r--r--app/Http/Controllers/Admin/ModuleController.php383
-rw-r--r--app/Http/Controllers/AdminController.php250
-rw-r--r--app/Module.php8
-rw-r--r--modules_v4/historic-events.example/module.php78
-rw-r--r--resources/views/admin/components.phtml149
-rw-r--r--resources/views/admin/control-panel.phtml31
-rw-r--r--resources/views/admin/module-components.phtml63
-rw-r--r--resources/views/admin/modules.phtml147
-rw-r--r--resources/views/modules/batch_update/admin.phtml2
-rw-r--r--resources/views/modules/faq/config.phtml2
-rw-r--r--resources/views/modules/faq/edit.phtml2
-rw-r--r--resources/views/modules/relationships-chart/config.phtml2
-rw-r--r--resources/views/modules/sitemap/config.phtml2
-rw-r--r--resources/views/modules/stories/config.phtml2
-rw-r--r--resources/views/modules/stories/edit.phtml2
-rw-r--r--routes/web.php21
16 files changed, 729 insertions, 415 deletions
diff --git a/app/Http/Controllers/Admin/ModuleController.php b/app/Http/Controllers/Admin/ModuleController.php
new file mode 100644
index 0000000000..3d34df35ca
--- /dev/null
+++ b/app/Http/Controllers/Admin/ModuleController.php
@@ -0,0 +1,383 @@
+<?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\Controllers\Admin;
+
+use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Module\ModuleBlockInterface;
+use Fisharebest\Webtrees\Module\ModuleChartInterface;
+use Fisharebest\Webtrees\Module\ModuleInterface;
+use Fisharebest\Webtrees\Module\ModuleMenuInterface;
+use Fisharebest\Webtrees\Module\ModuleReportInterface;
+use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
+use Fisharebest\Webtrees\Module\ModuleTabInterface;
+use Fisharebest\Webtrees\Tree;
+use Illuminate\Database\Capsule\Manager as DB;
+use Illuminate\Support\Collection;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller for configuring the modules.
+ */
+class ModuleController extends AbstractAdminController
+{
+ /**
+ * Show the administrator a list of modules.
+ *
+ * @return Response
+ */
+ public function list(): Response
+ {
+ return $this->viewResponse('admin/modules', [
+ 'title' => I18N::translate('Module administration'),
+ 'modules' => Module::all(),
+ 'deleted_modules' => $this->deletedModuleNames(),
+ ]);
+ }
+
+ /**
+ * Generate a list of module names which exist in the database but not on disk.
+ *
+ * @return Collection|string[]
+ */
+ public function deletedModuleNames(): Collection
+ {
+ $database_modules = DB::table('module')->pluck('module_name');
+
+ $disk_modules = Module::all()
+ ->map(function (ModuleInterface $module): string {
+ return $module->name();
+ });
+
+ return $database_modules->diff($disk_modules);
+ }
+
+ /**
+ * @return Response
+ */
+ public function listBlocks(): Response
+ {
+ return $this->listComponents(ModuleBlockInterface::class, 'block', I18N::translate('Blocks'));
+ }
+
+ /**
+ * @return Response
+ */
+ public function listCharts(): Response
+ {
+ return $this->listComponents(ModuleChartInterface::class, 'chart', I18N::translate('Charts'));
+ }
+
+ /**
+ * @return Response
+ */
+ public function listMenus(): Response
+ {
+ return $this->listComponents(ModuleMenuInterface::class, 'menu', I18N::translate('Menus'));
+ }
+
+ /**
+ * @return Response
+ */
+ public function listReports(): Response
+ {
+ return $this->listComponents(ModuleReportInterface::class, 'report', I18N::translate('Reports'));
+ }
+
+ /**
+ * @return Response
+ */
+ public function listSidebars(): Response
+ {
+ return $this->listComponents(ModuleSidebarInterface::class, 'sidebar', I18N::translate('Sidebars'));
+ }
+
+ /**
+ * @return Response
+ */
+ public function listTabs(): Response
+ {
+ return $this->listComponents(ModuleTabInterface::class, 'tab', I18N::translate('Tabs'));
+ }
+
+ /**
+ * @param string $interface
+ * @param string $component
+ * @param string $title
+ *
+ * @return Response
+ */
+ private function listComponents(string $interface, string $component, string $title): Response
+ {
+ return $this->viewResponse('admin/components', [
+ 'component' => $component,
+ 'modules' => Module::findByInterface($interface, true),
+ 'title' => $title,
+ 'trees' => Tree::all(),
+ ]);
+ }
+
+ /**
+ * Update the enabled/disabled status of the modules.
+ *
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function update(Request $request): RedirectResponse
+ {
+ $modules = Module::all();
+
+ foreach ($modules as $module) {
+ $new_status = (bool) $request->get('status-' . $module->name());
+ $old_status = $module->isEnabled();
+
+ if ($new_status !== $old_status) {
+ DB::table('module')
+ ->where('module_name', '=', $module->name())
+ ->update(['status' => $new_status ? 'enabled' : 'disabled']);
+
+ if ($new_status) {
+ FlashMessages::addMessage(I18N::translate('The module “%s” has been enabled.', $module->title()), 'success');
+ } else {
+ FlashMessages::addMessage(I18N::translate('The module “%s” has been disabled.', $module->title()), 'success');
+ }
+ }
+ }
+
+ return new RedirectResponse(route('modules'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function updateBlocks(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleBlockInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateAccessLevel($modules, 'block', $request);
+
+ return new RedirectResponse(route('blocks'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function updateCharts(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleChartInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateAccessLevel($modules, 'chart', $request);
+
+ return new RedirectResponse(route('charts'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function updateMenus(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleMenuInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateOrder($modules, 'menu_order', $request);
+ $this->updateAccessLevel($modules, 'menu', $request);
+
+ return new RedirectResponse(route('menus'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function updateReports(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleReportInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateAccessLevel($modules, 'report', $request);
+
+ return new RedirectResponse(route('reports'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function updateSidebars(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleSidebarInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateOrder($modules, 'sidebar_order', $request);
+ $this->updateAccessLevel($modules, 'sidebar', $request);
+
+ return new RedirectResponse(route('sidebars'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function updateTabs(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleTabInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateOrder($modules, 'tab_order', $request);
+ $this->updateAccessLevel($modules, 'tab', $request);
+
+ return new RedirectResponse(route('tabs'));
+ }
+
+ /**
+ * Update the access levels of the modules.
+ *
+ * @param Collection $modules
+ * @param string $column
+ * @param Request $request
+ *
+ * @return void
+ */
+ private function updateOrder(Collection $modules, string $column, Request $request): void
+ {
+ $order = (array) $request->get('order');
+ $order = array_flip($order);
+
+ foreach ($modules as $module) {
+ DB::table('module')
+ ->where('module_name', '=', $module->name())
+ ->update([
+ $column => $order[$module->name()] ?? 0
+ ]);
+ }
+ }
+
+ /**
+ * Update the access levels of the modules.
+ *
+ * @param Collection $modules
+ * @param Request $request
+ *
+ * @return void
+ */
+ private function updateStatus(Collection $modules, Request $request): void
+ {
+ foreach ($modules as $module) {
+ $enabled = (bool) $request->get('status-' . $module->name());
+
+ if ($enabled !== $module->isEnabled()) {
+ DB::table('module')
+ ->where('module_name', '=', $module->name())
+ ->update(['status' => $enabled ? 'enabled' : 'disabled']);
+
+ if ($enabled) {
+ $message = I18N::translate('The module “%s” has been enabled.', $module->title());
+ } else {
+ $message = I18N::translate('The module “%s” has been disabled.', $module->title());
+ }
+
+ FlashMessages::addMessage($message, 'success');
+ }
+ }
+ }
+
+ /**
+ * Update the access levels of the modules.
+ *
+ * @param Collection $modules
+ * @param string $component
+ * @param Request $request
+ *
+ * @return void
+ */
+ private function updateAccessLevel(Collection $modules, string $component, Request $request): void
+ {
+ $trees = Tree::all();
+
+ foreach ($modules as $module) {
+ foreach ($trees as $tree) {
+ $key = 'access-' . $module->name() . '-' . $tree->id();
+ $access_level = (int) $request->get($key);
+
+ if ($access_level !== $module->accessLevel($tree, $component)) {
+ DB::table('module_privacy')->updateOrInsert([
+ 'module_name' => $module->name(),
+ 'gedcom_id' => $tree->id(),
+ 'component' => $component,
+ ], [
+ 'access_level' => $access_level,
+ ]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Delete the database settings for a deleted module.
+ *
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
+ public function deleteModuleSettings(Request $request): RedirectResponse
+ {
+ $module_name = $request->get('module_name');
+
+ DB::table('block_setting')
+ ->join('block', 'block_setting.block_id', '=', 'block.block_id')
+ ->join('module', 'block.module_name', '=', 'module.module_name')
+ ->where('module.module_name', '=', $module_name)
+ ->delete();
+
+ DB::table('block')
+ ->join('module', 'block.module_name', '=', 'module.module_name')
+ ->where('module.module_name', '=', $module_name)
+ ->delete();
+
+ DB::table('module_setting')
+ ->where('module_name', '=', $module_name)
+ ->delete();
+
+ DB::table('module_privacy')
+ ->where('module_name', '=', $module_name)
+ ->delete();
+
+ DB::table('module')
+ ->where('module_name', '=', $module_name)
+ ->delete();
+
+ FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been deleted.', $module_name), 'success');
+
+ return new RedirectResponse(route('modules'));
+ }
+}
diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php
index ee46392691..0616f4a367 100644
--- a/app/Http/Controllers/AdminController.php
+++ b/app/Http/Controllers/AdminController.php
@@ -32,14 +32,7 @@ use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Media;
use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\ModuleBlockInterface;
-use Fisharebest\Webtrees\Module\ModuleChartInterface;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
-use Fisharebest\Webtrees\Module\ModuleInterface;
-use Fisharebest\Webtrees\Module\ModuleMenuInterface;
-use Fisharebest\Webtrees\Module\ModuleReportInterface;
-use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
-use Fisharebest\Webtrees\Module\ModuleTabInterface;
use Fisharebest\Webtrees\Note;
use Fisharebest\Webtrees\Repository;
use Fisharebest\Webtrees\Services\DatatablesService;
@@ -75,37 +68,23 @@ class AdminController extends AbstractBaseController
protected $layout = 'layouts/administration';
/**
- * Show the admin page for blocks.
- *
- * @return Response
- */
- public function blocks(): Response
- {
- return $this->components(ModuleBlockInterface::class, 'block', 'blocks', I18N::translate('Block'), I18N::translate('Blocks'));
- }
-
- /**
- * Show the admin page for charts.
- *
- * @return Response
- */
- public function charts(): Response
- {
- return $this->components(ModuleChartInterface::class, 'chart', 'charts', I18N::translate('Chart'), I18N::translate('Charts'));
- }
-
- /**
* The control panel shows a summary of the site and links to admin functions.
*
- * @param HousekeepingService $housekeeping_service
- * @param UpgradeService $upgrade_service
+ * @param HousekeepingService $housekeeping_service
+ * @param UpgradeService $upgrade_service
+ * @param Admin\ModuleController $module_controller
*
* @return Response
*/
- public function controlPanel(HousekeepingService $housekeeping_service, UpgradeService $upgrade_service): Response
+ public function controlPanel(
+ HousekeepingService $housekeeping_service,
+ UpgradeService $upgrade_service,
+ Admin\ModuleController $module_controller
+ ): Response
{
$filesystem = new Filesystem(new Local(WT_ROOT));
$files_to_delete = $housekeeping_service->deleteOldWebtreesFiles($filesystem);
+ $deleted_modules = $module_controller->deletedModuleNames();
return $this->viewResponse('admin/control-panel', [
'title' => I18N::translate('Control panel'),
@@ -127,7 +106,7 @@ class AdminController extends AbstractBaseController
'notes' => $this->totalNotes(),
'files_to_delete' => $files_to_delete,
'all_modules' => Module::all(),
- 'deleted_modules' => $this->deletedModuleNames(),
+ 'deleted_modules' => $deleted_modules,
'config_modules' => Module::findByInterface(ModuleConfigInterface::class),
]);
}
@@ -194,7 +173,6 @@ class AdminController extends AbstractBaseController
$earliest = $earliest->toDateString();
$latest = $latest->toDateString();
-
$ged = $request->get('ged');
$from = $request->get('from', $earliest);
$to = $request->get('to', $latest);
@@ -335,45 +313,6 @@ class AdminController extends AbstractBaseController
}
/**
- * Delete the database settings for a deleted module.
- *
- * @param Request $request
- *
- * @return RedirectResponse
- */
- public function deleteModuleSettings(Request $request): RedirectResponse
- {
- $module_name = $request->get('module_name');
-
- DB::table('block_setting')
- ->join('block', 'block_setting.block_id', '=', 'block.block_id')
- ->join('module', 'block.module_name', '=', 'module.module_name')
- ->where('module.module_name', '=', $module_name)
- ->delete();
-
- DB::table('block')
- ->join('module', 'block.module_name', '=', 'module.module_name')
- ->where('module.module_name', '=', $module_name)
- ->delete();
-
- DB::table('module_setting')
- ->where('module_name', '=', $module_name)
- ->delete();
-
- DB::table('module_privacy')
- ->where('module_name', '=', $module_name)
- ->delete();
-
- DB::table('module')
- ->where('module_name', '=', $module_name)
- ->delete();
-
- FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been deleted.', $module_name), 'success');
-
- return new RedirectResponse(route('admin-modules'));
- }
-
- /**
* If media objects are wronly linked to top-level records, reattach them
* to facts/events.
*
@@ -621,7 +560,7 @@ class AdminController extends AbstractBaseController
$data = array_map(function (string $thumbnail): array {
$original = $this->findOriginalFileFromThumbnail($thumbnail);
- $original_url = route('unused-media-thumbnail', [
+ $original_url = route('unused-media-thumbnail', [
'folder' => dirname($original),
'file' => basename($original),
'w' => 100,
@@ -883,60 +822,6 @@ class AdminController extends AbstractBaseController
}
/**
- * Show the administrator a list of modules.
- *
- * @return Response
- */
- public function modules(): Response
- {
- return $this->viewResponse('admin/modules', [
- 'title' => I18N::translate('Module administration'),
- 'modules' => Module::all(),
- 'deleted_modules' => $this->deletedModuleNames(),
- ]);
- }
-
- /**
- * Show the admin page for menus.
- *
- * @return Response
- */
- public function menus(): Response
- {
- return $this->components(ModuleMenuInterface::class, 'menu', 'menus', I18N::translate('Menu'), I18N::translate('Menus'));
- }
-
- /**
- * Show the admin page for reports.
- *
- * @return Response
- */
- public function reports(): Response
- {
- return $this->components(ModuleReportInterface::class, 'report', 'reports', I18N::translate('Report'), I18N::translate('Reports'));
- }
-
- /**
- * Show the admin page for sidebars.
- *
- * @return Response
- */
- public function sidebars(): Response
- {
- return $this->components(ModuleSidebarInterface::class, 'sidebar', 'sidebars', I18N::translate('Sidebar'), I18N::translate('Sidebars'));
- }
-
- /**
- * Show the admin page for tabs.
- *
- * @return Response
- */
- public function tabs(): Response
- {
- return $this->components(ModuleTabInterface::class, 'tab', 'tabs', I18N::translate('Tab'), I18N::translate('Tabs'));
- }
-
- /**
* @param Tree $tree
*
* @return Response
@@ -997,10 +882,10 @@ class AdminController extends AbstractBaseController
// Add (or update) the new data
DB::table('default_resn')->insert([
- 'gedcom_id' => $tree->id(),
- 'xref' => $xref,
- 'tag_type' => $tag_type,
- 'resn' => $resn,
+ 'gedcom_id' => $tree->id(),
+ 'xref' => $xref,
+ 'tag_type' => $tag_type,
+ 'resn' => $resn,
]);
}
}
@@ -1024,73 +909,10 @@ class AdminController extends AbstractBaseController
FlashMessages::addMessage(I18N::translate('The preferences for new family trees have been updated.', e($tree->title())), 'success');
}
-
return new RedirectResponse(route('admin-trees', ['ged' => $tree->name()]));
}
/**
- * Update the access levels of the modules.
- *
- * @param Request $request
- *
- * @return RedirectResponse
- */
- public function updateModuleAccess(Request $request): RedirectResponse
- {
- $component = $request->get('component');
- $interface = $request->get('interface');
- $modules = Module::findByInterface($interface);
-
- foreach ($modules as $module) {
- foreach (Tree::getAll() as $tree) {
- $key = 'access-' . $module->name() . '-' . $tree->id();
- $access_level = (int) $request->get($key);
-
- DB::table('module_privacy')->updateOrInsert([
- 'module_name' => $module->name(),
- 'gedcom_id' => $tree->id(),
- 'component' => $component,
- ], [
- 'access_level' => $access_level,
- ]);
- }
- }
-
- return new RedirectResponse(route('admin-' . $component . 's'));
- }
-
- /**
- * Update the enabled/disabled status of the modules.
- *
- * @param Request $request
- *
- * @return RedirectResponse
- */
- public function updateModuleStatus(Request $request): RedirectResponse
- {
- $modules = Module::all();
-
- foreach ($modules as $module) {
- $new_status = (bool) $request->get('status-' . $module->name());
- $old_status = $module->isEnabled();
-
- if ($new_status !== $old_status) {
- DB::table('module')
- ->where('module_name', '=', $module->name())
- ->update(['status' => $new_status ? 'enabled' : 'disabled']);
-
- if ($new_status) {
- FlashMessages::addMessage(I18N::translate('The module “%s” has been enabled.', $module->title()), 'success');
- } else {
- FlashMessages::addMessage(I18N::translate('The module “%s” has been disabled.', $module->title()), 'success');
- }
- }
- }
-
- return new RedirectResponse(route('admin-modules'));
- }
-
- /**
* Create a response object from a view.
*
* @param string $name
@@ -1178,46 +1000,6 @@ class AdminController extends AbstractBaseController
}
/**
- * Show the admin page for blocks, charts, menus, reports, sidebars, tabs, etc..
- *
- * @param string $interface
- * @param string $component
- * @param string $route
- * @param string $component_title
- * @param string $title
- *
- * @return Response
- */
- private function components(string $interface, string $component, string $route, string $component_title, string $title): Response
- {
- return $this->viewResponse('admin/module-components', [
- 'component' => $component,
- 'component_title' => $component_title,
- 'interface' => $interface,
- 'modules' => Module::findByInterface($interface),
- 'title' => $title,
- 'route' => $route,
- ]);
- }
-
- /**
- * Generate a list of module names which exist in the database but not on disk.
- *
- * @return Collection|string[]
- */
- private function deletedModuleNames(): Collection
- {
- $database_modules = DB::table('module')->pluck('module_name');
-
- $disk_modules = Module::all()
- ->map(function (ModuleInterface $module): string {
- return $module->name();
- });
-
- return $database_modules->diff($disk_modules);
- }
-
- /**
* Find the media object that uses a particular media file.
*
* @param string $file
@@ -1267,7 +1049,6 @@ class AdminController extends AbstractBaseController
/**
* Compare two images, and return a quantified difference.
- *
* 0 (different) ... 100 (same)
*
* @param string $thumbanil
@@ -1314,7 +1095,6 @@ class AdminController extends AbstractBaseController
/**
* Scale an image to 10x10 and read the individual pixels.
- *
* This is a slow operation, add we will do it many times on
* the "import wetbrees 1 thumbnails" page so cache the results.
*
diff --git a/app/Module.php b/app/Module.php
index 76dd1ed764..c2d426ba73 100644
--- a/app/Module.php
+++ b/app/Module.php
@@ -398,14 +398,18 @@ class Module
* All modules which provide a specific function.
*
* @param string $interface
+ * @param bool $include_disabled
*
* @return Collection|ModuleInterface[]
*/
- public static function findByInterface(string $interface): Collection
+ public static function findByInterface(string $interface, $include_disabled = false): Collection
{
$modules = self::all()
->filter(function (ModuleInterface $module) use ($interface): bool {
- return $module->isEnabled() && $module instanceof $interface;
+ return $module instanceof $interface;
+ })
+ ->filter(function (ModuleInterface $module) use ($include_disabled): bool {
+ return $include_disabled || $module->isEnabled();
});
switch ($interface) {
diff --git a/modules_v4/historic-events.example/module.php b/modules_v4/historic-events.example/module.php
new file mode 100644
index 0000000000..695c95e945
--- /dev/null
+++ b/modules_v4/historic-events.example/module.php
@@ -0,0 +1,78 @@
+<?php
+
+use Fisharebest\Webtrees\Module\AbstractModule;
+use Fisharebest\Webtrees\Module\ModuleCustomInterface;
+use Fisharebest\Webtrees\Module\ModuleCustomTrait;
+use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface;
+use Fisharebest\Webtrees\Module\ModuleHistoricEventsTrait;
+
+return new class extends AbstractModule implements ModuleCustomInterface, ModuleHistoricEventsInterface {
+ use ModuleCustomTrait;
+ use ModuleHistoricEventsTrait;
+
+ /**
+ * How should this module be labelled on tabs, menus, etc.?
+ *
+ * @return string
+ */
+ public function title(): string
+ {
+ return 'Presidents of the United States';
+ }
+
+ /**
+ * All events provided by this module.
+ *
+ * @return string[]
+ */
+ public function historicEventsAll(): array
+ {
+ return [
+ "1 EVEN George Washington\n2 TYPE 1st President of the United States\n2 DATE 30 APR 1789",
+ "1 EVEN John Adams\n2 TYPE 2nd President of the United States\n2 DATE 4 MAR 1797",
+ "1 EVEN Thomas Jefferson\n2 TYPE 3rd President of the United States\n2 DATE 4 MAR 1801",
+ "1 EVEN James Madison\n2 TYPE 4th President of the United States\n2 DATE 4 MAR 1809",
+ "1 EVEN James Monroe\n2 TYPE 5th President of the United States\n2 DATE 4 MAR 1817",
+ "1 EVEN John Quincy Adams\n2 TYPE 6th President of the United States\n2 DATE 4 MAR 1825",
+ "1 EVEN Andrew Jackson\n2 TYPE 7th President of the United States\n2 DATE 4 MAR 1829",
+ "1 EVEN Martin Van Buren\n2 TYPE 8th President of the United States\n2 DATE 4 MAR 1837",
+ "1 EVEN William Henry Harrison\n2 TYPE 9th President of the United States\n2 DATE 4 MAR 1841",
+ "1 EVEN John Tyler\n2 TYPE 10th President of the United States\n2 DATE 4 APR 1841",
+ "1 EVEN James K Polk\n2 TYPE 11th President of the United States\n2 DATE 4 MAR 1845",
+ "1 EVEN Zachary Taylor\n2 TYPE 12th President of the United States\n2 DATE 4 MAR 1849",
+ "1 EVEN Millard Fillmore\n2 TYPE 13th President of the United States\n2 DATE 9 JUL 1850",
+ "1 EVEN Franklin Pierce\n2 TYPE 14th President of the United States\n2 DATE 4 MAR 1853",
+ "1 EVEN James Buchanan\n2 TYPE 15th President of the United States\n2 DATE 4 MAR 1857",
+ "1 EVEN Abraham Lincoln\n2 TYPE 16th President of the United States\n2 DATE 4 MAR 1861",
+ "1 EVEN Andrew Johnson\n2 TYPE 17th President of the United States\n2 DATE 15 APR 1865",
+ "1 EVEN Ulysses S Grant\n2 TYPE 18th President of the United States\n2 DATE 4 MAR 1869",
+ "1 EVEN Rutherford B Hayes\n2 TYPE 19th President of the United States\n2 DATE 4 MAR 1877",
+ "1 EVEN James A Garfield\n2 TYPE 20th President of the United States\n2 DATE 4 MAR 1881",
+ "1 EVEN Chester A Arthur\n2 TYPE 21st President of the United States\n2 DATE 19 SEP 1881",
+ "1 EVEN Grover Cleveland\n2 TYPE 22nd President of the United States\n2 DATE 4 MAR 1885",
+ "1 EVEN Benjamin Harrison\n2 TYPE 23rd President of the United States\n2 DATE 4 MAR 1889",
+ "1 EVEN Grover Cleveland\n2 TYPE 24th President of the United States\n2 DATE 4 MAR 1893",
+ "1 EVEN William McKinley\n2 TYPE 25th President of the United States\n2 DATE 4 MAR 1897",
+ "1 EVEN Theodore Roosevelt\n2 TYPE 26th President of the United States\n2 DATE 14 SEP 1901",
+ "1 EVEN William Howard Taft\n2 TYPE 27th President of the United States\n2 DATE 4 MAR 1909",
+ "1 EVEN Woodrow Wilson\n2 TYPE 28th President of the United States\n2 DATE 4 MAR 1913",
+ "1 EVEN Warren G Harding\n2 TYPE 29th President of the United States\n2 DATE 4 MAR 1921",
+ "1 EVEN Calvin Coolidge\n2 TYPE 30th President of the United States\n2 DATE 2 AUG 1923",
+ "1 EVEN Herbert Hoover\n2 TYPE 31st President of the United States\n2 DATE 4 MAR 1929",
+ "1 EVEN Franklin D Roosevelt\n2 TYPE 32nd President of the United States\n2 DATE 4 MAR 1933",
+ "1 EVEN Harry S Truman\n2 TYPE 33rd President of the United States\n2 DATE 12 APR 1945",
+ "1 EVEN Dwight D Eisenhower\n2 TYPE 34th President of the United States\n2 DATE 20 JAN 1953",
+ "1 EVEN John F Kennedy\n2 TYPE 35th President of the United States\n2 DATE 20 JAN 1961",
+ "1 EVEN Lyndon B Johnson\n2 TYPE 36th President of the United States\n2 DATE 22 NOV 1963",
+ "1 EVEN Richard Nixon\n2 TYPE 37th President of the United States\n2 DATE 20 JAN 1969",
+ "1 EVEN Gerald Ford\n2 TYPE 38th President of the United States\n2 DATE 9 AUG 1974",
+ "1 EVEN Jimmy Carter\n2 TYPE 39th President of the United States\n2 DATE 20 JAN 1977",
+ "1 EVEN Ronald Reagan\n2 TYPE 40th President of the United States\n2 DATE 20 JAN 1981",
+ "1 EVEN George H W Bush\n2 TYPE 41st President of the United States\n2 DATE 20 JAN 1989",
+ "1 EVEN Bill Clinton\n2 TYPE 42nd President of the United States\n2 DATE 20 JAN 1993",
+ "1 EVEN George W Bush\n2 TYPE 43rd President of the United States\n2 DATE 20 JAN 2001",
+ "1 EVEN Barack Obama\n2 TYPE 44th President of the United States\n2 DATE 20 JAN 2009",
+ "1 EVEN Donald Trump\n2 TYPE 45th President of the United States\n2 DATE 20 JAN 2017",
+ ];
+ }
+};
diff --git a/resources/views/admin/components.phtml b/resources/views/admin/components.phtml
new file mode 100644
index 0000000000..6aeb20f4fd
--- /dev/null
+++ b/resources/views/admin/components.phtml
@@ -0,0 +1,149 @@
+<?php use Fisharebest\Webtrees\Bootstrap4; ?>
+<?php use Fisharebest\Webtrees\Functions\FunctionsEdit; ?>
+<?php use Fisharebest\Webtrees\I18N; ?>
+<?php use Fisharebest\Webtrees\Module\ModuleConfigInterface;
+use Fisharebest\Webtrees\View; ?>
+
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), $title]]) ?>
+
+<h1><?= $title ?></h1>
+
+<form method="post">
+ <?= csrf_field() ?>
+ <table class="table table-bordered wt-table-menu">
+ <thead>
+ <tr>
+ <th><?= I18N::translate('Menu') ?></th>
+ <th class="text-center"><?= I18N::translate('Enabled') ?></th>
+ <th class="text-center"><?= I18N::translate('Access level') ?></th>
+ <?php if ($component === 'menu' || $component === 'sidebar' || $component === 'tab') : ?>
+ <th class="text-center"><?= I18N::translate('Move up') ?></th>
+ <th class="text-center"><?= I18N::translate('Move down') ?></th>
+ <?php endif ?>
+ </tr>
+ </thead>
+
+ <tbody>
+ <?php foreach ($modules as $module_name => $module) : ?>
+ <tr>
+ <th scope="col">
+ <input type="hidden" name="order[]" value="<?= e($module->name()) ?>"?>
+ <?= $module->title() ?>
+ <?php if ($module instanceof ModuleConfigInterface) : ?>
+ <a href="<?= e($module->getConfigLink()) ?>" title="<?= I18N::translate('Preferences') ?>">
+ <?= view('icons/preferences') ?>
+ <span class="sr-only">
+ <?= I18N::translate('Preferences') ?>
+ </span>
+ </a>
+ <?php endif ?>
+ </th>
+
+ <td class="text-center">
+ <label class="d-block">
+ <input type="checkbox" name="status-<?= e($module->name()) ?>" id="status-<?= e($module->name()) ?>" <?= $module->isEnabled() ? 'checked' : '' ?>>
+ <span class="sr-only">
+ <?= I18N::translate('Enabled') ?>
+ </span>
+ </label>
+ </td>
+
+ <td class="text-center">
+ <div class="modal fade" id="access-level-<?= $module->name() ?>" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h2 class="modal-title">
+ <?= e($module->title()) ?> – <?= I18N::translate('Access level') ?>
+ </h2>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <table class="table table-sm">
+ <tbody>
+ <?php foreach ($trees as $tree) : ?>
+ <tr>
+ <td>
+ <?= e($tree->title()) ?>
+ </td>
+ <td>
+ <?= Bootstrap4::select(FunctionsEdit::optionsAccessLevels(), $module->accessLevel($tree, $component), ['name' => 'access-' . $module->name() . '-' . $tree->id()]) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <button type="button" class="btn btn-link" data-toggle="modal" data-target="#access-level-<?= $module->name() ?>">
+ <?= view('icons/edit') ?>
+ <span class="sr-only">
+ <?= I18N::translate('edit') ?>
+ </span>
+ </button>
+ </td>
+
+ <?php if ($component === 'menu' || $component === 'sidebar' || $component === 'tab') : ?>
+ <td class="move up text-center">
+ <a href="#" title="<?= I18N::translate('Move up') ?>">
+ <?= view('icons/arrow-up') ?>
+ </a>
+ </td>
+
+ <td class="move down text-center">
+ <a href="#" title="<?= I18N::translate('Move down') ?>">
+ <?= view('icons/arrow-down') ?>
+ </a>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+
+ <button class="btn btn-primary" type="submit">
+ <?= view('icons/save') ?>
+ <?= I18N::translate('save') ?>
+ </button>
+
+ <a class="btn btn-secondary" href="<?= e(route('admin-control-panel')) ?>">
+ <?= view('icons/cancel') ?>
+ <?= I18N::translate('cancel') ?>
+ </a>
+</form>
+
+
+<?php View::push('javascript') ?>
+<script>
+ $('.wt-table-menu td.move').click(function() {
+ var row = $(this).closest('tr');
+
+ if ($(this).hasClass('up')) {
+ row.prev().before(row);
+ } else {
+ row.next().after(row);
+ }
+
+ return false;
+ });
+</script>
+<?php View::endpush() ?>
+
+<?php View::push('styles') ?>
+<style>
+ .wt-table-menu tr:first-child .wt-icon-arrow-up {
+ display:none;
+ }
+ .wt-table-menu tr:last-child .wt-icon-arrow-down {
+ display:none;
+ }
+</style>
+<?php View::endpush() ?>
diff --git a/resources/views/admin/control-panel.phtml b/resources/views/admin/control-panel.phtml
index b575b41729..678b249958 100644
--- a/resources/views/admin/control-panel.phtml
+++ b/resources/views/admin/control-panel.phtml
@@ -287,43 +287,53 @@
</span>
</h2>
</div>
+
<div class="card-body">
+ <ul class="fa-ul">
+ <li>
+ <span class="fa-li"><?= view('icons/preferences') ?></span>
+ <a href="<?= e(route('modules')) ?>">
+ <?= I18N::translate('Module administration') ?>
+ </a>
+ </li>
+ </ul>
+
<div class="row">
<div class="col-sm-6">
<ul class="fa-ul">
<li>
<span class="fa-li"><?= view('icons/menu') ?></span>
- <a href="<?= e(route('admin-menus')) ?>">
+ <a href="<?= e(route('menus')) ?>">
<?= I18N::translate('Menus') ?>
</a>
</li>
<li>
<span class="fa-li"><?= view('icons/tab') ?></span>
- <a href="<?= e(route('admin-tabs')) ?>">
+ <a href="<?= e(route('tabs')) ?>">
<?= I18N::translate('Tabs') ?>
</a>
</li>
<li>
<span class="fa-li"><?= view('icons/block') ?></span>
- <a href="<?= e(route('admin-blocks')) ?>">
+ <a href="<?= e(route('blocks')) ?>">
<?= I18N::translate('Blocks') ?>
</a>
</li>
<li>
<span class="fa-li"><?= view('icons/sidebar') ?></span>
- <a href="<?= e(route('admin-sidebars')) ?>">
+ <a href="<?= e(route('sidebars')) ?>">
<?= I18N::translate('Sidebars') ?>
</a>
</li>
<li>
<span class="fa-li"><?= view('icons/chart') ?></span>
- <a href="<?= e(route('admin-charts')) ?>">
+ <a href="<?= e(route('charts')) ?>">
<?= I18N::translate('Charts') ?>
</a>
</li>
<li>
<span class="fa-li"><?= view('icons/report') ?></span>
- <a href="<?= e(route('admin-reports')) ?>">
+ <a href="<?= e(route('reports')) ?>">
<?= I18N::translate('Reports') ?>
</a>
</li>
@@ -342,15 +352,6 @@
</ul>
</div>
</div>
-
- <ul class="fa-ul">
- <li>
- <span class="fa-li"><?= view('icons/preferences') ?></span>
- <a href="<?= e(route('admin-modules')) ?>">
- <?= I18N::translate('Module administration') ?>
- </a>
- </li>
- </ul>
</div>
</div>
diff --git a/resources/views/admin/module-components.phtml b/resources/views/admin/module-components.phtml
deleted file mode 100644
index b11f2f748f..0000000000
--- a/resources/views/admin/module-components.phtml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php use Fisharebest\Webtrees\Bootstrap4; ?>
-<?php use Fisharebest\Webtrees\Functions\FunctionsEdit; ?>
-<?php use Fisharebest\Webtrees\I18N; ?>
-<?php use Fisharebest\Webtrees\Module\ModuleConfigInterface; ?>
-<?php use Fisharebest\Webtrees\Tree; ?>
-
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), $title]]) ?>
-
-<h1><?= $title ?></h1>
-
-<form action="<?= e(route('admin-update-module-access')) ?>" method="post">
- <input type="hidden" name="component" value="<?= e($component) ?>">
- <input type="hidden" name="interface" value="<?= e($interface) ?>">
- <?= csrf_field() ?>
- <table class="table table-bordered">
- <thead>
- <tr>
- <th><?= $component_title ?></th>
- <th class="d-none d-sm-table-cell"><?= I18N::translate('Description') ?></th>
- <th><?= I18N::translate('Access level') ?></th>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($modules as $module_name => $module) : ?>
- <tr>
- <td>
- <?php if ($module instanceof ModuleConfigInterface) : ?>
- <a href="<?= e($module->getConfigLink()) ?>">
- <?= $module->title() ?>
- <?= view('icons/preferences') ?>
- </a>
- <?php else : ?>
- <?= $module->title() ?>
- <?php endif ?>
- </td>
- <td class="d-none d-sm-table-cell">
- <?= $module->description() ?>
- </td>
- <td>
- <table class="table table-sm">
- <tbody>
- <?php foreach (Tree::all() as $tree) : ?>
- <tr>
- <td>
- <?= e($tree->title()) ?>
- </td>
- <td>
- <?= Bootstrap4::select(FunctionsEdit::optionsAccessLevels(), $module->accessLevel($tree, $component), ['name' => 'access-' . $module->name() . '-' . $tree->id()]) ?>
- </td>
- </tr>
- <?php endforeach ?>
- </tbody>
- </table>
- </td>
- </tr>
- <?php endforeach ?>
- </tbody>
- </table>
- <button class="btn btn-primary" type="submit">
- <?= view('icons/save') ?>
- <?= I18N::translate('save') ?>
- </button>
-</form>
diff --git a/resources/views/admin/modules.phtml b/resources/views/admin/modules.phtml
index 4191bccf19..b9f51288a9 100644
--- a/resources/views/admin/modules.phtml
+++ b/resources/views/admin/modules.phtml
@@ -43,64 +43,54 @@
<th>
<?= I18N::translate('Enabled') ?>
</th>
- <th class="d-none d-sm-table-cell" data-orderable="false">
+ <th data-orderable="false">
<?= I18N::translate('Description') ?>
</th>
- <th class="d-none d-sm-table-cell">
- <a href="<?= e(route('admin-menus')) ?>">
- <?= I18N::translate('Menus') ?>
- </a>
+ <th title="<?= I18N::translate('Preferences') ?>">
+ <?= view('icons/preferences') ?>
+ <span class="sr-only"><?= I18N::translate('Preferences') ?></span>
</th>
- <th class="d-none d-sm-table-cell">
- <a href="<?= e(route('admin-tabs')) ?>">
- <?= I18N::translate('Tabs') ?>
- </a>
+ <th title="<?= I18N::translate('Menus') ?>">
+ <?= view('icons/menu') ?>
+ <span class="sr-only"><?= I18N::translate('Menus') ?></span>
</th>
- <th class="d-none d-sm-table-cell">
- <a href="<?= e(route('admin-sidebars')) ?>">
- <?= I18N::translate('Sidebars') ?>
- </a>
+ <th title="<?= I18N::translate('Tabs') ?>">
+ <?= view('icons/tab') ?>
+ <span class="sr-only"><?= I18N::translate('Tabs') ?></span>
</th>
- <th class="d-none d-sm-table-cell">
- <a href="<?= e(route('admin-blocks')) ?>">
- <?= I18N::translate('Blocks') ?>
- </a>
+ <th title="<?= I18N::translate('Sidebars') ?>">
+ <?= view('icons/sidebar') ?>
+ <span class="sr-only"><?= I18N::translate('Sidebars') ?></span>
</th>
- <th class="d-none d-sm-table-cell">
- <a href="<?= e(route('admin-charts')) ?>">
- <?= I18N::translate('Charts') ?>
- </a>
+ <th title="<?= I18N::translate('Blocks') ?>">
+ <?= view('icons/block') ?>
+ <span class="sr-only"><?= I18N::translate('Blocks') ?></span>
</th>
- <th class="d-none d-sm-table-cell">
- <a href="<?= e(route('admin-reports')) ?>">
- <?= I18N::translate('Reports') ?>
- </a>
+ <th title="<?= I18N::translate('Charts') ?>">
+ <?= view('icons/chart') ?>
+ <span class="sr-only"><?= I18N::translate('Charts') ?></span>
</th>
- <th class="d-none">
- <?= I18N::translate('Themes') ?>
+ <th title="<?= I18N::translate('Reports') ?>">
+ <?= view('icons/report') ?>
+ <span class="sr-only"><?= I18N::translate('Reports') ?></span>
</th>
- <th class="d-sm-none" data-orderable="false">
- <?= I18N::translate('Type') ?>
+ <th class="d-none" title="<?= I18N::translate('Themes') ?>">
+ <?= view('icons/theme') ?>
+ <span class="sr-only"><?= I18N::translate('Themes') ?></span>
</th>
</tr>
</thead>
+
<tbody>
<?php foreach ($modules as $module) : ?>
<tr>
- <th scope="row" data-sort="<?= $module->title() ?>" dir="auto">
- <?php if ($module instanceof ModuleConfigInterface && $module->isEnabled()) : ?>
- <a href="<?= e($module->getConfigLink()) ?>">
- <?= $module->title() ?>
- <?= view('icons/preferences') ?>
- </a>
- <?php else : ?>
- <?= $module->title() ?>
- <?php endif ?>
+ <th scope="row" dir="auto">
+ <?= $module->title() ?>
</th>
<td class="text-center" data-sort="<?= $module->isEnabled() ?>">
<?= Bootstrap4::checkbox('', false, ['name' => 'status-' . $module->name(), 'checked' => $module->isEnabled()]) ?>
</td>
- <td class="d-none d-sm-table-cell">
+ <td>
<?= $module->description() ?>
<?php if ($module instanceof ModuleCustomInterface) : ?>
<br>
@@ -116,7 +106,17 @@
<?php endif ?>
<?php endif ?>
</td>
- <td class="text-center text-muted d-none d-sm-table-cell">
+ <td class="text-center text-muted" title="<?= I18N::translate('Preferences') ?>">
+ <?php if ($module instanceof ModuleConfigInterface) : ?>
+ <a href="<?= e($module->getConfigLink()) ?>" title="<?= I18N::translate('Preferences') ?>">
+ <?= view('icons/preferences') ?>
+ <span class="sr-only">
+ <?= I18N::translate('Preferences') ?>
+ </span>
+ </a>
+ <?php endif ?>
+ </td>
+ <td class="text-center text-muted" title="<?= I18N::translate('Menu') ?>">
<?php if ($module instanceof ModuleMenuInterface) : ?>
<?= view('icons/menu') ?>
<span class="sr-only"><?= I18N::translate('Menu') ?></span>
@@ -124,15 +124,15 @@
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-none d-sm-table-cell">
+ <td class="text-center text-muted" title="<?= I18N::translate('Tab') ?>">
<?php if ($module instanceof ModuleTabInterface) : ?>
<?= view('icons/tab') ?>
- <span class="sr-only"><?= I18N::translate('Tabs') ?></span>
+ <span class="sr-only"><?= I18N::translate('Tab') ?></span>
<?php else : ?>
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-none d-sm-table-cell">
+ <td class="text-center text-muted" title="<?= I18N::translate('Sidebar') ?>">
<?php if ($module instanceof ModuleSidebarInterface) : ?>
<?= view('icons/sidebar') ?>
<span class="sr-only"><?= I18N::translate('Sidebar') ?></span>
@@ -140,21 +140,30 @@
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-none d-sm-table-cell">
+ <td class="text-center text-muted">
<?php if ($module instanceof ModuleBlockInterface) : ?>
<?php if ($module->isUserBlock()) : ?>
- <?= view('icons/block-user') ?>
- <span class="sr-only"><?= I18N::translate('My page') ?></span>
+ <span title="<?= I18N::translate('My page') ?>">
+ <?= view('icons/block-user') ?>
+ </span>
+ <span class="sr-only">
+ <?= I18N::translate('My page') ?>
+ </span>
<?php endif ?>
- <?php if ($module->isUserBlock()) : ?>
- <?= view('icons/block-tree') ?>
- <span class="sr-only"><?= I18N::translate('Home page') ?></span>
+
+ <?php if ($module->isTreeBlock()) : ?>
+ <span title="<?= I18N::translate('Home page') ?>">
+ <?= view('icons/block-tree') ?>
+ </span>
+ <span class="sr-only">
+ <?= I18N::translate('Home page') ?>
+ </span>
<?php endif ?>
<?php else : ?>
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-none d-sm-table-cell">
+ <td class="text-center text-muted" title="<?= I18N::translate('Chart') ?>">
<?php if ($module instanceof ModuleChartInterface) : ?>
<?= view('icons/chart') ?>
<span class="sr-only"><?= I18N::translate('Chart') ?></span>
@@ -162,7 +171,7 @@
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-none d-sm-table-cell">
+ <td class="text-center text-muted" title="<?= I18N::translate('Report') ?>">
<?php if ($module instanceof ModuleReportInterface) : ?>
<?= view('icons/report') ?>
<span class="sr-only"><?= I18N::translate('Report') ?></span>
@@ -170,7 +179,7 @@
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-none">
+ <td class="text-center text-muted d-none" title="<?= I18N::translate('Theme') ?>">
<?php if ($module instanceof ModuleThemeInterface) : ?>
<?= view('icons/theme') ?>
<span class="sr-only"><?= I18N::translate('Theme') ?></span>
@@ -178,40 +187,6 @@
-
<?php endif ?>
</td>
- <td class="text-center text-muted d-sm-none">
- <?php if ($module instanceof ModuleMenuInterface) : ?>
- <?= view('icons/menu') ?>
- <span class="sr-only"><?= I18N::translate('Menu') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleTabInterface) : ?>
- <?= view('icons/tab') ?>
- <span class="sr-only"><?= I18N::translate('Tab') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleSidebarInterface) : ?>
- <?= view('icons/sidebar') ?>
- <span class="sr-only"><?= I18N::translate('Sidebar') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleBlockInterface && $module->isUserBlock()) : ?>
- <?= view('icons/block-user') ?>
- <span class="sr-only"><?= I18N::translate('My page') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleBlockInterface && $module->isUserBlock()) : ?>
- <?= view('icons/block-tree') ?>
- <span class="sr-only"><?= I18N::translate('Home page') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleChartInterface) : ?>
- <?= view('icons/chart') ?>
- <span class="sr-only"><?= I18N::translate('Chart') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleReportInterface) : ?>
- <?= view('icons/report') ?>
- <span class="sr-only"><?= I18N::translate('Report') ?></span>
- <?php endif ?>
- <?php if ($module instanceof ModuleThemeInterface) : ?>
- <?= view('icons/theme') ?>
- <span class="sr-only"><?= I18N::translate('Theme') ?></span>
- <?php endif ?>
- </td>
</tr>
<?php endforeach ?>
</tbody>
diff --git a/resources/views/modules/batch_update/admin.phtml b/resources/views/modules/batch_update/admin.phtml
index bce421fbc0..f4964f7bdf 100644
--- a/resources/views/modules/batch_update/admin.phtml
+++ b/resources/views/modules/batch_update/admin.phtml
@@ -1,7 +1,7 @@
<?php use Fisharebest\Webtrees\Bootstrap4; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Module administration'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Module administration'), $title]]) ?>
<h1><?= $title ?></h1>
diff --git a/resources/views/modules/faq/config.phtml b/resources/views/modules/faq/config.phtml
index 6854e5c3f0..c682e63d53 100644
--- a/resources/views/modules/faq/config.phtml
+++ b/resources/views/modules/faq/config.phtml
@@ -1,7 +1,7 @@
<?php use Fisharebest\Webtrees\Bootstrap4; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), $title]]) ?>
<h1><?= $title ?></h1>
diff --git a/resources/views/modules/faq/edit.phtml b/resources/views/modules/faq/edit.phtml
index 1438f5c452..b2b5dc159a 100644
--- a/resources/views/modules/faq/edit.phtml
+++ b/resources/views/modules/faq/edit.phtml
@@ -2,7 +2,7 @@
<?php use Fisharebest\Webtrees\Functions\FunctionsEdit; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), route('module', ['module' => 'faq', 'action' => 'Admin', 'ged' => $tree->name()]) => I18N::translate('Frequently asked questions'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), route('module', ['module' => 'faq', 'action' => 'Admin', 'ged' => $tree->name()]) => I18N::translate('Frequently asked questions'), $title]]) ?>
<h1><?= $title ?></h1>
diff --git a/resources/views/modules/relationships-chart/config.phtml b/resources/views/modules/relationships-chart/config.phtml
index d4212c3d3a..1b45cb4a09 100644
--- a/resources/views/modules/relationships-chart/config.phtml
+++ b/resources/views/modules/relationships-chart/config.phtml
@@ -1,7 +1,7 @@
<?php use Fisharebest\Webtrees\Bootstrap4; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), $title]]) ?>
<h1><?= $title ?></h1>
<p>
diff --git a/resources/views/modules/sitemap/config.phtml b/resources/views/modules/sitemap/config.phtml
index a96d237254..cb9a6c1527 100644
--- a/resources/views/modules/sitemap/config.phtml
+++ b/resources/views/modules/sitemap/config.phtml
@@ -1,7 +1,7 @@
<?php use Fisharebest\Webtrees\Bootstrap4; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), $title]]) ?>
<h1><?= $title ?></h1>
diff --git a/resources/views/modules/stories/config.phtml b/resources/views/modules/stories/config.phtml
index 2bc0b1e5c0..476c64c6b7 100644
--- a/resources/views/modules/stories/config.phtml
+++ b/resources/views/modules/stories/config.phtml
@@ -1,7 +1,7 @@
<?php use Fisharebest\Webtrees\Bootstrap4; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), $title]]) ?>
<h1><?= $title ?></h1>
diff --git a/resources/views/modules/stories/edit.phtml b/resources/views/modules/stories/edit.phtml
index 991c719412..0e6d82e3e9 100644
--- a/resources/views/modules/stories/edit.phtml
+++ b/resources/views/modules/stories/edit.phtml
@@ -1,7 +1,7 @@
<?php use Fisharebest\Webtrees\Functions\FunctionsEdit; ?>
<?php use Fisharebest\Webtrees\I18N; ?>
-<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('admin-modules') => I18N::translate('Modules'), route('module', ['module' => 'stories', 'action' => 'Admin', 'ged' => $tree->name()]) => I18N::translate('Stories'), $title]]) ?>
+<?= view('components/breadcrumbs', ['links' => [route('admin-control-panel') => I18N::translate('Control panel'), route('modules') => I18N::translate('Modules'), route('module', ['module' => 'stories', 'action' => 'Admin', 'ged' => $tree->name()]) => I18N::translate('Stories'), $title]]) ?>
<h1><?= $title ?></h1>
diff --git a/routes/web.php b/routes/web.php
index 44acaba735..999d23bdb6 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -27,18 +27,25 @@ if (Auth::isAdmin()) {
'GET:analytics' => 'Admin\\AnalyticsController@list',
'GET:analytics-edit' => 'Admin\\AnalyticsController@edit',
'POST:analytics-edit' => 'Admin\\AnalyticsController@save',
- 'GET:admin-blocks' => 'AdminController@blocks',
- 'GET:admin-charts' => 'AdminController@charts',
+ 'GET:modules' => 'Admin\\ModuleController@list',
+ 'POST:modules' => 'Admin\\ModuleController@update',
+ 'GET:blocks' => 'Admin\\ModuleController@listBlocks',
+ 'POST:blocks' => 'Admin\\ModuleController@updateBlocks',
+ 'GET:charts' => 'Admin\\ModuleController@listCharts',
+ 'POST:charts' => 'Admin\\ModuleController@updateCharts',
+ 'GET:menus' => 'Admin\\ModuleController@listMenus',
+ 'POST:menus' => 'Admin\\ModuleController@updateMenus',
+ 'GET:reports' => 'Admin\\ModuleController@listReports',
+ 'POST:reports' => 'Admin\\ModuleController@updateReports',
+ 'GET:sidebars' => 'Admin\\ModuleController@listSidebars',
+ 'POST:sidebars' => 'Admin\\ModuleController@updateSidebars',
+ 'GET:tabs' => 'Admin\\ModuleController@listTabs',
+ 'POST:tabs' => 'Admin\\ModuleController@updateTabs',
'GET:admin-control-panel' => 'AdminController@controlPanel',
'POST:admin-delete-module-settings' => 'AdminController@deleteModuleSettings',
'GET:admin-fix-level-0-media' => 'AdminController@fixLevel0Media',
'POST:admin-fix-level-0-media-action' => 'AdminController@fixLevel0MediaAction',
'GET:admin-fix-level-0-media-data' => 'AdminController@fixLevel0MediaData',
- 'GET:admin-menus' => 'AdminController@menus',
- 'GET:admin-modules' => 'AdminController@modules',
- 'GET:admin-reports' => 'AdminController@reports',
- 'GET:admin-sidebars' => 'AdminController@sidebars',
- 'GET:admin-tabs' => 'AdminController@tabs',
'POST:admin-update-module-access' => 'AdminController@updateModuleAccess',
'POST:admin-update-module-status' => 'AdminController@updateModuleStatus',
'GET:admin-webtrees1-thumbs' => 'AdminController@webtrees1Thumbnails',