001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.model.impl;
016    
017    import com.liferay.portal.kernel.exception.LayoutFriendlyURLException;
018    import com.liferay.portal.kernel.exception.NoSuchGroupException;
019    import com.liferay.portal.kernel.exception.PortalException;
020    import com.liferay.portal.kernel.exception.SystemException;
021    import com.liferay.portal.kernel.language.LanguageUtil;
022    import com.liferay.portal.kernel.log.Log;
023    import com.liferay.portal.kernel.log.LogFactoryUtil;
024    import com.liferay.portal.kernel.model.ColorScheme;
025    import com.liferay.portal.kernel.model.Group;
026    import com.liferay.portal.kernel.model.GroupConstants;
027    import com.liferay.portal.kernel.model.Layout;
028    import com.liferay.portal.kernel.model.LayoutConstants;
029    import com.liferay.portal.kernel.model.LayoutFriendlyURL;
030    import com.liferay.portal.kernel.model.LayoutSet;
031    import com.liferay.portal.kernel.model.LayoutType;
032    import com.liferay.portal.kernel.model.LayoutTypeController;
033    import com.liferay.portal.kernel.model.LayoutTypePortlet;
034    import com.liferay.portal.kernel.model.LayoutTypePortletConstants;
035    import com.liferay.portal.kernel.model.Portlet;
036    import com.liferay.portal.kernel.model.PortletPreferences;
037    import com.liferay.portal.kernel.model.Theme;
038    import com.liferay.portal.kernel.portlet.PortalPreferences;
039    import com.liferay.portal.kernel.security.permission.ActionKeys;
040    import com.liferay.portal.kernel.security.permission.PermissionChecker;
041    import com.liferay.portal.kernel.service.GroupLocalServiceUtil;
042    import com.liferay.portal.kernel.service.LayoutFriendlyURLLocalServiceUtil;
043    import com.liferay.portal.kernel.service.LayoutLocalServiceUtil;
044    import com.liferay.portal.kernel.service.LayoutSetLocalServiceUtil;
045    import com.liferay.portal.kernel.service.PortletLocalServiceUtil;
046    import com.liferay.portal.kernel.service.PortletPreferencesLocalServiceUtil;
047    import com.liferay.portal.kernel.service.ThemeLocalServiceUtil;
048    import com.liferay.portal.kernel.service.permission.LayoutPermissionUtil;
049    import com.liferay.portal.kernel.theme.ThemeDisplay;
050    import com.liferay.portal.kernel.util.ArrayUtil;
051    import com.liferay.portal.kernel.util.CharPool;
052    import com.liferay.portal.kernel.util.CookieKeys;
053    import com.liferay.portal.kernel.util.GetterUtil;
054    import com.liferay.portal.kernel.util.Http;
055    import com.liferay.portal.kernel.util.HttpUtil;
056    import com.liferay.portal.kernel.util.LayoutTypePortletFactoryUtil;
057    import com.liferay.portal.kernel.util.ListUtil;
058    import com.liferay.portal.kernel.util.LocaleUtil;
059    import com.liferay.portal.kernel.util.LocalizationUtil;
060    import com.liferay.portal.kernel.util.PortalUtil;
061    import com.liferay.portal.kernel.util.PortletKeys;
062    import com.liferay.portal.kernel.util.PropsKeys;
063    import com.liferay.portal.kernel.util.StringPool;
064    import com.liferay.portal.kernel.util.StringUtil;
065    import com.liferay.portal.kernel.util.UnicodeProperties;
066    import com.liferay.portal.kernel.util.Validator;
067    import com.liferay.portal.kernel.util.WebKeys;
068    import com.liferay.portal.util.LayoutClone;
069    import com.liferay.portal.util.LayoutCloneFactory;
070    import com.liferay.portal.util.PropsValues;
071    import com.liferay.portlet.PortletURLImpl;
072    
073    import java.io.IOException;
074    
075    import java.util.ArrayList;
076    import java.util.Collections;
077    import java.util.HashMap;
078    import java.util.HashSet;
079    import java.util.Iterator;
080    import java.util.List;
081    import java.util.Locale;
082    import java.util.Map;
083    import java.util.Objects;
084    import java.util.Set;
085    
086    import javax.portlet.PortletException;
087    import javax.portlet.PortletMode;
088    import javax.portlet.PortletRequest;
089    import javax.portlet.WindowState;
090    
091    import javax.servlet.http.HttpServletRequest;
092    import javax.servlet.http.HttpServletResponse;
093    import javax.servlet.http.HttpSession;
094    
095    /**
096     * Represents a portal layout, providing access to the layout's URLs, parent
097     * layouts, child layouts, theme settings, type settings, and more.
098     *
099     * <p>
100     * The UI name for a layout is "page." Thus, a layout represents a page in the
101     * portal. A single page is either part of the public or private layout set of a
102     * group (site). Layouts can be organized hierarchically and are summarized in a
103     * {@link LayoutSet}.
104     * </p>
105     *
106     * @author Brian Wing Shun Chan
107     */
108    public class LayoutImpl extends LayoutBaseImpl {
109    
110            public static boolean hasFriendlyURLKeyword(String friendlyURL) {
111                    String keyword = _getFriendlyURLKeyword(friendlyURL);
112    
113                    if (Validator.isNotNull(keyword)) {
114                            return true;
115                    }
116    
117                    return false;
118            }
119    
120            public static int validateFriendlyURL(String friendlyURL) {
121                    return validateFriendlyURL(friendlyURL, true);
122            }
123    
124            /**
125             * Checks whether the URL is a valid friendly URL. It checks for minimal
126             * length and that syntactic restrictions are met, and can check that the
127             * URL's length does not exceed the maximum length.
128             *
129             * @param  friendlyURL the URL to be checked
130             * @param  checkMaxLength whether to check that the URL's length does not
131             *         exceed the maximum length
132             * @return <code>-1</code> if the URL is a valid friendly URL; a {@link
133             *         LayoutFriendlyURLException} constant otherwise
134             */
135            public static int validateFriendlyURL(
136                    String friendlyURL, boolean checkMaxLength) {
137    
138                    if (friendlyURL.length() < 2) {
139                            return LayoutFriendlyURLException.TOO_SHORT;
140                    }
141    
142                    if (checkMaxLength &&
143                            (friendlyURL.length() > LayoutConstants.FRIENDLY_URL_MAX_LENGTH)) {
144    
145                            return LayoutFriendlyURLException.TOO_LONG;
146                    }
147    
148                    if (!friendlyURL.startsWith(StringPool.SLASH)) {
149                            return LayoutFriendlyURLException.DOES_NOT_START_WITH_SLASH;
150                    }
151    
152                    if (friendlyURL.endsWith(StringPool.SLASH)) {
153                            return LayoutFriendlyURLException.ENDS_WITH_SLASH;
154                    }
155    
156                    if (friendlyURL.contains(StringPool.DOUBLE_SLASH)) {
157                            return LayoutFriendlyURLException.ADJACENT_SLASHES;
158                    }
159    
160                    for (char c : friendlyURL.toCharArray()) {
161                            if (!Validator.isChar(c) && !Validator.isDigit(c) &&
162                                    (c != CharPool.DASH) && (c != CharPool.PERCENT) &&
163                                    (c != CharPool.PERIOD) && (c != CharPool.PLUS) &&
164                                    (c != CharPool.SLASH) && (c != CharPool.STAR) &&
165                                    (c != CharPool.UNDERLINE)) {
166    
167                                    return LayoutFriendlyURLException.INVALID_CHARACTERS;
168                            }
169                    }
170    
171                    return -1;
172            }
173    
174            public static void validateFriendlyURLKeyword(String friendlyURL)
175                    throws LayoutFriendlyURLException {
176    
177                    String keyword = _getFriendlyURLKeyword(friendlyURL);
178    
179                    if (Validator.isNotNull(keyword)) {
180                            LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException(
181                                    LayoutFriendlyURLException.KEYWORD_CONFLICT);
182    
183                            lfurle.setKeywordConflict(keyword);
184    
185                            throw lfurle;
186                    }
187            }
188    
189            /**
190             * Returns all layouts that are direct or indirect children of the current
191             * layout.
192             *
193             * @return the layouts that are direct or indirect children of the current
194             *         layout
195             */
196            @Override
197            public List<Layout> getAllChildren() {
198                    List<Layout> layouts = new ArrayList<>();
199    
200                    for (Layout layout : getChildren()) {
201                            layouts.add(layout);
202                            layouts.addAll(layout.getAllChildren());
203                    }
204    
205                    return layouts;
206            }
207    
208            /**
209             * Returns the ID of the topmost parent layout (e.g. n-th parent layout) of
210             * the current layout.
211             *
212             * @return the ID of the topmost parent layout of the current layout
213             */
214            @Override
215            public long getAncestorLayoutId() throws PortalException {
216                    long layoutId = 0;
217    
218                    Layout layout = this;
219    
220                    while (true) {
221                            if (!layout.isRootLayout()) {
222                                    layout = LayoutLocalServiceUtil.getLayout(
223                                            layout.getGroupId(), layout.isPrivateLayout(),
224                                            layout.getParentLayoutId());
225                            }
226                            else {
227                                    layoutId = layout.getLayoutId();
228    
229                                    break;
230                            }
231                    }
232    
233                    return layoutId;
234            }
235    
236            /**
237             * Returns the plid of the topmost parent layout (e.g. n-th parent layout)
238             * of the current layout.
239             *
240             * @return the plid of the topmost parent layout of the current layout
241             */
242            @Override
243            public long getAncestorPlid() throws PortalException {
244                    long plid = 0;
245    
246                    Layout layout = this;
247    
248                    while (true) {
249                            if (!layout.isRootLayout()) {
250                                    layout = LayoutLocalServiceUtil.getLayout(
251                                            layout.getGroupId(), layout.isPrivateLayout(),
252                                            layout.getParentLayoutId());
253                            }
254                            else {
255                                    plid = layout.getPlid();
256    
257                                    break;
258                            }
259                    }
260    
261                    return plid;
262            }
263    
264            /**
265             * Returns all parent layouts of the current layout. The list is retrieved
266             * recursively with the direct parent layout listed first, and most distant
267             * parent listed last.
268             *
269             * @return the current layout's list of parent layouts
270             */
271            @Override
272            public List<Layout> getAncestors() throws PortalException {
273                    List<Layout> layouts = new ArrayList<>();
274    
275                    Layout layout = this;
276    
277                    while (!layout.isRootLayout()) {
278                            layout = LayoutLocalServiceUtil.getLayout(
279                                    layout.getGroupId(), layout.isPrivateLayout(),
280                                    layout.getParentLayoutId());
281    
282                            layouts.add(layout);
283                    }
284    
285                    return layouts;
286            }
287    
288            /**
289             * Returns all child layouts of the current layout, independent of user
290             * access permissions.
291             *
292             * @return the list of all child layouts
293             */
294            @Override
295            public List<Layout> getChildren() {
296                    return LayoutLocalServiceUtil.getLayouts(
297                            getGroupId(), isPrivateLayout(), getLayoutId());
298            }
299    
300            /**
301             * Returns all child layouts of the current layout that the user has
302             * permission to access.
303             *
304             * @param  permissionChecker the user-specific context to check permissions
305             * @return the list of all child layouts that the user has permission to
306             *         access
307             */
308            @Override
309            public List<Layout> getChildren(PermissionChecker permissionChecker)
310                    throws PortalException {
311    
312                    List<Layout> layouts = ListUtil.copy(getChildren());
313    
314                    Iterator<Layout> itr = layouts.iterator();
315    
316                    while (itr.hasNext()) {
317                            Layout layout = itr.next();
318    
319                            if (layout.isHidden() ||
320                                    !LayoutPermissionUtil.contains(
321                                            permissionChecker, layout, ActionKeys.VIEW)) {
322    
323                                    itr.remove();
324                            }
325                    }
326    
327                    return layouts;
328            }
329    
330            /**
331             * Returns the color scheme that is configured for the current layout, or
332             * the color scheme of the layout set that contains the current layout if no
333             * color scheme is configured.
334             *
335             * @return the color scheme that is configured for the current layout, or
336             *         the color scheme  of the layout set that contains the current
337             *         layout if no color scheme is configured
338             */
339            @Override
340            public ColorScheme getColorScheme() throws PortalException {
341                    if (isInheritLookAndFeel()) {
342                            LayoutSet layoutSet = getLayoutSet();
343    
344                            return layoutSet.getColorScheme();
345                    }
346                    else {
347                            Theme theme = getTheme();
348    
349                            return ThemeLocalServiceUtil.getColorScheme(
350                                    getCompanyId(), theme.getThemeId(), getColorSchemeId());
351                    }
352            }
353    
354            /**
355             * Returns the CSS text for the current layout, or for the layout set if no
356             * CSS text is configured in the current layout.
357             *
358             * <p>
359             * Layouts and layout sets can configure CSS that is applied in addition to
360             * the theme's CSS.
361             * </p>
362             *
363             * @return the CSS text for the current layout, or for the layout set if no
364             *         CSS text is configured in the current layout
365             */
366            @Override
367            public String getCssText() throws PortalException {
368                    if (isInheritLookAndFeel()) {
369                            LayoutSet layoutSet = getLayoutSet();
370    
371                            return layoutSet.getCss();
372                    }
373                    else {
374                            return getCss();
375                    }
376            }
377    
378            @Override
379            public String getDefaultThemeSetting(
380                    String key, String device, boolean inheritLookAndFeel) {
381    
382                    if (!inheritLookAndFeel) {
383                            try {
384                                    Theme theme = getTheme();
385    
386                                    return theme.getSetting(key);
387                            }
388                            catch (Exception e) {
389                            }
390                    }
391    
392                    try {
393                            LayoutSet layoutSet = getLayoutSet();
394    
395                            return layoutSet.getThemeSetting(key, device);
396                    }
397                    catch (Exception e) {
398                    }
399    
400                    return StringPool.BLANK;
401            }
402    
403            @Override
404            public List<Portlet> getEmbeddedPortlets() {
405                    return getEmbeddedPortlets(getGroupId());
406            }
407    
408            @Override
409            public List<Portlet> getEmbeddedPortlets(long groupId) {
410                    List<PortletPreferences> portletPreferences = _getPortletPreferences(
411                            groupId);
412    
413                    if (portletPreferences.isEmpty()) {
414                            return Collections.emptyList();
415                    }
416    
417                    List<Portlet> portlets = new ArrayList<>();
418    
419                    Set<String> layoutPortletIds = _getLayoutPortletIds();
420    
421                    for (PortletPreferences portletPreference : portletPreferences) {
422                            String portletId = portletPreference.getPortletId();
423    
424                            Portlet portlet = PortletLocalServiceUtil.getPortletById(
425                                    getCompanyId(), portletId);
426    
427                            if ((portlet == null) || !portlet.isReady() ||
428                                    portlet.isUndeployedPortlet() || !portlet.isActive() ||
429                                    !layoutPortletIds.contains(portletId)) {
430    
431                                    continue;
432                            }
433    
434                            Portlet embeddedPortlet = portlet;
435    
436                            if (portlet.isInstanceable()) {
437    
438                                    // Instanceable portlets do not need to be cloned because they
439                                    // are already cloned. See the method getPortletById in the
440                                    // class PortletLocalServiceImpl and how it references the
441                                    // method getClonedInstance in the class PortletImpl.
442    
443                            }
444                            else {
445                                    embeddedPortlet = (Portlet)embeddedPortlet.clone();
446                            }
447    
448                            // We set embedded portlets as static on order to avoid adding the
449                            // close and/or move icons.
450    
451                            embeddedPortlet.setStatic(true);
452    
453                            portlets.add(embeddedPortlet);
454                    }
455    
456                    return portlets;
457            }
458    
459            /**
460             * Returns the layout's friendly URL for the given locale.
461             *
462             * @param  locale the locale that the friendly URL should be retrieved for
463             * @return the layout's friendly URL for the given locale
464             */
465            @Override
466            public String getFriendlyURL(Locale locale) {
467                    Layout layout = this;
468    
469                    String friendlyURL = layout.getFriendlyURL();
470    
471                    try {
472                            Group group = layout.getGroup();
473    
474                            UnicodeProperties typeSettingsProperties =
475                                    group.getTypeSettingsProperties();
476    
477                            if (!GetterUtil.getBoolean(
478                                            typeSettingsProperties.getProperty(
479                                                    GroupConstants.TYPE_SETTINGS_KEY_INHERIT_LOCALES),
480                                            true)) {
481    
482                                    String[] locales = StringUtil.split(
483                                            typeSettingsProperties.getProperty(PropsKeys.LOCALES));
484    
485                                    if (!ArrayUtil.contains(
486                                                    locales, LanguageUtil.getLanguageId(locale))) {
487    
488                                            return friendlyURL;
489                                    }
490                            }
491    
492                            LayoutFriendlyURL layoutFriendlyURL =
493                                    LayoutFriendlyURLLocalServiceUtil.getLayoutFriendlyURL(
494                                            layout.getPlid(), LocaleUtil.toLanguageId(locale));
495    
496                            friendlyURL = layoutFriendlyURL.getFriendlyURL();
497                    }
498                    catch (Exception e) {
499                    }
500    
501                    return friendlyURL;
502            }
503    
504            /**
505             * Returns the friendly URLs for all configured locales.
506             *
507             * @return the friendly URLs for all configured locales
508             */
509            @Override
510            public Map<Locale, String> getFriendlyURLMap() {
511                    Map<Locale, String> friendlyURLMap = new HashMap<>();
512    
513                    List<LayoutFriendlyURL> layoutFriendlyURLs =
514                            LayoutFriendlyURLLocalServiceUtil.getLayoutFriendlyURLs(getPlid());
515    
516                    for (LayoutFriendlyURL layoutFriendlyURL : layoutFriendlyURLs) {
517                            friendlyURLMap.put(
518                                    LocaleUtil.fromLanguageId(layoutFriendlyURL.getLanguageId()),
519                                    layoutFriendlyURL.getFriendlyURL());
520                    }
521    
522                    // If the site/portal default language changes, there may not exist a
523                    // value for the new default language. In this situation, we will use
524                    // the value from the previous default language.
525    
526                    Locale defaultSiteLocale = LocaleUtil.getSiteDefault();
527    
528                    if (Validator.isNull(friendlyURLMap.get(defaultSiteLocale))) {
529                            Locale defaultLocale = LocaleUtil.fromLanguageId(
530                                    getDefaultLanguageId());
531    
532                            String defaultFriendlyURL = friendlyURLMap.get(defaultLocale);
533    
534                            friendlyURLMap.put(defaultSiteLocale, defaultFriendlyURL);
535                    }
536    
537                    return friendlyURLMap;
538            }
539    
540            @Override
541            public String getFriendlyURLsXML() {
542                    Map<Locale, String> friendlyURLMap = getFriendlyURLMap();
543    
544                    return LocalizationUtil.updateLocalization(
545                            friendlyURLMap, StringPool.BLANK, "FriendlyURL",
546                            LocaleUtil.toLanguageId(LocaleUtil.getSiteDefault()));
547            }
548    
549            /**
550             * Returns the current layout's group.
551             *
552             * <p>
553             * Group is Liferay's technical name for a site.
554             * </p>
555             *
556             * @return the current layout's group
557             */
558            @Override
559            public Group getGroup() throws PortalException {
560                    return GroupLocalServiceUtil.getGroup(getGroupId());
561            }
562    
563            /**
564             * Returns the current layout's HTML title for the given locale, or the
565             * current layout's name for the given locale if no HTML title is
566             * configured.
567             *
568             * @param  locale the locale that the HTML title should be retrieved for
569             * @return the current layout's HTML title for the given locale, or the
570             *         current layout's name for the given locale if no HTML title is
571             *         configured
572             */
573            @Override
574            public String getHTMLTitle(Locale locale) {
575                    String localeLanguageId = LocaleUtil.toLanguageId(locale);
576    
577                    return getHTMLTitle(localeLanguageId);
578            }
579    
580            /**
581             * Returns the current layout's HTML title for the given locale language ID,
582             * or the current layout's name if no HTML title is configured.
583             *
584             * @param  localeLanguageId the locale that the HTML title should be
585             *         retrieved for
586             * @return the current layout's HTML title for the given locale language ID,
587             *         or the current layout's name if no HTML title is configured
588             */
589            @Override
590            public String getHTMLTitle(String localeLanguageId) {
591                    String htmlTitle = getTitle(localeLanguageId);
592    
593                    if (Validator.isNull(htmlTitle)) {
594                            htmlTitle = getName(localeLanguageId);
595                    }
596    
597                    return htmlTitle;
598            }
599    
600            /**
601             * Returns <code>true</code> if the current layout has a configured icon.
602             *
603             * @return <code>true</code> if the current layout has a configured icon;
604             *         <code>false</code> otherwise
605             */
606            @Override
607            public boolean getIconImage() {
608                    if (getIconImageId() > 0) {
609                            return true;
610                    }
611    
612                    return false;
613            }
614    
615            /**
616             * Returns the current layout's {@link LayoutSet}.
617             *
618             * @return the current layout's layout set
619             */
620            @Override
621            public LayoutSet getLayoutSet() throws PortalException {
622                    if (_layoutSet == null) {
623                            _layoutSet = LayoutSetLocalServiceUtil.getLayoutSet(
624                                    getGroupId(), isPrivateLayout());
625                    }
626    
627                    return _layoutSet;
628            }
629    
630            /**
631             * Returns the current layout's {@link LayoutType}.
632             *
633             * @return the current layout's layout type
634             */
635            @Override
636            public LayoutType getLayoutType() {
637                    if (_layoutType == null) {
638                            _layoutType = LayoutTypePortletFactoryUtil.create(this);
639                    }
640    
641                    return _layoutType;
642            }
643    
644            /**
645             * Returns the current layout's linked layout.
646             *
647             * @return the current layout's linked layout, or <code>null</code> if no
648             *         linked layout could be found
649             */
650            @Override
651            public Layout getLinkedToLayout() {
652                    long linkToLayoutId = GetterUtil.getLong(
653                            getTypeSettingsProperty("linkToLayoutId"));
654    
655                    if (linkToLayoutId <= 0) {
656                            return null;
657                    }
658    
659                    return LayoutLocalServiceUtil.fetchLayout(
660                            getGroupId(), isPrivateLayout(), linkToLayoutId);
661            }
662    
663            /**
664             * Returns the current layout's parent plid.
665             *
666             * @return the current layout's parent plid, or <code>0</code> if the
667             *         current layout is the topmost parent layout
668             */
669            @Override
670            public long getParentPlid() throws PortalException {
671                    if (getParentLayoutId() == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
672                            return 0;
673                    }
674    
675                    Layout layout = LayoutLocalServiceUtil.getLayout(
676                            getGroupId(), isPrivateLayout(), getParentLayoutId());
677    
678                    return layout.getPlid();
679            }
680    
681            @Override
682            public String getRegularURL(HttpServletRequest request)
683                    throws PortalException {
684    
685                    String url = _getURL(request, false, false);
686    
687                    if (!url.startsWith(Http.HTTP) && !url.startsWith(StringPool.SLASH)) {
688                            return StringPool.SLASH + url;
689                    }
690    
691                    return url;
692            }
693    
694            @Override
695            public String getResetLayoutURL(HttpServletRequest request)
696                    throws PortalException {
697    
698                    return _getURL(request, true, true);
699            }
700    
701            @Override
702            public String getResetMaxStateURL(HttpServletRequest request)
703                    throws PortalException {
704    
705                    return _getURL(request, true, false);
706            }
707    
708            @Override
709            public Group getScopeGroup() throws PortalException {
710                    Group group = null;
711    
712                    try {
713                            group = GroupLocalServiceUtil.getLayoutGroup(
714                                    getCompanyId(), getPlid());
715                    }
716                    catch (NoSuchGroupException nsge) {
717                    }
718    
719                    return group;
720            }
721    
722            @Override
723            public String getTarget() {
724                    return PortalUtil.getLayoutTarget(this);
725            }
726    
727            /**
728             * Returns the current layout's theme, or the layout set's theme if no
729             * layout theme is configured.
730             *
731             * @return the current layout's theme, or the layout set's theme if no
732             *         layout theme is configured
733             */
734            @Override
735            public Theme getTheme() throws PortalException {
736                    if (isInheritLookAndFeel()) {
737                            LayoutSet layoutSet = getLayoutSet();
738    
739                            return layoutSet.getTheme();
740                    }
741                    else {
742                            return ThemeLocalServiceUtil.getTheme(getCompanyId(), getThemeId());
743                    }
744            }
745    
746            @Override
747            public String getThemeSetting(String key, String device) {
748                    return getThemeSetting(key, device, isInheritLookAndFeel());
749            }
750    
751            @Override
752            public String getThemeSetting(
753                    String key, String device, boolean inheritLookAndFeel) {
754    
755                    UnicodeProperties typeSettingsProperties = getTypeSettingsProperties();
756    
757                    String value = typeSettingsProperties.getProperty(
758                            ThemeSettingImpl.namespaceProperty(device, key));
759    
760                    if (value != null) {
761                            return value;
762                    }
763    
764                    return getDefaultThemeSetting(key, device, inheritLookAndFeel);
765            }
766    
767            @Override
768            public String getTypeSettings() {
769                    if (_typeSettingsProperties == null) {
770                            return super.getTypeSettings();
771                    }
772                    else {
773                            return _typeSettingsProperties.toString();
774                    }
775            }
776    
777            @Override
778            public UnicodeProperties getTypeSettingsProperties() {
779                    if (_typeSettingsProperties == null) {
780                            _typeSettingsProperties = new UnicodeProperties(true);
781    
782                            _typeSettingsProperties.fastLoad(super.getTypeSettings());
783                    }
784    
785                    return _typeSettingsProperties;
786            }
787    
788            @Override
789            public String getTypeSettingsProperty(String key) {
790                    UnicodeProperties typeSettingsProperties = getTypeSettingsProperties();
791    
792                    return typeSettingsProperties.getProperty(key);
793            }
794    
795            @Override
796            public String getTypeSettingsProperty(String key, String defaultValue) {
797                    UnicodeProperties typeSettingsProperties = getTypeSettingsProperties();
798    
799                    return typeSettingsProperties.getProperty(key, defaultValue);
800            }
801    
802            /**
803             * Returns <code>true</code> if the given layout ID matches one of the
804             * current layout's hierarchical parents.
805             *
806             * @param  layoutId the layout ID to search for in the current layout's
807             *         parent list
808             * @return <code>true</code> if the given layout ID matches one of the
809             *         current layout's hierarchical parents; <code>false</code>
810             *         otherwise
811             */
812            @Override
813            public boolean hasAncestor(long layoutId) throws PortalException {
814                    long parentLayoutId = getParentLayoutId();
815    
816                    while (parentLayoutId != LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
817                            if (parentLayoutId == layoutId) {
818                                    return true;
819                            }
820    
821                            Layout parentLayout = LayoutLocalServiceUtil.getLayout(
822                                    getGroupId(), isPrivateLayout(), parentLayoutId);
823    
824                            parentLayoutId = parentLayout.getParentLayoutId();
825                    }
826    
827                    return false;
828            }
829    
830            /**
831             * Returns <code>true</code> if the current layout has child layouts.
832             *
833             * @return <code>true</code> if the current layout has child layouts,
834             *         <code>false</code> otherwise
835             */
836            @Override
837            public boolean hasChildren() {
838                    return LayoutLocalServiceUtil.hasLayouts(
839                            getGroupId(), isPrivateLayout(), getLayoutId());
840            }
841    
842            @Override
843            public boolean hasScopeGroup() throws PortalException {
844                    Group group = getScopeGroup();
845    
846                    if (group != null) {
847                            return true;
848                    }
849                    else {
850                            return false;
851                    }
852            }
853    
854            @Override
855            public boolean hasSetModifiedDate() {
856                    return true;
857            }
858    
859            @Override
860            public boolean includeLayoutContent(
861                            HttpServletRequest request, HttpServletResponse response)
862                    throws Exception {
863    
864                    LayoutType layoutType = getLayoutType();
865    
866                    LayoutTypeController layoutTypeController =
867                            layoutType.getLayoutTypeController();
868    
869                    return layoutTypeController.includeLayoutContent(
870                            request, response, this);
871            }
872    
873            @Override
874            public boolean isChildSelected(boolean selectable, Layout layout)
875                    throws PortalException {
876    
877                    if (selectable) {
878                            long plid = getPlid();
879    
880                            List<Layout> ancestors = layout.getAncestors();
881    
882                            for (Layout curLayout : ancestors) {
883                                    if (plid == curLayout.getPlid()) {
884                                            return true;
885                                    }
886                            }
887                    }
888    
889                    return false;
890            }
891    
892            /**
893             * Returns <code>true</code> if the current layout can be used as a content
894             * display page.
895             *
896             * <p>
897             * A content display page must have an Asset Publisher portlet that is
898             * configured as the default Asset Publisher for the layout.
899             * </p>
900             *
901             * @return <code>true</code> if the current layout can be used as a content
902             *         display page; <code>false</code> otherwise
903             */
904            @Override
905            public boolean isContentDisplayPage() {
906                    UnicodeProperties typeSettingsProperties = getTypeSettingsProperties();
907    
908                    String defaultAssetPublisherPortletId =
909                            typeSettingsProperties.getProperty(
910                                    LayoutTypePortletConstants.DEFAULT_ASSET_PUBLISHER_PORTLET_ID);
911    
912                    if (Validator.isNotNull(defaultAssetPublisherPortletId)) {
913                            return true;
914                    }
915    
916                    return false;
917            }
918    
919            @Override
920            public boolean isCustomizable() {
921                    if (!isTypePortlet()) {
922                            return false;
923                    }
924    
925                    if (GetterUtil.getBoolean(
926                                    getTypeSettingsProperty(LayoutConstants.CUSTOMIZABLE_LAYOUT))) {
927    
928                            return true;
929                    }
930    
931                    LayoutTypePortlet layoutTypePortlet =
932                            (LayoutTypePortlet)getLayoutType();
933    
934                    if (layoutTypePortlet.isCustomizable()) {
935                            return true;
936                    }
937    
938                    return false;
939            }
940    
941            /**
942             * Returns <code>true</code> if the current layout is the first layout in
943             * its parent's hierarchical list of children layouts.
944             *
945             * @return <code>true</code> if the current layout is the first layout in
946             *         its parent's hierarchical list of children layouts;
947             *         <code>false</code> otherwise
948             */
949            @Override
950            public boolean isFirstChild() {
951                    if (getPriority() == 0) {
952                            return true;
953                    }
954    
955                    return false;
956            }
957    
958            /**
959             * Returns <code>true</code> if the current layout is the topmost parent
960             * layout.
961             *
962             * @return <code>true</code> if the current layout is the topmost parent
963             *         layout; <code>false</code> otherwise
964             */
965            @Override
966            public boolean isFirstParent() {
967                    if (isFirstChild() && isRootLayout()) {
968                            return true;
969                    }
970    
971                    return false;
972            }
973    
974            @Override
975            public boolean isIconImage() {
976                    return getIconImage();
977            }
978    
979            /**
980             * Returns <code>true</code> if the current layout utilizes its {@link
981             * LayoutSet}'s look and feel options (e.g. theme and color scheme).
982             *
983             * @return <code>true</code> if the current layout utilizes its layout set's
984             *         look and feel options; <code>false</code> otherwise
985             */
986            @Override
987            public boolean isInheritLookAndFeel() {
988                    if (Validator.isNull(getThemeId()) ||
989                            Validator.isNull(getColorSchemeId())) {
990    
991                            return true;
992                    }
993    
994                    return false;
995            }
996    
997            /**
998             * Returns <code>true</code> if the current layout is built from a layout
999             * template and still maintains an active connection to it.
1000             *
1001             * @return <code>true</code> if the current layout is built from a layout
1002             *         template and still maintains an active connection to it;
1003             *         <code>false</code> otherwise
1004             */
1005            @Override
1006            public boolean isLayoutPrototypeLinkActive() {
1007                    if (isLayoutPrototypeLinkEnabled() &&
1008                            Validator.isNotNull(getLayoutPrototypeUuid())) {
1009    
1010                            return true;
1011                    }
1012    
1013                    return false;
1014            }
1015    
1016            @Override
1017            public boolean isPortletEmbedded(String portletId, long groupId) {
1018                    List<PortletPreferences> portletPreferences = _getPortletPreferences(
1019                            groupId);
1020    
1021                    if (portletPreferences.isEmpty()) {
1022                            return false;
1023                    }
1024    
1025                    for (PortletPreferences portletPreference : portletPreferences) {
1026                            String currentPortletId = portletPreference.getPortletId();
1027    
1028                            if (!portletId.equals(currentPortletId)) {
1029                                    continue;
1030                            }
1031    
1032                            Portlet portlet = PortletLocalServiceUtil.getPortletById(
1033                                    getCompanyId(), currentPortletId);
1034    
1035                            if ((portlet != null) && portlet.isReady() &&
1036                                    !portlet.isUndeployedPortlet() && portlet.isActive()) {
1037    
1038                                    return true;
1039                            }
1040                    }
1041    
1042                    return false;
1043            }
1044    
1045            /**
1046             * Returns <code>true</code> if the current layout is part of the public
1047             * {@link LayoutSet}.
1048             *
1049             * <p>
1050             * Note, the returned value reflects the layout's default access options,
1051             * not its access permissions.
1052             * </p>
1053             *
1054             * @return <code>true</code> if the current layout is part of the public
1055             *         layout set; <code>false</code> otherwise
1056             */
1057            @Override
1058            public boolean isPublicLayout() {
1059                    return !isPrivateLayout();
1060            }
1061    
1062            /**
1063             * Returns <code>true</code> if the current layout is the root layout.
1064             *
1065             * @return <code>true</code> if the current layout is the root layout;
1066             *         <code>false</code> otherwise
1067             */
1068            @Override
1069            public boolean isRootLayout() {
1070                    if (getParentLayoutId() == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
1071                            return true;
1072                    }
1073    
1074                    return false;
1075            }
1076    
1077            @Override
1078            public boolean isSelected(
1079                    boolean selectable, Layout layout, long ancestorPlid) {
1080    
1081                    if (selectable) {
1082                            long plid = getPlid();
1083    
1084                            if ((plid == layout.getPlid()) || (plid == ancestorPlid)) {
1085                                    return true;
1086                            }
1087                    }
1088    
1089                    return false;
1090            }
1091    
1092            /**
1093             * Returns <code>true</code> if the current layout can hold embedded
1094             * portlets.
1095             *
1096             * @return <code>true</code> if the current layout can hold embedded
1097             *         portlets; <code>false</code> otherwise
1098             */
1099            @Override
1100            public boolean isSupportsEmbeddedPortlets() {
1101                    if (isTypeEmbedded() || isTypePanel() || isTypePortlet()) {
1102                            return true;
1103                    }
1104    
1105                    return false;
1106            }
1107    
1108            /**
1109             * @deprecated As of 7.0.0, with no direct replacement
1110             */
1111            @Deprecated
1112            @Override
1113            public boolean isTypeArticle() {
1114                    return false;
1115            }
1116    
1117            @Override
1118            public boolean isTypeControlPanel() {
1119                    if (Objects.equals(getType(), LayoutConstants.TYPE_CONTROL_PANEL) ||
1120                            Objects.equals(
1121                                    _getLayoutTypeControllerType(),
1122                                    LayoutConstants.TYPE_CONTROL_PANEL)) {
1123    
1124                            return true;
1125                    }
1126    
1127                    return false;
1128            }
1129    
1130            @Override
1131            public boolean isTypeEmbedded() {
1132                    if (Objects.equals(getType(), LayoutConstants.TYPE_EMBEDDED) ||
1133                            Objects.equals(
1134                                    _getLayoutTypeControllerType(),
1135                                    LayoutConstants.TYPE_EMBEDDED)) {
1136    
1137                            return true;
1138                    }
1139    
1140                    return false;
1141            }
1142    
1143            @Override
1144            public boolean isTypeLinkToLayout() {
1145                    if (Objects.equals(getType(), LayoutConstants.TYPE_LINK_TO_LAYOUT) ||
1146                            Objects.equals(
1147                                    _getLayoutTypeControllerType(),
1148                                    LayoutConstants.TYPE_LINK_TO_LAYOUT)) {
1149    
1150                            return true;
1151                    }
1152    
1153                    return false;
1154            }
1155    
1156            @Override
1157            public boolean isTypePanel() {
1158                    if (Objects.equals(getType(), LayoutConstants.TYPE_PANEL) ||
1159                            Objects.equals(
1160                                    _getLayoutTypeControllerType(), LayoutConstants.TYPE_PANEL)) {
1161    
1162                            return true;
1163                    }
1164    
1165                    return false;
1166            }
1167    
1168            @Override
1169            public boolean isTypePortlet() {
1170                    if (Objects.equals(getType(), LayoutConstants.TYPE_PORTLET) ||
1171                            Objects.equals(
1172                                    _getLayoutTypeControllerType(), LayoutConstants.TYPE_PORTLET)) {
1173    
1174                            return true;
1175                    }
1176    
1177                    return false;
1178            }
1179    
1180            @Override
1181            public boolean isTypeSharedPortlet() {
1182                    if (Objects.equals(getType(), LayoutConstants.TYPE_SHARED_PORTLET)) {
1183                            return true;
1184                    }
1185    
1186                    return false;
1187            }
1188    
1189            @Override
1190            public boolean isTypeURL() {
1191                    if (Objects.equals(getType(), LayoutConstants.TYPE_URL)) {
1192                            return true;
1193                    }
1194    
1195                    return false;
1196            }
1197    
1198            @Override
1199            public boolean matches(HttpServletRequest request, String friendlyURL) {
1200                    LayoutType layoutType = getLayoutType();
1201    
1202                    LayoutTypeController layoutTypeController =
1203                            layoutType.getLayoutTypeController();
1204    
1205                    return layoutTypeController.matches(request, friendlyURL, this);
1206            }
1207    
1208            @Override
1209            public void setGroupId(long groupId) {
1210                    super.setGroupId(groupId);
1211    
1212                    _layoutSet = null;
1213            }
1214    
1215            @Override
1216            public void setLayoutSet(LayoutSet layoutSet) {
1217                    _layoutSet = layoutSet;
1218            }
1219    
1220            @Override
1221            public void setPrivateLayout(boolean privateLayout) {
1222                    super.setPrivateLayout(privateLayout);
1223    
1224                    _layoutSet = null;
1225            }
1226    
1227            @Override
1228            public void setTypeSettings(String typeSettings) {
1229                    _typeSettingsProperties = null;
1230    
1231                    super.setTypeSettings(typeSettings);
1232            }
1233    
1234            @Override
1235            public void setTypeSettingsProperties(
1236                    UnicodeProperties typeSettingsProperties) {
1237    
1238                    _typeSettingsProperties = typeSettingsProperties;
1239    
1240                    super.setTypeSettings(_typeSettingsProperties.toString());
1241            }
1242    
1243            private static String _getFriendlyURLKeyword(String friendlyURL) {
1244                    friendlyURL = StringUtil.toLowerCase(friendlyURL);
1245    
1246                    for (String keyword : _friendlyURLKeywords) {
1247                            if (friendlyURL.startsWith(keyword)) {
1248                                    return keyword;
1249                            }
1250    
1251                            if (keyword.equals(friendlyURL + StringPool.SLASH)) {
1252                                    return friendlyURL;
1253                            }
1254                    }
1255    
1256                    return null;
1257            }
1258    
1259            private static void _initFriendlyURLKeywords() {
1260                    _friendlyURLKeywords =
1261                            new String[PropsValues.LAYOUT_FRIENDLY_URL_KEYWORDS.length];
1262    
1263                    for (int i = 0; i < PropsValues.LAYOUT_FRIENDLY_URL_KEYWORDS.length;
1264                            i++) {
1265    
1266                            String keyword = PropsValues.LAYOUT_FRIENDLY_URL_KEYWORDS[i];
1267    
1268                            keyword = StringPool.SLASH + keyword;
1269    
1270                            if (!keyword.contains(StringPool.PERIOD)) {
1271                                    if (keyword.endsWith(StringPool.STAR)) {
1272                                            keyword = keyword.substring(0, keyword.length() - 1);
1273                                    }
1274                                    else {
1275                                            keyword = keyword + StringPool.SLASH;
1276                                    }
1277                            }
1278    
1279                            _friendlyURLKeywords[i] = StringUtil.toLowerCase(keyword);
1280                    }
1281            }
1282    
1283            private Set<String> _getLayoutPortletIds() {
1284                    Set<String> layoutPortletIds = new HashSet<>();
1285    
1286                    List<PortletPreferences> portletPreferences =
1287                            PortletPreferencesLocalServiceUtil.getPortletPreferences(
1288                                    PortletKeys.PREFS_OWNER_ID_DEFAULT,
1289                                    PortletKeys.PREFS_OWNER_TYPE_LAYOUT, getPlid());
1290    
1291                    for (PortletPreferences portletPreference : portletPreferences) {
1292                            layoutPortletIds.add(portletPreference.getPortletId());
1293                    }
1294    
1295                    return layoutPortletIds;
1296            }
1297    
1298            private String _getLayoutTypeControllerType() {
1299                    LayoutType layoutType = getLayoutType();
1300    
1301                    LayoutTypeController layoutTypeController =
1302                            layoutType.getLayoutTypeController();
1303    
1304                    return layoutTypeController.getType();
1305            }
1306    
1307            private LayoutTypePortlet _getLayoutTypePortletClone(
1308                            HttpServletRequest request)
1309                    throws IOException {
1310    
1311                    LayoutTypePortlet layoutTypePortlet = null;
1312    
1313                    LayoutClone layoutClone = LayoutCloneFactory.getInstance();
1314    
1315                    if (layoutClone != null) {
1316                            String typeSettings = layoutClone.get(request, getPlid());
1317    
1318                            if (typeSettings != null) {
1319                                    UnicodeProperties typeSettingsProperties =
1320                                            new UnicodeProperties(true);
1321    
1322                                    typeSettingsProperties.load(typeSettings);
1323    
1324                                    String stateMax = typeSettingsProperties.getProperty(
1325                                            LayoutTypePortletConstants.STATE_MAX);
1326                                    String stateMin = typeSettingsProperties.getProperty(
1327                                            LayoutTypePortletConstants.STATE_MIN);
1328    
1329                                    Layout layout = (Layout)this.clone();
1330    
1331                                    layoutTypePortlet = (LayoutTypePortlet)layout.getLayoutType();
1332    
1333                                    layoutTypePortlet.setStateMax(stateMax);
1334                                    layoutTypePortlet.setStateMin(stateMin);
1335                            }
1336                    }
1337    
1338                    if (layoutTypePortlet == null) {
1339                            layoutTypePortlet = (LayoutTypePortlet)getLayoutType();
1340                    }
1341    
1342                    return layoutTypePortlet;
1343            }
1344    
1345            private List<PortletPreferences> _getPortletPreferences(long groupId) {
1346                    List<PortletPreferences> portletPreferences =
1347                            PortletPreferencesLocalServiceUtil.getPortletPreferences(
1348                                    groupId, PortletKeys.PREFS_OWNER_TYPE_LAYOUT,
1349                                    PortletKeys.PREFS_PLID_SHARED);
1350    
1351                    if (isTypePortlet()) {
1352                            LayoutTypePortlet layoutTypePortlet =
1353                                    (LayoutTypePortlet)getLayoutType();
1354    
1355                            PortalPreferences portalPreferences =
1356                                    layoutTypePortlet.getPortalPreferences();
1357    
1358                            if ((portalPreferences != null) &&
1359                                    layoutTypePortlet.isCustomizable()) {
1360    
1361                                    portletPreferences = ListUtil.copy(portletPreferences);
1362    
1363                                    portletPreferences.addAll(
1364                                            PortletPreferencesLocalServiceUtil.getPortletPreferences(
1365                                                    portalPreferences.getUserId(),
1366                                                    PortletKeys.PREFS_OWNER_TYPE_USER, getPlid()));
1367                            }
1368                    }
1369    
1370                    return portletPreferences;
1371            }
1372    
1373            private String _getURL(
1374                            HttpServletRequest request, boolean resetMaxState,
1375                            boolean resetRenderParameters)
1376                    throws PortalException {
1377    
1378                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
1379                            WebKeys.THEME_DISPLAY);
1380    
1381                    if (resetMaxState) {
1382                            Layout layout = themeDisplay.getLayout();
1383    
1384                            LayoutTypePortlet layoutTypePortlet = null;
1385    
1386                            if (layout.equals(this)) {
1387                                    layoutTypePortlet = themeDisplay.getLayoutTypePortlet();
1388                            }
1389                            else {
1390                                    try {
1391                                            layoutTypePortlet = _getLayoutTypePortletClone(request);
1392                                    }
1393                                    catch (IOException ioe) {
1394                                            _log.error("Unable to clone layout settings", ioe);
1395    
1396                                            layoutTypePortlet = (LayoutTypePortlet)getLayoutType();
1397                                    }
1398                            }
1399    
1400                            if (layoutTypePortlet.hasStateMax()) {
1401                                    String portletId = StringUtil.split(
1402                                            layoutTypePortlet.getStateMax())[0];
1403    
1404                                    PortletURLImpl portletURLImpl = new PortletURLImpl(
1405                                            request, portletId, getPlid(), PortletRequest.ACTION_PHASE);
1406    
1407                                    try {
1408                                            portletURLImpl.setWindowState(WindowState.NORMAL);
1409                                            portletURLImpl.setPortletMode(PortletMode.VIEW);
1410                                    }
1411                                    catch (PortletException pe) {
1412                                            throw new SystemException(pe);
1413                                    }
1414    
1415                                    portletURLImpl.setAnchor(false);
1416    
1417                                    if (PropsValues.LAYOUT_DEFAULT_P_L_RESET &&
1418                                            !resetRenderParameters) {
1419    
1420                                            portletURLImpl.setParameter("p_l_reset", "0");
1421                                    }
1422                                    else if (!PropsValues.LAYOUT_DEFAULT_P_L_RESET &&
1423                                                     resetRenderParameters) {
1424    
1425                                            portletURLImpl.setParameter("p_l_reset", "1");
1426                                    }
1427    
1428                                    return portletURLImpl.toString();
1429                            }
1430                    }
1431    
1432                    String portalURL = PortalUtil.getPortalURL(request);
1433    
1434                    String url = PortalUtil.getLayoutURL(this, themeDisplay);
1435    
1436                    if (!CookieKeys.hasSessionId(request) &&
1437                            (url.startsWith(portalURL) || url.startsWith(StringPool.SLASH))) {
1438    
1439                            HttpSession session = request.getSession();
1440    
1441                            url = PortalUtil.getURLWithSessionId(url, session.getId());
1442                    }
1443    
1444                    if (!resetMaxState) {
1445                            return url;
1446                    }
1447    
1448                    if (PropsValues.LAYOUT_DEFAULT_P_L_RESET && !resetRenderParameters) {
1449                            url = HttpUtil.addParameter(url, "p_l_reset", 0);
1450                    }
1451                    else if (!PropsValues.LAYOUT_DEFAULT_P_L_RESET &&
1452                                     resetRenderParameters) {
1453    
1454                            url = HttpUtil.addParameter(url, "p_l_reset", 1);
1455                    }
1456    
1457                    return url;
1458            }
1459    
1460            private static final Log _log = LogFactoryUtil.getLog(LayoutImpl.class);
1461    
1462            private static String[] _friendlyURLKeywords;
1463    
1464            static {
1465                    _initFriendlyURLKeywords();
1466            }
1467    
1468            private LayoutSet _layoutSet;
1469            private transient LayoutType _layoutType;
1470            private UnicodeProperties _typeSettingsProperties;
1471    
1472    }