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.unusedcontentfinder;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsProject;
033import org.opencms.file.CmsProperty;
034import org.opencms.file.CmsPropertyDefinition;
035import org.opencms.file.CmsResource;
036import org.opencms.file.CmsResourceFilter;
037import org.opencms.file.types.CmsResourceTypeXmlAdeConfiguration;
038import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
039import org.opencms.file.types.CmsResourceTypeXmlContent;
040import org.opencms.file.types.I_CmsResourceType;
041import org.opencms.main.CmsException;
042import org.opencms.main.CmsLog;
043import org.opencms.main.OpenCms;
044import org.opencms.relations.CmsRelation;
045import org.opencms.relations.CmsRelationFilter;
046import org.opencms.ui.A_CmsUI;
047import org.opencms.ui.CmsVaadinUtils;
048import org.opencms.ui.I_CmsDialogContext;
049import org.opencms.ui.I_CmsDialogContext.ContextType;
050import org.opencms.ui.apps.CmsAppWorkplaceUi;
051import org.opencms.ui.apps.I_CmsContextProvider;
052import org.opencms.ui.apps.Messages;
053import org.opencms.ui.components.CmsComponentState;
054import org.opencms.ui.components.CmsErrorDialog;
055import org.opencms.ui.components.CmsFileTable;
056import org.opencms.ui.components.CmsFileTableDialogContext;
057import org.opencms.ui.components.CmsFolderSelector;
058import org.opencms.ui.components.CmsResourceTableProperty;
059import org.opencms.ui.components.CmsResultFilterComponent;
060import org.opencms.ui.components.CmsSiteSelector;
061import org.opencms.ui.components.CmsTypeSelector;
062import org.opencms.ui.components.OpenCmsTheme;
063import org.opencms.ui.contextmenu.CmsResourceContextMenuBuilder;
064import org.opencms.ui.dialogs.CmsDeleteDialog;
065import org.opencms.util.CmsStringUtil;
066import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
067import org.opencms.xml.CmsXmlContentDefinition;
068import org.opencms.xml.content.I_CmsXmlContentHandler;
069
070import java.util.ArrayList;
071import java.util.List;
072import java.util.Set;
073
074import org.apache.commons.logging.Log;
075
076import com.vaadin.ui.Alignment;
077import com.vaadin.ui.Button;
078import com.vaadin.ui.Button.ClickEvent;
079import com.vaadin.ui.Component;
080import com.vaadin.ui.FormLayout;
081import com.vaadin.v7.data.Property.ValueChangeEvent;
082import com.vaadin.v7.data.Property.ValueChangeListener;
083import com.vaadin.v7.data.util.filter.Or;
084import com.vaadin.v7.data.util.filter.SimpleStringFilter;
085import com.vaadin.v7.event.FieldEvents.TextChangeEvent;
086import com.vaadin.v7.event.FieldEvents.TextChangeListener;
087import com.vaadin.v7.ui.HorizontalLayout;
088import com.vaadin.v7.ui.VerticalLayout;
089
090/**
091 * Component that realizes a unused content finder.
092 */
093@SuppressWarnings("deprecation")
094public class CmsUnusedContentFinderComposite {
095
096    /**
097     * The delete button of the unused content finder.
098     */
099    private class DeleteButtonComponent extends Button {
100
101        /** Serial version id. */
102        private static final long serialVersionUID = 1L;
103
104        /**
105         * Creates a new delete button.
106         */
107        DeleteButtonComponent() {
108
109            String caption = CmsVaadinUtils.getMessageText(Messages.GUI_UNUSED_CONTENT_FINDER_DELETE_ALL_0);
110            addStyleName(OpenCmsTheme.BUTTON_RED);
111            setCaption(caption);
112            setVisible(false);
113            addClickListener(new ClickListener() {
114
115                private static final long serialVersionUID = 1L;
116
117                public void buttonClick(ClickEvent event) {
118
119                    m_resultComponent.getFileTable().selectAll();
120                    I_CmsDialogContext context = m_resultComponent.getFileTable().getContextProvider().getDialogContext();
121                    context.start(caption, new CmsDeleteDialog(context));
122                }
123            });
124        }
125    }
126
127    /**
128     * The form component of the unused content finder.
129     */
130    private class FormComponent extends VerticalLayout {
131
132        /** Serial version id. */
133        private static final long serialVersionUID = 1L;
134
135        /** The form layout. */
136        private FormLayout m_formLayout;
137
138        /** The site selector. */
139        private CmsSiteSelector m_siteSelector;
140
141        /** The folder selector. */
142        private CmsFolderSelector m_folderSelector;
143
144        /** The type selector. */
145        private CmsTypeSelector m_typeSelector;
146
147        /** The search button. */
148        private Button m_searchButton;
149
150        /**
151         * Creates a new form component.
152         */
153        FormComponent() {
154
155            setMargin(true);
156            setSpacing(true);
157            m_formLayout = new FormLayout();
158            m_formLayout.setMargin(true);
159            m_formLayout.setSpacing(true);
160            m_formLayout.addStyleName("o-formlayout-narrow");
161            initSiteSelector();
162            initFolderSelector();
163            initTypeSelector();
164            m_formLayout.addComponent(new VerticalLayout()); // fix layout bug
165            addComponent(m_formLayout);
166            initSearchButton();
167            initDeleteButton();
168        }
169
170        /**
171         * Returns the selected folder value.
172         * @return the selected folder value
173         */
174        String getFolderValue() {
175
176            return m_folderSelector.getValue();
177        }
178
179        /**
180         * Returns the selected site value.
181         * @return the selected site value
182         */
183        String getSiteValue() {
184
185            return (String)m_siteSelector.getValue();
186        }
187
188        /**
189         * Returns the selected type value.
190         * @return the selected type value
191         */
192        I_CmsResourceType getTypeValue() {
193
194            return (I_CmsResourceType)m_typeSelector.getValue();
195        }
196
197        /**
198         * Sets the folder selector value.
199         * @param folder the folder to set
200         */
201        void setFolderValue(String folder) {
202
203            m_folderSelector.setValue(folder);
204        }
205
206        /**
207         * Sets the site selector value.
208         * @param site the site to set
209         */
210        void setSiteValue(String site) {
211
212            m_siteSelector.setValue(site);
213        }
214
215        /**
216         * Sets the type selector value.
217         * @param type the type to set
218         */
219        void setTypeValue(I_CmsResourceType type) {
220
221            m_typeSelector.setValue(type);
222        }
223
224        /**
225         * Updates the search root.
226         * @throws CmsException if initializing the CMS object fails
227         */
228        void updateSearchRoot() throws CmsException {
229
230            CmsObject newCms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
231            newCms.getRequestContext().setSiteRoot((String)m_siteSelector.getValue());
232            m_folderSelector.setCmsObject(newCms);
233            m_folderSelector.setValue("/");
234        }
235
236        /**
237         * Initializes the delete button.
238         */
239        private void initDeleteButton() {
240
241            HorizontalLayout layout = new HorizontalLayout();
242            layout.setMargin(true);
243            layout.setSpacing(true);
244            layout.setWidth("100%");
245            layout.setStyleName("o-dialog-button-bar");
246            layout.addComponent(m_deleteButtonComponent);
247            layout.setComponentAlignment(m_deleteButtonComponent, Alignment.TOP_RIGHT);
248            addComponent(layout);
249        }
250
251        /**
252         * Initializes the folder selector.
253         */
254        private void initFolderSelector() {
255
256            m_folderSelector = new CmsFolderSelector();
257            m_formLayout.addComponent(m_folderSelector);
258        }
259
260        /**
261         * Initializes the search button.
262         */
263        private void initSearchButton() {
264
265            HorizontalLayout layout = new HorizontalLayout();
266            layout.setMargin(true);
267            layout.setSpacing(true);
268            layout.setWidth("100%");
269            layout.setStyleName("o-dialog-button-bar");
270            m_searchButton = new Button(
271                CmsVaadinUtils.getMessageText(Messages.GUI_SOURCESEARCH_SEARCH_0),
272                new Button.ClickListener() {
273
274                    private static final long serialVersionUID = 1L;
275
276                    public void buttonClick(ClickEvent event) {
277
278                        search(true);
279
280                    }
281                });
282            m_searchButton.addStyleName(OpenCmsTheme.BUTTON_BLUE);
283            layout.addComponent(m_searchButton);
284            layout.setComponentAlignment(m_searchButton, Alignment.TOP_RIGHT);
285            addComponent(layout);
286        }
287
288        /**
289         * Initializes the site selector.
290         */
291        @SuppressWarnings("serial")
292        private void initSiteSelector() {
293
294            m_siteSelector = new CmsSiteSelector();
295            m_siteSelector.setWidthFull();
296            m_siteSelector.addValueChangeListener(new ValueChangeListener() {
297
298                public void valueChange(ValueChangeEvent event) {
299
300                    try {
301                        updateSearchRoot();
302                    } catch (CmsException e) {
303                        LOG.error("Unable to initialize CmsObject", e);
304                    }
305                }
306
307            });
308            m_formLayout.addComponent(m_siteSelector);
309        }
310
311        /**
312         * Initializes the type selector.
313         */
314        private void initTypeSelector() {
315
316            m_typeSelector = new CmsTypeSelector();
317            m_typeSelector.updateTypes(getAvailableTypes());
318            m_formLayout.addComponent(m_typeSelector);
319        }
320    }
321
322    /**
323     * The result component of the unused content finder.
324     */
325    private class ResultComponent extends VerticalLayout {
326
327        /** Serial version id. */
328        private static final long serialVersionUID = 1L;
329
330        /** The file table. */
331        CmsFileTable m_fileTable;
332
333        /** Layout showing empty result message. */
334        private VerticalLayout m_infoEmptyResult;
335
336        /** Layout showing introduction message. */
337        private VerticalLayout m_infoIntroLayout;
338
339        /** Layout showing too many results message. */
340        private VerticalLayout m_infoTooManyResults;
341
342        /** Layout showing too invalid folder message. */
343        private VerticalLayout m_infoInvalidFolder;
344
345        /**
346         * Creates a new result component.
347         */
348        ResultComponent() {
349
350            setSizeFull();
351            initInfoIntroLayout();
352            initInfoEmptyResult();
353            initInfoTooManyResults();
354            initInfoInvalidFolder();
355            initFileTable();
356        }
357
358        /**
359         * Returns the file table.
360         * @return the file table
361         */
362        CmsFileTable getFileTable() {
363
364            return m_fileTable;
365        }
366
367        /**
368         * Updates the search result.
369         */
370        void updateResult() {
371
372            if (!isValidFolder()) {
373                showInfoInvalidFolder();
374                return;
375            }
376            CmsObject cms = getOfflineCms();
377            I_CmsResourceType type = m_formComponent.getTypeValue();
378            String folderName = m_formComponent.getFolderValue();
379            List<CmsResource> resourcesToShow = new ArrayList<CmsResource>();
380            boolean tooManyResults = false;
381            if (cms.existsResource(folderName)) {
382                try {
383                    List<I_CmsResourceType> resourceTypes = new ArrayList<>();
384                    if (type == null) {
385                        resourceTypes = getAvailableTypes();
386                    } else {
387                        resourceTypes.add(type);
388                    }
389                    for (I_CmsResourceType resourceType : resourceTypes) {
390                        CmsResourceFilter filter = CmsResourceFilter.ONLY_VISIBLE.addRequireType(resourceType);
391                        List<CmsResource> resources = cms.readResources(folderName, filter);
392                        for (CmsResource resource : resources) {
393                            if (isExcludedByProperties(resource, false)
394                                || isUsedByOtherContents(resource, false)
395                                || isUsedByOtherContents(resource, true)) {
396                                // resource is excluded by properties in the offline project
397                                // resource is used by other contents in the offline project
398                                // resource is used by other contents in the online project
399                            } else {
400                                resourcesToShow.add(resource);
401                            }
402                            if (resourcesToShow.size() > MAX_RESULTS) {
403                                tooManyResults = true;
404                                break;
405                            }
406                        }
407                    }
408                    m_fileTable.fillTable(cms, resourcesToShow);
409                } catch (CmsException e) {
410                    CmsErrorDialog.showErrorDialog(e);
411                }
412            }
413            m_infoIntroLayout.setVisible(false);
414            if (resourcesToShow.isEmpty()) {
415                showInfoEmptyResult();
416                m_deleteButtonComponent.setVisible(false);
417            } else if (tooManyResults) {
418                showInfoTooManyResults();
419                m_deleteButtonComponent.setVisible(false);
420            } else {
421                showFileTable();
422                m_deleteButtonComponent.setVisible(true);
423            }
424        }
425
426        /**
427         * Initializes the file table.
428         */
429        private void initFileTable() {
430
431            m_fileTable = new CmsFileTable(null) {
432
433                private static final long serialVersionUID = 1L;
434
435                /**
436                 * Path, title, and type columns shall be visible and not collapsed.
437                 * @see org.opencms.ui.components.CmsFileTable#applyWorkplaceAppSettings()
438                 */
439                @Override
440                public void applyWorkplaceAppSettings() {
441
442                    super.applyWorkplaceAppSettings();
443                    m_fileTable.setColumnCollapsed(CmsResourceTableProperty.PROPERTY_SIZE, true);
444                    m_fileTable.setColumnCollapsed(CmsResourceTableProperty.PROPERTY_INTERNAL_RESOURCE_TYPE, false);
445                }
446
447                /**
448                 * Filter by path, title, and type names.
449                 * @see org.opencms.ui.components.CmsFileTable#filterTable(java.lang.String)
450                 */
451                @Override
452                public void filterTable(String search) {
453
454                    m_container.removeAllContainerFilters();
455                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) {
456                        m_container.addContainerFilter(
457                            new Or(
458                                new SimpleStringFilter(
459                                    CmsResourceTableProperty.PROPERTY_SITE_PATH,
460                                    search,
461                                    true,
462                                    false),
463                                new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_TITLE, search, true, false),
464                                new SimpleStringFilter(
465                                    CmsResourceTableProperty.PROPERTY_RESOURCE_TYPE,
466                                    search,
467                                    true,
468                                    false),
469                                new SimpleStringFilter(
470                                    CmsResourceTableProperty.PROPERTY_INTERNAL_RESOURCE_TYPE,
471                                    search,
472                                    true,
473                                    false)));
474                    }
475                    if ((m_fileTable.getValue() != null) & !((Set<?>)m_fileTable.getValue()).isEmpty()) {
476                        m_fileTable.setCurrentPageFirstItemId(((Set<?>)m_fileTable.getValue()).iterator().next());
477                    }
478                }
479            };
480            m_fileTable.applyWorkplaceAppSettings();
481            m_fileTable.setContextProvider(new I_CmsContextProvider() {
482
483                /**
484                 * @see org.opencms.ui.apps.I_CmsContextProvider#getDialogContext()
485                 */
486                public I_CmsDialogContext getDialogContext() {
487
488                    CmsFileTableDialogContext context = new CmsFileTableDialogContext(
489                        CmsUnusedContentFinderConfiguration.APP_ID,
490                        ContextType.fileTable,
491                        m_fileTable,
492                        m_fileTable.getSelectedResources());
493                    return context;
494                }
495            });
496            m_fileTable.setSizeFull();
497            m_fileTable.setMenuBuilder(new CmsResourceContextMenuBuilder());
498            m_fileTable.setVisible(false);
499            addComponent(m_fileTable);
500            setExpandRatio(m_fileTable, 2);
501        }
502
503        /**
504         * Initializes the empty result info box.
505         */
506        private void initInfoEmptyResult() {
507
508            m_infoEmptyResult = CmsVaadinUtils.getInfoLayout(Messages.GUI_SOURCESEARCH_EMPTY_0);
509            m_infoEmptyResult.setVisible(false);
510            addComponent(m_infoEmptyResult);
511        }
512
513        /**
514         * Initializes the intro info box.
515         */
516        private void initInfoIntroLayout() {
517
518            m_infoIntroLayout = CmsVaadinUtils.getInfoLayout(Messages.GUI_SOURCESEARCH_INTRO_0);
519            m_infoIntroLayout.setVisible(true);
520            addComponent(m_infoIntroLayout);
521        }
522
523        /**
524         * Initializes the invalid folder info box.
525         */
526        private void initInfoInvalidFolder() {
527
528            m_infoInvalidFolder = CmsVaadinUtils.getInfoLayout(Messages.GUI_UNUSED_CONTENT_FINDER_INVALID_FOLDER_0);
529            m_infoInvalidFolder.setVisible(false);
530            addComponent(m_infoInvalidFolder);
531        }
532
533        /**
534         * Initializes the too many results info box.
535         */
536        private void initInfoTooManyResults() {
537
538            m_infoTooManyResults = CmsVaadinUtils.getInfoLayout(Messages.GUI_UNUSED_CONTENT_FINDER_TOO_MANY_RESULTS_0);
539            m_infoTooManyResults.setVisible(false);
540            addComponent(m_infoTooManyResults);
541        }
542
543        /**
544         * Shows the file table.
545         */
546        private void showFileTable() {
547
548            m_infoIntroLayout.setVisible(false);
549            m_infoTooManyResults.setVisible(false);
550            m_infoInvalidFolder.setVisible(false);
551            m_infoEmptyResult.setVisible(false);
552            m_fileTable.setVisible(true);
553        }
554
555        /**
556         * Shows the empty result info box.
557         */
558        private void showInfoEmptyResult() {
559
560            m_fileTable.setVisible(false);
561            m_infoIntroLayout.setVisible(false);
562            m_infoTooManyResults.setVisible(false);
563            m_infoInvalidFolder.setVisible(false);
564            m_infoEmptyResult.setVisible(true);
565        }
566
567        /**
568         * Shows the invalid folder info box.
569         */
570        private void showInfoInvalidFolder() {
571
572            m_fileTable.setVisible(false);
573            m_infoIntroLayout.setVisible(false);
574            m_infoTooManyResults.setVisible(false);
575            m_infoEmptyResult.setVisible(false);
576            m_infoInvalidFolder.setVisible(true);
577        }
578
579        /**
580         * Shows the empty result info box.
581         */
582        private void showInfoTooManyResults() {
583
584            m_fileTable.setVisible(false);
585            m_infoIntroLayout.setVisible(false);
586            m_infoEmptyResult.setVisible(false);
587            m_infoInvalidFolder.setVisible(false);
588            m_infoTooManyResults.setVisible(true);
589        }
590    }
591
592    /**
593     * Component to filter the result table.
594     */
595    @SuppressWarnings("serial")
596    private class ResultFilterComponent extends CmsResultFilterComponent {
597
598        /**
599         * Creates a new result filter.
600         */
601        ResultFilterComponent() {
602
603            super();
604            addTextChangeListener(new TextChangeListener() {
605
606                public void textChange(TextChangeEvent event) {
607
608                    m_resultComponent.getFileTable().filterTable(event.getText());
609
610                }
611            });
612        }
613    }
614
615    /** The log object for this class. */
616    static final Log LOG = CmsLog.getLog(CmsUnusedContentFinderComposite.class);
617
618    /** The maximum number of results. */
619    static final int MAX_RESULTS = 5000;
620
621    /** Schema parameter name. */
622    static final String SCHEMA_PARAM_NAME = "unusedcontentfinder";
623
624    /** Schema parameter value. */
625    static final String SCHEMA_PARAM_VALUE = "include";
626
627    /** The delete button of this unused content finder. */
628    DeleteButtonComponent m_deleteButtonComponent;
629
630    /** The form component of this unused content finder. */
631    FormComponent m_formComponent;
632
633    /** The result component of this unused content finder. */
634    ResultComponent m_resultComponent;
635
636    /** The result filter component of this unused content finder. */
637    ResultFilterComponent m_resultFilterComponent;
638
639    /**
640     * Creates a new component.
641     */
642    public CmsUnusedContentFinderComposite() {
643
644        m_deleteButtonComponent = new DeleteButtonComponent();
645        m_formComponent = new FormComponent();
646        m_resultComponent = new ResultComponent();
647        m_resultFilterComponent = new ResultFilterComponent();
648    }
649
650    /**
651     * Returns the delete button component of this unused content finder.
652     * @return the delete button component of this unused content finder
653     */
654    public Component getDeleteButtonComponent() {
655
656        return m_deleteButtonComponent;
657    }
658
659    /**
660     * Returns the form component of this unused content finder.
661     * @return the form component of this unused content finder
662     */
663    public Component getFormComponent() {
664
665        return m_formComponent;
666    }
667
668    /**
669     * Returns the result component of this unused content finder.
670     * @return the result component of this unused content finder
671     */
672    public Component getResultComponent() {
673
674        return m_resultComponent;
675    }
676
677    /**
678     * Returns the result filter component of this unused content finder.
679     * @return the result filter component of this unused content finder
680     */
681    public Component getResultFilterComponent() {
682
683        return m_resultFilterComponent;
684    }
685
686    /**
687     * Search for unused contents.
688     * @param updateState whether to also update the state when searching
689     */
690    public void search(boolean updateState) {
691
692        if (updateState) {
693            CmsComponentState componentState = new CmsComponentState();
694            componentState.setSite(m_formComponent.getSiteValue());
695            componentState.setFolder(m_formComponent.getFolderValue());
696            componentState.setResourceType(m_formComponent.getTypeValue());
697            CmsAppWorkplaceUi.get().changeCurrentAppState(componentState.generateStateString());
698        }
699        m_resultComponent.updateResult();
700    }
701
702    /**
703     * Initializes this component with data from a given state bean.
704     * @param componentState the state bean
705     */
706    public void setState(CmsComponentState componentState) {
707
708        m_formComponent.setSiteValue(componentState.getSite());
709        m_formComponent.setFolderValue(componentState.getFolder());
710        m_formComponent.setTypeValue(componentState.getResourceType());
711    }
712
713    /**
714     * Returns the list of all relevant XML content types. A content type is relevant
715     * if either there is a parameter "unusedcontentfinder" defined in the XML
716     * content schema and the value of this parameter is set to "include"; or, the
717     * "unusedcontentfinder" parameter is not defined at all but the containerPageOnly
718     * attribute of the searchsettings element is set to "true".
719     * @return the list of all relevant XML content types
720     */
721    List<I_CmsResourceType> getAvailableTypes() {
722
723        List<I_CmsResourceType> result = new ArrayList<>();
724        CmsObject cms = A_CmsUI.getCmsObject();
725        List<I_CmsResourceType> types = OpenCms.getResourceManager().getResourceTypes();
726        for (I_CmsResourceType type : types) {
727            CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(
728                type.getTypeName());
729            if (typeSetting == null) {
730                continue;
731            }
732            if ((type instanceof CmsResourceTypeXmlContent)
733                && !((type instanceof CmsResourceTypeXmlContainerPage)
734                    || (type instanceof CmsResourceTypeXmlAdeConfiguration))) {
735                CmsResourceTypeXmlContent xmlType = (CmsResourceTypeXmlContent)type;
736                try {
737                    CmsFile schemaFile = cms.readFile(xmlType.getSchema());
738                    CmsXmlContentDefinition definition = CmsXmlContentDefinition.unmarshal(
739                        cms,
740                        schemaFile.getRootPath());
741                    I_CmsXmlContentHandler contentHandler = definition.getContentHandler();
742                    String parameter = contentHandler.getParameter(SCHEMA_PARAM_NAME);
743                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parameter)) {
744                        if (parameter.equalsIgnoreCase(SCHEMA_PARAM_VALUE)) {
745                            result.add(type);
746                        }
747                    } else if (contentHandler.isContainerPageOnly()) {
748                        result.add(type);
749                    }
750                } catch (Throwable t) {
751                    // may happen for internal types
752                }
753            }
754        }
755        return result;
756    }
757
758    /**
759     * Returns the offline CMS.
760     * @return the offline CMS
761     */
762    CmsObject getOfflineCms() {
763
764        CmsObject offlineCms = null;
765        try {
766            offlineCms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
767            offlineCms.getRequestContext().setSiteRoot(m_formComponent.getSiteValue());
768            offlineCms.getRequestContext().setCurrentProject(offlineCms.readProject("Offline"));
769        } catch (CmsException e) {
770            LOG.error(e.getLocalizedMessage(), e);
771        }
772        return offlineCms;
773    }
774
775    /**
776     * Return the online CMS.
777     * @return the online CMS
778     */
779    CmsObject getOnlineCms() {
780
781        CmsObject onlineCms = null;
782        try {
783            onlineCms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
784            onlineCms.getRequestContext().setSiteRoot(m_formComponent.getSiteValue());
785            onlineCms.getRequestContext().setCurrentProject(onlineCms.readProject(CmsProject.ONLINE_PROJECT_ID));
786        } catch (CmsException e) {
787            LOG.error(e.getLocalizedMessage(), e);
788        }
789        return onlineCms;
790    }
791
792    /**
793     * Returns whether a resource is excluded by a property. This is the case for properties:
794     * <li>element.model=true
795     * <li>NavInfo=keep
796     * @param resource the resource to check
797     * @param online whether to check in the online project
798     * @return whether the resource in not excluded by a property
799     * @throws CmsException if reading the properties fails
800     */
801    boolean isExcludedByProperties(CmsResource resource, boolean online) throws CmsException {
802
803        CmsObject cms = online ? getOnlineCms() : getOfflineCms();
804        CmsProperty navInfo = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_NAVINFO, true);
805        if (!navInfo.isNullProperty() && navInfo.getValue().equals("keep")) {
806            return true;
807        }
808        CmsProperty elementModel = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_ELEMENT_MODEL, true);
809        if (!elementModel.isNullProperty() && elementModel.getValue().equals("true")) {
810            return true;
811        }
812        return false;
813    }
814
815    /**
816     * Returns whether a resource is used by other contents.
817     * @param resource the resource to check
818     * @param online whether the check should be performed online
819     * @return whether the resource is used by other contents
820     * @throws CmsException if reading related resources fails
821     */
822    boolean isUsedByOtherContents(CmsResource resource, boolean online) throws CmsException {
823
824        if (online && resource.getState().isNew()) {
825            return false;
826        }
827        CmsRelationFilter filter = CmsRelationFilter.SOURCES;
828        CmsObject cms = online ? getOnlineCms() : getOfflineCms();
829        List<CmsRelation> relations = cms.getRelationsForResource(resource, filter);
830        return !relations.isEmpty();
831    }
832
833    /**
834     * Returns whether the current site and folder selection is valid.
835     * Only path levels greater or equal than 2 are allowed.
836     * @return whether the current site and folder selection is valid
837     */
838    boolean isValidFolder() {
839
840        String rootPath = CmsStringUtil.joinPaths(m_formComponent.getSiteValue(), m_formComponent.getFolderValue());
841        if (CmsResource.getPathLevel(rootPath) < 2) {
842            return false;
843        }
844        return true;
845    }
846}