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.portlet.layoutsadmin.util;
016    
017    import com.liferay.portal.kernel.exception.PortalException;
018    import com.liferay.portal.kernel.language.LanguageUtil;
019    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
020    import com.liferay.portal.kernel.util.DateUtil;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.LocaleUtil;
023    import com.liferay.portal.kernel.util.StringBundler;
024    import com.liferay.portal.kernel.util.StringPool;
025    import com.liferay.portal.kernel.util.StringUtil;
026    import com.liferay.portal.kernel.util.UnicodeProperties;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.kernel.workflow.WorkflowConstants;
029    import com.liferay.portal.kernel.xml.Document;
030    import com.liferay.portal.kernel.xml.Element;
031    import com.liferay.portal.kernel.xml.SAXReaderUtil;
032    import com.liferay.portal.model.Layout;
033    import com.liferay.portal.model.LayoutConstants;
034    import com.liferay.portal.service.LayoutLocalServiceUtil;
035    import com.liferay.portal.service.LayoutSetLocalServiceUtil;
036    import com.liferay.portal.theme.ThemeDisplay;
037    import com.liferay.portal.util.PortalUtil;
038    import com.liferay.portal.util.PropsValues;
039    import com.liferay.portlet.journal.model.JournalArticle;
040    import com.liferay.portlet.journal.model.JournalArticleConstants;
041    import com.liferay.portlet.journal.service.JournalArticleServiceUtil;
042    
043    import java.text.DateFormat;
044    
045    import java.util.Date;
046    import java.util.HashMap;
047    import java.util.HashSet;
048    import java.util.List;
049    import java.util.Locale;
050    import java.util.Map;
051    import java.util.Set;
052    
053    /**
054     * @author Jorge Ferrer
055     * @author Vilmos Papp
056     */
057    @DoPrivileged
058    public class SitemapImpl implements Sitemap {
059    
060            @Override
061            public String encodeXML(String input) {
062                    return StringUtil.replace(
063                            input,
064                            new String[] {"&", "<", ">", "'", "\""},
065                            new String[] {"&amp;", "&lt;", "&gt;", "&apos;", "&quot;"});
066            }
067    
068            @Override
069            public String getSitemap(
070                            long groupId, boolean privateLayout, ThemeDisplay themeDisplay)
071                    throws PortalException {
072    
073                    Document document = SAXReaderUtil.createDocument();
074    
075                    document.setXMLEncoding(StringPool.UTF8);
076    
077                    Element rootElement = document.addElement(
078                            "urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
079    
080                    rootElement.addAttribute("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
081    
082                    List<Layout> layouts = LayoutLocalServiceUtil.getLayouts(
083                            groupId, privateLayout, LayoutConstants.DEFAULT_PARENT_LAYOUT_ID);
084    
085                    visitLayouts(rootElement, layouts, themeDisplay);
086    
087                    return document.asXML();
088            }
089    
090            protected void addURLElement(
091                    Element element, String url, UnicodeProperties typeSettingsProperties,
092                    Date modifiedDate, String canonicalURL,
093                    Map<Locale, String> alternateURLs) {
094    
095                    Element urlElement = element.addElement("url");
096    
097                    Element locElement = urlElement.addElement("loc");
098    
099                    locElement.addText(encodeXML(url));
100    
101                    if (typeSettingsProperties == null) {
102                            if (Validator.isNotNull(
103                                            PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY)) {
104    
105                                    Element changefreqElement = urlElement.addElement("changefreq");
106    
107                                    changefreqElement.addText(
108                                            PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY);
109                            }
110    
111                            if (Validator.isNotNull(
112                                            PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY)) {
113    
114                                    Element priorityElement = urlElement.addElement("priority");
115    
116                                    priorityElement.addText(
117                                            PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY);
118                            }
119                    }
120                    else {
121                            String changefreq = typeSettingsProperties.getProperty(
122                                    "sitemap-changefreq");
123    
124                            if (Validator.isNotNull(changefreq)) {
125                                    Element changefreqElement = urlElement.addElement("changefreq");
126    
127                                    changefreqElement.addText(changefreq);
128                            }
129                            else if (Validator.isNotNull(
130                                                    PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY)) {
131    
132                                    Element changefreqElement = urlElement.addElement("changefreq");
133    
134                                    changefreqElement.addText(
135                                            PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY);
136                            }
137    
138                            String priority = typeSettingsProperties.getProperty(
139                                    "sitemap-priority");
140    
141                            if (Validator.isNotNull(priority)) {
142                                    Element priorityElement = urlElement.addElement("priority");
143    
144                                    priorityElement.addText(priority);
145                            }
146                            else if (Validator.isNotNull(
147                                                    PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY)) {
148    
149                                    Element priorityElement = urlElement.addElement("priority");
150    
151                                    priorityElement.addText(
152                                            PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY);
153                            }
154                    }
155    
156                    if (modifiedDate != null) {
157                            Element modifiedDateElement = urlElement.addElement("lastmod");
158    
159                            DateFormat iso8601DateFormat = DateUtil.getISO8601Format();
160    
161                            modifiedDateElement.addText(iso8601DateFormat.format(modifiedDate));
162                    }
163    
164                    if (alternateURLs != null) {
165                            for (Map.Entry<Locale, String> entry : alternateURLs.entrySet()) {
166                                    Locale locale = entry.getKey();
167                                    String href = entry.getValue();
168    
169                                    Element alternateURLElement = urlElement.addElement(
170                                            "xhtml:link", "http://www.w3.org/1999/xhtml");
171    
172                                    alternateURLElement.addAttribute("href", href);
173                                    alternateURLElement.addAttribute(
174                                            "hreflang", LocaleUtil.toW3cLanguageId(locale));
175                                    alternateURLElement.addAttribute("rel", "alternate");
176                            }
177    
178                            Element alternateURLElement = urlElement.addElement(
179                                    "xhtml:link", "http://www.w3.org/1999/xhtml");
180    
181                            alternateURLElement.addAttribute("rel", "alternate");
182                            alternateURLElement.addAttribute("hreflang", "x-default");
183                            alternateURLElement.addAttribute("href", canonicalURL);
184                    }
185            }
186    
187            protected Map<Locale, String> getAlternateURLs(
188                            String canonicalURL, ThemeDisplay themeDisplay, Layout layout)
189                    throws PortalException {
190    
191                    Map<Locale, String> alternateURLs = new HashMap<Locale, String>();
192    
193                    Locale[] availableLocales = LanguageUtil.getAvailableLocales(
194                            layout.getGroupId());
195    
196                    for (Locale availableLocale : availableLocales) {
197                            String alternateURL = PortalUtil.getAlternateURL(
198                                    canonicalURL, themeDisplay, availableLocale, layout);
199    
200                            alternateURLs.put(availableLocale, alternateURL);
201                    }
202    
203                    return alternateURLs;
204            }
205    
206            protected void visitArticles(
207                            Element element, Layout layout, ThemeDisplay themeDisplay)
208                    throws PortalException {
209    
210                    List<JournalArticle> journalArticles =
211                            JournalArticleServiceUtil.getArticlesByLayoutUuid(
212                                    layout.getGroupId(), layout.getUuid());
213    
214                    if (journalArticles.isEmpty()) {
215                            return;
216                    }
217    
218                    Set<String> processedArticleIds = new HashSet<String>();
219    
220                    for (JournalArticle journalArticle : journalArticles) {
221                            if (processedArticleIds.contains(
222                                            journalArticle.getArticleId()) ||
223                                    (journalArticle.getStatus() !=
224                                            WorkflowConstants.STATUS_APPROVED)) {
225    
226                                    continue;
227                            }
228    
229                            String portalURL = PortalUtil.getPortalURL(layout, themeDisplay);
230    
231                            String groupFriendlyURL = PortalUtil.getGroupFriendlyURL(
232                                    LayoutSetLocalServiceUtil.getLayoutSet(
233                                            journalArticle.getGroupId(), false),
234                                    themeDisplay);
235    
236                            StringBundler sb = new StringBundler(4);
237    
238                            if (!groupFriendlyURL.startsWith(portalURL)) {
239                                    sb.append(portalURL);
240                            }
241    
242                            sb.append(groupFriendlyURL);
243                            sb.append(JournalArticleConstants.CANONICAL_URL_SEPARATOR);
244                            sb.append(journalArticle.getUrlTitle());
245    
246                            String articleURL = PortalUtil.getCanonicalURL(
247                                    sb.toString(), themeDisplay, layout);
248    
249                            addURLElement(
250                                    element, articleURL, null, journalArticle.getModifiedDate(),
251                                    articleURL, getAlternateURLs(articleURL, themeDisplay, layout));
252    
253                            Locale[] availableLocales = LanguageUtil.getAvailableLocales(
254                                    layout.getGroupId());
255    
256                            if (availableLocales.length > 1) {
257                                    Locale defaultLocale = LocaleUtil.getSiteDefault();
258    
259                                    for (Locale availableLocale : availableLocales) {
260                                            if (!availableLocale.equals(defaultLocale)) {
261                                                    String alternateURL = PortalUtil.getAlternateURL(
262                                                            articleURL, themeDisplay, availableLocale, layout);
263    
264                                                    addURLElement(
265                                                            element, alternateURL, null,
266                                                            journalArticle.getModifiedDate(), articleURL,
267                                                            getAlternateURLs(articleURL, themeDisplay, layout));
268                                            }
269                                    }
270                            }
271    
272                            processedArticleIds.add(journalArticle.getArticleId());
273                    }
274            }
275    
276            protected void visitLayout(
277                            Element element, Layout layout, ThemeDisplay themeDisplay)
278                    throws PortalException {
279    
280                    UnicodeProperties typeSettingsProperties =
281                            layout.getTypeSettingsProperties();
282    
283                    if (!PortalUtil.isLayoutSitemapable(layout) ||
284                            !GetterUtil.getBoolean(
285                                    typeSettingsProperties.getProperty("sitemap-include"), true)) {
286    
287                            return;
288                    }
289    
290                    String layoutFullURL = PortalUtil.getLayoutFullURL(
291                            layout, themeDisplay);
292    
293                    layoutFullURL = PortalUtil.getCanonicalURL(
294                            layoutFullURL, themeDisplay, layout);
295    
296                    addURLElement(
297                            element, layoutFullURL, typeSettingsProperties,
298                            layout.getModifiedDate(), layoutFullURL,
299                            getAlternateURLs(layoutFullURL, themeDisplay, layout));
300    
301                    Locale[] availableLocales = LanguageUtil.getAvailableLocales(
302                            layout.getGroupId());
303    
304                    if (availableLocales.length > 1) {
305                            Locale defaultLocale = LocaleUtil.getSiteDefault();
306    
307                            for (Locale availableLocale : availableLocales) {
308                                    if (availableLocale.equals(defaultLocale)) {
309                                            continue;
310                                    }
311    
312                                    String alternateURL = PortalUtil.getAlternateURL(
313                                            layoutFullURL, themeDisplay, availableLocale, layout);
314    
315                                    addURLElement(
316                                            element, alternateURL, typeSettingsProperties,
317                                            layout.getModifiedDate(), layoutFullURL,
318                                            getAlternateURLs(layoutFullURL, themeDisplay, layout));
319                            }
320                    }
321            }
322    
323            protected void visitLayouts(
324                            Element element, List<Layout> layouts, ThemeDisplay themeDisplay)
325                    throws PortalException {
326    
327                    for (Layout layout : layouts) {
328                            visitLayout(element, layout, themeDisplay);
329    
330                            visitArticles(element, layout, themeDisplay);
331    
332                            visitLayouts(element, layout.getChildren(), themeDisplay);
333                    }
334            }
335    
336    }