001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
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.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.LayoutSet;
034    import com.liferay.portal.service.GroupLocalServiceUtil;
035    import com.liferay.portal.service.LayoutLocalServiceUtil;
036    import com.liferay.portal.service.LayoutSetLocalServiceUtil;
037    import com.liferay.portal.theme.ThemeDisplay;
038    import com.liferay.portal.util.LayoutSettings;
039    import com.liferay.portal.util.PortalUtil;
040    import com.liferay.portal.util.PropsValues;
041    import com.liferay.portlet.journal.model.JournalArticle;
042    import com.liferay.portlet.journal.model.JournalArticleConstants;
043    import com.liferay.portlet.journal.service.JournalArticleServiceUtil;
044    
045    import java.text.DateFormat;
046    
047    import java.util.Date;
048    import java.util.HashMap;
049    import java.util.HashSet;
050    import java.util.List;
051    import java.util.Locale;
052    import java.util.Map;
053    import java.util.Set;
054    
055    /**
056     * @author Jorge Ferrer
057     * @author Vilmos Papp
058     */
059    @DoPrivileged
060    public class SitemapImpl implements Sitemap {
061    
062            @Override
063            public String encodeXML(String input) {
064                    StringBundler sb = null;
065    
066                    int lastReplacementIndex = 0;
067    
068                    for (int i = 0; i < input.length(); i++) {
069                            char c = input.charAt(i);
070    
071                            String html = null;
072    
073                            if (c == '&') {
074                                    html = "&amp;";
075                            }
076                            else if (c == '<') {
077                                    html = "&lt;";
078                            }
079                            else if (c == '>') {
080                                    html = "&gt;";
081                            }
082                            else if (c == '\'') {
083                                    html = "&apos;";
084                            }
085                            else if (c == '"') {
086                                    html = "&quot;";
087                            }
088    
089                            if (html != null) {
090                                    if (sb == null) {
091                                            sb = new StringBundler();
092                                    }
093    
094                                    if (i > lastReplacementIndex) {
095                                            sb.append(input.substring(lastReplacementIndex, i));
096                                    }
097    
098                                    sb.append(html);
099    
100                                    lastReplacementIndex = i + 1;
101                            }
102                    }
103    
104                    if (sb == null) {
105                            return input;
106                    }
107    
108                    if (lastReplacementIndex < input.length()) {
109                            sb.append(input.substring(lastReplacementIndex));
110                    }
111    
112                    return sb.toString();
113            }
114    
115            @Override
116            public String getSitemap(
117                            long groupId, boolean privateLayout, ThemeDisplay themeDisplay)
118                    throws PortalException, SystemException {
119    
120                    Document document = SAXReaderUtil.createDocument();
121    
122                    document.setXMLEncoding(StringPool.UTF8);
123    
124                    Element rootElement = document.addElement(
125                            "urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
126    
127                    rootElement.addAttribute("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
128    
129                    LayoutSet layoutSet = LayoutSetLocalServiceUtil.getLayoutSet(
130                            groupId, privateLayout);
131    
132                    visitLayoutSet(rootElement, layoutSet, themeDisplay);
133    
134                    visitArticles(rootElement, layoutSet, themeDisplay);
135    
136                    return document.asXML();
137            }
138    
139            protected void addURLElement(
140                    Element element, String url, UnicodeProperties typeSettingsProperties,
141                    Date modifiedDate, String canonicalURL,
142                    Map<Locale, String> alternateURLs) {
143    
144                    Element urlElement = element.addElement("url");
145    
146                    Element locElement = urlElement.addElement("loc");
147    
148                    locElement.addText(encodeXML(url));
149    
150                    if (typeSettingsProperties == null) {
151                            if (Validator.isNotNull(
152                                            PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY)) {
153    
154                                    Element changefreqElement = urlElement.addElement("changefreq");
155    
156                                    changefreqElement.addText(
157                                            PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY);
158                            }
159    
160                            if (Validator.isNotNull(
161                                            PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY)) {
162    
163                                    Element priorityElement = urlElement.addElement("priority");
164    
165                                    priorityElement.addText(
166                                            PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY);
167                            }
168                    }
169                    else {
170                            String changefreq = typeSettingsProperties.getProperty(
171                                    "sitemap-changefreq");
172    
173                            if (Validator.isNotNull(changefreq)) {
174                                    Element changefreqElement = urlElement.addElement("changefreq");
175    
176                                    changefreqElement.addText(changefreq);
177                            }
178                            else if (Validator.isNotNull(
179                                                    PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY)) {
180    
181                                    Element changefreqElement = urlElement.addElement("changefreq");
182    
183                                    changefreqElement.addText(
184                                            PropsValues.SITES_SITEMAP_DEFAULT_CHANGE_FREQUENCY);
185                            }
186    
187                            String priority = typeSettingsProperties.getProperty(
188                                    "sitemap-priority");
189    
190                            if (Validator.isNotNull(priority)) {
191                                    Element priorityElement = urlElement.addElement("priority");
192    
193                                    priorityElement.addText(priority);
194                            }
195                            else if (Validator.isNotNull(
196                                                    PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY)) {
197    
198                                    Element priorityElement = urlElement.addElement("priority");
199    
200                                    priorityElement.addText(
201                                            PropsValues.SITES_SITEMAP_DEFAULT_PRIORITY);
202                            }
203                    }
204    
205                    if (modifiedDate != null) {
206                            Element modifiedDateElement = urlElement.addElement("lastmod");
207    
208                            DateFormat iso8601DateFormat = DateUtil.getISO8601Format();
209    
210                            modifiedDateElement.addText(iso8601DateFormat.format(modifiedDate));
211                    }
212    
213                    if (alternateURLs != null) {
214                            for (Map.Entry<Locale, String> entry : alternateURLs.entrySet()) {
215                                    Locale locale = entry.getKey();
216                                    String href = entry.getValue();
217    
218                                    Element alternateURLElement = urlElement.addElement(
219                                            "xhtml:link", "http://www.w3.org/1999/xhtml");
220    
221                                    alternateURLElement.addAttribute("href", href);
222                                    alternateURLElement.addAttribute(
223                                            "hreflang", LocaleUtil.toW3cLanguageId(locale));
224                                    alternateURLElement.addAttribute("rel", "alternate");
225                            }
226    
227                            Element alternateURLElement = urlElement.addElement(
228                                    "xhtml:link", "http://www.w3.org/1999/xhtml");
229    
230                            alternateURLElement.addAttribute("rel", "alternate");
231                            alternateURLElement.addAttribute("hreflang", "x-default");
232                            alternateURLElement.addAttribute("href", canonicalURL);
233                    }
234            }
235    
236            protected Map<Locale, String> getAlternateURLs(
237                            String canonicalURL, ThemeDisplay themeDisplay, Layout layout)
238                    throws PortalException, SystemException {
239    
240                    Map<Locale, String> alternateURLs = new HashMap<Locale, String>();
241    
242                    Locale[] availableLocales = LanguageUtil.getAvailableLocales(
243                            layout.getGroupId());
244    
245                    for (Locale availableLocale : availableLocales) {
246                            String alternateURL = PortalUtil.getAlternateURL(
247                                    canonicalURL, themeDisplay, availableLocale, layout);
248    
249                            alternateURLs.put(availableLocale, alternateURL);
250                    }
251    
252                    return alternateURLs;
253            }
254    
255            protected void visitArticles(
256                            Element element, LayoutSet layoutSet, ThemeDisplay themeDisplay)
257                    throws PortalException, SystemException {
258    
259                    List<JournalArticle> journalArticles =
260                            JournalArticleServiceUtil.getLayoutArticles(layoutSet.getGroupId());
261    
262                    if (journalArticles.isEmpty()) {
263                            return;
264                    }
265    
266                    Set<String> processedArticleIds = new HashSet<String>();
267    
268                    String portalURL = PortalUtil.getPortalURL(layoutSet, themeDisplay);
269    
270                    for (JournalArticle journalArticle : journalArticles) {
271                            if (processedArticleIds.contains(
272                                            journalArticle.getArticleId()) ||
273                                    (journalArticle.getStatus() !=
274                                            WorkflowConstants.STATUS_APPROVED)) {
275    
276                                    continue;
277                            }
278    
279                            String groupFriendlyURL = PortalUtil.getGroupFriendlyURL(
280                                    GroupLocalServiceUtil.getGroup(journalArticle.getGroupId()),
281                                    false, themeDisplay);
282    
283                            StringBundler sb = new StringBundler(4);
284    
285                            if (!groupFriendlyURL.startsWith(portalURL)) {
286                                    sb.append(portalURL);
287                            }
288    
289                            sb.append(groupFriendlyURL);
290                            sb.append(JournalArticleConstants.CANONICAL_URL_SEPARATOR);
291                            sb.append(journalArticle.getUrlTitle());
292    
293                            Layout layout = LayoutLocalServiceUtil.getLayoutByUuidAndGroupId(
294                                    journalArticle.getLayoutUuid(), layoutSet.getGroupId(),
295                                    layoutSet.getPrivateLayout());
296    
297                            String articleURL = PortalUtil.getCanonicalURL(
298                                    sb.toString(), themeDisplay, layout);
299    
300                            Map<Locale, String> alternateURLs = getAlternateURLs(
301                                    articleURL, themeDisplay, layout);
302    
303                            addURLElement(
304                                    element, articleURL, null, journalArticle.getModifiedDate(),
305                                    articleURL, alternateURLs);
306    
307                            if (alternateURLs.size() > 1) {
308                                    Locale defaultLocale = LocaleUtil.getSiteDefault();
309    
310                                    for (Map.Entry<Locale, String> entry :
311                                                    alternateURLs.entrySet()) {
312    
313                                            Locale availableLocale = entry.getKey();
314                                            String alternateURL = entry.getValue();
315    
316                                            if (!availableLocale.equals(defaultLocale)) {
317                                                    addURLElement(
318                                                            element, alternateURL, null,
319                                                            journalArticle.getModifiedDate(), articleURL,
320                                                            alternateURLs);
321                                            }
322                                    }
323                            }
324    
325                            processedArticleIds.add(journalArticle.getArticleId());
326                    }
327            }
328    
329            protected void visitLayoutSet(
330                            Element element, LayoutSet layoutSet, ThemeDisplay themeDisplay)
331                    throws PortalException, SystemException {
332    
333                    if (layoutSet.isPrivateLayout()) {
334                            return;
335                    }
336    
337                    Map<String, LayoutSettings> layoutSettingsMap =
338                            LayoutSettings.getLayoutSettingsMap();
339    
340                    for (Map.Entry<String, LayoutSettings> entry :
341                                    layoutSettingsMap.entrySet()) {
342    
343                            LayoutSettings layoutSetting = entry.getValue();
344    
345                            if (!layoutSetting.isSitemapable()) {
346                                    continue;
347                            }
348    
349                            List<Layout> layouts = LayoutLocalServiceUtil.getLayouts(
350                                    layoutSet.getGroupId(), layoutSet.getPrivateLayout(),
351                                    entry.getKey());
352    
353                            for (Layout layout : layouts) {
354                                    visitLayout(element, layout, themeDisplay);
355                            }
356                    }
357            }
358    
359            protected void visitLayout(
360                            Element element, Layout layout, ThemeDisplay themeDisplay)
361                    throws PortalException, SystemException {
362    
363                    UnicodeProperties typeSettingsProperties =
364                            layout.getTypeSettingsProperties();
365    
366                    if (!GetterUtil.getBoolean(
367                                    typeSettingsProperties.getProperty("sitemap-include"), true)) {
368    
369                            return;
370                    }
371    
372                    String layoutFullURL = PortalUtil.getLayoutFullURL(
373                            layout, themeDisplay);
374    
375                    layoutFullURL = PortalUtil.getCanonicalURL(
376                            layoutFullURL, themeDisplay, layout);
377    
378                    Map<Locale, String> alternateURLs = getAlternateURLs(
379                            layoutFullURL, themeDisplay, layout);
380    
381                    addURLElement(
382                            element, layoutFullURL, typeSettingsProperties,
383                            layout.getModifiedDate(), layoutFullURL, alternateURLs);
384    
385                    if (alternateURLs.size() > 1) {
386                            Locale defaultLocale = LocaleUtil.getSiteDefault();
387    
388                            for (Map.Entry<Locale, String> entry : alternateURLs.entrySet()) {
389                                    Locale availableLocale = entry.getKey();
390                                    String alternateURL = entry.getValue();
391    
392                                    if (availableLocale.equals(defaultLocale)) {
393                                            addURLElement(
394                                                    element, alternateURL, typeSettingsProperties,
395                                                    layout.getModifiedDate(), layoutFullURL, alternateURLs);
396                                    }
397                            }
398                    }
399            }
400    
401    }