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.sitemaps.org/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(
171                                            "xhtml:link", "http://www.w3.org/1999/xhtml");
172    
173                                    alternateURLElement.addAttribute("href", href);
174                                    alternateURLElement.addAttribute(
175                                            "hreflang", LocaleUtil.toW3cLanguageId(locale));
176                                    alternateURLElement.addAttribute("rel", "alternate");
177                            }
178    
179                            Element alternateURLElement = urlElement.addElement(
180                                    "xhtml:link", "http://www.w3.org/1999/xhtml");
181    
182                            alternateURLElement.addAttribute("rel", "alternate");
183                            alternateURLElement.addAttribute("hreflang", "x-default");
184                            alternateURLElement.addAttribute("href", canonicalURL);
185                    }
186            }
187    
188            protected Map<Locale, String> getAlternateURLs(
189                            String canonicalURL, ThemeDisplay themeDisplay, Layout layout)
190                    throws PortalException, SystemException {
191    
192                    Map<Locale, String> alternateURLs = new HashMap<Locale, String>();
193    
194                    Locale[] availableLocales = LanguageUtil.getAvailableLocales(
195                            layout.getGroupId());
196    
197                    for (Locale availableLocale : availableLocales) {
198                            String alternateURL = PortalUtil.getAlternateURL(
199                                    canonicalURL, themeDisplay, availableLocale, layout);
200    
201                            alternateURLs.put(availableLocale, alternateURL);
202                    }
203    
204                    return alternateURLs;
205            }
206    
207            protected void visitArticles(
208                            Element element, Layout layout, ThemeDisplay themeDisplay)
209                    throws PortalException, SystemException {
210    
211                    List<JournalArticle> journalArticles =
212                            JournalArticleServiceUtil.getArticlesByLayoutUuid(
213                                    layout.getGroupId(), layout.getUuid());
214    
215                    if (journalArticles.isEmpty()) {
216                            return;
217                    }
218    
219                    Set<String> processedArticleIds = new HashSet<String>();
220    
221                    for (JournalArticle journalArticle : journalArticles) {
222                            if (processedArticleIds.contains(
223                                            journalArticle.getArticleId()) ||
224                                    (journalArticle.getStatus() !=
225                                            WorkflowConstants.STATUS_APPROVED)) {
226    
227                                    continue;
228                            }
229    
230                            String portalURL = PortalUtil.getPortalURL(layout, themeDisplay);
231    
232                            String groupFriendlyURL = PortalUtil.getGroupFriendlyURL(
233                                    GroupLocalServiceUtil.getGroup(journalArticle.getGroupId()),
234                                    false, 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, SystemException {
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, SystemException {
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    }