Index: main/plugin-cms/nodetypes/content_nodetypes.xml =================================================================== --- main/plugin-cms/nodetypes/content_nodetypes.xml (revision 25585) +++ main/plugin-cms/nodetypes/content_nodetypes.xml (working copy) @@ -33,7 +33,8 @@ </supertypes> <propertyDefinition name="ametys-internal:workflowId" requiredType="Long" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false" multiple="false"/> <propertyDefinition name="ametys-internal:workflowRef" requiredType="Reference" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false" multiple="false"/> - <propertyDefinition name="ametys-internal:contentType" requiredType="String" autoCreated="false" mandatory="true" onParentVersion="COPY" protected="false" multiple="false"/> + <propertyDefinition name="ametys-internal:contentType" requiredType="String" autoCreated="false" mandatory="true" onParentVersion="COPY" protected="false" multiple="true"/> + <propertyDefinition name="ametys-internal:mixins" requiredType="String" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false" multiple="true"/> <childNodeDefinition name="ametys-internal:consistency" autoCreated="false" defaultPrimaryType="nt:unstructured" mandatory="false" onParentVersion="IGNORE" protected="false" sameNameSiblings="false"> <requiredPrimaryTypes> <requiredPrimaryType>nt:unstructured</requiredPrimaryType> Index: main/plugin-cms/sitemap.xmap =================================================================== --- main/plugin-cms/sitemap.xmap (revision 25585) +++ main/plugin-cms/sitemap.xmap (working copy) @@ -22,7 +22,7 @@ <map:generator name="xhtml" src="org.ametys.cms.transformation.XhtmlGenerator"/> <map:generator name="input-stream" src="org.ametys.cms.transformation.InputStreamGenerator"/> <map:generator name="metadataset" src="org.ametys.cms.content.MetadataSetDefGenerator"/> - <map:generator name="additional-content-info" src="org.ametys.cms.content.AdditionContentPropertiesGenerator" logger="org.ametys.cms.content.ReferencingContentsGenerator"/> + <map:generator name="additional-content-info" src="org.ametys.cms.content.AdditionalContentPropertiesGenerator" logger="org.ametys.cms.content.ReferencingContentsGenerator"/> <map:generator name="search" src="org.ametys.cms.search.generators.SearchGenerator" logger="org.ametys.cms.search"/> <map:generator name="select-content-search" src="org.ametys.cms.search.generators.SelectContentSearchGenerator" logger="org.ametys.cms.search"/> @@ -89,6 +89,8 @@ <map:action name="select-workspace" src="org.ametys.cms.repository.SelectWorkspaceAction" logger="org.ametys.cms.repository.SelectWorkspaceAction"/> + <map:action name="set-content-type" src="org.ametys.cms.content.AddOrRemoveContentTypeAction"/> + <map:action name="set-mixin" src="org.ametys.cms.content.AddOrRemoveMixinAction"/> <map:action name="import-simple-contents" src="org.ametys.cms.repository.ImportSimpleContentsAction"/> <map:action name="create-content-by-copy" src="org.ametys.cms.workflow.copy.CreateContentByCopyAction"/> <map:action name="edit-content-by-copy" src="org.ametys.cms.workflow.copy.EditContentByCopyAction"/> @@ -273,6 +275,10 @@ <map:read src="context://WEB-INF/param/content-types/{1}/{2}"/> </map:match> + <map:match pattern="dynamic-content-types_resources/*/**"> + <map:read src="context://WEB-INF/param/content-types/_dynamic/{1}/{2}"/> + </map:match> + <map:match pattern="wrapped-content_resources/**"> <map:read src="context://WEB-INF/param/wrapped-content_resources/{1}"/> </map:match> @@ -779,6 +785,32 @@ </map:act> </map:match> + <map:match pattern="content/add-content-type"> + <map:act type="set-content-type"> + <map:read type="json"/> + </map:act> + </map:match> + + <map:match pattern="content/remove-content-type"> + <map:act type="set-content-type"> + <map:parameter name="remove" value="true"/> + <map:read type="json"/> + </map:act> + </map:match> + + <map:match pattern="content/add-mixin"> + <map:act type="set-mixin"> + <map:read type="json"/> + </map:act> + </map:match> + + <map:match pattern="content/remove-mixin"> + <map:act type="set-mixin"> + <map:parameter name="remove" value="true"/> + <map:read type="json"/> + </map:act> + </map:match> + <!-- + | CONTENT DUPLICATION + --> Index: main/plugin-cms/src/org/ametys/cms/source/DefaultContentView.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/source/DefaultContentView.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/source/DefaultContentView.java (working copy) @@ -26,6 +26,8 @@ import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceResolver; +import org.ametys.cms.contenttype.ContentTypesHelper; + /** * Default implementation of a {@link ContentView} * Will first look in directory context://WEB-INF/stylesheets/content/article/article-[view].xsl @@ -35,11 +37,14 @@ { /** The source resolver */ protected SourceResolver _resolver; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; @Override public void service(ServiceManager manager) throws ServiceException { _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } @Override @@ -78,7 +83,7 @@ /** * Returns the ordered list of URI to be tested to find a stylesheet to render the Content. * @param location the requested location - * @param contentType the type of the current Content + * @param contentType the content type for rendering * @param view the content view * @param format the format of the output (html, pdf, ...) * @param pluginName the plugin name @@ -91,6 +96,9 @@ ArrayList<String> result = new ArrayList<String>(); // first look in the filesystem (case of automatic content types) + result.add("context://WEB-INF/param/content-types/_dynamic" + pluginName + "/stylesheets/" + contentType + "/" + contentType + f + "-" + view + ".xsl"); + result.add("context://WEB-INF/param/content-types/_dynamic/" + pluginName + "/stylesheets/" + contentType + "/" + contentType + f + ".xsl"); + result.add("context://WEB-INF/param/content-types/" + pluginName + "/stylesheets/" + contentType + "/" + contentType + f + "-" + view + ".xsl"); result.add("context://WEB-INF/param/content-types/" + pluginName + "/stylesheets/" + contentType + "/" + contentType + f + ".xsl"); Index: main/plugin-cms/src/org/ametys/cms/lucene/MetadataIndexer.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/lucene/MetadataIndexer.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/lucene/MetadataIndexer.java (working copy) @@ -36,10 +36,10 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.AbstractMetadataSetElement; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.cms.contenttype.MetadataDefinitionReference; -import org.ametys.cms.contenttype.AbstractMetadataSetElement; import org.ametys.cms.contenttype.RepeaterDefinition; import org.ametys.plugins.explorer.resources.metadata.TikaProvider; import org.ametys.plugins.repository.metadata.CompositeMetadata; @@ -59,6 +59,8 @@ /** The users manager. */ protected UsersManager _usersManager; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; /** The Tika instance. */ protected Tika _tika; @@ -74,6 +76,7 @@ { _parser = (SAXParser) manager.lookup(SAXParser.ROLE); _usersManager = (UsersManager) manager.lookup(UsersManager.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); TikaProvider tikaProvider = (TikaProvider) manager.lookup(TikaProvider.ROLE); _tika = tikaProvider.getTika(); } @@ -94,11 +97,12 @@ * @param metadataSet the metadata set to index. * @param metadata the root composite metadata. * @param parentDefinition the parent metadata definition. - * @param cType the content type. + * @param cTypes the content types. + * @param mixins the mixins * @param document the document to index into. * @throws Exception if an error occurs. */ - public void indexMetadataSet(AbstractMetadataSetElement metadataSet, CompositeMetadata metadata, MetadataDefinition parentDefinition, ContentType cType, Document document) throws Exception + public void indexMetadataSet(AbstractMetadataSetElement metadataSet, CompositeMetadata metadata, MetadataDefinition parentDefinition, String[] cTypes, String[] mixins, Document document) throws Exception { for (AbstractMetadataSetElement element : metadataSet.getElements()) { @@ -112,20 +116,20 @@ MetadataDefinition definition; if (parentDefinition == null) { - definition = cType.getMetadataDefinition(name); + definition = _contentTypesHelper.getMetadataDefinition(name, cTypes, mixins); } else { definition = parentDefinition.getMetadataDefinition(name); } - indexMetadata(metadataSet, metadata, cType, document, element, name, definition); + indexMetadata(metadataSet, metadata, cTypes, mixins, document, element, name, definition); } } else { // case of fieldSets, even if it would be really strange to have a fieldSet in the index metadataSet ! - indexMetadataSet(element, metadata, parentDefinition, cType, document); + indexMetadataSet(element, metadata, parentDefinition, cTypes, mixins, document); } } } @@ -134,7 +138,8 @@ * Index a content metadata. * @param metadataSet * @param metadata the metadata to index. - * @param cType the content type. + * @param cTypes the content types. + * @param mixins the mixin types. * @param document the document to index into. * @param element the metadata set element. * @param name the metadata name. @@ -143,16 +148,17 @@ * @throws IOException * @throws Exception if an error occurs. */ - public void indexMetadata(AbstractMetadataSetElement metadataSet, CompositeMetadata metadata, ContentType cType, Document document, AbstractMetadataSetElement element, String name, MetadataDefinition definition) throws SAXException, IOException, Exception + public void indexMetadata(AbstractMetadataSetElement metadataSet, CompositeMetadata metadata, String[] cTypes, String[] mixins, Document document, AbstractMetadataSetElement element, String name, MetadataDefinition definition) throws SAXException, IOException, Exception { - indexMetadata(metadataSet, metadata, cType, document, element, name, name, definition); + indexMetadata(metadataSet, metadata, cTypes, mixins, document, element, name, name, definition); } /** * Index a content metadata. * @param metadataSet * @param metadata the metadata to index. - * @param cType the content type. + * @param cTypes the content types. + * @param mixins the mixins. * @param document the document to index into. * @param element the metadata set element. * @param name the metadata name. @@ -162,7 +168,7 @@ * @throws IOException * @throws Exception if an error occurs. */ - public void indexMetadata(AbstractMetadataSetElement metadataSet, CompositeMetadata metadata, ContentType cType, Document document, AbstractMetadataSetElement element, String name, String fieldName, MetadataDefinition definition) throws SAXException, IOException, Exception + public void indexMetadata(AbstractMetadataSetElement metadataSet, CompositeMetadata metadata, String[] cTypes, String[] mixins, Document document, AbstractMetadataSetElement element, String name, String fieldName, MetadataDefinition definition) throws SAXException, IOException, Exception { switch (definition.getType()) { @@ -184,12 +190,12 @@ for (String entry : entries) { CompositeMetadata subMetadata = composite.getCompositeMetadata(entry); - indexMetadataSet(element, subMetadata, definition, cType, document); + indexMetadataSet(element, subMetadata, definition, cTypes, mixins, document); } } else { - indexMetadataSet(element, composite, definition, cType, document); + indexMetadataSet(element, composite, definition, cTypes, mixins, document); } break; Index: main/plugin-cms/src/org/ametys/cms/clientsideelement/content/SmartContentClientSideElementHelper.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/clientsideelement/content/SmartContentClientSideElementHelper.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/clientsideelement/content/SmartContentClientSideElementHelper.java (revision 0) @@ -0,0 +1,298 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.clientsideelement.content; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.avalon.framework.component.Component; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.avalon.framework.service.Serviceable; +import org.apache.commons.lang.StringUtils; + +import org.ametys.cms.clientsideelement.SmartContentClientSideElement; +import org.ametys.cms.repository.Content; +import org.ametys.cms.repository.ModifiableContent; +import org.ametys.cms.repository.WorkflowAwareContent; +import org.ametys.cms.workflow.AbstractContentWorkflowComponent; +import org.ametys.plugins.repository.lock.LockHelper; +import org.ametys.plugins.repository.lock.LockableAmetysObject; +import org.ametys.plugins.workflow.Workflow; +import org.ametys.runtime.right.RightsManager; +import org.ametys.runtime.right.RightsManager.RightResult; +import org.ametys.runtime.user.CurrentUserProvider; +import org.ametys.runtime.user.User; +import org.ametys.runtime.user.UsersManager; +import org.ametys.runtime.util.I18nizableText; + +import com.opensymphony.workflow.spi.Step; + +/** + * Component helper for {@link SmartContentClientSideElement} + * + */ +public class SmartContentClientSideElementHelper implements Serviceable, Component +{ + /** The Avalon Role */ + public static final String ROLE = SmartContentClientSideElementHelper.class.getName(); + + private UsersManager _usersManager; + private Workflow _workflow; + private CurrentUserProvider _userProvider; + private RightsManager _rightsManager; + + @Override + public void service(ServiceManager manager) throws ServiceException + { + _usersManager = (UsersManager) manager.lookup(UsersManager.ROLE); + _workflow = (Workflow) manager.lookup(Workflow.ROLE); + _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); + _rightsManager = (RightsManager) manager.lookup(RightsManager.ROLE); + } + + /** + * Determines if the workflow step is correct + * @param parameters The client side element 's parameters + * @param content the content + * @return true if the workflow step is incorrect + */ + public boolean isWorkflowStepCorrect(Map<String, Object> parameters, Content content) + { + WorkflowAwareContent waContent = (WorkflowAwareContent) content; + long wId = waContent.getWorkflowId(); + List<Step> steps = _workflow.getCurrentSteps(wId); + + List<Integer> stepIds = new ArrayList<Integer>(); + for (Step step : steps) + { + stepIds.add(step.getStepId()); + } + + String correctStepsAsString = parameters.get("enabled-on-workflow-step-only").toString(); + String[] correctStepsAsStringArray = correctStepsAsString.split(", ?"); + + for (String correctStepAsString : correctStepsAsStringArray) + { + if (!StringUtils.isEmpty(correctStepAsString) && stepIds.contains(Integer.parseInt(correctStepAsString))) + { + return true; + } + } + + return false; + } + + /** + * Get i18n description when user can not do action because he has no right on content + * @param parameters The client side element 's parameters + * @param content The content + * @return The {@link I18nizableText} description + */ + public I18nizableText getNoRightDescription (Map<String, Object> parameters, Content content) + { + List<String> rightI18nParameters = new ArrayList<String>(); + rightI18nParameters.add(content.getTitle()); + + I18nizableText ed = (I18nizableText) parameters.get("noright-content-description"); + return new I18nizableText(ed.getCatalogue(), ed.getKey(), rightI18nParameters); + } + + /** + * Get i18n description when user can not do action because the content is locked + * @param parameters The client side element 's parameters + * @param content The content + * @return The {@link I18nizableText} description + */ + public I18nizableText getLockedDescription (Map<String, Object> parameters, Content content) + { + String currentLockerLogin = ((LockableAmetysObject) content).getLockOwner(); + User currentLocker = currentLockerLogin != null ? _usersManager.getUser(currentLockerLogin) : null; + + List<String> lockI18nParameters = new ArrayList<String>(); + lockI18nParameters.add(content.getTitle()); + lockI18nParameters.add(currentLocker != null ? currentLocker.getFullName() : ""); + lockI18nParameters.add(currentLockerLogin != null ? currentLockerLogin : "Anonymous"); + + I18nizableText ed = (I18nizableText) parameters.get("locked-content-description"); + return new I18nizableText(ed.getCatalogue(), ed.getKey(), lockI18nParameters); + } + + /** + * Get i18n description when user can not do action because there is no workflow action unvailable + * @param parameters The client side element 's parameters + * @param content The content + * @return The {@link I18nizableText} description + */ + public I18nizableText getWorkflowActionUnvailableDescription (Map<String, Object> parameters, Content content) + { + List<String> workflowI18nParameters = new ArrayList<String>(); + workflowI18nParameters.add(content.getTitle()); + + I18nizableText ed = (I18nizableText) parameters.get("workflowaction-content-description"); + return new I18nizableText(ed.getCatalogue(), ed.getKey(), workflowI18nParameters); + } + + /** + * Get i18n description when user can not do action because the workflow step is incorrect + * @param parameters The client side element 's parameters + * @param content The content + * @return The {@link I18nizableText} description + */ + public I18nizableText getIncorrectWorkflowStepDescription (Map<String, Object> parameters, Content content) + { + List<String> workflowI18nParameters = new ArrayList<String>(); + workflowI18nParameters.add(content.getTitle()); + + I18nizableText ed = (I18nizableText) parameters.get("workflowstep-content-description"); + return new I18nizableText(ed.getCatalogue(), ed.getKey(), workflowI18nParameters); + } + + /** + * Get i18n description when user can not do action because the content is not modifiable + * @param parameters The client side element 's parameters + * @param content The content + * @return The {@link I18nizableText} description + */ + public I18nizableText getNoModifiableDescription (Map<String, Object> parameters, Content content) + { + List<String> modifiableI18nParameters = new ArrayList<String>(); + modifiableI18nParameters.add(content.getTitle()); + + I18nizableText ed = (I18nizableText) parameters.get("nomodifiable-content-description"); + return new I18nizableText(ed.getCatalogue(), ed.getKey(), modifiableI18nParameters); + } + + /** + * Get i18n description when user can do action + * @param parameters The client side element 's parameters + * @param content The content + * @return The {@link I18nizableText} description + */ + public I18nizableText getAllRightDescription (Map<String, Object> parameters, Content content) + { + List<String> allrightI18nParameters = new ArrayList<String>(); + allrightI18nParameters.add(content.getTitle()); + + I18nizableText ed = (I18nizableText) parameters.get("allright-content-description"); + return new I18nizableText(ed.getCatalogue(), ed.getKey(), allrightI18nParameters); + } + + /** + * Determines if the content is locked + * @param content the content + * @return true if the content is locked + */ + public boolean isLocked(Content content) + { + if (content instanceof LockableAmetysObject) + { + LockableAmetysObject lockableContent = (LockableAmetysObject) content; + if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, _userProvider.getUser())) + { + return true; + } + } + + return false; + } + + /** + * Determines if the content is modifiable + * @param content the content + * @return true if the content is modifiable + */ + public boolean isModifiable(Content content) + { + return content instanceof ModifiableContent; + } + + /** + * Determines if the user has sufficient right for the given content + * @param rights The client side element 's rights + * @param content the content + * @return true if user has sufficient right + */ + public boolean hasRight (Map<String, String> rights, Content content) + { + if (rights.isEmpty()) + { + return true; + } + + Set<String> rightsToCheck = rights.keySet(); + for (String rightToCheck : rightsToCheck) + { + if (StringUtils.isNotEmpty(rightToCheck)) + { + if (_rightsManager.hasRight(_userProvider.getUser(), rightToCheck, getContentRightContext (content)) == RightResult.RIGHT_OK) + { + return true; + } + } + } + + return false; + } + + /** + * Get the right context to check right on content + * @param content the content + * @return the right context + */ + public String getContentRightContext (Content content) + { + return "/contents/" + content.getName(); + } + + /** + * Determines if the workflow action is correct + * @param parameters The client side element 's parameters + * @param content the content + * @return true if the workflow action is incorrect + */ + public int workflowAction(Map<String, Object> parameters, Content content) + { + WorkflowAwareContent waContent = (WorkflowAwareContent) content; + long wId = waContent.getWorkflowId(); + + Map<String, Object> vars = new HashMap<String, Object>(); + vars.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); + int[] actionIds = _workflow.getAvailableActions(wId, vars); + Arrays.sort(actionIds); + + String correctActionsAsString = parameters.get("enabled-on-workflow-action-only").toString(); + String[] correctActionsAsStringArray = correctActionsAsString.split(", ?"); + + int correctActionId = -1; + for (String correctActionAsString : correctActionsAsStringArray) + { + int actionId = Integer.parseInt(correctActionAsString); + if (!StringUtils.isEmpty(correctActionAsString) && Arrays.binarySearch(actionIds, actionId) >= 0) + { + correctActionId = actionId; + break; + } + } + + return correctActionId; + } + +} Index: main/plugin-cms/src/org/ametys/cms/clientsideelement/relations/SetContentMetadataClientSideElement.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/clientsideelement/relations/SetContentMetadataClientSideElement.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/clientsideelement/relations/SetContentMetadataClientSideElement.java (working copy) @@ -16,6 +16,7 @@ package org.ametys.cms.clientsideelement.relations; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -25,6 +26,8 @@ import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.ametys.cms.contenttype.ContentType; @@ -112,50 +115,78 @@ private List<MetadataDefinition> _findCompatibleMetadata(List<Content> contentToReference, List<Content> contentToEdit) { // First we need to find the type of the target metadata we are looking for - Set<String> contentTypesToReference = new HashSet<String>(); + Collection<String> contentTypesToReference = new HashSet<String>(); for (Content content: contentToReference) { - contentTypesToReference.add(content.getType()); + Set<String> ancestorsAndMySelf = new HashSet<String>(); + + String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + for (String id : allContentTypes) + { + ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); + ancestorsAndMySelf.add(id); + } + + if (contentTypesToReference == null) + { + contentTypesToReference = ancestorsAndMySelf; + } + else + { + contentTypesToReference = CollectionUtils.intersection(contentTypesToReference, ancestorsAndMySelf); + } } - String metadataContentType = _ctypesHelper.getCommonAncestor(contentTypesToReference); // Second we need to know if this metadata will be mulitple or not boolean requiresMultiple = contentToReference.size() > 1; // Third we need to know the target content type - Set<String> contentTypesToEdit = new HashSet<String>(); + Collection<String> contentTypesToEdit = new ArrayList<String>(); for (Content content: contentToEdit) { - contentTypesToEdit.add(content.getType()); + Set<String> ancestorsAndMySelf = new HashSet<String>(); + + String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + for (String id : allContentTypes) + { + ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); + ancestorsAndMySelf.add(id); + } + + if (contentTypesToEdit.size() == 0) + { + contentTypesToEdit = ancestorsAndMySelf; + } + else + { + contentTypesToEdit = CollectionUtils.intersection(contentTypesToEdit, ancestorsAndMySelf); + } } - String targetContentTypeName = _ctypesHelper.getCommonAncestor(contentTypesToEdit); - // Fourth lets list the whole content types tree of the targetContentType - Set<String> compatibleContentTypes = _ctypesHelper.getAncestors(metadataContentType); - compatibleContentTypes.add(metadataContentType); - // Now lets navigate in the target content type to find a content metadata limited to the metadata content type (or its parent types), that is multiple if necessary - List<MetadataDefinition> metadata = new ArrayList<MetadataDefinition>(); - ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); - for (String metadataName : targetContentType.getMetadataNames()) + for (String targetContentTypeName : contentTypesToEdit) { - MetadataDefinition metaDef = targetContentType.getMetadataDefinition(metadataName); - metadata.addAll(_findCompatibleMetadata(metaDef, false, compatibleContentTypes, requiresMultiple)); + ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); + for (String metadataName : targetContentType.getMetadataNames()) + { + MetadataDefinition metaDef = targetContentType.getMetadataDefinition(metadataName); + metadata.addAll(_findCompatibleMetadata(targetContentTypeName, metaDef, false, contentTypesToReference, requiresMultiple)); + } } - return metadata; } - private List<MetadataDefinition> _findCompatibleMetadata(MetadataDefinition metaDef, boolean anyParentIsMultiple, Set<String> compatibleContentTypes, boolean requiresMultiple) + private List<MetadataDefinition> _findCompatibleMetadata(String targetContentTypeName, MetadataDefinition metaDef, boolean anyParentIsMultiple, Collection<String> compatibleContentTypes, boolean requiresMultiple) { List<MetadataDefinition> metadata = new ArrayList<MetadataDefinition>(); if (MetadataType.CONTENT.equals(metaDef.getType()) && (metaDef.getContentType() == null || compatibleContentTypes.contains(metaDef.getContentType())) - && (!requiresMultiple || metaDef.isMultiple() || anyParentIsMultiple)) + && (!requiresMultiple || metaDef.isMultiple() || anyParentIsMultiple) + && metaDef.getReferenceContentType().equals(targetContentTypeName)) { metadata.add(metaDef); } @@ -164,7 +195,7 @@ for (String subMetadataName : metaDef.getMetadataComponentsName()) { MetadataDefinition subMetaDef = metaDef.getMetadataDefinition(subMetadataName); - metadata.addAll(_findCompatibleMetadata(subMetaDef, anyParentIsMultiple || metaDef instanceof RepeaterDefinition, compatibleContentTypes, requiresMultiple)); + metadata.addAll(_findCompatibleMetadata(targetContentTypeName, subMetaDef, anyParentIsMultiple || metaDef instanceof RepeaterDefinition, compatibleContentTypes, requiresMultiple)); } } @@ -184,18 +215,40 @@ { List<Content> contentToReference = _resolve(contentIdsToReference); List<Content> contentToEdit = _resolve(contentIdsToEdit); - - Set<String> contentTypesToEdit = new HashSet<String>(); + Collection<String> contentTypesToEdit = new ArrayList<String>(); for (Content content: contentToEdit) { - contentTypesToEdit.add(content.getType()); + Set<String> ancestorsAndMySelf = new HashSet<String>(); + + String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + for (String id : allContentTypes) + { + ancestorsAndMySelf.addAll(_ctypesHelper.getAncestors(id)); + ancestorsAndMySelf.add(id); + } + + if (contentTypesToEdit.size() == 0) + { + contentTypesToEdit = ancestorsAndMySelf; + } + else + { + contentTypesToEdit = CollectionUtils.intersection(contentTypesToEdit, ancestorsAndMySelf); + } + } + + for (String targetContentTypeName : contentTypesToEdit) + { + ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); + MetadataDefinition metadataDef = targetContentType.getMetadataDefinitionByPath(metadatapath); + if (metadataDef != null) + { + return _setContentMetatada(contentToReference, contentToEdit, metadataDef, workflowActionIds); + } } - String targetContentTypeName = _ctypesHelper.getCommonAncestor(contentTypesToEdit); - ContentType targetContentType = _ctypesEP.getExtension(targetContentTypeName); - MetadataDefinition metadataDef = targetContentType.getMetadataDefinitionByPath(metadatapath); - return _setContentMetatada(contentToReference, contentToEdit, metadataDef, workflowActionIds); + throw new IllegalStateException("Unable to find medatata definition to path '" + metadatapath + "'."); } /** Index: main/plugin-cms/src/org/ametys/cms/clientsideelement/FormEditionModeMenu.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/clientsideelement/FormEditionModeMenu.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/clientsideelement/FormEditionModeMenu.java (working copy) @@ -21,12 +21,13 @@ import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; +import org.apache.commons.lang.StringUtils; -import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.AbstractMetadataSetElement; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.Fieldset; import org.ametys.cms.contenttype.MetadataSet; -import org.ametys.cms.contenttype.AbstractMetadataSetElement; import org.ametys.cms.repository.Content; import org.ametys.cms.ui.SimpleMenu; import org.ametys.plugins.repository.AmetysObjectResolver; @@ -43,6 +44,8 @@ /** Content type extension point. */ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; @Override public void service(ServiceManager smanager) throws ServiceException @@ -50,6 +53,7 @@ super.service(smanager); _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); } /** @@ -64,23 +68,17 @@ { Map<String, Object> results = new HashMap<String, Object>(); - // CMS-5261 - // TODO remplace by : - // MetadataSet metadataSetForEdition = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes()); - // boolean found = _searchForTabFieldSet(metadataSetForEdition.getElements()); - Content content = _resolver.resolveById(contentId); - ContentType cType = _contentTypeExtensionPoint.getExtension(content.getType()); - MetadataSet metadataSet = cType.getMetadataSetForEdition(metadataSetName); + MetadataSet metadataSetForEdition = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); - if (metadataSet == null) + if (metadataSetForEdition == null) { - String errorMsg = String.format("Unknown metadata set '%s' of type 'edition' for content type '%s'", metadataSetName, cType.getId()); + String errorMsg = String.format("Unknown metadata set '%s' of type 'edition' for content type(s) '%s'", metadataSetName, StringUtils.join(content.getTypes(), ',')); getLogger().error(errorMsg); throw new IllegalArgumentException(errorMsg); } - boolean found = _searchForTabFieldSet(metadataSet.getElements()); + boolean found = _searchForTabFieldSet(metadataSetForEdition.getElements()); results.put("has-tab", found); return results; Index: main/plugin-cms/src/org/ametys/cms/clientsideelement/SmartContentTypesGallery.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/clientsideelement/SmartContentTypesGallery.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/clientsideelement/SmartContentTypesGallery.java (revision 0) @@ -0,0 +1,192 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.clientsideelement; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.context.Context; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; + +import org.ametys.cms.clientsideelement.content.SmartContentClientSideElementHelper; +import org.ametys.cms.repository.Content; +import org.ametys.cms.repository.WorkflowAwareContent; +import org.ametys.plugins.repository.AmetysObjectResolver; +import org.ametys.runtime.ui.Callable; + +/** + * This element creates a menu with one gallery item per content type classified by category. + * The user rights are checked. + */ +public class SmartContentTypesGallery extends ContentTypesGallery +{ + /** Ametys object resolver */ + protected AmetysObjectResolver _resolver; + /** The context */ + protected Context _context; + /** Helper for smart content client elements */ + protected SmartContentClientSideElementHelper _smartHelper; + + @Override + public void service(ServiceManager manager) throws ServiceException + { + super.service(manager); + _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); + _smartHelper = (SmartContentClientSideElementHelper) manager.lookup(SmartContentClientSideElementHelper.ROLE); + } + + /** + * Get informations on contents' state + * @param contentsId the ids of contents + * @return informations on contents' state + */ + @Callable + public Map<String, Object> getStatus(List<String> contentsId) + { + Map<String, Object> results = new HashMap<String, Object>(); + + results.put("unmodifiable-contents", new ArrayList<Map<String, Object>>()); + results.put("locked-contents", new ArrayList<Map<String, Object>>()); + results.put("noright-contents", new ArrayList<Map<String, Object>>()); + results.put("invalidworkflowaction-contents", new ArrayList<Map<String, Object>>()); + results.put("invalidworkflowstep-contents", new ArrayList<Map<String, Object>>()); + results.put("allright-contents", new ArrayList<Map<String, Object>>()); + + for (String contentId : contentsId) + { + Content content = _resolver.resolveById(contentId); + + boolean error = false; + + if (content instanceof WorkflowAwareContent) + { + // Is modifiable + String enabledOnModifiableOnly = (String) this._initialParameters.get("enabled-on-modifiable-only"); + if ("true".equals(enabledOnModifiableOnly) && !_smartHelper.isModifiable(content)) + { + Map<String, Object> contentParams = getContentDefaultParameters (content); + contentParams.put("description", _smartHelper.getNoModifiableDescription(this._initialParameters, content)); + + List<Map<String, Object>> unModifiableContents = (List<Map<String, Object>>) results.get("unmodifiable-contents"); + unModifiableContents.add(contentParams); + + error = true; + } + + // Is locked + String enabledOnUnlockOnly = (String) this._initialParameters.get("enabled-on-unlock-only"); + if ("true".equals(enabledOnUnlockOnly) && _smartHelper.isLocked(content)) + { + Map<String, Object> contentParams = getContentDefaultParameters (content); + contentParams.put("description", _smartHelper.getLockedDescription(this._initialParameters, content)); + + List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); + lockedContents.add(contentParams); + + error = true; + } + + // Has right correct + String enabledOnRightOnly = (String) this._initialParameters.get("enabled-on-right-only"); + if ("true".equals(enabledOnRightOnly) && !_smartHelper.hasRight(_rights, content)) + { + Map<String, Object> contentParams = getContentDefaultParameters (content); + contentParams.put("description", _smartHelper.getNoRightDescription (this._initialParameters, content)); + + List<Map<String, Object>> norightContents = (List<Map<String, Object>>) results.get("noright-contents"); + norightContents.add(contentParams); + + error = true; + } + + // Is workflow action correct + String enabledOnWorkflowActionOnly = (String) this._initialParameters.get("enabled-on-workflow-action-only"); + if (enabledOnWorkflowActionOnly != null) + { + int actionId = _smartHelper.workflowAction(this._initialParameters, content); + if (actionId == -1) + { + Map<String, Object> contentParams = getContentDefaultParameters (content); + contentParams.put("description", _smartHelper.getWorkflowActionUnvailableDescription(this._initialParameters, content)); + + List<Map<String, Object>> invalidActionContents = (List<Map<String, Object>>) results.get("invalidworkflowaction-contents"); + invalidActionContents.add(contentParams); + + error = true; + } + else + { + results.put("workflowaction-content-actionId", actionId); + } + } + + // Is workflow step correct + String enabledOnWorkflowStepOnly = (String) this._initialParameters.get("enabled-on-workflow-step-only"); + if (enabledOnWorkflowStepOnly != null && !_smartHelper.isWorkflowStepCorrect(this._initialParameters, content)) + { + Map<String, Object> contentParams = getContentDefaultParameters (content); + contentParams.put("description", _smartHelper.getIncorrectWorkflowStepDescription(this._initialParameters, content)); + + List<Map<String, Object>> invalidStepContents = (List<Map<String, Object>>) results.get("invalidworkflowstep-contents"); + invalidStepContents.add(contentParams); + + error = true; + } + + if (_isAllRight (content, error, results)) + { + Map<String, Object> contentParams = getContentDefaultParameters (content); + contentParams.put("description", _smartHelper.getAllRightDescription(this._initialParameters, content)); + + List<Map<String, Object>> allrightContents = (List<Map<String, Object>>) results.get("allright-contents"); + allrightContents.add(contentParams); + } + } + } + + return results; + } + + /** + * Determines if the user can finally do action on content + * @param content The content + * @param hasError true if a error has already occurs + * @param results The result parameters to be passed to client side + * @return true if the user can finally do action on content + */ + protected boolean _isAllRight (Content content, boolean hasError, Map<String, Object> results) + { + return !hasError; + } + + + /** + * Get the default content's parameters + * @param content The content + * @return The default parameters + */ + protected Map<String, Object> getContentDefaultParameters (Content content) + { + Map<String, Object> contentParams = new HashMap<String, Object>(); + contentParams.put("id", content.getId()); + contentParams.put("title", content.getTitle()); + + return contentParams; + } +} Index: main/plugin-cms/src/org/ametys/cms/clientsideelement/ContentTypesGallery.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/clientsideelement/ContentTypesGallery.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/clientsideelement/ContentTypesGallery.java (revision 0) @@ -0,0 +1,367 @@ +/* + * Copyright 2010 Anyware Services + * + * 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.ametys.cms.clientsideelement; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.apache.avalon.framework.component.ComponentException; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; + +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.ui.SimpleMenu; +import org.ametys.runtime.right.RightsManager.RightResult; +import org.ametys.runtime.ui.ClientSideElement; +import org.ametys.runtime.ui.StaticClientSideElement; +import org.ametys.runtime.util.I18nUtils; +import org.ametys.runtime.util.I18nizableText; + +/** + * This element creates a menu with one gallery item per content type classified by category. + * The user rights are checked. + */ +public class ContentTypesGallery extends SimpleMenu +{ + /** The list of content types */ + protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** The i18n utils */ + protected I18nUtils _i18nUtils; + + private boolean _contentTypesInitialized; + + @Override + public void service(ServiceManager smanager) throws ServiceException + { + super.service(smanager); + _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); + } + + @Override + protected void _getGalleryItems(Map<String, Object> parameters, Map<String, Object> contextualParameters) + { + try + { + _lazyInitializeContentTypeGallery(); + } + catch (Exception e) + { + throw new IllegalStateException("Unable to lookup client side element local components", e); + } + + if (_galleryItems.size() > 0) + { + parameters.put("gallery-item", new LinkedHashMap<String, Object>()); + + for (GalleryItem galleryItem : _galleryItems) + { + Map<String, Object> galleryItems = (Map<String, Object>) parameters.get("gallery-item"); + galleryItems.put("gallery-groups", new ArrayList<Object>()); + + for (GalleryGroup galleryGp : galleryItem.getGroups()) + { + List<Object> galleryGroups = (List<Object>) galleryItems.get("gallery-groups"); + + Map<String, Object> groupParams = new LinkedHashMap<String, Object>(); + + I18nizableText label = galleryGp.getLabel(); + groupParams.put("label", label); + + // Group items + List<String> gpItems = new ArrayList<String>(); + for (ClientSideElement element : galleryGp.getItems()) + { + String cTypeId = element.getId().substring(this.getId().length() + 1); + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + + if (hasRight(cType)) + { + gpItems.add(element.getId()); + } + } + + if (gpItems.size() > 0) + { + groupParams.put("items", gpItems); + galleryGroups.add(groupParams); + } + } + } + } + } + + private void _lazyInitializeContentTypeGallery() throws ConfigurationException + { + if (!_contentTypesInitialized) + { + Map<I18nizableText, Set<ContentType>> cTypesByGroup = _getContentTypesByGroup(); + + if (cTypesByGroup.size() > 0) + { + GalleryItem galleryItem = new GalleryItem(); + + for (I18nizableText groupLabel : cTypesByGroup.keySet()) + { + GalleryGroup galleryGroup = new GalleryGroup(groupLabel); + galleryItem.addGroup(galleryGroup); + + Set<ContentType> cTypes = cTypesByGroup.get(groupLabel); + for (ContentType cType : cTypes) + { + String id = this.getId() + "-" + cType.getId(); + + try + { + Configuration conf = _getContentTypeConfiguration (id, cType); + _galleryItemManager.addComponent(_pluginName, null, id, StaticClientSideElement.class, conf); + galleryGroup.addItem(new UnresolvedItem(id, true)); + } + catch (ComponentException e) + { + throw new ConfigurationException("Unable to configure local client side element of id " + id, e); + } + } + } + + _galleryItems.add(galleryItem); + } + + if (_galleryItems.size() > 0) + { + try + { + _galleryItemManager.initialize(); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to lookup parameter local components", e); + } + } + } + + _contentTypesInitialized = true; + } + + /** + * Get the configuration of the content type item + * @param id The id of item + * @param cType The content type + * @return The configuration + */ + protected Configuration _getContentTypeConfiguration (String id, ContentType cType) + { + DefaultConfiguration conf = new DefaultConfiguration("extension"); + conf.setAttribute("id", id); + + DefaultConfiguration classConf = new DefaultConfiguration("class"); + classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController"); + + // Parent controller id + DefaultConfiguration parentIdConf = new DefaultConfiguration("controllerParentId"); + parentIdConf.setValue(this.getId()); + classConf.addChild(parentIdConf); + + // Label + DefaultConfiguration labelConf = new DefaultConfiguration("label"); + labelConf.setValue(_i18nUtils.translate(cType.getLabel())); + classConf.addChild(labelConf); + + // Description + DefaultConfiguration descConf = new DefaultConfiguration("description"); + descConf.setValue(_i18nUtils.translate(cType.getDescription())); + classConf.addChild(descConf); + + // Content type + DefaultConfiguration typeConf = new DefaultConfiguration("contentTypes"); + typeConf.setValue(cType.getId()); + classConf.addChild(typeConf); + + // Icons + DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small"); + iconSmallConf.setValue(cType.getSmallIcon()); + classConf.addChild(iconSmallConf); + DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium"); + iconMediumConf.setValue(cType.getMediumIcon()); + classConf.addChild(iconMediumConf); + DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large"); + iconLargeConf.setValue(cType.getLargeIcon()); + classConf.addChild(iconLargeConf); + + // Common configuration + Map<String, Object> commonConfig = (Map<String, Object>) this._initialParameters.get("items-config"); + for (String tagName : commonConfig.keySet()) + { + DefaultConfiguration c = new DefaultConfiguration(tagName); + c.setValue(String.valueOf(commonConfig.get(tagName))); + classConf.addChild(c); + } + + conf.addChild(classConf); + return conf; + } + + /** + * Get the list of content types classified by groups + * @return The content types + */ + protected Map<I18nizableText, Set<ContentType>> _getContentTypesByGroup () + { + Map<I18nizableText, Set<ContentType>> groups = new TreeMap<I18nizableText, Set<ContentType>>(new I18nizableTextComparator()); + + if (this._initialParameters.get("contentTypes") != null) + { + String[] contentTypesIds = ((String) this._initialParameters.get("contentTypes")).split(","); + + boolean allowInherit = "true".equals(this._initialParameters.get("allowInherit")); + + for (String contentTypeId : contentTypesIds) + { + ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); + + if (isValidContentType(contentType)) + { + addContentType (contentType, groups); + } + + if (allowInherit) + { + for (String subTypeId : _contentTypeExtensionPoint.getSubTypes(contentTypeId)) + { + ContentType subContentType = _contentTypeExtensionPoint.getExtension(subTypeId); + if (isValidContentType(subContentType)) + { + addContentType (subContentType, groups); + } + } + } + } + } + else + { + Set<String> contentTypesIds = _contentTypeExtensionPoint.getExtensionsIds(); + + for (String contentTypeId : contentTypesIds) + { + ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); + + if (isValidContentType(contentType)) + { + addContentType (contentType, groups); + } + } + } + + return groups; + } + + /** + * Add content to groups + * @param contentType The content type + * @param groups The groups + */ + protected void addContentType (ContentType contentType, Map<I18nizableText, Set<ContentType>> groups) + { + I18nizableText group = contentType.getCategory(); + if ((group.isI18n() && group.getKey().isEmpty()) || (!group.isI18n() && group.getLabel().isEmpty())) + { + group = new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_CREATECONTENTMENU_GROUP_10_CONTENT"); + } + + if (!groups.containsKey(group)) + { + groups.put(group, new TreeSet<ContentType>(new ContentTypeComparator())); + } + Set<ContentType> cTypes = groups.get(group); + cTypes.add(contentType); + } + + + /** + * Determines if the content type is a valid content type for the gallery + * @param contentType The coentent + * @return true if it is a valid content type + */ + protected boolean isValidContentType (ContentType contentType) + { + return !contentType.isAbstract() && !contentType.isPrivate() && !contentType.isMixin(); + } + + /** + * Test if the current user has the right needed by the content type to create a content. + * @param cType the content type + * @return true if the user has the right needed, false otherwise. + */ + protected boolean hasRight(ContentType cType) + { + String right = cType.getRight(); + + if (right == null) + { + return true; + } + else + { + String login = _currentUserProvider.getUser(); + return _rightsManager.hasRight(login, right, "/contributor") == RightResult.RIGHT_OK || _rightsManager.hasRight(login, right, "/contents") == RightResult.RIGHT_OK; + } + } + + class ContentTypeComparator implements Comparator<ContentType> + { + @Override + public int compare(ContentType c1, ContentType c2) + { + I18nizableText t1 = c1.getLabel(); + I18nizableText t2 = c2.getLabel(); + + String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); + String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); + + int compareTo = str1.toString().compareTo(str2.toString()); + if (compareTo == 0) + { + // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet + // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal + return 1; + } + return compareTo; + } + } + + class I18nizableTextComparator implements Comparator<I18nizableText> + { + @Override + public int compare(I18nizableText t1, I18nizableText t2) + { + String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); + String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); + + return str1.toString().compareTo(str2.toString()); + } + } + +} Index: main/plugin-cms/src/org/ametys/cms/clientsideelement/MixinContentTypesGallery.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/clientsideelement/MixinContentTypesGallery.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/clientsideelement/MixinContentTypesGallery.java (revision 0) @@ -0,0 +1,31 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.clientsideelement; + +import org.ametys.cms.contenttype.ContentType; + +/** + * This element creates a menu with one gallery item per mixin content type classified by category. + * The user rights are checked. + */ +public class MixinContentTypesGallery extends SmartContentTypesGallery +{ + @Override + protected boolean isValidContentType(ContentType contentType) + { + return contentType.isMixin() && !contentType.isPrivate() && !contentType.isAbstract(); + } +} Index: main/plugin-cms/src/org/ametys/cms/transformation/xslt/AmetysXSLTHelper.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/transformation/xslt/AmetysXSLTHelper.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/transformation/xslt/AmetysXSLTHelper.java (working copy) @@ -35,6 +35,7 @@ import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; import org.ametys.cms.repository.Content; +import org.ametys.cms.transformation.dom.StringElement; import org.ametys.cms.transformation.dom.TagElement; import org.ametys.plugins.explorer.resources.Resource; import org.ametys.plugins.explorer.resources.ResourceCollection; @@ -82,33 +83,73 @@ /* ------------------------ */ /** - * Get the content type of a content + * Get the content types of a content * @param contentId The content id * @return The content type or empty if the content does not exist */ - public static String contentType (String contentId) + public static NodeList contentTypes(String contentId) { + ArrayList<StringElement> contentTypes = new ArrayList<StringElement>(); + try { Content content = _ametysObjectResolver.resolveById(contentId); + try { - return content.getType(); + for (String id : content.getTypes()) + { + contentTypes.add(new StringElement("content-type", "id", id)); + } } catch (AmetysRepositoryException e) { _logger.error("Can not get type of content with id '" + contentId + "'", e); - return ""; } } catch (UnknownAmetysObjectException e) { _logger.error("Can not get type of content with id '" + contentId + "'", e); - return ""; } + + return new AmetysNodeList(contentTypes); } /** + * Get the mixins of a content + * @param contentId The content id + * @return The content type or empty if the content does not exist + */ + public static NodeList contentMixinTypes(String contentId) + { + ArrayList<StringElement> contentTypes = new ArrayList<StringElement>(); + + try + { + Content content = _ametysObjectResolver.resolveById(contentId); + + try + { + for (String id : content.getMixinTypes()) + { + contentTypes.add(new StringElement("mixin", "id", id)); + } + } + catch (AmetysRepositoryException e) + { + _logger.error("Can not get type of content with id '" + contentId + "'", e); + } + } + catch (UnknownAmetysObjectException e) + { + _logger.error("Can not get type of content with id '" + contentId + "'", e); + } + + return new AmetysNodeList(contentTypes); + } + + + /** * Get the metadata of a content * @param contentId The content id * @param metadataName The metadata name (/ for composite) Index: main/plugin-cms/src/org/ametys/cms/repository/ModifiableContent.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/ModifiableContent.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/repository/ModifiableContent.java (working copy) @@ -31,12 +31,18 @@ public interface ModifiableContent extends Content, ModifiableMetadataAwareAmetysObject, ModifiableDublinCoreAwareAmetysObject, RemovableAmetysObject { /** - * Set the type of this Content.<br> - * This method may only be called on a new Content, ie. before its first save. - * @param type the type of this content. + * Set the types of this Content.<br> + * @param types the types of this content. + * @throws AmetysRepositoryException if an error occurs. + */ + public void setTypes(String[] types) throws AmetysRepositoryException; + + /** + * Set the mixins of this Content.<br> + * @param mixins the mixins of this content. * @throws AmetysRepositoryException if an error occurs. */ - public void setType(String type) throws AmetysRepositoryException; + public void setMixinTypes(String[] mixins) throws AmetysRepositoryException; /** * Set the type of this Content.<br> Index: main/plugin-cms/src/org/ametys/cms/repository/ModifiableContentHelper.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/ModifiableContentHelper.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/repository/ModifiableContentHelper.java (working copy) @@ -58,9 +58,6 @@ /** Constants for lastModified Metadata* */ public static final String METADATA_MODIFIED = "lastModified"; - /** Constants for contentType Metadata* */ - public static final String METADATA_CONTENTTYPE = "contentType"; - private ModifiableContentHelper() { // Hides default constructor. @@ -74,20 +71,48 @@ */ public static void setType(DefaultContent content, String type) throws AmetysRepositoryException { + setTypes(content, new String[] {type}); + } + + /** + * Set {@link DefaultContent} types. + * @param content the {@link DefaultContent} to set. + * @param types the types. + * @throws AmetysRepositoryException if an error occurs. + */ + public static void setTypes(DefaultContent content, String[] types) throws AmetysRepositoryException + { Node node = content.getNode(); - if (!node.isNew()) + try { - throw new AmetysRepositoryException("Cannot call setType on an already persisted Content"); + content._checkLock(); + node.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_CONTENTTYPE, types); } + catch (RepositoryException e) + { + throw new AmetysRepositoryException("Unable to set contentType property", e); + } + } + + /** + * Set {@link DefaultContent} mixins. + * @param content the {@link DefaultContent} to set. + * @param mixins the mixins. + * @throws AmetysRepositoryException if an error occurs. + */ + public static void setMixinTypes(DefaultContent content, String[] mixins) throws AmetysRepositoryException + { + Node node = content.getNode(); try { - node.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_CONTENTTYPE, type); + content._checkLock(); + node.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_MIXINCONTENTTYPES, mixins); } catch (RepositoryException e) { - throw new AmetysRepositoryException("Unable to set contentType property", e); + throw new AmetysRepositoryException("Unable to set mixins property", e); } } Index: main/plugin-cms/src/org/ametys/cms/repository/MixinTypeExpression.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/MixinTypeExpression.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/repository/MixinTypeExpression.java (revision 0) @@ -0,0 +1,75 @@ +/* + * Copyright 2010 Anyware Services + * + * 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.ametys.cms.repository; + +import org.ametys.plugins.repository.AmetysRepositoryException; +import org.ametys.plugins.repository.RepositoryConstants; +import org.ametys.plugins.repository.query.expression.Expression; + +/** + * Constructs an {@link Expression} testing the mixin type. + */ +public class MixinTypeExpression implements Expression +{ + private Operator _operator; + private String[] _values; + + /** + * Creates the expression. + * @param operator the operator to make the comparison (only Operator.EQ and Operator.NE allowed) + * @param value the content type value + */ + public MixinTypeExpression(Operator operator, String... value) + { + if (Operator.EQ != operator && Operator.NE != operator) + { + throw new AmetysRepositoryException("Test operator '" + "' is unknown for test's expression."); + } + + _operator = operator; + _values = value; + } + + public String build() + { + StringBuilder xpath = new StringBuilder(); + + if (_values.length == 1) + { + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_MIXINCONTENTTYPES) + .append(' ').append(_operator).append(" '").append(_values[0].replaceAll("'", "''")).append("'"); + + return xpath.toString(); + } + + xpath.append(Operator.EQ.equals(_operator) ? '(' : "not("); + + for (int i = 0; i < _values.length; i++) + { + if (i > 0) + { + xpath.append(" or "); + } + + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_MIXINCONTENTTYPES) + .append(" = '").append(_values[i].replaceAll("'", "''")).append("'"); + } + + xpath.append(')'); + + return xpath.toString(); + } +} Index: main/plugin-cms/src/org/ametys/cms/repository/SearchGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/SearchGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/repository/SearchGenerator.java (working copy) @@ -43,6 +43,7 @@ import org.ametys.cms.contenttype.AbstractMetadataSetElement; import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataDefinitionReference; import org.ametys.cms.contenttype.MetadataManager; import org.ametys.cms.contenttype.MetadataSet; @@ -67,6 +68,7 @@ import org.ametys.runtime.right.RightsManager; import org.ametys.runtime.user.User; import org.ametys.runtime.user.UsersManager; +import org.ametys.runtime.util.I18nUtils; import org.ametys.runtime.util.I18nizableText; import org.ametys.runtime.util.cocoon.AbstractCurrentUserProviderServiceableGenerator; import org.ametys.runtime.util.parameter.ParameterHelper; @@ -109,7 +111,13 @@ /** The cms language manager */ protected LanguagesManager _languagesManager; + + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; + /** I18n utils */ + protected I18nUtils _i18nUtils; + @Override public void service(ServiceManager serviceManager) throws ServiceException { @@ -122,6 +130,8 @@ _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); _metadataManager = (MetadataManager) manager.lookup(MetadataManager.ROLE); _languagesManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); + _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); } public void generate() throws IOException, SAXException, ProcessingException @@ -338,10 +348,11 @@ /** * Extracts the metadata in the request parameters and returns the {@link MetadataSet} to SAX * @param request The request - * @param contentType The content type + * @param cTypes The content types + * @param mixins The mixins * @return the metadata to SAX in a {@link MetadataSet} */ - protected MetadataSet getMetadataToSAX (Request request, ContentType contentType) + protected MetadataSet getMetadataToSAX (Request request, String[] cTypes, String[] mixins) { MetadataSet metadataSet = new MetadataSet(); @@ -355,7 +366,7 @@ String propertyPath = name.substring("property-".length()); // Split on composite metadata String[] parts = propertyPath.split("/"); - if (_isMetadata(contentType, parts[0])) + if (_isMetadata(cTypes, mixins, parts[0])) { for (String propertyName : parts) { @@ -376,9 +387,9 @@ return metadataSet; } - private boolean _isMetadata (ContentType contentType, String name) + private boolean _isMetadata (String[] cTypes, String[] mixins, String name) { - return contentType.getMetadataDefinition(name) != null; + return _contentTypesHelper.getMetadataDefinition(name, cTypes, mixins) != null; } /** @@ -548,11 +559,9 @@ { AttributesImpl attrs = new AttributesImpl(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); - - attrs.addCDATAAttribute("smallIcon", contentType != null ? contentType.getSmallIcon() : "/plugins/cms/resources/img/contenttype/unknown-small.png"); - attrs.addCDATAAttribute("mediumIcon", contentType != null ? contentType.getMediumIcon() : "/plugins/cms/resources/img/contenttype/unknown-medium.png"); - attrs.addCDATAAttribute("largeIcon", contentType != null ? contentType.getLargeIcon() : "/plugins/cms/resources/img/contenttype/unknown-large.png"); + attrs.addCDATAAttribute("smallIcon", _contentTypesHelper.getSmallIcon(content)); + attrs.addCDATAAttribute("mediumIcon", _contentTypesHelper.getMediumIcon(content)); + attrs.addCDATAAttribute("largeIcon", _contentTypesHelper.getLargeIcon(content)); attrs.addCDATAAttribute("id", content.getId()); attrs.addCDATAAttribute("name", content.getName()); attrs.addCDATAAttribute("title", content.getTitle()); @@ -566,12 +575,15 @@ XMLUtils.startElement(contentHandler, "content", attrs); - // SAX the content type label - if (contentType != null) + List<String> cTypeLabels = new ArrayList<String>(); + for (String cTypeId : content.getTypes()) { - contentType.getLabel().toSAX(contentHandler, "content-type"); + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + cTypeLabels.add(_i18nUtils.translate(cType.getLabel(), content.getLanguage())); } + XMLUtils.createElement(contentHandler, "content-type", StringUtils.join(cTypeLabels, ", ")); + if (saxWorkflow && content instanceof WorkflowAwareContent) { // SAX the current workflow step @@ -583,7 +595,7 @@ XMLUtils.startElement(contentHandler, "metadata"); - MetadataSet metadataSet = getMetadataToSAX(request, contentType); + MetadataSet metadataSet = getMetadataToSAX(request, content.getTypes(), content.getMixinTypes()); _metadataManager.saxReadableMetadata(contentHandler, content, metadataSet); // SAX commons properties Index: main/plugin-cms/src/org/ametys/cms/repository/CopiableContentComponent.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/CopiableContentComponent.java (revision 25586) +++ main/plugin-cms/src/org/ametys/cms/repository/CopiableContentComponent.java (working copy) @@ -94,7 +94,7 @@ ModifiableContent content = parent.createChild(contentName, originalContentType); content.setLanguage(originalContent.getLanguage()); - content.setType(originalContent.getType()); + content.setTypes(originalContent.getTypes()); if (originalContent instanceof WorkflowAwareContent) { Index: main/plugin-cms/src/org/ametys/cms/repository/ModifiableDefaultContent.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/ModifiableDefaultContent.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/repository/ModifiableDefaultContent.java (working copy) @@ -21,8 +21,6 @@ import javax.jcr.Node; import javax.jcr.RepositoryException; -import javax.jcr.lock.Lock; -import javax.jcr.lock.LockManager; import org.apache.commons.lang.ArrayUtils; @@ -44,8 +42,6 @@ */ public class ModifiableDefaultContent<F extends ModifiableContentFactory> extends DefaultContent<F> implements CommentableContent, LockableAmetysObject, ModifiableWorkflowAwareContent, AnnotableContent//, CopiableAmetysObject { - private boolean _lockAlreadyChecked; - /** * Creates a JCR-based Content. * @param node the JCR Node backing this Content. @@ -57,9 +53,14 @@ super(node, parentPath, factory); } - public void setType(String type) throws AmetysRepositoryException + public void setTypes(String[] types) throws AmetysRepositoryException { - ModifiableContentHelper.setType(this, type); + ModifiableContentHelper.setTypes(this, types); + } + + public void setMixinTypes(String[] mixins) throws AmetysRepositoryException + { + ModifiableContentHelper.setMixinTypes(this, mixins); } public void setLanguage(String language) throws AmetysRepositoryException @@ -379,19 +380,4 @@ annotations.removeMetadata(annotationName); } } - - private void _checkLock() throws RepositoryException - { - if (!_lockAlreadyChecked && getNode().isLocked()) - { - LockManager lockManager = getNode().getSession().getWorkspace().getLockManager(); - - Lock lock = lockManager.getLock(getNode().getPath()); - Node lockHolder = lock.getNode(); - - lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); - _lockAlreadyChecked = true; - } - } - } Index: main/plugin-cms/src/org/ametys/cms/repository/Content.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/Content.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/repository/Content.java (working copy) @@ -48,11 +48,11 @@ public interface Content extends MetadataAwareAmetysObject, DublinCoreAwareAmetysObject { /** - * Retrieves the type of this content. - * @return the type of this content. + * Retrieves the types of this content. + * @return the types of this content. * @throws AmetysRepositoryException if an error occurs. */ - public String getType() throws AmetysRepositoryException; + public String[] getTypes() throws AmetysRepositoryException; /** * Retrieves the mixin types of this content. Index: main/plugin-cms/src/org/ametys/cms/repository/ContentTypeOrMixinTypeExpression.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/ContentTypeOrMixinTypeExpression.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/repository/ContentTypeOrMixinTypeExpression.java (revision 0) @@ -0,0 +1,84 @@ +/* + * Copyright 2010 Anyware Services + * + * 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.ametys.cms.repository; + +import org.ametys.plugins.repository.AmetysRepositoryException; +import org.ametys.plugins.repository.RepositoryConstants; +import org.ametys.plugins.repository.query.expression.Expression; + +/** + * Constructs an {@link Expression} testing the content type. + */ +public class ContentTypeOrMixinTypeExpression implements Expression +{ + private Operator _operator; + private String[] _values; + + /** + * Creates the expression. + * @param operator the operator to make the comparison (only Operator.EQ and Operator.NE allowed) + * @param value the content type value + */ + public ContentTypeOrMixinTypeExpression(Operator operator, String... value) + { + if (Operator.EQ != operator && Operator.NE != operator) + { + throw new AmetysRepositoryException("Test operator '" + "' is unknown for test's expression."); + } + + _operator = operator; + _values = value; + } + + public String build() + { + StringBuilder xpath = new StringBuilder(); + + if (_values.length == 1) + { + xpath.append("("); + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_CONTENTTYPE) + .append(' ').append(_operator).append(" '").append(_values[0].replaceAll("'", "''")).append("'"); + + xpath.append(" or "); + + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_MIXINCONTENTTYPES) + .append(' ').append(_operator).append(" '").append(_values[0].replaceAll("'", "''")).append("'"); + xpath.append(")"); + return xpath.toString(); + } + + xpath.append(Operator.EQ.equals(_operator) ? '(' : "not("); + + for (int i = 0; i < _values.length; i++) + { + if (i > 0) + { + xpath.append(" or "); + } + + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_CONTENTTYPE) + .append(" = '").append(_values[i].replaceAll("'", "''")).append("'"); + xpath.append(" or "); + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_MIXINCONTENTTYPES) + .append(" = '").append(_values[i].replaceAll("'", "''")).append("'"); + } + + xpath.append(')'); + + return xpath.toString(); + } +} Index: main/plugin-cms/src/org/ametys/cms/repository/ContentTypeOrMixinExpression.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/ContentTypeOrMixinExpression.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/repository/ContentTypeOrMixinExpression.java (revision 0) @@ -0,0 +1,84 @@ +/* + * Copyright 2010 Anyware Services + * + * 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.ametys.cms.repository; + +import org.ametys.plugins.repository.AmetysRepositoryException; +import org.ametys.plugins.repository.RepositoryConstants; +import org.ametys.plugins.repository.query.expression.Expression; + +/** + * Constructs an {@link Expression} testing the content type. + */ +public class ContentTypeOrMixinExpression implements Expression +{ + private Operator _operator; + private String[] _values; + + /** + * Creates the expression. + * @param operator the operator to make the comparison (only Operator.EQ and Operator.NE allowed) + * @param value the content type value + */ + public ContentTypeOrMixinExpression(Operator operator, String... value) + { + if (Operator.EQ != operator && Operator.NE != operator) + { + throw new AmetysRepositoryException("Test operator '" + "' is unknown for test's expression."); + } + + _operator = operator; + _values = value; + } + + public String build() + { + StringBuilder xpath = new StringBuilder(); + + if (_values.length == 1) + { + xpath.append("("); + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_CONTENTTYPE) + .append(' ').append(_operator).append(" '").append(_values[0].replaceAll("'", "''")).append("'"); + + xpath.append(" or "); + + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_MIXINCONTENTTYPES) + .append(' ').append(_operator).append(" '").append(_values[0].replaceAll("'", "''")).append("'"); + xpath.append(")"); + return xpath.toString(); + } + + xpath.append(Operator.EQ.equals(_operator) ? '(' : "not("); + + for (int i = 0; i < _values.length; i++) + { + if (i > 0) + { + xpath.append(" or "); + } + + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_CONTENTTYPE) + .append(" = '").append(_values[i].replaceAll("'", "''")).append("'"); + xpath.append(" or "); + xpath.append('@').append(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL).append(':').append(DefaultContent.METADATA_MIXINCONTENTTYPES) + .append(" = '").append(_values[i].replaceAll("'", "''")).append("'"); + } + + xpath.append(')'); + + return xpath.toString(); + } +} Index: main/plugin-cms/src/org/ametys/cms/repository/DefaultContent.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/repository/DefaultContent.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/repository/DefaultContent.java (working copy) @@ -28,6 +28,8 @@ import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Value; +import javax.jcr.lock.Lock; +import javax.jcr.lock.LockManager; import org.ametys.plugins.explorer.resources.ResourceCollection; import org.ametys.plugins.repository.AmetysObject; @@ -88,6 +90,8 @@ /** Constant for the attachment node name. */ public static final String ATTACHMENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":attachments"; + private boolean _lockAlreadyChecked; + /** * Creates a JCR-based Content. * @param node the JCR Node backing this Content. @@ -100,11 +104,26 @@ } @Override - public String getType() throws AmetysRepositoryException + public String[] getTypes() throws AmetysRepositoryException { try { - return getNode().getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_CONTENTTYPE).getString(); + if (getNode().hasProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_CONTENTTYPE)) + { + Value[] values = getNode().getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_CONTENTTYPE).getValues(); + + String[] types = new String[values.length]; + for (int i = 0; i < values.length; i++) + { + Value value = values[i]; + types[i] = value.getString(); + } + return types; + } + else + { + return new String[0]; + } } catch (javax.jcr.RepositoryException ex) { @@ -291,6 +310,21 @@ return _getFactory()._getCopiableHelper().copy(this, parent, name); } + void _checkLock() throws RepositoryException + { + Node node = getNode(); + if (!_lockAlreadyChecked && getNode().isLocked()) + { + LockManager lockManager = node.getSession().getWorkspace().getLockManager(); + + Lock lock = lockManager.getLock(node.getPath()); + Node lockHolder = lock.getNode(); + + lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); + _lockAlreadyChecked = true; + } + } + // Dublin Core metadata. // @Override Index: main/plugin-cms/src/org/ametys/cms/workflow/copy/CreateContentByCopyFunction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/copy/CreateContentByCopyFunction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/copy/CreateContentByCopyFunction.java (working copy) @@ -73,7 +73,7 @@ transientVars.put(CreateContentFunction.CONTENT_NAME_KEY, contentTitle); transientVars.put(CreateContentFunction.CONTENT_TITLE_KEY, contentTitle); - transientVars.put(CreateContentFunction.CONTENT_TYPE_KEY, baseContent.getType()); + transientVars.put(CreateContentFunction.CONTENT_TYPES_KEY, baseContent.getTypes()); transientVars.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, baseContent.getLanguage()); // Super method call. Index: main/plugin-cms/src/org/ametys/cms/workflow/CreateContentFunction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/CreateContentFunction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/CreateContentFunction.java (working copy) @@ -59,6 +59,8 @@ public static final String CONTENT_TITLE_KEY = CreateContentFunction.class.getName() + "$contentTitle"; /** Constant for storing the content type to use into the transient variables map. */ public static final String CONTENT_TYPE_KEY = CreateContentFunction.class.getName() + "$contentType"; + /** Constant for storing the content type to use into the transient variables map. */ + public static final String CONTENT_TYPES_KEY = CreateContentFunction.class.getName() + "$contentTypes"; /** Constant for storing the content language to use into the transient variables map. */ public static final String CONTENT_LANGUAGE_KEY = CreateContentFunction.class.getName() + "$contentLanguage"; /** Constant for storing the content language to use into the transient variables map. */ @@ -88,7 +90,23 @@ { String desiredContentName = _getNonNullVar(transientVars, CONTENT_NAME_KEY, "Missing content name"); String contentTitle = _getNonNullVar(transientVars, CONTENT_TITLE_KEY, "Missing content title"); - String contentType = _getNonNullVar(transientVars, CONTENT_TYPE_KEY, "Missing content type"); + + String[] contentTypes = null; + String contentType = (String) transientVars.get(CONTENT_TYPE_KEY); + if (transientVars.get(CONTENT_TYPE_KEY) != null) + { + contentTypes = new String[] {contentType}; + } + else if (transientVars.get(CONTENT_TYPES_KEY) != null) + { + contentTypes = (String[]) transientVars.get(CONTENT_TYPES_KEY); + } + + if (contentTypes == null) + { + throw new WorkflowException("Missing content type"); + } + String contentLanguage = _getNonNullVar(transientVars, CONTENT_LANGUAGE_KEY, "Missing content language"); ModifiableTraversableAmetysObject contents = _getContentRoot(transientVars); @@ -96,7 +114,7 @@ ModifiableWorkflowAwareContent content = _createContent(desiredContentName, contents); content.setTitle(contentTitle); - content.setType(contentType); + content.setTypes(contentTypes); content.setLanguage(contentLanguage); // Set the workflow id Index: main/plugin-cms/src/org/ametys/cms/workflow/EditContentFunction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/EditContentFunction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/EditContentFunction.java (working copy) @@ -20,6 +20,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -36,6 +37,7 @@ import javax.jcr.ValueFactory; import org.apache.avalon.framework.activity.Initializable; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; @@ -44,6 +46,7 @@ import org.ametys.cms.contenttype.ConsistencyExtractor; import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.cms.contenttype.MetadataDefinitionReference; import org.ametys.cms.contenttype.MetadataSet; @@ -119,6 +122,8 @@ protected AmetysObjectResolver _resolver; /** Content type extension point. */ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; /** Upload manager. */ protected UploadManager _uploadManager; /** Observation manager available to subclasses. */ @@ -137,6 +142,7 @@ _observationManager = (ObservationManager) _manager.lookup(ObservationManager.ROLE); _jsonUtils = (JSONUtils) _manager.lookup(JSONUtils.ROLE); _workflow = (Workflow) _manager.lookup(Workflow.ROLE); + _contentTypesHelper = (ContentTypesHelper) _manager.lookup(ContentTypesHelper.ROLE); } @Override @@ -175,20 +181,11 @@ throw new WorkflowException("Unable to get the JSON parameters"); } - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - - if (contentType == null) - { - throw new WorkflowException(String.format("Unknown content type '%s' for content %s", - contentTypeId, content)); - } - Map<String, Object> rawValues = (Map<String, Object>) jsonParameters.get("values"); - MetadataSet metadataSet = getMetadataSet(jsonParameters, rawValues.keySet(), contentType, modifiableContent); + MetadataSet metadataSet = getMetadataSet(jsonParameters, rawValues.keySet(), modifiableContent); Map<String, Object> rawComments = (Map<String, Object>) jsonParameters.get("comments"); - _bindAndValidateContent(contentType, modifiableContent, errors, metadataSet, rawValues, rawComments, login); + _bindAndValidateContent(modifiableContent, errors, metadataSet, rawValues, rawComments, login); if (errors.hasErrors()) { @@ -237,22 +234,21 @@ * Get the metadata set for the content * @param jsParameters * @param parameterNames The name of the form parameters extracted from the request. - * @param contentType - * @param content + * @param content The content * @return The metadata set asked in the request or a builtin metadataset * @throws WorkflowException If an error occured while getting the metadata set */ - protected MetadataSet getMetadataSet(Map<String, Object> jsParameters, Set<String> parameterNames, ContentType contentType, Content content) throws WorkflowException + protected MetadataSet getMetadataSet(Map<String, Object> jsParameters, Set<String> parameterNames, Content content) throws WorkflowException { MetadataSet metadataSet; String metadataSetName = (String) jsParameters.get(METADATA_SET_PARAM); if (metadataSetName != null) { - metadataSet = contentType.getMetadataSetForEdition(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); if (metadataSet == null) { - throw new WorkflowException(String.format("No metadata set for name '%s' and content type '%s'", metadataSetName, contentType.getId())); + throw new WorkflowException(String.format("No metadata set for name '%s' and content type(s) '%s'", metadataSetName, StringUtils.join(content.getTypes(), ','))); } } else @@ -294,20 +290,23 @@ */ protected void _buildConsistencies(ModifiableContent content) { - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - ConsistencyExtractor consistencyExtractor = contentType.getConsistencyExtractor(); - if (consistencyExtractor != null) + Map<String, List<String>> elements = new HashMap<String, List<String>>(); + + for (String cTypeId : content.getTypes()) { - Map<String, List<String>> elements = consistencyExtractor.getConsistencyElement(content); - - content.setConsistencyElements(elements); + ContentType contentType = _contentTypeExtensionPoint.getExtension(cTypeId); + ConsistencyExtractor consistencyExtractor = contentType.getConsistencyExtractor(); + if (consistencyExtractor != null) + { + elements.putAll(consistencyExtractor.getConsistencyElement(content)); + } } + + content.setConsistencyElements(elements); } /** * Bind and validate a form. - * @param contentType * @param content the content. * @param allErrors the errors. * @param metadataSet @@ -316,27 +315,26 @@ * @param login the user login. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateContent(ContentType contentType, ModifiableContent content, AllErrors allErrors, MetadataSet metadataSet, Map<String, Object> rawValues, Map<String, Object> rawComments, String login) throws WorkflowException + protected void _bindAndValidateContent(ModifiableContent content, AllErrors allErrors, MetadataSet metadataSet, Map<String, Object> rawValues, Map<String, Object> rawComments, String login) throws WorkflowException { Form form = new Form(); // First bind and validate in the form object - _bindAndValidateMetadataSetElement(contentType, content, form, allErrors, metadataSet, rawValues, rawComments, null, ""); + _bindAndValidateMetadataSetElement(content, form, allErrors, metadataSet, rawValues, rawComments, null, ""); // Additional validation - _validateForm(contentType, content, form, allErrors); + _validateForm(content, form, allErrors); // Do not synchronize if there is at least one error if (!allErrors.hasErrors()) { // Synchronize form fields and content metadata - _synchronizeMetadataSetElement(contentType, content, content.getMetadataHolder(), form, allErrors, login, metadataSet, null, ""); + _synchronizeMetadataSetElement(content, content.getMetadataHolder(), form, allErrors, login, metadataSet, null, ""); } } /** * Bind and validate a metadata set element. - * @param contentType the content type. * @param content the content. * @param form the form. * @param allErrors the errors. @@ -347,14 +345,14 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateMetadataSetElement(ContentType contentType, Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, Object> rawComments, MetadataDefinition parentMetadataDefinition, String metadataPath) throws WorkflowException + protected void _bindAndValidateMetadataSetElement(Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, Object> rawComments, MetadataDefinition parentMetadataDefinition, String metadataPath) throws WorkflowException { for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) { if (subElement instanceof MetadataDefinitionReference) { MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement; - MetadataDefinition metadataDefinition = _getMetadataDefinition(contentType, parentMetadataDefinition, metadataDefRef.getMetadataName()); + MetadataDefinition metadataDefinition = _getMetadataDefinition(content, parentMetadataDefinition, metadataDefRef.getMetadataName()); if (metadataDefinition == null) { @@ -362,47 +360,50 @@ } String subMetadataPath = metadataPath + metadataDefinition.getName(); - if (contentType.canWrite(content, subMetadataPath)) + if (_contentTypesHelper.canWrite(content, subMetadataPath)) { - _bindAndValidateMetadata(contentType, content, form, allErrors, subElement, rawValues, rawComments, metadataDefinition, subMetadataPath); + _bindAndValidateMetadata(content, form, allErrors, subElement, rawValues, rawComments, metadataDefinition, subMetadataPath); _bindComments(rawComments, form, metadataDefinition, subMetadataPath); // Handle metadata comments } } else { - _bindAndValidateMetadataSetElement(contentType, content, form, allErrors, subElement, rawValues, rawComments, parentMetadataDefinition, metadataPath); + _bindAndValidateMetadataSetElement(content, form, allErrors, subElement, rawValues, rawComments, parentMetadataDefinition, metadataPath); } } } /** * Validates the form. - * @param contentType the content type. * @param content the content. * @param form the form. * @param allErrors the errors. */ - protected void _validateForm(ContentType contentType, Content content, Form form, AllErrors allErrors) + protected void _validateForm(Content content, Form form, AllErrors allErrors) { - Validator validator = contentType.getGlobalValidator(); - - if (validator != null) + Errors errors = new Errors(); + + for (String cTypeId : content.getTypes()) { - Errors errors = new Errors(); + ContentType contentType = _contentTypeExtensionPoint.getExtension(cTypeId); - validator.validate(form, errors); - - if (errors.hasErrors()) + Validator validator = contentType.getGlobalValidator(); + if (validator != null) { - // Global error - allErrors.addError("_global", errors); + validator.validate(form, errors); } } + + if (errors.hasErrors()) + { + // Global error + allErrors.addError("_global", errors); + } + } /** * Synchronize a metadata set element with a composite metadata. - * @param contentType the content type. * @param content the content. * @param metadata the composite metadata to synchronize. * @param form the form. @@ -413,25 +414,25 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _synchronizeMetadataSetElement(ContentType contentType, Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, MetadataDefinition parentMetadataDefinition, String metadataPath) throws WorkflowException + protected void _synchronizeMetadataSetElement(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, MetadataDefinition parentMetadataDefinition, String metadataPath) throws WorkflowException { for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) { if (subElement instanceof MetadataDefinitionReference) { MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement; - MetadataDefinition metadataDefinition = _getMetadataDefinition(contentType, parentMetadataDefinition, metadataDefRef.getMetadataName()); + MetadataDefinition metadataDefinition = _getMetadataDefinition(content, parentMetadataDefinition, metadataDefRef.getMetadataName()); String subMetadataPath = metadataPath + metadataDefinition.getName(); - if (contentType.canWrite(content, subMetadataPath)) + if (_contentTypesHelper.canWrite(content, subMetadataPath)) { - _synchronizeMetadata(contentType, content, metadata, form, allErrors, login, subElement, metadataDefinition, subMetadataPath); + _synchronizeMetadata(content, metadata, form, allErrors, login, subElement, metadataDefinition, subMetadataPath); } } else { - _synchronizeMetadataSetElement(contentType, content, metadata, form, allErrors, login, subElement, parentMetadataDefinition, metadataPath); + _synchronizeMetadataSetElement(content, metadata, form, allErrors, login, subElement, parentMetadataDefinition, metadataPath); } } } @@ -460,16 +461,16 @@ /** * Retrieves a sub metadata definition from a content type or * a parent metadata definition. - * @param contentType the content type. + * @param content the content. * @param parentMetadataDefinition the parent metadata definition. * @param metadataName the metadata name. * @return the metadata definition found or <code>null</code> otherwise. */ - protected MetadataDefinition _getMetadataDefinition(ContentType contentType, MetadataDefinition parentMetadataDefinition, String metadataName) + protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition parentMetadataDefinition, String metadataName) { if (parentMetadataDefinition == null) { - return contentType.getMetadataDefinition(metadataName); + return _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); } else { @@ -479,7 +480,6 @@ /** * Bind and validate a form. - * @param contentType the content type. * @param content the content. * @param form the form. * @param allErrors the errors. @@ -490,12 +490,12 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateMetadata(ContentType contentType, Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, Object> rawComments, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException + protected void _bindAndValidateMetadata(Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, Object> rawComments, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException { String metadataName = metadataDefinition.getName(); MetadataType type = metadataDefinition.getType(); - if (!contentType.canWrite(content, metadataPath)) + if (!_contentTypesHelper.canWrite(content, metadataPath)) { throw new WorkflowException("Current user has no right to edit metadata " + metadataName); } @@ -506,46 +506,46 @@ { case STRING: case USER: - _bindAndValidateStringMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateStringMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case DATE: - _bindAndValidateDateMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateDateMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case DATETIME: - _bindAndValidateDateTimeMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateDateTimeMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case LONG: - _bindAndValidateLongMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateLongMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case GEOCODE: - _bindAndValidateGeocodeMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateGeocodeMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case DOUBLE: - _bindAndValidateDoubleMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateDoubleMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case BOOLEAN: - _bindAndValidateBooleanMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateBooleanMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case BINARY: - _bindAndValidateBinaryMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateBinaryMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case FILE: - _bindAndValidateFileMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateFileMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case RICH_TEXT: - _bindAndValidateRichText(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateRichText(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case COMPOSITE: - _bindAndValidateCompositeMetadata(allErrors, form, contentType, content, metadataName, metadataSetElement, metadataDefinition, metadataPath, metadataValues, rawValues, rawComments); + _bindAndValidateCompositeMetadata(allErrors, form, content, metadataName, metadataSetElement, metadataDefinition, metadataPath, metadataValues, rawValues, rawComments); break; case REFERENCE: - _bindAndValidateReferenceMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateReferenceMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case CONTENT: - _bindAndValidateContentReferenceMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues); + _bindAndValidateContentReferenceMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues); break; case SUB_CONTENT: - _bindAndValidateSubContentMetadata(allErrors, form, contentType, metadataDefinition, metadataPath, metadataValues, rawValues); + _bindAndValidateSubContentMetadata(allErrors, form, content, metadataDefinition, metadataPath, metadataValues, rawValues); break; default: throw new WorkflowException("Unsupported type: " + type); @@ -595,7 +595,7 @@ } else { - metadataValues.add((String) rawValue); + metadataValues.add(String.valueOf(rawValue)); } } @@ -639,7 +639,6 @@ * @param allErrors for storing validation errors. * @param form the form. * @param metadataDefinition the metadata definition. - * @param contentType the content type * @param content the current content * @param metadataName the metadata name * @param metadataSetElement @@ -650,23 +649,22 @@ * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateCompositeMetadata(AllErrors allErrors, Form form, ContentType contentType, Content content, String metadataName, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues, Map<String, Object> rawValues, Map<String, Object> rawComments) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateCompositeMetadata(AllErrors allErrors, Form form, Content content, String metadataName, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues, Map<String, Object> rawValues, Map<String, Object> rawComments) throws AmetysRepositoryException, WorkflowException { if (metadataDefinition instanceof RepeaterDefinition) { - _bindAndValidateRepeater(contentType, content, form, allErrors, metadataSetElement, rawValues, rawComments, (RepeaterDefinition) metadataDefinition, metadataPath); + _bindAndValidateRepeater(content, form, allErrors, metadataSetElement, rawValues, rawComments, (RepeaterDefinition) metadataDefinition, metadataPath); } else { Form compositeForm = new Form(); - _bindAndValidateMetadataSetElement(contentType, content, compositeForm, allErrors, metadataSetElement, rawValues, rawComments, metadataDefinition, metadataPath + "/"); + _bindAndValidateMetadataSetElement(content, compositeForm, allErrors, metadataSetElement, rawValues, rawComments, metadataDefinition, metadataPath + "/"); form.setCompositeField(metadataName, compositeForm); } } /** * Bind and validate a repeater. - * @param contentType the content type. * @param content the content. * @param form the form. * @param allErrors the errors. @@ -677,7 +675,7 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateRepeater(ContentType contentType, Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, Object> rawComments, RepeaterDefinition repeaterDefinition, String metadataPath) throws WorkflowException + protected void _bindAndValidateRepeater(Content content, Form form, AllErrors allErrors, AbstractMetadataSetElement metadataSetElement, Map<String, Object> rawValues, Map<String, Object> rawComments, RepeaterDefinition repeaterDefinition, String metadataPath) throws WorkflowException { String metadataName = repeaterDefinition.getName(); int repeaterSizeValue; @@ -727,7 +725,7 @@ } // Bind and validate each entry - _bindAndValidateMetadataSetElement(contentType, content, repeaterEntry, allErrors, metadataSetElement, rawValues, rawComments, repeaterDefinition, metadataPath + "/" + i + "/"); + _bindAndValidateMetadataSetElement(content, repeaterEntry, allErrors, metadataSetElement, rawValues, rawComments, repeaterDefinition, metadataPath + "/" + i + "/"); repeaterField.addEntry(repeaterEntry); } @@ -763,17 +761,17 @@ * @param allErrors for storing validation errors. * @param form the form. * @param metadataDefinition the metadata definition. - * @param cType the content type + * @param content the content * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateStringMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateStringMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, metadataValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, metadataValues)) { if (metadataDefinition.isMultiple()) { @@ -793,14 +791,14 @@ * Bind and validate a date metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateDateMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateDateMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); List<Date> dateValues = new ArrayList<Date>(); @@ -824,7 +822,7 @@ Date[] dateArray = dateValues.toArray(new Date[dateValues.size()]); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, dateArray)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, dateArray)) { if (metadataDefinition.isMultiple()) { @@ -844,14 +842,14 @@ * Bind and validate a date time metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateDateTimeMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateDateTimeMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); List<Date> dateValues = new ArrayList<Date>(); @@ -875,7 +873,7 @@ Date[] dateArray = dateValues.toArray(new Date[dateValues.size()]); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, dateArray)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, dateArray)) { if (metadataDefinition.isMultiple()) { @@ -895,14 +893,14 @@ * Bind and validate a long metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateLongMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateLongMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); long[] longValues = new long[metadataValues.length]; @@ -924,7 +922,7 @@ } } - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, longValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, longValues)) { if (metadataDefinition.isMultiple()) { @@ -944,14 +942,14 @@ * Bind and validate a geocode metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateGeocodeMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateGeocodeMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); Map<String, Object> geocodeValues = Collections.emptyMap(); @@ -976,14 +974,14 @@ * Bind and validate a double metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateDoubleMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateDoubleMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); double[] doubleValues = new double[metadataValues.length]; @@ -1005,7 +1003,7 @@ } } - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, doubleValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, doubleValues)) { if (metadataDefinition.isMultiple()) { @@ -1025,14 +1023,14 @@ * Bind and validate a boolean metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateBooleanMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateBooleanMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); boolean[] booleanValues = new boolean[metadataValues.length]; @@ -1051,7 +1049,7 @@ } } - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, booleanValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, booleanValues)) { if (metadataDefinition.isMultiple()) { @@ -1071,18 +1069,18 @@ * Bind and validate a binary metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateBinaryMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateBinaryMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, metadataValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, metadataValues)) { if (metadataValues.length > 0 && !metadataValues[0].equals("")) { @@ -1095,14 +1093,14 @@ * Bind and validate a file metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateFileMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateFileMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { Map<String, Object> fileValues = Collections.emptyMap(); if (metadataValues.length > 0) @@ -1116,12 +1114,12 @@ if (EXPLORER_FILE.equals(fileType)) { - _bindAndValidateStringMetadata(allErrors, form, cType, metadataDefinition, metadataPath, values); + _bindAndValidateStringMetadata(allErrors, form, content, metadataDefinition, metadataPath, values); form.setField(metadataDefinition.getName() + "#type", new String[]{EXPLORER_FILE}); } else if (METADATA_FILE.equals(fileType)) { - _bindAndValidateBinaryMetadata(allErrors, form, cType, metadataDefinition, metadataPath, values); + _bindAndValidateBinaryMetadata(allErrors, form, content, metadataDefinition, metadataPath, values); form.setField(metadataDefinition.getName() + "#type", new String[]{METADATA_FILE}); } @@ -1131,18 +1129,18 @@ * Bind and validate a rich text metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateRichText(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateRichText(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, metadataValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, metadataValues)) { if (metadataValues.length > 0 && !metadataValues[0].equals("")) { @@ -1155,18 +1153,18 @@ * Bind and validate a reference metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateReferenceMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateReferenceMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, metadataValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, metadataValues)) { if (metadataDefinition.isMultiple()) { @@ -1186,14 +1184,14 @@ * Bind and validate a content reference metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type. + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. * @throws AmetysRepositoryException if an error occurs. * @throws WorkflowException if an error occurs. */ - protected void _bindAndValidateContentReferenceMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateContentReferenceMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); List<Content> contentList = new ArrayList<Content>(metadataValues.length); @@ -1217,7 +1215,7 @@ { Content contentMeta = (Content) ao; - if (cTypeId != null && !validContentTypes.contains(contentMeta.getType())) + if (cTypeId != null && CollectionUtils.intersection(validContentTypes, Arrays.asList(contentMeta.getTypes())).isEmpty()) { Errors parseErrors = new Errors(); parseErrors.addError(new I18nizableText("The content " + contentMeta + " must be typed " + cTypeId + " or one of its sub-types.")); @@ -1246,7 +1244,7 @@ Content[] contentValues = contentList.toArray(new Content[contentList.size()]); - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, contentValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, contentValues)) { if (metadataDefinition.isMultiple() || contentValues.length > 0) { @@ -1259,7 +1257,7 @@ * Bind and validate a content metadata. * @param allErrors for storing validation errors. * @param form the form. - * @param cType the content type. + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path from the content. * @param metadataValues the values. @@ -1268,7 +1266,7 @@ * @throws WorkflowException if an error occurs. */ @SuppressWarnings("unchecked") - protected void _bindAndValidateSubContentMetadata(AllErrors allErrors, Form form, ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues, Map<String, Object> rawValues) throws AmetysRepositoryException, WorkflowException + protected void _bindAndValidateSubContentMetadata(AllErrors allErrors, Form form, Content content, MetadataDefinition metadataDefinition, String metadataPath, String[] metadataValues, Map<String, Object> rawValues) throws AmetysRepositoryException, WorkflowException { String metadataName = metadataDefinition.getName(); String cTypeId = metadataDefinition.getContentType(); @@ -1286,7 +1284,7 @@ contentValues.add(_jsonUtils.convertJsonToMap(metadataValues[i])); } - if (_validateMetadata(cType, metadataDefinition, metadataPath, allErrors, contentValues)) + if (_validateMetadata(content, metadataDefinition, metadataPath, allErrors, contentValues)) { if (metadataDefinition.isMultiple() || !contentValues.isEmpty()) { @@ -1331,7 +1329,7 @@ /** * Validate a metadata value. - * @param cType the content type + * @param content the content * @param metadataDefinition the metadata definition. * @param metadataPath the metadata path. * @param allErrors the errors. @@ -1340,7 +1338,7 @@ * <code>false</code> otherwise. * @throws WorkflowException if an error occurs. */ - protected boolean _validateMetadata(ContentType cType, MetadataDefinition metadataDefinition, String metadataPath, AllErrors allErrors, Object value) throws WorkflowException + protected boolean _validateMetadata(Content content, MetadataDefinition metadataDefinition, String metadataPath, AllErrors allErrors, Object value) throws WorkflowException { Validator validator = metadataDefinition.getValidator(); @@ -1376,7 +1374,6 @@ /** * Synchronize a metadata with a composite metadata. - * @param contentType the content type. * @param content the content. * @param metadata the composite metadata to synchronize. * @param form the form. @@ -1387,7 +1384,7 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _synchronizeMetadata(ContentType contentType, Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException + protected void _synchronizeMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException { MetadataType type = metadataDefinition.getType(); @@ -1433,7 +1430,7 @@ } else if (type.equals(MetadataType.SUB_CONTENT)) { - _synchronizeSubContentMetadata(contentType, content, metadata, form, allErrors, login, metadataDefinition, metadataPath); + _synchronizeSubContentMetadata(content, metadata, form, allErrors, login, metadataDefinition, metadataPath); } else if (type.equals(MetadataType.GEOCODE)) { @@ -1441,7 +1438,7 @@ } else if (type.equals(MetadataType.COMPOSITE)) { - _synchronizeCompositeMetadata(contentType, content, metadata, form, allErrors, login, metadataSetElement, metadataDefinition, metadataPath); + _synchronizeCompositeMetadata(content, metadata, form, allErrors, login, metadataSetElement, metadataDefinition, metadataPath); } else { @@ -1499,7 +1496,6 @@ /** * Synchronize a composite-typed metadata with a a composite metadata. - * @param contentType the content type. * @param content the content. * @param metadata the composite metadata to synchronize. * @param form the form. @@ -1510,13 +1506,13 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _synchronizeCompositeMetadata(ContentType contentType, Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException + protected void _synchronizeCompositeMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException { String metadataName = metadataDefinition.getName(); if (metadataDefinition instanceof RepeaterDefinition) { - _synchronizeRepeater(contentType, content, metadata, form, allErrors, login, metadataSetElement, (RepeaterDefinition) metadataDefinition, metadataPath); + _synchronizeRepeater(content, metadata, form, allErrors, login, metadataSetElement, (RepeaterDefinition) metadataDefinition, metadataPath); } else { @@ -1525,7 +1521,7 @@ if (compositeForm != null) { ModifiableCompositeMetadata subMetadata = metadata.getCompositeMetadata(metadataName, true); - _synchronizeMetadataSetElement(contentType, content, subMetadata, compositeForm, allErrors, login, metadataSetElement, metadataDefinition, metadataPath + "/"); + _synchronizeMetadataSetElement(content, subMetadata, compositeForm, allErrors, login, metadataSetElement, metadataDefinition, metadataPath + "/"); } else { @@ -1536,7 +1532,6 @@ /** * Synchronize a repeater with a composite metadata. - * @param contentType the content type. * @param content the content. * @param metadata the composite metadata to synchronize. * @param form the form. @@ -1547,7 +1542,7 @@ * @param metadataPath the metadata path. * @throws WorkflowException if an error occurs. */ - protected void _synchronizeRepeater(ContentType contentType, Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath) throws WorkflowException + protected void _synchronizeRepeater(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, AbstractMetadataSetElement metadataSetElement, RepeaterDefinition repeaterDefinition, String metadataPath) throws WorkflowException { String metadataName = repeaterDefinition.getName(); RepeaterField repeaterField = form.getRepeaterField(metadataName); @@ -1625,7 +1620,7 @@ { // Synchronization ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(entryName); - _synchronizeMetadataSetElement(contentType, content, entryMetadata, repeaterEntry, allErrors, login, metadataSetElement, + _synchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, login, metadataSetElement, repeaterDefinition, metadataPath + "/" + entryName + "/"); } } @@ -1641,7 +1636,7 @@ { // New entry ModifiableCompositeMetadata entryMetadata = repeaterMetadata.getCompositeMetadata(String.valueOf(i), true); - _synchronizeMetadataSetElement(contentType, content, entryMetadata, repeaterEntry, allErrors, login, metadataSetElement, + _synchronizeMetadataSetElement(content, entryMetadata, repeaterEntry, allErrors, login, metadataSetElement, repeaterDefinition, metadataPath + "/" + i + "/"); } } @@ -2086,7 +2081,6 @@ /** * Synchronize a sub-content metadata from a field. * @param content The Content. - * @param contentType The content type. * @param metadata the metadata. * @param form the form containing the field. * @param allErrors the errors. @@ -2095,7 +2089,7 @@ * @param metadataPath the current metadata path. * @throws WorkflowException if an error occurs. */ - protected void _synchronizeSubContentMetadata(ContentType contentType, Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException + protected void _synchronizeSubContentMetadata(Content content, ModifiableCompositeMetadata metadata, Form form, AllErrors allErrors, String login, MetadataDefinition metadataDefinition, String metadataPath) throws WorkflowException { String metadataName = metadataDefinition.getName(); String[] values = form.getStringArray(metadataName); Index: main/plugin-cms/src/org/ametys/cms/workflow/WorkflowTasksComponent.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/WorkflowTasksComponent.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/WorkflowTasksComponent.java (working copy) @@ -46,8 +46,8 @@ import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; -import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.repository.Content; import org.ametys.cms.repository.DefaultContent; import org.ametys.cms.repository.WorkflowAwareContent; @@ -58,9 +58,9 @@ import org.ametys.plugins.repository.query.SortCriteria; import org.ametys.plugins.repository.query.expression.AndExpression; import org.ametys.plugins.repository.query.expression.Expression; +import org.ametys.plugins.repository.query.expression.Expression.Operator; import org.ametys.plugins.repository.query.expression.OrExpression; import org.ametys.plugins.repository.query.expression.StringExpression; -import org.ametys.plugins.repository.query.expression.Expression.Operator; import org.ametys.plugins.workflow.Workflow; import org.ametys.runtime.right.RightsManager; import org.ametys.runtime.right.RightsManager.RightResult; @@ -109,6 +109,9 @@ /** The content type extension point. */ protected ContentTypeExtensionPoint _cTypeEP; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; + /** The {@link Workflow} */ protected Workflow _workflow; @@ -117,6 +120,7 @@ /** Allow user querying ? */ protected boolean _allowUserQuery; + public void service(ServiceManager manager) throws ServiceException { @@ -124,6 +128,7 @@ _rightsManager = (RightsManager) manager.lookup(RightsManager.ROLE); _usersManager = (UsersManager) manager.lookup(UsersManager.ROLE); _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); _workflow = (Workflow) manager.lookup(Workflow.ROLE); } @@ -294,15 +299,13 @@ protected void _saxContent(ContentHandler ch, Content content) throws SAXException, RepositoryException { User authorUser = _usersManager.getUser(content.getLastContributor()); - String cType = content.getType(); - ContentType contentType = _cTypeEP.getExtension(cType); AttributesImpl attrs = new AttributesImpl(); attrs.addCDATAAttribute("id", content.getId()); attrs.addCDATAAttribute("name", content.getName()); attrs.addCDATAAttribute("title", content.getTitle()); attrs.addCDATAAttribute("lastModified", _DATE_FORMAT.format(content.getLastModified())); - attrs.addCDATAAttribute("contentType", cType); + attrs.addCDATAAttribute("contentType", StringUtils.join(content.getTypes(), ',')); if (authorUser != null) { @@ -311,9 +314,9 @@ _saxAdditionalAttributes (content, attrs); - attrs.addCDATAAttribute("smallIcon", contentType != null ? contentType.getSmallIcon() : "/plugins/cms/resources/img/contenttype/unknown-small.png"); - attrs.addCDATAAttribute("mediumIcon", contentType != null ? contentType.getMediumIcon() : "/plugins/cms/resources/img/contenttype/unknown-medium.png"); - attrs.addCDATAAttribute("largeIcon", contentType != null ? contentType.getLargeIcon() : "/plugins/cms/resources/img/contenttype/unknown-large.png"); + attrs.addCDATAAttribute("smallIcon", _contentTypesHelper.getSmallIcon(content)); + attrs.addCDATAAttribute("mediumIcon", _contentTypesHelper.getMediumIcon(content)); + attrs.addCDATAAttribute("largeIcon", _contentTypesHelper.getLargeIcon(content)); XMLUtils.startElement(ch, "content", attrs); if (content instanceof WorkflowAwareContent) Index: main/plugin-cms/src/org/ametys/cms/workflow/ValidateMetadataCondition.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/ValidateMetadataCondition.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/ValidateMetadataCondition.java (working copy) @@ -57,19 +57,24 @@ { Content content = getContent(transientVars); - ContentType contentType = _cTypeEP.getExtension(content.getType()); - - Set<String> metadataNames = contentType.getMetadataNames(); - for (String metadataName : metadataNames) + String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + for (String cTypeId : allContentTypes) { - // FIXME API Check modifiable - MetadataDefinition metadataDef = contentType.getMetadataDefinition(metadataName); - if (!validateMetadata((ModifiableCompositeMetadata) content.getMetadataHolder(), metadataDef, metadataName, new Errors())) + ContentType contentType = _cTypeEP.getExtension(cTypeId); + + Set<String> metadataNames = contentType.getMetadataNames(); + for (String metadataName : metadataNames) { - return false; + // FIXME API Check modifiable + MetadataDefinition metadataDef = contentType.getMetadataDefinition(metadataName); + if (!validateMetadata((ModifiableCompositeMetadata) content.getMetadataHolder(), metadataDef, metadataName, new Errors())) + { + return false; + } } } + return true; } Index: main/plugin-cms/src/org/ametys/cms/workflow/CreateContentRightCondition.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/CreateContentRightCondition.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/CreateContentRightCondition.java (working copy) @@ -43,7 +43,7 @@ @Override public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException { - String right = getContentType(transientVars).getRight(); + String right = getRight(transientVars); args.put(__RIGHT_KEY, right); return right != null ? super.passesCondition(transientVars, args, ps) : true; @@ -52,17 +52,25 @@ @Override protected boolean _checkRights(Map transientVars, Map args, String login, String rightNeeded) throws WorkflowException { - return _rightsManager.hasRight(login, rightNeeded, "/contributor") == RightResult.RIGHT_OK - || _rightsManager.hasRight(login, rightNeeded, "/contents") == RightResult.RIGHT_OK; + String[] rights = rightNeeded.split("|"); + + for (String right : rights) + { + if (_rightsManager.hasRight(login, right, "/contributor") == RightResult.RIGHT_OK || _rightsManager.hasRight(login, right, "/contents") == RightResult.RIGHT_OK) + { + return true; + } + } + return false; } /** - * Retrieve the content type of the content to create. - * @param transientVars the parameters from the call. - * @return the content. - * @throws WorkflowException if the content is not found. + * Get the needed right for creation + * @param transientVars variables that will not be persisted. + * @return The required right + * @throws WorkflowException */ - protected ContentType getContentType(Map transientVars) throws WorkflowException + protected String getRight (Map transientVars) throws WorkflowException { String contentTypeId = (String) transientVars.get(CreateContentFunction.CONTENT_TYPE_KEY); ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); @@ -72,8 +80,9 @@ throw new WorkflowException("Unable to retrieve content the content type"); } - return contentType; + return contentType.getRight(); } + @Override public void dispose() Index: main/plugin-cms/src/org/ametys/cms/workflow/CreateContentByCopyRightCondition.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/workflow/CreateContentByCopyRightCondition.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/workflow/CreateContentByCopyRightCondition.java (working copy) @@ -15,8 +15,12 @@ */ package org.ametys.cms.workflow; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import org.apache.commons.lang.StringUtils; + import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.repository.Content; import org.ametys.cms.workflow.copy.CreateContentByCopyFunction; @@ -40,7 +44,7 @@ } @Override - protected ContentType getContentType(Map transientVars) throws WorkflowException + protected String getRight(Map transientVars) throws WorkflowException { Content baseContent = (Content) transientVars.get(CreateContentByCopyFunction.BASE_CONTENT_KEY); if (baseContent == null) @@ -48,15 +52,21 @@ throw new WorkflowException("Unable to retrieve the base content for the duplication."); } - String contentTypeId = baseContent.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); + List<String> rights = new ArrayList<String>(); - if (contentType == null) + String[] cTypes = baseContent.getTypes(); + + for (String id : cTypes) { - throw new WorkflowException("Unable to retrieve content the content type"); + ContentType contentType = _contentTypeExtensionPoint.getExtension(id); + String right = contentType.getRight(); + if (StringUtils.isNotEmpty(right)) + { + rights.add(right); + } } - return contentType; + return rights.size() > 0 ? StringUtils.join(rights.toArray(new String[rights.size()]), '&') : null; } @Override Index: main/plugin-cms/src/org/ametys/cms/search/SearchModel.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/SearchModel.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/search/SearchModel.java (working copy) @@ -81,7 +81,7 @@ public abstract class SearchModel extends AbstractLogEnabled implements Serviceable, Contextualizable, Configurable, Disposable { /** The valid system properties */ - public static final String[] SYSTEM_PROPERTY_NAMES = {"creator", "contributor", "lastModified", "comments", "workflowStep", "contentLanguage", "contentType", "fulltext"}; + public static final String[] SYSTEM_PROPERTY_NAMES = {"creator", "contributor", "lastModified", "comments", "workflowStep", "contentLanguage", "contentType", "mixin", "contentTypeOrMixin", "fulltext"}; /** Prefix for id of metadata search criteria */ public static final String SEARCH_CRITERIA_METADATA_PREFIX = "metadata-"; /** Prefix for id of system property search criteria */ @@ -182,6 +182,10 @@ customEnumerator.addChild(cTypeConf); } + DefaultConfiguration excludeConf = new DefaultConfiguration("exclude-mixin"); + excludeConf.setValue(true); + customEnumerator.addChild(excludeConf); + enumConf.addChild(customEnumerator); conf.addChild(enumConf); @@ -194,6 +198,56 @@ try { + DefaultConfiguration conf = new DefaultConfiguration("criteria"); + + DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); + + DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); + customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); + + Set<String> contentTypes = getContentTypes(); + if (contentTypes.size() > 0) + { + DefaultConfiguration cTypeConf = new DefaultConfiguration("strict-content-types"); + cTypeConf.setValue(StringUtils.join(contentTypes, ",")); + customEnumerator.addChild(cTypeConf); + } + + enumConf.addChild(customEnumerator); + conf.addChild(enumConf); + + _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin", ContentTypeEnumerator.class, conf); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to instantiate enumerator for class: " + ContentTypeEnumerator.class.getName(), e); + } + + try + { + DefaultConfiguration conf = new DefaultConfiguration("criteria"); + + DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); + + DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); + customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); + + DefaultConfiguration includeConf = new DefaultConfiguration("include-mixin-only"); + includeConf.setValue(true); + customEnumerator.addChild(includeConf); + + enumConf.addChild(customEnumerator); + conf.addChild(enumConf); + + _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".Mixin", ContentTypeEnumerator.class, conf); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to instantiate enumerator for class: " + ContentTypeEnumerator.class.getName(), e); + } + + try + { _enumeratorManager.addComponent("cms", null, LanguageEnumerator.class.getName(), LanguageEnumerator.class, new DefaultConfiguration("enumeration")); } catch (Exception e) @@ -307,6 +361,12 @@ expressions.add(cTypeExpr); } + Expression mixinExpr = createMixinTypeExpressions(values); + if (mixinExpr != null) + { + expressions.add(mixinExpr); + } + expressions.addAll(getCriteriaExpressions(values)); if (excludeSubContents) @@ -546,7 +606,35 @@ getLogger().error("Unable to lookup enumerator role: '" + ContentTypeEnumerator.class.getName() + "'", e); } } - if (propertyName.equals("fulltext")) + else if (propertyName.equals("mixin")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_MIXIN")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_MIXIN")); + systemSC.setType(MetadataType.STRING); + try + { + systemSC.setEnumerator(_enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".Mixin")); + } + catch (Exception e) + { + getLogger().error("Unable to lookup enumerator role: '" + ContentTypeEnumerator.class.getName() + "'", e); + } + } + else if (propertyName.equals("contentTypeOrMixin")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CONTENT_TYPE")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CONTENT_TYPE")); + systemSC.setType(MetadataType.STRING); + try + { + systemSC.setEnumerator(_enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin")); + } + catch (Exception e) + { + getLogger().error("Unable to lookup enumerator role: '" + ContentTypeEnumerator.class.getName() + "'", e); + } + } + else if (propertyName.equals("fulltext")) { systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_FULLTEXT")); systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_FULLTEXT")); @@ -624,6 +712,20 @@ column.setWidth(150); column.setMapping("content-type"); } + else if (propertyName.equals("mixin")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_MIXIN")); + column.setType(MetadataType.STRING); + column.setWidth(150); + column.setMapping("mixin"); + } + else if (propertyName.equals("contentTypeOrMixin")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CONTENT_TYPE")); + column.setType(MetadataType.STRING); + column.setWidth(150); + column.setMapping("content-type-mixin"); + } return column; } @@ -659,13 +761,50 @@ Object cTypeParam = values.get(SEARCH_CRITERIA_SYSTEM_PREFIX + "contentType-eq"); - if (cTypeParam instanceof String && StringUtils.isNotEmpty((String) cTypeParam)) + if (cTypeParam == null) + { + return createContentTypeOrMixinExpressions(values); + } + else + { + if (cTypeParam instanceof String && StringUtils.isNotEmpty((String) cTypeParam)) + { + return _cTypeEP.createHierarchicalCTExpression((String) cTypeParam); + } + else if (cTypeParam != null && cTypeParam instanceof List<?>) + { + return _cTypeEP.createHierarchicalCTExpression(((List<String>) cTypeParam).toArray(new String[cTypes.size()])); + } + else if (cTypes != null && cTypes.size() > 0) + { + return _cTypeEP.createHierarchicalCTExpression(cTypes.toArray(new String[cTypes.size()])); + } + else + { + return null; + } + } + } + + /** + * Create expression on content type + * @param values The submitted values + * @return The content type expression or null if no + */ + @SuppressWarnings("unchecked") + protected Expression createContentTypeOrMixinExpressions(Map<String, Object> values) + { + Set<String> cTypes = getContentTypes(); + + Object cTypeOrMixinParam = values.get(SEARCH_CRITERIA_SYSTEM_PREFIX + "contentTypeOrMixin-eq"); + + if (cTypeOrMixinParam instanceof String && StringUtils.isNotEmpty((String) cTypeOrMixinParam)) { - return _cTypeEP.createHierarchicalCTExpression((String) cTypeParam); + return _cTypeEP.createHierarchicalContentTypeOrMixinExpression((String) cTypeOrMixinParam); } - else if (cTypeParam != null && cTypeParam instanceof List<?>) + else if (cTypeOrMixinParam != null && cTypeOrMixinParam instanceof List<?>) { - return _cTypeEP.createHierarchicalCTExpression(((List<String>) cTypeParam).toArray(new String[cTypes.size()])); + return _cTypeEP.createHierarchicalContentTypeOrMixinExpression(((List<String>) cTypeOrMixinParam).toArray(new String[cTypes.size()])); } else if (cTypes != null && cTypes.size() > 0) { @@ -678,6 +817,32 @@ } /** + * Create expression on mixin + * @param values The submitted values + * @return The content type expression or null if no + */ + @SuppressWarnings("unchecked") + protected Expression createMixinTypeExpressions(Map<String, Object> values) + { + Set<String> cTypes = getContentTypes(); + + Object mixinParam = values.get(SEARCH_CRITERIA_SYSTEM_PREFIX + "mixin-eq"); + + if (mixinParam instanceof String && StringUtils.isNotEmpty((String) mixinParam)) + { + return _cTypeEP.createHierarchicalMixinExpression((String) mixinParam); + } + else if (mixinParam != null && mixinParam instanceof List<?>) + { + return _cTypeEP.createHierarchicalCTExpression(((List<String>) mixinParam).toArray(new String[cTypes.size()])); + } + else + { + return null; + } + } + + /** * Returns the expression to execute * @param values The submitted values * @return The expression to execute Index: main/plugin-cms/src/org/ametys/cms/search/generators/SearchGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/generators/SearchGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/search/generators/SearchGenerator.java (working copy) @@ -16,6 +16,7 @@ package org.ametys.cms.search.generators; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -37,6 +38,7 @@ import org.ametys.cms.contenttype.AbstractMetadataSetElement; import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataDefinitionReference; import org.ametys.cms.contenttype.MetadataManager; import org.ametys.cms.contenttype.MetadataSet; @@ -54,6 +56,7 @@ import org.ametys.plugins.workflow.Workflow; import org.ametys.runtime.user.User; import org.ametys.runtime.user.UsersManager; +import org.ametys.runtime.util.I18nUtils; import org.ametys.runtime.util.I18nizableText; import org.ametys.runtime.util.parameter.ParameterHelper; @@ -84,11 +87,14 @@ protected Workflow _workflow; /** The user manager */ protected UsersManager _usersManager; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; + /** I18N utils */ + protected I18nUtils _i18nUtils; /** The cache for users' name */ protected Map<String, String> _userCache; - @Override public void service(ServiceManager smanager) throws ServiceException { @@ -100,6 +106,8 @@ _metadataManager = (MetadataManager) manager.lookup(MetadataManager.ROLE); _workflow = (Workflow) manager.lookup(Workflow.ROLE); _usersManager = (UsersManager) manager.lookup(UsersManager.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); + _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); } @Override @@ -189,11 +197,9 @@ AttributesImpl attrs = new AttributesImpl(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); - - attrs.addCDATAAttribute("smallIcon", contentType != null ? contentType.getSmallIcon() : "/plugins/cms/resources/img/contenttype/unknown-small.png"); - attrs.addCDATAAttribute("mediumIcon", contentType != null ? contentType.getMediumIcon() : "/plugins/cms/resources/img/contenttype/unknown-medium.png"); - attrs.addCDATAAttribute("largeIcon", contentType != null ? contentType.getLargeIcon() : "/plugins/cms/resources/img/contenttype/unknown-large.png"); + attrs.addCDATAAttribute("smallIcon", _contentTypesHelper.getSmallIcon(content)); + attrs.addCDATAAttribute("mediumIcon", _contentTypesHelper.getMediumIcon(content)); + attrs.addCDATAAttribute("largeIcon", _contentTypesHelper.getLargeIcon(content)); attrs.addCDATAAttribute("id", content.getId()); attrs.addCDATAAttribute("name", content.getName()); attrs.addCDATAAttribute("title", content.getTitle()); @@ -202,11 +208,11 @@ XMLUtils.startElement(contentHandler, "content", attrs); // System properties - saxSystemProperties(columns, content, contentType); + saxSystemProperties(columns, content); // Metadata XMLUtils.startElement(contentHandler, "metadata"); - MetadataSet metadataSet = getMetadataToSAX(model, model.getResultColumns(), contentType); + MetadataSet metadataSet = getMetadataToSAX(model, model.getResultColumns(), content); _metadataManager.saxReadableMetadata(contentHandler, content, metadataSet); XMLUtils.endElement(contentHandler, "metadata"); @@ -215,13 +221,12 @@ /** * SAX the system properties - * @param columns The - * @param content - * @param cType + * @param columns The search columns + * @param content The content * @throws SAXException * @throws RepositoryException */ - protected void saxSystemProperties (List<SearchColumn> columns, Content content, ContentType cType) throws SAXException, RepositoryException + protected void saxSystemProperties (List<SearchColumn> columns, Content content) throws SAXException, RepositoryException { for (SearchColumn searchColumn : columns) { @@ -231,9 +236,45 @@ { saxContentCurrentState((WorkflowAwareContent) content); } - else if ("contentType".equals(id) && cType != null) + else if ("contentType".equals(id)) { - cType.getLabel().toSAX(contentHandler, "content-type"); + List<String> labels = new ArrayList<String>(); + + for (String cTypeId : content.getTypes()) + { + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + labels.add(_i18nUtils.translate(cType.getLabel(), content.getLanguage())); + } + XMLUtils.createElement(contentHandler, "content-type", StringUtils.join(labels, ", ")); + } + else if ("mixin".equals(id)) + { + List<String> labels = new ArrayList<String>(); + + for (String cTypeId : content.getMixinTypes()) + { + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + labels.add(_i18nUtils.translate(cType.getLabel(), content.getLanguage())); + } + XMLUtils.createElement(contentHandler, "mixin", StringUtils.join(labels, ", ")); + } + else if ("contentTypeOrMixin".equals(id)) + { + List<String> labels = new ArrayList<String>(); + + for (String cTypeId : content.getTypes()) + { + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + labels.add(_i18nUtils.translate(cType.getLabel(), content.getLanguage())); + } + + for (String cTypeId : content.getMixinTypes()) + { + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + labels.add(_i18nUtils.translate(cType.getLabel(), content.getLanguage())); + } + + XMLUtils.createElement(contentHandler, "content-type-mixin", StringUtils.join(labels, ", ")); } else if ("creator".equals(id)) { @@ -275,10 +316,10 @@ * Extracts the metadata to SAX * @param model The search model * @param columns The result columns - * @param contentType The content type + * @param content The content * @return the metadata to SAX in a {@link MetadataSet} */ - protected MetadataSet getMetadataToSAX (SearchModel model, List<SearchColumn> columns, ContentType contentType) + protected MetadataSet getMetadataToSAX (SearchModel model, List<SearchColumn> columns, Content content) { MetadataSet metadataSet = new MetadataSet(); @@ -291,7 +332,7 @@ { String[] path = id.split("\\/|\\."); - if (contentType.getMetadataDefinition(path[0]) != null) + if (_contentTypesHelper.getMetadataDefinition(path[0], content.getTypes(), content.getMixinTypes()) != null) { for (String metadataName : path) { Index: main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeDescriptor.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeDescriptor.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeDescriptor.java (revision 0) @@ -0,0 +1,114 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.contenttype; + +import java.util.Set; + +import org.ametys.runtime.util.I18nizableText; + +/** + * This class represents a descriptor for content type + */ +public interface ContentTypeDescriptor +{ + /** + * Retrieves the id of the content type. + * @return the id. + */ + String getId(); + + /** + * Retrieves the name of the plugin declaring this content type. + * @return the name of the plugin. + */ + String getPluginName(); + + /** + * Retrieves the label of the content type. + * @return the label. + */ + I18nizableText getLabel(); + + /** + * Retrieves the description of the content type. + * @return the description. + */ + I18nizableText getDescription(); + + /** + * Retrieves the default title of the content type. + * @return the default title. + */ + I18nizableText getDefaultTitle(); + + /** + * Retrieves the category of the content type. + * @return the category. + */ + I18nizableText getCategory(); + + /** + * Retrieves the super type's ids. + * @return the super type's ids, or empty if this content type doesn't extend a specific content type. + */ + String[] getSupertypeIds(); + + /** + * Retrieves the URL of the icon without the context path. + * @return the icon URL for the small image 16x16. + */ + String getSmallIcon(); + + /** + * Retrieves the URL of the icon without the context path. + * @return the icon URL for the medium sized image 32x32. + */ + String getMediumIcon(); + + /** + * Retrieves the URL of the icon without the context path. + * @return the icon URL for the large image 48x48. + */ + String getLargeIcon(); + + /** + * Returns all names of "view" metadataSets. + * @param includeInternal if the result should include internal metadataSets. + * @return all names of "view" metadataSets. + */ + Set<String> getViewMetadataSetNames(boolean includeInternal); + + /** + * Returns all names of "edition" metadataSets. + * @param includeInternal if the result should include internal metadataSets. + * @return all names of "edition" metadataSets. + */ + Set<String> getEditionMetadataSetNames(boolean includeInternal); + + /** + * Retrieves the metadata set name for view. + * @param metadataSetName the metadata set name. + * @return the metadata definition. + */ + MetadataSet getMetadataSetForView(String metadataSetName); + + /** + * Retrieves the metadata set name for edition. + * @param metadataSetName the metadata set name. + * @return the metadata set. + */ + MetadataSet getMetadataSetForEdition(String metadataSetName); +} Index: main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeEnumerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeEnumerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeEnumerator.java (working copy) @@ -53,9 +53,13 @@ protected boolean _excludeSimple; /** True to exclude abstract content types */ protected boolean _excludeAbstract; - /** The parent content type */ + /** True to exclude mixin content types */ + protected boolean _excludeMixin; + /** True to include only mixin content types */ + protected boolean _mixinOnly; + /** The parent content types */ protected String[] _contentTypes; - /** The parent content type */ + /** The strict content types */ protected String[] _strictContentTypes; @Override @@ -95,6 +99,20 @@ } _excludeAbstract = abstractConf != null ? Boolean.valueOf(abstractConf.getValue("false")) : false; + Configuration mixinConf = configuration.getChild("exclude-mixin", false); + if (mixinConf == null) + { + mixinConf = configuration.getChild("enumeration").getChild("custom-enumerator").getChild("exclude-mixin", false); + } + _excludeMixin = mixinConf != null ? Boolean.valueOf(mixinConf.getValue("false")) : false; + + Configuration mixinOnlyConf = configuration.getChild("mixin-only", false); + if (mixinOnlyConf == null) + { + mixinOnlyConf = configuration.getChild("enumeration").getChild("custom-enumerator").getChild("include-mixin-only", false); + } + _mixinOnly = mixinOnlyConf != null ? Boolean.valueOf(mixinOnlyConf.getValue("false")) : false; + String strictCTypes = configuration.getChild("strict-content-types").getValue(null); if (strictCTypes == null) { @@ -224,6 +242,16 @@ return false; } + if (_excludeMixin && cType.isMixin()) + { + return false; + } + + if (_mixinOnly && !cType.isMixin()) + { + return false; + } + if (_excludeAbstract && cType.isAbstract()) { return false; Index: main/plugin-cms/src/org/ametys/cms/contenttype/DefaultContentIndexer.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/DefaultContentIndexer.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/DefaultContentIndexer.java (working copy) @@ -42,7 +42,8 @@ /** The associated content type. */ protected ContentTypeExtensionPoint _contentTypeEP; - + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; /** The metadata indexer. */ protected MetadataIndexer _metadataIndexer; @@ -51,6 +52,7 @@ { _metadataIndexer = (MetadataIndexer) manager.lookup(MetadataIndexer.ROLE); _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } @Override @@ -60,7 +62,10 @@ document.add(new Field(FieldNames.CONTENT_NAMES, content.getName(), Store.YES, Index.NOT_ANALYZED)); // Index the content type. - document.add(new Field(FieldNames.CONTENT_TYPES, content.getType(), Store.YES, Index.NOT_ANALYZED)); + for (String cTypeId : content.getTypes()) + { + document.add(new Field(FieldNames.CONTENT_TYPES, cTypeId, Store.YES, Index.NOT_ANALYZED)); + } // Index the content metadata. indexContentMetadata(content, document); @@ -75,13 +80,12 @@ protected void indexContentMetadata(Content content, Document document) throws Exception { CompositeMetadata metadata = content.getMetadataHolder(); - ContentType contentType = _contentTypeEP.getExtension(content.getType()); - MetadataSet metadataSet = contentType.getMetadataSetForView("index"); + MetadataSet metadataSet = _contentTypesHelper.getMetadataSetForView("index", content.getTypes(), content.getMixinTypes()); if (metadataSet != null) { - _metadataIndexer.indexMetadataSet(metadataSet, metadata, null, contentType, document); + _metadataIndexer.indexMetadataSet(metadataSet, metadata, null, content.getTypes(), content.getMixinTypes(), document); } else { Index: main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypesHelper.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypesHelper.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypesHelper.java (working copy) @@ -16,13 +16,16 @@ package org.ametys.cms.contenttype; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.configuration.ConfigurationException; @@ -32,6 +35,11 @@ import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.framework.thread.ThreadSafe; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ArrayUtils; + +import org.ametys.cms.repository.Content; +import org.ametys.plugins.repository.AmetysRepositoryException; +import org.ametys.runtime.util.I18nizableText; /** * Helper for manipulating {@link ContentType}s @@ -43,11 +51,55 @@ public static final String ROLE = ContentTypesHelper.class.getName(); private ContentTypeExtensionPoint _cTypeEP; + + private DynamicContentTypeDescriptorExtentionPoint _dynamicContentTypeManager; @Override public void service(ServiceManager smanager) throws ServiceException { _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + _dynamicContentTypeManager = (DynamicContentTypeDescriptorExtentionPoint) smanager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE); + } + + /** + * Determines if a content is a instance of given content type id + * @param content The content + * @param cTypeId The id of content type or mixin + * @return <code>true</code> if the content is an instance of content type + */ + public boolean isInstanceOf (Content content, String cTypeId) + { + String[] types = content.getTypes(); + if (ArrayUtils.contains(types, cTypeId)) + { + return true; + } + + String[] mixins = content.getMixinTypes(); + if (ArrayUtils.contains(mixins, cTypeId)) + { + return true; + } + + return _containsContentType((String[]) ArrayUtils.addAll(types, mixins), cTypeId); + } + + private boolean _containsContentType (String[] cTypesId, String cTypeId) + { + for (String id : cTypesId) + { + ContentType cType = _cTypeEP.getExtension(id); + + if (ArrayUtils.contains(cType.getSupertypeIds(), cTypeId)) + { + return true; + } + else if (_containsContentType(cType.getSupertypeIds(), cTypeId)) + { + return true; + } + } + return false; } /** @@ -120,36 +172,38 @@ } /** - * Get the metadata set names for view resulting of the concatenation of metadata sets of given content types. - * @param metadataSetName the name of metadata set to retrieve + * Retrieves the metadata names resulting of the intersection of metadata names of given content types and mixins * @param cTypes The id of content types - * @return The list of metadata set names. + * @param mixins The id of mixins + * @return the metadata names. */ - public List<String> getMetadataSetNamesForView (String metadataSetName, String[] cTypes) + public Set<String> getMetadataNames(String[] cTypes, String[] mixins) { - // TODO - return new ArrayList<String>(); - } - - /** - * Get the metadata set names for edition resulting of the concatenation of metadata sets of given content types. - * @param metadataSetName the name of metadata set to retrieve - * @param cTypes The id of content types - * @return The list of metadata set names. - */ - public List<String> getMetadataSetNamesForEdition (String metadataSetName, String[] cTypes) - { - // TODO - return new ArrayList<String>(); + Set<String> metadataNames = new HashSet<String>(); + + for (String id : cTypes) + { + ContentType cType = _cTypeEP.getExtension(id); + metadataNames.addAll(cType.getMetadataNames()); + } + + for (String id : mixins) + { + ContentType cType = _cTypeEP.getExtension(id); + metadataNames.addAll(cType.getMetadataNames()); + } + + return metadataNames; } /** - * Get all metadata sets for view resulting of the concatenation of metadata sets of given content types. + * Get all metadata sets for view resulting of the concatenation of metadata sets of given content types and mixins. * @param cTypes The id of content types + * @param mixins The id of mixins * @param includeInternal <code>true</code> True to include internal metadata sets * @return The metadata sets */ - public Map<String, MetadataSet> getMetadataSetsForView (String[] cTypes, boolean includeInternal) + public Map<String, MetadataSet> getMetadataSetsForView (String[] cTypes, String[] mixins, boolean includeInternal) { Map<String, MetadataSet> metadataSets = new HashMap<String, MetadataSet>(); @@ -168,19 +222,20 @@ for (String viewMetadataSetName : viewMetadataSetNames) { - metadataSets.put(viewMetadataSetName, getMetadataSetForView(viewMetadataSetName, cTypes)); + metadataSets.put(viewMetadataSetName, getMetadataSetForView(viewMetadataSetName, cTypes, mixins)); } return metadataSets; } /** - * Get all metadata sets for edition resulting of the concatenation of metadata sets of given content types. + * Get all metadata sets for edition resulting of the concatenation of metadata sets of given content types and mixins. * @param cTypes The id of content types + * @param mixins The id of mixins * @param includeInternal <code>true</code> True to include internal metadata sets * @return The metadata sets */ - public Map<String, MetadataSet> getMetadataSetsForEdition (String[] cTypes, boolean includeInternal) + public Map<String, MetadataSet> getMetadataSetsForEdition (String[] cTypes, String[] mixins, boolean includeInternal) { Map<String, MetadataSet> metadataSets = new HashMap<String, MetadataSet>(); @@ -199,19 +254,20 @@ for (String editionMetadataSetName : editionMetadataSetNames) { - metadataSets.put(editionMetadataSetName, getMetadataSetForEdition(editionMetadataSetName, cTypes)); + metadataSets.put(editionMetadataSetName, getMetadataSetForEdition(editionMetadataSetName, cTypes, mixins)); } return metadataSets; } /** - * Get the metadata set for view resulting of the concatenation of metadata sets of given content types. + * Get the metadata set for view resulting of the concatenation of metadata sets of given content types and mixins. * @param metadataSetName the name of metadata set to retrieve * @param cTypes The id of content types + * @param mixins The id of mixins * @return The list of metadata set names. */ - public MetadataSet getMetadataSetForView (String metadataSetName, String[] cTypes) + public MetadataSet getMetadataSetForView (String metadataSetName, String[] cTypes, String[] mixins) { MetadataSet joinMetadataSet = new MetadataSet(); @@ -234,16 +290,25 @@ } } + for (String id : mixins) + { + ContentType mixin = _cTypeEP.getExtension(id); + + MetadataSet metadataSet = mixin.getMetadataSetForView(metadataSetName); + copyMetadataSetElementsIfNotExist (metadataSet, joinMetadataSet); + } + return joinMetadataSet; } /** - * Get the metadata set for edition resulting of the concatenation of metadata sets of given content types. + * Get the metadata set for edition resulting of the concatenation of metadata sets of given content types and mixins. * @param metadataSetName the name of metadata set to retrieve * @param cTypes The id of content types + * @param mixins The id of mixins * @return The metadata set */ - public MetadataSet getMetadataSetForEdition (String metadataSetName, String[] cTypes) + public MetadataSet getMetadataSetForEdition (String metadataSetName, String[] cTypes, String[] mixins) { MetadataSet joinMetadataSet = new MetadataSet(); @@ -266,6 +331,14 @@ } } + for (String id : mixins) + { + ContentType mixin = _cTypeEP.getExtension(id); + + MetadataSet metadataSet = mixin.getMetadataSetForView(metadataSetName); + copyMetadataSetElementsIfNotExist (metadataSet, joinMetadataSet); + } + return joinMetadataSet; } @@ -302,7 +375,10 @@ _copyMetadataSetElementsIfNotExist(elmt, fieldset, dest); - dest.addElement(fieldset); + if (fieldset.getElements().size() > 0) + { + dest.addElement(fieldset); + } } } } @@ -311,9 +387,10 @@ * Retrieves the definition of a given metadata. * @param metadataName the metadata name. * @param cTypes The id of content types + * @param mixins The id of mixins * @return the metadata definition or <code>null</code> if not found */ - public MetadataDefinition getMetadataDefinition (String metadataName, String[] cTypes) + public MetadataDefinition getMetadataDefinition (String metadataName, String[] cTypes, String[] mixins) { for (String id : cTypes) { @@ -324,10 +401,45 @@ return cType.getMetadataDefinition(metadataName); } } + + for (String id : mixins) + { + ContentType cType = _cTypeEP.getExtension(id); + + if (cType.hasMetadataDefinition(metadataName)) + { + return cType.getMetadataDefinition(metadataName); + } + } + return null; } /** + * Determines if the given content type can be added to content + * @param content The content + * @param cTypeId The id of content type + * @return <code>true</code> if the content type is compatible with content + */ + public boolean isCompatibleContentType (Content content, String cTypeId) + { + String[] currentContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + + ArrayList<String> cTypes = new ArrayList<String>(Arrays.asList(currentContentTypes)); + cTypes.add(cTypeId); + + try + { + getMetadataDefinitions (cTypes.toArray(new String[cTypes.size()])); + return true; + } + catch (ConfigurationException e) + { + return false; + } + } + + /** * Retrieves all definitions of a metadata resulting of the concatenation of metadata of given content types. * @param cTypes The id of content types * @return the metadata definitions @@ -362,4 +474,251 @@ return metadata; } + /** + * Determine whether a metadata can be read at this time. + * @param metadataPath the path to the given metadata. + * @param content The content where metadata is to be read on. + * @return <code>true</code> if the current user is allowed to read the metadata of this content. + * @throws AmetysRepositoryException if an error occurs while accessing the content. + */ + public boolean canRead (Content content, String metadataPath) + { + for (String id : content.getTypes()) + { + ContentType cType = _cTypeEP.getExtension(id); + + if (cType.getMetadataDefinitionByPath(metadataPath) != null) + { + return cType.canRead(content, metadataPath); + } + } + + for (String id : content.getMixinTypes()) + { + ContentType cType = _cTypeEP.getExtension(id); + + if (cType.getMetadataDefinitionByPath(metadataPath) != null) + { + return cType.canRead(content, metadataPath); + } + } + + return false; + } + + /** + * Determine whether a metadata can be read at this time. + * @param metadataPath the path to the given metadata. + * @param content The content where metadata is to be read on. + * @return <code>true</code> if the current user is allowed to read the metadata of this content. + * @throws AmetysRepositoryException if an error occurs while accessing the content. + */ + public boolean canWrite (Content content, String metadataPath) + { + for (String id : content.getTypes()) + { + ContentType cType = _cTypeEP.getExtension(id); + + if (cType.getMetadataDefinitionByPath(metadataPath) != null) + { + return cType.canWrite(content, metadataPath); + } + } + + for (String id : content.getMixinTypes()) + { + ContentType cType = _cTypeEP.getExtension(id); + + if (cType.getMetadataDefinitionByPath(metadataPath) != null) + { + return cType.canWrite(content, metadataPath); + } + } + + return false; + } + + /** + * Get the id of content type to use for rendering + * @param content The content + * @return the id of dynamic or standard content type + */ + public String getContentTypeIdForRendering (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getId(); + } + + return getFirstContentType(content).getId(); + } + + /** + * Get the plugin name of content type to use for rendering + * @param content The content + * @return the plugin name of dynamic or standard content type + */ + public String getContentTypePluginForRendering (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getPluginName(); + } + + return getFirstContentType(content).getPluginName(); + } + + /** + * Retrieves the label of the content type. + * @param content The content + * @return the label. + */ + public I18nizableText getContentTypeLabel (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getLabel(); + } + + return getFirstContentType(content).getLabel(); + } + + /** + * Retrieves the description of the content type. + * @param content The content + * @return the label. + */ + public I18nizableText getContentTypeDescription (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getDescription(); + } + + return getFirstContentType(content).getDescription(); + } + + /** + * Retrieves the default title of the content type. + * @param content The content + * @return the label. + */ + public I18nizableText getContentTypeDefaultTitle (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getDefaultTitle(); + } + + return getFirstContentType(content).getDefaultTitle(); + } + + /** + * Retrieves the category of the content type. + * @param content The content + * @return the label. + */ + public I18nizableText getContentTypeCategory (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getCategory(); + } + + return getFirstContentType(content).getCategory(); + } + + /** + * Retrieves the URL of the icon without the context path. + * @param content The content + * @return the icon URL for the small image 16x16. + */ + public String getSmallIcon (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getSmallIcon(); + } + + return getFirstContentType(content).getSmallIcon(); + } + + /** + * Retrieves the URL of the icon without the context path. + * @param content The content + * @return the icon URL for the medium image 32x32. + */ + public String getMediumIcon (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getMediumIcon(); + } + + return getFirstContentType(content).getMediumIcon(); + } + + /** + * Retrieves the URL of the icon without the context path. + * @param content The content + * @return the icon URL for the large image 48x48. + */ + public String getLargeIcon (Content content) + { + DynamicContentTypeDescriptor dynamicContentType = _dynamicContentTypeManager.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); + if (dynamicContentType != null) + { + return dynamicContentType.getLargeIcon(); + } + + return getFirstContentType(content).getLargeIcon(); + } + + /** + * Get the content type which determines the content icons and rendering + * @param content The content + * @return The main content type + */ + public ContentType getFirstContentType (Content content) + { + TreeSet<ContentType> treeSet = new TreeSet<ContentType>(new ContentTypeComparator()); + + for (String id : content.getTypes()) + { + ContentType contentType = _cTypeEP.getExtension(id); + treeSet.add(contentType); + } + return treeSet.first(); + } + + class ContentTypeComparator implements Comparator<ContentType> + { + @Override + public int compare(ContentType c1, ContentType c2) + { + I18nizableText t1 = c1.getLabel(); + I18nizableText t2 = c2.getLabel(); + + String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); + String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); + + int compareTo = str1.toString().compareTo(str2.toString()); + if (compareTo == 0) + { + // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet + // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal + return 1; + } + return compareTo; + } + } + } Index: main/plugin-cms/src/org/ametys/cms/contenttype/GetMetadataSetDefinitionAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/GetMetadataSetDefinitionAction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/GetMetadataSetDefinitionAction.java (working copy) @@ -53,6 +53,8 @@ protected CurrentUserProvider _userProvider; /** The rights manager */ protected RightsManager _rightsManager; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; @Override public void service(ServiceManager smanager) throws ServiceException @@ -62,6 +64,7 @@ _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); _rightsManager = (RightsManager) smanager.lookup(RightsManager.ROLE); + _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); } @Override @@ -71,16 +74,23 @@ String contentTypeId = StringUtils.defaultString(request.getParameter("contentType")); String contentId = StringUtils.defaultString(request.getParameter("contentId")); + String metadataSetName = StringUtils.defaultIfEmpty(request.getParameter("metadataSetName"), "main"); + String metadataSetMode = StringUtils.defaultIfEmpty(request.getParameter("metadataSetMode"), "edition"); - ContentType cType = null; + String[] contentTypes; + String[] mixins = new String[0]; + + MetadataSet metadataSet = null; + Content content = null; if (StringUtils.isNotEmpty(contentTypeId)) { - cType = _contentTypeExtensionPoint.getExtension(contentTypeId); + contentTypes = contentTypeId.split(","); } else if (StringUtils.isNotEmpty(contentId)) { - Content content = _resolver.resolveById(contentId); - cType = _contentTypeExtensionPoint.getExtension(content.getType()); + content = _resolver.resolveById(contentId); + contentTypes = content.getTypes(); + mixins = content.getMixinTypes(); } else { @@ -89,32 +99,36 @@ throw new IllegalArgumentException(errorMsg); } - if (cType == null) + for (String id : contentTypes) { - String errorMsg = String.format("Unable to get metadata set definition: unknown content type '" + contentTypeId + "'"); - getLogger().error(errorMsg); - throw new IllegalArgumentException(errorMsg); + ContentType cType = _contentTypeExtensionPoint.getExtension(id); + + if (cType == null) + { + String errorMsg = String.format("Unable to get metadata set definition: unknown content type '" + id + "'"); + getLogger().error(errorMsg); + throw new IllegalArgumentException(errorMsg); + } } - List<Object> metadataSetProperties = new LinkedList<Object>(); + metadataSet = "view".equals(metadataSetMode) ? _contentTypesHelper.getMetadataSetForView(metadataSetName, contentTypes, mixins) : _contentTypesHelper.getMetadataSetForEdition(metadataSetName, contentTypes, mixins); - String metadataSetName = StringUtils.defaultIfEmpty(request.getParameter("metadataSetName"), "main"); - String metadataSetMode = StringUtils.defaultIfEmpty(request.getParameter("metadataSetMode"), "edition"); - MetadataSet metadataSet = "view".equals(metadataSetMode) ? cType.getMetadataSetForView(metadataSetName) : cType.getMetadataSetForEdition(metadataSetName); + + List<Object> metadataSetProperties = new LinkedList<Object>(); if (metadataSet == null) { - String errorMsg = String.format("Unknown metadata set '%s' of type '%s' for content type '%s'", metadataSetName, metadataSetMode, cType.getId()); + String errorMsg = String.format("Unknown metadata set '%s' of type '%s' for content types '%s'", metadataSetName, metadataSetMode, StringUtils.join(contentTypes, ',')); getLogger().error(errorMsg); - metadataSetProperties.add(getMetadataErrorProperties(cType, metadataSetName, metadataSetMode)); + metadataSetProperties.add(metadataError2JsonObject(contentTypes, metadataSetName, metadataSetMode)); } else { // Getting the metadata set properties. for (AbstractMetadataSetElement element : metadataSet.getElements()) { - Map<String, Object> childProperties = getMetadataSetElementInformation(cType, element); + Map<String, Object> childProperties = metadataSetElement2JsonObject(contentTypes, mixins, element); if (childProperties != null) { metadataSetProperties.add(childProperties); @@ -129,24 +143,24 @@ /** * Get some error properties when the requested metadata set is not found. - * @param contentType - * @param metadataSetName - * @param metadataSetMode + * @param contentTypes The content types + * @param metadataSetName The metadata set name + * @param metadataSetMode The metadata set mode * @return A map containing the properties. */ - protected Map<String, Object> getMetadataErrorProperties(ContentType contentType, String metadataSetName, String metadataSetMode) + protected Map<String, Object> metadataError2JsonObject(String[] contentTypes, String metadataSetName, String metadataSetMode) { - HashMap<String, Object> properties = new LinkedHashMap<String, Object>(); + HashMap<String, Object> jsonObject = new LinkedHashMap<String, Object>(); - properties.put("type", "error"); - properties.put("errorType", "metadataSet"); - properties.put("contentType", contentType.getId()); - properties.put("metadataSetName", metadataSetName); - properties.put("metadataSetMode", metadataSetMode); + jsonObject.put("type", "error"); + jsonObject.put("errorType", "metadataSet"); + jsonObject.put("contentType", StringUtils.join(contentTypes, ',')); + jsonObject.put("metadataSetName", metadataSetName); + jsonObject.put("metadataSetMode", metadataSetMode); - properties.put("children", Collections.EMPTY_LIST); + jsonObject.put("children", Collections.EMPTY_LIST); - return properties; + return jsonObject; } /* --------------------------------------------------------------------- */ @@ -156,45 +170,48 @@ /** * Get properties of the {@link AbstractMetadataSetElement} and its child element. * This method is the entry point to retrieves information of a MetadataSetElement. - * @param contentType + * @param contentTypes The content types + * @param mixins The mixins * @param metadataSetElement The metadata set element * @return A map containing the properties of this {@link AbstractMetadataSetElement}. */ - protected Map<String, Object> getMetadataSetElementInformation(ContentType contentType, AbstractMetadataSetElement metadataSetElement) + protected Map<String, Object> metadataSetElement2JsonObject(String[] contentTypes, String[] mixins, AbstractMetadataSetElement metadataSetElement) { - return getMetadataSetElementInformation(contentType, null, metadataSetElement, true); + return metadataSet2JsonObject(contentTypes, mixins, null, metadataSetElement, true); } /** * Get properties of the {@link AbstractMetadataSetElement} * This method is the entry point to retrieves information of a MetadataSetElement. - * @param contentType + * @param contentTypes The content types + * @param mixins The mixins * @param metadataSetElement The metadata set element * @param recurse If true, also retrieves the properties of the child elements. * @return A map containing the properties of this {@link AbstractMetadataSetElement}. */ - protected Map<String, Object> getMetadataSetElementInformation(ContentType contentType, AbstractMetadataSetElement metadataSetElement, boolean recurse) + protected Map<String, Object> getMetadataSetElementInformation(String[] contentTypes, String[] mixins, AbstractMetadataSetElement metadataSetElement, boolean recurse) { - return getMetadataSetElementInformation(contentType, null, metadataSetElement, recurse); + return metadataSet2JsonObject(contentTypes, mixins, null, metadataSetElement, recurse); } /** * Get properties of the {@link AbstractMetadataSetElement} - * @param contentType + * @param contentTypes The content types + * @param mixins The mixins * @param metadataDefinition * @param metadataSetElement The metadata set element * @param recurse If true, also retrieves the properties of the child elements. * @return A map containing the properties of this {@link AbstractMetadataSetElement}. */ - protected Map<String, Object> getMetadataSetElementInformation(ContentType contentType, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement, boolean recurse) + protected Map<String, Object> metadataSet2JsonObject(String[] contentTypes, String[] mixins, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement, boolean recurse) { if (metadataSetElement instanceof MetadataDefinitionReference) { - return getMetadataInformation(contentType, metadataDefinition, (MetadataDefinitionReference) metadataSetElement, recurse); + return metadata2JsonObject(contentTypes, mixins, metadataDefinition, (MetadataDefinitionReference) metadataSetElement, recurse); } else if (metadataSetElement instanceof Fieldset) { - return getFieldsetInformation(contentType, metadataDefinition, (Fieldset) metadataSetElement, recurse); + return fieldset2JsonObject(contentTypes, mixins, metadataDefinition, (Fieldset) metadataSetElement, recurse); } throw new IllegalArgumentException( @@ -206,17 +223,18 @@ /** * Add informations in a map of properties for the children of a metadata set element. * @param properties The map of properties to populate - * @param contentType + * @param contentTypes The content types + * @param mixins The mixins * @param metadataDefinition * @param metadataSetElement */ - protected void addChildrenInformation(Map<String, Object> properties, ContentType contentType, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement) + protected void addChildrenInformation(Map<String, Object> properties, String[] contentTypes, String[] mixins, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement) { List<Object> children = new LinkedList<Object>(); for (AbstractMetadataSetElement child : metadataSetElement.getElements()) { - Map<String, Object> childProperties = getMetadataSetElementInformation(contentType, metadataDefinition, child, true); + Map<String, Object> childProperties = metadataSet2JsonObject(contentTypes, mixins, metadataDefinition, child, true); if (childProperties != null) { children.add(childProperties); @@ -233,26 +251,27 @@ /** * Get the properties of a {@link Fieldset} - * @param contentType + * @param contentTypes The content types + * @param mixins The mixins * @param metadataDefinition * @param fieldset * @param recurse * @return A map containing the {@link Fieldset} properties. */ - protected Map<String, Object> getFieldsetInformation(ContentType contentType, MetadataDefinition metadataDefinition, Fieldset fieldset, boolean recurse) + protected Map<String, Object> fieldset2JsonObject(String[] contentTypes, String[] mixins, MetadataDefinition metadataDefinition, Fieldset fieldset, boolean recurse) { - HashMap<String, Object> properties = new LinkedHashMap<String, Object>(); + HashMap<String, Object> jsonObject = new LinkedHashMap<String, Object>(); - properties.put("type", "fieldset"); - properties.put("label", fieldset.getLabel()); - properties.put("role", fieldset.getRole()); + jsonObject.put("type", "fieldset"); + jsonObject.put("label", fieldset.getLabel()); + jsonObject.put("role", fieldset.getRole()); if (recurse) { - addChildrenInformation(properties, contentType, metadataDefinition, fieldset); + addChildrenInformation(jsonObject, contentTypes, mixins, metadataDefinition, fieldset); } - return properties; + return jsonObject; } @@ -263,47 +282,48 @@ /** * Get properties of the {@link MetadataDefinition} through its {@link MetadataDefinitionReference} - * @param contentType + * @param contentTypes The content types + * @param mixins The mixins * @param parentMetadataDef * @param metadataDefRef * @param recurse * @return A map containing the {@link MetadataDefinitionReference} properties. */ - protected Map<String, Object> getMetadataInformation(ContentType contentType, MetadataDefinition parentMetadataDef, MetadataDefinitionReference metadataDefRef, boolean recurse) + protected Map<String, Object> metadata2JsonObject(String[] contentTypes, String[] mixins, MetadataDefinition parentMetadataDef, MetadataDefinitionReference metadataDefRef, boolean recurse) { - MetadataDefinition metadataDefinition = _getMetadataDefinition(contentType, parentMetadataDef, metadataDefRef); + MetadataDefinition metadataDefinition = _getMetadataDefinition(contentTypes, mixins, parentMetadataDef, metadataDefRef); MetadataType type = metadataDefinition.getType(); - HashMap<String, Object> properties = new LinkedHashMap<String, Object>(); + HashMap<String, Object> jsonObject = new LinkedHashMap<String, Object>(); - properties.put("type", "metadata"); - properties.put("metadataName", metadataDefRef.getMetadataName()); - properties.put("metadataType", _getMetadataType(metadataDefinition)); + jsonObject.put("type", "metadata"); + jsonObject.put("metadataName", metadataDefRef.getMetadataName()); + jsonObject.put("metadataType", _getMetadataType(metadataDefinition)); - properties.put("label", metadataDefinition.getLabel()); - properties.put("description", metadataDefinition.getDescription()); - properties.put("mandatory", _isMandatory(metadataDefinition)); + jsonObject.put("label", metadataDefinition.getLabel()); + jsonObject.put("description", metadataDefinition.getDescription()); + jsonObject.put("mandatory", _isMandatory(metadataDefinition)); boolean isContentReference = MetadataType.CONTENT.equals(type); boolean isSubContent = MetadataType.SUB_CONTENT.equals(type); if (isSubContent || isContentReference) { - properties.put("contentType", metadataDefinition.getContentType()); - properties.put("metadataSetName", metadataDefRef.getMetadataSetName()); + jsonObject.put("contentType", metadataDefinition.getContentType()); + jsonObject.put("metadataSetName", metadataDefRef.getMetadataSetName()); } if (isContentReference) { - properties.put("canCreate", _hasRight(metadataDefinition.getContentType())); + jsonObject.put("canCreate", _hasRight(metadataDefinition.getContentType())); } // Children information are not added in case of a metadata of type content. - if (properties != null && recurse && !isSubContent && !isContentReference) + if (jsonObject != null && recurse && !isSubContent && !isContentReference) { - addChildrenInformation(properties, contentType, metadataDefinition, metadataDefRef); + addChildrenInformation(jsonObject, contentTypes, mixins, metadataDefinition, metadataDefRef); } - return properties; + return jsonObject; } /** @@ -400,13 +420,13 @@ * @param metadataDefRef * @return The metadata definition found */ - private MetadataDefinition _getMetadataDefinition(ContentType contentType, MetadataDefinition parentMetadataDef, MetadataDefinitionReference metadataDefRef) + private MetadataDefinition _getMetadataDefinition(String[] contentTypes, String[] mixins, MetadataDefinition parentMetadataDef, MetadataDefinitionReference metadataDefRef) { String metadataName = metadataDefRef.getMetadataName(); if (parentMetadataDef == null) { - return contentType.getMetadataDefinition(metadataName); + return _contentTypesHelper.getMetadataDefinition(metadataName, contentTypes, mixins); } else { Index: main/plugin-cms/src/org/ametys/cms/contenttype/DynamicContentTypeDescriptorExtentionPoint.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/DynamicContentTypeDescriptorExtentionPoint.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/contenttype/DynamicContentTypeDescriptorExtentionPoint.java (revision 0) @@ -0,0 +1,240 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.contenttype; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.Constants; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; + +import org.ametys.runtime.plugin.PluginsManager; +import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; + +/** + * This class is in charge of handling dynamic content type descriptor extension point.<br> + * This point handles the pool of available dynamic content type descriptors. + */ +public class DynamicContentTypeDescriptorExtentionPoint extends AbstractThreadSafeComponentExtensionPoint<DynamicContentTypeDescriptor> +{ + /** Avalon Role */ + public static final String ROLE = "org.ametys.cms.contenttype.DynamicContentTypeExtentionPoint"; // FIXME RUNTIME-1023 DynamicContentTypeDescriptorExtentionPoint.class.getName(); + + private ServiceManager _smanager; + private ContentTypeExtensionPoint _contentTypeEP; + private ContentTypesHelper _contentTypesHelper; + + private Map<String, String> _cache = new HashMap<String, String>(); + + @Override + public void service(ServiceManager manager) throws ServiceException + { + super.service(manager); + _smanager = manager; + _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); + } + + @Override + public void initializeExtensions() throws Exception + { + org.apache.cocoon.environment.Context cocoonContext = (org.apache.cocoon.environment.Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); + final Set<String> pluginNames = PluginsManager.getInstance().getPluginNames(); + + File rootFile = new File(cocoonContext.getRealPath("WEB-INF/param/content-types/_dynamic")); + File[] plugins = rootFile.listFiles(new FilenameFilter() + { + @Override + public boolean accept(File dir, String name) + { + return pluginNames.contains(name); + } + }); + + if (plugins != null) + { + for (File pluginDir : plugins) + { + String pluginName = pluginDir.getName(); + + File[] files = pluginDir.listFiles(new FilenameFilter() + { + @Override + public boolean accept(File dir, String name) + { + return name.endsWith(".xml"); + } + }); + + for (File file : files) + { + InputStream is = null; + Configuration configuration = null; + try + { + is = new FileInputStream(file); + configuration = new DefaultConfigurationBuilder(true).build(is); + + String id = "dynamic-content-type." + file.getName().substring(0, file.getName().lastIndexOf('.')); + + DefaultConfiguration conf = new DefaultConfiguration("extension"); + conf.setAttribute("id", id); + conf.addChild(configuration); + + addComponent(pluginName, "unknown", id, DynamicContentTypeDescriptor.class, conf); + } + catch (Exception ex) + { + throw new ConfigurationException("Unable to parse dynamic content type configuration at WEB-INF/param/content-types/dynamic/" + file.getName() + ".xml", configuration, ex); + } + finally + { + IOUtils.closeQuietly(is); + } + } + } + } + + super.initializeExtensions(); + } + + /** + * Get the matching dynamic descriptor for given content types and mixins + * @param contentTypes The content types + * @param mixinTypes The mixins + * @return The matching dynamic descriptor or <code>null</code> if not found + */ + public DynamicContentTypeDescriptor getMatchingDescriptor (String[] contentTypes, String[] mixinTypes) + { + String cacheId = _getCacheIdentifier(contentTypes, mixinTypes); + + if (_cache.containsKey(cacheId)) + { + return getExtension(_cache.get(cacheId)); + } + + DynamicContentTypeDescriptor matchingDynamicCType = null; + + int bestCTypeScore = 0; + int bestMixinScore = 0; + + for (String id : getExtensionsIds()) + { + int cTypeScore = 0; + int mixinScore = 0; + + DynamicContentTypeDescriptor dynamicContentType = getExtension(id); + + boolean dynamicTypesMatch = true; + String[] dynamicCTypes = dynamicContentType.getSupertypeIds(); + Set<String> cTypesAndAncestors = _getContentTypesAndAncestor(contentTypes, mixinTypes); + for (String cTypeId : dynamicCTypes) + { + if (!cTypesAndAncestors.contains(cTypeId)) + { + // Dynamic types do not include all content types, ignore + dynamicTypesMatch = false; + break; + } + } + + if (dynamicTypesMatch) + { + for (String cTypeId : dynamicContentType.getSupertypeIds()) + { + ContentType cType = _contentTypeEP.getExtension(cTypeId); + + if (!cType.isMixin() && ArrayUtils.contains(contentTypes, cTypeId)) + { + cTypeScore += 1; + } + else if (cType.isMixin() && ArrayUtils.contains(mixinTypes, cTypeId)) + { + mixinScore += 1; + } + } + + if (cTypeScore > bestCTypeScore || (cTypeScore == bestCTypeScore && mixinScore > bestMixinScore)) + { + bestCTypeScore = cTypeScore; + bestMixinScore = mixinScore; + + matchingDynamicCType = dynamicContentType; + _cache.put(cacheId, id); + } + } + } + + return matchingDynamicCType; + } + + private Set<String> _getContentTypesAndAncestor (String[] contentTypes, String[] mixins) + { + Set<String> cTypesAndAncestors = new HashSet<String>(); + + if (_contentTypesHelper == null) + { + try + { + _contentTypesHelper = (ContentTypesHelper) _smanager.lookup(ContentTypesHelper.ROLE); + } + catch (ServiceException e) + { + throw new IllegalStateException(e); + } + } + + String[] allContentTypes = (String[]) ArrayUtils.addAll(contentTypes, mixins); + + for (String id : allContentTypes) + { + cTypesAndAncestors.add(id); + cTypesAndAncestors.addAll(_contentTypesHelper.getAncestors(id)); + } + + return cTypesAndAncestors; + + } + + private String _getCacheIdentifier (String[] contentTypes, String[] mixins) + { + Arrays.sort(contentTypes); + String name = StringUtils.join(contentTypes, ";"); + + if (mixins.length > 0) + { + Arrays.sort(mixins); + name += ";"; + name += StringUtils.join(mixins, ";"); + } + + return name; + } +} Index: main/plugin-cms/src/org/ametys/cms/contenttype/MetadataManager.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/MetadataManager.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/MetadataManager.java (working copy) @@ -68,6 +68,8 @@ /** Content type extension point. */ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; /** Users manager */ protected UsersManager _usersManager; /** The JSON conversion utilities. */ @@ -88,6 +90,7 @@ _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); _usersManager = (UsersManager) manager.lookup(UsersManager.ROLE); _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } /** @@ -256,15 +259,7 @@ { if (content != null) { - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - - if (contentType == null) - { - throw new AmetysRepositoryException("Unknown content type: " + contentTypeId); - } - - metadataDefinition = contentType.getMetadataDefinition(metadataName); + metadataDefinition = _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); } } else @@ -887,21 +882,13 @@ if (metadataSetName != null) { - String contentTypeId = content.getType(); - - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - if (contentType == null) - { - throw new AmetysRepositoryException("Unknown content type: " + contentTypeId); - } - if (editionRendering) { - metadataSet = contentType.getMetadataSetForEdition(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); } else { - metadataSet = contentType.getMetadataSetForView(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); } } else @@ -1198,14 +1185,6 @@ */ protected boolean _canRead(Content content, String metadataPath) throws AmetysRepositoryException { - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - - if (contentType == null) - { - throw new AmetysRepositoryException("Unknown content type: " + contentTypeId); - } - - return contentType.canRead(content, metadataPath); + return _contentTypesHelper.canRead(content, metadataPath); } } Index: main/plugin-cms/src/org/ametys/cms/contenttype/DynamicContentTypeDescriptor.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/DynamicContentTypeDescriptor.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/contenttype/DynamicContentTypeDescriptor.java (revision 0) @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.contenttype; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; + +/** + * This class represents a dynamic content type descriptor + */ +public class DynamicContentTypeDescriptor extends AbstractContentTypeDescriptor +{ + @Override + protected Configuration getOverridenConfiguration() throws ConfigurationException + { + return null; + } + + @Override + protected Configuration getRootConfiguration(Configuration configuration) + { + return configuration.getChild("dynamic-content-type-descriptor"); + } + + @Override + protected String _getDefaultCatalogue() + { + return "application"; + } + + @Override + protected String _getIconPath(String pluginName) + { + return "/plugins/cms/dynamic-content-types_resources/" + pluginName + "/resources/"; + } +} Index: main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeExtensionPoint.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeExtensionPoint.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/ContentTypeExtensionPoint.java (working copy) @@ -32,6 +32,8 @@ import org.apache.commons.io.IOUtils; import org.ametys.cms.repository.ContentTypeExpression; +import org.ametys.cms.repository.ContentTypeOrMixinTypeExpression; +import org.ametys.cms.repository.MixinTypeExpression; import org.ametys.plugins.repository.query.expression.Expression.Operator; import org.ametys.runtime.plugin.PluginsManager; import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; @@ -143,6 +145,46 @@ } /** + * Create a hierarchical {@link ContentTypeExpression} which returns + * all contents of the specified mixin and all its descendant types. + * @param mixinIds the mixin ids + * @return the hierarchical content type expression. + */ + public MixinTypeExpression createHierarchicalMixinExpression(String... mixinIds) + { + Set<String> typesToSearch = new HashSet<String>(); + + for (String mixinId : mixinIds) + { + typesToSearch.add(mixinId); + typesToSearch.addAll(getSubTypes(mixinId)); + } + + return new MixinTypeExpression(Operator.EQ, typesToSearch.toArray(new String[typesToSearch.size()])); + } + + /** + * Create a hierarchical {@link ContentTypeExpression} which returns + * all contents of the specified type or mixin and all its descendant types. + * @param contentIds the mixin ids + * @return the hierarchical content type expression. + */ + public ContentTypeOrMixinTypeExpression createHierarchicalContentTypeOrMixinExpression(String... contentIds) + { + Set<String> typesToSearch = new HashSet<String>(); + + for (String contentTypeId : contentIds) + { + typesToSearch.add(contentTypeId); + typesToSearch.addAll(getSubTypes(contentTypeId)); + } + + return new ContentTypeOrMixinTypeExpression(Operator.EQ, typesToSearch.toArray(new String[typesToSearch.size()])); + } + + + + /** * Get all the descendant types of a given {@link ContentType}. * @param contentTypeId the content type id. * @return a collection of the ids of all the ContentType's descendants. Index: main/plugin-cms/src/org/ametys/cms/contenttype/CommonMetadataSetNamesFromContentsGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/CommonMetadataSetNamesFromContentsGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/CommonMetadataSetNamesFromContentsGenerator.java (working copy) @@ -21,6 +21,7 @@ import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.cocoon.environment.Request; +import org.apache.commons.lang.ArrayUtils; import org.ametys.cms.repository.Content; import org.ametys.plugins.repository.AmetysObjectResolver; @@ -49,7 +50,12 @@ for (String contentId : contentIds) { Content content = _resolver.resolveById(contentId); - cTypeIds.add(content.getType()); + + String[] allContentTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + for (String id : allContentTypes) + { + cTypeIds.add(id); + } } return cTypeIds.toArray(new String[cTypeIds.size()]); Index: main/plugin-cms/src/org/ametys/cms/contenttype/DefaultContentType.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/DefaultContentType.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/DefaultContentType.java (working copy) @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -37,7 +36,6 @@ import org.apache.avalon.framework.context.Context; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; -import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; @@ -46,14 +44,12 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.excalibur.source.Source; -import org.apache.excalibur.source.SourceResolver; import org.xml.sax.SAXException; import org.ametys.cms.repository.Content; import org.ametys.cms.repository.WorkflowAwareContent; import org.ametys.cms.transformation.RichTextTransformer; import org.ametys.cms.transformation.docbook.DocbookTransformer; -import org.ametys.plugins.explorer.dublincore.DublinCoreMetadataProvider; import org.ametys.plugins.repository.AmetysRepositoryException; import org.ametys.plugins.workflow.Workflow; import org.ametys.runtime.plugin.component.PluginAware; @@ -85,43 +81,15 @@ * </restrict-to><br> * </code> */ -public class DefaultContentType extends AbstractLogEnabled implements ContentType, PluginAware, Serviceable, Contextualizable, Configurable, ThreadSafe, Disposable +public class DefaultContentType extends AbstractContentTypeDescriptor implements ContentType, PluginAware, Serviceable, Contextualizable, Configurable, ThreadSafe, Disposable { /** Global validator role. */ protected static final String __GLOBAL_VALIDATOR_ROLE = "_global"; static Pattern __annotationNamePattern; - /** Plugin name. */ - protected String _pluginName; - /** Content type id. */ - protected String _id; - /** This content-type's supertype ids (can be empty). */ - protected String[] _superTypeIds; - /** Label. */ - protected I18nizableText _label; - /** Description. */ - protected I18nizableText _description; - /** Default title. */ - protected I18nizableText _defaultTitle; - /** Category. */ - protected I18nizableText _category; - /** Small icon URI 16x16. */ - protected String _smallIcon; - /** Medium icon URI 32x32. */ - protected String _mediumIcon; - /** Large icon URI 48x48. */ - protected String _largeIcon; /** Metadata definitions. */ protected Map<String, MetadataDefinition> _metadata = new LinkedHashMap<String, MetadataDefinition>(); - /** All metadata sets for view. */ - protected Map<String, MetadataSet> _allMetadataSetsForView = new LinkedHashMap<String, MetadataSet>(); - /** Non-internal metadata sets for view. */ - protected Map<String, MetadataSet> _metadataSetsForView = new LinkedHashMap<String, MetadataSet>(); - /** All metadata sets for edition. */ - protected Map<String, MetadataSet> _allMetadataSetsForEdition = new LinkedHashMap<String, MetadataSet>(); - /** Non-internal metadata sets for edition. */ - protected Map<String, MetadataSet> _metadataSetsForEdition = new LinkedHashMap<String, MetadataSet>(); /** The right needed to create a content of this type, or null if no right is needed. */ protected String _right; /** The abstract property */ @@ -134,8 +102,6 @@ protected Context _context; /** Cocoon Context */ protected org.apache.cocoon.environment.Context _cocoonContext; - /** The content type extension point. */ - protected ContentTypeExtensionPoint _cTypeEP; /** The workflow. */ protected Workflow _workflow; /** The rights manager. */ @@ -152,12 +118,6 @@ protected RichTextUpdater _richTextUpdater; /** The content indexer. */ protected ContentIndexer _contentIndexer; - /** The source resolver */ - protected SourceResolver _srcResolver; - /** The DublinCore metadata provider */ - protected DublinCoreMetadataProvider _dcProvider; - /** The content types helper */ - protected ContentTypesHelper _contentTypesHelper; // ComponentManager pour les Validator private ThreadSafeComponentManager<Validator> _validatorManager; @@ -165,32 +125,20 @@ //ComponentManager pour les Enumerator private ThreadSafeComponentManager<Enumerator> _enumeratorManager; - - - @Override - public void setPluginInfo(String pluginName, String featureName) - { - _pluginName = pluginName; - } - @Override public void service(ServiceManager manager) throws ServiceException { + super.service(manager); _manager = manager; - _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); _workflow = (Workflow) manager.lookup(Workflow.ROLE); _rightsManager = (RightsManager) manager.lookup(RightsManager.ROLE); _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); _richTextTransformer = (RichTextTransformer) manager.lookup(DocbookTransformer.ROLE); _consistencyExtractor = (ConsistencyExtractor) manager.lookup(DefaultConsistencyExtractor.ROLE); _richTextUpdater = (RichTextUpdater) manager.lookup(DocbookRichTextUpdater.ROLE); - _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); - _dcProvider = (DublinCoreMetadataProvider) manager.lookup(DublinCoreMetadataProvider.ROLE); _contentIndexer = (DefaultContentIndexer) manager.lookup(DefaultContentIndexer.ROLE); - _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } - @Override public void contextualize(Context context) throws ContextException { _context = context; @@ -208,6 +156,40 @@ } @Override + protected Configuration getRootConfiguration(Configuration configuration) + { + return configuration.getChild("content-type"); + } + + @Override + protected Configuration getOverridenConfiguration() throws ConfigurationException + { + Configuration overridenConf = null; + File ctFile = new File(_cocoonContext.getRealPath("WEB-INF/param/content-types/_override/" + _id + ".xml")); + + if (ctFile.exists()) + { + InputStream is = null; + + try + { + is = new FileInputStream(ctFile); + overridenConf = new DefaultConfigurationBuilder(true).build(is); + } + catch (Exception ex) + { + throw new ConfigurationException("Unable to parse overriden configuration for content type '" + _id + "' at WEB-INF/param/content-types/_override/" + _id + ".xml", overridenConf, ex); + } + finally + { + IOUtils.closeQuietly(is); + } + } + + return overridenConf; + } + + @Override public void configure(Configuration configuration) throws ConfigurationException { try @@ -229,110 +211,49 @@ _id = configuration.getAttribute("id"); - Configuration contentTypeConfiguration = configuration.getChild("content-type"); + Configuration rootConfiguration = getRootConfiguration(configuration); - String extendedCTypes = contentTypeConfiguration.getAttribute("extends", null); - List<String> superTypeIds = new ArrayList<String>(); - if (extendedCTypes != null) - { - String[] superIds = extendedCTypes.split(","); - for (String id : superIds) - { - superTypeIds.add(StringUtils.trim(id)); - } - } + _abstract = rootConfiguration.getAttributeAsBoolean("abstract", false); - _superTypeIds = superTypeIds.toArray(new String[superTypeIds.size()]); - _label = _parseI18nizableText(contentTypeConfiguration, "label"); - _description = _parseI18nizableText(contentTypeConfiguration, "description"); - _defaultTitle = _parseI18nizableText(contentTypeConfiguration, "default-title"); - _category = _parseI18nizableText(contentTypeConfiguration, "category"); + _configureSuperTypes(rootConfiguration); - _smallIcon = _parseIcon(contentTypeConfiguration.getChild("icons"), "small"); - _mediumIcon = _parseIcon(contentTypeConfiguration.getChild("icons"), "medium"); - _largeIcon = _parseIcon(contentTypeConfiguration.getChild("icons"), "large"); - _right = contentTypeConfiguration.getChild("right").getValue(null); - _abstract = contentTypeConfiguration.getAttributeAsBoolean("abstract", false); + _configureLabels(rootConfiguration); + _configureIcons(rootConfiguration); - Configuration overridenConf = null; - File ctFile = new File(_cocoonContext.getRealPath("WEB-INF/param/content-types/_override/" + _id + ".xml")); - - if (ctFile.exists()) - { - InputStream is = null; - - try - { - is = new FileInputStream(ctFile); - overridenConf = new DefaultConfigurationBuilder(true).build(is); - } - catch (Exception ex) - { - throw new ConfigurationException("Unable to parse overriden configuration for content type '" + _id + "' at WEB-INF/param/content-types/_override/" + _id + ".xml", overridenConf, ex); - } - finally - { - IOUtils.closeQuietly(is); - } - } - - List<ContentType> superTypes = new ArrayList<ContentType>(); + // Tags + _tags = new HashSet<String>(); - // Retrieve the super types, if applicable. - // TODO CMS-5262 Check super ctypes can be concatenated - for (String superTypeId : _superTypeIds) + if (rootConfiguration.getChild("tags", false) != null) { - if (_cTypeEP.hasExtension(superTypeId)) + if (rootConfiguration.getChild("tags").getAttributeAsBoolean("inherited", false)) { - ContentType superType = _cTypeEP.getExtension(superTypeId); - - if (_abstract && !superType.isAbstract()) + // Get tags from super types + for (String superTypeId : _superTypeIds) { - String message = "The abstract content-type '" + _id + "' declares '" + superTypeId + "' as a super-type, but the super-type is not abstract. An abstract content-type can not extend a non-abstract content-type."; - throw new ConfigurationException(message, configuration); + ContentType superType = _cTypeEP.getExtension(superTypeId); + _tags.addAll(superType.getTags()); } - - superTypes.add(superType); } - else - { - String message = "The content-type '" + _id + "' declares '" + superTypeId + "' as a super-type, but it doesn't exist or isn't properly initialized."; - throw new ConfigurationException(message, configuration); - } + _tags.addAll(_parseTags (rootConfiguration.getChild("tags"))); } - // Tags - if (contentTypeConfiguration.getChild("tags", false) != null) - { - _tags = _parseTags (contentTypeConfiguration.getChild("tags")); - } - else - { - _tags = new HashSet<String>(); - - // Get tags from super type if they are not overridden - for (String superTypeId : _superTypeIds) - { - ContentType superType = _cTypeEP.getExtension(superTypeId); - _tags.addAll(superType.getTags()); - } - } + // Rights + _right = rootConfiguration.getChild("right").getValue(null); // Metadata definitions - _configureMetadataDefinitions (contentTypeConfiguration, overridenConf); + _configureMetadataDefinitions (rootConfiguration); // Metadata sets - _configureMetadataSets (contentTypeConfiguration, overridenConf); + _configureMetadataSets (rootConfiguration); } /** * Configure metadata definitions * @param mainConfig The content type configuration - * @param overriddenConfig The overridden configuration * @throws ConfigurationException */ @SuppressWarnings("unchecked") - protected void _configureMetadataDefinitions (Configuration mainConfig, Configuration overriddenConfig) throws ConfigurationException + protected void _configureMetadataDefinitions (Configuration mainConfig) throws ConfigurationException { MetadataAndRepeaterDefinitionParser defParser = new MetadataAndRepeaterDefinitionParser(_enumeratorManager, _validatorManager); @@ -343,6 +264,7 @@ _getApplicableMetadata(mainConfig, metadataConfiguration, false); + Configuration overriddenConfig = getOverridenConfiguration(); if (overriddenConfig != null) { _getApplicableMetadata(overriddenConfig, metadataConfiguration, true); @@ -392,35 +314,6 @@ } /** - * Configure metadata sets - * @param mainConfig The content type configuration - * @param overriddenConfig The overridden configuration - * @throws ConfigurationException - */ - protected void _configureMetadataSets (Configuration mainConfig, Configuration overriddenConfig) throws ConfigurationException - { - // First, fill metadata-set from super types - _allMetadataSetsForView.putAll(_contentTypesHelper.getMetadataSetsForView(_superTypeIds, true)); - _allMetadataSetsForEdition.putAll(_contentTypesHelper.getMetadataSetsForEdition(_superTypeIds, true)); - _metadataSetsForView.putAll(_contentTypesHelper.getMetadataSetsForView(_superTypeIds, false)); - _metadataSetsForEdition.putAll(_contentTypesHelper.getMetadataSetsForEdition(_superTypeIds, false)); - - Map<String, Configuration> metadataSetViewConfs = new LinkedHashMap<String, Configuration>(); - Map<String, Configuration> metadataSetEditConfs = new LinkedHashMap<String, Configuration>(); - - _getApplicableMetadataSets(mainConfig, metadataSetViewConfs, metadataSetEditConfs, false); - - if (overriddenConfig != null) - { - _getApplicableMetadataSets(overriddenConfig, metadataSetViewConfs, metadataSetEditConfs, true); - } - - // Then parse own metadata sets - _parseMetadataSets(metadataSetViewConfs, _metadataSetsForView, _allMetadataSetsForView, _superTypeIds); - _parseMetadataSets(metadataSetEditConfs, _metadataSetsForEdition, _allMetadataSetsForEdition, _superTypeIds); - } - - /** * Fill a map of the applicable metadata configurations. * @param config the content type configuration. * @param metadataConfigurations the Map of metadata {@link Configuration}, indexed by name. @@ -550,7 +443,6 @@ Configuration configuration = new DefaultConfigurationBuilder(true).build(is); MetadataDefinition metadataDefinition = new MetadataDefinition(); - metadataDefinition.setReferenceContentType(_id); metadataDefinition.setId("/dc"); // FIXME ? metadataDefinition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_LABEL")); metadataDefinition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_DESC")); @@ -565,7 +457,6 @@ { MetadataDefinition metaDef = defParser.parseParameter(_manager, _pluginName, childConfiguration); String metadataName = metaDef.getName(); - metadataDefinition.setReferenceContentType(_id); metaDef.setId("/dc/" + metadataName); // FIXME ? if (metaDef.getEnumerator() == null && _dcProvider.isEnumerated(metadataName)) @@ -611,124 +502,6 @@ } /** - * Compute the applicable metadata-sets from their configurations. - * @param config The content type configuration - * @param metadataSetViewConfs the view metadata-set configurations, indexed by name. - * @param metadataSetEditConfs the edition metadata-set configurations, indexed by name. - * @param allowOverride if true, encountering a metadata-set which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. - * @throws ConfigurationException if the configuration is invalid - */ - protected void _getApplicableMetadataSets(Configuration config, Map<String, Configuration> metadataSetViewConfs, Map<String, Configuration> metadataSetEditConfs, boolean allowOverride) throws ConfigurationException - { - for (Configuration metadataSetConfig : config.getChildren("metadata-set")) - { - String name = metadataSetConfig.getAttribute("name"); - String type = metadataSetConfig.getAttribute("type"); - - if (type.equals("view")) - { - if (!allowOverride && metadataSetViewConfs.containsKey(name)) - { - throw new ConfigurationException("The view metadata-set '" + name + "' is already defined.", metadataSetConfig); - } - metadataSetViewConfs.put(name, metadataSetConfig); - } - else if (type.equals("edition")) - { - if (!allowOverride && metadataSetEditConfs.containsKey(name)) - { - throw new ConfigurationException("The edition metadata-set '" + name + "' is already defined.", metadataSetConfig); - } - metadataSetEditConfs.put(name, metadataSetConfig); - } - else - { - throw new ConfigurationException("Invalid type '" + type + "' for metadata set '" + name + "'", metadataSetConfig); - } - } - } - - /** - * Parse the metadata-set configurations - * @param metadataSetConfs the metadata-set configurations, indexed by name. - * @param metadataSets the Map of "public" {@link MetadataSet} objects to fill, indexed by name - * @param allMetadataSets the Map of {@link MetadataSet} objects to fill (including internal ones), indexed by name. - * @param superTypeIds the super content-types - * @throws ConfigurationException if the configuration is invalid - */ - protected void _parseMetadataSets(Map<String, Configuration> metadataSetConfs, Map<String, MetadataSet> metadataSets, Map<String, MetadataSet> allMetadataSets, String[] superTypeIds) throws ConfigurationException - { - for (Configuration metadataSetConfig : metadataSetConfs.values()) - { - String name = metadataSetConfig.getAttribute("name"); - String type = metadataSetConfig.getAttribute("type"); - boolean isInternal = metadataSetConfig.getAttributeAsBoolean("internal", false); - boolean isEdition = type.equals("edition"); - - MetadataSet metadataSet = _parseMetadataSet(metadataSetConfig, name, isEdition, isInternal, superTypeIds); - - allMetadataSets.put(name, metadataSet); - - if (!isInternal) - { - metadataSets.put(name, metadataSet); - } - } - } - - /** - * Parse an i18n text. - * @param config the configuration to use. - * @param name the child name. - * @return the i18n text. - * @throws ConfigurationException if the configuration is not valid. - */ - protected I18nizableText _parseI18nizableText(Configuration config, String name) throws ConfigurationException - { - return _parseI18nizableText(config, name, ""); - } - - /** - * Parse an i18n text. - * @param config the configuration to use. - * @param name the child name. - * @param defaultValue the default value if no present - * @return the i18n text. - * @throws ConfigurationException if the configuration is not valid. - */ - protected I18nizableText _parseI18nizableText(Configuration config, String name, String defaultValue) throws ConfigurationException - { - Configuration textConfig = config.getChild(name); - boolean i18nSupported = textConfig.getAttributeAsBoolean("i18n", false); - String text = textConfig.getValue(defaultValue); - - if (i18nSupported) - { - String catalogue = textConfig.getAttribute("catalogue", null); - - if (catalogue == null) - { - catalogue = _getDefaultCatalogue(); - } - - return new I18nizableText(catalogue, text); - } - else - { - return new I18nizableText(text); - } - } - - /** - * Returns the default i18n catalogue for this content type. - * @return the default i18n catalogue for this content type. - */ - protected String _getDefaultCatalogue() - { - return "plugin." + _pluginName; - } - - /** * Parse the tags * @param configuration the configuration to use * @return the tags @@ -747,301 +520,6 @@ return tags; } - - /** - * Parse an icon path - * @param configuration the configuration to use - * @param name the child name. - * @return The icon path - * @throws ConfigurationException if the configuration is not valid. - */ - protected String _parseIcon (Configuration configuration, String name) throws ConfigurationException - { - return _parseIcon(configuration, name, "/plugins/cms/resources/img/contenttype/unknown-" + name + ".png"); - } - - /** - * Parse an icon path - * @param configuration the configuration to use - * @param name the child name. - * @param defaultValue the default value. - * @return The icon path - * @throws ConfigurationException if the configuration is not valid. - */ - protected String _parseIcon(Configuration configuration, String name, String defaultValue) throws ConfigurationException - { - Configuration iconConfig = configuration.getChild(name, false); - if (iconConfig != null) - { - String pluginName = iconConfig.getAttribute("plugin", _pluginName); - return _getIconPath(pluginName) + iconConfig.getValue(); - } - - return defaultValue; - } - - /** - * Returns the path for icons - * @param pluginName the configured plugin - * @return the path for icons - */ - protected String _getIconPath(String pluginName) - { - return "/plugins/" + pluginName + "/resources/"; - } - - /** - * Parse a metadata-set configuration to create a {@link MetadataSet} object. - * @param metadataSetConfig the metadata set configuration to use. - * @param metadataSetName the metadata set name. - * @param isEdition the metadata set edition status. - * @param isInternal the metadata internal status. - * @param superTypeIds the super types. - * @return the metadata set - * @throws ConfigurationException if the configuration is not valid. - */ - protected MetadataSet _parseMetadataSet(Configuration metadataSetConfig, String metadataSetName, boolean isEdition, boolean isInternal, String[] superTypeIds) throws ConfigurationException - { - MetadataSet metadataSet = new MetadataSet(); - - I18nizableText label = _parseI18nizableText(metadataSetConfig, "label", metadataSetName); - I18nizableText description = _parseI18nizableText(metadataSetConfig, "description"); - - Configuration iconConf = metadataSetConfig.getChild("icons"); - - String smallIcon = _parseMetadataSetIcon(iconConf, "small", metadataSetName); - String mediumIcon = _parseMetadataSetIcon(iconConf, "medium", metadataSetName); - String largeIcon = _parseMetadataSetIcon(iconConf, "large", metadataSetName); - - metadataSet.setName(metadataSetName); - metadataSet.setLabel(label); - metadataSet.setDescription(description); - metadataSet.setSmallIcon(smallIcon); - metadataSet.setMediumIcon(mediumIcon); - metadataSet.setLargeIcon(largeIcon); - metadataSet.setEdition(isEdition); - - MetadataSet superMetadataSet = null; - if (superTypeIds.length > 0) - { - if (isEdition) - { - superMetadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, superTypeIds); - } - else - { - superMetadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, superTypeIds); - } - } - - _fillMetadataSetElement(metadataSet, metadataSetConfig, metadataSet, isEdition, superMetadataSet); - - return metadataSet; - } - - /** - * Parse a metadata set icon path. - * @param configuration the configuration to use. - * @param size the icon size (large, medium or small). - * @param metadataSetName the metadata set name. - * @return The icon path. - * @throws ConfigurationException if the configuration is not valid. - */ - protected String _parseMetadataSetIcon(Configuration configuration, String size, String metadataSetName) throws ConfigurationException - { - String defaultMetadataSetName = "unknown"; - - if ("main".equals(metadataSetName) || "abstract".equals(metadataSetName) || "link".equals(metadataSetName)) - { - defaultMetadataSetName = metadataSetName; - } - - String defaultPath = "/plugins/cms/resources/img/contenttype/metadataset/default-" + defaultMetadataSetName + "-" + size + ".png"; - - return _parseIcon(configuration, size, defaultPath); - } - - /** - * Parse the DublinCore metadata set. - * @param metadataSetElement the metadata set element to fill. - * @throws ConfigurationException if the configuration is not valid. - */ - protected void _fillMetadataSetDublinCore(AbstractMetadataSetElement metadataSetElement) throws ConfigurationException - { - MetadataDefinitionReference dcMetaDefRef = new MetadataDefinitionReference("dc"); - - Source src = null; - InputStream is = null; - - try - { - src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); - - if (src.exists()) - { - is = src.getInputStream(); - Configuration configuration = new DefaultConfigurationBuilder(true).build(is); - - Configuration metadataSetConf = configuration.getChild("metadata-set"); - - for (Configuration childConfiguration : metadataSetConf.getChildren("metadata-ref")) - { - dcMetaDefRef.addElement(new MetadataDefinitionReference(childConfiguration.getAttribute("name"))); - } - - IOUtils.closeQuietly(is); - } - } - catch (IOException e) - { - throw new ConfigurationException("Unable to parse Dublin Core metadata set", e); - } - catch (SAXException e) - { - throw new ConfigurationException("Unable to parse Dublin Core metadata set", e); - } - finally - { - if (src != null) - { - _srcResolver.release(src); - } - } - - metadataSetElement.addElement(dcMetaDefRef); - } - - /** - * Fill child elements for a metadata set element. - * @param rootMetadataSet The root metadata set - * @param metadataSetConfig the metadata set configuration to use. - * @param metadataSetElement the metadata set element to fill. - * @param isEdition true if it is the edition mode - * @param superMetadataSet the overridden metadata-set if applicable, null otherwise. - * @throws ConfigurationException if the configuration is not valid. - */ - protected void _fillMetadataSetElement(MetadataSet rootMetadataSet, Configuration metadataSetConfig, AbstractMetadataSetElement metadataSetElement, boolean isEdition, MetadataSet superMetadataSet) throws ConfigurationException - { - for (Configuration elementConfig : metadataSetConfig.getChildren()) - { - String elementConfigName = elementConfig.getName(); - - if (elementConfigName.equals("metadata-ref")) - { - String metadataName = elementConfig.getAttribute("name"); - String metadataSetName = elementConfig.getAttribute("metadata-set", null); - - MetadataDefinitionReference metadataDefRef = new MetadataDefinitionReference(metadataName, metadataSetName); - - if ("metadata-set".equals(metadataSetConfig.getName()) && rootMetadataSet.hasMetadataDefinitionReference(metadataName)) - { - throw new ConfigurationException("The metadata '" + metadataName + "' is already referenced by a super metadata-set or by the metadata-set '" + rootMetadataSet.getName() + "' itself.", elementConfig); - } - metadataSetElement.addElement(metadataDefRef); - - _fillMetadataSetElement(rootMetadataSet, elementConfig, metadataDefRef, isEdition, superMetadataSet); - } - else if (elementConfigName.equals("fieldset")) - { - Fieldset fieldset = new Fieldset(); - fieldset.setRole(elementConfig.getAttribute("role", "tab")); - fieldset.setLabel(_parseI18nizableText(elementConfig, "label")); - - _fillMetadataSetElement(rootMetadataSet, elementConfig, fieldset, isEdition, superMetadataSet); - - metadataSetElement.addElement(fieldset); - } - else if (elementConfigName.equals("dublin-core")) - { - _fillMetadataSetDublinCore(metadataSetElement); - } - else if (elementConfigName.equals("include")) - { - String supertype = elementConfig.getAttribute("from-supertype"); - if ("true".equals(supertype)) - { - if (superMetadataSet == null) - { - throw new ConfigurationException("The metadata-set element includes the super metadata-set without its content type referencing a super-type.", elementConfig); - } - - _contentTypesHelper.copyMetadataSetElementsIfNotExist(superMetadataSet, metadataSetElement); - } - else - { - ContentType superType = _cTypeEP.getExtension(supertype); - if (superType == null) - { - throw new ConfigurationException("The metadata-set element includes the super metadata-set of an unknown super-type '" + supertype + "'.", elementConfig); - } - - MetadataSet metadataSetToInclude = isEdition ? superType.getMetadataSetForEdition(rootMetadataSet.getName()) : superType.getMetadataSetForEdition(rootMetadataSet.getName()); - _contentTypesHelper.copyMetadataSetElementsIfNotExist(metadataSetToInclude, metadataSetElement); - } - } - } - } - - @Override - public String getId() - { - return _id; - } - - @Override - public String getPluginName() - { - return _pluginName; - } - - @Override - public I18nizableText getLabel() - { - return _label; - } - - @Override - public I18nizableText getDescription() - { - return _description; - } - - @Override - public I18nizableText getDefaultTitle() - { - return _defaultTitle; - } - - @Override - public String[] getSupertypeIds() - { - return _superTypeIds; - } - - @Override - public I18nizableText getCategory() - { - return _category; - } - - @Override - public String getSmallIcon() - { - return _smallIcon; - } - - @Override - public String getMediumIcon() - { - return _mediumIcon; - } - - @Override - public String getLargeIcon() - { - return _largeIcon; - } - @Override public Validator getGlobalValidator() { @@ -1085,41 +563,23 @@ } @Override - public Set<String> getEditionMetadataSetNames(boolean includeInternal) - { - if (includeInternal) - { - return Collections.unmodifiableSet(_allMetadataSetsForEdition.keySet()); - } - else - { - return Collections.unmodifiableSet(_metadataSetsForEdition.keySet()); - } - } - - @Override - public Set<String> getViewMetadataSetNames(boolean includeInternal) + public MetadataDefinition getMetadataDefinitionByPath (String metadataPath) { - if (includeInternal) + String[] pathSegments = metadataPath.split("/+"); + + if (pathSegments.length == 0) { - return Collections.unmodifiableSet(_allMetadataSetsForView.keySet()); + return null; } - else + + MetadataDefinition metadataDef = _metadata.get(pathSegments[0]); + + for (int i = 1; i < pathSegments.length && metadataDef != null; i++) { - return Collections.unmodifiableSet(_metadataSetsForView.keySet()); + metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); } - } - @Override - public MetadataSet getMetadataSetForView(String metadataSetName) - { - return _allMetadataSetsForView.get(metadataSetName); - } - - @Override - public MetadataSet getMetadataSetForEdition(String metadataSetName) - { - return _allMetadataSetsForEdition.get(metadataSetName); + return metadataDef; } @Override @@ -1223,6 +683,12 @@ } @Override + public boolean isMixin() + { + return hasTag(TAG_MIXIN); + } + + @Override public String getRight() { return _right; @@ -1235,26 +701,6 @@ } - @Override - public MetadataDefinition getMetadataDefinitionByPath(String metadataPath) - { - String[] pathSegments = metadataPath.split("/+"); - - if (pathSegments.length == 0) - { - return null; - } - - MetadataDefinition metadataDef = _metadata.get(pathSegments[0]); - - for (int i = 1; i < pathSegments.length && metadataDef != null; i++) - { - metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); - } - - return metadataDef; - } - /** * Retrieves the restrictions for a given path. * @param metadataPath the metadata path. @@ -1262,19 +708,7 @@ */ protected Restrictions _getRestrictionsForPath(String metadataPath) { - String[] pathSegments = metadataPath.split("/+"); - - if (pathSegments.length == 0) - { - return null; - } - - MetadataDefinition metadataDef = _metadata.get(pathSegments[0]); - - for (int i = 1; i < pathSegments.length && metadataDef != null; i++) - { - metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); - } + MetadataDefinition metadataDef = getMetadataDefinitionByPath (metadataPath); if (metadataDef != null && metadataDef instanceof RestrictedDefinition) { @@ -1539,7 +973,6 @@ super._additionalParsing(manager, pluginName, metadataConfiguration, metadataId, metadataDefinition); String metadataName = metadataConfiguration.getAttribute("name"); - metadataDefinition.setReferenceContentType(_id); metadataDefinition.setName(metadataName); metadataDefinition.setMultiple(metadataConfiguration.getAttributeAsBoolean("multiple", false)); // Use default transformer (docbook) Index: main/plugin-cms/src/org/ametys/cms/contenttype/ContentType.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/ContentType.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/contenttype/ContentType.java (working copy) @@ -22,13 +22,12 @@ import org.ametys.cms.repository.Content; import org.ametys.plugins.repository.AmetysRepositoryException; -import org.ametys.runtime.util.I18nizableText; import org.ametys.runtime.util.parameter.Validator; /** * This class represents a type of content. */ -public interface ContentType +public interface ContentType extends ContentTypeDescriptor { /** Tag for private content type */ public static final String TAG_PRIVATE = "private"; @@ -36,67 +35,10 @@ /** Tag for simple content type */ public static final String TAG_SIMPLE = "simple"; - /** - * Retrieves the id of the content type. - * @return the id. - */ - String getId(); - - /** - * Retrieves the name of the plugin declaring this content type. - * @return the name of the plugin. - */ - String getPluginName(); - - /** - * Retrieves the label of the content type. - * @return the label. - */ - I18nizableText getLabel(); - - /** - * Retrieves the description of the content type. - * @return the description. - */ - I18nizableText getDescription(); - - /** - * Retrieves the default title of the content type. - * @return the default title. - */ - I18nizableText getDefaultTitle(); - - /** - * Retrieves the category of the content type. - * @return the category. - */ - I18nizableText getCategory(); - - /** - * Retrieves the super type's ids. - * @return the super type's ids, or empty if this content type doesn't extend a specific content type. - */ - String[] getSupertypeIds(); + /** Tag for simple content type */ + public static final String TAG_MIXIN = "mixin"; /** - * Retrieves the URL of the icon without the context path. - * @return the icon URL for the small image 16x16. - */ - String getSmallIcon(); - - /** - * Retrieves the URL of the icon without the context path. - * @return the icon URL for the medium sized image 32x32. - */ - String getMediumIcon(); - - /** - * Retrieves the URL of the icon without the context path. - * @return the icon URL for the large image 48x48. - */ - String getLargeIcon(); - - /** * Retrieves the potential global validator. * @return the global validator or <code>null</code> if none. */ @@ -127,9 +69,9 @@ Set<String> getMetadataNames(); /** - * Retrieves the definition of a given metadata. + * Retrieves the definition of a given metadata by its name * @param metadataName the metadata name. - * @return the metadata definition. + * @return the metadata definition or <code>null</code> if not found */ MetadataDefinition getMetadataDefinition(String metadataName); @@ -148,34 +90,6 @@ boolean hasMetadataDefinition(String metadataName); /** - * Returns all names of "view" metadataSets. - * @param includeInternal if the result should include internal metadataSets. - * @return all names of "view" metadataSets. - */ - Set<String> getViewMetadataSetNames(boolean includeInternal); - - /** - * Returns all names of "edition" metadataSets. - * @param includeInternal if the result should include internal metadataSets. - * @return all names of "edition" metadataSets. - */ - Set<String> getEditionMetadataSetNames(boolean includeInternal); - - /** - * Retrieves the metadata set name for view. - * @param metadataSetName the metadata set name. - * @return the metadata definition. - */ - MetadataSet getMetadataSetForView(String metadataSetName); - - /** - * Retrieves the metadata set name for edition. - * @param metadataSetName the metadata set name. - * @return the metadata set. - */ - MetadataSet getMetadataSetForEdition(String metadataSetName); - - /** * Determine whether a metadata can be read at this time. * @param metadataPath the path to the given metadata. * @param content The content where metadata is to be read on. @@ -230,6 +144,12 @@ public boolean isAbstract(); /** + * Get whether the content type is a mixin, i.e. should used to add metadata to a existing content. + * @return true if the content is mixin, false otherwise. + */ + public boolean isMixin(); + + /** * Get the right needed to create a content of this type. * @return the right needed to create a content of this type. If null is returned, no right is needed. */ Index: main/plugin-cms/src/org/ametys/cms/contenttype/AbstractContentTypeDescriptor.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/contenttype/AbstractContentTypeDescriptor.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/contenttype/AbstractContentTypeDescriptor.java (revision 0) @@ -0,0 +1,658 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.contenttype; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.avalon.framework.service.Serviceable; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.excalibur.source.Source; +import org.apache.excalibur.source.SourceResolver; +import org.xml.sax.SAXException; + +import org.ametys.plugins.explorer.dublincore.DublinCoreMetadataProvider; +import org.ametys.runtime.plugin.component.PluginAware; +import org.ametys.runtime.util.I18nizableText; + +/** + * This abstract class represents a content type descriptor + */ +public abstract class AbstractContentTypeDescriptor extends AbstractLogEnabled implements ContentTypeDescriptor, Configurable, PluginAware, Serviceable +{ + /** Plugin name. */ + protected String _pluginName; + + /** Content type id. */ + protected String _id; + /** Label. */ + protected I18nizableText _label; + /** Description. */ + protected I18nizableText _description; + /** Default title. */ + protected I18nizableText _defaultTitle; + /** Category. */ + protected I18nizableText _category; + /** Small icon URI 16x16. */ + protected String _smallIcon; + /** Medium icon URI 32x32. */ + protected String _mediumIcon; + /** Large icon URI 48x48. */ + protected String _largeIcon; + + /** This content-type's supertype ids (can be empty). */ + protected String[] _superTypeIds; + /** All metadata sets for view. */ + protected Map<String, MetadataSet> _allMetadataSetsForView = new LinkedHashMap<String, MetadataSet>(); + /** Non-internal metadata sets for view. */ + protected Map<String, MetadataSet> _metadataSetsForView = new LinkedHashMap<String, MetadataSet>(); + /** All metadata sets for edition. */ + protected Map<String, MetadataSet> _allMetadataSetsForEdition = new LinkedHashMap<String, MetadataSet>(); + /** Non-internal metadata sets for edition. */ + protected Map<String, MetadataSet> _metadataSetsForEdition = new LinkedHashMap<String, MetadataSet>(); + + /** The content type extension point. */ + protected ContentTypeExtensionPoint _cTypeEP; + /** The content types helper */ + protected ContentTypesHelper _contentTypesHelper; + /** The source resolver */ + protected SourceResolver _srcResolver; + /** The DublinCore metadata provider */ + protected DublinCoreMetadataProvider _dcProvider; + + @Override + public void service(ServiceManager smanager) throws ServiceException + { + _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); + _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); + _dcProvider = (DublinCoreMetadataProvider) smanager.lookup(DublinCoreMetadataProvider.ROLE); + _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + } + + public void setPluginInfo(String pluginName, String featureName) + { + _pluginName = pluginName; + } + + /** + * Get the root configuration + * @param configuration The configuration + * @return The main configuration + */ + protected abstract Configuration getRootConfiguration (Configuration configuration); + + /** + * Get the overriden configuration + * @return the overriden configuration or null + * @throws ConfigurationException + */ + protected abstract Configuration getOverridenConfiguration() throws ConfigurationException; + + @Override + public void configure(Configuration configuration) throws ConfigurationException + { + _id = configuration.getAttribute("id"); + + Configuration rootConfiguration = getRootConfiguration(configuration); + _configureSuperTypes(rootConfiguration); + + _configureLabels(rootConfiguration); + _configureIcons(rootConfiguration); + + // Metadata sets + _configureMetadataSets (rootConfiguration); + } + + /** + * Configures the super types + * @param mainConfig The main configuration + */ + protected void _configureSuperTypes (Configuration mainConfig) + { + String extendedCTypes = mainConfig.getAttribute("extends", null); + List<String> superTypeIds = new ArrayList<String>(); + if (extendedCTypes != null) + { + String[] superIds = extendedCTypes.split(","); + for (String id : superIds) + { + superTypeIds.add(StringUtils.trim(id)); + } + } + + _superTypeIds = superTypeIds.toArray(new String[superTypeIds.size()]); + } + + /** + * Configures the labels + * @param mainConfig The main configuration + * @throws ConfigurationException + */ + protected void _configureLabels (Configuration mainConfig) throws ConfigurationException + { + _label = _parseI18nizableText(mainConfig, "label"); + _description = _parseI18nizableText(mainConfig, "description"); + _defaultTitle = _parseI18nizableText(mainConfig, "default-title"); + _category = _parseI18nizableText(mainConfig, "category"); + } + + /** + * Configures the icons + * @param mainConfig The main configuration + * @throws ConfigurationException + */ + protected void _configureIcons (Configuration mainConfig) throws ConfigurationException + { + _smallIcon = _parseIcon(mainConfig.getChild("icons"), "small"); + _mediumIcon = _parseIcon(mainConfig.getChild("icons"), "medium"); + _largeIcon = _parseIcon(mainConfig.getChild("icons"), "large"); + } + + /** + * Configure metadata sets + * @param mainConfig The content type configuration + * @throws ConfigurationException + */ + protected void _configureMetadataSets (Configuration mainConfig) throws ConfigurationException + { + // First, fill metadata-set from super types + _allMetadataSetsForView.putAll(_contentTypesHelper.getMetadataSetsForView(_superTypeIds, new String[0], true)); + _allMetadataSetsForEdition.putAll(_contentTypesHelper.getMetadataSetsForEdition(_superTypeIds, new String[0], true)); + _metadataSetsForView.putAll(_contentTypesHelper.getMetadataSetsForView(_superTypeIds, new String[0], false)); + _metadataSetsForEdition.putAll(_contentTypesHelper.getMetadataSetsForEdition(_superTypeIds, new String[0], false)); + + Map<String, Configuration> metadataSetViewConfs = new LinkedHashMap<String, Configuration>(); + Map<String, Configuration> metadataSetEditConfs = new LinkedHashMap<String, Configuration>(); + + _getApplicableMetadataSets(mainConfig, metadataSetViewConfs, metadataSetEditConfs, false); + + Configuration overriddenConfig = getOverridenConfiguration(); + if (overriddenConfig != null) + { + _getApplicableMetadataSets(overriddenConfig, metadataSetViewConfs, metadataSetEditConfs, true); + } + + // Then parse own metadata sets + _parseMetadataSets(metadataSetViewConfs, _metadataSetsForView, _allMetadataSetsForView, _superTypeIds); + _parseMetadataSets(metadataSetEditConfs, _metadataSetsForEdition, _allMetadataSetsForEdition, _superTypeIds); + } + + /** + * Compute the applicable metadata-sets from their configurations. + * @param config The content type configuration + * @param metadataSetViewConfs the view metadata-set configurations, indexed by name. + * @param metadataSetEditConfs the edition metadata-set configurations, indexed by name. + * @param allowOverride if true, encountering a metadata-set which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. + * @throws ConfigurationException if the configuration is invalid + */ + protected void _getApplicableMetadataSets(Configuration config, Map<String, Configuration> metadataSetViewConfs, Map<String, Configuration> metadataSetEditConfs, boolean allowOverride) throws ConfigurationException + { + for (Configuration metadataSetConfig : config.getChildren("metadata-set")) + { + String name = metadataSetConfig.getAttribute("name"); + String type = metadataSetConfig.getAttribute("type"); + + if (type.equals("view")) + { + if (!allowOverride && metadataSetViewConfs.containsKey(name)) + { + throw new ConfigurationException("The view metadata-set '" + name + "' is already defined.", metadataSetConfig); + } + metadataSetViewConfs.put(name, metadataSetConfig); + } + else if (type.equals("edition")) + { + if (!allowOverride && metadataSetEditConfs.containsKey(name)) + { + throw new ConfigurationException("The edition metadata-set '" + name + "' is already defined.", metadataSetConfig); + } + metadataSetEditConfs.put(name, metadataSetConfig); + } + else + { + throw new ConfigurationException("Invalid type '" + type + "' for metadata set '" + name + "'", metadataSetConfig); + } + } + } + + /** + * Parse the metadata-set configurations + * @param metadataSetConfs the metadata-set configurations, indexed by name. + * @param metadataSets the Map of "public" {@link MetadataSet} objects to fill, indexed by name + * @param allMetadataSets the Map of {@link MetadataSet} objects to fill (including internal ones), indexed by name. + * @param superTypeIds the super content-types + * @throws ConfigurationException if the configuration is invalid + */ + protected void _parseMetadataSets(Map<String, Configuration> metadataSetConfs, Map<String, MetadataSet> metadataSets, Map<String, MetadataSet> allMetadataSets, String[] superTypeIds) throws ConfigurationException + { + for (Configuration metadataSetConfig : metadataSetConfs.values()) + { + String name = metadataSetConfig.getAttribute("name"); + String type = metadataSetConfig.getAttribute("type"); + boolean isInternal = metadataSetConfig.getAttributeAsBoolean("internal", false); + boolean isEdition = type.equals("edition"); + + MetadataSet metadataSet = _parseMetadataSet(metadataSetConfig, name, isEdition, isInternal, superTypeIds); + + allMetadataSets.put(name, metadataSet); + + if (!isInternal) + { + metadataSets.put(name, metadataSet); + } + } + } + + /** + * Parse a metadata-set configuration to create a {@link MetadataSet} object. + * @param metadataSetConfig the metadata set configuration to use. + * @param metadataSetName the metadata set name. + * @param isEdition the metadata set edition status. + * @param isInternal the metadata internal status. + * @param superTypeIds the super types. + * @return the metadata set + * @throws ConfigurationException if the configuration is not valid. + */ + protected MetadataSet _parseMetadataSet(Configuration metadataSetConfig, String metadataSetName, boolean isEdition, boolean isInternal, String[] superTypeIds) throws ConfigurationException + { + MetadataSet metadataSet = new MetadataSet(); + + I18nizableText label = _parseI18nizableText(metadataSetConfig, "label", metadataSetName); + I18nizableText description = _parseI18nizableText(metadataSetConfig, "description"); + + Configuration iconConf = metadataSetConfig.getChild("icons"); + + String smallIcon = _parseMetadataSetIcon(iconConf, "small", metadataSetName); + String mediumIcon = _parseMetadataSetIcon(iconConf, "medium", metadataSetName); + String largeIcon = _parseMetadataSetIcon(iconConf, "large", metadataSetName); + + metadataSet.setName(metadataSetName); + metadataSet.setLabel(label); + metadataSet.setDescription(description); + metadataSet.setSmallIcon(smallIcon); + metadataSet.setMediumIcon(mediumIcon); + metadataSet.setLargeIcon(largeIcon); + metadataSet.setEdition(isEdition); + + MetadataSet superMetadataSet = null; + if (superTypeIds.length > 0) + { + if (isEdition) + { + superMetadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, superTypeIds, new String[0]); + } + else + { + superMetadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, superTypeIds, new String[0]); + } + } + + _fillMetadataSetElement(metadataSet, metadataSetConfig, metadataSet, isEdition, superMetadataSet); + + return metadataSet; + } + + /** + * Parse a metadata set icon path. + * @param configuration the configuration to use. + * @param size the icon size (large, medium or small). + * @param metadataSetName the metadata set name. + * @return The icon path. + * @throws ConfigurationException if the configuration is not valid. + */ + protected String _parseMetadataSetIcon(Configuration configuration, String size, String metadataSetName) throws ConfigurationException + { + String defaultMetadataSetName = "unknown"; + + if ("main".equals(metadataSetName) || "abstract".equals(metadataSetName) || "link".equals(metadataSetName)) + { + defaultMetadataSetName = metadataSetName; + } + + String defaultPath = "/plugins/cms/resources/img/contenttype/metadataset/default-" + defaultMetadataSetName + "-" + size + ".png"; + + return _parseIcon(configuration, size, defaultPath); + } + + + /** + * Fill child elements for a metadata set element. + * @param rootMetadataSet The root metadata set + * @param metadataSetConfig the metadata set configuration to use. + * @param metadataSetElement the metadata set element to fill. + * @param isEdition true if it is the edition mode + * @param superMetadataSet the overridden metadata-set if applicable, null otherwise. + * @throws ConfigurationException if the configuration is not valid. + */ + protected void _fillMetadataSetElement(MetadataSet rootMetadataSet, Configuration metadataSetConfig, AbstractMetadataSetElement metadataSetElement, boolean isEdition, MetadataSet superMetadataSet) throws ConfigurationException + { + for (Configuration elementConfig : metadataSetConfig.getChildren()) + { + String elementConfigName = elementConfig.getName(); + + if (elementConfigName.equals("metadata-ref")) + { + String metadataName = elementConfig.getAttribute("name"); + String metadataSetName = elementConfig.getAttribute("metadata-set", null); + + MetadataDefinitionReference metadataDefRef = new MetadataDefinitionReference(metadataName, metadataSetName); + + if ("metadata-set".equals(metadataSetConfig.getName()) && rootMetadataSet.hasMetadataDefinitionReference(metadataName)) + { + throw new ConfigurationException("The metadata '" + metadataName + "' is already referenced by a super metadata-set or by the metadata-set '" + rootMetadataSet.getName() + "' itself.", elementConfig); + } + metadataSetElement.addElement(metadataDefRef); + + _fillMetadataSetElement(rootMetadataSet, elementConfig, metadataDefRef, isEdition, superMetadataSet); + } + else if (elementConfigName.equals("fieldset")) + { + Fieldset fieldset = new Fieldset(); + fieldset.setRole(elementConfig.getAttribute("role", "tab")); + fieldset.setLabel(_parseI18nizableText(elementConfig, "label")); + + _fillMetadataSetElement(rootMetadataSet, elementConfig, fieldset, isEdition, superMetadataSet); + + metadataSetElement.addElement(fieldset); + } + else if (elementConfigName.equals("dublin-core")) + { + _fillMetadataSetDublinCore(metadataSetElement); + } + else if (elementConfigName.equals("include")) + { + String supertype = elementConfig.getAttribute("from-supertype"); + if ("true".equals(supertype)) + { + if (superMetadataSet == null) + { + throw new ConfigurationException("The metadata-set element includes the super metadata-set without its content type referencing a super-type.", elementConfig); + } + + _contentTypesHelper.copyMetadataSetElementsIfNotExist(superMetadataSet, metadataSetElement); + } + else + { + ContentType superType = _cTypeEP.getExtension(supertype); + if (superType == null) + { + throw new ConfigurationException("The metadata-set element includes the super metadata-set of an unknown super-type '" + supertype + "'.", elementConfig); + } + + MetadataSet metadataSetToInclude = isEdition ? superType.getMetadataSetForEdition(rootMetadataSet.getName()) : superType.getMetadataSetForEdition(rootMetadataSet.getName()); + _contentTypesHelper.copyMetadataSetElementsIfNotExist(metadataSetToInclude, metadataSetElement); + } + } + } + } + + /** + * Parse the DublinCore metadata set. + * @param metadataSetElement the metadata set element to fill. + * @throws ConfigurationException if the configuration is not valid. + */ + protected void _fillMetadataSetDublinCore(AbstractMetadataSetElement metadataSetElement) throws ConfigurationException + { + MetadataDefinitionReference dcMetaDefRef = new MetadataDefinitionReference("dc"); + + Source src = null; + InputStream is = null; + + try + { + src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); + + if (src.exists()) + { + is = src.getInputStream(); + Configuration configuration = new DefaultConfigurationBuilder(true).build(is); + + Configuration metadataSetConf = configuration.getChild("metadata-set"); + + for (Configuration childConfiguration : metadataSetConf.getChildren("metadata-ref")) + { + dcMetaDefRef.addElement(new MetadataDefinitionReference(childConfiguration.getAttribute("name"))); + } + + IOUtils.closeQuietly(is); + } + } + catch (IOException e) + { + throw new ConfigurationException("Unable to parse Dublin Core metadata set", e); + } + catch (SAXException e) + { + throw new ConfigurationException("Unable to parse Dublin Core metadata set", e); + } + finally + { + if (src != null) + { + _srcResolver.release(src); + } + } + + metadataSetElement.addElement(dcMetaDefRef); + } + /** + * Parse an i18n text. + * @param config the configuration to use. + * @param name the child name. + * @return the i18n text. + * @throws ConfigurationException if the configuration is not valid. + */ + protected I18nizableText _parseI18nizableText(Configuration config, String name) throws ConfigurationException + { + return _parseI18nizableText(config, name, ""); + } + + /** + * Parse an i18n text. + * @param config the configuration to use. + * @param name the child name. + * @param defaultValue the default value if no present + * @return the i18n text. + * @throws ConfigurationException if the configuration is not valid. + */ + protected I18nizableText _parseI18nizableText(Configuration config, String name, String defaultValue) throws ConfigurationException + { + Configuration textConfig = config.getChild(name); + boolean i18nSupported = textConfig.getAttributeAsBoolean("i18n", false); + String text = textConfig.getValue(defaultValue); + + if (i18nSupported) + { + String catalogue = textConfig.getAttribute("catalogue", null); + + if (catalogue == null) + { + catalogue = _getDefaultCatalogue(); + } + + return new I18nizableText(catalogue, text); + } + else + { + return new I18nizableText(text); + } + } + + /** + * Returns the default i18n catalogue for this content type. + * @return the default i18n catalogue for this content type. + */ + protected String _getDefaultCatalogue() + { + return "plugin." + _pluginName; + } + + /** + * Parse an icon path + * @param configuration the configuration to use + * @param name the child name. + * @return The icon path + * @throws ConfigurationException if the configuration is not valid. + */ + protected String _parseIcon (Configuration configuration, String name) throws ConfigurationException + { + return _parseIcon(configuration, name, "/plugins/cms/resources/img/contenttype/unknown-" + name + ".png"); + } + + /** + * Parse an icon path + * @param configuration the configuration to use + * @param name the child name. + * @param defaultValue the default value. + * @return The icon path + * @throws ConfigurationException if the configuration is not valid. + */ + protected String _parseIcon(Configuration configuration, String name, String defaultValue) throws ConfigurationException + { + Configuration iconConfig = configuration.getChild(name, false); + if (iconConfig != null) + { + String pluginName = iconConfig.getAttribute("plugin", _pluginName); + return _getIconPath(pluginName) + iconConfig.getValue(); + } + + return defaultValue; + } + + /** + * Returns the path for icons + * @param pluginName the configured plugin + * @return the path for icons + */ + protected String _getIconPath(String pluginName) + { + return "/plugins/" + pluginName + "/resources/"; + } + + @Override + public String getPluginName() + { + return _pluginName; + } + + @Override + public String getId() + { + return _id; + } + + @Override + public I18nizableText getLabel() + { + return _label; + } + + @Override + public I18nizableText getDescription() + { + return _description; + } + + @Override + public I18nizableText getCategory() + { + return _category; + } + + @Override + public I18nizableText getDefaultTitle() + { + return _defaultTitle; + } + + @Override + public String getSmallIcon() + { + return _smallIcon; + } + + @Override + public String getMediumIcon() + { + return _mediumIcon; + } + + @Override + public String getLargeIcon() + { + return _largeIcon; + } + + @Override + public String[] getSupertypeIds() + { + return _superTypeIds; + } + + @Override + public Set<String> getEditionMetadataSetNames(boolean includeInternal) + { + if (includeInternal) + { + return Collections.unmodifiableSet(_allMetadataSetsForEdition.keySet()); + } + else + { + return Collections.unmodifiableSet(_metadataSetsForEdition.keySet()); + } + } + + @Override + public Set<String> getViewMetadataSetNames(boolean includeInternal) + { + if (includeInternal) + { + return Collections.unmodifiableSet(_allMetadataSetsForView.keySet()); + } + else + { + return Collections.unmodifiableSet(_metadataSetsForView.keySet()); + } + } + + @Override + public MetadataSet getMetadataSetForView(String metadataSetName) + { + return _allMetadataSetsForView.get(metadataSetName); + } + + @Override + public MetadataSet getMetadataSetForEdition(String metadataSetName) + { + return _allMetadataSetsForEdition.get(metadataSetName); + } + +} + Index: main/plugin-cms/src/org/ametys/cms/content/ConsistencyGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/ConsistencyGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/ConsistencyGenerator.java (working copy) @@ -27,6 +27,7 @@ import org.apache.cocoon.generation.ServiceableGenerator; import org.apache.cocoon.xml.AttributesImpl; import org.apache.cocoon.xml.XMLUtils; +import org.apache.commons.lang.StringUtils; import org.xml.sax.SAXException; import org.ametys.cms.repository.Content; @@ -74,7 +75,7 @@ attrs.addCDATAAttribute("title", content.getTitle()); attrs.addCDATAAttribute("name", content.getName()); attrs.addCDATAAttribute("path", content.getPath()); - attrs.addCDATAAttribute("type", content.getType()); // CMS-5261 + attrs.addCDATAAttribute("type", StringUtils.join(content.getTypes(), ',')); attrs.addCDATAAttribute("lang", content.getLanguage()); XMLUtils.startElement(contentHandler, "content", attrs); Index: main/plugin-cms/src/org/ametys/cms/content/CopyContentMetadataComponent.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/CopyContentMetadataComponent.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/CopyContentMetadataComponent.java (working copy) @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import javax.jcr.Node; @@ -59,6 +60,7 @@ import org.ametys.cms.contenttype.ConsistencyExtractor; import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.cms.contenttype.MetadataDefinitionReference; import org.ametys.cms.contenttype.MetadataSet; @@ -160,6 +162,9 @@ /** Content type extension point. */ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; + @Override public void service(ServiceManager manager) throws ServiceException { @@ -167,6 +172,7 @@ _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); _saxParser = (SAXParser) manager.lookup(SAXParser.ROLE); _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } /** @@ -278,14 +284,17 @@ */ protected CopyReport _copyContent(Content baseContent, String title, Map<String, Object> copyMap, String metadataSetName, String metadataSetType, String targetContentId, String parentMetadataPath) { - ContentType contentType = null; String baseContentId = baseContent.getId(); CopyReport report = new CopyReport(baseContentId, baseContent.getTitle(), true, metadataSetName, metadataSetType, CopyMode.CREATION); try { - contentType = _contentTypeExtensionPoint.getExtension(baseContent.getType()); + boolean simple = false; + for (String cTypeId : baseContent.getTypes()) + { + simple = simple || _contentTypeExtensionPoint.getExtension(cTypeId).isSimple(); + } - report.setSimple(contentType.isSimple()); + report.setSimple(simple); Map<String, Object> internalCopyMap = copyMap; if (internalCopyMap == null) @@ -393,8 +402,7 @@ */ protected Map<String, Object> _buildCopyMap(Content baseContent, String metadataSetName, String metadataSetType) { - ContentType contentType = _contentTypeExtensionPoint.getExtension(baseContent.getType()); - MetadataSet metadataSet = "view".equals(metadataSetType) ? contentType.getMetadataSetForView(metadataSetName) : contentType.getMetadataSetForEdition(metadataSetName); + MetadataSet metadataSet = "view".equals(metadataSetType) ? _contentTypesHelper.getMetadataSetForView(metadataSetName, baseContent.getTypes(), baseContent.getMixinTypes()) : _contentTypesHelper.getMetadataSetForEdition(metadataSetName, baseContent.getTypes(), baseContent.getMixinTypes()); return _buildCopyMap(metadataSet, null); } @@ -449,14 +457,11 @@ public CopyReport editContent(String baseContentId, String targetContentId, Map<String, Object> copyMap, String metadataSetName, String metadataSetType) { Content baseContent = null; - ContentType contentType = null; CopyReport report = null; try { baseContent = _resolver.resolveById(baseContentId); - contentType = _contentTypeExtensionPoint.getExtension(baseContent.getType()); - - report = new CopyReport(baseContentId, baseContent.getTitle(), contentType.isSimple(), metadataSetName, metadataSetType, CopyMode.EDITION); + report = new CopyReport(baseContentId, baseContent.getTitle(), _isSimple(baseContent), metadataSetName, metadataSetType, CopyMode.EDITION); Map<String, Object> internalCopyMap = copyMap; if (internalCopyMap == null) @@ -499,13 +504,7 @@ } else { - boolean isSimple = true; - if (contentType != null) - { - isSimple = contentType.isSimple(); - } - - report = new CopyReport(baseContentId, isSimple, metadataSetName, metadataSetType, CopyMode.EDITION); + report = new CopyReport(baseContentId, _isSimple(baseContent), metadataSetName, metadataSetType, CopyMode.EDITION); report.notifyContentCopyError(); } } @@ -513,6 +512,18 @@ return report; } + private boolean _isSimple (Content content) + { + for (String cTypeId : content.getTypes()) + { + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + if (!cType.isSimple()) + { + return false; + } + } + return true; + } /** * Retrieve the target content from its id. * Also ensure that it is a workflow aware content. @@ -574,26 +585,26 @@ /** * Copy the specified set of metadata from a base content to a target content by iterating over the copyMap. - * @param baseContent + * @param baseContent The original content * @param targetContent * @param copyMap * @param copyReport The copy report being populated during the copy. */ public void copyMetadataMap(Content baseContent, ModifiableContent targetContent, Map<String, Object> copyMap, CopyReport copyReport) { - ContentType contentType = _contentTypeExtensionPoint.getExtension(baseContent.getType()); - copyReport.notifyContentCreation(targetContent.getId(), targetContent.getTitle(), contentType.isSimple()); + copyReport.notifyContentCreation(targetContent.getId(), targetContent.getTitle(), _isSimple(baseContent)); - _copyMetadataMap(baseContent.getMetadataHolder(), targetContent.getMetadataHolder(), "", contentType, copyMap, copyReport); + _copyMetadataMap(baseContent, baseContent.getMetadataHolder(), targetContent.getMetadataHolder(), "", null, copyMap, copyReport); copyReport.setTargetContentTitle(targetContent.getTitle()); - _updateRichTextMetadata(contentType, baseContent, targetContent, copyReport); + _updateRichTextMetadata(baseContent, targetContent, copyReport); _buildConsistencies(targetContent); } /** * Copy the specified set of metadata from a base composite metadata to a target composite metadata by iterating over the copyMap. + * @param baseContent The original content * @param baseMetaHolder * @param targetMetaHolder * @param metaPrefix The metadata prefix. @@ -601,13 +612,14 @@ * @param copyMap * @param copyReport The copy report being populated during the copy. */ - public void copyMetadataMap(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, ContentType contentType, Map<String, Object> copyMap, CopyReport copyReport) + public void copyMetadataMap(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, ContentType contentType, Map<String, Object> copyMap, CopyReport copyReport) { - _copyMetadataMap(baseMetaHolder, targetMetaHolder, metaPrefix, contentType, copyMap, copyReport); + _copyMetadataMap(baseContent, baseMetaHolder, targetMetaHolder, metaPrefix, null, copyMap, copyReport); } /** * Copy the specified set of metadata from a base composite metadata to a target composite metadata by iterating over the copyMap. + * @param baseContent The original content * @param baseMetaHolder * @param targetMetaHolder * @param metaPrefix The metadata prefix. @@ -615,21 +627,22 @@ * @param copyMap * @param copyReport The copy report being populated during the copy. */ - public void copyMetadataMap(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) + public void copyMetadataMap(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) { - _copyMetadataMap(baseMetaHolder, targetMetaHolder, metaPrefix, metadataDefinition, copyMap, copyReport); + _copyMetadataMap(baseContent, baseMetaHolder, targetMetaHolder, metaPrefix, metadataDefinition, copyMap, copyReport); } /** * Copy the specified set of metadata from a base composite metadata to a target composite metadata by iterating over the copyMap. - * @param baseMetaHolder + * @param baseContent The original content + * @param baseMetaHolder * @param targetMetaHolder * @param metaPrefix The metadata prefix. - * @param contentTypeOrMetadataDef Either a contentType (is at the root level of content metadata holder), or a metadata definition. + * @param parentMetadataDefinition The parent metadata definition. Can be null * @param copyMap * @param copyReport The copy report being populated during the copy. */ - protected void _copyMetadataMap(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, Object contentTypeOrMetadataDef, Map<String, Object> copyMap, CopyReport copyReport) + protected void _copyMetadataMap(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metaPrefix, MetadataDefinition parentMetadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) { if (copyMap == null) { @@ -647,10 +660,10 @@ MetadataDefinition metadataDefinition = null; try { - metadataDefinition = _getMetadataDefinition(contentTypeOrMetadataDef, metadataName); + metadataDefinition = _getMetadataDefinition(baseContent, parentMetadataDefinition, metadataName); if (metadataDefinition != null) { - copyMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metaPrefix, (Map<String, Object>) copyMap.get(metadataName), copyReport); + copyMetadata(baseContent, baseMetaHolder, targetMetaHolder, metadataDefinition, metaPrefix, (Map<String, Object>) copyMap.get(metadataName), copyReport); } } catch (Exception e) @@ -662,26 +675,22 @@ /** * Retrieves a {@link MetadataDefinition} through its - * parent defintion or the {@link ContentType} of the + * parent definition or the {@link ContentType} of the * current content if at the root level of the metadataset. - * @param contentTypeOrMetadataDef - * @param childMetadataName - * @return The retrieved metadata defintion. - * @throws IllegalArgumentException If the metadata could not be retrieved. + * @param content The content + * @param metadataDefinition The parent metadata definition. Can be null. + * @param metadataName The metadata name + * @return The retrieved metadata defintion or null if not found */ - protected MetadataDefinition _getMetadataDefinition(Object contentTypeOrMetadataDef, String childMetadataName) throws IllegalArgumentException + protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition metadataDefinition, String metadataName) { - if (contentTypeOrMetadataDef instanceof MetadataDefinition) + if (metadataDefinition != null) { - return ((MetadataDefinition) contentTypeOrMetadataDef).getMetadataDefinition(childMetadataName); - } - else if (contentTypeOrMetadataDef instanceof ContentType) - { - return ((ContentType) contentTypeOrMetadataDef).getMetadataDefinition(childMetadataName); + return metadataDefinition.getMetadataDefinition(metadataName); } else { - throw new IllegalArgumentException("Unable to retrieve metadata definition for metadata '" + childMetadataName + "'."); + return _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); } } @@ -706,6 +715,7 @@ /** * Copy the specified metadata from a base composite metadata to a target composite metadata. + * @param baseContent The original content * @param baseMetaHolder * @param targetMetaHolder * @param metadataDefinition @@ -713,14 +723,12 @@ * @param copyMap * @param copyReport */ - public void copyMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDefinition, String metaPrefix, Map<String, Object> copyMap, CopyReport copyReport) + public void copyMetadata(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDefinition, String metaPrefix, Map<String, Object> copyMap, CopyReport copyReport) { String metadataName = metadataDefinition.getName(); String metadataPath = metaPrefix + "/" + metadataName; - MetadataType type = metadataDefinition.getType(); - boolean multiple = metadataDefinition.isMultiple(); - switch (type) + switch (metadataDefinition.getType()) { case GEOCODE: copyGeocodeMetadata(baseMetaHolder, targetMetaHolder, metadataName); @@ -729,10 +737,10 @@ copyBinaryMetadata(baseMetaHolder, targetMetaHolder, metadataName); break; case FILE: - copyFileMetadata(baseMetaHolder, targetMetaHolder, type, metadataName, multiple); + copyFileMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName); break; case RICH_TEXT: - copyRichTextMetadata(baseMetaHolder, targetMetaHolder, metadataName, copyReport); + copyRichTextMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName, copyReport); break; case CONTENT: copyContentReferenceMetadata(baseMetaHolder, targetMetaHolder, metadataName, copyMap, copyReport); @@ -741,16 +749,16 @@ copySubContentMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataPath, copyMap, copyReport); break; case COMPOSITE: - copyCompositeMetadata(baseMetaHolder, targetMetaHolder, metadataPath, metadataDefinition, copyMap, copyReport); + copyCompositeMetadata(baseContent, baseMetaHolder, targetMetaHolder, metadataPath, metadataDefinition, copyMap, copyReport); break; default: - copyBasicMetadata(baseMetaHolder, targetMetaHolder, type, metadataName, multiple); + copyBasicMetadata(baseMetaHolder, targetMetaHolder, metadataDefinition, metadataName); break; } } /** - * Duplicate a basic metadata (nothing special to do). + * Copy a 'basic' metadata. * This is used to copy metadata of type : * {@link MetadataType#STRING}, {@link MetadataType#DATE}, {@link MetadataType#DATETIME}, * {@link MetadataType#DOUBLE}, {@link MetadataType#LONG}, {@link MetadataType#BOOLEAN}, @@ -758,34 +766,33 @@ * * @param baseMetaHolder * @param targetMetaHolder - * @param type - * @param metadataName - * @param isMultiple + * @param metadataDef The metadata definition + * @param metadataName The metadata name */ - public void copyBasicMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataType type, String metadataName, Boolean isMultiple) + public void copyBasicMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDef, String metadataName) { if (baseMetaHolder.hasMetadata(metadataName)) { - switch (type) + switch (metadataDef.getType()) { case DATE: case DATETIME: - _setDateMetadata(baseMetaHolder, targetMetaHolder, metadataName, isMultiple); + _setDateMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); break; case DOUBLE: - _setDoubleMetadata(baseMetaHolder, targetMetaHolder, metadataName, isMultiple); + _setDoubleMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); break; case LONG: - _setLongMetadata(baseMetaHolder, targetMetaHolder, metadataName, isMultiple); + _setLongMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); break; case BOOLEAN: - _setBooleanMetadata(baseMetaHolder, targetMetaHolder, metadataName, isMultiple); + _setBooleanMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); break; case STRING: case USER: case REFERENCE: default: - _setStringMetadata(baseMetaHolder, targetMetaHolder, metadataName, isMultiple); + _setStringMetadata(baseMetaHolder, targetMetaHolder, metadataName, metadataDef.isMultiple()); break; } } @@ -938,14 +945,13 @@ } /** - * Duplicate a metadata of type {@link MetadataType#FILE}. - * @param baseMetaHolder - * @param targetMetaHolder - * @param type - * @param metadataName - * @param isMultiple + * Copy a file metadata + * @param baseMetaHolder The copied composite metadata + * @param targetMetaHolder The target composite metadata + * @param metadataDef The metadata definition + * @param metadataName The metadata name */ - public void copyFileMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataType type, String metadataName, Boolean isMultiple) + public void copyFileMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDef, String metadataName) { if (baseMetaHolder.hasMetadata(metadataName)) { @@ -957,7 +963,7 @@ else { targetMetaHolder.setMetadata(metadataName, baseMetaHolder.getString(metadataName)); - copyBasicMetadata(baseMetaHolder, targetMetaHolder, type, metadataName, isMultiple); + copyBasicMetadata(baseMetaHolder, targetMetaHolder, metadataDef, metadataName); } } else if (targetMetaHolder.hasMetadata(metadataName)) @@ -967,13 +973,14 @@ } /** - * Duplicate a metadata of type {@link MetadataType#RICH_TEXT}. - * @param baseMetaHolder - * @param targetMetaHolder - * @param metadataName - * @param copyReport + * Copy a rich-text metadata + * @param baseMetaHolder The copied composite metadata + * @param targetMetaHolder The target composite metadata + * @param metadataName The metadata name + * @param metadataDef The metadata definition + * @param copyReport The copy report */ - protected void copyRichTextMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataName, CopyReport copyReport) + protected void copyRichTextMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, MetadataDefinition metadataDef, String metadataName, CopyReport copyReport) { if (baseMetaHolder.hasMetadata(metadataName)) { @@ -981,7 +988,7 @@ ModifiableRichText targetRichText = targetMetaHolder.getRichText(metadataName, true); // Notify the report that a rich text has been copied. - copyReport.addRichText(targetRichText); + copyReport.addRichText(targetRichText, metadataDef); targetRichText.setEncoding(baseRichText.getEncoding()); targetRichText.setMimeType(baseRichText.getMimeType()); @@ -1235,19 +1242,20 @@ } /** - * Duplicate a metadata of type {@link MetadataType#COMPOSITE}. - * @param baseMetaHolder - * @param targetMetaHolder - * @param metadataPath - * @param metadataDefinition - * @param copyMap - * @param copyReport + * Copy composite metadata + * @param baseContent The copied content + * @param baseMetaHolder The original composite metadata + * @param targetMetaHolder The target composite metadata + * @param metadataPath The metadata path + * @param metadataDefinition The metadata definition + * @param copyMap The map for copy + * @param copyReport The copy report */ - public void copyCompositeMetadata(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataPath, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) + public void copyCompositeMetadata(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataPath, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) { if (metadataDefinition instanceof RepeaterDefinition) { - _copyRepeater(baseMetaHolder, targetMetaHolder, metadataPath, metadataDefinition, copyMap, copyReport); + _copyRepeater(baseContent, baseMetaHolder, targetMetaHolder, metadataPath, metadataDefinition, copyMap, copyReport); } else { @@ -1258,7 +1266,7 @@ CompositeMetadata baseCompositeMetadata = baseMetaHolder.getCompositeMetadata(metadataName); ModifiableCompositeMetadata targetCompositeMetadata = targetMetaHolder.getCompositeMetadata(metadataName, true); - _copyMetadataMap(baseCompositeMetadata, targetCompositeMetadata, metadataPath, metadataDefinition, copyMap, copyReport); + _copyMetadataMap(baseContent, baseCompositeMetadata, targetCompositeMetadata, metadataPath, metadataDefinition, copyMap, copyReport); } else if (targetMetaHolder.hasMetadata(metadataName)) { @@ -1268,15 +1276,16 @@ } /** - * Duplicate a repeater - * @param baseMetaHolder - * @param targetMetaHolder - * @param metadataPath - * @param metadataDefinition - * @param copyMap - * @param copyReport + * Copy a repeater + * @param baseContent The copied content + * @param baseMetaHolder The original composite metadata + * @param targetMetaHolder The target composite metadata + * @param metadataPath The metadata path + * @param metadataDefinition The metadata definition + * @param copyMap The map for copy + * @param copyReport The copy report */ - protected void _copyRepeater(CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataPath, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) + protected void _copyRepeater(Content baseContent, CompositeMetadata baseMetaHolder, ModifiableCompositeMetadata targetMetaHolder, String metadataPath, MetadataDefinition metadataDefinition, Map<String, Object> copyMap, CopyReport copyReport) { String metadataName = metadataDefinition.getName(); @@ -1304,7 +1313,7 @@ String metaPrefix = metadataPath + "/" + entryName; - _copyMetadataMap(baseRepeaterEntry, targetRepeaterEntry, metaPrefix, metadataDefinition, copyMap, copyReport); + _copyMetadataMap(baseContent, baseRepeaterEntry, targetRepeaterEntry, metaPrefix, metadataDefinition, copyMap, copyReport); } } } @@ -1321,55 +1330,61 @@ */ protected void _buildConsistencies(ModifiableContent content) { - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - ConsistencyExtractor consistencyExtractor = contentType.getConsistencyExtractor(); - if (consistencyExtractor != null) + Map<String, List<String>> elements = new HashMap<String, List<String>>(); + for (String cTypeId : content.getTypes()) { - Map<String, List<String>> elements = consistencyExtractor.getConsistencyElement(content); - - content.setConsistencyElements(elements); + ContentType contentType = _contentTypeExtensionPoint.getExtension(cTypeId); + ConsistencyExtractor consistencyExtractor = contentType.getConsistencyExtractor(); + if (consistencyExtractor != null) + { + elements.putAll(consistencyExtractor.getConsistencyElement(content)); + } } + content.setConsistencyElements(elements); } /** * This method analyzes content rich texts and update their content to * ensure consistency. (link to image, attachments...) - * @param contentType - * @param baseContent - * @param targetContent - * @param copyReport + * @param baseContent The copied content + * @param targetContent The target content + * @param copyReport The copy report */ - protected void _updateRichTextMetadata(ContentType contentType, final Content baseContent, final ModifiableContent targetContent, CopyReport copyReport) + protected void _updateRichTextMetadata(final Content baseContent, final ModifiableContent targetContent, CopyReport copyReport) { try { - RichTextUpdater richTextUpdater = contentType.getRichTextUpdater(); - List<ModifiableRichText> richTexts = copyReport.getCopiedRichTexts(); + Map<ModifiableRichText, MetadataDefinition> copiedRichTexts = copyReport.getCopiedRichTexts(); - // Update rich text content - if (!richTexts.isEmpty() && richTextUpdater != null) + for (Entry<ModifiableRichText, MetadataDefinition> entry : copiedRichTexts.entrySet()) { - Map<String, Object> params = new HashMap<String, Object>(); - params.put("initialContent", baseContent); - params.put("createdContent", targetContent); + ModifiableRichText richText = entry.getKey(); + MetadataDefinition metadataDef = entry.getValue(); - // create the transformer instance - TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); + String referenceContentType = metadataDef.getReferenceContentType(); + ContentType contentType = _contentTypeExtensionPoint.getExtension(referenceContentType); - // create the format of result - Properties format = new Properties(); - format.put(OutputKeys.METHOD, "xml"); - format.put(OutputKeys.INDENT, "yes"); - format.put(OutputKeys.ENCODING, "UTF-8"); - format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); - th.getTransformer().setOutputProperties(format); - - // Update rich text contents - // Copy the needed original attachments. - for (ModifiableRichText richText : richTexts) + RichTextUpdater richTextUpdater = contentType.getRichTextUpdater(); + if (richTextUpdater != null) { + Map<String, Object> params = new HashMap<String, Object>(); + params.put("initialContent", baseContent); + params.put("createdContent", targetContent); + + // create the transformer instance + TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); + + // create the format of result + Properties format = new Properties(); + format.put(OutputKeys.METHOD, "xml"); + format.put(OutputKeys.INDENT, "yes"); + format.put(OutputKeys.ENCODING, "UTF-8"); + format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); + th.getTransformer().setOutputProperties(format); + + // Update rich text contents + // Copy the needed original attachments. InputStream is = null; OutputStream os = null; try Index: main/plugin-cms/src/org/ametys/cms/content/AddOrRemoveContentTypeAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/AddOrRemoveContentTypeAction.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/content/AddOrRemoveContentTypeAction.java (revision 0) @@ -0,0 +1,165 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.content; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.parameters.Parameters; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.acting.ServiceableAction; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Redirector; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.environment.SourceResolver; + +import org.ametys.cms.ObservationConstants; +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; +import org.ametys.cms.repository.Content; +import org.ametys.cms.repository.ModifiableContent; +import org.ametys.cms.repository.WorkflowAwareContent; +import org.ametys.cms.workflow.AbstractContentWorkflowComponent; +import org.ametys.plugins.repository.AmetysObjectResolver; +import org.ametys.plugins.workflow.Workflow; +import org.ametys.runtime.cocoon.JSonReader; +import org.ametys.runtime.observation.Event; +import org.ametys.runtime.observation.ObservationManager; +import org.ametys.runtime.user.CurrentUserProvider; + +/** + * Action to add or remove a content type to an existing content + * + */ +public class AddOrRemoveContentTypeAction extends ServiceableAction +{ + private AmetysObjectResolver _resolver; + private Workflow _workflow; + private ObservationManager _observationManager; + private CurrentUserProvider _currentUserProvider; + private ContentTypesHelper _contentTypesHelper; + private ContentTypeExtensionPoint _contentTypeEP; + + @Override + public void service(ServiceManager smanager) throws ServiceException + { + super.service(smanager); + _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); + _workflow = (Workflow) smanager.lookup(Workflow.ROLE); + _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); + _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); + _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); + _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + } + + @Override + public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception + { + Map<String, Object> result = new HashMap<String, Object>(); + + Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); + + String contentId = (String) jsParameters.get("contentId"); + String contentTypeId = (String) jsParameters.get("contentType"); + + int actionId = (Integer) jsParameters.get("actionId"); + boolean deleteMode = parameters.getParameterAsBoolean("remove", false); + + Content content = _resolver.resolveById(contentId); + + if (content instanceof ModifiableContent) + { + ModifiableContent modifiableContent = (ModifiableContent) content; + + List<String> currentTypes = new ArrayList<String>(Arrays.asList(content.getTypes())); + + boolean hasChange = false; + if (deleteMode) + { + if (currentTypes.size() > 1) + { + hasChange = currentTypes.remove(contentTypeId); + } + else + { + result.put("failure", true); + result.put("msg", "empty-list"); + } + } + else if (!currentTypes.contains(contentTypeId)) + { + ContentType cType = _contentTypeEP.getExtension(contentTypeId); + if (cType.isMixin()) + { + result.put("failure", true); + result.put("msg", "no-content-type"); + getLogger().error("Content type '" + contentTypeId + "' is a mixin type. It can not be added as content type."); + } + else if (!_contentTypesHelper.isCompatibleContentType(content, contentTypeId)) + { + result.put("failure", true); + result.put("msg", "invalid-content-type"); + getLogger().error("Content type '" + contentTypeId + "' is incompatible with content '" + contentId + "'."); + } + else + { + currentTypes.add(contentTypeId); + hasChange = true; + } + } + + if (hasChange) + { + // TODO check if the content type is compatible + modifiableContent.setTypes(currentTypes.toArray(new String[currentTypes.size()])); + modifiableContent.saveChanges(); + + if (content instanceof WorkflowAwareContent) + { + Map<String, Object> inputs = new HashMap<String, Object>(); + + inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); + inputs.put(AbstractContentWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); + + _workflow.doAction(((WorkflowAwareContent) content).getWorkflowId(), actionId, inputs); + } + + result.put("success", true); + + Map<String, Object> eventParams = new HashMap<String, Object>(); + eventParams.put(ObservationConstants.ARGS_CONTENT, modifiableContent); + _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams)); + } + } + else + { + result.put("failure", true); + result.put("msg", "no-modifiable-content"); + getLogger().error("Can not modified content types to a non-modifiable content '" + content.getId() + "'."); + } + + Request request = ObjectModelHelper.getRequest(objectModel); + request.setAttribute(JSonReader.OBJECT_TO_READ, result); + + return EMPTY_MAP; + } + +} Index: main/plugin-cms/src/org/ametys/cms/content/GetContentInformationAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/GetContentInformationAction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/GetContentInformationAction.java (working copy) @@ -116,7 +116,8 @@ infos.put("name", content.getName()); infos.put("title", content.getTitle()); infos.put("path", content.getPath()); - infos.put("type", content.getType()); + infos.put("types", content.getTypes()); + infos.put("mixins", content.getMixinTypes()); infos.put("lang", content.getLanguage()); infos.put("creator", content.getCreator()); infos.put("lastContributor", content.getLastContributor()); Index: main/plugin-cms/src/org/ametys/cms/content/ContentGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/ContentGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/ContentGenerator.java (working copy) @@ -40,6 +40,7 @@ import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataManager; import org.ametys.cms.contenttype.MetadataSet; import org.ametys.cms.languages.Language; @@ -76,7 +77,8 @@ protected LanguagesManager _languageManager; /** The workflow */ protected Workflow _workflow; - + /** Helper for content types */ + protected ContentTypesHelper _cTypesHelper; @Override public void service(ServiceManager serviceManager) throws ServiceException @@ -86,6 +88,7 @@ _metadataManager = (MetadataManager) serviceManager.lookup(MetadataManager.ROLE); _languageManager = (LanguagesManager) serviceManager.lookup(LanguagesManager.ROLE); _workflow = (Workflow) serviceManager.lookup(Workflow.ROLE); + _cTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); } public void generate() throws IOException, SAXException, ProcessingException @@ -147,10 +150,9 @@ attrs.addCDATAAttribute("lastContributor", content.getLastContributor()); attrs.addCDATAAttribute("commentable", Boolean.toString(content instanceof CommentableContent)); - ContentType cType = _contentTypeExtensionPoint.getExtension(content.getType()); - attrs.addCDATAAttribute("smallIcon", cType.getSmallIcon()); - attrs.addCDATAAttribute("mediumIcon", cType.getMediumIcon()); - attrs.addCDATAAttribute("largeIcon", cType.getMediumIcon()); + attrs.addCDATAAttribute("smallIcon", _cTypesHelper.getSmallIcon(content)); + attrs.addCDATAAttribute("mediumIcon", _cTypesHelper.getMediumIcon(content)); + attrs.addCDATAAttribute("largeIcon", _cTypesHelper.getLargeIcon(content)); XMLUtils.startElement(contentHandler, "content", attrs); @@ -166,7 +168,12 @@ XMLUtils.endElement(contentHandler, "comments"); } - cType.saxContentTypeAdditionalData(contentHandler, content); + String[] cTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); + for (String cTypeId : cTypes) + { + ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); + cType.saxContentTypeAdditionalData(contentHandler, content); + } // FIXME CMS-3057 Request request = ObjectModelHelper.getRequest(objectModel); @@ -177,7 +184,6 @@ _saxWorkflowStep(content); } - _saxContentType(cType); _saxLanguage (content); _saxDublinCoreMetadata(content); @@ -248,24 +254,6 @@ } /** - * SAX teh content type - * @param cType The content type - * @throws SAXException if an error occurs while SAXing. - */ - protected void _saxContentType (ContentType cType) throws SAXException - { - AttributesImpl attrs = new AttributesImpl(); - attrs.addCDATAAttribute("id", cType.getId()); - attrs.addCDATAAttribute("icon-small", cType.getSmallIcon()); - attrs.addCDATAAttribute("icon-medium", cType.getMediumIcon()); - attrs.addCDATAAttribute("icon-large", cType.getLargeIcon()); - - XMLUtils.startElement(contentHandler, "content-type", attrs); - cType.getLabel().toSAX(contentHandler); - XMLUtils.endElement(contentHandler, "content-type"); - - } - /** * SAX the content language * @param content The content * @throws SAXException if an error occurs while SAXing. @@ -464,7 +452,6 @@ */ protected MetadataSet _getMetadataSet(Content content) throws ProcessingException { - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); boolean isEditionMetadataSet = parameters.getParameterAsBoolean("isEditionMetadataSet", false); String metadataSetName = parameters.getParameter("metadataSetName", ""); if (StringUtils.isBlank(metadataSetName)) @@ -476,17 +463,17 @@ if (isEditionMetadataSet) { - metadataSet = contentType.getMetadataSetForEdition(metadataSetName); + metadataSet = _cTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); } else { - metadataSet = contentType.getMetadataSetForView(metadataSetName); + metadataSet = _cTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); } if (metadataSet == null) { - throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type '%s'", - metadataSetName, isEditionMetadataSet ? "edition" : "view", contentType.getId())); + throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type(s) '%s'", + metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ','))); } return metadataSet; Index: main/plugin-cms/src/org/ametys/cms/content/CopyReport.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/CopyReport.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/CopyReport.java (working copy) @@ -16,9 +16,12 @@ package org.ametys.cms.content; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.plugins.repository.metadata.ModifiableRichText; import org.ametys.runtime.util.I18nizableText; @@ -53,7 +56,7 @@ protected List<CopyReport> _innerReports; /** List of copied rich text metadata during the duplication process */ - protected List<ModifiableRichText> _copiedRichTexts; + protected Map<ModifiableRichText, MetadataDefinition> _copiedRichTexts; /** * List of paths (relative to the root attachment node of the target * content) of the copied attachments during the duplication @@ -119,7 +122,7 @@ _mode = copyMode; _innerReports = new LinkedList<CopyReport>(); - _copiedRichTexts = new LinkedList<ModifiableRichText>(); + _copiedRichTexts = new HashMap<ModifiableRichText, MetadataDefinition>(); _copiedAttachments = new LinkedList<String>(); _metadataCopyErrors = new LinkedList<I18nizableText>(); @@ -195,20 +198,21 @@ /** * Add a rich text to the list of copied rich texts. - * @param richText + * @param richText the rich text + * @param definition the metadata definition */ - protected void addRichText(ModifiableRichText richText) + protected void addRichText(ModifiableRichText richText, MetadataDefinition definition) { - _copiedRichTexts.add(richText); + _copiedRichTexts.put(richText, definition); } /** * Get the list of copied rich text metadata. * @return List of {@link ModifiableRichText} */ - protected List<ModifiableRichText> getCopiedRichTexts() + protected Map<ModifiableRichText, MetadataDefinition> getCopiedRichTexts() { - return Collections.unmodifiableList(_copiedRichTexts); + return Collections.unmodifiableMap(_copiedRichTexts); } /** Index: main/plugin-cms/src/org/ametys/cms/content/AddOrRemoveMixinAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/AddOrRemoveMixinAction.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/content/AddOrRemoveMixinAction.java (revision 0) @@ -0,0 +1,157 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.content; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.parameters.Parameters; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.acting.ServiceableAction; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Redirector; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.environment.SourceResolver; + +import org.ametys.cms.ObservationConstants; +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; +import org.ametys.cms.repository.Content; +import org.ametys.cms.repository.ModifiableContent; +import org.ametys.cms.repository.WorkflowAwareContent; +import org.ametys.cms.workflow.AbstractContentWorkflowComponent; +import org.ametys.plugins.repository.AmetysObjectResolver; +import org.ametys.plugins.workflow.Workflow; +import org.ametys.runtime.cocoon.JSonReader; +import org.ametys.runtime.observation.Event; +import org.ametys.runtime.observation.ObservationManager; +import org.ametys.runtime.user.CurrentUserProvider; + +/** + * Action to add or remove a content type to an existing content + * + */ +public class AddOrRemoveMixinAction extends ServiceableAction +{ + private AmetysObjectResolver _resolver; + private Workflow _workflow; + private ObservationManager _observationManager; + private CurrentUserProvider _currentUserProvider; + private ContentTypesHelper _contentTypesHelper; + private ContentTypeExtensionPoint _contentTypeEP; + + @Override + public void service(ServiceManager smanager) throws ServiceException + { + super.service(smanager); + _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); + _workflow = (Workflow) smanager.lookup(Workflow.ROLE); + _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); + _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); + _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); + _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + } + + @Override + public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception + { + Map<String, Object> result = new HashMap<String, Object>(); + + Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); + + String contentId = (String) jsParameters.get("contentId"); + String mixinId = (String) jsParameters.get("mixin"); + + int actionId = (Integer) jsParameters.get("actionId"); + boolean deleteMode = parameters.getParameterAsBoolean("remove", false); + + Content content = _resolver.resolveById(contentId); + + if (content instanceof ModifiableContent) + { + ModifiableContent modifiableContent = (ModifiableContent) content; + + List<String> currentMixins = new ArrayList<String>(Arrays.asList(content.getMixinTypes())); + + boolean hasChange = false; + if (deleteMode) + { + hasChange = currentMixins.remove(mixinId); + } + else if (!currentMixins.contains(mixinId)) + { + ContentType cType = _contentTypeEP.getExtension(mixinId); + if (!cType.isMixin()) + { + result.put("failure", true); + result.put("msg", "no-mixin"); + getLogger().error("The content type '" + mixinId + "' is not a mixin type, it can be not be added as a mixin."); + } + else if (!_contentTypesHelper.isCompatibleContentType(content, mixinId)) + { + result.put("failure", true); + result.put("msg", "invalid-mixin"); + getLogger().error("Mixin '" + mixinId + "' is incompatible with content '" + contentId + "'."); + } + else + { + currentMixins.add(mixinId); + hasChange = true; + } + } + + if (hasChange) + { + // TODO check if the content type is compatible + modifiableContent.setMixinTypes(currentMixins.toArray(new String[currentMixins.size()])); + modifiableContent.saveChanges(); + + if (content instanceof WorkflowAwareContent) + { + Map<String, Object> inputs = new HashMap<String, Object>(); + + inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content); + inputs.put(AbstractContentWorkflowComponent.RESULT_MAP_KEY, new HashMap<String, Object>()); + + _workflow.doAction(((WorkflowAwareContent) content).getWorkflowId(), actionId, inputs); + } + + result.put("success", true); + + Map<String, Object> eventParams = new HashMap<String, Object>(); + eventParams.put(ObservationConstants.ARGS_CONTENT, modifiableContent); + _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams)); + } + } + else + { + result.put("failure", true); + result.put("msg", "no-modifiable-content"); + getLogger().error("Can not modified mixins to a non-modifiable content '" + content.getId() + "'."); + } + + Request request = ObjectModelHelper.getRequest(objectModel); + request.setAttribute(JSonReader.OBJECT_TO_READ, result); + + return EMPTY_MAP; + } + +} Index: main/plugin-cms/src/org/ametys/cms/content/ContentInformationGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/ContentInformationGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/ContentInformationGenerator.java (working copy) @@ -28,6 +28,7 @@ import org.apache.cocoon.generation.ServiceableGenerator; import org.apache.cocoon.xml.AttributesImpl; import org.apache.cocoon.xml.XMLUtils; +import org.apache.commons.lang.StringUtils; import org.xml.sax.SAXException; import org.ametys.cms.repository.Content; @@ -98,7 +99,7 @@ attrs.addCDATAAttribute("title", content.getTitle()); attrs.addCDATAAttribute("name", content.getName()); attrs.addCDATAAttribute("path", content.getPath()); - attrs.addCDATAAttribute("type", content.getType()); + attrs.addCDATAAttribute("type", StringUtils.join(content.getTypes(), ',')); attrs.addCDATAAttribute("lang", content.getLanguage()); attrs.addCDATAAttribute("creator", content.getCreator()); attrs.addCDATAAttribute("lastContributor", content.getLastContributor()); Index: main/plugin-cms/src/org/ametys/cms/content/GetMetadataSetDefAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/GetMetadataSetDefAction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/GetMetadataSetDefAction.java (working copy) @@ -33,8 +33,8 @@ import org.apache.commons.lang.StringUtils; import org.ametys.cms.contenttype.AbstractMetadataSetElement; -import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.Fieldset; import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.cms.contenttype.MetadataDefinitionReference; @@ -44,7 +44,6 @@ import org.ametys.cms.contenttype.RichTextMetadataDefinition; import org.ametys.cms.contenttype.SemanticAnnotation; import org.ametys.cms.repository.Content; -import org.ametys.plugins.repository.AmetysRepositoryException; import org.ametys.runtime.cocoon.JSonReader; import org.ametys.runtime.util.I18nizableText; import org.ametys.runtime.util.parameter.Enumerator; @@ -59,12 +58,15 @@ { /** Content type extension point. */ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** Helper for content type */ + protected ContentTypesHelper _contentTypesHelper; @Override public void service(ServiceManager serviceManager) throws ServiceException { super.service(serviceManager); _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); + _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); } @Override @@ -98,26 +100,24 @@ jsonObject.put("id", content.getId()); jsonObject.put("name", content.getName()); jsonObject.put("title", content.getTitle()); - jsonObject.put("type", content.getType()); + jsonObject.put("type", content.getTypes()); jsonObject.put("language", content.getLanguage()); - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); - MetadataSet metadataSet = null; if (isEditionMetadataSet) { - metadataSet = contentType.getMetadataSetForEdition(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); } else { - metadataSet = contentType.getMetadataSetForView(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); } if (metadataSet == null) { - throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type '%s'", - metadataSetName, isEditionMetadataSet ? "edition" : "view", contentType.getId())); + throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type(s) '%s'", + metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ','))); } jsonObject.put("metadataSetName", metadataSetName); @@ -151,12 +151,10 @@ throw new IllegalArgumentException("Unable to get the metadata definition of metadata \"" + metadataName + "\""); } - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); - String metadataPath = StringUtils.stripStart(metaDef.getId(), "/"); - if (contentType.canRead(content, metadataPath)) + if (_contentTypesHelper.canRead(content, metadataPath)) { - jsonObject.put(metaDef.getName(), metadataDefinition2JsonObject(contentType, content, metadataDefRef, metaDef, metadataPath)); + jsonObject.put(metaDef.getName(), metadataDefinition2JsonObject(content, metadataDefRef, metaDef, metadataPath)); } } else @@ -184,7 +182,6 @@ /** * Convert a metadata to JSON object - * @param contentType The content type * @param content The content * @param metadataSetElement The metadataset element * @param metaDef The metadata definition @@ -192,7 +189,7 @@ * @return The JSON object representing the metadata * @throws ProcessingException */ - protected Map<String, Object> metadataDefinition2JsonObject(ContentType contentType, Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, String metadataPath) throws ProcessingException + protected Map<String, Object> metadataDefinition2JsonObject(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, String metadataPath) throws ProcessingException { Map<String, Object> jsonObject = new LinkedHashMap<String, Object>(); @@ -261,7 +258,7 @@ } - if (!contentType.canWrite(content, metadataPath)) + if (!_contentTypesHelper.canWrite(content, metadataPath)) { jsonObject.put("can-not-write", true); } @@ -358,15 +355,7 @@ { if (content != null) { - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - - if (contentType == null) - { - throw new AmetysRepositoryException("Unknown content type: " + contentTypeId); - } - - metadataDefinition = contentType.getMetadataDefinition(metadataName); + metadataDefinition = _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); } } else Index: main/plugin-cms/src/org/ametys/cms/content/consistency/GlobalContentConsistencyGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/consistency/GlobalContentConsistencyGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/consistency/GlobalContentConsistencyGenerator.java (working copy) @@ -31,14 +31,14 @@ import org.apache.cocoon.xml.XMLUtils; import org.xml.sax.SAXException; -import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.repository.Content; import org.ametys.cms.repository.ContentQueryHelper; import org.ametys.cms.repository.DefaultContent; import org.ametys.cms.transformation.ConsistencyChecker; -import org.ametys.cms.transformation.ConsistencyCheckerExtensionPoint; import org.ametys.cms.transformation.ConsistencyChecker.CHECK; +import org.ametys.cms.transformation.ConsistencyCheckerExtensionPoint; import org.ametys.plugins.repository.AmetysObjectIterable; import org.ametys.plugins.repository.AmetysObjectResolver; import org.ametys.plugins.repository.RepositoryConstants; @@ -70,10 +70,13 @@ protected UsersManager _usersManager; /** The content type extension point */ protected ContentTypeExtensionPoint _cTypeExtPt; + /** Helper for content types */ + protected ContentTypesHelper _cTypesHelper; /** The name cache */ protected Map<String, String> _nameCache; + @Override public void service(ServiceManager serviceManager) throws ServiceException { @@ -82,6 +85,7 @@ _consistencyCheckerExtensionPoint = (ConsistencyCheckerExtensionPoint) serviceManager.lookup(ConsistencyCheckerExtensionPoint.ROLE); _cTypeExtPt = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); _usersManager = (UsersManager) serviceManager.lookup(UsersManager.ROLE); + _cTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); } @Override @@ -177,12 +181,10 @@ */ protected void _saxContentConsistency(Content content, int successCount, int unknownCount, int failureCount) throws SAXException { - ContentType contentType = _cTypeExtPt.getExtension(content.getType()); - AttributesImpl atts = new AttributesImpl(); atts.addCDATAAttribute("id", content.getId()); atts.addCDATAAttribute("title", content.getTitle()); - atts.addCDATAAttribute("smallIcon", contentType != null ? contentType.getSmallIcon() : "/plugins/cms/resources/img/contenttype/unknown-small.png"); + atts.addCDATAAttribute("smallIcon", _cTypesHelper.getSmallIcon(content)); atts.addCDATAAttribute("failure-count", Integer.toString(failureCount)); atts.addCDATAAttribute("unknown-count", Integer.toString(unknownCount)); Index: main/plugin-cms/src/org/ametys/cms/content/AdditionalContentPropertiesGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/AdditionalContentPropertiesGenerator.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/content/AdditionalContentPropertiesGenerator.java (revision 0) @@ -0,0 +1,237 @@ +/* + * Copyright 2013 Anyware Services + * + * 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.ametys.cms.content; + +import java.io.IOException; +import java.util.Comparator; +import java.util.TreeSet; + +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.generation.ServiceableGenerator; +import org.apache.cocoon.xml.AttributesImpl; +import org.apache.cocoon.xml.XMLUtils; +import org.apache.commons.lang3.StringUtils; +import org.xml.sax.SAXException; + +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.repository.Content; +import org.ametys.plugins.repository.AmetysObjectResolver; +import org.ametys.plugins.repository.version.VersionableAmetysObject; +import org.ametys.runtime.util.I18nUtils; +import org.ametys.runtime.util.I18nizableText; + +/** + * Generates addition information on content + */ +public class AdditionalContentPropertiesGenerator extends ServiceableGenerator +{ + /** The AmetysObject resolver. */ + protected AmetysObjectResolver _resolver; + private ContentTypeExtensionPoint _contentTypeEP; + private I18nUtils _i18nUtils; + + @Override + public void service(ServiceManager serviceManager) throws ServiceException + { + super.service(serviceManager); + _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); + _contentTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); + _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); + } + + @Override + public void generate() throws IOException, SAXException, ProcessingException + { + Request request = ObjectModelHelper.getRequest(objectModel); + + String contentId = parameters.getParameter("contentId", request.getParameter("contentId")); + + Content content = null; + if (StringUtils.isNotEmpty(contentId)) + { + content = _resolver.resolveById(contentId); + } + else + { + content = (Content) request.getAttribute(Content.class.getName()); + } + + contentHandler.startDocument(); + + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", content.getId()); + XMLUtils.startElement(contentHandler, "content", attrs); + + // Take the HEAD revision + String revision = null; + if (content instanceof VersionableAmetysObject) + { + revision = ((VersionableAmetysObject) content).getRevision(); + ((VersionableAmetysObject) content).switchToRevision(null); + } + + _saxContentTypesHierarchy (content); + _saxMixinsHierarchy(content); + _saxReferencingContents(content); + + if (content instanceof VersionableAmetysObject && revision != null) + { + ((VersionableAmetysObject) content).switchToRevision(revision); + } + + XMLUtils.endElement(contentHandler, "content"); + contentHandler.endDocument(); + } + + /** + * Sax the referencing contents + * @param content The content + * @throws SAXException if an error occurred while saxing + */ + protected void _saxReferencingContents (Content content) throws SAXException + { + XMLUtils.startElement(contentHandler, "referencing-contents"); + + for (Content referrerContent : content.getReferencingContents()) + { + AttributesImpl refererAttrs = new AttributesImpl(); + refererAttrs.addCDATAAttribute("id", referrerContent.getId()); + refererAttrs.addCDATAAttribute("name", referrerContent.getName()); + refererAttrs.addCDATAAttribute("title", referrerContent.getTitle()); + + XMLUtils.createElement(contentHandler, "referencing-content", refererAttrs); + } + + XMLUtils.endElement(contentHandler, "referencing-contents"); + } + + /** + * SAX the content types hierarchy + * @param content The content type + * @throws SAXException if an error occurred while saxing + */ + protected void _saxContentTypesHierarchy (Content content) throws SAXException + { + // Sort content types + TreeSet<ContentType> treeSet = new TreeSet<ContentType>(new ContentTypeComparator()); + for (String id : content.getTypes()) + { + ContentType contentType = _contentTypeEP.getExtension(id); + treeSet.add(contentType); + } + + XMLUtils.startElement(contentHandler, "content-types"); + for (ContentType cType : treeSet) + { + _saxContentType (cType, content.getLanguage()); + } + XMLUtils.endElement(contentHandler, "content-types"); + } + + /** + * SAX the content types hierarchy + * @param content The content type + * @throws SAXException if an error occurred while saxing + */ + protected void _saxMixinsHierarchy (Content content) throws SAXException + { + XMLUtils.startElement(contentHandler, "mixins"); + + String[] contentTypes = content.getMixinTypes(); + for (String id : contentTypes) + { + ContentType cType = _contentTypeEP.getExtension(id); + _saxMixin(cType, content.getLanguage()); + } + + XMLUtils.endElement(contentHandler, "mixins"); + } + + /** + * SAX the content type hierarchy + * @param cType The content type + * @param lang The language + * @throws SAXException if an error occurred while saxing + */ + protected void _saxContentType (ContentType cType, String lang) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + + attrs.addCDATAAttribute("id", cType.getId()); + attrs.addCDATAAttribute("icon", cType.getSmallIcon()); + attrs.addCDATAAttribute("label", _i18nUtils.translate(cType.getLabel(), lang)); + + XMLUtils.startElement(contentHandler, "content-type", attrs); + + for (String id : cType.getSupertypeIds()) + { + ContentType superType = _contentTypeEP.getExtension(id); + _saxContentType(superType, lang); + } + XMLUtils.endElement(contentHandler, "content-type"); + } + + /** + * SAX the mixin hierarchy + * @param mixin The mixin type + * @param lang The language + * @throws SAXException if an error occurred while saxing + */ + protected void _saxMixin (ContentType mixin, String lang) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + + attrs.addCDATAAttribute("id", mixin.getId()); + attrs.addCDATAAttribute("icon", mixin.getSmallIcon()); + attrs.addCDATAAttribute("label", _i18nUtils.translate(mixin.getLabel(), lang)); + + XMLUtils.startElement(contentHandler, "mixin", attrs); + + for (String id : mixin.getSupertypeIds()) + { + ContentType superType = _contentTypeEP.getExtension(id); + _saxMixin(superType, lang); + } + XMLUtils.endElement(contentHandler, "mixin"); + } + + class ContentTypeComparator implements Comparator<ContentType> + { + @Override + public int compare(ContentType c1, ContentType c2) + { + I18nizableText t1 = c1.getLabel(); + I18nizableText t2 = c2.getLabel(); + + String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); + String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); + + int compareTo = str1.toString().compareTo(str2.toString()); + if (compareTo == 0) + { + // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet + // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal + return 1; + } + return compareTo; + } + } +} Index: main/plugin-cms/src/org/ametys/cms/content/MetadataSetDefGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/MetadataSetDefGenerator.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/MetadataSetDefGenerator.java (working copy) @@ -32,8 +32,8 @@ import org.xml.sax.SAXException; import org.ametys.cms.contenttype.AbstractMetadataSetElement; -import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.Fieldset; import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.cms.contenttype.MetadataDefinitionReference; @@ -44,7 +44,6 @@ import org.ametys.cms.contenttype.RichTextMetadataDefinition; import org.ametys.cms.contenttype.SemanticAnnotation; import org.ametys.cms.repository.Content; -import org.ametys.plugins.repository.AmetysRepositoryException; import org.ametys.runtime.util.I18nizableText; import org.ametys.runtime.util.parameter.Enumerator; import org.ametys.runtime.util.parameter.ParameterHelper; @@ -59,6 +58,8 @@ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; /** Metadata manager. */ protected MetadataManager _metadataManager; + /** Helper for content types */ + protected ContentTypesHelper _contentTypesHelper; @Override public void service(ServiceManager serviceManager) throws ServiceException @@ -66,6 +67,7 @@ super.service(serviceManager); _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); _metadataManager = (MetadataManager) serviceManager.lookup(MetadataManager.ROLE); + _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); } @Override @@ -108,23 +110,21 @@ */ protected void _saxMetadataSet(Content content, String metadataSetName, boolean isEditionMetadataSet) throws SAXException, ProcessingException, IOException { - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); - MetadataSet metadataSet = null; if (isEditionMetadataSet) { - metadataSet = contentType.getMetadataSetForEdition(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes()); } else { - metadataSet = contentType.getMetadataSetForView(metadataSetName); + metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); } if (metadataSet == null) { throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type '%s'", - metadataSetName, isEditionMetadataSet ? "edition" : "view", contentType.getId())); + metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ','))); } _saxMetadataSetElement(content, null, metadataSet); @@ -152,12 +152,10 @@ throw new IllegalArgumentException("Unable to get the metadata definition of metadata \"" + metadataName + "\""); } - ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); - String metadataPath = StringUtils.stripStart(metaDef.getId(), "/"); - if (contentType.canRead(content, metadataPath)) + if (_contentTypesHelper.canRead(content, metadataPath)) { - _saxMetadataDefinition(contentType, content, metadataDefRef, metaDef, metadataPath); + _saxMetadataDefinition(content, metadataDefRef, metaDef, metadataPath); } } else @@ -191,15 +189,7 @@ { if (content != null) { - String contentTypeId = content.getType(); - ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); - - if (contentType == null) - { - throw new AmetysRepositoryException("Unknown content type: " + contentTypeId); - } - - metadataDefinition = contentType.getMetadataDefinition(metadataName); + metadataDefinition = _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); } } else @@ -212,14 +202,13 @@ /** * Sax the metadata definition - * @param contentType The content type * @param content The content * @param metadataSetElement The metadata set element * @param metaDef The metadata definition * @param metadataPath Path of the metadata. Can not be null. * @throws SAXException if an error occurs while SAXing. */ - protected void _saxMetadataDefinition(ContentType contentType, Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, String metadataPath) throws SAXException + protected void _saxMetadataDefinition(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef, String metadataPath) throws SAXException { XMLUtils.startElement(contentHandler, metaDef.getName()); @@ -315,7 +304,7 @@ XMLUtils.endElement(contentHandler, "enumeration"); } - if (!contentType.canWrite(content, metadataPath)) + if (!_contentTypesHelper.canWrite(content, metadataPath)) { XMLUtils.createElement(contentHandler, "can-not-write", "true"); } Index: main/plugin-cms/src/org/ametys/cms/content/GetWrapperContextAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/GetWrapperContextAction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/GetWrapperContextAction.java (working copy) @@ -16,7 +16,9 @@ package org.ametys.cms.content; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.avalon.framework.parameters.Parameters; @@ -30,6 +32,7 @@ import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceResolver; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.repository.Content; /** @@ -42,11 +45,13 @@ { /** The source resolver */ protected SourceResolver _srcResolver; + private ContentTypesHelper _contentTypesHelper; @Override public void service(ServiceManager manager) throws ServiceException { _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); + _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } @Override @@ -71,38 +76,51 @@ */ protected String getWrapperURI(Request request, String uri) throws IOException { - String wrapperURI = null; - - Content content = (Content) request.getAttribute(Content.class.getName()); - String cType = content.getType(); - - // Ex : context://WEB-INF/stylesheets/content/_wrapper/content-[cType].xsl - int index = uri.indexOf(".xsl"); - wrapperURI = "context://WEB-INF/" + uri.substring(0, index) + "-" + cType + ".xsl"; - Source src = _srcResolver.resolveURI(wrapperURI); - - if (!src.exists()) + for (String wrapperURI : getSourceURIs(request, uri)) { - if (getLogger().isDebugEnabled()) - { - getLogger().debug("Failed to find a file at '" + wrapperURI + "'"); - } - - // Ex : context://WEB-INF/stylesheets/content/_wrapper/content.xsl - wrapperURI = "context://WEB-INF/" + uri; - src = _srcResolver.resolveURI(wrapperURI); + Source src = _srcResolver.resolveURI(wrapperURI); if (!src.exists()) { if (getLogger().isDebugEnabled()) { - getLogger().debug("Failed to find a file at '" + wrapperURI + "'"); + getLogger().debug("Failed to find a stylesheet at '" + wrapperURI + "'."); } - - // Ex : worspace:workspaceName://stylesheets/content/_wrapper/content.xsl - wrapperURI = "workspace:" + request.getAttribute("workspaceName") + "://" + uri; + } + else + { + return wrapperURI; } } - return wrapperURI; + return null; + } + + /** + * Get the source URI for wrapped content + * @param request The request + * @param requestedURI The requested URI + * @return the wrapped URI + */ + protected List<String> getSourceURIs (Request request, String requestedURI) + { + Content content = (Content) request.getAttribute(Content.class.getName()); + + List<String> sourceURIs = new ArrayList<String>(); + + // Ex: context://WEB-INF/stylesheets/content/_wrapper/content-[cType].xsl + int index = requestedURI.indexOf(".xsl"); + + String cTypeId = _contentTypesHelper.getContentTypeIdForRendering(content); + sourceURIs.add("context://WEB-INF/" + requestedURI.substring(0, index) + "-" + cTypeId + ".xsl"); + + cTypeId = _contentTypesHelper.getFirstContentType(content).getId(); + sourceURIs.add("context://WEB-INF/" + requestedURI.substring(0, index) + "-" + cTypeId + ".xsl"); + + sourceURIs.add("context://WEB-INF/" + requestedURI); + + sourceURIs.add("workspace:" + request.getAttribute("workspaceName") + "://" + requestedURI); + + return sourceURIs; + } } Index: main/plugin-cms/src/org/ametys/cms/content/GetContentAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/content/GetContentAction.java (revision 25585) +++ main/plugin-cms/src/org/ametys/cms/content/GetContentAction.java (working copy) @@ -27,8 +27,8 @@ import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.SourceResolver; -import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.repository.Content; import org.ametys.plugins.repository.AmetysObjectResolver; import org.ametys.plugins.repository.version.VersionableAmetysObject; @@ -51,12 +51,16 @@ protected ContentTypeExtensionPoint _contentTypeExtensionPoint; /** The ametys object resolver */ protected AmetysObjectResolver _resolver; + /** Helper for content types */ + protected ContentTypesHelper _contentTypeshelper; + @Override public void service(ServiceManager serviceManager) throws ServiceException { super.service(serviceManager); _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); + _contentTypeshelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); } public Map<String, String> act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception @@ -97,11 +101,10 @@ result.put(RESULT_RENDERINGLANGUAGE, (String) request.getAttribute(RESULT_RENDERINGLANGUAGE)); } - String contentTypeId = content.getType(); - ContentType cType = _contentTypeExtensionPoint.getExtension(contentTypeId); - + String contentTypeId = _contentTypeshelper.getContentTypeIdForRendering(content); + result.put(RESULT_CONTENTTYPE, contentTypeId); - result.put(RESULT_PLUGINNAME, cType.getPluginName()); + result.put(RESULT_PLUGINNAME, _contentTypeshelper.getContentTypePluginForRendering(content)); result.put(RESULT_CONTENTVERSION, revision); return result; Index: main/plugin-cms/stylesheets/default-content.xsl =================================================================== --- main/plugin-cms/stylesheets/default-content.xsl (revision 25585) +++ main/plugin-cms/stylesheets/default-content.xsl (working copy) @@ -99,11 +99,19 @@ <xsl:template name="display-content-metadata"> <xsl:variable name="contentId" select="@id" /> - <xsl:variable name="contentTypeId" select="ametys:contentType($contentId)" /> + <xsl:variable name="contentTypeIds" select="ametys:contentTypes($contentId)" /> - <xsl:variable name="contentTypeTags" select="ametys:contentTypeTags($contentTypeId)" /> + <xsl:variable name="isSimple"> + <xsl:for-each select="ametys:contentTypes($contentId)"> + <xsl:choose> + <xsl:when test="ametys:contentTypeTags(@id)[@name = 'simple']">true</xsl:when> + <xsl:otherwise></xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:variable> + <xsl:choose> - <xsl:when test="$contentTypeTags[@name = 'simple']"> + <xsl:when test="$isSimple != ''"> <xsl:value-of select="@title"/> </xsl:when> <xsl:otherwise> Index: main/plugin-cms/pages/properties/view.xsl =================================================================== --- main/plugin-cms/pages/properties/view.xsl (revision 25585) +++ main/plugin-cms/pages/properties/view.xsl (working copy) @@ -77,45 +77,82 @@ <span style="background: url('{$contextPath}{content/workflow-step/@icon-small}') left center no-repeat; padding-left: 20px; line-height: 16px; display: inline-block;"><xsl:value-of select="content/workflow-step"/></span> </p> - <p> - <span class="title"><i18n:text i18n:key="UITOOL_DETAILS_CONTENT_CONTENT_TYPE"/></span> - </p> + <xsl:call-template name="content-types"/> + <xsl:call-template name="mixins"/> - <ul> + </div> + </xsl:template> + + <!-- Content Types --> + <xsl:template name="content-types"> + <p> + <span class="title"> + <xsl:choose> + <xsl:when test="count(content/content-types/content-type) = 1"><i18n:text i18n:key="UITOOL_DETAILS_CONTENT_CONTENT_TYPE"/></xsl:when> + <xsl:otherwise><i18n:text i18n:key="UITOOL_DETAILS_CONTENT_CONTENT_TYPES"/></xsl:otherwise> + </xsl:choose> + </span> + + <xsl:if test="content/content-types/content-type/content-type"> + <xsl:text> </xsl:text><a class="showhide" href="javascript:Ametys.plugins.cms.content.tool.PropertiesTool.showHideSubTypes()">(...)</a> + </xsl:if> + </p> + + <ul> + <xsl:for-each select="content/content-types/content-type"> <li> - <img src="{$contextPath}{content/content-type/@icon-small}" alt="" title="{content/content-type}"/><xsl:value-of select="content/content-type"/> - <xsl:if test="content/super-types/super-type"> - <ul> - <xsl:apply-templates select="content/super-types/super-type"> - <xsl:with-param name="level" select="1"/> - </xsl:apply-templates> + <img src="{$contextPath}{@icon}" alt="" title="{@label}"/><xsl:value-of select="@label"/> + <xsl:if test="content-type"> + <ul class="subtypes hidden"> + <xsl:apply-templates select="content-type"/> </ul> </xsl:if> </li> + </xsl:for-each> + </ul> + </xsl:template> + + <!-- Mixins --> + <xsl:template name="mixins"> + <xsl:if test="content/mixins/mixin"> + <p> + <span class="title"> + <xsl:choose> + <xsl:when test="count(content/mixins/mixin) = 1"><i18n:text i18n:key="UITOOL_DETAILS_CONTENT_MIXIN"/></xsl:when> + <xsl:otherwise><i18n:text i18n:key="UITOOL_DETAILS_CONTENT_MIXINS"/></xsl:otherwise> + </xsl:choose> + </span> + + <xsl:if test="content/mixins/mixin/mixin"> + <xsl:text> </xsl:text><a class="showhide" href="javascript:Ametys.plugins.cms.content.tool.PropertiesTool.showHideMixins()">(...)</a> + </xsl:if> + </p> + <ul> + <xsl:for-each select="content/mixins/mixin"> + <li> + <img src="{$contextPath}{@icon}" alt="" title="{@label}"/><xsl:value-of select="@label"/> + <xsl:if test="mixin"> + <ul class="mixins hidden"> + <xsl:apply-templates select="mixin"/> + </ul> + </xsl:if> + </li> + </xsl:for-each> </ul> - </div> + </xsl:if> </xsl:template> - <xsl:template match="super-type"> - <xsl:param name="level"/> - - <xsl:variable name="padding" select="$level * 20"/> - <xsl:variable name="bgposition" select="($level - 1) * 20"/> - - <!-- <li style="padding-left: {$padding}px; background-position: {$bgposition}px center"> --> + <xsl:template match="content-type|mixin"> <li> <xsl:attribute name="class">content-type<xsl:if test="position() = last()"> last</xsl:if></xsl:attribute> <img src="{$contextPath}{@icon}" alt="" title="{@label}"/><xsl:value-of select="@label"/> - <xsl:if test="super-type"> + <xsl:if test="content-type|mixin"> <ul> - <xsl:apply-templates select="super-type"> - <xsl:with-param name="level" select="$level + 1"/> - </xsl:apply-templates> + <xsl:apply-templates select="content-type|mixin"/> </ul> </xsl:if> </li> - </xsl:template> <xsl:template match="*" mode="metadata-set"> @@ -167,10 +204,18 @@ <xsl:choose> <xsl:when test="$type = 'CONTENT' or $type = 'SUB_CONTENT'"> <xsl:variable name="contentId" select="@id" /> - <xsl:variable name="contentTypeId" select="ametys:contentType($contentId)" /> - <xsl:variable name="contentTypeTags" select="ametys:contentTypeTags($contentTypeId)" /> + + <xsl:variable name="isSimple"> + <xsl:for-each select="ametys:contentTypes($contentId)"> + <xsl:choose> + <xsl:when test="ametys:contentTypeTags(@id)[@name = 'simple']">true</xsl:when> + <xsl:otherwise></xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:variable> + <xsl:choose> - <xsl:when test="$contentTypeTags[@name = 'simple']"> + <xsl:when test="$isSimple != ''"> <xsl:value-of select="@title"/> </xsl:when> <xsl:otherwise> Index: main/plugin-cms/plugin.xml =================================================================== --- main/plugin-cms/plugin.xml (revision 25585) +++ main/plugin-cms/plugin.xml (working copy) @@ -92,6 +92,12 @@ <!-- Extension point for defining a content type --> </extension-point> + <!-- FIXME RUNTIME-1023 --> + <extension-point id="org.ametys.cms.contenttype.DynamicContentTypeExtentionPoint" + class="org.ametys.cms.contenttype.DynamicContentTypeDescriptorExtentionPoint"> + </extension-point> + + <extension-point id="org.ametys.cms.transformation.URIResolverExtensionPoint" class="org.ametys.cms.transformation.URIResolverExtensionPoint"> <!-- Extension point for resolving links from the inline editor --> @@ -507,6 +513,7 @@ <file>js/Ametys/cms/form/widget/SelectContent.i18n.js</file> <file>js/Ametys/cms/form/widget/SelectSimpleContent.js</file> <file>js/Ametys/cms/form/widget/ComposeContent.i18n.js</file> + <file>js/Ametys/cms/form/widget/FlipFlap.i18n.js</file> <file>js/Ametys/cms/uihelper/ChooseLocation.i18n.js</file> <file>js/Ametys/cms/uihelper/ChooseLocation/LoadGoogleMaps.i18n.js</file> @@ -4446,7 +4453,7 @@ <extensions> <extension id="org.ametys.cms.CreateContentMenu" point="org.ametys.cms.ribbon.RibbonControlsManager" - class="org.ametys.cms.clientsideelement.CreateContentMenu"> + class="org.ametys.cms.clientsideelement.ContentTypesGallery"> <class name="Ametys.ribbon.element.ui.ButtonController"> <label i18n="true">PLUGINS_CMS_CONTENT_CREATECONTENTMENU_LABEL</label> <description i18n="true">PLUGINS_CMS_CONTENT_CREATECONTENTMENU_DESCRIPTION</description> @@ -5473,6 +5480,13 @@ </extensions> </feature> + <feature name="cliensideelement.helper"> + <components> + <component class="org.ametys.cms.clientsideelement.content.SmartContentClientSideElementHelper" + role="org.ametys.cms.clientsideelement.content.SmartContentClientSideElementHelper"/> + </components> + </feature> + <feature name="linksresolver"> <components> <component class="org.ametys.cms.transformation.xslt.ResolveURIComponent" role="org.ametys.cms.transformation.xslt.ResolveURIComponent"/> Index: main/plugin-cms/i18n/messages_en.xml =================================================================== --- main/plugin-cms/i18n/messages_en.xml (revision 25585) +++ main/plugin-cms/i18n/messages_en.xml (working copy) @@ -116,6 +116,7 @@ <message key="UITOOL_SEARCH_CONTENT_LANGUAGE">Language</message> <message key="UITOOL_SEARCH_CONTENT_FULLTEXT">Full text</message> <message key="UITOOL_SEARCH_CONTENT_CONTENT_TYPE">Content type</message> + <message key="UITOOL_SEARCH_CONTENT_MIXIN">Mixin</message> <message key="UITOOL_SEARCH_CONTENT_LASTMODIFIED">Last modified</message> <message key="UITOOL_SEARCH_CONTENT_LASTMODIFIED_BEFORE">Modified before</message> <message key="UITOOL_SEARCH_CONTENT_LASTMODIFIED_AFTER">Modified after</message> @@ -690,6 +691,55 @@ <message key="CONTENT_UNLOCK_LABEL">Content locked</message> <message key="CONTENT_UNLOCK_DESCRIPTION">Not yet implemented.</message> + <!-- ADD / REMOVE CONTENT TYPE --> + <message key="CONTENT_SET_CONTENT_TYPE_LABEL">Content type</message> + + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_NOCONTENT">This button is disabled because it allows you update content types and no content is currently selected.</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_REFRESHING">Please wait... On-going operations on server.</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_MANYCONTENT">Action inactive as current selection include more than one content.<br/>Please select only one content.</message> + + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_ERROR">This button is disabled.</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_START">Click here to add or delete a content type to the content </message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_CONTENT">'{0}'</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_END">.</message> + + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_TITLE">Unable to add the content type</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT">Unable to add content type: content is not editable</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_INVALID_CONTENT_TYPE">The selected content type is not compatible with the selected content: the content type declares at least one metadata already defined in the content through one of its content types or mixins.</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_NO_CONTENT_TYPE">The selected content type is a 'mixin', it can not be added as a content type.</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR">An error has occurred: unable to add content type.</message> + + <message key="CONTENT_REMOVE_CONTENT_TYPE_CONFIRM_TITLE">Delete content type</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_CONFIRM">Are you sure you want to delete this content type?</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR_TITLE">Failed to delete</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR">An error has occurred: can not delete content type</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT">Unable to delete content type: content is not editable</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR_EMPTY_LIST">Unable to delete content type: content must be linked to at least one type of content</message> + + <!-- ADD / REMOVE CONTENT TYPE --> + <message key="CONTENT_SET_MIXIN_LABEL">Mixin</message> + + <message key="CONTENT_SET_MIXIN_DESCRIPTION_NOCONTENT">This button is disabled because it allows you update mixin types and no content is currently selected.</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_REFRESHING">Please wait... On-going operations on server.</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_MANYCONTENT">Action inactive as current selection include more than one content.<br/>Please select only one content.</message> + + <message key="CONTENT_SET_MIXIN_DESCRIPTION_ERROR">This button is disabled.</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_START">Click here to add or delete a mixin type to the content </message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_CONTENT">'{0}'</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_END">.</message> + + <message key="CONTENT_ADD_MIXIN_ERROR_TITLE">Unable to add mixin</message> + <message key="CONTENT_ADD_MIXIN_ERROR_NON_MODIFIABLE_CONTENT">Unable to add mixin: content is not editable</message> + <message key="CONTENT_ADD_MIXIN_ERROR_INVALID_MIXIN">The selected mixin is not compatible with the selected content: the mixin declares at least one metadata already defined in the content through one of its content types or mixins.</message> + <message key="CONTENT_ADD_MIXIN_ERROR_NO_MIXIN">The selected content type is not 'mixin', it can not be added as a mixin.</message> + <message key="CONTENT_ADD_MIXIN_ERROR">An error has occurred: unable to add mixin.</message> + + <message key="CONTENT_REMOVE_MIXIN_CONFIRM_TITLE">Delete mixin</message> + <message key="CONTENT_REMOVE_MIXIN_CONFIRM">Are you sure you want to delete this mixin?</message> + <message key="CONTENT_REMOVE_MIXIN_ERROR_TITLE">Failed to delete</message> + <message key="CONTENT_REMOVE_MIXIN_ERROR">An error has occurred: can not delete mixin</message> + <message key="CONTENT_REMOVE_MIXIN_ERROR_NON_MODIFIABLE_CONTENT">Unable to delete mixin: content is not editable</message> + <!-- DELETE --> <message key="CONTENT_DELETE_LABEL">Delete</message> Index: main/plugin-cms/i18n/messages_fr.xml =================================================================== --- main/plugin-cms/i18n/messages_fr.xml (revision 25585) +++ main/plugin-cms/i18n/messages_fr.xml (working copy) @@ -116,6 +116,7 @@ <message key="UITOOL_SEARCH_CONTENT_LANGUAGE">Langue</message> <message key="UITOOL_SEARCH_CONTENT_FULLTEXT">Texte libre</message> <message key="UITOOL_SEARCH_CONTENT_CONTENT_TYPE">Type de contenu</message> + <message key="UITOOL_SEARCH_CONTENT_MIXIN">Rôle</message> <message key="UITOOL_SEARCH_CONTENT_LASTMODIFIED">Dernière modification</message> <message key="UITOOL_SEARCH_CONTENT_LASTMODIFIED_BEFORE">Modifié avant le</message> <message key="UITOOL_SEARCH_CONTENT_LASTMODIFIED_AFTER">Modifié après le</message> @@ -403,6 +404,9 @@ <message key="UITOOL_DETAILS_CONTENT_LANGUAGE">Langue</message> <message key="UITOOL_DETAILS_CONTENT_WORKFLOW_STEP">Cycle de vie</message> <message key="UITOOL_DETAILS_CONTENT_CONTENT_TYPE">Type de contenu</message> + <message key="UITOOL_DETAILS_CONTENT_CONTENT_TYPES">Types de contenu</message> + <message key="UITOOL_DETAILS_CONTENT_MIXIN">Rôle</message> + <message key="UITOOL_DETAILS_CONTENT_MIXINS">Rôles</message> <message key="UITOOL_DETAILS_ERROR">Une erreur est survenue : elle a empêché d'afficher les propriétés du contenu</message> <message key="UITOOL_DETAILS_DATE_FORMAT">j F Y à H:i</message> <message key="UITOOL_DETAILS_BOOLEAN_TRUE">Oui</message> @@ -689,6 +693,55 @@ <message key="CONTENT_UNLOCK_LABEL">Contenu verrouillé</message> <message key="CONTENT_UNLOCK_DESCRIPTION">Ce bouton est désactivé dans cette version de développement.</message> + <!-- ADD / REMOVE CONTENT TYPE --> + <message key="CONTENT_SET_CONTENT_TYPE_LABEL">Type de contenu</message> + + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_NOCONTENT">Ce bouton est désactivé car il permet d'ajouter des types de contenus et actuellement aucun contenu n'est sélectionné.</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_REFRESHING">Veuillez patienter... ce bouton est désactivé pendant que des vérifications sont en cours sur le serveur.</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_MANYCONTENT">Ce bouton est désactivé car plusieurs contenus sont actuellement sélectionnés.<br/>Veuillez ne sélectionner qu'un seul contenu pour continuer.</message> + + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_ERROR">Ce bouton est désactivé.</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_START">Cliquez ici pour ajouter ou supprimer un type de contenu pour le contenu </message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_CONTENT">'{0}'</message> + <message key="CONTENT_SET_CONTENT_TYPE_DESCRIPTION_END">.</message> + + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_TITLE">Impossible d'ajouter le type de contenu</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT">Impossible d'ajouter le type de contenu: le contenu n'est pas modifiable</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_INVALID_CONTENT_TYPE">Le type de contenu choisi n'est pas compatible avec le contenu sélectionné: le type de contenu possède au moins une métadonnée déjà définit sur le contenu au travers l'un de ces types de contenus ou rôles.</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR_NO_CONTENT_TYPE">Le type de contenu choisit est un 'rôle', il ne peut pas être ajouté en tant que type de contenu.</message> + <message key="CONTENT_ADD_CONTENT_TYPE_ERROR">Une erreur est survenue : impossible d'ajouter le type de contenu</message> + + <message key="CONTENT_REMOVE_CONTENT_TYPE_CONFIRM_TITLE">Supprimer le type de contenu</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_CONFIRM">Etes-vous sûrs de vouloir supprimer ce type de contenu ?</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR_TITLE">Echec de la suppression</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR">Une erreur est survenue : impossible de supprimer le type de contenu</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT">Impossible de supprimer le type de contenu: le contenu n'est pas modifiable</message> + <message key="CONTENT_REMOVE_CONTENT_TYPE_ERROR_EMPTY_LIST">Impossible de supprimer le type de contenu: le contenu doit être lié à au moins un type de contenu</message> + + <!-- ADD / REMOVE CONTENT TYPE --> + <message key="CONTENT_SET_MIXIN_LABEL">Rôle</message> + + <message key="CONTENT_SET_MIXIN_DESCRIPTION_NOCONTENT">Ce bouton est désactivé car il permet d'ajouter des rôles d'un contenu et actuellement aucun contenu n'est sélectionné.</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_REFRESHING">Veuillez patienter... ce bouton est désactivé pendant que des vérifications sont en cours sur le serveur.</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_MANYCONTENT">Ce bouton est désactivé car plusieurs contenus sont actuellement sélectionnés.<br/>Veuillez ne sélectionner qu'un seul contenu pour continuer.</message> + + <message key="CONTENT_SET_MIXIN_DESCRIPTION_ERROR">Ce bouton est désactivé.</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_START">Cliquez ici pour ajouter ou supprimer un rôle pour le contenu </message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_CONTENT">'{0}'</message> + <message key="CONTENT_SET_MIXIN_DESCRIPTION_END">.</message> + + <message key="CONTENT_ADD_MIXIN_ERROR_TITLE">Impossible d'ajouter le rôle</message> + <message key="CONTENT_ADD_MIXIN_ERROR_NON_MODIFIABLE_CONTENT">Impossible d'ajouter le rôle: le contenu n'est pas modifiable</message> + <message key="CONTENT_ADD_MIXIN_ERROR_INVALID_MIXIN">Le rôle choisi n'est pas compatible avec le contenu sélectionné: le rôle possède au moins une métadonnée déjà définit sur le contenu au travers l'un de ces types de contenus ou rôles.</message> + <message key="CONTENT_ADD_MIXIN_ERROR_NO_MIXIN">Le type de contenu choisit n'est pas un 'rôle', il ne peut pas être ajouté en tant que 'rôle'.</message> + <message key="CONTENT_ADD_MIXIN_ERROR">Une erreur est survenue : impossible d'ajouter le rôle</message> + + <message key="CONTENT_REMOVE_MIXIN_CONFIRM_TITLE">Supprimer le rôle</message> + <message key="CONTENT_REMOVE_MIXIN_CONFIRM">Etes-vous sûrs de vouloir supprimer ce rôle ?</message> + <message key="CONTENT_REMOVE_MIXIN_ERROR_TITLE">Echec de la suppression</message> + <message key="CONTENT_REMOVE_MIXIN_ERROR">Une erreur est survenue : impossible de supprimer le rôle</message> + <message key="CONTENT_REMOVE_MIXIN_ERROR_NON_MODIFIABLE_CONTENT">Impossible de supprimer le rôle: le contenu n'est pas modifiable</message> + <!-- DELETE --> <message key="CONTENT_DELETE_LABEL">Supprimer</message> @@ -2155,6 +2208,7 @@ <message key="RIBBON_TABS_TAB_CONTENT_LABEL">Contenu</message> <message key="RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_TOOL_LABEL">Outils</message> <message key="RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_ACTION_LABEL">Actions</message> + <message key="RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_CONTENT_TYPE_LABEL">Type</message> <message key="RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_WORKFLOW_LABEL">Cycle de vie</message> <message key="RIBBON_TABS_TAB_CONTENT_GROUPS_GROUP_CLIPBOARD_LABEL">Presse-papiers</message> Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/ContentTypesGalleryController.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/ContentTypesGalleryController.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/ContentTypesGalleryController.i18n.js (revision 0) @@ -0,0 +1,74 @@ +/* + * Copyright 2013 Anyware Services + * + * 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. + */ + +/** + * @private + * This class control a ribbon menu representing the workflow state. + * It HAS to be used in conjunction with the java org.ametys.cms.clientsideelement.WorkflowMenu + */ +Ext.define('Ametys.plugins.cms.content.controller.ContentTypesGalleryController', { + extend: 'Ametys.plugins.cms.content.controller.SmartContentController', + + /** + * @inheritdoc + * Add a listener on 'menushow' event + */ + createUI: function () + { + var elt = this.callParent(arguments); + elt.on ('menushow', this._onMenuShow, this); + return elt; + }, + + /** + * Listener on 'menushow' event<br> + * @param {Ext.button.Button} btn The button + * @param {Ext.menu.Menu} menu The menu + * @private + */ + _onMenuShow: function (btn, menu) + { + var target = this.getMatchingTargets()[0]; + + this._mask = Ext.create('Ext.LoadMask', { + target: menu.items.get(0), + msg: "<i18n:text i18n:key='KERNEL_LOADMASK_DEFAULT_MESSAGE' i18n:catalogue='kernel'/>" + }); + this._mask.show(); + + // Current content types can not be up-to-date, send request to update + var content = Ametys.cms.content.ContentDAO.getContent(target.getParameters().id, Ext.bind(this._fillGallery, this, [menu], 1)); + }, + + _fillGallery: function (content, menu) + { + var cTypes = content.getTypes(); + + var menuGallery = menu.items.get(0); + + menuGallery.items.get(0).items.each(function (item) { + var cTypeId = Ametys.ribbon.RibbonManager.getElement(item.controlId).getInitialConfig('contentTypes'); + var state = Ext.Array.contains(cTypes, cTypeId); + item.toggle(state, true); + }); + + /*if (this._mask) + { + this._mask.hide(); + this._mask = null; + }*/ + } +}); \ No newline at end of file Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/MixinGalleryController.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/MixinGalleryController.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/MixinGalleryController.i18n.js (revision 0) @@ -0,0 +1,74 @@ +/* + * Copyright 2013 Anyware Services + * + * 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. + */ + +/** + * @private + * This class control a ribbon menu representing the workflow state. + * It HAS to be used in conjunction with the java org.ametys.cms.clientsideelement.WorkflowMenu + */ +Ext.define('Ametys.plugins.cms.content.controller.MixinGalleryController', { + extend: 'Ametys.plugins.cms.content.controller.SmartContentController', + + /** + * @inheritdoc + * Add a listener on 'menushow' event + */ + createUI: function () + { + var elt = this.callParent(arguments); + elt.on ('menushow', this._onMenuShow, this); + return elt; + }, + + /** + * Listener on 'menushow' event<br> + * @param {Ext.button.Button} btn The button + * @param {Ext.menu.Menu} menu The menu + * @private + */ + _onMenuShow: function (btn, menu) + { + var target = this.getMatchingTargets()[0]; + + this._mask = Ext.create('Ext.LoadMask', { + target: menu.items.get(0), + msg: "<i18n:text i18n:key='KERNEL_LOADMASK_DEFAULT_MESSAGE' i18n:catalogue='kernel'/>" + }); + this._mask.show(); + + // Current content types can not be up-to-date, send request to update + var content = Ametys.cms.content.ContentDAO.getContent(target.getParameters().id, Ext.bind(this._fillGallery, this, [menu], 1)); + }, + + _fillGallery: function (content, menu) + { + var mixins = content.getMixins(); + + var menuGallery = menu.items.get(0); + + menuGallery.items.get(0).items.each(function (item) { + var mixinId = Ametys.ribbon.RibbonManager.getElement(item.controlId).getInitialConfig('contentTypes'); + var state = Ext.Array.contains(mixins, mixinId); + item.toggle(state, true); + }); + + if (this._mask) + { + this._mask.hide(); + this._mask = null; + } + } +}); \ No newline at end of file Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/ContentTypeGalleryController.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/ContentTypeGalleryController.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/ContentTypeGalleryController.i18n.js (revision 0) @@ -0,0 +1,74 @@ +/* + * Copyright 2013 Anyware Services + * + * 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. + */ + +/** + * @private + * This class control a ribbon menu representing the workflow state. + * It HAS to be used in conjunction with the java org.ametys.cms.clientsideelement.WorkflowMenu + */ +Ext.define('Ametys.plugins.cms.content.controller.ContentTypeGalleryController', { + extend: 'Ametys.plugins.cms.content.controller.SmartContentController', + + /** + * @inheritdoc + * Add a listener on 'menushow' event + */ + createUI: function () + { + var elt = this.callParent(arguments); + elt.on ('menushow', this._onMenuShow, this); + return elt; + }, + + /** + * Listener on 'menushow' event<br> + * @param {Ext.button.Button} btn The button + * @param {Ext.menu.Menu} menu The menu + * @private + */ + _onMenuShow: function (btn, menu) + { + var target = this.getMatchingTargets()[0]; + + this._mask = Ext.create('Ext.LoadMask', { + target: menu.items.get(0), + msg: "<i18n:text i18n:key='KERNEL_LOADMASK_DEFAULT_MESSAGE' i18n:catalogue='kernel'/>" + }); + this._mask.show(); + + // Current content types can not be up-to-date, send request to update + var content = Ametys.cms.content.ContentDAO.getContent(target.getParameters().id, Ext.bind(this._fillGallery, this, [menu], 1)); + }, + + _fillGallery: function (content, menu) + { + var cTypes = content.getTypes(); + + var menuGallery = menu.items.get(0); + + menuGallery.items.get(0).items.each(function (item) { + var cTypeId = Ametys.ribbon.RibbonManager.getElement(item.controlId).getInitialConfig('contentTypes'); + var state = Ext.Array.contains(cTypes, cTypeId); + item.toggle(state, true); + }); + + if (this._mask) + { + this._mask.hide(); + this._mask = null; + } + } +}); \ No newline at end of file Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/MixinsGalleryController.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/MixinsGalleryController.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/controller/MixinsGalleryController.i18n.js (revision 0) @@ -0,0 +1,74 @@ +/* + * Copyright 2013 Anyware Services + * + * 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. + */ + +/** + * @private + * This class control a ribbon menu representing the workflow state. + * It HAS to be used in conjunction with the java org.ametys.cms.clientsideelement.WorkflowMenu + */ +Ext.define('Ametys.plugins.cms.content.controller.MixinsGalleryController', { + extend: 'Ametys.plugins.cms.content.controller.SmartContentController', + + /** + * @inheritdoc + * Add a listener on 'menushow' event + */ + createUI: function () + { + var elt = this.callParent(arguments); + elt.on ('menushow', this._onMenuShow, this); + return elt; + }, + + /** + * Listener on 'menushow' event<br> + * @param {Ext.button.Button} btn The button + * @param {Ext.menu.Menu} menu The menu + * @private + */ + _onMenuShow: function (btn, menu) + { + var target = this.getMatchingTargets()[0]; + + this._mask = Ext.create('Ext.LoadMask', { + target: menu.items.get(0), + msg: "<i18n:text i18n:key='KERNEL_LOADMASK_DEFAULT_MESSAGE' i18n:catalogue='kernel'/>" + }); + this._mask.show(); + + // Current content types can not be up-to-date, send request to update + var content = Ametys.cms.content.ContentDAO.getContent(target.getParameters().id, Ext.bind(this._fillGallery, this, [menu], 1)); + }, + + _fillGallery: function (content, menu) + { + var mixins = content.getMixins(); + + var menuGallery = menu.items.get(0); + + menuGallery.items.get(0).items.each(function (item) { + var cTypeId = Ametys.ribbon.RibbonManager.getElement(item.controlId).getInitialConfig('contentTypes'); + var state = Ext.Array.contains(mixins, cTypeId); + item.toggle(state, true); + }); + + if (this._mask) + { + this._mask.hide(); + this._mask = null; + } + } +}); \ No newline at end of file Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/tool/PropertiesTool.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/tool/PropertiesTool.i18n.js (revision 25585) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/tool/PropertiesTool.i18n.js (working copy) @@ -28,6 +28,30 @@ * @property {String} _metadataSetName The metadata set to display. See #cfg-metadata-set-name. */ + statics: { + + showHideSubTypes: function () + { + var show = false; + + Ext.select('.details-view-card ul.subtypes').each (function (ul) { + show = Ext.get(ul).hasCls('hidden'); + Ext.get(ul).toggleCls('hidden'); + }); + + Ext.select('.details-view-card a.showhide-subtypes', true).each (function (a){ + a.update(show ? 'replier tout' : 'deplier tout') + }) + }, + + showHideMixins: function () + { + Ext.select('.details-view-card ul.mixins').each (function (ul) { + Ext.get(ul).toggleCls('hidden'); + }); + } + }, + constructor: function(config) { this.callParent(arguments); Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/actions/ContentTypeActions.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/actions/ContentTypeActions.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/actions/ContentTypeActions.i18n.js (revision 0) @@ -0,0 +1,455 @@ +/* + * Copyright 2011 Anyware Services + * + * 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. + */ + +/** + * Singleton class to create a new content. + * @private + */ +Ext.define('Ametys.plugins.cms.content.actions.ContentTypeActions', { + singleton: true, + + /** + * The default workflow action id + * @private + */ + _DEFAULT_WORKFLOW_ACTION_ID: 220, + + /** + * Action function to be called by the controller. + * Add or remove the selected content type on concerned content + * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function + */ + setContentType: function (controller) + { + var btnConfig = controller.getInitialConfig(); + + var parentController = Ametys.ribbon.RibbonManager.getElement(btnConfig.controllerParentId); + var contentIds = parentController.getContentIds(); + + if (parentController.getContentIds().length > 0) + { + var contentType = btnConfig['contentTypes'] || btnConfig['contentType']; + var actionId = btnConfig['workflowActionId'] ? parseInt(btnConfig['workflowActionId']) : this._DEFAULT_WORKFLOW_ACTION_ID; + var contentId = parentController.getContentIds()[0]; + + if (controller.getUIControls().get(0).pressed) + { + this.removeContentType(contentId, contentType, actionId); + } + else + { + this.addContentType(contentId, contentType, actionId); + } + } + }, + + /** + * Add a content type on concerned content + * @param {String} contentId The id of concerned content + * @param {String} contentType The content type to add + * @param {Number} actionId The workflow action id + */ + addContentType: function (contentId, contentType, actionId) + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/add-content-type', + parameters: { + contentId: contentId, + contentType: contentType, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._addContentTypeCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + }, + + /** + * Remove a content type on concerned content + * @param {String} contentId The id of concerned content + * @param {String} contentType The content type to remove + * @param {Number} actionId The workflow action id + */ + removeContentType: function (contentId, contentType, actionId) + { + Ext.Msg.confirm("<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_CONFIRM_TITLE'/>", + "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_CONFIRM'/>", + Ext.bind(this._confirmRemoveContentType, this, [contentType, contentId, actionId], 1) + ); + }, + + /** + * Callback function invoked after the #removeContentType confirm box is closed + * @param {String} answer Id of the button that was clicked + * @param {String} contentId The id of concerned content + * @param {String} contentType The content type to remove + * @param {Number} actionId The workflow action id + * @private + */ + _confirmRemoveContentType: function (answer, contentType, contentId, actionId) + { + if (answer == 'yes') + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/remove-content-type', + parameters: { + contentId: contentId, + contentType: contentType, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._removeContentTypeCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + } + }, + + /** + * @private + * Callback function called after adding a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _addContentTypeCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else if (msg == 'no-content-type') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_NO_CONTENT_TYPE'/>"; + } + else if (msg == 'invalid-content-type') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_INVALID_CONTENT_TYPE'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * @private + * Callback function called after removing a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _removeContentTypeCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else if (msg == 'empty-list') + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR_EMPTY_LIST'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * Action function to be called by the controller. + * Add or remove the selected mixin on concerned content + * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function + */ + setMixinType: function (controller) + { + var btnConfig = controller.getInitialConfig(); + + var parentController = Ametys.ribbon.RibbonManager.getElement(btnConfig.controllerParentId); + var contentIds = parentController.getContentIds(); + + if (parentController.getContentIds().length > 0) + { + var mixin = btnConfig['contentTypes'] || btnConfig['contentType']; + var actionId = btnConfig['workflowActionId'] ? parseInt(btnConfig['workflowActionId']) : this.self._DEFAULT_WORKFLOW_ACTION_ID; + var contentId = parentController.getContentIds()[0]; + + if (controller.getUIControls().get(0).pressed) + { + this.removeMixinType(contentId, mixin, actionId); + } + else + { + this.addMixinType(contentId, mixin, actionId); + } + } + }, + + /** + * Add a mixin on concerned content + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to add + * @param {Number} actionId The workflow action id + */ + addMixinType: function (contentId, mixin, actionId) + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/add-mixin', + parameters: { + contentId: contentId, + mixin: mixin, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._addMixinTypeCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + }, + + /** + * Remove a mixin on concerned content + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to remove + * @param {Number} actionId The workflow action id + */ + removeMixinType: function (contentId, mixin, actionId) + { + Ext.Msg.confirm("<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_CONFIRM_TITLE'/>", + "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_CONFIRM'/>", + Ext.bind(this._confirmRemoveMixin, this, [mixin, contentId, actionId], 1) + ); + }, + + /** + * Callback function invoked after the #removeMixin confirm box is closed + * @param {String} answer Id of the button that was clicked + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to remove + * @param {Number} actionId The workflow action id + * @private + */ + _confirmRemoveMixin: function (answer, mixin, contentId, actionId) + { + if (answer == 'yes') + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/remove-mixin', + parameters: { + contentId: contentId, + mixin: mixin, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._removeMixinTypeCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + } + }, + + /** + * @private + * Callback function called after adding a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _addMixinTypeCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else if (msg == 'no-mixin') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_NO_MIXIN'/>"; + } + else if (msg == 'invalid-mixin') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_INVALID_MIXIN'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * @private + * Callback function called after removing a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _removeMixinTypeCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * Fire {@link Ametys.message.Message#MODIFIED} and {@link Ametys.message.Message#WORKFLOW_CHANGED} on content + * @param {Ametys.cms.content.Content} content The concerned content + * @private + */ + _fireMessages: function (content) + { + Ext.create("Ametys.message.Message", { + type: Ametys.message.Message.MODIFIED, + + targets: { + type: Ametys.message.MessageTarget.CONTENT, + parameters: { contents: [content] } + } + }); + + Ext.create("Ametys.message.Message", { + type: Ametys.message.Message.WORKFLOW_CHANGED, + + targets: { + type: Ametys.message.MessageTarget.CONTENT, + parameters: { contents: [content] } + } + }); + } + + +}); Index: main/plugin-cms/resources/js/Ametys/plugins/cms/content/actions/ContentTypeActionss.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/plugins/cms/content/actions/ContentTypeActionss.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/plugins/cms/content/actions/ContentTypeActionss.i18n.js (revision 0) @@ -0,0 +1,447 @@ +/* + * Copyright 2011 Anyware Services + * + * 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. + */ + +/** + * Singleton class to create a new content. + * @private + */ +Ext.define('Ametys.plugins.cms.content.actions.ContentActions', { + singleton: true, + + /** + * The default workflow action id + * @private + */ + _DEFAULT_WORKFLOW_ACTION_ID: 220, + + /** + * Action function to be called by the controller. + * Add or remove the selected content type on concerned content + * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function + */ + setContentType: function (controller) + { + var btnConfig = controller.getInitialConfig(); + + var parentController = Ametys.ribbon.RibbonManager.getElement(btnConfig.controllerParentId); + var contentIds = parentController.getContentIds(); + + if (parentController.getContentIds().length > 0) + { + var contentType = btnConfig['contentTypes'] || btnConfig['contentType']; + var actionId = btnConfig['workflowActionId'] ? parseInt(btnConfig['workflowActionId']) : this.self._DEFAULT_WORKFLOW_ACTION_ID; + var contentId = parentController.getContentIds()[0]; + + if (controller.getUIControls().get(0).pressed) + { + this.removeContentType(contentId, contentType, actionId); + } + else + { + this.addContentType(contentId, contentType, actionId); + } + } + }, + + /** + * Add a content type on concerned content + * @param {String} contentId The id of concerned content + * @param {String} contentType The content type to add + * @param {Number} actionId The workflow action id + */ + addContentType: function (contentId, contentType, actionId) + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/add-content-type', + parameters: { + contentId: contentId, + contentType: contentType, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._addContentTypeCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + }, + + /** + * Remove a content type on concerned content + * @param {String} contentId The id of concerned content + * @param {String} contentType The content type to remove + * @param {Number} actionId The workflow action id + */ + removeContentType: function (contentId, contentType, actionId) + { + Ext.Msg.confirm("<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_CONFIRM_TITLE'/>", + "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_CONFIRM'/>", + Ext.bind(this._confirmRemoveContentType, this, [contentType, contentId, actionId], 1) + ); + }, + + /** + * Callback function invoked after the #removeContentType confirm box is closed + * @param {String} answer Id of the button that was clicked + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to remove + * @param {Number} actionId The workflow action id + * @private + */ + _confirmRemoveContentType: function (answer, contentType, contentId, actionId) + { + if (answer == 'yes') + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/remove-content-type', + parameters: { + contentId: contentId, + contentType: contentType, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._removeContentTypeCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + } + }, + + /** + * @private + * Callback function called after adding a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _addContentTypeCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else if (msg = 'invalid-content-type') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_INVALID_CONTENT_TYPE'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_ADD_CONTENT_TYPE_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * @private + * Callback function called after removing a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _removeContentTypeCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else if (msg = 'empty-list') + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR_EMPTY_LIST'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_REMOVE_CONTENT_TYPE_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * Action function to be called by the controller. + * Add or remove the selected mixin on concerned content + * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function + */ + setMixin: function (controller) + { + var btnConfig = controller.getInitialConfig(); + + var parentController = Ametys.ribbon.RibbonManager.getElement(btnConfig.controllerParentId); + var contentIds = parentController.getContentIds(); + + if (parentController.getContentIds().length > 0) + { + var mixin = btnConfig['contentTypes'] || btnConfig['contentType']; + var actionId = btnConfig['workflowActionId'] ? parseInt(btnConfig['workflowActionId']) : this.self._DEFAULT_WORKFLOW_ACTION_ID; + var contentId = parentController.getContentIds()[0]; + + if (controller.getUIControls().get(0).pressed) + { + this.removeMixin(contentId, mixin, actionId); + } + else + { + this.addMixin(contentId, mixin, actionId); + } + } + }, + + /** + * Add a mixin on concerned content + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to add + * @param {Number} actionId The workflow action id + */ + addMixin: function (contentId, mixin, actionId) + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/add-mixin', + parameters: { + contentId: contentId, + mixin: mixin, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._addMixinCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + }, + + /** + * Remove a mixin on concerned content + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to remove + * @param {Number} actionId The workflow action id + */ + removeMixin: function (contentId, mixin, actionId) + { + Ext.Msg.confirm("<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_CONFIRM_TITLE'/>", + "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_CONFIRM'/>", + Ext.bind(this._confirmRemoveMixin, this, [mixin, contentId, actionId], 1) + ); + }, + + /** + * Callback function invoked after the #removeMixin confirm box is closed + * @param {String} answer Id of the button that was clicked + * @param {String} contentId The id of concerned content + * @param {String} mixin The mixin to remove + * @param {Number} actionId The workflow action id + * @private + */ + _confirmRemoveMixin: function (answer, mixin, contentId, actionId) + { + if (answer == 'yes') + { + Ametys.data.ServerComm.send({ + plugin: 'cms', + url: 'content/remove-mixin', + parameters: { + contentId: contentId, + mixin: mixin, + actionId: actionId, + }, + priority: Ametys.data.ServerComm.PRIORITY_MAJOR, + callback: { + scope: this, + handler: this._removeMixinCb, + arguments: { + contentId: contentId + } + }, + responseType: 'text' + }); + } + }, + + /** + * @private + * Callback function called after adding a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _addMixinCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else if (msg = 'invalid-mixin') + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_INVALID_CONTENT_TYPE'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_ADD_MIXIN_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * @private + * Callback function called after removing a content type + * @param {HTMLElement} response The XML document. + * @param {Array} params The callback arguments. + */ + _removeMixinCb: function (response, params) + { + if (Ametys.data.ServerComm.handleBadResponse("<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR'/>", response, 'Ametys.plugins.cms.content.actions.ContentActions')) + { + return; + } + + if (result.failure) + { + var errorMsg = ""; + + var msg = result.msg; + if (msg == 'non-modifiable-content') + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR_NON_MODIFIABLE_CONTENT'/>"; + } + else + { + errorMsg = "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR'/>"; + } + + Ext.Msg.show({ + title: "<i18n:text i18n:key='CONTENT_REMOVE_MIXIN_ERROR_TITLE'/>", + msg: errorMsg, + buttons: Ext.Msg.OK, + icon: Ext.Msg.ERROR + }); + } + + var result = Ext.JSON.decode(Ext.dom.Query.selectValue("", response)); + if (result.success) + { + Ametys.cms.content.ContentDAO.getContent(params.contentId, Ext.bind(this._fireMessages, this)); + } + }, + + /** + * Fire {@link Ametys.message.Message#MODIFIED} and {@link Ametys.message.Message#WORKFLOW_CHANGED} on content + * @param {Ametys.cms.content.Content} content The concerned content + * @private + */ + _fireMessages: function (content) + { + Ext.create("Ametys.message.Message", { + type: Ametys.message.Message.MODIFIED, + + targets: { + type: Ametys.message.MessageTarget.CONTENT, + parameters: { contents: [content] } + } + }); + + Ext.create("Ametys.message.Message", { + type: Ametys.message.Message.WORKFLOW_CHANGED, + + targets: { + type: Ametys.message.MessageTarget.CONTENT, + parameters: { contents: [content] } + } + }); + } + + +}); Index: main/plugin-cms/resources/js/Ametys/cms/form/widget/FlipFlap.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/cms/form/widget/FlipFlap.i18n.js (revision 0) +++ main/plugin-cms/resources/js/Ametys/cms/form/widget/FlipFlap.i18n.js (revision 0) @@ -0,0 +1,56 @@ +/* + * Copyright 2013 Anyware Services + * + * 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. + */ + +/** + * This class provides a control that allows selection of between two list controls.<br> + * + * This widget is registered for enumerated and multiple fields of type Ametys.cms.form.WidgetManager#TYPE_STRING.<br> + */ +Ext.define('Ametys.cms.form.widget.FlipFlap', { + extend: 'Ext.ux.form.ItemSelector', + + alias: ['widget.edition.flipflap'], + + constructor: function(config) + { + var storeCfg = { + id: 0, + fields: [ 'value', {name: 'text', sortType: Ext.data.SortTypes.asNonAccentedUCString}], + data: config.enumeration + }; + + config.naturalOrder = Ext.isBoolean(config.naturalOrder) ? config.naturalOrder : config.naturalOrder == 'true'; + if (!config.naturalOrder) + { + storeCfg.sorters = [{property: 'text', direction:'ASC'}]; // default order + } + + config = Ext.apply(config, { + cls: 'ametys', + + store: new Ext.data.SimpleStore(storeCfg), + + valueField: 'value', + displayField: 'text', + + allowBlank: this.allowBlank + }); + + this.callParent(arguments); + } +}); + +Ametys.cms.form.WidgetManager.registerWidget('edition.flipflap', Ametys.cms.form.WidgetManager.TYPE_STRING, true /*enumeration*/, true /*multiple*/, false /*default*/); \ No newline at end of file Index: main/plugin-cms/resources/js/Ametys/cms/content/Content.i18n.js =================================================================== --- main/plugin-cms/resources/js/Ametys/cms/content/Content.i18n.js (revision 25585) +++ main/plugin-cms/resources/js/Ametys/cms/content/Content.i18n.js (working copy) @@ -67,14 +67,23 @@ /** @ignore */ name: null, /** - * @cfg {String} type The content type + * @cfg {String[]} types The content types */ /** - * @method getType Get the #cfg-type - * @return {String} The type + * @method getTypes Get the #cfg-types + * @return {String[]} The types */ /** @ignore */ - type: null, + types: [], + /** + * @cfg {String[]} mixins The mixins + */ + /** + * @method getMixins Get the #cfg-mixins + * @return {String[]} The mixins + */ + /** @ignore */ + mixins: [], /** * @cfg {String} workflowName The identifier of the workflow used for this content */ @@ -416,7 +425,8 @@ title: this.title, path: this.path, lang: this.lang, - type: this.type, + types: this.types, + mixins: this.types, workflowName : this.workflowName, isModifiable : this.isModifiable, rights: this.rights Index: main/plugin-cms/resources/css/detail.css =================================================================== --- main/plugin-cms/resources/css/detail.css (revision 25585) +++ main/plugin-cms/resources/css/detail.css (working copy) @@ -28,6 +28,10 @@ text-overflow: ellipsis; } +.details-view .details-view-card .showhide { + color: #777777; +} + .details-view .details-view-card ul { list-style-type: none; padding-left: 0px; @@ -49,6 +53,10 @@ margin-right:0px; } +.details-view .details-view-card ul.hidden { + display: none; +} + .details-view ul li.content-type { font-style:italic; color: #444;