001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.content; 017 018import java.util.HashMap; 019import java.util.LinkedHashMap; 020import java.util.List; 021import java.util.Locale; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.components.ContextHelper; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.xml.AttributesImpl; 034import org.apache.cocoon.xml.XMLUtils; 035import org.apache.commons.collections.MapUtils; 036import org.apache.commons.lang3.ArrayUtils; 037import org.apache.commons.lang3.StringUtils; 038import org.xml.sax.ContentHandler; 039import org.xml.sax.SAXException; 040 041import org.ametys.cms.contenttype.ContentAttributeDefinition; 042import org.ametys.cms.contenttype.ContentType; 043import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 044import org.ametys.cms.data.type.ModelItemTypeConstants; 045import org.ametys.cms.repository.Content; 046import org.ametys.cms.search.query.AndQuery; 047import org.ametys.cms.search.query.ContentLanguageQuery; 048import org.ametys.cms.search.query.ContentTypeQuery; 049import org.ametys.cms.search.query.DocumentTypeQuery; 050import org.ametys.cms.search.query.Query; 051import org.ametys.cms.search.solr.SearcherFactory.Searcher; 052import org.ametys.cms.workflow.ContentWorkflowHelper; 053import org.ametys.cms.workflow.EditContentFunction; 054import org.ametys.plugins.repository.AmetysObjectIterable; 055import org.ametys.plugins.repository.AmetysRepositoryException; 056import org.ametys.plugins.repository.RepositoryConstants; 057import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 058import org.ametys.plugins.workflow.AbstractWorkflowComponent; 059import org.ametys.runtime.i18n.I18nizableText; 060import org.ametys.runtime.model.ModelHelper; 061import org.ametys.web.FOAmetysObjectCreationHelper; 062import org.ametys.web.frontoffice.FrontOfficeSearcherFactory; 063 064import com.google.common.collect.Multimap; 065import com.opensymphony.workflow.WorkflowException; 066 067/** 068 * Helper for creating and editing a content from the submitted form 069 */ 070public class FOContentCreationHelper extends FOAmetysObjectCreationHelper implements Contextualizable 071{ 072 /** The component role. */ 073 @SuppressWarnings("hiding") 074 public static final String ROLE = FOContentCreationHelper.class.getName(); 075 076 private ContentWorkflowHelper _contentWorkflowHelper; 077 078 private Context _context; 079 080 private ContentTypeExtensionPoint _cTypeExtPt; 081 082 private FrontOfficeSearcherFactory _searcherFactory; 083 084 @Override 085 public void service(ServiceManager smanager) throws ServiceException 086 { 087 super.service(smanager); 088 _cTypeExtPt = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 089 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 090 _searcherFactory = (FrontOfficeSearcherFactory) smanager.lookup(FrontOfficeSearcherFactory.ROLE); 091 } 092 093 public void contextualize(Context context) throws ContextException 094 { 095 _context = context; 096 } 097 098 /** 099 * Get the request 100 * @return the request 101 */ 102 protected Request _getRequest() 103 { 104 return ContextHelper.getRequest(_context); 105 } 106 107 /** 108 * SAX contents values for metadata of type CONTENT 109 * @param contentHandler The content handler to sax into 110 * @param contentType The content type 111 * @param rootTagName The root tag name 112 * @param language the current language 113 * @throws SAXException if an error occurs when saxing 114 */ 115 public void saxContentValues(ContentHandler contentHandler, ContentType contentType, String rootTagName, String language) throws SAXException 116 { 117 List<ContentAttributeDefinition> contentAttributes = ModelHelper.findModelItemsByType(contentType, ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID) 118 .stream() 119 .filter(ContentAttributeDefinition.class::isInstance) // Avoid properties, that are not modifiables 120 .map(ContentAttributeDefinition.class::cast) 121 .collect(Collectors.toList()); 122 for (ContentAttributeDefinition contentAttribute : contentAttributes) 123 { 124 AttributesImpl attrs = new AttributesImpl(); 125 attrs.addCDATAAttribute("name", contentAttribute.getPath().replace("/", ".")); 126 XMLUtils.startElement(contentHandler, rootTagName, attrs); 127 _saxContentEnumeratorValue(contentHandler, contentAttribute, language); 128 XMLUtils.endElement(contentHandler, rootTagName); 129 } 130 } 131 132 133 /** 134 * Sax enumeration value for enum or an attribute of type content 135 * @param contentHandler The content handler to sax into 136 * @param attribute The attribute of type content 137 * @param language The current language 138 * @throws SAXException If an error occurred while saxing 139 */ 140 private void _saxContentEnumeratorValue(ContentHandler contentHandler, ContentAttributeDefinition attribute, String language) throws SAXException 141 { 142 Map<String, String> values = getContentValues(attribute.getContentTypeId(), language); 143 144 XMLUtils.startElement(contentHandler, "enumeration"); 145 for (Entry<String, String> entry : values.entrySet()) 146 { 147 AttributesImpl attrItem = new AttributesImpl(); 148 attrItem.addCDATAAttribute("value", entry.getKey()); 149 XMLUtils.startElement(contentHandler, "item", attrItem); 150 XMLUtils.createElement(contentHandler, "label", entry.getValue()); 151 XMLUtils.endElement(contentHandler, "item"); 152 } 153 XMLUtils.endElement(contentHandler, "enumeration"); 154 } 155 156 /** 157 * Get values for contents enumeration 158 * @param cTypeId The id of content type 159 * @param language The current language 160 * @return The contents 161 */ 162 protected Map<String, String> getContentValues(String cTypeId, String language) 163 { 164 try 165 { 166 Query query = null; 167 boolean multilingual = false; 168 if (StringUtils.isNotEmpty(cTypeId)) 169 { 170 query = new ContentTypeQuery(cTypeId); 171 multilingual = _cTypeExtPt.getExtension(cTypeId).isMultilingual(); 172 } 173 174 if (!multilingual) 175 { 176 query = query != null ? new AndQuery(query, new ContentLanguageQuery(language)) : new ContentLanguageQuery(language); 177 } 178 179 Searcher searcher = _searcherFactory.create().withQuery(query) 180 .addFilterQuery(new DocumentTypeQuery("content")) 181 .withLimits(0, Integer.MAX_VALUE); 182 183 AmetysObjectIterable<Content> contents = searcher.search(); 184 185 return contents.stream() 186 .collect(Collectors.toMap(Content::getId, c -> c.getTitle(new Locale(language)))) 187 .entrySet() 188 .stream() 189 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); 190 } 191 catch (Exception e) 192 { 193 getLogger().error("Failed to get content enumeration for content type " + cTypeId, e); 194 return MapUtils.EMPTY_MAP; 195 } 196 } 197 198 /** 199 * Get the values for this content type from request 200 * @param request the request 201 * @param contentType the edited content type 202 * @param viewName the view name 203 * @param errors The errors to fill 204 * @return The values 205 */ 206 public Map<String, Object> getAndValidateFormValues(Request request, ContentType contentType, String viewName, Multimap<String, I18nizableText> errors) 207 { 208 Map<String, Object> values = getFormValues(request, contentType.getView(viewName), StringUtils.EMPTY, errors); 209 errors.putAll(validateValues(values, contentType.getView(viewName))); 210 return values; 211 } 212 213 /** 214 * Create and edit a content 215 * @param initActionId The initial workflow action id for creation and edition 216 * @param contentTypeId The id of content type 217 * @param siteName The current site name 218 * @param contentName The content name 219 * @param contentTitle The content title 220 * @param language The content language 221 * @param values The submitted values 222 * @param workflowName The workflow name 223 * @param viewName The view name 224 * @return The workflow result 225 * @throws AmetysRepositoryException if an error occurs 226 * @throws WorkflowException if an error occurs 227 */ 228 public Map<String, Object> createAndEditContent(int initActionId, String contentTypeId, String siteName, String contentName, String contentTitle, String language, Map<String, Object> values, String workflowName, String viewName) throws AmetysRepositoryException, WorkflowException 229 { 230 return createAndEditContent(initActionId, contentTypeId, siteName, contentName, contentTitle, language, values, workflowName, viewName, new HashMap<>()); 231 } 232 233 /** 234 * Create and edit a content 235 * @param initActionId The initial workflow action id for creation and edition 236 * @param contentTypeId The id of content type 237 * @param siteName The current site name 238 * @param contentName The content name 239 * @param contentTitle The content title 240 * @param language The content language 241 * @param values The submitted values 242 * @param workflowName The workflow name 243 * @param viewName The view name 244 * @param inputs The initial workflow inputs 245 * @return The workflow result 246 * @throws AmetysRepositoryException if an error occurs 247 * @throws WorkflowException if an error occurs 248 */ 249 public Map<String, Object> createAndEditContent(int initActionId, String contentTypeId, String siteName, String contentName, String contentTitle, String language, Map<String, Object> values, String workflowName, String viewName, Map<String, Object> inputs) throws AmetysRepositoryException, WorkflowException 250 { 251 return createAndEditContent(initActionId, new String[] {contentTypeId}, ArrayUtils.EMPTY_STRING_ARRAY, siteName, contentName, contentTitle, language, values, workflowName, viewName, inputs); 252 } 253 254 /** 255 * Create and edit a content 256 * @param initActionId The initial workflow action id for creation and edition 257 * @param contentTypeIds The new content types. Cannot be null. Cannot be empty. 258 * @param mixinIds The new mixins. Can be null. Can be empty. 259 * @param siteName The current site name 260 * @param contentName The content name 261 * @param contentTitle The content title 262 * @param language The content language 263 * @param values The values of the content attributes 264 * @param workflowName The workflow name 265 * @param viewName The view name 266 * @param inputs The initial workflow inputs 267 * @return The workflow result 268 * @throws AmetysRepositoryException if an error occurs 269 * @throws WorkflowException if an error occurs 270 */ 271 public Map<String, Object> createAndEditContent(int initActionId, String[] contentTypeIds, String[] mixinIds, String siteName, String contentName, String contentTitle, String language, Map<String, Object> values, String workflowName, String viewName, Map<String, Object> inputs) throws AmetysRepositoryException, WorkflowException 272 { 273 Request request = _getRequest(); 274 275 // Retrieve the current workspace. 276 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 277 278 try 279 { 280 // Force the default workspace. 281 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 282 283 // Workflow parameters. 284 inputs.put(org.ametys.web.workflow.CreateContentFunction.SITE_KEY, siteName); 285 286 Map<String, Object> contextParameters = new HashMap<>(); 287 contextParameters.put(EditContentFunction.QUIT, true); 288 contextParameters.put(EditContentFunction.VALUES_KEY, values); 289 if (viewName != null) 290 { 291 contextParameters.put(EditContentFunction.VIEW_NAME, viewName); 292 } 293 294 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters); 295 296 return _contentWorkflowHelper.createContent(workflowName, initActionId, contentName, contentTitle, contentTypeIds, mixinIds, language, inputs); 297 } 298 finally 299 { 300 // Restore context 301 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 302 } 303 304 } 305}