diff --git a/Phraseanet-production-client/src/components/basket/index.js b/Phraseanet-production-client/src/components/basket/index.js index 8518e68228..cab59617be 100644 --- a/Phraseanet-production-client/src/components/basket/index.js +++ b/Phraseanet-production-client/src/components/basket/index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; -import ui from '../ui'; -import notify from '../notify'; +// poll notification is now from menu bar +// so this is never called const basket = () => { const onUpdatedContent = (data) => { diff --git a/Phraseanet-production-client/src/components/notify/index.js b/Phraseanet-production-client/src/components/notify/index.js index 3214b8ad5f..669e96489c 100644 --- a/Phraseanet-production-client/src/components/notify/index.js +++ b/Phraseanet-production-client/src/components/notify/index.js @@ -4,6 +4,8 @@ import notifyService from './notifyService'; import * as Rx from 'rx'; import merge from 'lodash.merge'; + +// this module is now unused, poll notification is from menu bar const notify = (services) => { const { configService, localeService, appEvents } = services; @@ -63,9 +65,9 @@ const notify = (services) => { appEvents.emit('notification.refresh', data); } // append notification content - notifyLayout(services).addNotifications(data.notifications); + // notifyLayout(services).addNotifications(data.notifications); - window.setTimeout(poll, defaultPollingTime, notificationInstance); + // window.setTimeout(poll, defaultPollingTime, notificationInstance); return true; }; diff --git a/Phraseanet-production-client/src/components/notify/notifyLayout.js b/Phraseanet-production-client/src/components/notify/notifyLayout.js index 3014258a97..ae68c04a1a 100644 --- a/Phraseanet-production-client/src/components/notify/notifyLayout.js +++ b/Phraseanet-production-client/src/components/notify/notifyLayout.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import dialog from './../../phraseanet-common/components/dialog'; // import user from '../user/index.js'; @@ -8,34 +7,34 @@ const notifyLayout = (services) => { const $notificationBoxContainer = $('#notification_box'); const $notificationTrigger = $('.notification_trigger'); let $notificationDialog = $('#notifications-dialog'); + let $notificationsContent = null; + let $notificationsNavigation = null; + const initialize = () => { + /** + * click on menubar/notifications : drop a box with last 10 notification, and a button "see all" + * the box content is already set by poll notifications + */ $notificationTrigger.on('mousedown', (event) => { event.stopPropagation(); - const $target = $(event.currentTarget); - if ($target.hasClass('open')) { - $notificationBoxContainer.hide(); - $target.removeClass('open'); - clear_notifications(); - } else { - $notificationBoxContainer.show(); - - setBoxHeight(); - - $target.addClass('open'); - read_notifications(); - } - }); - - $(document).on('mousedown', () => { - if ($notificationTrigger.hasClass('open')) { - $notificationTrigger.trigger('click'); - } - + // toggle if ($notificationTrigger.hasClass('open')) { $notificationBoxContainer.hide(); $notificationTrigger.removeClass('open'); - clear_notifications(); } + else { + $notificationTrigger.addClass('open'); + $notificationBoxContainer.show(); + setBoxHeight(); + } + }); + + /** + * close on every mousedown + */ + $(document).on('mousedown', () => { + $notificationBoxContainer.hide(); + $notificationTrigger.removeClass('open'); }); $notificationBoxContainer @@ -48,11 +47,14 @@ const notifyLayout = (services) => { .on('mouseout', '.notification', (event) => { $(event.currentTarget).removeClass('hover'); }) + /** + * click on "see all notification" + */ .on('click', '.notification__print-action', (event) => { event.preventDefault(); - const $el = $(event.currentTarget); - const page = $el.data('page'); - print_notifications(page); + $notificationBoxContainer.hide(); + $notificationTrigger.removeClass('open'); + print_notifications(0); }); $(window).bind('resize', function () { @@ -62,24 +64,24 @@ const notifyLayout = (services) => { }; - const addNotifications = (notificationContent) => { - // var box = $('#notification_box'); - $notificationBoxContainer.empty().append(notificationContent); - - if ($notificationBoxContainer.is(':visible')) { - setBoxHeight(); - } - - if ($('.notification.unread', $notificationBoxContainer).length > 0) { - $('.counter', $notificationTrigger) - .empty() - .append($('.notification.unread', $notificationBoxContainer).length); - $('.counter', $notificationTrigger).css('visibility', 'visible'); - - } else { - $('.notification_trigger .counter').css('visibility', 'hidden').empty(); - } - }; + // const addNotifications = (notificationContent) => { + // // var box = $('#notification_box'); + // $notificationBoxContainer.empty().append(notificationContent); + // + // if ($notificationBoxContainer.is(':visible')) { + // setBoxHeight(); + // } + // + // if ($('.notification.unread', $notificationBoxContainer).length > 0) { + // $('.counter', $notificationTrigger) + // .empty() + // .append($('.notification.unread', $notificationBoxContainer).length); + // $('.counter', $notificationTrigger).css('visibility', 'visible'); + // + // } else { + // $('.notification_trigger .counter').css('visibility', 'hidden').empty(); + // } + // }; const setBoxHeight = () => { @@ -110,20 +112,32 @@ const notifyLayout = (services) => { } }; - const print_notifications = (page) => { + /** + * add 10 notifications into the dlgbox + * display the button "load more" while relevant + * + * @param offset + */ + const print_notifications = (offset) => { - page = parseInt(page, 10); + offset = parseInt(offset, 10); var buttons = {}; buttons[localeService.t('fermer')] = function () { $notificationDialog.dialog('close'); }; + // create the dlg div if it does not exists + // if ($notificationDialog.length === 0) { - $('body').append('
'); + $('body').append('
'); $notificationDialog = $('#notifications-dialog'); + $notificationsContent = $('.content', $notificationDialog); + $notificationsNavigation = $('.navigation', $notificationDialog); } + // open the dlg (even if it is already opened when "load more") + // $notificationDialog .dialog({ title: $('#notification-title').val(), @@ -141,20 +155,22 @@ const notifyLayout = (services) => { close: function (event, ui) { $notificationDialog.dialog('destroy').remove(); } - }).dialog('option', 'buttons', buttons).dialog('open').on('click','.notification_next .notification__print-action', function (event) { - event.preventDefault(); - var $el = $(event.currentTarget); - var page = $el.data('page'); - print_notifications(page); - }); - + }) + .dialog('option', 'buttons', buttons) + .dialog('open'); + // load 10 (more) notifications + // + $notificationDialog.addClass('loading'); $.ajax({ - type: 'GET', - url: '/user/notifications/', + type: 'POST', + // url: '/user/notifications/', + url: '/session/notifications/', dataType: 'json', data: { - page: page + 'offset': offset, + 'limit': 10, + 'what': 3, // 3 = read | unread }, error: function (data) { $notificationDialog.removeClass('loading'); @@ -165,47 +181,55 @@ const notifyLayout = (services) => { success: function (data) { $notificationDialog.removeClass('loading'); - - if (page === 0) { - $notificationDialog.empty(); - } else { - $('.notification_next', $notificationDialog).remove(); + if (offset === 0) { + $notificationsContent.empty(); } + const notifications = data.notifications.notifications; let i = 0; - for (i in data.notifications) { - var id = 'notif_date_' + i; - var date_cont = $('#' + id); - if (date_cont.length === 0) { - $notificationDialog.append('
' + data.notifications[i].display + '
'); - date_cont = $('#' + id); - } + for (i in notifications) { + const notification = notifications[i]; - let j = 0; - for (j in data.notifications[i].notifications) { - var loc_dat = data.notifications[i].notifications[j]; - var html = '
' + - '
' + - loc_dat.icon + - '' + - '
' + - loc_dat.text + ' ' + loc_dat.time + '
' + - '
' + - '
'; - date_cont.append(html); + // group notifs by day + // + const date = notification.created_on_day; + const id = 'notif_date_' + date; + let date_cont = $('#' + id, $notificationsContent); + if (date_cont.length === 0) { + $notificationsContent.append('
' + notifications[i].created_on + '
'); + date_cont = $('#' + id, $notificationsContent); } + // write notif + let html = '
' + + '
' + + '' + + '' + + '
' + + notification.text + ' ' + notification.time + '
' + + '
' + + '
'; + date_cont.append(html); } - var next_ln = $.trim(data.next); - - if (next_ln !== '') { - $notificationDialog.append('
' + next_ln + '
'); + if (data.notifications.next_page_html) { + $notificationsNavigation + .off('click', '.notification__print-action'); + $notificationsNavigation.empty().show().append(data.notifications.next_page_html); + $notificationsNavigation + .on('click', '.notification__print-action', function (event) { + event.preventDefault(); + let $el = $(event.currentTarget); + let offset = $el.data('offset'); + print_notifications(offset); + }); + } + else { + $notificationsNavigation.empty().hide(); } } }); - }; - + /* remove in favor of existing /session/ route const read_notifications = () => { var notifications = []; @@ -236,9 +260,10 @@ const notifyLayout = (services) => { $('.notification_trigger .counter').css('visibility', 'hidden').empty(); }; + */ + return { - initialize, - addNotifications + initialize }; }; diff --git a/Phraseanet-production-client/src/components/record/index.js b/Phraseanet-production-client/src/components/record/index.js index 9fc66e02fc..8d1834c6ed 100644 --- a/Phraseanet-production-client/src/components/record/index.js +++ b/Phraseanet-production-client/src/components/record/index.js @@ -2,6 +2,7 @@ import {Observable} from 'rx'; // import {ajax} from 'jquery'; import $ from 'jquery'; +// module unused now 06-2021 let recordService = (services) => { const {configService} = services; const url = configService.get('baseUrl'); diff --git a/Phraseanet-production-client/src/components/user/index.js b/Phraseanet-production-client/src/components/user/index.js index 44424e76f3..1682573293 100644 --- a/Phraseanet-production-client/src/components/user/index.js +++ b/Phraseanet-production-client/src/components/user/index.js @@ -1,7 +1,4 @@ -import $ from 'jquery'; import ui from '../ui'; -import notify from '../notify'; -import * as appCommons from './../../phraseanet-common'; const user = (services) => { const { configService, localeService, appEvents } = services; @@ -19,53 +16,43 @@ const user = (services) => { 'user.disconnected': onUserDisconnect }); - const manageSession = (...params) => { - let [data, showMessages] = params; + // const manageSession = (...params) => { + // let [data, showMessages] = params; + // + // if (typeof (showMessages) === 'undefined') { + // showMessages = false; + // } + // + // if (showMessages) { + // // @todo: to be moved + // if ($.trim(data.message) !== '') { + // if ($('#MESSAGE').length === 0) { + // $('body').append('
'); + // } + // $('#MESSAGE') + // .empty() + // .append(data.message + '
' + localeService.t('hideMessage') + '
') + // .attr('title', 'Global Message') + // .dialog({ + // autoOpen: false, + // closeOnEscape: true, + // resizable: false, + // draggable: false, + // modal: true, + // close: function () { + // if ($('.dialog_remove:checked', $(this)).length > 0) { + // // @TODO get from module + // appCommons.userModule.setTemporaryPref('message', 0); + // } + // } + // }) + // .dialog('open'); + // } + // } + // return true; + // }; - if (typeof (showMessages) === 'undefined') { - showMessages = false; - } - - if (showMessages) { - // @todo: to be moved - if ($.trim(data.message) !== '') { - if ($('#MESSAGE').length === 0) { - $('body').append('
'); - } - $('#MESSAGE') - .empty() - .append(data.message + '
' + localeService.t('hideMessage') + '
') - .attr('title', 'Global Message') - .dialog({ - autoOpen: false, - closeOnEscape: true, - resizable: false, - draggable: false, - modal: true, - close: function () { - if ($('.dialog_remove:checked', $(this)).length > 0) { - // setTemporaryPref - $.ajax({ - type: "POST", - url: "/user/preferences/temporary/", - data: { - prop: 'message', - value: 0 - }, - success: function (data) { - return; - } - }); - } - } - }) - .dialog('open'); - } - } - return true; - }; - - return {initialize, manageSession}; + return {initialize}; }; export default user; diff --git a/Phraseanet-production-client/src/phraseanet-common/components/common.js b/Phraseanet-production-client/src/phraseanet-common/components/common.js index 786d3179ed..3707ad28c6 100644 --- a/Phraseanet-production-client/src/phraseanet-common/components/common.js +++ b/Phraseanet-production-client/src/phraseanet-common/components/common.js @@ -36,7 +36,7 @@ /* eslint-disable no-loop-func*/ import $ from 'jquery'; -import dialog from './dialog'; + let cookie = require('js-cookie'); const initialize = () => { @@ -56,93 +56,92 @@ const initialize = () => { }; // @deprecated -function manageSession(data, showMessages) { - if (typeof (showMessages) === 'undefined') - showMessages = false; - - if (data.status === 'disconnected' || data.status === 'session') { - disconnected(); - return false; - } - if (showMessages) { - let box = $('#notification_box'); - box.empty().append(data.notifications); - - if (box.is(':visible')) - fix_notification_height(); - - if ($('.notification.unread', box).length > 0) { - let trigger = $('#notification_trigger'); - $('.counter', trigger) - .empty() - .append($('.notification.unread', box).length); - $('.counter', trigger).css('visibility', 'visible'); - - } - else - $('#notification_trigger .counter').css('visibility', 'hidden').empty(); - - if (data.changed.length > 0) { - let current_open = $('.SSTT.ui-state-active'); - let current_sstt = current_open.length > 0 ? current_open.attr('id').split('_').pop() : false; - - let main_open = false; - for (let i = 0; i !== data.changed.length; i++) { - let sstt = $('#SSTT_' + data.changed[i]); - if (sstt.size() === 0) { - if (main_open === false) { - $('#baskets .bloc').animate({'top': 30}, function () { - $('#baskets .alert_datas_changed:first').show(); - }); - main_open = true; - } - } - else { - if (!sstt.hasClass('active')) - sstt.addClass('unread'); - else { - $('.alert_datas_changed', $('#SSTT_content_' + data.changed[i])).show(); - } - } - } - } - if ($.trim(data.message) !== '') { - if ($('#MESSAGE').length === 0) - $('body').append('
'); - $('#MESSAGE') - .empty() - .append('

