1325 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1325 lines
		
	
	
		
			46 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 { CoreTextUtilsProvider } from '@providers/utils/text';
 | 
						|
import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
						|
import { CoreH5PProvider, CoreH5PLibraryData, CoreH5PLibraryAddonData, CoreH5PContentDepsTreeDependency } from '../providers/h5p';
 | 
						|
import { CoreH5PUtilsProvider } from '../providers/utils';
 | 
						|
import { TranslateService } from '@ngx-translate/core';
 | 
						|
 | 
						|
/**
 | 
						|
 * Equivalent to Moodle's H5PContentValidator, but without some of the validations.
 | 
						|
 * It's also used to build the dependency list.
 | 
						|
 */
 | 
						|
export class CoreH5PContentValidator {
 | 
						|
    protected static ALLOWED_STYLEABLE_TAGS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'td'];
 | 
						|
 | 
						|
    protected typeMap = {
 | 
						|
        text:  'validateText',
 | 
						|
        number:  'validateNumber',
 | 
						|
        boolean:  'validateBoolean',
 | 
						|
        list:  'validateList',
 | 
						|
        group:  'validateGroup',
 | 
						|
        file:  'validateFile',
 | 
						|
        image:  'validateImage',
 | 
						|
        video:  'validateVideo',
 | 
						|
        audio:  'validateAudio',
 | 
						|
        select:  'validateSelect',
 | 
						|
        library:  'validateLibrary',
 | 
						|
    };
 | 
						|
 | 
						|
    protected nextWeight = 1;
 | 
						|
    protected libraries: {[libString: string]: CoreH5PLibraryData} = {};
 | 
						|
    protected dependencies: {[key: string]: CoreH5PContentDepsTreeDependency} = {};
 | 
						|
    protected relativePathRegExp = /^((\.\.\/){1,2})(.*content\/)?(\d+|editor)\/(.+)$/;
 | 
						|
    protected allowedHtml: {[tag: string]: any} = {};
 | 
						|
    protected allowedStyles: RegExp[];
 | 
						|
    protected metadataSemantics: any[];
 | 
						|
    protected copyrightSemantics: any;
 | 
						|
 | 
						|
    constructor(protected h5pProvider: CoreH5PProvider,
 | 
						|
            protected h5pUtils: CoreH5PUtilsProvider,
 | 
						|
            protected textUtils: CoreTextUtilsProvider,
 | 
						|
            protected utils: CoreUtilsProvider,
 | 
						|
            protected translate: TranslateService,
 | 
						|
            protected siteId: string) { }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Add Addon library.
 | 
						|
     *
 | 
						|
     * @param library The addon library to add.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    addon(library: CoreH5PLibraryAddonData): Promise<void> {
 | 
						|
        const depKey = 'preloaded-' + library.machineName;
 | 
						|
 | 
						|
        this.dependencies[depKey] = {
 | 
						|
            library: library,
 | 
						|
            type: 'preloaded'
 | 
						|
        };
 | 
						|
 | 
						|
        return this.h5pProvider.findLibraryDependencies(this.dependencies, library, this.nextWeight).then((weight) => {
 | 
						|
            this.nextWeight = weight;
 | 
						|
            this.dependencies[depKey].weight = this.nextWeight++;
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get the flat dependency tree.
 | 
						|
     *
 | 
						|
     * @return array
 | 
						|
     */
 | 
						|
    getDependencies(): {[key: string]: CoreH5PContentDepsTreeDependency} {
 | 
						|
        return this.dependencies;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate metadata
 | 
						|
     *
 | 
						|
     * @param metadata Metadata.
 | 
						|
     * @return Promise resolved with metadata validated & filtered.
 | 
						|
     */
 | 
						|
    validateMetadata(metadata: any): Promise<any> {
 | 
						|
        const semantics = this.getMetadataSemantics();
 | 
						|
        const group = this.utils.clone(metadata || {});
 | 
						|
 | 
						|
        // Stop complaining about "invalid selected option in select" for old content without license chosen.
 | 
						|
        if (typeof group.license == 'undefined') {
 | 
						|
            group.license = 'U';
 | 
						|
        }
 | 
						|
 | 
						|
        return this.validateGroup(group, {type: 'group', fields: semantics}, false);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given text value against text semantics.
 | 
						|
     *
 | 
						|
     * @param text Text to validate.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Validated text.
 | 
						|
     */
 | 
						|
    validateText(text: string, semantics: any): string {
 | 
						|
        if (typeof text != 'string') {
 | 
						|
            text = '';
 | 
						|
        }
 | 
						|
 | 
						|
        if (semantics.tags) {
 | 
						|
            // Not testing for empty array allows us to use the 4 defaults without specifying them in semantics.
 | 
						|
            let tags = ['div', 'span', 'p', 'br'].concat(semantics.tags);
 | 
						|
 | 
						|
            // Add related tags for table etc.
 | 
						|
            if (tags.indexOf('table') != -1) {
 | 
						|
                tags = tags.concat(['tr', 'td', 'th', 'colgroup', 'thead', 'tbody', 'tfoot']);
 | 
						|
            }
 | 
						|
            if (tags.indexOf('b') != -1) {
 | 
						|
                tags.push('strong');
 | 
						|
            }
 | 
						|
            if (tags.indexOf('i') != -1) {
 | 
						|
                tags.push('em');
 | 
						|
            }
 | 
						|
            if (tags.indexOf('ul') != -1 || tags.indexOf('ol') != -1) {
 | 
						|
                tags.push('li');
 | 
						|
            }
 | 
						|
            if (tags.indexOf('del') != -1 || tags.indexOf('strike') != -1) {
 | 
						|
                tags.push('s');
 | 
						|
            }
 | 
						|
 | 
						|
            tags = this.utils.uniqueArray(tags);
 | 
						|
 | 
						|
            // Determine allowed style tags
 | 
						|
            const stylePatterns: RegExp[] = [];
 | 
						|
            // All styles must be start to end patterns (^...$)
 | 
						|
            if (semantics.font) {
 | 
						|
                if (semantics.font.size) {
 | 
						|
                    stylePatterns.push(/^font-size: *[0-9.]+(em|px|%) *;?$/i);
 | 
						|
                }
 | 
						|
                if (semantics.font.family) {
 | 
						|
                    stylePatterns.push(/^font-family: *[-a-z0-9," ]+;?$/i);
 | 
						|
                }
 | 
						|
                if (semantics.font.color) {
 | 
						|
                    stylePatterns.push(/^color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i);
 | 
						|
                }
 | 
						|
                if (semantics.font.background) {
 | 
						|
                    stylePatterns.push(/^background-color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i);
 | 
						|
                }
 | 
						|
                if (semantics.font.spacing) {
 | 
						|
                    stylePatterns.push(/^letter-spacing: *[0-9.]+(em|px|%) *;?$/i);
 | 
						|
                }
 | 
						|
                if (semantics.font.height) {
 | 
						|
                    stylePatterns.push(/^line-height: *[0-9.]+(em|px|%|) *;?$/i);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Alignment is allowed for all wysiwyg texts
 | 
						|
            stylePatterns.push(/^text-align: *(center|left|right);?$/i);
 | 
						|
 | 
						|
            // Strip invalid HTML tags.
 | 
						|
            text = this.filterXss(text, tags, stylePatterns);
 | 
						|
        } else {
 | 
						|
            // Filter text to plain text.
 | 
						|
            text = this.textUtils.escapeHTML(text);
 | 
						|
        }
 | 
						|
 | 
						|
        // Check if string is within allowed length.
 | 
						|
        if (typeof semantics.maxLength != 'undefined') {
 | 
						|
            text = text.substr(0, semantics.maxLength);
 | 
						|
        }
 | 
						|
 | 
						|
        return text;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validates content files
 | 
						|
     *
 | 
						|
     * @param contentPath The path containing content files to validate.
 | 
						|
     * @param isLibrary Whether it's a library.
 | 
						|
     * @return True if all files are valid.
 | 
						|
     */
 | 
						|
    validateContentFiles(contentPath: string, isLibrary: boolean = false): boolean {
 | 
						|
        // Nothing to do, already checked by Moodle.
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given value against number semantics.
 | 
						|
     *
 | 
						|
     * @param num Number to validate.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Validated number.
 | 
						|
     */
 | 
						|
    validateNumber(num: any, semantics: any): number {
 | 
						|
        // Validate that num is indeed a number.
 | 
						|
        num = Number(num);
 | 
						|
        if (isNaN(num)) {
 | 
						|
            num = 0;
 | 
						|
        }
 | 
						|
        // Check if number is within valid bounds. Move within bounds if not.
 | 
						|
        if (typeof semantics.min != 'undefined' && num < semantics.min) {
 | 
						|
            num = semantics.min;
 | 
						|
        }
 | 
						|
        if (typeof semantics.max != 'undefined' && num > semantics.max) {
 | 
						|
            num = semantics.max;
 | 
						|
        }
 | 
						|
        // Check if number is within allowed bounds even if step value is set.
 | 
						|
        if (typeof semantics.step != 'undefined') {
 | 
						|
            const testNumber = num - (typeof semantics.min != 'undefined' ? semantics.min : 0),
 | 
						|
                rest = testNumber % semantics.step;
 | 
						|
            if (rest !== 0) {
 | 
						|
                num -= rest;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        // Check if number has proper number of decimals.
 | 
						|
        if (typeof semantics.decimals != 'undefined') {
 | 
						|
            num = num.toFixed(semantics.decimals);
 | 
						|
        }
 | 
						|
 | 
						|
        return num;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given value against boolean semantics.
 | 
						|
     *
 | 
						|
     * @param bool Boolean to check.
 | 
						|
     * @return Validated bool.
 | 
						|
     */
 | 
						|
    validateBoolean(bool: boolean): boolean {
 | 
						|
        return !!bool;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate select values.
 | 
						|
     *
 | 
						|
     * @param select Values to validate.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Validated select.
 | 
						|
     */
 | 
						|
    validateSelect(select: any, semantics: any): any {
 | 
						|
        const optional = semantics.optional,
 | 
						|
            options = {};
 | 
						|
        let strict = false;
 | 
						|
 | 
						|
        if (semantics.options && semantics.options.length) {
 | 
						|
            // We have a strict set of options to choose from.
 | 
						|
            strict = true;
 | 
						|
 | 
						|
            semantics.options.forEach((option) => {
 | 
						|
                // Support optgroup - just flatten options into one.
 | 
						|
                if (option.type == 'optgroup') {
 | 
						|
                    option.options.forEach((subOption) => {
 | 
						|
                        options[subOption.value] = true;
 | 
						|
                    });
 | 
						|
                } else if (option.value) {
 | 
						|
                    options[option.value] = true;
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        if (semantics.multiple) {
 | 
						|
            // Multi-choice generates array of values. Test each one against valid options, if we are strict.
 | 
						|
            for (const key in select) {
 | 
						|
                const value = select[key];
 | 
						|
 | 
						|
                if (strict && !optional && !options[value]) {
 | 
						|
                    delete select[key];
 | 
						|
                } else {
 | 
						|
                    select[key] = this.textUtils.escapeHTML(value);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            // Single mode. If we get an array in here, we chop off the first element and use that instead.
 | 
						|
            if (Array.isArray(select)) {
 | 
						|
                select = select[0];
 | 
						|
            }
 | 
						|
 | 
						|
            if (strict && !optional && !options[select]) {
 | 
						|
                select = semantics.options[0].value;
 | 
						|
            }
 | 
						|
            select = this.textUtils.escapeHTML(select);
 | 
						|
        }
 | 
						|
 | 
						|
        return select;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given list value against list semantics.
 | 
						|
     * Will recurse into validating each item in the list according to the type.
 | 
						|
     *
 | 
						|
     * @param list List to validate.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Validated list.
 | 
						|
     */
 | 
						|
    validateList(list: any, semantics: any): Promise<any[]> {
 | 
						|
        const field = semantics.field,
 | 
						|
            fn = this[this.typeMap[field.type]].bind(this);
 | 
						|
        let promise = Promise.resolve(), // Use a chain of promises so the order is kept.
 | 
						|
            keys = Object.keys(list);
 | 
						|
 | 
						|
        // Check that list is not longer than allowed length.
 | 
						|
        if (typeof semantics.max != 'undefined') {
 | 
						|
            keys = keys.slice(0, semantics.max);
 | 
						|
        }
 | 
						|
 | 
						|
        // Validate each element in list.
 | 
						|
        keys.forEach((key) => {
 | 
						|
            if (isNaN(parseInt(key, 10))) {
 | 
						|
                // It's an object and the key isn't an integer. Delete it.
 | 
						|
                delete list[key];
 | 
						|
            } else {
 | 
						|
                promise = promise.then(() => {
 | 
						|
                    return Promise.resolve(fn(list[key], field)).then((val) => {
 | 
						|
                        if (val === null) {
 | 
						|
                            list.splice(key, 1);
 | 
						|
                        } else {
 | 
						|
                            list[key] = val;
 | 
						|
                        }
 | 
						|
                    });
 | 
						|
                });
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        return promise.then(() => {
 | 
						|
 | 
						|
            if (!Array.isArray(list)) {
 | 
						|
                list = this.utils.objectToArray(list);
 | 
						|
            }
 | 
						|
 | 
						|
            if (!list.length) {
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            return list;
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate a file like object, such as video, image, audio and file.
 | 
						|
     *
 | 
						|
     * @param file File to validate.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @param typeValidKeys List of valid keys.
 | 
						|
     * @return Promise resolved with the validated file.
 | 
						|
     */
 | 
						|
    protected validateFilelike(file: any, semantics: any, typeValidKeys: string[] = []): Promise<any> {
 | 
						|
        // Do not allow to use files from other content folders.
 | 
						|
        const matches = file.path.match(this.relativePathRegExp);
 | 
						|
        if (matches && matches.length) {
 | 
						|
            file.path = matches[5];
 | 
						|
        }
 | 
						|
 | 
						|
        // Remove temporary files suffix.
 | 
						|
        if (file.path.substr(-4, 4) === '#tmp') {
 | 
						|
            file.path = file.path.substr(0, file.path.length - 4);
 | 
						|
        }
 | 
						|
 | 
						|
        // Make sure path and mime does not have any special chars
 | 
						|
        file.path = this.textUtils.escapeHTML(file.path);
 | 
						|
        if (file.mime) {
 | 
						|
            file.mime = this.textUtils.escapeHTML(file.mime);
 | 
						|
        }
 | 
						|
 | 
						|
        // Remove attributes that should not exist, they may contain JSON escape code.
 | 
						|
        let validKeys = ['path', 'mime', 'copyright'].concat(typeValidKeys);
 | 
						|
        if (semantics.extraAttributes) {
 | 
						|
            validKeys = validKeys.concat(semantics.extraAttributes);
 | 
						|
        }
 | 
						|
        validKeys = this.utils.uniqueArray(validKeys);
 | 
						|
 | 
						|
        this.filterParams(file, validKeys);
 | 
						|
 | 
						|
        if (typeof file.width != 'undefined') {
 | 
						|
            file.width = parseInt(file.width, 10);
 | 
						|
        }
 | 
						|
 | 
						|
        if (typeof file.height != 'undefined') {
 | 
						|
            file.height = parseInt(file.height, 10);
 | 
						|
        }
 | 
						|
 | 
						|
        if (file.codecs) {
 | 
						|
            file.codecs = this.textUtils.escapeHTML(file.codecs);
 | 
						|
        }
 | 
						|
 | 
						|
        if (typeof file.bitrate != 'undefined') {
 | 
						|
            file.bitrate = parseInt(file.bitrate, 10);
 | 
						|
        }
 | 
						|
 | 
						|
        if (typeof file.quality != 'undefined') {
 | 
						|
            if (file.quality === null || typeof file.quality.level == 'undefined' || typeof file.quality.label == 'undefined') {
 | 
						|
                delete file.quality;
 | 
						|
            } else {
 | 
						|
                this.filterParams(file.quality, ['level', 'label']);
 | 
						|
                file.quality.level = parseInt(file.quality.level);
 | 
						|
                file.quality.label = this.textUtils.escapeHTML(file.quality.label);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (typeof file.copyright != 'undefined') {
 | 
						|
            return this.validateGroup(file.copyright, this.getCopyrightSemantics()).then(() => {
 | 
						|
                return file;
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        return Promise.resolve(file);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given file data.
 | 
						|
     *
 | 
						|
     * @param file File.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Promise resolved with the validated file.
 | 
						|
     */
 | 
						|
    validateFile(file: any, semantics: any): Promise<any> {
 | 
						|
        return this.validateFilelike(file, semantics);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given image data.
 | 
						|
     *
 | 
						|
     * @param image Image.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Promise resolved with the validated file.
 | 
						|
     */
 | 
						|
    validateImage(image: any, semantics: any): Promise<any> {
 | 
						|
        return this.validateFilelike(image, semantics, ['width', 'height', 'originalImage']);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given video data.
 | 
						|
     *
 | 
						|
     * @param video Video.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Promise resolved with the validated file.
 | 
						|
     */
 | 
						|
    validateVideo(video: any, semantics: any): Promise<any> {
 | 
						|
        let promise = Promise.resolve(); // Use a chain of promises so the order is kept.
 | 
						|
 | 
						|
        for (const key in video) {
 | 
						|
            promise = promise.then(() => {
 | 
						|
                return this.validateFilelike(video[key], semantics, ['width', 'height', 'codecs', 'quality', 'bitrate']);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        return promise.then(() => {
 | 
						|
            return video;
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given audio data.
 | 
						|
     *
 | 
						|
     * @param audio Audio.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Promise resolved with the validated file.
 | 
						|
     */
 | 
						|
    validateAudio(audio: any, semantics: any): Promise<any> {
 | 
						|
        let promise = Promise.resolve(); // Use a chain of promises so the order is kept.
 | 
						|
 | 
						|
        for (const key in audio) {
 | 
						|
            promise = promise.then(() => {
 | 
						|
                return this.validateFilelike(audio[key], semantics);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        return promise.then(() => {
 | 
						|
            return audio;
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given group value against group semantics.
 | 
						|
     * Will recurse into validating each group member.
 | 
						|
     *
 | 
						|
     * @param group Group.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @param flatten Whether to flatten.
 | 
						|
     */
 | 
						|
    validateGroup(group: any, semantics: any, flatten: boolean = true): Promise<any> {
 | 
						|
        // Groups with just one field are compressed in the editor to only output he child content.
 | 
						|
 | 
						|
        const isSubContent = semantics.isSubContent === true;
 | 
						|
 | 
						|
        if (semantics.fields.length == 1 && flatten && !isSubContent) {
 | 
						|
            const field = semantics.fields[0],
 | 
						|
                fn = this[this.typeMap[field.type]].bind(this);
 | 
						|
 | 
						|
            return Promise.resolve(fn(group, field));
 | 
						|
        } else {
 | 
						|
            let promise = Promise.resolve(); // Use a chain of promises so the order is kept.
 | 
						|
 | 
						|
            for (const key in group) {
 | 
						|
                // If subContentId is set, keep value
 | 
						|
                if (isSubContent && key == 'subContentId') {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
 | 
						|
                // Find semantics for name=key.
 | 
						|
                let found = false,
 | 
						|
                    fn = null,
 | 
						|
                    field = null;
 | 
						|
 | 
						|
                for (let i = 0; i < semantics.fields.length; i++) {
 | 
						|
                    field = semantics.fields[i];
 | 
						|
 | 
						|
                    if (field.name == key) {
 | 
						|
                        if (semantics.optional) {
 | 
						|
                            field.optional = true;
 | 
						|
                        }
 | 
						|
                        fn = this[this.typeMap[field.type]].bind(this);
 | 
						|
                        found = true;
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (found && fn) {
 | 
						|
                    promise = promise.then(() => {
 | 
						|
                        return Promise.resolve(fn(group[key], field)).then((val) => {
 | 
						|
                            group[key] = val;
 | 
						|
                            if (val === null) {
 | 
						|
                                delete group[key];
 | 
						|
                            }
 | 
						|
                        });
 | 
						|
                    });
 | 
						|
                } else {
 | 
						|
                    // Something exists in content that does not have a corresponding semantics field. Remove it.
 | 
						|
                    delete group.key;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            return promise.then(() => {
 | 
						|
                return group;
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validate given library value against library semantics.
 | 
						|
     * Check if provided library is within allowed options.
 | 
						|
     * Will recurse into validating the library's semantics too.
 | 
						|
     *
 | 
						|
     * @param value Value.
 | 
						|
     * @param semantics Semantics.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    validateLibrary(value: any, semantics: any): Promise<any> {
 | 
						|
        if (!value.library) {
 | 
						|
            return Promise.resolve();
 | 
						|
        }
 | 
						|
 | 
						|
        let promise;
 | 
						|
 | 
						|
        if (!this.libraries[value.library]) {
 | 
						|
            const libSpec = this.h5pUtils.libraryFromString(value.library);
 | 
						|
 | 
						|
            promise = this.h5pProvider.loadLibrary(libSpec.machineName, libSpec.majorVersion, libSpec.minorVersion, this.siteId)
 | 
						|
                    .then((library) => {
 | 
						|
                this.libraries[value.library] = library;
 | 
						|
 | 
						|
                return library;
 | 
						|
            });
 | 
						|
        } else {
 | 
						|
            promise = Promise.resolve(this.libraries[value.library]);
 | 
						|
        }
 | 
						|
 | 
						|
        return promise.then((library) => {
 | 
						|
            // Validate parameters.
 | 
						|
            return this.validateGroup(value.params, {type: 'group', fields: library.semantics}, false).then((validated) => {
 | 
						|
 | 
						|
                value.params = validated;
 | 
						|
 | 
						|
                // Validate subcontent's metadata
 | 
						|
                if (value.metadata) {
 | 
						|
                    return this.validateMetadata(value.metadata).then((res) => {
 | 
						|
                        value.metadata = res;
 | 
						|
                    });
 | 
						|
                }
 | 
						|
            }).then(() => {
 | 
						|
 | 
						|
                let validKeys = ['library', 'params', 'subContentId', 'metadata'];
 | 
						|
                if (semantics.extraAttributes) {
 | 
						|
                    validKeys = this.utils.uniqueArray(validKeys.concat(semantics.extraAttributes));
 | 
						|
                }
 | 
						|
 | 
						|
                this.filterParams(value, validKeys);
 | 
						|
 | 
						|
                if (value.subContentId &&
 | 
						|
                        !value.subContentId.match(/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/)) {
 | 
						|
                    delete value.subContentId;
 | 
						|
                }
 | 
						|
 | 
						|
                // Find all dependencies for this library.
 | 
						|
                const depKey = 'preloaded-' + library.machineName;
 | 
						|
                if (!this.dependencies[depKey]) {
 | 
						|
                    this.dependencies[depKey] = {
 | 
						|
                        library: library,
 | 
						|
                        type: 'preloaded'
 | 
						|
                    };
 | 
						|
 | 
						|
                    return this.h5pProvider.findLibraryDependencies(this.dependencies, library, this.nextWeight).then((weight) => {
 | 
						|
                        this.nextWeight = weight;
 | 
						|
                        this.dependencies[depKey].weight = this.nextWeight++;
 | 
						|
 | 
						|
                        return value;
 | 
						|
                    });
 | 
						|
                } else {
 | 
						|
                    return value;
 | 
						|
                }
 | 
						|
            });
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check params for a whitelist of allowed properties.
 | 
						|
     *
 | 
						|
     * @param params Object to filter.
 | 
						|
     * @param whitelist List of keys to keep.
 | 
						|
     */
 | 
						|
    filterParams(params: any, whitelist: string[]): void {
 | 
						|
        for (const key in params) {
 | 
						|
            if (whitelist.indexOf(key) == -1) {
 | 
						|
                delete params[key];
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
 | 
						|
     * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
 | 
						|
     *
 | 
						|
     * @param str The string with raw HTML in it.
 | 
						|
     * @param allowedTags An array of allowed tags.
 | 
						|
     * @param allowedStyles Allowed styles.
 | 
						|
     * @return An XSS safe version of the string.
 | 
						|
     */
 | 
						|
    protected filterXss(str: string, allowedTags?: string[], allowedStyles?: RegExp[]): string {
 | 
						|
        if (!str || typeof str != 'string') {
 | 
						|
            return str;
 | 
						|
        }
 | 
						|
 | 
						|
        allowedTags = allowedTags || ['a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd'];
 | 
						|
 | 
						|
        this.allowedStyles = allowedStyles;
 | 
						|
 | 
						|
        // Store the text format.
 | 
						|
        this.filterXssSplit(allowedTags, true);
 | 
						|
 | 
						|
        // Remove Netscape 4 JS entities.
 | 
						|
        str = str.replace(/&\s*\{[^}]*(\}\s*;?|$)/g, '');
 | 
						|
 | 
						|
        // Defuse all HTML entities.
 | 
						|
        str = str.replace(/&/g, '&');
 | 
						|
 | 
						|
        // Change back only well-formed entities in our whitelist:
 | 
						|
        // Decimal numeric entities.
 | 
						|
        str = str.replace(/&#([0-9]+;)/g, '&#$1');
 | 
						|
        // Hexadecimal numeric entities.
 | 
						|
        str = str.replace(/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/g, '&#x$1');
 | 
						|
        // Named entities.
 | 
						|
        str = str.replace(/&([A-Za-z][A-Za-z0-9]*;)/g, '&$1');
 | 
						|
 | 
						|
        const matches = str.match(/(<(?=[^a-zA-Z!\/])|<!--.*?-->|<[^>]*(>|$)|>)/g);
 | 
						|
        if (matches && matches.length) {
 | 
						|
            matches.forEach((match) => {
 | 
						|
                str = str.replace(match, this.filterXssSplit([match]));
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        return str;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Processes an HTML tag.
 | 
						|
     *
 | 
						|
     * @param m An array with various meaning depending on the value of store.
 | 
						|
     *          If store is TRUE then the array contains the allowed tags.
 | 
						|
     *          If store is FALSE then the array has one element, the HTML tag to process.
 | 
						|
     * @param store Whether to store m.
 | 
						|
     * @return string If the element isn't allowed, an empty string. Otherwise, the cleaned up version of the HTML element.
 | 
						|
     */
 | 
						|
    protected filterXssSplit(m: string[], store: boolean = false): string {
 | 
						|
 | 
						|
        if (store) {
 | 
						|
            this.allowedHtml = this.utils.arrayToObject(m);
 | 
						|
 | 
						|
            return '';
 | 
						|
        }
 | 
						|
 | 
						|
        const str = m[0];
 | 
						|
 | 
						|
        if (str.substr(0, 1) != '<') {
 | 
						|
            // We matched a lone ">" character.
 | 
						|
            return '>';
 | 
						|
        } else if (str.length == 1) {
 | 
						|
            // We matched a lone "<" character.
 | 
						|
            return '<';
 | 
						|
        }
 | 
						|
 | 
						|
        const matches = str.match(/^<\s*(\/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|(<!--.*?-->)$/);
 | 
						|
        if (!matches) {
 | 
						|
            // Seriously malformed.
 | 
						|
            return '';
 | 
						|
        }
 | 
						|
 | 
						|
        const slash = matches[1] ? matches[1].trim() : '',
 | 
						|
            attrList = matches[3] || '',
 | 
						|
            comment = matches[4] || '';
 | 
						|
        let elem = matches[2] || '';
 | 
						|
 | 
						|
        if (comment) {
 | 
						|
            elem = '!--';
 | 
						|
        }
 | 
						|
 | 
						|
        if (!this.allowedHtml[elem.toLowerCase()]) {
 | 
						|
            // Disallowed HTML element.
 | 
						|
            return '';
 | 
						|
        }
 | 
						|
 | 
						|
        if (comment) {
 | 
						|
            return comment;
 | 
						|
        }
 | 
						|
 | 
						|
        if (slash != '') {
 | 
						|
            return '</' + elem + '>';
 | 
						|
        }
 | 
						|
 | 
						|
        // Is there a closing XHTML slash at the end of the attributes?
 | 
						|
        const newAttrList = attrList.replace(/(\s?)\/\s*$/g, '$1'),
 | 
						|
           xhtmlSlash = attrList != newAttrList ? ' /' : '';
 | 
						|
 | 
						|
        // Clean up attributes.
 | 
						|
        let attr2 = this.filterXssAttributes(newAttrList,
 | 
						|
                (CoreH5PContentValidator.ALLOWED_STYLEABLE_TAGS.indexOf(elem) != -1 ? this.allowedStyles : null)).join(' ');
 | 
						|
        attr2 = attr2.replace(/[<>]/g, '');
 | 
						|
        attr2 = attr2.length ? ' ' + attr2 : '';
 | 
						|
 | 
						|
        return '<' + elem + attr2 + xhtmlSlash + '>';
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Processes a string of HTML attributes.
 | 
						|
     *
 | 
						|
     * @param attr HTML attributes.
 | 
						|
     * @param allowedStyles Allowed styles.
 | 
						|
     * @return Cleaned up version of the HTML attributes.
 | 
						|
     */
 | 
						|
    protected filterXssAttributes(attr: string, allowedStyles?: RegExp[]): string[] {
 | 
						|
        const attrArr = [];
 | 
						|
        let mode = 0,
 | 
						|
        attrName = '',
 | 
						|
        skip = false;
 | 
						|
 | 
						|
        while (attr.length != 0) {
 | 
						|
            // Was the last operation successful?
 | 
						|
            let working = 0,
 | 
						|
                matches,
 | 
						|
                thisVal;
 | 
						|
 | 
						|
            switch (mode) {
 | 
						|
                case 0:
 | 
						|
                    // Attribute name, href for instance.
 | 
						|
                    matches = attr.match(/^([-a-zA-Z]+)/);
 | 
						|
                    if (matches && matches.length > 1) {
 | 
						|
                        attrName = matches[1].toLowerCase();
 | 
						|
                        skip = (attrName == 'style' || attrName.substr(0, 2) == 'on');
 | 
						|
                        working = mode = 1;
 | 
						|
                        attr = attr.replace(/^[-a-zA-Z]+/, '');
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                case 1:
 | 
						|
                    // Equals sign or valueless ("selected").
 | 
						|
                    if (attr.match(/^\s*=\s*/)) {
 | 
						|
                        working = 1;
 | 
						|
                        mode = 2;
 | 
						|
                        attr = attr.replace(/^\s*=\s*/, '');
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (attr.match(/^\s+/)) {
 | 
						|
                        working = 1;
 | 
						|
                        mode = 0;
 | 
						|
                        if (!skip) {
 | 
						|
                            attrArr.push(attrName);
 | 
						|
                        }
 | 
						|
                        attr = attr.replace(/^\s+/, '');
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                case 2:
 | 
						|
                    // Attribute value, a URL after href= for instance.
 | 
						|
                    matches = attr.match(/^"([^"]*)"(\s+|$)/);
 | 
						|
                    if (matches && matches.length > 1) {
 | 
						|
                        if (allowedStyles && attrName === 'style') {
 | 
						|
                            // Allow certain styles.
 | 
						|
                            for (let i = 0; i < allowedStyles.length; i++) {
 | 
						|
                                const pattern = allowedStyles[i];
 | 
						|
                                if (matches[1].match(pattern)) {
 | 
						|
                                    // All patterns are start to end patterns, and CKEditor adds one span per style.
 | 
						|
                                    attrArr.push('style="' + matches[1] + '"');
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
 | 
						|
                        thisVal = this.filterXssBadProtocol(matches[1]);
 | 
						|
 | 
						|
                        if (!skip) {
 | 
						|
                            attrArr.push(attrName + '="' + thisVal + '"');
 | 
						|
                        }
 | 
						|
                        working = 1;
 | 
						|
                        mode = 0;
 | 
						|
                        attr = attr.replace(/^"[^"]*"(\s+|$)/, '');
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
 | 
						|
                    matches = attr.match(/^'([^']*)'(\s+|$)/);
 | 
						|
                    if (matches && matches.length > 1) {
 | 
						|
                        thisVal = this.filterXssBadProtocol(matches[1]);
 | 
						|
 | 
						|
                        if (!skip) {
 | 
						|
                            attrArr.push(attrName + '="' + thisVal + '"');
 | 
						|
                        }
 | 
						|
                        working = 1;
 | 
						|
                        mode = 0;
 | 
						|
                        attr = attr.replace(/^'[^']*'(\s+|$)/, '');
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
 | 
						|
                    matches = attr.match(/^([^\s\"']+)(\s+|$)/);
 | 
						|
                    if (matches && matches.length > 1) {
 | 
						|
                        thisVal = this.filterXssBadProtocol(matches[1]);
 | 
						|
 | 
						|
                        if (!skip) {
 | 
						|
                            attrArr.push(attrName + '="' + thisVal + '"');
 | 
						|
                        }
 | 
						|
                        working = 1;
 | 
						|
                        mode = 0;
 | 
						|
                        attr = attr.replace(/^([^\s\"']+)(\s+|$)/, '');
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                default:
 | 
						|
            }
 | 
						|
 | 
						|
            if (working == 0) {
 | 
						|
                // Not well formed; remove and try again.
 | 
						|
                attr = attr.replace(/^("[^"]*("|$)|\'[^\']*(\'|$)||\S)*\s*/, '');
 | 
						|
                mode = 0;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // The attribute list ends with a valueless attribute like "selected".
 | 
						|
        if (mode == 1 && !skip) {
 | 
						|
            attrArr.push(attrName);
 | 
						|
        }
 | 
						|
 | 
						|
        return attrArr;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Processes an HTML attribute value and strips dangerous protocols from URLs.
 | 
						|
     *
 | 
						|
     * @param str The string with the attribute value.
 | 
						|
     * @param decode Whether to decode entities in the str.
 | 
						|
     * @return Cleaned up and HTML-escaped version of str.
 | 
						|
     */
 | 
						|
    filterXssBadProtocol(str: string, decode: boolean = true): string {
 | 
						|
        // Get the plain text representation of the attribute value (i.e. its meaning).
 | 
						|
        if (decode) {
 | 
						|
            str = this.textUtils.decodeHTMLEntities(str);
 | 
						|
        }
 | 
						|
 | 
						|
        return this.textUtils.escapeHTML(this.stripDangerousProtocols(str));
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Strips dangerous protocols (e.g. 'javascript:') from a URI.
 | 
						|
     *
 | 
						|
     * @param uri A plain-text URI that might contain dangerous protocols.
 | 
						|
     * @return A plain-text URI stripped of dangerous protocols.
 | 
						|
     */
 | 
						|
    protected stripDangerousProtocols(uri: string): string {
 | 
						|
 | 
						|
        const allowedProtocols = {
 | 
						|
                ftp: true,
 | 
						|
                http: true,
 | 
						|
                https: true,
 | 
						|
                mailto: true
 | 
						|
            };
 | 
						|
        let before;
 | 
						|
 | 
						|
        // Iteratively remove any invalid protocol found.
 | 
						|
        do {
 | 
						|
            before = uri;
 | 
						|
            const colonPos = uri.indexOf(':');
 | 
						|
 | 
						|
            if (colonPos > 0) {
 | 
						|
                // We found a colon, possibly a protocol. Verify.
 | 
						|
                const protocol = uri.substr(0, colonPos);
 | 
						|
                // If a colon is preceded by a slash, question mark or hash, it cannot possibly be part of the URL scheme.
 | 
						|
                // This must be a relative URL, which inherits the (safe) protocol of the base document.
 | 
						|
                if (protocol.match(/[/?#]/)) {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                // Check if this is a disallowed protocol.
 | 
						|
                if (!allowedProtocols[protocol.toLowerCase()]) {
 | 
						|
                    uri = uri.substr(colonPos + 1);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        } while (before != uri);
 | 
						|
 | 
						|
        return uri;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get metadata semantics.
 | 
						|
     *
 | 
						|
     * @return Semantics.
 | 
						|
     */
 | 
						|
    getMetadataSemantics(): any[] {
 | 
						|
 | 
						|
        if (this.metadataSemantics) {
 | 
						|
            return this.metadataSemantics;
 | 
						|
        }
 | 
						|
 | 
						|
        const ccVersions = this.getCCVersions();
 | 
						|
 | 
						|
        this.metadataSemantics = [
 | 
						|
            {
 | 
						|
                name: 'title',
 | 
						|
                type: 'text',
 | 
						|
                label: this.translate.instant('core.h5p.title'),
 | 
						|
                placeholder: 'La Gioconda'
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'license',
 | 
						|
                type: 'select',
 | 
						|
                label: this.translate.instant('core.h5p.license'),
 | 
						|
                default: 'U',
 | 
						|
                options: [
 | 
						|
                    {
 | 
						|
                        value: 'U',
 | 
						|
                        label: this.translate.instant('core.h5p.undisclosed')
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        type: 'optgroup',
 | 
						|
                        label: this.translate.instant('core.h5p.creativecommons'),
 | 
						|
                        options: [
 | 
						|
                            {
 | 
						|
                                value: 'CC BY',
 | 
						|
                                label: this.translate.instant('core.h5p.ccattribution'),
 | 
						|
                                versions: ccVersions
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC BY-SA',
 | 
						|
                                label: this.translate.instant('core.h5p.ccattributionsa'),
 | 
						|
                                versions: ccVersions
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC BY-ND',
 | 
						|
                                label: this.translate.instant('core.h5p.ccattributionnd'),
 | 
						|
                                versions: ccVersions
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC BY-NC',
 | 
						|
                                label: this.translate.instant('core.h5p.ccattributionnc'),
 | 
						|
                                versions: ccVersions
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC BY-NC-SA',
 | 
						|
                                label: this.translate.instant('core.h5p.ccattributionncsa'),
 | 
						|
                                versions: ccVersions
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC BY-NC-ND',
 | 
						|
                                label: this.translate.instant('core.h5p.ccattributionncnd'),
 | 
						|
                                versions: ccVersions
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC0 1.0',
 | 
						|
                                label: this.translate.instant('core.h5p.ccpdd')
 | 
						|
                            },
 | 
						|
                            {
 | 
						|
                                value: 'CC PDM',
 | 
						|
                                label: this.translate.instant('core.h5p.pdm')
 | 
						|
                            },
 | 
						|
                        ]
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        value: 'GNU GPL',
 | 
						|
                        label: this.translate.instant('core.h5p.gpl')
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        value: 'PD',
 | 
						|
                        label: this.translate.instant('core.h5p.pd')
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        value: 'ODC PDDL',
 | 
						|
                        label: this.translate.instant('core.h5p.pddl')
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        value: 'C',
 | 
						|
                        label: this.translate.instant('core.h5p.copyrightstring')
 | 
						|
                    }
 | 
						|
                ]
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'licenseVersion',
 | 
						|
                type: 'select',
 | 
						|
                label: this.translate.instant('core.h5p.licenseversion'),
 | 
						|
                options: ccVersions,
 | 
						|
                optional: true
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'yearFrom',
 | 
						|
                type: 'number',
 | 
						|
                label: this.translate.instant('core.h5p.yearsfrom'),
 | 
						|
                placeholder: '1991',
 | 
						|
                min: '-9999',
 | 
						|
                max: '9999',
 | 
						|
                optional: true
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'yearTo',
 | 
						|
                type: 'number',
 | 
						|
                label: this.translate.instant('core.h5p.yearsto'),
 | 
						|
                placeholder: '1992',
 | 
						|
                min: '-9999',
 | 
						|
                max: '9999',
 | 
						|
                optional: true
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'source',
 | 
						|
                type: 'text',
 | 
						|
                label: this.translate.instant('core.h5p.source'),
 | 
						|
                placeholder: 'https://',
 | 
						|
                optional: true
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'authors',
 | 
						|
                type: 'list',
 | 
						|
                field: {
 | 
						|
                    name: 'author',
 | 
						|
                    type: 'group',
 | 
						|
                    fields: [
 | 
						|
                        {
 | 
						|
                            label: this.translate.instant('core.h5p.authorname'),
 | 
						|
                            name: 'name',
 | 
						|
                            optional: true,
 | 
						|
                            type: 'text'
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            name: 'role',
 | 
						|
                            type: 'select',
 | 
						|
                            label: this.translate.instant('core.h5p.authorrole'),
 | 
						|
                            default: 'Author',
 | 
						|
                            options: [
 | 
						|
                                {
 | 
						|
                                    value: 'Author',
 | 
						|
                                    label: this.translate.instant('core.h5p.author')
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'Editor',
 | 
						|
                                    label: this.translate.instant('core.h5p.editor')
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'Licensee',
 | 
						|
                                    label: this.translate.instant('core.h5p.licensee')
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'Originator',
 | 
						|
                                    label: this.translate.instant('core.h5p.originator')
 | 
						|
                                }
 | 
						|
                            ]
 | 
						|
                        }
 | 
						|
                    ]
 | 
						|
                }
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'licenseExtras',
 | 
						|
                type: 'text',
 | 
						|
                widget: 'textarea',
 | 
						|
                label: this.translate.instant('core.h5p.licenseextras'),
 | 
						|
                optional: true,
 | 
						|
                description: this.translate.instant('core.h5p.additionallicenseinfo')
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'changes',
 | 
						|
                type: 'list',
 | 
						|
                field: {
 | 
						|
                    name: 'change',
 | 
						|
                    type: 'group',
 | 
						|
                    label: this.translate.instant('core.h5p.changelog'),
 | 
						|
                    fields: [
 | 
						|
                        {
 | 
						|
                            name: 'date',
 | 
						|
                            type: 'text',
 | 
						|
                            label: this.translate.instant('core.h5p.date'),
 | 
						|
                            optional: true
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            name: 'author',
 | 
						|
                            type: 'text',
 | 
						|
                            label: this.translate.instant('core.h5p.changedby'),
 | 
						|
                            optional: true
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            name: 'log',
 | 
						|
                            type: 'text',
 | 
						|
                            widget: 'textarea',
 | 
						|
                            label: this.translate.instant('core.h5p.changedescription'),
 | 
						|
                            placeholder: this.translate.instant('core.h5p.changeplaceholder'),
 | 
						|
                            optional: true
 | 
						|
                        }
 | 
						|
                    ]
 | 
						|
                }
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'authorComments',
 | 
						|
                type: 'text',
 | 
						|
                widget: 'textarea',
 | 
						|
                label: this.translate.instant('core.h5p.authorcomments'),
 | 
						|
                description: this.translate.instant('core.h5p.authorcommentsdescription'),
 | 
						|
                optional: true
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'contentType',
 | 
						|
                type: 'text',
 | 
						|
                widget: 'none'
 | 
						|
            },
 | 
						|
            {
 | 
						|
                name: 'defaultLanguage',
 | 
						|
                type: 'text',
 | 
						|
                widget: 'none'
 | 
						|
            }
 | 
						|
        ];
 | 
						|
 | 
						|
        return this.metadataSemantics;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get copyright semantics.
 | 
						|
     *
 | 
						|
     * @return Semantics.
 | 
						|
     */
 | 
						|
    getCopyrightSemantics(): any {
 | 
						|
 | 
						|
        if (this.copyrightSemantics) {
 | 
						|
            return this.copyrightSemantics;
 | 
						|
        }
 | 
						|
 | 
						|
        const ccVersions = this.getCCVersions();
 | 
						|
 | 
						|
        this.copyrightSemantics = {
 | 
						|
            name: 'copyright',
 | 
						|
            type: 'group',
 | 
						|
            label: this.translate.instant('core.h5p.copyrightinfo'),
 | 
						|
            fields: [
 | 
						|
                {
 | 
						|
                    name: 'title',
 | 
						|
                    type: 'text',
 | 
						|
                    label: this.translate.instant('core.h5p.title'),
 | 
						|
                    placeholder: 'La Gioconda',
 | 
						|
                    optional: true
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    name: 'author',
 | 
						|
                    type: 'text',
 | 
						|
                    label: this.translate.instant('core.h5p.author'),
 | 
						|
                    placeholder: 'Leonardo da Vinci',
 | 
						|
                    optional: true
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    name: 'year',
 | 
						|
                    type: 'text',
 | 
						|
                    label: this.translate.instant('core.h5p.years'),
 | 
						|
                    placeholder: '1503 - 1517',
 | 
						|
                    optional: true
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    name: 'source',
 | 
						|
                    type: 'text',
 | 
						|
                    label: this.translate.instant('core.h5p.source'),
 | 
						|
                    placeholder: 'http://en.wikipedia.org/wiki/Mona_Lisa',
 | 
						|
                    optional: true,
 | 
						|
                    regexp: {
 | 
						|
                        pattern: '^http[s]?://.+',
 | 
						|
                        modifiers: 'i'
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    name: 'license',
 | 
						|
                    type: 'select',
 | 
						|
                    label: this.translate.instant('core.h5p.license'),
 | 
						|
                    default: 'U',
 | 
						|
                    options: [
 | 
						|
                        {
 | 
						|
                            value: 'U',
 | 
						|
                            label: this.translate.instant('core.h5p.undisclosed')
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'CC BY',
 | 
						|
                            label: this.translate.instant('core.h5p.ccattribution'),
 | 
						|
                            versions: ccVersions
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'CC BY-SA',
 | 
						|
                            label: this.translate.instant('core.h5p.ccattributionsa'),
 | 
						|
                            versions: ccVersions
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'CC BY-ND',
 | 
						|
                            label: this.translate.instant('core.h5p.ccattributionnd'),
 | 
						|
                            versions: ccVersions
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'CC BY-NC',
 | 
						|
                            label: this.translate.instant('core.h5p.ccattributionnc'),
 | 
						|
                            versions: ccVersions
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'CC BY-NC-SA',
 | 
						|
                            label: this.translate.instant('core.h5p.ccattributionncsa'),
 | 
						|
                            versions: ccVersions
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'CC BY-NC-ND',
 | 
						|
                            label: this.translate.instant('core.h5p.ccattributionncnd'),
 | 
						|
                            versions: ccVersions
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'GNU GPL',
 | 
						|
                            label: this.translate.instant('core.h5p.licenseGPL'),
 | 
						|
                            versions: [
 | 
						|
                                {
 | 
						|
                                    value: 'v3',
 | 
						|
                                    label: this.translate.instant('core.h5p.licenseV3')
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'v2',
 | 
						|
                                    label: this.translate.instant('core.h5p.licenseV2')
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'v1',
 | 
						|
                                    label: this.translate.instant('core.h5p.licenseV1')
 | 
						|
                                }
 | 
						|
                            ]
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'PD',
 | 
						|
                            label: this.translate.instant('core.h5p.pd'),
 | 
						|
                            versions: [
 | 
						|
                                {
 | 
						|
                                    value: '-',
 | 
						|
                                    label: '-'
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'CC0 1.0',
 | 
						|
                                    label: this.translate.instant('core.h5p.licenseCC010U')
 | 
						|
                                },
 | 
						|
                                {
 | 
						|
                                    value: 'CC PDM',
 | 
						|
                                    label: this.translate.instant('core.h5p.pdm')
 | 
						|
                                }
 | 
						|
                            ]
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            value: 'C',
 | 
						|
                            label: this.translate.instant('core.h5p.copyrightstring')
 | 
						|
                        }
 | 
						|
                    ]
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    name: 'version',
 | 
						|
                    type: 'select',
 | 
						|
                    label: this.translate.instant('core.h5p.licenseversion'),
 | 
						|
                    options: []
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        };
 | 
						|
 | 
						|
        return this.copyrightSemantics;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get CC versions for semantics.
 | 
						|
     *
 | 
						|
     * @return CC versions.
 | 
						|
     */
 | 
						|
    protected getCCVersions(): any[] {
 | 
						|
        return [
 | 
						|
            {
 | 
						|
                value: '4.0',
 | 
						|
                label: this.translate.instant('core.h5p.licenseCC40')
 | 
						|
            },
 | 
						|
            {
 | 
						|
                value: '3.0',
 | 
						|
                label: this.translate.instant('core.h5p.licenseCC30')
 | 
						|
            },
 | 
						|
            {
 | 
						|
                value: '2.5',
 | 
						|
                label: this.translate.instant('core.h5p.licenseCC25')
 | 
						|
            },
 | 
						|
            {
 | 
						|
                value: '2.0',
 | 
						|
                label: this.translate.instant('core.h5p.licenseCC20')
 | 
						|
            },
 | 
						|
            {
 | 
						|
                value: '1.0',
 | 
						|
                label: this.translate.instant('core.h5p.licenseCC10')
 | 
						|
            }
 | 
						|
        ];
 | 
						|
    }
 | 
						|
}
 |