001    /**
002     * Copyright (c) 2000-present 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.messageboards.util;
016    
017    import com.liferay.portal.kernel.dao.shard.ShardCallable;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.language.LanguageUtil;
020    import com.liferay.portal.kernel.log.Log;
021    import com.liferay.portal.kernel.log.LogFactoryUtil;
022    import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslatorUtil;
023    import com.liferay.portal.kernel.portlet.LiferayWindowState;
024    import com.liferay.portal.kernel.sanitizer.Sanitizer;
025    import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
026    import com.liferay.portal.kernel.search.Document;
027    import com.liferay.portal.kernel.search.Field;
028    import com.liferay.portal.kernel.search.Hits;
029    import com.liferay.portal.kernel.transaction.TransactionCommitCallbackRegistryUtil;
030    import com.liferay.portal.kernel.util.ArrayUtil;
031    import com.liferay.portal.kernel.util.CharPool;
032    import com.liferay.portal.kernel.util.ContentTypes;
033    import com.liferay.portal.kernel.util.GetterUtil;
034    import com.liferay.portal.kernel.util.HtmlUtil;
035    import com.liferay.portal.kernel.util.Http;
036    import com.liferay.portal.kernel.util.LocaleUtil;
037    import com.liferay.portal.kernel.util.ParamUtil;
038    import com.liferay.portal.kernel.util.PropsUtil;
039    import com.liferay.portal.kernel.util.StringBundler;
040    import com.liferay.portal.kernel.util.StringPool;
041    import com.liferay.portal.kernel.util.StringUtil;
042    import com.liferay.portal.kernel.util.Validator;
043    import com.liferay.portal.kernel.workflow.WorkflowConstants;
044    import com.liferay.portal.model.Company;
045    import com.liferay.portal.model.Group;
046    import com.liferay.portal.model.Organization;
047    import com.liferay.portal.model.ResourceConstants;
048    import com.liferay.portal.model.Role;
049    import com.liferay.portal.model.RoleConstants;
050    import com.liferay.portal.model.Subscription;
051    import com.liferay.portal.model.ThemeConstants;
052    import com.liferay.portal.model.UserGroup;
053    import com.liferay.portal.security.permission.ActionKeys;
054    import com.liferay.portal.security.permission.PermissionChecker;
055    import com.liferay.portal.security.permission.ResourceActionsUtil;
056    import com.liferay.portal.service.GroupLocalServiceUtil;
057    import com.liferay.portal.service.OrganizationLocalServiceUtil;
058    import com.liferay.portal.service.ResourcePermissionLocalServiceUtil;
059    import com.liferay.portal.service.RoleLocalServiceUtil;
060    import com.liferay.portal.service.ServiceContext;
061    import com.liferay.portal.service.SubscriptionLocalServiceUtil;
062    import com.liferay.portal.service.UserGroupLocalServiceUtil;
063    import com.liferay.portal.service.UserGroupRoleLocalServiceUtil;
064    import com.liferay.portal.service.UserLocalServiceUtil;
065    import com.liferay.portal.theme.PortletDisplay;
066    import com.liferay.portal.theme.ThemeDisplay;
067    import com.liferay.portal.util.PortalUtil;
068    import com.liferay.portal.util.PropsValues;
069    import com.liferay.portal.util.WebKeys;
070    import com.liferay.portlet.documentlibrary.model.DLFileEntry;
071    import com.liferay.portlet.documentlibrary.service.DLFileEntryLocalServiceUtil;
072    import com.liferay.portlet.messageboards.MBGroupServiceSettings;
073    import com.liferay.portlet.messageboards.model.MBBan;
074    import com.liferay.portlet.messageboards.model.MBCategory;
075    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
076    import com.liferay.portlet.messageboards.model.MBMessage;
077    import com.liferay.portlet.messageboards.model.MBStatsUser;
078    import com.liferay.portlet.messageboards.model.MBThread;
079    import com.liferay.portlet.messageboards.service.MBCategoryLocalServiceUtil;
080    import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;
081    import com.liferay.portlet.messageboards.service.MBThreadLocalServiceUtil;
082    import com.liferay.portlet.messageboards.service.permission.MBMessagePermission;
083    import com.liferay.util.mail.JavaMailUtil;
084    
085    import java.io.InputStream;
086    
087    import java.util.ArrayList;
088    import java.util.Calendar;
089    import java.util.Collections;
090    import java.util.Date;
091    import java.util.HashMap;
092    import java.util.HashSet;
093    import java.util.LinkedHashMap;
094    import java.util.List;
095    import java.util.Map;
096    import java.util.Set;
097    import java.util.concurrent.Callable;
098    
099    import javax.mail.BodyPart;
100    import javax.mail.Message;
101    import javax.mail.Part;
102    import javax.mail.internet.MimeMessage;
103    import javax.mail.internet.MimeMultipart;
104    
105    import javax.portlet.PortletRequest;
106    import javax.portlet.PortletURL;
107    import javax.portlet.RenderResponse;
108    
109    import javax.servlet.http.HttpServletRequest;
110    
111    /**
112     * @author Brian Wing Shun Chan
113     */
114    public class MBUtil {
115    
116            public static final String BB_CODE_EDITOR_WYSIWYG_IMPL_KEY =
117                    "editor.wysiwyg.portal-web.docroot.html.portlet.message_boards." +
118                            "edit_message.bb_code.jsp";
119    
120            public static final String EMOTICONS = "/emoticons";
121    
122            public static final String MESSAGE_POP_PORTLET_PREFIX = "mb_message.";
123    
124            public static void addPortletBreadcrumbEntries(
125                            long categoryId, HttpServletRequest request,
126                            RenderResponse renderResponse)
127                    throws Exception {
128    
129                    if ((categoryId == MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) ||
130                            (categoryId == MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
131    
132                            return;
133                    }
134    
135                    MBCategory category = MBCategoryLocalServiceUtil.getCategory(
136                            categoryId);
137    
138                    addPortletBreadcrumbEntries(category, request, renderResponse);
139            }
140    
141            public static void addPortletBreadcrumbEntries(
142                            MBCategory category, HttpServletRequest request,
143                            RenderResponse renderResponse)
144                    throws Exception {
145    
146                    String strutsAction = ParamUtil.getString(request, "struts_action");
147    
148                    PortletURL portletURL = renderResponse.createRenderURL();
149    
150                    if (strutsAction.equals("/message_boards/select_category") ||
151                            strutsAction.equals("/message_boards_admin/select_category")) {
152    
153                            ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
154                                    WebKeys.THEME_DISPLAY);
155    
156                            portletURL.setParameter(
157                                    "struts_action", "/message_boards/select_category");
158                            portletURL.setWindowState(LiferayWindowState.POP_UP);
159    
160                            PortalUtil.addPortletBreadcrumbEntry(
161                                    request, themeDisplay.translate("categories"),
162                                    portletURL.toString());
163                    }
164                    else {
165                            portletURL.setParameter("struts_action", "/message_boards/view");
166                    }
167    
168                    List<MBCategory> ancestorCategories = category.getAncestors();
169    
170                    Collections.reverse(ancestorCategories);
171    
172                    for (MBCategory curCategory : ancestorCategories) {
173                            portletURL.setParameter(
174                                    "mbCategoryId", String.valueOf(curCategory.getCategoryId()));
175    
176                            PortalUtil.addPortletBreadcrumbEntry(
177                                    request, curCategory.getName(), portletURL.toString());
178                    }
179    
180                    portletURL.setParameter(
181                            "mbCategoryId", String.valueOf(category.getCategoryId()));
182    
183                    PortalUtil.addPortletBreadcrumbEntry(
184                            request, category.getName(), portletURL.toString());
185            }
186    
187            public static void addPortletBreadcrumbEntries(
188                            MBMessage message, HttpServletRequest request,
189                            RenderResponse renderResponse)
190                    throws Exception {
191    
192                    if (message.getCategoryId() ==
193                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID) {
194    
195                            return;
196                    }
197    
198                    if (message.getCategoryId() !=
199                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
200    
201                            addPortletBreadcrumbEntries(
202                                    message.getCategory(), request, renderResponse);
203                    }
204    
205                    PortletURL portletURL = renderResponse.createRenderURL();
206    
207                    portletURL.setParameter(
208                            "struts_action", "/message_boards/view_message");
209                    portletURL.setParameter(
210                            "messageId", String.valueOf(message.getMessageId()));
211    
212                    PortalUtil.addPortletBreadcrumbEntry(
213                            request, message.getSubject(), portletURL.toString());
214            }
215    
216            public static void collectMultipartContent(
217                            MimeMultipart multipart, MBMailMessage collector)
218                    throws Exception {
219    
220                    for (int i = 0; i < multipart.getCount(); i++) {
221                            BodyPart part = multipart.getBodyPart(i);
222    
223                            collectPartContent(part, collector);
224                    }
225            }
226    
227            public static void collectPartContent(
228                            Part part, MBMailMessage mbMailMessage)
229                    throws Exception {
230    
231                    Object partContent = part.getContent();
232    
233                    String contentType = StringUtil.toLowerCase(part.getContentType());
234    
235                    if ((part.getDisposition() != null) &&
236                            StringUtil.equalsIgnoreCase(
237                                    part.getDisposition(), MimeMessage.ATTACHMENT)) {
238    
239                            if (_log.isDebugEnabled()) {
240                                    _log.debug("Processing attachment");
241                            }
242    
243                            byte[] bytes = null;
244    
245                            if (partContent instanceof String) {
246                                    bytes = ((String)partContent).getBytes();
247                            }
248                            else if (partContent instanceof InputStream) {
249                                    bytes = JavaMailUtil.getBytes(part);
250                            }
251    
252                            mbMailMessage.addBytes(part.getFileName(), bytes);
253                    }
254                    else {
255                            if (partContent instanceof MimeMultipart) {
256                                    MimeMultipart mimeMultipart = (MimeMultipart)partContent;
257    
258                                    collectMultipartContent(mimeMultipart, mbMailMessage);
259                            }
260                            else if (partContent instanceof String) {
261                                    Map<String, Object> options = new HashMap<>();
262    
263                                    options.put("emailPartToMBMessageBody", Boolean.TRUE);
264    
265                                    String messageBody = SanitizerUtil.sanitize(
266                                            0, 0, 0, MBMessage.class.getName(), 0, contentType,
267                                            Sanitizer.MODE_ALL, (String)partContent, options);
268    
269                                    if (contentType.startsWith(ContentTypes.TEXT_HTML)) {
270                                            mbMailMessage.setHtmlBody(messageBody);
271                                    }
272                                    else {
273                                            mbMailMessage.setPlainBody(messageBody);
274                                    }
275                            }
276                    }
277            }
278    
279            public static String getAbsolutePath(
280                            PortletRequest portletRequest, long mbCategoryId)
281                    throws PortalException {
282    
283                    ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
284                            WebKeys.THEME_DISPLAY);
285    
286                    if (mbCategoryId == MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
287                            return themeDisplay.translate("home");
288                    }
289    
290                    MBCategory mbCategory = MBCategoryLocalServiceUtil.fetchMBCategory(
291                            mbCategoryId);
292    
293                    List<MBCategory> categories = mbCategory.getAncestors();
294    
295                    Collections.reverse(categories);
296    
297                    StringBundler sb = new StringBundler((categories.size() * 3) + 5);
298    
299                    sb.append(themeDisplay.translate("home"));
300                    sb.append(StringPool.SPACE);
301    
302                    for (MBCategory curCategory : categories) {
303                            sb.append(StringPool.RAQUO_CHAR);
304                            sb.append(StringPool.SPACE);
305                            sb.append(curCategory.getName());
306                    }
307    
308                    sb.append(StringPool.RAQUO_CHAR);
309                    sb.append(StringPool.SPACE);
310                    sb.append(mbCategory.getName());
311    
312                    return sb.toString();
313            }
314    
315            public static String getBBCodeHTML(String msgBody, String pathThemeImages) {
316                    return StringUtil.replace(
317                            BBCodeTranslatorUtil.getHTML(msgBody),
318                            ThemeConstants.TOKEN_THEME_IMAGES_PATH + EMOTICONS,
319                            pathThemeImages + EMOTICONS);
320            }
321    
322            public static long getCategoryId(
323                    HttpServletRequest request, MBCategory category) {
324    
325                    long categoryId = MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID;
326    
327                    if (category != null) {
328                            categoryId = category.getCategoryId();
329                    }
330    
331                    categoryId = ParamUtil.getLong(request, "mbCategoryId", categoryId);
332    
333                    return categoryId;
334            }
335    
336            public static long getCategoryId(
337                    HttpServletRequest request, MBMessage message) {
338    
339                    long categoryId = MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID;
340    
341                    if (message != null) {
342                            categoryId = message.getCategoryId();
343                    }
344    
345                    categoryId = ParamUtil.getLong(request, "mbCategoryId", categoryId);
346    
347                    return categoryId;
348            }
349    
350            public static long getCategoryId(String messageIdString) {
351                    String[] parts = _getMessageIdStringParts(messageIdString);
352    
353                    return GetterUtil.getLong(parts[0]);
354            }
355    
356            public static Set<Long> getCategorySubscriptionClassPKs(long userId) {
357                    List<Subscription> subscriptions =
358                            SubscriptionLocalServiceUtil.getUserSubscriptions(
359                                    userId, MBCategory.class.getName());
360    
361                    Set<Long> classPKs = new HashSet<>(subscriptions.size());
362    
363                    for (Subscription subscription : subscriptions) {
364                            classPKs.add(subscription.getClassPK());
365                    }
366    
367                    return classPKs;
368            }
369    
370            public static Map<String, String> getEmailDefinitionTerms(
371                    PortletRequest portletRequest, String emailFromAddress,
372                    String emailFromName) {
373    
374                    ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
375                            WebKeys.THEME_DISPLAY);
376    
377                    Map<String, String> definitionTerms = new LinkedHashMap<>();
378    
379                    definitionTerms.put(
380                            "[$CATEGORY_NAME$]",
381                            LanguageUtil.get(
382                                    themeDisplay.getLocale(),
383                                    "the-category-in-which-the-message-has-been-posted"));
384                    definitionTerms.put(
385                            "[$COMPANY_ID$]",
386                            LanguageUtil.get(
387                                    themeDisplay.getLocale(),
388                                    "the-company-id-associated-with-the-message-board"));
389                    definitionTerms.put(
390                            "[$COMPANY_MX$]",
391                            LanguageUtil.get(
392                                    themeDisplay.getLocale(),
393                                    "the-company-mx-associated-with-the-message-board"));
394                    definitionTerms.put(
395                            "[$COMPANY_NAME$]",
396                            LanguageUtil.get(
397                                    themeDisplay.getLocale(),
398                                    "the-company-name-associated-with-the-message-board"));
399                    definitionTerms.put(
400                            "[$FROM_ADDRESS$]", HtmlUtil.escape(emailFromAddress));
401                    definitionTerms.put("[$FROM_NAME$]", HtmlUtil.escape(emailFromName));
402    
403                    if (PropsValues.POP_SERVER_NOTIFICATIONS_ENABLED) {
404                            definitionTerms.put(
405                                    "[$MAILING_LIST_ADDRESS$]",
406                                    LanguageUtil.get(
407                                            themeDisplay.getLocale(),
408                                            "the-email-address-of-the-mailing-list"));
409                    }
410    
411                    definitionTerms.put(
412                            "[$MESSAGE_BODY$]",
413                            LanguageUtil.get(themeDisplay.getLocale(), "the-message-body"));
414                    definitionTerms.put(
415                            "[$MESSAGE_ID$]",
416                            LanguageUtil.get(themeDisplay.getLocale(), "the-message-id"));
417                    definitionTerms.put(
418                            "[$MESSAGE_SUBJECT$]",
419                            LanguageUtil.get(themeDisplay.getLocale(), "the-message-subject"));
420                    definitionTerms.put(
421                            "[$MESSAGE_URL$]",
422                            LanguageUtil.get(themeDisplay.getLocale(), "the-message-url"));
423                    definitionTerms.put(
424                            "[$MESSAGE_USER_ADDRESS$]",
425                            LanguageUtil.get(
426                                    themeDisplay.getLocale(),
427                                    "the-email-address-of-the-user-who-added-the-message"));
428                    definitionTerms.put(
429                            "[$MESSAGE_USER_NAME$]",
430                            LanguageUtil.get(
431                                    themeDisplay.getLocale(), "the-user-who-added-the-message"));
432    
433                    Company company = themeDisplay.getCompany();
434    
435                    definitionTerms.put("[$PORTAL_URL$]", company.getVirtualHostname());
436    
437                    PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
438    
439                    definitionTerms.put(
440                            "[$PORTLET_NAME$]", HtmlUtil.escape(portletDisplay.getTitle()));
441    
442                    definitionTerms.put(
443                            "[$SITE_NAME$]",
444                            LanguageUtil.get(
445                                    themeDisplay.getLocale(),
446                                    "the-site-name-associated-with-the-message-board"));
447    
448                    if (!PropsValues.MESSAGE_BOARDS_EMAIL_BULK) {
449                            definitionTerms.put(
450                                    "[$TO_ADDRESS$]",
451                                    LanguageUtil.get(
452                                            themeDisplay.getLocale(),
453                                            "the-address-of-the-email-recipient"));
454                            definitionTerms.put(
455                                    "[$TO_NAME$]",
456                                    LanguageUtil.get(
457                                            themeDisplay.getLocale(),
458                                            "the-name-of-the-email-recipient"));
459                    }
460    
461                    return definitionTerms;
462            }
463    
464            public static Map<String, String> getEmailFromDefinitionTerms(
465                    PortletRequest portletRequest) {
466    
467                    ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
468                            WebKeys.THEME_DISPLAY);
469    
470                    Map<String, String> definitionTerms = new LinkedHashMap<>();
471    
472                    definitionTerms.put(
473                            "[$COMPANY_ID$]",
474                            LanguageUtil.get(
475                                    themeDisplay.getLocale(),
476                                    "the-company-id-associated-with-the-message-board"));
477                    definitionTerms.put(
478                            "[$COMPANY_MX$]",
479                            LanguageUtil.get(
480                                    themeDisplay.getLocale(),
481                                    "the-company-mx-associated-with-the-message-board"));
482                    definitionTerms.put(
483                            "[$COMPANY_NAME$]",
484                            LanguageUtil.get(
485                                    themeDisplay.getLocale(),
486                                    "the-company-name-associated-with-the-message-board"));
487    
488                    if (PropsValues.POP_SERVER_NOTIFICATIONS_ENABLED) {
489                            definitionTerms.put(
490                                    "[$MAILING_LIST_ADDRESS$]",
491                                    LanguageUtil.get(
492                                            themeDisplay.getLocale(),
493                                            "the-email-address-of-the-mailing-list"));
494                    }
495    
496                    definitionTerms.put(
497                            "[$MESSAGE_USER_ADDRESS$]",
498                            LanguageUtil.get(
499                                    themeDisplay.getLocale(),
500                                    "the-email-address-of-the-user-who-added-the-message"));
501                    definitionTerms.put(
502                            "[$MESSAGE_USER_NAME$]",
503                            LanguageUtil.get(
504                                    themeDisplay.getLocale(), "the-user-who-added-the-message"));
505    
506                    PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
507    
508                    definitionTerms.put(
509                            "[$PORTLET_NAME$]", HtmlUtil.escape(portletDisplay.getTitle()));
510    
511                    definitionTerms.put(
512                            "[$SITE_NAME$]",
513                            LanguageUtil.get(
514                                    themeDisplay.getLocale(),
515                                    "the-site-name-associated-with-the-message-board"));
516    
517                    return definitionTerms;
518            }
519    
520            public static List<Object> getEntries(Hits hits) {
521                    List<Object> entries = new ArrayList<>();
522    
523                    for (Document document : hits.getDocs()) {
524                            long categoryId = GetterUtil.getLong(
525                                    document.get(Field.CATEGORY_ID));
526    
527                            try {
528                                    MBCategoryLocalServiceUtil.getCategory(categoryId);
529                            }
530                            catch (Exception e) {
531                                    if (_log.isWarnEnabled()) {
532                                            _log.warn(
533                                                    "Message boards search index is stale and contains " +
534                                                            "category " + categoryId);
535                                    }
536    
537                                    continue;
538                            }
539    
540                            long threadId = GetterUtil.getLong(document.get("threadId"));
541    
542                            try {
543                                    MBThreadLocalServiceUtil.getThread(threadId);
544                            }
545                            catch (Exception e) {
546                                    if (_log.isWarnEnabled()) {
547                                            _log.warn(
548                                                    "Message boards search index is stale and contains " +
549                                                            "thread " + threadId);
550                                    }
551    
552                                    continue;
553                            }
554    
555                            String entryClassName = document.get(Field.ENTRY_CLASS_NAME);
556                            long entryClassPK = GetterUtil.getLong(
557                                    document.get(Field.ENTRY_CLASS_PK));
558    
559                            Object obj = null;
560    
561                            try {
562                                    if (entryClassName.equals(DLFileEntry.class.getName())) {
563                                            long classPK = GetterUtil.getLong(
564                                                    document.get(Field.CLASS_PK));
565    
566                                            MBMessageLocalServiceUtil.getMessage(classPK);
567    
568                                            obj = DLFileEntryLocalServiceUtil.getDLFileEntry(
569                                                    entryClassPK);
570                                    }
571                                    else if (entryClassName.equals(MBMessage.class.getName())) {
572                                            obj = MBMessageLocalServiceUtil.getMessage(entryClassPK);
573                                    }
574    
575                                    entries.add(obj);
576                            }
577                            catch (Exception e) {
578                                    if (_log.isWarnEnabled()) {
579                                            _log.warn(
580                                                    "Message boards search index is stale and contains " +
581                                                            "entry {className=" + entryClassName + ", " +
582                                                                    "classPK=" + entryClassPK + "}");
583                                    }
584    
585                                    continue;
586                            }
587                    }
588    
589                    return entries;
590            }
591    
592            public static long getMessageId(String messageIdString) {
593                    String[] parts = _getMessageIdStringParts(messageIdString);
594    
595                    return GetterUtil.getLong(parts[1]);
596            }
597    
598            public static int getMessageIdStringOffset() {
599                    if (PropsValues.POP_SERVER_SUBDOMAIN.length() == 0) {
600                            return 1;
601                    }
602    
603                    return 0;
604            }
605    
606            public static long getParentMessageId(Message message) throws Exception {
607                    long parentMessageId = -1;
608    
609                    String parentMessageIdString = getParentMessageIdString(message);
610    
611                    if (parentMessageIdString != null) {
612                            if (_log.isDebugEnabled()) {
613                                    _log.debug("Parent header " + parentMessageIdString);
614                            }
615    
616                            parentMessageId = getMessageId(parentMessageIdString);
617    
618                            if (_log.isDebugEnabled()) {
619                                    _log.debug("Parent message id " + parentMessageId);
620                            }
621                    }
622    
623                    return parentMessageId;
624            }
625    
626            public static String getParentMessageIdString(Message message)
627                    throws Exception {
628    
629                    // If the previous block failed, try to get the parent message ID from
630                    // the "References" header as explained in
631                    // http://cr.yp.to/immhf/thread.html. Some mail clients such as Yahoo!
632                    // Mail use the "In-Reply-To" header, so we check that as well.
633    
634                    String parentHeader = null;
635    
636                    String[] references = message.getHeader("References");
637    
638                    if (ArrayUtil.isNotEmpty(references)) {
639                            String reference = references[0];
640    
641                            int x = reference.lastIndexOf(
642                                    StringPool.LESS_THAN + MESSAGE_POP_PORTLET_PREFIX);
643    
644                            if (x > -1) {
645                                    int y = reference.indexOf(StringPool.GREATER_THAN, x);
646    
647                                    parentHeader = reference.substring(x, y + 1);
648                            }
649                    }
650    
651                    if (parentHeader == null) {
652                            String[] inReplyToHeaders = message.getHeader("In-Reply-To");
653    
654                            if (ArrayUtil.isNotEmpty(inReplyToHeaders)) {
655                                    parentHeader = inReplyToHeaders[0];
656                            }
657                    }
658    
659                    if (Validator.isNull(parentHeader) ||
660                            !parentHeader.startsWith(MESSAGE_POP_PORTLET_PREFIX, 1)) {
661    
662                            parentHeader = _getParentMessageIdFromSubject(message);
663                    }
664    
665                    return parentHeader;
666            }
667    
668            public static String getReplyToAddress(
669                    long categoryId, long messageId, String mx,
670                    String defaultMailingListAddress) {
671    
672                    if (PropsValues.POP_SERVER_SUBDOMAIN.length() <= 0) {
673                            return defaultMailingListAddress;
674                    }
675    
676                    StringBundler sb = new StringBundler(8);
677    
678                    sb.append(MESSAGE_POP_PORTLET_PREFIX);
679                    sb.append(categoryId);
680                    sb.append(StringPool.PERIOD);
681                    sb.append(messageId);
682                    sb.append(StringPool.AT);
683                    sb.append(PropsValues.POP_SERVER_SUBDOMAIN);
684                    sb.append(StringPool.PERIOD);
685                    sb.append(mx);
686    
687                    return sb.toString();
688            }
689    
690            public static String getSubjectForEmail(MBMessage message)
691                    throws Exception {
692    
693                    String subject = message.getSubject();
694    
695                    if (subject.startsWith("RE:")) {
696                            return subject;
697                    }
698                    else {
699                            return "RE: " + message.getSubject();
700                    }
701            }
702    
703            public static String getSubjectWithoutMessageId(Message message)
704                    throws Exception {
705    
706                    String subject = message.getSubject();
707    
708                    String parentMessageId = _getParentMessageIdFromSubject(message);
709    
710                    if (Validator.isNotNull(parentMessageId)) {
711                            int pos = subject.indexOf(parentMessageId);
712    
713                            if (pos != -1) {
714                                    subject = subject.substring(0, pos);
715                            }
716                    }
717    
718                    return subject;
719            }
720    
721            public static String[] getThreadPriority(
722                            MBGroupServiceSettings mbGroupServiceSettings, String languageId,
723                            double value, ThemeDisplay themeDisplay)
724                    throws Exception {
725    
726                    String[] priorities = mbGroupServiceSettings.getPriorities(languageId);
727    
728                    String[] priorityPair = _findThreadPriority(
729                            value, themeDisplay, priorities);
730    
731                    if (priorityPair == null) {
732                            String defaultLanguageId = LocaleUtil.toLanguageId(
733                                    LocaleUtil.getSiteDefault());
734    
735                            priorities = mbGroupServiceSettings.getPriorities(
736                                    defaultLanguageId);
737    
738                            priorityPair = _findThreadPriority(value, themeDisplay, priorities);
739                    }
740    
741                    return priorityPair;
742            }
743    
744            public static Set<Long> getThreadSubscriptionClassPKs(long userId) {
745                    List<Subscription> subscriptions =
746                            SubscriptionLocalServiceUtil.getUserSubscriptions(
747                                    userId, MBThread.class.getName());
748    
749                    Set<Long> classPKs = new HashSet<>(subscriptions.size());
750    
751                    for (Subscription subscription : subscriptions) {
752                            classPKs.add(subscription.getClassPK());
753                    }
754    
755                    return classPKs;
756            }
757    
758            public static Date getUnbanDate(MBBan ban, int expireInterval) {
759                    Date banDate = ban.getCreateDate();
760    
761                    Calendar cal = Calendar.getInstance();
762    
763                    cal.setTime(banDate);
764    
765                    cal.add(Calendar.DATE, expireInterval);
766    
767                    return cal.getTime();
768            }
769    
770            public static String getUserRank(
771                            MBGroupServiceSettings mbGroupServiceSettings, String languageId,
772                            int posts)
773                    throws Exception {
774    
775                    String rank = StringPool.BLANK;
776    
777                    String[] ranks = mbGroupServiceSettings.getRanks(languageId);
778    
779                    for (int i = 0; i < ranks.length; i++) {
780                            String[] kvp = StringUtil.split(ranks[i], CharPool.EQUAL);
781    
782                            String kvpName = kvp[0];
783                            int kvpPosts = GetterUtil.getInteger(kvp[1]);
784    
785                            if (posts >= kvpPosts) {
786                                    rank = kvpName;
787                            }
788                            else {
789                                    break;
790                            }
791                    }
792    
793                    return rank;
794            }
795    
796            public static String[] getUserRank(
797                            MBGroupServiceSettings mbGroupServiceSettings, String languageId,
798                            MBStatsUser statsUser)
799                    throws Exception {
800    
801                    String[] rank = {StringPool.BLANK, StringPool.BLANK};
802    
803                    int maxPosts = 0;
804    
805                    Group group = GroupLocalServiceUtil.getGroup(statsUser.getGroupId());
806    
807                    long companyId = group.getCompanyId();
808    
809                    String[] ranks = mbGroupServiceSettings.getRanks(languageId);
810    
811                    for (int i = 0; i < ranks.length; i++) {
812                            String[] kvp = StringUtil.split(ranks[i], CharPool.EQUAL);
813    
814                            String curRank = kvp[0];
815                            String curRankValue = kvp[1];
816    
817                            String[] curRankValueKvp = StringUtil.split(
818                                    curRankValue, CharPool.COLON);
819    
820                            if (curRankValueKvp.length <= 1) {
821                                    int posts = GetterUtil.getInteger(curRankValue);
822    
823                                    if ((posts <= statsUser.getMessageCount()) &&
824                                            (posts >= maxPosts)) {
825    
826                                            rank[0] = curRank;
827                                            maxPosts = posts;
828                                    }
829                            }
830                            else {
831                                    String entityType = curRankValueKvp[0];
832                                    String entityValue = curRankValueKvp[1];
833    
834                                    try {
835                                            if (_isEntityRank(
836                                                            companyId, statsUser, entityType, entityValue)) {
837    
838                                                    rank[1] = curRank;
839    
840                                                    break;
841                                            }
842                                    }
843                                    catch (Exception e) {
844                                            if (_log.isWarnEnabled()) {
845                                                    _log.warn(e);
846                                            }
847                                    }
848                            }
849                    }
850    
851                    return rank;
852            }
853    
854            public static boolean hasMailIdHeader(Message message) throws Exception {
855                    String[] messageIds = message.getHeader("Message-ID");
856    
857                    if (messageIds == null) {
858                            return false;
859                    }
860    
861                    for (String messageId : messageIds) {
862                            if (Validator.isNotNull(PropsValues.POP_SERVER_SUBDOMAIN) &&
863                                    messageId.contains(PropsValues.POP_SERVER_SUBDOMAIN)) {
864    
865                                    return true;
866                            }
867                    }
868    
869                    return false;
870            }
871    
872            public static boolean isValidMessageFormat(String messageFormat) {
873                    String editorName = PropsUtil.get(BB_CODE_EDITOR_WYSIWYG_IMPL_KEY);
874    
875                    if (messageFormat.equals("bbcode") &&
876                            !(editorName.equals("bbcode") ||
877                              editorName.equals("ckeditor_bbcode"))) {
878    
879                            return false;
880                    }
881    
882                    return true;
883            }
884    
885            public static boolean isViewableMessage(
886                            ThemeDisplay themeDisplay, MBMessage message)
887                    throws Exception {
888    
889                    return isViewableMessage(themeDisplay, message, message);
890            }
891    
892            public static boolean isViewableMessage(
893                            ThemeDisplay themeDisplay, MBMessage message,
894                            MBMessage parentMessage)
895                    throws Exception {
896    
897                    PermissionChecker permissionChecker =
898                            themeDisplay.getPermissionChecker();
899    
900                    if (!MBMessagePermission.contains(
901                                    permissionChecker, parentMessage, ActionKeys.VIEW)) {
902    
903                            return false;
904                    }
905    
906                    if ((message.getMessageId() != parentMessage.getMessageId()) &&
907                            !MBMessagePermission.contains(
908                                    permissionChecker, message, ActionKeys.VIEW)) {
909    
910                            return false;
911                    }
912    
913                    if (!message.isApproved() &&
914                            !Validator.equals(message.getUserId(), themeDisplay.getUserId()) &&
915                            !permissionChecker.isGroupAdmin(themeDisplay.getScopeGroupId())) {
916    
917                            return false;
918                    }
919    
920                    return true;
921            }
922    
923            public static void propagatePermissions(
924                            long companyId, long groupId, long parentMessageId,
925                            ServiceContext serviceContext)
926                    throws PortalException {
927    
928                    MBMessage parentMessage = MBMessageLocalServiceUtil.getMBMessage(
929                            parentMessageId);
930    
931                    Role defaultGroupRole = RoleLocalServiceUtil.getDefaultGroupRole(
932                            groupId);
933                    Role guestRole = RoleLocalServiceUtil.getRole(
934                            companyId, RoleConstants.GUEST);
935    
936                    List<String> actionIds = ResourceActionsUtil.getModelResourceActions(
937                            MBMessage.class.getName());
938    
939                    Map<Long, Set<String>> roleIdsToActionIds =
940                            ResourcePermissionLocalServiceUtil.
941                                    getAvailableResourcePermissionActionIds(
942                                            companyId, MBMessage.class.getName(),
943                                            ResourceConstants.SCOPE_INDIVIDUAL,
944                                            String.valueOf(parentMessage.getMessageId()), actionIds);
945    
946                    Set<String> defaultGroupActionIds = roleIdsToActionIds.get(
947                            defaultGroupRole.getRoleId());
948    
949                    if (defaultGroupActionIds == null) {
950                            serviceContext.setGroupPermissions(new String[] {});
951                    }
952                    else {
953                            serviceContext.setGroupPermissions(
954                                    defaultGroupActionIds.toArray(
955                                            new String[defaultGroupActionIds.size()]));
956                    }
957    
958                    Set<String> guestActionIds = roleIdsToActionIds.get(
959                            guestRole.getRoleId());
960    
961                    if (guestActionIds == null) {
962                            serviceContext.setGuestPermissions(new String[] {});
963                    }
964                    else {
965                            serviceContext.setGuestPermissions(
966                                    guestActionIds.toArray(new String[guestActionIds.size()]));
967                    }
968            }
969    
970            public static String replaceMessageBodyPaths(
971                    ThemeDisplay themeDisplay, String messageBody) {
972    
973                    return StringUtil.replace(
974                            messageBody,
975                            new String[] {
976                                    ThemeConstants.TOKEN_THEME_IMAGES_PATH, "href=\"/", "src=\"/"
977                            },
978                            new String[] {
979                                    themeDisplay.getPathThemeImages(),
980                                    "href=\"" + themeDisplay.getURLPortal() + "/",
981                                    "src=\"" + themeDisplay.getURLPortal() + "/"
982                            });
983            }
984    
985            public static void updateCategoryMessageCount(
986                    long companyId, final long categoryId) {
987    
988                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
989    
990                            @Override
991                            protected Void doCall() throws Exception {
992                                    MBCategory category =
993                                            MBCategoryLocalServiceUtil.fetchMBCategory(categoryId);
994    
995                                    if (category == null) {
996                                            return null;
997                                    }
998    
999                                    int messageCount = _getMessageCount(category);
1000    
1001                                    category.setMessageCount(messageCount);
1002    
1003                                    MBCategoryLocalServiceUtil.updateMBCategory(category);
1004    
1005                                    return null;
1006                            }
1007    
1008                    };
1009    
1010                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1011            }
1012    
1013            public static void updateCategoryStatistics(
1014                    long companyId, final long categoryId) {
1015    
1016                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1017    
1018                            @Override
1019                            protected Void doCall() throws Exception {
1020                                    MBCategory category =
1021                                            MBCategoryLocalServiceUtil.fetchMBCategory(categoryId);
1022    
1023                                    if (category == null) {
1024                                            return null;
1025                                    }
1026    
1027                                    int messageCount = _getMessageCount(category);
1028    
1029                                    int threadCount =
1030                                            MBThreadLocalServiceUtil.getCategoryThreadsCount(
1031                                                    category.getGroupId(), category.getCategoryId(),
1032                                                    WorkflowConstants.STATUS_APPROVED);
1033    
1034                                    category.setMessageCount(messageCount);
1035                                    category.setThreadCount(threadCount);
1036    
1037                                    MBCategoryLocalServiceUtil.updateMBCategory(category);
1038    
1039                                    return null;
1040                            }
1041    
1042                    };
1043    
1044                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1045            }
1046    
1047            public static void updateCategoryThreadCount(
1048                    long companyId, final long categoryId) {
1049    
1050                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1051    
1052                            @Override
1053                            protected Void doCall() throws Exception {
1054                                    MBCategory category =
1055                                            MBCategoryLocalServiceUtil.fetchMBCategory(categoryId);
1056    
1057                                    if (category == null) {
1058                                            return null;
1059                                    }
1060    
1061                                    int threadCount =
1062                                            MBThreadLocalServiceUtil.getCategoryThreadsCount(
1063                                                    category.getGroupId(), category.getCategoryId(),
1064                                                    WorkflowConstants.STATUS_APPROVED);
1065    
1066                                    category.setThreadCount(threadCount);
1067    
1068                                    MBCategoryLocalServiceUtil.updateMBCategory(category);
1069    
1070                                    return null;
1071                            }
1072    
1073                    };
1074    
1075                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1076            }
1077    
1078            public static void updateThreadMessageCount(
1079                    long companyId, final long threadId) {
1080    
1081                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1082    
1083                            @Override
1084                            protected Void doCall() throws Exception {
1085                                    MBThread thread = MBThreadLocalServiceUtil.fetchThread(
1086                                            threadId);
1087    
1088                                    if (thread == null) {
1089                                            return null;
1090                                    }
1091    
1092                                    int messageCount =
1093                                            MBMessageLocalServiceUtil.getThreadMessagesCount(
1094                                                    threadId, WorkflowConstants.STATUS_APPROVED);
1095    
1096                                    thread.setMessageCount(messageCount);
1097    
1098                                    MBThreadLocalServiceUtil.updateMBThread(thread);
1099    
1100                                    return null;
1101                            }
1102    
1103                    };
1104    
1105                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1106            }
1107    
1108            private static String[] _findThreadPriority(
1109                    double value, ThemeDisplay themeDisplay, String[] priorities) {
1110    
1111                    for (int i = 0; i < priorities.length; i++) {
1112                            String[] priority = StringUtil.split(
1113                                    priorities[i], StringPool.PIPE);
1114    
1115                            try {
1116                                    String priorityName = priority[0];
1117                                    String priorityImage = priority[1];
1118                                    double priorityValue = GetterUtil.getDouble(priority[2]);
1119    
1120                                    if (value == priorityValue) {
1121                                            if (!priorityImage.startsWith(Http.HTTP)) {
1122                                                    priorityImage =
1123                                                            themeDisplay.getPathThemeImages() + priorityImage;
1124                                            }
1125    
1126                                            return new String[] {priorityName, priorityImage};
1127                                    }
1128                            }
1129                            catch (Exception e) {
1130                                    _log.error("Unable to determine thread priority", e);
1131                            }
1132                    }
1133    
1134                    return null;
1135            }
1136    
1137            private static int _getMessageCount(MBCategory category) {
1138                    return MBMessageLocalServiceUtil.getCategoryMessagesCount(
1139                            category.getGroupId(), category.getCategoryId(),
1140                            WorkflowConstants.STATUS_APPROVED);
1141            }
1142    
1143            private static String[] _getMessageIdStringParts(String messageIdString) {
1144                    int pos = messageIdString.indexOf(CharPool.AT);
1145    
1146                    return StringUtil.split(
1147                            messageIdString.substring(
1148                                    MBUtil.MESSAGE_POP_PORTLET_PREFIX.length() +
1149                                            getMessageIdStringOffset(),
1150                                    pos),
1151                            CharPool.PERIOD);
1152            }
1153    
1154            private static String _getParentMessageIdFromSubject(Message message)
1155                    throws Exception {
1156    
1157                    if (message.getSubject() == null) {
1158                            return null;
1159                    }
1160    
1161                    String parentMessageId = null;
1162    
1163                    String subject = StringUtil.reverse(message.getSubject());
1164    
1165                    int pos = subject.indexOf(CharPool.LESS_THAN);
1166    
1167                    if (pos != -1) {
1168                            parentMessageId = StringUtil.reverse(subject.substring(0, pos + 1));
1169                    }
1170    
1171                    return parentMessageId;
1172            }
1173    
1174            private static boolean _isEntityRank(
1175                            long companyId, MBStatsUser statsUser, String entityType,
1176                            String entityValue)
1177                    throws Exception {
1178    
1179                    long groupId = statsUser.getGroupId();
1180                    long userId = statsUser.getUserId();
1181    
1182                    if (entityType.equals("organization-role") ||
1183                            entityType.equals("site-role")) {
1184    
1185                            Role role = RoleLocalServiceUtil.getRole(companyId, entityValue);
1186    
1187                            if (UserGroupRoleLocalServiceUtil.hasUserGroupRole(
1188                                            userId, groupId, role.getRoleId(), true)) {
1189    
1190                                    return true;
1191                            }
1192                    }
1193                    else if (entityType.equals("organization")) {
1194                            Organization organization =
1195                                    OrganizationLocalServiceUtil.getOrganization(
1196                                            companyId, entityValue);
1197    
1198                            if (OrganizationLocalServiceUtil.hasUserOrganization(
1199                                            userId, organization.getOrganizationId(), false, false)) {
1200    
1201                                    return true;
1202                            }
1203                    }
1204                    else if (entityType.equals("regular-role")) {
1205                            if (RoleLocalServiceUtil.hasUserRole(
1206                                            userId, companyId, entityValue, true)) {
1207    
1208                                    return true;
1209                            }
1210                    }
1211                    else if (entityType.equals("user-group")) {
1212                            UserGroup userGroup = UserGroupLocalServiceUtil.getUserGroup(
1213                                    companyId, entityValue);
1214    
1215                            if (UserLocalServiceUtil.hasUserGroupUser(
1216                                            userGroup.getUserGroupId(), userId)) {
1217    
1218                                    return true;
1219                            }
1220                    }
1221    
1222                    return false;
1223            }
1224    
1225            private static final Log _log = LogFactoryUtil.getLog(MBUtil.class);
1226    
1227    }