2024-03-18 09:41:55 +01:00

335 lines
11 KiB
TypeScript

// (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 { CoreError } from '@classes/errors/error';
import { CoreWSError } from '@classes/errors/wserror';
import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
import { CoreWSExternalWarning } from '@services/ws';
import { makeSingleton } from '@singletons';
import { POLICY_PAGE_NAME, SITE_POLICY_PAGE_NAME } from '../constants';
import { CoreSite } from '@classes/sites/site';
/**
* Service that provides some common features regarding policies.
*/
@Injectable({ providedIn: 'root' })
export class CorePolicyService {
protected static readonly ROOT_CACHE_KEY = 'CorePolicy:';
/**
* Accept all mandatory site policies.
*
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved if success, rejected if failure.
*/
async acceptMandatorySitePolicies(siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
const result = await site.write<CorePolicyAgreeSitePolicyResult>('core_user_agree_site_policy', {});
if (result.status) {
return;
}
if (!result.warnings?.length) {
throw new CoreError('Cannot agree site policy');
}
// Check if there is a warning 'alreadyagreed'.
const found = result.warnings.some((warning) => warning.warningcode === 'alreadyagreed');
if (found) {
// Policy already agreed, treat it as a success.
return;
}
// Another warning, reject.
throw new CoreWSError(result.warnings[0]);
}
/**
* Get the URL to view the site policy (or all the site policies in a single page if there's more than one).
*
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved with the site policy.
*/
async getSitePoliciesURL(siteId?: string): Promise<string> {
const site = await CoreSites.getSite(siteId);
let sitePolicy: string | undefined;
try {
// Try to get the latest config, maybe the site policy was just added or has changed.
sitePolicy = await site.getConfig('sitepolicy', true);
} catch (error) {
// Cannot get config, try to get the site policy using signup settings.
const settings = await CoreLoginHelper.getEmailSignupSettings(site.getURL());
sitePolicy = settings.sitepolicy;
}
if (!sitePolicy) {
throw new CoreError('Cannot retrieve site policy');
}
return sitePolicy;
}
/**
* Get the next policies to accept.
*
* @param options Options
* @returns Next pending policies
*/
async getNextPendingPolicies(options: CoreSitesCommonWSOptions = {}): Promise<CorePolicySitePolicy[]> {
const policies = await this.getUserAcceptances(options);
const pendingPolicies: CorePolicySitePolicy[] = [];
for (const i in policies) {
const policy = policies[i];
const hasAccepted = policy.acceptance?.status === 1;
const hasDeclined = policy.acceptance?.status === 0;
if (hasAccepted || (hasDeclined && policy.optional === 1)) {
// Policy already answered, ignore.
continue;
}
if (policy.agreementstyle === CorePolicyAgreementStyle.OwnPage) {
// Policy needs to be accepted on its own page, it's the next policy to accept.
return [policy];
}
pendingPolicies.push(policy);
}
return pendingPolicies;
}
/**
* Get user acceptances.
*
* @param options Options
* @returns List of policies with their acceptances.
*/
async getUserAcceptances(options: CorePolicyGetAcceptancesOptions = {}): Promise<CorePolicySitePolicy[]> {
const site = await CoreSites.getSite(options.siteId);
const userId = options.userId || site.getUserId();
const data: CorePolicyGetUserAcceptancesWSParams = {
userid: userId,
};
const preSets = {
cacheKey: this.getUserAcceptancesCacheKey(userId),
updateFrequency: CoreSite.FREQUENCY_RARELY,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
};
const response = await site.read<CorePolicyGetUserAcceptancesWSResponse>('tool_policy_get_user_acceptances', data, preSets);
if (response.warnings?.length) {
throw new CoreWSError(response.warnings[0]);
}
return response.policies;
}
/**
* Get the cache key for the get user acceptances call.
*
* @param userId ID of the user to get the badges from.
* @returns Cache key.
*/
protected getUserAcceptancesCacheKey(userId: number): string {
return CorePolicyService.ROOT_CACHE_KEY + 'userAcceptances:' + userId;
}
/**
* Open page to accept site policies.
*
* @param siteId Site ID. If not defined, current site.
*/
goToAcceptSitePolicies(siteId?: string): void {
siteId = siteId || CoreSites.getCurrentSiteId();
if (!siteId || siteId != CoreSites.getCurrentSiteId()) {
// Only current site allowed.
return;
}
const routePath = `/${POLICY_PAGE_NAME}/${SITE_POLICY_PAGE_NAME}`;
// If current page is already site policy, stop.
if (CoreNavigator.isCurrent(routePath)) {
return;
}
CoreNavigator.navigate(routePath, { params: { siteId }, reset: true });
}
/**
* Invalidate acceptances WS call.
*
* @param options Options.
* @returns Promise resolved when data is invalidated.
*/
async invalidateAcceptances(options: {userId?: number; siteId?: string} = {}): Promise<void> {
const site = await CoreSites.getSite(options.siteId);
await site.invalidateWsCacheForKey(this.getUserAcceptancesCacheKey(options.userId || site.getUserId()));
}
/**
* Check whether a site allows getting and setting acceptances.
*
* @param siteId Site Id.
* @returns Whether the site allows getting and setting acceptances.
*/
async isManageAcceptancesAvailable(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return site.wsAvailable('tool_policy_get_user_acceptances') && site.wsAvailable('tool_policy_set_acceptances_status');
}
/**
* Set user acceptances.
*
* @param policies Policies to accept or decline. Keys are policy version id, value is whether to accept or decline.
* @param siteId Site ID. If not defined, current site.
* @returns New value for policyagreed.
*/
async setUserAcceptances(policies: Record<number, number>, siteId?: string): Promise<number> {
const site = await CoreSites.getSite(siteId);
const data: CorePolicySetAcceptancesWSParams = {
userid: site.getUserId(),
policies: Object.keys(policies).map((versionId) => ({
versionid: Number(versionId),
status: policies[versionId],
})),
};
const response = await site.write<CorePolicySetAcceptancesWSResponse>('tool_policy_set_acceptances_status', data);
if (response.warnings?.length) {
throw new CoreWSError(response.warnings[0]);
}
return response.policyagreed;
}
}
export const CorePolicy = makeSingleton(CorePolicyService);
/**
* Options for get policy acceptances.
*/
type CorePolicyGetAcceptancesOptions = CoreSitesCommonWSOptions & {
userId?: number; // User ID. If not defined, current user.
};
/**
* Result of WS core_user_agree_site_policy.
*/
type CorePolicyAgreeSitePolicyResult = {
status: boolean; // Status: true only if we set the policyagreed to 1 for the user.
warnings?: CoreWSExternalWarning[];
};
/**
* Params of tool_policy_get_user_acceptances WS.
*/
type CorePolicyGetUserAcceptancesWSParams = {
userid?: number; // The user id we want to retrieve the acceptances.
};
/**
* Data returned by tool_policy_get_user_acceptances WS.
*/
type CorePolicyGetUserAcceptancesWSResponse = {
policies: CorePolicySitePolicy[];
warnings?: CoreWSExternalWarning[];
};
/**
* Policy data returned by tool_policy_get_user_acceptances WS.
*/
export type CorePolicySitePolicy = {
policyid: number; // The policy id.
versionid: number; // The policy version id.
agreementstyle: number; // The policy agreement style. 0: consent page, 1: own page.
optional: number; // Whether the policy is optional. 0: compulsory, 1: optional.
revision: string; // The policy revision.
status: number; // The policy status. 0: draft, 1: active, 2: archived.
name: string; // The policy name.
summary?: string; // The policy summary.
summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN, or 4 = MARKDOWN).
content?: string; // The policy content.
contentformat: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN, or 4 = MARKDOWN).
acceptance?: CorePolicySitePolicyAcceptance; // Acceptance status for the given user.
canaccept: boolean; // Whether the policy can be accepted.
candecline: boolean; // Whether the policy can be declined.
canrevoke: boolean; // Whether the policy can be revoked.
};
/**
* Policy acceptance data returned by tool_policy_get_user_acceptances WS.
*/
export type CorePolicySitePolicyAcceptance = {
status: number; // The acceptance status. 0: declined, 1: accepted.
lang: string; // The policy lang.
timemodified: number; // The time the acceptance was set.
usermodified: number; // The user who accepted.
note?: string; // The policy note/remarks.
modfullname?: string; // The fullname who accepted on behalf.
};
/**
* Params of tool_policy_set_acceptances_status WS.
*/
type CorePolicySetAcceptancesWSParams = {
policies: {
versionid: number; // The policy version id.
status: number; // The policy acceptance status. 0: decline, 1: accept.
}[]; // Policies acceptances for the given user.
userid?: number; // The user id we want to set the acceptances. Default is the current user.
};
/**
* Data returned by tool_policy_set_acceptances_status WS.
*/
type CorePolicySetAcceptancesWSResponse = {
policyagreed: number; // Whether the user has provided acceptance to all current site policies. 1 if yes, 0 if not.
warnings?: CoreWSExternalWarning[];
};
/**
* Agreement style.
*/
export enum CorePolicyAgreementStyle {
ConsentPage = 0, // Policy to be accepted together with others on the consent page.
OwnPage = 1, // Policy to be accepted on its own page before reaching the consent page.
}
/**
* Status of a policy.
*/
export enum CorePolicyStatus {
Draft = 0,
Active = 1,
Archived = 2,
}