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