From 7c5a7050e9155a0ee0b851b9d0fc8ca287a4f96f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 17 Nov 2021 08:41:21 +0100 Subject: [PATCH] MOBILE-3902 notifications: Warn admin if push are disabled --- scripts/langindex.json | 2 + src/addons/notifications/lang.json | 1 + .../notifications/notifications.module.ts | 3 +- .../notifications/services/notifications.ts | 97 ++++++++++++++++++- src/core/lang.json | 1 + src/core/services/utils/dom.ts | 57 ++++++++--- 6 files changed, 142 insertions(+), 19 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 94f7fe43e..489993f04 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1063,6 +1063,7 @@ "addon.notifications.notificationpreferences": "message", "addon.notifications.notifications": "local_moodlemobileapp", "addon.notifications.playsound": "local_moodlemobileapp", + "addon.notifications.pushdisabledwarning": "local_moodlemobileapp", "addon.notifications.therearentnotificationsyet": "local_moodlemobileapp", "addon.notifications.unreadnotification": "message", "addon.privatefiles.couldnotloadfiles": "local_moodlemobileapp", @@ -1667,6 +1668,7 @@ "core.fulllistofcourses": "moodle", "core.fullnameandsitename": "local_moodlemobileapp", "core.fullscreen": "h5p", + "core.goto": "local_moodlemobileapp", "core.grades.aggregatemean": "grades", "core.grades.aggregatesum": "grades", "core.grades.average": "grades", diff --git a/src/addons/notifications/lang.json b/src/addons/notifications/lang.json index b3c02b52e..ef4ffefe4 100644 --- a/src/addons/notifications/lang.json +++ b/src/addons/notifications/lang.json @@ -4,6 +4,7 @@ "notificationpreferences": "Notification preferences", "notifications": "Notifications", "playsound": "Play sound", + "pushdisabledwarning": "Your users are not receiving any notification from this site on their mobile devices. Enable mobile notifications in the Notification settings page.", "therearentnotificationsyet": "There are no notifications.", "unreadnotification": "Unread notification: {{$a}}" } diff --git a/src/addons/notifications/notifications.module.ts b/src/addons/notifications/notifications.module.ts index 0f2142d22..6ea333938 100644 --- a/src/addons/notifications/notifications.module.ts +++ b/src/addons/notifications/notifications.module.ts @@ -26,7 +26,7 @@ import { AddonNotificationsCronHandler } from './services/handlers/cron'; import { AddonNotificationsPushClickHandler } from './services/handlers/push-click'; import { AddonNotificationsSettingsHandler, AddonNotificationsSettingsHandlerService } from './services/handlers/settings'; import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing'; -import { AddonNotificationsProvider } from './services/notifications'; +import { AddonNotifications, AddonNotificationsProvider } from './services/notifications'; import { AddonNotificationsHelperProvider } from './services/notifications-helper'; export const ADDON_NOTIFICATIONS_SERVICES: Type[] = [ @@ -65,6 +65,7 @@ const preferencesRoutes: Routes = [ CoreSettingsDelegate.registerHandler(AddonNotificationsSettingsHandler.instance); AddonNotificationsMainMenuHandler.initialize(); + AddonNotifications.initialize(); }, }, ], diff --git a/src/addons/notifications/services/notifications.ts b/src/addons/notifications/services/notifications.ts index 3a28a19dc..bd1732ba3 100644 --- a/src/addons/notifications/services/notifications.ts +++ b/src/addons/notifications/services/notifications.ts @@ -14,15 +14,18 @@ import { Injectable } from '@angular/core'; -import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites'; +import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; import { CoreWSExternalWarning } from '@services/ws'; import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreUser } from '@features/user/services/user'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreLogger } from '@singletons/logger'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; +import { CoreEvents } from '@singletons/events'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; const ROOT_CACHE_KEY = 'mmaNotifications:'; @@ -43,6 +46,15 @@ export class AddonNotificationsProvider { this.logger = CoreLogger.getInstance('AddonNotificationsProvider'); } + /** + * Initialize the service. + */ + initialize(): void { + CoreEvents.on(CoreEvents.LOGIN, (data) => { + this.warnPushDisabledForAdmin(data.siteId); + }); + } + /** * Function to format notification data. * @@ -115,16 +127,17 @@ export class AddonNotificationsProvider { /** * Get notification preferences. * - * @param siteId Site ID. If not defined, use current site. + * @param options Options. * @return Promise resolved with the notification preferences. */ - async getNotificationPreferences(siteId?: string): Promise { + async getNotificationPreferences(options: CoreSitesCommonWSOptions = {}): Promise { this.logger.debug('Get notification preferences'); - const site = await CoreSites.getSite(siteId); + const site = await CoreSites.getSite(options.siteId); const preSets: CoreSiteWSPreSets = { cacheKey: this.getNotificationPreferencesCacheKey(), updateFrequency: CoreSite.FREQUENCY_SOMETIMES, + ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; const data = await site.read( @@ -382,6 +395,80 @@ export class AddonNotificationsProvider { await site.invalidateWsCacheForKey(this.getNotificationsCacheKey()); } + /** + * Is user is an admin and push are disabled, notify him. + * + * @param siteId Site ID. + * @return Promise resolved when done. + */ + protected async warnPushDisabledForAdmin(siteId?: string): Promise { + if (!siteId) { + return; + } + + try { + const site = await CoreSites.getSite(siteId); + + if (!site.getInfo()?.userissiteadmin) { + // Not an admin or we don't know, stop. + return; + } + + // Check if the admin already asked not to be reminded. + const dontAsk = await site.getLocalSiteConfig('AddonNotificationsDontRemindPushDisabled', 0); + if (dontAsk) { + return; + } + + // Check if push are disabled. + const preferences = await this.getNotificationPreferences({ + readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK, + siteId, + }); + + const processor = preferences.processors.find(processor => processor.name === 'airnotifier'); + if (processor) { + // Enabled. + return; + } + + // Warn the admin. + const dontShowAgain = await CoreDomUtils.showPrompt( + Translate.instant('addon.notifications.pushdisabledwarning'), + undefined, + Translate.instant('core.dontshowagain'), + 'checkbox', + [ + { + text: Translate.instant('core.ok'), + }, + { + text: Translate.instant('core.goto', { $a: Translate.instant('core.settings.settings') }), + handler: (data, resolve) => { + resolve(data[0]); + + const url = CoreTextUtils.concatenatePaths( + site.getURL(), + site.isVersionGreaterEqualThan('3.11') ? + 'message/output/airnotifier/checkconfiguration.php' : + 'admin/message.php', + ); + + // Don't try auto-login, admins cannot use it. + CoreUtils.openInBrowser(url); + }, + }, + ], + ); + + if (dontShowAgain) { + await site.setLocalSiteConfig('AddonNotificationsDontRemindPushDisabled', 1); + } + } catch { + // Ignore errors. + } + } + } export const AddonNotifications = makeSingleton(AddonNotificationsProvider); diff --git a/src/core/lang.json b/src/core/lang.json index 2a7408ed0..08efb25f7 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -123,6 +123,7 @@ "fulllistofcourses": "All courses", "fullnameandsitename": "{{fullname}} ({{sitename}})", "fullscreen": "Fullscreen", + "goto": "Go to {{$a}}", "group": "Group", "groupsseparate": "Separate groups", "groupsvisible": "Visible groups", diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 610b1753b..bc52b22fe 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -1478,7 +1478,7 @@ export class CoreDomUtilsProvider { * @param header Modal header. * @param placeholderOrLabel Placeholder (for textual/numeric inputs) or label (for radio/checkbox). By default, "Password". * @param type Type of the input element. By default, password. - * @param options More options to pass to the alert. + * @param buttons Buttons. If not provided, OK and Cancel buttons will be displayed. * @return Promise resolved with the input data (true for checkbox/radio) if the user clicks OK, rejected if cancels. */ showPrompt( @@ -1486,12 +1486,25 @@ export class CoreDomUtilsProvider { header?: string, placeholderOrLabel?: string, type: TextFieldTypes | 'checkbox' | 'radio' | 'textarea' = 'password', + buttons?: PromptButton[], ): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { placeholderOrLabel = placeholderOrLabel ?? Translate.instant('core.login.password'); const isCheckbox = type === 'checkbox'; const isRadio = type === 'radio'; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const resolvePromise = (data: any) => { + if (isCheckbox) { + resolve(data[0]); + } else if (isRadio) { + resolve(data); + } else { + resolve(data.promptinput); + } + }; + const options: AlertOptions = { header, message, @@ -1504,7 +1517,25 @@ export class CoreDomUtilsProvider { value: (isCheckbox || isRadio) ? true : undefined, }, ], - buttons: [ + }; + + if (buttons?.length) { + options.buttons = buttons.map((button) => ({ + ...button, + handler: (data) => { + if (!button.handler) { + // Just resolve the promise. + resolvePromise(data); + + return; + } + + button.handler(data, resolve, reject); + }, + })); + } else { + // Default buttons. + options.buttons = [ { text: Translate.instant('core.cancel'), role: 'cancel', @@ -1514,18 +1545,10 @@ export class CoreDomUtilsProvider { }, { text: Translate.instant('core.ok'), - handler: (data) => { - if (isCheckbox) { - resolve(data[0]); - } else if (isRadio) { - resolve(data); - } else { - resolve(data.promptinput); - } - }, + handler: resolvePromise, }, - ], - }; + ]; + } this.showAlertWithOptions(options); }); @@ -2045,3 +2068,11 @@ export type OpenModalOptions = ModalOptions & { waitForDismissCompleted?: boolean; closeOnNavigate?: boolean; // Default true. }; + +/** + * Buttons for prompt alert. + */ +export type PromptButton = Omit & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler?: (value: any, resolve: (value: any) => void, reject: (reason: any) => void) => void; +};