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