' + data.message + '

') - .attr('title', 'Global Message') - .dialog({ - autoOpen: false, - closeOnEscape: true, - resizable: false, - draggable: false, - modal: true, - close: function () { - if ($('.dialog_remove:checked', $(this)).length > 0) { - // setTemporaryPref - $.ajax({ - type: 'POST', - url: '/user/preferences/temporary/', - data: { - prop: 'message', - value: 0 - }, - success: function (data) { - return; - } - }); - } - } - }) - .dialog('open'); - } - } - return true; -} +// function manageSession(data, showMessages) { +// if (typeof (showMessages) === 'undefined') +// showMessages = false; +// +// if (data.status === 'disconnected' || data.status === 'session') { +// disconnected(); +// return false; +// } +// if (showMessages) { +// let box = $('#notification_box'); +// box.empty().append(data.notifications); +// +// if (box.is(':visible')) +// fix_notification_height(); +// +// if ($('.notification.unread', box).length > 0) { +// let trigger = $('#notification_trigger'); +// $('.counter', trigger) +// .empty() +// .append($('.notification.unread', box).length); +// $('.counter', trigger).css('visibility', 'visible'); +// +// } +// else +// $('#notification_trigger .counter').css('visibility', 'hidden').empty(); +// +// if (data.changed.length > 0) { +// let current_open = $('.SSTT.ui-state-active'); +// let current_sstt = current_open.length > 0 ? current_open.attr('id').split('_').pop() : false; +// +// let main_open = false; +// for (let i = 0; i !== data.changed.length; i++) { +// let sstt = $('#SSTT_' + data.changed[i]); +// if (sstt.size() === 0) { +// if (main_open === false) { +// $('#baskets .bloc').animate({'top': 30}, function () { +// $('#baskets .alert_datas_changed:first').show(); +// }); +// main_open = true; +// } +// } +// else { +// if (!sstt.hasClass('active')) +// sstt.addClass('unread'); +// else { +// $('.alert_datas_changed', $('#SSTT_content_' + data.changed[i])).show(); +// } +// } +// } +// } +// if ($.trim(data.message) !== '') { +// if ($('#MESSAGE').length === 0) +// $('body').append('
'); +// $('#MESSAGE') +// .empty() +// .append('

