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}