diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java index 8537f9d70275..f0524294a1c0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java @@ -431,7 +431,7 @@ public static boolean shouldShowGutenbergEditor(boolean isNewPost, String postCo public static String replaceMediaFileWithUrlInGutenbergPost(@NonNull String postContent, @NonNull String localMediaId, MediaFile mediaFile, - String siteUrl) { + @NonNull String siteUrl) { if (mediaFile != null && contentContainsGutenbergBlocks(postContent)) { MediaUploadCompletionProcessor processor = new MediaUploadCompletionProcessor(localMediaId, mediaFile, siteUrl); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt index 03e2ac4a1886..18b7a82b0ad4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt @@ -5,21 +5,18 @@ import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile class AudioBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { - override fun processBlockContentDocument(document: Document?): Boolean { - val audioElements = document?.select(AUDIO_TAG) + override fun processBlockContentDocument(document: Document): Boolean { + val audioElements = document.select(AUDIO_TAG) - audioElements?.let { elements -> - for (element in elements) { - // replaces the src attribute's local url with the remote counterpart. - element.attr(SRC_ATTRIBUTE, remoteUrl) - } - return true + for (element in audioElements) { + // replaces the src attribute's local url with the remote counterpart. + element.attr(SRC_ATTRIBUTE, remoteUrl) } - return false + return true } - override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { - val id = jsonAttributes?.get(ID_ATTRIBUTE) + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes.get(ID_ATTRIBUTE) return if (id != null && !id.isJsonNull && id.asString == localId) { jsonAttributes.apply { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt index b024416d51f4..0de94a0e19ed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt @@ -72,37 +72,37 @@ abstract class BlockProcessor internal constructor(@JvmField var localId: String * @return A string containing content with ids and urls replaced */ @JvmOverloads - fun processBlock(block: String, isSelfClosingTag: Boolean = false) = if (splitBlock(block, isSelfClosingTag)) { - if (processBlockJsonAttributes(jsonAttributes)) { - if (isSelfClosingTag) { - // return injected block - StringBuilder() - .append("") - .toString() - } else if (processBlockContentDocument(blockContentDocument)) { - // return injected block - StringBuilder() - .append("\n") - .append(blockContentDocument?.body()?.html()) // HTML parser output - .append(closingComment) - .toString() - } else { - block - } - } else { - processInnerBlock(block) // delegate to inner blocks if needed + fun processBlock(block: String, isSelfClosingTag: Boolean = false) = when { + !splitBlock(block, isSelfClosingTag) -> block // leave block unchanged + jsonAttributes?.let { !processBlockJsonAttributes(it) } == true -> { + // delegate to inner blocks if needed + processInnerBlock(block) + } + isSelfClosingTag -> { + // return injected block + StringBuilder() + .append("") + .toString() } - } else { - // leave block unchanged - block + + blockContentDocument?.let { processBlockContentDocument(it) } == true -> { + // return injected block + StringBuilder() + .append("\n") + .append(blockContentDocument?.body()?.html()) // HTML parser output + .append(closingComment) + .toString() + } + + else -> block // leave block unchanged } fun addIntPropertySafely(jsonAttributes: JsonObject, propertyName: String, value: String) = try { @@ -121,7 +121,7 @@ abstract class BlockProcessor internal constructor(@JvmField var localId: String * @param document The document to be mutated to make the necessary replacements * @return A boolean value indicating whether or not the block contents should be replaced */ - abstract fun processBlockContentDocument(document: Document?): Boolean + abstract fun processBlockContentDocument(document: Document): Boolean /** * All concrete implementations must implement this method for the particular block type. The jsonAttributes object @@ -134,7 +134,7 @@ abstract class BlockProcessor internal constructor(@JvmField var localId: String * @param jsonAttributes the attributes object used to check for a match with the local id, and mutated if necessary * @return */ - abstract fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean + abstract fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean /** * This method can be optionally overridden by concrete implementations to delegate further processing via recursion diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java deleted file mode 100644 index 1e8fd766a95c..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; - -import org.wordpress.android.util.helpers.MediaFile; - -import java.util.HashMap; -import java.util.Map; - -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.AUDIO; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.COVER; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.FILE; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.GALLERY; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.IMAGE; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.MEDIA_TEXT; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.VIDEO; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.VIDEOPRESS; - -class BlockProcessorFactory { - private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - private final Map mMediaBlockTypeBlockProcessorMap; - - /** - * This factory initializes block processors for all media block types and provides a method to retrieve a block - * processor instance for a given block type. - */ - BlockProcessorFactory(MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { - mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - mMediaBlockTypeBlockProcessorMap = new HashMap<>(); - } - - /** - * @param localId The local media id that needs replacement - * @param mediaFile The mediaFile containing the remote id and remote url - * @param siteUrl The site url - used to generate the attachmentPage url - * @return The factory instance - useful for chaining this method upon instantiation - */ - BlockProcessorFactory init(@NonNull String localId, @NonNull MediaFile mediaFile, String siteUrl) { - mMediaBlockTypeBlockProcessorMap.put(IMAGE, new ImageBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(VIDEOPRESS, new VideoPressBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(VIDEO, new VideoBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(MEDIA_TEXT, new MediaTextBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(GALLERY, new GalleryBlockProcessor(localId, mediaFile, siteUrl, - mMediaUploadCompletionProcessor)); - mMediaBlockTypeBlockProcessorMap.put(COVER, new CoverBlockProcessor(localId, mediaFile, - mMediaUploadCompletionProcessor)); - mMediaBlockTypeBlockProcessorMap.put(FILE, new FileBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(AUDIO, new AudioBlockProcessor(localId, mediaFile)); - - return this; - } - - /** - * Retrieves the block processor instance for the given media block type. - * - * @param blockType The media block type for which to provide a {@link BlockProcessor} - * @return The {@link BlockProcessor} for the given media block type - */ - BlockProcessor getProcessorForMediaBlockType(MediaBlockType blockType) { - return mMediaBlockTypeBlockProcessorMap.get(blockType); - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt new file mode 100644 index 000000000000..98a34a391d75 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt @@ -0,0 +1,37 @@ +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import org.wordpress.android.util.helpers.MediaFile + +/** + * This factory initializes block processors for all media block types and provides a method to retrieve a block + * processor instance for a given block type. + * @param localId The local media id that needs replacement + * @param mediaFile The mediaFile containing the remote id and remote url` + * @param siteUrl The site url - used to generate the attachmentPage url + * @return The factory instance - useful for chaining this method upon instantiation + */ +internal class BlockProcessorFactory( + mediaUploadCompletionProcessor: MediaUploadCompletionProcessor, + localId: String, + mediaFile: MediaFile, + siteUrl: String +) { + private val mediaBlockTypeBlockProcessorMap = hashMapOf( + MediaBlockType.IMAGE to ImageBlockProcessor(localId, mediaFile), + MediaBlockType.VIDEOPRESS to VideoPressBlockProcessor(localId, mediaFile), + MediaBlockType.VIDEO to VideoBlockProcessor(localId, mediaFile), + MediaBlockType.MEDIA_TEXT to MediaTextBlockProcessor(localId, mediaFile), + MediaBlockType.GALLERY to GalleryBlockProcessor(localId, mediaFile, siteUrl, mediaUploadCompletionProcessor), + MediaBlockType.COVER to CoverBlockProcessor(localId, mediaFile, mediaUploadCompletionProcessor), + MediaBlockType.FILE to FileBlockProcessor(localId, mediaFile), + MediaBlockType.AUDIO to AudioBlockProcessor(localId, mediaFile) + ) + + /** + * Retrieves the block processor instance for the given media block type. + * + * @param blockType The media block type for which to provide a [BlockProcessor] + * @return The [BlockProcessor] for the given media block type + */ + fun getProcessorForMediaBlockType(blockType: MediaBlockType) = mediaBlockTypeBlockProcessorMap[blockType] +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java deleted file mode 100644 index d9ab600428cd..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class CoverBlockProcessor extends BlockProcessor { - private boolean mHasVideoBackground = false; - - /** - * Template pattern used to match and splice cover inner blocks - */ - private static final Pattern PATTERN_COVER_INNER = Pattern.compile(new StringBuilder() - .append("(^.*?
\\s*)") - .append("(.*)") // inner block contents - .append("(\\s*
\\s*\\s*.*)").toString(), Pattern.DOTALL); - - /** - * Pattern to match background-image url in cover block html content - */ - private static final Pattern PATTERN_BACKGROUND_IMAGE_URL = Pattern.compile( - "background-image:\\s*url\\([^\\)]+\\)"); - - private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - - public CoverBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, - MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { - super(localId, mediaFile); - mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - } - - @NonNull - @Override - public String processInnerBlock(@NonNull String block) { - Matcher innerMatcher = PATTERN_COVER_INNER.matcher(block); - boolean innerCapturesFound = innerMatcher.find(); - - // process inner contents recursively - if (innerCapturesFound) { - String innerProcessed = mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)); // - return new StringBuilder() - .append(innerMatcher.group(1)) - .append(innerProcessed) - .append(innerMatcher.group(3)) - .toString(); - } - - return block; - } - - @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("id"); - if (id != null && !id.isJsonNull() && id.getAsInt() == Integer.parseInt(localId, 10)) { - addIntPropertySafely(jsonAttributes, "id", remoteId); - - jsonAttributes.addProperty("url", remoteUrl); - - // check if background type is video - JsonElement backgroundType = jsonAttributes.get("backgroundType"); - mHasVideoBackground = backgroundType != null && !backgroundType.isJsonNull() && "video".equals( - backgroundType.getAsString()); - return true; - } - - return false; - } - - @Override - public boolean processBlockContentDocument(@Nullable Document document) { - // select cover block div - Element targetDiv = document.selectFirst(".wp-block-cover"); - - // if a match is found, proceed with replacement - if (targetDiv != null) { - if (mHasVideoBackground) { - Element videoElement = targetDiv.selectFirst("video"); - if (videoElement != null) { - videoElement.attr("src", remoteUrl); - } else { - return false; - } - } else { - // replace background-image url in style attribute - String style = PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDiv.attr("style")).replaceFirst( - String.format("background-image:url(%1$s)", remoteUrl)); - targetDiv.attr("style", style); - } - - // return injected block - return true; - } - - return false; - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt new file mode 100644 index 000000000000..afdbb466c5fe --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt @@ -0,0 +1,92 @@ +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile +import java.util.Locale +import java.util.regex.Pattern + +class CoverBlockProcessor( + localId: String, + mediaFile: MediaFile, + private val mediaUploadCompletionProcessor: MediaUploadCompletionProcessor +) : BlockProcessor(localId, mediaFile) { + private var hasVideoBackground = false + + override fun processInnerBlock(block: String): String { + val innerMatcher = PATTERN_COVER_INNER.matcher(block) + val innerCapturesFound = innerMatcher.find() + + // process inner contents recursively + if (innerCapturesFound) { + val innerProcessed = mediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) + return StringBuilder() + .append(innerMatcher.group(1)) + .append(innerProcessed) + .append(innerMatcher.group(GROUP_3)) + .toString() + } + + return block + } + + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["id"] + if (id != null && !id.isJsonNull && id.asInt == localId.toInt(RADIX)) { + addIntPropertySafely(jsonAttributes, "id", remoteId) + + jsonAttributes.addProperty("url", remoteUrl) + + // check if background type is video + val backgroundType = jsonAttributes["backgroundType"] + hasVideoBackground = backgroundType != null && + !backgroundType.isJsonNull && + "video" == backgroundType.asString + return true + } + + return false + } + + override fun processBlockContentDocument(document: Document): Boolean { + // select cover block div + val targetDiv = document.selectFirst(".wp-block-cover") + + // if a match is found, proceed with replacement + return targetDiv?.let { targetDivElement -> + if (hasVideoBackground) { + val videoElement = targetDivElement.selectFirst("video") + videoElement?.attr("src", remoteUrl) ?: return false + } else { + // replace background-image url in style attribute + val style = PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDivElement.attr("style")) + .replaceFirst(String.format(Locale.getDefault(), "background-image:url(%1\$s)", remoteUrl)) + targetDivElement.attr("style", style) + } + + // return injected block + true + } ?: false + } + + companion object { + /** + * Template pattern used to match and splice cover inner blocks + */ + private val PATTERN_COVER_INNER = Pattern.compile( + StringBuilder() + .append("(^.*?
\\s*)") + .append("(.*)") // inner block contents + .append("(\\s*
\\s*\\s*.*)") + .toString(), + Pattern.DOTALL + ) + + /** + * Pattern to match background-image url in cover block html content + */ + private val PATTERN_BACKGROUND_IMAGE_URL = Pattern.compile("background-image:\\s*url\\([^)]+\\)") + private const val GROUP_3 = 3 + private const val RADIX = 10 + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt index bc33b669ba65..0b6997e4dac6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt @@ -9,21 +9,18 @@ import org.wordpress.android.util.helpers.MediaFile * remote url for all a tags present within the wp:file block. */ class FileBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { - override fun processBlockContentDocument(document: Document?): Boolean { - val hyperLinkTargets = document?.select(HYPERLINK_TAG) + override fun processBlockContentDocument(document: Document): Boolean { + val hyperLinkTargets = document.select(HYPERLINK_TAG) - hyperLinkTargets?.let { - for (target in hyperLinkTargets) { - // replaces the href attribute's local url with the remote counterpart. - target.attr(HREF_ATTRIBUTE, remoteUrl) - } - return true + for (target in hyperLinkTargets) { + // replaces the href attribute's local url with the remote counterpart. + target.attr(HREF_ATTRIBUTE, remoteUrl) } - return false + return true } - override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { - val id = jsonAttributes?.get(ID_ATTRIBUTE) + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes.get(ID_ATTRIBUTE) return if (id != null && !id.isJsonNull && id.asString == localId) { jsonAttributes.apply { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java deleted file mode 100644 index a1a28992c574..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.helpers.MediaFile; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.wordpress.android.util.AppLog.T.MEDIA; - -public class GalleryBlockProcessor extends BlockProcessor { - private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - private String mAttachmentPageUrl; - private String mLinkTo; - - /** - * Query selector for selecting the img element from gallery which needs processing - */ - private String mGalleryImageQuerySelector; - - /** - * Template pattern used to match and splice inner image blocks in the refactored gallery format - */ - private static final Pattern PATTERN_GALLERY_INNER = Pattern.compile(new StringBuilder() - .append("(^.*?
\\s*)") - .append("(.*)") // inner block contents - .append("(\\s*
\\s*.*)").toString(), Pattern.DOTALL); - - public GalleryBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, String siteUrl, - MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { - super(localId, mediaFile); - mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - mGalleryImageQuerySelector = new StringBuilder() - .append("img[data-id=\"") - .append(localId) - .append("\"]") - .toString(); - mAttachmentPageUrl = mediaFile.getAttachmentPageURL(siteUrl); - } - - @Override - public boolean processBlockContentDocument(@Nullable Document document) { - // select image element with our local id - Element targetImg = document.select(mGalleryImageQuerySelector).first(); - - // if a match is found, proceed with replacement - if (targetImg != null) { - // replace attributes - targetImg.attr("src", remoteUrl); - targetImg.attr("data-id", remoteId); - targetImg.attr("data-full-url", remoteUrl); - targetImg.attr("data-link", mAttachmentPageUrl); - - // replace class - targetImg.removeClass("wp-image-" + localId); - targetImg.addClass("wp-image-" + remoteId); - - // set parent anchor href if necessary - Element parent = targetImg.parent(); - if (parent != null && parent.is("a") && mLinkTo != null) { - switch (mLinkTo) { - case "file": - parent.attr("href", remoteUrl); - break; - case "post": - parent.attr("href", mAttachmentPageUrl); - break; - default: - return false; - } - } - - // return injected block - return true; - } - - return false; - } - - @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { - // The new format does not have an `ids` attributes, so returning false here will defer to recursive processing - JsonArray ids = jsonAttributes.getAsJsonArray("ids"); - if (ids == null || ids.isJsonNull()) { - return false; - } - JsonElement linkTo = jsonAttributes.get("linkTo"); - if (linkTo != null && !linkTo.isJsonNull()) { - mLinkTo = linkTo.getAsString(); - } - for (int i = 0; i < ids.size(); i++) { - JsonElement id = ids.get(i); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - try { - ids.set(i, new JsonPrimitive(Integer.parseInt(remoteId, 10))); - } catch (NumberFormatException e) { - AppLog.e(MEDIA, e.getMessage()); - } - return true; - } - } - return false; - } - - @NonNull - @Override - public String processInnerBlock(@NonNull String block) { - Matcher innerMatcher = PATTERN_GALLERY_INNER.matcher(block); - boolean innerCapturesFound = innerMatcher.find(); - - // process inner contents recursively - if (innerCapturesFound) { - String innerProcessed = mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)); // - return new StringBuilder() - .append(innerMatcher.group(1)) - .append(innerProcessed) - .append(innerMatcher.group(3)) - .toString(); - } - - return block; - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt new file mode 100644 index 000000000000..fd325ebe8f19 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt @@ -0,0 +1,114 @@ +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import org.jsoup.nodes.Document +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.helpers.MediaFile +import java.util.regex.Pattern + +class GalleryBlockProcessor( + localId: String, + mediaFile: MediaFile, + siteUrl: String, + private val mediaUploadCompletionProcessor: MediaUploadCompletionProcessor +) : BlockProcessor(localId, mediaFile) { + private val attachmentPageUrl = mediaFile.getAttachmentPageURL(siteUrl) + private var linkTo: String? = null + + /** + * Query selector for selecting the img element from gallery which needs processing + */ + private val galleryImageQuerySelector = StringBuilder() + .append("img[data-id=\"") + .append(localId) + .append("\"]") + .toString() + + override fun processBlockContentDocument(document: Document): Boolean { + // select image element with our local id + val targetImg = document.select(galleryImageQuerySelector).first() + + // if a match is found, proceed with replacement + return targetImg?.let { + // replace attributes + it.attr("src", remoteUrl) + it.attr("data-id", remoteId) + it.attr("data-full-url", remoteUrl) + it.attr("data-link", attachmentPageUrl) + + // replace class + it.removeClass("wp-image-$localId") + it.addClass("wp-image-$remoteId") + + // set parent anchor href if necessary + val parent = it.parent() + if (parent != null && parent.`is`("a") && linkTo != null) { + when (linkTo) { + "file" -> parent.attr("href", remoteUrl) + "post" -> parent.attr("href", attachmentPageUrl) + else -> return false + } + } + + // return injected block + true + } ?: false + } + + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + // The new format does not have an `ids` attributes, so returning false here will defer to recursive processing + val ids = jsonAttributes.getAsJsonArray("ids") + + if (ids != null && !ids.isJsonNull) { + val linkTo = jsonAttributes["linkTo"] + if (linkTo != null && !linkTo.isJsonNull) { + this.linkTo = linkTo.asString + } + + ids.forEachIndexed { index, jsonElement -> + if (jsonElement != null && !jsonElement.isJsonNull && jsonElement.asString == localId) { + try { + ids[index] = JsonPrimitive(remoteId.toInt(RADIX)) + } catch (e: NumberFormatException) { + AppLog.e(AppLog.T.MEDIA, e.message) + } + return true + } + } + } + return false + } + + override fun processInnerBlock(block: String): String { + val innerMatcher = PATTERN_GALLERY_INNER.matcher(block) + val innerCapturesFound = innerMatcher.find() + + // process inner contents recursively + if (innerCapturesFound) { + val innerProcessed = mediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) + return StringBuilder() + .append(innerMatcher.group(1)) + .append(innerProcessed) + .append(innerMatcher.group(GROUP_3)) + .toString() + } + + return block + } + + companion object { + /** + * Template pattern used to match and splice inner image blocks in the refactored gallery format + */ + private val PATTERN_GALLERY_INNER = Pattern.compile( + StringBuilder() + .append("(^.*?
\\s*)") + .append("(.*)") // inner block contents + .append("(\\s*
\\s*.*)").toString(), + Pattern.DOTALL + ) + private const val RADIX = 10 + private const val GROUP_3 = 3 + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java deleted file mode 100644 index 0811bf2ac128..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - - -public class ImageBlockProcessor extends BlockProcessor { - public ImageBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile) { - super(localId, mediaFile); - } - - @Override - public boolean processBlockContentDocument(@Nullable Document document) { - // select image element with our local id - Element targetImg = document.select("img").first(); - - // if a match is found, proceed with replacement - if (targetImg != null) { - // replace attributes - targetImg.attr("src", remoteUrl); - - // replace class - targetImg.removeClass("wp-image-" + localId); - targetImg.addClass("wp-image-" + remoteId); - - return true; - } - - return false; - } - - @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("id"); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - addIntPropertySafely(jsonAttributes, "id", remoteId); - return true; - } - return false; - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt new file mode 100644 index 000000000000..5165596d47f1 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt @@ -0,0 +1,34 @@ +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile + +class ImageBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { + override fun processBlockContentDocument(document: Document): Boolean { + // select image element with our local id + val targetImg = document.select("img").first() + + // if a match is found, proceed with replacement + return targetImg?.let { + // replace attributes + it.attr("src", remoteUrl) + + // replace class + it.removeClass("wp-image-$localId") + it.addClass("wp-image-$remoteId") + + true + } ?: false + } + + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["id"] + return if (id != null && !id.isJsonNull && id.asString == localId) { + addIntPropertySafely(jsonAttributes, "id", remoteId) + true + } else { + false + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java deleted file mode 100644 index f57e6d96616c..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - -public class MediaTextBlockProcessor extends BlockProcessor { - public MediaTextBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile) { - super(localId, mediaFile); - } - - @Override - public boolean processBlockContentDocument(@Nullable Document document) { - // select image element with our local id - Element targetImg = document.select("img").first(); - - // if a match is found for img, proceed with replacement - if (targetImg != null) { - // replace attributes - targetImg.attr("src", remoteUrl); - - // replace class - targetImg.removeClass("wp-image-" + localId); - targetImg.addClass("wp-image-" + remoteId); - - // return injected block - return true; - } else { // try video - // select video element with our local id - Element targetVideo = document.select("video").first(); - - // if a match is found for video, proceed with replacement - if (targetVideo != null) { - // replace attribute - targetVideo.attr("src", remoteUrl); - - // return injected block - return true; - } - } - - return false; - } - - @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("mediaId"); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - addIntPropertySafely(jsonAttributes, "mediaId", remoteId); - return true; - } - - return false; - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt new file mode 100644 index 000000000000..810c59709c54 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt @@ -0,0 +1,47 @@ +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile + +class MediaTextBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { + override fun processBlockContentDocument(document: Document): Boolean { + // select image element with our local id + val targetImg = document.select("img").first() + + // if a match is found for img, proceed with replacement + return if (targetImg != null) { + // replace attributes + targetImg.attr("src", remoteUrl) + + // replace class + targetImg.removeClass("wp-image-$localId") + targetImg.addClass("wp-image-$remoteId") + + // return injected block + true + } else { // try video + // select video element with our local id + val targetVideo = document.select("video").first() + + // if a match is found for video, proceed with replacement + targetVideo?.let { + // replace attribute + targetVideo.attr("src", remoteUrl) + + // return injected block + true + } ?: false + } + } + + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["mediaId"] + return if (id != null && !id.isJsonNull && id.asString == localId) { + addIntPropertySafely(jsonAttributes, "mediaId", remoteId) + true + } else { + false + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java index 51f3f7790161..9cf13a536657 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java @@ -21,9 +21,9 @@ public class MediaUploadCompletionProcessor { * @param mediaFile The mediaFile containing the remote id and remote url * @param siteUrl The site url - used to generate the attachmentPage url */ - public MediaUploadCompletionProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, String siteUrl) { - mBlockProcessorFactory = new BlockProcessorFactory(this) - .init(localId, mediaFile, siteUrl); + public MediaUploadCompletionProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, + @NonNull String siteUrl) { + mBlockProcessorFactory = new BlockProcessorFactory(this, localId, mediaFile, siteUrl); } /** @@ -82,10 +82,12 @@ public String processContent(String content) { @NonNull private String processBlock(@NonNull String block, Boolean isSelfClosingTag) { final MediaBlockType blockType = MediaBlockType.detectBlockType(block); - final BlockProcessor blockProcessor = mBlockProcessorFactory.getProcessorForMediaBlockType(blockType); - if (blockProcessor != null) { - return blockProcessor.processBlock(block, isSelfClosingTag); + if (blockType != null) { + final BlockProcessor blockProcessor = mBlockProcessorFactory.getProcessorForMediaBlockType(blockType); + if (blockProcessor != null) { + return blockProcessor.processBlock(block, isSelfClosingTag); + } } return block; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java deleted file mode 100644 index ac630bdb8b80..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - -public class VideoBlockProcessor extends BlockProcessor { - public VideoBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile) { - super(localId, mediaFile); - } - - @Override - public boolean processBlockContentDocument(@Nullable Document document) { - // select video element with our local id - Element targetVideo = document.select("video").first(); - - // if a match is found for video, proceed with replacement - if (targetVideo != null) { - // replace attribute - targetVideo.attr("src", remoteUrl); - - // return injected block - return true; - } - - return false; - } - - @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("id"); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - addIntPropertySafely(jsonAttributes, "id", remoteId); - return true; - } - return false; - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt new file mode 100644 index 000000000000..4ede032a5b20 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt @@ -0,0 +1,31 @@ +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile + +class VideoBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { + override fun processBlockContentDocument(document: Document): Boolean { + // select video element with our local id + val targetVideo = document.select("video").first() + + // if a match is found for video, proceed with replacement + return targetVideo?.let { + // replace attribute + targetVideo.attr("src", remoteUrl) + + // return injected block + true + } ?: false + } + + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["id"] + return if (id != null && !id.isJsonNull && id.asString == localId) { + addIntPropertySafely(jsonAttributes, "id", remoteId) + true + } else { + false + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt index 1f4ef2086860..7e5a6168c03b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt @@ -5,13 +5,13 @@ import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile class VideoPressBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { - override fun processBlockContentDocument(document: Document?): Boolean { + override fun processBlockContentDocument(document: Document): Boolean { return false } - override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { - val id = jsonAttributes?.get(ID_ATTRIBUTE) - val src = jsonAttributes?.get(SRC_ATTRIBUTE)?.asString + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes.get(ID_ATTRIBUTE) + val src = jsonAttributes.get(SRC_ATTRIBUTE)?.asString return if (id != null && !id.isJsonNull && id.asString == localId) { jsonAttributes.apply {