// (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 { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { CoreApp } from '@services/app'; import { CoreLang } from '@services/lang'; import { CoreError } from '@classes/errors/error'; import { makeSingleton, Translate } from '@singletons/core.singletons'; import { CoreWSExternalFile } from '@services/ws'; import { Locutus } from '@singletons/locutus'; /** * Different type of errors the app can treat. */ export type CoreTextErrorObject = { message?: string; error?: string; content?: string; body?: string; debuginfo?: string; backtrace?: string; }; /* * "Utils" service with helper functions for text. */ @Injectable({ providedIn: 'root' }) export class CoreTextUtilsProvider { // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features. protected readonly DISABLED_FEATURES_COMPAT_REGEXPS: { old: RegExp; new: string }[] = [ { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' }, { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' }, { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' }, { old: /\$mmUserDelegate/g, new: 'CoreUserDelegate' }, { old: /\$mmCourseDelegate/g, new: 'CoreCourseModuleDelegate' }, { old: /_mmCourses/g, new: '_CoreCourses' }, { old: /_mmaFrontpage/g, new: '_CoreSiteHome' }, { old: /_mmaGrades/g, new: '_CoreGrades' }, { old: /_mmaCompetency/g, new: '_AddonCompetency' }, { old: /_mmaNotifications/g, new: '_AddonNotifications' }, { old: /_mmaMessages/g, new: '_AddonMessages' }, { old: /_mmaCalendar/g, new: '_AddonCalendar' }, { old: /_mmaFiles/g, new: '_AddonPrivateFiles' }, { old: /_mmaParticipants/g, new: '_CoreUserParticipants' }, { old: /_mmaCourseCompletion/g, new: '_AddonCourseCompletion' }, { old: /_mmaNotes/g, new: '_AddonNotes' }, { old: /_mmaBadges/g, new: '_AddonBadges' }, { old: /files_privatefiles/g, new: 'AddonPrivateFilesPrivateFiles' }, { old: /files_sitefiles/g, new: 'AddonPrivateFilesSiteFiles' }, { old: /files_upload/g, new: 'AddonPrivateFilesUpload' }, { old: /_mmaModAssign/g, new: '_AddonModAssign' }, { old: /_mmaModBook/g, new: '_AddonModBook' }, { old: /_mmaModChat/g, new: '_AddonModChat' }, { old: /_mmaModChoice/g, new: '_AddonModChoice' }, { old: /_mmaModData/g, new: '_AddonModData' }, { old: /_mmaModFeedback/g, new: '_AddonModFeedback' }, { old: /_mmaModFolder/g, new: '_AddonModFolder' }, { old: /_mmaModForum/g, new: '_AddonModForum' }, { old: /_mmaModGlossary/g, new: '_AddonModGlossary' }, { old: /_mmaModH5pactivity/g, new: '_AddonModH5PActivity' }, { old: /_mmaModImscp/g, new: '_AddonModImscp' }, { old: /_mmaModLabel/g, new: '_AddonModLabel' }, { old: /_mmaModLesson/g, new: '_AddonModLesson' }, { old: /_mmaModLti/g, new: '_AddonModLti' }, { old: /_mmaModPage/g, new: '_AddonModPage' }, { old: /_mmaModQuiz/g, new: '_AddonModQuiz' }, { old: /_mmaModResource/g, new: '_AddonModResource' }, { old: /_mmaModScorm/g, new: '_AddonModScorm' }, { old: /_mmaModSurvey/g, new: '_AddonModSurvey' }, { old: /_mmaModUrl/g, new: '_AddonModUrl' }, { old: /_mmaModWiki/g, new: '_AddonModWiki' }, { old: /_mmaModWorkshop/g, new: '_AddonModWorkshop' }, { old: /remoteAddOn_/g, new: 'sitePlugin_' }, ]; protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element. constructor(private sanitizer: DomSanitizer) { } /** * Add ending slash from a path or URL. * * @param text Text to treat. * @return Treated text. */ addEndingSlash(text: string): string { if (!text) { return ''; } if (text.slice(-1) != '/') { return text + '/'; } return text; } /** * Add some text to an error message. * * @param error Error message or object. * @param text Text to add. * @return Modified error. */ addTextToError(error: string | CoreTextErrorObject, text: string): string | CoreTextErrorObject { if (typeof error == 'string') { return error + text; } if (error) { if (typeof error.message == 'string') { error.message += text; } else if (typeof error.error == 'string') { error.error += text; } else if (typeof error.content == 'string') { error.content += text; } else if (typeof error.body == 'string') { error.body += text; } } return error; } /** * Given an address as a string, return a URL to open the address in maps. * * @param address The address. * @return URL to view the address. */ buildAddressURL(address: string): SafeUrl { return this.sanitizer.bypassSecurityTrustUrl((CoreApp.instance.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') + encodeURIComponent(address)); } /** * Given a list of sentences, build a message with all of them wrapped in
. * * @param messages Messages to show. * @return Message with all the messages. */ buildMessage(messages: string[]): string { let result = ''; messages.forEach((message) => { if (message) { result += `
${message}
`; } }); return result; } /** * Build a message with several paragraphs. * * @param paragraphs List of paragraphs. * @return Built message. */ buildSeveralParagraphsMessage(paragraphs: (string | CoreTextErrorObject)[]): string { // Filter invalid messages, and convert them to messages in case they're errors. const messages: string[] = []; paragraphs.forEach(paragraph => { // If it's an error, get its message. const message = this.getErrorMessageFromError(paragraph); if (paragraph && message) { messages.push(message); } }); if (messages.length < 2) { return messages[0] || ''; } let builtMessage = messages[0]; for (let i = 1; i < messages.length; i++) { builtMessage = Translate.instance.instant('core.twoparagraphs', { p1: builtMessage, p2: messages[i] }); } return builtMessage; } /** * Convert size in bytes into human readable format * * @param bytes Number of bytes to convert. * @param precision Number of digits after the decimal separator. * @return Size in human readable format. */ bytesToSize(bytes: number, precision: number = 2): string { if (typeof bytes == 'undefined' || bytes === null || bytes < 0) { return Translate.instance.instant('core.notapplicable'); } if (precision < 0) { precision = 2; } const keys = ['core.sizeb', 'core.sizekb', 'core.sizemb', 'core.sizegb', 'core.sizetb']; const units = Translate.instance.instant(keys); let pos = 0; if (bytes >= 1024) { while (bytes >= 1024) { pos++; bytes = bytes / 1024; } // Round to "precision" decimals if needed. bytes = Number(Math.round(parseFloat(bytes + 'e+' + precision)) + 'e-' + precision); } return Translate.instance.instant('core.humanreadablesize', { size: bytes, unit: units[keys[pos]] }); } /** * Clean HTML tags. * * @param text The text to be cleaned. * @param singleLine True if new lines should be removed (all the text in a single line). * @return Clean text. */ cleanTags(text: string, singleLine?: boolean): string { if (typeof text != 'string') { return text; } if (!text) { return ''; } // First, we use a regexpr. text = text.replace(/(<([^>]+)>)/ig, ''); // Then, we rely on the browser. We need to wrap the text to be sure is HTML. text = this.convertToElement(text).textContent!; // Recover or remove new lines. text = this.replaceNewLines(text, singleLine ? ' ' : '