Skip to content
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

fix: incorrect scope for haloCommentEnabled template variable #4385

Merged
merged 1 commit into from Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,33 +1,21 @@
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;


/**
* <p>Comment element tag processor.</p>
* <p>Replace the comment tag <code>&#x3C;halo:comment /&#x3E;</code> with the given content.</p>
*
* @author guqing
* @see CommentEnabledVariableProcessor
* @since 2.0.0
*/
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;
Expand All @@ -51,49 +39,12 @@ public CommentElementTagProcessor(final String dialectPrefix) {
@Override
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler) {
getCommentWidget(context).ifPresentOrElse(commentWidget -> {
populateAllowCommentAttribute(context, true);
commentWidget.render(context, tag, structureHandler);
}, () -> {
populateAllowCommentAttribute(context, false);
var commentWidget = (CommentWidget) context.getVariable(
CommentEnabledVariableProcessor.COMMENT_WIDGET_OBJECT_VARIABLE);
if (commentWidget == null) {
structureHandler.replaceWith("", false);
});
}

static void populateAllowCommentAttribute(ITemplateContext context, boolean allowComment) {
if (Contexts.isWebContext(context)) {
IWebContext webContext = Contexts.asWebContext(context);
webContext.getExchange()
.setAttributeValue(COMMENT_ENABLED_MODEL_ATTRIBUTE, allowComment);
}
}

static Optional<CommentWidget> 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();
return;
}

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();
commentWidget.render(context, tag, structureHandler);
}
}
@@ -0,0 +1,91 @@
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.ITemplateEnd;
import org.thymeleaf.model.ITemplateStart;
import org.thymeleaf.processor.templateboundaries.AbstractTemplateBoundariesProcessor;
import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesStructureHandler;
import org.thymeleaf.spring6.context.SpringContextUtils;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.templatemode.TemplateMode;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;

/**
* Comment enabled variable processor.
* <p>Compute comment enabled state and set it to the model when the template is start rendering</p>
* <p>It is not suitable for scenarios where there are multiple comment components on the same page
* and some of them need to be controlled to be closed.</p>
*
* @author guqing
* @since 2.9.0
*/
public class CommentEnabledVariableProcessor extends AbstractTemplateBoundariesProcessor {

public static final String COMMENT_WIDGET_OBJECT_VARIABLE = CommentWidget.class.getName();
public static final String COMMENT_ENABLED_MODEL_ATTRIBUTE = "haloCommentEnabled";

public CommentEnabledVariableProcessor() {
super(TemplateMode.HTML, StandardDialect.PROCESSOR_PRECEDENCE);
}

@Override
public void doProcessTemplateStart(ITemplateContext context, ITemplateStart templateStart,
ITemplateBoundariesStructureHandler structureHandler) {
getCommentWidget(context).ifPresentOrElse(commentWidget -> {
populateAllowCommentAttribute(context, true);
structureHandler.setLocalVariable(COMMENT_WIDGET_OBJECT_VARIABLE, commentWidget);
}, () -> populateAllowCommentAttribute(context, false));
}

@Override
public void doProcessTemplateEnd(ITemplateContext context, ITemplateEnd templateEnd,
ITemplateBoundariesStructureHandler structureHandler) {
structureHandler.removeLocalVariable(COMMENT_WIDGET_OBJECT_VARIABLE);
}

static void populateAllowCommentAttribute(ITemplateContext context, boolean allowComment) {
if (Contexts.isWebContext(context)) {
IWebContext webContext = Contexts.asWebContext(context);
webContext.getExchange()
.setAttributeValue(COMMENT_ENABLED_MODEL_ATTRIBUTE, allowComment);
}
}

static Optional<CommentWidget> 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();
}
}
Expand Up @@ -35,6 +35,7 @@ public Set<IProcessor> getProcessors(String dialectPrefix) {
processors.add(new TemplateFooterElementTagProcessor(dialectPrefix));
processors.add(new JsonNodePropertyAccessorBoundariesProcessor());
processors.add(new CommentElementTagProcessor(dialectPrefix));
processors.add(new CommentEnabledVariableProcessor());
return processors;
}

Expand Down
@@ -1,6 +1,8 @@
package run.halo.app.theme;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand All @@ -24,6 +26,8 @@
import org.thymeleaf.templateresource.StringTemplateResource;
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.infra.utils.JsonUtils;
import run.halo.app.theme.dialect.HaloProcessorDialect;

Expand All @@ -40,6 +44,9 @@ public class ReactiveFinderExpressionParserTests {
@Mock
private ApplicationContext applicationContext;

@Mock
private SystemConfigurableEnvironmentFetcher environmentFetcher;

private TemplateEngine templateEngine;

@BeforeEach
Expand All @@ -53,6 +60,10 @@ public IStandardVariableExpressionEvaluator getVariableExpressionEvaluator() {
}
}));
templateEngine.addTemplateResolver(new TestTemplateResolver());
lenient().when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class)))
.thenReturn(environmentFetcher);
lenient().when(environmentFetcher.fetchComment())
.thenReturn(Mono.just(new SystemSetting.Comment()));
}

@Test
Expand Down
Expand Up @@ -4,7 +4,6 @@
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;
Expand All @@ -19,15 +18,13 @@
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;
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;
Expand Down Expand Up @@ -105,74 +102,6 @@ void doProcess() {
""");
}

@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
Expand Down