forked from EVOgeek/Vmeda.Online
		
	MOBILE-2272 quiz: Support viewing attachments and inline files
This commit is contained in:
		
							parent
							
								
									b6ed831b25
								
							
						
					
					
						commit
						3f40127661
					
				| @ -1861,6 +1861,7 @@ | ||||
|   "core.mainmenu.help": "moodle", | ||||
|   "core.mainmenu.logout": "moodle", | ||||
|   "core.mainmenu.website": "local_moodlemobileapp", | ||||
|   "core.maxfilesize": "moodle", | ||||
|   "core.maxsizeandattachments": "moodle", | ||||
|   "core.min": "moodle", | ||||
|   "core.mins": "moodle", | ||||
| @ -1944,8 +1945,8 @@ | ||||
|   "core.question.certainty": "qbehaviour_deferredcbm", | ||||
|   "core.question.complete": "question", | ||||
|   "core.question.correct": "question", | ||||
|   "core.question.errorattachmentsnotsupported": "local_moodlemobileapp", | ||||
|   "core.question.errorinlinefilesnotsupported": "local_moodlemobileapp", | ||||
|   "core.question.errorattachmentsnotsupportedinsite": "local_moodlemobileapp", | ||||
|   "core.question.errorembeddedfilesnotsupportedinsite": "local_moodlemobileapp", | ||||
|   "core.question.errorquestionnotsupported": "local_moodlemobileapp", | ||||
|   "core.question.feedback": "question", | ||||
|   "core.question.howtodraganddrop": "local_moodlemobileapp", | ||||
|  | ||||
| @ -411,7 +411,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid | ||||
|                                 const params = this.urlUtils.extractUrlParams(response.data.reviewlesson.value); | ||||
|                                 if (params && params.pageid) { | ||||
|                                     // The retake can be reviewed, mark it as finished. Don't block the user for this.
 | ||||
|                                     this.setRetakeFinishedInSync(lessonId, retake.retake, params.pageid, siteId); | ||||
|                                     this.setRetakeFinishedInSync(lessonId, retake.retake, Number(params.pageid), siteId); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
| @ -5,9 +5,10 @@ | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <!-- Textarea. --> | ||||
|     <ion-item *ngIf="question.textarea && !question.hasDraftFiles"> | ||||
|         <!-- "Format" hidden input --> | ||||
|     <ion-item *ngIf="question.textarea && (!question.hasDraftFiles || uploadFilesSupported)"> | ||||
|         <!-- "Format" and draftid hidden inputs --> | ||||
|         <input item-content *ngIf="question.formatInput" type="hidden" [name]="question.formatInput.name" [value]="question.formatInput.value" > | ||||
|         <input item-content *ngIf="question.answerDraftIdInput" type="hidden" [name]="question.answerDraftIdInput.name" [value]="question.answerDraftIdInput.value" > | ||||
|         <!-- Plain text textarea. --> | ||||
|         <ion-textarea *ngIf="question.isPlainText" class="core-question-textarea" [ngClass]='{"core-monospaced": question.isMonospaced}' placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.textarea.name" aria-multiline="true" [ngModel]="question.textarea.text"></ion-textarea> | ||||
|         <!-- Rich text editor. --> | ||||
| @ -15,22 +16,29 @@ | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <!-- Draft files not supported. --> | ||||
|     <ng-container *ngIf="question.textarea && question.hasDraftFiles"> | ||||
|     <ng-container *ngIf="question.textarea && question.hasDraftFiles && !uploadFilesSupported"> | ||||
|         <ion-item text-wrap class="core-danger-item"> | ||||
|             <p class="core-question-warning">{{ 'core.question.errorinlinefilesnotsupported' | translate }}</p> | ||||
|             <p class="core-question-warning">{{ 'core.question.errorinlinefilesnotsupportedinsite' | translate }}</p> | ||||
|         </ion-item> | ||||
|         <ion-item text-wrap> | ||||
|             <p><core-format-text [component]="component" [componentId]="componentId" [text]="question.textarea.text" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|         </ion-item> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <!-- Attachments not supported in the app yet. --> | ||||
|     <ion-item text-wrap *ngIf="question.allowsAttachments" class="core-danger-item"> | ||||
|         <p class="core-question-warning">{{ 'core.question.errorattachmentsnotsupported' | translate }}</p> | ||||
|     </ion-item> | ||||
|     <!-- Attachments. --> | ||||
|     <ng-container *ngIf="question.allowsAttachments"> | ||||
|         <core-attachments *ngIf="uploadFilesSupported" [files]="attachments" [component]="component" [componentId]="componentId" [maxSize]="question.attachmentsMaxBytes" [maxSubmissions]="question.attachmentsMaxFiles"></core-attachments> | ||||
| 
 | ||||
|         <input item-content *ngIf="uploadFilesSupported" type="hidden" [name]="question.attachmentsDraftIdInput.name" [value]="question.attachmentsDraftIdInput.value" > | ||||
| 
 | ||||
|         <!-- Attachments not supported in this site. --> | ||||
|         <ion-item text-wrap *ngIf="!uploadFilesSupported" class="core-danger-item"> | ||||
|             <p class="core-question-warning">{{ 'core.question.errorattachmentsnotsupportedinsite' | translate }}</p> | ||||
|         </ion-item> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <!-- Answer to the question and attachments (reviewing). --> | ||||
|     <ion-item text-wrap *ngIf="!question.textarea && (question.answer || (!question.attachments.length && !question.allowsAttachments))"> | ||||
|     <ion-item text-wrap *ngIf="!question.textarea && (question.answer || question.answer == '')"> | ||||
|         <p><core-format-text [ngClass]='{"core-monospaced": question.isMonospaced}' [component]="component" [componentId]="componentId" [text]="question.answer" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|     </ion-item> | ||||
| 
 | ||||
|  | ||||
| @ -14,8 +14,11 @@ | ||||
| 
 | ||||
| import { Component, OnInit, Injector } from '@angular/core'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSites } from '@providers/sites'; | ||||
| import { CoreWSExternalFile } from '@providers/ws'; | ||||
| import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component'; | ||||
| import { FormControl, FormBuilder } from '@angular/forms'; | ||||
| import { CoreFileSession } from '@providers/file-session'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to render an essay question. | ||||
| @ -28,6 +31,9 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen | ||||
| 
 | ||||
