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