// (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 { CoreSites } from '@services/sites'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreWSExternalWarning } from '@services/ws'; import { CoreSite } from '@classes/site'; import { makeSingleton } from '@singletons/core.singletons'; const ROOT_CACHE_KEY = 'mmaFiles:'; /** * Service to handle my files and site files. */ @Injectable({ providedIn: 'root', }) export class AddonPrivateFilesProvider { // Keep old names for backwards compatibility. static readonly PRIVATE_FILES_COMPONENT = 'mmaFilesMy'; static readonly SITE_FILES_COMPONENT = 'mmaFilesSite'; /** * Check if core_user_get_private_files_info WS call is available. * * @return Whether the WS is available, false otherwise. */ canGetPrivateFilesInfo(): boolean { return CoreSites.instance.wsAvailableInCurrentSite('core_user_get_private_files_info'); } /** * Check if user can view his private files. * * @return Whether the user can view his private files. */ canViewPrivateFiles(): boolean { const currentSite = CoreSites.instance.getCurrentSite(); if (!currentSite) { return false; } return currentSite.canAccessMyFiles() && !this.isPrivateFilesDisabledInSite(); } /** * Check if user can view site files. * * @return Whether the user can view site files. */ canViewSiteFiles(): boolean { return !this.isSiteFilesDisabledInSite(); } /** * Check if user can upload private files. * * @return Whether the user can upload private files. */ canUploadFiles(): boolean { const currentSite = CoreSites.instance.getCurrentSite(); if (!currentSite) { return false; } return currentSite.canAccessMyFiles() && currentSite.canUploadFiles() && !this.isUploadDisabledInSite(); } /** * Get the list of files. * * @param params A list of parameters accepted by the Web service. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the files. */ async getFiles(params: AddonPrivateFilesGetFilesWSParams, siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); const preSets = { cacheKey: this.getFilesListCacheKey(params), updateFrequency: CoreSite.FREQUENCY_SOMETIMES, }; const result: AddonPrivateFilesGetFilesWSResult = await site.read('core_files_get_files', params, preSets); if (!result.files) { return []; } return result.files.map((entry) => { entry.fileurl = entry.url; if (entry.isdir) { entry.imgPath = CoreMimetypeUtils.instance.getFolderIcon(); } else { entry.imgPath = CoreMimetypeUtils.instance.getFileIcon(entry.filename); } return entry; }); } /** * Get cache key for file list WS calls. * * @param params Params of the WS. * @return Cache key. */ protected getFilesListCacheKey(params: AddonPrivateFilesGetFilesWSParams): string { const root = !params.component ? 'site' : 'my'; return ROOT_CACHE_KEY + 'list:' + root + ':' + params.contextid + ':' + params.filepath; } /** * Get the private files of the current user. * * @return Promise resolved with the files. */ getPrivateFiles(): Promise { return this.getFiles(this.getPrivateFilesRootParams()); } /** * Get params to get root private files directory. * * @return Params. */ protected getPrivateFilesRootParams(): AddonPrivateFilesGetFilesWSParams { return { contextid: -1, component: 'user', filearea: 'private', contextlevel: 'user', instanceid: CoreSites.instance.getCurrentSite()?.getUserId(), itemid: 0, filepath: '', filename: '', }; } /** * Get private files info. * * @param userId User ID. If not defined, current user in the site. * @param siteId Site ID. If not defined, use current site. * @return Promise resolved with the info. */ async getPrivateFilesInfo(userId?: number, siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); userId = userId || site.getUserId(); const params: AddonPrivateFilesGetUserInfoWSParams = { userid: userId, }; const preSets = { cacheKey: this.getPrivateFilesInfoCacheKey(userId), updateFrequency: CoreSite.FREQUENCY_SOMETIMES, }; return site.read('core_user_get_private_files_info', params, preSets); } /** * Get the cache key for private files info WS calls. * * @param userId User ID. * @return Cache key. */ protected getPrivateFilesInfoCacheKey(userId: number): string { return this.getPrivateFilesInfoCommonCacheKey() + ':' + userId; } /** * Get the common part of the cache keys for private files info WS calls. * * @return Cache key. */ protected getPrivateFilesInfoCommonCacheKey(): string { return ROOT_CACHE_KEY + 'privateInfo'; } /** * Get the site files. * * @return Promise resolved with the files. */ getSiteFiles(): Promise { return this.getFiles(this.getSiteFilesRootParams()); } /** * Get params to get root site files directory. * * @return Params. */ protected getSiteFilesRootParams(): AddonPrivateFilesGetFilesWSParams { return { contextid: 0, component: '', filearea: '', itemid: 0, filepath: '', filename: '', }; } /** * Invalidates list of files in a certain directory. * * @param root Root of the directory ('my' for private files, 'site' for site files). * @param params Params to the directory. * @param siteId Site ID. If not defined, use current site. * @return Promise resolved when the data is invalidated. */ async invalidateDirectory(root?: 'my' | 'site', params?: AddonPrivateFilesGetFilesWSParams, siteId?: string): Promise { if (!root) { return; } if (!params) { if (root === 'site') { params = this.getSiteFilesRootParams(); } else { params = this.getPrivateFilesRootParams(); } } const site = await CoreSites.instance.getSite(siteId); await site.invalidateWsCacheForKey(this.getFilesListCacheKey(params)); } /** * Invalidates private files info for all users. * * @param siteId Site ID. If not defined, use current site. * @return Promise resolved when the data is invalidated. */ async invalidatePrivateFilesInfo(siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); await site.invalidateWsCacheForKeyStartingWith(this.getPrivateFilesInfoCommonCacheKey()); } /** * Invalidates private files info for a certain user. * * @param userId User ID. If not defined, current user in the site. * @param siteId Site ID. If not defined, use current site. * @return Promise resolved when the data is invalidated. */ async invalidatePrivateFilesInfoForUser(userId?: number, siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); await site.invalidateWsCacheForKey(this.getPrivateFilesInfoCacheKey(userId || site.getUserId())); } /** * Check if Files is disabled in a certain site. * * @param siteId Site Id. If not defined, use current site. * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ async isDisabled(siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); return this.isDisabledInSite(site); } /** * Check if Files is disabled in a certain site. * * @param site Site. If not defined, use current site. * @return Whether it's disabled. */ isDisabledInSite(site: CoreSite): boolean { site = site || CoreSites.instance.getCurrentSite(); return site.isFeatureDisabled('CoreMainMenuDelegate_AddonPrivateFiles'); } /** * Return whether or not the plugin is enabled. * * @return True if enabled, false otherwise. */ isPluginEnabled(): boolean { return this.canViewPrivateFiles() || this.canViewSiteFiles() || this.canUploadFiles(); } /** * Check if private files is disabled in a certain site. * * @param siteId Site Id. If not defined, use current site. * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ async isPrivateFilesDisabled(siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); return this.isPrivateFilesDisabledInSite(site); } /** * Check if private files is disabled in a certain site. * * @param site Site. If not defined, use current site. * @return Whether it's disabled. */ isPrivateFilesDisabledInSite(site?: CoreSite): boolean { site = site || CoreSites.instance.getCurrentSite(); return !!site && site.isFeatureDisabled('AddonPrivateFilesPrivateFiles'); } /** * Check if site files is disabled in a certain site. * * @param siteId Site Id. If not defined, use current site. * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ async isSiteFilesDisabled(siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); return this.isSiteFilesDisabledInSite(site); } /** * Check if site files is disabled in a certain site. * * @param site Site. If not defined, use current site. * @return Whether it's disabled. */ isSiteFilesDisabledInSite(site?: CoreSite): boolean { site = site || CoreSites.instance.getCurrentSite(); return !!site && site.isFeatureDisabled('AddonPrivateFilesSiteFiles'); } /** * Check if upload files is disabled in a certain site. * * @param siteId Site Id. If not defined, use current site. * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ async isUploadDisabled(siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); return this.isUploadDisabledInSite(site); } /** * Check if upload files is disabled in a certain site. * * @param site Site. If not defined, use current site. * @return Whether it's disabled. */ isUploadDisabledInSite(site?: CoreSite): boolean { site = site || CoreSites.instance.getCurrentSite(); return !!site && site.isFeatureDisabled('AddonPrivateFilesUpload'); } /** * Move a file from draft area to private files. * * @param draftId The draft area ID of the file. * @param siteid ID of the site. If not defined, use current site. * @return Promise resolved in success, rejected otherwise. */ async moveFromDraftToPrivate(draftId: number, siteId?: string): Promise { const params: AddonPrivateFilesAddUserPrivateFilesWSParams = { draftid: draftId, }; const preSets = { responseExpected: false, }; const site = await CoreSites.instance.getSite(siteId); return site.write('core_user_add_user_private_files', params, preSets); } /** * Check the Moodle version in order to check if upload files is working. * * @param siteId Site ID. If not defined, use current site. * @return Promise resolved with true if WS is working, false otherwise. */ async versionCanUploadFiles(siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); // Upload private files doesn't work for Moodle 3.1.0 due to a bug. return site.isVersionGreaterEqualThan('3.1.1'); } } export class AddonPrivateFiles extends makeSingleton(AddonPrivateFilesProvider) {} /** * File data returned by core_files_get_files. */ export type AddonPrivateFilesFile = { contextid: number; component: string; filearea: string; itemid: number; filepath: string; filename: string; isdir: boolean; url: string; timemodified: number; timecreated?: number; // Time created. filesize?: number; // File size. author?: string; // File owner. license?: string; // File license. } & AddonPrivateFilesFileCalculatedData; /** * Calculated data for AddonPrivateFilesFile. */ export type AddonPrivateFilesFileCalculatedData = { fileurl: string; // File URL, using same name as CoreWSExternalFile. imgPath?: string; // Path to file icon's image. }; /** * Params of WS core_files_get_files. */ export type AddonPrivateFilesGetFilesWSParams = { contextid: number; // Context id Set to -1 to use contextlevel and instanceid. component: string; // Component. filearea: string; // File area. itemid: number; // Associated id. filepath: string; // File path. filename: string; // File name. modified?: number; // Timestamp to return files changed after this time. contextlevel?: string; // The context level for the file location. instanceid?: number; // The instance id for where the file is located. }; /** * Result of WS core_files_get_files. */ export type AddonPrivateFilesGetFilesWSResult = { parents: { contextid: number; component: string; filearea: string; itemid: number; filepath: string; filename: string; }[]; files: AddonPrivateFilesFile[]; }; /** * Params of core_user_get_private_files_info WS. */ export type AddonPrivateFilesGetUserInfoWSParams = { userid?: number; // Id of the user, default to current user. }; /** * Data returned by core_user_get_private_files_info WS. */ export type AddonPrivateFilesGetUserInfoWSResult = { filecount: number; // Number of files in the area. foldercount: number; // Number of folders in the area. filesize: number; // Total size of the files in the area. filesizewithoutreferences: number; // Total size of the area excluding file references. warnings?: CoreWSExternalWarning[]; }; /** * Params of core_user_add_user_private_files WS. */ export type AddonPrivateFilesAddUserPrivateFilesWSParams = { draftid: number; // Draft area id. };