|     protected formControl: FormControl; | ||||
| 
 | ||||
|     attachments: CoreWSExternalFile[]; | ||||
|     uploadFilesSupported: boolean; | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, injector: Injector, protected fb: FormBuilder) { | ||||
|         super(logger, 'AddonQtypeEssayComponent', injector); | ||||
|     } | ||||
| @ -36,8 +42,14 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.uploadFilesSupported = CoreSites.instance.getCurrentSite().isVersionGreaterEqualThan('3.10'); | ||||
|         this.initEssayComponent(); | ||||
| 
 | ||||
|         this.formControl = this.fb.control(this.question.textarea && this.question.textarea.text); | ||||
| 
 | ||||
|         if (this.question.allowsAttachments && this.uploadFilesSupported) { | ||||
|             this.attachments = Array.from(this.questionHelper.getResponseFileAreaFiles(this.question, 'attachments')); | ||||
|             CoreFileSession.instance.setFiles(this.component, this.componentId + '_' + this.question.id, this.attachments); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,7 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, Injector } from '@angular/core'; | ||||
| import { CoreSites } from '@providers/sites'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| @ -68,11 +69,11 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | ||||
| 
 | ||||
|         if (element.querySelector('div[id*=filemanager]')) { | ||||
|             // The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
 | ||||
|             return 'core.question.errorattachmentsnotsupported'; | ||||
|             return 'core.question.errorattachmentsnotsupportedinsite'; | ||||
|         } | ||||
| 
 | ||||
