ate 2005/04/21 01:50:37 Modified: portal/src/webapp/WEB-INF/templates/layout/html/columns layout.vm portal/src/java/org/apache/jetspeed/velocity JetspeedPowerTool.java Added: portal/src/java/org/apache/jetspeed/velocity PageActionAccess.java Log: Fixes for http://issues.apache.org/jira/browse/JS2-222. Also (see issue comments) fixes Portlet API requirement to keep Render Parameters on activation of a DecoratorAction by no longer caching these. Finally, support true browser caching on the DecoratorAction image links by no longer embedding encoded NavigationalState. Revision Changes Path 1.21 +5 -4 jakarta-jetspeed-2/portal/src/webapp/WEB-INF/templates/layout/html/columns/layout.vm Index: layout.vm =================================================================== RCS file: /home/cvs/jakarta-jetspeed-2/portal/src/webapp/WEB-INF/templates/layout/html/columns/layout.vm,v retrieving revision 1.20 retrieving revision 1.21 diff -u -r1.20 -r1.21 --- layout.vm 29 Jan 2005 21:53:04 -0000 1.20 +++ layout.vm 21 Apr 2005 08:50:36 -0000 1.21 @@ -76,6 +76,7 @@ <table width="100%" cellspacing="0" cellpadding="0" > <tr> #set ($sizeIndex = 0) + #set ($layoutImageBase = "${jetspeed.pageBasePath}/content/images") #foreach($entry in $table) #if ($sizes) #if ($sizeIndex < $sizes.size()) @@ -108,7 +109,7 @@ #set($upUrl = $renderResponse.createRenderURL()) $!upUrl.setParameter("moveBy","0,-1") $!upUrl.setParameter("fragmentId","$f.id") - <a href="$upUrl"><img src="content/images/movePortletUp.gif" border="0" title="Move Portlet Up"/></a> + <a href="$upUrl"><img src="${layoutImageBase}/movePortletUp.gif" border="0" title="Move Portlet Up"/></a> #end </td> <td> @@ -120,7 +121,7 @@ #set($leftUrl = $renderResponse.createRenderURL()) $!leftUrl.setParameter("moveBy","-1,0") $!leftUrl.setParameter("fragmentId","$f.id") - <a href="$leftUrl"><img src="content/images/movePortletLeft.gif" border="0" title="Move Portlet Left"/></a> + <a href="$leftUrl"><img src="${layoutImageBase}/movePortletLeft.gif" border="0" title="Move Portlet Left"/></a> #end </td> <td align="center" style="font-size:smaller; font-weight:bold" > @@ -131,7 +132,7 @@ #set($rightUrl = $renderResponse.createRenderURL()) $!rightUrl.setParameter("moveBy","1,0") $!rightUrl.setParameter("fragmentId","$f.id") - <a href="$rightUrl"><img src="content/images/movePortletRight.gif" border="0" title="Move Portlet Right"/></a> + <a href="$rightUrl"><img src="${layoutImageBase}/movePortletRight.gif" border="0" title="Move Portlet Right"/></a> #end </td> </tr> @@ -143,7 +144,7 @@ #set($downUrl = $renderResponse.createRenderURL()) $!downUrl.setParameter("moveBy","0,1") $!downUrl.setParameter("fragmentId","$f.id") - <a href="$downUrl"><img src="content/images/movePortletDown.gif" border="0" title="Move Portlet Down"/></a> + <a href="$downUrl"><img src="${layoutImageBase}/movePortletDown.gif" border="0" title="Move Portlet Down"/></a> #end </td> <td ></td> 1.43 +143 -276 jakarta-jetspeed-2/portal/src/java/org/apache/jetspeed/velocity/JetspeedPowerTool.java Index: JetspeedPowerTool.java =================================================================== RCS file: /home/cvs/jakarta-jetspeed-2/portal/src/java/org/apache/jetspeed/velocity/JetspeedPowerTool.java,v retrieving revision 1.42 retrieving revision 1.43 diff -u -r1.42 -r1.43 --- JetspeedPowerTool.java 1 Mar 2005 23:32:15 -0000 1.42 +++ JetspeedPowerTool.java 21 Apr 2005 08:50:37 -0000 1.43 @@ -18,9 +18,8 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; -import java.security.AccessControlException; -import java.security.AccessController; import java.security.Principal; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -49,6 +48,7 @@ import org.apache.jetspeed.components.portletentity.PortletEntityNotGeneratedException; import org.apache.jetspeed.components.portletentity.PortletEntityNotStoredException; import org.apache.jetspeed.container.state.NavigationalState; +import org.apache.jetspeed.container.url.PortalURL; import org.apache.jetspeed.container.window.FailedToRetrievePortletWindow; import org.apache.jetspeed.container.window.PortletWindowAccessor; import org.apache.jetspeed.locator.LocatorDescriptor; @@ -59,11 +59,10 @@ import org.apache.jetspeed.om.page.Fragment; import org.apache.jetspeed.om.page.Page; import org.apache.jetspeed.request.RequestContext; -import org.apache.jetspeed.security.PortletPermission; -import org.apache.jetspeed.services.information.PortletURLProviderImpl; import org.apache.jetspeed.util.ArgUtil; import org.apache.pluto.om.entity.PortletEntity; import org.apache.pluto.om.portlet.ContentTypeSet; +import org.apache.pluto.om.window.PortletWindow; import org.apache.velocity.context.Context; /** @@ -619,11 +618,7 @@ /** * Gets the list of decorator actions for a window. Each window (on each - * page) has its own collection of actions associated with it. The creation - * of the decorator action list per window will only be called once per - * session. This optimization is to avoid the expensive operation of - * security checks and action object creation and logic on a per request - * basis. + * page) has its own collection of actionAccess flags associated with it. * * @return A list of actions available to the current window, filtered by * securty access and current state. @@ -631,105 +626,152 @@ */ public List getDecoratorActions() { - try - { - - String key = getPage().getId() + ":" + this.getCurrentFragment().getId(); - Map sessionActions = (Map) getRequestContext().getSessionAttribute(POWER_TOOL_SESSION_ACTIONS); - if (null == sessionActions) - { - sessionActions = new HashMap(); - getRequestContext().setSessionAttribute(POWER_TOOL_SESSION_ACTIONS, sessionActions); - } - PortletWindowActionState actionState = (PortletWindowActionState) sessionActions.get(key); + return getDecoratorActions(false); + } - String state = getWindowState().toString(); - String mode = getPortletMode().toString(); + /** + * Gets the list of decorator actions for a page. Each layout fragment on a + * page has its own collection of actionAccess flags associated with it. + * + * @return A list of actions available to the current window, filtered by + * securty access and current state. + * @throws Exception + */ + public List getPageDecoratorActions() throws Exception + { + return getDecoratorActions(true); + } - if (null == actionState) - { - actionState = new PortletWindowActionState(state, mode); - sessionActions.put(key, actionState); - } - else + protected List getDecoratorActions(boolean layout) + { + try + { + String key = getPage().getId(); + boolean anonymous = !getLoggedOn(); + PageActionAccess pageActionAccess = null; + + synchronized (getRequestContext().getRequest().getSession()) { - // check to see if state or mode has changed - if (actionState.getWindowState().equals(state)) + Map sessionActions = (Map) getRequestContext().getSessionAttribute(POWER_TOOL_SESSION_ACTIONS); + if ( sessionActions == null ) { - if (actionState.getPortletMode().equals(mode)) - { - // nothing has changed - return actionState.getActions(); - } - else - { - actionState.setPortletMode(mode); - } + sessionActions = new HashMap(); + getRequestContext().setSessionAttribute(POWER_TOOL_SESSION_ACTIONS, sessionActions); } else { - actionState.setWindowState(state); + pageActionAccess = (PageActionAccess)sessionActions.get(key); + } + if ( pageActionAccess == null ) + { + pageActionAccess = new PageActionAccess(anonymous, getPage()); + sessionActions.put(key, pageActionAccess); + } + else + { + pageActionAccess.checkReset(getLoggedOn(), getPage()); } - // something has changed, rebuild the list } - - List actions = actionState.getActions(); - actions.clear(); - + PortletDefinitionComposite portlet = (PortletDefinitionComposite) getCurrentPortletEntity() .getPortletDefinition(); if (null == portlet) { - return actions; // allow nothing + return Collections.EMPTY_LIST; // allow nothing } - ContentTypeSet content = portlet.getContentTypeSet(); + List actions = new ArrayList(); - if (state.equals(WindowState.NORMAL.toString())) - { - createAction(actions, JetspeedActions.INDEX_MINIMIZE, portlet); - createAction(actions, JetspeedActions.INDEX_MAXIMIZE, portlet); - } - else if (state.equals(WindowState.MAXIMIZED.toString())) - { - createAction(actions, JetspeedActions.INDEX_MINIMIZE, portlet); - createAction(actions, JetspeedActions.INDEX_NORMAL, portlet); - } - else - // minimized - { - createAction(actions, JetspeedActions.INDEX_MAXIMIZE, portlet); - createAction(actions, JetspeedActions.INDEX_NORMAL, portlet); - } + PortletMode mode = getPortletMode(); + WindowState state = getWindowState(); + + ContentTypeSet content = portlet.getContentTypeSet(); + Fragment fragment = getCurrentFragment(); + String fragmentId = fragment.getId(); + String portletName = portlet.getUniqueName(); + PortletWindow window = windowAccess.getPortletWindow(fragment); + String resourceBase = getPageBasePath(); - if (mode.equals(PortletMode.VIEW.toString())) + if ( !layout ) { - if (content.supportsPortletMode(PortletMode.EDIT) && checkAccess(Page.EDIT_ACTION)) + if (state.equals(WindowState.NORMAL)) { - createAction(actions, JetspeedActions.INDEX_EDIT, portlet); + if ( pageActionAccess.checkWindowState(fragmentId, portletName, WindowState.MINIMIZED)) + { + actions.add(createWindowStateAction(window, JetspeedActions.MINIMIZE, WindowState.MINIMIZED, resourceBase)); + } + if ( pageActionAccess.checkWindowState(fragmentId, portletName, WindowState.MAXIMIZED)) + { + actions.add(createWindowStateAction(window, JetspeedActions.MAXIMIZE, WindowState.MAXIMIZED, resourceBase)); + } } - if (content.supportsPortletMode(PortletMode.HELP)) + else if (state.equals(WindowState.MAXIMIZED)) { - createAction(actions, JetspeedActions.INDEX_HELP, portlet); + if ( pageActionAccess.checkWindowState(fragmentId, portletName, WindowState.MINIMIZED)) + { + actions.add(createWindowStateAction(window, JetspeedActions.MINIMIZE, WindowState.MINIMIZED, resourceBase)); + } + if ( pageActionAccess.checkWindowState(fragmentId, portletName, JetspeedActions.RESTORED)) + { + actions.add(createWindowStateAction(window, JetspeedActions.RESTORE, WindowState.NORMAL, resourceBase)); + } } - } - else if (mode.equals(PortletMode.EDIT.toString())) - { - createAction(actions, JetspeedActions.INDEX_VIEW, portlet); - if (content.supportsPortletMode(PortletMode.HELP)) + else + // minimized { - createAction(actions, JetspeedActions.INDEX_HELP, portlet); + if ( pageActionAccess.checkWindowState(fragmentId, portletName, WindowState.MAXIMIZED)) + { + actions.add(createWindowStateAction(window, JetspeedActions.MAXIMIZE, WindowState.MAXIMIZED, resourceBase)); + } + if ( pageActionAccess.checkWindowState(fragmentId, portletName, JetspeedActions.RESTORED)) + { + actions.add(createWindowStateAction(window, JetspeedActions.RESTORE, WindowState.NORMAL, resourceBase)); + } } } - else - // help + + if ( !layout || pageActionAccess.isEditAllowed() ) { - createAction(actions, JetspeedActions.INDEX_VIEW, portlet); - if (content.supportsPortletMode(PortletMode.EDIT) && checkAccess(Page.EDIT_ACTION)) + if (mode.equals(PortletMode.VIEW)) { - createAction(actions, JetspeedActions.INDEX_EDIT, portlet); + if (content.supportsPortletMode(PortletMode.EDIT) && pageActionAccess.isEditAllowed() && + pageActionAccess.checkPortletMode(fragmentId, portletName, PortletMode.EDIT)) + { + actions.add(createPortletModeAction(window, JetspeedActions.EDIT, PortletMode.EDIT, resourceBase)); + } + if (content.supportsPortletMode(PortletMode.HELP) && + pageActionAccess.checkPortletMode(fragmentId, portletName, PortletMode.HELP)) + { + actions.add(createPortletModeAction(window, JetspeedActions.HELP, PortletMode.HELP, resourceBase)); + } + } + else if (mode.equals(PortletMode.EDIT)) + { + if (pageActionAccess.checkPortletMode(fragmentId, portletName, PortletMode.VIEW)) + { + actions.add(createPortletModeAction(window, JetspeedActions.VIEW, PortletMode.VIEW, resourceBase)); + } + if (content.supportsPortletMode(PortletMode.HELP) && + pageActionAccess.checkPortletMode(fragmentId, portletName, PortletMode.HELP)) + { + actions.add(createPortletModeAction(window, JetspeedActions.HELP, PortletMode.HELP, resourceBase)); + } + } + else + // help + { + if (pageActionAccess.checkPortletMode(fragmentId, portletName, PortletMode.VIEW)) + { + actions.add(createPortletModeAction(window, JetspeedActions.VIEW, PortletMode.VIEW, resourceBase)); + } + if (content.supportsPortletMode(PortletMode.EDIT) && pageActionAccess.isEditAllowed() && + pageActionAccess.checkPortletMode(fragmentId, portletName, PortletMode.EDIT)) + { + actions.add(createPortletModeAction(window, JetspeedActions.EDIT, PortletMode.EDIT, resourceBase)); + } } } + return actions; } catch (Exception e) @@ -739,213 +781,35 @@ } } - protected boolean checkAccess(String action) + protected DecoratorAction createDecoratorAction( String resourceBase, String actionName ) { - boolean access = true; - try - { - getPage().checkAccess(action); - } - catch (SecurityException se) - { - access = false; - } - return access; + // TODO: HARD-CODED .gif link + String link = getRequestContext().getResponse().encodeURL(resourceBase+"/content/images/"+actionName+".gif"); + return new DecoratorAction(actionName, actionName, link); } + /** - * Gets the list of decorator actions for a page. Each layout fragment on a - * page has its own collection of actions associated with it. The creation - * of the layout decorator action list per page will only be called once per - * session. This optimization is to avoid the expensive operation of - * security checks and action object creation and logic on a per request - * basis. - * - * @return A list of actions available to the current window, filtered by - * securty access and current state. - * @throws Exception - */ - public List getPageDecoratorActions() throws Exception - { - // check page access - boolean readOnlyPageAccess = true; - try - { - getPage().checkAccess(Page.EDIT_ACTION); - readOnlyPageAccess = false; - } - catch (SecurityException se) - { - } - - // determine cached actions state key - String key = "PAGE " + getPage().getId() + ":" + this.getCurrentFragment().getId() + ":" - + (readOnlyPageAccess ? Page.VIEW_ACTION : Page.EDIT_ACTION); - - // get cached actions state - - Map sessionActions = (Map) getRequestContext().getSessionAttribute(POWER_TOOL_SESSION_ACTIONS); - if (null == sessionActions) - { - sessionActions = new HashMap(); - getRequestContext().setSessionAttribute(POWER_TOOL_SESSION_ACTIONS, sessionActions); - } - PortletWindowActionState actionState = (PortletWindowActionState) sessionActions.get(key); - - String state = getWindowState().toString(); - String mode = getPortletMode().toString(); - - if (null == actionState) - { - actionState = new PortletWindowActionState(state, mode); - sessionActions.put(key, actionState); - } - else - { - if (actionState.getPortletMode().equals(mode)) - { - // nothing has changed - return actionState.getActions(); - } - // something has changed, rebuild the list - actionState.setPortletMode(mode); - } - - List actions = actionState.getActions(); - actions.clear(); - - // if there is no root fragment, return no actions - PortletDefinitionComposite portlet = (PortletDefinitionComposite) getCurrentPortletEntity() - .getPortletDefinition(); - if (null == portlet) - { - return actions; - } - - // if the page is being read only accessed, return no actions - if (readOnlyPageAccess) - { - return actions; - } - - // generate standard page actions depending on - // portlet capabilities - ContentTypeSet content = portlet.getContentTypeSet(); - if (mode.equals(PortletMode.VIEW.toString())) - { - if (content.supportsPortletMode(PortletMode.EDIT)) - { - createAction(actions, JetspeedActions.INDEX_EDIT, portlet); - } - if (content.supportsPortletMode(PortletMode.HELP)) - { - createAction(actions, JetspeedActions.INDEX_HELP, portlet); - } - } - else if (mode.equals(PortletMode.EDIT.toString())) - { - createAction(actions, JetspeedActions.INDEX_VIEW, portlet); - if (content.supportsPortletMode(PortletMode.HELP)) - { - createAction(actions, JetspeedActions.INDEX_HELP, portlet); - } - } - else - // help - { - createAction(actions, JetspeedActions.INDEX_VIEW, portlet); - if (content.supportsPortletMode(PortletMode.EDIT)) - { - createAction(actions, JetspeedActions.INDEX_EDIT, portlet); - } - } - return actions; - } - - /** - * Determines whether the access request indicated by the specified - * permission should be allowed or denied, based on the security policy - * currently in effect. - * - * @param resource - * The fully qualified resource name of the portlet - * (PA::portletName) - * @param action - * The action to perform on this resource (i.e. view, edit, help, - * max, min...) - * @return true if the action is allowed, false if it is not + * Creates a Decorator PortletMode Action to be added to the list of actions + * decorating a portlet. */ - protected boolean checkPermission( String resource, String action ) + protected DecoratorAction createPortletModeAction( PortletWindow window, String actionName, PortletMode mode, String resourceBase ) { - try - { - // TODO: it may be better to check the PagePermission for the outer - // most - // fragment (i.e. the PSML page) - AccessController.checkPermission(new PortletPermission(resource, action)); - } - catch (AccessControlException e) - { - return false; - } - return true; + DecoratorAction action = createDecoratorAction(resourceBase, actionName); + PortalURL portalURL = getRequestContext().getPortalURL(); + action.setAction(portalURL.createPortletURL(window, mode, null, portalURL.isSecure()).toString()); + return action; } /** - * Creates a Decorator Action link to be added to the list of actions + * Creates a Decorator WindowState Action to be added to the list of actions * decorating a portlet. - * - * @param actions - * @param kind - * @param resource - * @return - * @throws Exception */ - public DecoratorAction createAction( List actions, int actionId, PortletDefinitionComposite portlet ) - throws Exception + protected DecoratorAction createWindowStateAction( PortletWindow window, String actionName, WindowState state, String resourceBase ) { - String resource = portlet.getUniqueName(); - String actionName = JetspeedActions.ACTIONS[actionId]; - if (checkPermission(resource, actionName)) // TODO: - // should - // be - // !checkPermission - { - return null; - } - DecoratorAction action = new DecoratorAction(actionName, actionName, "content/images/" + actionName + ".gif"); // TODO: - // HARD-CODED - // .gif - - PortletEntity entity = getCurrentPortletEntity(); - - PortletURLProviderImpl url = new PortletURLProviderImpl(getRequestContext(), windowAccess - .getPortletWindow(getCurrentFragment())); - switch (actionId) - { - case JetspeedActions.INDEX_MAXIMIZE : - url.setWindowState(WindowState.MAXIMIZED); - break; - case JetspeedActions.INDEX_MINIMIZE : - url.setWindowState(WindowState.MINIMIZED); - break; - case JetspeedActions.INDEX_NORMAL : - url.setWindowState(WindowState.NORMAL); - break; - case JetspeedActions.INDEX_VIEW : - url.setPortletMode(PortletMode.VIEW); - break; - case JetspeedActions.INDEX_EDIT : - url.setPortletMode(PortletMode.EDIT); - break; - case JetspeedActions.INDEX_HELP : - url.setPortletMode(PortletMode.HELP); - break; - } - - action.setAction(url.toString()); - actions.add(action); + DecoratorAction action = createDecoratorAction(resourceBase, actionName); + PortalURL portalURL = getRequestContext().getPortalURL(); + action.setAction(portalURL.createPortletURL(window, null, state, portalURL.isSecure()).toString()); return action; - } /** @@ -1029,6 +893,9 @@ return (principal != null); } - + public String getPageBasePath() + { + return getRequestContext().getPortalURL().getPageBasePath(); + } } 1.1 jakarta-jetspeed-2/portal/src/java/org/apache/jetspeed/velocity/PageActionAccess.java Index: PageActionAccess.java =================================================================== /* * Copyright 2000-2001,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jetspeed.velocity; import java.io.Serializable; import java.security.AccessControlException; import java.security.AccessController; import java.util.HashMap; import javax.portlet.PortletMode; import javax.portlet.WindowState; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jetspeed.JetspeedActions; import org.apache.jetspeed.om.page.Page; import org.apache.jetspeed.security.PortletPermission; /** * PageActionAccess * * @author <a href="mailto:[EMAIL PROTECTED]">Ate Douma</a> * @version $Id: PageActionAccess.java,v 1.1 2005/04/21 08:50:37 ate Exp $ */ public class PageActionAccess implements Serializable { protected static final Log log = LogFactory.getLog(PageActionAccess.class); private static final class ActionAccess implements Serializable { int checkedFlags; int actionFlags; } private boolean anonymous; private boolean editAllowed; private HashMap fragmentActionAccess; public PageActionAccess(boolean anonymous, Page page) { this.anonymous = anonymous; this.editAllowed = checkEditPage(page); this.fragmentActionAccess = new HashMap(); } public void checkReset(boolean anonymous, Page page) { if (this.anonymous != anonymous) { this.anonymous = anonymous; this.editAllowed = checkEditPage(page); this.fragmentActionAccess.clear(); } } public boolean isAnonymous() { return anonymous; } public boolean isEditAllowed() { return editAllowed; } public boolean checkPortletMode(String fragmentId, String portletName, PortletMode mode) { return checkActionAccess(fragmentId, portletName, mode.toString()); } public boolean checkWindowState(String fragmentId, String portletName, WindowState state) { return checkActionAccess(fragmentId, portletName, state.toString()); } protected synchronized boolean checkActionAccess(String fragmentId, String portletName, String action) { try { int actionIndex = getActionMask(action); ActionAccess actionAccess = (ActionAccess)fragmentActionAccess.get(fragmentId); if ( actionAccess == null ) { actionAccess = new ActionAccess(); fragmentActionAccess.put(fragmentId, actionAccess); } if ( (actionAccess.checkedFlags & actionIndex) != actionIndex ) { // TODO: not handling PortletPermission checks yet // boolean access = checkPermission(portletName, action); boolean access = true; if ( access ) { actionAccess.actionFlags |= actionIndex; } actionAccess.checkedFlags |= actionIndex; } return ((actionAccess.actionFlags & actionIndex) == actionIndex); } catch (IndexOutOfBoundsException e) { log.error("Unknown action: "+action, e); return false; } } /** * Determines whether the access request indicated by the specified * permission should be allowed or denied, based on the security policy * currently in effect. * * @param resource * The fully qualified resource name of the portlet * (PA::portletName) * @param action * The action to perform on this resource (i.e. view, edit, help, * max, min...) * @return true if the action is allowed, false if it is not */ protected boolean checkPermission( String resource, String action ) { try { // TODO: it may be better to check the PagePermission for the outer // most // fragment (i.e. the PSML page) AccessController.checkPermission(new PortletPermission(resource, action)); } catch (AccessControlException e) { return false; } return true; } protected boolean checkEditPage(Page page) { boolean allowed = false; try { page.checkAccess(Page.EDIT_ACTION); allowed = true; } catch (SecurityException se) {} return allowed; } protected int getActionMask(String action) throws IndexOutOfBoundsException { int i = 0; // will throw IndexOutOfBoundsExceptions on unknown action while ( !JetspeedActions.ACTIONS[i++].equals(action) ) ; return 1<<i; } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]