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.ade.containerpage.client;
029
030import org.opencms.ade.containerpage.client.ui.A_CmsToolbarOptionButton;
031import org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer;
032import org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel;
033import org.opencms.ade.containerpage.client.ui.CmsElementOptionBar;
034import org.opencms.ade.containerpage.client.ui.CmsGroupContainerElementPanel;
035import org.opencms.ade.containerpage.client.ui.CmsMenuListItem;
036import org.opencms.ade.containerpage.client.ui.I_CmsDropContainer;
037import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
038import org.opencms.ade.containerpage.shared.CmsContainer;
039import org.opencms.ade.containerpage.shared.CmsContainerElement;
040import org.opencms.ade.containerpage.shared.CmsContainerElementData;
041import org.opencms.ade.contenteditor.client.CmsContentEditor;
042import org.opencms.gwt.client.CmsCoreProvider;
043import org.opencms.gwt.client.CmsEditableData;
044import org.opencms.gwt.client.ui.CmsErrorDialog;
045import org.opencms.gwt.client.ui.CmsPushButton;
046import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuButton;
047import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuHandler;
048import org.opencms.gwt.client.util.CmsDebugLog;
049import org.opencms.gwt.client.util.CmsDomUtil;
050import org.opencms.gwt.shared.CmsCoreData.AdeContext;
051import org.opencms.gwt.shared.CmsGwtConstants;
052import org.opencms.gwt.shared.CmsTemplateContextInfo;
053import org.opencms.util.CmsStringUtil;
054import org.opencms.util.CmsUUID;
055
056import java.util.ArrayList;
057import java.util.HashMap;
058import java.util.Iterator;
059import java.util.List;
060import java.util.Map;
061
062import com.google.common.collect.Lists;
063import com.google.gwt.dom.client.Element;
064import com.google.gwt.dom.client.Node;
065import com.google.gwt.dom.client.Style;
066import com.google.gwt.event.dom.client.ClickEvent;
067import com.google.gwt.event.dom.client.ClickHandler;
068import com.google.gwt.user.client.DOM;
069import com.google.gwt.user.client.Window;
070
071/**
072 * Utility class for the container-page editor.<p>
073 *
074 * @since 8.0.0
075 */
076public class CmsContainerpageUtil {
077
078    /** The container page controller. */
079    private CmsContainerpageController m_controller;
080
081    /** List of buttons of the tool-bar. */
082    private A_CmsToolbarOptionButton[] m_optionButtons;
083
084    /**
085     * Constructor.<p>
086     *
087     * @param controller the container page controller
088     * @param optionButtons the tool-bar option buttons
089     */
090    public CmsContainerpageUtil(CmsContainerpageController controller, A_CmsToolbarOptionButton... optionButtons) {
091
092        m_controller = controller;
093        m_optionButtons = optionButtons;
094    }
095
096    /**
097     * Adds an option bar to the given drag element.<p>
098     *
099     * @param element the element
100     */
101    public void addOptionBar(CmsContainerPageElementPanel element) {
102
103        // the view permission is required for any actions regarding this element
104        if (element.hasViewPermission()) {
105            CmsElementOptionBar optionBar = CmsElementOptionBar.createOptionBarForElement(
106                element,
107                m_controller.getDndHandler(),
108                m_optionButtons);
109            element.setElementOptionBar(optionBar);
110        }
111    }
112
113    /**
114     * Transforms all contained elements into {@link CmsContainerPageElementPanel}.<p>
115     *
116     * @param container the container
117     */
118    public void consumeContainerElements(I_CmsDropContainer container) {
119
120        boolean containsElements = false;
121        // the drag element widgets are created from the existing DOM elements,
122        Element child = container.getElement().getFirstChildElement();
123        List<CmsContainerPageElementPanel> children = Lists.newArrayList();
124        while (child != null) {
125            boolean isContainerElement = CmsDomUtil.hasClass(
126                CmsContainerElement.CLASS_CONTAINER_ELEMENT_START_MARKER,
127                child);
128            boolean isGroupcontainerElement = CmsDomUtil.hasClass(
129                CmsContainerElement.CLASS_GROUP_CONTAINER_ELEMENT_MARKER,
130                child);
131            if (isContainerElement || isGroupcontainerElement) {
132                containsElements = true;
133                String serializedData = child.getAttribute(CmsGwtConstants.ATTR_DATA_ELEMENT);
134                child.removeAttribute(CmsGwtConstants.ATTR_DATA_ELEMENT);
135                CmsContainerElement elementData = null;
136                try {
137                    elementData = m_controller.getSerializedElement(serializedData);
138                } catch (Exception e) {
139                    CmsErrorDialog.handleException(
140                        new Exception(
141                            "Deserialization of element data failed. This may be caused by expired java-script resources, please clear your browser cache and try again.",
142                            e));
143                }
144                if (isContainerElement) {
145
146                    // searching for content element root
147                    Element elementRoot = (Element)child.getNextSibling();
148                    while ((elementRoot != null) && (elementRoot.getNodeType() != Node.ELEMENT_NODE)) {
149                        Element temp = elementRoot;
150                        elementRoot = (Element)elementRoot.getNextSibling();
151                        temp.removeFromParent();
152                    }
153                    if (elementRoot == null) {
154                        child.removeFromParent();
155                        child = null;
156                        continue;
157                    }
158                    if (CmsDomUtil.hasClass(CmsContainerElement.CLASS_CONTAINER_ELEMENT_START_MARKER, elementRoot)) {
159                        // broken element, already at next start marker
160                        if (elementData != null) {
161                            alertParsingError(elementData.getSitePath());
162                        }
163                        child.removeFromParent();
164                        child = elementRoot;
165                        continue;
166                    }
167                    if (CmsDomUtil.hasClass(CmsContainerElement.CLASS_CONTAINER_ELEMENT_END_MARKER, elementRoot)) {
168                        // broken element, no content element root
169                        if (elementData != null) {
170                            alertParsingError(elementData.getSitePath());
171                        }
172                        child.removeFromParent();
173                        child = elementRoot.getNextSiblingElement();
174                        elementRoot.removeFromParent();
175                        continue;
176                    } else {
177                        // looking for the next marker that wraps the current element
178                        Element endMarker = (Element)elementRoot.getNextSibling();
179                        // only if the end marker node is not null and has neither the end-marker class or start-marker class
180                        // remove the current node and check the next sibling
181                        while (!((endMarker == null)
182                            || ((endMarker.getNodeType() == Node.ELEMENT_NODE)
183                                && (CmsDomUtil.hasClass(
184                                    CmsContainerElement.CLASS_CONTAINER_ELEMENT_END_MARKER,
185                                    endMarker)
186                                    || CmsDomUtil.hasClass(
187                                        CmsContainerElement.CLASS_CONTAINER_ELEMENT_START_MARKER,
188                                        endMarker))))) {
189                            Element temp = endMarker;
190                            endMarker = (Element)endMarker.getNextSibling();
191                            temp.removeFromParent();
192                        }
193                        if (endMarker == null) {
194                            if (elementData != null) {
195                                alertParsingError(elementData.getSitePath());
196                            }
197                            // broken element, end marker missing
198                            elementRoot.removeFromParent();
199                            child.removeFromParent();
200                            child = null;
201                            continue;
202                        }
203                        if (CmsDomUtil.hasClass(CmsContainerElement.CLASS_CONTAINER_ELEMENT_START_MARKER, endMarker)) {
204                            if (elementData != null) {
205                                alertParsingError(elementData.getSitePath());
206                            }
207                            // broken element, end marker missing
208                            elementRoot.removeFromParent();
209                            child.removeFromParent();
210                            child = endMarker;
211                        }
212                        if (elementData == null) {
213                            // deserialization failed, remove whole element
214                            child.removeFromParent();
215                            elementRoot.removeFromParent();
216                            child = endMarker.getNextSiblingElement();
217                            endMarker.removeFromParent();
218                            continue;
219                        }
220                        CmsDomUtil.removeScriptTags(elementRoot);
221                        CmsContainerPageElementPanel containerElement = createElement(
222                            elementRoot,
223                            container,
224                            elementData);
225                        children.add(containerElement);
226                        if (elementData.isNew()) {
227                            containerElement.setNewType(elementData.getResourceType());
228                        }
229                        container.adoptElement(containerElement);
230                        child.removeFromParent();
231                        child = endMarker.getNextSiblingElement();
232                        endMarker.removeFromParent();
233                    }
234                } else if (isGroupcontainerElement && (container instanceof CmsContainerPageContainer)) {
235                    if (elementData == null) {
236                        Element sibling = child.getNextSiblingElement();
237                        container.getElement().removeChild(child);
238                        child = sibling;
239                        continue;
240                    }
241                    CmsDomUtil.removeScriptTags(child);
242                    CmsGroupContainerElementPanel groupContainer = createGroupcontainer(child, container, elementData);
243                    groupContainer.setContainerId(container.getContainerId());
244                    container.adoptElement(groupContainer);
245                    consumeContainerElements(groupContainer);
246                    if (groupContainer.getWidgetCount() == 0) {
247                        groupContainer.addStyleName(
248                            I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
249                    }
250                    children.add(groupContainer);
251                    // important: adding the option-bar only after the group-containers have been consumed
252                    if (container.isEditable()
253                        && (!m_controller.isDetailPage() || container.isDetailView() || container.isDetailOnly())) {
254                        //only allow editing if either element of detail only container or not in detail view
255                        if (m_controller.requiresOptionBar(groupContainer, container)) {
256                            addOptionBar(groupContainer);
257                        }
258                    }
259                    child = child.getNextSiblingElement();
260                }
261            } else {
262                Element sibling = child.getNextSiblingElement();
263                if (!containsElements && (sibling == null) && (container instanceof CmsContainerPageContainer)) {
264                    // this element is no container element and is the container only child, assume it is an empty container marker
265                    ((CmsContainerPageContainer)container).setEmptyContainerElement(child);
266                } else {
267                    // e.g. option bar
268                    if (!CmsContainerPageElementPanel.isOverlay(child)) {
269                        container.getElement().removeChild(child);
270                    }
271                }
272                child = sibling;
273                continue;
274            }
275        }
276        container.onConsumeChildren(children);
277    }
278
279    /**
280     * The method will create {@link CmsContainerPageContainer} object for all given containers
281     * by converting the associated DOM elements. The contained elements will be transformed into {@link CmsContainerPageElementPanel}.<p>
282     *
283     * @param containers the container data
284     * @param context the parent element to the containers
285     *
286     * @return the drag target containers
287     */
288    public Map<String, CmsContainerPageContainer> consumeContainers(
289        Map<String, CmsContainer> containers,
290        Element context) {
291
292        Map<String, CmsContainerPageContainer> result = new HashMap<String, CmsContainerPageContainer>();
293        List<Element> containerElements = CmsDomUtil.getElementsByClass(CmsContainerElement.CLASS_CONTAINER, context);
294        for (Element containerElement : containerElements) {
295            String data = containerElement.getAttribute(CmsGwtConstants.ATTR_DATA_CONTAINER);
296            try {
297                CmsContainer container = m_controller.getSerializedContainer(data);
298                containers.put(container.getName(), container);
299                try {
300                    CmsContainerPageContainer dragContainer = new CmsContainerPageContainer(
301                        container,
302                        containerElement);
303                    consumeContainerElements(dragContainer);
304                    result.put(container.getName(), dragContainer);
305                } catch (Exception e) {
306                    CmsErrorDialog.handleException(
307                        "Error parsing container "
308                            + container.getName()
309                            + ". Please check if your HTML is well formed.",
310                        e);
311                }
312            } catch (Exception e) {
313                CmsErrorDialog.handleException(
314                    new Exception(
315                        "Deserialization of container data failed. This may be caused by expired java-script resources, please clear your browser cache and try again.",
316                        e));
317            }
318            containerElement.removeAttribute(CmsGwtConstants.ATTR_DATA_CONTAINER);
319        }
320        return result;
321    }
322
323    /**
324     * Creates an drag container element.<p>
325     *
326     * @param containerElement the container element data
327     * @param container the container parent
328     * @param isNew in case of a newly created element
329     *
330     * @return the draggable element
331     *
332     * @throws Exception if something goes wrong
333     */
334    public CmsContainerPageElementPanel createElement(
335        CmsContainerElementData containerElement,
336        I_CmsDropContainer container,
337        boolean isNew)
338    throws Exception {
339
340        if (containerElement.isGroupContainer() || containerElement.isInheritContainer()) {
341            List<CmsContainerElementData> subElements = new ArrayList<CmsContainerElementData>();
342            for (String subId : containerElement.getSubItems()) {
343                CmsContainerElementData element = m_controller.getCachedElement(subId);
344                if (element != null) {
345                    subElements.add(element);
346                } else {
347                    CmsDebugLog.getInstance().printLine("Cached element not found");
348                }
349            }
350            return createGroupcontainerElement(containerElement, subElements, container);
351        }
352        Element element = CmsDomUtil.createElement(containerElement.getContents().get(container.getContainerId()));
353        // ensure any embedded flash players are set opaque so UI elements may be placed above them
354        CmsDomUtil.fixFlashZindex(element);
355        if (isNew) {
356            CmsContentEditor.replaceResourceIds(
357                element,
358                CmsUUID.getNullUUID().toString(),
359                CmsContainerpageController.getServerId(containerElement.getClientId()));
360        }
361
362        CmsContainerPageElementPanel result = createElement(element, container, containerElement);
363        if (!CmsContainerpageController.get().shouldShowInContext(containerElement)) {
364            result.getElement().getStyle().setDisplay(Style.Display.NONE);
365            result.addStyleName(CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER);
366        }
367        return result;
368    }
369
370    /**
371     * Creates a drag container element for group-container elements.<p>
372     *
373     * @param containerElement the container element data
374     * @param subElements the sub-elements
375     * @param container the drag parent
376     *
377     * @return the draggable element
378     *
379     * @throws Exception if something goes wrong
380     */
381    public CmsContainerPageElementPanel createGroupcontainerElement(
382        CmsContainerElementData containerElement,
383        List<CmsContainerElementData> subElements,
384        I_CmsDropContainer container)
385    throws Exception {
386
387        Element element = DOM.createDiv();
388        element.addClassName(CmsContainerElement.CLASS_GROUP_CONTAINER_ELEMENT_MARKER);
389        CmsGroupContainerElementPanel groupContainer = createGroupcontainer(element, container, containerElement);
390        groupContainer.setContainerId(container.getContainerId());
391        //adding sub-elements
392        Iterator<CmsContainerElementData> it = subElements.iterator();
393        while (it.hasNext()) {
394            CmsContainerElementData subElement = it.next();
395            if (subElement.getContents().containsKey(container.getContainerId())) {
396                CmsContainerPageElementPanel subDragElement = createElement(subElement, groupContainer, false);
397                groupContainer.add(subDragElement);
398            }
399        }
400        if (subElements.size() == 0) {
401            groupContainer.addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer());
402        }
403        addOptionBar(groupContainer);
404        return groupContainer;
405    }
406
407    /**
408     * Creates a list item.<p>
409     *
410     * @param containerElement the element data
411     *
412     * @return the list item widget
413     */
414    public CmsMenuListItem createListItem(final CmsContainerElementData containerElement) {
415
416        CmsMenuListItem listItem = new CmsMenuListItem(containerElement);
417        if (!containerElement.isGroupContainer()
418            && !containerElement.isInheritContainer()
419            && CmsStringUtil.isEmptyOrWhitespaceOnly(containerElement.getNoEditReason())) {
420            listItem.enableEdit(new ClickHandler() {
421
422                public void onClick(ClickEvent event) {
423
424                    CmsEditableData editableData = new CmsEditableData();
425                    editableData.setElementLanguage(CmsCoreProvider.get().getLocale());
426                    editableData.setStructureId(
427                        new CmsUUID(CmsContainerpageController.getServerId(containerElement.getClientId())));
428                    editableData.setSitePath(containerElement.getSitePath());
429                    getController().getContentEditorHandler().openDialog(editableData, false, null, null, null);
430                    ((CmsPushButton)event.getSource()).clearHoverState();
431                }
432            });
433        } else {
434            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(containerElement.getNoEditReason())) {
435                listItem.disableEdit(containerElement.getNoEditReason(), true);
436            } else {
437                listItem.disableEdit(Messages.get().key(Messages.GUI_CLIPBOARD_ITEM_CAN_NOT_BE_EDITED_0), false);
438            }
439        }
440        String clientId = containerElement.getClientId();
441        String serverId = CmsContainerpageController.getServerId(clientId);
442        if (CmsUUID.isValidUUID(serverId)) {
443            CmsContextMenuButton button = new CmsContextMenuButton(new CmsUUID(serverId), new CmsContextMenuHandler() {
444
445                @Override
446                public void refreshResource(CmsUUID structureId) {
447
448                    Window.Location.reload();
449                }
450            }, AdeContext.gallery);
451            listItem.getListItemWidget().addButtonToFront(button);
452        }
453        if (!containerElement.isAddDisabled()) {
454            listItem.initMoveHandle(m_controller.getDndHandler(), true);
455        }
456        return listItem;
457    }
458
459    /**
460     * Returns the container page controller.<p>
461     *
462     * @return the container page controller
463     */
464    protected CmsContainerpageController getController() {
465
466        return m_controller;
467    }
468
469    /**
470     * Displays the element parsing error dialog.<p>
471     *
472     * @param sitePath the element site path
473     */
474    private void alertParsingError(String sitePath) {
475
476        new CmsErrorDialog(
477            "Error parsing element "
478                + sitePath
479                + ". Please check if the HTML generated by the element formatter is well formed.",
480            null).center();
481    }
482
483    /**
484     * Creates an drag container element.<p>
485     *
486     * @param element the DOM element
487     * @param dragParent the drag parent
488     * @param elementData the element data
489     *
490     * @return the draggable element
491     */
492    private CmsContainerPageElementPanel createElement(
493        Element element,
494        I_CmsDropContainer dragParent,
495        CmsContainerElement elementData) {
496
497        CmsContainerPageElementPanel dragElement = new CmsContainerPageElementPanel(
498            element,
499            dragParent,
500            elementData.getClientId(),
501            elementData.getSitePath(),
502            elementData.getNoEditReason(),
503            elementData.getLockInfo(),
504            elementData.getTitle(),
505            elementData.getSubTitle(),
506            elementData.getResourceType(),
507            elementData.hasSettings(dragParent.getContainerId()),
508            elementData.hasViewPermission(),
509            elementData.hasWritePermission(),
510            elementData.isReleasedAndNotExpired(),
511            elementData.isNewEditorDisabled(),
512            elementData.hasEditHandler(),
513            elementData.getModelGroupId(),
514            elementData.isWasModelGroup(),
515            elementData.getElementView(),
516            elementData.getBigIconClasses());
517        dragElement.setCreateNew(elementData.isCreateNew());
518        if (m_controller.requiresOptionBar(dragElement, dragParent)) {
519            addOptionBar(dragElement);
520        }
521        if (m_controller.isInlineEditable(dragElement, dragParent)) {
522            dragElement.initInlineEditor(m_controller);
523        }
524        return dragElement;
525    }
526
527    /**
528     * Creates a drag container element. This will not add an option-bar!<p>
529     *
530     * @param element the DOM element
531     * @param dragParent the drag parent
532     * @param elementData the element data
533     *
534     * @return the draggable element
535     */
536    private CmsGroupContainerElementPanel createGroupcontainer(
537        Element element,
538        I_CmsDropContainer dragParent,
539        CmsContainerElement elementData) {
540
541        CmsGroupContainerElementPanel groupContainer = new CmsGroupContainerElementPanel(
542            element,
543            dragParent,
544            elementData.getClientId(),
545            elementData.getSitePath(),
546            elementData.getResourceType(),
547            elementData.getNoEditReason(),
548            elementData.getTitle(),
549            elementData.getSubTitle(),
550            elementData.hasSettings(dragParent.getContainerId()),
551            elementData.hasViewPermission(),
552            elementData.hasWritePermission(),
553            elementData.isReleasedAndNotExpired(),
554            elementData.getElementView(),
555            elementData.getBigIconClasses());
556        return groupContainer;
557    }
558}