mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
. Added cm:name based path support to the UITemplate component
- this component is generally used to render FreeMarker templates directly to the page - particularly useful for Dashlet pages - now supports an additional attribute 'templatePath' which should be set to a cm:name based path to a template for rendering - For example the simplest portable dashlet JSP page to render a template would look like this: <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> <r:template templatePath="/Company Home/Data Dictionary/Presentation Templates/my_docs.ftl" /> - Fixes http://issues.alfresco.com/browse/AWC-1091 . Additional helper override for resolving cm:name based webdav style paths to a NodeRef . ClipboardItem interface javadoc and removal of obsolete methods from implementing classes git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4963 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -208,6 +208,19 @@ public abstract class BaseServlet extends HttpServlet
|
|||||||
return resolveWebDAVPath(wc, args, true);
|
return resolveWebDAVPath(wc, args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the given path elements to a NodeRef in the current repository
|
||||||
|
*
|
||||||
|
* @param context Faces context
|
||||||
|
* @param args The elements of the path to lookup
|
||||||
|
* @param decode True to decode the arg from UTF-8 format, false for no decoding
|
||||||
|
*/
|
||||||
|
public static NodeRef resolveWebDAVPath(FacesContext context, String[] args, boolean decode)
|
||||||
|
{
|
||||||
|
WebApplicationContext wc = FacesContextUtils.getRequiredWebApplicationContext(context);
|
||||||
|
return resolveWebDAVPath(wc, args, decode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the given path elements to a NodeRef in the current repository
|
* Resolves the given path elements to a NodeRef in the current repository
|
||||||
*
|
*
|
||||||
|
@@ -103,11 +103,6 @@ abstract class AbstractClipboardItem implements ClipboardItem
|
|||||||
return this.icon;
|
return this.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId()
|
|
||||||
{
|
|
||||||
return this.ref.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeRef getNodeRef()
|
public NodeRef getNodeRef()
|
||||||
{
|
{
|
||||||
return this.ref;
|
return this.ref;
|
||||||
|
@@ -28,21 +28,51 @@ import org.alfresco.service.namespace.QName;
|
|||||||
*/
|
*/
|
||||||
public interface ClipboardItem
|
public interface ClipboardItem
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @return the mode status of the clipboard item, the enum can be either Cut or Copy
|
||||||
|
*/
|
||||||
public ClipboardStatus getMode();
|
public ClipboardStatus getMode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return display label (cm:name) of the clipboard item
|
||||||
|
*/
|
||||||
public String getName();
|
public String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return QName type of the clipboard item
|
||||||
|
*/
|
||||||
public QName getType();
|
public QName getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the app:icon property of the clipboard item
|
||||||
|
*/
|
||||||
public String getIcon();
|
public String getIcon();
|
||||||
|
|
||||||
public String getId();
|
/**
|
||||||
|
* @return the NodeRef of the clipboard item
|
||||||
|
*/
|
||||||
public NodeRef getNodeRef();
|
public NodeRef getNodeRef();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the item on the clipboard supports linking (.url) as a link type
|
||||||
|
*/
|
||||||
public boolean supportsLink();
|
public boolean supportsLink();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param viewId JSF View Id to check against
|
||||||
|
*
|
||||||
|
* @return true if the clipboard item can be pasted to the specified JSF view
|
||||||
|
*/
|
||||||
public boolean canPasteToViewId(String viewId);
|
public boolean canPasteToViewId(String viewId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fc FacesContext
|
||||||
|
* @param viewId JSF View Id to paste into
|
||||||
|
* @param action Clipboard action constant (@see org.alfresco.web.ui.repo.component.shelf.UIClipboardShelfItem)
|
||||||
|
*
|
||||||
|
* @return true on successful paste, false otherwise
|
||||||
|
*
|
||||||
|
* @throws Throwable on fatal error during paste
|
||||||
|
*/
|
||||||
public boolean paste(FacesContext fc, String viewId, int action) throws Throwable;
|
public boolean paste(FacesContext fc, String viewId, int action) throws Throwable;
|
||||||
}
|
}
|
||||||
|
@@ -127,7 +127,7 @@ public class WorkspaceClipboardItem extends AbstractClipboardItem
|
|||||||
{
|
{
|
||||||
// LINK operation
|
// LINK operation
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug("Attempting to link node ID: " + getId() + " into node ID: " + destRef.getId());
|
logger.debug("Attempting to link node ID: " + getNodeRef() + " into node: " + destRef.toString());
|
||||||
|
|
||||||
// we create a special Link Object node that has a property to reference the original
|
// we create a special Link Object node that has a property to reference the original
|
||||||
// create the node using the nodeService (can only use FileFolderService for content)
|
// create the node using the nodeService (can only use FileFolderService for content)
|
||||||
@@ -178,7 +178,7 @@ public class WorkspaceClipboardItem extends AbstractClipboardItem
|
|||||||
{
|
{
|
||||||
// COPY operation
|
// COPY operation
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug("Attempting to copy node ID: " + getId() + " into node ID: " + destRef.getId());
|
logger.debug("Attempting to copy node: " + getNodeRef() + " into node ID: " + destRef.toString());
|
||||||
|
|
||||||
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT) ||
|
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT) ||
|
||||||
dd.isSubClass(getType(), ContentModel.TYPE_FOLDER))
|
dd.isSubClass(getType(), ContentModel.TYPE_FOLDER))
|
||||||
@@ -218,7 +218,7 @@ public class WorkspaceClipboardItem extends AbstractClipboardItem
|
|||||||
{
|
{
|
||||||
// MOVE operation
|
// MOVE operation
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug("Attempting to move node ID: " + getId() + " into node ID: " + destRef.getId());
|
logger.debug("Attempting to move node: " + getNodeRef() + " into node ID: " + destRef.toString());
|
||||||
|
|
||||||
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT) ||
|
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT) ||
|
||||||
dd.isSubClass(getType(), ContentModel.TYPE_FOLDER))
|
dd.isSubClass(getType(), ContentModel.TYPE_FOLDER))
|
||||||
|
@@ -18,6 +18,7 @@ package org.alfresco.web.ui.repo.component.template;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import javax.faces.context.FacesContext;
|
import javax.faces.context.FacesContext;
|
||||||
import javax.faces.el.ValueBinding;
|
import javax.faces.el.ValueBinding;
|
||||||
@@ -29,6 +30,7 @@ import org.alfresco.service.cmr.repository.TemplateException;
|
|||||||
import org.alfresco.service.cmr.repository.TemplateImageResolver;
|
import org.alfresco.service.cmr.repository.TemplateImageResolver;
|
||||||
import org.alfresco.service.cmr.repository.TemplateService;
|
import org.alfresco.service.cmr.repository.TemplateService;
|
||||||
import org.alfresco.web.app.Application;
|
import org.alfresco.web.app.Application;
|
||||||
|
import org.alfresco.web.app.servlet.BaseServlet;
|
||||||
import org.alfresco.web.bean.repository.Repository;
|
import org.alfresco.web.bean.repository.Repository;
|
||||||
import org.alfresco.web.bean.repository.User;
|
import org.alfresco.web.bean.repository.User;
|
||||||
import org.alfresco.web.ui.common.Utils;
|
import org.alfresco.web.ui.common.Utils;
|
||||||
@@ -36,21 +38,30 @@ import org.alfresco.web.ui.common.component.SelfRenderingComponent;
|
|||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Component responsible for rendering the output of a FreeMarker template directly to the page.
|
||||||
|
* <p>
|
||||||
|
* FreeMarker templates can be specified as a NodeRef or classpath location. The template output
|
||||||
|
* will be processed against the default model merged with any custom model reference supplied to
|
||||||
|
* the component as a value binding attribute. The output of the template is the output of the
|
||||||
|
* component tag.
|
||||||
|
*
|
||||||
* @author Kevin Roast
|
* @author Kevin Roast
|
||||||
*/
|
*/
|
||||||
public class UITemplate extends SelfRenderingComponent
|
public class UITemplate extends SelfRenderingComponent
|
||||||
{
|
{
|
||||||
private final static String ENGINE_DEFAULT = "freemarker";
|
private final static String ENGINE_DEFAULT = "freemarker";
|
||||||
private final static String TEMPLATE_KEY = "_template_";
|
|
||||||
|
|
||||||
private static Logger logger = Logger.getLogger(UITemplate.class);
|
private static Logger logger = Logger.getLogger(UITemplate.class);
|
||||||
|
|
||||||
/** Template engine name */
|
/** Template engine name */
|
||||||
private String engine = null;
|
private String engine = null;
|
||||||
|
|
||||||
/** Template name/path */
|
/** Template name/classpath */
|
||||||
private String template = null;
|
private String template = null;
|
||||||
|
|
||||||
|
/** Template cm:name based path */
|
||||||
|
private String templatePath = null;
|
||||||
|
|
||||||
/** Data model reference */
|
/** Data model reference */
|
||||||
private Object model = null;
|
private Object model = null;
|
||||||
|
|
||||||
@@ -76,7 +87,8 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
super.restoreState(context, values[0]);
|
super.restoreState(context, values[0]);
|
||||||
this.engine = (String)values[1];
|
this.engine = (String)values[1];
|
||||||
this.template = (String)values[2];
|
this.template = (String)values[2];
|
||||||
this.model = (Object)values[3];
|
this.templatePath = (String)values[3];
|
||||||
|
this.model = (Object)values[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,12 +96,8 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
*/
|
*/
|
||||||
public Object saveState(FacesContext context)
|
public Object saveState(FacesContext context)
|
||||||
{
|
{
|
||||||
Object values[] = new Object[4];
|
Object values[] = new Object[] {
|
||||||
// standard component attributes are saved by the super class
|
super.saveState(context), this.engine, this.template, this.templatePath, this.model};
|
||||||
values[0] = super.saveState(context);
|
|
||||||
values[1] = this.engine;
|
|
||||||
values[2] = this.template;
|
|
||||||
values[3] = this.model;
|
|
||||||
return (values);
|
return (values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,8 +112,30 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the template to process
|
// get the template to process
|
||||||
String template = getTemplate();
|
String templateRef = getTemplate();
|
||||||
if (template != null && template.length() != 0)
|
if (templateRef == null || templateRef.length() == 0)
|
||||||
|
{
|
||||||
|
// no noderef/classpath template found - try a name based path
|
||||||
|
String path = getTemplatePath();
|
||||||
|
if (path != null && path.length() != 0)
|
||||||
|
{
|
||||||
|
// convert cm:name based path to a NodeRef
|
||||||
|
StringTokenizer t = new StringTokenizer(path, "/");
|
||||||
|
int tokenCount = t.countTokens();
|
||||||
|
String[] elements = new String[tokenCount];
|
||||||
|
for (int i=0; i<tokenCount; i++)
|
||||||
|
{
|
||||||
|
elements[i] = t.nextToken();
|
||||||
|
}
|
||||||
|
NodeRef pathRef = BaseServlet.resolveWebDAVPath(context, elements, false);
|
||||||
|
if (pathRef != null)
|
||||||
|
{
|
||||||
|
templateRef = pathRef.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (templateRef != null && templateRef.length() != 0)
|
||||||
{
|
{
|
||||||
// get the class name of the processor to instantiate
|
// get the class name of the processor to instantiate
|
||||||
String engine = getEngine();
|
String engine = getEngine();
|
||||||
@@ -117,14 +147,14 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
startTime = System.currentTimeMillis();
|
startTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the data model to use - building default if required
|
// get the data model to use - building default FreeMarker model as required
|
||||||
Object model = getModel();
|
Object model = getFreeMarkerModel(getModel(), templateRef);
|
||||||
|
|
||||||
// process the template against the model
|
// process the template against the model
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
TemplateService templateService = Repository.getServiceRegistry(context).getTemplateService();
|
TemplateService templateService = Repository.getServiceRegistry(context).getTemplateService();
|
||||||
templateService.processTemplate(engine, getTemplate(), model, context.getResponseWriter());
|
templateService.processTemplate(engine, templateRef, model, context.getResponseWriter());
|
||||||
}
|
}
|
||||||
catch (TemplateException err)
|
catch (TemplateException err)
|
||||||
{
|
{
|
||||||
@@ -139,6 +169,49 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default we return a Map model containing root references to the Company Home Space,
|
||||||
|
* the users Home Space and the Person Node for the current user.
|
||||||
|
*
|
||||||
|
* @param model Custom model to merge into default model
|
||||||
|
* @param template Optional reference to the template to add to model
|
||||||
|
*
|
||||||
|
* @return Returns the data model to bind template against.
|
||||||
|
*/
|
||||||
|
private Object getFreeMarkerModel(Object model, String template)
|
||||||
|
{
|
||||||
|
if (getEngine().equals(ENGINE_DEFAULT))
|
||||||
|
{
|
||||||
|
// create an instance of the default FreeMarker template object model
|
||||||
|
FacesContext fc = FacesContext.getCurrentInstance();
|
||||||
|
ServiceRegistry services = Repository.getServiceRegistry(fc);
|
||||||
|
User user = Application.getCurrentUser(fc);
|
||||||
|
|
||||||
|
// add the template itself to the model
|
||||||
|
NodeRef templateRef = null;
|
||||||
|
if (template.indexOf(StoreRef.URI_FILLER) != -1)
|
||||||
|
{
|
||||||
|
// found a noderef template
|
||||||
|
templateRef = new NodeRef(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map root = DefaultModelHelper.buildDefaultModel(services, user, templateRef);
|
||||||
|
|
||||||
|
// merge models
|
||||||
|
if (model instanceof Map)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Found valid Map model to merge with FreeMarker: " + model);
|
||||||
|
|
||||||
|
root.putAll((Map)model);
|
||||||
|
}
|
||||||
|
|
||||||
|
model = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Strongly typed component property accessors
|
// Strongly typed component property accessors
|
||||||
@@ -166,61 +239,23 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the data model to bind template against.
|
* Return the custom data model to bind template against.
|
||||||
* <p>
|
|
||||||
* By default we return a Map model containing root references to the Company Home Space,
|
|
||||||
* the users Home Space and the Person Node for the current user.
|
|
||||||
*
|
*
|
||||||
* @return Returns the data model to bind template against.
|
* @return Returns the custom data model to bind template against.
|
||||||
*/
|
*/
|
||||||
public Object getModel()
|
public Object getModel()
|
||||||
{
|
{
|
||||||
if (this.model == null)
|
if (this.model == null)
|
||||||
{
|
{
|
||||||
Object model = null;
|
|
||||||
ValueBinding vb = getValueBinding("model");
|
ValueBinding vb = getValueBinding("model");
|
||||||
if (vb != null)
|
if (vb != null)
|
||||||
{
|
{
|
||||||
model = vb.getValue(getFacesContext());
|
this.model = vb.getValue(getFacesContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getEngine().equals(ENGINE_DEFAULT))
|
|
||||||
{
|
|
||||||
// create an instance of the default FreeMarker template object model
|
|
||||||
FacesContext fc = FacesContext.getCurrentInstance();
|
|
||||||
ServiceRegistry services = Repository.getServiceRegistry(fc);
|
|
||||||
User user = Application.getCurrentUser(fc);
|
|
||||||
|
|
||||||
// add the template itself to the model
|
|
||||||
NodeRef templateRef = null;
|
|
||||||
if (getTemplate().indexOf(StoreRef.URI_FILLER) != -1)
|
|
||||||
{
|
|
||||||
// found a noderef template
|
|
||||||
templateRef = new NodeRef(getTemplate());
|
|
||||||
}
|
|
||||||
|
|
||||||
Map root = DefaultModelHelper.buildDefaultModel(services, user, templateRef);
|
|
||||||
|
|
||||||
// merge models
|
|
||||||
if (model instanceof Map)
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
logger.debug("Found valid Map model to merge with FreeMarker: " + model);
|
|
||||||
|
|
||||||
root.putAll((Map)model);
|
|
||||||
}
|
|
||||||
|
|
||||||
model = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.model;
|
|
||||||
}
|
}
|
||||||
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param model The model to set.
|
* @param model The model to set.
|
||||||
*/
|
*/
|
||||||
@@ -230,7 +265,7 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns the template name.
|
* @return Returns the template NodeRef/classpath.
|
||||||
*/
|
*/
|
||||||
public String getTemplate()
|
public String getTemplate()
|
||||||
{
|
{
|
||||||
@@ -249,13 +284,40 @@ public class UITemplate extends SelfRenderingComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param template The template name to set.
|
* @param template The template NodeRef/classpath to set.
|
||||||
*/
|
*/
|
||||||
public void setTemplate(String template)
|
public void setTemplate(String template)
|
||||||
{
|
{
|
||||||
this.template = template;
|
this.template = template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the template path.
|
||||||
|
*/
|
||||||
|
public String getTemplatePath()
|
||||||
|
{
|
||||||
|
ValueBinding vb = getValueBinding("templatePath");
|
||||||
|
if (vb != null)
|
||||||
|
{
|
||||||
|
String val = (String)vb.getValue(getFacesContext());
|
||||||
|
if (val != null)
|
||||||
|
{
|
||||||
|
this.templatePath = val.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.templatePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param templatePath The template cm:name based path to set.
|
||||||
|
*/
|
||||||
|
public void setTemplatePath(String templatePath)
|
||||||
|
{
|
||||||
|
this.templatePath = templatePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Template Image resolver helper */
|
/** Template Image resolver helper */
|
||||||
private TemplateImageResolver imageResolver = new TemplateImageResolver()
|
private TemplateImageResolver imageResolver = new TemplateImageResolver()
|
||||||
{
|
{
|
||||||
|
@@ -52,6 +52,7 @@ public class TemplateTag extends BaseComponentTag
|
|||||||
|
|
||||||
setStringProperty(component, "engine", this.engine);
|
setStringProperty(component, "engine", this.engine);
|
||||||
setStringProperty(component, "template", this.template);
|
setStringProperty(component, "template", this.template);
|
||||||
|
setStringProperty(component, "templatePath", this.templatePath);
|
||||||
setStringBindingProperty(component, "model", this.model);
|
setStringBindingProperty(component, "model", this.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ public class TemplateTag extends BaseComponentTag
|
|||||||
|
|
||||||
this.engine = null;
|
this.engine = null;
|
||||||
this.template = null;
|
this.template = null;
|
||||||
|
this.templatePath = null;
|
||||||
this.model = null;
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +88,16 @@ public class TemplateTag extends BaseComponentTag
|
|||||||
{
|
{
|
||||||
this.template = template;
|
this.template = template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the template name based path
|
||||||
|
*
|
||||||
|
* @param templatePath the template name based path
|
||||||
|
*/
|
||||||
|
public void setTemplatePath(String templatePath)
|
||||||
|
{
|
||||||
|
this.templatePath = templatePath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the data model
|
* Set the data model
|
||||||
@@ -98,6 +110,9 @@ public class TemplateTag extends BaseComponentTag
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** the template name based path */
|
||||||
|
private String templatePath;
|
||||||
|
|
||||||
/** the engine name */
|
/** the engine name */
|
||||||
private String engine;
|
private String engine;
|
||||||
|
|
||||||
|
@@ -1292,7 +1292,13 @@
|
|||||||
|
|
||||||
<attribute>
|
<attribute>
|
||||||
<name>template</name>
|
<name>template</name>
|
||||||
<required>true</required>
|
<required>false</required>
|
||||||
|
<rtexprvalue>true</rtexprvalue>
|
||||||
|
</attribute>
|
||||||
|
|
||||||
|
<attribute>
|
||||||
|
<name>templatePath</name>
|
||||||
|
<required>false</required>
|
||||||
<rtexprvalue>true</rtexprvalue>
|
<rtexprvalue>true</rtexprvalue>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user