001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.widgets; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsResourceTypeConfig; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProperty; 034import org.opencms.file.CmsPropertyDefinition; 035import org.opencms.file.CmsResource; 036import org.opencms.i18n.CmsMessages; 037import org.opencms.json.JSONArray; 038import org.opencms.json.JSONException; 039import org.opencms.json.JSONObject; 040import org.opencms.loader.CmsTemplateLoaderFacade; 041import org.opencms.main.CmsException; 042import org.opencms.main.CmsLog; 043import org.opencms.main.OpenCms; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.util.CmsUUID; 046import org.opencms.workplace.CmsDialog; 047import org.opencms.workplace.CmsWorkplaceMessages; 048import org.opencms.xml.containerpage.I_CmsFormatterBean; 049import org.opencms.xml.types.A_CmsXmlContentValue; 050import org.opencms.xml.types.CmsXmlDisplayFormatterValue; 051 052import java.util.ArrayList; 053import java.util.Arrays; 054import java.util.Collections; 055import java.util.Comparator; 056import java.util.HashMap; 057import java.util.HashSet; 058import java.util.List; 059import java.util.Locale; 060import java.util.Map; 061import java.util.Set; 062 063import org.apache.commons.logging.Log; 064 065import com.google.common.collect.ComparisonChain; 066import com.google.common.collect.Sets; 067 068/** 069 * Widget to select a type and formatter combination.<p> 070 */ 071public class CmsDisplayTypeSelectWidget extends CmsSelectWidget { 072 073 /** 074 * Formatter select option.<p> 075 */ 076 class FormatterOption { 077 078 /** the display type. */ 079 String m_displayType; 080 081 /** The option key. */ 082 String m_key; 083 084 /** The option label. */ 085 String m_label; 086 087 /** The formatter rank. */ 088 int m_rank; 089 090 /** Position of resource type in the sitemap config. */ 091 Integer m_typePosition; 092 093 /** The type name. */ 094 String m_typeName; 095 096 /** 097 * Constructor.<p> 098 * 099 * @param key the option key 100 * @param typeName the type name 101 * @param displayType the display type 102 * @param label the option label 103 * @param rank the formatter rank 104 * @param typePos the position of the type in the sitemap config 105 */ 106 FormatterOption(String key, String typeName, String displayType, String label, int rank, Integer typePos) { 107 108 m_key = key; 109 m_typeName = typeName; 110 m_displayType = displayType; 111 m_label = label; 112 m_rank = rank; 113 m_typePosition = typePos; 114 } 115 116 /** 117 * Gets the position of the type in the sitemap configuration. 118 * 119 * @return the position of the type in the sitemap configuration 120 */ 121 public Integer getTypePosition() { 122 123 return m_typePosition; 124 } 125 } 126 127 /** Name of the sitemap attribute to control whether the old or new way of collecting formatter options should be used. */ 128 public static final String ATTR_USE_CONFIG_FORMATTERS = "list.use.configured.formatters"; 129 130 /** The match display types key. */ 131 public static final String MATCH_TYPES_KEY = "matchTypes"; 132 133 /** The logger instance for this class. */ 134 private static final Log LOG = CmsLog.getLog(CmsDisplayTypeSelectWidget.class); 135 136 /** The display types split regex. */ 137 private static final String TYPES_SPLITTER = " *, *"; 138 139 /** The display container type configuration. */ 140 private String m_displayContainerTypeConfig; 141 142 /** Flag indicating display types should be matched. */ 143 private boolean m_matchTypes; 144 145 /** The configuration String. */ 146 private String m_config; 147 148 /** 149 * @see org.opencms.widgets.A_CmsSelectWidget#getConfiguration() 150 */ 151 @Override 152 public String getConfiguration() { 153 154 return m_config; 155 } 156 157 /** 158 * @see org.opencms.widgets.A_CmsSelectWidget#getConfiguration(org.opencms.file.CmsObject, org.opencms.xml.types.A_CmsXmlContentValue, org.opencms.i18n.CmsMessages, org.opencms.file.CmsResource, java.util.Locale) 159 */ 160 @Override 161 public String getConfiguration( 162 CmsObject cms, 163 A_CmsXmlContentValue schemaType, 164 CmsMessages messages, 165 CmsResource resource, 166 Locale contentLocale) { 167 168 List<FormatterOption> options = getFormatterOptions(cms, resource); 169 JSONObject config = new JSONObject(); 170 try { 171 String path; 172 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(schemaType.getPath())) { 173 path = CmsStringUtil.joinPaths(schemaType.getPath(), schemaType.getName()); 174 } else { 175 path = schemaType.getName(); 176 } 177 178 config.put("valuePath", path); 179 config.put("matchTypes", m_matchTypes); 180 config.put("emptyLabel", messages.key(Messages.GUI_DISPLAYTYPE_SELECT_0)); 181 JSONArray optionArray = new JSONArray(); 182 for (FormatterOption option : options) { 183 JSONObject opt = new JSONObject(); 184 try { 185 opt.put("value", option.m_key); 186 opt.put("label", option.m_label); 187 opt.put("displayType", option.m_displayType); 188 optionArray.put(opt); 189 } catch (JSONException e) { 190 LOG.error(e.getLocalizedMessage(), e); 191 } 192 } 193 config.put("options", optionArray); 194 } catch (JSONException e) { 195 LOG.error(e.getLocalizedMessage(), e); 196 } 197 return config.toString(); 198 } 199 200 /** 201 * @see org.opencms.widgets.CmsSelectWidget#getWidgetName() 202 */ 203 @Override 204 public String getWidgetName() { 205 206 return CmsDisplayTypeSelectWidget.class.getName(); 207 } 208 209 /** 210 * @see org.opencms.widgets.CmsSelectWidget#newInstance() 211 */ 212 @Override 213 public I_CmsWidget newInstance() { 214 215 return new CmsDisplayTypeSelectWidget(); 216 } 217 218 /** 219 * @see org.opencms.widgets.A_CmsSelectWidget#setConfiguration(java.lang.String) 220 */ 221 @Override 222 public void setConfiguration(String configuration) { 223 224 m_config = configuration; 225 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(configuration)) { 226 String[] conf = configuration.split("\\|"); 227 for (int i = 0; i < conf.length; i++) { 228 if (MATCH_TYPES_KEY.equals(conf[i])) { 229 m_matchTypes = true; 230 } else { 231 m_displayContainerTypeConfig = conf[i]; 232 } 233 234 } 235 } 236 } 237 238 /** 239 * @see org.opencms.widgets.A_CmsSelectWidget#parseSelectOptions(org.opencms.file.CmsObject, org.opencms.widgets.I_CmsWidgetDialog, org.opencms.widgets.I_CmsWidgetParameter) 240 */ 241 @Override 242 protected List<CmsSelectWidgetOption> parseSelectOptions( 243 CmsObject cms, 244 I_CmsWidgetDialog widgetDialog, 245 I_CmsWidgetParameter param) { 246 247 CmsResource resource = null; 248 List<CmsSelectWidgetOption> result = new ArrayList<CmsSelectWidgetOption>(); 249 try { 250 if (widgetDialog instanceof CmsDummyWidgetDialog) { 251 resource = ((CmsDummyWidgetDialog)widgetDialog).getResource(); 252 } else if (widgetDialog instanceof CmsDialog) { 253 String sitePath = ((CmsDialog)widgetDialog).getParamResource(); 254 if (sitePath != null) { 255 resource = cms.readResource(sitePath); 256 } 257 } 258 for (FormatterOption option : getFormatterOptions(cms, resource)) { 259 result.add(new CmsSelectWidgetOption(option.m_key, false, option.m_label)); 260 } 261 } catch (Exception e) { 262 LOG.error(e.getLocalizedMessage(), e); 263 } 264 return result; 265 } 266 267 /** 268 * Evaluates the display type of the given formatter.<p> 269 * 270 * @param formatter the formatter configuration bean 271 * 272 * @return the display type 273 */ 274 private String getDisplayType(I_CmsFormatterBean formatter) { 275 276 return formatter.getDisplayType(); 277 } 278 279 /** 280 * Returns the available formatter options.<p> 281 * 282 * @param cms the cms context 283 * @param resource the edited resource 284 * 285 * @return the formatter options 286 */ 287 private List<FormatterOption> getFormatterOptions(CmsObject cms, CmsResource resource) { 288 289 List<FormatterOption> options = new ArrayList<FormatterOption>(); 290 Set<String> containerTypes = new HashSet<>(); 291 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_displayContainerTypeConfig)) { 292 String types = null; 293 294 if (CmsPropertyDefinition.PROPERTY_TEMPLATE_DISPLAY_TYPES.equals(m_displayContainerTypeConfig)) { 295 try { 296 CmsProperty prop = cms.readPropertyObject( 297 resource, 298 CmsPropertyDefinition.PROPERTY_TEMPLATE_DISPLAY_TYPES, 299 true); 300 String propValue = prop.getValue(); 301 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(propValue)) { 302 types = propValue; 303 } else { 304 // look up template property 305 try { 306 CmsTemplateLoaderFacade loaderFacade = OpenCms.getResourceManager().getTemplateLoaderFacade( 307 cms, 308 null, 309 resource, 310 CmsPropertyDefinition.PROPERTY_TEMPLATE); 311 CmsResource template = loaderFacade.getLoaderStartResource(); 312 if (template != null) { 313 prop = cms.readPropertyObject( 314 template, 315 CmsPropertyDefinition.PROPERTY_TEMPLATE_DISPLAY_TYPES, 316 false); 317 propValue = prop.getValue(); 318 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(propValue)) { 319 types = propValue; 320 } 321 } 322 } catch (Exception ex) { 323 LOG.debug(ex.getMessage(), ex); 324 } 325 } 326 } catch (CmsException e) { 327 LOG.warn(e.getLocalizedMessage(), e); 328 } 329 } else { 330 types = m_displayContainerTypeConfig; 331 } 332 if (types != null) { 333 containerTypes.addAll(Arrays.asList(types.split(TYPES_SPLITTER))); 334 } 335 } 336 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(cms, resource.getRootPath()); 337 338 if (config != null) { 339 List<CmsResourceTypeConfig> typeConfigs = config.getResourceTypes(); 340 Map<String, Integer> typePositions = new HashMap<>(); 341 int index = 0; 342 for (CmsResourceTypeConfig type : typeConfigs) { 343 typePositions.put(type.getTypeName(), Integer.valueOf(index)); 344 index += 1; 345 } 346 boolean useConfiguredFormatters = Boolean.parseBoolean( 347 config.getAttribute(ATTR_USE_CONFIG_FORMATTERS, "false")); 348 Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 349 350 for (I_CmsFormatterBean formatter : config.getDisplayFormatters(cms)) { 351 352 if (useConfiguredFormatters) { 353 boolean inactive = (formatter.getId() == null) 354 || !config.getActiveFormatters().containsKey(new CmsUUID(formatter.getId())); 355 if (inactive) { 356 continue; 357 } 358 } else { 359 if (!containerTypes.isEmpty()) { 360 if (Sets.intersection(containerTypes, formatter.getContainerTypes()).isEmpty()) { 361 continue; 362 } 363 } 364 } 365 for (String typeName : formatter.getResourceTypeNames()) { 366 String label = formatter.getNiceName(wpLocale) 367 + " (" 368 + CmsWorkplaceMessages.getResourceTypeName(wpLocale, typeName) 369 + ")"; 370 options.add( 371 new FormatterOption( 372 typeName + CmsXmlDisplayFormatterValue.SEPARATOR + formatter.getKeyOrId(), 373 typeName, 374 getDisplayType(formatter), 375 label, 376 formatter.getRank(), 377 typePositions.get(typeName))); 378 } 379 } 380 } 381 382 // Formatters for types in sitemap config come first, in the order defined there, then sorted by formatter rank. 383 // After that, formatters for types not in the sitemap config, ordered by type, then rank. 384 Collections.sort( 385 options, 386 ( 387 f1, 388 f2) -> ComparisonChain.start().compare( 389 f1.getTypePosition(), 390 f2.getTypePosition(), 391 Comparator.nullsLast(Comparator.naturalOrder())).compare(f1.m_typeName, f2.m_typeName).compare( 392 f2.m_rank, 393 f1.m_rank).compare(f1.m_label, f2.m_label).result() 394 395 ); 396 return options; 397 } 398}