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