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.wiki.util;
016    
017    import com.liferay.portal.kernel.configuration.Filter;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.exception.SystemException;
020    import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
021    import com.liferay.portal.kernel.portlet.LiferayPortletURL;
022    import com.liferay.portal.kernel.util.DiffHtmlUtil;
023    import com.liferay.portal.kernel.util.GetterUtil;
024    import com.liferay.portal.kernel.util.HtmlUtil;
025    import com.liferay.portal.kernel.util.HttpUtil;
026    import com.liferay.portal.kernel.util.InstancePool;
027    import com.liferay.portal.kernel.util.ListUtil;
028    import com.liferay.portal.kernel.util.OrderByComparator;
029    import com.liferay.portal.kernel.util.PropsKeys;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.security.permission.ActionKeys;
035    import com.liferay.portal.security.permission.PermissionChecker;
036    import com.liferay.portal.theme.ThemeDisplay;
037    import com.liferay.portal.util.PortalUtil;
038    import com.liferay.portal.util.PropsUtil;
039    import com.liferay.portal.util.PropsValues;
040    import com.liferay.portal.util.WebKeys;
041    import com.liferay.portlet.wiki.PageContentException;
042    import com.liferay.portlet.wiki.WikiFormatException;
043    import com.liferay.portlet.wiki.engines.WikiEngine;
044    import com.liferay.portlet.wiki.model.WikiNode;
045    import com.liferay.portlet.wiki.model.WikiPage;
046    import com.liferay.portlet.wiki.service.WikiNodeLocalServiceUtil;
047    import com.liferay.portlet.wiki.service.permission.WikiNodePermission;
048    import com.liferay.portlet.wiki.util.comparator.PageCreateDateComparator;
049    import com.liferay.portlet.wiki.util.comparator.PageTitleComparator;
050    import com.liferay.portlet.wiki.util.comparator.PageVersionComparator;
051    import com.liferay.util.ContentUtil;
052    
053    import java.io.IOException;
054    
055    import java.util.ArrayList;
056    import java.util.Arrays;
057    import java.util.Collections;
058    import java.util.HashSet;
059    import java.util.Iterator;
060    import java.util.List;
061    import java.util.Map;
062    import java.util.Set;
063    import java.util.concurrent.ConcurrentHashMap;
064    import java.util.regex.Matcher;
065    import java.util.regex.Pattern;
066    
067    import javax.portlet.PortletPreferences;
068    import javax.portlet.PortletRequest;
069    import javax.portlet.PortletURL;
070    
071    /**
072     * @author Brian Wing Shun Chan
073     * @author Jorge Ferrer
074     */
075    public class WikiUtil {
076    
077            public static String convert(
078                            WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
079                            String attachmentURLPrefix)
080                    throws PageContentException, WikiFormatException {
081    
082                    return _instance._convert(
083                            page, viewPageURL, editPageURL, attachmentURLPrefix);
084            }
085    
086            public static String diffHtml(
087                            WikiPage sourcePage, WikiPage targetPage, PortletURL viewPageURL,
088                            PortletURL editPageURL, String attachmentURLPrefix)
089                    throws Exception {
090    
091                    String sourceContent = StringPool.BLANK;
092                    String targetContent = StringPool.BLANK;
093    
094                    if (sourcePage != null) {
095                            sourceContent = convert(
096                                    sourcePage, viewPageURL, editPageURL, attachmentURLPrefix);
097                    }
098    
099                    if (targetPage != null) {
100                            targetContent = convert(
101                                    targetPage, viewPageURL, editPageURL, attachmentURLPrefix);
102                    }
103    
104                    return DiffHtmlUtil.diff(
105                            new UnsyncStringReader(sourceContent),
106                            new UnsyncStringReader(targetContent));
107            }
108    
109            public static List<WikiPage> filterOrphans(List<WikiPage> pages)
110                    throws PortalException {
111    
112                    List<Map<String, Boolean>> pageTitles =
113                            new ArrayList<Map<String, Boolean>>();
114    
115                    for (WikiPage page : pages) {
116                            pageTitles.add(WikiCacheUtil.getOutgoingLinks(page));
117                    }
118    
119                    Set<WikiPage> notOrphans = new HashSet<WikiPage>();
120    
121                    for (WikiPage page : pages) {
122                            for (Map<String, Boolean> pageTitle : pageTitles) {
123                                    String pageTitleLowerCase = page.getTitle();
124    
125                                    if (pageTitle.get(pageTitleLowerCase) != null) {
126                                            notOrphans.add(page);
127    
128                                            break;
129                                    }
130                            }
131                    }
132    
133                    List<WikiPage> orphans = new ArrayList<WikiPage>();
134    
135                    for (WikiPage page : pages) {
136                            if (!notOrphans.contains(page)) {
137                                    orphans.add(page);
138                            }
139                    }
140    
141                    orphans = ListUtil.sort(orphans);
142    
143                    return orphans;
144            }
145    
146            public static String getAttachmentURLPrefix(
147                    String mainPath, long plid, long nodeId, String title) {
148    
149                    StringBundler sb = new StringBundler(8);
150    
151                    sb.append(mainPath);
152                    sb.append("/wiki/get_page_attachment?p_l_id=");
153                    sb.append(plid);
154                    sb.append("&nodeId=");
155                    sb.append(nodeId);
156                    sb.append("&title=");
157                    sb.append(HttpUtil.encodeURL(title));
158                    sb.append("&fileName=");
159    
160                    return sb.toString();
161            }
162    
163            public static String getEditPage(String format) {
164                    return _instance._getEditPage(format);
165            }
166    
167            public static String getEmailFromAddress(
168                            PortletPreferences preferences, long companyId)
169                    throws SystemException {
170    
171                    return PortalUtil.getEmailFromAddress(
172                            preferences, companyId, PropsValues.WIKI_EMAIL_FROM_ADDRESS);
173            }
174    
175            public static String getEmailFromName(
176                            PortletPreferences preferences, long companyId)
177                    throws SystemException {
178    
179                    return PortalUtil.getEmailFromName(
180                            preferences, companyId, PropsValues.WIKI_EMAIL_FROM_NAME);
181            }
182    
183            public static String getEmailPageAddedBody(PortletPreferences preferences) {
184                    String emailPageAddedBody = preferences.getValue(
185                            "emailPageAddedBody", StringPool.BLANK);
186    
187                    if (Validator.isNotNull(emailPageAddedBody)) {
188                            return emailPageAddedBody;
189                    }
190                    else {
191                            return ContentUtil.get(
192                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_BODY));
193                    }
194            }
195    
196            public static boolean getEmailPageAddedEnabled(
197                    PortletPreferences preferences) {
198    
199                    String emailPageAddedEnabled = preferences.getValue(
200                            "emailPageAddedEnabled", StringPool.BLANK);
201    
202                    if (Validator.isNotNull(emailPageAddedEnabled)) {
203                            return GetterUtil.getBoolean(emailPageAddedEnabled);
204                    }
205                    else {
206                            return GetterUtil.getBoolean(
207                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_ENABLED));
208                    }
209            }
210    
211            public static String getEmailPageAddedSignature(
212                    PortletPreferences preferences) {
213    
214                    String emailPageAddedSignature = preferences.getValue(
215                            "emailPageAddedSignature", StringPool.BLANK);
216    
217                    if (Validator.isNotNull(emailPageAddedSignature)) {
218                            return emailPageAddedSignature;
219                    }
220                    else {
221                            return ContentUtil.get(
222                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_SIGNATURE));
223                    }
224            }
225    
226            public static String getEmailPageAddedSubjectPrefix(
227                    PortletPreferences preferences) {
228    
229                    String emailPageAddedSubjectPrefix = preferences.getValue(
230                            "emailPageAddedSubjectPrefix", StringPool.BLANK);
231    
232                    if (Validator.isNotNull(emailPageAddedSubjectPrefix)) {
233                            return emailPageAddedSubjectPrefix;
234                    }
235                    else {
236                            return ContentUtil.get(
237                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_SUBJECT_PREFIX));
238                    }
239            }
240    
241            public static String getEmailPageUpdatedBody(
242                    PortletPreferences preferences) {
243    
244                    String emailPageUpdatedBody = preferences.getValue(
245                            "emailPageUpdatedBody", StringPool.BLANK);
246    
247                    if (Validator.isNotNull(emailPageUpdatedBody)) {
248                            return emailPageUpdatedBody;
249                    }
250                    else {
251                            return ContentUtil.get(
252                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_BODY));
253                    }
254            }
255    
256            public static boolean getEmailPageUpdatedEnabled(
257                    PortletPreferences preferences) {
258    
259                    String emailPageUpdatedEnabled = preferences.getValue(
260                            "emailPageUpdatedEnabled", StringPool.BLANK);
261    
262                    if (Validator.isNotNull(emailPageUpdatedEnabled)) {
263                            return GetterUtil.getBoolean(emailPageUpdatedEnabled);
264                    }
265                    else {
266                            return GetterUtil.getBoolean(
267                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_ENABLED));
268                    }
269            }
270    
271            public static String getEmailPageUpdatedSignature(
272                    PortletPreferences preferences) {
273    
274                    String emailPageUpdatedSignature = preferences.getValue(
275                            "emailPageUpdatedSignature", StringPool.BLANK);
276    
277                    if (Validator.isNotNull(emailPageUpdatedSignature)) {
278                            return emailPageUpdatedSignature;
279                    }
280                    else {
281                            return ContentUtil.get(
282                                    PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_SIGNATURE));
283                    }
284            }
285    
286            public static String getEmailPageUpdatedSubjectPrefix(
287                    PortletPreferences preferences) {
288    
289                    String emailPageUpdatedSubject = preferences.getValue(
290                            "emailPageUpdatedSubjectPrefix", StringPool.BLANK);
291    
292                    if (Validator.isNotNull(emailPageUpdatedSubject)) {
293                            return emailPageUpdatedSubject;
294                    }
295                    else {
296                            return ContentUtil.get(
297                                    PropsUtil.get(
298                                            PropsKeys.WIKI_EMAIL_PAGE_UPDATED_SUBJECT_PREFIX));
299                    }
300            }
301    
302            public static WikiNode getFirstNode(PortletRequest portletRequest)
303                    throws PortalException, SystemException {
304    
305                    ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
306                            WebKeys.THEME_DISPLAY);
307                    long groupId = themeDisplay.getScopeGroupId();
308                    PermissionChecker permissionChecker =
309                            themeDisplay.getPermissionChecker();
310    
311                    List<WikiNode> nodes = WikiNodeLocalServiceUtil.getNodes(groupId);
312    
313                    PortletPreferences preferences = portletRequest.getPreferences();
314                    String[] visibleNodeNames = StringUtil.split(
315                            preferences.getValue("visibleNodes", null));
316                    nodes = orderNodes(nodes, visibleNodeNames);
317    
318                    String[] hiddenNodes = StringUtil.split(
319                            preferences.getValue("hiddenNodes", StringPool.BLANK));
320                    Arrays.sort(hiddenNodes);
321    
322                    for (WikiNode node : nodes) {
323                            if ((Arrays.binarySearch(hiddenNodes, node.getName()) < 0) &&
324                                    WikiNodePermission.contains(
325                                            permissionChecker, node, ActionKeys.VIEW)) {
326    
327                                    return node;
328                            }
329                    }
330    
331                    return null;
332            }
333    
334            public static String getHelpPage(String format) {
335                    return _instance._getHelpPage(format);
336            }
337    
338            public static String getHelpURL(String format) {
339                    return _instance._getHelpURL(format);
340            }
341    
342            public static Map<String, Boolean> getLinks(WikiPage page)
343                    throws PageContentException {
344    
345                    return _instance._getLinks(page);
346            }
347    
348            public static List<String> getNodeNames(List<WikiNode> nodes) {
349                    List<String> nodeNames = new ArrayList<String>(nodes.size());
350    
351                    for (WikiNode node : nodes) {
352                            nodeNames.add(node.getName());
353                    }
354    
355                    return nodeNames;
356            }
357    
358            public static List<WikiNode> getNodes(
359                    List<WikiNode> nodes, String[] hiddenNodes,
360                    PermissionChecker permissionChecker) {
361    
362                    nodes = ListUtil.copy(nodes);
363    
364                    Arrays.sort(hiddenNodes);
365    
366                    Iterator<WikiNode> itr = nodes.iterator();
367    
368                    while (itr.hasNext()) {
369                            WikiNode node = itr.next();
370    
371                            if (!(Arrays.binarySearch(hiddenNodes, node.getName()) < 0) ||
372                                    !WikiNodePermission.contains(
373                                            permissionChecker, node, ActionKeys.VIEW)) {
374    
375                                    itr.remove();
376                            }
377                    }
378    
379                    return nodes;
380            }
381    
382            public static OrderByComparator getPageOrderByComparator(
383                    String orderByCol, String orderByType) {
384    
385                    boolean orderByAsc = false;
386    
387                    if (orderByType.equals("asc")) {
388                            orderByAsc = true;
389                    }
390    
391                    OrderByComparator orderByComparator = null;
392    
393                    if (orderByCol.equals("modifiedDate")) {
394                            orderByComparator = new PageCreateDateComparator(orderByAsc);
395                    }
396                    else if (orderByCol.equals("title")) {
397                            orderByComparator = new PageTitleComparator(orderByAsc);
398                    }
399                    else if (orderByCol.equals("version")) {
400                            orderByComparator = new PageVersionComparator(orderByAsc);
401                    }
402    
403                    return orderByComparator;
404            }
405    
406            public static List<WikiNode> orderNodes(
407                    List<WikiNode> nodes, String[] visibleNodeNames) {
408    
409                    if ((visibleNodeNames == null) || (visibleNodeNames.length == 0)) {
410                            return nodes;
411                    }
412    
413                    nodes = ListUtil.copy(nodes);
414    
415                    List<WikiNode> orderedNodes = new ArrayList<WikiNode>(nodes.size());
416    
417                    for (String visibleNodeName : visibleNodeNames) {
418                            for (WikiNode node : nodes) {
419                                    if (node.getName().equals(visibleNodeName)) {
420                                            orderedNodes.add(node);
421    
422                                            nodes.remove(node);
423    
424                                            break;
425                                    }
426                            }
427                    }
428    
429                    orderedNodes.addAll(nodes);
430    
431                    return orderedNodes;
432            }
433    
434            public static String processContent(String content) {
435                    content = content.replaceAll("</p>", "</p>\n");
436                    content = content.replaceAll("</br>", "</br>\n");
437                    content = content.replaceAll("</div>", "</div>\n");
438    
439                    return content;
440            }
441    
442            public static boolean validate(long nodeId, String content, String format)
443                    throws WikiFormatException {
444    
445                    return _instance._validate(nodeId, content, format);
446            }
447    
448            private String _convert(
449                            WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
450                            String attachmentURLPrefix)
451                    throws PageContentException, WikiFormatException {
452    
453                    LiferayPortletURL liferayViewPageURL = (LiferayPortletURL)viewPageURL;
454                    LiferayPortletURL liferayEditPageURL = (LiferayPortletURL)editPageURL;
455    
456                    WikiEngine engine = _getEngine(page.getFormat());
457    
458                    String content = engine.convert(
459                            page, viewPageURL, editPageURL, attachmentURLPrefix);
460    
461                    String editPageURLString = StringPool.BLANK;
462    
463                    if (editPageURL != null) {
464                            liferayEditPageURL.setParameter("title", "__REPLACEMENT__", false);
465    
466                            editPageURLString = editPageURL.toString();
467    
468                            editPageURLString = StringUtil.replace(
469                                    editPageURLString, "__REPLACEMENT__", "$1");
470                    }
471    
472                    Matcher matcher = _editPageURLPattern.matcher(content);
473    
474                    content = _convertURLs(editPageURLString, matcher);
475    
476                    String viewPageURLString = StringPool.BLANK;
477    
478                    if (viewPageURL != null) {
479                            liferayViewPageURL.setParameter("title", "__REPLACEMENT__", false);
480    
481                            viewPageURLString = viewPageURL.toString();
482    
483                            viewPageURLString = StringUtil.replace(
484                                    viewPageURLString, "__REPLACEMENT__", "$1");
485                    }
486    
487                    matcher = _viewPageURLPattern.matcher(content);
488    
489                    content = _convertURLs(viewPageURLString, matcher);
490    
491                    content = _replaceAttachments(
492                            content, page.getTitle(), attachmentURLPrefix);
493    
494                    return content;
495            }
496    
497            private String _convertURLs(String url, Matcher matcher) {
498                    StringBuffer sb = new StringBuffer();
499    
500                    while (matcher.find()) {
501                            String replacement = null;
502    
503                            if (matcher.groupCount() >= 1) {
504                                    String encodedTitle = HttpUtil.encodeURL(
505                                            HtmlUtil.unescape(matcher.group(1)));
506    
507                                    replacement = url.replace("$1", encodedTitle);
508                            }
509                            else {
510                                    replacement = url;
511                            }
512    
513                            matcher.appendReplacement(sb, replacement);
514                    }
515    
516                    return matcher.appendTail(sb).toString();
517            }
518    
519            private String _getEditPage(String format) {
520                    return PropsUtil.get(
521                            PropsKeys.WIKI_FORMATS_EDIT_PAGE, new Filter(format));
522            }
523    
524            private WikiEngine _getEngine(String format) throws WikiFormatException {
525                    WikiEngine engine = _engines.get(format);
526    
527                    if (engine != null) {
528                            return engine;
529                    }
530    
531                    synchronized (_engines) {
532                            engine = _engines.get(format);
533    
534                            if (engine != null) {
535                                    return engine;
536                            }
537    
538                            try {
539                                    String engineClass = PropsUtil.get(
540                                            PropsKeys.WIKI_FORMATS_ENGINE, new Filter(format));
541    
542                                    if (engineClass == null) {
543                                            throw new WikiFormatException(format);
544                                    }
545    
546                                    if (!InstancePool.contains(engineClass)) {
547                                            engine = (WikiEngine)InstancePool.get(engineClass);
548    
549                                            engine.setMainConfiguration(
550                                                    _readConfigurationFile(
551                                                            PropsKeys.WIKI_FORMATS_CONFIGURATION_MAIN, format));
552    
553                                            engine.setInterWikiConfiguration(
554                                                    _readConfigurationFile(
555                                                            PropsKeys.WIKI_FORMATS_CONFIGURATION_INTERWIKI,
556                                                            format));
557                                    }
558                                    else {
559                                            engine = (WikiEngine)InstancePool.get(engineClass);
560                                    }
561    
562                                    _engines.put(format, engine);
563    
564                                    return engine;
565                            }
566                            catch (Exception e) {
567                                    throw new WikiFormatException(e);
568                            }
569                    }
570            }
571    
572            private String _getHelpPage(String format) {
573                    return PropsUtil.get(
574                            PropsKeys.WIKI_FORMATS_HELP_PAGE, new Filter(format));
575            }
576    
577            private String _getHelpURL(String format) {
578                    return PropsUtil.get(
579                            PropsKeys.WIKI_FORMATS_HELP_URL, new Filter(format));
580            }
581    
582            private Map<String, Boolean> _getLinks(WikiPage page)
583                    throws PageContentException {
584    
585                    try {
586                            return _getEngine(page.getFormat()).getOutgoingLinks(page);
587                    }
588                    catch (WikiFormatException wfe) {
589                            return Collections.emptyMap();
590                    }
591            }
592    
593            private String _readConfigurationFile(String propertyName, String format)
594                    throws IOException {
595    
596                    ClassLoader classLoader = getClass().getClassLoader();
597    
598                    String configurationFile = PropsUtil.get(
599                            propertyName, new Filter(format));
600    
601                    if (Validator.isNotNull(configurationFile)) {
602                            return HttpUtil.URLtoString(
603                                    classLoader.getResource(configurationFile));
604                    }
605                    else {
606                            return StringPool.BLANK;
607                    }
608            }
609    
610            private String _replaceAttachments(
611                    String content, String title, String attachmentURLPrefix) {
612    
613                    content = StringUtil.replace(content, "[$WIKI_PAGE_NAME$]", title);
614    
615                    content = StringUtil.replace(
616                            content, "[$ATTACHMENT_URL_PREFIX$]", attachmentURLPrefix);
617    
618                    return content;
619            }
620    
621            private boolean _validate(long nodeId, String content, String format)
622                    throws WikiFormatException {
623    
624                    return _getEngine(format).validate(nodeId, content);
625            }
626    
627            private static WikiUtil _instance = new WikiUtil();
628    
629            private static Pattern _editPageURLPattern = Pattern.compile(
630                    "\\[\\$BEGIN_PAGE_TITLE_EDIT\\$\\](.*?)" +
631                            "\\[\\$END_PAGE_TITLE_EDIT\\$\\]");
632            private static Pattern _viewPageURLPattern = Pattern.compile(
633                    "\\[\\$BEGIN_PAGE_TITLE\\$\\](.*?)\\[\\$END_PAGE_TITLE\\$\\]");
634    
635            private Map<String, WikiEngine> _engines =
636                    new ConcurrentHashMap<String, WikiEngine>();
637    
638    }