/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.celements.web.plugin.cmd;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryManager;
import com.celements.cells.CellRenderStrategy;
import com.celements.cells.DivWriter;
import com.celements.cells.IRenderStrategy;
import com.celements.cells.RenderingEngine;
import com.celements.inheritor.InheritorFactory;
import com.celements.web.service.IWebUtilsService;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.plugin.packaging.DocumentInfo;
import com.xpn.xwiki.plugin.packaging.PackageAPI;
import com.xpn.xwiki.web.Utils;
public class PageLayoutCommand {
public static final String SIMPLE_LAYOUT = "SimpleLayout";
public static final String XWIKICFG_CELEMENTS_LAYOUT_DEFAULT = "celements.layout.default";
private static Log LOGGER = LogFactory.getFactory().getInstance(PageLayoutCommand.class);
QueryManager queryManager;
IWebUtilsService webUtilsService;
public static final String PAGE_LAYOUT_PROPERTIES_CLASS_SPACE = "Celements";
public static final String PAGE_LAYOUT_PROPERTIES_CLASS_DOC = "PageLayoutPropertiesClass";
public static final String PAGE_LAYOUT_PROPERTIES_CLASS = PAGE_LAYOUT_PROPERTIES_CLASS_SPACE + "."
+ PAGE_LAYOUT_PROPERTIES_CLASS_DOC;
public static final String CEL_LAYOUT_EDITOR_PL_NAME = "CelLayoutEditor";
private static final String CEL_RENDERING_LAYOUT_CONTEXT_PROPERTY = "celRenderingLayout";
private static final String CEL_RENDERING_LAYOUT_STACK_PROPERTY = "celRenderingLayoutStack";
/**
* The name of the internal packaging plugin.
*/
private static final String PACKAGEPLUGIN_NAME = "package";
private InheritorFactory _injectedInheritorFactory;
private QueryManager getQueryManager() {
if (queryManager == null) {
queryManager = Utils.getComponent(QueryManager.class);
}
return queryManager;
}
public Map<String, String> getAllPageLayouts() {
return getPageLayoutMap(false);
}
private Map<String, String> getPageLayoutMap(boolean onlyActive) {
Map<String, String> plMap = new HashMap<String, String>();
try {
for (Object resultRowObj : getContext().getWiki().search(getPageLayoutHQL(onlyActive),
getContext())) {
Object[] resultRow = (Object[]) resultRowObj;
plMap.put(resultRow[0].toString(), resultRow[1].toString());
}
} catch (XWikiException exp) {
LOGGER.error("Failed to get all page layouts", exp);
}
return plMap;
}
String getPageLayoutHQL(boolean onlyActive) {
String hql = "select doc.space, pl.prettyname" + " from XWikiDocument as doc, BaseObject obj,"
+ " Celements.PageLayoutPropertiesClass as pl" + " where doc.fullName = obj.name"
+ " and obj.className='Celements.PageLayoutPropertiesClass'" + " and pl.id.id=obj.id";
if (onlyActive) {
hql += " and pl.isActive = 1";
}
hql += " order by pl.prettyname asc";
return hql;
}
public Map<String, String> getActivePageLyouts() {
return getPageLayoutMap(true);
}
public String createNew(SpaceReference layoutSpaceRef) {
if (layoutSpaceRef != null) {
if (!layoutExists(layoutSpaceRef)) {
try {
XWikiDocument propXdoc = getContext().getWiki().getDocument(standardPropDocRef(
layoutSpaceRef), getContext());
BaseObject layoutPropObj = propXdoc.newXObject(getPageLayoutPropertiesClassRef(
propXdoc.getDocumentReference().getWikiReference().getName()), getContext());
layoutPropObj.setStringValue("prettyname", layoutSpaceRef.getName() + " Layout");
layoutPropObj.setStringValue("doctype", getDocType());
getContext().getWiki().saveDocument(propXdoc, "Creating page layout", false,
getContext());
return "cel_layout_create_successful";
} catch (XWikiException exp) {
LOGGER.error("createNew: failed to create new page layout.", exp);
}
}
}
return "cel_layout_empty_name_msg";
}
private String getDocType() {
return Utils.getComponent(ConfigurationSource.class).getProperty("celements.layout.docType",
"HTML 5");
}
public boolean deleteLayout(SpaceReference layoutSpaceRef) {
XWiki xwiki = getContext().getWiki();
try {
Query spaceDocsQuery = getQueryManager().createQuery("where doc.space = :space", Query.XWQL);
spaceDocsQuery.bindValue("space", layoutSpaceRef.getName());
for (String docName : spaceDocsQuery.<String>execute()) {
DocumentReference docReference = getWebUtilsService().resolveDocumentReference(docName);
xwiki.deleteAllDocuments(xwiki.getDocument(docReference, getContext()), getContext());
}
return true;
} catch (QueryException exp) {
LOGGER.warn("Failed to get the list of documents while trying to delete space ["
+ layoutSpaceRef + "]", exp);
} catch (XWikiException exp) {
LOGGER.error("deleteLayout: Failed to delete documents for space [" + layoutSpaceRef + "].",
exp);
}
return false;
}
private DocumentReference getPageLayoutPropertiesClassRef(String dbName) {
return new DocumentReference(dbName, PAGE_LAYOUT_PROPERTIES_CLASS_SPACE,
PAGE_LAYOUT_PROPERTIES_CLASS_DOC);
}
/**
* checks if the layout exists locally (in terms of layoutSpaceRef)
*
* @param layoutSpaceRef
* @return
*/
public boolean layoutExists(SpaceReference layoutSpaceRef) {
return (getLayoutPropDoc(layoutSpaceRef) != null);
}
public boolean canRenderLayout(SpaceReference layoutSpaceRef) {
layoutSpaceRef = decideLocalOrCentral(layoutSpaceRef);
return (layoutSpaceRef != null);
}
public BaseObject getLayoutPropertyObj(SpaceReference layoutSpaceRef) {
XWikiDocument layoutPropDoc = getLayoutPropDoc(layoutSpaceRef);
if (layoutPropDoc != null) {
return layoutPropDoc.getXObject(getPageLayoutPropertiesClassRef(
layoutPropDoc.getDocumentReference().getWikiReference().getName()));
} else {
return null;
}
}
public XWikiDocument getLayoutPropDoc() {
SpaceReference currDocPageLayout = getPageLayoutForCurrentDoc();
if (currDocPageLayout != null) {
LOGGER.debug("getLayoutPropDoc: found page layout [" + currDocPageLayout + "] for page ["
+ getContext().getDoc().getDocumentReference() + "].");
return getLayoutPropDoc(currDocPageLayout);
} else {
LOGGER.debug("getLayoutPropDoc: found NO page layout for page ["
+ getContext().getDoc().getDocumentReference() + "].");
}
return null;
}
/**
* getLayoutPropDoc
*
* @param layoutSpaceRef
* may not be null (NPE if null)
* @return property doc for layoutSpaceRef
*/
public XWikiDocument getLayoutPropDoc(SpaceReference layoutSpaceRef) {
XWikiDocument layoutPropDoc = null;
DocumentReference layoutPropDocRef = standardPropDocRef(layoutSpaceRef);
if (getContext().getWiki().exists(layoutPropDocRef, getContext())) {
try {
XWikiDocument theDoc = getContext().getWiki().getDocument(layoutPropDocRef, getContext());
if (theDoc.getXObject(getPageLayoutPropertiesClassRef(
theDoc.getDocumentReference().getWikiReference().getName())) != null) {
layoutPropDoc = theDoc;
}
} catch (XWikiException exp) {
LOGGER.error("getLayoutPropDoc: Failed to get layout property doc for [" + layoutSpaceRef
+ "].", exp);
}
}
return layoutPropDoc;
}
public DocumentReference standardPropDocRef(SpaceReference layoutSpaceRef) {
return new DocumentReference("WebHome", layoutSpaceRef);
}
public String renderPageLayout() {
return renderPageLayoutLocal(getPageLayoutForCurrentDoc());
}
public String renderPageLayout(SpaceReference layoutSpaceRef) {
layoutSpaceRef = decideLocalOrCentral(layoutSpaceRef);
return renderPageLayoutLocal(layoutSpaceRef);
}
/**
* renderPageLayout(SpaceReference) does NOT check any access rights. Or if the given
* layout exists. This MUST be done before calling renderPageLayout(SpaceReference).
*
* @param layoutSpaceRef
* @return
*/
public String renderPageLayoutLocal(SpaceReference layoutSpaceRef) {
long millisec = System.currentTimeMillis();
LOGGER.debug("renderPageLayout for layout [" + layoutSpaceRef + "].");
IRenderStrategy cellRenderer = new CellRenderStrategy(getContext()).setOutputWriter(
new DivWriter());
getRenderingLayoutStack().push(layoutSpaceRef);
setRenderLayoutInVelocityContext(getCurrentRenderingLayout());
RenderingEngine renderEngine = new RenderingEngine().setRenderStrategy(cellRenderer);
renderEngine.renderPageLayout(layoutSpaceRef);
getRenderingLayoutStack().pop();
setRenderLayoutInVelocityContext(getCurrentRenderingLayout());
LOGGER.info("renderPageLayout finishing. Time used in millisec: " + (System.currentTimeMillis()
- millisec));
return cellRenderer.getAsString();
}
public SpaceReference getCurrentRenderingLayout() {
if (!getRenderingLayoutStack().isEmpty()) {
return getRenderingLayoutStack().peek();
}
return null;
}
private void setRenderLayoutInVelocityContext(SpaceReference layoutSpaceRef) {
VelocityContext vcontext = (VelocityContext) getContext().get("vcontext");
if (layoutSpaceRef != null) {
/*
* IMPORTANT: do not use .clone() on any reference it will not be available on
* unstable branch
*/
SpaceReference copyOfLayoutSpaceRef = new SpaceReference(layoutSpaceRef.getName(),
(WikiReference) layoutSpaceRef.getParent());
vcontext.put(CEL_RENDERING_LAYOUT_CONTEXT_PROPERTY, copyOfLayoutSpaceRef);
} else {
vcontext.remove(CEL_RENDERING_LAYOUT_CONTEXT_PROPERTY);
}
}
@SuppressWarnings("unchecked")
private Stack<SpaceReference> getRenderingLayoutStack() {
ExecutionContext execContext = getExecution().getContext();
if (execContext.getProperty(CEL_RENDERING_LAYOUT_STACK_PROPERTY) == null) {
execContext.setProperty(CEL_RENDERING_LAYOUT_STACK_PROPERTY, new Stack<SpaceReference>());
}
return (Stack<SpaceReference>) execContext.getProperty(CEL_RENDERING_LAYOUT_STACK_PROPERTY);
}
/**
* getPageLayoutForCurrentDoc checks that the layout returned exists and that it may be
* used by the current context database.
*
* @return
*/
public SpaceReference getPageLayoutForCurrentDoc() {
return getPageLayoutForDoc(getContext().getDoc().getDocumentReference());
}
/**
* @deprecated since 2.14.0 instead use getPageLayoutForDoc(DocumentReference)
*/
@Deprecated
public String getPageLayoutForDoc(String fullName, XWikiContext context) {
DocumentReference docRef = getWebUtilsService().resolveDocumentReference(fullName);
SpaceReference spaceRef = getPageLayoutForDoc(docRef);
if (spaceRef != null) {
String layoutWikiName = spaceRef.getParent().getName();
if (context.getDatabase().equals(layoutWikiName)) {
return spaceRef.getName();
} else {
return layoutWikiName + ":" + spaceRef.getName();
}
}
return null;
}
public SpaceReference getPageLayoutForDoc(DocumentReference documentReference) {
long millisec = System.currentTimeMillis();
LOGGER.debug("getPageLayoutForDoc: for [" + documentReference + "].");
SpaceReference layoutSpaceRef = null;
if (layoutExists(documentReference.getLastSpaceReference())) {
layoutSpaceRef = getCelLayoutEditorSpaceRef();
} else {
String spaceName = getInheritorFactory().getPageLayoutInheritor(getFullNameForDocRef(
documentReference), getContext()).getStringValue("page_layout", null);
if (spaceName != null) {
layoutSpaceRef = getWebUtilsService().resolveSpaceReference(spaceName);
}
}
layoutSpaceRef = decideLocalOrCentral(layoutSpaceRef);
if ((layoutSpaceRef == null) || !checkLayoutAccess(layoutSpaceRef)) {
layoutSpaceRef = getDefaultLayoutSpaceReference();
}
LOGGER.info("getPageLayoutForDoc: for [" + documentReference + "] returning [" + layoutSpaceRef
+ "]. Time used in millisec: " + (System.currentTimeMillis() - millisec));
return layoutSpaceRef;
}
/**
* prohibit layout access in different db except central celements2web (or default
* layout configured on disk). TODO add allowedDBs to layout properties
*
* @param layoutSpaceRef
* @return
*/
public boolean checkLayoutAccess(SpaceReference layoutSpaceRef) {
String layoutWikiName = layoutSpaceRef.getParent().getName();
return getContext().getDatabase().equals(layoutWikiName) || "celements2web".equals(
layoutWikiName);
}
public SpaceReference getDefaultLayoutSpaceReference() {
SpaceReference defaultLayoutSpaceRef = new SpaceReference(getDefaultLayout(), new WikiReference(
getContext().getDatabase()));
defaultLayoutSpaceRef = decideLocalOrCentral(defaultLayoutSpaceRef);
return defaultLayoutSpaceRef;
}
String getDefaultLayout() {
String defaultLayout = getContext().getWiki().Param(XWIKICFG_CELEMENTS_LAYOUT_DEFAULT,
SIMPLE_LAYOUT);
if ((getContext() != null) && (getContext().getAction() != null) && !"view".equals(
getContext().getAction())) {
defaultLayout = getContext().getWiki().Param(XWIKICFG_CELEMENTS_LAYOUT_DEFAULT + "."
+ getContext().getAction(), defaultLayout);
LOGGER.debug("getDefaultLayout for action [" + getContext().getAction() + "] got ["
+ defaultLayout + "].");
} else {
LOGGER.debug("getDefaultLayout got [" + defaultLayout + "].");
}
return defaultLayout;
}
private SpaceReference decideLocalOrCentral(SpaceReference layoutSpaceRef) {
if ((layoutSpaceRef != null) && !layoutExists(layoutSpaceRef)) {
SpaceReference centralLayoutSpaceRef = new SpaceReference(layoutSpaceRef.getName(),
new WikiReference("celements2web"));
if (!layoutSpaceRef.equals(centralLayoutSpaceRef) && layoutExists(centralLayoutSpaceRef)) {
layoutSpaceRef = centralLayoutSpaceRef;
} else {
layoutSpaceRef = null;
}
}
return layoutSpaceRef;
}
SpaceReference getCelLayoutEditorSpaceRef() {
return new SpaceReference(CEL_LAYOUT_EDITOR_PL_NAME, new WikiReference(
getContext().getDatabase()));
}
private String getFullNameForDocRef(DocumentReference documentReference) {
return documentReference.getLastSpaceReference().getName() + "." + documentReference.getName();
}
private InheritorFactory getInheritorFactory() {
if (_injectedInheritorFactory != null) {
return _injectedInheritorFactory;
}
return new InheritorFactory();
}
/**
* For TESTS ONLY!!!
*
* @param injectedInheritorFactory
*/
void inject_TEST_InheritorFactory(InheritorFactory injectedInheritorFactory) {
_injectedInheritorFactory = injectedInheritorFactory;
}
public boolean isActive(SpaceReference layoutSpaceRef) {
BaseObject layoutPropertyObj = getLayoutPropertyObj(layoutSpaceRef);
if (layoutPropertyObj != null) {
return layoutPropertyObj.getIntValue("isActive", 0) > 0;
}
return false;
}
public String getPrettyName(SpaceReference layoutSpaceRef) {
BaseObject layoutPropertyObj = getLayoutPropertyObj(layoutSpaceRef);
if ((layoutPropertyObj != null) && (layoutPropertyObj.getStringValue("prettyname") != null)) {
return layoutPropertyObj.getStringValue("prettyname");
}
return "";
}
public String getVersion(SpaceReference layoutSpaceRef) {
BaseObject layoutPropertyObj = getLayoutPropertyObj(layoutSpaceRef);
if ((layoutPropertyObj != null) && (layoutPropertyObj.getStringValue("version") != null)) {
return layoutPropertyObj.getStringValue("version");
}
return "";
}
public boolean layoutEditorAvailable() {
SpaceReference localLayoutEditorSpaceRef = getCelLayoutEditorSpaceRef();
SpaceReference layoutEditorSpaceRef = decideLocalOrCentral(localLayoutEditorSpaceRef);
return (layoutEditorSpaceRef != null);
}
/**
* Export an page layout space into XAR using Packaging plugin.
*
* @param layoutSpaceRef
* the layout space reference of the application to export.
* @param withDocHistory
* indicate if history of documents is exported.
* @param context
* the XWiki context.
* @throws XWikiException
* error when :
* <ul>
* <li>or getting page-layouts documents to export.</li>
* <li>or when apply export.</li>
* </ul>
* @throws IOException
* error when apply export.
*/
public void exportLayoutXAR(SpaceReference layoutSpaceRef, boolean withDocHistory)
throws XWikiException, IOException {
PackageAPI export = ((PackageAPI) getContext().getWiki().getPluginApi(PACKAGEPLUGIN_NAME,
getContext()));
export.setName(getPrettyName(layoutSpaceRef) + "-" + getVersion(layoutSpaceRef));
for (String documentName : getContext().getWiki().getSpaceDocsName(layoutSpaceRef.getName(),
getContext())) {
export.add(extendToFullName(layoutSpaceRef.getName(), documentName),
DocumentInfo.ACTION_OVERWRITE);
}
export.setWithVersions(withDocHistory);
export.export();
}
String extendToFullName(String layoutSpaceName, String documentName) {
if (!documentName.contains(".")) {
return layoutSpaceName + "." + documentName;
} else {
return documentName;
}
}
private XWikiContext getContext() {
return (XWikiContext) getExecution().getContext().getProperty("xwikicontext");
}
private Execution getExecution() {
return Utils.getComponent(Execution.class);
}
IWebUtilsService getWebUtilsService() {
if (webUtilsService != null) {
return webUtilsService;
}
return Utils.getComponent(IWebUtilsService.class);
}
}