|         if (this.questionHelper.hasDraftFileUrls(element.innerHTML)) { | ||||
|             return 'core.question.errorinlinefilesnotsupported'; | ||||
|             return 'core.question.errorinlinefilesnotsupportedinsite'; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -139,13 +140,21 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Return a promise resolved when done if async, void if sync. | ||||
|      */ | ||||
|     prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> { | ||||
|     async prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): Promise<void> { | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         // Search the textarea to get its name.
 | ||||
|         const textarea = <HTMLTextAreaElement> element.querySelector('textarea[name*=_answer]'); | ||||
| 
 | ||||
|         if (textarea && typeof answers[textarea.name] != 'undefined') { | ||||
|             if (this.questionHelper.hasDraftFileUrls(question.html) && question.responsefileareas) { | ||||
|                 // Restore draftfile URLs.
 | ||||
|                 const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|                 answers[textarea.name] = this.textUtils.restoreDraftfileUrls(site.getURL(), answers[textarea.name], | ||||
|                         question.html, this.questionHelper.getResponseFileAreaFiles(question, 'answer')); | ||||
|             } | ||||
| 
 | ||||
|             // Add some HTML to the text if needed.
 | ||||
|             answers[textarea.name] = this.textUtils.formatHtmlLines(answers[textarea.name]); | ||||
|         } | ||||
|  | ||||
| @ -1945,8 +1945,8 @@ | ||||
|     "core.question.certainty": "Certainty", | ||||
|     "core.question.complete": "Complete", | ||||
|     "core.question.correct": "Correct", | ||||
|     "core.question.errorattachmentsnotsupported": "The application doesn't support attaching files to answers yet.", | ||||
|     "core.question.errorinlinefilesnotsupported": "The application doesn't support editing inline files yet.", | ||||
|     "core.question.errorattachmentsnotsupportedinsite": "Your site doesn't support attaching files to answers yet.", | ||||
|     "core.question.errorinlinefilesnotsupportedinsite": "Your site doesn't support editing inline files yet.", | ||||
|     "core.question.errorquestionnotsupported": "This question type is not supported by the app: {{$a}}.", | ||||
|     "core.question.feedback": "Feedback", | ||||
|     "core.question.howtodraganddrop": "Tap to select then tap to drop.", | ||||
|  | ||||
| @ -221,13 +221,16 @@ export class CoreSite { | ||||
| 
 | ||||
|     // Versions of Moodle releases.
 | ||||
|     protected MOODLE_RELEASES = { | ||||
|         3.1: 2016052300, | ||||
|         3.2: 2016120500, | ||||
|         3.3: 2017051503, | ||||
|         3.4: 2017111300, | ||||
|         3.5: 2018051700, | ||||
|         3.6: 2018120300, | ||||
|         3.7: 2019052000 | ||||
|         '3.1': 2016052300, | ||||
|         '3.2': 2016120500, | ||||
|         '3.3': 2017051503, | ||||
|         '3.4': 2017111300, | ||||
|         '3.5': 2018051700, | ||||
|         '3.6': 2018120300, | ||||
|         '3.7': 2019052000, | ||||
|         '3.8': 2019111800, | ||||
|         '3.9': 2020061500, | ||||
|         '3.10': 2020092400, // @todo: Replace with the right value once 3.10 is released.
 | ||||
|     }; | ||||
|     static MINIMUM_MOODLE_VERSION = '3.1'; | ||||
| 
 | ||||
|  | ||||
| @ -56,6 +56,7 @@ ion-app.app-root core-rich-text-editor { | ||||
|         img { | ||||
|             @include padding(null, null, null, 2px); | ||||
|             max-width: 95%; | ||||
|             width: auto; | ||||
|         } | ||||
|         &:empty:before { | ||||
|             content: attr(data-placeholder-text); | ||||
|  | ||||
| @ -407,7 +407,7 @@ export class CoreGradesHelperProvider { | ||||
|                     if (matches && matches.length) { | ||||
|                         const hrefParams = this.urlUtils.extractUrlParams(matches[1]); | ||||
| 
 | ||||
|                         return hrefParams && hrefParams.id == moduleId; | ||||
|                         return hrefParams && Number(hrefParams.id) == moduleId; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  | ||||
| @ -1152,7 +1152,7 @@ export class CoreLoginHelperProvider { | ||||
|                     const providerToUse = identityProviders.find((provider) => { | ||||
|                         const params = this.urlUtils.extractUrlParams(provider.url); | ||||
| 
 | ||||
|                         return params.id == currentSite.getOAuthId(); | ||||
|                         return Number(params.id) == currentSite.getOAuthId(); | ||||
|                     }); | ||||
| 
 | ||||
|                     if (providerToUse) { | ||||
|  | ||||
| @ -14,8 +14,10 @@ | ||||
| 
 | ||||
| import { Input, Output, EventEmitter, Injector, ElementRef } from '@angular/core'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSites } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreUrlUtils } from '@providers/utils/url'; | ||||
| import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; | ||||
| 
 | ||||
| /** | ||||
| @ -162,6 +164,8 @@ export class CoreQuestionBaseComponent { | ||||
|             const input = questionEl.querySelector('input[type="text"][name*=answer]'); | ||||
|             this.question.optionsFirst = | ||||
|                     questionEl.innerHTML.indexOf(input.outerHTML) > questionEl.innerHTML.indexOf(radios[0].outerHTML); | ||||
| 
 | ||||
|             return questionEl; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -201,27 +205,38 @@ export class CoreQuestionBaseComponent { | ||||
|         const questionEl = this.initComponent(); | ||||
| 
 | ||||
|         if (questionEl) { | ||||
|             // First search the textarea.
 | ||||
|             const textarea = <HTMLTextAreaElement> questionEl.querySelector('textarea[name*=_answer]'); | ||||
|             const answerDraftIdInput = <HTMLInputElement> questionEl.querySelector('input[name*="_answer:itemid"]'); | ||||
| 
 | ||||
|             this.question.allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); | ||||
|             this.question.allowsAnswerFiles = !!answerDraftIdInput; | ||||
|             this.question.isMonospaced = !!questionEl.querySelector('.qtype_essay_monospaced'); | ||||
|             this.question.isPlainText = this.question.isMonospaced || !!questionEl.querySelector('.qtype_essay_plain'); | ||||
|             this.question.hasDraftFiles = this.questionHelper.hasDraftFileUrls(questionEl.innerHTML); | ||||
|             this.question.hasDraftFiles = this.question.allowsAnswerFiles && | ||||
|                     this.questionHelper.hasDraftFileUrls(questionEl.innerHTML); | ||||
| 
 | ||||
|             if (!textarea) { | ||||
|                 // Textarea not found, we might be in review. Search the answer and the attachments.
 | ||||
|             if (!textarea && !this.question.allowsAttachments) { | ||||
|                 // Textarea and filemanager not found, we might be in review. Search the answer and the attachments.
 | ||||
|                 this.question.answer = this.domUtils.getContentsOfElement(questionEl, '.qtype_essay_response'); | ||||
|                 this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml( | ||||
|                         this.domUtils.getContentsOfElement(questionEl, '.attachments')); | ||||
|             } else { | ||||
|                 // Textarea found.
 | ||||
|                 const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]'), | ||||
|                     content = textarea.innerHTML; | ||||
| 
 | ||||
|                 return questionEl; | ||||
|             } | ||||
| 
 | ||||
|             if (textarea) { | ||||
|                 const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]'); | ||||
|                 let content = this.textUtils.decodeHTML(textarea.innerHTML || ''); | ||||
| 
 | ||||
|                 if (this.question.hasDraftFiles && this.question.responsefileareas) { | ||||
|                     content = this.textUtils.replaceDraftfileUrls(CoreSites.instance.getCurrentSite().getURL(), content, | ||||
|                             this.questionHelper.getResponseFileAreaFiles(this.question, 'answer')).text; | ||||
|                 } | ||||
| 
 | ||||
|                 this.question.textarea = { | ||||
|                     id: textarea.id, | ||||
|                     name: textarea.name, | ||||
|                     text: content ? this.textUtils.decodeHTML(content) : '' | ||||
|                     text: content, | ||||
|                 }; | ||||
| 
 | ||||
|                 if (input) { | ||||
| @ -231,6 +246,38 @@ export class CoreQuestionBaseComponent { | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (answerDraftIdInput) { | ||||
|                 this.question.answerDraftIdInput = { | ||||
|                     name: answerDraftIdInput.name, | ||||
|                     value: Number(answerDraftIdInput.value), | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             if (this.question.allowsAttachments) { | ||||
|                 const attachmentsInput = <HTMLInputElement> questionEl.querySelector('.attachments input[name*=_attachments]'); | ||||
|                 const objectElement = <HTMLObjectElement> questionEl.querySelector('.attachments object'); | ||||
|                 const fileManagerUrl = objectElement && objectElement.data; | ||||
| 
 | ||||
|                 if (attachmentsInput) { | ||||
|                     this.question.attachmentsDraftIdInput = { | ||||
|                         name: attachmentsInput.name, | ||||
|                         value: Number(attachmentsInput.value), | ||||
|                     }; | ||||
|                 } | ||||
| 
 | ||||
|                 if (fileManagerUrl) { | ||||
|                     const params = CoreUrlUtils.instance.extractUrlParams(fileManagerUrl); | ||||
|                     const maxBytes = Number(params.maxbytes); | ||||
|                     const areaMaxBytes = Number(params.areamaxbytes); | ||||
| 
 | ||||
|                     this.question.attachmentsMaxFiles = Number(params.maxfiles); | ||||
|                     this.question.attachmentsMaxBytes = maxBytes === -1 || areaMaxBytes === -1 ? | ||||
|                             Math.max(maxBytes, areaMaxBytes) : Math.min(maxBytes, areaMaxBytes); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return questionEl; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -270,6 +317,8 @@ export class CoreQuestionBaseComponent { | ||||
| 
 | ||||
|         // Set the question text.
 | ||||
|         this.question.text = content.innerHTML; | ||||
| 
 | ||||
|         return element; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -5,8 +5,8 @@ | ||||
|     "certainty": "Certainty", | ||||
|     "complete": "Complete", | ||||
|     "correct": "Correct", | ||||
|     "errorattachmentsnotsupported": "The application doesn't support attaching files to answers yet.", | ||||
|     "errorinlinefilesnotsupported": "The application doesn't support editing inline files yet.", | ||||
|     "errorattachmentsnotsupportedinsite": "Your site doesn't support attaching files to answers yet.", | ||||
|     "errorinlinefilesnotsupportedinsite": "Your site doesn't support editing inline files yet.", | ||||
|     "errorquestionnotsupported": "This question type is not supported by the app: {{$a}}.", | ||||
|     "feedback": "Feedback", | ||||
|     "howtodraganddrop": "Tap to select then tap to drop.", | ||||
|  | ||||
| @ -16,6 +16,7 @@ import { Injectable, EventEmitter } from '@angular/core'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreFilepoolProvider } from '@providers/filepool'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreWSExternalFile } from '@providers/ws'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreUrlUtilsProvider } from '@providers/utils/url'; | ||||
| @ -421,6 +422,25 @@ export class CoreQuestionHelperProvider { | ||||
|         return state ? state.class : ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the files of a certain response file area. | ||||
|      * | ||||
|      * @param question Question. | ||||
|      * @param areaName Name of the area, e.g. 'attachments'. | ||||
|      * @return List of files. | ||||
|      */ | ||||
|     getResponseFileAreaFiles(question: any, areaName: string): CoreWSExternalFile[] { | ||||
|         if (!question.responsefileareas) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         const area = question.responsefileareas.find((area) => { | ||||
|             return area.area == areaName; | ||||
|         }); | ||||
| 
 | ||||
|         return area && area.files || []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation error message from a question HTML if it's there. | ||||
|      * | ||||
|  | ||||
| @ -19,6 +19,7 @@ import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreLangProvider } from '../lang'; | ||||
| import { makeSingleton } from '@singletons/core.singletons'; | ||||
| import { CoreApp } from '../app'; | ||||
| import { CoreWSExternalFile } from '../ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Different type of errors the app can treat. | ||||
| @ -699,6 +700,60 @@ export class CoreTextUtilsProvider { | ||||
|         return text.replace(/(?:\r\n|\r|\n)/g, newValue); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Replace draftfile URLs with the equivalent pluginfile URL. | ||||
|      * | ||||
|      * @param siteUrl URL of the site. | ||||
|      * @param text Text to treat, including draftfile URLs. | ||||
|      * @param files List of files of the area, using pluginfile URLs. | ||||
|      * @return Treated text and map with the replacements. | ||||
|      */ | ||||
|     replaceDraftfileUrls(siteUrl: string, text: string, files: CoreWSExternalFile[]) | ||||
|             : {text: string, replaceMap?: {[url: string]: string}} { | ||||
| 
 | ||||
|         if (!text || !files || !files.length) { | ||||
|             return {text}; | ||||
|         } | ||||
| 
 | ||||
|         const draftfileUrl = this.concatenatePaths(siteUrl, 'draftfile.php'); | ||||
|         const matches = text.match(new RegExp(this.escapeForRegex(draftfileUrl) + '[^\'" ]+', 'ig')); | ||||
| 
 | ||||
|         if (!matches || !matches.length) { | ||||
|             return {text}; | ||||
|         } | ||||
| 
 | ||||
|         // Index the pluginfile URLs by file name.
 | ||||
|         const pluginfileMap: {[name: string]: string} = {}; | ||||
|         files.forEach((file) => { | ||||
|             pluginfileMap[file.filename] = file.fileurl; | ||||
|         }); | ||||
| 
 | ||||
|         // Replace each draftfile with the corresponding pluginfile URL.
 | ||||
|         const replaceMap: {[url: string]: string} = {}; | ||||
|         matches.forEach((url) => { | ||||
|             if (replaceMap[url]) { | ||||
|                 // URL already treated, same file embedded more than once.
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Get the filename from the URL.
 | ||||
|             let filename = url.substr(url.lastIndexOf('/') + 1); | ||||
|             if (filename.indexOf('?') != -1) { | ||||
|                 filename = filename.substr(0, filename.indexOf('?')); | ||||
|             } | ||||
| 
 | ||||
|             if (pluginfileMap[filename]) { | ||||
|                 replaceMap[url] = pluginfileMap[filename]; | ||||
|                 text = text.replace(new RegExp(this.escapeForRegex(url), 'g'), pluginfileMap[filename]); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         return { | ||||
|             text, | ||||
|             replaceMap, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Replace @@PLUGINFILE@@ wildcards with the real URL in a text. | ||||
|      * | ||||
| @ -717,6 +772,36 @@ export class CoreTextUtilsProvider { | ||||
|         return text; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Restore original draftfile URLs. | ||||
|      * | ||||
|      * @param text Text to treat, including pluginfile URLs. | ||||
|      * @param replaceMap Map of the replacements that were done. | ||||
|      * @return Treated text. | ||||
|      */ | ||||
|     restoreDraftfileUrls(siteUrl: string, treatedText: string, originalText: string, files: CoreWSExternalFile[]): string { | ||||
|         if (!treatedText || !files || !files.length) { | ||||
|             return treatedText; | ||||
|         } | ||||
| 
 | ||||
|         const draftfileUrl = this.concatenatePaths(siteUrl, 'draftfile.php'); | ||||
|         const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/'; | ||||
| 
 | ||||
|         files.forEach((file) => { | ||||
|             // Search the draftfile URL in the original text.
 | ||||
|             const matches = originalText.match(new RegExp( | ||||
|                     draftfileUrlRegexPrefix + this.escapeForRegex(file.filename) + '[^\'" ]*', 'i')); | ||||
| 
 | ||||
|             if (!matches || !matches[0]) { | ||||
|                 return; // Original URL not found, skip.
 | ||||
|             } | ||||
| 
 | ||||
|             treatedText = treatedText.replace(new RegExp(this.escapeForRegex(file.fileurl), 'g'), matches[0]); | ||||
|         }); | ||||
| 
 | ||||
|         return treatedText; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Replace pluginfile URLs with @@PLUGINFILE@@ wildcards. | ||||
|      * | ||||
|  | ||||
| @ -114,10 +114,10 @@ export class CoreUrlUtilsProvider { | ||||
|      * @param url URL to treat. | ||||
|      * @return Object with the params. | ||||
|      */ | ||||
|     extractUrlParams(url: string): any { | ||||
|     extractUrlParams(url: string): {[name: string]: string} { | ||||
|         const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, | ||||
|             subParamsPlaceholder = '@@@SUBPARAMS@@@', | ||||
|             params: any = {}, | ||||
|             params: {[name: string]: string} = {}, | ||||
|             urlAndHash = url.split('#'), | ||||
|             questionMarkSplit = urlAndHash[0].split('?'); | ||||
|         let subParams; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user