1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portlet.wiki.service.impl;
24  
25  import com.liferay.portal.PortalException;
26  import com.liferay.portal.SystemException;
27  import com.liferay.portal.kernel.language.LanguageUtil;
28  import com.liferay.portal.kernel.util.Diff;
29  import com.liferay.portal.kernel.util.DiffResult;
30  import com.liferay.portal.kernel.util.DiffUtil;
31  import com.liferay.portal.kernel.util.GetterUtil;
32  import com.liferay.portal.kernel.util.HtmlUtil;
33  import com.liferay.portal.kernel.util.HttpUtil;
34  import com.liferay.portal.kernel.util.ObjectValuePair;
35  import com.liferay.portal.kernel.util.StringPool;
36  import com.liferay.portal.kernel.util.StringUtil;
37  import com.liferay.portal.kernel.velocity.VelocityContext;
38  import com.liferay.portal.kernel.velocity.VelocityEngineUtil;
39  import com.liferay.portal.security.permission.ActionKeys;
40  import com.liferay.portal.theme.ThemeDisplay;
41  import com.liferay.portal.util.ContentUtil;
42  import com.liferay.portal.util.PortalUtil;
43  import com.liferay.portal.util.PortletKeys;
44  import com.liferay.portal.util.PropsKeys;
45  import com.liferay.portal.util.PropsUtil;
46  import com.liferay.portlet.wiki.model.WikiNode;
47  import com.liferay.portlet.wiki.model.WikiPage;
48  import com.liferay.portlet.wiki.model.impl.WikiPageImpl;
49  import com.liferay.portlet.wiki.service.base.WikiPageServiceBaseImpl;
50  import com.liferay.portlet.wiki.service.permission.WikiNodePermission;
51  import com.liferay.portlet.wiki.service.permission.WikiPagePermission;
52  import com.liferay.portlet.wiki.util.WikiUtil;
53  import com.liferay.portlet.wiki.util.comparator.PageCreateDateComparator;
54  import com.liferay.util.RSSUtil;
55  
56  import com.sun.syndication.feed.synd.SyndContent;
57  import com.sun.syndication.feed.synd.SyndContentImpl;
58  import com.sun.syndication.feed.synd.SyndEntry;
59  import com.sun.syndication.feed.synd.SyndEntryImpl;
60  import com.sun.syndication.feed.synd.SyndFeed;
61  import com.sun.syndication.feed.synd.SyndFeedImpl;
62  import com.sun.syndication.io.FeedException;
63  
64  import java.io.StringReader;
65  import java.io.StringWriter;
66  
67  import java.util.ArrayList;
68  import java.util.Iterator;
69  import java.util.List;
70  import java.util.Locale;
71  
72  import javax.portlet.PortletPreferences;
73  
74  /**
75   * <a href="WikiPageServiceImpl.java.html"><b><i>View Source</i></b></a>
76   *
77   * @author Brian Wing Shun Chan
78   * @author Jorge Ferrer
79   * @author Raymond Augé
80   *
81   */
82  public class WikiPageServiceImpl extends WikiPageServiceBaseImpl {
83  
84      public WikiPage addPage(
85              long nodeId, String title, String content, String summary,
86              boolean minorEdit, PortletPreferences prefs,
87              ThemeDisplay themeDisplay)
88          throws PortalException, SystemException {
89  
90          WikiNodePermission.check(
91              getPermissionChecker(), nodeId, ActionKeys.ADD_PAGE);
92  
93          return wikiPageLocalService.addPage(
94              getUserId(), nodeId, title, content, summary, minorEdit, prefs,
95              themeDisplay);
96      }
97  
98      public WikiPage addPage(
99              long nodeId, String title, String content, String summary,
100             boolean minorEdit, String format, String parentTitle,
101             String redirectTitle, String[] tagsEntries,
102             PortletPreferences prefs, ThemeDisplay themeDisplay)
103         throws PortalException, SystemException {
104 
105         WikiNodePermission.check(
106             getPermissionChecker(), nodeId, ActionKeys.ADD_PAGE);
107 
108         return wikiPageLocalService.addPage(
109             null, getUserId(), nodeId, title, WikiPageImpl.DEFAULT_VERSION,
110             content, summary, minorEdit, format, true, parentTitle,
111             redirectTitle, tagsEntries, prefs, themeDisplay);
112     }
113 
114     public void addPageAttachments(
115             long nodeId, String title,
116             List<ObjectValuePair<String, byte[]>> files)
117         throws PortalException, SystemException {
118 
119         WikiNodePermission.check(
120             getPermissionChecker(), nodeId, ActionKeys.ADD_ATTACHMENT);
121 
122         wikiPageLocalService.addPageAttachments(nodeId, title, files);
123     }
124 
125     public void changeParent(
126             long nodeId, String title, String newParentTitle,
127             PortletPreferences prefs, ThemeDisplay themeDisplay)
128         throws PortalException, SystemException {
129 
130         WikiPagePermission.check(
131             getPermissionChecker(), nodeId, title, ActionKeys.DELETE);
132 
133         WikiNodePermission.check(
134             getPermissionChecker(), nodeId, ActionKeys.ADD_PAGE);
135 
136         wikiPageLocalService.changeParent(
137             getUserId(), nodeId, title, newParentTitle, prefs, themeDisplay);
138     }
139 
140     public void deletePage(long nodeId, String title)
141         throws PortalException, SystemException {
142 
143         WikiPagePermission.check(
144             getPermissionChecker(), nodeId, title, ActionKeys.DELETE);
145 
146         wikiPageLocalService.deletePage(nodeId, title);
147     }
148 
149     public void deletePageAttachment(long nodeId, String title, String fileName)
150         throws PortalException, SystemException {
151 
152         WikiPagePermission.check(
153             getPermissionChecker(), nodeId, title, ActionKeys.DELETE);
154 
155         wikiPageLocalService.deletePageAttachment(nodeId, title, fileName);
156     }
157 
158     public List<WikiPage> getNodePages(long nodeId, int max)
159         throws PortalException, SystemException {
160 
161         List<WikiPage> pages = new ArrayList<WikiPage>();
162 
163         int lastIntervalStart = 0;
164         boolean listNotExhausted = true;
165 
166         while ((pages.size() < max) && listNotExhausted) {
167             List<WikiPage> pageList = wikiPageLocalService.getPages(
168                 nodeId, true, lastIntervalStart, lastIntervalStart + max);
169 
170             Iterator<WikiPage> itr = pageList.iterator();
171 
172             lastIntervalStart += max;
173             listNotExhausted = (pageList.size() == max);
174 
175             while (itr.hasNext() && (pages.size() < max)) {
176                 WikiPage page = itr.next();
177 
178                 if (WikiPagePermission.contains(getPermissionChecker(), page,
179                         ActionKeys.VIEW)) {
180 
181                     pages.add(page);
182                 }
183             }
184         }
185 
186         return pages;
187     }
188 
189     public String getNodePagesRSS(
190             long nodeId, int max, String type, double version,
191             String displayStyle, String feedURL, String entryURL)
192         throws PortalException, SystemException {
193 
194         WikiNodePermission.check(
195             getPermissionChecker(), nodeId, ActionKeys.VIEW);
196 
197         WikiNode node = wikiNodePersistence.findByPrimaryKey(nodeId);
198 
199         long companyId = node.getCompanyId();
200         String name = node.getName();
201         String description = node.getDescription();
202         List<WikiPage> pages = getNodePages(nodeId, max);
203         boolean diff = false;
204         Locale locale = null;
205 
206         return exportToRSS(
207             companyId, name, description, type, version, displayStyle,
208             feedURL, entryURL, pages, diff, locale);
209     }
210 
211     public WikiPage getPage(long nodeId, String title)
212         throws PortalException, SystemException {
213 
214         WikiPagePermission.check(
215             getPermissionChecker(), nodeId, title, ActionKeys.VIEW);
216 
217         return wikiPageLocalService.getPage(nodeId, title);
218     }
219 
220     public WikiPage getPage(long nodeId, String title, double version)
221         throws PortalException, SystemException {
222 
223         WikiPagePermission.check(
224             getPermissionChecker(), nodeId, title, ActionKeys.VIEW);
225 
226         return wikiPageLocalService.getPage(nodeId, title, version);
227     }
228 
229     public String getPagesRSS(
230             long companyId, long nodeId, String title, int max, String type,
231             double version, String displayStyle, String feedURL,
232             String entryURL, Locale locale)
233         throws PortalException, SystemException {
234 
235         WikiPagePermission.check(
236             getPermissionChecker(), nodeId, title, ActionKeys.VIEW);
237 
238         String description = title;
239         List<WikiPage> pages = wikiPageLocalService.getPages(
240             nodeId, title, 0, max, new PageCreateDateComparator(true));
241         boolean diff = true;
242 
243         return exportToRSS(
244             companyId, title, description, type, version, displayStyle, feedURL,
245             entryURL, pages, diff, locale);
246     }
247 
248     public void movePage(
249             long nodeId, String title, String newTitle,
250             PortletPreferences prefs, ThemeDisplay themeDisplay)
251         throws PortalException, SystemException {
252 
253         WikiPagePermission.check(
254             getPermissionChecker(), nodeId, title, ActionKeys.DELETE);
255 
256         WikiNodePermission.check(
257             getPermissionChecker(), nodeId, ActionKeys.ADD_PAGE);
258 
259         wikiPageLocalService.movePage(
260             getUserId(), nodeId, title, newTitle, prefs, themeDisplay);
261     }
262 
263     public WikiPage revertPage(
264             long nodeId, String title, double version, PortletPreferences prefs,
265             ThemeDisplay themeDisplay)
266         throws PortalException, SystemException {
267 
268         WikiPagePermission.check(
269             getPermissionChecker(), nodeId, title, ActionKeys.UPDATE);
270 
271         return wikiPageLocalService.revertPage(
272             getUserId(), nodeId, title, version, prefs, themeDisplay);
273     }
274 
275     public void subscribePage(long nodeId, String title)
276         throws PortalException, SystemException {
277 
278         WikiPagePermission.check(
279             getPermissionChecker(), nodeId, title, ActionKeys.SUBSCRIBE);
280 
281         wikiPageLocalService.subscribePage(getUserId(), nodeId, title);
282     }
283 
284     public void unsubscribePage(long nodeId, String title)
285         throws PortalException, SystemException {
286 
287         WikiPagePermission.check(
288             getPermissionChecker(), nodeId, title, ActionKeys.SUBSCRIBE);
289 
290         wikiPageLocalService.unsubscribePage(getUserId(), nodeId, title);
291     }
292 
293     public WikiPage updatePage(
294             long nodeId, String title, double version, String content,
295             String summary, boolean minorEdit, String format,
296             String parentTitle, String redirectTitle, String[] tagsEntries,
297             PortletPreferences prefs, ThemeDisplay themeDisplay)
298         throws PortalException, SystemException {
299 
300         WikiPagePermission.check(
301             getPermissionChecker(), nodeId, title, ActionKeys.UPDATE);
302 
303         return wikiPageLocalService.updatePage(
304             getUserId(), nodeId, title, version, content, summary, minorEdit,
305             format, parentTitle, redirectTitle, tagsEntries, prefs,
306             themeDisplay);
307     }
308 
309     protected String exportToRSS(
310             long companyId, String name, String description, String type,
311             double version, String displayStyle, String feedURL,
312             String entryURL, List<WikiPage> pages, boolean diff, Locale locale)
313         throws SystemException {
314 
315         SyndFeed syndFeed = new SyndFeedImpl();
316 
317         syndFeed.setFeedType(RSSUtil.getFeedType(type, version));
318         syndFeed.setTitle(name);
319         syndFeed.setLink(feedURL);
320         syndFeed.setDescription(description);
321 
322         List<SyndEntry> entries = new ArrayList<SyndEntry>();
323 
324         syndFeed.setEntries(entries);
325 
326         WikiPage latestPage = null;
327 
328         for (WikiPage page : pages) {
329             String author = PortalUtil.getUserName(
330                 page.getUserId(), page.getUserName());
331 
332             String title =
333                 page.getTitle() + StringPool.SPACE + page.getVersion();
334 
335             if (page.isMinorEdit()) {
336                 title +=
337                     StringPool.SPACE + StringPool.OPEN_PARENTHESIS +
338                         LanguageUtil.get(locale, "minor-edit") +
339                             StringPool.CLOSE_PARENTHESIS;
340             }
341 
342             StringBuilder link = new StringBuilder();
343 
344             link.append(entryURL);
345             link.append(StringPool.AMPERSAND);
346             link.append(HttpUtil.encodeURL(page.getTitle()));
347 
348             SyndEntry syndEntry = new SyndEntryImpl();
349 
350             syndEntry.setAuthor(author);
351             syndEntry.setTitle(title);
352             syndEntry.setPublishedDate(page.getCreateDate());
353             syndEntry.setUpdatedDate(page.getModifiedDate());
354 
355             SyndContent syndContent = new SyndContentImpl();
356 
357             syndContent.setType(RSSUtil.DEFAULT_ENTRY_TYPE);
358 
359             if (diff) {
360                 if (latestPage != null) {
361                     link.append(StringPool.QUESTION);
362                     link.append(
363                         PortalUtil.getPortletNamespace(PortletKeys.WIKI));
364                     link.append("version=");
365                     link.append(page.getVersion());
366 
367                     String value = getPageDiff(
368                         companyId, latestPage, page, locale);
369 
370                     syndContent.setValue(value);
371 
372                     syndEntry.setDescription(syndContent);
373 
374                     entries.add(syndEntry);
375                 }
376             }
377             else {
378                 String value = null;
379 
380                 if (displayStyle.equals(RSSUtil.DISPLAY_STYLE_ABSTRACT)) {
381                     value = StringUtil.shorten(
382                         HtmlUtil.extractText(page.getContent()),
383                         _RSS_ABSTRACT_LENGTH, StringPool.BLANK);
384                 }
385                 else if (displayStyle.equals(RSSUtil.DISPLAY_STYLE_TITLE)) {
386                     value = StringPool.BLANK;
387                 }
388                 else {
389                     value = page.getContent();
390                 }
391 
392                 syndContent.setValue(value);
393 
394                 syndEntry.setDescription(syndContent);
395 
396                 entries.add(syndEntry);
397             }
398 
399             syndEntry.setLink(link.toString());
400             syndEntry.setUri(syndEntry.getLink());
401 
402             latestPage = page;
403         }
404 
405         try {
406             return RSSUtil.export(syndFeed);
407         }
408         catch (FeedException fe) {
409             throw new SystemException(fe);
410         }
411     }
412 
413     protected String getPageDiff(
414             long companyId, WikiPage latestPage, WikiPage page,
415             Locale locale)
416         throws SystemException {
417 
418         String sourceContent = WikiUtil.processContent(latestPage.getContent());
419         String targetContent = WikiUtil.processContent(page.getContent());
420 
421         sourceContent = HtmlUtil.escape(sourceContent);
422         targetContent = HtmlUtil.escape(targetContent);
423 
424         List<DiffResult>[] diffResults = DiffUtil.diff(
425             new StringReader(sourceContent), new StringReader(targetContent));
426 
427         String velocityTemplateId =
428             "com/liferay/portlet/wiki/dependencies/rss.vm";
429         String velocityTemplateContent = ContentUtil.get(velocityTemplateId);
430 
431         VelocityContext velocityContext =
432             VelocityEngineUtil.getWrappedStandardToolsContext();
433 
434         velocityContext.put("companyId", companyId);
435         velocityContext.put("contextLine", Diff.CONTEXT_LINE);
436         velocityContext.put("diffUtil", new DiffUtil());
437         velocityContext.put("languageUtil", LanguageUtil.getLanguage());
438         velocityContext.put("locale", locale);
439         velocityContext.put("sourceResults", diffResults[0]);
440         velocityContext.put("targetResults", diffResults[1]);
441 
442         try {
443             StringWriter stringWriter = new StringWriter();
444 
445             VelocityEngineUtil.mergeTemplate(
446                 velocityTemplateId, velocityTemplateContent, velocityContext,
447                 stringWriter);
448 
449             return stringWriter.toString();
450         }
451         catch (Exception e) {
452             throw new SystemException(e);
453         }
454     }
455 
456     private static final int _RSS_ABSTRACT_LENGTH = GetterUtil.getInteger(
457         PropsUtil.get(PropsKeys.WIKI_RSS_ABSTRACT_LENGTH));
458 
459 }