MOBILE-3565 contentlinks: Add content links delegate structure
parent
f3ae7e5e4a
commit
dd43b9460b
|
@ -0,0 +1,116 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { CoreContentLinksHandler, CoreContentLinksAction } from '../services/contentlinks.delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base handler to be registered in CoreContentLinksHandler. It is useful to minimize the amount of
|
||||||
|
* functions that handlers need to implement.
|
||||||
|
*
|
||||||
|
* It allows you to specify a "pattern" (RegExp) that will be used to check if the handler handles a URL and to get its site URL.
|
||||||
|
*/
|
||||||
|
export class CoreContentLinksHandlerBase implements CoreContentLinksHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A name to identify the handler.
|
||||||
|
*/
|
||||||
|
name = 'CoreContentLinksHandlerBase';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler's priority. The highest priority is treated first.
|
||||||
|
*/
|
||||||
|
priority = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the isEnabled function should be called for all the users in a site. It should be true only if the isEnabled call
|
||||||
|
* can return different values for different users in same site.
|
||||||
|
*/
|
||||||
|
checkAllUsers = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the feature this handler is related to.
|
||||||
|
* It will be used to check if the feature is disabled (@see CoreSite.isFeatureDisabled).
|
||||||
|
*/
|
||||||
|
featureName = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pattern to use to detect if the handler handles a URL and to get its site URL. Required if "handles" and
|
||||||
|
* "getSiteUrl" functions aren't overridden.
|
||||||
|
*/
|
||||||
|
pattern?: RegExp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of actions for a link (url).
|
||||||
|
*
|
||||||
|
* @param siteIds List of sites the URL belongs to.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return List of (or promise resolved with list of) actions.
|
||||||
|
*/
|
||||||
|
getActions(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
siteIds: string[],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
url: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
params: Params,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
courseId?: number,
|
||||||
|
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a URL is handled by this handler.
|
||||||
|
*
|
||||||
|
* @param url The URL to check.
|
||||||
|
* @return Whether the URL is handled by this handler
|
||||||
|
*/
|
||||||
|
handles(url: string): boolean {
|
||||||
|
return !!this.pattern && url.search(this.pattern) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the URL is handled by this handler, return the site URL.
|
||||||
|
*
|
||||||
|
* @param url The URL to check.
|
||||||
|
* @return Site URL if it is handled, undefined otherwise.
|
||||||
|
*/
|
||||||
|
getSiteUrl(url: string): string | undefined {
|
||||||
|
if (this.pattern) {
|
||||||
|
const position = url.search(this.pattern);
|
||||||
|
if (position > -1) {
|
||||||
|
return url.substr(0, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||||
|
* If not defined, defaults to true.
|
||||||
|
*
|
||||||
|
* @param siteId The site ID.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return Whether the handler is enabled for the URL and site.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
isEnabled(siteId: string, url: string, params: Params, courseId?: number): boolean | Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreContentLinksAction } from '../services/contentlinks.delegate';
|
||||||
|
import { CoreContentLinksHandlerBase } from './base-handler';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
// import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to handle URLs pointing to the grade of a module.
|
||||||
|
*/
|
||||||
|
export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the module can be reviewed in the app. If true, the handler needs to implement the goToReview function.
|
||||||
|
*/
|
||||||
|
canReview = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this boolean is set to true, the app will retrieve all modules with this modName with a single WS call.
|
||||||
|
* This reduces the number of WS calls, but it isn't recommended for modules that can return a lot of contents.
|
||||||
|
*/
|
||||||
|
protected useModNameToGetModule = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the handler.
|
||||||
|
*
|
||||||
|
* @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled.
|
||||||
|
* @param modName Name of the module (assign, book, ...).
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public addon: string,
|
||||||
|
public modName: string,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// Match the grade.php URL with an id param.
|
||||||
|
this.pattern = new RegExp('/mod/' + modName + '/grade.php.*([&?]id=\\d+)');
|
||||||
|
this.featureName = 'CoreCourseModuleDelegate_' + addon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of actions for a link (url).
|
||||||
|
*
|
||||||
|
* @param siteIds Unused. List of sites the URL belongs to.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return List of (or promise resolved with list of) actions.
|
||||||
|
*/
|
||||||
|
getActions(
|
||||||
|
siteIds: string[],
|
||||||
|
url: string,
|
||||||
|
params: Params,
|
||||||
|
courseId?: number,
|
||||||
|
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
|
||||||
|
courseId = courseId || params.courseid || params.cid;
|
||||||
|
|
||||||
|
return [{
|
||||||
|
action: async (siteId): Promise<void> => {
|
||||||
|
// Check if userid is the site's current user.
|
||||||
|
const modal = await CoreDomUtils.instance.showModalLoading();
|
||||||
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
if (!params.userid || params.userid == site.getUserId()) {
|
||||||
|
// No user specified or current user. Navigate to module.
|
||||||
|
// @todo this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined,
|
||||||
|
// this.useModNameToGetModule ? this.modName : undefined, undefined, navCtrl);
|
||||||
|
} else if (this.canReview) {
|
||||||
|
// Use the goToReview function.
|
||||||
|
this.goToReview(url, params, courseId!, siteId);
|
||||||
|
} else {
|
||||||
|
// Not current user and cannot review it in the app, open it in browser.
|
||||||
|
site.openInBrowserWithAutoLogin(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.dismiss();
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the page to review.
|
||||||
|
*
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL.
|
||||||
|
* @param siteId Site to use.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async goToReview(
|
||||||
|
url: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
params: Params, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
courseId: number, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
siteId: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
): Promise<void> {
|
||||||
|
// This function should be overridden.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreContentLinksHandlerBase } from './base-handler';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { CoreContentLinksAction } from '../services/contentlinks.delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to handle URLs pointing to the index of a module.
|
||||||
|
*/
|
||||||
|
export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this boolean is set to true, the app will retrieve all modules with this modName with a single WS call.
|
||||||
|
* This reduces the number of WS calls, but it isn't recommended for modules that can return a lot of contents.
|
||||||
|
*/
|
||||||
|
protected useModNameToGetModule = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the handler.
|
||||||
|
*
|
||||||
|
* @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled.
|
||||||
|
* @param modName Name of the module (assign, book, ...).
|
||||||
|
* @param instanceIdParam Param name for instance ID gathering. Only if set.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public addon: string,
|
||||||
|
public modName: string,
|
||||||
|
protected instanceIdParam?: string,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const pattern = instanceIdParam ?
|
||||||
|
'/mod/' + modName + '/view.php.*([&?](' + instanceIdParam + '|id)=\\d+)' :
|
||||||
|
'/mod/' + modName + '/view.php.*([&?]id=\\d+)';
|
||||||
|
|
||||||
|
// Match the view.php URL with an id param.
|
||||||
|
this.pattern = new RegExp(pattern);
|
||||||
|
this.featureName = 'CoreCourseModuleDelegate_' + addon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mod params necessary to open an activity.
|
||||||
|
*
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return List of params to pass to navigateToModule / navigateToModuleByInstance.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
getPageParams(url: string, params: Params, courseId?: number): Params {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of actions for a link (url).
|
||||||
|
*
|
||||||
|
* @param siteIds List of sites the URL belongs to.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return List of (or promise resolved with list of) actions.
|
||||||
|
*/
|
||||||
|
getActions(
|
||||||
|
siteIds: string[], // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
url: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
params: Params, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
courseId?: number, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
return [];
|
||||||
|
/*
|
||||||
|
courseId = courseId || params.courseid || params.cid;
|
||||||
|
const pageParams = this.getPageParams(url, params, courseId);
|
||||||
|
|
||||||
|
if (this.instanceIdParam && typeof params[this.instanceIdParam] != 'undefined') {
|
||||||
|
const instanceId = parseInt(params[this.instanceIdParam], 10);
|
||||||
|
|
||||||
|
return [{
|
||||||
|
action: (siteId): void => {
|
||||||
|
this.courseHelper.navigateToModuleByInstance(instanceId, this.modName, siteId, courseId, undefined,
|
||||||
|
this.useModNameToGetModule, pageParams);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
action: (siteId): void => {
|
||||||
|
this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined,
|
||||||
|
this.useModNameToGetModule ? this.modName : undefined, pageParams);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreContentLinksHelper } from '../services/contentlinks.helper';
|
||||||
|
import { CoreContentLinksHandlerBase } from './base-handler';
|
||||||
|
import { Translate } from '@/app/singletons/core.singletons';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { CoreContentLinksAction } from '../services/contentlinks.delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to handle URLs pointing to a list of a certain type of modules.
|
||||||
|
*/
|
||||||
|
export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title to use in the new page. If not defined, the app will try to calculate it.
|
||||||
|
*/
|
||||||
|
protected title = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the handler.
|
||||||
|
*
|
||||||
|
* @param linkHelper The CoreContentLinksHelperProvider instance.
|
||||||
|
* @param translate The TranslateService instance.
|
||||||
|
* @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled.
|
||||||
|
* @param modName Name of the module (assign, book, ...).
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public addon: string,
|
||||||
|
public modName: string,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// Match the view.php URL with an id param.
|
||||||
|
this.pattern = new RegExp('/mod/' + modName + '/index.php.*([&?]id=\\d+)');
|
||||||
|
this.featureName = 'CoreCourseModuleDelegate_' + addon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of actions for a link (url).
|
||||||
|
*
|
||||||
|
* @param siteIds List of sites the URL belongs to.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @return List of (or promise resolved with list of) actions.
|
||||||
|
*/
|
||||||
|
getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
|
||||||
|
return [{
|
||||||
|
action: (siteId): void => {
|
||||||
|
const stateParams = {
|
||||||
|
courseId: params.id,
|
||||||
|
modName: this.modName,
|
||||||
|
title: this.title || Translate.instance.instant('addon.mod_' + this.modName + '.modulenameplural'),
|
||||||
|
};
|
||||||
|
|
||||||
|
CoreContentLinksHelper.instance.goInSite('CoreCourseListModTypePage @todo', stateParams, siteId);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"chooseaccount": "Choose account",
|
||||||
|
"chooseaccounttoopenlink": "Choose an account to open the link with.",
|
||||||
|
"confirmurlothersite": "This link belongs to another site. Do you want to open it?",
|
||||||
|
"errornoactions": "Couldn't find an action to perform with this link.",
|
||||||
|
"errornosites": "Couldn't find any site to handle this link.",
|
||||||
|
"errorredirectothersite": "The redirect URL cannot point to a different site."
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{ 'core.contentlinks.chooseaccount' | translate }}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<ion-list>
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<p class="item-heading">{{ 'core.contentlinks.chooseaccounttoopenlink' | translate }}</p>
|
||||||
|
<p>{{ url }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngFor="let site of sites" (click)="siteClicked(site.id)" detail="false">
|
||||||
|
<ion-avatar slot="start">
|
||||||
|
<img [src]="site.avatar" core-external-content [siteId]="site.id"
|
||||||
|
alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}" role="presentation"
|
||||||
|
onError="this.src='assets/img/user-avatar.png'">
|
||||||
|
</ion-avatar>
|
||||||
|
<h2>{{site.fullName}}</h2>
|
||||||
|
<p><core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text></p>
|
||||||
|
<p>{{site.siteUrl}}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-button expand="block" (click)="cancel()">{{ 'core.login.cancel' | translate }}</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,47 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
|
||||||
|
import { CoreContentLinksChooseSitePage } from './choose-site.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CoreContentLinksChooseSitePage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CoreContentLinksChooseSitePage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreContentLinksChooseSitePageModule {}
|
|
@ -0,0 +1,122 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { NavController } from '@ionic/angular';
|
||||||
|
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { Translate } from '@singletons/core.singletons';
|
||||||
|
import { CoreLoginHelper } from '@core/login/services/helper';
|
||||||
|
import { CoreContentLinksAction } from '../../services/contentlinks.delegate';
|
||||||
|
import { CoreContentLinksHelper } from '../../services/contentlinks.helper';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page to display the list of sites to choose one to perform a content link action.
|
||||||
|
*
|
||||||
|
* @todo Include routing and testing.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-content-links-choose-site',
|
||||||
|
templateUrl: 'choose-site.html',
|
||||||
|
})
|
||||||
|
export class CoreContentLinksChooseSitePage implements OnInit {
|
||||||
|
|
||||||
|
url: string;
|
||||||
|
sites: CoreSiteBasicInfo[] = [];
|
||||||
|
loaded = false;
|
||||||
|
protected action?: CoreContentLinksAction;
|
||||||
|
protected isRootURL = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
route: ActivatedRoute,
|
||||||
|
protected navCtrl: NavController,
|
||||||
|
) {
|
||||||
|
this.url = route.snapshot.queryParamMap.get('url')!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
if (!this.url) {
|
||||||
|
return this.leaveView();
|
||||||
|
}
|
||||||
|
|
||||||
|
let siteIds: string[] | undefined = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if it's the root URL.
|
||||||
|
const data = await CoreSites.instance.isStoredRootURL(this.url);
|
||||||
|
if (data.site) {
|
||||||
|
// It's the root URL.
|
||||||
|
this.isRootURL = true;
|
||||||
|
|
||||||
|
siteIds = data.siteIds;
|
||||||
|
} else if (data.siteIds.length) {
|
||||||
|
// Not root URL, but the URL belongs to at least 1 site. Check if there is any action to treat the link.
|
||||||
|
this.action = await CoreContentLinksHelper.instance.getFirstValidActionFor(this.url);
|
||||||
|
if (!this.action) {
|
||||||
|
throw new CoreError(Translate.instance.instant('core.contentlinks.errornoactions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
siteIds = this.action.sites;
|
||||||
|
} else {
|
||||||
|
// No sites to treat the URL.
|
||||||
|
throw new CoreError(Translate.instance.instant('core.contentlinks.errornosites'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sites that can perform the action.
|
||||||
|
this.sites = await CoreSites.instance.getSites(siteIds);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'core.contentlinks.errornosites', true);
|
||||||
|
this.leaveView();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel.
|
||||||
|
*/
|
||||||
|
cancel(): void {
|
||||||
|
this.leaveView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the action on a certain site.
|
||||||
|
*
|
||||||
|
* @param siteId Site ID.
|
||||||
|
*/
|
||||||
|
siteClicked(siteId: string): void {
|
||||||
|
if (this.isRootURL) {
|
||||||
|
CoreLoginHelper.instance.redirect('', {}, siteId);
|
||||||
|
} else if (this.action) {
|
||||||
|
this.action.action(siteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel and leave the view.
|
||||||
|
*/
|
||||||
|
protected async leaveView(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await CoreSites.instance.logout();
|
||||||
|
} finally {
|
||||||
|
await this.navCtrl.navigateRoot('/login/sites');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,309 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUrlUtils } from '@services/utils/url';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that all handlers must implement.
|
||||||
|
*/
|
||||||
|
export interface CoreContentLinksHandler {
|
||||||
|
/**
|
||||||
|
* A name to identify the handler.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler's priority. The highest priority is treated first.
|
||||||
|
*/
|
||||||
|
priority?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the isEnabled function should be called for all the users in a site. It should be true only if the isEnabled call
|
||||||
|
* can return different values for different users in same site.
|
||||||
|
*/
|
||||||
|
checkAllUsers?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the feature this handler is related to.
|
||||||
|
* It will be used to check if the feature is disabled (@see CoreSite.isFeatureDisabled).
|
||||||
|
*/
|
||||||
|
featureName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of actions for a link (url).
|
||||||
|
*
|
||||||
|
* @param siteIds List of sites the URL belongs to.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @param data Extra data to handle the URL.
|
||||||
|
* @return List of (or promise resolved with list of) actions.
|
||||||
|
*/
|
||||||
|
getActions(siteIds: string[], url: string, params: Params, courseId?: number, data?: unknown):
|
||||||
|
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a URL is handled by this handler.
|
||||||
|
*
|
||||||
|
* @param url The URL to check.
|
||||||
|
* @return Whether the URL is handled by this handler
|
||||||
|
*/
|
||||||
|
handles(url: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the URL is handled by this handler, return the site URL.
|
||||||
|
*
|
||||||
|
* @param url The URL to check.
|
||||||
|
* @return Site URL if it is handled, undefined otherwise.
|
||||||
|
*/
|
||||||
|
getSiteUrl(url: string): string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||||
|
* If not defined, defaults to true.
|
||||||
|
*
|
||||||
|
* @param siteId The site ID.
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return Whether the handler is enabled for the URL and site.
|
||||||
|
*/
|
||||||
|
isEnabled?(siteId: string, url: string, params: Params, courseId?: number): boolean | Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to perform when a link is clicked.
|
||||||
|
*/
|
||||||
|
export interface CoreContentLinksAction {
|
||||||
|
/**
|
||||||
|
* A message to identify the action. Default: 'core.view'.
|
||||||
|
*/
|
||||||
|
message?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the icon of the action. Default: 'fas-eye'.
|
||||||
|
*/
|
||||||
|
icon?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDs of the sites that support the action.
|
||||||
|
*/
|
||||||
|
sites?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to perform when the link is clicked.
|
||||||
|
*
|
||||||
|
* @param siteId The site ID.
|
||||||
|
*/
|
||||||
|
action(siteId: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions and priority for a handler and URL.
|
||||||
|
*/
|
||||||
|
export interface CoreContentLinksHandlerActions {
|
||||||
|
/**
|
||||||
|
* Handler's priority.
|
||||||
|
*/
|
||||||
|
priority: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of actions.
|
||||||
|
*/
|
||||||
|
actions: CoreContentLinksAction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate to register handlers to handle links.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class CoreContentLinksDelegate {
|
||||||
|
|
||||||
|
protected logger: CoreLogger;
|
||||||
|
protected handlers: { [s: string]: CoreContentLinksHandler } = {}; // All registered handlers.
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.logger = CoreLogger.getInstance('CoreContentLinksDelegate');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of possible actions to do for a URL.
|
||||||
|
*
|
||||||
|
* @param url URL to handle.
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @param username Username to use to filter sites.
|
||||||
|
* @param data Extra data to handle the URL.
|
||||||
|
* @return Promise resolved with the actions.
|
||||||
|
*/
|
||||||
|
async getActionsFor(url: string, courseId?: number, username?: string, data?: unknown): Promise<CoreContentLinksAction[]> {
|
||||||
|
if (!url) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list of sites the URL belongs to.
|
||||||
|
const siteIds = await CoreSites.instance.getSiteIdsFromUrl(url, true, username);
|
||||||
|
const linkActions: CoreContentLinksHandlerActions[] = [];
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
const params = CoreUrlUtils.instance.extractUrlParams(url);
|
||||||
|
for (const name in this.handlers) {
|
||||||
|
const handler = this.handlers[name];
|
||||||
|
const checkAll = handler.checkAllUsers;
|
||||||
|
const isEnabledFn = this.isHandlerEnabled.bind(this, handler, url, params, courseId);
|
||||||
|
|
||||||
|
if (!handler.handles(url)) {
|
||||||
|
// Invalid handler or it doesn't handle the URL. Stop.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the site IDs using the isEnabled function.
|
||||||
|
promises.push(CoreUtils.instance.filterEnabledSites(siteIds, isEnabledFn, checkAll).then(async (siteIds) => {
|
||||||
|
if (!siteIds.length) {
|
||||||
|
// No sites supported, no actions.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = await handler.getActions(siteIds, url, params, courseId, data);
|
||||||
|
|
||||||
|
if (actions && actions.length) {
|
||||||
|
// Set default values if any value isn't supplied.
|
||||||
|
actions.forEach((action) => {
|
||||||
|
action.message = action.message || 'core.view';
|
||||||
|
action.icon = action.icon || 'fas-eye';
|
||||||
|
action.sites = action.sites || siteIds;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add them to the list.
|
||||||
|
linkActions.push({
|
||||||
|
priority: handler.priority || 0,
|
||||||
|
actions: actions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await CoreUtils.instance.allPromises(promises);
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort link actions by priority.
|
||||||
|
return this.sortActionsByPriority(linkActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the site URL if the URL is supported by any handler.
|
||||||
|
*
|
||||||
|
* @param url URL to handle.
|
||||||
|
* @return Site URL if the URL is supported by any handler, undefined otherwise.
|
||||||
|
*/
|
||||||
|
getSiteUrl(url: string): string | void {
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any handler supports this URL.
|
||||||
|
for (const name in this.handlers) {
|
||||||
|
const handler = this.handlers[name];
|
||||||
|
const siteUrl = handler.getSiteUrl(url);
|
||||||
|
|
||||||
|
if (siteUrl) {
|
||||||
|
return siteUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a handler is enabled for a certain site and URL.
|
||||||
|
*
|
||||||
|
* @param handler Handler to check.
|
||||||
|
* @param url The URL to check.
|
||||||
|
* @param params The params of the URL
|
||||||
|
* @param courseId Course ID the URL belongs to (can be undefined).
|
||||||
|
* @param siteId The site ID to check.
|
||||||
|
* @return Promise resolved with boolean: whether the handler is enabled.
|
||||||
|
*/
|
||||||
|
protected async isHandlerEnabled(
|
||||||
|
handler: CoreContentLinksHandler,
|
||||||
|
url: string,
|
||||||
|
params: Params,
|
||||||
|
courseId: number,
|
||||||
|
siteId: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
|
||||||
|
let disabled = false;
|
||||||
|
if (handler.featureName) {
|
||||||
|
// Check if the feature is disabled.
|
||||||
|
disabled = await CoreSites.instance.isFeatureDisabled(handler.featureName, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler.isEnabled) {
|
||||||
|
// Handler doesn't implement isEnabled, assume it's enabled.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.isEnabled(siteId, url, params, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a handler.
|
||||||
|
*
|
||||||
|
* @param handler The handler to register.
|
||||||
|
* @return True if registered successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
registerHandler(handler: CoreContentLinksHandler): boolean {
|
||||||
|
if (typeof this.handlers[handler.name] !== 'undefined') {
|
||||||
|
this.logger.log(`Addon '${handler.name}' already registered`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.logger.log(`Registered addon '${handler.name}'`);
|
||||||
|
this.handlers[handler.name] = handler;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort actions by priority.
|
||||||
|
*
|
||||||
|
* @param actions Actions to sort.
|
||||||
|
* @return Sorted actions.
|
||||||
|
*/
|
||||||
|
protected sortActionsByPriority(actions: CoreContentLinksHandlerActions[]): CoreContentLinksAction[] {
|
||||||
|
let sorted: CoreContentLinksAction[] = [];
|
||||||
|
|
||||||
|
// Sort by priority.
|
||||||
|
actions = actions.sort((a, b) => (a.priority || 0) <= (b.priority || 0) ? 1 : -1);
|
||||||
|
|
||||||
|
// Fill result array.
|
||||||
|
actions.forEach((entry) => {
|
||||||
|
sorted = sorted.concat(entry.actions);
|
||||||
|
});
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { NavController } from '@ionic/angular';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreLoginHelper } from '@core/login/services/helper';
|
||||||
|
import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks.delegate';
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
|
import { CoreMainMenu } from '@core/mainmenu/services/mainmenu';
|
||||||
|
import { makeSingleton, NgZone, Translate } from '@singletons/core.singletons';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features regarding content links.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class CoreContentLinksHelperProvider {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected contentLinksDelegate: CoreContentLinksDelegate,
|
||||||
|
protected navCtrl: NavController,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a link can be handled by the app.
|
||||||
|
*
|
||||||
|
* @param url URL to handle.
|
||||||
|
* @param courseId Unused param: Course ID related to the URL.
|
||||||
|
* @param username Username to use to filter sites.
|
||||||
|
* @param checkRoot Whether to check if the URL is the root URL of a site.
|
||||||
|
* @return Promise resolved with a boolean: whether the URL can be handled.
|
||||||
|
*/
|
||||||
|
async canHandleLink(url: string, courseId?: number, username?: string, checkRoot?: boolean): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (checkRoot) {
|
||||||
|
const data = await CoreSites.instance.isStoredRootURL(url, username);
|
||||||
|
|
||||||
|
if (data.site) {
|
||||||
|
// URL is the root of the site, can handle it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = await this.getFirstValidActionFor(url, undefined, username);
|
||||||
|
|
||||||
|
return !!action;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first valid action in the list of possible actions to do for a URL.
|
||||||
|
*
|
||||||
|
* @param url URL to handle.
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @param username Username to use to filter sites.
|
||||||
|
* @param data Extra data to handle the URL.
|
||||||
|
* @return Promise resolved with the first valid action. Returns undefined if no valid action found..
|
||||||
|
*/
|
||||||
|
async getFirstValidActionFor(
|
||||||
|
url: string,
|
||||||
|
courseId?: number,
|
||||||
|
username?: string,
|
||||||
|
data?: unknown,
|
||||||
|
): Promise<CoreContentLinksAction | undefined> {
|
||||||
|
const actions = await this.contentLinksDelegate.getActionsFor(url, courseId, username, data);
|
||||||
|
if (!actions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions.find((action) => action && action.sites && action.sites.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes to a certain page in a certain site. If the site is current site it will perform a regular navigation,
|
||||||
|
* otherwise it will 'redirect' to the other site.
|
||||||
|
*
|
||||||
|
* @param pageName Name of the page to go.
|
||||||
|
* @param pageParams Params to send to the page.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @param checkMenu If true, check if the root page of a main menu tab. Only the page name will be checked.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
goInSite(
|
||||||
|
pageName: string,
|
||||||
|
pageParams: Params,
|
||||||
|
siteId?: string,
|
||||||
|
checkMenu?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||||
|
|
||||||
|
const deferred = CoreUtils.instance.promiseDefer<void>();
|
||||||
|
|
||||||
|
// Execute the code in the Angular zone, so change detection doesn't stop working.
|
||||||
|
NgZone.instance.run(async () => {
|
||||||
|
try {
|
||||||
|
if (siteId == CoreSites.instance.getCurrentSiteId()) {
|
||||||
|
if (checkMenu) {
|
||||||
|
let isInMenu = false;
|
||||||
|
// Check if the page is in the main menu.
|
||||||
|
try {
|
||||||
|
isInMenu = await CoreMainMenu.instance.isCurrentMainMenuHandler(pageName);
|
||||||
|
} catch {
|
||||||
|
isInMenu = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInMenu) {
|
||||||
|
// Just select the tab. @todo test.
|
||||||
|
CoreLoginHelper.instance.loadPageInMainMenu(pageName, pageParams);
|
||||||
|
} else {
|
||||||
|
await this.navCtrl.navigateForward(pageName, { queryParams: pageParams });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.navCtrl.navigateForward(pageName, { queryParams: pageParams });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await CoreLoginHelper.instance.redirect(pageName, pageParams, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
deferred.resolve();
|
||||||
|
} catch (error) {
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the page to choose a site.
|
||||||
|
*
|
||||||
|
* @param url URL to treat.
|
||||||
|
* @todo set correct root.
|
||||||
|
*/
|
||||||
|
async goToChooseSite(url: string): Promise<void> {
|
||||||
|
await this.navCtrl.navigateRoot('CoreContentLinksChooseSitePage @todo', { queryParams: { url } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a link.
|
||||||
|
*
|
||||||
|
* @param url URL to handle.
|
||||||
|
* @param username Username related with the URL. E.g. in 'http://myuser@m.com', url would be 'http://m.com' and
|
||||||
|
* the username 'myuser'. Don't use it if you don't want to filter by username.
|
||||||
|
* @param checkRoot Whether to check if the URL is the root URL of a site.
|
||||||
|
* @param openBrowserRoot Whether to open in browser if it's root URL and it belongs to current site.
|
||||||
|
* @return Promise resolved with a boolean: true if URL was treated, false otherwise.
|
||||||
|
*/
|
||||||
|
async handleLink(
|
||||||
|
url: string,
|
||||||
|
username?: string,
|
||||||
|
checkRoot?: boolean,
|
||||||
|
openBrowserRoot?: boolean,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (checkRoot) {
|
||||||
|
const data = await CoreSites.instance.isStoredRootURL(url, username);
|
||||||
|
|
||||||
|
if (data.site) {
|
||||||
|
// URL is the root of the site.
|
||||||
|
this.handleRootURL(data.site, openBrowserRoot);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the link should be treated by some component/addon.
|
||||||
|
const action = await this.getFirstValidActionFor(url, undefined, username);
|
||||||
|
if (!action) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!CoreSites.instance.isLoggedIn()) {
|
||||||
|
// No current site. Perform the action if only 1 site found, choose the site otherwise.
|
||||||
|
if (action.sites?.length == 1) {
|
||||||
|
action.action(action.sites[0]);
|
||||||
|
} else {
|
||||||
|
this.goToChooseSite(url);
|
||||||
|
}
|
||||||
|
} else if (action.sites?.length == 1 && action.sites[0] == CoreSites.instance.getCurrentSiteId()) {
|
||||||
|
// Current site.
|
||||||
|
action.action(action.sites[0]);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Not current site or more than one site. Ask for confirmation.
|
||||||
|
await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.contentlinks.confirmurlothersite'));
|
||||||
|
if (action.sites?.length == 1) {
|
||||||
|
action.action(action.sites[0]);
|
||||||
|
} else {
|
||||||
|
this.goToChooseSite(url);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// User canceled.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a root URL of a site.
|
||||||
|
*
|
||||||
|
* @param site Site to handle.
|
||||||
|
* @param openBrowserRoot Whether to open in browser if it's root URL and it belongs to current site.
|
||||||
|
* @param checkToken Whether to check that token is the same to verify it's current site. If false or not defined,
|
||||||
|
* only the URL will be checked.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async handleRootURL(site: CoreSite, openBrowserRoot?: boolean, checkToken?: boolean): Promise<void> {
|
||||||
|
const currentSite = CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
|
if (currentSite && currentSite.getURL() == site.getURL() && (!checkToken || currentSite.getToken() == site.getToken())) {
|
||||||
|
// Already logged in.
|
||||||
|
if (openBrowserRoot) {
|
||||||
|
return site.openInBrowserWithAutoLogin(site.getURL());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Login in the site.
|
||||||
|
return CoreLoginHelper.instance.redirect('', {}, site.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreContentLinksHelper extends makeSingleton(CoreContentLinksHelperProvider) {}
|
|
@ -26,6 +26,7 @@ import { CoreConfig } from '@services/config';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
// import { CoreCourseProvider } from '@core/course/providers/course';
|
// import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object with space usage and cache entries that can be erased.
|
* Object with space usage and cache entries that can be erased.
|
||||||
|
@ -281,12 +282,12 @@ export class CoreSettingsHelperProvider {
|
||||||
|
|
||||||
if (site.isLoggedOut()) {
|
if (site.isLoggedOut()) {
|
||||||
// Cannot sync logged out sites.
|
// Cannot sync logged out sites.
|
||||||
throw Translate.instance.instant('core.settings.cannotsyncloggedout');
|
throw new CoreError(Translate.instance.instant('core.settings.cannotsyncloggedout'));
|
||||||
} else if (hasSyncHandlers && !CoreApp.instance.isOnline()) {
|
} else if (hasSyncHandlers && !CoreApp.instance.isOnline()) {
|
||||||
// We need connection to execute sync.
|
// We need connection to execute sync.
|
||||||
throw Translate.instance.instant('core.settings.cannotsyncoffline');
|
throw new CoreError(Translate.instance.instant('core.settings.cannotsyncoffline'));
|
||||||
} else if (hasSyncHandlers && syncOnlyOnWifi && CoreApp.instance.isNetworkAccessLimited()) {
|
} else if (hasSyncHandlers && syncOnlyOnWifi && CoreApp.instance.isNetworkAccessLimited()) {
|
||||||
throw Translate.instance.instant('core.settings.cannotsyncwithoutwifi');
|
throw new CoreError(Translate.instance.instant('core.settings.cannotsyncwithoutwifi'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncPromise = Promise.all([
|
const syncPromise = Promise.all([
|
||||||
|
@ -329,7 +330,7 @@ export class CoreSettingsHelperProvider {
|
||||||
// Local mobile was added. Throw invalid session to force reconnect and create a new token.
|
// Local mobile was added. Throw invalid session to force reconnect and create a new token.
|
||||||
CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {}, site.getId());
|
CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {}, site.getId());
|
||||||
|
|
||||||
throw Translate.instance.instant('core.lostconnection');
|
throw new CoreError(Translate.instance.instant('core.lostconnection'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -231,7 +231,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
|
||||||
if (!site.canDownloadFiles() && CoreUrlUtils.instance.isPluginFileUrl(url)) {
|
if (!site.canDownloadFiles() && CoreUrlUtils.instance.isPluginFileUrl(url)) {
|
||||||
this.element.parentElement?.removeChild(this.element); // Remove element since it'll be broken.
|
this.element.parentElement?.removeChild(this.element); // Remove element since it'll be broken.
|
||||||
|
|
||||||
throw 'Site doesn\'t allow downloading files.';
|
throw new CoreError('Site doesn\'t allow downloading files.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download images, tracks and posters if size is unknown.
|
// Download images, tracks and posters if size is unknown.
|
||||||
|
|
|
@ -353,6 +353,12 @@
|
||||||
"core.considereddigitalminor": "You are too young to create an account on this site.",
|
"core.considereddigitalminor": "You are too young to create an account on this site.",
|
||||||
"core.content": "Content",
|
"core.content": "Content",
|
||||||
"core.contenteditingsynced": "The content you are editing has been synced.",
|
"core.contenteditingsynced": "The content you are editing has been synced.",
|
||||||
|
"core.contentlinks.chooseaccount": "Choose account",
|
||||||
|
"core.contentlinks.chooseaccounttoopenlink": "Choose an account to open the link with.",
|
||||||
|
"core.contentlinks.confirmurlothersite": "This link belongs to another site. Do you want to open it?",
|
||||||
|
"core.contentlinks.errornoactions": "Couldn't find an action to perform with this link.",
|
||||||
|
"core.contentlinks.errornosites": "Couldn't find any site to handle this link.",
|
||||||
|
"core.contentlinks.errorredirectothersite": "The redirect URL cannot point to a different site.",
|
||||||
"core.continue": "Continue",
|
"core.continue": "Continue",
|
||||||
"core.copiedtoclipboard": "Text copied to clipboard",
|
"core.copiedtoclipboard": "Text copied to clipboard",
|
||||||
"core.copytoclipboard": "Copy to clipboard",
|
"core.copytoclipboard": "Copy to clipboard",
|
||||||
|
|
Loading…
Reference in New Issue