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