summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Roach <fisharebest@webtrees.net>2019-01-28 21:55:31 +0000
committerGreg Roach <fisharebest@webtrees.net>2019-01-28 22:36:12 +0000
commit33c343964203a24deeb34413eac7f48bee172865 (patch)
treea4bfc035116bb2b1dce79bcdad72ee3ce464572d
parent9e5cb8c9c4992a543a6e21f7ba387f3b275fa389 (diff)
downloadwebtrees-33c343964203a24deeb34413eac7f48bee172865.tar.gz
webtrees-33c343964203a24deeb34413eac7f48bee172865.tar.bz2
webtrees-33c343964203a24deeb34413eac7f48bee172865.zip
Convert page footers to modules
-rw-r--r--app/Http/Controllers/Admin/ModuleController.php53
-rw-r--r--app/Module.php45
-rw-r--r--app/Module/ContactsFooterModule.php193
-rw-r--r--app/Module/CookieWarningModule.php131
-rw-r--r--app/Module/HitCountFooterModule.php (renamed from app/Http/Middleware/PageHitCounter.php)129
-rw-r--r--app/Module/ModuleFooterInterface.php54
-rw-r--r--app/Module/ModuleFooterTrait.php69
-rw-r--r--app/Module/ModuleMenuTrait.php15
-rw-r--r--app/Module/PoweredByWebtreesModule.php78
-rw-r--r--app/Schema/Migration41.php39
-rw-r--r--app/Theme/AbstractTheme.php156
-rw-r--r--app/Theme/ThemeInterface.php30
-rw-r--r--app/Webtrees.php2
-rw-r--r--index.php6
-rw-r--r--resources/views/admin/components.phtml16
-rw-r--r--resources/views/admin/control-panel.phtml6
-rw-r--r--resources/views/admin/modules.phtml13
-rw-r--r--resources/views/icons/footer.phtml1
-rw-r--r--resources/views/layouts/default.phtml17
-rw-r--r--resources/views/modules/contact-links/footer.phtml3
-rw-r--r--resources/views/modules/cookie-warning/footer.phtml13
-rw-r--r--resources/views/modules/hit-counter/footer.phtml3
-rw-r--r--resources/views/modules/powered-by-webtrees/footer.phtml5
-rw-r--r--routes/web.php2
-rwxr-xr-xthemes/_common/css-2.0.0/style.css4
-rwxr-xr-xthemes/clouds/css-2.0.0/style.css4
-rwxr-xr-xthemes/colors/css-2.0.0/style.css4
-rwxr-xr-xthemes/fab/css-2.0.0/style.css4
-rwxr-xr-xthemes/minimal/css-2.0.0/style.css4
-rwxr-xr-xthemes/webtrees/css-2.0.0/style.css4
-rwxr-xr-xthemes/xenea/css-2.0.0/style.css4
31 files changed, 814 insertions, 293 deletions
diff --git a/app/Http/Controllers/Admin/ModuleController.php b/app/Http/Controllers/Admin/ModuleController.php
index 3d34df35ca..2fbcfeb1c3 100644
--- a/app/Http/Controllers/Admin/ModuleController.php
+++ b/app/Http/Controllers/Admin/ModuleController.php
@@ -22,6 +22,7 @@ use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Module\ModuleBlockInterface;
use Fisharebest\Webtrees\Module\ModuleChartInterface;
+use Fisharebest\Webtrees\Module\ModuleFooterInterface;
use Fisharebest\Webtrees\Module\ModuleInterface;
use Fisharebest\Webtrees\Module\ModuleMenuInterface;
use Fisharebest\Webtrees\Module\ModuleReportInterface;
@@ -39,6 +40,22 @@ use Symfony\Component\HttpFoundation\Response;
*/
class ModuleController extends AbstractAdminController
{
+ private const COMPONENTS_WITH_ACCESS = [
+ 'block',
+ 'chart',
+ 'menu',
+ 'report',
+ 'sidebar',
+ 'tab',
+ ];
+
+ private const COMPONENTS_WITH_SORT = [
+ 'footer',
+ 'menu',
+ 'sidebar',
+ 'tab',
+ ];
+
/**
* Show the administrator a list of modules.
*
@@ -89,6 +106,14 @@ class ModuleController extends AbstractAdminController
/**
* @return Response
*/
+ public function listFooters(): Response
+ {
+ return $this->listComponents(ModuleFooterInterface::class, 'footer', I18N::translate('Footers'));
+ }
+
+ /**
+ * @return Response
+ */
public function listMenus(): Response
{
return $this->listComponents(ModuleMenuInterface::class, 'menu', I18N::translate('Menus'));
@@ -127,11 +152,16 @@ class ModuleController extends AbstractAdminController
*/
private function listComponents(string $interface, string $component, string $title): Response
{
+ $uses_access = in_array($component, self::COMPONENTS_WITH_ACCESS);
+ $uses_sorting = in_array($component, self::COMPONENTS_WITH_SORT);
+
return $this->viewResponse('admin/components', [
- 'component' => $component,
- 'modules' => Module::findByInterface($interface, true),
- 'title' => $title,
- 'trees' => Tree::all(),
+ 'component' => $component,
+ 'modules' => Module::findByInterface($interface, true),
+ 'title' => $title,
+ 'trees' => Tree::all(),
+ 'uses_access' => $uses_access,
+ 'uses_sorting' => $uses_sorting,
]);
}
@@ -201,6 +231,21 @@ class ModuleController extends AbstractAdminController
*
* @return RedirectResponse
*/
+ public function updateFooters(Request $request): RedirectResponse
+ {
+ $modules = Module::findByInterface(ModuleFooterInterface::class, true);
+
+ $this->updateStatus($modules, $request);
+ $this->updateOrder($modules, 'footer_order', $request);
+
+ return new RedirectResponse(route('footers'));
+ }
+
+ /**
+ * @param Request $request
+ *
+ * @return RedirectResponse
+ */
public function updateMenus(Request $request): RedirectResponse
{
$modules = Module::findByInterface(ModuleMenuInterface::class, true);
diff --git a/app/Module.php b/app/Module.php
index 9cf7d0c327..8e78f2903b 100644
--- a/app/Module.php
+++ b/app/Module.php
@@ -34,6 +34,8 @@ use Fisharebest\Webtrees\Module\ChartsMenuModule;
use Fisharebest\Webtrees\Module\CkeditorModule;
use Fisharebest\Webtrees\Module\ClippingsCartModule;
use Fisharebest\Webtrees\Module\CompactTreeChartModule;
+use Fisharebest\Webtrees\Module\ContactsFooterModule;
+use Fisharebest\Webtrees\Module\CookieWarningModule;
use Fisharebest\Webtrees\Module\DeathReportModule;
use Fisharebest\Webtrees\Module\DescendancyChartModule;
use Fisharebest\Webtrees\Module\DescendancyModule;
@@ -50,6 +52,7 @@ use Fisharebest\Webtrees\Module\FanChartModule;
use Fisharebest\Webtrees\Module\FrequentlyAskedQuestionsModule;
use Fisharebest\Webtrees\Module\GoogleAnalyticsModule;
use Fisharebest\Webtrees\Module\GoogleWebmasterToolsModule;
+use Fisharebest\Webtrees\Module\HitCountFooterModule;
use Fisharebest\Webtrees\Module\HourglassChartModule;
use Fisharebest\Webtrees\Module\HtmlBlockModule;
use Fisharebest\Webtrees\Module\IndividualFactsTabModule;
@@ -67,6 +70,7 @@ use Fisharebest\Webtrees\Module\MissingFactsReportModule;
use Fisharebest\Webtrees\Module\ModuleBlockInterface;
use Fisharebest\Webtrees\Module\ModuleChartInterface;
use Fisharebest\Webtrees\Module\ModuleCustomInterface;
+use Fisharebest\Webtrees\Module\ModuleFooterInterface;
use Fisharebest\Webtrees\Module\ModuleInterface;
use Fisharebest\Webtrees\Module\ModuleMenuInterface;
use Fisharebest\Webtrees\Module\ModuleReportInterface;
@@ -104,6 +108,7 @@ use Fisharebest\Webtrees\Module\UserFavoritesModule;
use Fisharebest\Webtrees\Module\UserJournalModule;
use Fisharebest\Webtrees\Module\UserMessagesModule;
use Fisharebest\Webtrees\Module\UserWelcomeModule;
+use Fisharebest\Webtrees\Module\PoweredByWebtreesModule;
use Fisharebest\Webtrees\Module\WelcomeBlockModule;
use Fisharebest\Webtrees\Module\YahrzeitModule;
use Illuminate\Database\Capsule\Manager as DB;
@@ -144,6 +149,8 @@ class Module
'ckeditor' => CkeditorModule::class,
'clippings' => ClippingsCartModule::class,
'compact-chart' => CompactTreeChartModule::class,
+ 'contact-links' => ContactsFooterModule::class,
+ 'cookie-warning' => CookieWarningModule::class,
'death_report' => DeathReportModule::class,
'descendancy' => DescendancyModule::class,
'descendancy_chart' => DescendancyChartModule::class,
@@ -161,6 +168,7 @@ class Module
'gedcom_stats' => FamilyTreeStatisticsModule::class,
'google-analytics' => GoogleAnalyticsModule::class,
'google-webmaster-tools' => GoogleWebmasterToolsModule::class,
+ 'hit-counter' => HitCountFooterModule::class,
'hourglass_chart' => HourglassChartModule::class,
'html' => HtmlBlockModule::class,
'individual_ext_report' => IndividualFamiliesReportModule::class,
@@ -181,6 +189,7 @@ class Module
'pedigree_report' => PedigreeReportModule::class,
'personal_facts' => IndividualFactsTabModule::class,
'places' => PlacesModule::class,
+ 'powered-by-webtrees' => PoweredByWebtreesModule::class,
'random_media' => SlideShowModule::class,
'recent_changes' => RecentChangesModule::class,
'relationships_chart' => RelationshipsChartModule::class,
@@ -294,6 +303,10 @@ class Module
if ($info instanceof stdClass) {
$module->setEnabled($info->status === 'enabled');
+ if ($module instanceof ModuleFooterInterface && $info->footer_order !== null) {
+ $module->setFooterOrder((int) $info->footer_order);
+ }
+
if ($module instanceof ModuleMenuInterface && $info->menu_order !== null) {
$module->setMenuOrder((int) $info->menu_order);
}
@@ -316,6 +329,23 @@ class Module
}
/**
+ * Boot all the modules.
+ */
+ public static function boot(): void
+ {
+ foreach (self::all() as $module) {
+ if (method_exists($module, 'boot')) {
+ try {
+ app()->dispatch($module, 'boot');
+ } catch (Throwable $ex) {
+ $message = '<pre>' . e($ex->getMessage()) . "\n" . e($ex->getTraceAsString()) . '</pre>';
+ FlashMessages::addMessage($message, 'danger');
+ }
+ }
+ }
+ }
+
+ /**
* Load a module in a separate scope, to prevent it from modifying local variables.
*
* @param string $filename
@@ -340,6 +370,18 @@ class Module
}
/**
+ * A function to sort footers
+ *
+ * @return Closure
+ */
+ private static function footerSorter(): Closure
+ {
+ return function (ModuleFooterInterface $x, ModuleFooterInterface $y): int {
+ return $x->getFooterOrder() <=> $y->getFooterOrder();
+ };
+ }
+
+ /**
* A function to sort menus
*
* @return Closure
@@ -413,6 +455,9 @@ class Module
});
switch ($interface) {
+ case ModuleFooterInterface::class:
+ return $modules->sort(self::footerSorter());
+
case ModuleMenuInterface::class:
return $modules->sort(self::menuSorter());
diff --git a/app/Module/ContactsFooterModule.php b/app/Module/ContactsFooterModule.php
new file mode 100644
index 0000000000..5f955a088f
--- /dev/null
+++ b/app/Module/ContactsFooterModule.php
@@ -0,0 +1,193 @@
+<?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\I18N;
+use Fisharebest\Webtrees\Tree;
+use Fisharebest\Webtrees\User;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class ContactsFooterModule - provide a link to the site owner.
+ */
+class ContactsFooterModule extends AbstractModule implements ModuleFooterInterface
+{
+ use ModuleFooterTrait;
+
+ /** @var Request */
+ protected $request;
+
+ /** @var Tree|null */
+ protected $tree;
+
+ /** @var User */
+ protected $user;
+
+ /**
+ * How should this module be labelled on tabs, footers, etc.?
+ *
+ * @return string
+ */
+ public function title(): string
+ {
+ /* I18N: Name of a module */
+ return I18N::translate('Contact information');
+ }
+
+ /**
+ * A sentence describing what this module does.
+ *
+ * @return string
+ */
+ public function description(): string
+ {
+ /* I18N: Description of the “Hit counters” module */
+ return I18N::translate('A link to the site contacts.');
+ }
+
+ /**
+ * Dependency injection.
+ *
+ * @param Tree|null $tree
+ * @param User $user
+ * @param Request $request
+ */
+ public function boot(?Tree $tree, User $user, Request $request): void
+ {
+ $this->tree = $tree;
+ $this->user = $user;
+ $this->request = $request;
+ }
+
+ /**
+ * The default position for this footer. It can be changed in the control panel.
+ *
+ * @return int
+ */
+ public function defaultFooterOrder(): int
+ {
+ return 2;
+ }
+
+ /**
+ * A footer, to be added at the bottom of every page.
+ *
+ * @return string
+ */
+ public function getFooter(): string
+ {
+ if ($this->tree === null) {
+ return '';
+ }
+
+ $contact_user = User::find((int) $this->tree->getPreference('CONTACT_USER_ID'));
+ $webmaster_user = User::find((int) $this->tree->getPreference('WEBMASTER_USER_ID'));
+
+ if ($contact_user instanceof User && $contact_user === $webmaster_user) {
+ return view('modules/contact-links/footer', [
+ 'contact_links' => $this->contactLinkEverything($contact_user),
+ ]);
+ }
+
+ if ($contact_user instanceof User && $webmaster_user instanceof User) {
+ return view('modules/contact-links/footer', [
+ 'contact_links' => $this->contactLinkGenealogy($contact_user) . '<br>' . $this->contactLinkTechnical($webmaster_user),
+ ]);
+ }
+
+ if ($contact_user instanceof User) {
+ return view('modules/contact-links/footer', [
+ 'contact_links' => $this->contactLinkGenealogy($contact_user),
+ ]);
+ }
+
+ if ($webmaster_user instanceof User) {
+ return view('modules/contact-links/footer', [
+ 'contact_links' => $this->contactLinkTechnical($contact_user),
+ ]);
+ }
+
+ return '';
+ }
+
+ /**
+ * Create contact link for both technical and genealogy support.
+ *
+ * @param User $user
+ *
+ * @return string
+ */
+ public function contactLinkEverything(User $user): string
+ {
+ return I18N::translate('For technical support or genealogy questions contact %s.', $this->contactLink($user));
+ }
+
+ /**
+ * Create contact link for genealogy support.
+ *
+ * @param User $user
+ *
+ * @return string
+ */
+ public function contactLinkGenealogy(User $user): string
+ {
+ return I18N::translate('For help with genealogy questions contact %s.', $this->contactLink($user));
+ }
+
+ /**
+ * Create contact link for technical support.
+ *
+ * @param User $user
+ *
+ * @return string
+ */
+ public function contactLinkTechnical(User $user): string
+ {
+ return I18N::translate('For technical support and information contact %s.', $this->contactLink($user));
+ }
+
+ /**
+ * Create a contact link for a user.
+ *
+ * @param User $user
+ *
+ * @return string
+ */
+ private function contactLink(User $user): string
+ {
+ $method = $user->getPreference('contactmethod');
+
+ switch ($method) {
+ case 'none':
+ return '';
+
+ case 'mailto':
+ return '<a href="mailto:' . e($user->getEmail()) . '">' . e($user->getRealName()) . '</a>';
+
+ default:
+ $url = route(Auth::check() ? 'message' : 'contact', [
+ 'ged' => $this->tree->name(),
+ 'to' => $user->getUserName(),
+ 'url' => $this->request->getRequestUri(),
+ ]);
+
+ return '<a href="' . e($url) . '">' . e($user->getRealName()) . '</a>';
+ }
+ }
+}
diff --git a/app/Module/CookieWarningModule.php b/app/Module/CookieWarningModule.php
new file mode 100644
index 0000000000..a425051d1c
--- /dev/null
+++ b/app/Module/CookieWarningModule.php
@@ -0,0 +1,131 @@
+<?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\I18N;
+use Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Site;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class PoweredByWebtreesModule - show a cookie warning, to comply with the GDPR.
+ */
+class CookieWarningModule extends AbstractModule implements ModuleFooterInterface
+{
+ use ModuleFooterTrait;
+
+ /** @var Request */
+ protected $request;
+
+ // We only need to show a warning if we are using tracking
+ protected const TRACKING_MODULES = [
+ GoogleAnalyticsModule::class,
+ MatomoAnalyticsModule::class,
+ StatcounterModule::class,
+ ];
+
+ /**
+ * How should this module be labelled on tabs, footers, etc.?
+ *
+ * @return string
+ */
+ public function title(): string
+ {
+ /* I18N: Name of a module */
+ return I18N::translate('Cookie warning');
+ }
+
+ /**
+ * A sentence describing what this module does.
+ *
+ * @return string
+ */
+ public function description(): string
+ {
+ /* I18N: Description of the “Cookie warning” module */
+ return I18N::translate('Tell visitors why this site uses cookies.');
+ }
+
+ /**
+ * Dependency injection.
+ *
+ * @param Request $request
+ */
+ public function boot(Request $request): void
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * The default position for this footer. It can be changed in the control panel.
+ *
+ * @return int
+ */
+ public function defaultFooterOrder(): int
+ {
+ return 4;
+ }
+
+ /**
+ * A footer, to be added at the bottom of every page.
+ *
+ * @return string
+ */
+ public function getFooter(): string
+ {
+ if ($this->isCookieWarningAcknowledged()) {
+ return '';
+ }
+
+ if ($this->siteUsesAnalyticss()) {
+ return view('modules/cookie-warning/footer');
+ }
+ return '';
+ }
+
+ /**
+ * @return bool
+ */
+ protected function isCookieWarningAcknowledged(): bool
+ {
+ return $this->request->cookies->get('cookie', '') !== '';
+ }
+
+ /**
+ * @return bool
+ */
+ protected function siteUsesAnalyticss(): bool
+ {
+ // If the browser sets the DNT header, then we won't use analytics.
+ if ($this->request->server->get('HTTP_DNT') === '1') {
+ return false;
+ }
+
+ foreach (self::TRACKING_MODULES as $class) {
+ $module = Module::findByClass($class);
+
+ if ($module instanceof ModuleAnalyticsInterface) {
+ if ($module->analyticsCanShow()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/app/Http/Middleware/PageHitCounter.php b/app/Module/HitCountFooterModule.php
index b0b3f2cdc5..23e1d83700 100644
--- a/app/Http/Middleware/PageHitCounter.php
+++ b/app/Module/HitCountFooterModule.php
@@ -15,26 +15,25 @@
*/
declare(strict_types=1);
-namespace Fisharebest\Webtrees\Http\Middleware;
+namespace Fisharebest\Webtrees\Module;
-use Closure;
+use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Session;
use Fisharebest\Webtrees\Tree;
use Fisharebest\Webtrees\User;
-use Fisharebest\Webtrees\View;
use Illuminate\Database\Capsule\Manager as DB;
use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Throwable;
/**
- * Middleware to count requests for particular pages.
- * For historical reasons, we record the names of the original webtrees script and parameter.
+ * Class HitCountFooterModule - show the popular sites in the footer.
*/
-class PageHitCounter implements MiddlewareInterface
+class HitCountFooterModule extends AbstractModule implements ModuleFooterInterface
{
+ use ModuleFooterTrait;
+
// Which pages/routes do we count?
- private const PAGE_NAMES = [
+ // For historical reasons, we record the names of the original webtrees script and parameter.
+ protected const PAGE_NAMES = [
'family' => 'family.php',
'individual' => 'individual.php',
'media' => 'mediaviewer.php',
@@ -45,34 +44,52 @@ class PageHitCounter implements MiddlewareInterface
'user-page' => 'index.php',
];
+ /** @var Request */
+ protected $request;
+
/** @var Tree|null */
- private $tree;
+ protected $tree;
/** @var User */
- private $user;
+ protected $user;
+
+ /** @var int */
+ protected $page_hits = 0;
/**
- * PageHitCounter constructor.
+ * How should this module be labelled on tabs, footers, etc.?
*
- * @param User $user
- * @param Tree|null $tree
+ * @return string
*/
- public function __construct(User $user, ?Tree $tree)
+ public function title(): string
{
- $this->tree = $tree;
- $this->user = $user;
+ /* I18N: Name of a module */
+ return I18N::translate('Hit counters');
}
/**
- * @param Request $request
- * @param Closure $next
+ * A sentence describing what this module does.
*
- * @return Response
- * @throws Throwable
+ * @return string
*/
- public function handle(Request $request, Closure $next): Response
+ public function description(): string
{
- $page_hits = 0;
+ /* I18N: Description of the “Hit counters” module */
+ return I18N::translate('Count the visits to each page');
+ }
+
+ /**
+ * Dependency injection.
+ *
+ * @param Tree|null $tree
+ * @param User $user
+ * @param Request $request
+ */
+ public function boot(?Tree $tree, User $user, Request $request): void
+ {
+ $this->tree = $tree;
+ $this->user = $user;
+ $this->request = $request;
if ($this->tree !== null && $this->tree->getPreference('SHOW_COUNTER')) {
$route = $request->get('route');
@@ -86,37 +103,59 @@ class PageHitCounter implements MiddlewareInterface
case 'note':
case 'repository':
case 'source':
- $page_hits = $this->countHit($this->tree, $page_name, $request->get('xref', ''));
+ $this->page_hits = $this->countHit($page_name, $request->get('xref', ''));
break;
case 'tree-page':
- $page_hits = $this->countHit($this->tree, $page_name, 'gedcom:' . $this->tree->id());
+ $this->page_hits = $this->countHit($page_name, 'gedcom:' . $this->tree->id());
break;
case 'user-page':
- $page_hits = $this->countHit($this->tree, $page_name, 'user:' . $this->user->id());
+ $this->page_hits = $this->countHit($page_name, 'user:' . $this->user->id());
break;
}
}
+ }
+
+ /**
+ * The default position for this footer. It can be changed in the control panel.
+ *
+ * @return int
+ */
+ public function defaultFooterOrder(): int
+ {
+ return 3;
+ }
+
+ /**
+ * A footer, to be added at the bottom of every page.
+ *
+ * @return string
+ */
+ public function getFooter(): string
+ {
+ if ($this->tree === null || $this->page_hits === 0) {
+ return '';
+ }
- // Make the count available to the layout.
- View::share('page_hits', $page_hits);
+ $digits = '<span class="odometer">' . I18N::digits($this->page_hits) . '</span>';
- return $next($request);
+ return view('modules/hit-counter/footer', [
+ 'hit_counter' => I18N::plural('This page has been viewed %s time.', 'This page has been viewed %s times.', $this->page_hits, $digits),
+ ]);
}
/**
* Increment the page count.
*
- * @param Tree $tree
* @param string $page
* @param string $parameter
*
* @return int
*/
- private function countHit(Tree $tree, $page, $parameter): int
+ protected function countHit($page, $parameter): int
{
- $gedcom_id = $tree->id();
+ $gedcom_id = $this->tree->id();
// Don't increment the counter while we stay on the same page.
if (
@@ -127,12 +166,16 @@ class PageHitCounter implements MiddlewareInterface
return Session::get('last_count');
}
- $count = $this->getCount($tree, $page, $parameter);
+ $count = (int) DB::table('hit_counter')
+ ->where('gedcom_id', '=', $this->tree->id())
+ ->where('page_name', '=', $page)
+ ->where('page_parameter', '=', $parameter)
+ ->value('page_count');;
$count++;
DB::table('hit_counter')->updateOrInsert([
- 'gedcom_id' => $tree->id(),
+ 'gedcom_id' => $this->tree->id(),
'page_name' => $page,
'page_parameter' => $parameter,
], [
@@ -146,22 +189,4 @@ class PageHitCounter implements MiddlewareInterface
return $count;
}
-
- /**
- * How many times has a page been viewed
- *
- * @param Tree $tree
- * @param string $page
- * @param string $parameter
- *
- * @return int
- */
- public function getCount(Tree $tree, $page, $parameter): int
- {
- return (int) DB::table('hit_counter')
- ->where('gedcom_id', '=', $tree->id())
- ->where('page_name', '=', $page)
- ->where('page_parameter', '=', $parameter)
- ->value('page_count');
- }
}
diff --git a/app/Module/ModuleFooterInterface.php b/app/Module/ModuleFooterInterface.php
new file mode 100644
index 0000000000..fc3c398611
--- /dev/null
+++ b/app/Module/ModuleFooterInterface.php
@@ -0,0 +1,54 @@
+<?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;
+
+/**
+ * Interface ModuleFooterInterface - Add content to the bottom of every page.
+ */
+interface ModuleFooterInterface extends ModuleInterface
+{
+ /**
+ * Users change change the order of footers using the control panel.
+ *
+ * @param int $footer_order
+ *
+ * @return void
+ */
+ public function setFooterOrder(int $footer_order): void;
+
+ /**
+ * Users change change the order of footers using the control panel.
+ *
+ * @return int
+ */
+ public function getFooterOrder(): int;
+
+ /**
+ * The default position for this footer. It can be changed in the control panel.
+ *
+ * @return int
+ */
+ public function defaultFooterOrder(): int;
+
+ /**
+ * A footer, to be added at the bottom of every page.
+ *
+ * @return string
+ */
+ public function getFooter(): string;
+}
diff --git a/app/Module/ModuleFooterTrait.php b/app/Module/ModuleFooterTrait.php
new file mode 100644
index 0000000000..8889655744
--- /dev/null
+++ b/app/Module/ModuleFooterTrait.php
@@ -0,0 +1,69 @@
+<?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;
+
+/**
+ * Trait ModuleFooterTrait - default implementation of ModuleFooterInterface
+ */
+trait ModuleFooterTrait
+{
+ /** @var int The default position for this footer. It can be changed in the control panel. */
+ protected $footer_order = 0;
+
+ /**
+ * Users change change the order of footers using the control panel.
+ *
+ * @param int $footer_order
+ *
+ * @return void
+ */
+ public function setFooterOrder(int $footer_order): void
+ {
+ $this->footer_order = $footer_order;
+ }
+
+ /**
+ * Users change change the order of footers using the control panel.
+ *
+ * @return int
+ */
+ public function getFooterOrder(): int
+ {
+ return $this->footer_order ?? $this->defaultFooterOrder();
+ }
+
+ /**
+ * The default position for this footer.
+ *
+ * @return int
+ */
+ public function defaultFooterOrder(): int
+ {
+ return 9999;
+ }
+
+ /**
+ * A footer, to be added at the bottom of every page.
+ *
+ * @return string
+ */
+ public function getFooter(): string
+ {
+ return '';
+ }
+}
diff --git a/app/Module/ModuleMenuTrait.php b/app/Module/ModuleMenuTrait.php
index 9b7d57a80f..0f8f81e716 100644
--- a/app/Module/ModuleMenuTrait.php
+++ b/app/Module/ModuleMenuTrait.php
@@ -17,6 +17,9 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Menu;
+use Fisharebest\Webtrees\Tree;
+
/**
* Trait ModuleMenuTrait - default implementation of ModuleMenuInterface
*/
@@ -56,4 +59,16 @@ trait ModuleMenuTrait
{
return 9999;
}
+
+ /**
+ * A menu, to be added to the main application menu.
+ *
+ * @param Tree $tree
+ *
+ * @return Menu|null
+ */
+ public function getMenu(Tree $tree): ?Menu
+ {
+ return null;
+ }
}
diff --git a/app/Module/PoweredByWebtreesModule.php b/app/Module/PoweredByWebtreesModule.php
new file mode 100644
index 0000000000..c219ffbada
--- /dev/null
+++ b/app/Module/PoweredByWebtreesModule.php
@@ -0,0 +1,78 @@
+<?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\I18N;
+use Fisharebest\Webtrees\Footer;
+use Fisharebest\Webtrees\Tree;
+use Fisharebest\Webtrees\User;
+use Fisharebest\Webtrees\Webtrees;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class PoweredByWebtreesModule - provide a link to the project home page.
+ */
+class PoweredByWebtreesModule extends AbstractModule implements ModuleFooterInterface
+{
+ use ModuleFooterTrait;
+
+ /**
+ * How should this module be labelled on tabs, footers, etc.?
+ *
+ * @return string
+ */
+ public function title(): string
+ {
+ /* I18N: Name of a module */
+ return I18N::translate('Powered by webtrees™');
+ }
+
+ /**
+ * A sentence describing what this module does.
+ *
+ * @return string
+ */
+ public function description(): string
+ {
+ /* I18N: Description of the “webtrees” module */
+ return I18N::translate('A link to the webtrees home page.');
+ }
+
+ /**
+ * The default position for this footer. It can be changed in the control panel.
+ *
+ * @return int
+ */
+ public function defaultFooterOrder(): int
+ {
+ return 1;
+ }
+
+ /**
+ * A footer, to be added at the bottom of every page.
+ *
+ * @return string
+ */
+ public function getFooter(): string
+ {
+ return view('modules/powered-by-webtrees/footer', [
+ 'name' => Webtrees::NAME,
+ 'url' => Webtrees::URL,
+ ]);
+ }
+}
diff --git a/app/Schema/Migration41.php b/app/Schema/Migration41.php
new file mode 100644
index 0000000000..a7a5145d53
--- /dev/null
+++ b/app/Schema/Migration41.php
@@ -0,0 +1,39 @@
+<?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 41 to version 42.
+ */
+class Migration41 implements MigrationInterface
+{
+ /**
+ * Upgrade to to the next version
+ *
+ * @return void
+ */
+ public function upgrade(): void
+ {
+ DB::schema()->table('module', function (Blueprint $table): void {
+ $table->integer('footer_order')->nullable()->after('sidebar_order');
+ });
+ }
+}
diff --git a/app/Theme/AbstractTheme.php b/app/Theme/AbstractTheme.php
index de3f0c2d0d..aea3c29d3b 100644
--- a/app/Theme/AbstractTheme.php
+++ b/app/Theme/AbstractTheme.php
@@ -101,138 +101,6 @@ abstract class AbstractTheme
}
/**
- * Create a contact link for a user.
- *
- * @param User $user
- *
- * @return string
- */
- public function contactLink(User $user): string
- {
- $method = $user->getPreference('contactmethod');
-
- switch ($method) {
- case 'none':
- return '';
- case 'mailto':
- return '<a href="mailto:' . e($user->getEmail()) . '">' . e($user->getRealName()) . '</a>';
- default:
- $url = route(Auth::check() ? 'message' : 'contact', [
- 'ged' => $this->tree->name(),
- 'to' => $user->getUserName(),
- 'url' => $this->request->getRequestUri(),
- ]);
-
- return '<a href="' . e($url) . '">' . e($user->getRealName()) . '</a>';
- }
- }
-
- /**
- * Create contact link for both technical and genealogy support.
- *
- * @param User $user
- *
- * @return string
- */
- public function contactLinkEverything(User $user): string
- {
- return I18N::translate('For technical support or genealogy questions contact %s.', $this->contactLink($user));
- }
-
- /**
- * Create contact link for genealogy support.
- *
- * @param User $user
- *
- * @return string
- */
- public function contactLinkGenealogy(User $user): string
- {
- return I18N::translate('For help with genealogy questions contact %s.', $this->contactLink($user));
- }
-
- /**
- * Create contact link for technical support.
- *
- * @param User $user
- *
- * @return string
- */
- public function contactLinkTechnical(User $user): string
- {
- return I18N::translate('For technical support and information contact %s.', $this->contactLink($user));
- }
-
- /**
- * Create contact links for the page footer.
- *
- * @return string
- */
- public function contactLinks()
- {
- $contact_user = User::find((int) $this->tree->getPreference('CONTACT_USER_ID'));
- $webmaster_user = User::find((int) $this->tree->getPreference('WEBMASTER_USER_ID'));
-
- if ($contact_user instanceof User && $contact_user === $webmaster_user) {
- return $this->contactLinkEverything($contact_user);
- }
-
- if ($contact_user instanceof User && $webmaster_user instanceof User) {
- return $this->contactLinkGenealogy($contact_user) . '<br>' . $this->contactLinkTechnical($webmaster_user);
- }
-
- if ($contact_user instanceof User) {
- return $this->contactLinkGenealogy($contact_user);
- }
-
- if ($webmaster_user instanceof User) {
- return $this->contactLinkTechnical($webmaster_user);
- }
-
- return '';
- }
-
- /**
- * Create a cookie warning.
- *
- * @return string
- */
- public function cookieWarning()
- {
- // Do not track?
- if ($this->request->server->get('HTTP_DNT', '')) {
- return '';
- }
-
- // Cookies accepted?
- if ($this->request->cookies->get('cookie', '')) {
- return '';
- }
-
- // Not using trackers or analytics?
- if (Site::getPreference('GOOGLE_ANALYTICS_ID') !== '1' && Site::getPreference('PIWIK_SITE_ID') !== '1' && Site::getPreference('STATCOUNTER_PROJECT_ID') !== '1') {
- return '';
- }
-
- return
- '<div class="wt-cookie-warning">' .
- I18N::translate('Cookies') . ' - ' .
- I18N::translate('This website uses cookies to learn about visitor behaviour.') . ' ' .
- '<button onclick="document.cookie=\'cookie=1\'; this.parentNode.classList.add(\'hidden\');">' . I18N::translate('continue') . '</button>' .
- '</div>';
- }
-
- /**
- * Create the <DOCTYPE> tag.
- *
- * @return string
- */
- public function doctype(): string
- {
- return '<!DOCTYPE html>';
- }
-
- /**
* Add markup to a flash message.
*
* @param stdClass $message
@@ -268,20 +136,6 @@ abstract class AbstractTheme
}
/**
- * Add markup to the contact links.
- *
- * @return string
- */
- public function formatContactLinks()
- {
- if ($this->tree) {
- return '<div class="wt-contact-links">' . $this->contactLinks() . '</div>';
- }
-
- return '';
- }
-
- /**
* Create a pending changes link for the page footer.
*
* @return string
@@ -661,16 +515,6 @@ abstract class AbstractTheme
}
/**
- * A small "powered by webtrees" logo for the footer.
- *
- * @return string
- */
- public function logoPoweredBy(): string
- {
- return '<a href="' . e(Webtrees::URL) . '" class="wt-powered-by-webtrees" dir="ltr">' . e(Webtrees::NAME) . '</a>';
- }
-
- /**
* Generate a menu item to change the blocks on the current (index.php) page.
*
* @return Menu|null
diff --git a/app/Theme/ThemeInterface.php b/app/Theme/ThemeInterface.php
index fd6d1469d0..014bc0a8ce 100644
--- a/app/Theme/ThemeInterface.php
+++ b/app/Theme/ThemeInterface.php
@@ -30,29 +30,6 @@ use Symfony\Component\HttpFoundation\Request;
interface ThemeInterface
{
/**
- * Create a contact link for a user.
- *
- * @param User $user
- *
- * @return string
- */
- public function contactLink(User $user): string;
-
- /**
- * Create a cookie warning.
- *
- * @return string
- */
- public function cookieWarning();
-
- /**
- * Add markup to the contact links.
- *
- * @return string
- */
- public function formatContactLinks();
-
- /**
* Display an icon for this fact.
*
* @param Fact $fact
@@ -123,13 +100,6 @@ interface ThemeInterface
public function individualBoxMenu(Individual $individual): array;
/**
- * A small "powered by webtrees" logo for the footer.
- *
- * @return string
- */
- public function logoPoweredBy(): string;
-
- /**
* Themes menu.
*
* @return Menu|null
diff --git a/app/Webtrees.php b/app/Webtrees.php
index 41569c4e62..62889f883e 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 = 41;
+ public const SCHEMA_VERSION = 42;
// e.g. "dev", "alpha", "beta.3", etc.
public const STABILITY = 'alpha.5';
diff --git a/index.php b/index.php
index fa6e7af0e8..264cc8537f 100644
--- a/index.php
+++ b/index.php
@@ -26,9 +26,9 @@ use Fisharebest\Webtrees\Http\Middleware\CheckCsrf;
use Fisharebest\Webtrees\Http\Middleware\CheckForMaintenanceMode;
use Fisharebest\Webtrees\Http\Middleware\DebugBarData;
use Fisharebest\Webtrees\Http\Middleware\Housekeeping;
-use Fisharebest\Webtrees\Http\Middleware\PageHitCounter;
use Fisharebest\Webtrees\Http\Middleware\UseTransaction;
use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Services\TimeoutService;
use Fisharebest\Webtrees\Session;
use Fisharebest\Webtrees\Site;
@@ -267,7 +267,6 @@ try {
}
if ($request->getMethod() === Request::METHOD_GET) {
- $middleware_stack[] = PageHitCounter::class;
$middleware_stack[] = Housekeeping::class;
}
@@ -276,6 +275,9 @@ try {
$middleware_stack[] = CheckCsrf::class;
}
+ // Boot the modules.
+ Module::boot();
+
// Apply the middleware using the "onion" pattern.
$pipeline = array_reduce($middleware_stack, function (Closure $next, string $middleware): Closure {
// Create a closure to apply the middleware.
diff --git a/resources/views/admin/components.phtml b/resources/views/admin/components.phtml
index 6aeb20f4fd..b90650c0d5 100644
--- a/resources/views/admin/components.phtml
+++ b/resources/views/admin/components.phtml
@@ -15,8 +15,10 @@ use Fisharebest\Webtrees\View; ?>
<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') : ?>
+ <?php if ($uses_access) : ?>
+ <th class="text-center"><?= I18N::translate('Access level') ?></th>
+ <?php endif ?>
+ <?php if ($uses_sorting) : ?>
<th class="text-center"><?= I18N::translate('Move up') ?></th>
<th class="text-center"><?= I18N::translate('Move down') ?></th>
<?php endif ?>
@@ -28,7 +30,9 @@ use Fisharebest\Webtrees\View; ?>
<tr>
<th scope="col">
<input type="hidden" name="order[]" value="<?= e($module->name()) ?>"?>
- <?= $module->title() ?>
+ <span title="<?= e($module->description()) ?>">
+ <?= $module->title() ?>
+ </span>
<?php if ($module instanceof ModuleConfigInterface) : ?>
<a href="<?= e($module->getConfigLink()) ?>" title="<?= I18N::translate('Preferences') ?>">
<?= view('icons/preferences') ?>
@@ -48,7 +52,8 @@ use Fisharebest\Webtrees\View; ?>
</label>
</td>
- <td class="text-center">
+ <?php if ($uses_access) : ?>
+ <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">
@@ -90,8 +95,9 @@ use Fisharebest\Webtrees\View; ?>
</span>
</button>
</td>
+ <?php endif ?>
- <?php if ($component === 'menu' || $component === 'sidebar' || $component === 'tab') : ?>
+ <?php if ($uses_sorting) : ?>
<td class="move up text-center">
<a href="#" title="<?= I18N::translate('Move up') ?>">
<?= view('icons/arrow-up') ?>
diff --git a/resources/views/admin/control-panel.phtml b/resources/views/admin/control-panel.phtml
index 678b249958..43b04710d1 100644
--- a/resources/views/admin/control-panel.phtml
+++ b/resources/views/admin/control-panel.phtml
@@ -337,6 +337,12 @@
<?= I18N::translate('Reports') ?>
</a>
</li>
+ <li>
+ <span class="fa-li"><?= view('icons/footer') ?></span>
+ <a href="<?= e(route('footers')) ?>">
+ <?= I18N::translate('Footers') ?>
+ </a>
+ </li>
</ul>
</div>
<div class="col-sm-6">
diff --git a/resources/views/admin/modules.phtml b/resources/views/admin/modules.phtml
index 20ceea1f0e..feb529004b 100644
--- a/resources/views/admin/modules.phtml
+++ b/resources/views/admin/modules.phtml
@@ -5,6 +5,7 @@
<?php use Fisharebest\Webtrees\Module\ModuleChartInterface; ?>
<?php use Fisharebest\Webtrees\Module\ModuleConfigInterface; ?>
<?php use Fisharebest\Webtrees\Module\ModuleCustomInterface; ?>
+<?php use Fisharebest\Webtrees\Module\ModuleFooterInterface; ?>
<?php use Fisharebest\Webtrees\Module\ModuleMenuInterface; ?>
<?php use Fisharebest\Webtrees\Module\ModuleReportInterface; ?>
<?php use Fisharebest\Webtrees\Module\ModuleSidebarInterface; ?>
@@ -75,6 +76,10 @@
<?= view('icons/report') ?>
<span class="sr-only"><?= I18N::translate('Reports') ?></span>
</th>
+ <th title="<?= I18N::translate('Footers') ?>">
+ <?= view('icons/footer') ?>
+ <span class="sr-only"><?= I18N::translate('Footers') ?></span>
+ </th>
<th title="<?= I18N::translate('Tracking and analytics') ?>">
<?= view('icons/analytics') ?>
<span class="sr-only"><?= I18N::translate('Tracking and analytics') ?></span>
@@ -184,6 +189,14 @@
-
<?php endif ?>
</td>
+ <td class="text-center text-muted" title="<?= I18N::translate('Footer') ?>">
+ <?php if ($module instanceof ModuleFooterInterface) : ?>
+ <?= view('icons/footer') ?>
+ <span class="sr-only"><?= I18N::translate('Footer') ?></span>
+ <?php else : ?>
+ -
+ <?php endif ?>
+ </td>
<td class="text-center text-muted" title="<?= I18N::translate('Tracking and analytics') ?>">
<?php if ($module instanceof ModuleAnalyticsInterface) : ?>
<?= view('icons/analytics') ?>
diff --git a/resources/views/icons/footer.phtml b/resources/views/icons/footer.phtml
new file mode 100644
index 0000000000..a43d75faa5
--- /dev/null
+++ b/resources/views/icons/footer.phtml
@@ -0,0 +1 @@
+<i class="fas fa-grip-lines fa-fw wt-icon-footer" aria-hidden="true"></i>
diff --git a/resources/views/layouts/default.phtml b/resources/views/layouts/default.phtml
index 236c6f3358..ba197351ca 100644
--- a/resources/views/layouts/default.phtml
+++ b/resources/views/layouts/default.phtml
@@ -4,6 +4,7 @@
<?php use Fisharebest\Webtrees\I18N; ?>
<?php use Fisharebest\Webtrees\Module; ?>
<?php use Fisharebest\Webtrees\Module\ModuleAnalyticsInterface; ?>
+<?php use Fisharebest\Webtrees\Module\ModuleFooterInterface; ?>
<?php use Fisharebest\Webtrees\Theme; ?>
<?php use Fisharebest\Webtrees\View; ?>
<?php use Fisharebest\Webtrees\Webtrees; ?>
@@ -119,21 +120,7 @@
<footer class="wt-footer-container">
<div class="wt-footer-content container d-print-none">
- <?= Theme::theme()->formatContactLinks() ?>
- <?= Theme::theme()->logoPoweredBy() ?>
-
- <?php if ($page_hits ?? 0 > 0) : ?>
- <div class="wt-page-views">
- <?= I18N::plural(
- 'This page has been viewed %s time.',
- 'This page has been viewed %s times.',
- $page_hits,
- '<span class="odometer">' . I18N::digits($page_hits) . '</span>'
- ) ?>
- </div>
- <?php endif ?>
-
- <?= Theme::theme()->cookieWarning()?>
+ <?= Module::findByInterface(ModuleFooterInterface::class)->map(function (ModuleFooterInterface $module) use ($tree): string { return app()->dispatch($module, 'getFooter'); })->implode('') ?>
</div>
</footer>
diff --git a/resources/views/modules/contact-links/footer.phtml b/resources/views/modules/contact-links/footer.phtml
new file mode 100644
index 0000000000..23efa3c6e6
--- /dev/null
+++ b/resources/views/modules/contact-links/footer.phtml
@@ -0,0 +1,3 @@
+<div class="wt-footer-contact-links">
+ <?= $contact_links ?>
+</div>
diff --git a/resources/views/modules/cookie-warning/footer.phtml b/resources/views/modules/cookie-warning/footer.phtml
new file mode 100644
index 0000000000..df4331e25c
--- /dev/null
+++ b/resources/views/modules/cookie-warning/footer.phtml
@@ -0,0 +1,13 @@
+<?php use Fisharebest\Webtrees\I18N; ?>
+
+<div class="wt-footer-cookie-warning">
+ <div class="wt-cookie-warning">
+ <?= I18N::translate('Cookies') ?>
+ -
+ <?= I18N::translate('This website uses cookies to learn about visitor behaviour.') ?>
+
+ <button onclick="document.cookie='cookie=1'; this.parentNode.classList.add('d-none');">
+ <?= I18N::translate('continue') ?>
+ </button>
+ </div>
+</div>
diff --git a/resources/views/modules/hit-counter/footer.phtml b/resources/views/modules/hit-counter/footer.phtml
new file mode 100644
index 0000000000..21d473c484
--- /dev/null
+++ b/resources/views/modules/hit-counter/footer.phtml
@@ -0,0 +1,3 @@
+<div class="wt-footer-hit-counter">
+ <?= $hit_counter ?>
+</div>
diff --git a/resources/views/modules/powered-by-webtrees/footer.phtml b/resources/views/modules/powered-by-webtrees/footer.phtml
new file mode 100644
index 0000000000..d9ea7cc288
--- /dev/null
+++ b/resources/views/modules/powered-by-webtrees/footer.phtml
@@ -0,0 +1,5 @@
+<div class="wt-footer-powered-by-webtrees">
+ <a href="<?= e($url) ?>" class="wt-powered-by-webtrees" dir="ltr">
+ <?= e($name) ?>
+ </a>
+</div>
diff --git a/routes/web.php b/routes/web.php
index 7ecd2debfe..c86bfe93d3 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -33,6 +33,8 @@ if (Auth::isAdmin()) {
'POST:blocks' => 'Admin\\ModuleController@updateBlocks',
'GET:charts' => 'Admin\\ModuleController@listCharts',
'POST:charts' => 'Admin\\ModuleController@updateCharts',
+ 'GET:footers' => 'Admin\\ModuleController@listFooters',
+ 'POST:footers' => 'Admin\\ModuleController@updateFooters',
'GET:menus' => 'Admin\\ModuleController@listMenus',
'POST:menus' => 'Admin\\ModuleController@updateMenus',
'GET:reports' => 'Admin\\ModuleController@listReports',
diff --git a/themes/_common/css-2.0.0/style.css b/themes/_common/css-2.0.0/style.css
index 86a706e6b1..64852758f7 100755
--- a/themes/_common/css-2.0.0/style.css
+++ b/themes/_common/css-2.0.0/style.css
@@ -42,10 +42,6 @@
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-global {
diff --git a/themes/clouds/css-2.0.0/style.css b/themes/clouds/css-2.0.0/style.css
index 854c87b850..1bbd748da3 100755
--- a/themes/clouds/css-2.0.0/style.css
+++ b/themes/clouds/css-2.0.0/style.css
@@ -278,10 +278,6 @@ a > .wt-icon-arrow-up:hover::before {
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-global {
diff --git a/themes/colors/css-2.0.0/style.css b/themes/colors/css-2.0.0/style.css
index 2b4261e56c..bdb40c5a9b 100755
--- a/themes/colors/css-2.0.0/style.css
+++ b/themes/colors/css-2.0.0/style.css
@@ -323,10 +323,6 @@ i[class*="wt-icon-media"] {
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-block-header::before {
diff --git a/themes/fab/css-2.0.0/style.css b/themes/fab/css-2.0.0/style.css
index 284c56a7af..8970c49ff8 100755
--- a/themes/fab/css-2.0.0/style.css
+++ b/themes/fab/css-2.0.0/style.css
@@ -114,10 +114,6 @@ table {
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-global {
diff --git a/themes/minimal/css-2.0.0/style.css b/themes/minimal/css-2.0.0/style.css
index f48d0366d3..2d4817c24c 100755
--- a/themes/minimal/css-2.0.0/style.css
+++ b/themes/minimal/css-2.0.0/style.css
@@ -114,10 +114,6 @@ table {
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-global {
diff --git a/themes/webtrees/css-2.0.0/style.css b/themes/webtrees/css-2.0.0/style.css
index 97dc31ab0a..71dd42d340 100755
--- a/themes/webtrees/css-2.0.0/style.css
+++ b/themes/webtrees/css-2.0.0/style.css
@@ -268,10 +268,6 @@ a > .wt-icon-arrow-up:hover::before {
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-global {
diff --git a/themes/xenea/css-2.0.0/style.css b/themes/xenea/css-2.0.0/style.css
index 7e99e397c4..cc473128ec 100755
--- a/themes/xenea/css-2.0.0/style.css
+++ b/themes/xenea/css-2.0.0/style.css
@@ -268,10 +268,6 @@ a > .wt-icon-arrow-up:hover::before {
* | +---wt-page-content
* +---wt-footer-container
* +---wt-footer-content
- * +---wt-contact-links
- * +---wt-powered-by-webtrees
- * +---wt-page-views
- * +---wt-cookie-warning
*/
.wt-global {