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