001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.dialogs; 029 030import org.opencms.db.CmsResourceState; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProperty; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.file.types.CmsResourceTypeImage; 037import org.opencms.lock.CmsLockActionRecord; 038import org.opencms.lock.CmsLockUtil; 039import org.opencms.main.CmsException; 040import org.opencms.main.CmsLog; 041import org.opencms.main.OpenCms; 042import org.opencms.relations.CmsRelation; 043import org.opencms.relations.CmsRelationFilter; 044import org.opencms.ui.A_CmsUI; 045import org.opencms.ui.CmsVaadinUtils; 046import org.opencms.ui.FontOpenCms; 047import org.opencms.ui.I_CmsDialogContext; 048import org.opencms.ui.apps.CmsAppWorkplaceUi; 049import org.opencms.ui.components.CmsBasicDialog; 050import org.opencms.ui.components.CmsConfirmationDialog; 051import org.opencms.ui.components.CmsGwtContextMenuButton; 052import org.opencms.ui.components.CmsOkCancelActionHandler; 053import org.opencms.ui.components.CmsResourceInfo; 054import org.opencms.ui.components.OpenCmsTheme; 055import org.opencms.ui.shared.rpc.I_CmsGwtContextMenuServerRpc; 056import org.opencms.ui.util.CmsGalleryFilePreview; 057import org.opencms.util.CmsDateUtil; 058import org.opencms.util.CmsStringUtil; 059import org.opencms.util.CmsUUID; 060import org.opencms.workplace.explorer.CmsResourceUtil; 061 062import java.text.DateFormat; 063import java.util.ArrayList; 064import java.util.Arrays; 065import java.util.Collections; 066import java.util.Comparator; 067import java.util.Date; 068import java.util.HashSet; 069import java.util.List; 070import java.util.Set; 071import java.util.concurrent.TimeUnit; 072import java.util.stream.Collectors; 073 074import org.apache.commons.logging.Log; 075 076import com.vaadin.data.Binder; 077import com.vaadin.data.ValidationException; 078import com.vaadin.data.provider.ListDataProvider; 079import com.vaadin.data.provider.Query; 080import com.vaadin.server.Page; 081import com.vaadin.server.SerializableComparator; 082import com.vaadin.server.SerializablePredicate; 083import com.vaadin.server.VaadinService; 084import com.vaadin.server.WrappedSession; 085import com.vaadin.shared.Position; 086import com.vaadin.shared.ui.ContentMode; 087import com.vaadin.shared.ui.MarginInfo; 088import com.vaadin.ui.AbsoluteLayout; 089import com.vaadin.ui.Alignment; 090import com.vaadin.ui.Button; 091import com.vaadin.ui.CheckBox; 092import com.vaadin.ui.Component; 093import com.vaadin.ui.CssLayout; 094import com.vaadin.ui.FormLayout; 095import com.vaadin.ui.GridLayout; 096import com.vaadin.ui.HorizontalLayout; 097import com.vaadin.ui.ItemCaptionGenerator; 098import com.vaadin.ui.Label; 099import com.vaadin.ui.NativeSelect; 100import com.vaadin.ui.Notification; 101import com.vaadin.ui.Panel; 102import com.vaadin.ui.TextField; 103import com.vaadin.ui.VerticalLayout; 104import com.vaadin.ui.Window; 105import com.vaadin.ui.themes.ValoTheme; 106 107/** 108 * Class representing a dialog for optimizing galleries.<p> 109 */ 110public class CmsGalleryOptimizeDialog extends CmsBasicDialog { 111 112 /** 113 * The context used for child dialogs.<p> 114 */ 115 public class ContextMenu implements I_CmsGwtContextMenuServerRpc { 116 117 /** Default serial version uid. */ 118 private static final long serialVersionUID = 1L; 119 120 /** 121 * The data item handled by this dialog context. 122 */ 123 private final DataItem m_dataItem; 124 125 /** 126 * Creates a new instance. 127 128 * @param dataItem the data item 129 */ 130 public ContextMenu(DataItem dataItem) { 131 132 m_dataItem = dataItem; 133 } 134 135 /** 136 * @see org.opencms.ui.shared.rpc.I_CmsGwtContextMenuServerRpc#refresh(java.lang.String) 137 */ 138 public void refresh(String uuid) { 139 140 if (uuid != null) { 141 try { 142 TimeUnit.SECONDS.sleep(1); 143 CmsResource resource = getCms().readResource(new CmsUUID(uuid), CmsResourceFilter.ONLY_VISIBLE); 144 boolean deleted = resource.getState() == CmsResourceState.STATE_DELETED; 145 if (deleted) { 146 CmsGalleryOptimizeDialog.this.handleDataListDelete(Arrays.asList(m_dataItem)); 147 } else { 148 CmsGalleryOptimizeDialog.this.handleDataListUpdate(Arrays.asList(m_dataItem)); 149 } 150 String message = CmsVaadinUtils.getMessageText( 151 Messages.GUI_GALLERY_OPTIMIZE_LABEL_SUCCESSFULLY_SAVED_0); 152 Notification notification = new Notification(message, "", Notification.Type.HUMANIZED_MESSAGE); 153 notification.setPosition(Position.TOP_CENTER); 154 notification.show(Page.getCurrent()); 155 CmsAppWorkplaceUi.get().enableGlobalShortcuts(); 156 } catch (CmsException | InterruptedException e) { 157 LOG.error(e.getLocalizedMessage(), e); 158 Notification notification = new Notification( 159 "", 160 e.getLocalizedMessage(), 161 Notification.Type.ERROR_MESSAGE); 162 notification.setHtmlContentAllowed(true); 163 notification.setPosition(Position.TOP_CENTER); 164 notification.show(Page.getCurrent()); 165 } 166 } 167 } 168 } 169 170 /** 171 * Class representing an editable gallery item.<p> 172 */ 173 private class DataItem { 174 175 /** The data binder of this editable gallery item. */ 176 private Binder<DataItem> m_binder = new Binder<DataItem>(); 177 178 /** The file composite of this editable gallery item. */ 179 private FileComposite m_compositeFile; 180 181 /** The file delete composite of this editable gallery item. */ 182 private FileDeleteComposite m_compositeFileDelete; 183 184 /** The form composite of this editable gallery item. */ 185 private FormComposite m_compositeForm; 186 187 /** The copyright information of this editable gallery item. */ 188 private String m_copyright; 189 190 /** Date when this editable gallery item was last modified. */ 191 private Long m_dateLastModified; 192 193 /** Whether this editable gallery item shall be deleted. */ 194 private Boolean m_deleteFlag = Boolean.valueOf(false); 195 196 /** The description of this editable gallery item. */ 197 private String m_description; 198 199 /** Whether this editable gallery item is used. */ 200 private Boolean m_isUsed; 201 202 /** The file name of this editable gallery item. */ 203 private String m_name; 204 205 /** The full path of this editable gallery item. */ 206 private String m_path; 207 208 /** The CMS resource of this editable gallery item. */ 209 private CmsResource m_resource; 210 211 /** The CMS resource utility of this editable gallery item. */ 212 private CmsResourceUtil m_resourceUtil; 213 214 /** The title of this editable gallery item. */ 215 private String m_title; 216 217 /** 218 * Creates a new editable gallery item for a given CMS resource.<p> 219 * 220 * @param resource the CMS resource 221 */ 222 public DataItem(CmsResource resource) { 223 224 m_resource = resource; 225 initData(); 226 initComponent(); 227 } 228 229 /** 230 * Returns the binder of this editable gallery item.<p> 231 * 232 * @return the binder 233 */ 234 public Binder<DataItem> getBinder() { 235 236 return m_binder; 237 } 238 239 /** 240 * Returns the file composite of this editable gallery item.<p> 241 * 242 * @return the file composite 243 */ 244 public FileComposite getCompositeFile() { 245 246 return m_compositeFile; 247 } 248 249 /** 250 * Returns the file delete composite of this editable gallery item.<p> 251 * 252 * @return the file delete composite 253 */ 254 public FileDeleteComposite getCompositeFileDelete() { 255 256 return m_compositeFileDelete; 257 } 258 259 /** 260 * Returns the form composite of this editable gallery item.<p> 261 * 262 * @return the form composite 263 */ 264 public FormComposite getCompositeForm() { 265 266 return m_compositeForm; 267 } 268 269 /** 270 * Returns the copyright information of this editable gallery item.<p> 271 * 272 * @return the copyright information 273 */ 274 public String getCopyright() { 275 276 return m_copyright; 277 } 278 279 /** 280 * Returns the date when this editable gallery item was last modified.<p> 281 * 282 * @return the date 283 */ 284 public Long getDateLastModified() { 285 286 return m_dateLastModified; 287 } 288 289 /** 290 * Returns whether this editable gallery item shall be deleted.<p> 291 * 292 * @return whether delete or not 293 */ 294 public Boolean getDeleteFlag() { 295 296 return m_deleteFlag; 297 } 298 299 /** 300 * Returns the description of this editable gallery item.<p> 301 * 302 * @return the description 303 */ 304 public String getDescription() { 305 306 return m_description; 307 } 308 309 /** 310 * Returns the filter text.<p> 311 * 312 * @return the filter text 313 */ 314 public String getFilterText() { 315 316 return (m_name + " " + m_title + " " + m_copyright + " " + m_description).toLowerCase(); 317 } 318 319 /** 320 * Returns whether this editable gallery item is used.<p> 321 * 322 * @return whether used or not 323 */ 324 public Boolean getIsUsed() { 325 326 return m_isUsed; 327 } 328 329 /** 330 * Returns the name of this editable gallery item.<p> 331 * 332 * @return the name 333 */ 334 public String getName() { 335 336 return m_name; 337 } 338 339 /** 340 * Returns whether this data item has no copyright information.<p> 341 * 342 * @return whether this data item has no copyright information 343 */ 344 public Boolean getNoCopyright() { 345 346 return Boolean.valueOf(CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_copyright)); 347 } 348 349 /** 350 * Returns whether this data item has no description.<p> 351 * 352 * @return whether this data item has no description 353 */ 354 public Boolean getNoDescription() { 355 356 return Boolean.valueOf(CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_description)); 357 } 358 359 /** 360 * Returns the full path of this editable gallery item.<p> 361 * 362 * @return the full path 363 */ 364 public String getPath() { 365 366 return m_path; 367 } 368 369 /** 370 * Converts the form data of this editable gallery item into a list of CMS properties.<p> 371 * 372 * @return the CMS property list 373 */ 374 public List<CmsProperty> getPropertyList() { 375 376 List<CmsProperty> propertyList = new ArrayList<CmsProperty>(); 377 propertyList.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, m_title, null)); 378 propertyList.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_COPYRIGHT, m_copyright, null)); 379 propertyList.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_DESCRIPTION, m_description, null)); 380 return propertyList; 381 } 382 383 /** 384 * Returns the CMS resource this editable gallery item was created from.<p> 385 * 386 * @return the CMS resource 387 */ 388 public CmsResource getResource() { 389 390 return m_resource; 391 } 392 393 /** 394 * Returns the CMS resource utility class for this editable gallery item.<p> 395 * 396 * @return the CMS resource utility class 397 */ 398 public CmsResourceUtil getResourceUtil() { 399 400 return m_resourceUtil; 401 } 402 403 /** 404 * Returns the title of this editable gallery item.<p> 405 * 406 * @return the title 407 */ 408 public String getTitle() { 409 410 return m_title; 411 } 412 413 /** 414 * Returns whether this editable gallery item has value changes compared 415 * to the property values actually persisted.<p> 416 * 417 * @return whether changes or not 418 */ 419 public boolean hasChanges() { 420 421 boolean hasChanges = false; 422 try { 423 if (!hasChanges) { 424 hasChanges = !m_title.equals(readPropertyTitle()); 425 } 426 if (!hasChanges) { 427 hasChanges = !m_copyright.equals(readPropertyCopyright()); 428 } 429 if (!hasChanges) { 430 hasChanges = !m_description.equals(readPropertyDescription()); 431 } 432 } catch (CmsException e) { 433 hasChanges = true; 434 LOG.warn(e.getLocalizedMessage(), e); 435 } 436 return hasChanges; 437 } 438 439 /** 440 * Returns whether this editable gallery item is renamed compared to 441 * the resource actually persisted.<p> 442 * 443 * @return whether renamed or not 444 */ 445 public boolean isRenamed() { 446 447 boolean isRenamed = false; 448 try { 449 isRenamed = !m_name.equals(readName()); 450 } catch (CmsException e) { 451 LOG.warn(e.getLocalizedMessage(), e); 452 } 453 return isRenamed; 454 } 455 456 /** 457 * Returns whether this editable gallery item is an image.<p> 458 * 459 * @return whether an image or not 460 */ 461 public boolean isTypeImage() { 462 463 return OpenCms.getResourceManager().getResourceType(m_resource) instanceof CmsResourceTypeImage; 464 } 465 466 /** 467 * Sets the copyright information of this editable gallery item.<p> 468 * 469 * @param copyright the copyright information 470 */ 471 public void setCopyright(String copyright) { 472 473 m_copyright = copyright; 474 } 475 476 /** 477 * Sets the flag that states whether this editable gallery item shall be deleted.<p> 478 * 479 * @param deleteFlag the flag 480 */ 481 public void setDeleteFlag(Boolean deleteFlag) { 482 483 m_deleteFlag = deleteFlag; 484 } 485 486 /** 487 * Sets the description of this editable gallery item.<p> 488 * 489 * @param description the description 490 */ 491 public void setDescription(String description) { 492 493 m_description = description; 494 } 495 496 /** 497 * Sets the name of this editable gallery item.<p> 498 * 499 * @param name the name 500 */ 501 public void setName(String name) { 502 503 m_name = name; 504 } 505 506 /** 507 * Sets the CMS resource of this editable gallery item and re-initializes all data and components.<p> 508 * 509 * @param resource the CMS resource 510 */ 511 public void setResource(CmsResource resource) { 512 513 m_resource = resource; 514 initData(); 515 initComponent(); 516 } 517 518 /** 519 * Sets the title of this editable gallery item.<p> 520 * 521 * @param title the title 522 */ 523 public void setTitle(String title) { 524 525 m_title = title; 526 } 527 528 /** 529 * Initializes all UI components of this editable gallery item and initializes all data fields.<p> 530 */ 531 private void initComponent() { 532 533 m_compositeFile = new FileComposite(this); 534 m_compositeFileDelete = new FileDeleteComposite(this); 535 m_compositeForm = new FormComposite(this); 536 m_binder.readBean(this); 537 } 538 539 /** 540 * Initializes all data of this editable gallery item.<p> 541 */ 542 private void initData() { 543 544 m_resourceUtil = new CmsResourceUtil(A_CmsUI.getCmsObject(), m_resource); 545 try { 546 List<CmsRelation> relations = getCms().getRelationsForResource(m_resource, CmsRelationFilter.SOURCES); 547 m_name = m_resource.getName(); 548 m_path = m_resource.getRootPath(); 549 m_title = readPropertyTitle(); 550 m_copyright = readPropertyCopyright(); 551 m_description = readPropertyDescription(); 552 m_dateLastModified = Long.valueOf(m_resource.getDateLastModified()); 553 m_isUsed = Boolean.valueOf(!((relations == null) || relations.isEmpty())); 554 } catch (CmsException e) { 555 LOG.error(e.getLocalizedMessage(), e); 556 } 557 } 558 559 /** 560 * Reads the persisted resource name.<p> 561 * 562 * @return the resource name 563 * @throws CmsException thrown if reading the resource fails 564 */ 565 private String readName() throws CmsException { 566 567 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireFile(); 568 return getCms().readResource(m_resource.getStructureId(), resourceFilter).getName(); 569 } 570 571 /** 572 * Reads the persisted copyright property value.<p> 573 * 574 * @return the copyright property value 575 * @throws CmsException thrown if the property read fails 576 */ 577 private String readPropertyCopyright() throws CmsException { 578 579 String value = getCms().readPropertyObject( 580 m_resource, 581 CmsPropertyDefinition.PROPERTY_COPYRIGHT, 582 false).getValue(); 583 return value == null ? "" : value; 584 } 585 586 /** 587 * Reads the persisted description property value.<p> 588 * 589 * @return the description property value 590 * @throws CmsException thrown if the property read fails 591 */ 592 private String readPropertyDescription() throws CmsException { 593 594 String value = getCms().readPropertyObject( 595 m_resource, 596 CmsPropertyDefinition.PROPERTY_DESCRIPTION, 597 false).getValue(); 598 return value == null ? "" : value; 599 } 600 601 /** 602 * Reads the persisted title property value.<p> 603 * 604 * @return the title property value 605 * @throws CmsException thrown if the property read fails 606 */ 607 private String readPropertyTitle() throws CmsException { 608 609 String value = getCms().readPropertyObject( 610 m_resource, 611 CmsPropertyDefinition.PROPERTY_TITLE, 612 false).getValue(); 613 return value == null ? "" : value; 614 } 615 } 616 617 /** 618 * Class representing the data list header view with components for 619 * sorting, paging, and filtering the gallery item list.<p> 620 */ 621 private class DataListHeaderComposite extends AbsoluteLayout { 622 623 /** The default serial version UID. */ 624 private static final long serialVersionUID = 1L; 625 626 /** The page info label. */ 627 private Label m_labelPageInfo; 628 629 /** The select box for page selection. */ 630 private NativeSelect<Integer> m_selectPage; 631 632 /** The select box for sort order selection. */ 633 private NativeSelect<String> m_selectSortOrder; 634 635 /** The text field for filtering. */ 636 private TextField m_textFieldFilter; 637 638 /** 639 * Creates a new data list header composite.<p> 640 */ 641 public DataListHeaderComposite() { 642 643 setHeight("34px"); 644 setWidthFull(); 645 m_selectSortOrder = createSelectSortOrder(); 646 m_textFieldFilter = createTextFieldFilter(); 647 648 addComponent(m_selectSortOrder, "left: 0px; top: 2px;"); 649 addComponent(m_textFieldFilter, "right: 0px; top: 2px;"); 650 addStyleName("o-optimize-gallery-header"); 651 refresh(); 652 } 653 654 /** 655 * Refreshes this component.<p> 656 */ 657 public void refresh() { 658 659 NativeSelect<Integer> selectPage = createSelectPage(); 660 Label pageInfo = createLabelPageInfo(); 661 if (m_selectPage == null) { 662 m_selectPage = selectPage; 663 addComponent(m_selectPage, "left: 436px; top: 2px;"); 664 } else { 665 replaceComponent(m_selectPage, selectPage); 666 m_selectPage = selectPage; 667 } 668 if (m_labelPageInfo == null) { 669 m_labelPageInfo = pageInfo; 670 addComponent(m_labelPageInfo, "right: 228px; top: 6px;"); 671 } else { 672 replaceComponent(m_labelPageInfo, pageInfo); 673 m_labelPageInfo = pageInfo; 674 } 675 } 676 677 /** 678 * Programmatically selects a page according to a given index.<p> 679 * 680 * @param index the page index 681 */ 682 public void selectPage(int index) { 683 684 m_selectPage.setValue(null); 685 handlePageChange(index, false); 686 } 687 688 /** 689 * Creates a page info label.<p> 690 * 691 * @return the page info label 692 */ 693 private Label createLabelPageInfo() { 694 695 String text = ""; 696 if (m_pageHandler.hasPages()) { 697 text = CmsVaadinUtils.getMessageText( 698 Messages.GUI_GALLERY_OPTIMIZE_LABEL_PAGE_INFO_3, 699 String.valueOf(m_pageHandler.getNumFirstItem()), 700 String.valueOf(m_pageHandler.getNumLastItem()), 701 String.valueOf(m_pageHandler.getSizeItem())); 702 } else if (m_pageHandler.getSizeItem() == 1) { 703 text = CmsVaadinUtils.getMessageText( 704 Messages.GUI_GALLERY_OPTIMIZE_LABEL_PAGE_INFO_ONE_0, 705 String.valueOf(m_pageHandler.getSizeItem())); 706 } else { 707 text = CmsVaadinUtils.getMessageText( 708 Messages.GUI_GALLERY_OPTIMIZE_LABEL_PAGE_INFO_1, 709 String.valueOf(m_pageHandler.getSizeItem())); 710 } 711 Label label = new Label(text); 712 label.setWidthUndefined(); 713 return label; 714 } 715 716 /** 717 * Creates a select box for page select.<p> 718 * 719 * @return the page select box 720 */ 721 private NativeSelect<Integer> createSelectPage() { 722 723 NativeSelect<Integer> selectPage = new NativeSelect<Integer>(); 724 selectPage.setWidthUndefined(); 725 int numPages = m_pageHandler.getNumPages(); 726 selectPage.setItemCaptionGenerator(new ItemCaptionGenerator<Integer>() { 727 728 private static final long serialVersionUID = 1L; 729 730 public String apply(Integer item) { 731 732 return CmsVaadinUtils.getMessageText( 733 Messages.GUI_GALLERY_OPTIMIZE_SELECTED_PAGE_2, 734 String.valueOf(item.intValue() + 1), 735 String.valueOf(numPages)); 736 } 737 738 }); 739 Integer firstItem = Integer.valueOf(0); 740 List<Integer> items = new ArrayList<Integer>(); 741 for (int i = 0; i < numPages; i++) { 742 if (i > 0) { 743 items.add(Integer.valueOf(i)); 744 } 745 } 746 selectPage.setEmptySelectionCaption(selectPage.getItemCaptionGenerator().apply(firstItem)); 747 selectPage.setItems(items); 748 selectPage.addValueChangeListener(event -> { 749 if (event.isUserOriginated()) { 750 int index = event.getValue() != null ? event.getValue().intValue() : 0; 751 handlePageChange(index, true); 752 } 753 }); 754 selectPage.setVisible(m_pageHandler.hasPages()); 755 return selectPage; 756 } 757 758 /** 759 * Creates a select box for sort order select.<p> 760 * 761 * @return the sort order select box 762 */ 763 private NativeSelect<String> createSelectSortOrder() { 764 765 NativeSelect<String> selectSortOrder = new NativeSelect<String>(); 766 selectSortOrder.setWidthUndefined(); 767 selectSortOrder.setEmptySelectionCaption(m_messageSortTitleAscending); 768 selectSortOrder.setItems( 769 m_messageSortTitleDescending, 770 m_messageSortDateLastModifiedAscending, 771 m_messageSortDateLastModifiedDescending, 772 m_messageSortPathAscending, 773 m_messageSortPathDescending, 774 m_messageSortUnusedFirst, 775 m_messageSortNoCopyrightFirst, 776 m_messageSortNoDescriptionFirst); 777 selectSortOrder.addValueChangeListener(event -> { 778 if (event.isUserOriginated()) { 779 selectPage(0); 780 m_provider.refreshAll(); 781 displayDataListViewSorted(event.getValue()); 782 } 783 }); 784 selectSortOrder.setValue(getSessionSortOrder()); 785 return selectSortOrder; 786 } 787 788 /** 789 * Creates a text field for filtering.<p> 790 * 791 * @return the filter text field 792 */ 793 private TextField createTextFieldFilter() { 794 795 TextField textField = new TextField(); 796 textField.setPlaceholder(CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_EXPLORER_FILTER_0)); 797 textField.setWidth("200px"); 798 textField.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); 799 textField.setIcon(FontOpenCms.FILTER); 800 textField.addValueChangeListener(event -> { 801 handleFilterChange(event.getValue()); 802 }); 803 return textField; 804 } 805 806 /** 807 * Filter change event handler. Updates the page info label and the gallery list.<p> 808 * 809 * @param query the filter query string 810 */ 811 private void handleFilterChange(String query) { 812 813 String clean = query.trim(); 814 m_filterHandler.setQuery(clean); 815 m_pageHandler.setCurrentPage(0); 816 refresh(); 817 displayDataListView(true); 818 } 819 820 /** 821 * Page change event handler. Updates the page info label.<p> 822 * 823 * @param index the index of the page to select 824 * @param display whether to re-render the data item list 825 */ 826 private void handlePageChange(int index, boolean display) { 827 828 m_pageHandler.setCurrentPage(index); 829 Label label = createLabelPageInfo(); 830 replaceComponent(m_labelPageInfo, label); 831 m_labelPageInfo = label; 832 if (display) { 833 displayDataListView(true); 834 } 835 } 836 } 837 838 /** 839 * Class representing a file composite offering a file preview.<p> 840 */ 841 private class FileComposite extends HorizontalLayout { 842 843 /** The panel height. */ 844 private static final String PANEL_HEIGHT = "172px"; 845 846 /** The panel width. */ 847 private static final String PANEL_WIDTH = "202px"; 848 849 /** The default serial version UID. */ 850 private static final long serialVersionUID = 1L; 851 852 /** The main panel of this file composite. */ 853 private AbsoluteLayout m_panel; 854 855 /** 856 * Creates a new file composite for a given data item.<p> 857 * 858 * @param dataItem the data item 859 */ 860 public FileComposite(DataItem dataItem) { 861 862 setSizeUndefined(); 863 setMargin(true); 864 m_panel = new AbsoluteLayout(); 865 m_panel.setWidth(PANEL_WIDTH); 866 m_panel.setHeight(PANEL_HEIGHT); 867 m_panel.addStyleName("v-panel"); 868 Component link = CmsGalleryFilePreview.createClickableFile( 869 dataItem.getResource(), 870 dataItem.getResourceUtil()); 871 m_panel.addComponent(link, "left: 0px; top: 0px;"); 872 m_panel.addStyleName("o-optimize-gallery-preview-panel"); 873 addComponent(m_panel); 874 } 875 } 876 877 /** 878 * Class representing a file delete composite with a check box to mark a file as deleted.<p> 879 */ 880 private class FileDeleteComposite extends VerticalLayout { 881 882 /** The default serial version UID. */ 883 private static final long serialVersionUID = 1L; 884 885 /** The component width. */ 886 private static final String WIDTH = "206px"; 887 888 /** The data item of this file delete composite. */ 889 private DataItem m_dataItem; 890 891 /** 892 * Creates a new file delete composite for a given data item.<p> 893 * 894 * @param dataItem the data item 895 */ 896 public FileDeleteComposite(DataItem dataItem) { 897 898 m_dataItem = dataItem; 899 setMargin(new MarginInfo(true, true, true, false)); 900 setSpacing(false); 901 setWidth(WIDTH); 902 setHeightFull(); 903 Label fileSize = createDisplayFileSize(); 904 addComponent(fileSize); 905 Label dimension = createDisplayDimension(); 906 if (m_dataItem.isTypeImage()) { 907 addComponent(dimension); 908 } 909 if (!m_dataItem.getIsUsed().booleanValue()) { 910 CssLayout layout = new CssLayout(); 911 Label labelInUse = createDisplayInUseInfo(); 912 CheckBox fieldDeleteFlag = createFieldDeleteFlag(); 913 layout.addComponent(labelInUse); 914 layout.addComponent(fieldDeleteFlag); 915 addComponent(layout); 916 setExpandRatio(layout, 1.0f); 917 setComponentAlignment(layout, Alignment.BOTTOM_LEFT); 918 } else if (m_dataItem.isTypeImage()) { 919 setExpandRatio(dimension, 1.0f); 920 } else { 921 setExpandRatio(fileSize, 1.0f); 922 } 923 } 924 925 /** 926 * Creates a component displaying the dimension of this editable gallery item.<p> 927 * 928 * @return the display component 929 */ 930 private Label createDisplayDimension() { 931 932 return new Label(getFormattedDimension()); 933 } 934 935 /** 936 * Creates a component displaying the size of this editable gallery item.<p> 937 * 938 * @return the display component 939 */ 940 private Label createDisplayFileSize() { 941 942 return new Label(getFormattedFileSize()); 943 } 944 945 /** 946 * Creates a component displaying whether this editable gallery item is used.<p> 947 * 948 * @return the display component 949 */ 950 private Label createDisplayInUseInfo() { 951 952 String notInUse = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_LABEL_NOT_IN_USE_0); 953 return new Label(notInUse); 954 } 955 956 /** 957 * Creates a check box to mark an item as deleted.<p> 958 * 959 * @return the check box 960 */ 961 private CheckBox createFieldDeleteFlag() { 962 963 String caption = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_INPUT_DELETE_UNUSED_0); 964 CheckBox field = new CheckBox(caption); 965 field.setCaptionAsHtml(true); 966 field.setWidthFull(); 967 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 968 m_dataItem.getBinder().bind(field, DataItem::getDeleteFlag, DataItem::setDeleteFlag); 969 return field; 970 } 971 972 /** 973 * Returns the dimension of this file in the case the file is an image.<p> 974 * 975 * @return the formatted dimension 976 */ 977 private String getFormattedDimension() { 978 979 String imageSize = null; 980 try { 981 imageSize = getCms().readPropertyObject( 982 m_dataItem.getResource(), 983 CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, 984 false).getValue(); 985 } catch (CmsException e) { 986 LOG.warn(e.getLocalizedMessage(), e); 987 } 988 String dimension = "? x ?"; 989 if (imageSize != null) { 990 String[] tokens = imageSize.split(","); 991 if ((tokens.length == 2) && (tokens[0].length() > 2) && (tokens[1].length() > 2)) { 992 dimension = tokens[0].substring(2) + " x " + tokens[1].substring(2); 993 } 994 } 995 return dimension; 996 } 997 998 /** 999 * Returns the size of this editable gallery item in a formatted way.<p> 1000 * 1001 * @return the formatted file size 1002 */ 1003 private String getFormattedFileSize() { 1004 1005 return (m_dataItem.getResource().getLength() / 1024) + " kb"; 1006 } 1007 } 1008 1009 /** 1010 * Filter handler. Keeps track of the query string with which the user 1011 * currently filters the gallery. 1012 */ 1013 private class FilterHandler { 1014 1015 /** The filter query string. */ 1016 private String m_query; 1017 1018 /** 1019 * Creates a new filter handler.<p> 1020 */ 1021 public FilterHandler() { 1022 1023 } 1024 1025 /** 1026 * Returns the filter query string.<p> 1027 * 1028 * @return the filter query string 1029 */ 1030 public String getQuery() { 1031 1032 return m_query != null ? m_query.toLowerCase() : null; 1033 } 1034 1035 /** 1036 * Sets the filter query string.<p> 1037 * 1038 * @param query the filter query string 1039 */ 1040 public void setQuery(String query) { 1041 1042 m_query = query; 1043 } 1044 } 1045 1046 /** 1047 * Class representing a form composite to edit gallery item data.<p> 1048 */ 1049 private class FormComposite extends VerticalLayout { 1050 1051 /** The default serial version UID. */ 1052 private static final long serialVersionUID = 1L; 1053 1054 /** The data item of this form composite. */ 1055 DataItem m_dataItem; 1056 1057 /** 1058 * Creates a new form composite for a given data item.<p> 1059 * 1060 * @param dataItem the data item 1061 */ 1062 public FormComposite(DataItem dataItem) { 1063 1064 m_dataItem = dataItem; 1065 setSizeFull(); 1066 setMargin(true); 1067 setSpacing(false); 1068 addComponent(createCompositeResourceInfo()); 1069 addComponent(createDisplayResourceDate()); 1070 FormLayout formLayout = new FormLayout(); 1071 formLayout.setMargin(false); 1072 formLayout.setSpacing(false); 1073 formLayout.addStyleName(OpenCmsTheme.GALLERY_FORM); 1074 formLayout.addComponent(createFieldTitle()); 1075 formLayout.addComponent(createFieldCopyright()); 1076 formLayout.addComponent(createFieldDescription()); 1077 addComponent(formLayout); 1078 setComponentAlignment(formLayout, Alignment.BOTTOM_LEFT); 1079 setExpandRatio(formLayout, 1.0f); 1080 } 1081 1082 /** 1083 * Returns a composite to display and edit resource information.<p> 1084 * 1085 * @return the component 1086 */ 1087 private CmsResourceInfo createCompositeResourceInfo() { 1088 1089 CmsResourceInfo resourceInfo = new CmsResourceInfo(m_dataItem.getResource()); 1090 resourceInfo.setTopLineText(m_dataItem.getName()); 1091 resourceInfo.decorateTopInput(); 1092 TextField field = resourceInfo.getTopInput(); 1093 m_dataItem.getBinder().bind(field, DataItem::getName, DataItem::setName); 1094 CmsGwtContextMenuButton contextMenu = new CmsGwtContextMenuButton( 1095 m_dataItem.getResource().getStructureId(), 1096 new ContextMenu(m_dataItem)); 1097 contextMenu.addStyleName("o-gwt-contextmenu-button-margin"); 1098 resourceInfo.setButtonWidget(contextMenu); 1099 return resourceInfo; 1100 } 1101 1102 /** 1103 * Returns a component to display resource date information.<p> 1104 * 1105 * @return the component 1106 */ 1107 private Label createDisplayResourceDate() { 1108 1109 String lastModified = formatDateTime(m_dataItem.getDateLastModified().longValue()); 1110 String lastModifiedBy = m_dataItem.getResourceUtil().getUserLastModified(); 1111 String message = CmsVaadinUtils.getMessageText( 1112 Messages.GUI_GALLERY_OPTIMIZE_LASTMODIFIED_BY_2, 1113 lastModified, 1114 lastModifiedBy); 1115 Label label = new Label(message); 1116 label.addStyleNames(ValoTheme.LABEL_LIGHT, ValoTheme.LABEL_TINY); 1117 return label; 1118 } 1119 1120 /** 1121 * Creates the copyright form field.<p> 1122 * 1123 * @return the copyright form field. 1124 */ 1125 private TextField createFieldCopyright() { 1126 1127 String caption = CmsVaadinUtils.getMessageText( 1128 org.opencms.workplace.explorer.Messages.GUI_INPUT_COPYRIGHT_0); 1129 TextField field = new TextField(caption); 1130 field.setWidthFull(); 1131 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1132 m_dataItem.getBinder().bind(field, DataItem::getCopyright, DataItem::setCopyright); 1133 return field; 1134 } 1135 1136 /** 1137 * Creates the description form field.<p> 1138 * 1139 * @return the description form field. 1140 */ 1141 private TextField createFieldDescription() { 1142 1143 String caption = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_INPUT_DESCRIPTION_0); 1144 TextField field = new TextField(caption); 1145 field.setWidthFull(); 1146 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1147 m_dataItem.getBinder().bind(field, DataItem::getDescription, DataItem::setDescription); 1148 return field; 1149 } 1150 1151 /** 1152 * Creates the title form field.<p> 1153 * 1154 * @return the title form field. 1155 */ 1156 private TextField createFieldTitle() { 1157 1158 String caption = CmsVaadinUtils.getMessageText(org.opencms.workplace.explorer.Messages.GUI_INPUT_TITLE_0); 1159 TextField field = new TextField(caption); 1160 field.setWidthFull(); 1161 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1162 m_dataItem.getBinder().bind(field, DataItem::getTitle, DataItem::setTitle); 1163 return field; 1164 } 1165 1166 /** 1167 * Utility function for date formatting.<p> 1168 * 1169 * @param date the date to format 1170 * @return the formatted date 1171 */ 1172 private String formatDateTime(long date) { 1173 1174 return CmsDateUtil.getDateTime( 1175 new Date(date), 1176 DateFormat.SHORT, 1177 OpenCms.getWorkplaceManager().getWorkplaceLocale(getCms())); 1178 } 1179 } 1180 1181 /** 1182 * Page handler. Keeps track of the page currently selected by the user. 1183 */ 1184 private class PageHandler { 1185 1186 /** The maximum number of items per page. */ 1187 public static final int LIMIT = 50; 1188 1189 /** The index of the page currently selected by the user. */ 1190 private int m_currentPage = 0; 1191 1192 /** 1193 * Creates a new page handler.<p> 1194 */ 1195 public PageHandler() {} 1196 1197 /** 1198 * Returns the maximum number of items per page.<p> 1199 * 1200 * @return the maximum number of items per page 1201 */ 1202 public int getLimit() { 1203 1204 return LIMIT; 1205 } 1206 1207 /** 1208 * Returns the number of the first item on the page currently selected. 1209 * The first item on the first page has number 1.<p> 1210 * 1211 * @return the number of the first item currently selected 1212 */ 1213 public int getNumFirstItem() { 1214 1215 return (LIMIT * m_currentPage) + 1; 1216 } 1217 1218 /** 1219 * Returns the number of the last item on the page currently selected. 1220 * The number of the last item on the last page is equal to the total number of items. 1221 * 1222 * @return the number of the last item currently selected 1223 */ 1224 public int getNumLastItem() { 1225 1226 int lastItem = ((LIMIT * m_currentPage) + LIMIT); 1227 int sizeItem = getSizeItem(); 1228 if (lastItem > sizeItem) { 1229 lastItem = sizeItem; 1230 } 1231 return lastItem; 1232 } 1233 1234 /** 1235 * Returns the number of available pages.<p> 1236 * 1237 * @return the number of available pages 1238 */ 1239 public int getNumPages() { 1240 1241 return (int)Math.ceil((double)getSizeItem() / PageHandler.LIMIT); 1242 } 1243 1244 /** 1245 * Returns the index of the first item on the page currently selected. 1246 * The index of the first item on the first page is 0.<p> 1247 * 1248 * @return the index of the first item ob the page currently selected 1249 */ 1250 public int getOffset() { 1251 1252 return LIMIT * m_currentPage; 1253 } 1254 1255 /** 1256 * Returns the total number of items.<p> 1257 * 1258 * @return the total number of items 1259 */ 1260 public int getSizeItem() { 1261 1262 return m_provider.size(m_filterHandler); 1263 } 1264 1265 /** 1266 * Returns whether the current data list has pages. 1267 * 1268 * @return whether has pages 1269 */ 1270 public boolean hasPages() { 1271 1272 return getSizeItem() > LIMIT; 1273 } 1274 1275 /** 1276 * Sets the current page index to a given value. 1277 * The index of the first page is 0.<p> 1278 * 1279 * @param index the page index to set 1280 */ 1281 public void setCurrentPage(int index) { 1282 1283 m_currentPage = index; 1284 } 1285 } 1286 1287 /** 1288 * Class representing a data provider for sorting and paging the in-memory data list. 1289 */ 1290 private class Provider extends ListDataProvider<DataItem> { 1291 1292 /** The default serial version UID. */ 1293 private static final long serialVersionUID = 1L; 1294 1295 /** Comparator. */ 1296 final SerializableComparator<DataItem> SORT_DATE_ASCENDING = Comparator.comparing( 1297 DataItem::getDateLastModified)::compare; 1298 1299 /** Comparator. */ 1300 final SerializableComparator<DataItem> SORT_DATE_DESCENDING = Comparator.comparing( 1301 DataItem::getDateLastModified).reversed()::compare; 1302 1303 /** Comparator. */ 1304 final SerializableComparator<DataItem> SORT_NOCOPYRIGHT_FIRST = Comparator.comparing( 1305 DataItem::getNoCopyright)::compare; 1306 1307 /** Comparator. */ 1308 final SerializableComparator<DataItem> SORT_NODESCRIPTION_FIRST = Comparator.comparing( 1309 DataItem::getNoDescription)::compare; 1310 1311 /** Comparator. */ 1312 final SerializableComparator<DataItem> SORT_PATH_ASCENDING = Comparator.comparing( 1313 DataItem::getPath, 1314 String.CASE_INSENSITIVE_ORDER)::compare; 1315 1316 /** Comparator. */ 1317 final SerializableComparator<DataItem> SORT_PATH_DESCENDING = Comparator.comparing( 1318 DataItem::getPath, 1319 String.CASE_INSENSITIVE_ORDER).reversed()::compare; 1320 1321 /** Comparator. */ 1322 final SerializableComparator<DataItem> SORT_TITLE_ASCENDING = Comparator.comparing( 1323 DataItem::getTitle, 1324 String.CASE_INSENSITIVE_ORDER)::compare; 1325 1326 /** Comparator. */ 1327 final SerializableComparator<DataItem> SORT_TITLE_DESCENDING = Comparator.comparing( 1328 DataItem::getTitle, 1329 String.CASE_INSENSITIVE_ORDER).reversed()::compare; 1330 1331 /** Comparator. */ 1332 final SerializableComparator<DataItem> SORT_UNUSED_FIRST = Comparator.comparing(DataItem::getIsUsed)::compare; 1333 1334 /** 1335 * Create a new provider for a given data item list. 1336 * 1337 * @param dataItemList the data item list 1338 */ 1339 public Provider(List<DataItem> dataItemList) { 1340 1341 super(dataItemList); 1342 } 1343 1344 /** 1345 * Fetches one page of the sorted, filtered in-memory data item list.<p> 1346 * 1347 * @param pageHandler the page handler containing offset and limit information 1348 * @param filterHandler the filter handler containing the actual filter query string 1349 * @return the sorted data item page 1350 */ 1351 public List<DataItem> fetch(PageHandler pageHandler, FilterHandler filterHandler) { 1352 1353 SerializablePredicate<DataItem> filter = null; 1354 Query<DataItem, SerializablePredicate<DataItem>> filterQuery = composeFilterQuery(filterHandler); 1355 if (filterQuery != null) { 1356 filter = filterQuery.getFilter().orElse(null); 1357 } 1358 Query<DataItem, SerializablePredicate<DataItem>> query = new Query<DataItem, SerializablePredicate<DataItem>>( 1359 pageHandler.getOffset(), 1360 pageHandler.getLimit(), 1361 Collections.emptyList(), 1362 getSortComparator(), 1363 filter); 1364 return super.fetch(query).collect(Collectors.toList()); 1365 } 1366 1367 /** 1368 * @see com.vaadin.data.provider.ListDataProvider#getItems() 1369 */ 1370 @Override 1371 public List<DataItem> getItems() { 1372 1373 return (List<DataItem>)super.getItems(); 1374 } 1375 1376 /** 1377 * Returns the size of the list respecting the current filter. 1378 * 1379 * @param filterHandler the filter handler 1380 * @return the size 1381 */ 1382 public int size(FilterHandler filterHandler) { 1383 1384 Query<DataItem, SerializablePredicate<DataItem>> filterQuery = composeFilterQuery(filterHandler); 1385 return filterQuery == null ? getItems().size() : super.size(filterQuery); 1386 } 1387 1388 /** 1389 * Composes a provider query for a given filter handler.<p> 1390 * 1391 * @param filterHandler the given filter handler 1392 * @return the provider query 1393 */ 1394 private Query<DataItem, SerializablePredicate<DataItem>> composeFilterQuery(FilterHandler filterHandler) { 1395 1396 Query<DataItem, SerializablePredicate<DataItem>> filterQuery = null; 1397 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(filterHandler.getQuery())) { 1398 filterQuery = new Query<DataItem, SerializablePredicate<DataItem>>( 1399 dataItem -> dataItem.getFilterText().contains(filterHandler.getQuery())); 1400 } 1401 return filterQuery; 1402 } 1403 } 1404 1405 /** 1406 * Utility class to handle dialog save actions. Keeps track of the changes the 1407 * user has made since opening the dialog on the one hand and the changes since 1408 * the last save action on the other. 1409 */ 1410 private class SaveHandler { 1411 1412 /** Data items modified or marked deleted since opening the dialog. */ 1413 Set<DataItem> m_changed = new HashSet<DataItem>(); 1414 1415 /** Data items modified or marked deleted since the last save action. */ 1416 Set<DataItem> m_changedCurrent = new HashSet<DataItem>(); 1417 1418 /** IDs of the resources modified or marked as deleted since opening the dialog. */ 1419 Set<CmsUUID> m_changedIds = new HashSet<CmsUUID>(); 1420 1421 /** Resource IDs modified or marked as deleted since the last save action. */ 1422 Set<CmsUUID> m_changedIdsCurrent = new HashSet<CmsUUID>(); 1423 1424 /** Data items marked deleted since opening the dialog. */ 1425 Set<DataItem> m_deleted = new HashSet<DataItem>(); 1426 1427 /** Data items marked deleted since the last save action. */ 1428 Set<DataItem> m_deletedCurrent = new HashSet<DataItem>(); 1429 1430 /** Resources deleted since the last save action. */ 1431 Set<CmsResource> m_deletedCurrentResource = new HashSet<CmsResource>(); 1432 1433 /** Whether the user has cancelled the last save action. */ 1434 boolean m_flagCancelSave = false; 1435 1436 /** 1437 * Creates a new save handler.<p> 1438 */ 1439 public SaveHandler() {} 1440 1441 /** 1442 * Marks the last save action as cancelled.<p> 1443 * 1444 * @param flagCancelSave Whether to mark as cancelled 1445 */ 1446 public void setFlagCancelSave(boolean flagCancelSave) { 1447 1448 m_flagCancelSave = flagCancelSave; 1449 } 1450 1451 /** 1452 * Returns the data items modified or marked deleted since the last save action.<p> 1453 * 1454 * @return the data items 1455 */ 1456 Set<DataItem> getChangedCurrent() { 1457 1458 return m_changedCurrent; 1459 } 1460 1461 /** 1462 * Returns the IDs of the resources modified or marked as deleted since opening the dialog. 1463 * 1464 * @return the resource IDs 1465 */ 1466 List<CmsUUID> getChangedIds() { 1467 1468 return new ArrayList<CmsUUID>(m_changedIds); 1469 } 1470 1471 /** 1472 * Returns the data items marked deleted since the last save action.<p> 1473 * 1474 * @return the data items 1475 */ 1476 Set<DataItem> getDeletedCurrent() { 1477 1478 return m_deletedCurrent; 1479 } 1480 1481 /** 1482 * Returns the resources marked deleted since the last save action.<p> 1483 * 1484 * @return the resources 1485 */ 1486 List<CmsResource> getDeletedCurrentResource() { 1487 1488 return new ArrayList<CmsResource>(m_deletedCurrentResource); 1489 } 1490 1491 /** 1492 * Whether data items have been marked as deleted since the last save action.<p> 1493 * 1494 * @return whether data items have been marked as deleted or not 1495 */ 1496 boolean hasDeletedCurrent() { 1497 1498 return m_deletedCurrent.size() > 0; 1499 } 1500 1501 /** 1502 * Handles a save action for a given list of data items.<p> 1503 * 1504 * @param dataList the data item list 1505 */ 1506 void save(List<DataItem> dataList) { 1507 1508 if (!m_flagCancelSave) { 1509 flush(); 1510 } 1511 for (DataItem dataItem : dataList) { 1512 boolean dataItemHasChanges = dataItem.getBinder().hasChanges(); 1513 if (dataItemHasChanges) { 1514 try { 1515 dataItem.getBinder().writeBean(dataItem); 1516 m_changedCurrent.add(dataItem); 1517 if (dataItem.getDeleteFlag().booleanValue()) { 1518 m_deletedCurrent.add(dataItem); 1519 m_deletedCurrentResource.add(dataItem.getResource()); 1520 } else if ((dataItem.getDeleteFlag().booleanValue() == false) 1521 && m_deletedCurrent.contains(dataItem)) { 1522 m_deletedCurrent.remove(dataItem); 1523 m_deletedCurrentResource.remove(dataItem.getResource()); 1524 } 1525 } catch (ValidationException e) { 1526 LOG.warn(e.getLocalizedMessage(), e); 1527 } 1528 } 1529 } 1530 } 1531 1532 /** 1533 * Secures and resets the current changes. 1534 */ 1535 private void flush() { 1536 1537 m_deleted.addAll(m_deletedCurrent); 1538 m_changed.addAll(m_changedCurrent); 1539 m_changedIds.addAll(m_changedIdsCurrent); 1540 m_deletedCurrent.clear(); 1541 m_changedCurrent.clear(); 1542 m_changedIdsCurrent.clear(); 1543 m_deletedCurrentResource.clear(); 1544 } 1545 } 1546 1547 /** The sort order session attribute. */ 1548 static final String GALLERY_OPTIMIZE_ATTR_SORT_ORDER = "GALLERY_OPTIMIZE_ATTR_SORT_ORDER"; 1549 1550 /** Logger instance for this class. */ 1551 static final Log LOG = CmsLog.getLog(CmsGalleryOptimizeDialog.class); 1552 1553 /** The default serial version UID. */ 1554 private static final long serialVersionUID = 1L; 1555 1556 /** The save button. */ 1557 private Button m_buttonSave; 1558 1559 /** The save and exit button. */ 1560 private Button m_buttonSaveAndExit; 1561 1562 /** The UI composite representing the gallery item list header view. */ 1563 private Object m_compositeDataListHeader; 1564 1565 /** The dialog context. */ 1566 private I_CmsDialogContext m_context; 1567 1568 /** The UI component representing the gallery item list header view. */ 1569 private VerticalLayout m_dataListHeaderView; 1570 1571 /** The UI component representing the gallery item list view. */ 1572 private GridLayout m_dataListView; 1573 1574 /** The UI component representing the scrollable wrapper around the gallery item list view. */ 1575 private Panel m_dataListViewScrollable; 1576 1577 /** The filter handler. */ 1578 private FilterHandler m_filterHandler = new FilterHandler(); 1579 1580 /** The gallery */ 1581 private CmsResource m_gallery; 1582 1583 /** The lock action record for the gallery folder. */ 1584 private CmsLockActionRecord m_lockActionRecord; 1585 1586 /** Localized message. */ 1587 private String m_messageSortDateLastModifiedAscending; 1588 1589 /** Localized message. */ 1590 private String m_messageSortDateLastModifiedDescending; 1591 1592 /** Localized message. */ 1593 private String m_messageSortNoCopyrightFirst; 1594 1595 /** Localized message. */ 1596 private String m_messageSortNoDescriptionFirst; 1597 1598 /** Localized message. */ 1599 private String m_messageSortPathAscending; 1600 1601 /** Localized message. */ 1602 private String m_messageSortPathDescending; 1603 1604 /** Localized message. */ 1605 private String m_messageSortTitleAscending; 1606 1607 /** Localized message. */ 1608 private String m_messageSortTitleDescending; 1609 1610 /** Localized message. */ 1611 private String m_messageSortUnusedFirst; 1612 1613 /** The page handler. */ 1614 private PageHandler m_pageHandler = new PageHandler(); 1615 1616 /** The data provider. */ 1617 private Provider m_provider; 1618 1619 /** The save handler. */ 1620 private SaveHandler m_saveHandler = new SaveHandler(); 1621 1622 /** Contains information about unused images. */ 1623 private CssLayout m_unusedInfo = new CssLayout(); 1624 1625 /** 1626 * Creates a new instance of a gallery optimize dialog.<p> 1627 * 1628 * @param context the dialog context 1629 * @param gallery the gallery folder to optimize 1630 */ 1631 public CmsGalleryOptimizeDialog(I_CmsDialogContext context, CmsResource gallery) { 1632 1633 m_context = context; 1634 m_gallery = gallery; 1635 initMessages(); 1636 initDialog(); 1637 initLock(); 1638 initEvents(); 1639 m_unusedInfo.setWidth("100%"); 1640 dataListLoad(); 1641 displayDataListHeaderView(); 1642 displayDataListViewSorted(getSessionSortOrder()); 1643 } 1644 1645 /** 1646 * Returns the CMS object of this dialog.<p> 1647 * 1648 * @return the CMS object 1649 */ 1650 public CmsObject getCms() { 1651 1652 return m_context.getCms(); 1653 } 1654 1655 /** 1656 * Whether this dialog was opened from a read-only context. 1657 * 1658 * @see com.vaadin.ui.AbstractComponent#isReadOnly() 1659 */ 1660 @Override 1661 protected boolean isReadOnly() { 1662 1663 return m_context.getCms().getRequestContext().getCurrentProject().isOnlineProject(); 1664 } 1665 1666 /** 1667 * Finishes this dialog.<p> 1668 * 1669 * @param changedIds list of IDs of the changed resources 1670 */ 1671 void finishDialog(List<CmsUUID> changedIds) { 1672 1673 m_context.finish(changedIds); 1674 } 1675 1676 /** 1677 * Refreshes the UI state after a list of data items has been successfully deleted.<p> 1678 * 1679 * @param dataItemList the list of deleted data items 1680 */ 1681 void handleDataListDelete(List<DataItem> dataItemList) { 1682 1683 m_provider.getItems().removeAll(dataItemList); 1684 ((DataListHeaderComposite)m_compositeDataListHeader).refresh(); 1685 displayDataListView(false); 1686 } 1687 1688 /** 1689 * Refreshes the UI state after a list of data items has been successfully updated.<p> 1690 * 1691 * @param dataItemList the list of updated data items 1692 * @throws CmsException thrown if reading a persisted CMS resource fails 1693 */ 1694 void handleDataListUpdate(List<DataItem> dataItemList) throws CmsException { 1695 1696 for (DataItem dataItem : dataItemList) { 1697 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireFile(); 1698 CmsResource reload = getCms().readResource(dataItem.getResource().getStructureId(), resourceFilter); 1699 dataItem.setResource(reload); 1700 } 1701 displayDataListView(false); 1702 } 1703 1704 /** 1705 * Event handler handling the dialog attach event. 1706 */ 1707 void handleDialogAttach() { 1708 1709 Window window = CmsVaadinUtils.getWindow(CmsGalleryOptimizeDialog.this); 1710 window.removeAllCloseShortcuts(); // this is because Vaadin by default adds an ESC shortcut to every window 1711 // this is because the grid view unintentionally catches the focus whenever the height of the grid view 1712 // gets larger / smaller than it's containing layout, i.e., whenever the scroll-bar appears or disappears 1713 Page.getCurrent().getStyles().add(".o-gallery-grid-force-scroll { min-height: 1000px; }"); 1714 m_dataListView.addStyleName("o-gallery-grid-force-scroll"); 1715 } 1716 1717 /** 1718 * Event handler that discards all data edited and closes the 1719 * dialog. Asks the user for confirmation beforehand.<p> 1720 */ 1721 void handleDialogCancel() { 1722 1723 if (dataListHasChanges()) { 1724 String title = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_CANCEL_TITLE_0); 1725 String message = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_CANCEL_0); 1726 CmsConfirmationDialog.show(title, message, new Runnable() { 1727 1728 @Override 1729 public void run() { 1730 1731 finishDialog(new ArrayList<CmsUUID>()); 1732 } 1733 }); 1734 } else { 1735 finishDialog(new ArrayList<CmsUUID>()); 1736 } 1737 } 1738 1739 /** 1740 * Event handler that handles the dialog detach event. 1741 */ 1742 void handleDialogDetach() { 1743 1744 unlock(); 1745 } 1746 1747 /** 1748 * Event handler that saves all changes and optionally closes the dialog. If there are data items 1749 * marked as deleted the user is asked for confirmation beforehand. 1750 * 1751 * @param exit whether to exit the dialog 1752 */ 1753 void handleDialogSave(boolean exit) { 1754 1755 m_saveHandler.save(m_provider.getItems()); 1756 if (m_saveHandler.hasDeletedCurrent()) { 1757 String title = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_DELETE_TITLE_0); 1758 String message = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_DELETE_0); 1759 CmsConfirmationDialog confirmationDialog = CmsConfirmationDialog.show(title, message, new Runnable() { 1760 1761 @Override 1762 public void run() { 1763 1764 persist(exit); 1765 m_saveHandler.setFlagCancelSave(false); 1766 if (exit) { 1767 finishDialog(m_saveHandler.getChangedIds()); 1768 } 1769 } 1770 }, new Runnable() { 1771 1772 @Override 1773 public void run() { 1774 1775 m_saveHandler.setFlagCancelSave(true); 1776 } 1777 1778 }); 1779 confirmationDialog.displayResourceInfo( 1780 m_saveHandler.getDeletedCurrentResource(), 1781 org.opencms.ui.Messages.GUI_SELECTED_0); 1782 } else { 1783 persist(exit); 1784 if (exit) { 1785 finishDialog(m_saveHandler.getChangedIds()); 1786 } 1787 } 1788 } 1789 1790 /** 1791 * For a given gallery folder resource, creates a panel with information whether 1792 * this gallery is in use.<p> 1793 * 1794 * @return the gallery in use panel 1795 * @throws CmsException the CMS exception 1796 */ 1797 private HorizontalLayout createDisplayGalleryInUse() throws CmsException { 1798 1799 String galleryTitle = getGalleryTitle(); 1800 String text = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_DIRECTLY_USED_1, galleryTitle); 1801 HorizontalLayout layout1 = new HorizontalLayout(); 1802 layout1.setWidthFull(); 1803 layout1.addStyleNames("v-panel", "o-error-dialog", OpenCmsTheme.GALLERY_ALERT_IN_USE); 1804 HorizontalLayout layout2 = new HorizontalLayout(); 1805 layout2.setWidthUndefined(); 1806 Label icon = new Label(FontOpenCms.WARNING.getHtml()); 1807 icon.setContentMode(ContentMode.HTML); 1808 icon.setWidthUndefined(); 1809 icon.setStyleName("o-warning-icon"); 1810 Label message = new Label(text); 1811 message.setContentMode(ContentMode.HTML); 1812 message.setWidthUndefined(); 1813 layout2.addComponent(icon); 1814 layout2.addComponent(message); 1815 layout2.setComponentAlignment(message, Alignment.MIDDLE_LEFT); 1816 layout1.addComponent(layout2); 1817 layout1.setComponentAlignment(layout2, Alignment.MIDDLE_CENTER); 1818 return layout1; 1819 } 1820 1821 /** 1822 * Creates a component showing a warning if this dialog was opened from the online project context.<p> 1823 * 1824 * @return the component 1825 */ 1826 private HorizontalLayout createDisplayInOnlineProject() { 1827 1828 String text = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_LABEL_IN_ONLINE_PROJECT_0); 1829 return createNote(text); 1830 } 1831 1832 /** 1833 * Creates an info box. 1834 * 1835 * @param text the text (HTML) to display in the box 1836 * @param styles the additional CSS styles for the info box 1837 * @return the info box 1838 */ 1839 private HorizontalLayout createNote(String text, String... styles) { 1840 1841 HorizontalLayout layout = new HorizontalLayout(); 1842 layout.setWidthFull(); 1843 layout.addStyleNames("v-panel", "o-error-dialog"); 1844 layout.addStyleNames(styles); 1845 Label icon = new Label(FontOpenCms.WARNING.getHtml()); 1846 icon.setContentMode(ContentMode.HTML); 1847 icon.setWidthUndefined(); 1848 icon.setStyleName("o-warning-icon"); 1849 Label message = new Label(text); 1850 message.setContentMode(ContentMode.HTML); 1851 message.setWidthUndefined(); 1852 layout.addComponent(icon); 1853 layout.addComponent(message); 1854 layout.setComponentAlignment(message, Alignment.MIDDLE_LEFT); 1855 layout.setExpandRatio(message, 1.0f); 1856 return layout; 1857 } 1858 1859 /** 1860 * Creates a note widget to display above the list. 1861 * 1862 * @param html the HTML content 1863 * @param styles the additional CSS classes 1864 * @return the created widget 1865 */ 1866 private HorizontalLayout createSimpleNote(String html, String... styles) { 1867 1868 HorizontalLayout layout1 = new HorizontalLayout(); 1869 layout1.setWidthFull(); 1870 layout1.addStyleNames("v-panel", "o-error-dialog"); 1871 layout1.addStyleName("o-optimize-gallery-note"); 1872 layout1.addStyleNames(styles); 1873 Label message = new Label(html); 1874 message.setContentMode(ContentMode.HTML); 1875 layout1.addComponent(message); 1876 return layout1; 1877 } 1878 1879 /** 1880 * Whether one of the editable gallery items has been modified by the user.<p> 1881 * 1882 * @return whether has changes 1883 */ 1884 private boolean dataListHasChanges() { 1885 1886 boolean hasChanges = false; 1887 for (DataItem dataItem : m_provider.getItems()) { 1888 if (dataItem.getBinder().hasChanges()) { 1889 hasChanges = true; 1890 } 1891 } 1892 return hasChanges; 1893 } 1894 1895 /** 1896 * Loads the gallery item list.<p> 1897 */ 1898 private void dataListLoad() { 1899 1900 List<DataItem> dataList = new ArrayList<DataItem>(); 1901 CmsObject cms = A_CmsUI.getCmsObject(); 1902 try { 1903 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireFile(); 1904 List<CmsResource> resources = cms.readResources(cms.getSitePath(m_gallery), resourceFilter); 1905 for (CmsResource resource : resources) { 1906 DataItem dataItem = new DataItem(resource); 1907 dataList.add(dataItem); 1908 } 1909 } catch (CmsException exception) { 1910 m_context.error(exception); 1911 } 1912 m_provider = new Provider(dataList); 1913 } 1914 1915 /** 1916 * Displays the UI component representing the dialog header view.<p> 1917 */ 1918 private void displayDataListHeaderView() { 1919 1920 if (isReadOnly()) { 1921 m_dataListHeaderView.addComponent(createDisplayInOnlineProject()); 1922 } else { 1923 try { 1924 List<CmsRelation> relations = getCms().getRelationsForResource(m_gallery, CmsRelationFilter.SOURCES); 1925 if ((relations != null) && !relations.isEmpty()) { 1926 m_dataListHeaderView.addComponent(createDisplayGalleryInUse()); 1927 } 1928 } catch (CmsException e) { 1929 LOG.warn(e.getLocalizedMessage(), e); 1930 } 1931 } 1932 m_compositeDataListHeader = new DataListHeaderComposite(); 1933 1934 m_dataListHeaderView.addComponent((Component)m_compositeDataListHeader); 1935 m_dataListHeaderView.addComponent(m_unusedInfo); 1936 } 1937 1938 /** 1939 * Displays the UI component representing the scrollable gallery item list.<p> 1940 * 1941 * @param scrollToTop whether to scroll to top after displaying the gallery item list 1942 */ 1943 private void displayDataListView(boolean scrollToTop) { 1944 1945 m_dataListView.removeAllComponents(); 1946 List<DataItem> dataItemList = m_provider.fetch(m_pageHandler, m_filterHandler); 1947 m_dataListView.setColumns(3); 1948 m_dataListView.setRows(dataItemList.size() + 2); 1949 m_dataListView.setColumnExpandRatio(2, 1.0f); 1950 int i = 1; 1951 Label dummy = new Label(" "); 1952 dummy.setId("scrollToTop"); 1953 dummy.setHeight("0px"); 1954 m_dataListView.addComponent(dummy, 0, 0); 1955 for (DataItem dataItem : dataItemList) { 1956 dataItem.getCompositeFile().removeStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 1957 dataItem.getCompositeFileDelete().removeStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 1958 dataItem.getCompositeForm().removeStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 1959 if ((i % 2) == 0) { 1960 dataItem.getCompositeFile().addStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 1961 dataItem.getCompositeFileDelete().addStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 1962 dataItem.getCompositeForm().addStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 1963 } 1964 m_dataListView.addComponent(dataItem.getCompositeFile(), 0, i); 1965 m_dataListView.addComponent(dataItem.getCompositeFileDelete(), 1, i); 1966 m_dataListView.addComponent(dataItem.getCompositeForm(), 2, i); 1967 i++; 1968 } 1969 updateUnusedInfo(); 1970 if (scrollToTop) { 1971 m_dataListViewScrollable.setScrollTop(0); 1972 } 1973 } 1974 1975 /** 1976 * Sorts the gallery item list according to a given sort order and re-renders the 1977 * gallery item list view.<p> 1978 * 1979 * @param sortOrder the sort order 1980 */ 1981 private void displayDataListViewSorted(String sortOrder) { 1982 1983 SerializableComparator<DataItem> defaultSortOrder = m_provider.SORT_PATH_ASCENDING; 1984 if (sortOrder == null) { 1985 m_provider.setSortComparator(m_provider.SORT_TITLE_ASCENDING); 1986 } else if (sortOrder == m_messageSortTitleAscending) { 1987 m_provider.setSortComparator(m_provider.SORT_TITLE_ASCENDING); 1988 } else if (sortOrder == m_messageSortTitleDescending) { 1989 m_provider.setSortComparator(m_provider.SORT_TITLE_DESCENDING); 1990 } else if (sortOrder == m_messageSortDateLastModifiedAscending) { 1991 m_provider.setSortComparator(m_provider.SORT_DATE_ASCENDING); 1992 } else if (sortOrder == m_messageSortDateLastModifiedDescending) { 1993 m_provider.setSortComparator(m_provider.SORT_DATE_DESCENDING); 1994 } else if (sortOrder == m_messageSortPathAscending) { 1995 m_provider.setSortComparator(m_provider.SORT_PATH_ASCENDING); 1996 } else if (sortOrder == m_messageSortPathDescending) { 1997 m_provider.setSortComparator(m_provider.SORT_PATH_DESCENDING); 1998 } else if (sortOrder == m_messageSortUnusedFirst) { 1999 m_provider.setSortComparator(m_provider.SORT_UNUSED_FIRST); 2000 } else if (sortOrder == m_messageSortNoCopyrightFirst) { 2001 m_provider.setSortComparator(m_provider.SORT_NOCOPYRIGHT_FIRST); 2002 } else if (sortOrder == m_messageSortNoDescriptionFirst) { 2003 m_provider.setSortComparator(m_provider.SORT_NODESCRIPTION_FIRST); 2004 } else { 2005 m_provider.setSortComparator(defaultSortOrder); 2006 } 2007 setSessionSortOrder(sortOrder); 2008 displayDataListView(true); 2009 } 2010 2011 /** 2012 * Returns the gallery title for a given gallery folder resource.<p> 2013 * 2014 * @return the title 2015 * @throws CmsException the CMS exception 2016 */ 2017 private String getGalleryTitle() throws CmsException { 2018 2019 String galleryTitle = getCms().readPropertyObject( 2020 m_gallery, 2021 CmsPropertyDefinition.PROPERTY_TITLE, 2022 false).getValue(); 2023 if (CmsStringUtil.isEmptyOrWhitespaceOnly(galleryTitle)) { 2024 galleryTitle = m_gallery.getName(); 2025 } 2026 return galleryTitle; 2027 } 2028 2029 /** 2030 * Returns the current sort order saved in the user session with lazy initialization.<p> 2031 * 2032 * @return the sort order 2033 */ 2034 private String getSessionSortOrder() { 2035 2036 WrappedSession wrappedSession = VaadinService.getCurrentRequest().getWrappedSession(); 2037 String currentSortOrder = (String)wrappedSession.getAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER); 2038 if (currentSortOrder == null) { 2039 wrappedSession.setAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER, m_messageSortPathAscending); 2040 } 2041 return (String)wrappedSession.getAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER); 2042 } 2043 2044 /** 2045 * Initializes this dialog.<p> 2046 */ 2047 private void initDialog() { 2048 2049 CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null); 2050 displayResourceInfo(m_gallery); 2051 Button buttonCancel = createButtonCancel(); 2052 buttonCancel.addClickListener(event -> { 2053 CmsGalleryOptimizeDialog.this.handleDialogCancel(); 2054 }); 2055 addButton(buttonCancel, false); 2056 m_buttonSave.setEnabled(!isReadOnly()); 2057 m_buttonSaveAndExit.setEnabled(!isReadOnly()); 2058 } 2059 2060 /** 2061 * Initializes the events of this dialog.<p> 2062 */ 2063 private void initEvents() { 2064 2065 m_buttonSave.addClickListener(event -> { 2066 CmsGalleryOptimizeDialog.this.handleDialogSave(false); 2067 }); 2068 m_buttonSaveAndExit.addClickListener(event -> { 2069 CmsGalleryOptimizeDialog.this.handleDialogSave(true); 2070 }); 2071 setActionHandler(new CmsOkCancelActionHandler() { 2072 2073 private static final long serialVersionUID = 1L; 2074 2075 @Override 2076 protected void cancel() { 2077 2078 CmsGalleryOptimizeDialog.this.handleDialogCancel(); 2079 } 2080 2081 @Override 2082 protected void ok() { 2083 2084 CmsGalleryOptimizeDialog.this.handleDialogSave(true); 2085 } 2086 }); 2087 addAttachListener(event -> { 2088 CmsGalleryOptimizeDialog.this.handleDialogAttach(); 2089 }); 2090 addDetachListener(event -> { 2091 CmsGalleryOptimizeDialog.this.handleDialogDetach(); 2092 }); 2093 } 2094 2095 /** 2096 * Locks the gallery folder.<p> 2097 */ 2098 private void initLock() { 2099 2100 try { 2101 m_lockActionRecord = CmsLockUtil.ensureLock(getCms(), m_gallery); 2102 } catch (CmsException e) { 2103 LOG.warn(e.getLocalizedMessage(), e); 2104 } 2105 } 2106 2107 /** 2108 * Initializes the localized messages of this dialog.<p> 2109 */ 2110 private void initMessages() { 2111 2112 m_messageSortDateLastModifiedAscending = CmsVaadinUtils.getMessageText( 2113 Messages.GUI_GALLERY_OPTIMIZE_SORT_DATE_MODIFIED_ASCENDING_0); 2114 m_messageSortDateLastModifiedDescending = CmsVaadinUtils.getMessageText( 2115 Messages.GUI_GALLERY_OPTIMIZE_SORT_DATE_MODIFIED_DESCENDING_0); 2116 m_messageSortPathAscending = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_SORT_PATH_ASCENDING_0); 2117 m_messageSortPathDescending = CmsVaadinUtils.getMessageText( 2118 Messages.GUI_GALLERY_OPTIMIZE_SORT_PATH_DESCENDING_0); 2119 m_messageSortTitleAscending = CmsVaadinUtils.getMessageText( 2120 Messages.GUI_GALLERY_OPTIMIZE_SORT_TITLE_ASCENDING_0); 2121 m_messageSortTitleDescending = CmsVaadinUtils.getMessageText( 2122 Messages.GUI_GALLERY_OPTIMIZE_SORT_TITLE_DESCENDING_0); 2123 m_messageSortUnusedFirst = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_SORT_UNUSED_FIRST_0); 2124 m_messageSortNoCopyrightFirst = CmsVaadinUtils.getMessageText( 2125 Messages.GUI_GALLERY_OPTIMIZE_SORT_NOCOPYRIGHT_FIRST_0); 2126 m_messageSortNoDescriptionFirst = CmsVaadinUtils.getMessageText( 2127 Messages.GUI_GALLERY_OPTIMIZE_SORT_NODESCRIPTION_FIRST_0); 2128 } 2129 2130 /** 2131 * Persists all data changes that have not been saved yet. Refreshes the UI. 2132 * Informs the user about failed updates, failed renames and failed deletes.<p> 2133 * 2134 * @param exit true if we exit the dialog after saving 2135 */ 2136 private void persist(boolean exit) { 2137 2138 StringBuilder errorMessageList = new StringBuilder(); 2139 persistUpdateAndRename(errorMessageList); 2140 persistDelete(errorMessageList); 2141 2142 // In the embedded dialog case, using the Vaadin notifications when exiting the dialog doesn't work (the iframe 2143 // is hidden before the notifications show up). 2144 // We don't really need notifications for the successful case, but when errors occur, we send them to the 2145 // ADE notification mechanism. 2146 boolean embeddedExit = (m_context instanceof CmsEmbeddedDialogContext) && exit; 2147 2148 if (errorMessageList.length() == 0) { 2149 2150 if (!embeddedExit) { 2151 String message = CmsVaadinUtils.getMessageText( 2152 Messages.GUI_GALLERY_OPTIMIZE_LABEL_SUCCESSFULLY_SAVED_0); 2153 Notification notification = new Notification(message, "", Notification.Type.HUMANIZED_MESSAGE); 2154 notification.setPosition(Position.TOP_CENTER); 2155 notification.show(Page.getCurrent()); 2156 } 2157 } else { 2158 if (embeddedExit) { 2159 ((CmsEmbeddedDialogContext)m_context).sendNotification(true, errorMessageList.toString()); 2160 } else { 2161 Notification notification = new Notification( 2162 "", 2163 errorMessageList.toString(), 2164 Notification.Type.ERROR_MESSAGE); 2165 notification.setHtmlContentAllowed(true); 2166 notification.setPosition(Position.TOP_CENTER); 2167 notification.show(Page.getCurrent()); 2168 } 2169 } 2170 } 2171 2172 /** 2173 * Persists all deleted gallery items.<p> 2174 * 2175 * @param errorMessageList string builder to append error messages 2176 */ 2177 private void persistDelete(StringBuilder errorMessageList) { 2178 2179 List<DataItem> deleted = new ArrayList<DataItem>(); 2180 for (DataItem dataItem : m_saveHandler.getDeletedCurrent()) { 2181 CmsResource resource = dataItem.getResource(); 2182 try { 2183 getCms().deleteResource(getCms().getSitePath(resource), CmsResource.DELETE_PRESERVE_SIBLINGS); 2184 deleted.add(dataItem); 2185 } catch (CmsException e) { 2186 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2187 LOG.warn(e.getLocalizedMessage(), e); 2188 } 2189 } 2190 handleDataListDelete(deleted); 2191 if (m_saveHandler.hasDeletedCurrent()) { 2192 displayDataListView(true); 2193 } 2194 } 2195 2196 /** 2197 * Persists all updated and renamed gallery items.<p> 2198 * 2199 * @param errorMessageList string builder to append error messages 2200 */ 2201 private void persistUpdateAndRename(StringBuilder errorMessageList) { 2202 2203 List<DataItem> updated = new ArrayList<DataItem>(); 2204 for (DataItem dataItem : m_saveHandler.getChangedCurrent()) { 2205 CmsResource resource = dataItem.getResource(); 2206 if (dataItem.hasChanges()) { 2207 try { 2208 getCms().writePropertyObjects(resource, dataItem.getPropertyList()); 2209 getCms().writeResource(resource); 2210 updated.add(dataItem); 2211 } catch (CmsException e) { 2212 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2213 LOG.warn(e.getLocalizedMessage(), e); 2214 } 2215 } 2216 if (dataItem.isRenamed()) { 2217 String source = getCms().getSitePath(resource); 2218 String destination = CmsStringUtil.joinPaths(CmsResource.getParentFolder(source), dataItem.getName()); 2219 try { 2220 getCms().renameResource(source, destination); 2221 if (!updated.contains(dataItem)) { 2222 updated.add(dataItem); 2223 } 2224 } catch (CmsException e) { 2225 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2226 LOG.warn(e.getLocalizedMessage(), e); 2227 } 2228 } 2229 } 2230 try { 2231 handleDataListUpdate(updated); 2232 } catch (CmsException e) { 2233 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2234 LOG.warn(e.getLocalizedMessage(), e); 2235 } 2236 } 2237 2238 /** 2239 * Saves the selected sort order in the user session.<p> 2240 * 2241 * @param sortOrder the sort order 2242 */ 2243 private void setSessionSortOrder(String sortOrder) { 2244 2245 WrappedSession wrappedSession = VaadinService.getCurrentRequest().getWrappedSession(); 2246 wrappedSession.setAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER, sortOrder); 2247 } 2248 2249 /** 2250 * Unlocks the gallery folder.<p> 2251 */ 2252 private void unlock() { 2253 2254 if (m_lockActionRecord != null) { 2255 try { 2256 getCms().unlockResource(m_gallery); 2257 } catch (CmsException e) { 2258 LOG.warn(e.getLocalizedMessage(), e); 2259 } 2260 } 2261 } 2262 2263 /** 2264 * Updates the 'unused elements' information. 2265 */ 2266 private void updateUnusedInfo() { 2267 2268 m_unusedInfo.removeAllComponents(); 2269 m_unusedInfo.setVisible(true); 2270 boolean isImageGallery = OpenCms.getResourceManager().matchResourceType("imagegallery", m_gallery.getTypeId()); 2271 if (m_provider.getSortComparator() == m_provider.SORT_UNUSED_FIRST) { 2272 long unusedCount = m_provider.getItems().stream().filter(item -> !item.getIsUsed()).count(); 2273 if (unusedCount == 0) { 2274 String text = CmsVaadinUtils.getMessageText( 2275 isImageGallery 2276 ? Messages.GUI_GALLERY_OPTIMIZE_NO_UNUSED_0 2277 : Messages.GUI_GALLERY_OPTIMIZE_NO_UNUSED_DOWNLOADS_0); 2278 m_unusedInfo.addComponent(createSimpleNote(text, "o-optimize-gallery-warning")); 2279 } else { 2280 String text = CmsVaadinUtils.getMessageText( 2281 isImageGallery 2282 ? Messages.GUI_GALLERY_OPTIMIZE_NUM_UNUSED_1 2283 : Messages.GUI_GALLERY_OPTIMIZE_NUM_UNUSED_DOWNLOADS_1, 2284 unusedCount); 2285 m_unusedInfo.addComponent(createSimpleNote(text)); 2286 } 2287 } else if (m_provider.getSortComparator() == m_provider.SORT_NOCOPYRIGHT_FIRST) { 2288 long noCopyright = m_provider.getItems().stream().filter(item -> !item.getNoCopyright()).count(); 2289 if (noCopyright == 0) { 2290 String text = CmsVaadinUtils.getMessageText( 2291 isImageGallery 2292 ? Messages.GUI_GALLERY_OPTIMIZE_NO_NOCOPYRIGHT_0 2293 : Messages.GUI_GALLERY_OPTIMIZE_NO_NOCOPYRIGHT_DOWNLOADS_0); 2294 m_unusedInfo.addComponent(createSimpleNote(text, "o-optimize-gallery-warning")); 2295 } else if (noCopyright == 1) { 2296 String text = CmsVaadinUtils.getMessageText( 2297 isImageGallery 2298 ? Messages.GUI_GALLERY_OPTIMIZE_ONE_NOCOPYRIGHT_0 2299 : Messages.GUI_GALLERY_OPTIMIZE_ONE_NOCOPYRIGHT_DOWNLOADS_0); 2300 m_unusedInfo.addComponent(createSimpleNote(text)); 2301 } else { 2302 String text = CmsVaadinUtils.getMessageText( 2303 isImageGallery 2304 ? Messages.GUI_GALLERY_OPTIMIZE_NUM_NOCOPYRIGHT_1 2305 : Messages.GUI_GALLERY_OPTIMIZE_NUM_NOCOPYRIGHT_DOWNLOADS_1, 2306 noCopyright); 2307 m_unusedInfo.addComponent(createSimpleNote(text)); 2308 } 2309 } else if (m_provider.getSortComparator() == m_provider.SORT_NODESCRIPTION_FIRST) { 2310 long noDescription = m_provider.getItems().stream().filter(item -> !item.getNoDescription()).count(); 2311 if (noDescription == 0) { 2312 String text = CmsVaadinUtils.getMessageText( 2313 isImageGallery 2314 ? Messages.GUI_GALLERY_OPTIMIZE_NO_NODESCRIPTION_0 2315 : Messages.GUI_GALLERY_OPTIMIZE_NO_NODESCRIPTION_DOWNLOADS_0); 2316 m_unusedInfo.addComponent(createSimpleNote(text, "o-optimize-gallery-warning")); 2317 } else if (noDescription == 1) { 2318 String text = CmsVaadinUtils.getMessageText( 2319 isImageGallery 2320 ? Messages.GUI_GALLERY_OPTIMIZE_ONE_NODESCRIPTION_0 2321 : Messages.GUI_GALLERY_OPTIMIZE_ONE_NODESCRIPTION_DOWNLOADS_0); 2322 m_unusedInfo.addComponent(createSimpleNote(text)); 2323 } else { 2324 String text = CmsVaadinUtils.getMessageText( 2325 isImageGallery 2326 ? Messages.GUI_GALLERY_OPTIMIZE_NUM_NODESCRIPTION_1 2327 : Messages.GUI_GALLERY_OPTIMIZE_NUM_NODESCRIPTION_DOWNLOADS_1, 2328 noDescription); 2329 2330 m_unusedInfo.addComponent(createSimpleNote(text)); 2331 } 2332 } else { 2333 m_unusedInfo.setVisible(false); 2334 } 2335 2336 } 2337 2338}