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.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 SiteSelectorOption option = m_siteBox.getOptionForSiteRoot(cms.getRequestContext().getSiteRoot()); 279 m_siteBox.setValue( 280 option != null ? option : new SiteSelectorOption(cms.getRequestContext().getSiteRoot(), null, null)); 281 m_siteBox.addValueChangeListener(evt -> { 282 if (!evt.getOldValue().equals(evt.getValue())) { 283 m_context.changeSite(evt.getValue()); 284 } 285 }); 286 m_siteBox.setPageLength(CmsExtendedSiteSelector.LONG_PAGE_LENGTH); 287 if (OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_USER)) { 288 LinkedHashMap<CmsUUID, String> projects = CmsVaadinUtils.getProjectsMap(cms); 289 m_projectBox.setDataProvider(new ListDataProvider<>(projects.keySet())); 290 m_projectBox.setItemCaptionGenerator(project -> projects.get(project)); 291 m_projectBox.setEmptySelectionAllowed(false); 292 m_projectBox.setValue(cms.getRequestContext().getCurrentProject().getId()); 293 m_projectBox.addValueChangeListener(evt -> { 294 if (!evt.getOldValue().equals(evt.getValue())) { 295 m_context.changeProject(evt.getValue()); 296 } 297 }); 298 } else { 299 m_projectBox.setVisible(false); 300 m_projectBoxLabel.setVisible(false); 301 } 302 } 303 304 /** 305 * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_RowBuilder#buildRow(org.opencms.ui.components.editablegroup.CmsEditableGroup, com.vaadin.ui.Component) 306 */ 307 public CmsFavInfo buildRow(CmsEditableGroup group, Component component) { 308 309 CmsFavInfo info = (CmsFavInfo)component; 310 CmsEditableGroupButtons buttons = new CmsEditableGroupButtons(new SaveAfterChangeActionHandler(info)); 311 info.setButtons(buttons); 312 return info; 313 } 314 315 /** 316 * Saves the list of currently displayed favorites. 317 */ 318 protected void doSave() { 319 320 List<CmsFavoriteEntry> entries = getEntries(); 321 try { 322 m_favDao.saveFavorites(entries); 323 } catch (Exception e) { 324 CmsErrorDialog.showErrorDialog(e); 325 } 326 } 327 328 /** 329 * @see org.opencms.ui.components.CmsBasicDialog#enableMaxHeight() 330 */ 331 @Override 332 protected void enableMaxHeight() { 333 334 // do nothing here, we want to disable the max height mechanism 335 } 336 337 /** 338 * Initializes the bookmark widgets. 339 * 340 * @param entries the list of bookmark entries 341 */ 342 protected void initEntries(List<CmsFavoriteEntry> entries) { 343 344 m_favContainer.removeAllComponents(); 345 m_group = new CmsEditableGroup(m_favContainer, null, new EmptyHandler()); 346 m_group.setEditEnabled(true); 347 m_group.setAddButtonVisible(false); 348 m_group.setRowBuilder(this); 349 m_group.init(); 350 351 for (CmsFavoriteEntry favEntry : entries) { 352 Component favInfo; 353 try { 354 favInfo = createFavInfo(favEntry); 355 m_group.addRow(favInfo); 356 } catch (CmsException e) { 357 LOG.warn(e.getLocalizedMessage(), e); 358 } 359 360 } 361 } 362 363 /** 364 * Gets the favorite entries corresponding to the currently displayed favorite widgets. 365 * 366 * @return the list of favorite entries 367 */ 368 List<CmsFavoriteEntry> getEntries() { 369 370 List<CmsFavoriteEntry> result = new ArrayList<>(); 371 for (I_CmsEditableGroupRow row : m_group.getRows()) { 372 CmsFavoriteEntry entry = ((CmsFavInfo)row).getEntry(); 373 result.add(entry); 374 } 375 return result; 376 } 377 378 /** 379 * Creates a favorite widget for a favorite entry. 380 * 381 * @param entry the favorite entry 382 * @return the favorite widget 383 * 384 * @throws CmsException if something goes wrong 385 */ 386 private CmsFavInfo createFavInfo(CmsFavoriteEntry entry) throws CmsException { 387 388 String title = ""; 389 String subtitle = ""; 390 CmsFavInfo result = new CmsFavInfo(entry); 391 CmsObject cms = A_CmsUI.getCmsObject(); 392 String project = getProject(cms, entry); 393 String site = getSite(cms, entry); 394 CmsResource resource = null; 395 try { 396 CmsUUID idToLoad = entry.getDetailId() != null ? entry.getDetailId() : entry.getStructureId(); 397 resource = cms.readResource(idToLoad, CmsResourceFilter.IGNORE_EXPIRATION.addRequireVisible()); 398 CmsResourceUtil resutil = new CmsResourceUtil(cms, resource); 399 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(entry.getCustomTitle())) { 400 title = entry.getCustomTitle(); 401 } else { 402 switch (entry.getType()) { 403 case explorerFolder: 404 default: 405 title = CmsStringUtil.isEmpty(resutil.getTitle()) 406 ? CmsResource.getName(resource.getRootPath()) 407 : resutil.getTitle(); 408 break; 409 case page: 410 title = resutil.getTitle(); 411 break; 412 } 413 } 414 subtitle = resource.getRootPath(); 415 CmsResourceIcon icon = result.getResourceIcon(); 416 icon.initContent(resutil, CmsResource.STATE_UNCHANGED, false, false); 417 } catch (CmsException e) { 418 LOG.warn(e.getLocalizedMessage(), e); 419 } 420 result.setResource(resource); 421 result.getTopLine().setValue(title); 422 result.getBottomLine().setValue(subtitle); 423 result.getProjectLabel().setValue(project); 424 result.getSiteLabel().setValue(site); 425 426 return result; 427 428 } 429 430 /** 431 * Gets the favorite entry for a given row. 432 * 433 * @param row the widget used to display the favorite 434 * @return the favorite entry for the widget 435 */ 436 private CmsFavoriteEntry getEntry(Component row) { 437 438 if (row instanceof CmsFavInfo) { 439 440 return ((CmsFavInfo)row).getEntry(); 441 442 } 443 return null; 444 445 } 446 447 /** 448 * Gets the project name for a favorite entry. 449 * 450 * @param cms the CMS context 451 * @param entry the favorite entry 452 * @return the project name for the favorite entry 453 * @throws CmsException if something goes wrong 454 */ 455 private String getProject(CmsObject cms, CmsFavoriteEntry entry) throws CmsException { 456 457 String result = m_projectLabels.get(entry.getProjectId()); 458 if (result == null) { 459 result = cms.readProject(entry.getProjectId()).getName(); 460 m_projectLabels.put(entry.getProjectId(), result); 461 } 462 return result; 463 } 464 465 /** 466 * Gets the site label for the entry. 467 * 468 * @param cms the current CMS context 469 * @param entry the entry 470 * @return the site label for the entry 471 */ 472 private String getSite(CmsObject cms, CmsFavoriteEntry entry) { 473 474 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(entry.getSiteRoot()); 475 if (m_siteLabels.containsKey(entry.getSiteRoot())) { 476 return m_siteLabels.get(entry.getSiteRoot()); 477 } 478 String result = entry.getSiteRoot(); 479 if (site != null) { 480 if (!CmsStringUtil.isEmpty(site.getTitle())) { 481 result = site.getTitle(); 482 } 483 } 484 return result; 485 } 486 487 /** 488 * The click handler for the add button. 489 */ 490 private void onClickAdd() { 491 492 if (m_currentLocation.isPresent()) { 493 CmsFavoriteEntry entry = m_currentLocation.get(); 494 List<CmsFavoriteEntry> entries = getEntries(); 495 entries.add(entry); 496 try { 497 m_favDao.saveFavorites(entries); 498 } catch (Exception e) { 499 CmsErrorDialog.showErrorDialog(e); 500 } 501 m_context.close(); 502 } 503 } 504 505}