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.service.impl;
016    
017    import com.liferay.portal.exception.LayoutFriendlyURLException;
018    import com.liferay.portal.exception.LayoutFriendlyURLsException;
019    import com.liferay.portal.exception.LayoutNameException;
020    import com.liferay.portal.exception.LayoutParentLayoutIdException;
021    import com.liferay.portal.exception.LayoutTypeException;
022    import com.liferay.portal.exception.NoSuchLayoutException;
023    import com.liferay.portal.kernel.bean.BeanReference;
024    import com.liferay.portal.kernel.exception.PortalException;
025    import com.liferay.portal.kernel.language.LanguageUtil;
026    import com.liferay.portal.kernel.module.framework.service.IdentifiableOSGiService;
027    import com.liferay.portal.kernel.portlet.FriendlyURLMapper;
028    import com.liferay.portal.kernel.util.FriendlyURLNormalizerUtil;
029    import com.liferay.portal.kernel.util.GetterUtil;
030    import com.liferay.portal.kernel.util.LocaleUtil;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.model.Group;
035    import com.liferay.portal.model.Layout;
036    import com.liferay.portal.model.LayoutConstants;
037    import com.liferay.portal.model.LayoutFriendlyURL;
038    import com.liferay.portal.model.LayoutSet;
039    import com.liferay.portal.model.LayoutSetPrototype;
040    import com.liferay.portal.model.LayoutType;
041    import com.liferay.portal.model.LayoutTypeController;
042    import com.liferay.portal.model.ResourceConstants;
043    import com.liferay.portal.model.Role;
044    import com.liferay.portal.model.RoleConstants;
045    import com.liferay.portal.model.impl.LayoutImpl;
046    import com.liferay.portal.security.permission.ActionKeys;
047    import com.liferay.portal.service.PortletLocalServiceUtil;
048    import com.liferay.portal.service.ResourcePermissionLocalService;
049    import com.liferay.portal.service.RoleLocalServiceUtil;
050    import com.liferay.portal.service.ServiceContext;
051    import com.liferay.portal.service.persistence.LayoutFriendlyURLPersistence;
052    import com.liferay.portal.service.persistence.LayoutPersistence;
053    import com.liferay.portal.service.persistence.LayoutSetPersistence;
054    import com.liferay.portal.util.LayoutTypeControllerTracker;
055    import com.liferay.portal.util.Portal;
056    import com.liferay.portal.util.PortalUtil;
057    import com.liferay.portal.util.comparator.LayoutPriorityComparator;
058    import com.liferay.sites.kernel.util.SitesUtil;
059    
060    import java.util.HashMap;
061    import java.util.List;
062    import java.util.Locale;
063    import java.util.Map;
064    
065    /**
066     * @author Raymond Aug??
067     */
068    public class LayoutLocalServiceHelper implements IdentifiableOSGiService {
069    
070            public String getFriendlyURL(
071                            long groupId, boolean privateLayout, long layoutId, String name,
072                            String friendlyURL)
073                    throws PortalException {
074    
075                    friendlyURL = getFriendlyURL(friendlyURL);
076    
077                    if (Validator.isNotNull(friendlyURL)) {
078                            return friendlyURL;
079                    }
080    
081                    friendlyURL = StringPool.SLASH + getFriendlyURL(name);
082    
083                    String originalFriendlyURL = friendlyURL;
084    
085                    for (int i = 1;; i++) {
086                            try {
087                                    validateFriendlyURL(
088                                            groupId, privateLayout, layoutId, friendlyURL);
089    
090                                    break;
091                            }
092                            catch (LayoutFriendlyURLException lfurle) {
093                                    int type = lfurle.getType();
094    
095                                    if (type == LayoutFriendlyURLException.DUPLICATE) {
096                                            friendlyURL = originalFriendlyURL + i;
097                                    }
098                                    else {
099                                            friendlyURL = StringPool.SLASH + layoutId;
100    
101                                            break;
102                                    }
103                            }
104                    }
105    
106                    return friendlyURL;
107            }
108    
109            public String getFriendlyURL(String friendlyURL) {
110                    return FriendlyURLNormalizerUtil.normalize(friendlyURL);
111            }
112    
113            public Map<Locale, String> getFriendlyURLMap(
114                            long groupId, boolean privateLayout, long layoutId, String name,
115                            Map<Locale, String> friendlyURLMap)
116                    throws PortalException {
117    
118                    Map<Locale, String> newFriendlyURLMap = new HashMap<>();
119    
120                    for (Locale locale : LanguageUtil.getAvailableLocales(groupId)) {
121                            String friendlyURL = friendlyURLMap.get(locale);
122    
123                            if (Validator.isNotNull(friendlyURL)) {
124                                    friendlyURL = getFriendlyURL(
125                                            groupId, privateLayout, layoutId, name, friendlyURL);
126    
127                                    newFriendlyURLMap.put(locale, friendlyURL);
128                            }
129                    }
130    
131                    Locale siteDefaultLocale = LocaleUtil.getSiteDefault();
132    
133                    if (newFriendlyURLMap.isEmpty() ||
134                            Validator.isNull(newFriendlyURLMap.get(siteDefaultLocale))) {
135    
136                            String friendlyURL = getFriendlyURL(
137                                    groupId, privateLayout, layoutId, name, StringPool.BLANK);
138    
139                            newFriendlyURLMap.put(siteDefaultLocale, friendlyURL);
140                    }
141    
142                    return newFriendlyURLMap;
143            }
144    
145            public int getNextPriority(
146                    long groupId, boolean privateLayout, long parentLayoutId,
147                    String sourcePrototypeLayoutUuid, int defaultPriority) {
148    
149                    try {
150                            int priority = defaultPriority;
151    
152                            if (priority < 0) {
153                                    Layout layout = layoutPersistence.findByG_P_P_First(
154                                            groupId, privateLayout, parentLayoutId,
155                                            new LayoutPriorityComparator(false));
156    
157                                    priority = layout.getPriority() + 1;
158                            }
159    
160                            if ((priority < _PRIORITY_BUFFER) &&
161                                    Validator.isNull(sourcePrototypeLayoutUuid)) {
162    
163                                    LayoutSet layoutSet = layoutSetPersistence.fetchByG_P(
164                                            groupId, privateLayout);
165    
166                                    if (Validator.isNotNull(
167                                                    layoutSet.getLayoutSetPrototypeUuid()) &&
168                                            layoutSet.isLayoutSetPrototypeLinkEnabled()) {
169    
170                                            priority = priority + _PRIORITY_BUFFER;
171                                    }
172                            }
173    
174                            return priority;
175                    }
176                    catch (NoSuchLayoutException nsle) {
177                            return 0;
178                    }
179            }
180    
181            @Override
182            public String getOSGiServiceIdentifier() {
183                    return LayoutLocalServiceHelper.class.getName();
184            }
185    
186            public long getParentLayoutId(
187                    long groupId, boolean privateLayout, long parentLayoutId) {
188    
189                    if (parentLayoutId != LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
190    
191                            // Ensure parent layout exists
192    
193                            Layout parentLayout = layoutPersistence.fetchByG_P_L(
194                                    groupId, privateLayout, parentLayoutId);
195    
196                            if (parentLayout == null) {
197                                    parentLayoutId = LayoutConstants.DEFAULT_PARENT_LAYOUT_ID;
198                            }
199                    }
200    
201                    return parentLayoutId;
202            }
203    
204            public boolean hasLayoutSetPrototypeLayout(
205                            LayoutSetPrototype layoutSetPrototype, String layoutUuid)
206                    throws PortalException {
207    
208                    Layout layout = layoutPersistence.fetchByUUID_G_P(
209                            layoutUuid, layoutSetPrototype.getGroupId(), true);
210    
211                    if (layout != null) {
212                            return true;
213                    }
214    
215                    return false;
216            }
217    
218            public void validate(
219                            long groupId, boolean privateLayout, long layoutId,
220                            long parentLayoutId, String name, String type, boolean hidden,
221                            Map<Locale, String> friendlyURLMap, ServiceContext serviceContext)
222                    throws PortalException {
223    
224                    validateName(name);
225    
226                    boolean firstLayout = false;
227    
228                    if (parentLayoutId == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
229                            List<Layout> layouts = layoutPersistence.findByG_P_P(
230                                    groupId, privateLayout, parentLayoutId, 0, 1);
231    
232                            if (layouts.isEmpty()) {
233                                    firstLayout = true;
234                            }
235                            else {
236                                    long firstLayoutId = layouts.get(0).getLayoutId();
237    
238                                    if (firstLayoutId == layoutId) {
239                                            firstLayout = true;
240                                    }
241                            }
242                    }
243                    else {
244    
245                            // Layout cannot become a child of a layout that is not sortable
246                            // because it is linked to a layout set prototype
247    
248                            Layout layout = layoutPersistence.fetchByG_P_L(
249                                    groupId, privateLayout, layoutId);
250                            Layout parentLayout = layoutPersistence.findByG_P_L(
251                                    groupId, privateLayout, parentLayoutId);
252    
253                            if (((layout == null) ||
254                                     Validator.isNull(layout.getSourcePrototypeLayoutUuid())) &&
255                                    !SitesUtil.isLayoutSortable(parentLayout)) {
256    
257                                    throw new LayoutParentLayoutIdException(
258                                            LayoutParentLayoutIdException.NOT_SORTABLE);
259                            }
260                    }
261    
262                    if (firstLayout) {
263                            validateFirstLayout(type);
264                    }
265    
266                    LayoutTypeController layoutTypeController =
267                            LayoutTypeControllerTracker.getLayoutTypeController(type);
268    
269                    if (!layoutTypeController.isInstanceable()) {
270                            boolean layoutInstanceableAllowed = GetterUtil.getBoolean(
271                                    serviceContext.getAttribute("layout.instanceable.allowed"));
272    
273                            if (!layoutInstanceableAllowed) {
274                                    throw new LayoutTypeException(
275                                            LayoutTypeException.NOT_INSTANCEABLE);
276                            }
277                    }
278    
279                    if (!layoutTypeController.isParentable()) {
280                            if (layoutPersistence.countByG_P_P(
281                                            groupId, privateLayout, layoutId) > 0) {
282    
283                                    throw new LayoutTypeException(
284                                            LayoutTypeException.NOT_PARENTABLE);
285                            }
286                    }
287    
288                    validateFriendlyURLs(groupId, privateLayout, layoutId, friendlyURLMap);
289            }
290    
291            public void validateFirstLayout(Layout layout) throws PortalException {
292                    Group group = layout.getGroup();
293    
294                    if (group.isGuest() && layout.isPublicLayout() &&
295                            !hasGuestViewPermission(layout)) {
296    
297                            LayoutTypeException lte = new LayoutTypeException(
298                                    LayoutTypeException.FIRST_LAYOUT_PERMISSION);
299    
300                            throw lte;
301                    }
302    
303                    validateFirstLayout(layout.getType());
304            }
305    
306            public void validateFirstLayout(String type) throws PortalException {
307                    LayoutTypeController layoutTypeController =
308                            LayoutTypeControllerTracker.getLayoutTypeController(type);
309    
310                    if (Validator.isNull(type) || !layoutTypeController.isFirstPageable()) {
311                            LayoutTypeException lte = new LayoutTypeException(
312                                    LayoutTypeException.FIRST_LAYOUT);
313    
314                            lte.setLayoutType(type);
315    
316                            throw lte;
317                    }
318            }
319    
320            public void validateFriendlyURL(
321                            long groupId, boolean privateLayout, long layoutId,
322                            String friendlyURL)
323                    throws PortalException {
324    
325                    if (Validator.isNull(friendlyURL)) {
326                            return;
327                    }
328    
329                    int exceptionType = LayoutImpl.validateFriendlyURL(friendlyURL);
330    
331                    if (exceptionType != -1) {
332                            throw new LayoutFriendlyURLException(exceptionType);
333                    }
334    
335                    List<LayoutFriendlyURL> layoutFriendlyURLs =
336                            layoutFriendlyURLPersistence.findByG_P_F(
337                                    groupId, privateLayout, friendlyURL);
338    
339                    for (LayoutFriendlyURL layoutFriendlyURL : layoutFriendlyURLs) {
340                            Layout layout = layoutPersistence.findByPrimaryKey(
341                                    layoutFriendlyURL.getPlid());
342    
343                            if (layout.getLayoutId() != layoutId) {
344                                    LayoutFriendlyURLException lfurle =
345                                            new LayoutFriendlyURLException(
346                                                    LayoutFriendlyURLException.DUPLICATE);
347    
348                                    lfurle.setDuplicateClassPK(layout.getPlid());
349                                    lfurle.setDuplicateClassName(Layout.class.getName());
350    
351                                    throw lfurle;
352                            }
353                    }
354    
355                    LayoutImpl.validateFriendlyURLKeyword(friendlyURL);
356    
357                    if (friendlyURL.contains(Portal.FRIENDLY_URL_SEPARATOR)) {
358                            LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException(
359                                    LayoutFriendlyURLException.KEYWORD_CONFLICT);
360    
361                            lfurle.setKeywordConflict(Portal.FRIENDLY_URL_SEPARATOR);
362    
363                            throw lfurle;
364                    }
365    
366                    List<FriendlyURLMapper> friendlyURLMappers =
367                            PortletLocalServiceUtil.getFriendlyURLMappers();
368    
369                    for (FriendlyURLMapper friendlyURLMapper : friendlyURLMappers) {
370                            if (friendlyURLMapper.isCheckMappingWithPrefix()) {
371                                    continue;
372                            }
373    
374                            String mapping = StringPool.SLASH + friendlyURLMapper.getMapping();
375    
376                            if (friendlyURL.contains(mapping + StringPool.SLASH) ||
377                                    friendlyURL.endsWith(mapping)) {
378    
379                                    LayoutFriendlyURLException lfurle =
380                                            new LayoutFriendlyURLException(
381                                                    LayoutFriendlyURLException.KEYWORD_CONFLICT);
382    
383                                    lfurle.setKeywordConflict(friendlyURLMapper.getMapping());
384    
385                                    throw lfurle;
386                            }
387                    }
388    
389                    for (Locale locale : LanguageUtil.getAvailableLocales()) {
390                            String languageId = StringUtil.toLowerCase(
391                                    LocaleUtil.toLanguageId(locale));
392    
393                            String i18nPathLanguageId =
394                                    StringPool.SLASH +
395                                            PortalUtil.getI18nPathLanguageId(locale, languageId);
396    
397                            if (friendlyURL.startsWith(i18nPathLanguageId + StringPool.SLASH) ||
398                                    friendlyURL.startsWith(
399                                            StringPool.SLASH + languageId + StringPool.SLASH) ||
400                                    friendlyURL.endsWith(i18nPathLanguageId) ||
401                                    friendlyURL.endsWith(StringPool.SLASH + languageId)) {
402    
403                                    LayoutFriendlyURLException lfurle =
404                                            new LayoutFriendlyURLException(
405                                                    LayoutFriendlyURLException.KEYWORD_CONFLICT);
406    
407                                    lfurle.setKeywordConflict(i18nPathLanguageId);
408    
409                                    throw lfurle;
410                            }
411                    }
412    
413                    String layoutIdFriendlyURL = friendlyURL.substring(1);
414    
415                    if (Validator.isNumber(layoutIdFriendlyURL) &&
416                            !layoutIdFriendlyURL.equals(String.valueOf(layoutId))) {
417    
418                            LayoutFriendlyURLException lfurle = new LayoutFriendlyURLException(
419                                    LayoutFriendlyURLException.POSSIBLE_DUPLICATE);
420    
421                            lfurle.setKeywordConflict(layoutIdFriendlyURL);
422    
423                            throw lfurle;
424                    }
425            }
426    
427            public void validateFriendlyURLs(
428                            long groupId, boolean privateLayout, long layoutId,
429                            Map<Locale, String> friendlyURLMap)
430                    throws PortalException {
431    
432                    LayoutFriendlyURLsException layoutFriendlyURLsException = null;
433    
434                    for (Map.Entry<Locale, String> entry : friendlyURLMap.entrySet()) {
435                            try {
436                                    String friendlyURL = entry.getValue();
437    
438                                    validateFriendlyURL(
439                                            groupId, privateLayout, layoutId, friendlyURL);
440                            }
441                            catch (LayoutFriendlyURLException lfurle) {
442                                    Locale locale = entry.getKey();
443    
444                                    if (layoutFriendlyURLsException == null) {
445                                            layoutFriendlyURLsException =
446                                                    new LayoutFriendlyURLsException(lfurle);
447                                    }
448                                    else {
449                                            layoutFriendlyURLsException.addSuppressed(lfurle);
450                                    }
451    
452                                    layoutFriendlyURLsException.addLocalizedException(
453                                            locale, lfurle);
454                            }
455                    }
456    
457                    if (layoutFriendlyURLsException != null) {
458                            throw layoutFriendlyURLsException;
459                    }
460            }
461    
462            public void validateName(String name) throws PortalException {
463                    if (Validator.isNull(name)) {
464                            throw new LayoutNameException();
465                    }
466            }
467    
468            public void validateName(String name, String languageId)
469                    throws PortalException {
470    
471                    String defaultLanguageId = LocaleUtil.toLanguageId(
472                            LocaleUtil.getSiteDefault());
473    
474                    if (defaultLanguageId.equals(languageId)) {
475                            validateName(name);
476                    }
477            }
478    
479            public void validateParentLayoutId(
480                            long groupId, boolean privateLayout, long layoutId,
481                            long parentLayoutId)
482                    throws PortalException {
483    
484                    Layout layout = layoutPersistence.findByG_P_L(
485                            groupId, privateLayout, layoutId);
486    
487                    if (parentLayoutId == layout.getParentLayoutId()) {
488                            return;
489                    }
490    
491                    // Layouts can always be moved to the root level
492    
493                    if (parentLayoutId == LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
494                            return;
495                    }
496    
497                    // Layout cannot become a child of a layout that is not parentable
498    
499                    Layout parentLayout = layoutPersistence.findByG_P_L(
500                            groupId, privateLayout, parentLayoutId);
501    
502                    LayoutType parentLayoutType = parentLayout.getLayoutType();
503    
504                    if (!parentLayoutType.isParentable()) {
505                            throw new LayoutParentLayoutIdException(
506                                    LayoutParentLayoutIdException.NOT_PARENTABLE);
507                    }
508    
509                    // Layout cannot become a child of a layout that is not sortable because
510                    // it is linked to a layout set prototype
511    
512                    if (Validator.isNull(layout.getSourcePrototypeLayoutUuid()) &&
513                            !SitesUtil.isLayoutSortable(parentLayout)) {
514    
515                            throw new LayoutParentLayoutIdException(
516                                    LayoutParentLayoutIdException.NOT_SORTABLE);
517                    }
518    
519                    // Layout cannot become descendant of itself
520    
521                    if (PortalUtil.isLayoutDescendant(layout, parentLayoutId)) {
522                            throw new LayoutParentLayoutIdException(
523                                    LayoutParentLayoutIdException.SELF_DESCENDANT);
524                    }
525    
526                    // If layout is moved, the new first layout must be valid
527    
528                    if (layout.getParentLayoutId() ==
529                                    LayoutConstants.DEFAULT_PARENT_LAYOUT_ID) {
530    
531                            List<Layout> layouts = layoutPersistence.findByG_P_P(
532                                    groupId, privateLayout,
533                                    LayoutConstants.DEFAULT_PARENT_LAYOUT_ID, 0, 2);
534    
535                            // You can only reach this point if there are more than two layouts
536                            // at the root level because of the descendant check
537    
538                            long firstLayoutId = layouts.get(0).getLayoutId();
539    
540                            if (firstLayoutId == layoutId) {
541                                    Layout secondLayout = layouts.get(1);
542    
543                                    LayoutType layoutType = secondLayout.getLayoutType();
544    
545                                    if (Validator.isNull(secondLayout.getType()) ||
546                                            !layoutType.isFirstPageable()) {
547    
548                                            throw new LayoutParentLayoutIdException(
549                                                    LayoutParentLayoutIdException.FIRST_LAYOUT_TYPE);
550                                    }
551                            }
552                    }
553            }
554    
555            protected boolean hasGuestViewPermission(Layout layout)
556                    throws PortalException {
557    
558                    Role role = RoleLocalServiceUtil.getRole(
559                            layout.getCompanyId(), RoleConstants.GUEST);
560    
561                    return resourcePermissionLocalService.hasResourcePermission(
562                            layout.getCompanyId(), Layout.class.getName(),
563                            ResourceConstants.SCOPE_INDIVIDUAL,
564                            String.valueOf(layout.getPlid()), role.getRoleId(),
565                            ActionKeys.VIEW);
566            }
567    
568            @BeanReference(type = LayoutFriendlyURLPersistence.class)
569            protected LayoutFriendlyURLPersistence layoutFriendlyURLPersistence;
570    
571            @BeanReference(type = LayoutPersistence.class)
572            protected LayoutPersistence layoutPersistence;
573    
574            @BeanReference(type = LayoutSetPersistence.class)
575            protected LayoutSetPersistence layoutSetPersistence;
576    
577            @BeanReference(type = ResourcePermissionLocalService.class)
578            protected ResourcePermissionLocalService resourcePermissionLocalService;
579    
580            private static final int _PRIORITY_BUFFER = 1000000;
581    
582    }