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.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.file.CmsResource.CmsResourceDeleteMode; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.CmsVfsResourceNotFoundException; 035import org.opencms.lock.CmsLockActionRecord; 036import org.opencms.lock.CmsLockActionRecord.LockChange; 037import org.opencms.lock.CmsLockException; 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.security.CmsRole; 045import org.opencms.ui.A_CmsUI; 046import org.opencms.ui.CmsVaadinUtils; 047import org.opencms.ui.I_CmsDialogContext; 048import org.opencms.ui.components.CmsBasicDialog; 049import org.opencms.ui.components.CmsGwtContextMenuButton; 050import org.opencms.ui.components.CmsOkCancelActionHandler; 051import org.opencms.ui.components.CmsResourceInfo; 052import org.opencms.ui.components.OpenCmsTheme; 053import org.opencms.ui.shared.rpc.I_CmsGwtContextMenuServerRpc; 054import org.opencms.util.CmsUUID; 055import org.opencms.workplace.commons.Messages; 056 057import java.util.ArrayList; 058import java.util.Arrays; 059import java.util.Collections; 060import java.util.HashSet; 061import java.util.List; 062import java.util.Set; 063 064import org.apache.commons.logging.Log; 065 066import com.google.common.collect.HashMultimap; 067import com.google.common.collect.Lists; 068import com.google.common.collect.Multimap; 069import com.vaadin.ui.Button; 070import com.vaadin.ui.Button.ClickListener; 071import com.vaadin.ui.Component; 072import com.vaadin.v7.data.Property.ValueChangeEvent; 073import com.vaadin.v7.data.Property.ValueChangeListener; 074import com.vaadin.v7.ui.HorizontalLayout; 075import com.vaadin.v7.ui.Label; 076import com.vaadin.v7.ui.OptionGroup; 077import com.vaadin.v7.ui.VerticalLayout; 078 079/** 080 * Dialog for deleting resources.<p> 081 */ 082public class CmsDeleteDialog extends CmsBasicDialog { 083 084 /** Logger instance for this class. */ 085 static final Log LOG = CmsLog.getLog(CmsDeleteDialog.class); 086 087 /** Serial version id. */ 088 private static final long serialVersionUID = 1L; 089 090 /** Box for displaying resource widgets. */ 091 private VerticalLayout m_resourceBox; 092 093 // private AbstractComponent m_container; 094 095 /** Message if deleting resources is allowed or not. */ 096 private Label m_deleteResource; 097 098 /** Label for the links. */ 099 private Label m_linksLabel; 100 101 /** The OK button. */ 102 private Button m_okButton; 103 104 /** The cancel button. */ 105 private Button m_cancelButton; 106 107 /** The dialog context. */ 108 private I_CmsDialogContext m_context; 109 110 /** The delete siblings check box group. */ 111 private OptionGroup m_deleteSiblings; 112 113 /** 114 * Creates a new instance.<p> 115 * 116 * @param context the dialog context 117 */ 118 public CmsDeleteDialog(I_CmsDialogContext context) { 119 120 m_context = context; 121 CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null); 122 m_deleteSiblings.addItem(CmsResource.DELETE_PRESERVE_SIBLINGS); 123 m_deleteSiblings.setItemCaption( 124 CmsResource.DELETE_PRESERVE_SIBLINGS, 125 CmsVaadinUtils.getMessageText(Messages.GUI_DELETE_PRESERVE_SIBLINGS_0)); 126 m_deleteSiblings.addItem(CmsResource.DELETE_REMOVE_SIBLINGS); 127 m_deleteSiblings.setItemCaption( 128 CmsResource.DELETE_REMOVE_SIBLINGS, 129 CmsVaadinUtils.getMessageText(Messages.GUI_DELETE_ALL_SIBLINGS_0)); 130 m_deleteSiblings.setValue(CmsResource.DELETE_PRESERVE_SIBLINGS); 131 m_deleteSiblings.addValueChangeListener(new ValueChangeListener() { 132 133 private static final long serialVersionUID = 1L; 134 135 public void valueChange(ValueChangeEvent event) { 136 137 displayBrokenLinks(); 138 } 139 }); 140 m_deleteSiblings.setVisible(hasSiblings()); 141 displayResourceInfo(m_context.getResources()); 142 143 m_cancelButton.addClickListener(new ClickListener() { 144 145 /** Serial version id. */ 146 private static final long serialVersionUID = 1L; 147 148 public void buttonClick(Button.ClickEvent event) { 149 150 cancel(); 151 } 152 }); 153 154 m_okButton.addStyleName(OpenCmsTheme.BUTTON_RED); 155 156 m_okButton.addClickListener(new ClickListener() { 157 158 /** Serial version id. */ 159 private static final long serialVersionUID = 1L; 160 161 public void buttonClick(Button.ClickEvent event) { 162 163 submit(); 164 } 165 }); 166 167 displayBrokenLinks(); 168 169 setActionHandler(new CmsOkCancelActionHandler() { 170 171 private static final long serialVersionUID = 1L; 172 173 @Override 174 protected void cancel() { 175 176 CmsDeleteDialog.this.cancel(); 177 } 178 179 @Override 180 protected void ok() { 181 182 submit(); 183 } 184 }); 185 } 186 187 /** 188 * Gets the broken links.<p> 189 * 190 * @param cms the CMS context 191 * @param selectedResources the selected resources 192 * @param includeSiblings <code>true</code> if siblings would be deleted too 193 * 194 * @return multimap of broken links, with sources as keys and targets as values 195 * 196 * @throws CmsException if something goes wrong 197 */ 198 public static Multimap<CmsResource, CmsResource> getBrokenLinks( 199 CmsObject cms, 200 List<CmsResource> selectedResources, 201 boolean includeSiblings) 202 throws CmsException { 203 204 return getBrokenLinks(cms, selectedResources, includeSiblings, false); 205 206 } 207 208 /** 209 * Gets the broken links.<p> 210 * 211 * @param cms the CMS context 212 * @param selectedResources the selected resources 213 * @param includeSiblings <code>true</code> if siblings would be deleted too 214 * @param reverse <code>true</code> if the resulting map should be reverted 215 * 216 * @return multimap of broken links, with sources as keys and targets as values 217 * 218 * @throws CmsException if something goes wrong 219 */ 220 public static Multimap<CmsResource, CmsResource> getBrokenLinks( 221 CmsObject cms, 222 List<CmsResource> selectedResources, 223 boolean includeSiblings, 224 boolean reverse) 225 throws CmsException { 226 227 Set<CmsResource> descendants = new HashSet<CmsResource>(); 228 for (CmsResource root : selectedResources) { 229 descendants.add(root); 230 if (root.isFolder()) { 231 descendants.addAll(cms.readResources(cms.getSitePath(root), CmsResourceFilter.IGNORE_EXPIRATION)); 232 } 233 } 234 235 if (includeSiblings) { 236 // add siblings 237 for (CmsResource res : new HashSet<CmsResource>(descendants)) { 238 if (res.isFile()) { 239 descendants.addAll(cms.readSiblings(res, CmsResourceFilter.IGNORE_EXPIRATION)); 240 } 241 } 242 } 243 HashSet<CmsUUID> deleteIds = new HashSet<CmsUUID>(); 244 for (CmsResource deleteRes : descendants) { 245 deleteIds.add(deleteRes.getStructureId()); 246 } 247 Multimap<CmsResource, CmsResource> linkMap = HashMultimap.create(); 248 for (CmsResource resource : descendants) { 249 List<CmsRelation> relations = cms.getRelationsForResource(resource, CmsRelationFilter.SOURCES); 250 List<CmsResource> result1 = new ArrayList<CmsResource>(); 251 for (CmsRelation relation : relations) { 252 // only add related resources that are not going to be deleted 253 if (!deleteIds.contains(relation.getSourceId())) { 254 try { 255 CmsResource source1 = relation.getSource(cms, CmsResourceFilter.ALL); 256 if (!source1.getState().isDeleted()) { 257 result1.add(source1); 258 } 259 } catch (Exception e) { 260 LOG.warn( 261 "Couldn't find relation source while checking the following relation: " 262 + relation.toString(), 263 e); 264 265 } 266 } 267 } 268 List<CmsResource> linkSources = result1; 269 for (CmsResource source : linkSources) { 270 if (reverse) { 271 linkMap.put(resource, source); 272 } else { 273 linkMap.put(source, resource); 274 } 275 } 276 } 277 return linkMap; 278 279 } 280 281 /** 282 * Cancels the dialog.<p> 283 */ 284 void cancel() { 285 286 m_context.finish(new ArrayList<CmsUUID>()); 287 } 288 289 /** 290 * Displays the broken links.<p> 291 */ 292 void displayBrokenLinks() { 293 294 I_CmsGwtContextMenuServerRpc rpc = new I_CmsGwtContextMenuServerRpc() { 295 296 public void refresh(String id) { 297 298 if (id != null) { 299 m_context.finish(Arrays.asList(new CmsUUID(id))); 300 } else { 301 m_context.finish(Collections.emptyList()); 302 } 303 } 304 }; 305 CmsObject cms = A_CmsUI.getCmsObject(); 306 m_resourceBox.removeAllComponents(); 307 m_resourceBox.addStyleName("o-broken-links"); 308 m_deleteResource.setVisible(false); 309 m_okButton.setVisible(true); 310 boolean canIgnoreBrokenLinks = OpenCms.getWorkplaceManager().getDefaultUserSettings().isAllowBrokenRelations() 311 || OpenCms.getRoleManager().hasRole(cms, CmsRole.VFS_MANAGER); 312 try { 313 Multimap<CmsResource, CmsResource> brokenLinks = getBrokenLinks( 314 cms, 315 m_context.getResources(), 316 CmsResource.DELETE_REMOVE_SIBLINGS.equals(m_deleteSiblings.getValue())); 317 if (brokenLinks.isEmpty()) { 318 m_linksLabel.setVisible(false); 319 String noLinksBroken = CmsVaadinUtils.getMessageText( 320 org.opencms.workplace.commons.Messages.GUI_DELETE_RELATIONS_NOT_BROKEN_0); 321 m_resourceBox.addComponent(new Label(noLinksBroken)); 322 } else { 323 if (!canIgnoreBrokenLinks) { 324 m_deleteResource.setVisible(true); 325 m_deleteResource.setValue( 326 CmsVaadinUtils.getMessageText( 327 org.opencms.workplace.commons.Messages.GUI_DELETE_RELATIONS_NOT_ALLOWED_0)); 328 m_okButton.setVisible(false); 329 } 330 for (CmsResource source : brokenLinks.keySet()) { 331 CmsResourceInfo parentInfo = new CmsResourceInfo(source); 332 CmsGwtContextMenuButton contextMenu = new CmsGwtContextMenuButton(source.getStructureId(), rpc); 333 contextMenu.addStyleName("o-gwt-contextmenu-button-margin"); 334 parentInfo.setButtonWidget(contextMenu); 335 m_resourceBox.addComponent(parentInfo); 336 for (CmsResource target : brokenLinks.get(source)) { 337 CmsResourceInfo childInfo = new CmsResourceInfo(target); 338 childInfo.addStyleName("o-deleted"); 339 m_resourceBox.addComponent(indent(childInfo)); 340 } 341 342 } 343 } 344 } catch (CmsException e) { 345 m_context.error(e); 346 return; 347 } 348 } 349 350 /** 351 * Submits the dialog.<p> 352 */ 353 void submit() { 354 355 CmsObject cms = A_CmsUI.getCmsObject(); 356 try { 357 List<CmsUUID> changedIds = Lists.newArrayList(); 358 CmsResourceDeleteMode mode = (CmsResourceDeleteMode)m_deleteSiblings.getValue(); 359 for (CmsResource resource : m_context.getResources()) { 360 if (resource.getState().isDeleted()) { 361 continue; 362 } 363 changedIds.add(resource.getStructureId()); 364 CmsLockActionRecord lockRecord = CmsLockUtil.ensureLock(cms, resource); 365 try { 366 cms.deleteResource(cms.getSitePath(resource), mode); 367 } finally { 368 if (lockRecord.getChange().equals(LockChange.locked)) { 369 if (!resource.getState().isNew()) { 370 try { 371 cms.unlockResource(resource); 372 } catch (CmsVfsResourceNotFoundException e) { 373 LOG.warn(e.getLocalizedMessage(), e); 374 } catch (CmsLockException e) { 375 LOG.warn(e.getLocalizedMessage(), e); 376 } 377 } 378 } 379 } 380 } 381 m_context.finish(changedIds); 382 } catch (Exception e) { 383 m_context.error(e); 384 } 385 } 386 387 /** 388 * Checks whether the selected resources have siblings.<p> 389 * 390 * @return whether the selected resources have siblings 391 */ 392 private boolean hasSiblings() { 393 394 for (CmsResource res : m_context.getResources()) { 395 if (res.getSiblingCount() > 1) { 396 return true; 397 } 398 } 399 return false; 400 } 401 402 /** 403 * Indents a resources box.<p> 404 * 405 * @param resourceInfo the resource box 406 * 407 * @return an indented resource box 408 */ 409 private Component indent(CmsResourceInfo resourceInfo) { 410 411 boolean simple = false; 412 413 if (simple) { 414 return resourceInfo; 415 416 } else { 417 HorizontalLayout hl = new HorizontalLayout(); 418 Label label = new Label(""); 419 label.setWidth("35px"); 420 hl.addComponent(label); 421 hl.addComponent(resourceInfo); 422 hl.setExpandRatio(resourceInfo, 1.0f); 423 hl.setWidth("100%"); 424 return hl; 425 } 426 } 427 428}