diff --git a/config/alfresco/extension/web-client-config-custom.xml.sample b/config/alfresco/extension/web-client-config-custom.xml.sample index fcaf522a8f..712964539a 100644 --- a/config/alfresco/extension/web-client-config-custom.xml.sample +++ b/config/alfresco/extension/web-client-config-custom.xml.sample @@ -68,11 +68,8 @@ - - - diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 1956d9b31a..b728d940cc 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -94,6 +94,7 @@ recent_spaces=Recent Spaces shortcuts=Shortcuts company_home=Company Home my_home=My Home +guest_home=Guest Home new_search=New Search search_results=Search Results search_detail=Search for \"{0}\" results shown below @@ -636,6 +637,7 @@ edit_rule_desc=This wizard helps you modify a rule. edit_rule_finish_instruction=To update the rule click Finish. To review or change your selections click Back. select_action=Select Action select_an_action=Select an action... +select_a_type=Select a type... action=Action action_settings=Action Settings set_action_values=Set action values @@ -802,10 +804,8 @@ delete_item_confirm=Are you sure you want to permanently delete \"{0}\" from the recover_item=Recover Item recover_item_info=Recover an item from the deleted file store recover_item_confirm=Are you sure you want to recover \"{0}\" from the deleted file store? -delete_all_items=Delete All Items delete_all_items_info=Permanently delete all files and spaces from the deleted file store delete_all_items_confirm=Are you sure you want to permanently delete all files and spaces from the deleted file store? The items cannot be recovered once this action has been performed. -recover_all_items=Recover All Items recover_all_items_info=Recover all files and spaces from the deleted file store recover_all_items_confirm=Are you sure you want to recover all the deleted files and spaces from the deleted file store? delete_listed_items=Delete Listed Items diff --git a/config/alfresco/web-client-config-actions.xml b/config/alfresco/web-client-config-actions.xml index 406f207f15..40243aa8eb 100644 --- a/config/alfresco/web-client-config-actions.xml +++ b/config/alfresco/web-client-config-actions.xml @@ -381,6 +381,7 @@ TakeOwnership + org.alfresco.web.action.evaluator.TakeOwnershipDocEvaluator take_ownership /images/icons/take_ownership.gif #{DocumentDetailsBean.takeOwnership} @@ -601,6 +602,7 @@ + @@ -610,6 +612,7 @@ + diff --git a/config/alfresco/web-client-config-properties.xml b/config/alfresco/web-client-config-properties.xml index 762489503e..e7afc96f0f 100644 --- a/config/alfresco/web-client-config-properties.xml +++ b/config/alfresco/web-client-config-properties.xml @@ -5,21 +5,27 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + @@ -138,12 +162,6 @@ - - - - - - diff --git a/config/alfresco/web-client-config.xml b/config/alfresco/web-client-config.xml index 441149f6f9..00d737eccd 100644 --- a/config/alfresco/web-client-config.xml +++ b/config/alfresco/web-client-config.xml @@ -193,7 +193,7 @@ - + diff --git a/source/java/org/alfresco/web/action/evaluator/TakeOwnershipDocEvaluator.java b/source/java/org/alfresco/web/action/evaluator/TakeOwnershipDocEvaluator.java new file mode 100644 index 0000000000..091302ebe5 --- /dev/null +++ b/source/java/org/alfresco/web/action/evaluator/TakeOwnershipDocEvaluator.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.web.action.evaluator; + +import org.alfresco.web.action.ActionEvaluator; +import org.alfresco.web.bean.repository.Node; + +/** + * UI Action Evaluator - Take ownership of a document. + * + * @author Kevin Roast + */ +public final class TakeOwnershipDocEvaluator implements ActionEvaluator +{ + /** + * @see org.alfresco.web.action.ActionEvaluator#evaluate(org.alfresco.web.bean.repository.Node) + */ + public boolean evaluate(Node node) + { + return (node.isLocked() == false); + } +} diff --git a/source/java/org/alfresco/web/app/Application.java b/source/java/org/alfresco/web/app/Application.java index f7502318de..04552ed611 100644 --- a/source/java/org/alfresco/web/app/Application.java +++ b/source/java/org/alfresco/web/app/Application.java @@ -72,6 +72,7 @@ public class Application private static String emailTemplatesFolderName; private static String savedSearchesFolderName; private static String scriptsFolderName; + private static String guestHomeFolderName; /** * Private constructor to prevent instantiation of this class @@ -380,7 +381,6 @@ public class Application return getSavedSearchesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); } - /** * @return Return the JavaScript scripts folder name */ @@ -397,6 +397,22 @@ public class Application return getScriptsFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); } + /** + * @return Return the Guest Home folder name + */ + public static String getGuestHomeFolderName(ServletContext context) + { + return getGuestHomeFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the Guest Home folder name + */ + public static String getGuestHomeFolderName(FacesContext context) + { + return getGuestHomeFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + /** * Set the language locale for the current user context * @@ -764,6 +780,24 @@ public class Application return scriptsFolderName; } + /** + * Returns the Guest Home folder name name + * + * @param context The spring context + * @return The Guest Home folder name + */ + private static String getGuestHomeFolderName(WebApplicationContext context) + { + if (guestHomeFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + guestHomeFolderName = configuration.getProperty("spaces.guest_home.childname"); + } + + return guestHomeFolderName; + } + /** * Retrieves the configured error page for the application * diff --git a/source/java/org/alfresco/web/bean/NavigationBean.java b/source/java/org/alfresco/web/bean/NavigationBean.java index 897aff4c8e..0cc97b2301 100644 --- a/source/java/org/alfresco/web/bean/NavigationBean.java +++ b/source/java/org/alfresco/web/bean/NavigationBean.java @@ -105,7 +105,7 @@ public class NavigationBean { this.ruleService = ruleService; } - + /** * @param cifsServer The cifsServer to set. */ @@ -129,6 +129,14 @@ public class NavigationBean { return Application.getCurrentUser(FacesContext.getCurrentInstance()); } + + /** + * @return true if the system is running within a JSR-168 portal container + */ + public boolean getInPortalServer() + { + return Application.inPortalServer(); + } /** * Return the expanded state of the Shelf panel wrapper component @@ -476,6 +484,67 @@ public class NavigationBean return this.dispatchContext; } + /** + * @return Node representing the Company Home folder + */ + public Node getCompanyHomeNode() + { + if (this.companyHomeNode == null) + { + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + this.companyHomeNode = new Node(companyRootRef); + } + return this.companyHomeNode; + } + + /** + * @return Node representing the Guest Home Space folder + */ + public Node getGuestHomeNode() + { + if (this.guestHomeNode == null) + { + try + { + FacesContext fc = FacesContext.getCurrentInstance(); + String xpath = Application.getRootPath(fc) + "/" + Application.getGuestHomeFolderName(fc); + List guestHomeRefs = this.searchService.selectNodes( + this.nodeService.getRootNode(Repository.getStoreRef()), + xpath, null, this.namespaceService, false); + if (guestHomeRefs.size() == 1) + { + this.guestHomeNode = new Node(guestHomeRefs.get(0)); + } + } + catch (InvalidNodeRefException err1) + { + // cannot continue if this occurs + } + catch (AccessDeniedException err2) + { + // cannot see node if this occurs + } + } + return this.guestHomeNode; + } + + /** + * @return true if the Company home node is accessable to the current user + */ + public boolean getCompanyHomeVisible() + { + return getCompanyHomeNode().hasPermission(PermissionService.READ); + } + + /** + * @return true if the Guest home node is accessable to the current user + */ + public boolean getGuestHomeVisible() + { + Node guestHome = getGuestHomeNode(); + return guestHome != null && guestHome.hasPermission(PermissionService.READ); + } + // ------------------------------------------------------------------------------ // Navigation action event handlers @@ -514,11 +583,10 @@ public class NavigationBean if (LOCATION_COMPANY.equals(location)) { List elements = new ArrayList(1); - NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); - String companySpaceName = Repository.getNameForNode(this.nodeService, companyRootRef); - elements.add(new NavigationBreadcrumbHandler(companyRootRef, companySpaceName)); + Node companyHome = getCompanyHomeNode(); + elements.add(new NavigationBreadcrumbHandler(companyHome.getNodeRef(), companyHome.getName())); setLocation(elements); - setCurrentNodeId(companyRootRef.getId()); + setCurrentNodeId(companyHome.getId()); } else if (LOCATION_HOME.equals(location)) { @@ -530,6 +598,14 @@ public class NavigationBean setLocation(elements); setCurrentNodeId(homeSpaceRef.getId()); } + else if (LOCATION_GUEST.equals(location)) + { + List elements = new ArrayList(1); + Node guestHome = getGuestHomeNode(); + elements.add(new NavigationBreadcrumbHandler(guestHome.getNodeRef(), guestHome.getName())); + setLocation(elements); + setCurrentNodeId(guestHome.getId()); + } // we need to force a navigation to refresh the browse screen breadcrumb context.getApplication().getNavigationHandler().handleNavigation(context, null, "browse"); @@ -657,6 +733,7 @@ public class NavigationBean /** constant values used by the toolbar location modelist control */ private static final String LOCATION_COMPANY = "company"; private static final String LOCATION_HOME = "home"; + private static final String LOCATION_GUEST = "guest"; private static final String ERROR_DELETED_FOLDER = "error_deleted_folder"; @@ -693,6 +770,12 @@ public class NavigationBean /** Node we are using for dispatching */ private Node dispatchContext = null; + /** Node representing the guest home */ + private Node guestHomeNode = null; + + /** Node representing the company home */ + private Node companyHomeNode = null; + /** Current toolbar location */ private String toolbarLocation = LOCATION_HOME; diff --git a/source/java/org/alfresco/web/bean/actions/BaseActionWizard.java b/source/java/org/alfresco/web/bean/actions/BaseActionWizard.java index f62a63863a..dd05c0ddb5 100644 --- a/source/java/org/alfresco/web/bean/actions/BaseActionWizard.java +++ b/source/java/org/alfresco/web/bean/actions/BaseActionWizard.java @@ -329,10 +329,8 @@ public abstract class BaseActionWizard extends BaseWizardBean // add the well known object type to start with this.objectTypes = new ArrayList(5); - this.objectTypes.add(new SelectItem(ContentModel.TYPE_CONTENT.toString(), - Application.getMessage(context, "content"))); - // add any configured content sub-types to the list + // add any configured content or folder sub-types to the list ConfigService svc = Application.getConfigService(FacesContext.getCurrentInstance()); Config wizardCfg = svc.getConfig("Action Wizards"); if (wizardCfg != null) @@ -345,8 +343,13 @@ public abstract class BaseActionWizard extends BaseWizardBean QName idQName = Repository.resolveToQName(child.getAttribute("name")); TypeDefinition typeDef = this.dictionaryService.getType(idQName); + // make sure the type is a subtype of content or folder but not + // the content or folder type itself if (typeDef != null && - this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT)) + typeDef.getName().equals(ContentModel.TYPE_CONTENT) == false && + typeDef.getName().equals(ContentModel.TYPE_FOLDER) == false && + (this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT) || + this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_FOLDER))) { // try and get the display label from config String label = Utils.getDisplayLabel(context, child); @@ -370,6 +373,10 @@ public abstract class BaseActionWizard extends BaseWizardBean // make sure the list is sorted by the label QuickSort sorter = new QuickSort(this.objectTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); sorter.sort(); + + // add the select an action item at the start of the list + this.objectTypes.add(0, new SelectItem("null", + Application.getMessage(FacesContext.getCurrentInstance(), "select_a_type"))); } else { diff --git a/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java b/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java index cef45fff85..69048669df 100644 --- a/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java +++ b/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java @@ -31,6 +31,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.CopyService; @@ -260,7 +261,7 @@ public class ClipboardBean // we create a special Link Object node that has a property to reference the original // use FileFolderService to check if already exists as using nodeService directly here - String linkTo = Application.getMessage(FacesContext.getCurrentInstance(), MSG_LINK_TO); + String linkTo = Application.getMessage(FacesContext.getCurrentInstance(), MSG_LINK_TO); // create the node using the nodeService (can only use FileFolderService for content) Map props = new HashMap(4, 1.0f); @@ -357,8 +358,16 @@ public class ClipboardBean } catch (FileExistsException fileExistsErr) { - String copyOf = Application.getMessage(FacesContext.getCurrentInstance(), MSG_COPY_OF); - name = copyOf + ' ' + name; + if (item.Mode == ClipboardStatus.COPY) + { + String copyOf = Application.getMessage(FacesContext.getCurrentInstance(), MSG_COPY_OF); + name = copyOf + ' ' + name; + } + else + { + // we should not rename an item when it is being moved + throw fileExistsErr; + } } } } diff --git a/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java b/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java index 13f864b46c..04cc367d9b 100644 --- a/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java +++ b/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java @@ -227,6 +227,12 @@ public class EditContentPropertiesDialog extends BaseDialogBean } } + @Override + public boolean getFinishButtonDisabled() + { + return false; + } + // ------------------------------------------------------------------------------ // Bean getters and setters diff --git a/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java b/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java index 123bd4f8c9..8137ec929b 100644 --- a/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/BaseComponentGenerator.java @@ -35,6 +35,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet; import org.alfresco.web.ui.repo.component.property.UIPropertySheet.ClientValidation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.StringUtils; import org.springframework.web.jsf.FacesContextUtils; public abstract class BaseComponentGenerator implements IComponentGenerator @@ -398,7 +399,8 @@ public abstract class BaseComponentGenerator implements IComponentGenerator // add the validation failed message to show (use the value of the // label component of the given item) String msg = Application.getMessage(context, "validation_mandatory"); - params.add("'" + MessageFormat.format(msg, new Object[] {item.getResolvedDisplayLabel()}) + "'"); + addStringConstraintParam(params, + MessageFormat.format(msg, new Object[] {item.getResolvedDisplayLabel()})); // add the validation case to the property sheet propertySheet.addClientValidation(new ClientValidation("validateMandatory", @@ -504,12 +506,12 @@ public abstract class BaseComponentGenerator implements IComponentGenerator try { // encode the expression so it can be unescaped by JavaScript - params.add("'" + URLEncoder.encode(expression, "UTF-8") + "'"); + addStringConstraintParam(params, URLEncoder.encode(expression, "UTF-8")); } catch (UnsupportedEncodingException e) { // just add the expression as is - params.add("'" + expression + "'"); + addStringConstraintParam(params, expression); } // add the requiresMatch parameter @@ -517,12 +519,12 @@ public abstract class BaseComponentGenerator implements IComponentGenerator // add the validation failed messages String matchMsg = Application.getMessage(context, "validation_regex"); - params.add("'" + MessageFormat.format(matchMsg, new Object[] - {property.getResolvedDisplayLabel()}) + "'"); + addStringConstraintParam(params, + MessageFormat.format(matchMsg, new Object[] {property.getResolvedDisplayLabel()})); String noMatchMsg = Application.getMessage(context, "validation_regex_not_match"); - params.add("'" + MessageFormat.format(noMatchMsg, new Object[] - {property.getResolvedDisplayLabel()}) + "'"); + addStringConstraintParam(params, + MessageFormat.format(noMatchMsg, new Object[] {property.getResolvedDisplayLabel()})); // add the validation case to the property sheet propertySheet.addClientValidation(new ClientValidation("validateRegex", @@ -562,8 +564,8 @@ public abstract class BaseComponentGenerator implements IComponentGenerator // add the validation failed message to show String msg = Application.getMessage(context, "validation_string_length"); - params.add("'" + MessageFormat.format(msg, new Object[] - {property.getResolvedDisplayLabel(), min, max}) + "'"); + addStringConstraintParam(params, + MessageFormat.format(msg, new Object[] {property.getResolvedDisplayLabel(), min, max})); // add the validation case to the property sheet propertySheet.addClientValidation(new ClientValidation("validateStringLength", @@ -603,8 +605,8 @@ public abstract class BaseComponentGenerator implements IComponentGenerator // add the validation failed message to show String msg = Application.getMessage(context, "validation_numeric_range"); - params.add("'" + MessageFormat.format(msg, new Object[] - {property.getResolvedDisplayLabel(), min, max}) + "'"); + addStringConstraintParam(params, + MessageFormat.format(msg, new Object[] {property.getResolvedDisplayLabel(), min, max})); // add the validation case to the property sheet propertySheet.addClientValidation(new ClientValidation("validateNumberRange", @@ -695,6 +697,20 @@ public abstract class BaseComponentGenerator implements IComponentGenerator return getDataDictionary(context).getAssociationDefinition(node, associationName); } + /** + * Adds the given string parameter to the list of parameters to be used for + * validating constraints on the client. + * This method adds the quotes around the given parameter and also escapes + * any ocurrences of the double quote character. + * + * @param params The list of parameters for the constraint + * @param param The string parameter to add + */ + protected void addStringConstraintParam(List params, String param) + { + params.add("\"" + StringUtils.replace(param, "\"", "\\\"") + "\""); + } + private DataDictionary getDataDictionary(FacesContext context) { if (this.dataDictionary == null) diff --git a/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java b/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java index 048c132ba3..d95da6cffb 100644 --- a/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java +++ b/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java @@ -41,6 +41,12 @@ public class EditSpaceDialog extends CreateSpaceDialog return Application.getMessage(FacesContext.getCurrentInstance(), "ok"); } + @Override + public boolean getFinishButtonDisabled() + { + return false; + } + /** * Returns the editable node * diff --git a/source/java/org/alfresco/web/ui/repo/component/shelf/UIClipboardShelfItem.java b/source/java/org/alfresco/web/ui/repo/component/shelf/UIClipboardShelfItem.java index cd82ec0969..d849bd071f 100644 --- a/source/java/org/alfresco/web/ui/repo/component/shelf/UIClipboardShelfItem.java +++ b/source/java/org/alfresco/web/ui/repo/component/shelf/UIClipboardShelfItem.java @@ -200,7 +200,7 @@ public class UIClipboardShelfItem extends UIShelfItem out.write(buildActionLink(ACTION_REMOVE_ITEM, i, bundle.getString(MSG_REMOVE_ITEM), WebResources.IMAGE_REMOVE)); out.write(" "); out.write(buildActionLink(ACTION_PASTE_ITEM, i, bundle.getString(MSG_PASTE_ITEM), WebResources.IMAGE_PASTE)); - if (item.Mode == ClipboardStatus.COPY) + if (item.Mode == ClipboardStatus.COPY && dd.isSubClass(item.Node.getType(), ContentModel.TYPE_LINK) == false) { out.write(" "); out.write(buildActionLink(ACTION_PASTE_LINK, i, bundle.getString(MSG_PASTE_LINK), WebResources.IMAGE_PASTE_LINK)); diff --git a/source/web/images/icons/required_field.gif b/source/web/images/icons/required_field.gif index 32280a6eef..a0ffa2caf9 100644 Binary files a/source/web/images/icons/required_field.gif and b/source/web/images/icons/required_field.gif differ diff --git a/source/web/jsp/actions/specialise-type.jsp b/source/web/jsp/actions/specialise-type.jsp index 443b807e67..2c8f381d63 100644 --- a/source/web/jsp/actions/specialise-type.jsp +++ b/source/web/jsp/actions/specialise-type.jsp @@ -26,6 +26,20 @@ + + <%-- load a bundle of properties with I18N strings --%> @@ -105,7 +119,8 @@ - + @@ -120,7 +135,8 @@ diff --git a/source/web/jsp/dialog/filelink-details.jsp b/source/web/jsp/dialog/filelink-details.jsp index 8142b719aa..ab3d6fface 100644 --- a/source/web/jsp/dialog/filelink-details.jsp +++ b/source/web/jsp/dialog/filelink-details.jsp @@ -134,11 +134,13 @@ + +
- +
diff --git a/source/web/jsp/dialog/spacelink-details.jsp b/source/web/jsp/dialog/spacelink-details.jsp index 6674acee79..c4032a00f1 100644 --- a/source/web/jsp/dialog/spacelink-details.jsp +++ b/source/web/jsp/dialog/spacelink-details.jsp @@ -122,11 +122,13 @@ + + diff --git a/source/web/jsp/forums/forum.jsp b/source/web/jsp/forums/forum.jsp index cac63efbca..5011d355c1 100644 --- a/source/web/jsp/forums/forum.jsp +++ b/source/web/jsp/forums/forum.jsp @@ -155,7 +155,7 @@ <%-- Actions column --%> - + diff --git a/source/web/jsp/forums/forums.jsp b/source/web/jsp/forums/forums.jsp index 9c77a2c0bb..b835d3ddb9 100644 --- a/source/web/jsp/forums/forums.jsp +++ b/source/web/jsp/forums/forums.jsp @@ -213,7 +213,7 @@ <%-- Actions column --%> - + diff --git a/source/web/jsp/forums/topic.jsp b/source/web/jsp/forums/topic.jsp index a125e03556..3d4fd09741 100644 --- a/source/web/jsp/forums/topic.jsp +++ b/source/web/jsp/forums/topic.jsp @@ -189,7 +189,7 @@ <%-- Actions column --%> - + diff --git a/source/web/jsp/parts/titlebar.jsp b/source/web/jsp/parts/titlebar.jsp index d5629917ee..3c6d812cba 100644 --- a/source/web/jsp/parts/titlebar.jsp +++ b/source/web/jsp/parts/titlebar.jsp @@ -26,10 +26,11 @@ <%-- Toolbar --%> - + +