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