/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.service.impl; import com.liferay.portal.kernel.bean.BeanReference; import com.liferay.portal.kernel.exception.LayoutFriendlyURLException; import com.liferay.portal.kernel.exception.LayoutFriendlyURLsException; import com.liferay.portal.kernel.exception.LayoutNameException; import com.liferay.portal.kernel.exception.LayoutParentLayoutIdException; import com.liferay.portal.kernel.exception.LayoutTypeException; import com.liferay.portal.kernel.exception.NoSuchLayoutException; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.language.LanguageUtil; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.Group; import com.liferay.portal.kernel.model.Layout; import com.liferay.portal.kernel.model.LayoutConstants; import com.liferay.portal.kernel.model.LayoutFriendlyURL; import com.liferay.portal.kernel.model.LayoutSet; import com.liferay.portal.kernel.model.LayoutSetPrototype; import com.liferay.portal.kernel.model.LayoutType; import com.liferay.portal.kernel.model.LayoutTypeController; import com.liferay.portal.kernel.model.ResourceConstants; import com.liferay.portal.kernel.model.Role; import com.liferay.portal.kernel.model.RoleConstants; import com.liferay.portal.kernel.module.framework.service.IdentifiableOSGiService; import com.liferay.portal.kernel.portlet.FriendlyURLMapper; import com.liferay.portal.kernel.security.permission.ActionKeys; import com.liferay.portal.kernel.service.PortletLocalServiceUtil; import com.liferay.portal.kernel.service.ResourcePermissionLocalService; import com.liferay.portal.kernel.service.RoleLocalServiceUtil; import com.liferay.portal.kernel.service.ServiceContext; import com.liferay.portal.kernel.service.persistence.LayoutFriendlyURLPersistence; import com.liferay.portal.kernel.service.persistence.LayoutPersistence; import com.liferay.portal.kernel.service.persistence.LayoutSetPersistence; import com.liferay.portal.kernel.util.FriendlyURLNormalizerUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.LocaleUtil; import com.liferay.portal.kernel.util.Portal; import com.liferay.portal.kernel.util.PortalUtil; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.kernel.util.comparator.LayoutPriorityComparator; import com.liferay.portal.model.impl.LayoutImpl; import com.liferay.portal.util.LayoutTypeControllerTracker; import com.liferay.sites.kernel.util.SitesUtil; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; /** * @author Raymond Augé */ public class LayoutLocalServiceHelper implements IdentifiableOSGiService { public String getFriendlyURL( long groupId, boolean privateLayout, long layoutId, String name, String friendlyURL) throws PortalException { friendlyURL = getFriendlyURL(friendlyURL); if (Validator.isNotNull(friendlyURL)) { return friendlyURL; } friendlyURL = StringPool.SLASH + getFriendlyURL(name); String originalFriendlyURL = friendlyURL; for (int i = 1;; i++) { try { validateFriendlyURL( groupId, privateLayout, layoutId, friendlyURL); break; } catch (LayoutFriendlyURLException lfurle) { int type = lfurle.getType(); if (type == LayoutFriendlyURLException.DUPLICATE) { friendlyURL = originalFriendlyURL + i; } else { friendlyURL = StringPool.SLASH + layoutId; break; } } } return friendlyURL; } public String getFriendlyURL(String friendlyURL) { return FriendlyURLNormalizerUtil.normalizeWithEncoding(friendlyURL); } public Map<Locale, String> getFriendlyURLMap( long groupId, boolean privateLayout, long layoutId, String name, Map<Locale, String> friendlyURLMap) throws PortalException { Map<Locale, String> newFriendlyURLMap = new HashMap<>(); for (Locale locale : LanguageUtil.getAvailableLocales(groupId)) { String friendlyURL = friendlyURLMap.get(locale); if (Validator.isNotNull(friendlyURL)) { friendlyURL = getFriendlyURL( groupId, privateLayout, layoutId, name, friendlyURL); newFriendlyURLMap.put(locale, friendlyURL); } } Locale siteDefaultLocale = LocaleUtil.getSiteDefault(); if (newFriendlyURLMap.isEmpty() || Validator.isNull(newFriendlyURLMap.get(siteDefaultLocale))) { String friendlyURL = getFriendlyURL( groupId, privateLayout, layoutId, name, StringPool.BLANK); newFriendlyURLMap.put(siteDefaultLocale, friendlyURL); } return newFriendlyURLMap; } public int getNextPriority( long groupId, boolean privateLayout, long parentLayoutId, String sourcePrototypeLayoutUuid, int defaultPriority) { try { int priority = defaultPriority; if (priority < 0) { Layout layout = layoutPersistence.findByG_P_P_First( groupId, privateLayout, parentLayoutId, new LayoutPriorityComparator(false)); priority = layout.getPriority() + 1; } if ((priority < _PRIORITY_BUFFER) && Validator.isNull(sourcePrototypeLayoutUuid)) { LayoutSet layoutSet = layoutSetPersistence.fetchByG_P( groupId, privateLayout); if (Validator.isNotNull( layoutSet.getLayoutSetPrototypeUuid()) && layoutSet.isLayoutSetPrototypeLinkEnabled()) { priority = priority + _PRIORITY_BUFFER; } } return priority; } catch (NoSuchLayoutException nsle) { // LPS-52675 if (_log.isDebugEnabled()) { _log.debug(nsle, nsle); } return 0; } } @Override public String getOSGiServiceIdentifier() { return LayoutLocalServiceHelper.class.getName(); } public long getParentLayoutId( long groupId, boolean privateLayout, long parentLayoutId) { if (parentLayoutId != LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) { // Ensure parent layout exists Layout parentLayout = layoutPersistence.fetchByG_P_L( groupId, privateLayout, parentLayoutId); if (parentLayout == null) { parentLayoutId = LayoutConstants.DEFAULT_PARENT_LAYOUT_ID; } } return parentLayoutId; } public boolean hasLayoutSetPrototypeLayout( LayoutSetPrototype layoutSetPrototype, String layoutUuid) throws PortalException { Layout layout = layoutPersistence.fetchByUUID_G_P( layoutUuid, layoutSetPrototype.getGroupId(), true); if (layout != null) { return true; } return false; } public void validate( long groupId, boolean privateLayout, long layoutId, long parentLayoutId, String name, String type, boolean hidden, Map<Locale, String> friendlyURLMap, ServiceContext serviceContext) throws PortalException { validateName(name); boolean firstLayout = false; if (parentLayoutId == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) { List<Layout> layouts = layoutPersistence.findByG_P_P( groupId, privateLayout, parentLayoutId, 0, 1); if (layouts.isEmpty()) { firstLayout = true; } else { long firstLayoutId = layouts.get(0).getLayoutId(); if (firstLayoutId == layoutId) { firstLayout = true; } } } else { // Layout cannot become a child of a layout that is not sortable // because it is linked to a layout set prototype Layout layout = layoutPersistence.fetchByG_P_L( groupId, privateLayout, layoutId); Layout parentLayout = layoutPersistence.findByG_P_L( groupId, privateLayout, parentLayoutId); if (((layout == null) || Validator.isNull(layout.getSourcePrototypeLayoutUuid())) && !SitesUtil.isLayoutSortable(parentLayout)) { throw new LayoutParentLayoutIdException( LayoutParentLayoutIdException.NOT_SORTABLE); } } if (firstLayout) { validateFirstLayout(type); } LayoutTypeController layoutTypeController = LayoutTypeControllerTracker.getLayoutTypeController(type); if (!layoutTypeController.isInstanceable()) { boolean layoutInstanceableAllowed = GetterUtil.getBoolean( serviceContext.getAttribute("layout.instanceable.allowed")); if (!layoutInstanceableAllowed) { throw new LayoutTypeException( LayoutTypeException.NOT_INSTANCEABLE); } } if (!layoutTypeController.isParentable()) { if (layoutPersistence.countByG_P_P( groupId, privateLayout, layoutId) > 0) { throw new LayoutTypeException( LayoutTypeException.NOT_PARENTABLE); } } validateFriendlyURLs(groupId, privateLayout, layoutId, friendlyURLMap); } public void validateFirstLayout(Layout layout) throws PortalException { Group group = layout.getGroup(); if (group.isGuest() && layout.isPublicLayout() && !hasGuestViewPermission(layout)) { LayoutTypeException lte = new LayoutTypeException( LayoutTypeException.FIRST_LAYOUT_PERMISSION); throw lte; } validateFirstLayout(layout.getType()); } public void validateFirstLayout(String type) throws PortalException { LayoutTypeController layoutTypeController = LayoutTypeControllerTracker.getLayoutTypeController(type); if (Validator.isNull(type) || !layoutTypeController.isFirstPageable()) { LayoutTypeException lte = new LayoutTypeException( LayoutTypeException.FIRST_LAYOUT); lte.setLayoutType(type); throw lte; } } public void validateFriendlyURL( long groupId, boolean privateLayout, long layoutId, String friendlyURL) throws PortalException { if (Validator.isNull(friendlyURL)) { return; } int exceptionType = LayoutImpl.validateFriendlyURL(friendlyURL); if (exceptionType != -1) { throw new LayoutFriendlyURLException(exceptionType); } List<LayoutFriendlyURL> layoutFriendlyURLs = layoutFriendlyURLPersistence.findByG_P_F( groupId, privateLayout, friendlyURL); for (LayoutFriendlyURL layoutFriendlyURL : layoutFriendlyURLs) { Layout layout = layoutPersistence.findByPrimaryKey( layoutFriendlyURL.getPlid()); if (layout.getLayoutId() != layoutId) { LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException( LayoutFriendlyURLException.DUPLICATE); lfurle.setDuplicateClassPK(layout.getPlid()); lfurle.setDuplicateClassName(Layout.class.getName()); throw lfurle; } } LayoutImpl.validateFriendlyURLKeyword(friendlyURL); if (friendlyURL.contains(Portal.FRIENDLY_URL_SEPARATOR) || friendlyURL.endsWith(_FRIENDLY_URL_SEPARATOR_HEAD)) { LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException( LayoutFriendlyURLException.KEYWORD_CONFLICT); lfurle.setKeywordConflict(Portal.FRIENDLY_URL_SEPARATOR); throw lfurle; } List<FriendlyURLMapper> friendlyURLMappers = PortletLocalServiceUtil.getFriendlyURLMappers(); for (FriendlyURLMapper friendlyURLMapper : friendlyURLMappers) { if (friendlyURLMapper.isCheckMappingWithPrefix()) { continue; } String mapping = StringPool.SLASH + friendlyURLMapper.getMapping(); if (friendlyURL.contains(mapping + StringPool.SLASH) || friendlyURL.endsWith(mapping)) { LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException( LayoutFriendlyURLException.KEYWORD_CONFLICT); lfurle.setKeywordConflict(friendlyURLMapper.getMapping()); throw lfurle; } } for (Locale locale : LanguageUtil.getAvailableLocales()) { String languageId = StringUtil.toLowerCase( LocaleUtil.toLanguageId(locale)); String i18nPathLanguageId = StringPool.SLASH + PortalUtil.getI18nPathLanguageId(locale, languageId); if (friendlyURL.startsWith(i18nPathLanguageId + StringPool.SLASH) || friendlyURL.startsWith( StringPool.SLASH + languageId + StringPool.SLASH) || friendlyURL.equals(i18nPathLanguageId) || friendlyURL.equals(StringPool.SLASH + languageId)) { LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException( LayoutFriendlyURLException.KEYWORD_CONFLICT); lfurle.setKeywordConflict(i18nPathLanguageId); throw lfurle; } } String layoutIdFriendlyURL = friendlyURL.substring(1); if (Validator.isNumber(layoutIdFriendlyURL) && !layoutIdFriendlyURL.equals(String.valueOf(layoutId))) { LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException( LayoutFriendlyURLException.POSSIBLE_DUPLICATE); lfurle.setKeywordConflict(layoutIdFriendlyURL); throw lfurle; } } public void validateFriendlyURLs( long groupId, boolean privateLayout, long layoutId, Map<Locale, String> friendlyURLMap) throws PortalException { LayoutFriendlyURLsException layoutFriendlyURLsException = null; for (Map.Entry<Locale, String> entry : friendlyURLMap.entrySet()) { try { String friendlyURL = entry.getValue(); validateFriendlyURL( groupId, privateLayout, layoutId, friendlyURL); } catch (LayoutFriendlyURLException lfurle) { Locale locale = entry.getKey(); if (layoutFriendlyURLsException == null) { layoutFriendlyURLsException = new LayoutFriendlyURLsException(lfurle); } else { layoutFriendlyURLsException.addSuppressed(lfurle); } layoutFriendlyURLsException.addLocalizedException( locale, lfurle); } } if (layoutFriendlyURLsException != null) { throw layoutFriendlyURLsException; } } public void validateName(String name) throws PortalException { if (Validator.isNull(name)) { throw new LayoutNameException(); } } public void validateName(String name, String languageId) throws PortalException { String defaultLanguageId = LocaleUtil.toLanguageId( LocaleUtil.getSiteDefault()); if (defaultLanguageId.equals(languageId)) { validateName(name); } } public void validateParentLayoutId( long groupId, boolean privateLayout, long layoutId, long parentLayoutId) throws PortalException { Layout layout = layoutPersistence.findByG_P_L( groupId, privateLayout, layoutId); if (parentLayoutId == layout.getParentLayoutId()) { return; } // Layouts can always be moved to the root level if (parentLayoutId == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) { return; } // Layout cannot become a child of a layout that is not parentable Layout parentLayout = layoutPersistence.findByG_P_L( groupId, privateLayout, parentLayoutId); LayoutType parentLayoutType = parentLayout.getLayoutType(); if (!parentLayoutType.isParentable()) { throw new LayoutParentLayoutIdException( LayoutParentLayoutIdException.NOT_PARENTABLE); } // Layout cannot become a child of a layout that is not sortable because // it is linked to a layout set prototype if (Validator.isNull(layout.getSourcePrototypeLayoutUuid()) && !SitesUtil.isLayoutSortable(parentLayout)) { throw new LayoutParentLayoutIdException( LayoutParentLayoutIdException.NOT_SORTABLE); } // Layout cannot become descendant of itself if (PortalUtil.isLayoutDescendant(layout, parentLayoutId)) { throw new LayoutParentLayoutIdException( LayoutParentLayoutIdException.SELF_DESCENDANT); } // If layout is moved, the new first layout must be valid if (layout.getParentLayoutId() == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) { List<Layout> layouts = layoutPersistence.findByG_P_P( groupId, privateLayout, LayoutConstants.DEFAULT_PARENT_LAYOUT_ID, 0, 2); // You can only reach this point if there are more than two layouts // at the root level because of the descendant check long firstLayoutId = layouts.get(0).getLayoutId(); if (firstLayoutId == layoutId) { Layout secondLayout = layouts.get(1); LayoutType layoutType = secondLayout.getLayoutType(); if (Validator.isNull(secondLayout.getType()) || !layoutType.isFirstPageable()) { throw new LayoutParentLayoutIdException( LayoutParentLayoutIdException.FIRST_LAYOUT_TYPE); } } } } protected boolean hasGuestViewPermission(Layout layout) throws PortalException { Role role = RoleLocalServiceUtil.getRole( layout.getCompanyId(), RoleConstants.GUEST); return resourcePermissionLocalService.hasResourcePermission( layout.getCompanyId(), Layout.class.getName(), ResourceConstants.SCOPE_INDIVIDUAL, String.valueOf(layout.getPlid()), role.getRoleId(), ActionKeys.VIEW); } @BeanReference(type = LayoutFriendlyURLPersistence.class) protected LayoutFriendlyURLPersistence layoutFriendlyURLPersistence; @BeanReference(type = LayoutPersistence.class) protected LayoutPersistence layoutPersistence; @BeanReference(type = LayoutSetPersistence.class) protected LayoutSetPersistence layoutSetPersistence; @BeanReference(type = ResourcePermissionLocalService.class) protected ResourcePermissionLocalService resourcePermissionLocalService; private static final String _FRIENDLY_URL_SEPARATOR_HEAD = Portal.FRIENDLY_URL_SEPARATOR.substring( 0, Portal.FRIENDLY_URL_SEPARATOR.length() - 1); private static final int _PRIORITY_BUFFER = 1000000; private static final Log _log = LogFactoryUtil.getLog( LayoutLocalServiceHelper.class); }