diff --git a/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java b/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java index 21214115e..25c4ae34c 100644 --- a/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java +++ b/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java @@ -13,6 +13,8 @@ import org.thymeleaf.processor.element.IElementTagStructureHandler; */ public interface CommentWidget extends ExtensionPoint { + String ENABLE_COMMENT_ATTRIBUTE = CommentWidget.class.getName() + ".ENABLE"; + void render(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structureHandler); } diff --git a/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java b/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java index b4db33e1d..e27c15608 100644 --- a/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java +++ b/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java @@ -1,12 +1,20 @@ package run.halo.app.theme.dialect; +import static org.apache.commons.lang3.BooleanUtils.isFalse; +import static org.apache.commons.lang3.BooleanUtils.isTrue; + +import java.util.Optional; import org.springframework.context.ApplicationContext; +import org.springframework.core.convert.support.DefaultConversionService; +import org.thymeleaf.context.Contexts; import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.context.IWebContext; import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.processor.element.AbstractElementTagProcessor; import org.thymeleaf.processor.element.IElementTagStructureHandler; import org.thymeleaf.spring6.context.SpringContextUtils; import org.thymeleaf.templatemode.TemplateMode; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.plugin.extensionpoint.ExtensionGetter; @@ -19,6 +27,7 @@ import run.halo.app.plugin.extensionpoint.ExtensionGetter; */ public class CommentElementTagProcessor extends AbstractElementTagProcessor { + public static final String COMMENT_ENABLED_MODEL_ATTRIBUTE = "haloCommentEnabled"; private static final String TAG_NAME = "comment"; private static final int PRECEDENCE = 1000; @@ -42,16 +51,49 @@ public class CommentElementTagProcessor extends AbstractElementTagProcessor { @Override protected void doProcess(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structureHandler) { - final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context); - ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class); - CommentWidget commentWidget = - extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class) - .next() - .block(); - if (commentWidget == null) { + getCommentWidget(context).ifPresentOrElse(commentWidget -> { + populateAllowCommentAttribute(context, true); + commentWidget.render(context, tag, structureHandler); + }, () -> { + populateAllowCommentAttribute(context, false); structureHandler.replaceWith("", false); - return; + }); + } + + static void populateAllowCommentAttribute(ITemplateContext context, boolean allowComment) { + if (Contexts.isWebContext(context)) { + IWebContext webContext = Contexts.asWebContext(context); + webContext.getExchange() + .setAttributeValue(COMMENT_ENABLED_MODEL_ATTRIBUTE, allowComment); } - commentWidget.render(context, tag, structureHandler); + } + + static Optional getCommentWidget(ITemplateContext context) { + final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context); + SystemConfigurableEnvironmentFetcher environmentFetcher = + appCtx.getBean(SystemConfigurableEnvironmentFetcher.class); + var commentSetting = environmentFetcher.fetchComment() + .blockOptional() + .orElseThrow(); + var globalEnabled = isTrue(commentSetting.getEnable()); + if (!globalEnabled) { + return Optional.empty(); + } + + if (Contexts.isWebContext(context)) { + IWebContext webContext = Contexts.asWebContext(context); + Object attributeValue = webContext.getExchange() + .getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE); + Boolean enabled = DefaultConversionService.getSharedInstance() + .convert(attributeValue, Boolean.class); + if (isFalse(enabled)) { + return Optional.empty(); + } + } + + ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class); + return extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class) + .next() + .blockOptional(); } } diff --git a/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java b/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java index 1cd3cbb9d..892006c78 100644 --- a/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java +++ b/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java @@ -6,6 +6,7 @@ import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; import run.halo.app.extension.Scheme; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.dialect.CommentWidget; import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.SinglePageVo; @@ -32,6 +33,7 @@ public abstract class ModelMapUtils { model.put("groupVersionKind", POST_SCHEME.groupVersionKind()); model.put("plural", POST_SCHEME.plural()); model.put("post", postVo); + model.put(CommentWidget.ENABLE_COMMENT_ATTRIBUTE, postVo.getSpec().getAllowComment()); return model; } @@ -48,6 +50,7 @@ public abstract class ModelMapUtils { model.put("plural", SINGLE_PAGE_SCHEME.plural()); model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue()); model.put("singlePage", pageVo); + model.put(CommentWidget.ENABLE_COMMENT_ATTRIBUTE, pageVo.getSpec().getAllowComment()); return model; } } diff --git a/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java b/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java index c93bdf373..d8245cc46 100644 --- a/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java +++ b/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java @@ -2,6 +2,9 @@ package run.halo.app.theme.dialect; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Map; @@ -16,6 +19,7 @@ import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.context.WebEngineContext; import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.processor.element.IElementTagStructureHandler; import org.thymeleaf.spring6.dialect.SpringStandardDialect; @@ -23,7 +27,11 @@ import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext; import org.thymeleaf.templateresolver.StringTemplateResolver; import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.StringTemplateResource; +import org.thymeleaf.web.IWebExchange; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; import run.halo.app.plugin.ExtensionComponentsFinder; import run.halo.app.plugin.extensionpoint.ExtensionGetter; @@ -44,6 +52,9 @@ class CommentElementTagProcessorTest { @Mock private ExtensionGetter extensionGetter; + @Mock + private SystemConfigurableEnvironmentFetcher environmentFetcher; + private TemplateEngine templateEngine; @BeforeEach @@ -52,7 +63,7 @@ class CommentElementTagProcessorTest { templateEngine = new TemplateEngine(); templateEngine.setDialects(Set.of(haloProcessorDialect, new SpringStandardDialect())); templateEngine.addTemplateResolver(new TestTemplateResolver()); - when(applicationContext.getBean(eq(ExtensionGetter.class))) + lenient().when(applicationContext.getBean(eq(ExtensionGetter.class))) .thenReturn(extensionGetter); } @@ -60,6 +71,13 @@ class CommentElementTagProcessorTest { void doProcess() { Context context = getContext(); + when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class))) + .thenReturn(environmentFetcher); + var commentSetting = mock(SystemSetting.Comment.class); + when(environmentFetcher.fetchComment()) + .thenReturn(Mono.just(commentSetting)); + when(commentSetting.getEnable()).thenReturn(true); + when(extensionGetter.getEnabledExtensionByDefinition(eq(CommentWidget.class))) .thenReturn(Flux.empty()); String result = templateEngine.process("commentWidget", context); @@ -87,6 +105,74 @@ class CommentElementTagProcessorTest { """); } + @Test + void getCommentWidget() { + when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class))) + .thenReturn(environmentFetcher); + SystemSetting.Comment commentSetting = mock(SystemSetting.Comment.class); + when(environmentFetcher.fetchComment()) + .thenReturn(Mono.just(commentSetting)); + + CommentWidget commentWidget = mock(CommentWidget.class); + when(extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class)) + .thenReturn(Flux.just(commentWidget)); + WebEngineContext webContext = mock(WebEngineContext.class); + var evaluationContext = mock(ThymeleafEvaluationContext.class); + when(webContext.getVariable( + eq(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME))) + .thenReturn(evaluationContext); + when(evaluationContext.getApplicationContext()).thenReturn(applicationContext); + IWebExchange webExchange = mock(IWebExchange.class); + when(webContext.getExchange()).thenReturn(webExchange); + + // comment disabled + when(commentSetting.getEnable()).thenReturn(true); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isTrue(); + + // comment enabled + when(commentSetting.getEnable()).thenReturn(false); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isFalse(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is true + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn(true); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isTrue(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is false + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn(false); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isFalse(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is null + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn(null); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isTrue(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is 'false' + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn("false"); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isFalse(); + } + + @Test + void populateAllowCommentAttribute() { + WebEngineContext webContext = mock(WebEngineContext.class); + IWebExchange webExchange = mock(IWebExchange.class); + when(webContext.getExchange()).thenReturn(webExchange); + + CommentElementTagProcessor.populateAllowCommentAttribute(webContext, true); + verify(webExchange).setAttributeValue( + eq(CommentElementTagProcessor.COMMENT_ENABLED_MODEL_ATTRIBUTE), eq(true)); + + CommentElementTagProcessor.populateAllowCommentAttribute(webContext, false); + verify(webExchange).setAttributeValue( + eq(CommentElementTagProcessor.COMMENT_ENABLED_MODEL_ATTRIBUTE), eq(false)); + } + static class DefaultCommentWidget implements CommentWidget { @Override