001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.messageboards.service.impl;
016    
017    import com.liferay.portal.kernel.dao.orm.QueryDefinition;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.increment.BufferedIncrement;
020    import com.liferay.portal.kernel.increment.NumberIncrement;
021    import com.liferay.portal.kernel.json.JSONFactoryUtil;
022    import com.liferay.portal.kernel.json.JSONObject;
023    import com.liferay.portal.kernel.search.Field;
024    import com.liferay.portal.kernel.search.Hits;
025    import com.liferay.portal.kernel.search.Indexer;
026    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
027    import com.liferay.portal.kernel.search.SearchContext;
028    import com.liferay.portal.kernel.search.Sort;
029    import com.liferay.portal.kernel.systemevent.SystemEvent;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.Validator;
032    import com.liferay.portal.kernel.workflow.WorkflowConstants;
033    import com.liferay.portal.model.Group;
034    import com.liferay.portal.model.ResourceConstants;
035    import com.liferay.portal.model.SystemEventConstants;
036    import com.liferay.portal.model.User;
037    import com.liferay.portal.portletfilerepository.PortletFileRepositoryUtil;
038    import com.liferay.portal.service.ServiceContext;
039    import com.liferay.portal.util.PortletKeys;
040    import com.liferay.portlet.asset.model.AssetEntry;
041    import com.liferay.portlet.documentlibrary.model.DLFolderConstants;
042    import com.liferay.portlet.exportimport.lar.ExportImportThreadLocal;
043    import com.liferay.portlet.messageboards.NoSuchCategoryException;
044    import com.liferay.portlet.messageboards.SplitThreadException;
045    import com.liferay.portlet.messageboards.model.MBCategory;
046    import com.liferay.portlet.messageboards.model.MBCategoryConstants;
047    import com.liferay.portlet.messageboards.model.MBMessage;
048    import com.liferay.portlet.messageboards.model.MBMessageDisplay;
049    import com.liferay.portlet.messageboards.model.MBThread;
050    import com.liferay.portlet.messageboards.model.MBThreadConstants;
051    import com.liferay.portlet.messageboards.model.MBTreeWalker;
052    import com.liferay.portlet.messageboards.service.base.MBThreadLocalServiceBaseImpl;
053    import com.liferay.portlet.messageboards.util.MBUtil;
054    import com.liferay.portlet.social.model.SocialActivityConstants;
055    import com.liferay.portlet.trash.model.TrashEntry;
056    import com.liferay.portlet.trash.model.TrashVersion;
057    
058    import java.util.ArrayList;
059    import java.util.Date;
060    import java.util.HashSet;
061    import java.util.List;
062    import java.util.Set;
063    
064    /**
065     * @author Brian Wing Shun Chan
066     * @author Shuyang Zhou
067     */
068    public class MBThreadLocalServiceImpl extends MBThreadLocalServiceBaseImpl {
069    
070            @Override
071            public MBThread addThread(
072                            long categoryId, MBMessage message, ServiceContext serviceContext)
073                    throws PortalException {
074    
075                    // Thread
076    
077                    long threadId = message.getThreadId();
078    
079                    if (!message.isRoot() || (threadId <= 0)) {
080                            threadId = counterLocalService.increment();
081                    }
082    
083                    MBThread thread = mbThreadPersistence.create(threadId);
084    
085                    thread.setUuid(serviceContext.getUuid());
086                    thread.setGroupId(message.getGroupId());
087                    thread.setCompanyId(message.getCompanyId());
088                    thread.setUserId(message.getUserId());
089                    thread.setUserName(message.getUserName());
090                    thread.setCategoryId(categoryId);
091                    thread.setRootMessageId(message.getMessageId());
092                    thread.setRootMessageUserId(message.getUserId());
093    
094                    if (message.isAnonymous()) {
095                            thread.setLastPostByUserId(0);
096                    }
097                    else {
098                            thread.setLastPostByUserId(message.getUserId());
099                    }
100    
101                    thread.setLastPostDate(message.getModifiedDate());
102    
103                    if (message.getPriority() != MBThreadConstants.PRIORITY_NOT_GIVEN) {
104                            thread.setPriority(message.getPriority());
105                    }
106    
107                    thread.setStatus(message.getStatus());
108                    thread.setStatusByUserId(message.getStatusByUserId());
109                    thread.setStatusByUserName(message.getStatusByUserName());
110                    thread.setStatusDate(message.getStatusDate());
111    
112                    mbThreadPersistence.update(thread);
113    
114                    // Asset
115    
116                    if (categoryId >= 0) {
117                            assetEntryLocalService.updateEntry(
118                                    message.getUserId(), message.getGroupId(),
119                                    thread.getStatusDate(), thread.getLastPostDate(),
120                                    MBThread.class.getName(), thread.getThreadId(),
121                                    thread.getUuid(), 0, new long[0], new String[0], false, null,
122                                    null, null, null, String.valueOf(thread.getRootMessageId()),
123                                    null, null, null, null, 0, 0, null);
124                    }
125    
126                    return thread;
127            }
128    
129            @Override
130            public void deleteThread(long threadId) throws PortalException {
131                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
132    
133                    mbThreadLocalService.deleteThread(thread);
134            }
135    
136            @Override
137            @SystemEvent(
138                    action = SystemEventConstants.ACTION_SKIP,
139                    type = SystemEventConstants.TYPE_DELETE
140            )
141            public void deleteThread(MBThread thread) throws PortalException {
142                    MBMessage rootMessage = mbMessagePersistence.findByPrimaryKey(
143                            thread.getRootMessageId());
144    
145                    // Indexer
146    
147                    Indexer messageIndexer = IndexerRegistryUtil.nullSafeGetIndexer(
148                            MBMessage.class);
149    
150                    // Attachments
151    
152                    long folderId = thread.getAttachmentsFolderId();
153    
154                    if (folderId != DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
155                            PortletFileRepositoryUtil.deletePortletFolder(folderId);
156                    }
157    
158                    // Subscriptions
159    
160                    subscriptionLocalService.deleteSubscriptions(
161                            thread.getCompanyId(), MBThread.class.getName(),
162                            thread.getThreadId());
163    
164                    // Thread flags
165    
166                    mbThreadFlagLocalService.deleteThreadFlagsByThreadId(
167                            thread.getThreadId());
168    
169                    // Messages
170    
171                    List<MBMessage> messages = mbMessagePersistence.findByThreadId(
172                            thread.getThreadId());
173    
174                    for (MBMessage message : messages) {
175    
176                            // Ratings
177    
178                            ratingsStatsLocalService.deleteStats(
179                                    message.getWorkflowClassName(), message.getMessageId());
180    
181                            // Asset
182    
183                            assetEntryLocalService.deleteEntry(
184                                    message.getWorkflowClassName(), message.getMessageId());
185    
186                            // Resources
187    
188                            if (!message.isDiscussion()) {
189                                    resourceLocalService.deleteResource(
190                                            message.getCompanyId(), message.getWorkflowClassName(),
191                                            ResourceConstants.SCOPE_INDIVIDUAL, message.getMessageId());
192                            }
193    
194                            // Message
195    
196                            mbMessagePersistence.remove(message);
197    
198                            // Indexer
199    
200                            messageIndexer.delete(message);
201    
202                            // Statistics
203    
204                            if (!message.isDiscussion()) {
205                                    mbStatsUserLocalService.updateStatsUser(
206                                            message.getGroupId(), message.getUserId());
207                            }
208    
209                            // Workflow
210    
211                            workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
212                                    message.getCompanyId(), message.getGroupId(),
213                                    message.getWorkflowClassName(), message.getMessageId());
214                    }
215    
216                    // Category
217    
218                    if ((rootMessage.getCategoryId() !=
219                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
220                            (rootMessage.getCategoryId() !=
221                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
222    
223                            try {
224                                    MBCategory category = mbCategoryPersistence.findByPrimaryKey(
225                                            thread.getCategoryId());
226    
227                                    MBUtil.updateCategoryStatistics(
228                                            category.getCompanyId(), category.getCategoryId());
229                            }
230                            catch (NoSuchCategoryException nsce) {
231                                    if (!thread.isInTrash()) {
232                                            throw nsce;
233                                    }
234                            }
235                    }
236    
237                    // Thread Asset
238    
239                    AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
240                            MBThread.class.getName(), thread.getThreadId());
241    
242                    if (assetEntry != null) {
243                            assetEntry.setTitle(rootMessage.getSubject());
244    
245                            assetEntryLocalService.updateAssetEntry(assetEntry);
246                    }
247    
248                    assetEntryLocalService.deleteEntry(
249                            MBThread.class.getName(), thread.getThreadId());
250    
251                    // Trash
252    
253                    if (thread.isInTrashExplicitly()) {
254                            trashEntryLocalService.deleteEntry(
255                                    MBThread.class.getName(), thread.getThreadId());
256                    }
257                    else {
258                            trashVersionLocalService.deleteTrashVersion(
259                                    MBThread.class.getName(), thread.getThreadId());
260                    }
261    
262                    // Indexer
263    
264                    Indexer threadIndexer = IndexerRegistryUtil.nullSafeGetIndexer(
265                            MBThread.class);
266    
267                    threadIndexer.delete(thread);
268    
269                    // Thread
270    
271                    mbThreadPersistence.remove(thread);
272            }
273    
274            @Override
275            public void deleteThreads(long groupId, long categoryId)
276                    throws PortalException {
277    
278                    deleteThreads(groupId, categoryId, true);
279            }
280    
281            @Override
282            public void deleteThreads(
283                            long groupId, long categoryId, boolean includeTrashedEntries)
284                    throws PortalException {
285    
286                    List<MBThread> threads = mbThreadPersistence.findByG_C(
287                            groupId, categoryId);
288    
289                    for (MBThread thread : threads) {
290                            if (includeTrashedEntries || !thread.isInTrashExplicitly()) {
291                                    mbThreadLocalService.deleteThread(thread);
292                            }
293                    }
294    
295                    if (mbThreadPersistence.countByGroupId(groupId) == 0) {
296                            PortletFileRepositoryUtil.deletePortletRepository(
297                                    groupId, PortletKeys.MESSAGE_BOARDS);
298                    }
299            }
300    
301            @Override
302            public MBThread fetchThread(long threadId) {
303                    return mbThreadPersistence.fetchByPrimaryKey(threadId);
304            }
305    
306            @Override
307            public int getCategoryThreadsCount(
308                    long groupId, long categoryId, int status) {
309    
310                    if (status == WorkflowConstants.STATUS_ANY) {
311                            return mbThreadPersistence.countByG_C(groupId, categoryId);
312                    }
313                    else {
314                            return mbThreadPersistence.countByG_C_S(
315                                    groupId, categoryId, status);
316                    }
317            }
318    
319            /**
320             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreads(long,
321             *             QueryDefinition)}
322             */
323            @Deprecated
324            @Override
325            public List<MBThread> getGroupThreads(
326                    long groupId, int status, int start, int end) {
327    
328                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
329                            status, start, end, null);
330    
331                    return getGroupThreads(groupId, queryDefinition);
332            }
333    
334            @Override
335            public List<MBThread> getGroupThreads(
336                    long groupId, long userId, boolean subscribed, boolean includeAnonymous,
337                    QueryDefinition<MBThread> queryDefinition) {
338    
339                    if (userId <= 0) {
340                            return getGroupThreads(groupId, queryDefinition);
341                    }
342    
343                    if (subscribed) {
344                            return mbThreadFinder.findByS_G_U_C(
345                                    groupId, userId, null, queryDefinition);
346                    }
347                    else {
348                            if (includeAnonymous) {
349                                    return mbThreadFinder.findByG_U_C(
350                                            groupId, userId, null, queryDefinition);
351                            }
352                            else {
353                                    return mbThreadFinder.findByG_U_C_A(
354                                            groupId, userId, null, false, queryDefinition);
355                            }
356                    }
357            }
358    
359            @Override
360            public List<MBThread> getGroupThreads(
361                    long groupId, long userId, boolean subscribed,
362                    QueryDefinition<MBThread> queryDefinition) {
363    
364                    return getGroupThreads(
365                            groupId, userId, subscribed, true, queryDefinition);
366            }
367    
368            /**
369             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreads(long, long,
370             *             boolean, boolean, QueryDefinition)}
371             */
372            @Deprecated
373            @Override
374            public List<MBThread> getGroupThreads(
375                    long groupId, long userId, int status, boolean subscribed,
376                    boolean includeAnonymous, int start, int end) {
377    
378                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
379                            status, start, end, null);
380    
381                    return getGroupThreads(
382                            groupId, userId, subscribed, includeAnonymous, queryDefinition);
383            }
384    
385            /**
386             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreads(long, long,
387             *             boolean, QueryDefinition)}
388             */
389            @Deprecated
390            @Override
391            public List<MBThread> getGroupThreads(
392                    long groupId, long userId, int status, boolean subscribed, int start,
393                    int end) {
394    
395                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
396                            status, start, end, null);
397    
398                    return getGroupThreads(groupId, userId, subscribed, queryDefinition);
399            }
400    
401            /**
402             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreads(long, long,
403             *             QueryDefinition)}
404             */
405            @Deprecated
406            @Override
407            public List<MBThread> getGroupThreads(
408                    long groupId, long userId, int status, int start, int end) {
409    
410                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
411                            status, start, end, null);
412    
413                    return getGroupThreads(groupId, userId, false, queryDefinition);
414            }
415    
416            @Override
417            public List<MBThread> getGroupThreads(
418                    long groupId, long userId, QueryDefinition<MBThread> queryDefinition) {
419    
420                    return getGroupThreads(groupId, userId, false, queryDefinition);
421            }
422    
423            @Override
424            public List<MBThread> getGroupThreads(
425                    long groupId, QueryDefinition<MBThread> queryDefinition) {
426    
427                    if (queryDefinition.isExcludeStatus()) {
428                            return mbThreadPersistence.findByG_NotC_NotS(
429                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
430                                    queryDefinition.getStatus(), queryDefinition.getStart(),
431                                    queryDefinition.getEnd());
432                    }
433                    else {
434                            return mbThreadPersistence.findByG_NotC_S(
435                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
436                                    queryDefinition.getStatus(), queryDefinition.getStart(),
437                                    queryDefinition.getEnd());
438                    }
439            }
440    
441            /**
442             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreadsCount(long,
443             *             QueryDefinition)}
444             */
445            @Deprecated
446            @Override
447            public int getGroupThreadsCount(long groupId, int status) {
448                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
449                            status);
450    
451                    return getGroupThreadsCount(groupId, queryDefinition);
452            }
453    
454            @Override
455            public int getGroupThreadsCount(
456                    long groupId, long userId, boolean subscribed, boolean includeAnonymous,
457                    QueryDefinition<MBThread> queryDefinition) {
458    
459                    if (userId <= 0) {
460                            return getGroupThreadsCount(groupId, queryDefinition);
461                    }
462    
463                    if (subscribed) {
464                            return mbThreadFinder.countByS_G_U_C(
465                                    groupId, userId, null, queryDefinition);
466                    }
467                    else {
468                            if (includeAnonymous) {
469                                    return mbThreadFinder.countByG_U_C(
470                                            groupId, userId, null, queryDefinition);
471                            }
472                            else {
473                                    return mbThreadFinder.countByG_U_C_A(
474                                            groupId, userId, null, false, queryDefinition);
475                            }
476                    }
477            }
478    
479            @Override
480            public int getGroupThreadsCount(
481                    long groupId, long userId, boolean subscribed,
482                    QueryDefinition<MBThread> queryDefinition) {
483    
484                    return getGroupThreadsCount(
485                            groupId, userId, subscribed, true, queryDefinition);
486            }
487    
488            /**
489             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreadsCount(long,
490             *             long, QueryDefinition)}
491             */
492            @Deprecated
493            @Override
494            public int getGroupThreadsCount(long groupId, long userId, int status) {
495                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
496                            status);
497    
498                    return getGroupThreadsCount(groupId, userId, false, queryDefinition);
499            }
500    
501            /**
502             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreadsCount(long,
503             *             long, boolean, QueryDefinition)}
504             */
505            @Deprecated
506            @Override
507            public int getGroupThreadsCount(
508                    long groupId, long userId, int status, boolean subscribed) {
509    
510                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
511                            status);
512    
513                    return getGroupThreadsCount(
514                            groupId, userId, subscribed, true, queryDefinition);
515            }
516    
517            /**
518             * @deprecated As of 6.2.0, replaced by {@link #getGroupThreadsCount(long,
519             *             long, boolean, boolean, QueryDefinition)}
520             */
521            @Deprecated
522            @Override
523            public int getGroupThreadsCount(
524                    long groupId, long userId, int status, boolean subscribed,
525                    boolean includeAnonymous) {
526    
527                    QueryDefinition<MBThread> queryDefinition = new QueryDefinition<>(
528                            status);
529    
530                    return getGroupThreadsCount(
531                            groupId, userId, subscribed, includeAnonymous, queryDefinition);
532            }
533    
534            @Override
535            public int getGroupThreadsCount(
536                    long groupId, long userId, QueryDefinition<MBThread> queryDefinition) {
537    
538                    return getGroupThreadsCount(groupId, userId, false, queryDefinition);
539            }
540    
541            @Override
542            public int getGroupThreadsCount(
543                    long groupId, QueryDefinition<MBThread> queryDefinition) {
544    
545                    if (queryDefinition.isExcludeStatus()) {
546                            return mbThreadPersistence.countByG_NotC_NotS(
547                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
548                                    queryDefinition.getStatus());
549                    }
550                    else {
551                            return mbThreadPersistence.countByG_NotC_S(
552                                    groupId, MBCategoryConstants.DISCUSSION_CATEGORY_ID,
553                                    queryDefinition.getStatus());
554                    }
555            }
556    
557            @Override
558            public List<MBThread> getNoAssetThreads() {
559                    return mbThreadFinder.findByNoAssets();
560            }
561    
562            @Override
563            public List<MBThread> getPriorityThreads(long categoryId, double priority)
564                    throws PortalException {
565    
566                    return getPriorityThreads(categoryId, priority, false);
567            }
568    
569            @Override
570            public List<MBThread> getPriorityThreads(
571                            long categoryId, double priority, boolean inherit)
572                    throws PortalException {
573    
574                    if (!inherit) {
575                            return mbThreadPersistence.findByC_P(categoryId, priority);
576                    }
577    
578                    List<MBThread> threads = new ArrayList<>();
579    
580                    while ((categoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
581                               (categoryId != MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
582    
583                            threads.addAll(
584                                    0, mbThreadPersistence.findByC_P(categoryId, priority));
585    
586                            MBCategory category = mbCategoryPersistence.findByPrimaryKey(
587                                    categoryId);
588    
589                            categoryId = category.getParentCategoryId();
590                    }
591    
592                    return threads;
593            }
594    
595            @Override
596            public MBThread getThread(long threadId) throws PortalException {
597                    return mbThreadPersistence.findByPrimaryKey(threadId);
598            }
599    
600            @Override
601            public List<MBThread> getThreads(
602                    long groupId, long categoryId, int status, int start, int end) {
603    
604                    if (status == WorkflowConstants.STATUS_ANY) {
605                            return mbThreadPersistence.findByG_C(
606                                    groupId, categoryId, start, end);
607                    }
608                    else {
609                            return mbThreadPersistence.findByG_C_S(
610                                    groupId, categoryId, status, start, end);
611                    }
612            }
613    
614            @Override
615            public int getThreadsCount(long groupId, long categoryId, int status) {
616                    if (status == WorkflowConstants.STATUS_ANY) {
617                            return mbThreadPersistence.countByG_C(groupId, categoryId);
618                    }
619                    else {
620                            return mbThreadPersistence.countByG_C_S(
621                                    groupId, categoryId, status);
622                    }
623            }
624    
625            @Override
626            public boolean hasAnswerMessage(long threadId) {
627                    int count = mbMessagePersistence.countByT_A(threadId, true);
628    
629                    if (count > 0) {
630                            return true;
631                    }
632                    else {
633                            return false;
634                    }
635            }
636    
637            @BufferedIncrement(
638                    configuration = "MBThread", incrementClass = NumberIncrement.class
639            )
640            @Override
641            public void incrementViewCounter(long threadId, int increment)
642                    throws PortalException {
643    
644                    if (ExportImportThreadLocal.isImportInProcess()) {
645                            return;
646                    }
647    
648                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
649    
650                    thread.setViewCount(thread.getViewCount() + increment);
651    
652                    mbThreadPersistence.update(thread);
653            }
654    
655            @Override
656            public void moveDependentsToTrash(
657                            long groupId, long threadId, long trashEntryId)
658                    throws PortalException {
659    
660                    Set<Long> userIds = new HashSet<>();
661    
662                    List<MBMessage> messages = mbMessageLocalService.getThreadMessages(
663                            threadId, WorkflowConstants.STATUS_ANY);
664    
665                    for (MBMessage message : messages) {
666    
667                            // Message
668    
669                            if (message.isDiscussion()) {
670                                    continue;
671                            }
672    
673                            int oldStatus = message.getStatus();
674    
675                            message.setStatus(WorkflowConstants.STATUS_IN_TRASH);
676    
677                            mbMessagePersistence.update(message);
678    
679                            userIds.add(message.getUserId());
680    
681                            // Trash
682    
683                            int status = oldStatus;
684    
685                            if (oldStatus == WorkflowConstants.STATUS_PENDING) {
686                                    status = WorkflowConstants.STATUS_DRAFT;
687                            }
688    
689                            if (oldStatus != WorkflowConstants.STATUS_APPROVED) {
690                                    trashVersionLocalService.addTrashVersion(
691                                            trashEntryId, MBMessage.class.getName(),
692                                            message.getMessageId(), status, null);
693                            }
694    
695                            // Asset
696    
697                            if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
698                                    assetEntryLocalService.updateVisible(
699                                            MBMessage.class.getName(), message.getMessageId(), false);
700                            }
701    
702                            // Indexer
703    
704                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
705                                    MBMessage.class);
706    
707                            indexer.reindex(message);
708    
709                            // Workflow
710    
711                            if (oldStatus == WorkflowConstants.STATUS_PENDING) {
712                                    workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
713                                            message.getCompanyId(), message.getGroupId(),
714                                            MBMessage.class.getName(), message.getMessageId());
715                            }
716                    }
717    
718                    // Statistics
719    
720                    for (long userId : userIds) {
721                            mbStatsUserLocalService.updateStatsUser(groupId, userId);
722                    }
723            }
724    
725            @Override
726            public MBThread moveThread(long groupId, long categoryId, long threadId)
727                    throws PortalException {
728    
729                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
730    
731                    long oldCategoryId = thread.getCategoryId();
732    
733                    MBCategory oldCategory = null;
734    
735                    if (oldCategoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
736                            oldCategory = mbCategoryPersistence.fetchByPrimaryKey(
737                                    oldCategoryId);
738                    }
739    
740                    MBCategory category = null;
741    
742                    if (categoryId != MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
743                            category = mbCategoryPersistence.fetchByPrimaryKey(categoryId);
744                    }
745    
746                    // Thread
747    
748                    thread.setCategoryId(categoryId);
749    
750                    mbThreadPersistence.update(thread);
751    
752                    // Messages
753    
754                    List<MBMessage> messages = mbMessagePersistence.findByG_C_T(
755                            groupId, oldCategoryId, thread.getThreadId());
756    
757                    for (MBMessage message : messages) {
758                            message.setCategoryId(categoryId);
759    
760                            mbMessagePersistence.update(message);
761    
762                            // Indexer
763    
764                            if (!message.isDiscussion()) {
765                                    Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
766                                            MBMessage.class);
767    
768                                    indexer.reindex(message);
769                            }
770                    }
771    
772                    // Category
773    
774                    if ((oldCategory != null) && (categoryId != oldCategoryId)) {
775                            MBUtil.updateCategoryStatistics(
776                                    oldCategory.getCompanyId(), oldCategory.getCategoryId());
777                    }
778    
779                    if ((category != null) && (categoryId != oldCategoryId)) {
780                            MBUtil.updateCategoryStatistics(
781                                    category.getCompanyId(), category.getCategoryId());
782                    }
783    
784                    // Indexer
785    
786                    Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
787                            MBThread.class);
788    
789                    indexer.reindex(thread);
790    
791                    return thread;
792            }
793    
794            @Override
795            public MBThread moveThreadFromTrash(
796                            long userId, long categoryId, long threadId)
797                    throws PortalException {
798    
799                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
800    
801                    if (thread.isInTrashExplicitly()) {
802                            restoreThreadFromTrash(userId, threadId);
803                    }
804                    else {
805    
806                            // Thread
807    
808                            TrashVersion trashVersion = trashVersionLocalService.fetchVersion(
809                                    MBThread.class.getName(), thread.getThreadId());
810    
811                            int status = WorkflowConstants.STATUS_APPROVED;
812    
813                            if (trashVersion != null) {
814                                    status = trashVersion.getStatus();
815                            }
816    
817                            updateStatus(userId, threadId, status);
818    
819                            // Trash
820    
821                            if (trashVersion != null) {
822                                    trashVersionLocalService.deleteTrashVersion(trashVersion);
823                            }
824    
825                            // Messages
826    
827                            restoreDependentsFromTrash(thread.getGroupId(), threadId);
828                    }
829    
830                    return moveThread(thread.getGroupId(), categoryId, threadId);
831            }
832    
833            @Override
834            public void moveThreadsToTrash(long groupId, long userId)
835                    throws PortalException {
836    
837                    List<MBThread> threads = mbThreadPersistence.findByGroupId(groupId);
838    
839                    for (MBThread thread : threads) {
840                            moveThreadToTrash(userId, thread);
841                    }
842            }
843    
844            @Override
845            public MBThread moveThreadToTrash(long userId, long threadId)
846                    throws PortalException {
847    
848                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
849    
850                    return moveThreadToTrash(userId, thread);
851            }
852    
853            @Override
854            public MBThread moveThreadToTrash(long userId, MBThread thread)
855                    throws PortalException {
856    
857                    // Thread
858    
859                    if (thread.getCategoryId() ==
860                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID) {
861    
862                            return thread;
863                    }
864    
865                    int oldStatus = thread.getStatus();
866    
867                    if (oldStatus == WorkflowConstants.STATUS_PENDING) {
868                            thread.setStatus(WorkflowConstants.STATUS_DRAFT);
869    
870                            mbThreadPersistence.update(thread);
871                    }
872    
873                    thread = updateStatus(
874                            userId, thread.getThreadId(), WorkflowConstants.STATUS_IN_TRASH);
875    
876                    // Trash
877    
878                    TrashEntry trashEntry = trashEntryLocalService.addTrashEntry(
879                            userId, thread.getGroupId(), MBThread.class.getName(),
880                            thread.getThreadId(), thread.getUuid(), null, oldStatus, null,
881                            null);
882    
883                    // Messages
884    
885                    moveDependentsToTrash(
886                            thread.getGroupId(), thread.getThreadId(), trashEntry.getEntryId());
887    
888                    // Social
889    
890                    MBMessage message = mbMessageLocalService.getMBMessage(
891                            thread.getRootMessageId());
892    
893                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
894    
895                    extraDataJSONObject.put("rootMessageId", thread.getRootMessageId());
896                    extraDataJSONObject.put("title", message.getSubject());
897    
898                    socialActivityLocalService.addActivity(
899                            userId, thread.getGroupId(), MBThread.class.getName(),
900                            thread.getThreadId(), SocialActivityConstants.TYPE_MOVE_TO_TRASH,
901                            extraDataJSONObject.toString(), 0);
902    
903                    return thread;
904            }
905    
906            @Override
907            public void restoreDependentsFromTrash(long groupId, long threadId)
908                    throws PortalException {
909    
910                    Set<Long> userIds = new HashSet<>();
911    
912                    List<MBMessage> messages = mbMessageLocalService.getThreadMessages(
913                            threadId, WorkflowConstants.STATUS_ANY);
914    
915                    for (MBMessage message : messages) {
916    
917                            // Message
918    
919                            if (message.isDiscussion()) {
920                                    continue;
921                            }
922    
923                            TrashVersion trashVersion = trashVersionLocalService.fetchVersion(
924                                    MBMessage.class.getName(), message.getMessageId());
925    
926                            int oldStatus = WorkflowConstants.STATUS_APPROVED;
927    
928                            if (trashVersion != null) {
929                                    oldStatus = trashVersion.getStatus();
930                            }
931    
932                            message.setStatus(oldStatus);
933    
934                            mbMessagePersistence.update(message);
935    
936                            userIds.add(message.getUserId());
937    
938                            // Trash
939    
940                            if (trashVersion != null) {
941                                    trashVersionLocalService.deleteTrashVersion(trashVersion);
942                            }
943    
944                            // Asset
945    
946                            if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
947                                    assetEntryLocalService.updateVisible(
948                                            MBMessage.class.getName(), message.getMessageId(), true);
949                            }
950    
951                            // Indexer
952    
953                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
954                                    MBMessage.class);
955    
956                            indexer.reindex(message);
957                    }
958    
959                    // Statistics
960    
961                    for (long userId : userIds) {
962                            mbStatsUserLocalService.updateStatsUser(groupId, userId);
963                    }
964            }
965    
966            /**
967             * @deprecated As of 7.0.0, replaced by {@link
968             *             #restoreDependentsFromTrash(long, long)}
969             */
970            @Deprecated
971            @Override
972            public void restoreDependentsFromTrash(
973                            long groupId, long threadId, long trashEntryId)
974                    throws PortalException {
975    
976                    restoreDependentsFromTrash(groupId, threadId);
977            }
978    
979            @Override
980            public void restoreThreadFromTrash(long userId, long threadId)
981                    throws PortalException {
982    
983                    // Thread
984    
985                    MBThread thread = getThread(threadId);
986    
987                    if (thread.getCategoryId() ==
988                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID) {
989    
990                            return;
991                    }
992    
993                    TrashEntry trashEntry = trashEntryLocalService.getEntry(
994                            MBThread.class.getName(), threadId);
995    
996                    updateStatus(userId, threadId, trashEntry.getStatus());
997    
998                    // Messages
999    
1000                    restoreDependentsFromTrash(thread.getGroupId(), threadId);
1001    
1002                    // Trash
1003    
1004                    trashEntryLocalService.deleteEntry(trashEntry.getEntryId());
1005    
1006                    // Social
1007    
1008                    MBMessage message = mbMessageLocalService.getMBMessage(
1009                            thread.getRootMessageId());
1010    
1011                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
1012    
1013                    extraDataJSONObject.put("rootMessageId", thread.getRootMessageId());
1014                    extraDataJSONObject.put("title", message.getSubject());
1015    
1016                    socialActivityLocalService.addActivity(
1017                            userId, thread.getGroupId(), MBThread.class.getName(),
1018                            thread.getThreadId(),
1019                            SocialActivityConstants.TYPE_RESTORE_FROM_TRASH,
1020                            extraDataJSONObject.toString(), 0);
1021            }
1022    
1023            @Override
1024            public Hits search(
1025                            long groupId, long userId, long creatorUserId, int status,
1026                            int start, int end)
1027                    throws PortalException {
1028    
1029                    return search(groupId, userId, creatorUserId, 0, 0, status, start, end);
1030            }
1031    
1032            @Override
1033            public Hits search(
1034                            long groupId, long userId, long creatorUserId, long startDate,
1035                            long endDate, int status, int start, int end)
1036                    throws PortalException {
1037    
1038                    Indexer indexer = IndexerRegistryUtil.getIndexer(
1039                            MBThread.class.getName());
1040    
1041                    SearchContext searchContext = new SearchContext();
1042    
1043                    searchContext.setAttribute(Field.STATUS, status);
1044    
1045                    if (endDate > 0) {
1046                            searchContext.setAttribute("endDate", endDate);
1047                    }
1048    
1049                    searchContext.setAttribute("paginationType", "none");
1050    
1051                    if (creatorUserId > 0) {
1052                            searchContext.setAttribute(
1053                                    "participantUserId", String.valueOf(creatorUserId));
1054                    }
1055    
1056                    if (startDate > 0) {
1057                            searchContext.setAttribute("startDate", startDate);
1058                    }
1059    
1060                    Group group = groupLocalService.getGroup(groupId);
1061    
1062                    searchContext.setCompanyId(group.getCompanyId());
1063    
1064                    searchContext.setEnd(end);
1065                    searchContext.setGroupIds(new long[] {groupId});
1066                    searchContext.setSorts(new Sort("lastPostDate", true));
1067                    searchContext.setStart(start);
1068                    searchContext.setUserId(userId);
1069    
1070                    return indexer.search(searchContext);
1071            }
1072    
1073            @Override
1074            public MBThread splitThread(
1075                            long messageId, String subject, ServiceContext serviceContext)
1076                    throws PortalException {
1077    
1078                    MBMessage message = mbMessagePersistence.findByPrimaryKey(messageId);
1079    
1080                    if (message.isRoot()) {
1081                            throw new SplitThreadException();
1082                    }
1083    
1084                    MBCategory category = message.getCategory();
1085                    MBThread oldThread = message.getThread();
1086                    MBMessage rootMessage = mbMessagePersistence.findByPrimaryKey(
1087                            oldThread.getRootMessageId());
1088    
1089                    // Message flags
1090    
1091                    mbMessageLocalService.updateAnswer(message, false, true);
1092    
1093                    // Create new thread
1094    
1095                    MBThread thread = addThread(
1096                            message.getCategoryId(), message, serviceContext);
1097    
1098                    mbThreadPersistence.update(oldThread);
1099    
1100                    // Update messages
1101    
1102                    if (Validator.isNotNull(subject)) {
1103                            MBMessageDisplay messageDisplay =
1104                                    mbMessageService.getMessageDisplay(
1105                                            messageId, WorkflowConstants.STATUS_ANY,
1106                                            MBThreadConstants.THREAD_VIEW_TREE, false);
1107    
1108                            MBTreeWalker treeWalker = messageDisplay.getTreeWalker();
1109    
1110                            List<MBMessage> messages = treeWalker.getMessages();
1111    
1112                            int[] range = treeWalker.getChildrenRange(message);
1113    
1114                            for (int i = range[0]; i < range[1]; i++) {
1115                                    MBMessage curMessage = messages.get(i);
1116    
1117                                    String oldSubject = message.getSubject();
1118                                    String curSubject = curMessage.getSubject();
1119    
1120                                    if (oldSubject.startsWith("RE: ")) {
1121                                            curSubject = StringUtil.replace(
1122                                                    curSubject, rootMessage.getSubject(), subject);
1123                                    }
1124                                    else {
1125                                            curSubject = StringUtil.replace(
1126                                                    curSubject, oldSubject, subject);
1127                                    }
1128    
1129                                    curMessage.setSubject(curSubject);
1130    
1131                                    mbMessagePersistence.update(curMessage);
1132                            }
1133    
1134                            message.setSubject(subject);
1135                    }
1136    
1137                    message.setThreadId(thread.getThreadId());
1138                    message.setRootMessageId(thread.getRootMessageId());
1139                    message.setParentMessageId(0);
1140    
1141                    mbMessagePersistence.update(message);
1142    
1143                    // Indexer
1144    
1145                    if (!message.isDiscussion()) {
1146                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1147                                    MBMessage.class);
1148    
1149                            indexer.reindex(message);
1150                    }
1151    
1152                    // Update children
1153    
1154                    moveChildrenMessages(message, category, oldThread.getThreadId());
1155    
1156                    // Update new thread
1157    
1158                    MBUtil.updateThreadMessageCount(
1159                            thread.getCompanyId(), thread.getThreadId());
1160    
1161                    // Update old thread
1162    
1163                    MBUtil.updateThreadMessageCount(
1164                            oldThread.getCompanyId(), oldThread.getThreadId());
1165    
1166                    // Category
1167    
1168                    if ((message.getCategoryId() !=
1169                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) &&
1170                            (message.getCategoryId() !=
1171                                    MBCategoryConstants.DISCUSSION_CATEGORY_ID)) {
1172    
1173                            MBUtil.updateCategoryThreadCount(
1174                                    category.getCompanyId(), category.getCategoryId());
1175                    }
1176    
1177                    // Indexer
1178    
1179                    Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1180                            MBThread.class);
1181    
1182                    indexer.reindex(oldThread);
1183                    indexer.reindex(message.getThread());
1184    
1185                    return thread;
1186            }
1187    
1188            @Override
1189            public void updateQuestion(long threadId, boolean question)
1190                    throws PortalException {
1191    
1192                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
1193    
1194                    if (thread.isQuestion() == question) {
1195                            return;
1196                    }
1197    
1198                    thread.setQuestion(question);
1199    
1200                    mbThreadPersistence.update(thread);
1201    
1202                    if (!question) {
1203                            MBMessage message = mbMessagePersistence.findByPrimaryKey(
1204                                    thread.getRootMessageId());
1205    
1206                            mbMessageLocalService.updateAnswer(message, false, true);
1207                    }
1208            }
1209    
1210            @Override
1211            public MBThread updateStatus(long userId, long threadId, int status)
1212                    throws PortalException {
1213    
1214                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
1215    
1216                    // Thread
1217    
1218                    User user = userPersistence.findByPrimaryKey(userId);
1219    
1220                    thread.setStatus(status);
1221                    thread.setStatusByUserId(user.getUserId());
1222                    thread.setStatusByUserName(user.getFullName());
1223                    thread.setStatusDate(new Date());
1224    
1225                    mbThreadPersistence.update(thread);
1226    
1227                    // Messages
1228    
1229                    if (thread.getCategoryId() !=
1230                                    MBCategoryConstants.DEFAULT_PARENT_CATEGORY_ID) {
1231    
1232                            // Category
1233    
1234                            MBCategory category = mbCategoryPersistence.fetchByPrimaryKey(
1235                                    thread.getCategoryId());
1236    
1237                            if (category != null) {
1238                                    MBUtil.updateCategoryStatistics(
1239                                            category.getCompanyId(), category.getCategoryId());
1240                            }
1241                    }
1242    
1243                    // Indexer
1244    
1245                    Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1246                            MBThread.class);
1247    
1248                    indexer.reindex(thread);
1249    
1250                    return thread;
1251            }
1252    
1253            /**
1254             * @deprecated As of 6.2.0, replaced by {@link #incrementViewCounter(long,
1255             *             int)}
1256             */
1257            @Deprecated
1258            @Override
1259            public MBThread updateThread(long threadId, int viewCount)
1260                    throws PortalException {
1261    
1262                    MBThread thread = mbThreadPersistence.findByPrimaryKey(threadId);
1263    
1264                    thread.setViewCount(viewCount);
1265    
1266                    mbThreadPersistence.update(thread);
1267    
1268                    return thread;
1269            }
1270    
1271            protected void moveChildrenMessages(
1272                            MBMessage parentMessage, MBCategory category, long oldThreadId)
1273                    throws PortalException {
1274    
1275                    List<MBMessage> messages = mbMessagePersistence.findByT_P(
1276                            oldThreadId, parentMessage.getMessageId());
1277    
1278                    for (MBMessage message : messages) {
1279                            message.setCategoryId(parentMessage.getCategoryId());
1280                            message.setThreadId(parentMessage.getThreadId());
1281                            message.setRootMessageId(parentMessage.getRootMessageId());
1282    
1283                            mbMessagePersistence.update(message);
1284    
1285                            if (!message.isDiscussion()) {
1286                                    Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
1287                                            MBMessage.class);
1288    
1289                                    indexer.reindex(message);
1290                            }
1291    
1292                            moveChildrenMessages(message, category, oldThreadId);
1293                    }
1294            }
1295    
1296    }