001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.sitemap;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProject;
032import org.opencms.file.CmsProperty;
033import org.opencms.file.CmsPropertyDefinition;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
037import org.opencms.gwt.CmsCoreService;
038import org.opencms.i18n.CmsLocaleGroupService;
039import org.opencms.i18n.CmsLocaleManager;
040import org.opencms.lock.CmsLockActionRecord;
041import org.opencms.lock.CmsLockActionRecord.LockChange;
042import org.opencms.lock.CmsLockUtil;
043import org.opencms.main.CmsException;
044import org.opencms.main.CmsLog;
045import org.opencms.main.OpenCms;
046import org.opencms.site.CmsSite;
047import org.opencms.ui.A_CmsUI;
048import org.opencms.ui.CmsVaadinUtils;
049import org.opencms.ui.FontOpenCms;
050import org.opencms.ui.I_CmsDialogContext;
051import org.opencms.ui.Messages;
052import org.opencms.ui.actions.CmsResourceInfoAction;
053import org.opencms.ui.apps.CmsSitemapEditorConfiguration;
054import org.opencms.ui.components.CmsBasicDialog;
055import org.opencms.ui.components.CmsBasicDialog.DialogWidth;
056import org.opencms.ui.components.CmsErrorDialog;
057import org.opencms.ui.components.CmsResourceIcon;
058import org.opencms.ui.components.CmsResourceIcon.IconMode;
059import org.opencms.ui.components.CmsResourceInfo;
060import org.opencms.ui.components.OpenCmsTheme;
061import org.opencms.ui.contextmenu.CmsContextMenu;
062import org.opencms.ui.contextmenu.CmsMenuItemVisibilityMode;
063import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry;
064import org.opencms.util.CmsUUID;
065
066import java.util.Arrays;
067import java.util.Collection;
068import java.util.IdentityHashMap;
069import java.util.List;
070import java.util.Locale;
071
072import org.apache.commons.logging.Log;
073
074import com.google.common.base.Joiner;
075import com.google.common.base.Predicate;
076import com.google.common.collect.Lists;
077import com.vaadin.event.LayoutEvents.LayoutClickEvent;
078import com.vaadin.event.LayoutEvents.LayoutClickListener;
079import com.vaadin.server.FontAwesome;
080import com.vaadin.server.Resource;
081import com.vaadin.ui.AbstractComponent;
082import com.vaadin.ui.Button.ClickEvent;
083import com.vaadin.ui.Button.ClickListener;
084import com.vaadin.ui.Component;
085import com.vaadin.ui.ComponentContainer;
086import com.vaadin.ui.CssLayout;
087import com.vaadin.ui.MenuBar;
088import com.vaadin.ui.MenuBar.Command;
089import com.vaadin.ui.MenuBar.MenuItem;
090import com.vaadin.ui.Notification;
091import com.vaadin.ui.Notification.Type;
092import com.vaadin.ui.UI;
093import com.vaadin.ui.Window;
094
095/**
096 * Manages the sitemap tree in the 'locale comparison' view in the sitemap editor.<p>
097 */
098public class CmsSitemapTreeController {
099
100    /**
101     * The context used for child dialogs.<p>
102     */
103    public class DialogContext implements I_CmsDialogContext {
104
105        /** The tree node. */
106        private CmsSitemapTreeNode m_node;
107
108        /** The resource. */
109        private CmsResource m_resource;
110
111        /**
112         * Creates a new instance.<p>
113         *
114         * @param resource the resource
115         * @param node the tree node
116         */
117        public DialogContext(CmsResource resource, CmsSitemapTreeNode node) {
118
119            m_resource = resource;
120            m_node = node;
121        }
122
123        /**
124         * Closes the dialog window.<p>
125         */
126        public void closeWindow() {
127
128            if (m_window != null) {
129                m_window.close();
130                m_window = null;
131            }
132        }
133
134        /**
135         * @see org.opencms.ui.I_CmsDialogContext#error(java.lang.Throwable)
136         */
137        public void error(Throwable error) {
138
139            getTreeControllerLog().error(error.getLocalizedMessage(), error);
140            CmsErrorDialog.showErrorDialog(error);
141        }
142
143        /**
144         * @see org.opencms.ui.I_CmsDialogContext#finish(org.opencms.file.CmsProject, java.lang.String)
145         */
146        public void finish(CmsProject project, String siteRoot) {
147
148            closeWindow();
149        }
150
151        /**
152         * @see org.opencms.ui.I_CmsDialogContext#finish(java.util.Collection)
153         */
154        @SuppressWarnings("synthetic-access")
155        public void finish(Collection<CmsUUID> result) {
156
157            closeWindow();
158            if (result.isEmpty()) {
159                return;
160            }
161            if (m_node != null) {
162                if (m_node == m_currentRootNode) {
163                    m_localeContext.refreshAll();
164                } else {
165                    updateNode(m_node);
166                }
167            }
168        }
169
170        /**
171         * @see org.opencms.ui.I_CmsDialogContext#focus(org.opencms.util.CmsUUID)
172         */
173        public void focus(CmsUUID structureId) {
174
175            // not used
176        }
177
178        /**
179         * @see org.opencms.ui.I_CmsDialogContext#getAllStructureIdsInView()
180         */
181        public List<CmsUUID> getAllStructureIdsInView() {
182
183            return null;
184        }
185
186        /**
187         * @see org.opencms.ui.I_CmsDialogContext#getAppId()
188         */
189        public String getAppId() {
190
191            return CmsSitemapEditorConfiguration.APP_ID;
192        }
193
194        /**
195         * @see org.opencms.ui.I_CmsDialogContext#getCms()
196         */
197        public CmsObject getCms() {
198
199            return A_CmsUI.getCmsObject();
200        }
201
202        /**
203         * @see org.opencms.ui.I_CmsDialogContext#getContextType()
204         */
205        public ContextType getContextType() {
206
207            return null;
208        }
209
210        /**
211         * @see org.opencms.ui.I_CmsDialogContext#getResources()
212         */
213        public List<CmsResource> getResources() {
214
215            return Arrays.asList(m_resource);
216        }
217
218        /**
219         * @see org.opencms.ui.I_CmsDialogContext#navigateTo(java.lang.String)
220         */
221        public void navigateTo(String appId) {
222
223            // not used
224        }
225
226        /**
227         * @see org.opencms.ui.I_CmsDialogContext#onViewChange()
228         */
229        public void onViewChange() {
230
231            // do nothing
232        }
233
234        /**
235         * @see org.opencms.ui.I_CmsDialogContext#reload()
236         */
237        public void reload() {
238
239            // do nothing
240
241        }
242
243        /**
244         * @see org.opencms.ui.I_CmsDialogContext#setWindow(com.vaadin.ui.Window)
245         */
246        public void setWindow(Window window) {
247
248            m_window = window;
249        }
250
251        /**
252         * @see org.opencms.ui.I_CmsDialogContext#start(java.lang.String, com.vaadin.ui.Component)
253         */
254        public void start(String title, Component dialog) {
255
256            start(title, dialog, DialogWidth.narrow);
257        }
258
259        /**
260         * @see org.opencms.ui.I_CmsDialogContext#start(java.lang.String, com.vaadin.ui.Component, org.opencms.ui.components.CmsBasicDialog.DialogWidth)
261         */
262        public void start(String title, Component dialog, DialogWidth width) {
263
264            if (dialog != null) {
265                m_window = CmsBasicDialog.prepareWindow(width);
266                m_window.setCaption(title);
267                m_window.setContent(dialog);
268                UI.getCurrent().addWindow(m_window);
269                if (dialog instanceof CmsBasicDialog) {
270                    ((CmsBasicDialog)dialog).initActionHandler(m_window);
271                }
272            }
273        }
274
275        /**
276         * @see org.opencms.ui.I_CmsDialogContext#updateUserInfo()
277         */
278        public void updateUserInfo() {
279
280            // not supported
281        }
282    }
283
284    /**
285     * Copy menu entry.
286     */
287    class EntryCopy implements I_CmsSimpleContextMenuEntry<MenuContext> {
288
289        /**
290         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
291         */
292        public void executeAction(MenuContext context) {
293
294            try {
295                CmsResource resource = context.getTargetResource();
296                DialogContext dialogContext = new DialogContext(resource, context.getNode());
297                CmsCopyPageDialog dialog = new CmsCopyPageDialog(dialogContext);
298                String title = CmsVaadinUtils.getMessageText(Messages.GUI_COPYPAGE_DIALOG_TITLE_0);
299                dialogContext.start(title, dialog);
300            } catch (Exception e) {
301                LOG.error(e.getLocalizedMessage(), e);
302                CmsErrorDialog.showErrorDialog(e);
303            }
304
305        }
306
307        /**
308         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
309         */
310        public String getTitle(Locale locale) {
311
312            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_COPY_PAGE_0);
313        }
314
315        /**
316         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
317         */
318        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
319
320            return visibleIfTrue(
321                context.isTargetingLinkedResource()
322                ? CmsResourceTypeXmlContainerPage.isContainerPage(context.getData().getLinkedResource())
323                : context.getData().isCopyable());
324
325        }
326    }
327
328    /**
329     * Menu entry for opening the explorer.<p>
330     */
331    class EntryExplorer implements I_CmsSimpleContextMenuEntry<MenuContext> {
332
333        /**
334         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
335         */
336        public void executeAction(MenuContext context) {
337
338            String link = CmsCoreService.getVaadinWorkplaceLink(
339                A_CmsUI.getCmsObject(),
340                context.getTargetResource().getStructureId());
341            A_CmsUI.get().getPage().setLocation(link);
342
343        }
344
345        /**
346         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
347         */
348        public String getTitle(Locale locale) {
349
350            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_EXPLORER_0);
351        }
352
353        /**
354         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
355         */
356        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
357
358            return visibleIfTrue(true);
359        }
360
361    }
362
363    /**
364     * Menu entry for opening the info dialog.<p>
365     */
366    class EntryInfo implements I_CmsSimpleContextMenuEntry<MenuContext> {
367
368        /**
369         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
370         */
371        public void executeAction(MenuContext context) {
372
373            CmsResourceInfoAction infoAction = new CmsResourceInfoAction();
374            infoAction.executeAction(new DialogContext(context.getTargetResource(), context.getNode()));
375        }
376
377        /**
378         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
379         */
380        public String getTitle(Locale locale) {
381
382            return CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCE_INFO_0);
383        }
384
385        /**
386         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
387         */
388        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
389
390            return visibleIfTrue(true);
391        }
392
393    }
394
395    /**
396     * Menu entry for opening the 'LInk locale' dialog.<p>
397     */
398    class EntryLink implements I_CmsSimpleContextMenuEntry<MenuContext> {
399
400        /**
401         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
402         */
403        @SuppressWarnings("synthetic-access")
404        public void executeAction(MenuContext data) {
405
406            try {
407                DialogContext dialogContext = new DialogContext(
408                    A_CmsUI.getCmsObject().readResource(
409                        data.getData().getClientEntry().getId(),
410                        CmsResourceFilter.IGNORE_EXPIRATION),
411                    data.getNode());
412                CmsLocaleLinkTargetSelectionDialog dialog = new CmsLocaleLinkTargetSelectionDialog(
413                    dialogContext,
414                    m_localeContext);
415                dialogContext.start(
416                    CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_LINK_LOCALE_VARIANT_0),
417                    dialog,
418                    DialogWidth.narrow);
419            } catch (CmsException e) {
420                LOG.error(e.getLocalizedMessage(), e);
421                CmsErrorDialog.showErrorDialog(e);
422            }
423        }
424
425        /**
426         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
427         */
428        public String getTitle(Locale locale) {
429
430            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_LINK_LOCALE_VARIANT_0);
431        }
432
433        /**
434         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
435         */
436        @SuppressWarnings("synthetic-access")
437        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
438
439            return activeIfTrue(
440                !context.getData().isLinked()
441                    && !context.getData().isMarkedNoTranslation(m_localeContext.getComparisonLocale()));
442        }
443    }
444
445    /**
446     * Context menu entry for the 'Do not translate' mark.<p>
447     */
448    class EntryMark implements I_CmsSimpleContextMenuEntry<MenuContext> {
449
450        /**
451         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
452         */
453        @SuppressWarnings("synthetic-access")
454        public void executeAction(MenuContext context) {
455
456            CmsSitemapTreeNodeData entry = context.getData();
457            CmsSitemapTreeNode node = context.getNode();
458
459            CmsObject cms = A_CmsUI.getCmsObject();
460            CmsLockActionRecord actionRecord = null;
461            CmsResource fileToModify2 = null;
462            try {
463
464                CmsResource primary = A_CmsUI.getCmsObject().readResource(
465                    entry.getClientEntry().getId(),
466                    CmsResourceFilter.IGNORE_EXPIRATION);
467                if (primary.isFolder()) {
468                    CmsResource defaultFile = A_CmsUI.getCmsObject().readDefaultFile(
469                        primary,
470                        CmsResourceFilter.IGNORE_EXPIRATION);
471                    if (defaultFile != null) {
472                        primary = defaultFile;
473                    }
474                }
475                final CmsResource primaryFinal = primary;
476
477                fileToModify2 = primaryFinal;
478                if (fileToModify2.isFolder()) {
479                    try {
480                        fileToModify2 = A_CmsUI.getCmsObject().readDefaultFile(
481                            fileToModify2,
482                            CmsResourceFilter.IGNORE_EXPIRATION);
483                    } catch (CmsException e) {
484                        LOG.error(e.getLocalizedMessage(), e);
485                    }
486                }
487
488                actionRecord = CmsLockUtil.ensureLock(cms, fileToModify2);
489                m_localeContext.getComparisonLocale().toString();
490                CmsProperty prop = cms.readPropertyObject(
491                    fileToModify2,
492                    CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
493                    false);
494                String propValue = prop.getValue();
495                if (propValue == null) {
496                    propValue = ""; // make getLocales not return null
497                }
498                List<Locale> currentLocales = CmsLocaleManager.getLocales(propValue);
499                if (!currentLocales.contains(m_localeContext.getComparisonLocale())) {
500                    currentLocales.add(m_localeContext.getComparisonLocale());
501                    String newPropValue = Joiner.on(",").join(currentLocales);
502                    CmsProperty newProp = new CmsProperty(
503                        CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
504                        newPropValue,
505                        null);
506                    cms.writePropertyObjects(fileToModify2, Arrays.asList(newProp));
507                    DialogContext dialogContext = new DialogContext(
508                        A_CmsUI.getCmsObject().readResource(
509                            entry.getClientEntry().getId(),
510                            CmsResourceFilter.IGNORE_EXPIRATION),
511                        node);
512                    dialogContext.finish(Arrays.asList(fileToModify2.getStructureId()));
513
514                }
515
516            } catch (CmsException e) {
517                LOG.error(e.getLocalizedMessage(), e);
518                CmsErrorDialog.showErrorDialog(e);
519
520            } finally {
521                if ((actionRecord != null) && (actionRecord.getChange() == LockChange.locked)) {
522                    try {
523                        cms.unlockResource(fileToModify2);
524                    } catch (CmsException e) {
525                        LOG.error(e.getLocalizedMessage(), e);
526                        CmsErrorDialog.showErrorDialog(e);
527                    }
528                }
529            }
530
531        }
532
533        /**
534         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
535         */
536        public String getTitle(Locale locale) {
537
538            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_ADD_DONT_TRANSLATE_0);
539        }
540
541        /**
542         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
543         */
544        @SuppressWarnings("synthetic-access")
545        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
546
547            CmsSitemapTreeNodeData entry = context.getData();
548            boolean result = context.isMainLocale()
549                && !entry.isMarkedNoTranslation(m_localeContext.getComparisonLocale())
550                && !entry.isLinked();
551            return visibleIfTrue(result);
552
553        }
554
555    }
556
557    /**
558     * 'Open page' menu entry.<p>
559     */
560    class EntryOpen implements I_CmsSimpleContextMenuEntry<MenuContext> {
561
562        /**
563         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
564         */
565        @SuppressWarnings("synthetic-access")
566        public void executeAction(MenuContext context) {
567
568            openTargetPage((CmsSitemapTreeNodeData)(context.getNode().getData()), context.isTargetingLinkedResource());
569
570        }
571
572        /**
573         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
574         */
575        public String getTitle(Locale locale) {
576
577            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_OPEN_PAGE_0);
578
579        }
580
581        /**
582         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
583         */
584        public CmsMenuItemVisibilityMode getVisibility(MenuContext data) {
585
586            return visibleIfTrue(true);
587        }
588
589    }
590
591    /**
592     * 'Properties' menu entry.<p>
593     */
594    class EntryProperties implements I_CmsSimpleContextMenuEntry<MenuContext> {
595
596        /**
597         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
598         */
599        @SuppressWarnings("synthetic-access")
600        public void executeAction(MenuContext context) {
601
602            ((CmsSitemapUI)A_CmsUI.get()).getSitemapExtension().openPropertyDialog(
603                context.getTargetResource().getStructureId(),
604                m_root.getStructureId());
605        }
606
607        /**
608         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
609         */
610        public String getTitle(Locale locale) {
611
612            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_PROPERTIES_0);
613
614        }
615
616        /**
617         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
618         */
619        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
620
621            return visibleIfTrue(true);
622        }
623
624    }
625
626    /**
627     * 'Remove mark' menu entry.<p>
628     */
629    class EntryRemoveMark implements I_CmsSimpleContextMenuEntry<MenuContext> {
630
631        /**
632         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
633         */
634        @SuppressWarnings("synthetic-access")
635        public void executeAction(MenuContext context) {
636
637            CmsSitemapTreeNodeData entry = context.getData();
638            CmsSitemapTreeNode node = context.getNode();
639
640            CmsObject cms = A_CmsUI.getCmsObject();
641            CmsLockActionRecord actionRecord = null;
642            CmsResource fileToModify2 = null;
643            try {
644                CmsResource primary = A_CmsUI.getCmsObject().readResource(
645                    entry.getClientEntry().getId(),
646                    CmsResourceFilter.IGNORE_EXPIRATION);
647                if (primary.isFolder()) {
648                    CmsResource defaultFile = A_CmsUI.getCmsObject().readDefaultFile(
649                        primary,
650                        CmsResourceFilter.IGNORE_EXPIRATION);
651                    if (defaultFile != null) {
652                        primary = defaultFile;
653                    }
654                }
655                final CmsResource primaryFinal = primary;
656
657                fileToModify2 = primaryFinal;
658                if (fileToModify2.isFolder()) {
659                    try {
660                        fileToModify2 = A_CmsUI.getCmsObject().readDefaultFile(
661                            fileToModify2,
662                            CmsResourceFilter.IGNORE_EXPIRATION);
663                    } catch (CmsException e) {
664                        LOG.error(e.getLocalizedMessage(), e);
665                    }
666                }
667
668                actionRecord = CmsLockUtil.ensureLock(cms, fileToModify2);
669                m_localeContext.getComparisonLocale().toString();
670                CmsProperty prop = cms.readPropertyObject(
671                    fileToModify2,
672                    CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
673                    false);
674                String propValue = prop.getValue();
675                if (propValue == null) {
676                    propValue = ""; // make getLocales not return null
677                }
678                List<Locale> currentLocales = CmsLocaleManager.getLocales(propValue);
679                if (currentLocales.contains(m_localeContext.getComparisonLocale())) {
680                    currentLocales.remove(m_localeContext.getComparisonLocale());
681                    String newPropValue = Joiner.on(",").join(currentLocales);
682                    CmsProperty newProp = new CmsProperty(
683                        CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
684                        newPropValue,
685                        null);
686                    cms.writePropertyObjects(primaryFinal, Arrays.asList(newProp));
687                    DialogContext dialogContext = new DialogContext(
688                        A_CmsUI.getCmsObject().readResource(
689                            entry.getClientEntry().getId(),
690                            CmsResourceFilter.IGNORE_EXPIRATION),
691                        node);
692                    dialogContext.finish(Arrays.asList(fileToModify2.getStructureId()));
693                }
694
695            } catch (CmsException e) {
696                LOG.error(e.getLocalizedMessage(), e);
697                CmsErrorDialog.showErrorDialog(e);
698
699            } finally {
700                if ((actionRecord != null) && (actionRecord.getChange() == LockChange.locked)) {
701                    try {
702                        cms.unlockResource(fileToModify2);
703                    } catch (CmsException e) {
704                        LOG.error(e.getLocalizedMessage(), e);
705                        CmsErrorDialog.showErrorDialog(e);
706                    }
707                }
708            }
709
710        }
711
712        /**
713         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
714         */
715        public String getTitle(Locale locale) {
716
717            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_REMOVE_DONT_TRANSLATE_0);
718        }
719
720        /**
721         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
722         */
723        @SuppressWarnings("synthetic-access")
724        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
725
726            boolean result = context.isMainLocale()
727                && context.getData().isMarkedNoTranslation(m_localeContext.getComparisonLocale());
728            return visibleIfTrue(result);
729
730        }
731    }
732
733    /**
734     * 'Unlink' menu entry.<p>
735     */
736    class EntryUnlink implements I_CmsSimpleContextMenuEntry<MenuContext> {
737
738        /**
739         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
740         */
741        @SuppressWarnings("synthetic-access")
742        public void executeAction(MenuContext context) {
743
744            try {
745                CmsResource secondary = context.getData().getLinkedResource();
746                DialogContext dialogContext = new DialogContext(
747                    A_CmsUI.getCmsObject().readResource(
748                        context.getData().getClientEntry().getId(),
749                        CmsResourceFilter.IGNORE_EXPIRATION),
750                    context.getNode());
751                CmsUnlinkDialog dialog = new CmsUnlinkDialog(dialogContext, secondary);
752                dialogContext.start(
753                    CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_UNLINK_LOCALE_VARIANT_0),
754                    dialog,
755                    DialogWidth.wide);
756            } catch (CmsException e) {
757                LOG.error(e.getLocalizedMessage(), e);
758                CmsErrorDialog.showErrorDialog(e);
759            }
760        }
761
762        /**
763         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
764         */
765        public String getTitle(Locale locale) {
766
767            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_UNLINK_LOCALE_VARIANT_0);
768        }
769
770        /**
771         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
772         */
773        @SuppressWarnings("synthetic-access")
774        public CmsMenuItemVisibilityMode getVisibility(final MenuContext context) {
775
776            if (!context.getData().isLinked()) {
777                return visibleIfTrue(false);
778            }
779            try {
780                CmsResource primary = A_CmsUI.getCmsObject().readResource(
781                    context.getData().getClientEntry().getId(),
782                    CmsResourceFilter.IGNORE_EXPIRATION);
783                if (primary.isFolder()) {
784                    CmsResource defaultFile = A_CmsUI.getCmsObject().readDefaultFile(
785                        primary,
786                        CmsResourceFilter.IGNORE_EXPIRATION);
787                    if (defaultFile != null) {
788                        primary = defaultFile;
789                    }
790                }
791                CmsLocaleGroupService groupService = A_CmsUI.getCmsObject().getLocaleGroupService();
792                Locale mainLocale = groupService.getMainLocale(m_localeContext.getRoot().getRootPath());
793                int mainLocaleCount = 0;
794                for (Locale testLocale : Arrays.asList(
795                    m_localeContext.getRootLocale(),
796                    m_localeContext.getComparisonLocale())) {
797                    mainLocaleCount += mainLocale.equals(testLocale) ? 1 : 0;
798                }
799                return visibleIfTrue(mainLocaleCount == 1);
800            } catch (Exception e) {
801                return visibleIfTrue(false);
802
803            }
804
805        }
806    }
807
808    /**
809     * Context object for the context menu.<p>
810     */
811    class MenuContext {
812
813        /** The tree node data. */
814        private CmsSitemapTreeNodeData m_data;
815
816        /** The tree node widget. */
817        private CmsSitemapTreeNode m_node;
818
819        /** True if menu entries should act on the linked resource. */
820        private boolean m_isTargetingLinkedResource;
821
822        /**
823         * Creates a new instance.<p>
824         *
825         * @param data the sitemap tree data
826         * @param node the tree node widget
827         * @param isTargetingLinkedResource true if the menu entries should act on the linked resource
828         */
829        public MenuContext(CmsSitemapTreeNodeData data, CmsSitemapTreeNode node, boolean isTargetingLinkedResource) {
830
831            m_node = node;
832            m_data = data;
833            m_isTargetingLinkedResource = isTargetingLinkedResource;
834
835        }
836
837        /**
838         * Gets the tree node data.<p>
839         *
840         * @return the tree node data
841         */
842        public CmsSitemapTreeNodeData getData() {
843
844            return m_data;
845        }
846
847        /**
848         * Gets the tree node widget.<p>
849         *
850         * @return the tree node widget
851         */
852        public CmsSitemapTreeNode getNode() {
853
854            return m_node;
855        }
856
857        /**
858         * Gets the resource which context menu entries should operate on.
859         *
860         * @return the resource which context menu entries should operate on
861         */
862        public CmsResource getTargetResource() {
863
864            if (m_isTargetingLinkedResource) {
865                return m_data.getLinkedEntryResource();
866            } else {
867                return m_data.getResource();
868            }
869        }
870
871        /**
872         * Checks if the currently selected locale is the main locale.<p>
873         *
874         * @return true if we are in the main locale
875         */
876        @SuppressWarnings("synthetic-access")
877        public boolean isMainLocale() {
878
879            return m_localeContext.getRootLocale().equals(
880                A_CmsUI.getCmsObject().getLocaleGroupService().getMainLocale(m_localeContext.getRoot().getRootPath()));
881        }
882
883        /**
884         * Returns true if the menu entries should act on the linked resource if possible.
885         *
886         * @return true if the menu entries should act on the linked resource
887         */
888        public boolean isTargetingLinkedResource() {
889
890            return m_isTargetingLinkedResource;
891        }
892
893    }
894
895    /** Default width for linked items displayed on the right side of tree items. */
896    public static final int RHS_WIDTH = 420;
897
898    /** The log isntance for this class. */
899    private static final Log LOG = CmsLog.getLog(CmsSitemapTreeController.class);
900
901    /** The context menu. */
902    CmsContextMenu m_menu = new CmsContextMenu();
903
904    /** The currently opened window. */
905    Window m_window;
906
907    /** Map of already loaded nodes. */
908    private IdentityHashMap<CmsSitemapTreeNode, Void> m_alreadyLoaded = new IdentityHashMap<>();
909
910    /** Current root node widget. */
911    private CmsSitemapTreeNode m_currentRootNode;
912
913    /** The locale context. */
914    private I_CmsLocaleCompareContext m_localeContext;
915
916    /** The resource corresponding to the tree's root. */
917    private CmsResource m_root;
918
919    /** The tree data provider. */
920    private CmsSitemapTreeDataProvider m_treeDataProvider;
921
922    /**
923     * Creates a new instance.<p>
924     *
925     * @param cms the CMS  context
926     * @param root the tree's root resource
927     * @param context the locale comparison context
928     * @param parent the parent widget in which the tree will be rendered
929     */
930    public CmsSitemapTreeController(
931        CmsObject cms,
932        CmsResource root,
933        I_CmsLocaleCompareContext context,
934        Component parent) {
935
936        m_treeDataProvider = new CmsSitemapTreeDataProvider(cms, root, context);
937        m_localeContext = context;
938        m_root = root;
939        m_menu.extend((AbstractComponent)parent);
940
941    }
942
943    /**
944     * Returns VISIBILITY_ACTIVE if the given parameter is true, and VISIBILITY_INACTIVE otherwise.<p>
945     *
946     * @param condition a boolean value
947     * @return the visibility based on the condition value
948     */
949    public static CmsMenuItemVisibilityMode activeIfTrue(boolean condition) {
950
951        return condition ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE : CmsMenuItemVisibilityMode.VISIBILITY_INACTIVE;
952    }
953
954    /**
955     * If the given resource is the default file of a sitmeap entry folder, then returns that
956     * folder, else the original file.<p>
957     *
958     * @param resource a resource
959     * @return the resource or its parent folder
960     */
961    public static CmsResource readSitemapEntryFolderIfPossible(CmsResource resource) {
962
963        CmsObject cms = A_CmsUI.getCmsObject();
964        try {
965            if (resource.isFolder()) {
966                return resource;
967            }
968            CmsResource parent = cms.readParentFolder(resource.getStructureId());
969            CmsResource defaultFile = cms.readDefaultFile(parent, CmsResourceFilter.IGNORE_EXPIRATION);
970            if ((defaultFile != null) && defaultFile.equals(resource)) {
971                return parent;
972            }
973            return resource;
974        } catch (CmsException e) {
975            LOG.error(e.getLocalizedMessage(), e);
976            return resource;
977        }
978    }
979
980    /**
981     * Returns VISIBILITY_ACTIVE if the given parameter is true, and VISIBILITY_INVISIBLE otherwise.<p>
982     *
983     * @param condition a boolean value
984     * @return the visibility based on the condition value
985     */
986    public static CmsMenuItemVisibilityMode visibleIfTrue(boolean condition) {
987
988        return condition ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE;
989    }
990
991    /**
992     * Creates a sitemap tree node widget from a tree node bean.<p>
993     *
994     * @param entry the tree node bean
995     * @return the tree node widget
996     */
997    public CmsSitemapTreeNode createNode(final CmsSitemapTreeNodeData entry) {
998
999        final CmsSitemapTreeNode node = new CmsSitemapTreeNode();
1000
1001        node.addLayoutClickListener(new LayoutClickListener() {
1002
1003            private static final long serialVersionUID = 1L;
1004
1005            @SuppressWarnings("synthetic-access")
1006            public void layoutClick(LayoutClickEvent event) {
1007
1008                Component currentComponent = event.getClickedComponent();
1009                if (currentComponent != null) {
1010                    boolean linked = false;
1011                    do {
1012                        currentComponent = currentComponent.getParent();
1013                        if ((currentComponent != null)
1014                            && "linked".equals(((AbstractComponent)currentComponent).getData())) {
1015                            linked = true;
1016                        }
1017                        if (event.getClickedComponent() instanceof CmsResourceIcon) {
1018                            if (currentComponent == node) {
1019                                openTargetPage((CmsSitemapTreeNodeData)(node.getData()), linked);
1020                            } else if (currentComponent instanceof CmsSitemapTreeNode) {
1021                                break;
1022                            }
1023                        }
1024                    } while (currentComponent != null);
1025                }
1026
1027            }
1028
1029        });
1030        Resource icon = CmsResourceIcon.getSitemapResourceIcon(
1031            A_CmsUI.getCmsObject(),
1032            entry.getResource(),
1033            IconMode.localeCompare);
1034        CmsResourceInfo info = new CmsResourceInfo(
1035            entry.getClientEntry().getTitle(),
1036            entry.getClientEntry().getSitePath(),
1037            icon);
1038        info = CmsResourceInfo.createSitemapResourceInfo(
1039            entry.getResource(),
1040            OpenCms.getSiteManager().getSiteForRootPath(m_localeContext.getRoot().getRootPath()));
1041        info.getResourceIcon().addStyleName(OpenCmsTheme.POINTER);
1042        info.getResourceIcon().setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_OPEN_PAGE_0));
1043
1044        if (entry.getClientEntry().isHiddenNavigationEntry()) {
1045            info.addStyleName(OpenCmsTheme.RESOURCE_INFO_WEAK);
1046        }
1047
1048        boolean noTranslation = false;
1049        noTranslation = entry.isMarkedNoTranslation(m_localeContext.getComparisonLocale());
1050        CssLayout rightSide = new CssLayout();
1051        info.setButtonWidget(rightSide);
1052
1053        final MenuBar menu = createMenu(entry, node, false);
1054        rightSide.addComponent(menu);
1055        if (entry.isLinked()) {
1056            CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(m_localeContext.getRoot().getRootPath());
1057            CmsResourceInfo linkedInfo = CmsResourceInfo.createSitemapResourceInfo(
1058                readSitemapEntryFolderIfPossible(entry.getLinkedResource()),
1059                site);
1060            linkedInfo.addStyleName(OpenCmsTheme.RESOURCE_INFO_DIRECTLINK);
1061            CssLayout linkedRightSide = new CssLayout();
1062            linkedInfo.setButtonWidget(linkedRightSide);
1063            linkedRightSide.addComponent(createMenu(entry, node, true));
1064            rightSide.addComponent(linkedInfo);
1065            linkedInfo.setWidth(RHS_WIDTH + "px");
1066            node.setContent(info);
1067            linkedInfo.setData("linked"); // Data used by click handler to distinguish clicked resource icons
1068            linkedInfo.getResourceIcon().setDescription(
1069                CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_OPEN_PAGE_0));
1070            linkedInfo.getResourceIcon().addStyleName(OpenCmsTheme.POINTER);
1071        } else {
1072            if (noTranslation) {
1073                CmsResourceInfo noTranslationInfo = new CmsResourceInfo();
1074                String topMessage = CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_NO_TRANSLATION_TOP_0);
1075                String bottomMessage = CmsVaadinUtils.getMessageText(
1076                    Messages.GUI_LOCALECOMPARE_NO_TRANSLATION_BOTTOM_0);
1077                noTranslationInfo.getTopLine().setValue(topMessage);
1078                noTranslationInfo.getBottomLine().setValue(bottomMessage);
1079                noTranslationInfo.getResourceIcon().setValue(
1080                    "<span class=\""
1081                        + OpenCmsTheme.RESOURCE_ICON
1082                        + " "
1083                        + OpenCmsTheme.NO_TRANSLATION_ICON
1084                        + "\">"
1085                        + FontAwesome.BAN.getHtml()
1086                        + "</span>");
1087                noTranslationInfo.addStyleName(OpenCmsTheme.RESOURCE_INFO_DIRECTLINK);
1088                noTranslationInfo.setWidth(RHS_WIDTH + "px");
1089                rightSide.addComponent(noTranslationInfo);
1090            }
1091            node.setContent(info);
1092        }
1093
1094        if (entry.hasNoChildren()) {
1095            node.setOpen(true);
1096            node.setOpenerVisible(false);
1097        }
1098        node.setData(entry);
1099        return node;
1100
1101    }
1102
1103    /**
1104     * Creates the root node of the tree.<p>
1105     *
1106     * @return the root node of the tree
1107     */
1108    public CmsSitemapTreeNode createRootNode() {
1109
1110        m_currentRootNode = createNode(m_treeDataProvider.getRoot());
1111        return m_currentRootNode;
1112    }
1113
1114    /**
1115     * Gets the resource corresponding to the tree's root.<p>
1116     *
1117     * @return the resource for the root node
1118     */
1119    public CmsResource getRoot() {
1120
1121        return m_root;
1122    }
1123
1124    /**
1125     * Initializes the event handlers for a tree node widget.<p>
1126     *
1127     * @param node the node for which to initialize the event handlers
1128     */
1129    public void initEventHandlers(final CmsSitemapTreeNode node) {
1130
1131        node.getOpener().addClickListener(new ClickListener() {
1132
1133            private static final long serialVersionUID = 1L;
1134
1135            public void buttonClick(ClickEvent event) {
1136
1137                CmsSitemapTreeController.this.onClickOpen(node);
1138            }
1139        });
1140    }
1141
1142    /**
1143     * Called when the user clicks on the 'opener' icon of a sitemap tree entry.<p>
1144     *
1145     * @param node the sitemap node widget
1146     */
1147    public void onClickOpen(CmsSitemapTreeNode node) {
1148
1149        if (node.isOpen()) {
1150            node.setOpen(false);
1151        } else {
1152            if (!m_alreadyLoaded.containsKey(node)) {
1153                Object nodeData = node.getData();
1154                List<CmsSitemapTreeNodeData> children = m_treeDataProvider.getChildren(
1155                    (CmsSitemapTreeNodeData)nodeData);
1156                m_alreadyLoaded.put(node, null);
1157                if (children.isEmpty()) {
1158                    node.setOpenerVisible(false);
1159                } else {
1160                    for (CmsSitemapTreeNodeData child : children) {
1161                        CmsSitemapTreeNode childNode = createNode(child);
1162                        childNode.setData(child);
1163                        initEventHandlers(childNode);
1164                        node.getChildren().addComponent(childNode);
1165                    }
1166                }
1167            }
1168            node.setOpen(true);
1169        }
1170    }
1171
1172    /**
1173     * Updates a sitemap node widget after the resource it corresponds to has changed.<p>
1174     *
1175     * @param node the sitemap node
1176     */
1177    public void updateNode(CmsSitemapTreeNode node) {
1178
1179        CmsSitemapTreeNodeData data = (CmsSitemapTreeNodeData)node.getData();
1180        try {
1181            CmsSitemapTreeNodeData changedData = m_treeDataProvider.getData(
1182                A_CmsUI.getCmsObject().readResource(
1183                    data.getClientEntry().getId(),
1184                    CmsResourceFilter.IGNORE_EXPIRATION));
1185            CmsSitemapTreeNode changedNode = createNode(changedData);
1186            initEventHandlers(changedNode);
1187            ComponentContainer parent = (ComponentContainer)(node.getParent());
1188            parent.replaceComponent(node, changedNode);
1189        } catch (CmsException e) {
1190            LOG.error(e.getLocalizedMessage(), e);
1191        }
1192    }
1193
1194    /**
1195     * Updates the tree node for the resource with the given structure id, if it exists.<p>
1196     *
1197     * @param id the structure id of a resource
1198     */
1199    public void updateNodeForId(final CmsUUID id) {
1200
1201        final List<CmsSitemapTreeNode> nodes = Lists.newArrayList();
1202        CmsVaadinUtils.visitDescendants(m_currentRootNode, new Predicate<Component>() {
1203
1204            public boolean apply(Component input) {
1205
1206                if (input instanceof CmsSitemapTreeNode) {
1207                    CmsSitemapTreeNode node = (CmsSitemapTreeNode)input;
1208                    CmsSitemapTreeNodeData data = (CmsSitemapTreeNodeData)node.getData();
1209                    if (data.getResource().getStructureId().equals(id)
1210                        || ((data.getLinkedEntryResource() != null)
1211                            && data.getLinkedEntryResource().getStructureId().equals(id))) {
1212                        nodes.add(node);
1213                        return false;
1214                    }
1215                }
1216                return true;
1217            }
1218        });
1219        if (nodes.size() == 1) {
1220            updateNode(nodes.get(0));
1221        }
1222
1223    }
1224
1225    /**
1226     * Gets the logger for the tree controller.<p>
1227     *
1228     * @return the logger
1229     */
1230    Log getTreeControllerLog() {
1231
1232        return LOG;
1233    }
1234
1235    /**
1236     * Creates the context menu for either the main resource or the linked resource of a tree node.
1237     *
1238     * @param entry the tree node data
1239     * @param node the tree node widget
1240     * @param linked true if we want to create the menu for the linked resource
1241     *
1242     * @return the context menu
1243     */
1244    private MenuBar createMenu(final CmsSitemapTreeNodeData entry, final CmsSitemapTreeNode node, boolean linked) {
1245
1246        final MenuBar menu = new MenuBar();
1247        final MenuItem main = menu.addItem("", null);
1248        main.setIcon(FontOpenCms.CONTEXT_MENU);
1249        main.setCommand(new Command() {
1250
1251            /** Serial version id. */
1252            private static final long serialVersionUID = 1L;
1253
1254            public void menuSelected(MenuItem selectedItem) {
1255
1256                List<I_CmsSimpleContextMenuEntry<MenuContext>> entries = Arrays.asList(
1257
1258                    new EntryOpen(),
1259                    new EntryExplorer(),
1260                    new EntryProperties(),
1261                    new EntryLink(),
1262                    new EntryUnlink(),
1263                    new EntryMark(),
1264                    new EntryRemoveMark(),
1265                    new EntryCopy(),
1266                    new EntryInfo());
1267
1268                MenuContext context = new MenuContext(entry, node, linked);
1269                m_menu.setEntries(entries, context);
1270                m_menu.open(menu);
1271
1272            }
1273
1274        });
1275        menu.addStyleName("borderless o-toolbar-button o-resourceinfo-toolbar");
1276        return menu;
1277    }
1278
1279    /**
1280     * Opens the page corresponding to a sitemap entry.<p>
1281     *
1282     * @param nodeData the node bean
1283     * @param second true if the user has clicked on the second resource box in a tree node
1284     */
1285    private void openTargetPage(CmsSitemapTreeNodeData nodeData, boolean second) {
1286
1287        CmsUUID id = nodeData.getClientEntry().getId();
1288        CmsUUID defaultFileId = nodeData.getClientEntry().getDefaultFileId();
1289        CmsUUID targetId = defaultFileId;
1290        if (targetId == null) {
1291            targetId = id;
1292        }
1293        try {
1294            CmsResource resource = A_CmsUI.getCmsObject().readResource(targetId, CmsResourceFilter.IGNORE_EXPIRATION);
1295            String link = OpenCms.getLinkManager().substituteLink(A_CmsUI.getCmsObject(), resource);
1296            if (second) {
1297                resource = A_CmsUI.getCmsObject().readResource(
1298                    nodeData.getLinkedResource().getStructureId(),
1299                    CmsResourceFilter.IGNORE_EXPIRATION);
1300                link = OpenCms.getLinkManager().substituteLink(A_CmsUI.getCmsObject(), resource);
1301            }
1302
1303            String mySiteRoot = A_CmsUI.getCmsObject().getRequestContext().getSiteRoot();
1304            final boolean sameSite = mySiteRoot.equals(OpenCms.getSiteManager().getSiteRoot(resource.getRootPath()));
1305
1306            if (sameSite) {
1307                A_CmsUI.get().getPage().setLocation(link);
1308            } else {
1309                String message = CmsVaadinUtils.getMessageText(
1310                    Messages.GUI_LOCALECOMPARE_SHOW_WRONGSITE_1,
1311                    resource.getRootPath());
1312
1313                Notification.show(message, Type.ERROR_MESSAGE);
1314            }
1315        } catch (CmsException e) {
1316            LOG.error(e.getLocalizedMessage(), e);
1317        }
1318
1319    }
1320
1321}