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