diff --git a/assets/src/modules/Announcement.js b/assets/src/modules/Announcement.js new file mode 100644 index 0000000000..546032f047 --- /dev/null +++ b/assets/src/modules/Announcement.js @@ -0,0 +1,149 @@ +/** + * @module modules/Announcement.js + * @name Announcement + * @copyright 2026 3Liz + * @license MPL-2.0 + */ + +/** + * Fetches pending announcements from the server and displays them + * sequentially in the #lizmap-modal Bootstrap 5 modal. + */ +export default class Announcement { + + init() { + const announcementUrl = globalThis['lizUrls']?.announcement; + if (!announcementUrl) { + return; + } + + const params = globalThis['lizUrls'].params || {}; + + const url = new URL(announcementUrl, window.location.origin); + if (params.repository) { + url.searchParams.set('repository', params.repository); + } + if (params.project) { + url.searchParams.set('project', params.project); + } + + fetch(url.toString()) + .then(response => response.json()) + .then(data => { + if (data.announcements && data.announcements.length > 0) { + this._showAnnouncements(data.announcements); + } + }) + .catch(err => { + console.warn('Failed to fetch announcements:', err); + }); + } + + /** + * Show announcements one at a time. + * @param {Array} announcements + * @param {number} index + */ + _showAnnouncements(announcements, index = 0) { + if (index >= announcements.length) { + return; + } + + const announcement = announcements[index]; + this._showModal(announcement, () => { + this._markSeen(announcement.id); + // Show next announcement after a short delay + if (index + 1 < announcements.length) { + setTimeout(() => { + this._showAnnouncements(announcements, index + 1); + }, 500); + } + }); + } + + /** + * Display a single announcement in the #lizmap-modal. + * @param {Object} announcement + * @param {Function} onClose + */ + _showModal(announcement, onClose) { + const modalEl = document.getElementById('lizmap-modal'); + if (!modalEl) { + return; + } + + modalEl.innerHTML = ` + + `; + + const handleHidden = () => { + modalEl.removeEventListener('hidden.bs.modal', handleHidden); + if (onClose) { + onClose(); + } + }; + modalEl.addEventListener('hidden.bs.modal', handleHidden); + + // Use Bootstrap 5 Modal API if available, fallback to jQuery + if (globalThis.bootstrap && globalThis.bootstrap.Modal) { + const modal = new globalThis.bootstrap.Modal(modalEl); + modal.show(); + } else if (globalThis.jQuery) { + globalThis.jQuery(modalEl).modal('show'); + } + } + + /** + * Tell the server the user has seen this announcement. + * @param {number} announcementId + */ + _markSeen(announcementId) { + const markSeenUrl = globalThis['lizUrls']?.announcementMarkSeen; + if (!markSeenUrl) { + // Store in sessionStorage as fallback for anonymous users + try { + const key = 'lizmap_announcement_seen'; + const seen = JSON.parse(sessionStorage.getItem(key) || '[]'); + if (!seen.includes(announcementId)) { + seen.push(announcementId); + sessionStorage.setItem(key, JSON.stringify(seen)); + } + } catch (e) { + // sessionStorage not available + } + return; + } + + const url = new URL(markSeenUrl, window.location.origin); + url.searchParams.set('id', announcementId); + + fetch(url.toString(), { method: 'POST' }) + .catch(err => { + console.warn('Failed to mark announcement as seen:', err); + }); + } + + /** + * Escape HTML special characters for safe text display. + * @param {string} text + * @returns {string} + */ + _escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } +} diff --git a/assets/src/modules/Lizmap.js b/assets/src/modules/Lizmap.js index 881428d4d4..da4015f68b 100644 --- a/assets/src/modules/Lizmap.js +++ b/assets/src/modules/Lizmap.js @@ -28,6 +28,7 @@ import Permalink from './Permalink.js'; import Search from './Search.js'; import Tooltip from './Tooltip.js'; import LocateByLayer from './LocateByLayer.js'; +import Announcement from './Announcement.js'; import WMSCapabilities from 'ol/format/WMSCapabilities.js'; import WFSCapabilities from 'ol-wfs-capabilities'; @@ -199,6 +200,9 @@ export default class Lizmap { * ['lizmap.modules.initialized'] * ); */ + this.announcement = new Announcement(); + this.announcement.init(); + eventDispatcher.dispatch('lizmap.modules.initialized'); } }; diff --git a/lizmap/modules/admin/classes/config.listener.php b/lizmap/modules/admin/classes/config.listener.php index bc3c62a217..00bd62acb7 100644 --- a/lizmap/modules/admin/classes/config.listener.php +++ b/lizmap/modules/admin/classes/config.listener.php @@ -51,6 +51,17 @@ public function onmasteradminGetMenuContent($event) ); } + // Configure project announcements + if (jAcl2::check('lizmap.admin.announcement.manage')) { + $bloc->childItems[] = new masterAdminMenuItem( + 'lizmap_announcements', + jLocale::get('admin~admin.menu.lizmap.announcements.label'), + jUrl::get('admin~announcement:index'), + 116, + 'lizmap' + ); + } + // Configure the theme if (jAcl2::check('lizmap.admin.theme.update')) { $bloc->childItems[] = new masterAdminMenuItem( diff --git a/lizmap/modules/admin/controllers/announcement.classic.php b/lizmap/modules/admin/controllers/announcement.classic.php new file mode 100644 index 0000000000..55438ff24f --- /dev/null +++ b/lizmap/modules/admin/controllers/announcement.classic.php @@ -0,0 +1,238 @@ + array('jacl2.right' => 'lizmap.admin.announcement.manage'), + ); + + /** + * List all announcements. + * + * @return jResponseHtml + */ + public function index() + { + /** @var jResponseHtml $rep */ + $rep = $this->getResponse('html'); + + $dao = jDao::get('lizmap~announcement', 'lizlog'); + $announcements = $dao->findAll(); + $conditions = jDao::createConditions(); + $announcementCount = $dao->countBy($conditions); + + $tpl = new jTpl(); + $tpl->assign('announcements', $announcements); + $tpl->assign('announcementCount', $announcementCount); + $rep->body->assign('MAIN', $tpl->fetch('announcement_list')); + $rep->body->assign('selectedMenuItem', 'lizmap_announcements'); + + return $rep; + } + + /** + * Display the create form. + * + * @return jResponseHtml + */ + public function create() + { + /** @var jResponseHtml $rep */ + $rep = $this->getResponse('html'); + + $tpl = new jTpl(); + $tpl->assign('id', 0); + $tpl->assign('title', ''); + $tpl->assign('content', ''); + $tpl->assign('target_repository', ''); + $tpl->assign('target_project', ''); + $tpl->assign('target_groups', ''); + $tpl->assign('selectedGroupsArray', array()); + $tpl->assign('max_display_count', 1); + $tpl->assign('is_active', true); + $tpl->assign('repositories', lizmap::getRepositoryList()); + $tpl->assign('groups', jAcl2DbUserGroup::getGroupList()); + $rep->body->assign('MAIN', $tpl->fetch('announcement_form')); + $rep->body->assign('selectedMenuItem', 'lizmap_announcements'); + + return $rep; + } + + /** + * Display the edit form. + * + * @return jResponseHtml|jResponseRedirect + */ + public function edit() + { + $id = $this->intParam('id'); + if (!$id) { + /** @var jResponseRedirect $rep */ + $rep = $this->getResponse('redirect'); + $rep->action = 'admin~announcement:index'; + + return $rep; + } + + /** @var jResponseHtml $rep */ + $rep = $this->getResponse('html'); + + $dao = jDao::get('lizmap~announcement', 'lizlog'); + $record = $dao->get($id); + + if (!$record) { + /** @var jResponseRedirect $rep */ + $rep = $this->getResponse('redirect'); + jMessage::add(jLocale::get('admin~admin.announcement.error.notfound'), 'error'); + $rep->action = 'admin~announcement:index'; + + return $rep; + } + + $tpl = new jTpl(); + $selectedGroupsArray = !empty($record->target_groups) + ? array_map('trim', explode(',', $record->target_groups)) + : array(); + + $tpl->assign('id', $record->id); + $tpl->assign('title', $record->title); + $tpl->assign('content', $record->content); + $tpl->assign('target_repository', $record->target_repository); + $tpl->assign('target_project', $record->target_project); + $tpl->assign('target_groups', $record->target_groups); + $tpl->assign('selectedGroupsArray', $selectedGroupsArray); + $tpl->assign('max_display_count', $record->max_display_count); + $tpl->assign('is_active', $record->is_active); + $tpl->assign('repositories', lizmap::getRepositoryList()); + $tpl->assign('groups', jAcl2DbUserGroup::getGroupList()); + $rep->body->assign('MAIN', $tpl->fetch('announcement_form')); + $rep->body->assign('selectedMenuItem', 'lizmap_announcements'); + + return $rep; + } + + /** + * Save an announcement (create or update). + * + * @return jResponseRedirect + */ + public function save() + { + /** @var jResponseRedirect $rep */ + $rep = $this->getResponse('redirect'); + + $id = $this->intParam('id'); + $title = $this->param('title'); + $content = $this->param('content'); + + if (!$title || !$content) { + jMessage::add(jLocale::get('admin~admin.announcement.error.required'), 'error'); + $rep->action = 'admin~announcement:index'; + + return $rep; + } + + $dao = jDao::get('lizmap~announcement', 'lizlog'); + + if ($id) { + // Update existing + $record = $dao->get($id); + if (!$record) { + jMessage::add(jLocale::get('admin~admin.announcement.error.notfound'), 'error'); + $rep->action = 'admin~announcement:index'; + + return $rep; + } + } else { + // Create new + $record = jDao::createRecord('lizmap~announcement', 'lizlog'); + } + + $record->title = htmlspecialchars($title); + $record->content = $content; + $record->target_repository = $this->param('target_repository', ''); + $record->target_project = $this->param('target_project', ''); + + // Handle groups multi-select + $groups = $this->param('target_groups'); + if (is_array($groups)) { + $record->target_groups = implode(',', $groups); + } else { + $record->target_groups = $groups ?: ''; + } + + $record->max_display_count = $this->intParam('max_display_count', 1); + $record->is_active = $this->param('is_active') ? true : false; + + if ($id) { + $dao->update($record); + jMessage::add(jLocale::get('admin~admin.announcement.saved')); + } else { + $dao->insert($record); + jMessage::add(jLocale::get('admin~admin.announcement.created')); + } + + $rep->action = 'admin~announcement:index'; + + return $rep; + } + + /** + * Toggle announcement active status. + * + * @return jResponseRedirect + */ + public function toggle() + { + /** @var jResponseRedirect $rep */ + $rep = $this->getResponse('redirect'); + + $id = $this->intParam('id'); + if ($id) { + $dao = jDao::get('lizmap~announcement', 'lizlog'); + $record = $dao->get($id); + if ($record) { + $record->is_active = !$record->is_active; + $dao->update($record); + jMessage::add(jLocale::get('admin~admin.announcement.toggled')); + } + } + + $rep->action = 'admin~announcement:index'; + + return $rep; + } + + /** + * Delete an announcement. + * + * @return jResponseRedirect + */ + public function delete() + { + /** @var jResponseRedirect $rep */ + $rep = $this->getResponse('redirect'); + + $id = $this->intParam('id'); + if ($id) { + $dao = jDao::get('lizmap~announcement', 'lizlog'); + $dao->delete($id); + jMessage::add(jLocale::get('admin~admin.announcement.deleted')); + } + + $rep->action = 'admin~announcement:index'; + + return $rep; + } +} diff --git a/lizmap/modules/admin/install/install.php b/lizmap/modules/admin/install/install.php index 7cbdef4995..c9bbc99516 100644 --- a/lizmap/modules/admin/install/install.php +++ b/lizmap/modules/admin/install/install.php @@ -34,6 +34,7 @@ public function install() jAcl2DbManager::createRight('lizmap.admin.server.information.view', 'admin~jacl2.lizmap.admin.server.information.view', 'lizmap.admin.grp'); jAcl2DbManager::createRight('lizmap.admin.lizmap.log.view', 'admin~jacl2.lizmap.admin.lizmap.log.view', 'lizmap.admin.grp'); jAcl2DbManager::createRight('lizmap.admin.lizmap.log.delete', 'admin~jacl2.lizmap.admin.lizmap.log.delete', 'lizmap.admin.grp'); + jAcl2DbManager::createRight('lizmap.admin.announcement.manage', 'admin~jacl2.lizmap.admin.announcement.manage', 'lizmap.admin.grp'); jAcl2DbManager::createRight('lizmap.repositories.view', 'admin~jacl2.lizmap.repositories.view', 'lizmap.grp'); // the right code could be lizmap.view.repository.projects jAcl2DbManager::createRight('lizmap.tools.edition.use', 'admin~jacl2.lizmap.tools.edition.use', 'lizmap.grp'); @@ -57,6 +58,7 @@ public function install() jAcl2DbManager::addRight('admins', 'lizmap.admin.server.information.view'); jAcl2DbManager::addRight('admins', 'lizmap.admin.lizmap.log.view'); jAcl2DbManager::addRight('admins', 'lizmap.admin.lizmap.log.delete'); + jAcl2DbManager::addRight('admins', 'lizmap.admin.announcement.manage'); // Create a new publishers group jAcl2DbUserGroup::createGroup('Publishers', 'publishers'); diff --git a/lizmap/modules/admin/install/upgrade_announcement_acl.php b/lizmap/modules/admin/install/upgrade_announcement_acl.php new file mode 100644 index 0000000000..27198c6f11 --- /dev/null +++ b/lizmap/modules/admin/install/upgrade_announcement_acl.php @@ -0,0 +1,24 @@ +firstExec('acl2_announcement')) { + $this->useDbProfile('auth'); + + // Create announcement management right + jAcl2DbManager::createRight('lizmap.admin.announcement.manage', 'admin~jacl2.lizmap.admin.announcement.manage', 'lizmap.admin.grp'); + + // Assign to admins group + if (jAcl2DbUserGroup::getGroup('admins')) { + jAcl2DbManager::addRight('admins', 'lizmap.admin.announcement.manage'); + } + } + } +} diff --git a/lizmap/modules/admin/locales/en_US/admin.UTF-8.properties b/lizmap/modules/admin/locales/en_US/admin.UTF-8.properties index cec9fa2e83..f0ab7d0ee7 100644 --- a/lizmap/modules/admin/locales/en_US/admin.UTF-8.properties +++ b/lizmap/modules/admin/locales/en_US/admin.UTF-8.properties @@ -9,6 +9,7 @@ menu.lizmap.landingPageContent.label=Landing page content menu.lizmap.logs.label=Lizmap Logs menu.lizmap.logs.view.label=View logs menu.lizmap.search.label=Lizmap search +menu.lizmap.announcements.label=Project Announcements menu.server.label=Server @@ -419,6 +420,47 @@ landingPageContent.unauthed.position.before=Before content for authenticated landingPageContent.unauthed.position.after=After content for authenticated landingPageContent.unauthed.position.no=No +announcement.list.title=Project Announcements +announcement.list.empty=No announcements found. +announcement.create=Create announcement +announcement.create.title=Create announcement +announcement.edit.title=Edit announcement +announcement.col.title=Title +announcement.col.repository=Repository +announcement.col.project=Project +announcement.col.groups=Target groups +announcement.col.maxviews=Max views +announcement.col.status=Status +announcement.col.created=Created +announcement.col.actions=Actions +announcement.status.active=Active +announcement.status.inactive=Inactive +announcement.btn.edit=Edit +announcement.btn.activate=Activate +announcement.btn.deactivate=Deactivate +announcement.btn.delete=Delete +announcement.btn.save=Save +announcement.confirm.delete=Are you sure you want to delete this announcement? +announcement.form.title=Title +announcement.form.content=Content (HTML) +announcement.form.content.help=HTML content that will be displayed in the popup modal. +announcement.form.repository=Target repository +announcement.form.repository.help=Leave empty to target all repositories. +announcement.form.project=Target project +announcement.form.project.help=Leave empty to target all projects in the selected repository. +announcement.form.groups=Target groups +announcement.form.groups.help=Select groups to target. Leave empty to target all users. +announcement.form.maxviews=Max display count +announcement.form.maxviews.help=Maximum number of times this announcement will be shown to each user. 0 means unlimited. +announcement.form.active=Active +announcement.form.all=All +announcement.saved=Announcement updated successfully. +announcement.created=Announcement created successfully. +announcement.toggled=Announcement status toggled. +announcement.deleted=Announcement deleted successfully. +announcement.error.notfound=Announcement not found. +announcement.error.required=Title and content are required. + landingPageContent.error.save=Error while saving the landing page contents landingPageContent.error.submit=An error occurred while saving the content. Your session has been terminated, please retry. landingPageContent.saved=Content of the landing page has been saved diff --git a/lizmap/modules/admin/locales/en_US/jacl2.UTF-8.properties b/lizmap/modules/admin/locales/en_US/jacl2.UTF-8.properties index 6c978aadbd..afa7e3c9f2 100644 --- a/lizmap/modules/admin/locales/en_US/jacl2.UTF-8.properties +++ b/lizmap/modules/admin/locales/en_US/jacl2.UTF-8.properties @@ -15,6 +15,7 @@ lizmap.admin.theme.update=Update the application theme lizmap.admin.server.information.view=View the detailed server information lizmap.admin.lizmap.log.view=View the lizmap logs lizmap.admin.lizmap.log.delete=Delete the lizmap logs +lizmap.admin.announcement.manage=Manage project announcements lizmap.repositories.view=View projects in the repository lizmap.tools.edition.use=Use the Edition tool diff --git a/lizmap/modules/admin/templates/announcement_form.tpl b/lizmap/modules/admin/templates/announcement_form.tpl new file mode 100644 index 0000000000..57ee32fd1e --- /dev/null +++ b/lizmap/modules/admin/templates/announcement_form.tpl @@ -0,0 +1,64 @@ + +

{if $id}{@admin~admin.announcement.edit.title@}{else}{@admin~admin.announcement.create.title@}{/if}

+ +
+
+ + +
+ + +
+ +
+ + +
{@admin~admin.announcement.form.content.help@}
+
+ +
+ + +
{@admin~admin.announcement.form.repository.help@}
+
+ +
+ + +
{@admin~admin.announcement.form.project.help@}
+
+ +
+ + +
{@admin~admin.announcement.form.groups.help@}
+
+ +
+ + +
{@admin~admin.announcement.form.maxviews.help@}
+
+ +
+ + +
+ +
+ + {@admin~admin.configuration.button.back.label@} +
+
+
diff --git a/lizmap/modules/admin/templates/announcement_list.tpl b/lizmap/modules/admin/templates/announcement_list.tpl new file mode 100644 index 0000000000..d7995a11d2 --- /dev/null +++ b/lizmap/modules/admin/templates/announcement_list.tpl @@ -0,0 +1,55 @@ + +

{@admin~admin.announcement.list.title@}

+ +{ifacl2 'lizmap.admin.announcement.manage'} +
+ +
+ {@admin~admin.announcement.create@} +
+ + {if $announcementCount == 0} +

{@admin~admin.announcement.list.empty@}

+ {else} + + + + + + + + + + + + + + + {foreach $announcements as $item} + + + + + + + + + + + {/foreach} + +
{@admin~admin.announcement.col.title@}{@admin~admin.announcement.col.repository@}{@admin~admin.announcement.col.project@}{@admin~admin.announcement.col.groups@}{@admin~admin.announcement.col.maxviews@}{@admin~admin.announcement.col.status@}{@admin~admin.announcement.col.created@}{@admin~admin.announcement.col.actions@}
{$item->title}{$item->target_repository}{$item->target_project}{$item->target_groups}{$item->max_display_count} + {if $item->is_active} + {@admin~admin.announcement.status.active@} + {else} + {@admin~admin.announcement.status.inactive@} + {/if} + {$item->created_at|jdatetime:'db_datetime','lang_datetime'} + {@admin~admin.announcement.btn.edit@} + {if $item->is_active}{@admin~admin.announcement.btn.deactivate@}{else}{@admin~admin.announcement.btn.activate@}{/if} + {@admin~admin.announcement.btn.delete@} +
+ {/if} + +
+{/ifacl2} diff --git a/lizmap/modules/admin/urls.xml b/lizmap/modules/admin/urls.xml index 27b708ad9d..ec6ef357a2 100644 --- a/lizmap/modules/admin/urls.xml +++ b/lizmap/modules/admin/urls.xml @@ -54,4 +54,11 @@ + + + + + + + diff --git a/lizmap/modules/lizmap/controllers/announcement.classic.php b/lizmap/modules/lizmap/controllers/announcement.classic.php new file mode 100644 index 0000000000..fca56cd366 --- /dev/null +++ b/lizmap/modules/lizmap/controllers/announcement.classic.php @@ -0,0 +1,128 @@ +getResponse('json'); + + $repository = $this->param('repository', ''); + $project = $this->param('project', ''); + + $dao = jDao::get('lizmap~announcement', 'lizlog'); + $activeAnnouncements = $dao->findActive(); + + $isConnected = jAuth::isConnected(); + $usr_login = ''; + $userGroups = array(); + + if ($isConnected) { + $usr_login = jAuth::getUserSession()->login; + $groups = jAcl2DbUserGroup::getGroupList($usr_login); + foreach ($groups as $group) { + $userGroups[] = $group->id_aclgrp; + } + } + + $viewDao = jDao::get('lizmap~announcementView', 'lizlog'); + + $pending = array(); + foreach ($activeAnnouncements as $announcement) { + // Filter by repository + if (!empty($announcement->target_repository) && $announcement->target_repository !== $repository) { + continue; + } + + // Filter by project + if (!empty($announcement->target_project) && $announcement->target_project !== $project) { + continue; + } + + // Filter by groups + if (!empty($announcement->target_groups)) { + $targetGroups = explode(',', $announcement->target_groups); + $targetGroups = array_map('trim', $targetGroups); + if (!$isConnected || empty(array_intersect($targetGroups, $userGroups))) { + continue; + } + } + + // Check view count for authenticated users + if ($isConnected && $announcement->max_display_count > 0) { + $view = $viewDao->getByAnnouncementAndUser($announcement->id, $usr_login); + if ($view && $view->view_count >= $announcement->max_display_count) { + continue; + } + } + + $pending[] = array( + 'id' => (int) $announcement->id, + 'title' => $announcement->title, + 'content' => $announcement->content, + ); + } + + $rep->data = array('announcements' => $pending); + + return $rep; + } + + /** + * Mark an announcement as seen by the current user. + * + * @return jResponseJson + */ + public function markSeen() + { + /** @var jResponseJson $rep */ + $rep = $this->getResponse('json'); + + if (!jAuth::isConnected()) { + $rep->data = array('status' => 'ok'); + + return $rep; + } + + $announcementId = $this->intParam('id'); + if (!$announcementId) { + $rep->data = array('status' => 'error', 'message' => 'Missing announcement id'); + + return $rep; + } + + $usr_login = jAuth::getUserSession()->login; + $viewDao = jDao::get('lizmap~announcementView', 'lizlog'); + + $view = $viewDao->getByAnnouncementAndUser($announcementId, $usr_login); + if ($view) { + $view->view_count = $view->view_count + 1; + $viewDao->update($view); + } else { + $record = jDao::createRecord('lizmap~announcementView', 'lizlog'); + $record->announcement_id = $announcementId; + $record->usr_login = $usr_login; + $record->view_count = 1; + $viewDao->insert($record); + } + + $rep->data = array('status' => 'ok'); + + return $rep; + } +} diff --git a/lizmap/modules/lizmap/daos/announcement.dao.xml b/lizmap/modules/lizmap/daos/announcement.dao.xml new file mode 100644 index 0000000000..50c6cd365d --- /dev/null +++ b/lizmap/modules/lizmap/daos/announcement.dao.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lizmap/modules/lizmap/daos/announcementView.dao.xml b/lizmap/modules/lizmap/daos/announcementView.dao.xml new file mode 100644 index 0000000000..17bd99e468 --- /dev/null +++ b/lizmap/modules/lizmap/daos/announcementView.dao.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lizmap/modules/lizmap/install/install.php b/lizmap/modules/lizmap/install/install.php index c4d3fff88d..166ae1313d 100644 --- a/lizmap/modules/lizmap/install/install.php +++ b/lizmap/modules/lizmap/install/install.php @@ -43,6 +43,9 @@ public function install() $this->useDbProfile('lizlog'); $this->execSQLScript('sql/lizlog'); + // Add announcement tables + $this->execSQLScript('sql/lizannouncement'); + // Add geobookmark table $this->useDbProfile('jauth'); $this->execSQLScript('sql/lizgeobookmark'); diff --git a/lizmap/modules/lizmap/install/sql/lizannouncement.pgsql.sql b/lizmap/modules/lizmap/install/sql/lizannouncement.pgsql.sql new file mode 100644 index 0000000000..494fe671dc --- /dev/null +++ b/lizmap/modules/lizmap/install/sql/lizannouncement.pgsql.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS lizmap_announcement ( + id SERIAL PRIMARY KEY, + title character varying(255) NOT NULL, + content TEXT NOT NULL, + target_repository character varying(100), + target_project character varying(100), + target_groups TEXT, + max_display_count INTEGER DEFAULT 1, + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp DEFAULT CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE +); + +CREATE TABLE IF NOT EXISTS lizmap_announcement_views ( + id SERIAL PRIMARY KEY, + announcement_id INTEGER NOT NULL REFERENCES lizmap_announcement(id) ON DELETE CASCADE, + usr_login character varying(100) NOT NULL, + view_count INTEGER DEFAULT 0, + last_viewed_at timestamp DEFAULT CURRENT_TIMESTAMP, + UNIQUE(announcement_id, usr_login) +); diff --git a/lizmap/modules/lizmap/install/sql/lizannouncement.sqlite.sql b/lizmap/modules/lizmap/install/sql/lizannouncement.sqlite.sql new file mode 100644 index 0000000000..9af6a9e1b6 --- /dev/null +++ b/lizmap/modules/lizmap/install/sql/lizannouncement.sqlite.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS "lizmap_announcement" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, + "title" VARCHAR NOT NULL, + "content" TEXT NOT NULL, + "target_repository" VARCHAR, + "target_project" VARCHAR, + "target_groups" TEXT, + "max_display_count" INTEGER DEFAULT 1, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "is_active" INTEGER DEFAULT 1); + +CREATE TABLE IF NOT EXISTS "lizmap_announcement_views" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, + "announcement_id" INTEGER NOT NULL REFERENCES "lizmap_announcement"("id") ON DELETE CASCADE, + "usr_login" VARCHAR NOT NULL, + "view_count" INTEGER DEFAULT 0, + "last_viewed_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE("announcement_id", "usr_login")); diff --git a/lizmap/modules/lizmap/install/upgrade_announcement.php b/lizmap/modules/lizmap/install/upgrade_announcement.php new file mode 100644 index 0000000000..0b755c3f95 --- /dev/null +++ b/lizmap/modules/lizmap/install/upgrade_announcement.php @@ -0,0 +1,17 @@ +firstExec('announcement')) { + $this->useDbProfile('lizlog'); + $this->execSQLScript('sql/lizannouncement'); + } + } +} diff --git a/lizmap/modules/lizmap/urls.xml b/lizmap/modules/lizmap/urls.xml index 38c18b1d4d..84115d3f13 100644 --- a/lizmap/modules/lizmap/urls.xml +++ b/lizmap/modules/lizmap/urls.xml @@ -22,4 +22,7 @@ + + + diff --git a/lizmap/modules/view/controllers/lizAjax.classic.php b/lizmap/modules/view/controllers/lizAjax.classic.php index 2e73da1979..c53016f636 100644 --- a/lizmap/modules/view/controllers/lizAjax.classic.php +++ b/lizmap/modules/view/controllers/lizAjax.classic.php @@ -173,6 +173,8 @@ public function map() 'nominatim' => jUrl::getFull('lizmap~osm:nominatim'), 'edition' => jUrl::getFull('lizmap~edition:getFeature'), 'permalink' => jUrl::getFull('view~map:index'), + 'announcement' => jUrl::getFull('lizmap~announcement:pending'), + 'announcementMarkSeen' => jUrl::getFull('lizmap~announcement:markSeen'), ); // Get optional WMS public url list diff --git a/lizmap/modules/view/controllers/lizMap.classic.php b/lizmap/modules/view/controllers/lizMap.classic.php index 35472a8424..014ea46759 100644 --- a/lizmap/modules/view/controllers/lizMap.classic.php +++ b/lizmap/modules/view/controllers/lizMap.classic.php @@ -218,6 +218,8 @@ public function index() 'basepath' => $bp, 'geobookmark' => jUrl::get('lizmap~geobookmark:index'), 'service' => jUrl::get('lizmap~service:index').'?repository='.$repository.'&project='.$project, + 'announcement' => jUrl::get('lizmap~announcement:pending'), + 'announcementMarkSeen' => jUrl::get('lizmap~announcement:markSeen'), 'resourceUrlReplacement' => array(), );