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(MBMessageConstants.MESSAGE_SUBJECT_PREFIX_RE)) {
694                            return subject;
695                    }
696                    else {
697                            return MBMessageConstants.MESSAGE_SUBJECT_PREFIX_RE +
698                                    message.getSubject();
699                    }
700            }
701    
702            public static String getSubjectWithoutMessageId(Message message)
703                    throws Exception {
704    
705                    String subject = message.getSubject();
706    
707                    String parentMessageId = _getParentMessageIdFromSubject(message);
708    
709                    if (Validator.isNotNull(parentMessageId)) {
710                            int pos = subject.indexOf(parentMessageId);
711    
712                            if (pos != -1) {
713                                    subject = subject.substring(0, pos);
714                            }
715                    }
716    
717                    return subject;
718            }
719    
720            public static String[] getThreadPriority(
721                            PortletPreferences preferences, String languageId, double value,
722                            ThemeDisplay themeDisplay)
723                    throws Exception {
724    
725                    String[] priorities = LocalizationUtil.getPreferencesValues(
726                            preferences, "priorities", 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 = LocalizationUtil.getPreferencesValues(
736                                    preferences, "priorities", defaultLanguageId);
737    
738                            priorityPair = _findThreadPriority(value, themeDisplay, priorities);
739                    }
740    
741                    return priorityPair;
742            }
743    
744            public static Set<Long> getThreadSubscriptionClassPKs(long userId)
745                    throws SystemException {
746    
747                    List<Subscription> subscriptions =
748                            SubscriptionLocalServiceUtil.getUserSubscriptions(
749                                    userId, MBThread.class.getName());
750    
751                    Set<Long> classPKs = new HashSet<Long>(subscriptions.size());
752    
753                    for (Subscription subscription : subscriptions) {
754                            classPKs.add(subscription.getClassPK());
755                    }
756    
757                    return classPKs;
758            }
759    
760            public static Date getUnbanDate(MBBan ban, int expireInterval) {
761                    Date banDate = ban.getCreateDate();
762    
763                    Calendar cal = Calendar.getInstance();
764    
765                    cal.setTime(banDate);
766    
767                    cal.add(Calendar.DATE, expireInterval);
768    
769                    return cal.getTime();
770            }
771    
772            public static String getUserRank(
773                            PortletPreferences preferences, String languageId, int posts)
774                    throws Exception {
775    
776                    String rank = StringPool.BLANK;
777    
778                    String[] ranks = LocalizationUtil.getPreferencesValues(
779                            preferences, "ranks", languageId);
780    
781                    for (int i = 0; i < ranks.length; i++) {
782                            String[] kvp = StringUtil.split(ranks[i], CharPool.EQUAL);
783    
784                            String kvpName = kvp[0];
785                            int kvpPosts = GetterUtil.getInteger(kvp[1]);
786    
787                            if (posts >= kvpPosts) {
788                                    rank = kvpName;
789                            }
790                            else {
791                                    break;
792                            }
793                    }
794    
795                    return rank;
796            }
797    
798            public static String[] getUserRank(
799                            PortletPreferences preferences, String languageId,
800                            MBStatsUser statsUser)
801                    throws Exception {
802    
803                    String[] rank = {StringPool.BLANK, StringPool.BLANK};
804    
805                    int maxPosts = 0;
806    
807                    Group group = GroupLocalServiceUtil.getGroup(statsUser.getGroupId());
808    
809                    long companyId = group.getCompanyId();
810    
811                    String[] ranks = LocalizationUtil.getPreferencesValues(
812                            preferences, "ranks", languageId);
813    
814                    for (int i = 0; i < ranks.length; i++) {
815                            String[] kvp = StringUtil.split(ranks[i], CharPool.EQUAL);
816    
817                            String curRank = kvp[0];
818                            String curRankValue = kvp[1];
819    
820                            String[] curRankValueKvp = StringUtil.split(
821                                    curRankValue, CharPool.COLON);
822    
823                            if (curRankValueKvp.length <= 1) {
824                                    int posts = GetterUtil.getInteger(curRankValue);
825    
826                                    if ((posts <= statsUser.getMessageCount()) &&
827                                            (posts >= maxPosts)) {
828    
829                                            rank[0] = curRank;
830                                            maxPosts = posts;
831                                    }
832                            }
833                            else {
834                                    String entityType = curRankValueKvp[0];
835                                    String entityValue = curRankValueKvp[1];
836    
837                                    try {
838                                            if (_isEntityRank(
839                                                            companyId, statsUser, entityType, entityValue)) {
840    
841                                                    rank[1] = curRank;
842    
843                                                    break;
844                                            }
845                                    }
846                                    catch (Exception e) {
847                                            if (_log.isWarnEnabled()) {
848                                                    _log.warn(e);
849                                            }
850                                    }
851                            }
852                    }
853    
854                    return rank;
855            }
856    
857            public static boolean hasMailIdHeader(Message message) throws Exception {
858                    String[] messageIds = message.getHeader("Message-ID");
859    
860                    if (messageIds == null) {
861                            return false;
862                    }
863    
864                    for (String messageId : messageIds) {
865                            if (Validator.isNotNull(PropsValues.POP_SERVER_SUBDOMAIN) &&
866                                    messageId.contains(PropsValues.POP_SERVER_SUBDOMAIN)) {
867    
868                                    return true;
869                            }
870                    }
871    
872                    return false;
873            }
874    
875            public static boolean isAllowAnonymousPosting(
876                    PortletPreferences preferences) {
877    
878                    return GetterUtil.getBoolean(
879                            preferences.getValue("allowAnonymousPosting", null),
880                            PropsValues.MESSAGE_BOARDS_ANONYMOUS_POSTING_ENABLED);
881            }
882    
883            public static boolean isValidMessageFormat(String messageFormat) {
884                    String editorImpl = PropsUtil.get(BB_CODE_EDITOR_WYSIWYG_IMPL_KEY);
885    
886                    if (messageFormat.equals("bbcode") &&
887                            !(editorImpl.equals("bbcode") ||
888                              editorImpl.equals("ckeditor_bbcode"))) {
889    
890                            return false;
891                    }
892    
893                    if (!ArrayUtil.contains(MBMessageConstants.FORMATS, messageFormat)) {
894                            return false;
895                    }
896    
897                    return true;
898            }
899    
900            public static boolean isViewableMessage(
901                            ThemeDisplay themeDisplay, MBMessage message)
902                    throws Exception {
903    
904                    return isViewableMessage(themeDisplay, message, message);
905            }
906    
907            public static boolean isViewableMessage(
908                            ThemeDisplay themeDisplay, MBMessage message,
909                            MBMessage parentMessage)
910                    throws Exception {
911    
912                    PermissionChecker permissionChecker =
913                            themeDisplay.getPermissionChecker();
914    
915                    if (!MBMessagePermission.contains(
916                                    permissionChecker, parentMessage, ActionKeys.VIEW)) {
917    
918                            return false;
919                    }
920    
921                    if ((message.getMessageId() != parentMessage.getMessageId()) &&
922                            !MBMessagePermission.contains(
923                                    permissionChecker, message, ActionKeys.VIEW)) {
924    
925                            return false;
926                    }
927    
928                    if (!message.isApproved() &&
929                            !Validator.equals(message.getUserId(), themeDisplay.getUserId()) &&
930                            !permissionChecker.isGroupAdmin(themeDisplay.getScopeGroupId())) {
931    
932                            return false;
933                    }
934    
935                    return true;
936            }
937    
938            public static void propagatePermissions(
939                            long companyId, long groupId, long parentMessageId,
940                            ServiceContext serviceContext)
941                    throws PortalException, SystemException {
942    
943                    MBMessage parentMessage = MBMessageLocalServiceUtil.getMBMessage(
944                            parentMessageId);
945    
946                    Role defaultGroupRole = RoleLocalServiceUtil.getDefaultGroupRole(
947                            groupId);
948                    Role guestRole = RoleLocalServiceUtil.getRole(
949                            companyId, RoleConstants.GUEST);
950    
951                    long[] roleIds = {defaultGroupRole.getRoleId(), guestRole.getRoleId()};
952    
953                    List<String> actionIds = ResourceActionsUtil.getModelResourceActions(
954                            MBMessage.class.getName());
955    
956                    Map<Long, Set<String>> roleIdsToActionIds =
957                            ResourcePermissionLocalServiceUtil.
958                                    getAvailableResourcePermissionActionIds(
959                                            companyId, MBMessage.class.getName(),
960                                            ResourceConstants.SCOPE_INDIVIDUAL,
961                                            String.valueOf(parentMessage.getMessageId()), roleIds,
962                                            actionIds);
963    
964                    Set<String> defaultGroupActionIds = roleIdsToActionIds.get(
965                            defaultGroupRole.getRoleId());
966    
967                    if (defaultGroupActionIds == null) {
968                            serviceContext.setGroupPermissions(new String[]{});
969                    }
970                    else {
971                            serviceContext.setGroupPermissions(
972                                    defaultGroupActionIds.toArray(
973                                            new String[defaultGroupActionIds.size()]));
974                    }
975    
976                    Set<String> guestActionIds = roleIdsToActionIds.get(
977                            guestRole.getRoleId());
978    
979                    if (guestActionIds == null) {
980                            serviceContext.setGuestPermissions(new String[]{});
981                    }
982                    else {
983                            serviceContext.setGuestPermissions(
984                                    guestActionIds.toArray(new String[guestActionIds.size()]));
985                    }
986            }
987    
988            public static String replaceMessageBodyPaths(
989                    ThemeDisplay themeDisplay, String messageBody) {
990    
991                    return StringUtil.replace(
992                            messageBody,
993                            new String[] {
994                                    "@theme_images_path@", "href=\"/", "src=\"/"
995                            },
996                            new String[] {
997                                    themeDisplay.getPathThemeImages(),
998                                    "href=\"" + themeDisplay.getURLPortal() + "/",
999                                    "src=\"" + themeDisplay.getURLPortal() + "/"
1000                            });
1001            }
1002    
1003            public static void updateCategoryMessageCount(
1004                    long companyId, final long categoryId) {
1005    
1006                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1007    
1008                            @Override
1009                            protected Void doCall() throws Exception {
1010                                    MBCategory category =
1011                                            MBCategoryLocalServiceUtil.fetchMBCategory(categoryId);
1012    
1013                                    if (category == null) {
1014                                            return null;
1015                                    }
1016    
1017                                    int messageCount = _getMessageCount(category);
1018    
1019                                    category.setMessageCount(messageCount);
1020    
1021                                    MBCategoryLocalServiceUtil.updateMBCategory(category);
1022    
1023                                    return null;
1024                            }
1025    
1026                    };
1027    
1028                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1029            }
1030    
1031            public static void updateCategoryStatistics(
1032                    long companyId, final long categoryId) {
1033    
1034                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1035    
1036                            @Override
1037                            protected Void doCall() throws Exception {
1038                                    MBCategory category =
1039                                            MBCategoryLocalServiceUtil.fetchMBCategory(categoryId);
1040    
1041                                    if (category == null) {
1042                                            return null;
1043                                    }
1044    
1045                                    int messageCount = _getMessageCount(category);
1046    
1047                                    int threadCount =
1048                                            MBThreadLocalServiceUtil.getCategoryThreadsCount(
1049                                                    category.getGroupId(), category.getCategoryId(),
1050                                                    WorkflowConstants.STATUS_APPROVED);
1051    
1052                                    category.setMessageCount(messageCount);
1053                                    category.setThreadCount(threadCount);
1054    
1055                                    MBCategoryLocalServiceUtil.updateMBCategory(category);
1056    
1057                                    return null;
1058                            }
1059    
1060                    };
1061    
1062                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1063            }
1064    
1065            public static void updateCategoryThreadCount(
1066                    long companyId, final long categoryId) {
1067    
1068                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1069    
1070                            @Override
1071                            protected Void doCall() throws Exception {
1072                                    MBCategory category =
1073                                            MBCategoryLocalServiceUtil.fetchMBCategory(categoryId);
1074    
1075                                    if (category == null) {
1076                                            return null;
1077                                    }
1078    
1079                                    int threadCount =
1080                                            MBThreadLocalServiceUtil.getCategoryThreadsCount(
1081                                                    category.getGroupId(), category.getCategoryId(),
1082                                                    WorkflowConstants.STATUS_APPROVED);
1083    
1084                                    category.setThreadCount(threadCount);
1085    
1086                                    MBCategoryLocalServiceUtil.updateMBCategory(category);
1087    
1088                                    return null;
1089                            }
1090    
1091                    };
1092    
1093                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1094            }
1095    
1096            public static void updateThreadMessageCount(
1097                    long companyId, final long threadId) {
1098    
1099                    Callable<Void> callable = new ShardCallable<Void>(companyId) {
1100    
1101                            @Override
1102                            protected Void doCall() throws Exception {
1103                                    MBThread thread = MBThreadLocalServiceUtil.fetchThread(
1104                                            threadId);
1105    
1106                                    if (thread == null) {
1107                                            return null;
1108                                    }
1109    
1110                                    int messageCount =
1111                                            MBMessageLocalServiceUtil.getThreadMessagesCount(
1112                                                    threadId, WorkflowConstants.STATUS_APPROVED);
1113    
1114                                    thread.setMessageCount(messageCount);
1115    
1116                                    MBThreadLocalServiceUtil.updateMBThread(thread);
1117    
1118                                    return null;
1119                            }
1120    
1121                    };
1122    
1123                    TransactionCommitCallbackRegistryUtil.registerCallback(callable);
1124            }
1125    
1126            private static String[] _findThreadPriority(
1127                    double value, ThemeDisplay themeDisplay, String[] priorities) {
1128    
1129                    for (int i = 0; i < priorities.length; i++) {
1130                            String[] priority = StringUtil.split(priorities[i]);
1131    
1132                            try {
1133                                    String priorityName = priority[0];
1134                                    String priorityImage = priority[1];
1135                                    double priorityValue = GetterUtil.getDouble(priority[2]);
1136    
1137                                    if (value == priorityValue) {
1138                                            if (!priorityImage.startsWith(Http.HTTP)) {
1139                                                    priorityImage =
1140                                                            themeDisplay.getPathThemeImages() + priorityImage;
1141                                            }
1142    
1143                                            return new String[] {priorityName, priorityImage};
1144                                    }
1145                            }
1146                            catch (Exception e) {
1147                                    _log.error("Unable to determine thread priority", e);
1148                            }
1149                    }
1150    
1151                    return null;
1152            }
1153    
1154            private static int _getMessageCount(MBCategory category)
1155                    throws SystemException {
1156    
1157                    return MBMessageLocalServiceUtil.getCategoryMessagesCount(
1158                            category.getGroupId(), category.getCategoryId(),
1159                            WorkflowConstants.STATUS_APPROVED);
1160            }
1161    
1162            private static String[] _getMessageIdStringParts(String messageIdString) {
1163                    int pos = messageIdString.indexOf(CharPool.AT);
1164    
1165                    return StringUtil.split(
1166                            messageIdString.substring(
1167                                    MBUtil.MESSAGE_POP_PORTLET_PREFIX.length() +
1168                                            getMessageIdStringOffset(),
1169                                    pos),
1170                            CharPool.PERIOD);
1171            }
1172    
1173            private static String _getParentMessageIdFromSubject(Message message)
1174                    throws Exception {
1175    
1176                    if (message.getSubject() == null) {
1177                            return null;
1178                    }
1179    
1180                    String parentMessageId = null;
1181    
1182                    String subject = StringUtil.reverse(message.getSubject());
1183    
1184                    int pos = subject.indexOf(CharPool.LESS_THAN);
1185    
1186                    if (pos != -1) {
1187                            parentMessageId = StringUtil.reverse(subject.substring(0, pos + 1));
1188                    }
1189    
1190                    return parentMessageId;
1191            }
1192    
1193            private static boolean _isEntityRank(
1194                            long companyId, MBStatsUser statsUser, String entityType,
1195                            String entityValue)
1196                    throws Exception {
1197    
1198                    long groupId = statsUser.getGroupId();
1199                    long userId = statsUser.getUserId();
1200    
1201                    if (entityType.equals("organization-role") ||
1202                            entityType.equals("site-role")) {
1203    
1204                            Role role = RoleLocalServiceUtil.getRole(companyId, entityValue);
1205    
1206                            if (UserGroupRoleLocalServiceUtil.hasUserGroupRole(
1207                                            userId, groupId, role.getRoleId(), true)) {
1208    
1209                                    return true;
1210                            }
1211                    }
1212                    else if (entityType.equals("organization")) {
1213                            Organization organization =
1214                                    OrganizationLocalServiceUtil.getOrganization(
1215                                            companyId, entityValue);
1216    
1217                            if (OrganizationLocalServiceUtil.hasUserOrganization(
1218                                            userId, organization.getOrganizationId(), false, false)) {
1219    
1220                                    return true;
1221                            }
1222                    }
1223                    else if (entityType.equals("regular-role")) {
1224                            if (RoleLocalServiceUtil.hasUserRole(
1225                                            userId, companyId, entityValue, true)) {
1226    
1227                                    return true;
1228                            }
1229                    }
1230                    else if (entityType.equals("user-group")) {
1231                            UserGroup userGroup = UserGroupLocalServiceUtil.getUserGroup(
1232                                    companyId, entityValue);
1233    
1234                            if (UserLocalServiceUtil.hasUserGroupUser(
1235                                            userGroup.getUserGroupId(), userId)) {
1236    
1237                                    return true;
1238                            }
1239                    }
1240    
1241                    return false;
1242            }
1243    
1244            private static Log _log = LogFactoryUtil.getLog(MBUtil.class);
1245    
1246    }