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.apps.sitemanager; 029 030import org.opencms.configuration.CmsSitesConfiguration; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.letsencrypt.CmsLetsEncryptConfiguration.Trigger; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.main.OpenCms; 037import org.opencms.site.CmsSSLMode; 038import org.opencms.site.CmsSite; 039import org.opencms.ui.A_CmsUI; 040import org.opencms.ui.CmsCssIcon; 041import org.opencms.ui.CmsVaadinUtils; 042import org.opencms.ui.FontOpenCms; 043import org.opencms.ui.apps.A_CmsWorkplaceApp; 044import org.opencms.ui.apps.I_CmsAppUIContext; 045import org.opencms.ui.apps.I_CmsCRUDApp; 046import org.opencms.ui.apps.Messages; 047import org.opencms.ui.apps.sitemanager.CmsSitesTable.TableProperty; 048import org.opencms.ui.components.CmsBasicDialog; 049import org.opencms.ui.components.CmsBasicDialog.DialogWidth; 050import org.opencms.ui.components.CmsErrorDialog; 051import org.opencms.ui.components.CmsInfoButton; 052import org.opencms.ui.components.CmsToolBar; 053import org.opencms.ui.components.OpenCmsTheme; 054import org.opencms.util.CmsStringUtil; 055 056import java.util.LinkedHashMap; 057import java.util.List; 058import java.util.Map; 059import java.util.Set; 060import java.util.stream.Collectors; 061 062import org.apache.commons.logging.Log; 063 064import com.vaadin.server.ExternalResource; 065import com.vaadin.server.FontAwesome; 066import com.vaadin.server.Resource; 067import com.vaadin.ui.Button; 068import com.vaadin.ui.Button.ClickEvent; 069import com.vaadin.ui.Button.ClickListener; 070import com.vaadin.ui.Component; 071import com.vaadin.ui.UI; 072import com.vaadin.ui.Window; 073import com.vaadin.ui.themes.ValoTheme; 074import com.vaadin.v7.event.FieldEvents.TextChangeEvent; 075import com.vaadin.v7.event.FieldEvents.TextChangeListener; 076import com.vaadin.v7.ui.TextField; 077 078/** 079 * Manager class for the Site manager app. 080 */ 081 082public class CmsSiteManager extends A_CmsWorkplaceApp implements I_CmsCRUDApp<CmsSite> { 083 084 /**Bundel name for the sites which are used as templates for new sites.*/ 085 public static final String BUNDLE_NAME = "siteMacroBundle"; 086 087 /**Constant.*/ 088 public static final String FAVICON = "favicon.ico"; 089 090 /** Name of the macros folder for site templates.*/ 091 public static final String MACRO_FOLDER = ".macros"; 092 093 /** The add project path name. */ 094 public static final String PATH_NAME_ADD = "newSite"; 095 096 /** The edit project path name. */ 097 public static final String PATH_NAME_EDIT = "editSite"; 098 099 /**The global settings path name. */ 100 public static final String PATH_NAME_GLOBAL = "global"; 101 102 /**The webserver setting path name. */ 103 public static final String PATH_NAME_WEBSERVER = "webserver"; 104 105 /**path attribute to transmit root of a site to be edited. */ 106 public static final String SITE_ROOT = "siteRoot"; 107 108 /** The logger for this class. */ 109 static Log LOG = CmsLog.getLog(CmsSiteManager.class.getName()); 110 111 /**Path to the sites folder.*/ 112 static final String PATH_SITES = "/sites/"; 113 114 /** The currently opened dialog window. */ 115 protected Window m_dialogWindow; 116 117 /** The site table. */ 118 protected CmsSitesTable m_sitesTable; 119 120 /** The file table filter input. */ 121 protected TextField m_siteTableFilter; 122 123 /**Info Button. */ 124 private CmsInfoButton m_infoButton; 125 126 /**The publish button.*/ 127 private Button m_publishButton; 128 129 /** The root cms object. */ 130 private CmsObject m_rootCms; 131 132 /** 133 * Method to check if a folder under given path contains a bundle for macro resolving.<p> 134 * 135 * @param cms CmsObject 136 * @param folderPathRoot root path of folder 137 * @return true if macros bundle found 138 */ 139 public static boolean isFolderWithMacros(CmsObject cms, String folderPathRoot) { 140 141 if (!CmsResource.isFolder(folderPathRoot)) { 142 folderPathRoot = folderPathRoot.concat("/"); 143 } 144 try { 145 cms.readResource(folderPathRoot + MACRO_FOLDER); 146 cms.readResource(folderPathRoot + MACRO_FOLDER + "/" + BUNDLE_NAME + "_desc"); 147 } catch (CmsException e) { 148 return false; 149 } 150 return true; 151 } 152 153 /** 154 * Check if LetsEncrypt updates are configured to be triggered by webserver configuration updates.<p> 155 * 156 * @return true if LetsEncrypt updates are configured to be triggered by webserver configuration updates 157 */ 158 public static boolean isLetsEncryptConfiguredForWebserverThread() { 159 160 return (OpenCms.getLetsEncryptConfig() != null) 161 && OpenCms.getLetsEncryptConfig().isValidAndEnabled() 162 && (OpenCms.getLetsEncryptConfig().getTrigger() == Trigger.webserverThread); 163 } 164 165 /** 166 * Centers the currently open window. 167 */ 168 public void centerWindow() { 169 170 if (m_dialogWindow != null) { 171 m_dialogWindow.center(); 172 } 173 } 174 175 /** 176 * Closes the current dialog window and updates the sites table if requested.<p> 177 * 178 * @param updateTable <code>true</code> to update the sites table 179 */ 180 public void closeDialogWindow(boolean updateTable) { 181 182 if (m_dialogWindow != null) { 183 m_dialogWindow.close(); 184 m_dialogWindow = null; 185 } 186 if (updateTable) { 187 final String filter = m_siteTableFilter.getValue(); 188 // reload the sites, but reset the filter first because loadSites in subclasses may not work right 189 // for filtered lists. Restore the filter afterwards. 190 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(filter)) { 191 m_sitesTable.filter(null); 192 } 193 try { 194 m_sitesTable.loadSites(); 195 } finally { 196 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(filter)) { 197 m_sitesTable.filter(filter); 198 } 199 } 200 } 201 } 202 203 /** 204 * @see org.opencms.ui.apps.I_CmsCRUDApp#createElement(java.lang.Object) 205 */ 206 public void createElement(CmsSite element) { 207 208 try { 209 OpenCms.getSiteManager().addSite(getRootCmsObject(), element); 210 } catch (CmsException e) { 211 LOG.error("unable to save site", e); 212 } 213 214 } 215 216 /** 217 * @see org.opencms.ui.apps.I_CmsCRUDApp#defaultAction(java.lang.String) 218 */ 219 public void defaultAction(String elementId) { 220 221 openEditDialog(elementId); 222 223 } 224 225 /** 226 * @see org.opencms.ui.apps.I_CmsCRUDApp#deleteElements(java.util.List) 227 */ 228 public void deleteElements(List<String> elementId) { 229 230 for (String siteRoot : elementId) { 231 try { 232 CmsSite site = getElement(siteRoot); 233 if (site != null) { 234 OpenCms.getSiteManager().removeSite(getRootCmsObject(), site); 235 } 236 } catch (CmsException e) { 237 LOG.error("Unable to delete site", e); 238 } 239 } 240 updateInfo(); 241 } 242 243 /** 244 * @see org.opencms.ui.apps.I_CmsCRUDApp#getAllElements() 245 */ 246 public List<CmsSite> getAllElements() { 247 248 List<CmsSite> res = OpenCms.getSiteManager().getAvailableSites(getRootCmsObject(), false).stream().filter( 249 site -> !site.isGenerated()).collect(Collectors.toList()); 250 return res; 251 } 252 253 /** 254 * Get corrupted sites.<p> 255 * 256 * @return List<CmsSite> 257 */ 258 public List<CmsSite> getCorruptedSites() { 259 260 return OpenCms.getSiteManager().getAvailableCorruptedSites(getRootCmsObject(), true); 261 } 262 263 /** 264 * @see org.opencms.ui.apps.I_CmsCRUDApp#getElement(java.lang.String) 265 */ 266 public CmsSite getElement(String elementId) { 267 268 return OpenCms.getSiteManager().getSiteForSiteRoot(elementId); 269 } 270 271 /** 272 * Returns the fav icon path for the given site.<p> 273 * 274 * @param siteRoot the site root 275 * 276 * @return the icon path 277 */ 278 public Resource getFavIcon(String siteRoot) { 279 280 CmsResource iconResource = null; 281 try { 282 iconResource = getRootCmsObject().readResource(siteRoot + "/" + CmsSiteManager.FAVICON); 283 } catch (CmsException e) { 284 //no favicon there 285 } 286 if (iconResource != null) { 287 return new ExternalResource( 288 OpenCms.getLinkManager().getPermalink(getRootCmsObject(), iconResource.getRootPath())); 289 } 290 return new CmsCssIcon(OpenCmsTheme.ICON_SITE); 291 } 292 293 /** 294 * @see org.opencms.ui.apps.A_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext) 295 */ 296 @Override 297 public void initUI(I_CmsAppUIContext context) { 298 299 context.addPublishButton(changes -> { 300 A_CmsUI.get().reload(); 301 }); 302 super.initUI(context); 303 } 304 305 /** 306 * Checks if site export is enabled. 307 * @return true if site export is enabled 308 */ 309 public boolean isExportEnabled() { 310 311 // Classes that extend CmsSiteManager must explicitly override this method if the feature should be enabled for them 312 return this.getClass() == CmsSiteManager.class; 313 } 314 315 /** 316 * Opens the delete dialog for the given sites.<p> 317 * 318 * @param data the site roots 319 */ 320 public void openDeleteDialog(Set<String> data) { 321 322 CmsDeleteSiteDialog form = new CmsDeleteSiteDialog(this, data); 323 openDialog(form, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_DELETE_0)); 324 } 325 326 /** 327 * Opens the edit site dialog.<p> 328 * 329 * @param siteRoot the site root of the site to edit, if <code>null</code> 330 */ 331 public void openEditDialog(String siteRoot) { 332 333 CmsEditSiteForm form; 334 String caption; 335 if (siteRoot != null) { 336 form = new CmsEditSiteForm(m_rootCms, this, siteRoot); 337 caption = CmsVaadinUtils.getMessageText( 338 Messages.GUI_SITE_CONFIGURATION_EDIT_1, 339 m_sitesTable.getContainer().getItem(siteRoot).getItemProperty(TableProperty.Title).getValue()); 340 } else { 341 form = new CmsEditSiteForm(m_rootCms, this); 342 caption = CmsVaadinUtils.getMessageText(Messages.GUI_SITE_ADD_0); 343 } 344 openDialog(form, caption); 345 } 346 347 /** 348 * Opens the global settings dialog.<p> 349 */ 350 public void openSettingsDailog() { 351 352 CmsGlobalForm form = new CmsGlobalForm(this); 353 openDialog(form, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_GLOBAL_CONFIGURATION_0)); 354 } 355 356 /** 357 * Opens the update server configuration dialog.<p> 358 */ 359 public void openUpdateServerConfigDailog() { 360 361 CmsWebServerConfigForm form = new CmsWebServerConfigForm(this); 362 openDialog(form, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_WEBSERVERCONFIG_0)); 363 } 364 365 /** 366 * Updates the general settings.<p> 367 * 368 * @param cms the cms to use 369 * @param defaultUri the default URI 370 * @param workplaceServers the workplace server URLs 371 * @param sharedFolder the shared folder URI 372 */ 373 public void updateGeneralSettings( 374 CmsObject cms, 375 String defaultUri, 376 Map<String, CmsSSLMode> workplaceServers, 377 String sharedFolder) { 378 379 try { 380 OpenCms.getSiteManager().updateGeneralSettings(cms, defaultUri, workplaceServers, sharedFolder); 381 OpenCms.writeConfiguration(CmsSitesConfiguration.class); 382 } catch (Exception e) { 383 LOG.error(e.getLocalizedMessage(), e); 384 CmsErrorDialog.showErrorDialog(e); 385 } 386 } 387 388 /** 389 * @see org.opencms.ui.apps.I_CmsCRUDApp#writeElement(java.lang.Object) 390 */ 391 public void writeElement(CmsSite element) { 392 393 try { 394 OpenCms.getSiteManager().updateSite(m_rootCms, getElement(element.getSiteRoot()), element); 395 } catch (CmsException e) { 396 LOG.error("Unabel to update site", e); 397 } 398 //updateInfo(); 399 //m_sitesTable.loadSites(); 400 } 401 402 /** 403 * Creates the table holdings all available sites. 404 * @return a vaadin table component 405 */ 406 407 protected CmsSitesTable createSitesTable() { 408 409 CmsSitesTable table = new CmsSitesTable(this); 410 table.loadSites(); 411 return table; 412 } 413 414 /** 415 * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getBreadCrumbForState(java.lang.String) 416 */ 417 @Override 418 protected LinkedHashMap<String, String> getBreadCrumbForState(String state) { 419 420 LinkedHashMap<String, String> crumbs = new LinkedHashMap<String, String>(); 421 crumbs.put("", CmsVaadinUtils.getMessageText(Messages.GUI_SITE_MANAGER_TITLE_SHORT_0)); 422 return crumbs; 423 } 424 425 /** 426 * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getComponentForState(java.lang.String) 427 */ 428 @Override 429 protected Component getComponentForState(String state) { 430 431 m_sitesTable = createSitesTable(); 432 433 m_rootLayout.setMainHeightFull(true); 434 m_siteTableFilter = new TextField(); 435 m_siteTableFilter.setIcon(FontOpenCms.FILTER); 436 m_siteTableFilter.setInputPrompt( 437 Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_FILTER_0)); 438 m_siteTableFilter.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); 439 m_siteTableFilter.setWidth("200px"); 440 m_siteTableFilter.addTextChangeListener(new TextChangeListener() { 441 442 private static final long serialVersionUID = 1L; 443 444 public void textChange(TextChangeEvent event) { 445 446 m_sitesTable.filter(event.getText()); 447 } 448 }); 449 m_infoLayout.addComponent(m_siteTableFilter); 450 addToolbarButtons(); 451 return m_sitesTable; 452 } 453 454 /** 455 * Returns the root cms object.<p> 456 * 457 * @return the root cms object 458 */ 459 protected CmsObject getRootCmsObject() { 460 461 if (m_rootCms == null) { 462 463 m_rootCms = getOfflineCmsObject(A_CmsUI.getCmsObject()); 464 m_rootCms.getRequestContext().setSiteRoot(""); 465 466 } 467 return m_rootCms; 468 } 469 470 /** 471 * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getSubNavEntries(java.lang.String) 472 */ 473 @Override 474 protected List<NavEntry> getSubNavEntries(String state) { 475 476 return null; 477 } 478 479 /** 480 * Opens a given dialog.<p> 481 * 482 * @param dialog to be shown 483 * @param windowCaption caption of window 484 */ 485 protected void openDialog(CmsBasicDialog dialog, String windowCaption) { 486 487 if (m_dialogWindow != null) { 488 m_dialogWindow.close(); 489 } 490 491 m_dialogWindow = CmsBasicDialog.prepareWindow(DialogWidth.wide); 492 m_dialogWindow.setContent(dialog); 493 m_dialogWindow.setCaption(windowCaption); 494 495 A_CmsUI.get().addWindow(m_dialogWindow); 496 m_dialogWindow.center(); 497 } 498 499 /** 500 * Update the info button.<p> 501 */ 502 protected void updateInfo() { 503 504 m_infoButton.replaceData(getInfoMap()); 505 } 506 507 /** 508 * Adds the toolbar buttons.<p> 509 */ 510 private void addToolbarButtons() { 511 512 Button add = CmsToolBar.createButton(FontOpenCms.WAND, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_ADD_0)); 513 add.addClickListener(new ClickListener() { 514 515 private static final long serialVersionUID = 1L; 516 517 public void buttonClick(ClickEvent event) { 518 519 openEditDialog(null); 520 } 521 }); 522 m_uiContext.addToolbarButton(add); 523 524 Button settings = CmsToolBar.createButton( 525 FontOpenCms.SETTINGS, 526 CmsVaadinUtils.getMessageText(Messages.GUI_SITE_GLOBAL_0)); 527 settings.addClickListener(new ClickListener() { 528 529 private static final long serialVersionUID = 1L; 530 531 public void buttonClick(ClickEvent event) { 532 533 openSettingsDailog(); 534 } 535 }); 536 m_uiContext.addToolbarButton(settings); 537 if (OpenCms.getSiteManager().isConfigurableWebServer() || isLetsEncryptConfiguredForWebserverThread()) { 538 Button webServer = CmsToolBar.createButton( 539 FontAwesome.SERVER, 540 CmsVaadinUtils.getMessageText(Messages.GUI_SITE_WEBSERVERCONFIG_0)); 541 webServer.addClickListener(new ClickListener() { 542 543 private static final long serialVersionUID = 1L; 544 545 public void buttonClick(ClickEvent event) { 546 547 openUpdateServerConfigDailog(); 548 } 549 }); 550 m_uiContext.addToolbarButton(webServer); 551 } 552 553 m_infoButton = new CmsInfoButton(getInfoMap()); 554 555 m_infoButton.setWindowCaption(CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_CAPTION_0)); 556 m_infoButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_CAPTION_0)); 557 m_uiContext.addToolbarButton(m_infoButton); 558 } 559 560 /** 561 * Get info map.<p> 562 * 563 * @return map of sites info 564 */ 565 private Map<String, String> getInfoMap() { 566 567 Map<String, String> infos = new LinkedHashMap<String, String>(); 568 int corruptedSites = getCorruptedSites().size(); 569 infos.put( 570 CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_NUM_WEBSITES_0), 571 String.valueOf(getAllElements().size() + corruptedSites)); 572 573 if (corruptedSites > 0) { 574 infos.put( 575 CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_NUM_CORRUPTED_WEBSITES_0), 576 String.valueOf(corruptedSites)); 577 } 578 579 return infos; 580 } 581}