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