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     * Checks if site export is enabled.
294     * @return true if site export is enabled
295     */
296    public boolean isExportEnabled() {
297
298        // Classes that extend CmsSiteManager must explicitly override this method if the feature should be enabled for them
299        return this.getClass() == CmsSiteManager.class;
300    }
301
302    /**
303     * Opens the delete dialog for the given sites.<p>
304     *
305     * @param data the site roots
306     */
307    public void openDeleteDialog(Set<String> data) {
308
309        CmsDeleteSiteDialog form = new CmsDeleteSiteDialog(this, data);
310        openDialog(form, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_DELETE_0));
311    }
312
313    /**
314     * Opens the edit site dialog.<p>
315     *
316     * @param siteRoot the site root of the site to edit, if <code>null</code>
317     */
318    public void openEditDialog(String siteRoot) {
319
320        CmsEditSiteForm form;
321        String caption;
322        if (siteRoot != null) {
323            form = new CmsEditSiteForm(m_rootCms, this, siteRoot);
324            caption = CmsVaadinUtils.getMessageText(
325                Messages.GUI_SITE_CONFIGURATION_EDIT_1,
326                m_sitesTable.getContainer().getItem(siteRoot).getItemProperty(TableProperty.Title).getValue());
327        } else {
328            form = new CmsEditSiteForm(m_rootCms, this);
329            caption = CmsVaadinUtils.getMessageText(Messages.GUI_SITE_ADD_0);
330        }
331        openDialog(form, caption);
332    }
333
334    /**
335     * Opens the global settings dialog.<p>
336     */
337    public void openSettingsDailog() {
338
339        CmsGlobalForm form = new CmsGlobalForm(this);
340        openDialog(form, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_GLOBAL_CONFIGURATION_0));
341    }
342
343    /**
344     * Opens the update server configuration dialog.<p>
345     */
346    public void openUpdateServerConfigDailog() {
347
348        CmsWebServerConfigForm form = new CmsWebServerConfigForm(this);
349        openDialog(form, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_WEBSERVERCONFIG_0));
350    }
351
352    /**
353     * Updates the general settings.<p>
354     *
355     * @param cms the cms to use
356     * @param defaultUri the default URI
357     * @param workplaceServers the workplace server URLs
358     * @param sharedFolder the shared folder URI
359     */
360    public void updateGeneralSettings(
361        CmsObject cms,
362        String defaultUri,
363        Map<String, CmsSSLMode> workplaceServers,
364        String sharedFolder) {
365
366        try {
367            OpenCms.getSiteManager().updateGeneralSettings(cms, defaultUri, workplaceServers, sharedFolder);
368            OpenCms.writeConfiguration(CmsSitesConfiguration.class);
369        } catch (Exception e) {
370            LOG.error(e.getLocalizedMessage(), e);
371            CmsErrorDialog.showErrorDialog(e);
372        }
373    }
374
375    /**
376     * @see org.opencms.ui.apps.I_CmsCRUDApp#writeElement(java.lang.Object)
377     */
378    public void writeElement(CmsSite element) {
379
380        try {
381            OpenCms.getSiteManager().updateSite(m_rootCms, getElement(element.getSiteRoot()), element);
382        } catch (CmsException e) {
383            LOG.error("Unabel to update site", e);
384        }
385        //updateInfo();
386        //m_sitesTable.loadSites();
387    }
388
389    /**
390     * Creates the table holdings all available sites.
391     * @return a vaadin table component
392     */
393
394    protected CmsSitesTable createSitesTable() {
395
396        CmsSitesTable table = new CmsSitesTable(this);
397        table.loadSites();
398        return table;
399    }
400
401    /**
402     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getBreadCrumbForState(java.lang.String)
403     */
404    @Override
405    protected LinkedHashMap<String, String> getBreadCrumbForState(String state) {
406
407        LinkedHashMap<String, String> crumbs = new LinkedHashMap<String, String>();
408        crumbs.put("", CmsVaadinUtils.getMessageText(Messages.GUI_SITE_MANAGER_TITLE_SHORT_0));
409        return crumbs;
410    }
411
412    /**
413     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getComponentForState(java.lang.String)
414     */
415    @Override
416    protected Component getComponentForState(String state) {
417
418        m_sitesTable = createSitesTable();
419
420        m_rootLayout.setMainHeightFull(true);
421        m_siteTableFilter = new TextField();
422        m_siteTableFilter.setIcon(FontOpenCms.FILTER);
423        m_siteTableFilter.setInputPrompt(
424            Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_FILTER_0));
425        m_siteTableFilter.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON);
426        m_siteTableFilter.setWidth("200px");
427        m_siteTableFilter.addTextChangeListener(new TextChangeListener() {
428
429            private static final long serialVersionUID = 1L;
430
431            public void textChange(TextChangeEvent event) {
432
433                m_sitesTable.filter(event.getText());
434            }
435        });
436        m_infoLayout.addComponent(m_siteTableFilter);
437        addToolbarButtons();
438        return m_sitesTable;
439    }
440
441    /**
442     * Returns the root cms object.<p>
443     *
444     * @return the root cms object
445     */
446    protected CmsObject getRootCmsObject() {
447
448        if (m_rootCms == null) {
449
450            m_rootCms = getOfflineCmsObject(A_CmsUI.getCmsObject());
451            m_rootCms.getRequestContext().setSiteRoot("");
452
453        }
454        return m_rootCms;
455    }
456
457    /**
458     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getSubNavEntries(java.lang.String)
459     */
460    @Override
461    protected List<NavEntry> getSubNavEntries(String state) {
462
463        return null;
464    }
465
466    /**
467     * Opens a given dialog.<p>
468     *
469     * @param dialog to be shown
470     * @param windowCaption caption of window
471     */
472    protected void openDialog(CmsBasicDialog dialog, String windowCaption) {
473
474        if (m_dialogWindow != null) {
475            m_dialogWindow.close();
476        }
477
478        m_dialogWindow = CmsBasicDialog.prepareWindow(DialogWidth.wide);
479        m_dialogWindow.setContent(dialog);
480        m_dialogWindow.setCaption(windowCaption);
481
482        A_CmsUI.get().addWindow(m_dialogWindow);
483        m_dialogWindow.center();
484    }
485
486    /**
487     * Update the info button.<p>
488     */
489    protected void updateInfo() {
490
491        m_infoButton.replaceData(getInfoMap());
492    }
493
494    /**
495     * Adds the toolbar buttons.<p>
496     */
497    private void addToolbarButtons() {
498
499        Button add = CmsToolBar.createButton(FontOpenCms.WAND, CmsVaadinUtils.getMessageText(Messages.GUI_SITE_ADD_0));
500        add.addClickListener(new ClickListener() {
501
502            private static final long serialVersionUID = 1L;
503
504            public void buttonClick(ClickEvent event) {
505
506                openEditDialog(null);
507            }
508        });
509        m_uiContext.addToolbarButton(add);
510
511        Button settings = CmsToolBar.createButton(
512            FontOpenCms.SETTINGS,
513            CmsVaadinUtils.getMessageText(Messages.GUI_SITE_GLOBAL_0));
514        settings.addClickListener(new ClickListener() {
515
516            private static final long serialVersionUID = 1L;
517
518            public void buttonClick(ClickEvent event) {
519
520                openSettingsDailog();
521            }
522        });
523        m_uiContext.addToolbarButton(settings);
524        if (OpenCms.getSiteManager().isConfigurableWebServer() || isLetsEncryptConfiguredForWebserverThread()) {
525            Button webServer = CmsToolBar.createButton(
526                FontAwesome.SERVER,
527                CmsVaadinUtils.getMessageText(Messages.GUI_SITE_WEBSERVERCONFIG_0));
528            webServer.addClickListener(new ClickListener() {
529
530                private static final long serialVersionUID = 1L;
531
532                public void buttonClick(ClickEvent event) {
533
534                    openUpdateServerConfigDailog();
535                }
536            });
537            m_uiContext.addToolbarButton(webServer);
538        }
539
540        m_infoButton = new CmsInfoButton(getInfoMap());
541
542        m_infoButton.setWindowCaption(CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_CAPTION_0));
543        m_infoButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_CAPTION_0));
544        m_uiContext.addToolbarButton(m_infoButton);
545    }
546
547    /**
548     * Get info map.<p>
549     *
550     * @return map of sites info
551     */
552    private Map<String, String> getInfoMap() {
553
554        Map<String, String> infos = new LinkedHashMap<String, String>();
555        int corruptedSites = getCorruptedSites().size();
556        infos.put(
557            CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_NUM_WEBSITES_0),
558            String.valueOf(getAllElements().size() + corruptedSites));
559
560        if (corruptedSites > 0) {
561            infos.put(
562                CmsVaadinUtils.getMessageText(Messages.GUI_SITE_STATISTICS_NUM_CORRUPTED_WEBSITES_0),
563                String.valueOf(corruptedSites));
564        }
565
566        return infos;
567    }
568}