diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 9658ee73fd..eb6f45e818 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -778,13 +778,15 @@ manage_deleted_items=Manage Deleted Items manage_deleted_items_description=Remove or recover previously deleted items recover_all_items=Recover All Items delete_all_items=Delete All Items -deleted_items_info=Use the Search to find specific named deleted items and use the Filters to reduce the list. +deleted_items_info=Use the Search to find deleted items by name or content and use the filters to reduce the list. original_location=Original Location deleted_date=Date Deleted deleted_user=Deleted by User recover=Recover clear_search_results=Clear Search Results -search_deleted_items=Search Deleted Items +search_deleted_items_name=Search by Name +search_deleted_items_text=Search by Content +deleted_items_for=for ''{0}'' delete_item=Delete Item delete_item_info=Permanently delete an item from the deleted file store delete_item_confirm=Are you sure you want to permanently delete \"{0}\" from the deleted file store? The item cannot be recovered once this action has been performed. @@ -809,6 +811,17 @@ recovered_item_permission=Failed to recover the item \"{0}\" as you do not have recovered_item_integrity=Failed to recover the item \"{0}\" as there is now an item in the original parent folder with the same name, please select a new folder destination. recovered_item_failure=Failed to recover the item \"{0}\" due to error: {1} delete_item_success=The item \"{0}\" has been permanently deleted. +title_deleted_item_details=Deleted Item Details +deleteditem_details_description=Details of the deleted item +alternative_destination=You may select a destination where you wish the recovered items to be placed. If you do not select a destination, the original location of the item is used. Recovery of an item may fail if the destination does not exist or you do not have permission to add items there. +user_filter_who=Who +user_filter_all=All +user_filter_user=User +date_filter_when=When +date_filter_all=All +date_filter_today=Today +date_filter_week=Last 7 days +date_filter_month=Last 30 days # Admin Console messages title_admin_console=Administration Console diff --git a/config/alfresco/web-client-config-actions.xml b/config/alfresco/web-client-config-actions.xml index 4817bbb163..b77e6510cd 100644 --- a/config/alfresco/web-client-config-actions.xml +++ b/config/alfresco/web-client-config-actions.xml @@ -10,7 +10,7 @@ - Write + Write AddChildren @@ -378,7 +379,6 @@ manage_deleted_items /images/icons/trashcan.gif - #{TrashcanBean.setupTrashcan} dialog:manageDeletedItems @@ -431,7 +431,11 @@ other_action /images/icons/action.gif - wizard:runAction + wizard:runAction + #{WizardManager.setupParameters} + + #{actionContext.id} + @@ -581,6 +585,7 @@ + diff --git a/config/alfresco/web-client-config-forum-actions.xml b/config/alfresco/web-client-config-forum-actions.xml index e5c1f2eaa2..64539be46d 100644 --- a/config/alfresco/web-client-config-forum-actions.xml +++ b/config/alfresco/web-client-config-forum-actions.xml @@ -263,6 +263,7 @@ + diff --git a/config/alfresco/web-client-config-properties.xml b/config/alfresco/web-client-config-properties.xml index 03da2577eb..2c852cd6c1 100644 --- a/config/alfresco/web-client-config-properties.xml +++ b/config/alfresco/web-client-config-properties.xml @@ -44,19 +44,32 @@ - + + + + + + + + + + + @@ -132,5 +145,12 @@ - + + + + + + + + diff --git a/source/java/org/alfresco/web/app/ResourceBundleWrapper.java b/source/java/org/alfresco/web/app/ResourceBundleWrapper.java index 5b5f69c23e..a76f851b19 100644 --- a/source/java/org/alfresco/web/app/ResourceBundleWrapper.java +++ b/source/java/org/alfresco/web/app/ResourceBundleWrapper.java @@ -20,6 +20,7 @@ import java.util.Enumeration; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.Vector; import org.alfresco.error.AlfrescoRuntimeException; import org.apache.log4j.Logger; @@ -28,6 +29,7 @@ import org.apache.log4j.Priority; /** * Wrapper around Alfresco Resource Bundle objects. Used to catch and handle missing * resource exception to help identify missing I18N strings in client apps. + * Also used to look for the requested string in a custom resource bundle. * * @author Kevin Roast */ @@ -36,15 +38,19 @@ public final class ResourceBundleWrapper extends ResourceBundle private static Logger logger = Logger.getLogger(ResourceBundleWrapper.class); private ResourceBundle delegate; + private ResourceBundle delegateCustom; /** * Constructor * - * @param bundle The ResourceBundle to route calls too + * @param bundle The ResourceBundle to route calls too + * @param customBundle A custom version of bundle to look in if the string is + * not found in bundle */ - private ResourceBundleWrapper(ResourceBundle bundle) + private ResourceBundleWrapper(ResourceBundle bundle, ResourceBundle customBundle) { this.delegate = bundle; + this.delegateCustom = customBundle; } /** @@ -52,7 +58,29 @@ public final class ResourceBundleWrapper extends ResourceBundle */ public Enumeration getKeys() { - return this.delegate.getKeys(); + if (this.delegateCustom == null) + { + return this.delegate.getKeys(); + } + else + { + // get existing keys + Enumeration keys = this.delegate.getKeys(); + Enumeration customKeys = this.delegateCustom.getKeys(); + + // combine keys into one list + Vector allKeys = new Vector(100, 2); + while (keys.hasMoreElements()) + { + allKeys.add(keys.nextElement()); + } + while (customKeys.hasMoreElements()) + { + allKeys.add(customKeys.nextElement()); + } + + return allKeys.elements(); + } } /** @@ -60,17 +88,39 @@ public final class ResourceBundleWrapper extends ResourceBundle */ protected Object handleGetObject(String key) { + Object result = null; + try { - return this.delegate.getObject(key); + result = this.delegate.getObject(key); } catch (MissingResourceException err) { - if (logger.isEnabledFor(Priority.WARN)) - logger.warn("Failed to find I18N message string key: " + key); + // if the string wasn't found in the normal bundle + // try the custom bundle if there is one + try + { + if (this.delegateCustom != null) + { + result = this.delegateCustom.getObject(key); + } + } + catch (MissingResourceException mre) + { + // don't do anything here, dealt with below + } - return "$$" + key + "$$"; + // if the key was not found return a default string + if (result == null) + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Failed to find I18N message string key: " + key); + + result = "$$" + key + "$$"; + } } + + return result; } /** @@ -89,7 +139,48 @@ public final class ResourceBundleWrapper extends ResourceBundle throw new AlfrescoRuntimeException("Unable to load Alfresco messages bundle: " + name); } + // also look up the custom version of the bundle in the extension package + String customName = determineCustomBundleName(name); + ResourceBundle customBundle = null; + try + { + customBundle = ResourceBundle.getBundle(customName, locale); + + if (logger.isDebugEnabled()) + logger.debug("Located and loaded custom bundle: " + customName); + } + catch (MissingResourceException mre) + { + // ignore the error, just leave custom bundle as null + } + // apply our wrapper to catch MissingResourceException - return new ResourceBundleWrapper(bundle); + return new ResourceBundleWrapper(bundle, customBundle); + } + + /** + * Determines the name for the custom bundle to lookup based on the given + * bundle name + * + * @param bundleName The standard bundle + * @return The name of the custom bundle (in the alfresco.extension package) + */ + protected static String determineCustomBundleName(String bundleName) + { + String extensionPackage = "alfresco.extension."; + String customBundleName = null; + + int idx = bundleName.lastIndexOf("."); + if (idx == -1) + { + customBundleName = extensionPackage + bundleName; + } + else + { + customBundleName = extensionPackage + + bundleName.substring(idx+1, bundleName.length()); + } + + return customBundleName; } } diff --git a/source/java/org/alfresco/web/app/servlet/command/ExecuteScriptCommand.java b/source/java/org/alfresco/web/app/servlet/command/ExecuteScriptCommand.java index bfca600c94..f0c139e8b5 100644 --- a/source/java/org/alfresco/web/app/servlet/command/ExecuteScriptCommand.java +++ b/source/java/org/alfresco/web/app/servlet/command/ExecuteScriptCommand.java @@ -71,18 +71,23 @@ public final class ExecuteScriptCommand implements Command "Unable to execute ExecuteScriptCommand - mandatory parameter not supplied: " + PROP_USERPERSON); } - // get the optional document context ref + // get the optional document and space context ref + NodeService nodeService = serviceRegistry.getNodeService(); NodeRef docRef = (NodeRef)properties.get(PROP_DOCUMENT); + NodeRef spaceRef = null; + if (docRef != null) + { + spaceRef = nodeService.getPrimaryParent(docRef).getParentRef(); + } // build the model needed to execute the script - NodeService nodeService = serviceRegistry.getNodeService(); Map model = RhinoScriptService.buildDefaultModel( serviceRegistry, personRef, new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()), (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER), docRef, - nodeService.getPrimaryParent(docRef).getParentRef(), + spaceRef, DefaultModelHelper.imageResolver); // execute the script and return the result diff --git a/source/java/org/alfresco/web/bean/TrashcanBean.java b/source/java/org/alfresco/web/bean/TrashcanBean.java index cd212494f4..eab0d28bdd 100644 --- a/source/java/org/alfresco/web/bean/TrashcanBean.java +++ b/source/java/org/alfresco/web/bean/TrashcanBean.java @@ -17,8 +17,10 @@ package org.alfresco.web.bean; import java.text.MessageFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; @@ -42,6 +44,7 @@ import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.CachingDateFormat; import org.alfresco.web.app.Application; import org.alfresco.web.app.context.IContextListener; import org.alfresco.web.bean.repository.MapNode; @@ -49,8 +52,11 @@ import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.NodePropertyResolver; import org.alfresco.web.bean.repository.QNameNodeMap; import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.wizard.NewSpaceWizard; import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.Utils.URLMode; import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIModeList; import org.alfresco.web.ui.common.component.data.UIRichList; /** @@ -58,6 +64,15 @@ import org.alfresco.web.ui.common.component.data.UIRichList; */ public class TrashcanBean implements IContextListener { + private static final String FILTER_DATE_ALL = "all"; + private static final String FILTER_DATE_TODAY = "today"; + private static final String FILTER_DATE_WEEK = "week"; + private static final String FILTER_DATE_MONTH = "month"; + private static final String FILTER_USER_ALL = "all"; + private static final String FILTER_USER_USER = "user"; + + private static final String MSG_DELETED_ITEMS_FOR = "deleted_items_for"; + private static final String MSG_DELETED_ITEMS = "deleted_items"; private static final String MSG_RECOVERED_ITEM_INTEGRITY = "recovered_item_integrity"; private static final String MSG_RECOVERED_ITEM_PERMISSION = "recovered_item_permission"; private static final String MSG_RECOVERED_ITEM_PARENT = "recovered_item_parent"; @@ -69,11 +84,16 @@ public class TrashcanBean implements IContextListener private static final String RICHLIST_ID = "trashcan-list"; private static final String RICHLIST_MSG_ID = "trashcan" + ':' + RICHLIST_ID; - private final static String NAME_ATTR = Repository.escapeQName(ContentModel.PROP_NAME); + private final static String NAME_ATTR = Repository.escapeQName(ContentModel.PROP_NAME); + private final static String USER_ATTR = Repository.escapeQName(ContentModel.PROP_ARCHIVED_BY); + private final static String DATE_ATTR = Repository.escapeQName(ContentModel.PROP_ARCHIVED_DATE); private final static String SEARCH_ALL = "PARENT:\"%s\" AND ASPECT:\"%s\""; - private final static String SEARCH_NAME = "PARENT:\"%s\" AND ASPECT:\"%s\" AND (@" + NAME_ATTR + ":*%s* TEXT:%s)"; - private final static String SEARCH_NAME_QUOTED = "PARENT:\"%s\" AND ASPECT:\"%s\" AND (@" + NAME_ATTR + ":\"%s\" TEXT:\"%s\")"; + private final static String SEARCH_NAME = "PARENT:\"%s\" AND ASPECT:\"%s\" AND @" + NAME_ATTR + ":*%s*"; + private final static String SEARCH_TEXT = "PARENT:\"%s\" AND ASPECT:\"%s\" AND TEXT:%s"; + private final static String SEARCH_NAME_QUOTED = "PARENT:\"%s\" AND ASPECT:\"%s\" AND @" + NAME_ATTR + ":\"%s\""; + private final static String SEARCH_TEXT_QUOTED = "PARENT:\"%s\" AND ASPECT:\"%s\" AND TEXT:\"%s\""; + private final static String SEARCH_USERPREFIX = "@" + USER_ATTR + ":%s AND "; /** NodeService bean reference */ protected NodeService nodeService; @@ -96,6 +116,8 @@ public class TrashcanBean implements IContextListener /** We show an empty list until a Search or Show All is executed */ private boolean showItems = false; + private boolean fullTextSearch = false; + /** Currently listed items */ private List listedItems = Collections.emptyList(); @@ -105,6 +127,18 @@ public class TrashcanBean implements IContextListener /** Root node to the spaces store archive store*/ private NodeRef archiveRootRef = null; + /** Alternative destination for recovered items */ + private NodeRef destination = null; + + /** Date filter selection */ + private String dateFilter = FILTER_DATE_ALL; + + /** User filter selection */ + private String userFilter = FILTER_USER_ALL; + + /** User filter search box text */ + private String userSearchText = null; + // ------------------------------------------------------------------------------ // Bean property getters and setters @@ -173,6 +207,115 @@ public class TrashcanBean implements IContextListener this.searchText = searchText; } + /** + * @return Returns the alternative destination to use if recovery fails. + */ + public NodeRef getDestination() + { + return this.destination; + } + + /** + * @param destination The alternative destination to use if recovery fails. + */ + public void setDestination(NodeRef destination) + { + this.destination = destination; + } + + /** + * @return Returns the dateFilter. + */ + public String getDateFilter() + { + return this.dateFilter; + } + + /** + * @param dateFilter The dateFilter to set. + */ + public void setDateFilter(String dateFilter) + { + this.dateFilter = dateFilter; + } + + /** + * @return Returns the userFilter. + */ + public String getUserFilter() + { + return this.userFilter; + } + + /** + * @param userFilter The userFilter to set. + */ + public void setUserFilter(String userFilter) + { + this.userFilter = userFilter; + } + + /** + * @return Returns the userSearchText. + */ + public String getUserSearchText() + { + return this.userSearchText; + } + + /** + * @param userSearchText The userSearchText to set. + */ + public void setUserSearchText(String userSearchText) + { + this.userSearchText = userSearchText; + } + + /** + * @return Message to display in the title of the panel area + */ + public String getPanelMessage() + { + FacesContext fc = FacesContext.getCurrentInstance(); + String msg = Application.getMessage(fc, MSG_DELETED_ITEMS); + if (isAdminUser() == false) + { + msg = msg + ' ' + MessageFormat.format( + Application.getMessage(fc, MSG_DELETED_ITEMS_FOR), Application.getCurrentUser(fc).getUserName()); + } + return msg; + } + + /** + * Returns the URL to the content for the current document item + * + * @return Content url to the current document item + */ + public String getItemBrowserUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getItem(), URLMode.HTTP_INLINE); + } + + /** + * Returns the download URL to the content for the current document item + * + * @return Download url to the current document item + */ + public String getItemDownloadUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getItem(), URLMode.HTTP_DOWNLOAD); + } + + /** + * Return the Alfresco NodeRef URL for the current item node + * + * @return the Alfresco NodeRef URL + */ + public String getItemNodeRefUrl() + { + return getItem().getNodeRef().toString(); + } + /** * @return Returns the listed items. */ @@ -224,7 +367,7 @@ public class TrashcanBean implements IContextListener // get the root node to the deleted items store if (getArchiveRootRef() != null && this.showItems == true) { - String query = getSearchQuery(); + String query = buildSearchQuery(); SearchParameters sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_LUCENE); sp.setQuery(query); @@ -244,27 +387,24 @@ public class TrashcanBean implements IContextListener { QName type = this.nodeService.getType(nodeRef); + MapNode node = new MapNode(nodeRef, this.nodeService, false); + + node.addPropertyResolver("locationPath", resolverLocationPath); + node.addPropertyResolver("displayPath", resolverDisplayPath); + node.addPropertyResolver("deletedDate", resolverDeletedDate); + node.addPropertyResolver("deletedBy", resolverDeletedBy); + node.addPropertyResolver("isFolder", resolverIsFolder); + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) == true && this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) { - MapNode node = new MapNode(nodeRef, this.nodeService, false); - node.addPropertyResolver("locationPath", resolverLocationPath); - node.addPropertyResolver("displayPath", resolverDisplayPath); - node.addPropertyResolver("deletedDate", resolverDeletedDate); - node.addPropertyResolver("deletedBy", resolverDeletedBy); node.addPropertyResolver("typeIcon", this.resolverSmallIcon); - itemNodes.add(node); } else { - MapNode node = new MapNode(nodeRef, this.nodeService, false); - node.addPropertyResolver("locationPath", resolverLocationPath); - node.addPropertyResolver("displayPath", resolverDisplayPath); - node.addPropertyResolver("deletedDate", resolverDeletedDate); - node.addPropertyResolver("deletedBy", resolverDeletedBy); node.addPropertyResolver("typeIcon", this.resolverFileType16); - itemNodes.add(node); } + itemNodes.add(node); } } } @@ -292,15 +432,12 @@ public class TrashcanBean implements IContextListener private NodePropertyResolver resolverLocationPath = new NodePropertyResolver() { public Object get(Node node) { - //ChildAssociationRef childRef = (ChildAssociationRef)node.getProperties().get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); - //return nodeService.getPath(childRef.getChildRef()); return (Path)node.getProperties().get(ContentModel.PROP_ARCHIVED_ORIGINAL_PATH); } }; private NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() { public Object get(Node node) { - //ChildAssociationRef childRef = (ChildAssociationRef)node.getProperties().get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); return Repository.getDisplayPath((Path)node.getProperties().get(ContentModel.PROP_ARCHIVED_ORIGINAL_PATH)); } }; @@ -319,6 +456,20 @@ public class TrashcanBean implements IContextListener } }; + private NodePropertyResolver resolverFileType32 = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.getFileTypeImage(node.getName(), false); + } + }; + + private NodePropertyResolver resolverLargeIcon = new NodePropertyResolver() { + public Object get(Node node) { + QNameNodeMap props = (QNameNodeMap)node.getProperties(); + String icon = (String)props.getRaw("app:icon"); + return "/images/icons/" + (icon != null ? icon : NewSpaceWizard.SPACE_ICON_DEFAULT) + ".gif"; + } + }; + private NodePropertyResolver resolverDeletedDate = new NodePropertyResolver() { public Object get(Node node) { return node.getProperties().get(ContentModel.PROP_ARCHIVED_DATE); @@ -331,23 +482,42 @@ public class TrashcanBean implements IContextListener } }; + private NodePropertyResolver resolverIsFolder = new NodePropertyResolver() { + public Object get(Node node) { + return dictionaryService.isSubClass(node.getType(), ContentModel.TYPE_FOLDER); + } + }; + // ------------------------------------------------------------------------------ // Action handlers // TODO: // need the following Action Handlers: - // deleteItemOK, recoverItemOK, deleteAllItemsOK, recoverAllItemsOK, recoverListedItemsOK, deleteListedItemsOK + // deleteAllItemsOK, recoverAllItemsOK, recoverListedItemsOK, deleteListedItemsOK /** * Search the deleted item store by name */ - public void search(ActionEvent event) + public void searchName(ActionEvent event) { // simply clear the current list and refresh the screen // the search query text will be found and processed by the getItems() method contextUpdated(); this.showItems = true; + this.fullTextSearch = false; + } + + /** + * Search the deleted item store by text + */ + public void searchContent(ActionEvent event) + { + // simply clear the current list and refresh the screen + // the search query text will be found and processed by the getItems() method + contextUpdated(); + this.showItems = true; + this.fullTextSearch = true; } /** @@ -376,11 +546,24 @@ public class TrashcanBean implements IContextListener NodeRef ref = new NodeRef(getArchiveRootRef().getStoreRef(), id); Node node = new Node(ref); - // resolve icon in-case one has not been set - //node.addPropertyResolver("icon", this.resolverSpaceIcon); + node.addPropertyResolver("locationPath", resolverLocationPath); + node.addPropertyResolver("deletedDate", resolverDeletedDate); + node.addPropertyResolver("deletedBy", resolverDeletedBy); + node.addPropertyResolver("isFolder", resolverIsFolder); + + if (this.dictionaryService.isSubClass(node.getType(), ContentModel.TYPE_FOLDER) == true && + this.dictionaryService.isSubClass(node.getType(), ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + node.addPropertyResolver("icon", this.resolverLargeIcon); + } + else + { + node.addPropertyResolver("icon", this.resolverFileType32); + } // prepare a node for the action context setItem(node); + setDestination(null); } catch (InvalidNodeRefException refErr) { @@ -397,6 +580,9 @@ public class TrashcanBean implements IContextListener contextUpdated(); } + /** + * Delete single item OK button handler + */ public String deleteItemOK() { Node item = getItem(); @@ -421,6 +607,9 @@ public class TrashcanBean implements IContextListener return OUTCOME_DIALOGCLOSE; } + /** + * Recover single item OK button handler + */ public String recoverItemOK() { String outcome = null; @@ -434,7 +623,16 @@ public class TrashcanBean implements IContextListener String msg; FacesMessage errorfacesMsg = null; - RestoreNodeReport report = this.nodeArchiveService.restoreArchivedNode(item.getNodeRef()); + // restore the node - the user may have requested a restore to a different parent + RestoreNodeReport report; + if (this.destination == null) + { + report = this.nodeArchiveService.restoreArchivedNode(item.getNodeRef()); + } + else + { + report = this.nodeArchiveService.restoreArchivedNode(item.getNodeRef(), this.destination, null, null); + } switch (report.getStatus()) { case SUCCESS: @@ -491,14 +689,6 @@ public class TrashcanBean implements IContextListener return outcome; } - /** - * Action handler to reset all filters and search - */ - public void resetAll(ActionEvent event) - { - // TODO: reset all filter and search - } - /** * Action handler to initially setup the trashcan screen */ @@ -507,6 +697,28 @@ public class TrashcanBean implements IContextListener contextUpdated(); } + /** + * Action handler called when the Date filter is changed by the user + */ + public void dateFilterChanged(ActionEvent event) + { + UIModeList filterComponent = (UIModeList)event.getComponent(); + setDateFilter(filterComponent.getValue().toString()); + contextUpdated(); + this.showItems = true; + } + + /** + * Action handler called when the User filter is changed by the user + */ + public void userFilterChanged(ActionEvent event) + { + UIModeList filterComponent = (UIModeList)event.getComponent(); + setUserFilter(filterComponent.getValue().toString()); + contextUpdated(); + this.showItems = true; + } + // ------------------------------------------------------------------------------ // Private helpers @@ -526,7 +738,7 @@ public class TrashcanBean implements IContextListener /** * @return the search query to use when displaying the list of deleted items */ - private String getSearchQuery() + private String buildSearchQuery() { String query; if (this.searchText == null || this.searchText.length() == 0) @@ -540,16 +752,84 @@ public class TrashcanBean implements IContextListener String safeText = QueryParser.escape(this.searchText); if (safeText.indexOf(' ') == -1) { - query = String.format(SEARCH_NAME, archiveRootRef, ContentModel.ASPECT_ARCHIVED, safeText, safeText); + if (this.fullTextSearch) + { + query = String.format(SEARCH_TEXT, archiveRootRef, ContentModel.ASPECT_ARCHIVED, safeText); + } + else + { + query = String.format(SEARCH_NAME, archiveRootRef, ContentModel.ASPECT_ARCHIVED, safeText); + } } else { - query = String.format(SEARCH_NAME_QUOTED, archiveRootRef, ContentModel.ASPECT_ARCHIVED, safeText, safeText); + if (this.fullTextSearch) + { + query = String.format(SEARCH_TEXT_QUOTED, archiveRootRef, ContentModel.ASPECT_ARCHIVED, safeText); + } + else + { + query = String.format(SEARCH_NAME_QUOTED, archiveRootRef, ContentModel.ASPECT_ARCHIVED, safeText); + } } } + + // append user search clause + String username = null; + if (isAdminUser() == false) + { + // prefix the current username + username = Application.getCurrentUser(FacesContext.getCurrentInstance()).getUserName(); + } + else if (FILTER_USER_USER.equals(getUserFilter())) + { + // append the entered user if admin has requested a search + username = getUserSearchText(); + } + if (username != null && username.length() != 0) + { + query = String.format(SEARCH_USERPREFIX, username) + query; + } + + // append date search clause + if (FILTER_DATE_ALL.equals(getDateFilter()) == false) + { + Date toDate = new Date(); + Date fromDate = null; + if (FILTER_DATE_TODAY.equals(getDateFilter())) + { + fromDate = new Date(toDate.getYear(), toDate.getMonth(), toDate.getDate(), 0, 0, 0); + } + else if (FILTER_DATE_WEEK.equals(getDateFilter())) + { + fromDate = new Date(toDate.getTime() - (1000L*60L*60L*24L*7L)); + } + else if (FILTER_DATE_MONTH.equals(getDateFilter())) + { + fromDate = new Date(toDate.getTime() - (1000L*60L*60L*24L*30L)); + } + if (fromDate != null) + { + SimpleDateFormat df = CachingDateFormat.getDateFormat(); + String strFromDate = QueryParser.escape(df.format(fromDate)); + String strToDate = QueryParser.escape(df.format(toDate)); + StringBuilder buf = new StringBuilder(128); + buf.append("@").append(DATE_ATTR) + .append(":").append("[").append(strFromDate) + .append(" TO ").append(strToDate).append("] AND "); + + query = buf.toString() + query; + } + } + return query; } + private boolean isAdminUser() + { + return Application.getCurrentUser(FacesContext.getCurrentInstance()).isAdmin(); + } + // ------------------------------------------------------------------------------ // IContextListener implementation diff --git a/source/java/org/alfresco/web/bean/actions/RunActionWizard.java b/source/java/org/alfresco/web/bean/actions/RunActionWizard.java index 5fbe9fc0b6..e9b140ad25 100644 --- a/source/java/org/alfresco/web/bean/actions/RunActionWizard.java +++ b/source/java/org/alfresco/web/bean/actions/RunActionWizard.java @@ -1,13 +1,21 @@ package org.alfresco.web.bean.actions; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.ResourceBundle; import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; /** * Bean implementation for the "Run Action" wizard. @@ -37,12 +45,38 @@ public class RunActionWizard extends BaseActionWizard action.setParameterValues(repoActionParams); // execute the action on the current document node - this.actionService.executeAction(action, this.browseBean.getDocument().getNodeRef()); + NodeRef nodeRef = new NodeRef(Repository.getStoreRef(), this.parameters.get("id")); + this.actionService.executeAction(action, nodeRef); } return outcome; } + @Override + public List getActions() + { + if (this.actions == null) + { + NodeRef nodeRef = new NodeRef(Repository.getStoreRef(), this.parameters.get("id")); + List ruleActions = this.actionService.getActionDefinitions(nodeRef); + this.actions = new ArrayList(); + for (ActionDefinition ruleActionDef : ruleActions) + { + this.actions.add(new SelectItem(ruleActionDef.getName(), ruleActionDef.getTitle())); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.actions, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + + // add the select an action item at the start of the list + this.actions.add(0, new SelectItem("null", + Application.getMessage(FacesContext.getCurrentInstance(), "select_an_action"))); + } + + return this.actions; + } + @Override protected String doPostCommitProcessing(FacesContext context, String outcome) { diff --git a/source/java/org/alfresco/web/bean/content/AddContentDialog.java b/source/java/org/alfresco/web/bean/content/AddContentDialog.java index 9c696abc3b..e3f6042431 100644 --- a/source/java/org/alfresco/web/bean/content/AddContentDialog.java +++ b/source/java/org/alfresco/web/bean/content/AddContentDialog.java @@ -1,13 +1,19 @@ package org.alfresco.web.bean.content; import java.io.File; +import java.io.Serializable; import java.text.MessageFormat; +import java.util.HashMap; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; +import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; import org.alfresco.web.app.Application; import org.alfresco.web.bean.FileUploadBean; import org.alfresco.web.bean.repository.Node; @@ -29,6 +35,25 @@ public class AddContentDialog extends BaseContentWizard protected String finishImpl(FacesContext context, String outcome) throws Exception { + // Try and extract metadata from the file + ContentReader cr = new FileContentReader(this.file); + cr.setMimetype(this.mimeType); + // create properties for content type + Map contentProps = new HashMap(5, 1.0f); + + if (Repository.extractMetadata(FacesContext.getCurrentInstance(), cr, contentProps)) + { + this.author = (String)(contentProps.get(ContentModel.PROP_AUTHOR)); + this.title = (String)(contentProps.get(ContentModel.PROP_TITLE)); + this.description = (String)(contentProps.get(ContentModel.PROP_DESCRIPTION)); + } + + // default the title to the file name if not set + if (this.title == null) + { + this.title = this.fileName; + } + saveContent(this.file, null); // return default outcome diff --git a/source/java/org/alfresco/web/bean/content/BaseContentWizard.java b/source/java/org/alfresco/web/bean/content/BaseContentWizard.java index d9ea580c02..5f29234d2e 100644 --- a/source/java/org/alfresco/web/bean/content/BaseContentWizard.java +++ b/source/java/org/alfresco/web/bean/content/BaseContentWizard.java @@ -358,13 +358,10 @@ public abstract class BaseContentWizard extends BaseWizardBean Repository.resolveToQName(this.objectType)); NodeRef fileNodeRef = fileInfo.getNodeRef(); - // set the author aspect (if we have one) - if (this.author != null && this.author.length() > 0) - { - Map authorProps = new HashMap(1, 1.0f); - authorProps.put(ContentModel.PROP_AUTHOR, this.author); - this.nodeService.addAspect(fileNodeRef, ContentModel.ASPECT_AUTHOR, authorProps); - } + // set the author aspect + Map authorProps = new HashMap(1, 1.0f); + authorProps.put(ContentModel.PROP_AUTHOR, this.author); + this.nodeService.addAspect(fileNodeRef, ContentModel.ASPECT_AUTHOR, authorProps); if (logger.isDebugEnabled()) logger.debug("Created file node for file: " + this.fileName); diff --git a/source/java/org/alfresco/web/ui/repo/renderer/NodePathLinkRenderer.java b/source/java/org/alfresco/web/ui/repo/renderer/NodePathLinkRenderer.java index 75c5d131a6..dd84b3ef49 100644 --- a/source/java/org/alfresco/web/ui/repo/renderer/NodePathLinkRenderer.java +++ b/source/java/org/alfresco/web/ui/repo/renderer/NodePathLinkRenderer.java @@ -92,70 +92,73 @@ public class NodePathLinkRenderer extends BaseRenderer { path = (Path)val; } - else + else if (val != null) { throw new IllegalArgumentException("UINodePath component 'value' property must resolve to a NodeRef or Path!"); } - boolean isBreadcrumb = false; - Boolean breadcrumb = (Boolean)component.getAttributes().get("breadcrumb"); - if (breadcrumb != null) + if (val != null) { - isBreadcrumb = breadcrumb.booleanValue(); - } - - boolean isDisabled = false; - Boolean disabled = (Boolean)component.getAttributes().get("disabled"); - if (disabled != null) - { - isDisabled = disabled.booleanValue(); - } - - boolean showLeaf = false; - Boolean showLeafBool = (Boolean)component.getAttributes().get("showLeaf"); - if (showLeafBool != null) - { - showLeaf = showLeafBool.booleanValue(); - } - - // use Spring JSF integration to get the node service bean - NodeService service = getNodeService(context); - UserTransaction tx = null; - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); - tx.begin(); - - if (path == null) + boolean isBreadcrumb = false; + Boolean breadcrumb = (Boolean)component.getAttributes().get("breadcrumb"); + if (breadcrumb != null) { - path = service.getPath(nodeRef); + isBreadcrumb = breadcrumb.booleanValue(); } - if (isBreadcrumb == false || isDisabled == true) + boolean isDisabled = false; + Boolean disabled = (Boolean)component.getAttributes().get("disabled"); + if (disabled != null) { - out.write(buildPathAsSingular(context, component, path, showLeaf, isDisabled)); - } - else - { - out.write(buildPathAsBreadcrumb(context, component, path, showLeaf)); + isDisabled = disabled.booleanValue(); } - tx.commit(); - } - catch (InvalidNodeRefException refErr) - { - // this error simple means we cannot output the path - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - catch (AccessDeniedException accessErr) - { - // this error simple means we cannot output the path - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - catch (Throwable err) - { - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - throw new RuntimeException(err); + boolean showLeaf = false; + Boolean showLeafBool = (Boolean)component.getAttributes().get("showLeaf"); + if (showLeafBool != null) + { + showLeaf = showLeafBool.booleanValue(); + } + + // use Spring JSF integration to get the node service bean + NodeService service = getNodeService(context); + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + if (path == null) + { + path = service.getPath(nodeRef); + } + + if (isBreadcrumb == false || isDisabled == true) + { + out.write(buildPathAsSingular(context, component, path, showLeaf, isDisabled)); + } + else + { + out.write(buildPathAsBreadcrumb(context, component, path, showLeaf)); + } + + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + // this error simple means we cannot output the path + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (AccessDeniedException accessErr) + { + // this error simple means we cannot output the path + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + throw new RuntimeException(err); + } } } diff --git a/source/web/WEB-INF/faces-config-navigation.xml b/source/web/WEB-INF/faces-config-navigation.xml index 88813750a5..d004f78ee2 100644 --- a/source/web/WEB-INF/faces-config-navigation.xml +++ b/source/web/WEB-INF/faces-config-navigation.xml @@ -923,6 +923,10 @@ deleteListedItems /jsp/trashcan/delete-listed.jsp + + itemDetails + /jsp/trashcan/item-details.jsp + diff --git a/source/web/css/main.css b/source/web/css/main.css index dec15aad95..b66583be3c 100644 --- a/source/web/css/main.css +++ b/source/web/css/main.css @@ -393,6 +393,15 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl padding-top: 3px; } +.filterBorders +{ + padding: 2px; + background-color: #cddbe8; + border-width: 1px; + border-style: solid; + border-color: #003366; +} + .wizardSectionHeading { padding: 2px; diff --git a/source/web/jsp/dialog/import.jsp b/source/web/jsp/dialog/import.jsp index eb76fa9691..dcb291c822 100644 --- a/source/web/jsp/dialog/import.jsp +++ b/source/web/jsp/dialog/import.jsp @@ -106,7 +106,7 @@ - + diff --git a/source/web/jsp/trashcan/item-details.jsp b/source/web/jsp/trashcan/item-details.jsp new file mode 100644 index 0000000000..4ac3d3140d --- /dev/null +++ b/source/web/jsp/trashcan/item-details.jsp @@ -0,0 +1,201 @@ +<%-- + 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. +--%> +<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> +<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> +<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> + +<%@ page buffer="64kb" contentType="text/html;charset=UTF-8" %> +<%@ page isELIgnored="false" %> +<%@ page import="org.alfresco.web.ui.common.PanelGenerator" %> + + + + + + <%-- load a bundle of properties with I18N strings --%> + + + + + <%-- Main outer table --%> +
1. 1.
+ + <%-- Title bar --%> + + + + + <%-- Main area --%> + + <%-- Shelf --%> + + + <%-- Work Area --%> + + +
+ <%@ include file="../parts/titlebar.jsp" %> +
+ <%@ include file="../parts/shelf.jsp" %> + + + <%-- Breadcrumb --%> + <%@ include file="../parts/breadcrumb.jsp" %> + + <%-- Status and Actions --%> + + + + + + + <%-- separator row with gradient shadow --%> + + + + + + + <%-- Details --%> + + + + + + + <%-- separator row with bottom panel graphics --%> + + + + + + +
+ + <%-- Status and Actions inner contents table --%> + <%-- Generally this consists of an icon, textual summary and actions for the current object --%> + + + + + +
+ + +
+ '' +
+
+ : +
+
+
+
+ + + + + + +
+ + + + + + + +
+ + + + + +
+
+ +
+ + + + + + + +
+ <%-- icon image for the object --%> + + + + + + + + +
+
+ + +
+
+
+ <%-- properties for the item --%> + + + +
+
+ +
+ + <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "blue", "#D3E6FE"); %> + + + + +
+ +
+ <% PanelGenerator.generatePanelEnd(out, request.getContextPath(), "blue"); %> + +
+ + <%-- Item Actions --%> + + + + + + + + +
+
+
+ + + + + + \ No newline at end of file diff --git a/source/web/jsp/trashcan/recover-item.jsp b/source/web/jsp/trashcan/recover-item.jsp index d6e65e8899..bc8fea3246 100644 --- a/source/web/jsp/trashcan/recover-item.jsp +++ b/source/web/jsp/trashcan/recover-item.jsp @@ -98,14 +98,41 @@ <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "white", "white"); %> - + + + + + + + + + + +
+
+ <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "yellowInner", "#ffffcc"); %> + + + + + +
+ +
+ <% PanelGenerator.generatePanelEnd(out, request.getContextPath(), "yellowInner"); %> +
+ :  + + +
<%-- Error Messages --%> <%-- messages tag to show messages not handled by other specific message tags --%> diff --git a/source/web/jsp/trashcan/trash-list.jsp b/source/web/jsp/trashcan/trash-list.jsp index eab545002e..e657e751f2 100644 --- a/source/web/jsp/trashcan/trash-list.jsp +++ b/source/web/jsp/trashcan/trash-list.jsp @@ -40,13 +40,29 @@ { if (document.getElementById("trashcan:search-text").value.length == 0) { - document.getElementById("trashcan:search-btn").disabled = true; + document.getElementById("trashcan:search-btn1").disabled = true; + document.getElementById("trashcan:search-btn2").disabled = true; } else { - document.getElementById("trashcan:search-btn").disabled = false; + document.getElementById("trashcan:search-btn1").disabled = false; + document.getElementById("trashcan:search-btn2").disabled = false; } } + + function userSearch(e) + { + var keycode; + if (window.event) keycode = window.event.keyCode; + else if (e) keycode = e.which; + if (keycode == 13) + { + document.forms['trashcan']['trashcan:modelist'].value='trashcan:user-filter:user'; + document.forms['trashcan'].submit(); + return false; + } + return true; + } @@ -123,7 +139,7 @@ <%-- Deleted Items List --%> - + <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "yellowInner", "#ffffcc"); %> @@ -139,28 +155,63 @@ <%-- Search controls --%>
  -   +   +  
<%-- Filter controls --%>
- - + + <%-- Only the admin user needs the username filter --%> + - - + +
...TODO: Date filter here... + + + + + + +
: + + + + + + +
+
...TODO: Username filter here - admin only... + + + + + + +
: +
+ + + + + + +
+
+
- <%-- TODO: only show user filter for admin user --%>
- <%-- Recover Listed Items button --%> -   - + <%-- Recover Listed Items actions --%> +   +
<%-- Primary column showing item name --%> - + - + + + - + + + <%-- Original Location Path column --%> @@ -183,11 +238,11 @@ - + <%-- Deleted Date column --%> - +