MOBILE-3726 core: Display warning modal before open browser

main
Dani Palou 2021-10-07 08:58:05 +02:00
parent be8a7b35b7
commit 84354cade7
50 changed files with 177 additions and 80 deletions

View File

@ -1578,6 +1578,7 @@
"core.dismiss": "local_moodlemobileapp",
"core.displayoptions": "atto_media",
"core.done": "survey",
"core.dontshowagain": "local_moodlemobileapp",
"core.download": "moodle",
"core.downloaded": "local_moodlemobileapp",
"core.downloadfile": "moodle",
@ -2257,6 +2258,7 @@
"core.viewembeddedcontent": "local_moodlemobileapp",
"core.viewprofile": "moodle",
"core.warningofflinedatadeleted": "local_moodlemobileapp",
"core.warnopeninbrowser": "local_moodlemobileapp",
"core.whatisyourage": "moodle",
"core.wheredoyoulive": "moodle",
"core.whoissiteadmin": "local_moodlemobileapp",

View File

@ -53,7 +53,9 @@
<ion-item class="ion-text-wrap" *ngIf="badge.issuercontact">
<ion-label>
<h2>{{ 'addon.badges.contact' | translate}}</h2>
<p><a href="mailto:{{badge.issuercontact}}" core-link auto-login="no"> {{ badge.issuercontact }} </a></p>
<p><a href="mailto:{{badge.issuercontact}}" core-link auto-login="no" [showBrowserWarning]="false">
{{ badge.issuercontact }}
</a></p>
</ion-label>
</ion-item>
</ion-item-group>
@ -97,7 +99,9 @@
<ion-item class="ion-text-wrap" *ngIf="badge.imageauthoremail">
<ion-label>
<h2>{{ 'addon.badges.imageauthoremail' | translate}}</h2>
<p><a href="mailto:{{badge.imageauthoremail}}" core-link auto-login="no"> {{ badge.imageauthoremail }} </a></p>
<p><a href="mailto:{{badge.imageauthoremail}}" core-link auto-login="no" [showBrowserWarning]="false">
{{ badge.imageauthoremail }}
</a></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="badge.imageauthorurl">
@ -165,7 +169,8 @@
<ion-label>
<h2>{{ 'addon.badges.issueremail' | translate}}</h2>
<p>
<a href="mailto:{{badge.endorsement.issueremail}}" core-link auto-login="no">
<a href="mailto:{{badge.endorsement.issueremail}}" core-link auto-login="no"
[showBrowserWarning]="false">
{{ badge.endorsement.issueremail }}
</a>
</p>

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="assign && (description || (assign.introattachments && assign.introattachments.length))"
[priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()"

View File

@ -353,7 +353,7 @@
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p>{{ 'addon.mod_assign.cannotgradefromapp' | translate }}</p>
<ion-button expand="block" *ngIf="gradeUrl" [href]="gradeUrl" core-link >
<ion-button expand="block" *ngIf="gradeUrl" [href]="gradeUrl" core-link [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -5,7 +5,7 @@
</ion-button>
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt"></core-context-menu-item>
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -5,7 +5,7 @@
</ion-button>
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">>
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -17,7 +17,8 @@
<p class="item-heading">{{ 'core.numwords' | translate: {'$a': wordCount} }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" [href]="url" *ngIf="url" core-link capture="false" button detail="false">
<ion-item class="ion-text-wrap" [href]="url" *ngIf="url" core-link capture="false" button detail="false"
[showBrowserWarning]="false">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{ 'core.openinbrowser' | translate }}</p>

View File

@ -11,7 +11,7 @@
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -10,7 +10,7 @@
iconAction="stats-chart">
</core-context-menu-item>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -5,7 +5,7 @@
</ion-button>
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -74,7 +74,7 @@ export class AddonModLtiHelperProvider {
module,
};
return site.openInBrowserWithAutoLogin(module.url!);
return site.openInBrowserWithAutoLogin(module.url || '');
}
// Open in app.

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">
@ -218,7 +218,8 @@
<!-- Button to open in browser if it cannot be attempted in the app. -->
<ion-button class="ion-margin" *ngIf="!buttonText && ((!hasSupportedQuestions && unsupportedQuestions.length) ||
unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="externalUrl" core-link>
unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="externalUrl" core-link
[showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -165,7 +165,8 @@
</ion-label>
</ion-item>
<ion-button *ngIf="preventSubmitMessages.length" expand="block" [href]="moduleUrl" core-link>
<ion-button *ngIf="preventSubmitMessages.length" expand="block" [href]="moduleUrl" core-link
[showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>
@ -182,7 +183,7 @@
<ion-item class="ion-text-wrap">
<ion-label>{{ 'addon.mod_quiz.errorparsequestions' | translate }}</ion-label>
</ion-item>
<ion-button expand="block" class="ion-margin" [href]="moduleUrl" core-link>
<ion-button expand="block" class="ion-margin" [href]="moduleUrl" core-link [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt"></core-context-menu-item>
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"

View File

@ -188,7 +188,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
}
// The resource cannot be downloaded, open the activity in browser.
await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module.url!);
await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module.url || '');
}
/**

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">
@ -185,7 +185,7 @@
<p class="text-danger">{{ errorMessage | translate }}</p>
</ion-label>
</ion-item>
<ion-button class="ion-margin ion-text-wrap" expand="block" [href]="externalUrl" core-link>
<ion-button class="ion-margin ion-text-wrap" expand="block" [href]="externalUrl" core-link [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt"></core-context-menu-item>
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"

View File

@ -14,7 +14,7 @@
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
[href]="externalUrl" iconAction="fas-external-link-alt">
[href]="externalUrl" iconAction="fas-external-link-alt" [showBrowserWarning]="false">
</core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">

View File

@ -355,7 +355,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
*
* @param task Task to be done.
*/
runTask(task: AddonModWorkshopPhaseTaskData): void {
async runTask(task: AddonModWorkshopPhaseTaskData): Promise<void> {
if (task.code == 'submit') {
this.gotoSubmit();
} else if (task.link) {

View File

@ -57,7 +57,7 @@ export class AddonModWorkshopPhaseInfoComponent implements OnInit {
*
* @param task Task to be done.
*/
runTask(task: AddonModWorkshopPhaseTaskData): void {
async runTask(task: AddonModWorkshopPhaseTaskData): Promise<void> {
if (task.code == 'submit') {
// This will close the modal and go to the submit.
ModalController.dismiss(true);

View File

@ -104,17 +104,20 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi
case 'browser':
return CoreUtils.openInBrowser(url);
default:
if (CoreContentLinksHelper.handleLink(url, undefined, undefined, true)) {
default: {
const treated = await CoreContentLinksHelper.handleLink(url, undefined, undefined, true);
if (treated) {
// Link treated, stop.
return;
}
}
}
}
// No appurl or cannot be handled by the app. Try to handle the contexturl now.
if (notification.contexturl) {
if (CoreContentLinksHelper.handleLink(notification.contexturl)) {
const treated = await CoreContentLinksHelper.handleLink(notification.contexturl);
if (treated) {
// Link treated, stop.
return;
}

View File

@ -125,7 +125,7 @@ export class AppComponent implements OnInit, AfterViewInit {
const urlScheme = CoreUrlUtils.getUrlProtocol(url);
if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') {
// Open in browser should launch the right app if found and do nothing if not found.
CoreUtils.openInBrowser(url);
CoreUtils.openInBrowser(url, { showBrowserWarning: false });
// At this point the InAppBrowser is showing a "Webpage not available" error message.
// Try to navigate to last loaded URL so this error message isn't found.

View File

@ -32,7 +32,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrlUtils, CoreUrlParams } from '@services/utils/url';
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
import { CoreUtils, CoreUtilsOpenInBrowserOptions, PromiseDefer } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { SQLiteDB } from '@classes/sqlitedb';
import { CoreError } from '@classes/errors/error';
@ -1381,10 +1381,15 @@ export class CoreSite {
*
* @param url The URL to open.
* @param alertMessage If defined, an alert will be shown before opening the browser.
* @param options Other options.
* @return Promise resolved when done, rejected otherwise.
*/
async openInBrowserWithAutoLogin(url: string, alertMessage?: string): Promise<void> {
await this.openWithAutoLogin(false, url, undefined, alertMessage);
async openInBrowserWithAutoLogin(
url: string,
alertMessage?: string,
options: CoreUtilsOpenInBrowserOptions = {},
): Promise<void> {
await this.openWithAutoLogin(false, url, options, alertMessage);
}
/**
@ -1392,10 +1397,15 @@ export class CoreSite {
*
* @param url The URL to open.
* @param alertMessage If defined, an alert will be shown before opening the browser.
* @param options Other options.
* @return Promise resolved when done, rejected otherwise.
*/
async openInBrowserWithAutoLoginIfSameSite(url: string, alertMessage?: string): Promise<void> {
await this.openWithAutoLoginIfSameSite(false, url, undefined, alertMessage);
async openInBrowserWithAutoLoginIfSameSite(
url: string,
alertMessage?: string,
options: CoreUtilsOpenInBrowserOptions = {},
): Promise<void> {
await this.openWithAutoLoginIfSameSite(false, url, options, alertMessage);
}
/**
@ -1442,7 +1452,7 @@ export class CoreSite {
async openWithAutoLogin(
inApp: boolean,
url: string,
options?: InAppBrowserOptions,
options: InAppBrowserOptions & CoreUtilsOpenInBrowserOptions = {},
alertMessage?: string,
): Promise<InAppBrowserObject | void> {
// Get the URL to open.
@ -1458,13 +1468,14 @@ export class CoreSite {
);
await alert.onDidDismiss();
options.showBrowserWarning = false; // A warning already shown, no need to show another.
}
// Open the URL.
if (inApp) {
return CoreUtils.openInApp(url, options);
} else {
return CoreUtils.openInBrowser(url);
return CoreUtils.openInBrowser(url, options);
}
}
@ -1480,7 +1491,7 @@ export class CoreSite {
async openWithAutoLoginIfSameSite(
inApp: boolean,
url: string,
options?: InAppBrowserOptions,
options: InAppBrowserOptions & CoreUtilsOpenInBrowserOptions = {},
alertMessage?: string,
): Promise<InAppBrowserObject | void> {
if (this.containsUrl(url)) {
@ -1489,7 +1500,7 @@ export class CoreSite {
if (inApp) {
return Promise.resolve(CoreUtils.openInApp(url, options));
} else {
CoreUtils.openInBrowser(url);
CoreUtils.openInBrowser(url, options);
}
}
}

View File

@ -51,6 +51,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
@Input() badgeClass?: number; // A class to set in the badge.
@Input() badgeA11yText?: string; // Description for the badge, if needed.
@Input() hidden?: boolean; // Whether the item should be hidden.
@Input() showBrowserWarning = true; // Whether to show a warning before opening browser (for links). Defaults to true.
@Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked.
@Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked.

View File

@ -4,7 +4,8 @@
</ion-list-header>
<ion-item class="ion-text-wrap" *ngFor="let item of items" core-link [capture]="item.captureLink" [autoLogin]="item.autoLogin"
[href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden"
[detail]="(item.href && !item.iconAction) || null" role="menuitem" [button]="(item.href && !item.iconAction)">
[detail]="(item.href && !item.iconAction) || null" role="menuitem" [button]="(item.href && !item.iconAction)"
[showBrowserWarning]="item.showBrowserWarning">
<ion-icon *ngIf="item.iconDescription" [name]="item.iconDescription" aria-hidden="true" slot="start">
</ion-icon>
<ion-label>

View File

@ -63,6 +63,7 @@ export class CoreConstants {
static readonly SETTINGS_ZOOM_LEVEL = 'CoreSettingsZoomLevel';
static readonly SETTINGS_COLOR_SCHEME = 'CoreSettingsColorScheme';
static readonly SETTINGS_ANALYTICS_ENABLED = 'CoreSettingsAnalyticsEnabled';
static readonly SETTINGS_DONT_SHOW_EXTERNAL_LINK_WARN = 'CoreSettingsDontShowExtLinkWarn';
// WS constants.
static readonly WS_TIMEOUT = 30000; // Timeout when not in WiFi.

View File

@ -43,6 +43,7 @@ export class CoreLinkDirective implements OnInit {
"no" -> Never auto-login.
"check" -> Auto-login only if it points to the current site. Default value. */
@Input() autoLogin = 'check';
@Input() showBrowserWarning = true; // Whether to show a warning before opening browser. Defaults to true.
protected element: Element;
@ -199,33 +200,35 @@ export class CoreLinkDirective implements OnInit {
if (this.inApp) {
CoreUtils.openInApp(href);
} else {
CoreUtils.openInBrowser(href);
CoreUtils.openInBrowser(href, { showBrowserWarning: this.showBrowserWarning });
}
return;
}
const currentSite = CoreSites.getRequiredCurrentSite();
// Check if URL does not have any protocol, so it's a relative URL.
if (!CoreUrlUtils.isAbsoluteURL(href)) {
// Add the site URL at the begining.
if (href.charAt(0) == '/') {
href = CoreSites.getCurrentSite()!.getURL() + href;
href = currentSite.getURL() + href;
} else {
href = CoreSites.getCurrentSite()!.getURL() + '/' + href;
href = currentSite.getURL() + '/' + href;
}
}
if (this.autoLogin == 'yes') {
if (this.inApp) {
await CoreSites.getCurrentSite()!.openInAppWithAutoLogin(href);
await currentSite.openInAppWithAutoLogin(href);
} else {
await CoreSites.getCurrentSite()!.openInBrowserWithAutoLogin(href);
await currentSite.openInBrowserWithAutoLogin(href, undefined, { showBrowserWarning: this.showBrowserWarning });
}
} else if (this.autoLogin == 'no') {
if (this.inApp) {
CoreUtils.openInApp(href);
} else {
CoreUtils.openInBrowser(href);
CoreUtils.openInBrowser(href, { showBrowserWarning: this.showBrowserWarning });
}
} else {
// Priority order is: core-link inApp attribute > forceOpenLinksIn setting > data-open-in HTML attribute.
@ -239,9 +242,13 @@ export class CoreLinkDirective implements OnInit {
}
if (openInApp) {
await CoreSites.getCurrentSite()!.openInAppWithAutoLoginIfSameSite(href);
await currentSite.openInAppWithAutoLoginIfSameSite(href);
} else {
await CoreSites.getCurrentSite()!.openInBrowserWithAutoLoginIfSameSite(href);
await currentSite.openInBrowserWithAutoLoginIfSameSite(
href,
undefined,
{ showBrowserWarning: this.showBrowserWarning },
);
}
}
}

View File

@ -17,7 +17,7 @@
<div *ngIf="module && module.url">
<p><strong>{{ 'core.course.useactivityonbrowser' | translate }}</strong></p>
<ion-button expand="block" [href]="module.url" core-link>
<ion-button expand="block" [href]="module.url" core-link [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>

View File

@ -123,7 +123,8 @@
<ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'core.course.contents' | translate }}</h2></ion-label>
</ion-item>
<ion-item [href]="courseUrl" core-link [attr.aria-label]="course.fullname" button detail="false">
<ion-item [href]="courseUrl" core-link [attr.aria-label]="course.fullname" button detail="false"
[showBrowserWarning]="false">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'core.openinbrowser' | translate }}</h2></ion-label>
</ion-item>

View File

@ -222,7 +222,7 @@ export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandler
try {
await CoreDomUtils.showConfirm(body);
CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(url);
CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(url, undefined, { showBrowserWarning: false });
} catch {
// User cancelled.
};

View File

@ -28,7 +28,7 @@
{{ 'core.login.signuprequiredfieldnotsupported' | translate }}
</ion-label>
</ion-item>
<ion-button expand="block" class="ion-margin" [href]="signupUrl" core-link autoLogin="no">
<ion-button expand="block" class="ion-margin" [href]="signupUrl" core-link autoLogin="no" [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
</ion-button>
</ion-list>

View File

@ -384,7 +384,10 @@ export class CoreLoginEmailSignupPage implements OnInit {
* Show contact information on site (we have to display again the age verification form).
*/
showContactOnSite(): void {
CoreUtils.openInBrowser(CoreTextUtils.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'));
CoreUtils.openInBrowser(
CoreTextUtils.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'),
{ showBrowserWarning: false },
);
}
/**

View File

@ -660,7 +660,7 @@ export class CoreLoginHelperProvider {
});
// Always open it in browser because the user might have the session stored in there.
CoreUtils.openInBrowser(loginUrl);
CoreUtils.openInBrowser(loginUrl, { showBrowserWarning: false });
CoreApp.closeApp();
return true;
@ -692,7 +692,7 @@ export class CoreLoginHelperProvider {
closebuttoncaption: Translate.instant('core.login.cancel'),
});
} else {
CoreUtils.openInBrowser(loginUrl);
CoreUtils.openInBrowser(loginUrl, { showBrowserWarning: false });
CoreApp.closeApp();
}
}

View File

@ -30,8 +30,8 @@
</p>
<p>{{ 'core.settings.license' | translate }}{{ 'core.labelsep' | translate }} {{ license.licenses }}</p>
<p><a *ngIf="license.url" [href]="license.url" core-link auto-login="no">{{ license.url }}</a></p>
<p><a *ngIf="license.email" [href]="'mailto:' + license.email" core-link
auto-login="no">{{ license.email }}</a></p>
<p><a *ngIf="license.email" [href]="'mailto:' + license.email" core-link auto-login="no"
[showBrowserWarning]="false">{{ license.email }}</a></p>
</ion-label>
<ion-button *ngIf="license.licenseUrl" [href]="license.licenseUrl" target="_blank"
fill="clear" slot="end" core-link auto-login="no">{{ 'core.view' | translate }}</ion-button>

View File

@ -17,7 +17,8 @@
<ion-item class="ion-text-wrap" *ngIf="user.email">
<ion-label>
<h2>{{ 'core.user.email' | translate }}</h2>
<p><a class="core-anchor" href="mailto:{{user.email}}" core-link auto-login="no">
<p><a class="core-anchor" href="mailto:{{user.email}}" core-link auto-login="no"
[showBrowserWarning]="false">
{{ user.email }}
</a></p>
</ion-label>
@ -25,7 +26,7 @@
<ion-item class="ion-text-wrap" *ngIf="user.phone1">
<ion-label>
<h2>{{ 'core.user.phone1' | translate}}</h2>
<p><a class="core-anchor" href="tel:{{user.phone1}}" core-link auto-login="no">
<p><a class="core-anchor" href="tel:{{user.phone1}}" core-link auto-login="no" [showBrowserWarning]="false">
{{ user.phone1 }}
</a></p>
</ion-label>
@ -33,7 +34,7 @@
<ion-item class="ion-text-wrap" *ngIf="user.phone2">
<ion-label>
<h2>{{ 'core.user.phone2' | translate}}</h2>
<p><a class="core-anchor" href="tel:{{user.phone2}}" core-link auto-login="no">
<p><a class="core-anchor" href="tel:{{user.phone2}}" core-link auto-login="no" [showBrowserWarning]="false">
{{ user.phone2 }}
</a></p>
</ion-label>
@ -41,7 +42,7 @@
<ion-item class="ion-text-wrap" *ngIf="formattedAddress">
<ion-label>
<h2>{{ 'core.user.address' | translate}}</h2>
<p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no">
<p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no" [showBrowserWarning]="false">
{{ formattedAddress }}
</a></p>
</ion-label>

View File

@ -56,7 +56,7 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler
event.preventDefault();
event.stopPropagation();
CoreUtils.openInBrowser('mailto:' + user.email);
CoreUtils.openInBrowser('mailto:' + user.email, { showBrowserWarning: false });
},
};
}

View File

@ -86,6 +86,7 @@
"dismiss": "Dismiss",
"displayoptions": "Display options",
"done": "Done",
"dontshowagain": "Don't show again.",
"download": "Download",
"downloaded": "Downloaded",
"downloadfile": "Download file",
@ -327,6 +328,7 @@
"viewembeddedcontent": "View embedded content",
"viewprofile": "View profile",
"warningofflinedatadeleted": "Offline data from {{component}} '{{name}}' has been deleted. {{error}}",
"warnopeninbrowser": "<p>You are about to leave the app to open the following URL in your device's browser. Do you want to continue?</p>\n<p><b>{{url}}</b></p>",
"whatisyourage": "What is your age?",
"wheredoyoulive": "In which country do you live?",
"whoissiteadmin": "\"Site Administrators\" are the people who manage the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.",

View File

@ -731,7 +731,7 @@ export class CoreSitesProvider {
Translate.instant('core.updaterequired'),
Translate.instant('core.download'),
Translate.instant(siteId ? 'core.mainmenu.logout' : 'core.cancel'),
).then(() => CoreUtils.openInBrowser(downloadUrl)).catch(() => {
).then(() => CoreUtils.openInBrowser(downloadUrl, { showBrowserWarning: false })).catch(() => {
// Do nothing.
});
} else {

View File

@ -1440,7 +1440,7 @@ export class CoreDomUtilsProvider {
buttons.push({
text: Translate.instant('core.download'),
handler: (): void => {
CoreUtils.openInBrowser(link);
CoreUtils.openInBrowser(link, { showBrowserWarning: false });
},
});
}
@ -1465,28 +1465,32 @@ export class CoreDomUtilsProvider {
*
* @param message Modal message.
* @param header Modal header.
* @param placeholder Placeholder of the input element. By default, "Password".
* @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.
* @return Promise resolved with the input data if the user clicks OK, rejected if cancels.
* @return Promise resolved with the input data (true for checkbox/radio) if the user clicks OK, rejected if cancels.
*/
showPrompt(
message: string,
header?: string,
placeholder?: string,
placeholderOrLabel?: string,
type: TextFieldTypes | 'checkbox' | 'radio' | 'textarea' = 'password',
): Promise<any> { // eslint-disable-line @typescript-eslint/no-explicit-any
return new Promise((resolve, reject) => {
placeholder = placeholder ?? Translate.instant('core.login.password');
placeholderOrLabel = placeholderOrLabel ?? Translate.instant('core.login.password');
const isCheckbox = type === 'checkbox';
const isRadio = type === 'radio';
const options: AlertOptions = {
header,
message,
inputs: [
{
name: 'promptinput',
placeholder: placeholder,
placeholder: placeholderOrLabel,
label: placeholderOrLabel,
type,
value: (isCheckbox || isRadio) ? true : undefined,
},
],
buttons: [
@ -1500,7 +1504,13 @@ export class CoreDomUtilsProvider {
{
text: Translate.instant('core.ok'),
handler: (data) => {
resolve(data.promptinput);
if (isCheckbox) {
resolve(data[0]);
} else if (isRadio) {
resolve(data);
} else {
resolve(data.promptinput);
}
},
},
],

View File

@ -32,6 +32,7 @@ import { CoreViewerQRScannerComponent } from '@features/viewer/components/qr-sca
import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CoreFileEntry } from '@services/file-helper';
import { CoreConstants } from '@/core/constants';
import { CoreWindow } from '@singletons/window';
type TreeNode<T> = T & { children: TreeNode<T>[] };
@ -1047,8 +1048,17 @@ export class CoreUtilsProvider {
* Do not use for files, refer to {@link openFile}.
*
* @param url The URL to open.
* @param options Options.
*/
openInBrowser(url: string): void {
async openInBrowser(url: string, options: CoreUtilsOpenInBrowserOptions = {}): Promise<void> {
if (options.showBrowserWarning || options.showBrowserWarning === undefined) {
try {
await CoreWindow.confirmOpenBrowserIfNeeded(url);
} catch (error) {
return; // Cancelled, stop.
}
}
window.open(url, '_system');
}
@ -1727,6 +1737,13 @@ export type CoreUtilsOpenFileOptions = {
iOSOpenFileAction?: OpenFileAction; // Action to do when opening a file.
};
/**
* Options for opening in browser.
*/
export type CoreUtilsOpenInBrowserOptions = {
showBrowserWarning?: boolean; // Whether to display a warning before opening in browser. Defaults to true.
};
/**
* Possible default picker actions.
*/

View File

@ -14,11 +14,15 @@
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
import { NavController } from '@ionic/angular';
import { CoreConfig } from '@services/config';
import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreConstants } from '../constants';
/**
* Options for the open function.
@ -44,6 +48,31 @@ export class CoreWindow {
// Nothing to do.
}
/**
* Show a confirm before opening a link in browser, unless the user previously marked to not show again.
*
* @param url URL to open.
* @return Promise resolved if confirmed, rejected if rejected.
*/
static async confirmOpenBrowserIfNeeded(url: string): Promise<void> {
// Check if the user decided not to see the warning.
const dontShowWarning = await CoreConfig.get(CoreConstants.SETTINGS_DONT_SHOW_EXTERNAL_LINK_WARN, 0);
if (dontShowWarning) {
return;
}
const dontShowAgain = await CoreDomUtils.showPrompt(
Translate.instant('core.warnopeninbrowser', { url }),
undefined,
Translate.instant('core.dontshowagain'),
'checkbox',
);
if (dontShowAgain) {
CoreConfig.set(CoreConstants.SETTINGS_DONT_SHOW_EXTERNAL_LINK_WARN, 1);
}
}
/**
* "Safe" implementation of window.open. It will open the URL without overriding the app.
*
@ -73,12 +102,12 @@ export class CoreWindow {
}
if (!treated) {
// Not opened in the app, open with browser. Check if we need to auto-login
// Not opened in the app, open with browser. Check if we need to auto-login.
if (!CoreSites.isLoggedIn()) {
// Not logged in, cannot auto-login.
CoreUtils.openInBrowser(url);
} else {
await CoreSites.getCurrentSite()!.openInBrowserWithAutoLoginIfSameSite(url);
await CoreSites.getRequiredCurrentSite().openInBrowserWithAutoLoginIfSameSite(url);
}
}
}