001/* 002 * Copyright 2017 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.cms.search.systemprop; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.configuration.DefaultConfiguration; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.lang3.StringUtils; 034import org.xml.sax.ContentHandler; 035import org.xml.sax.SAXException; 036 037import org.ametys.cms.contenttype.ContentType; 038import org.ametys.cms.contenttype.ContentTypeEnumerator; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.contenttype.ContentTypesHelper; 041import org.ametys.cms.contenttype.MetadataType; 042import org.ametys.cms.repository.Content; 043import org.ametys.cms.search.SearchField; 044import org.ametys.cms.search.model.SystemProperty; 045import org.ametys.cms.search.query.ContentTypeOrMixinTypeQuery; 046import org.ametys.cms.search.query.ContentTypeQuery; 047import org.ametys.cms.search.query.MixinTypeQuery; 048import org.ametys.cms.search.query.Query; 049import org.ametys.cms.search.query.Query.Operator; 050import org.ametys.cms.search.solr.field.ContentTypeSearchField; 051import org.ametys.cms.search.solr.field.MixinTypeSearchField; 052import org.ametys.runtime.i18n.I18nizableText; 053 054/** 055 * {@link SystemProperty} which represents the content types (and optionally mixins) of a content. 056 */ 057public class ContentTypeSystemProperty extends AbstractSystemProperty 058{ 059 060 /** The content type extension point. */ 061 protected ContentTypeExtensionPoint _cTypeEP; 062 063 /** The content types helper. */ 064 protected ContentTypesHelper _cTypeHelper; 065 066 /** True to include content types. */ 067 protected boolean _includeCTypes; 068 069 /** True to include mixins. */ 070 protected boolean _includeMixins; 071 072 /** True to recursively include supertypes. */ 073 protected boolean _includeSupertypes; 074 075 @Override 076 public void service(ServiceManager manager) throws ServiceException 077 { 078 super.service(manager); 079 _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 080 _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 081 } 082 083 @Override 084 public void configure(Configuration configuration) throws ConfigurationException 085 { 086 super.configure(configuration); 087 088 _includeCTypes = configuration.getChild("contentTypes").getValueAsBoolean(true); 089 _includeMixins = configuration.getChild("mixins").getValueAsBoolean(false); 090 _includeSupertypes = configuration.getChild("includeSupertypes").getValueAsBoolean(false); 091 } 092 093 @Override 094 public MetadataType getType() 095 { 096 return MetadataType.STRING; 097 } 098 099 @Override 100 public boolean isMultiple() 101 { 102 return true; 103 } 104 105 @Override 106 public boolean isSortable() 107 { 108 return true; 109 } 110 111 @Override 112 public Query getQuery(Object value, Operator operator, String language, Map<String, Object> contextualParameters) 113 { 114 String[] contentTypes = value != null ? parseStringArray(value) : new String[0]; 115 116 if (_includeCTypes && _includeMixins) 117 { 118 return new ContentTypeOrMixinTypeQuery(operator, contentTypes); 119 } 120 else if (_includeCTypes) 121 { 122 return new ContentTypeQuery(operator, contentTypes); 123 } 124 else if (_includeMixins) 125 { 126 return new MixinTypeQuery(operator, contentTypes); 127 } 128 129 return null; 130 } 131 132 @Override 133 public String getRenderer() 134 { 135 return "Ametys.plugins.cms.search.SearchGridHelper.renderMultipleString"; 136 } 137 138// @Override 139// public String getField() 140// { 141// // TODO Specific field? 142// return getId(); 143// } 144 145// @Override 146// public String getSortField() 147// { 148// return getField() + SolrFieldHelper.getSystemPropSortFieldSuffix(); 149// } 150 151 @Override 152 public SearchField getSearchField() 153 { 154 if (_includeCTypes && _includeMixins) 155 { 156 // Not searchable, not facetable. 157// return new ContentTypeOrMixinSearchField(_includeSupertypes); 158 return null; 159 } 160 else if (_includeCTypes) 161 { 162 return new ContentTypeSearchField(_includeSupertypes); 163 } 164 else if (_includeMixins) 165 { 166 return new MixinTypeSearchField(_includeSupertypes); 167 } 168 169 return null; 170 } 171 172 @Override 173 public Object getValue(Content content) 174 { 175 Set<String> types = new LinkedHashSet<>(); 176 177 if (_includeCTypes) 178 { 179 _addAll(types, content.getTypes()); 180 } 181 if (_includeMixins) 182 { 183 _addAll(types, content.getMixinTypes()); 184 } 185 186 return types.toArray(new String[types.size()]); 187 } 188 189 public Object getJsonValue(Content content, boolean full) 190 { 191 String[] cTypeIds = (String[]) getValue(content); 192 193 List<I18nizableText> cTypes = new ArrayList<>(); 194 for (String cTypeId : cTypeIds) 195 { 196 ContentType cType = _cTypeEP.getExtension(cTypeId); 197 if (cType != null) 198 { 199 cTypes.add(cType.getLabel()); 200 } 201 else if (getLogger().isWarnEnabled()) 202 { 203 getLogger().warn(String.format("Trying to get the label for an unknown content type : '%s'.", cTypeId)); 204 } 205 } 206 207 return cTypes; 208 } 209 210 @Override 211 public Object getSortValue(Content content) 212 { 213 String[] cTypeIds = (String[]) getValue(content); 214 if (cTypeIds.length > 0) 215 { 216 ContentType cType = _cTypeEP.getExtension(cTypeIds[0]); 217 if (cType != null) 218 { 219 return _i18nUtils.translate(cType.getLabel(), content.getLanguage()); 220 } 221 } 222 223 return null; 224 } 225 226 private void _addAll(Set<String> allTypes, String[] types) 227 { 228 for (String cType : types) 229 { 230 allTypes.add(cType); 231 if (_includeSupertypes && _cTypeEP.hasExtension(cType)) 232 { 233 allTypes.addAll(_cTypeHelper.getAncestors(cType)); 234 } 235 } 236 } 237 238 @Override 239 public void saxValue(ContentHandler handler, Content content) throws SAXException 240 { 241 String[] cTypeIds = (String[]) getValue(content); 242 243 XMLUtils.startElement(handler, getId()); 244 245 for (String cTypeId : cTypeIds) 246 { 247 AttributesImpl attr = new AttributesImpl(); 248 attr.addCDATAAttribute("id", cTypeId); 249 250 XMLUtils.startElement(handler, "contentType", attr); 251 ContentType cType = _cTypeEP.getExtension(cTypeId); 252 cType.getLabel().toSAX(handler); 253 XMLUtils.endElement(handler, "contentType"); 254 } 255 256 XMLUtils.endElement(handler, getId()); 257 } 258 259 /** 260 * Get the definition of enumerator. 261 * @param configuration The enumerator configuration. 262 * @return The enumerator definition 263 */ 264 @Override 265 public EnumeratorDefinition getEnumeratorDefinition(Configuration configuration) 266 { 267 DefaultConfiguration conf = new DefaultConfiguration("criteria"); 268 269 DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); 270 271 DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); 272 customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); 273 274 try 275 { 276 boolean containsRefTable = false; 277 Set<String> strictContentTypes = new HashSet<>(); 278 for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("type")) 279 { 280 String cTypeId = cTypeConf.getAttribute("id"); 281 ContentType cType = _cTypeEP.getExtension(cTypeId); 282 if (cType == null) 283 { 284 getLogger().error("A search model references a non-existing content type with id '{}', it will be ignored for system property {} ", cTypeId, _id); 285 } 286 else 287 { 288 strictContentTypes.add(cTypeId); 289 containsRefTable = cType.isReferenceTable() || containsRefTable; 290 } 291 } 292 if (!strictContentTypes.isEmpty() && _includeCTypes) 293 { 294 DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes"); 295 cTypeConf.setValue(StringUtils.join(strictContentTypes, ",")); 296 customEnumerator.addChild(cTypeConf); 297 298 if (containsRefTable) 299 { 300 // Search model focuses on at least one reference table, do not exclude reference table for content type enumerator 301 DefaultConfiguration excludeRefTableConf = new DefaultConfiguration("excludeReferenceTable"); 302 excludeRefTableConf.setValue(false); 303 customEnumerator.addChild(excludeRefTableConf); 304 } 305 } 306 } 307 catch (ConfigurationException e) 308 { 309 getLogger().error("Failed to configure strict content types for system property {}", _id, e); 310 } 311 312 if (!_includeMixins) 313 { 314 DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin"); 315 excludeConf.setValue(true); 316 customEnumerator.addChild(excludeConf); 317 } 318 else if (!_includeCTypes) // Mixins and not CTypes 319 { 320 DefaultConfiguration includeConf = new DefaultConfiguration("includeMixinOnly"); 321 includeConf.setValue(true); 322 customEnumerator.addChild(includeConf); 323 } 324 325 DefaultConfiguration allOptionConf = new DefaultConfiguration("all-option"); 326 allOptionConf.setValue("disabled"); 327 customEnumerator.addChild(allOptionConf); 328 329 enumConf.addChild(customEnumerator); 330 conf.addChild(enumConf); 331 332 return new EnumeratorDefinition(ContentTypeEnumerator.class, conf); 333 } 334 335 /** 336 * Get the default widget to use when rendering this property as a criterion. 337 * @return The default widget to use. 338 */ 339 @Override 340 public String getWidget() 341 { 342 return "edition.select-content-types"; 343 } 344 345 @Override 346 public Map<String, I18nizableText> getWidgetParameters(Configuration configuration) 347 { 348 Map<String, I18nizableText> parameters = new HashMap<>(); 349 350 try 351 { 352 Set<String> strictContentTypes = new HashSet<>(); 353 for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("type")) 354 { 355 strictContentTypes.add(cTypeConf.getAttribute("id")); 356 } 357 358 if (!strictContentTypes.isEmpty() && _includeCTypes) 359 { 360 parameters.put("strictContentTypes", new I18nizableText(StringUtils.join(strictContentTypes, ","))); 361 } 362 } 363 catch (ConfigurationException e) 364 { 365 getLogger().error("Failed to configure strict content types for system property {}", _id, e); 366 } 367 368 parameters.put("excludeMixin", new I18nizableText(_includeMixins ? "false" : "true")); 369 parameters.put("includeSupertype", new I18nizableText(_includeSupertypes ? "true" : "false")); 370 parameters.put("includeContentTypes", new I18nizableText(_includeCTypes ? "true" : "false")); 371 parameters.put("excludeReferenceTable", new I18nizableText("true")); 372 parameters.put("excludePrivate", new I18nizableText("true")); 373 parameters.put("emptyText", new I18nizableText("plugin.cms", "WIDGET_COMBOBOX_ALL_OPTIONS")); 374 375 return parameters; 376 } 377}