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}