Skip to content

feat: add asset existence check before replacing static URLs #1708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ exports[`TextEditor snapshots renders static images with relative paths 1`] = `
</Toast>
<TinyMceWidget
disabled={false}
editorContentHtml="eDiTablE Text with <img src="/asset+org+run+type@asset+block@img.jpg" />"
editorContentHtml="eDiTablE Text with <img src="/static/img.jpg" />"
editorRef={
{
"current": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,207 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TinyMceWidget snapshots ImageUploadModal is not rendered 1`] = `
<Fragment>
<SourceCodeModal
close={[MockFunction modal.closeModal]}
editorRef={
{
"current": {
"value": "something",
},
}
<[object Object]
value={
{
"store": {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
},
"subscription": {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
isOpen={false}
/>
<Editor
}
>
<TinyMceWidget
disabled={false}
editorConfig={
{
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
"content": undefined,
"editorContentHtml": undefined,
"editorRef": {
"current": {
"value": "something",
},
},
"editorType": "text",
"enableImageUpload": false,
"images": {
"current": [
{
"externalUrl": "/assets/sOmEaSsET",
},
],
},
"initializeEditor": undefined,
"learningContextId": "course+org+run",
"lmsEndpointUrl": "sOmEvaLue.cOm",
"minHeight": undefined,
"openImgModal": [MockFunction modal.openModal],
"openSourceCodeModal": [MockFunction modal.openModal],
"placeholder": undefined,
"selection": "hooks.selectedImage.selection",
"setEditorRef": undefined,
"setSelection": [MockFunction hooks.selectedImage.setSelection],
"staticRootUrl": undefined,
"studioEndpointUrl": "sOmEoThERvaLue.cOm",
"updateContent": [Function],
}
}
id="sOMeiD"
onEditorChange={[Function]}
/>
</Fragment>
`;

exports[`TinyMceWidget snapshots SourcecodeModal is not rendered 1`] = `
<Fragment>
<ImageUploadModal
clearSelection={[MockFunction hooks.selectedImage.clearSelection]}
close={[MockFunction modal.closeModal]}
editorRef={
{
"current": {
"value": "something",
},
}
}
editorType="problem"
editorType="text"
enableImageUpload={false}
id="sOMeiD"
images={
{
"current": [
{
"externalUrl": "/assets/sOmEaSsET",
},
],
}
}
isLibrary={true}
isOpen={false}
lmsEndpointUrl="http://localhost:18000"
selection="hooks.selectedImage.selection"
setSelection={[MockFunction hooks.selectedImage.setSelection]}
/>
<Editor
disabled={false}
editorConfig={
{
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
"content": undefined,
"editorContentHtml": undefined,
"editorRef": {
"current": {
"value": "something",
},
"sOmEaSsET": {
"staTICUrl": "/assets/sOmEaSsET",
},
"editorType": "problem",
"enableImageUpload": true,
"images": {
"current": [
{
"externalUrl": "/assets/sOmEaSsET",
},
],
},
"initializeEditor": undefined,
"learningContextId": "course+org+run",
"lmsEndpointUrl": "sOmEvaLue.cOm",
"minHeight": undefined,
"openImgModal": [MockFunction modal.openModal],
"openSourceCodeModal": [MockFunction modal.openModal],
"placeholder": undefined,
"selection": "hooks.selectedImage.selection",
"setEditorRef": undefined,
"setSelection": [MockFunction hooks.selectedImage.setSelection],
"staticRootUrl": undefined,
"studioEndpointUrl": "sOmEoThERvaLue.cOm",
"updateContent": [Function],
}
}
id="sOMeiD"
onEditorChange={[Function]}
isLibrary={false}
learningContextId="course+org+run"
lmsEndpointUrl="sOmEvaLue.cOm"
onChange={[Function]}
studioEndpointUrl="sOmEoThERvaLue.cOm"
updateContent={[Function]}
/>
</Fragment>
</[object Object]>
`;

exports[`TinyMceWidget snapshots renders as expected with default behavior 1`] = `
<Fragment>
<ImageUploadModal
clearSelection={[MockFunction hooks.selectedImage.clearSelection]}
close={[MockFunction modal.closeModal]}
editorRef={
{
"current": {
"value": "something",
},
}
}
editorType="text"
images={
{
"current": [
{
"externalUrl": "/assets/sOmEaSsET",
},
],
}
exports[`TinyMceWidget snapshots SourcecodeModal is not rendered 1`] = `
<[object Object]
value={
{
"store": {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
},
"subscription": {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
isLibrary={true}
isOpen={false}
lmsEndpointUrl="http://localhost:18000"
selection="hooks.selectedImage.selection"
setSelection={[MockFunction hooks.selectedImage.setSelection]}
/>
<SourceCodeModal
close={[MockFunction modal.closeModal]}
}
>
<TinyMceWidget
disabled={false}
editorRef={
{
"current": {
"value": "something",
},
}
}
isOpen={false}
/>
<Editor
disabled={false}
editorConfig={
editorType="problem"
enableImageUpload={true}
id="sOMeiD"
images={
{
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
"content": undefined,
"editorContentHtml": undefined,
"editorRef": {
"current": {
"value": "something",
},
"sOmEaSsET": {
"staTICUrl": "/assets/sOmEaSsET",
},
"editorType": "text",
"enableImageUpload": true,
"images": {
"current": [
{
"externalUrl": "/assets/sOmEaSsET",
},
],
},
"initializeEditor": undefined,
"learningContextId": "course+org+run",
"lmsEndpointUrl": "sOmEvaLue.cOm",
"minHeight": undefined,
"openImgModal": [MockFunction modal.openModal],
"openSourceCodeModal": [MockFunction modal.openModal],
"placeholder": undefined,
"selection": "hooks.selectedImage.selection",
"setEditorRef": undefined,
"setSelection": [MockFunction hooks.selectedImage.setSelection],
"staticRootUrl": undefined,
"studioEndpointUrl": "sOmEoThERvaLue.cOm",
"updateContent": [Function],
}
}
id="sOMeiD"
onEditorChange={[Function]}
isLibrary={false}
learningContextId="course+org+run"
lmsEndpointUrl="sOmEvaLue.cOm"
onChange={[Function]}
studioEndpointUrl="sOmEoThERvaLue.cOm"
updateContent={[Function]}
/>
</Fragment>
</[object Object]>
`;

exports[`TinyMceWidget snapshots renders as expected with default behavior 1`] = `undefined`;
25 changes: 21 additions & 4 deletions src/editors/sharedComponents/TinyMceWidget/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
useEffect,
} from 'react';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getLocale, isRtl } from '@edx/frontend-platform/i18n';
import { a11ycheckerCss } from 'frontend-components-tinymce-advanced-plugins';
import { isEmpty } from 'lodash';
import { updateCourseDetailsOverview } from '../../../schedule-and-details/data/slice';
import tinyMCEStyles from '../../data/constants/tinyMCEStyles';
import { StrictDict } from '../../utils';
import pluginConfig from './pluginConfig';
Expand Down Expand Up @@ -83,14 +85,15 @@ export const replaceStaticWithAsset = ({
learningContextId,
editorType,
lmsEndpointUrl,
validateAssetUrl = true,
}) => {
let content = initialContent;
let hasChanges = false;
const srcs = content.split(/(src="|src=&quot;|href="|href=&quot)/g).filter(
src => src.startsWith('/static') || src.startsWith('/asset'),
);
if (!isEmpty(srcs)) {
srcs.forEach(src => {
srcs.forEach(async src => {
const currentContent = content;
let staticFullUrl;
const isStatic = src.startsWith('/static/');
Expand Down Expand Up @@ -121,8 +124,18 @@ export const replaceStaticWithAsset = ({
}
if (staticFullUrl) {
const currentSrc = src.substring(0, src.indexOf('"'));
content = currentContent.replace(currentSrc, staticFullUrl);
hasChanges = true;

// check if the asset exists on the server before replacing
try {
if (validateAssetUrl) {
await getAuthenticatedHttpClient()
.get(staticFullUrl);
}
content = currentContent.replace(currentSrc, staticFullUrl);
hasChanges = true;
} catch (e) {
content = currentContent;
}
}
});
if (hasChanges) { return content; }
Expand Down Expand Up @@ -191,6 +204,7 @@ export const setupCustomBehavior = ({
setImage,
lmsEndpointUrl,
learningContextId,
dispatch,
}) => (editor) => {
// image upload button
editor.ui.registry.addButton(tinyMCE.buttons.imageUploadButton, {
Expand Down Expand Up @@ -272,7 +286,8 @@ export const setupCustomBehavior = ({
initialContent,
learningContextId,
});
if (newContent) { editor.setContent(newContent); }
// Update initialValue so the change is not taken as a user action
if (newContent) { dispatch(updateCourseDetailsOverview(newContent)); }
}
if (e.command === 'RemoveFormat') {
editor.formatter.remove('blockquote');
Expand Down Expand Up @@ -302,6 +317,7 @@ export const editorConfig = ({
learningContextId,
staticRootUrl,
enableImageUpload,
dispatch,
}) => {
const lmsEndpointUrl = getConfig().LMS_BASE_URL;
const studioEndpointUrl = getConfig().STUDIO_BASE_URL;
Expand Down Expand Up @@ -346,6 +362,7 @@ export const editorConfig = ({
content,
images,
learningContextId,
dispatch,
}),
quickbars_insert_toolbar: quickbarsInsertToolbar,
quickbars_selection_toolbar: quickbarsSelectionToolbar,
Expand Down
8 changes: 7 additions & 1 deletion src/editors/sharedComponents/TinyMceWidget/hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,12 @@ describe('TinyMceEditor hooks', () => {
const lmsEndpointUrl = getConfig().LMS_BASE_URL;
it('returns updated src for text editor to update content', () => {
const expected = `<img src="/${baseAssetUrl}@soMEImagEURl1.jpeg"/><a href="/${baseAssetUrl}@test.pdf">test</a><img src="/${baseAssetUrl}@correct.png" /><img src="/${baseAssetUrl}@correct.png" />`;
const actual = module.replaceStaticWithAsset({ initialContent, learningContextId });
const actual = module.replaceStaticWithAsset({
initialContent,
learningContextId,
validateAssetUrl: false,

});
expect(actual).toEqual(expected);
});
it('returns updated src with absolute url for expandable editor to update content', () => {
Expand All @@ -205,6 +210,7 @@ describe('TinyMceEditor hooks', () => {
editorType,
lmsEndpointUrl,
learningContextId,
validateAssetUrl: false,
});
expect(actual).toEqual(expected);
});
Expand Down
Loading