/*
* Symphony - A modern community (forum/SNS/blog) platform written in Java.
* Copyright (C) 2012-2017, b3log.org & hacpai.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.b3log.symphony.processor;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.b3log.latke.Keys;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.model.User;
import org.b3log.latke.servlet.HTTPRequestContext;
import org.b3log.latke.servlet.renderer.freemarker.AbstractFreeMarkerRenderer;
import org.b3log.latke.util.Locales;
import org.b3log.symphony.model.UserExt;
import org.b3log.symphony.util.Skins;
import org.b3log.symphony.util.Symphonys;
import org.json.JSONObject;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
import java.util.TimeZone;
/**
* Skin user-switchable FreeMarker Renderer.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.3.0.1, Mar 2, 2017
* @since 1.3.0
*/
public final class SkinRenderer extends AbstractFreeMarkerRenderer {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(SkinRenderer.class.getName());
/**
* HTTP servlet request.
*/
private final HttpServletRequest request;
/**
* Constructs a skin renderer with the specified HTTP servlet request.
*
* @param request the specified HTTP servlet request
*/
public SkinRenderer(final HttpServletRequest request) {
this.request = request;
}
/**
* Gets a templat with the specified template directory name, template name, search engine bot flag and user.
*
* @param templateDirName the specified template directory name
* @param templateName the specified template name
* @param isSearchEnginBot the specified search engine bot flag
* @param user the specified user
* @return
*/
public static Template getTemplate(final String templateDirName, final String templateName,
final boolean isSearchEnginBot, final JSONObject user) {
Configuration cfg = Skins.TEMPLATE_HOLDER.get(templateDirName);
try {
if (null == cfg) {
LOGGER.warn("Can't get template dir [" + templateDirName + "]");
cfg = Skins.TEMPLATE_HOLDER.get(Symphonys.get("skinDirName"));
}
final Template ret = cfg.getTemplate(templateName);
if (isSearchEnginBot) {
return ret;
}
ret.setLocale(Locales.getLocale());
if (null != user) {
ret.setTimeZone(TimeZone.getTimeZone(user.optString(UserExt.USER_TIMEZONE)));
} else {
ret.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
}
return ret;
} catch (final IOException e) {
LOGGER.log(Level.ERROR, "Get template [dir=" + templateDirName + ", name=" + templateName + "] error", e);
return null;
}
}
/**
* Determines whether the specified request is sending with pjax.
*
* @param request the specified request
* @return {@code true} if it is sending with pjax, otherwise returns {@code false}
*/
public static boolean isPJAX(final HttpServletRequest request) {
final boolean pjax = Boolean.valueOf(request.getHeader("X-PJAX"));
final String pjaxContainer = (String) request.getHeader("X-PJAX-Container");
return pjax && StringUtils.isNotBlank(pjaxContainer);
}
/**
* Gets a template with the specified template directory name and template name.
*
* @param templateDirName the specified template directory name
* @param templateName the specified template name
* @return template
*/
@Override
protected Template getTemplate(final String templateDirName, final String templateName) {
final boolean isSearchEngineBot = (Boolean) request.getAttribute(Keys.HttpRequest.IS_SEARCH_ENGINE_BOT);
final JSONObject user = (JSONObject) request.getAttribute(User.USER);
return getTemplate(templateDirName, templateName, isSearchEngineBot, user);
}
/**
* Processes the specified FreeMarker template with the specified request, data model, pjax hacking.
*
* @param request the specified request
* @param dataModel the specified data model
* @param template the specified FreeMarker template
* @return generated HTML
* @throws Exception exception
*/
protected String genHTML(final HttpServletRequest request, final Map<String, Object> dataModel, final Template template)
throws Exception {
final boolean isPJAX = isPJAX(request);
dataModel.put("pjax", isPJAX);
if (!isPJAX) {
return super.genHTML(request, dataModel, template);
}
final StringWriter stringWriter = new StringWriter();
template.setOutputEncoding("UTF-8");
template.process(dataModel, stringWriter);
final StringBuilder pageContentBuilder = new StringBuilder(stringWriter.toString());
final long endimeMillis = System.currentTimeMillis();
final String dateString = DateFormatUtils.format(endimeMillis, "yyyy/MM/dd HH:mm:ss");
final long startTimeMillis = (Long) request.getAttribute(Keys.HttpRequest.START_TIME_MILLIS);
final String msg = String.format("\n<!-- Generated by B3log Latke(%1$d ms), %2$s -->", endimeMillis - startTimeMillis, dateString);
final String pjaxContainer = (String) request.getHeader("X-PJAX-Container");
return StringUtils.substringBetween(pageContentBuilder.toString(),
"<!---- pjax {" + pjaxContainer + "} start ---->",
"<!---- pjax {" + pjaxContainer + "} end ---->") + msg;
}
@Override
protected void beforeRender(final HTTPRequestContext context) throws Exception {
}
@Override
protected void afterRender(final HTTPRequestContext context) throws Exception {
}
}