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