001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.ui.components; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.file.CmsResourceFilter; 033import org.opencms.file.CmsVfsResourceNotFoundException; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.main.OpenCms; 037import org.opencms.security.CmsPermissionViolationException; 038import org.opencms.site.CmsSite; 039import org.opencms.ui.CmsVaadinUtils; 040import org.opencms.ui.components.CmsExtendedSiteSelector.SiteSelectorOption; 041import org.opencms.util.CmsFileUtil; 042import org.opencms.util.CmsPath; 043import org.opencms.util.CmsStringUtil; 044import org.opencms.workplace.explorer.CmsResourceUtil; 045 046import java.util.ArrayList; 047import java.util.Collections; 048import java.util.LinkedHashMap; 049import java.util.List; 050import java.util.Map; 051import java.util.Optional; 052 053import org.apache.commons.logging.Log; 054 055import com.google.common.base.Objects; 056import com.google.common.collect.ArrayListMultimap; 057import com.google.common.collect.Multimap; 058import com.vaadin.data.provider.ListDataProvider; 059import com.vaadin.ui.ComboBox; 060 061/** 062 * Site selector widget which also optionally offers subsite options. 063 **/ 064public class CmsExtendedSiteSelector extends ComboBox<SiteSelectorOption> { 065 066 /** 067 * Class representing a single option. 068 */ 069 public static class SiteSelectorOption { 070 071 /** The label for the option. */ 072 private String m_label; 073 074 /** The path in the site (may be null). */ 075 private String m_path; 076 077 /** The site root. */ 078 private String m_site; 079 080 /** 081 * Creates a new instance. 082 * 083 * @param site the site root 084 * @param path the path in the site (may be null) 085 * @param label the option label 086 */ 087 public SiteSelectorOption(String site, String path, String label) { 088 089 m_site = normalizePath(site); 090 m_path = normalizePath(path); 091 m_label = label; 092 } 093 094 /** 095 * @see java.lang.Object#equals(java.lang.Object) 096 */ 097 @Override 098 public boolean equals(Object obj) { 099 100 if (this == obj) { 101 return true; 102 } 103 if (obj == null) { 104 return false; 105 } 106 if (getClass() != obj.getClass()) { 107 return false; 108 } 109 SiteSelectorOption other = (SiteSelectorOption)obj; 110 return Objects.equal(m_site, other.m_site) && Objects.equal(m_path, other.m_path); 111 } 112 113 /** 114 * Gets the option label. 115 * 116 * @return the option label 117 */ 118 public String getLabel() { 119 120 return m_label; 121 } 122 123 /** 124 * Returns the full path for this site selector option. 125 * @return the full path for this site selector option 126 */ 127 public String getOptionPath() { 128 129 return m_path == null ? m_site : CmsStringUtil.joinPaths(m_site, m_path); 130 } 131 132 /** 133 * Gets the path to jump to as a site path (may be null). 134 * 135 * @return the path to jump to 136 */ 137 public String getPath() { 138 139 return m_path; 140 } 141 142 /** 143 * Gets the site root 144 * 145 * @return the site root 146 */ 147 public String getSite() { 148 149 return m_site; 150 } 151 152 /** 153 * @see java.lang.Object#hashCode() 154 */ 155 @Override 156 public int hashCode() { 157 158 final int prime = 31; 159 int result = 1; 160 result = (prime * result) + ((m_path == null) ? 0 : m_path.hashCode()); 161 result = (prime * result) + ((m_site == null) ? 0 : m_site.hashCode()); 162 return result; 163 } 164 165 /** 166 * Cuts off trailing slashes if the path is not null. 167 * 168 * @param path a path 169 * @return the normalized path 170 */ 171 private String normalizePath(String path) { 172 173 if (path == null) { 174 return null; 175 } 176 return CmsFileUtil.removeTrailingSeparator(path); 177 } 178 179 } 180 181 /** Logger instance for this class. */ 182 private static final Log LOG = CmsLog.getLog(CmsExtendedSiteSelector.class); 183 184 /** Serial version id. */ 185 private static final long serialVersionUID = 1L; 186 187 /** Longer default page length for site selectors. */ 188 public static final int LONG_PAGE_LENGTH = 20; 189 190 /** True if the options have been initialized. */ 191 private boolean m_initialized; 192 193 /** The options. */ 194 private List<SiteSelectorOption> m_options = new ArrayList<>(); 195 196 /** 197 * Creates a new instance.<p> 198 * 199 * To actually use the widget, the initOptions method has to be called first. 200 */ 201 public CmsExtendedSiteSelector() { 202 203 super(); 204 205 setDataProvider(new ListDataProvider<SiteSelectorOption>(m_options)); 206 setItemCaptionGenerator(item -> item.getLabel()); 207 setTextInputAllowed(true); 208 setEmptySelectionAllowed(false); 209 addAttachListener(evt -> { 210 if (!m_initialized) { 211 throw new RuntimeException(getClass().getName() + " must be initialized with initOptions"); 212 } 213 214 }); 215 } 216 217 /** 218 * Builds a list of site selector option that also includes subsites with the 'include in site selector' option enabled in their configuration. 219 * 220 * @param cms the current CMS context 221 * @param addSubsites true if subsite options should be added 222 * @return the the extended site selecctor options 223 */ 224 public static List<CmsExtendedSiteSelector.SiteSelectorOption> getExplorerSiteSelectorOptions( 225 CmsObject cms, 226 boolean addSubsites) { 227 228 LinkedHashMap<String, String> siteOptions = CmsVaadinUtils.getAvailableSitesMap(cms); 229 List<String> subsites = new ArrayList<>( 230 OpenCms.getADEManager().getSubsitesForSiteSelector( 231 cms.getRequestContext().getCurrentProject().isOnlineProject())); 232 Collections.sort(subsites); 233 Multimap<CmsPath, CmsExtendedSiteSelector.SiteSelectorOption> subsitesForSite = ArrayListMultimap.create(); 234 if (addSubsites) { 235 try { 236 CmsObject titleCms = OpenCms.initCmsObject(cms); 237 titleCms.getRequestContext().setSiteRoot(""); 238 for (String subsite : subsites) { 239 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(subsite); 240 if ((site != null) && site.isSubsiteSelectionEnabled()) { // only use subsites that are in an actual site; also, subsite selection must be enabled on the site 241 CmsPath siteRootPath = new CmsPath(site.getSiteRoot()); 242 if (!siteRootPath.equals(new CmsPath(subsite))) { // Don't allow the site itself as a subsite 243 Optional<String> remainingPath = CmsStringUtil.removePrefixPath( 244 site.getSiteRoot(), 245 subsite); 246 if (remainingPath.isPresent()) { 247 try { 248 CmsResource subsiteRes = titleCms.readResource( 249 subsite, 250 CmsResourceFilter.ONLY_VISIBLE_NO_DELETED); 251 CmsResourceUtil resUtil = new CmsResourceUtil(titleCms, subsiteRes); 252 String title = resUtil.getTitle(); 253 if (CmsStringUtil.isEmptyOrWhitespaceOnly(title)) { 254 title = subsiteRes.getName(); 255 } 256 SiteSelectorOption option = new SiteSelectorOption( 257 site.getSiteRoot(), 258 remainingPath.get(), 259 "\u2013 " + title); 260 subsitesForSite.put(siteRootPath, option); 261 } catch (CmsPermissionViolationException | CmsVfsResourceNotFoundException e) { 262 LOG.info(e.getLocalizedMessage(), e); 263 } catch (CmsException e) { 264 LOG.warn(e.getLocalizedMessage(), e); 265 } 266 } 267 } 268 } 269 } 270 } catch (Exception e) { 271 LOG.error(e.getLocalizedMessage(), e); 272 } 273 } 274 List<SiteSelectorOption> result = new ArrayList<>(); 275 for (Map.Entry<String, String> entry : siteOptions.entrySet()) { 276 result.add(new SiteSelectorOption(entry.getKey(), null, entry.getValue())); 277 result.addAll(subsitesForSite.get(new CmsPath(entry.getKey()))); 278 } 279 return result; 280 281 } 282 283 /** 284 * Gets the option for the specific site root (without any subsite path). 285 * 286 * <p>If no option for the site is found, returns null. 287 * 288 * @param siteRoot the site root 289 * @return the option for the site root 290 */ 291 public SiteSelectorOption getOptionForSiteRoot(String siteRoot) { 292 293 for (SiteSelectorOption option : m_options) { 294 if (Objects.equal(option.getSite(), siteRoot) && (option.getPath() == null)) { 295 return option; 296 } 297 } 298 return null; 299 300 } 301 302 /** 303 * Initializes the select options. 304 * 305 * @param cms the CMS context 306 * @param addSubsites true if subsites should be shown 307 */ 308 public void initOptions(CmsObject cms, boolean addSubsites) { 309 310 List<SiteSelectorOption> options = CmsExtendedSiteSelector.getExplorerSiteSelectorOptions(cms, addSubsites); 311 m_options.clear(); 312 m_options.addAll(options); 313 m_initialized = true; 314 } 315 316 /** 317 * Selects a specific site. 318 * 319 * @param siteRoot the site root 320 */ 321 public void selectSite(String siteRoot) { 322 323 SiteSelectorOption option = getOptionForSiteRoot(siteRoot); 324 if (option != null) { 325 setValue(option); 326 } 327 328 } 329 330}