From 2361669bb63fd8f64e758ee684650b8f31fee833 Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 2 Mar 2023 13:53:06 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Added=20media=20file=20support=20to?= =?UTF-8?q?=20the=20zip=20importer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs https://github.com/TryGhost/Toolbox/issues/523 - When migrating or importing ZIP files into Ghost there's often a need to include more than just post content and images. - When media files are present in the imported zip file they are now copied across and processed along with the rest of import files: json, images, csvs, etc. - The importer also searches for use of the media files in the imported "posts" substituting the links with local ones - The media importer recognizes media files inside of "media" or "content/media" folders present in the zip. The supported media file extensions are same as for media upload widget: ".mp4", ".webm", ".ogv", ".mp3", ".wav", ".ogg", ".m4a" with following content-types: "video/mp4", "video/webm", "video/ogg", "audio/mpeg", "audio/vnd.wav", "audio/wave", "audio/wav", "audio/x-wav", "audio/ogg", "audio/mp4", "audio/x-m4a" --- .../server/data/importer/import-manager.js | 7 ++- .../importer/importers/ContentFileImporter.js | 46 ++++++++++++++----- .../unit/server/data/importer/index.test.js | 9 +++- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/ghost/core/core/server/data/importer/import-manager.js b/ghost/core/core/server/data/importer/import-manager.js index afb00c8080..f6cd379f27 100644 --- a/ghost/core/core/server/data/importer/import-manager.js +++ b/ghost/core/core/server/data/importer/import-manager.js @@ -64,10 +64,15 @@ class ImportManager { type: 'images', store: imageStorage }); + const mediaImporter = new ContentFileImporter({ + type: 'media', + store: mediaStorage + }); + /** * @type {Importer[]} importers */ - this.importers = [imageImporter, RevueImporter, DataImporter]; + this.importers = [imageImporter, mediaImporter, RevueImporter, DataImporter]; /** * @type {Handler[]} diff --git a/ghost/core/core/server/data/importer/importers/ContentFileImporter.js b/ghost/core/core/server/data/importer/importers/ContentFileImporter.js index a690930dc0..29bf6237ba 100644 --- a/ghost/core/core/server/data/importer/importers/ContentFileImporter.js +++ b/ghost/core/core/server/data/importer/importers/ContentFileImporter.js @@ -15,14 +15,21 @@ replaceImage = function (markdown, image) { return markdown.replace(regex, image.newPath); }; -preProcessPosts = function (data, image) { +/** + * @param {Object} data + * @param {Object[]} data.posts + * @param {Object} contentFile + * @param {String} contentFile.originalPath + * @param {String} contentFile.newPath + */ +preProcessPosts = function (data, contentFile) { _.each(data.posts, function (post) { - post.markdown = replaceImage(post.markdown, image); + post.markdown = replaceImage(post.markdown, contentFile); if (post.html) { - post.html = replaceImage(post.html, image); + post.html = replaceImage(post.html, contentFile); } if (post.feature_image) { - post.feature_image = replaceImage(post.feature_image, image); + post.feature_image = replaceImage(post.feature_image, contentFile); } }); }; @@ -56,7 +63,7 @@ class ContentFileImporter { /** * * @param {Object} deps - * @param {'images'} deps.type - importer type + * @param {'images' | 'media'} deps.type - importer type * @param {import('ghost-storage-base')} deps.store */ constructor(deps) { @@ -65,15 +72,30 @@ class ContentFileImporter { } preProcess(importData) { - if (importData.images && importData.data) { - _.each(importData.images, function (image) { - preProcessPosts(importData.data.data, image); - preProcessTags(importData.data.data, image); - preProcessUsers(importData.data.data, image); - }); + if (this.type === 'images') { + if (importData.images && importData.data){ + _.each(importData.images, function (image) { + preProcessPosts(importData.data.data, image); + preProcessTags(importData.data.data, image); + preProcessUsers(importData.data.data, image); + }); + } + + importData.preProcessedByImage = true; + } + + // @NOTE: the type === 'media' check does not belong here and should be abstracted away + // to make this importer more generic + if (this.type === 'media') { + if (importData.media && importData.data) { + _.each(importData.media, function (file) { + preProcessPosts(importData.data.data, file); + }); + } + + importData.preProcessedByMedia = true; } - importData.preProcessedByImage = true; return importData; } diff --git a/ghost/core/test/unit/server/data/importer/index.test.js b/ghost/core/test/unit/server/data/importer/index.test.js index 3ed7f657b1..5c7bda6558 100644 --- a/ghost/core/test/unit/server/data/importer/index.test.js +++ b/ghost/core/test/unit/server/data/importer/index.test.js @@ -30,7 +30,7 @@ describe('Importer', function () { describe('ImportManager', function () { it('has the correct interface', function () { ImportManager.handlers.should.be.instanceof(Array).and.have.lengthOf(5); - ImportManager.importers.should.be.instanceof(Array).and.have.lengthOf(3); + ImportManager.importers.should.be.instanceof(Array).and.have.lengthOf(4); ImportManager.loadFile.should.be.instanceof(Function); ImportManager.preProcess.should.be.instanceof(Function); ImportManager.doImport.should.be.instanceof(Function); @@ -391,7 +391,11 @@ describe('Importer', function () { describe('preProcess', function () { // preProcess can modify the data prior to importing it('calls the DataImporter preProcess method', function (done) { - const input = {data: {}, images: []}; + const input = { + data: {}, + images: [], + media: [] + }; // pass a copy so that input doesn't get modified const inputCopy = _.cloneDeep(input); @@ -412,6 +416,7 @@ describe('Importer', function () { output.should.not.equal(input); output.should.have.property('preProcessedByData', true); output.should.have.property('preProcessedByImage', true); + output.should.have.property('preProcessedByMedia', true); done(); }).catch(done); });