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.ade.contenteditor.shared;
029
030import org.opencms.acacia.shared.CmsAttributeConfiguration;
031import org.opencms.acacia.shared.CmsEntity;
032import org.opencms.acacia.shared.CmsEntityAttribute;
033import org.opencms.acacia.shared.CmsTabInfo;
034import org.opencms.acacia.shared.CmsType;
035import org.opencms.gwt.shared.CmsModelResourceInfo;
036import org.opencms.util.CmsUUID;
037
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.List;
041import java.util.Map;
042import java.util.Set;
043
044/**
045 * Contains all information needed for editing an XMLContent.<p>
046 */
047public class CmsContentDefinition extends org.opencms.acacia.shared.CmsContentDefinition {
048
049    /** The id of the element settings tab. */
050    public static final String SETTINGS_TAB_ID = "###formattersettings###";
051
052    /** The entity id prefix. */
053    private static final String ENTITY_ID_PREFIX = "http://opencms.org/resources/";
054
055    /** The value of the acacia-unlock configuration option. */
056    private boolean m_autoUnlock;
057
058    /** The available locales. */
059    private Map<String, String> m_availableLocales;
060
061    /** A map from attribute names to complex widget configurations. */
062    private Map<String, CmsComplexWidgetData> m_complexWidgetData;
063
064    /** The content locales. */
065    private List<String> m_contentLocales;
066
067    /** Flag indicating the resource needs to removed on cancel. */
068    private boolean m_deleteOnCancel;
069
070    /** Indicates that editor change handlers are configured. */
071    private Set<String> m_editorChangeScopes;
072
073    /** The external widget configurations. */
074    private List<CmsExternalWidgetConfiguration> m_externalWidgetConfigurations;
075
076    /** The resource icon classes. */
077    private String m_iconClasses;
078
079    /** The direct edit flag (set to true for classic direct edit mode). */
080    private boolean m_isDirectEdit;
081
082    /** The model file informations. */
083    private List<CmsModelResourceInfo> m_modelInfos;
084
085    /** The new link. */
086    private String m_newLink;
087
088    /** Flag indicating the current content has an invalid XML structure and was auto corrected. */
089    private boolean m_performedAutocorrection;
090
091    /** The reference resource structure id. */
092    private CmsUUID m_referenceResourceId;
093
094    /** The resource type name. */
095    private String m_resourceType;
096
097    /** True if the element should be marked as 'reused' in the content editor. */
098    private boolean m_reusedElement;
099
100    /** The site path. */
101    private String m_sitePath;
102
103    /** The paths to skip during locale synchronization. */
104    private Collection<String> m_skipPaths;
105
106    /** The elements that require a synchronization across all locales. */
107    private List<String> m_synchronizations;
108
109    /** The locale synchronization values. */
110    private Map<String, String> m_syncValues;
111
112    /** Is there an active content augmentation? */
113    private boolean m_isTranslationEnabled;
114
115    /** The content title. */
116    private String m_title;
117
118    /**
119     * Constructor for model file informations object.<p>
120     *
121     * @param modelInfos the model file informations
122     * @param newLink the new link
123     * @param referenceId the reference resource structure id
124     * @param locale the locale
125     */
126    public CmsContentDefinition(
127        List<CmsModelResourceInfo> modelInfos,
128        String newLink,
129        CmsUUID referenceId,
130        String locale) {
131
132        super(null, null, null, null, null, true, locale);
133        m_modelInfos = modelInfos;
134        m_newLink = newLink;
135        m_referenceResourceId = referenceId;
136    }
137
138    /**
139     * Constructor.<p>
140     *
141     * @param entityId the entity id
142     * @param entities the locale specific entities of the content
143     * @param configurations the attribute configurations
144     * @param externalWidgetConfigurations the external widget configurations
145     * @param complexWidgetData the complex widget configurations
146     * @param types the types
147     * @param tabInfos the tab information
148     * @param locale the content locale
149     * @param contentLocales the content locales
150     * @param availableLocales the available locales
151     * @param synchronizations the elements that require a synchronization across all locales
152     * @param syncValues the locale synchronization values
153     * @param skipPaths the paths to skip during locale synchronization
154     * @param title the content title
155     * @param sitePath the site path
156     * @param resourceType the resource type name
157     * @param iconClasses the resource icon classes
158     * @param performedAutocorrection flag indicating the current content has an invalid XML structure and was auto corrected
159     * @param autoUnlock false if the editor should not unlock resources automatically in standalone mode
160     * @param editorChangeScopes the editor change handler scopes
161     */
162    public CmsContentDefinition(
163        String entityId,
164        Map<String, CmsEntity> entities,
165        Map<String, CmsAttributeConfiguration> configurations,
166        Collection<CmsExternalWidgetConfiguration> externalWidgetConfigurations,
167        Map<String, CmsComplexWidgetData> complexWidgetData,
168        Map<String, CmsType> types,
169        List<CmsTabInfo> tabInfos,
170        String locale,
171        List<String> contentLocales,
172        Map<String, String> availableLocales,
173        List<String> synchronizations,
174        Map<String, String> syncValues,
175        Collection<String> skipPaths,
176        String title,
177        String sitePath,
178        String resourceType,
179        String iconClasses,
180        boolean performedAutocorrection,
181        boolean autoUnlock,
182        Set<String> editorChangeScopes) {
183
184        super(entityId, entities, configurations, types, tabInfos, true, locale);
185        m_contentLocales = contentLocales;
186        m_availableLocales = availableLocales;
187        m_synchronizations = synchronizations;
188        m_syncValues = syncValues;
189        m_skipPaths = skipPaths;
190        m_complexWidgetData = complexWidgetData;
191        m_title = title;
192        m_sitePath = sitePath;
193        m_resourceType = resourceType;
194        m_iconClasses = iconClasses;
195        m_externalWidgetConfigurations = new ArrayList<CmsExternalWidgetConfiguration>(externalWidgetConfigurations);
196        m_performedAutocorrection = performedAutocorrection;
197        m_autoUnlock = autoUnlock;
198        m_editorChangeScopes = editorChangeScopes;
199    }
200
201    /**
202     * Constructor for serialization only.<p>
203     */
204    protected CmsContentDefinition() {
205
206        super();
207    }
208
209    /**
210     * Returns the UUID according to the given entity id.<p>
211     *
212     * @param entityId the entity id
213     *
214     * @return the entity id
215     */
216    public static CmsUUID entityIdToUuid(String entityId) {
217
218        if (entityId.startsWith(ENTITY_ID_PREFIX)) {
219            entityId = entityId.substring(entityId.lastIndexOf("/") + 1);
220        }
221        return new CmsUUID(entityId);
222    }
223
224    /**
225     * Extracts the locale from the entity id.<p>
226     *
227     * @param entityId the entity id
228     *
229     * @return the locale
230     */
231    public static String getLocaleFromId(String entityId) {
232
233        if (entityId.startsWith(ENTITY_ID_PREFIX)) {
234            return entityId.substring(ENTITY_ID_PREFIX.length(), entityId.lastIndexOf("/"));
235        }
236        return null;
237    }
238
239    /**
240     * Returns the value for the given XPath expression.<p>
241     *
242     * @param entity the entity
243     * @param path the path
244     *
245     * @return the value
246     */
247    public static String getValueForPath(CmsEntity entity, String path) {
248
249        String result = null;
250        if (path.startsWith("/")) {
251            path = path.substring(1);
252        }
253        String attributeName;
254        if (path.contains("/")) {
255            attributeName = path.substring(0, path.indexOf("/"));
256            path = path.substring(path.indexOf("/"));
257        } else {
258            attributeName = path;
259            path = null;
260        }
261        int index = org.opencms.acacia.shared.CmsContentDefinition.extractIndex(attributeName);
262        if (index > 0) {
263            index--;
264
265        }
266        String typeName = entity.getTypeName();
267
268        attributeName = typeName + "/" + org.opencms.acacia.shared.CmsContentDefinition.removeIndex(attributeName);
269        CmsEntityAttribute choiceAttr = entity.getAttribute(CmsType.CHOICE_ATTRIBUTE_NAME);
270        if ((choiceAttr != null) && choiceAttr.isComplexValue()) {
271            List<CmsEntity> choiceChildren = new ArrayList<>();
272            for (CmsEntity child : choiceAttr.getComplexValues()) {
273                if (child.getAttribute(attributeName) != null) {
274                    choiceChildren.add(child);
275                }
276            }
277            if (index < choiceChildren.size()) {
278                entity = choiceChildren.get(index);
279                index = 0;
280            } else {
281                return null;
282            }
283        }
284
285        CmsEntityAttribute attribute = entity.getAttribute(attributeName);
286        if (!((attribute == null) || (attribute.isComplexValue() && (path == null)))) {
287            if (attribute.isSimpleValue()) {
288                if ((path == null) && (attribute.getValueCount() > 0)) {
289                    List<String> values = attribute.getSimpleValues();
290                    result = values.get(index);
291                }
292            } else if (attribute.getValueCount() > (index)) {
293                List<CmsEntity> values = attribute.getComplexValues();
294                result = getValueForPath(values.get(index), path);
295            }
296        }
297        return result;
298    }
299
300    /**
301     * Transfers values from the original entity to the given target entity.<p>
302     *
303     * @param original the original entity
304     * @param target the target entity
305     * @param transferAttributes the attributes to consider for the value transfer
306     * @param entityTypes the entity types
307     * @param attributeConfigurations the attribute configurations
308     * @param considerDefaults if default values should be added according to minimum occurrence settings
309     */
310    public static void transferValues(
311        CmsEntity original,
312        CmsEntity target,
313        List<String> transferAttributes,
314        Map<String, CmsType> entityTypes,
315        Map<String, CmsAttributeConfiguration> attributeConfigurations,
316        boolean considerDefaults) {
317
318        CmsType entityType = entityTypes.get(target.getTypeName());
319        for (String attributeName : entityType.getAttributeNames()) {
320            CmsType attributeType = entityTypes.get(entityType.getAttributeTypeName(attributeName));
321            if (transferAttributes.contains(attributeName)) {
322
323                target.removeAttribute(attributeName);
324                CmsEntityAttribute attribute = original != null ? original.getAttribute(attributeName) : null;
325                if (attribute != null) {
326                    if (attributeType.isSimpleType()) {
327                        for (String value : attribute.getSimpleValues()) {
328                            target.addAttributeValue(attributeName, value);
329                        }
330                        if (considerDefaults) {
331                            for (int i = attribute.getValueCount(); i < entityType.getAttributeMinOccurrence(
332                                attributeName); i++) {
333                                target.addAttributeValue(
334                                    attributeName,
335                                    attributeConfigurations.get(attributeName).getDefaultValue());
336                            }
337                        }
338                    } else {
339                        for (CmsEntity value : attribute.getComplexValues()) {
340                            target.addAttributeValue(attributeName, value);
341                        }
342                        if (considerDefaults) {
343                            for (int i = attribute.getValueCount(); i < entityType.getAttributeMinOccurrence(
344                                attributeName); i++) {
345                                target.addAttributeValue(
346                                    attributeName,
347                                    createDefaultValueEntity(attributeType, entityTypes, attributeConfigurations));
348                            }
349                        }
350                    }
351                } else if (considerDefaults) {
352                    for (int i = 0; i < entityType.getAttributeMinOccurrence(attributeName); i++) {
353                        if (attributeType.isSimpleType()) {
354                            target.addAttributeValue(
355                                attributeName,
356                                attributeConfigurations.get(attributeName).getDefaultValue());
357                        } else {
358                            target.addAttributeValue(
359                                attributeName,
360                                createDefaultValueEntity(attributeType, entityTypes, attributeConfigurations));
361                        }
362                    }
363                }
364            } else {
365                if (!attributeType.isSimpleType()) {
366                    CmsEntityAttribute targetAttribute = target.getAttribute(attributeName);
367                    CmsEntityAttribute originalAttribute = original != null
368                    ? original.getAttribute(attributeName)
369                    : null;
370                    if (targetAttribute != null) {
371                        for (int i = 0; i < targetAttribute.getComplexValues().size(); i++) {
372                            CmsEntity subTarget = targetAttribute.getComplexValues().get(i);
373                            CmsEntity subOriginal = (originalAttribute != null)
374                                && (originalAttribute.getComplexValues().size() > i)
375                                ? originalAttribute.getComplexValues().get(i)
376                                : null;
377                            transferValues(
378                                subOriginal,
379                                subTarget,
380                                transferAttributes,
381                                entityTypes,
382                                attributeConfigurations,
383                                considerDefaults);
384                        }
385                    }
386                }
387            }
388        }
389    }
390
391    /**
392     * Returns the entity id according to the given UUID.<p>
393     *
394     * @param uuid the UUID
395     * @param locale the content locale
396     *
397     * @return the entity id
398     */
399    public static String uuidToEntityId(CmsUUID uuid, String locale) {
400
401        return ENTITY_ID_PREFIX + locale + "/" + uuid.toString();
402    }
403
404    /**
405     * Creates an entity object containing the default values configured for it's type.<p>
406     *
407     * @param entityType the entity type
408     * @param entityTypes the entity types
409     * @param attributeConfigurations the attribute configurations
410     *
411     * @return the created entity
412     */
413    protected static CmsEntity createDefaultValueEntity(
414        CmsType entityType,
415        Map<String, CmsType> entityTypes,
416        Map<String, CmsAttributeConfiguration> attributeConfigurations) {
417
418        CmsEntity result = new CmsEntity(null, entityType.getId());
419        for (String attributeName : entityType.getAttributeNames()) {
420            CmsType attributeType = entityTypes.get(entityType.getAttributeTypeName(attributeName));
421            for (int i = 0; i < entityType.getAttributeMinOccurrence(attributeName); i++) {
422                if (attributeType.isSimpleType()) {
423                    result.addAttributeValue(
424                        attributeName,
425                        attributeConfigurations.get(attributeName).getDefaultValue());
426                } else {
427                    result.addAttributeValue(
428                        attributeName,
429                        createDefaultValueEntity(attributeType, entityTypes, attributeConfigurations));
430                }
431            }
432        }
433        return result;
434    }
435
436    /**
437     * Returns the available locales.<p>
438     *
439     * @return the available locales
440     */
441    public Map<String, String> getAvailableLocales() {
442
443        return m_availableLocales;
444    }
445
446    /**
447     * Gets the complex widget configurations.<p>
448     *
449     * @return the complex widget configurations
450     */
451    public Map<String, CmsComplexWidgetData> getComplexWidgetData() {
452
453        return m_complexWidgetData;
454    }
455
456    /**
457     * Returns the content locales.<p>
458     *
459     * @return the content locales
460     */
461    public List<String> getContentLocales() {
462
463        return m_contentLocales;
464    }
465
466    /**
467     * Returns the editor change handler scopes.<p>
468     *
469     * @return the editor change handler scopes
470     */
471    public Set<String> getEditorChangeScopes() {
472
473        return m_editorChangeScopes;
474    }
475
476    /**
477     * Returns the external widget configurations.<p>
478     *
479     * @return the external widget configurations
480     */
481    public List<CmsExternalWidgetConfiguration> getExternalWidgetConfigurations() {
482
483        return m_externalWidgetConfigurations;
484    }
485
486    /**
487     * Returns the resource icon classes.<p>
488     *
489     * @return the resource icon classes
490     */
491    public String getIconClasses() {
492
493        return m_iconClasses;
494    }
495
496    /**
497     * Returns the model file informations.<p>
498     *
499     * @return the model file informations
500     */
501    public List<CmsModelResourceInfo> getModelInfos() {
502
503        return m_modelInfos;
504    }
505
506    /**
507     * Returns the new link.<p>
508     *
509     * @return the new link
510     */
511    public String getNewLink() {
512
513        return m_newLink;
514    }
515
516    /**
517     * Returns the reference resource structure id.<p>
518     *
519     * @return the reference resource structure id
520     */
521    public CmsUUID getReferenceResourceId() {
522
523        return m_referenceResourceId;
524    }
525
526    /**
527     * Returns the resource type.<p>
528     *
529     * @return the resource type
530     */
531    public String getResourceType() {
532
533        return m_resourceType;
534    }
535
536    /**
537     * Returns the site path.<p>
538     *
539     * @return the site path
540     */
541    public String getSitePath() {
542
543        return m_sitePath;
544    }
545
546    /**
547     * Returns the paths to skip during locale synchronization.<p>
548     *
549     * @return the paths to skip during locale synchronization
550     */
551    public Collection<String> getSkipPaths() {
552
553        return m_skipPaths;
554    }
555
556    /**
557     * Returns the elements that require a synchronization across all locales.<p>
558     *
559     * @return the element paths
560     */
561    public List<String> getSynchronizations() {
562
563        return m_synchronizations;
564    }
565
566    /**
567     * Returns the locale synchronization values.<p>
568     *
569     * @return the locale synchronization values
570     */
571    public Map<String, String> getSyncValues() {
572
573        return m_syncValues;
574    }
575
576    /**
577     * Returns the title.<p>
578     *
579     * @return the title
580     */
581    public String getTitle() {
582
583        return m_title;
584    }
585
586    /**
587     * Checks whether there is an active content augmentation.
588     *
589     * @return true if there is an active content augmentation
590     */
591    public boolean isTranslationEnabled() {
592
593        return m_isTranslationEnabled;
594    }
595
596    /**
597     * Returns <code>true</code> if any editor change handlers have been configured for this content type.<p>
598     *
599     * @return <code>true</code> if any editor change handlers have been configured for this content type.<p>
600     */
601    public boolean hasEditorChangeHandlers() {
602
603        return (m_editorChangeScopes != null) && !m_editorChangeScopes.isEmpty();
604    }
605
606    /**
607     * Returns if there are locale synchronized elements configured.<p>
608     *
609     * @return <code>true</code> if there are locale synchronized elements configured
610     */
611    public boolean hasSynchronizedElements() {
612
613        return !m_synchronizations.isEmpty();
614    }
615
616    /**
617     * Returns the value of the acacia-unlock configuration option.<p>
618     *
619     * @return the value of the acacia-unlock configuration option
620     */
621    public boolean isAutoUnlock() {
622
623        return m_autoUnlock;
624    }
625
626    /**
627     * Returns if the resource needs to removed on cancel.<p>
628     *
629     * @return <code>true</code> if the resource needs to removed on cancel
630     */
631    public boolean isDeleteOnCancel() {
632
633        return m_deleteOnCancel;
634    }
635
636    /**
637     * Returns true if the direct edit flag is set, which means that the editor was opened from the classic direct edit mode.<p>
638     *
639     * @return true if the direct edit flag is set
640     */
641    public boolean isDirectEdit() {
642
643        return m_isDirectEdit;
644    }
645
646    /**
647     * Returns if the model file informations are present, in this case no additional data is contained.<p>
648     *
649     * @return <code>true</code> if the definition contains the model file informations
650     */
651    public boolean isModelInfo() {
652
653        return m_modelInfos != null;
654    }
655
656    /**
657     * Returns if auto correction was performed.<p>
658     *
659     * @return <code>true</code> if auto correction was performed
660     */
661    public boolean isPerformedAutocorrection() {
662
663        return m_performedAutocorrection;
664    }
665
666    /**
667     * Checks if the element should be marked as 'reused' in the editor.
668     *
669     * @return true if the element should be marked as 'reused' in the editor
670     */
671    public boolean isReusedElement() {
672
673        return m_reusedElement;
674    }
675
676    /**
677     * Sets if the resource needs to removed on cancel.<p>
678     *
679     * @param deleteOnCancel <code>true</code> if the resource needs to removed on cancel
680     */
681    public void setDeleteOnCancel(boolean deleteOnCancel) {
682
683        m_deleteOnCancel = deleteOnCancel;
684    }
685
686    /**
687     * Sets the value of the direct edit flag.<p>
688     *
689     * @param isDirectEdit the new value for the direct edit flag
690     */
691    public void setDirectEdit(boolean isDirectEdit) {
692
693        m_isDirectEdit = isDirectEdit;
694    }
695
696    public void setTranslationEnabled(boolean translationEnabled) {
697
698        m_isTranslationEnabled = translationEnabled;
699    }
700
701    /**
702     * Enables / disables marking of the element as 'reused' in the content editor.
703     *
704     * @param reused true if the element should be shown as 'reused'
705     */
706    public void setReusedElement(boolean reused) {
707
708        m_reusedElement = reused;
709
710    }
711}