' + data.message + '

') +// .attr('title', 'Global Message') +// .dialog({ +// autoOpen: false, +// closeOnEscape: true, +// resizable: false, +// draggable: false, +// modal: true, +// close: function () { +// if ($('.dialog_remove:checked', $(this)).length > 0) { +// // setTemporaryPref +// $.ajax({ +// type: 'POST', +// url: '/user/preferences/temporary/', +// data: { +// prop: 'message', +// value: 0 +// }, +// success: function (data) { +// return; +// } +// }); +// } +// } +// }) +// .dialog('open'); +// } +// } +// return true; +// } export default { - initialize, - manageSession + initialize }; diff --git a/Phraseanet-production-client/src/prod/bootstrap.js b/Phraseanet-production-client/src/prod/bootstrap.js index f29f6c987f..91913a4a4a 100644 --- a/Phraseanet-production-client/src/prod/bootstrap.js +++ b/Phraseanet-production-client/src/prod/bootstrap.js @@ -1,15 +1,11 @@ import $ from 'jquery'; -const humane = require('humane-js'); -require('imports-loader?define=>false&exports=>false!./../components/utils/jquery-plugins/colorAnimation'); import * as AppCommons from './../phraseanet-common'; import publication from '../components/publication'; import workzone from '../components/ui/workzone'; -import notify from '../components/notify/index'; -import Locale from '../components/locale'; +import notifyLayout from '../components/notify/notifyLayout'; +import LocaleService from '../components/locale'; import ui from '../components/ui'; import ConfigService from './../components/core/configService'; -import LocaleService from '../components/locale'; -import i18next from 'i18next'; import defaultConfig from './config'; import Emitter from '../components/core/emitter'; import user from '../components/user'; @@ -18,6 +14,10 @@ import search from '../components/search'; import utils from './../components/utils/utils'; import dialog from './../phraseanet-common/components/dialog'; import merge from 'lodash.merge'; + +const humane = require('humane-js'); +require('imports-loader?define=>false&exports=>false!./../components/utils/jquery-plugins/colorAnimation'); + class Bootstrap { app; @@ -69,27 +69,28 @@ class Bootstrap { const userSession = user(this.appServices); - let appProdNotification = { - url: this.configService.get('notify.url'), - moduleId: this.configService.get('notify.moduleId'), - userId: this.configService.get('notify.userId') - }; + // let appProdNotification = { + // url: this.configService.get('notify.url'), + // moduleId: this.configService.get('notify.moduleId'), + // userId: this.configService.get('notify.userId') + // }; + + notifyLayout(this.appServices).initialize(); /** - * Initialize notifier - * @type {{bindEvents, createNotifier, isValid, poll}} + * Poll just in menu_bar */ - const notifier = notify(this.appServices); - notifier.initialize(); - - // create a new notification poll: - appProdNotification = notifier.createNotifier(appProdNotification); - - if (notifier.isValid(appProdNotification)) { - notifier.poll(appProdNotification); - } else { - throw new Error('implementation error: failed to configure new notifier'); - } + // const notifier = notify(this.appServices); + // notifier.initialize(); + // + // // create a new notification poll: + // appProdNotification = notifier.createNotifier(appProdNotification); + // + // if (notifier.isValid(appProdNotification)) { + // notifier.poll(appProdNotification); + // } else { + // throw new Error('implementation error: failed to configure new notifier'); + // } // @TODO remove global variables // register some global variables, diff --git a/lib/Alchemy/Phrasea/Controller/Root/SessionController.php b/lib/Alchemy/Phrasea/Controller/Root/SessionController.php index 7c91b16498..38a9e33535 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/SessionController.php +++ b/lib/Alchemy/Phrasea/Controller/Root/SessionController.php @@ -11,10 +11,10 @@ namespace Alchemy\Phrasea\Controller\Root; use Alchemy\Phrasea\Application\Helper\EntityManagerAware; use Alchemy\Phrasea\Controller\Controller; -use Alchemy\Phrasea\Model\Entities\SessionModule; use Alchemy\Phrasea\Model\Repositories\BasketRepository; use Alchemy\Phrasea\Model\Repositories\SessionRepository; use Alchemy\Phrasea\Utilities\Stopwatch; +use eventsmanager_broker; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -42,10 +42,14 @@ class SessionController extends Controller 'status' => 'unknown', 'message' => '', 'notifications' => false, - 'changed' => [] + 'notifications_html' => false, + 'unread_basket_ids' => [] ]; $authenticator = $this->getAuthenticator(); + // every request is catched by SessionManagerSubscriber, which handles disconnect + // so this code is probably useless + /* if ($authenticator->isAuthenticated()) { $usr_id = $authenticator->getUser()->getId(); if ($usr_id != $request->request->get('usr')) { // I logged with another user @@ -58,29 +62,39 @@ class SessionController extends Controller return $this->app->json($ret); } + */ try { $this->getApplicationBox()->get_connection(); - } catch (\Exception $e) { + } + catch (\Exception $e) { return $this->app->json($ret); } + // module id is only used to track apps, its done in SessioManagerSubscriber (parsing url) + /* if (1 > $moduleId = (int) $request->request->get('module')) { $ret['message'] = 'Missing or Invalid `module` parameter'; return $this->app->json($ret); } + */ $ret['status'] = 'ok'; $stopwatch->lap("start"); - $notifs = $this->getEventsManager()->get_notifications($stopwatch); + $offset = (int)$request->get('offset', 0); + $limit = (int)$request->get('limit', 10); + $what = (int)$request->get('what', eventsmanager_broker::UNREAD | eventsmanager_broker::READ); + + $notifications = $this->getEventsManager()->get_notifications($offset, $limit, $what, $stopwatch); $stopwatch->lap("get_notifications done"); - $ret['notifications'] = $this->render('prod/notifications.html.twig', [ - 'notifications' => $notifs + $ret['notifications'] = $notifications; + $ret['notifications_html'] = $this->render('prod/notifications.html.twig', [ + 'notifications' => $notifications['notifications'] ]); $stopwatch->lap("render done"); @@ -90,7 +104,7 @@ class SessionController extends Controller $stopwatch->lap("baskets::findUnreadActiveByUser done"); foreach ($baskets as $basket) { - $ret['changed'][] = $basket->getId(); + $ret['unread_basket_ids'][] = $basket->getId(); } if (in_array($this->getSession()->get('phraseanet.message'), ['1', null])) { @@ -121,96 +135,6 @@ class SessionController extends Controller return $response; } - /** - * @param Request $request - * @return JsonResponse - * @throws \Exception in case "new \DateTime()" fails ? - */ - public function updateSession(Request $request) - { - if (!$request->isXmlHttpRequest()) { - $this->app->abort(400); - } - - $ret = [ - 'status' => 'unknown', - 'message' => '', - 'notifications' => false, - 'changed' => [] - ]; - - $authenticator = $this->getAuthenticator(); - if ($authenticator->isAuthenticated()) { - $usr_id = $authenticator->getUser()->getId(); - if ($usr_id != $request->request->get('usr')) { // I logged with another user - $ret['status'] = 'disconnected'; - - return $this->app->json($ret); - } - } - else { - $ret['status'] = 'disconnected'; - - return $this->app->json($ret); - } - - try { - $this->getApplicationBox()->get_connection(); - } - catch (\Exception $e) { - return $this->app->json($ret); - } - - if (1 > $moduleId = (int) $request->request->get('module')) { - $ret['message'] = 'Missing or Invalid `module` parameter'; - - return $this->app->json($ret); - } - - /** @var \Alchemy\Phrasea\Model\Entities\Session $session */ - $session = $this->getSessionRepository()->find($this->getSession()->get('session_id')); - $session->setUpdated(new \DateTime()); - - $manager = $this->getEntityManager(); - if (!$session->hasModuleId($moduleId)) { - $module = new SessionModule(); - $module->setModuleId($moduleId); - $module->setSession($session); - $manager->persist($module); - } - else { - $manager->persist($session->getModuleById($moduleId)->setUpdated($now)); - } - - $manager->persist($session); - $manager->flush(); - - $ret['status'] = 'ok'; - - $ret['notifications'] = $this->render('prod/notifications.html.twig', [ - 'notifications' => $this->getEventsManager()->get_notifications() - ]); - - $baskets = $this->getBasketRepository()->findUnreadActiveByUser($authenticator->getUser()); - - foreach ($baskets as $basket) { - $ret['changed'][] = $basket->getId(); - } - - if (in_array($this->getSession()->get('phraseanet.message'), ['1', null])) { - $conf = $this->getConf(); - if ($conf->get(['main', 'maintenance'])) { - $ret['message'] .= $this->app->trans('The application is going down for maintenance, please logout.'); - } - - if ($conf->get(['registry', 'maintenance', 'enabled'])) { - $ret['message'] .= strip_tags($conf->get(['registry', 'maintenance', 'message'])); - } - } - - return $this->app->json($ret); - } - /** * Deletes identified session * diff --git a/lib/Alchemy/Phrasea/Controller/User/UserNotificationController.php b/lib/Alchemy/Phrasea/Controller/User/UserNotificationController.php index 5bce930b69..f7e9581208 100644 --- a/lib/Alchemy/Phrasea/Controller/User/UserNotificationController.php +++ b/lib/Alchemy/Phrasea/Controller/User/UserNotificationController.php @@ -21,6 +21,7 @@ class UserNotificationController extends Controller * @param Request $request * @return JsonResponse */ + /* remove in favor of existing /session/ route public function readNotifications(Request $request) { if (!$request->isXmlHttpRequest()) { @@ -38,6 +39,7 @@ class UserNotificationController extends Controller return $this->app->json(['success' => false, 'message' => $e->getMessage()]); } } + */ /** * Get all notifications @@ -45,6 +47,7 @@ class UserNotificationController extends Controller * @param Request $request * @return JsonResponse */ + /* remove in favor of existing /session/ route public function listNotifications(Request $request) { if (!$request->isXmlHttpRequest()) { @@ -55,12 +58,15 @@ class UserNotificationController extends Controller return $this->app->json($this->getEventsManager()->get_notifications_as_array(($page < 0 ? 0 : $page))); } + */ /** * @return \eventsmanager_broker */ + /* remove in favor of existing /session/ route private function getEventsManager() { return $this->app['events-manager']; } + */ } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Admin/Databox.php b/lib/Alchemy/Phrasea/ControllerProvider/Admin/Databox.php index ff0ad4ecc0..7f809c1e32 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Admin/Databox.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Admin/Databox.php @@ -13,9 +13,8 @@ namespace Alchemy\Phrasea\ControllerProvider\Admin; use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\Admin\DataboxController; -use Alchemy\Phrasea\Core\LazyLocator; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; -use Alchemy\Phrasea\Security\Firewall; +use Alchemy\Phrasea\Core\LazyLocator; use Silex\Application; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; @@ -89,6 +88,7 @@ class Databox implements ControllerProviderInterface, ServiceProviderInterface ->before([$this, 'requireChangeSbasStructureRight']) ->bind('admin_database_submit_cgus'); + // polled by admin/databox to display indexation progress bar $controllers->get('/{databox_id}/informations/documents/', 'controller.admin.databox:progressBarInfos') ->before([$this, 'requireManageRightOnSbas']) ->bind('admin_database_display_document_information'); diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Root/Session.php b/lib/Alchemy/Phrasea/ControllerProvider/Root/Session.php index b549a2bc03..63b7b18c1e 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Root/Session.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Root/Session.php @@ -12,9 +12,9 @@ namespace Alchemy\Phrasea\ControllerProvider\Root; use Alchemy\Phrasea\Application as PhraseaApplication; -use Alchemy\Phrasea\Core\LazyLocator; use Alchemy\Phrasea\Controller\Root\SessionController; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; +use Alchemy\Phrasea\Core\LazyLocator; use Silex\Application; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; @@ -41,12 +41,12 @@ class Session implements ControllerProviderInterface, ServiceProviderInterface { $controllers = $this->createCollection($app); - $controllers->post('/update/', 'controller.session:updateSession') - ->bind('update_session'); - + /** @uses SessionController::getNotifications() */ $controllers->post('/notifications/', 'controller.session:getNotifications') ->bind('list_notifications'); + /** @uses SessionController::deleteSession() */ + // used in admin/connected_users to kill a session $controller = $controllers->post('/delete/{id}', 'controller.session:deleteSession') ->bind('delete_session'); diff --git a/lib/Alchemy/Phrasea/ControllerProvider/User/Notifications.php b/lib/Alchemy/Phrasea/ControllerProvider/User/Notifications.php index ded279f367..f12f0f3bd6 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/User/Notifications.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/User/Notifications.php @@ -11,8 +11,6 @@ namespace Alchemy\Phrasea\ControllerProvider\User; -use Alchemy\Phrasea\Application as PhraseaApplication; -use Alchemy\Phrasea\Controller\User\UserNotificationController; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; use Silex\Application; use Silex\ControllerProviderInterface; @@ -24,9 +22,12 @@ class Notifications implements ControllerProviderInterface, ServiceProviderInter public function register(Application $app) { + /* remove in favor of existing /session/ route + * $app['controller.user.notifications'] = $app->share(function (PhraseaApplication $app) { return (new UserNotificationController($app)); }); + */ } public function boot(Application $app) @@ -46,11 +47,16 @@ class Notifications implements ControllerProviderInterface, ServiceProviderInter $firewall->requireNotGuest(); }); + /* remove in favor of existing /session/ route + * + /** @uses UserNotificationController::listNotifications * / $controllers->get('/', 'controller.user.notifications:listNotifications') ->bind('get_notifications'); + /** @uses UserNotificationController::readNotifications() * / $controllers->post('/read/', 'controller.user.notifications:readNotifications') ->bind('set_notifications_readed'); + */ return $controllers; } diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/SessionManagerSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/SessionManagerSubscriber.php index 689ae02837..89990330e5 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/SessionManagerSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/SessionManagerSubscriber.php @@ -13,13 +13,12 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Model\Entities\Session; use Alchemy\Phrasea\Model\Entities\SessionModule; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; class SessionManagerSubscriber implements EventSubscriberInterface { @@ -43,56 +42,52 @@ class SessionManagerSubscriber implements EventSubscriberInterface { return [ KernelEvents::REQUEST => [ - ['initSession', Application::EARLY_EVENT], + /** @uses SessionManagerSubscriber::checkSessionActivity */ ['checkSessionActivity', Application::LATE_EVENT] ] ]; } - public function initSession(GetResponseEvent $event) - { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { - return; - } - - $request = $event->getRequest(); - - if ($this->isFlashUploadRequest($request) && null !== $sessionId = $request->request->get('php_session_id')) { - $request->cookies->set($this->app['session']->getName(), $sessionId); - } - } - /** * log real human activity on application, to keep session alive + * + * to "auto-disconnect" when idle duration is passed, we use the "poll" requests. + * nb : the route "/sessions/notifications" is not considered as comming from a "module" (prod, admin, ...) + * so it will not update session + * * @param GetResponseEvent $event */ public function checkSessionActivity(GetResponseEvent $event) { $request = $event->getRequest(); + // ignore routes that comes from api (?) : todo : check if usefull since "api" is not a "module" + // todo : "LOG" ??? if ($request->request->has('oauth_token') || $request->query->has('oauth_token') || $request->query->has('LOG') - || null === $moduleId = $this->getModuleId($request->getPathInfo()) ) { return; } - if ($this->isAdminJsPolledRoute($moduleId, $request)) { - return; - } - - if ($moduleId === self::$modulesIds['prod'] && $this->isFlashUploadRequest($request)) { - return; - } + $moduleId = $this->getModuleId($request->getPathInfo()); // if we are already disconnected (ex. from another window), quit immediately + // if (!($this->app->getAuthenticator()->isAuthenticated())) { $this->setDisconnectResponse($event); return; } + // we must still ignore some "polling" (js) routes + // + if ($this->isJsPollingRoute($moduleId, $request)) { + return; + } + + // ANY route can disconnect the user if idle duration is over + // /** @var Session $session */ $session = $this->app['repo.sessions']->find($this->app['session']->get('session_id')); @@ -112,6 +107,14 @@ class SessionManagerSubscriber implements EventSubscriberInterface return; } + // only routes from "modules" (prod, admin, ...) are considered as "user activity" + // + if(is_null($moduleId)) { + return; + } + + // here the route is considered as "user activity" : update session + // $entityManager = $this->app['orm.em']; $module = $this->addOrUpdateSessionModule($session, $moduleId, $now); @@ -121,11 +124,6 @@ class SessionManagerSubscriber implements EventSubscriberInterface $entityManager->flush(); } - private function isFlashUploadRequest(Request $request) - { - return false !== stripos($request->server->get('HTTP_USER_AGENT'), 'flash') && $request->getRequestUri() === '/prod/upload/'; - } - /** * @param GetResponseEvent $event */ @@ -133,9 +131,10 @@ class SessionManagerSubscriber implements EventSubscriberInterface { $request = $event->getRequest(); - $response = $request->isXmlHttpRequest() ? $this->getXmlHttpResponse() : $this->getRedirectResponse($request); - - $event->setResponse($response); + if($this->getModuleName($request->getPathInfo()) !== 'login') { // prevent infinite redirections + $response = $request->isXmlHttpRequest() ? $this->getXmlHttpResponse() : $this->getRedirectResponse($request); + $event->setResponse($response); + } } /** @@ -163,7 +162,7 @@ class SessionManagerSubscriber implements EventSubscriberInterface * @param string $pathInfo * @return int|null */ - private function getModuleId($pathInfo) + private function getModuleName($pathInfo) { $parts = array_filter(explode('/', $pathInfo)); @@ -171,7 +170,16 @@ class SessionManagerSubscriber implements EventSubscriberInterface return null; } - $moduleName = strtolower($parts[1]); + return strtolower($parts[1]); + } + + /** + * @param string $pathInfo + * @return int|null + */ + private function getModuleId($pathInfo) + { + $moduleName = $this->getModuleName($pathInfo); if (!isset(self::$modulesIds[$moduleName])) { return null; @@ -181,23 +189,39 @@ class SessionManagerSubscriber implements EventSubscriberInterface } /** + * returns true is the route match a "polling" route (databox progressionbar, task manager, notifications, ...) + * polling routes (sent every n seconds with no user action) must not update the session + * + * the request should contain a "update-session=0" header, but for now we still test hardcoded routes + * * @param int $moduleId * @param Request $request * @return bool */ - private function isAdminJsPolledRoute($moduleId, Request $request) + private function isJsPollingRoute($moduleId, Request $request) { - if ($moduleId !== self::$modulesIds['admin']) { - return false; + if($request->headers->get('update-session', '1') === '0') { + return true; } $pathInfo = $request->getPathInfo(); + // admin/task managers poll tasks if ($pathInfo === '/admin/task-manager/tasks/' && $request->getContentType() === 'json') { return true; } - return preg_match('#^/admin/databox/\d+/informations/documents/#', $pathInfo) === 1; + // admin/databox poll to update the indexation progress bar (header "update-session=0") sent + if(preg_match('#^/admin/databox/\d+/informations/documents/#', $pathInfo)) { + return true; + } + + // admin/databox poll to update the indexation progress bar + if(preg_match('#^/.*/notifications/#', $pathInfo)) { + return true; + } + + return false; } /** diff --git a/lib/classes/eventsmanager/broker.php b/lib/classes/eventsmanager/broker.php index 86c63d78f3..d8b7c19819 100644 --- a/lib/classes/eventsmanager/broker.php +++ b/lib/classes/eventsmanager/broker.php @@ -96,15 +96,29 @@ class eventsmanager_broker return true; } + /* public function get_notifications_as_array($page = 0) { + $r = $this->get_notifications($page * 10, 10); + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!! FAKE USER FOR TESTING !!!!!!!!!!!!!!!!!!!!!!!! + + // $usr_id = $this->app->getAuthenticatedUser()->getId(); + + $usr_id = 15826; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + $total = 0; $sql = 'SELECT count(id) as total, sum(unread) as unread FROM notifications WHERE usr_id = :usr_id'; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $this->app->getAuthenticatedUser()->getId()]); + $stmt->execute([':usr_id' => $usr_id]); $row = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -122,7 +136,7 @@ class eventsmanager_broker $data = ['notifications' => [], 'next' => '']; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $this->app->getAuthenticatedUser()->getId()]); + $stmt->execute([':usr_id' => $usr_id]); $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); @@ -134,7 +148,9 @@ class eventsmanager_broker continue; } - $content = $this->pool_classes[$type]->datas($json, $row['unread']); + /** @var eventsmanager_notifyAbstract $obj * / + $obj = $this->pool_classes[$type]; + $content = $obj->datas($json, $row['unread']); if ( ! isset($this->pool_classes[$type]) || count($content) === 0) { $sql = 'DELETE FROM notifications WHERE id = :id'; @@ -169,7 +185,8 @@ class eventsmanager_broker return $data; } - + */ +/* public function get_unread_notifications_number() { $total = 0; @@ -188,92 +205,152 @@ class eventsmanager_broker return $total; } +*/ + const READ = 1; + const UNREAD = 2; - public function get_notifications(\Alchemy\Phrasea\Utilities\Stopwatch $stopwatch = null) + public function get_notifications(int $offset=0, int $limit=10, $readFilter = self::READ | self::UNREAD, \Alchemy\Phrasea\Utilities\Stopwatch $stopwatch = null) { - $unread = 0; - - $sql = 'SELECT count(id) as total, sum(unread) as unread - FROM notifications WHERE usr_id = :usr_id'; + if($stopwatch) $stopwatch->lap("broker start"); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!! FAKE USER FOR TESTING !!!!!!!!!!!!!!!!!!!!!!!! - // $usr_id = $this->app->getAuthenticatedUser()->getId(); + $usr_id = $this->app->getAuthenticatedUser()->getId(); - $usr_id = 29882; + // $usr_id = 29882; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - if($stopwatch) $stopwatch->lap("broker start"); + + + // delete old already read notifs (nb: we do this for everybody - not only the current user -) + // todo: for now we use "created_on" since there is no timestamp set when reading. + // + $sql = "DELETE FROM `notifications` WHERE `unread`=0 AND TIMESTAMPDIFF(HOUR, `created_on`, NOW()) > 10"; + $this->app->getApplicationBox()->get_connection()->exec($sql); + + // get count of unread notifications (to be displayed on navbar) + // + $total = 0; + $unread = 0; + $sql = 'SELECT COUNT(`id`) AS `total`, SUM(`unread`) AS `unread` FROM `notifications` WHERE `usr_id` = :usr_id'; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $usr_id]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); + if( ($row = $stmt->fetch(PDO::FETCH_ASSOC)) ) { + $total = (int)$row['total']; + $unread = (int)$row['unread']; + } $stmt->closeCursor(); - if($stopwatch) $stopwatch->lap("sql 1"); + if($stopwatch) $stopwatch->lap("sql count unread"); - if ($row) { - $unread = $row['unread']; + $notifications = []; + + // fetch notifications + // + $sql = "SELECT * FROM `notifications` WHERE `usr_id` = :usr_id"; + switch ($readFilter) { + case self::READ: + $sql .= " AND `unread`=0"; + $total -= $unread; // fix total to match the filter + break; + case self::UNREAD: + $sql .= " AND `unread`=1"; + $total = $unread; // fix total to match the filter + break; + default: + // any other case : fetch both ; no need to fix total + break; } + $sql .= " ORDER BY created_on DESC LIMIT " . $offset . ", " . $limit; - if ($unread < 3) { - $sql = 'SELECT * FROM notifications - WHERE usr_id = :usr_id ORDER BY created_on DESC LIMIT 0,4'; - } else { - $sql = 'SELECT * FROM notifications - WHERE usr_id = :usr_id AND unread="1" ORDER BY created_on DESC'; - } - - $ret = []; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $usr_id]); - // $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - //$stmt->closeCursor(); + $stmt->execute([':usr_id' => $usr_id]); // , ':offset' => $offset, ':limit' => $limit]); + $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); + $stmt->closeCursor(); if($stopwatch) $stopwatch->lap("sql 2"); - // foreach ($rs as $row) { - while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $bad_ids = []; + // nb : we asked for a "page" of notifs (limit), but since some notifications may be ignored (bad type, bad json, ...) + // the result array may contain less than expected. + foreach ($rs as $row) { $type = 'eventsmanager_' . $row['type']; if ( ! isset($this->pool_classes[$type])) { + $bad_ids[] = $row['id']; continue; } + $data = @json_decode($row['datas'], true); - if (json_last_error() !== JSON_ERROR_NONE) { + $bad_ids[] = $row['id']; continue; } - $datas = $this->pool_classes[$type]->datas($data, $row['unread']); + /** @var eventsmanager_notifyAbstract $obj */ + $obj = $this->pool_classes[$type]; + $datas = $obj->datas($data, $row['unread']); + // $datas = [ + // 'text' => "blabla" + // 'class' => "" | "relaod_baskets" + // ] -// if ( ! isset($this->pool_classes[$type]) || count($datas) === 0) { -// $sql = 'DELETE FROM notifications WHERE id = :id'; -// $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); -// $stmt->execute([':id' => $row['id']]); -// $stmt->closeCursor(); -// continue; -// } + if (count($datas) === 0) { + $bad_ids[] = $row['id']; + continue; + } - $ret[] = array_merge( - $datas - , [ - 'created_on' => $this->app['date-formatter']->getPrettyString(new DateTime($row['created_on'])) - , 'icon' => $this->pool_classes[$type]->icon_url() - , 'id' => $row['id'] - , 'unread' => $row['unread'] + // add infos to the notification and add to list + // + $created_on = new DateTime($row['created_on']); + $notifications[] = array_merge( + $datas, + [ + 'created_on_day' => $created_on->format('Ymd'), + 'created_on' => $this->app['date-formatter']->getPrettyString($created_on), + 'time' => $this->app['date-formatter']->getTime($created_on), + //, 'icon' => '' + 'icon' => $this->pool_classes[$type]->icon_url(), + 'id' => $row['id'], + 'unread' => $row['unread'] ] ); } $stmt->closeCursor(); - if($stopwatch) $stopwatch->lap("broker ret"); + if($stopwatch) $stopwatch->lap("fetch"); + + if(!empty($bad_ids)) { + // delete broken notifs + $sql = 'DELETE FROM `notifications` WHERE `id` IN (' . join(',', $bad_ids) . ')'; + $stmt = $this->app->getApplicationBox()->get_connection()->exec($sql); + } + + $next_offset = $offset+$limit; + $ret = [ + 'unread_count' => $unread, + 'offset' => $offset, + 'limit' => $limit, + // 'prev_offset' => $offset === 0 ? null : max(0, $offset-$limit), + 'next_offset' => $next_offset < $total ? $next_offset : null, + 'next_page_html' => $next_offset < $total ? '' . $this->app->trans('charger d\'avantages de notifications') . '' : null, + 'notifications' => $notifications + ]; return $ret; } + /** + * mark a notification as read + * todo : add a "read_on" datetime so we can delete read notifs after N days. For now we use "created_on" + * + * @param array $notifications + * @param $usr_id + * @return $this|false + */ public function read(Array $notifications, $usr_id) { if (count($notifications) == 0) { diff --git a/resources/www/common/js/components/common.js b/resources/www/common/js/components/common.js index 16ba0264d9..479f5fb4a7 100644 --- a/resources/www/common/js/components/common.js +++ b/resources/www/common/js/components/common.js @@ -1,3 +1,4 @@ +// import notifyLayout from "../../../../../Phraseanet-production-client/src/components/notify/notifyLayout"; var p4 = p4 || {}; var datepickerLang = []; @@ -32,7 +33,6 @@ var commonModule = (function ($, p4) { $(this).removeClass('context-menu-item-hover'); }); - $('body').on('click', '.infoDialog', function (event) { infoDialog($(this)); }); @@ -42,11 +42,13 @@ var commonModule = (function ($, p4) { function showOverlay(n, appendto, callback, zIndex) { var div = "OVERLAY"; - if (typeof(n) != "undefined") + if (typeof(n) != "undefined") { div += n; + } if ($('#' + div).length === 0) { - if (typeof(appendto) == 'undefined') + if (typeof(appendto) == 'undefined') { appendto = 'body'; + } $(appendto).append(''); } @@ -61,12 +63,13 @@ var commonModule = (function ($, p4) { left: 0 }; - if (parseInt(zIndex) > 0) + if (parseInt(zIndex) > 0) { css['zIndex'] = parseInt(zIndex); + } - if (typeof(callback) != 'function') - callback = function () { - }; + if (typeof(callback) != 'function') { + callback = function () {}; + } $('#' + div).css(css).addClass('overlay').fadeTo(500, 0.7).bind('click', function () { (callback)(); }); @@ -85,8 +88,9 @@ var commonModule = (function ($, p4) { }); } var div = "OVERLAY"; - if (typeof(n) != "undefined") + if (typeof(n) != "undefined") { div += n; + } $('#' + div).hide().remove(); } @@ -110,92 +114,118 @@ var commonModule = (function ($, p4) { }).dialog('open').css({'overflow-x': 'auto', 'overflow-y': 'hidden', 'padding': '0'}); } - - // @deprecated - function manageSession(data, showMessages) { - if (typeof(showMessages) == "undefined") - showMessages = false; - + function manageSession(data) + { if (data.status == 'disconnected' || data.status == 'session') { self.location.replace(self.location.href); } - if (showMessages) { - var box = $('#notification_box'); - box.empty().append(data.notifications); - if (box.is(':visible')) - fix_notification_height(); + // add notification in bar - if ($('.notification.unread', box).length > 0) { - var trigger = $('.notification_trigger'); - $('.counter', trigger) - .empty() - .append($('.notification.unread', box).length); - $('.counter', trigger).css('visibility', 'visible'); + // fill the pseudo-dropdown with pre-formatted list of notifs (10 unread) + // + var box = $('#notification_box'); + box.empty().append(data.notifications_html); - } - else - $('.notification_trigger .counter').css('visibility', 'hidden').empty(); + if (box.is(':visible')) { + fix_notification_height(); // duplicated, better call notifyLayout.setBoxHeight(); + } - if (data.changed.length > 0) { - var current_open = $('.SSTT.ui-state-active'); - var current_sstt = current_open.length > 0 ? current_open.attr('id').split('_').pop() : false; + // fill the count of uread (red button) + // + var trigger = $('.notification_trigger'); + if(data.notifications.unread_count > 0) { + $('.counter', trigger) + .empty() + .append(data.notifications.unread_count).show(); + } + else { + $('.counter', trigger) + .hide() + .empty(); + } - var main_open = false; - for (var i = 0; i != data.changed.length; i++) { - var sstt = $('#SSTT_' + data.changed[i]); - if (sstt.size() === 0) { - if (main_open === false) { - $('#baskets .bloc').animate({'top': 30}, function () { - $('#baskets .alert_datas_changed:first').show() - }); - main_open = true; - } + // diplay notification about unread baskets + // + if (data.unread_basket_ids.length > 0) { + var current_open = $('.SSTT.ui-state-active'); + var current_sstt = current_open.length > 0 ? current_open.attr('id').split('_').pop() : false; + + var main_open = false; + for (var i = 0; i != data.unread_basket_ids.length; i++) { + var sstt = $('#SSTT_' + data.unread_basket_ids[i]); + if (sstt.size() === 0) { + if (main_open === false) { + $('#baskets .bloc').animate({'top': 30}, function () { + $('#baskets .alert_datas_changed:first').show() + }); + main_open = true; + } + } + else { + if (!sstt.hasClass('active')) { + sstt.addClass('unread'); } else { - if (!sstt.hasClass('active')) - sstt.addClass('unread'); - else { - $('.alert_datas_changed', $('#SSTT_content_' + data.changed[i])).show(); - } + $('.alert_datas_changed', $('#SSTT_content_' + data.unread_basket_ids[i])).show(); } } } - if ('' !== $.trim(data.message)) { - if ($('#MESSAGE').length === 0) - $('body').append('
'); - $('#MESSAGE') - .empty() - .append('

