001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.messageboards.service.impl;
016    
017    import com.liferay.portal.kernel.comment.Comment;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.exception.SystemException;
020    import com.liferay.portal.kernel.json.JSONFactoryUtil;
021    import com.liferay.portal.kernel.json.JSONObject;
022    import com.liferay.portal.kernel.log.Log;
023    import com.liferay.portal.kernel.log.LogFactoryUtil;
024    import com.liferay.portal.kernel.notifications.UserNotificationDefinition;
025    import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslatorUtil;
026    import com.liferay.portal.kernel.portlet.PortletProvider;
027    import com.liferay.portal.kernel.portlet.PortletProviderUtil;
028    import com.liferay.portal.kernel.repository.model.FileEntry;
029    import com.liferay.portal.kernel.repository.model.Folder;
030    import com.liferay.portal.kernel.sanitizer.Sanitizer;
031    import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
032    import com.liferay.portal.kernel.search.Indexable;
033    import com.liferay.portal.kernel.search.IndexableType;
034    import com.liferay.portal.kernel.search.Indexer;
035    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
036    import com.liferay.portal.kernel.settings.LocalizedValuesMap;
037    import com.liferay.portal.kernel.social.SocialActivityManagerUtil;
038    import com.liferay.portal.kernel.systemevent.SystemEvent;
039    import com.liferay.portal.kernel.util.Constants;
040    import com.liferay.portal.kernel.util.ContentTypes;
041    import com.liferay.portal.kernel.util.GetterUtil;
042    import com.liferay.portal.kernel.util.ListUtil;
043    import com.liferay.portal.kernel.util.LocalizationUtil;
044    import com.liferay.portal.kernel.util.ObjectValuePair;
045    import com.liferay.portal.kernel.util.OrderByComparator;
046    import com.liferay.portal.kernel.util.ParamUtil;
047    import com.liferay.portal.kernel.util.PropsKeys;
048    import com.liferay.portal.kernel.util.StringPool;
049    import com.liferay.portal.kernel.util.StringUtil;
050    import com.liferay.portal.kernel.util.Validator;
051    import com.liferay.portal.kernel.util.WebKeys;
052    import com.liferay.portal.kernel.workflow.WorkflowConstants;
053    import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil;
054    import com.liferay.portal.kernel.workflow.WorkflowThreadLocal;
055    import com.liferay.portal.model.Company;
056    import com.liferay.portal.model.Group;
057    import com.liferay.portal.model.ModelHintsUtil;
058    import com.liferay.portal.model.ResourceConstants;
059    import com.liferay.portal.model.SystemEventConstants;
060    import com.liferay.portal.model.User;
061    import com.liferay.portal.portletfilerepository.PortletFileRepositoryUtil;
062    import com.liferay.portal.security.auth.PrincipalException;
063    import com.liferay.portal.security.permission.ActionKeys;
064    import com.liferay.portal.service.ServiceContext;
065    import com.liferay.portal.service.permission.ModelPermissions;
066    import com.liferay.portal.theme.ThemeDisplay;
067    import com.liferay.portal.util.LayoutURLUtil;
068    import com.liferay.portal.util.Portal;
069    import com.liferay.portal.util.PortalUtil;
070    import com.liferay.portal.util.PrefsPropsUtil;
071    import com.liferay.portal.util.PropsValues;
072    import com.liferay.portal.util.SubscriptionSender;
073    import com.liferay.portlet.asset.model.AssetEntry;
074    import com.liferay.portlet.asset.model.AssetLinkConstants;
075    import com.liferay.portlet.blogs.util.LinkbackProducerUtil;
076    import com.liferay.portlet.documentlibrary.model.DLFolderConstants;
077    import com.liferay.portlet.messageboards.DiscussionMaxCommentsException;
078    import com.liferay.portlet.messageboards.MBGroupServiceSettings;
079    import com.liferay.portlet.messageboards.MessageBodyException;
080    import com.liferay.portlet.messageboards.MessageSubjectException;
081    import com.liferay.portlet.messageboards.NoSuchThreadException;
082    import com.liferay.portlet.messageboards.RequiredMessageException;
083    import com.liferay.portlet.messageboards.constants.MBConstants;
084    import com.liferay.portlet.messageboards.model.MBCategory;
085    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
086    import com.liferay.portlet.messageboards.model.MBDiscussion;
087    import com.liferay.portlet.messageboards.model.MBMessage;
088    import com.liferay.portlet.messageboards.model.MBMessageConstants;
089    import com.liferay.portlet.messageboards.model.MBMessageDisplay;
090    import com.liferay.portlet.messageboards.model.MBThread;
091    import com.liferay.portlet.messageboards.model.MBThreadConstants;
092    import com.liferay.portlet.messageboards.model.impl.MBCategoryImpl;
093    import com.liferay.portlet.messageboards.model.impl.MBMessageDisplayImpl;
094    import com.liferay.portlet.messageboards.service.base.MBMessageLocalServiceBaseImpl;
095    import com.liferay.portlet.messageboards.service.permission.MBPermission;
096    import com.liferay.portlet.messageboards.social.MBActivityKeys;
097    import com.liferay.portlet.messageboards.util.MBSubscriptionSender;
098    import com.liferay.portlet.messageboards.util.MBUtil;
099    import com.liferay.portlet.messageboards.util.MailingListThreadLocal;
100    import com.liferay.portlet.messageboards.util.comparator.MessageCreateDateComparator;
101    import com.liferay.portlet.messageboards.util.comparator.MessageThreadComparator;
102    import com.liferay.portlet.messageboards.util.comparator.ThreadLastPostDateComparator;
103    import com.liferay.portlet.social.model.SocialActivityConstants;
104    import com.liferay.portlet.trash.util.TrashUtil;
105    
106    import java.io.File;
107    import java.io.FileInputStream;
108    import java.io.FileNotFoundException;
109    import java.io.InputStream;
110    import java.io.Serializable;
111    
112    import java.util.ArrayList;
113    import java.util.Collections;
114    import java.util.Comparator;
115    import java.util.Date;
116    import java.util.HashMap;
117    import java.util.List;
118    import java.util.Map;
119    
120    import javax.portlet.PortletRequest;
121    import javax.portlet.PortletURL;
122    
123    import javax.servlet.http.HttpServletRequest;
124    
125    import net.htmlparser.jericho.Source;
126    import net.htmlparser.jericho.StartTag;
127    
128    /**
129     * @author Brian Wing Shun Chan
130     * @author Raymond Aug??
131     * @author Mika Koivisto
132     * @author Jorge Ferrer
133     * @author Juan Fern??ndez
134     * @author Shuyang Zhou
135     */
136    public class MBMessageLocalServiceImpl extends MBMessageLocalServiceBaseImpl {
137    
138            @Override
139            public MBMessage addDiscussionMessage(
140                            long userId, String userName, long groupId, String className,
141                            long classPK, int workflowAction)
142                    throws PortalException {
143    
144                    long threadId = 0;
145                    long parentMessageId = MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID;
146                    String subject = String.valueOf(classPK);
147                    String body = subject;
148    
149                    ServiceContext serviceContext = new ServiceContext();
150    
151                    serviceContext.setWorkflowAction(workflowAction);
152    
153                    boolean workflowEnabled = WorkflowThreadLocal.isEnabled();
154    
155                    WorkflowThreadLocal.setEnabled(false);
156    
157                    try {
158                            return addDiscussionMessage(
159                                    userId, userName, groupId, className, classPK, threadId,
160                                    parentMessageId, subject, body, serviceContext);
161                    }
162                    finally {
163                            WorkflowThreadLocal.setEnabled(workflowEnabled);
164                    }
165            }
166    
167            @Override
168            public MBMessage addDiscussionMessage(
169                            long userId, String userName, long groupId, String className,
170                            long classPK, long threadId, long parentMessageId, String subject,
171                            String body, ServiceContext serviceContext)
172                    throws PortalException {
173    
174                    // Message
175    
176                    validateDiscussionMaxComments(className, classPK);
177    
178                    long categoryId = MBCategoryConstants.DISCUSSION_CATEGORY_ID;
179    
180                    if (Validator.isNull(subject)) {
181                            if (Validator.isNotNull(body)) {
182                                    int pos = Math.min(body.length(), 50);
183    
184                                    subject = body.substring(0, pos) + "...";
185                            }
186                            else {
187                                    throw new MessageBodyException();
188                            }
189                    }
190    
191                    List<ObjectValuePair<String, InputStream>> inputStreamOVPs =
192                            Collections.emptyList();
193                    boolean anonymous = false;
194                    double priority = 0.0;
195                    boolean allowPingbacks = false;
196    
197                    serviceContext.setAddGroupPermissions(true);
198                    serviceContext.setAddGuestPermissions(true);
199                    serviceContext.setAttribute("className", className);
200                    serviceContext.setAttribute("classPK", String.valueOf(classPK));
201    
202                    Date now = new Date();
203    
204                    if (serviceContext.getCreateDate() == null) {
205                            serviceContext.setCreateDate(now);
206                    }
207    
208                    if (serviceContext.getModifiedDate() == null) {
209                            serviceContext.setModifiedDate(now);
210                    }
211    
212                    MBMessage message = addMessage(
213                            userId, userName, groupId, categoryId, threadId, parentMessageId,
214                            subject, body, PropsValues.DISCUSSION_COMMENTS_FORMAT,
215                            inputStreamOVPs, anonymous, priority, allowPingbacks,
216                            serviceContext);
217    
218                    // Discussion
219    
220                    if (parentMessageId == MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) {
221                            long classNameId = classNameLocalService.getClassNameId(className);
222    
223                            MBDiscussion discussion = mbDiscussionPersistence.fetchByC_C(
224                                    classNameId, classPK);
225    
226                            if (discussion == null) {
227                                    mbDiscussionLocalService.addDiscussion(
228                                            userId, groupId, classNameId, classPK,
229                                            message.getThreadId(), serviceContext);
230                            }
231                    }
232    
233                    return message;
234            }
235    
236            @Override
237            public MBMessage addMessage(
238                            long userId, String userName, long groupId, long categoryId,
239                            long threadId, long parentMessageId, String subject, String body,
240                            String format,
241                            List<ObjectValuePair<String, InputStream>> inputStreamOVPs,
242                            boolean anonymous, double priority, boolean allowPingbacks,
243                            ServiceContext serviceContext)
244                    throws PortalException {
245    
246                    // Message
247    
248                    User user = userPersistence.findByPrimaryKey(userId);
249                    userName = user.isDefaultUser() ? userName : user.getFullName();
250                    subject = ModelHintsUtil.trimString(
251                            MBMessage.class.getName(), "subject", subject);
252    
253                    MBGroupServiceSettings mbGroupServiceSettings =
254                            MBGroupServiceSettings.getInstance(groupId);
255    
256                    if (mbGroupServiceSettings != null) {
257                            if (!mbGroupServiceSettings.isAllowAnonymousPosting()) {
258                                    if (anonymous || user.isDefaultUser()) {
259                                            throw new PrincipalException.MustHavePermission(
260                                                    userId, ActionKeys.ADD_MESSAGE);
261                                    }
262                            }
263                    }
264    
265                    if (user.isDefaultUser()) {
266                            anonymous = true;
267                    }
268    
269                    Date now = new Date();
270    
271                    long messageId = counterLocalService.increment();
272    
273                    Map<String, Object> options = new HashMap<>();
274    
275                    boolean discussion = false;
276    
277                    if (categoryId == MBCategoryConstants.DISCUSSION_CATEGORY_ID) {
278                            discussion = true;
279                    }
280    
281                    options.put("discussion", discussion);
282    
283                    body = SanitizerUtil.sanitize(
284                            user.getCompanyId(), groupId, userId, MBMessage.class.getName(),
285                            messageId, "text/" + format, Sanitizer.MODE_ALL, body, options);
286    
287                    validate(subject, body);
288    
289                    subject = getSubject(subject, body);
290                    body = getBody(subject, body);
291    
292                    MBMessage message = mbMessagePersistence.create(messageId);
293    
294                    message.setUuid(serviceContext.getUuid());
295                    message.setGroupId(groupId);
296                    message.setCompanyId(user.getCompanyId());
297                    message.setUserId(user.getUserId());
298                    message.setUserName(userName);
299                    message.setCreateDate(serviceContext.getCreateDate(now));
300                    message.setModifiedDate(serviceContext.getModifiedDate(now));
301    
302                    if (threadId > 0) {
303                            message.setThreadId(threadId);
304                    }
305    
306                    if (priority != MBThreadConstants.PRIORITY_NOT_GIVEN) {
307                            message.setPriority(priority);
308                    }
309    
310                    message.setAllowPingbacks(allowPingbacks);
311                    message.setStatus(WorkflowConstants.STATUS_DRAFT);
312                    message.setStatusByUserId(user.getUserId());
313                    message.setStatusByUserName(userName);
314                    message.setStatusDate(serviceContext.getModifiedDate(now));
315    
316                    // Thread
317    
318                    if (parentMessageId != MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) {
319                            MBMessage parentMessage = mbMessagePersistence.fetchByPrimaryKey(
320                                    parentMessageId);
321    
322                            if (parentMessage == null) {
323                                    parentMessageId = MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID;
324                            }
325                    }
326    
327                    MBThread thread = null;
328    
329                    if (threadId > 0) {
330                            thread = mbThreadPersistence.fetchByPrimaryKey(threadId);
331                    }
332    
333                    if (thread == null) {
334                            if (parentMessageId ==
335                                            MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) {
336    
337                                    thread = mbThreadLocalService.addThread(
338                                            categoryId, message, serviceContext);
339                            }
340                            else {
341                                    throw new NoSuchThreadException("{threadId=" + threadId + "}");
342                            }
343                    }
344    
345                    if ((priority != MBThreadConstants.PRIORITY_NOT_GIVEN) &&
346                            (thread.getPriority() != priority)) {
347    
348                            thread.setPriority(priority);
349    
350                            mbThreadPersistence.update(thread);
351    
352                            updatePriorities(thread.getThreadId(), priority);
353                    }
354    
355                    // Message
356    
357                    message.setCategoryId(categoryId);
358                    message.setThreadId(thread.getThreadId());
359                    message.setRootMessageId(thread.getRootMessageId());
360                    message.setParentMessageId(parentMessageId);
361                    message.setSubject(subject);
362                    message.setBody(body);
363                    message.setFormat(format);
364                    message.setAnonymous(anonymous);
365    
366                    if (message.isDiscussion()) {
367                            long classNameId = classNameLocalService.getClassNameId(
368                                    (String)serviceContext.getAttribute("className"));
369                            long classPK = ParamUtil.getLong(serviceContext, "classPK");
370    
371                            message.setClassNameId(classNameId);
372                            message.setClassPK(classPK);
373                    }
374    
375                    message.setExpandoBridgeAttributes(serviceContext);
376    
377                    mbMessagePersistence.update(message);
378    
379                    // Attachments
380    
381                    if (ListUtil.isNotEmpty(inputStreamOVPs)) {
382                            Folder folder = message.addAttachmentsFolder();
383    
384                            PortletFileRepositoryUtil.addPortletFileEntries(
385                                    message.getGroupId(), userId, MBMessage.class.getName(),
386                                    message.getMessageId(), MBConstants.SERVICE_NAME,
387                                    folder.getFolderId(), inputStreamOVPs);
388                    }
389    
390                    // Resources
391    
392                    if ((parentMessageId !=
393                                    MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) &&
394                            GetterUtil.getBoolean(
395                                    serviceContext.getAttribute("propagatePermissions"))) {
396    
397                            MBUtil.propagatePermissions(
398                                    message.getCompanyId(), groupId, parentMessageId,
399                                    serviceContext);
400                    }
401    
402                    if (!message.isDiscussion()) {
403                            if (user.isDefaultUser()) {
404                                    addMessageResources(message, true, true);
405                            }
406                            else if (serviceContext.isAddGroupPermissions() ||
407                                             serviceContext.isAddGuestPermissions()) {
408    
409                                    addMessageResources(
410                                            message, serviceContext.isAddGroupPermissions(),
411                                            serviceContext.isAddGuestPermissions());
412                            }
413                            else {
414                                    addMessageResources(
415                                            message, serviceContext.getModelPermissions());
416                            }
417                    }
418    
419                    // Asset
420    
421                    updateAsset(
422                            userId, message, serviceContext.getAssetCategoryIds(),
423                            serviceContext.getAssetTagNames(),
424                            serviceContext.getAssetLinkEntryIds(),
425                            serviceContext.isAssetEntryVisible());
426    
427                    // Workflow
428    
429                    startWorkflowInstance(userId, message, serviceContext);
430    
431                    return message;
432            }
433    
434            @Override
435            public MBMessage addMessage(
436                            long userId, String userName, long groupId, long categoryId,
437                            String subject, String body, ServiceContext serviceContext)
438                    throws PortalException {
439    
440                    List<ObjectValuePair<String, InputStream>> inputStreamOVPs =
441                            Collections.emptyList();
442    
443                    return addMessage(
444                            userId, userName, groupId, categoryId, 0, 0, subject, body,
445                            MBMessageConstants.DEFAULT_FORMAT, inputStreamOVPs, false, 0.0,
446                            false, serviceContext);
447            }
448    
449            @Override
450            public MBMessage addMessage(
451                            long userId, String userName, long groupId, long categoryId,
452                            String subject, String body, String format,
453                            List<ObjectValuePair<String, InputStream>> inputStreamOVPs,
454                            boolean anonymous, double priority, boolean allowPingbacks,
455                            ServiceContext serviceContext)
456                    throws PortalException {
457    
458                    long threadId = 0;
459                    long parentMessageId = MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID;
460    
461                    return addMessage(
462                            userId, userName, groupId, categoryId, threadId, parentMessageId,
463                            subject, body, format, inputStreamOVPs, anonymous, priority,
464                            allowPingbacks, serviceContext);
465            }
466    
467            @Override
468            public MBMessage addMessage(
469                            long userId, String userName, long groupId, long categoryId,
470                            String subject, String body, String format, String fileName,
471                            File file, boolean anonymous, double priority,
472                            boolean allowPingbacks, ServiceContext serviceContext)
473                    throws FileNotFoundException, PortalException {
474    
475                    List<ObjectValuePair<String, InputStream>> inputStreamOVPs =
476                            new ArrayList<>(1);
477    
478                    InputStream inputStream = new FileInputStream(file);
479    
480                    ObjectValuePair<String, InputStream> inputStreamOVP =
481                            new ObjectValuePair<>(fileName, inputStream);
482    
483                    inputStreamOVPs.add(inputStreamOVP);
484    
485                    return addMessage(
486                            userId, userName, groupId, categoryId, 0, 0, subject, body, format,
487                            inputStreamOVPs, anonymous, priority, allowPingbacks,
488                            serviceContext);
489            }
490    
491            /**
492             * @deprecated As of 7.0.0, replaced by {@link #addMessage(long, String,
493             *             long, long, String, String, ServiceContext)}
494             */
495            @Deprecated
496            @Override
497            public MBMessage addMessage(
498                            long userId, String userName, long categoryId, String subject,
499                            String body, ServiceContext serviceContext)
500                    throws PortalException {
501    
502                    long groupId = serviceContext.getScopeGroupId();
503    
504                    if (categoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
505                            MBCategory category = mbCategoryPersistence.findByPrimaryKey(
506                                    categoryId);
507    
508                            groupId = category.getGroupId();
509                    }
510    
511                    return addMessage(
512                            userId, userName, groupId, categoryId, subject, body,
513                            serviceContext);
514            }
515    
516            @Override
517            public void addMessageAttachment(
518                            long userId, long messageId, String fileName, File file,
519                            String mimeType)
520                    throws PortalException {
521    
522                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
523    
524                    Folder folder = message.addAttachmentsFolder();
525    
526                    PortletFileRepositoryUtil.addPortletFileEntry(
527                            message.getGroupId(), userId, MBMessage.class.getName(),
528                            message.getMessageId(), MBConstants.SERVICE_NAME,
529                            folder.getFolderId(), file, fileName, mimeType, true);
530            }
531    
532            @Override
533            public void addMessageResources(
534                            long messageId, boolean addGroupPermissions,
535                            boolean addGuestPermissions)
536                    throws PortalException {
537    
538                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
539    
540                    addMessageResources(message, addGroupPermissions, addGuestPermissions);
541            }
542    
543            @Override
544            public void addMessageResources(
545                            long messageId, ModelPermissions modelPermissions)
546                    throws PortalException {
547    
548                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
549    
550                    addMessageResources(message, modelPermissions);
551            }
552    
553            @Override
554            public void addMessageResources(
555                            MBMessage message, boolean addGroupPermissions,
556                            boolean addGuestPermissions)
557                    throws PortalException {
558    
559                    resourceLocalService.addResources(
560                            message.getCompanyId(), message.getGroupId(), message.getUserId(),
561                            MBMessage.class.getName(), message.getMessageId(), false,
562                            addGroupPermissions, addGuestPermissions);
563            }
564    
565            @Override
566            public void addMessageResources(
567                            MBMessage message, ModelPermissions modelPermissions)
568                    throws PortalException {
569    
570                    resourceLocalService.addModelResources(
571                            message.getCompanyId(), message.getGroupId(), message.getUserId(),
572                            MBMessage.class.getName(), message.getMessageId(),
573                            modelPermissions);
574            }
575    
576            @Indexable(type = IndexableType.DELETE)
577            @Override
578            public MBMessage deleteDiscussionMessage(long messageId)
579                    throws PortalException {
580    
581                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
582    
583                    SocialActivityManagerUtil.deleteActivities(message);
584    
585                    return mbMessageLocalService.deleteMessage(messageId);
586            }
587    
588            @Override
589            public void deleteDiscussionMessages(String className, long classPK)
590                    throws PortalException {
591    
592                    long classNameId = classNameLocalService.getClassNameId(className);
593    
594                    MBDiscussion discussion = mbDiscussionPersistence.fetchByC_C(
595                            classNameId, classPK);
596    
597                    if (discussion == null) {
598                            if (_log.isInfoEnabled()) {
599                                    _log.info(
600                                            "Unable to delete discussion message for class name " +
601                                                    className + " and class PK " + classPK +
602                                                            " because it does not exist");
603                            }
604    
605                            return;
606                    }
607    
608                    List<MBMessage> messages = mbMessagePersistence.findByT_P(
609                            discussion.getThreadId(),
610                            MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID, 0, 1);
611    
612                    if (!messages.isEmpty()) {
613                            MBMessage message = messages.get(0);
614    
615                            SocialActivityManagerUtil.deleteActivities(message);
616    
617                            mbThreadLocalService.deleteThread(message.getThreadId());
618                    }
619    
620                    mbDiscussionPersistence.remove(discussion);
621            }
622    
623            @Indexable(type = IndexableType.DELETE)
624            @Override
625            public MBMessage deleteMessage(long messageId) throws PortalException {
626                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
627    
628                    return mbMessageLocalService.deleteMessage(message);
629            }
630    
631            @Indexable(type = IndexableType.DELETE)
632            @Override
633            @SystemEvent(type = SystemEventConstants.TYPE_DELETE)
634            public MBMessage deleteMessage(MBMessage message) throws PortalException {
635    
636                    // Attachments
637    
638                    long folderId = message.getAttachmentsFolderId();
639    
640                    if (folderId != DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
641                            PortletFileRepositoryUtil.deletePortletFolder(folderId);
642                    }
643    
644                    // Thread
645    
646                    int count = mbMessagePersistence.countByThreadId(message.getThreadId());
647    
648                    if (count == 1) {
649    
650                            // Attachments
651    
652                            long threadAttachmentsFolderId =
653                                    message.getThreadAttachmentsFolderId();
654    
655                            if (threadAttachmentsFolderId !=
656                                            DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
657    
658                                    PortletFileRepositoryUtil.deletePortletFolder(
659                                            threadAttachmentsFolderId);
660                            }
661    
662                            // Subscriptions
663    
664                            subscriptionLocalService.deleteSubscriptions(
665                                    message.getCompanyId(), MBThread.class.getName(),
666                                    message.getThreadId());
667    
668                            // Thread
669    
670                            MBThread thread = mbThreadPersistence.findByPrimaryKey(
671                                    message.getThreadId());
672    
673                            mbThreadPersistence.remove(thread);
674    
675                            // Category
676    
677                            if ((message.getCategoryId() !=
678                                            MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
679                                    (message.getCategoryId() !=
680                                            MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
681    
682                                    MBUtil.updateCategoryStatistics(message.getCategoryId());
683                            }
684    
685                            // Indexer
686    
687                            Indexer<MBThread> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
688                                    MBThread.class);
689    
690                            indexer.delete(thread);
691                    }
692                    else {
693                            MBThread thread = mbThreadPersistence.findByPrimaryKey(
694                                    message.getThreadId());
695    
696                            // Message is a root message
697    
698                            if (thread.getRootMessageId() == message.getMessageId()) {
699                                    List<MBMessage> childrenMessages =
700                                            mbMessagePersistence.findByT_P(
701                                                    message.getThreadId(), message.getMessageId());
702    
703                                    if (childrenMessages.size() > 1) {
704                                            throw new RequiredMessageException(
705                                                    String.valueOf(message.getMessageId()));
706                                    }
707                                    else if (childrenMessages.size() == 1) {
708                                            MBMessage childMessage = childrenMessages.get(0);
709    
710                                            childMessage.setRootMessageId(childMessage.getMessageId());
711                                            childMessage.setParentMessageId(
712                                                    MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID);
713    
714                                            mbMessagePersistence.update(childMessage);
715    
716                                            List<MBMessage> repliesMessages =
717                                                    mbMessagePersistence.findByThreadReplies(
718                                                            message.getThreadId());
719    
720                                            for (MBMessage repliesMessage : repliesMessages) {
721                                                    repliesMessage.setRootMessageId(
722                                                            childMessage.getMessageId());
723    
724                                                    mbMessagePersistence.update(repliesMessage);
725                                            }
726    
727                                            thread.setRootMessageId(childMessage.getMessageId());
728                                            thread.setRootMessageUserId(childMessage.getUserId());
729    
730                                            mbThreadPersistence.update(thread);
731                                    }
732                            }
733    
734                            // Message is a child message
735    
736                            else {
737                                    List<MBMessage> childrenMessages =
738                                            mbMessagePersistence.findByT_P(
739                                                    message.getThreadId(), message.getMessageId());
740    
741                                    // Message has children messages
742    
743                                    if (!childrenMessages.isEmpty()) {
744                                            for (MBMessage childMessage : childrenMessages) {
745                                                    childMessage.setParentMessageId(
746                                                            message.getParentMessageId());
747    
748                                                    mbMessagePersistence.update(childMessage);
749                                            }
750                                    }
751                                    else if (message.getStatus() ==
752                                                            WorkflowConstants.STATUS_APPROVED) {
753    
754                                            MessageCreateDateComparator comparator =
755                                                    new MessageCreateDateComparator(true);
756    
757                                            MBMessage[] prevAndNextMessages =
758                                                    mbMessagePersistence.findByT_S_PrevAndNext(
759                                                            message.getMessageId(), thread.getThreadId(),
760                                                            WorkflowConstants.STATUS_APPROVED, comparator);
761    
762                                            if (prevAndNextMessages[2] == null) {
763                                                    thread.setLastPostByUserId(
764                                                            prevAndNextMessages[0].getUserId());
765                                                    thread.setLastPostDate(
766                                                            prevAndNextMessages[0].getModifiedDate());
767    
768                                                    mbThreadPersistence.update(thread);
769                                            }
770                                    }
771                            }
772    
773                            // Thread
774    
775                            if (message.isApproved()) {
776                                    MBUtil.updateThreadMessageCount(thread.getThreadId());
777                            }
778    
779                            // Category
780    
781                            if ((message.getCategoryId() !=
782                                            MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
783                                    (message.getCategoryId() !=
784                                            MBCategoryConstants.DISCUSSION_CATEGORY_ID) &&
785                                    !message.isDraft()) {
786    
787                                    MBUtil.updateCategoryMessageCount(message.getCategoryId());
788                            }
789    
790                            // Indexer
791    
792                            Indexer<MBThread> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
793                                    MBThread.class);
794    
795                            indexer.reindex(thread);
796                    }
797    
798                    // Asset
799    
800                    assetEntryLocalService.deleteEntry(
801                            message.getWorkflowClassName(), message.getMessageId());
802    
803                    // Expando
804    
805                    expandoRowLocalService.deleteRows(message.getMessageId());
806    
807                    // Ratings
808    
809                    ratingsStatsLocalService.deleteStats(
810                            message.getWorkflowClassName(), message.getMessageId());
811    
812                    // Resources
813    
814                    if (!message.isDiscussion()) {
815                            resourceLocalService.deleteResource(
816                                    message.getCompanyId(), message.getWorkflowClassName(),
817                                    ResourceConstants.SCOPE_INDIVIDUAL, message.getMessageId());
818                    }
819    
820                    // Message
821    
822                    mbMessagePersistence.remove(message);
823    
824                    // Statistics
825    
826                    if (!message.isDiscussion()) {
827                            mbStatsUserLocalService.updateStatsUser(
828                                    message.getGroupId(), message.getUserId());
829                    }
830    
831                    // Workflow
832    
833                    workflowInstanceLinkLocalService.deleteWorkflowInstanceLinks(
834                            message.getCompanyId(), message.getGroupId(),
835                            message.getWorkflowClassName(), message.getMessageId());
836    
837                    return message;
838            }
839    
840            @Override
841            public void deleteMessageAttachment(long messageId, String fileName)
842                    throws PortalException {
843    
844                    MBMessage message = getMessage(messageId);
845    
846                    long folderId = message.getAttachmentsFolderId();
847    
848                    if (folderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
849                            return;
850                    }
851    
852                    PortletFileRepositoryUtil.deletePortletFileEntry(
853                            message.getGroupId(), folderId, fileName);
854            }
855    
856            @Override
857            public void deleteMessageAttachments(long messageId)
858                    throws PortalException {
859    
860                    MBMessage message = getMessage(messageId);
861    
862                    long folderId = message.getAttachmentsFolderId();
863    
864                    if (folderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
865                            return;
866                    }
867    
868                    PortletFileRepositoryUtil.deletePortletFileEntries(
869                            message.getGroupId(), folderId);
870            }
871    
872            @Override
873            public void emptyMessageAttachments(long messageId) throws PortalException {
874                    MBMessage message = getMessage(messageId);
875    
876                    long folderId = message.getAttachmentsFolderId();
877    
878                    if (folderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
879                            return;
880                    }
881    
882                    PortletFileRepositoryUtil.deletePortletFileEntries(
883                            message.getGroupId(), folderId, WorkflowConstants.STATUS_IN_TRASH);
884            }
885    
886            @Override
887            public List<MBMessage> getCategoryMessages(
888                    long groupId, long categoryId, int status, int start, int end) {
889    
890                    if (status == WorkflowConstants.STATUS_ANY) {
891                            return mbMessagePersistence.findByG_C(
892                                    groupId, categoryId, start, end);
893                    }
894                    else {
895                            return mbMessagePersistence.findByG_C_S(
896                                    groupId, categoryId, status, start, end);
897                    }
898            }
899    
900            @Override
901            public List<MBMessage> getCategoryMessages(
902                    long groupId, long categoryId, int status, int start, int end,
903                    OrderByComparator<MBMessage> obc) {
904    
905                    if (status == WorkflowConstants.STATUS_ANY) {
906                            return mbMessagePersistence.findByG_C(
907                                    groupId, categoryId, start, end, obc);
908                    }
909                    else {
910                            return mbMessagePersistence.findByG_C_S(
911                                    groupId, categoryId, status, start, end, obc);
912                    }
913            }
914    
915            @Override
916            public int getCategoryMessagesCount(
917                    long groupId, long categoryId, int status) {
918    
919                    if (status == WorkflowConstants.STATUS_ANY) {
920                            return mbMessagePersistence.countByG_C(groupId, categoryId);
921                    }
922                    else {
923                            return mbMessagePersistence.countByG_C_S(
924                                    groupId, categoryId, status);
925                    }
926            }
927    
928            @Override
929            public List<MBMessage> getCompanyMessages(
930                    long companyId, int status, int start, int end) {
931    
932                    if (status == WorkflowConstants.STATUS_ANY) {
933                            return mbMessagePersistence.findByCompanyId(companyId, start, end);
934                    }
935                    else {
936                            return mbMessagePersistence.findByC_S(
937                                    companyId, status, start, end);
938                    }
939            }
940    
941            @Override
942            public List<MBMessage> getCompanyMessages(
943                    long companyId, int status, int start, int end,
944                    OrderByComparator<MBMessage> obc) {
945    
946                    if (status == WorkflowConstants.STATUS_ANY) {
947                            return mbMessagePersistence.findByCompanyId(
948                                    companyId, start, end, obc);
949                    }
950                    else {
951                            return mbMessagePersistence.findByC_S(
952                                    companyId, status, start, end, obc);
953                    }
954            }
955    
956            @Override
957            public int getCompanyMessagesCount(long companyId, int status) {
958                    if (status == WorkflowConstants.STATUS_ANY) {
959                            return mbMessagePersistence.countByCompanyId(companyId);
960                    }
961                    else {
962                            return mbMessagePersistence.countByC_S(companyId, status);
963                    }
964            }
965    
966            @Override
967            public MBMessageDisplay getDiscussionMessageDisplay(
968                            long userId, long groupId, String className, long classPK,
969                            int status)
970                    throws PortalException {
971    
972                    return getDiscussionMessageDisplay(
973                            userId, groupId, className, classPK, status,
974                            new MessageThreadComparator());
975            }
976    
977            @Override
978            public MBMessageDisplay getDiscussionMessageDisplay(
979                            long userId, long groupId, String className, long classPK,
980                            int status, Comparator<MBMessage> comparator)
981                    throws PortalException {
982    
983                    long classNameId = classNameLocalService.getClassNameId(className);
984    
985                    MBMessage message = null;
986    
987                    MBDiscussion discussion = mbDiscussionPersistence.fetchByC_C(
988                            classNameId, classPK);
989    
990                    if (discussion != null) {
991                            message = mbMessagePersistence.findByT_P_First(
992                                    discussion.getThreadId(),
993                                    MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID, null);
994                    }
995                    else {
996                            boolean workflowEnabled = WorkflowThreadLocal.isEnabled();
997    
998                            WorkflowThreadLocal.setEnabled(false);
999    
1000                            try {
1001                                    String subject = String.valueOf(classPK);
1002                                    //String body = subject;
1003    
1004                                    message = addDiscussionMessage(
1005                                            userId, null, groupId, className, classPK, 0,
1006                                            MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID, subject,
1007                                            subject, new ServiceContext());
1008                            }
1009                            catch (SystemException se) {
1010                                    if (_log.isWarnEnabled()) {
1011                                            _log.warn(
1012                                                    "Add failed, fetch {threadId=0, parentMessageId=" +
1013                                                            MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID + "}");
1014                                    }
1015    
1016                                    message = mbMessagePersistence.fetchByT_P_First(
1017                                            0, MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID, null);
1018    
1019                                    if (message == null) {
1020                                            throw se;
1021                                    }
1022                            }
1023                            finally {
1024                                    WorkflowThreadLocal.setEnabled(workflowEnabled);
1025                            }
1026                    }
1027    
1028                    return getMessageDisplay(
1029                            userId, message, status, MBThreadConstants.THREAD_VIEW_COMBINATION,
1030                            false, comparator);
1031            }
1032    
1033            /**
1034             * @deprecated As of 7.0.0, replaced by {@link
1035             *             #getDiscussionMessageDisplay(long, long, String, long, int)}
1036             */
1037            @Deprecated
1038            @Override
1039            public MBMessageDisplay getDiscussionMessageDisplay(
1040                            long userId, long groupId, String className, long classPK,
1041                            int status, String threadView)
1042                    throws PortalException {
1043    
1044                    return getDiscussionMessageDisplay(
1045                            userId, groupId, className, classPK, status);
1046            }
1047    
1048            @Override
1049            public int getDiscussionMessagesCount(
1050                    long classNameId, long classPK, int status) {
1051    
1052                    MBDiscussion discussion = mbDiscussionPersistence.fetchByC_C(
1053                            classNameId, classPK);
1054    
1055                    if (discussion == null) {
1056                            return 0;
1057                    }
1058    
1059                    int count = 0;
1060    
1061                    if (status == WorkflowConstants.STATUS_ANY) {
1062                            count = mbMessagePersistence.countByThreadId(
1063                                    discussion.getThreadId());
1064                    }
1065                    else {
1066                            count = mbMessagePersistence.countByT_S(
1067                                    discussion.getThreadId(), status);
1068                    }
1069    
1070                    if (count >= 1) {
1071                            return count - 1;
1072                    }
1073                    else {
1074                            return 0;
1075                    }
1076            }
1077    
1078            @Override
1079            public int getDiscussionMessagesCount(
1080                    String className, long classPK, int status) {
1081    
1082                    long classNameId = classNameLocalService.getClassNameId(className);
1083    
1084                    return getDiscussionMessagesCount(classNameId, classPK, status);
1085            }
1086    
1087            @Override
1088            public List<MBDiscussion> getDiscussions(String className) {
1089                    long classNameId = classNameLocalService.getClassNameId(className);
1090    
1091                    return mbDiscussionPersistence.findByClassNameId(classNameId);
1092            }
1093    
1094            @Override
1095            public List<MBMessage> getGroupMessages(
1096                    long groupId, int status, int start, int end) {
1097    
1098                    if (status == WorkflowConstants.STATUS_ANY) {
1099                            return mbMessagePersistence.findByGroupId(groupId, start, end);
1100                    }
1101                    else {
1102                            return mbMessagePersistence.findByG_S(groupId, status, start, end);
1103                    }
1104            }
1105    
1106            @Override
1107            public List<MBMessage> getGroupMessages(
1108                    long groupId, int status, int start, int end,
1109                    OrderByComparator<MBMessage> obc) {
1110    
1111                    if (status == WorkflowConstants.STATUS_ANY) {
1112                            return mbMessagePersistence.findByGroupId(groupId, start, end, obc);
1113                    }
1114                    else {
1115                            return mbMessagePersistence.findByG_S(
1116                                    groupId, status, start, end, obc);
1117                    }
1118            }
1119    
1120            @Override
1121            public List<MBMessage> getGroupMessages(
1122                    long groupId, long userId, int status, int start, int end) {
1123    
1124                    if (status == WorkflowConstants.STATUS_ANY) {
1125                            return mbMessagePersistence.findByG_U(groupId, userId, start, end);
1126                    }
1127                    else {
1128                            return mbMessagePersistence.findByG_U_S(
1129                                    groupId, userId, status, start, end);
1130                    }
1131            }
1132    
1133            @Override
1134            public List<MBMessage> getGroupMessages(
1135                    long groupId, long userId, int status, int start, int end,
1136                    OrderByComparator<MBMessage> obc) {
1137    
1138                    if (status == WorkflowConstants.STATUS_ANY) {
1139                            return mbMessagePersistence.findByG_U(
1140                                    groupId, userId, start, end, obc);
1141                    }
1142                    else {
1143                            return mbMessagePersistence.findByG_U_S(
1144                                    groupId, userId, status, start, end, obc);
1145                    }
1146            }
1147    
1148            @Override
1149            public int getGroupMessagesCount(long groupId, int status) {
1150                    if (status == WorkflowConstants.STATUS_ANY) {
1151                            return mbMessagePersistence.countByGroupId(groupId);
1152                    }
1153                    else {
1154                            return mbMessagePersistence.countByG_S(groupId, status);
1155                    }
1156            }
1157    
1158            @Override
1159            public int getGroupMessagesCount(long groupId, long userId, int status) {
1160                    if (status == WorkflowConstants.STATUS_ANY) {
1161                            return mbMessagePersistence.countByG_U(groupId, userId);
1162                    }
1163                    else {
1164                            return mbMessagePersistence.countByG_U_S(groupId, userId, status);
1165                    }
1166            }
1167    
1168            @Override
1169            public MBMessage getMessage(long messageId) throws PortalException {
1170                    return mbMessagePersistence.findByPrimaryKey(messageId);
1171            }
1172    
1173            @Override
1174            public MBMessageDisplay getMessageDisplay(
1175                            long userId, long messageId, int status, String threadView,
1176                            boolean includePrevAndNext)
1177                    throws PortalException {
1178    
1179                    MBMessage message = getMessage(messageId);
1180    
1181                    return getMessageDisplay(
1182                            userId, message, status, threadView, includePrevAndNext);
1183            }
1184    
1185            @Override
1186            public MBMessageDisplay getMessageDisplay(
1187                            long userId, MBMessage message, int status, String threadView,
1188                            boolean includePrevAndNext)
1189                    throws PortalException {
1190    
1191                    return getMessageDisplay(
1192                            userId, message, status, threadView, includePrevAndNext,
1193                            new MessageThreadComparator());
1194            }
1195    
1196            @Override
1197            public MBMessageDisplay getMessageDisplay(
1198                            long userId, MBMessage message, int status, String threadView,
1199                            boolean includePrevAndNext, Comparator<MBMessage> comparator)
1200                    throws PortalException {
1201    
1202                    MBCategory category = null;
1203    
1204                    if ((message.getCategoryId() !=
1205                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
1206                            (message.getCategoryId() !=
1207                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
1208    
1209                            category = mbCategoryPersistence.findByPrimaryKey(
1210                                    message.getCategoryId());
1211                    }
1212                    else {
1213                            category = new MBCategoryImpl();
1214    
1215                            category.setCategoryId(message.getCategoryId());
1216                            category.setDisplayStyle(MBCategoryConstants.DEFAULT_DISPLAY_STYLE);
1217                    }
1218    
1219                    MBMessage parentMessage = null;
1220    
1221                    if (message.isReply()) {
1222                            parentMessage = mbMessagePersistence.findByPrimaryKey(
1223                                    message.getParentMessageId());
1224                    }
1225    
1226                    MBThread thread = mbThreadPersistence.findByPrimaryKey(
1227                            message.getThreadId());
1228    
1229                    if (message.isApproved() && !message.isDiscussion()) {
1230                            mbThreadLocalService.incrementViewCounter(thread.getThreadId(), 1);
1231    
1232                            SocialActivityManagerUtil.addActivity(
1233                                    userId, thread, SocialActivityConstants.TYPE_VIEW,
1234                                    StringPool.BLANK, 0);
1235                    }
1236    
1237                    MBThread previousThread = null;
1238                    MBThread nextThread = null;
1239    
1240                    if (message.isApproved() && includePrevAndNext) {
1241                            ThreadLastPostDateComparator threadLastPostDateComparator =
1242                                    new ThreadLastPostDateComparator(false);
1243    
1244                            MBThread[] prevAndNextThreads = null;
1245    
1246                            if (status == WorkflowConstants.STATUS_ANY) {
1247                                    prevAndNextThreads =
1248                                            mbThreadPersistence.filterFindByG_C_NotS_PrevAndNext(
1249                                                    message.getThreadId(), message.getGroupId(),
1250                                                    message.getCategoryId(),
1251                                                    WorkflowConstants.STATUS_IN_TRASH,
1252                                                    threadLastPostDateComparator);
1253                            }
1254                            else {
1255                                    prevAndNextThreads =
1256                                            mbThreadPersistence.filterFindByG_C_S_PrevAndNext(
1257                                                    message.getThreadId(), message.getGroupId(),
1258                                                    message.getCategoryId(), status,
1259                                                    threadLastPostDateComparator);
1260                            }
1261    
1262                            previousThread = prevAndNextThreads[0];
1263                            nextThread = prevAndNextThreads[2];
1264                    }
1265    
1266                    return new MBMessageDisplayImpl(
1267                            message, parentMessage, category, thread, previousThread,
1268                            nextThread, status, threadView, this, comparator);
1269            }
1270    
1271            @Override
1272            public List<MBMessage> getMessages(
1273                    String className, long classPK, int status) {
1274    
1275                    long classNameId = classNameLocalService.getClassNameId(className);
1276    
1277                    if (status == WorkflowConstants.STATUS_ANY) {
1278                            return mbMessagePersistence.findByC_C(classNameId, classPK);
1279                    }
1280                    else {
1281                            return mbMessagePersistence.findByC_C_S(
1282                                    classNameId, classPK, status);
1283                    }
1284            }
1285    
1286            @Override
1287            public List<MBMessage> getNoAssetMessages() {
1288                    return mbMessageFinder.findByNoAssets();
1289            }
1290    
1291            @Override
1292            public int getPositionInThread(long messageId) throws PortalException {
1293                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1294    
1295                    return mbMessageFinder.countByC_T(
1296                            message.getCreateDate(), message.getThreadId());
1297            }
1298    
1299            @Override
1300            public List<MBMessage> getThreadMessages(long threadId, int status) {
1301                    return getThreadMessages(
1302                            threadId, status, new MessageThreadComparator());
1303            }
1304    
1305            @Override
1306            public List<MBMessage> getThreadMessages(
1307                    long threadId, int status, Comparator<MBMessage> comparator) {
1308    
1309                    List<MBMessage> messages = null;
1310    
1311                    if (status == WorkflowConstants.STATUS_ANY) {
1312                            messages = mbMessagePersistence.findByThreadId(threadId);
1313                    }
1314                    else {
1315                            messages = mbMessagePersistence.findByT_S(threadId, status);
1316                    }
1317    
1318                    return ListUtil.sort(messages, comparator);
1319            }
1320    
1321            @Override
1322            public List<MBMessage> getThreadMessages(
1323                    long threadId, int status, int start, int end) {
1324    
1325                    if (status == WorkflowConstants.STATUS_ANY) {
1326                            return mbMessagePersistence.findByThreadId(threadId, start, end);
1327                    }
1328                    else {
1329                            return mbMessagePersistence.findByT_S(threadId, status, start, end);
1330                    }
1331            }
1332    
1333            @Override
1334            public int getThreadMessagesCount(long threadId, int status) {
1335                    if (status == WorkflowConstants.STATUS_ANY) {
1336                            return mbMessagePersistence.countByThreadId(threadId);
1337                    }
1338                    else {
1339                            return mbMessagePersistence.countByT_S(threadId, status);
1340                    }
1341            }
1342    
1343            @Override
1344            public List<MBMessage> getThreadRepliesMessages(
1345                    long threadId, int status, int start, int end) {
1346    
1347                    if (status == WorkflowConstants.STATUS_ANY) {
1348                            return mbMessagePersistence.findByThreadReplies(
1349                                    threadId, start, end);
1350                    }
1351                    else {
1352                            return mbMessagePersistence.findByTR_S(
1353                                    threadId, status, start, end);
1354                    }
1355            }
1356    
1357            @Override
1358            public List<MBMessage> getUserDiscussionMessages(
1359                    long userId, long classNameId, long classPK, int status, int start,
1360                    int end, OrderByComparator<MBMessage> obc) {
1361    
1362                    if (status == WorkflowConstants.STATUS_ANY) {
1363                            return mbMessagePersistence.findByU_C_C(
1364                                    userId, classNameId, classPK, start, end, obc);
1365                    }
1366                    else {
1367                            return mbMessagePersistence.findByU_C_C_S(
1368                                    userId, classNameId, classPK, status, start, end, obc);
1369                    }
1370            }
1371    
1372            @Override
1373            public List<MBMessage> getUserDiscussionMessages(
1374                    long userId, long[] classNameIds, int status, int start, int end,
1375                    OrderByComparator<MBMessage> obc) {
1376    
1377                    if (status == WorkflowConstants.STATUS_ANY) {
1378                            return mbMessagePersistence.findByU_C(
1379                                    userId, classNameIds, start, end, obc);
1380                    }
1381                    else {
1382                            return mbMessagePersistence.findByU_C_S(
1383                                    userId, classNameIds, status, start, end, obc);
1384                    }
1385            }
1386    
1387            @Override
1388            public List<MBMessage> getUserDiscussionMessages(
1389                    long userId, String className, long classPK, int status, int start,
1390                    int end, OrderByComparator<MBMessage> obc) {
1391    
1392                    long classNameId = classNameLocalService.getClassNameId(className);
1393    
1394                    return getUserDiscussionMessages(
1395                            userId, classNameId, classPK, status, start, end, obc);
1396            }
1397    
1398            @Override
1399            public int getUserDiscussionMessagesCount(
1400                    long userId, long classNameId, long classPK, int status) {
1401    
1402                    if (status == WorkflowConstants.STATUS_ANY) {
1403                            return mbMessagePersistence.countByU_C_C(
1404                                    userId, classNameId, classPK);
1405                    }
1406                    else {
1407                            return mbMessagePersistence.countByU_C_C_S(
1408                                    userId, classNameId, classPK, status);
1409                    }
1410            }
1411    
1412            @Override
1413            public int getUserDiscussionMessagesCount(
1414                    long userId, long[] classNameIds, int status) {
1415    
1416                    if (status == WorkflowConstants.STATUS_ANY) {
1417                            return mbMessagePersistence.countByU_C(userId, classNameIds);
1418                    }
1419                    else {
1420                            return mbMessagePersistence.countByU_C_S(
1421                                    userId, classNameIds, status);
1422                    }
1423            }
1424    
1425            @Override
1426            public int getUserDiscussionMessagesCount(
1427                    long userId, String className, long classPK, int status) {
1428    
1429                    long classNameId = classNameLocalService.getClassNameId(className);
1430    
1431                    return getUserDiscussionMessagesCount(
1432                            userId, classNameId, classPK, status);
1433            }
1434    
1435            @Override
1436            public long moveMessageAttachmentToTrash(
1437                            long userId, long messageId, String fileName)
1438                    throws PortalException {
1439    
1440                    MBMessage message = getMessage(messageId);
1441    
1442                    long folderId = message.getAttachmentsFolderId();
1443    
1444                    FileEntry fileEntry = PortletFileRepositoryUtil.getPortletFileEntry(
1445                            message.getGroupId(), folderId, fileName);
1446    
1447                    PortletFileRepositoryUtil.movePortletFileEntryToTrash(
1448                            userId, fileEntry.getFileEntryId());
1449    
1450                    return fileEntry.getFileEntryId();
1451            }
1452    
1453            @Override
1454            public void restoreMessageAttachmentFromTrash(
1455                            long userId, long messageId, String deletedFileName)
1456                    throws PortalException {
1457    
1458                    MBMessage message = getMessage(messageId);
1459    
1460                    Folder folder = message.addAttachmentsFolder();
1461    
1462                    PortletFileRepositoryUtil.restorePortletFileEntryFromTrash(
1463                            message.getGroupId(), userId, folder.getFolderId(),
1464                            deletedFileName);
1465            }
1466    
1467            @Override
1468            public void subscribeMessage(long userId, long messageId)
1469                    throws PortalException {
1470    
1471                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1472    
1473                    subscriptionLocalService.addSubscription(
1474                            userId, message.getGroupId(), MBThread.class.getName(),
1475                            message.getThreadId());
1476            }
1477    
1478            @Override
1479            public void unsubscribeMessage(long userId, long messageId)
1480                    throws PortalException {
1481    
1482                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1483    
1484                    subscriptionLocalService.deleteSubscription(
1485                            userId, MBThread.class.getName(), message.getThreadId());
1486            }
1487    
1488            @Override
1489            public void updateAnswer(long messageId, boolean answer, boolean cascade)
1490                    throws PortalException {
1491    
1492                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1493    
1494                    updateAnswer(message, answer, cascade);
1495            }
1496    
1497            @Override
1498            public void updateAnswer(MBMessage message, boolean answer, boolean cascade)
1499                    throws PortalException {
1500    
1501                    if (message.isAnswer() != answer) {
1502                            message.setAnswer(answer);
1503    
1504                            mbMessagePersistence.update(message);
1505                    }
1506    
1507                    if (cascade) {
1508                            List<MBMessage> messages = mbMessagePersistence.findByT_P(
1509                                    message.getThreadId(), message.getMessageId());
1510    
1511                            for (MBMessage curMessage : messages) {
1512                                    updateAnswer(curMessage, answer, cascade);
1513                            }
1514                    }
1515            }
1516    
1517            @Override
1518            public void updateAsset(
1519                            long userId, MBMessage message, long[] assetCategoryIds,
1520                            String[] assetTagNames, long[] assetLinkEntryIds)
1521                    throws PortalException {
1522    
1523                    updateAsset(
1524                            userId, message, assetCategoryIds, assetTagNames, assetLinkEntryIds,
1525                            true);
1526            }
1527    
1528            @Override
1529            public MBMessage updateDiscussionMessage(
1530                            long userId, long messageId, String className, long classPK,
1531                            String subject, String body, ServiceContext serviceContext)
1532                    throws PortalException {
1533    
1534                    if (Validator.isNull(subject)) {
1535                            if (Validator.isNotNull(body)) {
1536                                    int pos = Math.min(body.length(), 50);
1537    
1538                                    subject = body.substring(0, pos) + "...";
1539                            }
1540                            else {
1541                                    throw new MessageBodyException();
1542                            }
1543                    }
1544    
1545                    List<ObjectValuePair<String, InputStream>> inputStreamOVPs = null;
1546                    List<String> existingFiles = null;
1547                    double priority = 0.0;
1548                    boolean allowPingbacks = false;
1549    
1550                    serviceContext.setAttribute("className", className);
1551                    serviceContext.setAttribute("classPK", String.valueOf(classPK));
1552    
1553                    return updateMessage(
1554                            userId, messageId, subject, body, inputStreamOVPs, existingFiles,
1555                            priority, allowPingbacks, serviceContext);
1556            }
1557    
1558            @Override
1559            public MBMessage updateMessage(
1560                            long userId, long messageId, String body,
1561                            ServiceContext serviceContext)
1562                    throws PortalException {
1563    
1564                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1565    
1566                    return updateMessage(
1567                            userId, messageId, message.getSubject(), body, null, null,
1568                            message.getPriority(), message.isAllowPingbacks(), serviceContext);
1569            }
1570    
1571            @Override
1572            public MBMessage updateMessage(
1573                            long userId, long messageId, String subject, String body,
1574                            List<ObjectValuePair<String, InputStream>> inputStreamOVPs,
1575                            List<String> existingFiles, double priority, boolean allowPingbacks,
1576                            ServiceContext serviceContext)
1577                    throws PortalException {
1578    
1579                    // Message
1580    
1581                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1582    
1583                    int oldStatus = message.getStatus();
1584    
1585                    Date modifiedDate = serviceContext.getModifiedDate(null);
1586                    subject = ModelHintsUtil.trimString(
1587                            MBMessage.class.getName(), "subject", subject);
1588    
1589                    Map<String, Object> options = new HashMap<>();
1590    
1591                    options.put("discussion", message.isDiscussion());
1592    
1593                    body = SanitizerUtil.sanitize(
1594                            message.getCompanyId(), message.getGroupId(), userId,
1595                            MBMessage.class.getName(), messageId, "text/" + message.getFormat(),
1596                            Sanitizer.MODE_ALL, body, options);
1597    
1598                    validate(subject, body);
1599    
1600                    subject = getSubject(subject, body);
1601                    body = getBody(subject, body);
1602    
1603                    message.setModifiedDate(modifiedDate);
1604                    message.setSubject(subject);
1605                    message.setBody(body);
1606                    message.setAllowPingbacks(allowPingbacks);
1607    
1608                    if (priority != MBThreadConstants.PRIORITY_NOT_GIVEN) {
1609                            message.setPriority(priority);
1610                    }
1611    
1612                    MBThread thread = mbThreadPersistence.findByPrimaryKey(
1613                            message.getThreadId());
1614    
1615                    if (serviceContext.getWorkflowAction() ==
1616                                    WorkflowConstants.ACTION_SAVE_DRAFT) {
1617    
1618                            if (!message.isDraft() && !message.isPending()) {
1619                                    message.setStatus(WorkflowConstants.STATUS_DRAFT);
1620    
1621                                    // Thread
1622    
1623                                    User user = userPersistence.findByPrimaryKey(userId);
1624    
1625                                    updateThreadStatus(
1626                                            thread, message, user, oldStatus, modifiedDate);
1627    
1628                                    // Asset
1629    
1630                                    assetEntryLocalService.updateVisible(
1631                                            message.getWorkflowClassName(), message.getMessageId(),
1632                                            false);
1633    
1634                                    if (!message.isDiscussion()) {
1635    
1636                                            // Indexer
1637    
1638                                            Indexer<MBMessage> indexer =
1639                                                    IndexerRegistryUtil.nullSafeGetIndexer(MBMessage.class);
1640    
1641                                            indexer.delete(message);
1642                                    }
1643                            }
1644                    }
1645    
1646                    // Attachments
1647    
1648                    if ((inputStreamOVPs != null) || (existingFiles != null)) {
1649                            if (ListUtil.isNotEmpty(inputStreamOVPs) ||
1650                                    ListUtil.isNotEmpty(existingFiles)) {
1651    
1652                                    List<FileEntry> fileEntries =
1653                                            message.getAttachmentsFileEntries();
1654    
1655                                    for (FileEntry fileEntry : fileEntries) {
1656                                            String fileEntryId = String.valueOf(
1657                                                    fileEntry.getFileEntryId());
1658    
1659                                            if (!existingFiles.contains(fileEntryId)) {
1660                                                    if (!TrashUtil.isTrashEnabled(message.getGroupId())) {
1661                                                            deleteMessageAttachment(
1662                                                                    messageId, fileEntry.getTitle());
1663                                                    }
1664                                                    else {
1665                                                            moveMessageAttachmentToTrash(
1666                                                                    userId, messageId, fileEntry.getTitle());
1667                                                    }
1668                                            }
1669                                    }
1670    
1671                                    Folder folder = message.addAttachmentsFolder();
1672    
1673                                    PortletFileRepositoryUtil.addPortletFileEntries(
1674                                            message.getGroupId(), userId, MBMessage.class.getName(),
1675                                            message.getMessageId(), MBConstants.SERVICE_NAME,
1676                                            folder.getFolderId(), inputStreamOVPs);
1677                            }
1678                            else {
1679                                    if (TrashUtil.isTrashEnabled(message.getGroupId())) {
1680                                            List<FileEntry> fileEntries =
1681                                                    message.getAttachmentsFileEntries();
1682    
1683                                            for (FileEntry fileEntry : fileEntries) {
1684                                                    moveMessageAttachmentToTrash(
1685                                                            userId, messageId, fileEntry.getTitle());
1686                                            }
1687                                    }
1688                                    else {
1689                                            deleteMessageAttachments(message.getMessageId());
1690                                    }
1691                            }
1692                    }
1693    
1694                    message.setExpandoBridgeAttributes(serviceContext);
1695    
1696                    mbMessagePersistence.update(message);
1697    
1698                    // Statistics
1699    
1700                    if ((serviceContext.getWorkflowAction() ==
1701                                    WorkflowConstants.ACTION_SAVE_DRAFT) &&
1702                            !message.isDiscussion()) {
1703    
1704                            mbStatsUserLocalService.updateStatsUser(
1705                                    message.getGroupId(), userId, message.getModifiedDate());
1706                    }
1707    
1708                    // Thread
1709    
1710                    if ((priority != MBThreadConstants.PRIORITY_NOT_GIVEN) &&
1711                            (thread.getPriority() != priority)) {
1712    
1713                            thread.setPriority(priority);
1714    
1715                            mbThreadPersistence.update(thread);
1716    
1717                            updatePriorities(thread.getThreadId(), priority);
1718                    }
1719    
1720                    // Asset
1721    
1722                    updateAsset(
1723                            userId, message, serviceContext.getAssetCategoryIds(),
1724                            serviceContext.getAssetTagNames(),
1725                            serviceContext.getAssetLinkEntryIds());
1726    
1727                    // Workflow
1728    
1729                    startWorkflowInstance(userId, message, serviceContext);
1730    
1731                    return message;
1732            }
1733    
1734            /**
1735             * @deprecated As of 7.0.0, with no direct replacement
1736             */
1737            @Deprecated
1738            @Override
1739            public MBMessage updateMessage(long messageId, String body)
1740                    throws PortalException {
1741    
1742                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1743    
1744                    message.setBody(body);
1745    
1746                    mbMessagePersistence.update(message);
1747    
1748                    return message;
1749            }
1750    
1751            /**
1752             * @deprecated As of 7.0.0, replaced by {@link #updateStatus(long, long,
1753             *             int, ServiceContext, Map)}
1754             */
1755            @Deprecated
1756            @Override
1757            public MBMessage updateStatus(
1758                            long userId, long messageId, int status,
1759                            ServiceContext serviceContext)
1760                    throws PortalException {
1761    
1762                    return updateStatus(
1763                            userId, messageId, status, serviceContext,
1764                            new HashMap<String, Serializable>());
1765            }
1766    
1767            @Override
1768            public MBMessage updateStatus(
1769                            long userId, long messageId, int status,
1770                            ServiceContext serviceContext,
1771                            Map<String, Serializable> workflowContext)
1772                    throws PortalException {
1773    
1774                    // Message
1775    
1776                    MBMessage message = getMessage(messageId);
1777    
1778                    int oldStatus = message.getStatus();
1779    
1780                    User user = userPersistence.findByPrimaryKey(userId);
1781                    Date now = new Date();
1782    
1783                    Date modifiedDate = serviceContext.getModifiedDate(now);
1784    
1785                    message.setStatus(status);
1786                    message.setStatusByUserId(userId);
1787                    message.setStatusByUserName(user.getFullName());
1788                    message.setStatusDate(modifiedDate);
1789    
1790                    mbMessagePersistence.update(message);
1791    
1792                    // Thread
1793    
1794                    MBThread thread = mbThreadPersistence.findByPrimaryKey(
1795                            message.getThreadId());
1796    
1797                    updateThreadStatus(thread, message, user, oldStatus, modifiedDate);
1798    
1799                    Indexer<MBMessage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1800                            MBMessage.class);
1801    
1802                    if (status == WorkflowConstants.STATUS_APPROVED) {
1803                            if (oldStatus != WorkflowConstants.STATUS_APPROVED) {
1804    
1805                                    // Asset
1806    
1807                                    if (serviceContext.isAssetEntryVisible() &&
1808                                            ((message.getClassNameId() == 0) ||
1809                                             (message.getParentMessageId() != 0))) {
1810    
1811                                            Date publishDate = null;
1812    
1813                                            AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
1814                                                    message.getWorkflowClassName(), message.getMessageId());
1815    
1816                                            if ((assetEntry != null) &&
1817                                                    (assetEntry.getPublishDate() != null)) {
1818    
1819                                                    publishDate = assetEntry.getPublishDate();
1820                                            }
1821                                            else {
1822                                                    publishDate = now;
1823    
1824                                                    serviceContext.setCommand(Constants.ADD);
1825                                            }
1826    
1827                                            assetEntryLocalService.updateEntry(
1828                                                    message.getWorkflowClassName(), message.getMessageId(),
1829                                                    publishDate, true);
1830                                    }
1831    
1832                                    if (serviceContext.isCommandAdd()) {
1833    
1834                                            // Social
1835    
1836                                            JSONObject extraDataJSONObject =
1837                                                    JSONFactoryUtil.createJSONObject();
1838    
1839                                            extraDataJSONObject.put("title", message.getSubject());
1840    
1841                                            if (!message.isDiscussion() ) {
1842                                                    if (!message.isAnonymous() && !user.isDefaultUser()) {
1843                                                            long receiverUserId = 0;
1844    
1845                                                            MBMessage parentMessage =
1846                                                                    mbMessagePersistence.fetchByPrimaryKey(
1847                                                                            message.getParentMessageId());
1848    
1849                                                            if (parentMessage != null) {
1850                                                                    receiverUserId = parentMessage.getUserId();
1851                                                            }
1852    
1853                                                            SocialActivityManagerUtil.addActivity(
1854                                                                    message.getUserId(), message,
1855                                                                    MBActivityKeys.ADD_MESSAGE,
1856                                                                    extraDataJSONObject.toString(), receiverUserId);
1857    
1858                                                            if ((parentMessage != null) &&
1859                                                                    (receiverUserId != message.getUserId())) {
1860    
1861                                                                    SocialActivityManagerUtil.addActivity(
1862                                                                            message.getUserId(), parentMessage,
1863                                                                            MBActivityKeys.REPLY_MESSAGE,
1864                                                                            extraDataJSONObject.toString(), 0);
1865                                                            }
1866                                                    }
1867                                            }
1868                                            else {
1869                                                    String className = (String)serviceContext.getAttribute(
1870                                                            "className");
1871                                                    long classPK = ParamUtil.getLong(
1872                                                            serviceContext, "classPK");
1873                                                    long parentMessageId = message.getParentMessageId();
1874    
1875                                                    if (parentMessageId !=
1876                                                                    MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) {
1877    
1878                                                            AssetEntry assetEntry =
1879                                                                    assetEntryLocalService.fetchEntry(
1880                                                                            className, classPK);
1881    
1882                                                            if (assetEntry != null) {
1883                                                                    extraDataJSONObject.put(
1884                                                                            "messageId", message.getMessageId());
1885    
1886                                                                    SocialActivityManagerUtil.addActivity(
1887                                                                            message.getUserId(), assetEntry,
1888                                                                            SocialActivityConstants.TYPE_ADD_COMMENT,
1889                                                                            extraDataJSONObject.toString(),
1890                                                                            assetEntry.getUserId());
1891                                                            }
1892                                                    }
1893                                            }
1894                                    }
1895                            }
1896    
1897                            // Subscriptions
1898    
1899                            notifySubscribers(
1900                                    userId, (MBMessage)message.clone(),
1901                                    (String)workflowContext.get(WorkflowConstants.CONTEXT_URL),
1902                                    serviceContext);
1903    
1904                            // Indexer
1905    
1906                            indexer.reindex(message);
1907    
1908                            // Ping
1909    
1910                            pingPingback(message, serviceContext);
1911                    }
1912                    else if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
1913    
1914                            // Asset
1915    
1916                            assetEntryLocalService.updateVisible(
1917                                    message.getWorkflowClassName(), message.getMessageId(), false);
1918    
1919                            // Indexer
1920    
1921                            indexer.delete(message);
1922                    }
1923    
1924                    // Statistics
1925    
1926                    if (!message.isDiscussion()) {
1927                            mbStatsUserLocalService.updateStatsUser(
1928                                    message.getGroupId(), userId,
1929                                    serviceContext.getModifiedDate(now));
1930                    }
1931    
1932                    return message;
1933            }
1934    
1935            @Override
1936            public void updateUserName(long userId, String userName) {
1937                    List<MBMessage> messages = mbMessagePersistence.findByUserId(userId);
1938    
1939                    for (MBMessage message : messages) {
1940                            message.setUserName(userName);
1941    
1942                            mbMessagePersistence.update(message);
1943                    }
1944            }
1945    
1946            protected String getBody(String subject, String body) {
1947                    if (Validator.isNull(body)) {
1948                            return subject;
1949                    }
1950    
1951                    return body;
1952            }
1953    
1954            protected String getMessageURL(
1955                            MBMessage message, ServiceContext serviceContext)
1956                    throws PortalException {
1957    
1958                    String entryURL = GetterUtil.getString(
1959                            serviceContext.getAttribute("entryURL"));
1960    
1961                    if (Validator.isNotNull(entryURL)) {
1962                            return entryURL;
1963                    }
1964    
1965                    HttpServletRequest request = serviceContext.getRequest();
1966    
1967                    if (request == null) {
1968                            if (Validator.isNull(serviceContext.getLayoutFullURL())) {
1969                                    return StringPool.BLANK;
1970                            }
1971    
1972                            return serviceContext.getLayoutFullURL() +
1973                                    Portal.FRIENDLY_URL_SEPARATOR + "message_boards/view_message/" +
1974                                            message.getMessageId();
1975                    }
1976    
1977                    String portletId = PortletProviderUtil.getPortletId(
1978                            MBMessage.class.getName(), PortletProvider.Action.VIEW);
1979    
1980                    String layoutURL = LayoutURLUtil.getLayoutURL(
1981                            message.getGroupId(), portletId, serviceContext);
1982    
1983                    if (Validator.isNotNull(layoutURL)) {
1984                            return layoutURL + Portal.FRIENDLY_URL_SEPARATOR +
1985                                    "message_boards/view_message/" + message.getMessageId();
1986                    }
1987                    else {
1988                            portletId = PortletProviderUtil.getPortletId(
1989                                    MBMessage.class.getName(), PortletProvider.Action.MANAGE);
1990    
1991                            PortletURL portletURL = PortalUtil.getControlPanelPortletURL(
1992                                    request, portletId, 0, PortletRequest.RENDER_PHASE);
1993    
1994                            portletURL.setParameter(
1995                                    "mvcRenderCommandName", "/message_boards/view_message");
1996                            portletURL.setParameter(
1997                                    "messageId", String.valueOf(message.getMessageId()));
1998    
1999                            return portletURL.toString();
2000                    }
2001            }
2002    
2003            protected String getSubject(String subject, String body) {
2004                    if (Validator.isNull(subject)) {
2005                            return StringUtil.shorten(body);
2006                    }
2007    
2008                    return subject;
2009            }
2010    
2011            protected MBSubscriptionSender getSubscriptionSender(
2012                    long userId, MBMessage message, String messageURL, String entryTitle,
2013                    boolean htmlFormat, String messageBody, String categoryName,
2014                    String inReplyTo, String fromName, String fromAddress,
2015                    String replyToAddress, String emailAddress, String fullName,
2016                    LocalizedValuesMap subjectLocalizedValuesMap,
2017                    LocalizedValuesMap bodyLocalizedValuesMap,
2018                    ServiceContext serviceContext) {
2019    
2020                    MBSubscriptionSender subscriptionSender = new MBSubscriptionSender(
2021                            MBPermission.RESOURCE_NAME);
2022    
2023                    subscriptionSender.setBulk(PropsValues.MESSAGE_BOARDS_EMAIL_BULK);
2024                    subscriptionSender.setClassName(message.getModelClassName());
2025                    subscriptionSender.setClassPK(message.getMessageId());
2026                    subscriptionSender.setCompanyId(message.getCompanyId());
2027                    subscriptionSender.setContextAttribute(
2028                            "[$MESSAGE_BODY$]", messageBody, false);
2029                    subscriptionSender.setContextAttributes(
2030                            "[$CATEGORY_NAME$]", categoryName, "[$MAILING_LIST_ADDRESS$]",
2031                            replyToAddress, "[$MESSAGE_ID$]", message.getMessageId(),
2032                            "[$MESSAGE_SUBJECT$]", entryTitle, "[$MESSAGE_URL$]", messageURL,
2033                            "[$MESSAGE_USER_ADDRESS$]", emailAddress, "[$MESSAGE_USER_NAME$]",
2034                            fullName);
2035                    subscriptionSender.setCurrentUserId(userId);
2036                    subscriptionSender.setEntryTitle(entryTitle);
2037                    subscriptionSender.setEntryURL(messageURL);
2038                    subscriptionSender.setFrom(fromAddress, fromName);
2039                    subscriptionSender.setHtmlFormat(htmlFormat);
2040                    subscriptionSender.setInReplyTo(inReplyTo);
2041    
2042                    if (bodyLocalizedValuesMap != null) {
2043                            subscriptionSender.setLocalizedBodyMap(
2044                                    LocalizationUtil.getMap(bodyLocalizedValuesMap));
2045                    }
2046    
2047                    if (subjectLocalizedValuesMap != null) {
2048                            subscriptionSender.setLocalizedSubjectMap(
2049                                    LocalizationUtil.getMap(subjectLocalizedValuesMap));
2050                    }
2051    
2052                    Date modifiedDate = message.getModifiedDate();
2053    
2054                    subscriptionSender.setMailId(
2055                            MBUtil.MESSAGE_POP_PORTLET_PREFIX, message.getCategoryId(),
2056                            message.getMessageId(), modifiedDate.getTime());
2057    
2058                    int notificationType =
2059                            UserNotificationDefinition.NOTIFICATION_TYPE_ADD_ENTRY;
2060    
2061                    if (serviceContext.isCommandUpdate()) {
2062                            notificationType =
2063                                    UserNotificationDefinition.NOTIFICATION_TYPE_UPDATE_ENTRY;
2064                    }
2065    
2066                    subscriptionSender.setNotificationType(notificationType);
2067    
2068                    String portletId = PortletProviderUtil.getPortletId(
2069                            MBMessage.class.getName(), PortletProvider.Action.VIEW);
2070    
2071                    subscriptionSender.setPortletId(portletId);
2072    
2073                    subscriptionSender.setReplyToAddress(replyToAddress);
2074                    subscriptionSender.setScopeGroupId(message.getGroupId());
2075                    subscriptionSender.setServiceContext(serviceContext);
2076                    subscriptionSender.setUniqueMailId(false);
2077    
2078                    return subscriptionSender;
2079            }
2080    
2081            protected void notifyDiscussionSubscribers(
2082                            long userId, MBMessage message, ServiceContext serviceContext)
2083                    throws PortalException {
2084    
2085                    if (!PrefsPropsUtil.getBoolean(
2086                                    message.getCompanyId(),
2087                                    PropsKeys.DISCUSSION_EMAIL_COMMENTS_ADDED_ENABLED)) {
2088    
2089                            return;
2090                    }
2091    
2092                    MBDiscussion mbDiscussion =
2093                            mbDiscussionLocalService.getThreadDiscussion(message.getThreadId());
2094    
2095                    String contentURL = (String)serviceContext.getAttribute("contentURL");
2096    
2097                    String userAddress = StringPool.BLANK;
2098                    String userName = (String)serviceContext.getAttribute(
2099                            "pingbackUserName");
2100    
2101                    if (Validator.isNull(userName)) {
2102                            userAddress = PortalUtil.getUserEmailAddress(message.getUserId());
2103                            userName = PortalUtil.getUserName(
2104                                    message.getUserId(), StringPool.BLANK);
2105                    }
2106    
2107                    String fromName = PrefsPropsUtil.getString(
2108                            message.getCompanyId(), PropsKeys.ADMIN_EMAIL_FROM_NAME);
2109                    String fromAddress = PrefsPropsUtil.getString(
2110                            message.getCompanyId(), PropsKeys.ADMIN_EMAIL_FROM_ADDRESS);
2111    
2112                    String subject = PrefsPropsUtil.getContent(
2113                            message.getCompanyId(), PropsKeys.DISCUSSION_EMAIL_SUBJECT);
2114                    String body = PrefsPropsUtil.getContent(
2115                            message.getCompanyId(), PropsKeys.DISCUSSION_EMAIL_BODY);
2116    
2117                    SubscriptionSender subscriptionSender = new SubscriptionSender();
2118    
2119                    subscriptionSender.setBody(body);
2120                    subscriptionSender.setCompanyId(message.getCompanyId());
2121                    subscriptionSender.setClassName(MBDiscussion.class.getName());
2122                    subscriptionSender.setClassPK(mbDiscussion.getDiscussionId());
2123                    subscriptionSender.setContextAttribute(
2124                            "[$COMMENTS_BODY$]", message.getBody(true), false);
2125                    subscriptionSender.setContextAttributes(
2126                            "[$COMMENTS_USER_ADDRESS$]", userAddress, "[$COMMENTS_USER_NAME$]",
2127                            userName, "[$CONTENT_URL$]", contentURL);
2128                    subscriptionSender.setCurrentUserId(userId);
2129                    subscriptionSender.setEntryTitle(message.getBody());
2130                    subscriptionSender.setEntryURL(contentURL);
2131                    subscriptionSender.setFrom(fromAddress, fromName);
2132                    subscriptionSender.setHtmlFormat(true);
2133    
2134                    Date modifiedDate = message.getModifiedDate();
2135    
2136                    subscriptionSender.setMailId(
2137                            "mb_discussion", message.getCategoryId(), message.getMessageId(),
2138                            modifiedDate.getTime());
2139    
2140                    int notificationType =
2141                            UserNotificationDefinition.NOTIFICATION_TYPE_ADD_ENTRY;
2142    
2143                    if (serviceContext.isCommandUpdate()) {
2144                            notificationType =
2145                                    UserNotificationDefinition.NOTIFICATION_TYPE_UPDATE_ENTRY;
2146                    }
2147    
2148                    subscriptionSender.setNotificationType(notificationType);
2149    
2150                    String portletId = PortletProviderUtil.getPortletId(
2151                            Comment.class.getName(), PortletProvider.Action.VIEW);
2152    
2153                    subscriptionSender.setPortletId(portletId);
2154    
2155                    subscriptionSender.setScopeGroupId(message.getGroupId());
2156                    subscriptionSender.setServiceContext(serviceContext);
2157                    subscriptionSender.setSubject(subject);
2158                    subscriptionSender.setUniqueMailId(false);
2159    
2160                    String className = (String)serviceContext.getAttribute("className");
2161                    long classPK = ParamUtil.getLong(serviceContext, "classPK");
2162    
2163                    subscriptionSender.addPersistedSubscribers(className, classPK);
2164    
2165                    subscriptionSender.flushNotificationsAsync();
2166            }
2167    
2168            protected void notifySubscribers(
2169                            long userId, MBMessage message, String messageURL,
2170                            ServiceContext serviceContext)
2171                    throws PortalException {
2172    
2173                    if (!message.isApproved() || Validator.isNull(messageURL)) {
2174                            return;
2175                    }
2176    
2177                    if (message.isDiscussion()) {
2178                            try {
2179                                    notifyDiscussionSubscribers(userId, message, serviceContext);
2180                            }
2181                            catch (Exception e) {
2182                                    _log.error(e, e);
2183                            }
2184    
2185                            return;
2186                    }
2187    
2188                    MBGroupServiceSettings mbGroupServiceSettings =
2189                            MBGroupServiceSettings.getInstance(message.getGroupId());
2190    
2191                    if (serviceContext.isCommandAdd() &&
2192                            mbGroupServiceSettings.isEmailMessageAddedEnabled()) {
2193                    }
2194                    else if (serviceContext.isCommandUpdate() &&
2195                                     mbGroupServiceSettings.isEmailMessageUpdatedEnabled()) {
2196                    }
2197                    else {
2198                            return;
2199                    }
2200    
2201                    Company company = companyPersistence.findByPrimaryKey(
2202                            message.getCompanyId());
2203    
2204                    Group group = groupPersistence.findByPrimaryKey(message.getGroupId());
2205    
2206                    String emailAddress = PortalUtil.getUserEmailAddress(
2207                            message.getUserId());
2208                    String fullName = PortalUtil.getUserName(
2209                            message.getUserId(), message.getUserName());
2210    
2211                    if (message.isAnonymous()) {
2212                            emailAddress = StringPool.BLANK;
2213                            fullName = serviceContext.translate("anonymous");
2214                    }
2215    
2216                    MBCategory category = message.getCategory();
2217    
2218                    String categoryName = category.getName();
2219    
2220                    if (category.getCategoryId() ==
2221                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
2222    
2223                            categoryName = serviceContext.translate("message-boards-home");
2224    
2225                            categoryName += " - " + group.getDescriptiveName();
2226                    }
2227    
2228                    List<Long> categoryIds = new ArrayList<>();
2229    
2230                    categoryIds.add(message.getCategoryId());
2231    
2232                    if (message.getCategoryId() !=
2233                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
2234    
2235                            categoryIds.addAll(category.getAncestorCategoryIds());
2236                    }
2237    
2238                    String entryTitle = message.getSubject();
2239    
2240                    String fromName = mbGroupServiceSettings.getEmailFromName();
2241                    String fromAddress = mbGroupServiceSettings.getEmailFromAddress();
2242    
2243                    String replyToAddress = StringPool.BLANK;
2244    
2245                    if (PropsValues.POP_SERVER_NOTIFICATIONS_ENABLED) {
2246                            replyToAddress = MBUtil.getReplyToAddress(
2247                                    message.getCategoryId(), message.getMessageId(),
2248                                    company.getMx(), fromAddress);
2249                    }
2250    
2251                    LocalizedValuesMap subjectLocalizedValuesMap = null;
2252                    LocalizedValuesMap bodyLocalizedValuesMap = null;
2253    
2254                    if (serviceContext.isCommandUpdate()) {
2255                            subjectLocalizedValuesMap =
2256                                    mbGroupServiceSettings.getEmailMessageUpdatedSubject();
2257                            bodyLocalizedValuesMap =
2258                                    mbGroupServiceSettings.getEmailMessageUpdatedBody();
2259                    }
2260                    else {
2261                            subjectLocalizedValuesMap =
2262                                    mbGroupServiceSettings.getEmailMessageAddedSubject();
2263                            bodyLocalizedValuesMap =
2264                                    mbGroupServiceSettings.getEmailMessageAddedBody();
2265                    }
2266    
2267                    boolean htmlFormat = mbGroupServiceSettings.isEmailHtmlFormat();
2268    
2269                    String messageBody = message.getBody();
2270    
2271                    if (htmlFormat && message.isFormatBBCode()) {
2272                            try {
2273                                    messageBody = BBCodeTranslatorUtil.getHTML(messageBody);
2274    
2275                                    HttpServletRequest request = serviceContext.getRequest();
2276    
2277                                    if (request != null) {
2278                                            ThemeDisplay themeDisplay =
2279                                                    (ThemeDisplay)request.getAttribute(
2280                                                            WebKeys.THEME_DISPLAY);
2281    
2282                                            messageBody = MBUtil.replaceMessageBodyPaths(
2283                                                    themeDisplay, messageBody);
2284                                    }
2285                            }
2286                            catch (Exception e) {
2287                                    _log.error(
2288                                            "Could not parse message " + message.getMessageId() +
2289                                                    " " + e.getMessage());
2290                            }
2291                    }
2292    
2293                    String inReplyTo = null;
2294    
2295                    if (message.getParentMessageId() !=
2296                                    MBMessageConstants.DEFAULT_PARENT_MESSAGE_ID) {
2297    
2298                            MBMessage parentMessage = mbMessageLocalService.getMessage(
2299                                    message.getParentMessageId());
2300    
2301                            Date modifiedDate = parentMessage.getModifiedDate();
2302    
2303                            inReplyTo = PortalUtil.getMailId(
2304                                    company.getMx(), MBUtil.MESSAGE_POP_PORTLET_PREFIX,
2305                                    message.getCategoryId(), parentMessage.getMessageId(),
2306                                    modifiedDate.getTime());
2307                    }
2308    
2309                    SubscriptionSender subscriptionSender = getSubscriptionSender(
2310                            userId, message, messageURL, entryTitle, htmlFormat, messageBody,
2311                            categoryName, inReplyTo, fromName, fromAddress, replyToAddress,
2312                            emailAddress, fullName, subjectLocalizedValuesMap,
2313                            bodyLocalizedValuesMap, serviceContext);
2314    
2315                    subscriptionSender.addPersistedSubscribers(
2316                            MBCategory.class.getName(), message.getGroupId());
2317    
2318                    for (long categoryId : categoryIds) {
2319                            if (categoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
2320                                    subscriptionSender.addPersistedSubscribers(
2321                                            MBCategory.class.getName(), categoryId);
2322                            }
2323                    }
2324    
2325                    subscriptionSender.addPersistedSubscribers(
2326                            MBThread.class.getName(), message.getThreadId());
2327    
2328                    subscriptionSender.flushNotificationsAsync();
2329    
2330                    if (!MailingListThreadLocal.isSourceMailingList()) {
2331                            for (long categoryId : categoryIds) {
2332                                    MBSubscriptionSender sourceMailingListSubscriptionSender =
2333                                            getSubscriptionSender(
2334                                                    userId, message, messageURL, entryTitle, htmlFormat,
2335                                                    messageBody, categoryName, inReplyTo, fromName,
2336                                                    fromAddress, replyToAddress, emailAddress, fullName,
2337                                                    subjectLocalizedValuesMap, bodyLocalizedValuesMap,
2338                                                    serviceContext);
2339    
2340                                    sourceMailingListSubscriptionSender.setBulk(false);
2341    
2342                                    sourceMailingListSubscriptionSender.addMailingListSubscriber(
2343                                            message.getGroupId(), categoryId);
2344    
2345                                    sourceMailingListSubscriptionSender.flushNotificationsAsync();
2346                            }
2347                    }
2348            }
2349    
2350            protected void pingPingback(
2351                    MBMessage message, ServiceContext serviceContext) {
2352    
2353                    if (!PropsValues.BLOGS_PINGBACK_ENABLED ||
2354                            !message.isAllowPingbacks() || !message.isApproved()) {
2355    
2356                            return;
2357                    }
2358    
2359                    String layoutFullURL = serviceContext.getLayoutFullURL();
2360    
2361                    if (Validator.isNull(layoutFullURL)) {
2362                            return;
2363                    }
2364    
2365                    String sourceUri =
2366                            layoutFullURL + Portal.FRIENDLY_URL_SEPARATOR +
2367                                    "message_boards/view_message/" + message.getMessageId();
2368    
2369                    Source source = new Source(message.getBody(true));
2370    
2371                    List<StartTag> startTags = source.getAllStartTags("a");
2372    
2373                    for (StartTag startTag : startTags) {
2374                            String targetUri = startTag.getAttributeValue("href");
2375    
2376                            if (Validator.isNotNull(targetUri)) {
2377                                    try {
2378                                            LinkbackProducerUtil.sendPingback(sourceUri, targetUri);
2379                                    }
2380                                    catch (Exception e) {
2381                                            _log.error("Error while sending pingback " + targetUri, e);
2382                                    }
2383                            }
2384                    }
2385            }
2386    
2387            protected void startWorkflowInstance(
2388                            long userId, MBMessage message, ServiceContext serviceContext)
2389                    throws PortalException {
2390    
2391                    Map<String, Serializable> workflowContext = new HashMap<>();
2392    
2393                    workflowContext.put(
2394                            WorkflowConstants.CONTEXT_URL,
2395                            getMessageURL(message, serviceContext));
2396    
2397                    WorkflowHandlerRegistryUtil.startWorkflowInstance(
2398                            message.getCompanyId(), message.getGroupId(), userId,
2399                            message.getWorkflowClassName(), message.getMessageId(), message,
2400                            serviceContext, workflowContext);
2401            }
2402    
2403            protected void updateAsset(
2404                            long userId, MBMessage message, long[] assetCategoryIds,
2405                            String[] assetTagNames, long[] assetLinkEntryIds,
2406                            boolean assetEntryVisible)
2407                    throws PortalException {
2408    
2409                    boolean visible = false;
2410    
2411                    if (assetEntryVisible && message.isApproved() &&
2412                            ((message.getClassNameId() == 0) ||
2413                             (message.getParentMessageId() != 0))) {
2414    
2415                            visible = true;
2416                    }
2417    
2418                    AssetEntry assetEntry = assetEntryLocalService.updateEntry(
2419                            userId, message.getGroupId(), message.getCreateDate(),
2420                            message.getModifiedDate(), message.getWorkflowClassName(),
2421                            message.getMessageId(), message.getUuid(), 0, assetCategoryIds,
2422                            assetTagNames, visible, null, null, null, ContentTypes.TEXT_HTML,
2423                            message.getSubject(), null, null, null, null, 0, 0, null);
2424    
2425                    assetLinkLocalService.updateLinks(
2426                            userId, assetEntry.getEntryId(), assetLinkEntryIds,
2427                            AssetLinkConstants.TYPE_RELATED);
2428            }
2429    
2430            protected void updatePriorities(long threadId, double priority) {
2431                    List<MBMessage> messages = mbMessagePersistence.findByThreadId(
2432                            threadId);
2433    
2434                    for (MBMessage message : messages) {
2435                            if (message.getPriority() != priority) {
2436                                    message.setPriority(priority);
2437    
2438                                    mbMessagePersistence.update(message);
2439                            }
2440                    }
2441            }
2442    
2443            protected void updateThreadStatus(
2444                            MBThread thread, MBMessage message, User user, int oldStatus,
2445                            Date modifiedDate)
2446                    throws PortalException {
2447    
2448                    MBCategory category = null;
2449    
2450                    int status = message.getStatus();
2451    
2452                    if ((thread.getCategoryId() !=
2453                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
2454                            (thread.getCategoryId() !=
2455                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
2456    
2457                            category = mbCategoryPersistence.findByPrimaryKey(
2458                                    thread.getCategoryId());
2459                    }
2460    
2461                    if ((thread.getRootMessageId() == message.getMessageId()) &&
2462                            (oldStatus != status)) {
2463    
2464                            thread.setModifiedDate(modifiedDate);
2465                            thread.setStatus(status);
2466                            thread.setStatusByUserId(user.getUserId());
2467                            thread.setStatusByUserName(user.getFullName());
2468                            thread.setStatusDate(modifiedDate);
2469                    }
2470    
2471                    if (status == oldStatus) {
2472                            return;
2473                    }
2474    
2475                    if (status == WorkflowConstants.STATUS_APPROVED) {
2476                            if (message.isAnonymous()) {
2477                                    thread.setLastPostByUserId(0);
2478                            }
2479                            else {
2480                                    thread.setLastPostByUserId(message.getUserId());
2481                            }
2482    
2483                            thread.setLastPostDate(modifiedDate);
2484    
2485                            if (category != null) {
2486                                    category.setLastPostDate(modifiedDate);
2487    
2488                                    category = mbCategoryPersistence.update(category);
2489                            }
2490                    }
2491    
2492                    if ((oldStatus == WorkflowConstants.STATUS_APPROVED) ||
2493                            (status == WorkflowConstants.STATUS_APPROVED)) {
2494    
2495                            // Thread
2496    
2497                            MBUtil.updateThreadMessageCount(thread.getThreadId());
2498    
2499                            // Category
2500    
2501                            if ((category != null) &&
2502                                    (thread.getRootMessageId() == message.getMessageId())) {
2503    
2504                                    MBUtil.updateCategoryStatistics(category.getCategoryId());
2505                            }
2506    
2507                            if ((category != null) &&
2508                                    !(thread.getRootMessageId() == message.getMessageId())) {
2509    
2510                                    MBUtil.updateCategoryMessageCount(category.getCategoryId());
2511                            }
2512                    }
2513    
2514                    // Indexer
2515    
2516                    Indexer<MBThread> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
2517                            MBThread.class);
2518    
2519                    indexer.reindex(thread);
2520    
2521                    mbThreadPersistence.update(thread);
2522            }
2523    
2524            protected void validate(String subject, String body)
2525                    throws PortalException {
2526    
2527                    if (Validator.isNull(subject) && Validator.isNull(body)) {
2528                            throw new MessageSubjectException();
2529                    }
2530            }
2531    
2532            protected void validateDiscussionMaxComments(String className, long classPK)
2533                    throws PortalException {
2534    
2535                    if (PropsValues.DISCUSSION_MAX_COMMENTS <= 0) {
2536                            return;
2537                    }
2538    
2539                    int count = getDiscussionMessagesCount(
2540                            className, classPK, WorkflowConstants.STATUS_APPROVED);
2541    
2542                    if (count >= PropsValues.DISCUSSION_MAX_COMMENTS) {
2543                            throw new DiscussionMaxCommentsException();
2544                    }
2545            }
2546    
2547            private static final Log _log = LogFactoryUtil.getLog(
2548                    MBMessageLocalServiceImpl.class);
2549    
2550    }