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.ui.favorites;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.i18n.CmsEncoder;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.OpenCms;
037import org.opencms.security.CmsRole;
038import org.opencms.site.CmsSite;
039import org.opencms.ui.A_CmsUI;
040import org.opencms.ui.CmsVaadinUtils;
041import org.opencms.ui.Messages;
042import org.opencms.ui.components.CmsBasicDialog;
043import org.opencms.ui.components.CmsErrorDialog;
044import org.opencms.ui.components.CmsExtendedSiteSelector;
045import org.opencms.ui.components.CmsExtendedSiteSelector.SiteSelectorOption;
046import org.opencms.ui.components.CmsResourceIcon;
047import org.opencms.ui.components.OpenCmsTheme;
048import org.opencms.ui.components.editablegroup.CmsDefaultActionHandler;
049import org.opencms.ui.components.editablegroup.CmsEditableGroup;
050import org.opencms.ui.components.editablegroup.CmsEditableGroupButtons;
051import org.opencms.ui.components.editablegroup.I_CmsEditableGroupRow;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.util.CmsUUID;
054import org.opencms.workplace.explorer.CmsResourceUtil;
055
056import java.util.ArrayList;
057import java.util.HashMap;
058import java.util.LinkedHashMap;
059import java.util.List;
060import java.util.Map;
061import java.util.Optional;
062
063import org.apache.commons.logging.Log;
064
065import com.vaadin.data.provider.ListDataProvider;
066import com.vaadin.shared.ui.ContentMode;
067import com.vaadin.ui.Button;
068import com.vaadin.ui.ComboBox;
069import com.vaadin.ui.Component;
070import com.vaadin.ui.Label;
071import com.vaadin.ui.VerticalLayout;
072import com.vaadin.ui.Window;
073
074/**
075 * Dialog which shows the list of favorites for the current user and allows them to jump to individual favorites,
076 * edit the list, or add the current location to the favorite list.
077 */
078public class CmsFavoriteDialog extends CmsBasicDialog implements CmsEditableGroup.I_RowBuilder {
079
080    /**
081     * Action handler that saves favorites after every change.
082     */
083    public class SaveAfterChangeActionHandler extends CmsDefaultActionHandler {
084
085        /**
086         * Creates a new instance.
087         *
088         * @param row the row
089         */
090        @SuppressWarnings("synthetic-access")
091        public SaveAfterChangeActionHandler(I_CmsEditableGroupRow row) {
092
093            super(CmsFavoriteDialog.this.m_group, row);
094        }
095
096        /**
097         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onAdd()
098         */
099        @Override
100        public void onAdd() {
101
102            super.onAdd();
103            doSave();
104
105        }
106
107        /**
108         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onDelete()
109         */
110        @Override
111        public void onDelete() {
112
113            super.onDelete();
114            doSave();
115
116        }
117
118        /**
119         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onDown()
120         */
121        @Override
122        public void onDown() {
123
124            super.onDown();
125            doSave();
126
127        }
128
129        /**
130         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onEdit()
131         */
132        @Override
133        public void onEdit() {
134
135            Window window = CmsBasicDialog.prepareWindow(DialogWidth.narrow);
136            window.setCaption(CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_FAVORITES_EDIT_TITLE_0));
137            CmsFavInfo info = (CmsFavInfo)m_row.getComponent();
138
139            CmsEditFavoriteDialog dialog = new CmsEditFavoriteDialog(info, entry -> {
140                info.setEntry(entry);
141                doSave();
142                initEntries(getEntries());
143            });
144            window.setContent(dialog);
145            A_CmsUI.get().addWindow(window);
146
147        }
148
149        /**
150         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onUp()
151         */
152        @Override
153        public void onUp() {
154
155            super.onUp();
156            doSave();
157        }
158
159    }
160
161    /**
162     * Handles changes in empty/not empty state by hiding or displaying a message.
163     */
164    class EmptyHandler implements CmsEditableGroup.I_EmptyHandler {
165
166        /** The group. */
167        private CmsEditableGroup m_groupForHandler;
168
169        /** The placeholder. */
170        private Label m_placeholder;
171
172        /**
173         * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_EmptyHandler#init(org.opencms.ui.components.editablegroup.CmsEditableGroup)
174         */
175        public void init(CmsEditableGroup group) {
176
177            String message = CmsVaadinUtils.getMessageText(Messages.GUI_FAVORITES_EMPTY_LIST_PLACEHOLDER_0);
178            m_groupForHandler = group;
179            m_placeholder = new Label();
180            m_placeholder.setContentMode(ContentMode.HTML);
181            String spacer = "<div></div>";
182            String content = "<div>" + CmsEncoder.escapeXml(message) + "</div>";
183            m_placeholder.setValue(spacer + content + spacer);
184            m_placeholder.addStyleName(OpenCmsTheme.BOOKMARKS_PLACEHOLDER);
185            m_placeholder.setHeight("100%");
186
187        }
188
189        /**
190         * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_EmptyHandler#setEmpty(boolean)
191         */
192        public void setEmpty(boolean empty) {
193
194            if (!m_placeholder.isAttached()) {
195                m_groupForHandler.getContainer().addComponent(m_placeholder);
196                m_groupForHandler.getContainer().setExpandRatio(m_placeholder, 1.0f);
197            }
198            m_groupForHandler.getContainer().setHeight(empty ? "100%" : null);
199            m_placeholder.setVisible(empty);
200        }
201
202    }
203
204    /** Logger instance for this class. */
205    private static final Log LOG = CmsLog.getLog(CmsFavoriteDialog.class);
206
207    /** Serial version id. */
208    private static final long serialVersionUID = 1L;
209
210    /** The Add button. */
211    private Button m_addButton;
212
213    /** The Cancel button. */
214    private Button m_cancelButton;
215
216    /** The favorite context. */
217    private I_CmsFavoriteContext m_context;
218
219    /** Current favorite location. */
220    private Optional<CmsFavoriteEntry> m_currentLocation;
221
222    /** The container layout for the favorite widgets. */
223    private VerticalLayout m_favContainer;
224
225    /** Load/save handler for favorites. */
226    private CmsFavoriteDAO m_favDao;
227
228    /** The group for the favorite widgets. */
229    private CmsEditableGroup m_group;
230
231    /** Project selector. */
232    private ComboBox<CmsUUID> m_projectBox;
233
234    /** Label for project selector. */
235    private Label m_projectBoxLabel;
236
237    /** Map of project labels. */
238    private Map<CmsUUID, String> m_projectLabels = new HashMap<>();
239
240    /** Site selector. */
241    private CmsExtendedSiteSelector m_siteBox;
242
243    /** Site labels. */
244    private Map<String, String> m_siteLabels;
245
246    /**
247     * Creates a new dialog instance.
248     *
249     * @param context the favorite context
250     * @param favDao the favorite load/save handler
251     *
252     * @throws CmsException if something goes wrong
253     */
254    public CmsFavoriteDialog(I_CmsFavoriteContext context, CmsFavoriteDAO favDao)
255    throws CmsException {
256
257        super();
258        m_favDao = favDao;
259        m_siteLabels = CmsVaadinUtils.getAvailableSitesMap(A_CmsUI.getCmsObject());
260        m_context = context;
261        context.setDialog(this);
262        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
263        List<CmsFavoriteEntry> entries = m_favDao.loadFavorites();
264        m_cancelButton.addClickListener(evt -> m_context.close());
265        m_favContainer.addLayoutClickListener(evt -> {
266            CmsFavoriteEntry entry = getEntry(evt.getChildComponent());
267            if (entry != null) { // may be null when user double clicks on delete icon
268                m_context.openFavorite(entry);
269            }
270        });
271        m_currentLocation = context.getFavoriteForCurrentLocation();
272        m_addButton.setEnabled(m_currentLocation.isPresent());
273        m_addButton.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_FAVORITES_ADD_BUTTON_0));
274        m_addButton.addClickListener(evt -> onClickAdd());
275        initEntries(entries);
276        CmsObject cms = A_CmsUI.getCmsObject();
277        m_siteBox.initOptions(cms, true);
278        m_siteBox.setValue(new SiteSelectorOption(cms.getRequestContext().getSiteRoot(), null, null));
279        m_siteBox.addValueChangeListener(evt -> {
280            if (!evt.getOldValue().equals(evt.getValue())) {
281                m_context.changeSite(evt.getValue());
282            }
283        });
284        m_siteBox.setPageLength(CmsExtendedSiteSelector.LONG_PAGE_LENGTH);
285        if (OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_USER)) {
286            LinkedHashMap<CmsUUID, String> projects = CmsVaadinUtils.getProjectsMap(cms);
287            m_projectBox.setDataProvider(new ListDataProvider<>(projects.keySet()));
288            m_projectBox.setItemCaptionGenerator(project -> projects.get(project));
289            m_projectBox.setEmptySelectionAllowed(false);
290            m_projectBox.setValue(cms.getRequestContext().getCurrentProject().getId());
291            m_projectBox.addValueChangeListener(evt -> {
292                if (!evt.getOldValue().equals(evt.getValue())) {
293                    m_context.changeProject(evt.getValue());
294                }
295            });
296        } else {
297            m_projectBox.setVisible(false);
298            m_projectBoxLabel.setVisible(false);
299        }
300    }
301
302    /**
303     * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_RowBuilder#buildRow(org.opencms.ui.components.editablegroup.CmsEditableGroup, com.vaadin.ui.Component)
304     */
305    public CmsFavInfo buildRow(CmsEditableGroup group, Component component) {
306
307        CmsFavInfo info = (CmsFavInfo)component;
308        CmsEditableGroupButtons buttons = new CmsEditableGroupButtons(new SaveAfterChangeActionHandler(info));
309        info.setButtons(buttons);
310        return info;
311    }
312
313    /**
314     * Saves the list of currently displayed favorites.
315     */
316    protected void doSave() {
317
318        List<CmsFavoriteEntry> entries = getEntries();
319        try {
320            m_favDao.saveFavorites(entries);
321        } catch (Exception e) {
322            CmsErrorDialog.showErrorDialog(e);
323        }
324    }
325
326    /**
327     * @see org.opencms.ui.components.CmsBasicDialog#enableMaxHeight()
328     */
329    @Override
330    protected void enableMaxHeight() {
331
332        // do nothing here, we want to disable the max height mechanism
333    }
334
335    /**
336     * Initializes the bookmark widgets.
337     *
338     * @param entries the list of bookmark entries
339     */
340    protected void initEntries(List<CmsFavoriteEntry> entries) {
341
342        m_favContainer.removeAllComponents();
343        m_group = new CmsEditableGroup(m_favContainer, null, new EmptyHandler());
344        m_group.setEditEnabled(true);
345        m_group.setAddButtonVisible(false);
346        m_group.setRowBuilder(this);
347        m_group.init();
348
349        for (CmsFavoriteEntry favEntry : entries) {
350            Component favInfo;
351            try {
352                favInfo = createFavInfo(favEntry);
353                m_group.addRow(favInfo);
354            } catch (CmsException e) {
355                LOG.warn(e.getLocalizedMessage(), e);
356            }
357
358        }
359    }
360
361    /**
362     * Gets the favorite entries corresponding to the currently displayed favorite widgets.
363     *
364     * @return the list of favorite entries
365     */
366    List<CmsFavoriteEntry> getEntries() {
367
368        List<CmsFavoriteEntry> result = new ArrayList<>();
369        for (I_CmsEditableGroupRow row : m_group.getRows()) {
370            CmsFavoriteEntry entry = ((CmsFavInfo)row).getEntry();
371            result.add(entry);
372        }
373        return result;
374    }
375
376    /**
377     * Creates a favorite widget for a favorite entry.
378     *
379     * @param entry the favorite entry
380     * @return the favorite widget
381     *
382     * @throws CmsException if something goes wrong
383     */
384    private CmsFavInfo createFavInfo(CmsFavoriteEntry entry) throws CmsException {
385
386        String title = "";
387        String subtitle = "";
388        CmsFavInfo result = new CmsFavInfo(entry);
389        CmsObject cms = A_CmsUI.getCmsObject();
390        String project = getProject(cms, entry);
391        String site = getSite(cms, entry);
392        CmsResource resource = null;
393        try {
394            CmsUUID idToLoad = entry.getDetailId() != null ? entry.getDetailId() : entry.getStructureId();
395            resource = cms.readResource(idToLoad, CmsResourceFilter.IGNORE_EXPIRATION.addRequireVisible());
396            CmsResourceUtil resutil = new CmsResourceUtil(cms, resource);
397            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(entry.getCustomTitle())) {
398                title = entry.getCustomTitle();
399            } else {
400                switch (entry.getType()) {
401                    case explorerFolder:
402                    default:
403                        title = CmsStringUtil.isEmpty(resutil.getTitle())
404                        ? CmsResource.getName(resource.getRootPath())
405                        : resutil.getTitle();
406                        break;
407                    case page:
408                        title = resutil.getTitle();
409                        break;
410                }
411            }
412            subtitle = resource.getRootPath();
413            CmsResourceIcon icon = result.getResourceIcon();
414            icon.initContent(resutil, CmsResource.STATE_UNCHANGED, false, false);
415        } catch (CmsException e) {
416            LOG.warn(e.getLocalizedMessage(), e);
417        }
418        result.setResource(resource);
419        result.getTopLine().setValue(title);
420        result.getBottomLine().setValue(subtitle);
421        result.getProjectLabel().setValue(project);
422        result.getSiteLabel().setValue(site);
423
424        return result;
425
426    }
427
428    /**
429     * Gets the favorite entry for a given row.
430     *
431     * @param row the widget used to display the favorite
432     * @return the favorite entry for the widget
433     */
434    private CmsFavoriteEntry getEntry(Component row) {
435
436        if (row instanceof CmsFavInfo) {
437
438            return ((CmsFavInfo)row).getEntry();
439
440        }
441        return null;
442
443    }
444
445    /**
446     * Gets the project name for a favorite entry.
447     *
448     * @param cms the CMS context
449     * @param entry the favorite entry
450     * @return the project name for the favorite entry
451     * @throws CmsException if something goes wrong
452     */
453    private String getProject(CmsObject cms, CmsFavoriteEntry entry) throws CmsException {
454
455        String result = m_projectLabels.get(entry.getProjectId());
456        if (result == null) {
457            result = cms.readProject(entry.getProjectId()).getName();
458            m_projectLabels.put(entry.getProjectId(), result);
459        }
460        return result;
461    }
462
463    /**
464     * Gets the site label for the entry.
465     *
466     * @param cms the current CMS context
467     * @param entry the entry
468     * @return the site label for the entry
469     */
470    private String getSite(CmsObject cms, CmsFavoriteEntry entry) {
471
472        CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(entry.getSiteRoot());
473        if (m_siteLabels.containsKey(entry.getSiteRoot())) {
474            return m_siteLabels.get(entry.getSiteRoot());
475        }
476        String result = entry.getSiteRoot();
477        if (site != null) {
478            if (!CmsStringUtil.isEmpty(site.getTitle())) {
479                result = site.getTitle();
480            }
481        }
482        return result;
483    }
484
485    /**
486     * The click handler for the add button.
487     */
488    private void onClickAdd() {
489
490        if (m_currentLocation.isPresent()) {
491            CmsFavoriteEntry entry = m_currentLocation.get();
492            List<CmsFavoriteEntry> entries = getEntries();
493            entries.add(entry);
494            try {
495                m_favDao.saveFavorites(entries);
496            } catch (Exception e) {
497                CmsErrorDialog.showErrorDialog(e);
498            }
499            m_context.close();
500        }
501    }
502
503}