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; 029 030import org.opencms.ade.galleries.CmsSiteSelectorOptionBuilder; 031import org.opencms.ade.galleries.shared.CmsSiteSelectorOption; 032import org.opencms.db.CmsUserSettings; 033import org.opencms.file.CmsGroup; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsProject; 036import org.opencms.file.CmsUser; 037import org.opencms.file.types.A_CmsResourceTypeFolderBase; 038import org.opencms.file.types.CmsResourceTypeXmlContent; 039import org.opencms.file.types.I_CmsResourceType; 040import org.opencms.i18n.CmsEncoder; 041import org.opencms.i18n.CmsMessages; 042import org.opencms.i18n.I_CmsMessageBundle; 043import org.opencms.main.CmsException; 044import org.opencms.main.CmsLog; 045import org.opencms.main.OpenCms; 046import org.opencms.security.CmsOrganizationalUnit; 047import org.opencms.security.CmsRole; 048import org.opencms.security.I_CmsPrincipal; 049import org.opencms.ui.apps.Messages; 050import org.opencms.ui.apps.user.CmsOUHandler; 051import org.opencms.ui.components.OpenCmsTheme; 052import org.opencms.ui.contextmenu.CmsContextMenu; 053import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry; 054import org.opencms.util.CmsFileUtil; 055import org.opencms.util.CmsMacroResolver; 056import org.opencms.util.CmsStringUtil; 057import org.opencms.workplace.CmsWorkplace; 058import org.opencms.workplace.CmsWorkplaceMessages; 059import org.opencms.workplace.explorer.CmsExplorerTypeSettings; 060import org.opencms.workplace.explorer.CmsResourceUtil; 061 062import java.io.ByteArrayInputStream; 063import java.io.IOException; 064import java.io.InputStream; 065import java.io.UnsupportedEncodingException; 066import java.util.ArrayList; 067import java.util.Arrays; 068import java.util.Collection; 069import java.util.Collections; 070import java.util.HashSet; 071import java.util.Iterator; 072import java.util.List; 073import java.util.Locale; 074import java.util.Map; 075import java.util.Map.Entry; 076 077import javax.servlet.http.HttpServletRequest; 078 079import org.apache.commons.lang3.ClassUtils; 080import org.apache.commons.logging.Log; 081 082import com.google.common.base.Function; 083import com.google.common.base.Predicate; 084import com.google.common.collect.Lists; 085import com.vaadin.server.ErrorMessage; 086import com.vaadin.server.ExternalResource; 087import com.vaadin.server.FontIcon; 088import com.vaadin.server.Resource; 089import com.vaadin.server.VaadinService; 090import com.vaadin.shared.MouseEventDetails.MouseButton; 091import com.vaadin.shared.Version; 092import com.vaadin.ui.AbstractComponent; 093import com.vaadin.ui.Alignment; 094import com.vaadin.ui.Button; 095import com.vaadin.ui.Button.ClickEvent; 096import com.vaadin.ui.Button.ClickListener; 097import com.vaadin.ui.Component; 098import com.vaadin.ui.ComponentContainer; 099import com.vaadin.ui.HasComponents; 100import com.vaadin.ui.JavaScript; 101import com.vaadin.ui.Panel; 102import com.vaadin.ui.SingleComponentContainer; 103import com.vaadin.ui.TextField; 104import com.vaadin.ui.UI; 105import com.vaadin.ui.Window; 106import com.vaadin.ui.declarative.Design; 107import com.vaadin.ui.themes.ValoTheme; 108import com.vaadin.v7.data.Container; 109import com.vaadin.v7.data.Container.Filter; 110import com.vaadin.v7.data.Item; 111import com.vaadin.v7.data.util.IndexedContainer; 112import com.vaadin.v7.event.ItemClickEvent; 113import com.vaadin.v7.shared.ui.combobox.FilteringMode; 114import com.vaadin.v7.ui.AbstractField; 115import com.vaadin.v7.ui.ComboBox; 116import com.vaadin.v7.ui.Label; 117import com.vaadin.v7.ui.OptionGroup; 118import com.vaadin.v7.ui.Table; 119import com.vaadin.v7.ui.VerticalLayout; 120 121/** 122 * Vaadin utility functions.<p> 123 * 124 */ 125@SuppressWarnings("deprecation") 126public final class CmsVaadinUtils { 127 128 /** 129 * Helper class for building option groups.<p> 130 */ 131 public static class OptionGroupBuilder { 132 133 /** The option group being built. */ 134 private OptionGroup m_optionGroup = new OptionGroup(); 135 136 /** 137 * Adds an option.<p> 138 * 139 * @param key the option key 140 * @param text the option text 141 * 142 * @return this instance 143 */ 144 public OptionGroupBuilder add(String key, String text) { 145 146 m_optionGroup.addItem(key); 147 m_optionGroup.setItemCaption(key, text); 148 return this; 149 } 150 151 /** 152 * Returns the option group.<p> 153 * 154 * @return the option group 155 */ 156 public OptionGroup build() { 157 158 return m_optionGroup; 159 } 160 161 /** 162 * Adds horizontal style to option group.<p> 163 * 164 * @return this instance 165 */ 166 public OptionGroupBuilder horizontal() { 167 168 m_optionGroup.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); 169 return this; 170 } 171 } 172 173 /** Container property ids. */ 174 public static enum PropertyId { 175 /** The caption id. */ 176 caption, 177 /** The icon id. */ 178 icon, 179 /** The is folder id. */ 180 isFolder, 181 /** The is XML content id. */ 182 isXmlContent 183 } 184 185 /** Container filter for the resource type container to show not folder types only. */ 186 public static final Filter FILTER_NO_FOLDERS = new Filter() { 187 188 private static final long serialVersionUID = 1L; 189 190 public boolean appliesToProperty(Object propertyId) { 191 192 return PropertyId.isFolder.equals(propertyId); 193 } 194 195 public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException { 196 197 return !((Boolean)item.getItemProperty(PropertyId.isFolder).getValue()).booleanValue(); 198 } 199 }; 200 201 /** Container filter for the resource type container to show XML content types only. */ 202 public static final Filter FILTER_XML_CONTENTS = new Filter() { 203 204 private static final long serialVersionUID = 1L; 205 206 public boolean appliesToProperty(Object propertyId) { 207 208 return PropertyId.isXmlContent.equals(propertyId); 209 } 210 211 public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException { 212 213 return ((Boolean)item.getItemProperty(PropertyId.isXmlContent).getValue()).booleanValue(); 214 } 215 }; 216 /** The combo box label item property id. */ 217 public static final String PROPERTY_LABEL = "label"; 218 219 /** The combo box value item property id. */ 220 public static final String PROPERTY_VALUE = "value"; 221 222 /** The Vaadin bootstrap script, with some macros to be dynamically replaced later. */ 223 protected static final String BOOTSTRAP_SCRIPT = "vaadin.initApplication(\"%(elementId)\", {\n" 224 + " \"browserDetailsUrl\": \"%(vaadinServlet)\",\n" 225 + " \"serviceUrl\": \"%(vaadinServlet)\",\n" 226 + " \"widgetset\": \"org.opencms.ui.WidgetSet\",\n" 227 + " \"theme\": \"opencms\",\n" 228 + " \"versionInfo\": {\"vaadinVersion\": \"%(vaadinVersion)\"},\n" 229 + " \"vaadinDir\": \"%(vaadinDir)\",\n" 230 + " \"heartbeatInterval\": 30,\n" 231 + " \"debug\": false,\n" 232 + " \"standalone\": false,\n" 233 + " \"authErrMsg\": {\n" 234 + " \"message\": \"Take note of any unsaved data, \"+\n" 235 + " \"and <u>click here<\\/u> to continue.\",\n" 236 + " \"caption\": \"Authentication problem\"\n" 237 + " },\n" 238 + " \"comErrMsg\": {\n" 239 + " \"message\": \"Take note of any unsaved data, \"+\n" 240 + " \"and <u>click here<\\/u> to continue.\",\n" 241 + " \"caption\": \"Communication problem\"\n" 242 + " },\n" 243 + " \"sessExpMsg\": {\n" 244 + " \"message\": \"Take note of any unsaved data, \"+\n" 245 + " \"and <u>click here<\\/u> to continue.\",\n" 246 + " \"caption\": \"Session Expired\"\n" 247 + " }\n" 248 + " });"; 249 250 /** The logger of this class. */ 251 private static final Log LOG = CmsLog.getLog(CmsVaadinUtils.class); 252 253 /** 254 * Hidden default constructor for utility class.<p> 255 */ 256 private CmsVaadinUtils() { 257 258 } 259 260 /** 261 * Builds a container for use in combo boxes from a map of key/value pairs, where the keys are options and the values are captions.<p> 262 * 263 * @param captionProperty the property name to use for captions 264 * @param map the map 265 * @return the new container 266 */ 267 public static IndexedContainer buildContainerFromMap(String captionProperty, Map<String, String> map) { 268 269 IndexedContainer container = new IndexedContainer(); 270 for (Map.Entry<String, String> entry : map.entrySet()) { 271 container.addItem(entry.getKey()).getItemProperty(captionProperty).setValue(entry.getValue()); 272 } 273 return container; 274 } 275 276 /** 277 * Centers the parent window of given component.<p> 278 * 279 * @param component Component as child of window 280 */ 281 public static void centerWindow(Component component) { 282 283 Window window = getWindow(component); 284 if (window != null) { 285 window.center(); 286 } 287 } 288 289 /** 290 * Closes the window containing the given component. 291 * 292 * @param component a component 293 */ 294 public static void closeWindow(Component component) { 295 296 Window window = getWindow(component); 297 if (window != null) { 298 window.close(); 299 } 300 } 301 302 /** 303 * Creates a click listener which calls a Runnable when activated.<p> 304 * 305 * @param action the Runnable to execute on a click 306 * 307 * @return the click listener 308 */ 309 public static Button.ClickListener createClickListener(final Runnable action) { 310 311 return new Button.ClickListener() { 312 313 /** Serial version id. */ 314 private static final long serialVersionUID = 1L; 315 316 public void buttonClick(ClickEvent event) { 317 318 action.run(); 319 } 320 }; 321 } 322 323 /** 324 * Simple context menu handler for multi-select tables. 325 * 326 * @param table the table 327 * @param menu the table's context menu 328 * @param event the click event 329 * @param entries the context menu entries 330 */ 331 @SuppressWarnings("unchecked") 332 public static <T> void defaultHandleContextMenuForMultiselect( 333 Table table, 334 CmsContextMenu menu, 335 ItemClickEvent event, 336 List<I_CmsSimpleContextMenuEntry<Collection<T>>> entries) { 337 338 if (!event.isCtrlKey() && !event.isShiftKey()) { 339 if (event.getButton().equals(MouseButton.RIGHT)) { 340 Collection<T> oldValue = ((Collection<T>)table.getValue()); 341 if (oldValue.isEmpty() || !oldValue.contains(event.getItemId())) { 342 table.setValue(new HashSet<Object>(Arrays.asList(event.getItemId()))); 343 } 344 Collection<T> selection = (Collection<T>)table.getValue(); 345 menu.setEntries(entries, selection); 346 menu.openForTable(event, table); 347 } 348 } 349 350 } 351 352 /** 353 * Reads the content of an input stream into a string (using UTF-8 encoding), performs a function on the string, and returns the result 354 * again as an input stream.<p> 355 * 356 * @param stream the stream producing the input data 357 * @param transformation the function to apply to the input 358 * 359 * @return the stream producing the transformed input data 360 */ 361 public static InputStream filterUtf8ResourceStream(InputStream stream, Function<String, String> transformation) { 362 363 try { 364 byte[] streamData = CmsFileUtil.readFully(stream); 365 String dataAsString = new String(streamData, "UTF-8"); 366 byte[] transformedData = transformation.apply(dataAsString).getBytes("UTF-8"); 367 return new ByteArrayInputStream(transformedData); 368 } catch (UnsupportedEncodingException e) { 369 LOG.error(e.getLocalizedMessage(), e); 370 return null; 371 } catch (IOException e) { 372 LOG.error(e.getLocalizedMessage(), e); 373 throw new RuntimeException(e); 374 } 375 } 376 377 /** 378 * Get all groups with blacklist.<p> 379 * 380 * @param cms CmsObject 381 * @param ouFqn ou name 382 * @param propCaption property 383 * @param propIcon property for icon 384 * @param propOu organizational unit 385 * @param blackList blacklist 386 * @param iconProvider the icon provider 387 * @return indexed container 388 */ 389 public static IndexedContainer getAvailableGroupsContainerWithout( 390 CmsObject cms, 391 String ouFqn, 392 String propCaption, 393 String propIcon, 394 String propOu, 395 List<CmsGroup> blackList, 396 java.util.function.Function<CmsGroup, CmsCssIcon> iconProvider) { 397 398 if (blackList == null) { 399 blackList = new ArrayList<CmsGroup>(); 400 } 401 IndexedContainer res = new IndexedContainer(); 402 res.addContainerProperty(propCaption, String.class, ""); 403 res.addContainerProperty(propOu, String.class, ""); 404 if (propIcon != null) { 405 res.addContainerProperty(propIcon, CmsCssIcon.class, null); 406 } 407 try { 408 for (CmsGroup group : OpenCms.getRoleManager().getManageableGroups(cms, ouFqn, true)) { 409 if (!blackList.contains(group)) { 410 Item item = res.addItem(group); 411 if (item == null) { 412 continue; 413 } 414 if (iconProvider != null) { 415 item.getItemProperty(propIcon).setValue(iconProvider.apply(group)); 416 } 417 item.getItemProperty(propCaption).setValue(group.getSimpleName()); 418 item.getItemProperty(propOu).setValue(group.getOuFqn()); 419 } 420 } 421 422 } catch (CmsException e) { 423 LOG.error("Unable to read groups", e); 424 } 425 return res; 426 } 427 428 /** 429 * Returns the available projects.<p> 430 * 431 * @param cms the CMS context 432 * 433 * @return the available projects 434 */ 435 public static List<CmsProject> getAvailableProjects(CmsObject cms) { 436 437 // get all project information 438 List<CmsProject> allProjects; 439 try { 440 String ouFqn = ""; 441 CmsUserSettings settings = new CmsUserSettings(cms); 442 if (!settings.getListAllProjects()) { 443 ouFqn = cms.getRequestContext().getCurrentUser().getOuFqn(); 444 } 445 allProjects = new ArrayList<CmsProject>( 446 OpenCms.getOrgUnitManager().getAllAccessibleProjects(cms, ouFqn, settings.getListAllProjects())); 447 Iterator<CmsProject> itProjects = allProjects.iterator(); 448 while (itProjects.hasNext()) { 449 CmsProject prj = itProjects.next(); 450 if (prj.isHiddenFromSelector()) { 451 itProjects.remove(); 452 } 453 } 454 } catch (CmsException e) { 455 // should usually never happen 456 LOG.error(e.getLocalizedMessage(), e); 457 allProjects = Collections.emptyList(); 458 } 459 return allProjects; 460 } 461 462 /** 463 * Builds an IndexedContainer containing the sites selectable by the current user.<p> 464 * 465 * @param cms the CMS context 466 * @param captionPropertyName the name of the property used to store captions 467 * 468 * @return the container with the available sites 469 */ 470 public static IndexedContainer getAvailableSitesContainer(CmsObject cms, String captionPropertyName) { 471 472 CmsSiteSelectorOptionBuilder optBuilder = new CmsSiteSelectorOptionBuilder(cms); 473 optBuilder.addNormalSites(true, (new CmsUserSettings(cms)).getStartFolder()); 474 optBuilder.addSharedSite(); 475 IndexedContainer availableSites = new IndexedContainer(); 476 availableSites.addContainerProperty(captionPropertyName, String.class, null); 477 for (CmsSiteSelectorOption option : optBuilder.getOptions()) { 478 Item siteItem = availableSites.addItem(option.getSiteRoot()); 479 siteItem.getItemProperty(captionPropertyName).setValue(option.getMessage()); 480 } 481 String currentSiteRoot = cms.getRequestContext().getSiteRoot(); 482 if (!availableSites.containsId(currentSiteRoot)) { 483 availableSites.addItem(currentSiteRoot).getItemProperty(captionPropertyName).setValue(currentSiteRoot); 484 } 485 return availableSites; 486 } 487 488 /** 489 * Returns the Javascript code to use for initializing a Vaadin UI.<p> 490 * 491 * @param cms the CMS context 492 * @param elementId the id of the DOM element in which to initialize the UI 493 * @param servicePath the UI servlet path 494 * @return the Javascript code to initialize Vaadin 495 * 496 * @throws Exception if something goes wrong 497 */ 498 public static String getBootstrapScript(CmsObject cms, String elementId, String servicePath) throws Exception { 499 500 String script = BOOTSTRAP_SCRIPT; 501 CmsMacroResolver resolver = new CmsMacroResolver(); 502 String context = OpenCms.getSystemInfo().getContextPath(); 503 String vaadinDir = CmsStringUtil.joinPaths(context, "VAADIN/"); 504 String vaadinVersion = Version.getFullVersion(); 505 String vaadinServlet = CmsStringUtil.joinPaths(context, servicePath); 506 String vaadinBootstrap = CmsStringUtil.joinPaths(context, "VAADIN/vaadinBootstrap.js"); 507 resolver.addMacro("vaadinDir", vaadinDir); 508 resolver.addMacro("vaadinVersion", vaadinVersion); 509 resolver.addMacro("elementId", elementId); 510 resolver.addMacro("vaadinServlet", vaadinServlet); 511 resolver.addMacro("vaadinBootstrap", vaadinBootstrap); 512 script = resolver.resolveMacros(script); 513 return script; 514 515 } 516 517 /** 518 * Returns the path to the design template file of the given component.<p> 519 * 520 * @param component the component 521 * 522 * @return the path 523 */ 524 public static String getDefaultDesignPath(Component component) { 525 526 String className = component.getClass().getName(); 527 String designPath = className.replace(".", "/") + ".html"; 528 return designPath; 529 } 530 531 /** 532 * Gets container with alls groups of a certain user. 533 * 534 * @param cms cmsobject 535 * @param user to find groups for 536 * @param caption caption property 537 * @param iconProp property 538 * @param ou ou 539 * @param propStatus status property 540 * @param iconProvider the icon provider 541 * @return Indexed Container 542 */ 543 public static IndexedContainer getGroupsOfUser( 544 CmsObject cms, 545 CmsUser user, 546 String caption, 547 String iconProp, 548 String ou, 549 String propStatus, 550 Function<CmsGroup, CmsCssIcon> iconProvider) { 551 552 IndexedContainer container = new IndexedContainer(); 553 container.addContainerProperty(caption, String.class, ""); 554 container.addContainerProperty(ou, String.class, ""); 555 container.addContainerProperty(propStatus, Boolean.class, new Boolean(true)); 556 if (iconProvider != null) { 557 container.addContainerProperty(iconProp, CmsCssIcon.class, null); 558 } 559 try { 560 for (CmsGroup group : cms.getGroupsOfUser(user.getName(), true)) { 561 Item item = container.addItem(group); 562 item.getItemProperty(caption).setValue(group.getSimpleName()); 563 item.getItemProperty(ou).setValue(group.getOuFqn()); 564 if (iconProvider != null) { 565 item.getItemProperty(iconProp).setValue(iconProvider.apply(group)); 566 } 567 } 568 } catch (CmsException e) { 569 LOG.error("Unable to read groups from user", e); 570 } 571 return container; 572 } 573 574 /** 575 * Creates a layout with info panel.<p> 576 * 577 * @param messageString Message to be displayed 578 * @return layout 579 */ 580 public static VerticalLayout getInfoLayout(String messageString) { 581 582 VerticalLayout ret = new VerticalLayout(); 583 ret.setMargin(true); 584 ret.addStyleName("o-center"); 585 ret.setWidth("100%"); 586 VerticalLayout inner = new VerticalLayout(); 587 inner.addStyleName("o-workplace-maxwidth"); 588 Panel panel = new Panel(); 589 panel.setWidth("100%"); 590 591 Label label = new Label(CmsVaadinUtils.getMessageText(messageString)); 592 label.addStyleName("o-report"); 593 panel.setContent(label); 594 595 inner.addComponent(panel); 596 ret.addComponent(inner); 597 return ret; 598 } 599 600 /** 601 * Get container with languages.<p> 602 * 603 * @param captionPropertyName name 604 * @return indexed container 605 */ 606 public static IndexedContainer getLanguageContainer(String captionPropertyName) { 607 608 IndexedContainer result = new IndexedContainer(); 609 result.addContainerProperty(captionPropertyName, String.class, ""); 610 611 Iterator<Locale> itLocales = OpenCms.getLocaleManager().getAvailableLocales().iterator(); 612 while (itLocales.hasNext()) { 613 Locale locale = itLocales.next(); 614 Item item = result.addItem(locale); 615 item.getItemProperty(captionPropertyName).setValue(locale.getDisplayName(A_CmsUI.get().getLocale())); 616 } 617 618 return result; 619 620 } 621 622 /** 623 * Gets the message for the current locale and the given key and arguments.<p> 624 * 625 * @param messages the messages instance 626 * @param key the message key 627 * @param args the message arguments 628 * 629 * @return the message text for the current locale 630 */ 631 public static String getMessageText(I_CmsMessageBundle messages, String key, Object... args) { 632 633 return messages.getBundle(A_CmsUI.get().getLocale()).key(key, args); 634 } 635 636 /** 637 * Gets the workplace message for the current locale and the given key and arguments.<p> 638 * 639 * @param key the message key 640 * @param args the message arguments 641 * 642 * @return the message text for the current locale 643 */ 644 public static String getMessageText(String key, Object... args) { 645 646 return getWpMessagesForCurrentLocale().key(key, args); 647 } 648 649 /** 650 * Creates the ComboBox for OU selection.<p> 651 * @param cms CmsObject 652 * @param baseOu OU 653 * @param log Logger object 654 * 655 * @return ComboBox 656 */ 657 public static ComboBox getOUComboBox(CmsObject cms, String baseOu, Log log) { 658 659 return getOUComboBox(cms, baseOu, log, true); 660 } 661 662 /** 663 * Creates the ComboBox for OU selection.<p> 664 * @param cms CmsObject 665 * @param baseOu OU 666 * @param log Logger object 667 * @param includeWebOU include webou? 668 * 669 * @return ComboBox 670 */ 671 public static ComboBox getOUComboBox(CmsObject cms, String baseOu, Log log, boolean includeWebOU) { 672 673 ComboBox combo = null; 674 try { 675 IndexedContainer container = new IndexedContainer(); 676 container.addContainerProperty("desc", String.class, ""); 677 for (String ou : CmsOUHandler.getManagableOUs(cms)) { 678 if (includeWebOU | !OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, ou).hasFlagWebuser()) { 679 Item item = container.addItem(ou); 680 if (ou == "") { 681 CmsOrganizationalUnit root = OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, ""); 682 item.getItemProperty("desc").setValue(root.getDisplayName(A_CmsUI.get().getLocale())); 683 } else { 684 item.getItemProperty("desc").setValue( 685 OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, ou).getDisplayName( 686 A_CmsUI.get().getLocale())); 687 } 688 } 689 } 690 combo = new ComboBox(null, container); 691 combo.setTextInputAllowed(true); 692 combo.setNullSelectionAllowed(false); 693 combo.setWidth("379px"); 694 combo.setInputPrompt( 695 Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_CLICK_TO_EDIT_0)); 696 combo.setItemCaptionPropertyId("desc"); 697 698 combo.setFilteringMode(FilteringMode.CONTAINS); 699 700 combo.select(baseOu); 701 702 } catch (CmsException e) { 703 if (log != null) { 704 log.error("Unable to read OU", e); 705 } 706 } 707 return combo; 708 } 709 710 /** 711 * Gives item id from path.<p> 712 * 713 * @param cnt to be used 714 * @param path to obtain item id from 715 * @return item id 716 */ 717 public static String getPathItemId(Container cnt, String path) { 718 719 for (String id : Arrays.asList(path, CmsFileUtil.toggleTrailingSeparator(path))) { 720 if (cnt.containsId(id)) { 721 return id; 722 } 723 } 724 return null; 725 } 726 727 /** 728 * Get container for principal. 729 * 730 * @param cms cmsobject 731 * @param list of principals 732 * @param captionID caption id 733 * @param descID description id 734 * @param iconID icon id 735 * @param ouID ou id 736 * @param icon icon 737 * @param iconList iconlist 738 * @return indexedcontainer 739 */ 740 public static IndexedContainer getPrincipalContainer( 741 CmsObject cms, 742 List<? extends I_CmsPrincipal> list, 743 String captionID, 744 String descID, 745 String iconID, 746 String ouID, 747 String icon, 748 List<FontIcon> iconList) { 749 750 IndexedContainer res = new IndexedContainer(); 751 752 res.addContainerProperty(captionID, String.class, ""); 753 res.addContainerProperty(ouID, String.class, ""); 754 res.addContainerProperty(iconID, FontIcon.class, new CmsCssIcon(icon)); 755 if (descID != null) { 756 res.addContainerProperty(descID, String.class, ""); 757 } 758 759 for (I_CmsPrincipal group : list) { 760 761 Item item = res.addItem(group); 762 item.getItemProperty(captionID).setValue(group.getSimpleName()); 763 item.getItemProperty(ouID).setValue(group.getOuFqn()); 764 if (descID != null) { 765 item.getItemProperty(descID).setValue(group.getDescription(A_CmsUI.get().getLocale())); 766 } 767 } 768 769 for (int i = 0; i < iconList.size(); i++) { 770 res.getItem(res.getIdByIndex(i)).getItemProperty(iconID).setValue(iconList.get(i)); 771 } 772 773 return res; 774 } 775 776 /** 777 * Returns the selectable projects container.<p> 778 * 779 * @param cms the CMS context 780 * @param captionPropertyName the name of the property used to store captions 781 * 782 * @return the projects container 783 */ 784 public static IndexedContainer getProjectsContainer(CmsObject cms, String captionPropertyName) { 785 786 IndexedContainer result = new IndexedContainer(); 787 result.addContainerProperty(captionPropertyName, String.class, null); 788 Locale locale = A_CmsUI.get().getLocale(); 789 List<CmsProject> projects = getAvailableProjects(cms); 790 boolean isSingleOu = isSingleOu(projects); 791 for (CmsProject project : projects) { 792 String projectName = project.getSimpleName(); 793 if (!isSingleOu && !project.isOnlineProject()) { 794 try { 795 projectName = projectName 796 + " - " 797 + OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, project.getOuFqn()).getDisplayName( 798 locale); 799 } catch (CmsException e) { 800 LOG.debug("Error reading project OU.", e); 801 projectName = projectName + " - " + project.getOuFqn(); 802 } 803 } 804 Item projectItem = result.addItem(project.getUuid()); 805 projectItem.getItemProperty(captionPropertyName).setValue(projectName); 806 } 807 return result; 808 } 809 810 /** 811 * Gets the current Vaadin request, cast to a HttpServletRequest.<p> 812 * 813 * @return the current request 814 */ 815 public static HttpServletRequest getRequest() { 816 817 return (HttpServletRequest)VaadinService.getCurrentRequest(); 818 } 819 820 /** 821 * Gets list of resource types.<p> 822 * 823 * @return List 824 */ 825 public static List<I_CmsResourceType> getResourceTypes() { 826 827 List<I_CmsResourceType> res = new ArrayList<I_CmsResourceType>(); 828 for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) { 829 CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting( 830 type.getTypeName()); 831 if (typeSetting != null) { 832 res.add(type); 833 } 834 } 835 return res; 836 } 837 838 /** 839 * Returns the available resource types container.<p> 840 * 841 * @return the resource types container 842 */ 843 public static IndexedContainer getResourceTypesContainer() { 844 845 IndexedContainer types = new IndexedContainer(); 846 types.addContainerProperty(PropertyId.caption, String.class, null); 847 types.addContainerProperty(PropertyId.icon, Resource.class, null); 848 types.addContainerProperty(PropertyId.isFolder, Boolean.class, null); 849 types.addContainerProperty(PropertyId.isXmlContent, Boolean.class, null); 850 for (I_CmsResourceType type : getResourceTypes()) { 851 CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting( 852 type.getTypeName()); 853 Item typeItem = types.addItem(type); 854 typeItem.getItemProperty(PropertyId.caption).setValue(CmsVaadinUtils.getMessageText(typeSetting.getKey())); 855 typeItem.getItemProperty(PropertyId.icon).setValue(CmsResourceUtil.getSmallIconResource(typeSetting, null)); 856 typeItem.getItemProperty(PropertyId.isXmlContent).setValue( 857 Boolean.valueOf(type instanceof CmsResourceTypeXmlContent)); 858 typeItem.getItemProperty(PropertyId.isFolder).setValue( 859 Boolean.valueOf(type instanceof A_CmsResourceTypeFolderBase)); 860 } 861 862 return types; 863 } 864 865 /** 866 * Returns the roles available for a given user.<p> 867 * 868 * @param cms CmsObject 869 * @param user to get available roles for 870 * @param captionPropertyName name of caption property 871 * @return indexed container 872 */ 873 public static IndexedContainer getRoleContainerForUser(CmsObject cms, CmsUser user, String captionPropertyName) { 874 875 IndexedContainer result = new IndexedContainer(); 876 result.addContainerProperty(captionPropertyName, String.class, ""); 877 try { 878 List<CmsRole> roles = OpenCms.getRoleManager().getRoles(cms, user.getOuFqn(), false); 879 CmsRole.applySystemRoleOrder(roles); 880 for (CmsRole role : roles) { 881 Item item = result.addItem(role); 882 item.getItemProperty(captionPropertyName).setValue(role.getDisplayName(cms, A_CmsUI.get().getLocale())); 883 } 884 } catch (CmsException e) { 885 LOG.error("Unabel to read roles for user", e); 886 } 887 return result; 888 } 889 890 /** 891 * Gets the window which contains a given component.<p> 892 * 893 * @param component the component 894 * @return the window containing the component, or null if no component is found 895 */ 896 public static Window getWindow(Component component) { 897 898 if (component == null) { 899 return null; 900 } else if (component instanceof Window) { 901 return (Window)component; 902 } else { 903 return getWindow(component.getParent()); 904 } 905 906 } 907 908 /** 909 * Gets the link to the (new) workplace.<p> 910 * 911 * @return the link to the workplace 912 */ 913 public static String getWorkplaceLink() { 914 915 return CmsStringUtil.joinPaths("/", OpenCms.getSystemInfo().getContextPath(), "workplace"); 916 } 917 918 /** 919 * Gets external resource from workplace resource folder.<p> 920 * 921 * @param subPath path relative to workplace resource folder 922 * 923 * @return the external resource 924 */ 925 public static ExternalResource getWorkplaceResource(String subPath) { 926 927 return new ExternalResource(CmsWorkplace.getResourceUri(subPath)); 928 929 } 930 931 /** 932 * Gets the workplace messages for the current locale.<p> 933 * 934 * @return the workplace messages 935 */ 936 public static CmsMessages getWpMessagesForCurrentLocale() { 937 938 return OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()); 939 } 940 941 /** 942 * Checks if path is itemid in container.<p> 943 * 944 * @param cnt to be checked 945 * @param path as itemid 946 * @return true id path is itemid in container 947 */ 948 public static boolean hasPathAsItemId(Container cnt, String path) { 949 950 return cnt.containsId(path) || cnt.containsId(CmsFileUtil.toggleTrailingSeparator(path)); 951 } 952 953 /** 954 * Checks if a button is pressed.<p> 955 * 956 * @param button the button 957 * 958 * @return true if the button is pressed 959 */ 960 public static boolean isButtonPressed(Button button) { 961 962 if (button == null) { 963 return false; 964 } 965 List<String> styles = Arrays.asList(button.getStyleName().split(" ")); 966 967 return styles.contains(OpenCmsTheme.BUTTON_PRESSED); 968 } 969 970 /** 971 * Uses the currently set locale to resolve localization macros in the input string using workplace message bundles.<p> 972 * 973 * @param baseString the string to localize 974 * 975 * @return the localized string 976 */ 977 public static String localizeString(String baseString) { 978 979 if (baseString == null) { 980 return null; 981 } 982 CmsWorkplaceMessages wpMessages = OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()); 983 CmsMacroResolver resolver = new CmsMacroResolver(); 984 resolver.setMessages(wpMessages); 985 String result = resolver.resolveMacros(baseString); 986 return result; 987 } 988 989 /** 990 * Message accessior function.<p> 991 * 992 * @return the message for Cancel buttons 993 */ 994 public static String messageCancel() { 995 996 return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0); 997 } 998 999 /** 1000 * Message accessior function.<p> 1001 * 1002 * @return the message for Cancel buttons 1003 */ 1004 public static String messageClose() { 1005 1006 return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CLOSE_0); 1007 } 1008 1009 /** 1010 * Message accessor function.<p> 1011 * 1012 * @return the message for OK buttons 1013 */ 1014 public static String messageOk() { 1015 1016 return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0); 1017 } 1018 1019 /** 1020 * Generates the options items for the combo box using the map entry keys as values and the values as labels.<p> 1021 * 1022 * @param box the combo box to prepare 1023 * @param options the box options 1024 */ 1025 public static void prepareComboBox(ComboBox box, Map<?, String> options) { 1026 1027 IndexedContainer container = new IndexedContainer(); 1028 container.addContainerProperty(PROPERTY_VALUE, Object.class, null); 1029 container.addContainerProperty(PROPERTY_LABEL, String.class, ""); 1030 for (Entry<?, String> entry : options.entrySet()) { 1031 Item item = container.addItem(entry.getKey()); 1032 item.getItemProperty(PROPERTY_VALUE).setValue(entry.getKey()); 1033 item.getItemProperty(PROPERTY_LABEL).setValue(entry.getValue()); 1034 } 1035 box.setContainerDataSource(container); 1036 box.setItemCaptionPropertyId(PROPERTY_LABEL); 1037 } 1038 1039 /** 1040 * Reads the declarative design for a component and localizes it using a messages object.<p> 1041 * 1042 * The design will need to be located in the same directory as the component's class and have '.html' as a file extension. 1043 * 1044 * @param component the component for which to read the design 1045 * @param messages the message bundle to use for localization 1046 * @param macros the macros to use on the HTML template 1047 */ 1048 @SuppressWarnings("resource") 1049 public static void readAndLocalizeDesign(Component component, CmsMessages messages, Map<String, String> macros) { 1050 1051 Class<?> componentClass = component.getClass(); 1052 List<Class<?>> classes = Lists.newArrayList(); 1053 classes.add(componentClass); 1054 classes.addAll(ClassUtils.getAllSuperclasses(componentClass)); 1055 InputStream designStream = null; 1056 for (Class<?> cls : classes) { 1057 if (cls.getName().startsWith("com.vaadin")) { 1058 break; 1059 } 1060 String filename = cls.getSimpleName() + ".html"; 1061 designStream = cls.getResourceAsStream(filename); 1062 if (designStream != null) { 1063 break; 1064 } 1065 1066 } 1067 if (designStream == null) { 1068 throw new IllegalArgumentException("Design not found for : " + component.getClass()); 1069 } 1070 readAndLocalizeDesign(component, designStream, messages, macros); 1071 } 1072 1073 /** 1074 * Reads a layout from a resource, applies basic i18n macro substitution on the contained text, and returns a stream of the transformed 1075 * data.<p> 1076 * 1077 * @param layoutClass the class relative to which the layout resource will be looked up 1078 * @param relativeName the file name of the layout file 1079 * 1080 * @return an input stream which produces the transformed layout resource html 1081 */ 1082 public static InputStream readCustomLayout(Class<? extends Component> layoutClass, String relativeName) { 1083 1084 CmsMacroResolver resolver = new CmsMacroResolver() { 1085 1086 @Override 1087 public String getMacroValue(String macro) { 1088 1089 return CmsEncoder.escapeXml(super.getMacroValue(macro)); 1090 } 1091 }; 1092 resolver.setMessages(CmsVaadinUtils.getWpMessagesForCurrentLocale()); 1093 InputStream layoutStream = CmsVaadinUtils.filterUtf8ResourceStream( 1094 layoutClass.getResourceAsStream(relativeName), 1095 resolver.toFunction()); 1096 return layoutStream; 1097 } 1098 1099 /** 1100 * Replaces component with new component.<p> 1101 * 1102 * @param component to be replaced 1103 * @param replacement new component 1104 */ 1105 public static void replaceComponent(Component component, Component replacement) { 1106 1107 if (!component.isAttached()) { 1108 throw new IllegalArgumentException("Component must be attached"); 1109 } 1110 HasComponents parent = component.getParent(); 1111 if (parent instanceof ComponentContainer) { 1112 ((ComponentContainer)parent).replaceComponent(component, replacement); 1113 } else if (parent instanceof SingleComponentContainer) { 1114 ((SingleComponentContainer)parent).setContent(replacement); 1115 } else { 1116 throw new IllegalArgumentException("Illegal class for parent: " + parent.getClass()); 1117 } 1118 } 1119 1120 /** 1121 * Configures a text field to look like a filter box for a table. 1122 * 1123 * @param searchBox the text field to configure 1124 */ 1125 public static void setFilterBoxStyle(TextField searchBox) { 1126 1127 searchBox.setIcon(FontOpenCms.FILTER); 1128 1129 searchBox.setPlaceholder( 1130 org.opencms.ui.apps.Messages.get().getBundle(UI.getCurrent().getLocale()).key( 1131 org.opencms.ui.apps.Messages.GUI_EXPLORER_FILTER_0)); 1132 searchBox.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); 1133 } 1134 1135 /** 1136 * Sets the value of a text field which may be set to read-only mode.<p> 1137 * 1138 * When setting a Vaadin field to read-only, you also can't set its value programmatically anymore. 1139 * So we need to temporarily disable read-only mode, set the value, and then switch back to read-only mode. 1140 * 1141 * @param field the field 1142 * @param value the value to set 1143 */ 1144 public static <T> void setReadonlyValue(AbstractField<T> field, T value) { 1145 1146 boolean readonly = field.isReadOnly(); 1147 try { 1148 field.setReadOnly(false); 1149 field.setValue(value); 1150 } finally { 1151 field.setReadOnly(readonly); 1152 } 1153 } 1154 1155 /** 1156 * Shows an alert box to the user with the given information, which will perform the given action after the user clicks on OK.<p> 1157 * 1158 * @param title the title 1159 * @param message the message 1160 * 1161 * @param callback the callback to execute after clicking OK 1162 */ 1163 public static void showAlert(String title, String message, final Runnable callback) { 1164 1165 final Window window = new Window(); 1166 window.setModal(true); 1167 Panel panel = new Panel(); 1168 panel.setCaption(title); 1169 panel.setWidth("500px"); 1170 VerticalLayout layout = new VerticalLayout(); 1171 layout.setMargin(true); 1172 panel.setContent(layout); 1173 layout.addComponent(new Label(message)); 1174 Button okButton = new Button(); 1175 okButton.addClickListener(new ClickListener() { 1176 1177 /** The serial version id. */ 1178 private static final long serialVersionUID = 1L; 1179 1180 public void buttonClick(ClickEvent event) { 1181 1182 window.close(); 1183 if (callback != null) { 1184 callback.run(); 1185 } 1186 } 1187 }); 1188 layout.addComponent(okButton); 1189 layout.setComponentAlignment(okButton, Alignment.BOTTOM_RIGHT); 1190 okButton.setCaption( 1191 org.opencms.workplace.Messages.get().getBundle(A_CmsUI.get().getLocale()).key( 1192 org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0)); 1193 window.setContent(panel); 1194 window.setClosable(false); 1195 window.setResizable(false); 1196 A_CmsUI.get().addWindow(window); 1197 1198 } 1199 1200 /** 1201 * Creates a new option group builder.<p> 1202 * 1203 * @return a new option group builder 1204 */ 1205 public static OptionGroupBuilder startOptionGroup() { 1206 1207 return new OptionGroupBuilder(); 1208 } 1209 1210 /** 1211 * Sets style of a toggle button depending on its current state.<p> 1212 * 1213 * @param button the button to update 1214 */ 1215 public static void toggleButton(Button button) { 1216 1217 if (isButtonPressed(button)) { 1218 button.removeStyleName(OpenCmsTheme.BUTTON_PRESSED); 1219 } else { 1220 button.addStyleName(OpenCmsTheme.BUTTON_PRESSED); 1221 } 1222 } 1223 1224 /** 1225 * Updates the component error of a component, but only if it differs from the currently set 1226 * error.<p> 1227 * 1228 * @param component the component 1229 * @param error the error 1230 * 1231 * @return true if the error was changed 1232 */ 1233 public static boolean updateComponentError(AbstractComponent component, ErrorMessage error) { 1234 1235 if (component.getComponentError() != error) { 1236 component.setComponentError(error); 1237 return true; 1238 } 1239 return false; 1240 } 1241 1242 /** 1243 * Visits all descendants of a given component (including the component itself) and applies a predicate 1244 * to each.<p> 1245 * 1246 * If the predicate returns false for a component, no further descendants will be processed.<p> 1247 * 1248 * @param component the component 1249 * @param handler the predicate 1250 */ 1251 public static void visitDescendants(Component component, Predicate<Component> handler) { 1252 1253 List<Component> stack = Lists.newArrayList(); 1254 stack.add(component); 1255 while (!stack.isEmpty()) { 1256 Component currentComponent = stack.get(stack.size() - 1); 1257 stack.remove(stack.size() - 1); 1258 if (!handler.apply(currentComponent)) { 1259 return; 1260 } 1261 if (currentComponent instanceof HasComponents) { 1262 List<Component> children = Lists.newArrayList((HasComponents)currentComponent); 1263 Collections.reverse(children); 1264 stack.addAll(children); 1265 } 1266 } 1267 } 1268 1269 /** 1270 * Waggle the component.<p> 1271 * 1272 * @param component to be waggled 1273 */ 1274 public static void waggleMeOnce(Component component) { 1275 1276 //TODO Until now, the component gets a waggler class which can not be removed again here.. 1277 component.addStyleName("waggler"); 1278 //Add JavaScript code, which adds the waggle class and removes it after a short time. 1279 JavaScript.getCurrent().execute( 1280 "waggler=document.querySelectorAll(\".waggler\")[0];" 1281 + "waggler.className=waggler.className + \" waggle\";" 1282 + "setTimeout(function () {\n" 1283 + "waggler.className=waggler.className.replace(/\\bwaggle\\b/g, \"\");" 1284 + " }, 1500);"); 1285 } 1286 1287 /** 1288 * Reads the given design and resolves the given macros and localizations.<p> 1289 1290 * @param component the component whose design to read 1291 * @param designStream stream to read the design from 1292 * @param messages the message bundle to use for localization in the design (may be null) 1293 * @param macros other macros to substitute in the macro design (may be null) 1294 */ 1295 protected static void readAndLocalizeDesign( 1296 Component component, 1297 InputStream designStream, 1298 CmsMessages messages, 1299 Map<String, String> macros) { 1300 1301 try { 1302 byte[] designBytes = CmsFileUtil.readFully(designStream, true); 1303 final String encoding = "UTF-8"; 1304 String design = new String(designBytes, encoding); 1305 CmsMacroResolver resolver = new CmsMacroResolver() { 1306 1307 @Override 1308 public String getMacroValue(String macro) { 1309 1310 String result = super.getMacroValue(macro); 1311 // The macro may contain quotes or angle brackets, so we need to escape the values for insertion into the design file 1312 return CmsEncoder.escapeXml(result); 1313 1314 } 1315 }; 1316 1317 if (macros != null) { 1318 for (Map.Entry<String, String> entry : macros.entrySet()) { 1319 resolver.addMacro(entry.getKey(), entry.getValue()); 1320 } 1321 } 1322 if (messages != null) { 1323 resolver.setMessages(messages); 1324 } 1325 String resolvedDesign = resolver.resolveMacros(design); 1326 Design.read(new ByteArrayInputStream(resolvedDesign.getBytes(encoding)), component); 1327 } catch (IOException e) { 1328 throw new RuntimeException("Could not read design", e); 1329 } finally { 1330 try { 1331 designStream.close(); 1332 } catch (IOException e) { 1333 LOG.warn(e.getLocalizedMessage(), e); 1334 } 1335 } 1336 } 1337 1338 /** 1339 * Returns whether only a single OU is visible to the current user.<p> 1340 * 1341 * @param projects the selectable projects 1342 * 1343 * @return <code>true</code> if only a single OU is visible to the current user 1344 */ 1345 private static boolean isSingleOu(List<CmsProject> projects) { 1346 1347 String ouFqn = null; 1348 for (CmsProject project : projects) { 1349 if (project.isOnlineProject()) { 1350 // skip the online project 1351 continue; 1352 } 1353 if (ouFqn == null) { 1354 // set the first ou 1355 ouFqn = project.getOuFqn(); 1356 } else if (!ouFqn.equals(project.getOuFqn())) { 1357 // break if one different ou is found 1358 return false; 1359 } 1360 } 1361 return true; 1362 } 1363 1364}