' + data.message + '

') - .attr('title', 'Global Message') - .dialog({ - autoOpen: false, - closeOnEscape: true, - resizable: false, - draggable: false, - modal: true, - close: function () { - if ($('.dialog_remove:checked', $(this)).length > 0) { - // setTemporaryPref - $.ajax({ - type: "POST", - url: "/user/preferences/temporary/", - data: { - prop: 'message', - value: 0 - }, - success: function (data) { - return; - } - }); - } - } - }) - .dialog('open'); - } } + + if ('' !== $.trim(data.message)) { + if ($('#MESSAGE').length === 0) { + $('body').append('
'); + } + $('#MESSAGE') + .empty() + .append('

' + data.message + '

') + .attr('title', 'Global Message') + .dialog({ + autoOpen: false, + closeOnEscape: true, + resizable: false, + draggable: false, + modal: true, + close: function () { + if ($('.dialog_remove:checked', $(this)).length > 0) { + // setTemporaryPref + $.ajax({ + type: "POST", + url: "/user/preferences/temporary/", + data: { + prop: 'message', + value: 0 + }, + success: function (data) { + return; + } + }); + } + } + }) + .dialog('open'); + } + return true; } + + /** + * duplicated from production_client::notifyLayout.js + */ + function fix_notification_height() { + const $notificationBoxContainer = $('#notification_box'); + const not = $('.notification', $notificationBoxContainer); + const n = not.length; + const not_t = $('.notification_title', $notificationBoxContainer); + const n_t = not_t.length; + + var h = not.outerHeight() * n + not_t.outerHeight() * n_t; + h = h > 350 ? 350 : h; + + $notificationBoxContainer.stop().animate({height: h}); + } + return { showOverlay: showOverlay, hideOverlay: hideOverlay, diff --git a/resources/www/report/js/report.js b/resources/www/report/js/report.js index 64b7bc3ea6..af439032f5 100644 --- a/resources/www/report/js/report.js +++ b/resources/www/report/js/report.js @@ -201,34 +201,35 @@ function reportDatePicker() { }); } +// poll only from menu bar -function pollNotifications() { - $.ajax({ - type: "POST", - url: "/session/notifications/", - dataType: 'json', - data: { - module: 10, - usr: usrId - }, - error: function () { - window.setTimeout("pollNotifications();", 10000); - }, - timeout: function () { - window.setTimeout("pollNotifications();", 10000); - }, - success: function (data) { - if (data) { - commonModule.manageSession(data); - } - var t = 120000; - if (data.apps && parseInt(data.apps) > 1) { - t = Math.round((Math.sqrt(parseInt(data.apps) - 1) * 1.3 * 120000)); - } - window.setTimeout("pollNotifications();", t); - return; - } - }); -}; - -window.setTimeout("pollNotifications();", 10000); +// function pollNotifications() { +// $.ajax({ +// type: "POST", +// url: "/session/notifications/", +// dataType: 'json', +// data: { +// module: 10, +// usr: usrId +// }, +// error: function () { +// window.setTimeout("pollNotifications();", 10000); +// }, +// timeout: function () { +// window.setTimeout("pollNotifications();", 10000); +// }, +// success: function (data) { +// if (data) { +// commonModule.manageSession(data); +// } +// var t = 120000; +// if (data.apps && parseInt(data.apps) > 1) { +// t = Math.round((Math.sqrt(parseInt(data.apps) - 1) * 1.3 * 120000)); +// } +// window.setTimeout("pollNotifications();", t); +// return; +// } +// }); +// }; +// +// window.setTimeout("pollNotifications();", 10000); diff --git a/templates/web/admin/databox/databox.html.twig b/templates/web/admin/databox/databox.html.twig index d5e6a62d25..e4d4777090 100644 --- a/templates/web/admin/databox/databox.html.twig +++ b/templates/web/admin/databox/databox.html.twig @@ -242,7 +242,12 @@ type: "GET", url: "/admin/databox/{{ databox.get_sbas_id() }}/informations/documents/", dataType: 'json', - data: {}, + headers: { + 'update-session': '0' // polling should not maintain the session alive + // also : use lowercase as recomended / normalized + }, + data: { + }, success: function (data) { try { if (data.viewname === '') { diff --git a/templates/web/common/menubar.html.twig b/templates/web/common/menubar.html.twig index 3ee72dd2cf..3b47bc6265 100644 --- a/templates/web/common/menubar.html.twig +++ b/templates/web/common/menubar.html.twig @@ -202,19 +202,13 @@
    {% if app.getAuthenticator().isAuthenticated() and module == "prod" %} -
  1. - - - - {{ 'Notifications' | trans }} - - -
  2. +
  3. + + +  {{ 'Notifications' | trans }} + + +
  4. {% endif %}
  5. {% if app.getAuthenticator().isAuthenticated() %} @@ -274,26 +268,19 @@
    {% if app.getAuthenticator().isAuthenticated() and module == "prod" %} -
  6. - - - - {{ 'Notifications' | trans }} - - -
  7. +
  8. + + +  {{ 'Notifications' | trans }} + + +
  9. {% endif %}
{% if app.getAuthenticator().isAuthenticated() and module == "prod" %} {% endif %} @@ -430,31 +417,36 @@ }); }); /**manage session and redirect to login page**/ - var usrId = '{{ app.getAuthenticator().user.getId }}' ; + // var usrId = '{{ app.getAuthenticator().user.getId }}' ; + var module = '{{ module }}'; + function pollNotifications() { $.ajax({ type: "POST", url: "/session/notifications/", dataType: 'json', data: { - module: 10, - usr: usrId + 'offset': 0, + 'limit': 10, + 'what': 2, // 2 : only unread }, - error: function () { - window.setTimeout("pollNotifications();", 10000); + error: function (data) { + if(data.getResponseHeader('x-phraseanet-end-session')) { + self.location.replace(self.location.href); // refresh will redirect to login + } }, timeout: function () { window.setTimeout("pollNotifications();", 10000); }, success: function (data) { - if (data) { + // there is no notification bar nor a basket notification if not on prod module + if (module === 'prod' && data) { commonModule.manageSession(data); } window.setTimeout("pollNotifications();", 60000); - return; } }); - }; + } - window.setTimeout("pollNotifications();", 10000); + window.setTimeout("pollNotifications();", 2000); diff --git a/templates/web/lightbox/index.html.twig b/templates/web/lightbox/index.html.twig index fd2629e123..3ac4aa8e2b 100644 --- a/templates/web/lightbox/index.html.twig +++ b/templates/web/lightbox/index.html.twig @@ -99,7 +99,7 @@ {% if basket.getValidation is null %} {% set basket_length = basket.getElements().count() %} {% set counter = ( counter | default(0) ) + 1 %} - {% set counter_length = baskets_collection.length() %} + {% set counter_length = baskets_collection | length %} diff --git a/templates/web/prod/index.html.twig b/templates/web/prod/index.html.twig index ad0d519547..15328f978f 100644 --- a/templates/web/prod/index.html.twig +++ b/templates/web/prod/index.html.twig @@ -101,11 +101,11 @@ lang: "{{ app.locale }}", baseUrl: '{{ app['request'].getUriForPath('/') }}', basePath: '{{ app.request.basePath|e('js') }}', - notify: { - url: "{{ path('list_notifications') }}", - moduleId: 1, - userId: {{app.getAuthenticatedUser().getId()}} - }, + {#notify: {#} + {# url: "{{ path('list_notifications') }}",#} + {# moduleId: 1,#} + {# userId: {{app.getAuthenticatedUser().getId()}}#} + {#},#} debug: {% if app.debug %}true{% else %}false{% endif %}, initialState: "{{ initialAppState }}", geonameServerUrl: '{{ app['geonames.server-uri'] }}', diff --git a/templates/web/prod/notifications.html.twig b/templates/web/prod/notifications.html.twig index 313c6230df..c390cda1e5 100644 --- a/templates/web/prod/notifications.html.twig +++ b/templates/web/prod/notifications.html.twig @@ -6,7 +6,7 @@ {% else %}
- {{ 'toutes les notifications' | trans }} + {{ 'toutes les notifications' | trans }}
{% endif %} diff --git a/templates/web/thesaurus/thesaurus.html.twig b/templates/web/thesaurus/thesaurus.html.twig index 1ce8830e10..8013e063d2 100644 --- a/templates/web/thesaurus/thesaurus.html.twig +++ b/templates/web/thesaurus/thesaurus.html.twig @@ -150,7 +150,7 @@ alert("{{ 'phraseanet::erreur: Votre session est fermee, veuillez vous re-authentifier' | trans }}"); self.location.replace(self.location.href); } - window.setTimeout("pollNotifications();", 10000); + window.setTimeout("pollNotifications();", 60000); return; } diff --git a/www/scripts/apps/admin/main/app.js b/www/scripts/apps/admin/main/app.js index 50f2c690e6..54fe2ea6c3 100644 --- a/www/scripts/apps/admin/main/app.js +++ b/www/scripts/apps/admin/main/app.js @@ -21,35 +21,37 @@ define([ eventManager: _.extend({}, Backbone.Events) }; - window.pullNotifications = function (){ - $.ajax({ - type: "POST", - url: AdminApp.$scope.data("notif-url"), - dataType: 'json', - data: { - module : 3, - usr : AdminApp.$scope.data("usr") - }, - error: function(){ - window.setTimeout("pullNotifications();", 10000); - }, - timeout: function(){ - window.setTimeout("pullNotifications();", 10000); - }, - success: function(data){ - if (data) { - if (data.status == 'disconnected' || data.status == 'session') { - self.location.replace(self.location.href); - } - } - var t = 120000; - if (data.apps && parseInt(data.apps) > 1) { - t = Math.round((Math.sqrt(parseInt(data.apps)-1) * 1.3 * 120000)); - } - window.setTimeout("pullNotifications();", t); - } - }); - }; + // pull notification is called from menu bar + + // window.pullNotifications = function (){ + // $.ajax({ + // type: "POST", + // url: AdminApp.$scope.data("notif-url"), + // dataType: 'json', + // data: { + // module : 3, + // usr : AdminApp.$scope.data("usr") + // }, + // error: function(){ + // window.setTimeout("pullNotifications();", 10000); + // }, + // timeout: function(){ + // window.setTimeout("pullNotifications();", 10000); + // }, + // success: function(data){ + // if (data) { + // if (data.status == 'disconnected' || data.status == 'session') { + // self.location.replace(self.location.href); + // } + // } + // var t = 120000; + // if (data.apps && parseInt(data.apps) > 1) { + // t = Math.round((Math.sqrt(parseInt(data.apps)-1) * 1.3 * 120000)); + // } + // window.setTimeout("pullNotifications();", t); + // } + // }); + // }; window.enableForms = function (forms) { forms.bind('submit', function(event){ var method = $(this).attr('method'); diff --git a/www/scripts/apps/admin/tasks-manager/collections/tasks.js b/www/scripts/apps/admin/tasks-manager/collections/tasks.js index 9cac331e81..63253dc376 100644 --- a/www/scripts/apps/admin/tasks-manager/collections/tasks.js +++ b/www/scripts/apps/admin/tasks-manager/collections/tasks.js @@ -15,7 +15,7 @@ define([ var TaskCollection = Backbone.Collection.extend({ model: TaskModel, url: function () { - return "/admin/task-manager/tasks"; + return "/admin/task-manager/tasks?update_session=0"; } });