com.liferay.knowledgebase.service.impl.KBArticleLocalServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.liferay.knowledgebase.service.impl.KBArticleLocalServiceImpl.java

Source

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.knowledgebase.service.impl;

import com.liferay.knowledgebase.DuplicateKBArticleUrlTitleException;
import com.liferay.knowledgebase.InvalidKBArticleUrlTitleException;
import com.liferay.knowledgebase.KBArticleContentException;
import com.liferay.knowledgebase.KBArticleParentException;
import com.liferay.knowledgebase.KBArticlePriorityException;
import com.liferay.knowledgebase.KBArticleSourceURLException;
import com.liferay.knowledgebase.KBArticleTitleException;
import com.liferay.knowledgebase.NoSuchArticleException;
import com.liferay.knowledgebase.admin.importer.KBArticleImporter;
import com.liferay.knowledgebase.admin.social.AdminActivityKeys;
import com.liferay.knowledgebase.admin.util.AdminSubscriptionSender;
import com.liferay.knowledgebase.admin.util.AdminUtil;
import com.liferay.knowledgebase.model.KBArticle;
import com.liferay.knowledgebase.model.KBArticleConstants;
import com.liferay.knowledgebase.model.KBFolder;
import com.liferay.knowledgebase.model.KBFolderConstants;
import com.liferay.knowledgebase.service.KBArticleLocalServiceUtil;
import com.liferay.knowledgebase.service.base.KBArticleLocalServiceBaseImpl;
import com.liferay.knowledgebase.util.KnowledgeBaseConstants;
import com.liferay.knowledgebase.util.KnowledgeBaseUtil;
import com.liferay.knowledgebase.util.PortletKeys;
import com.liferay.knowledgebase.util.PortletPropsValues;
import com.liferay.knowledgebase.util.comparator.KBArticlePriorityComparator;
import com.liferay.knowledgebase.util.comparator.KBArticleVersionComparator;
import com.liferay.portal.kernel.bean.BeanPropertiesUtil;
import com.liferay.portal.kernel.dao.orm.Conjunction;
import com.liferay.portal.kernel.dao.orm.Criterion;
import com.liferay.portal.kernel.dao.orm.Disjunction;
import com.liferay.portal.kernel.dao.orm.DynamicQuery;
import com.liferay.portal.kernel.dao.orm.DynamicQueryFactoryUtil;
import com.liferay.portal.kernel.dao.orm.Junction;
import com.liferay.portal.kernel.dao.orm.Property;
import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.repository.model.FileEntry;
import com.liferay.portal.kernel.search.Indexer;
import com.liferay.portal.kernel.search.IndexerRegistryUtil;
import com.liferay.portal.kernel.search.SearchEngineUtil;
import com.liferay.portal.kernel.systemevent.SystemEvent;
import com.liferay.portal.kernel.systemevent.SystemEventHierarchyEntryThreadLocal;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.OrderByComparator;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.TempFileEntryUtil;
import com.liferay.portal.kernel.util.Time;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.workflow.WorkflowConstants;
import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil;
import com.liferay.portal.model.Group;
import com.liferay.portal.model.ResourceConstants;
import com.liferay.portal.model.Subscription;
import com.liferay.portal.model.SystemEventConstants;
import com.liferay.portal.model.User;
import com.liferay.portal.portletfilerepository.PortletFileRepositoryUtil;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.util.SubscriptionSender;
import com.liferay.portlet.asset.model.AssetEntry;
import com.liferay.portlet.asset.model.AssetLink;
import com.liferay.portlet.asset.model.AssetLinkConstants;

import java.io.InputStream;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.portlet.PortletPreferences;

/**
 * @author Peter Shin
 * @author Brian Wing Shun Chan
 * @author Edward Han
 */
public class KBArticleLocalServiceImpl extends KBArticleLocalServiceBaseImpl {

    @Override
    public KBArticle addKBArticle(long userId, long parentResourceClassNameId, long parentResourcePrimKey,
            String title, String urlTitle, String content, String description, String sourceURL, String[] sections,
            String[] selectedFileNames, ServiceContext serviceContext) throws PortalException {

        // KB article

        User user = userPersistence.findByPrimaryKey(userId);
        long groupId = serviceContext.getScopeGroupId();
        double priority = getPriority(groupId, parentResourcePrimKey);
        Date now = new Date();

        validate(title, content, sourceURL);
        validateParent(parentResourceClassNameId, parentResourcePrimKey);

        long kbFolderId = KnowledgeBaseUtil.getKBFolderId(parentResourceClassNameId, parentResourcePrimKey);

        validateUrlTitle(groupId, kbFolderId, urlTitle);

        long kbArticleId = counterLocalService.increment();

        long resourcePrimKey = counterLocalService.increment();

        long rootResourcePrimKey = getRootResourcePrimKey(resourcePrimKey, parentResourceClassNameId,
                parentResourcePrimKey);

        KBArticle kbArticle = kbArticlePersistence.create(kbArticleId);

        kbArticle.setUuid(serviceContext.getUuid());
        kbArticle.setResourcePrimKey(resourcePrimKey);
        kbArticle.setGroupId(groupId);
        kbArticle.setCompanyId(user.getCompanyId());
        kbArticle.setUserId(user.getUserId());
        kbArticle.setUserName(user.getFullName());
        kbArticle.setCreateDate(serviceContext.getCreateDate(now));
        kbArticle.setModifiedDate(serviceContext.getModifiedDate(now));
        kbArticle.setRootResourcePrimKey(rootResourcePrimKey);
        kbArticle.setParentResourceClassNameId(parentResourceClassNameId);
        kbArticle.setParentResourcePrimKey(parentResourcePrimKey);
        kbArticle.setKbFolderId(kbFolderId);
        kbArticle.setVersion(KBArticleConstants.DEFAULT_VERSION);
        kbArticle.setTitle(title);
        kbArticle.setUrlTitle(getUniqueUrlTitle(groupId, kbFolderId, kbArticleId, title, urlTitle));
        kbArticle.setContent(content);
        kbArticle.setDescription(description);
        kbArticle.setPriority(priority);
        kbArticle.setSections(StringUtil.merge(AdminUtil.escapeSections(sections)));
        kbArticle.setViewCount(0);
        kbArticle.setLatest(true);
        kbArticle.setMain(false);
        kbArticle.setStatus(WorkflowConstants.STATUS_DRAFT);
        kbArticle.setSourceURL(sourceURL);

        kbArticlePersistence.update(kbArticle);

        // Resources

        if (serviceContext.isAddGroupPermissions() || serviceContext.isAddGuestPermissions()) {

            addKBArticleResources(kbArticle, serviceContext.isAddGroupPermissions(),
                    serviceContext.isAddGuestPermissions());
        } else {
            addKBArticleResources(kbArticle, serviceContext.getGroupPermissions(),
                    serviceContext.getGuestPermissions());
        }

        // Asset

        updateKBArticleAsset(userId, kbArticle, serviceContext.getAssetCategoryIds(),
                serviceContext.getAssetTagNames(), serviceContext.getAssetLinkEntryIds());

        // Attachments

        addKBArticleAttachments(userId, kbArticle, selectedFileNames);

        // Workflow

        WorkflowHandlerRegistryUtil.startWorkflowInstance(user.getCompanyId(), groupId, userId,
                KBArticle.class.getName(), resourcePrimKey, kbArticle, serviceContext);

        return kbArticle;
    }

    @Override
    public void addKBArticleResources(KBArticle kbArticle, boolean addGroupPermissions, boolean addGuestPermissions)
            throws PortalException {

        resourceLocalService.addResources(kbArticle.getCompanyId(), kbArticle.getGroupId(), kbArticle.getUserId(),
                KBArticle.class.getName(), kbArticle.getResourcePrimKey(), false, addGroupPermissions,
                addGuestPermissions);
    }

    @Override
    public void addKBArticleResources(KBArticle kbArticle, String[] groupPermissions, String[] guestPermissions)
            throws PortalException {

        resourceLocalService.addModelResources(kbArticle.getCompanyId(), kbArticle.getGroupId(),
                kbArticle.getUserId(), KBArticle.class.getName(), kbArticle.getResourcePrimKey(), groupPermissions,
                guestPermissions);
    }

    @Override
    public void addKBArticleResources(long kbArticleId, boolean addGroupPermissions, boolean addGuestPermissions)
            throws PortalException {

        KBArticle kbArticle = kbArticlePersistence.findByPrimaryKey(kbArticleId);

        addKBArticleResources(kbArticle, addGroupPermissions, addGuestPermissions);
    }

    @Override
    public void addKBArticleResources(long kbArticleId, String[] groupPermissions, String[] guestPermissions)
            throws PortalException {

        KBArticle kbArticle = kbArticlePersistence.findByPrimaryKey(kbArticleId);

        addKBArticleResources(kbArticle, groupPermissions, guestPermissions);
    }

    @Override
    public void addKBArticlesMarkdown(long userId, long groupId, long parentKbFolderId, String fileName,
            InputStream inputStream, ServiceContext serviceContext) throws PortalException {

        KBArticleImporter kbArticleImporter = new KBArticleImporter();

        kbArticleImporter.processZipFile(userId, groupId, parentKbFolderId, inputStream, serviceContext);
    }

    @Override
    public void addTempAttachment(long groupId, long userId, String fileName, String tempFolderName,
            InputStream inputStream, String mimeType) throws PortalException {

        TempFileEntryUtil.addTempFileEntry(groupId, userId, fileName, tempFolderName, inputStream, mimeType);
    }

    @Override
    public void deleteGroupKBArticles(long groupId) throws PortalException {

        // KB articles

        deleteKBArticles(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID);

        // Subscriptions

        Group group = groupLocalService.getGroup(groupId);

        List<Subscription> subscriptions = subscriptionLocalService.getSubscriptions(group.getCompanyId(),
                KBArticle.class.getName(), groupId);

        for (Subscription subscription : subscriptions) {
            unsubscribeGroupKBArticles(subscription.getUserId(), groupId);
        }
    }

    @Override
    @SystemEvent(action = SystemEventConstants.ACTION_SKIP, type = SystemEventConstants.TYPE_DELETE)
    public KBArticle deleteKBArticle(KBArticle kbArticle) throws PortalException {

        // Child KB articles

        deleteKBArticles(kbArticle.getGroupId(), kbArticle.getResourcePrimKey());

        // Resources

        resourceLocalService.deleteResource(kbArticle.getCompanyId(), KBArticle.class.getName(),
                ResourceConstants.SCOPE_INDIVIDUAL, kbArticle.getResourcePrimKey());

        // KB articles

        kbArticlePersistence.removeByResourcePrimKey(kbArticle.getResourcePrimKey());

        // KB comments

        kbCommentLocalService.deleteKBComments(KBArticle.class.getName(), kbArticle.getResourcePrimKey());

        // Asset

        deleteAssets(kbArticle);

        // Ratings

        ratingsStatsLocalService.deleteStats(KBArticle.class.getName(), kbArticle.getResourcePrimKey());

        // Social

        socialActivityLocalService.deleteActivities(KBArticle.class.getName(), kbArticle.getResourcePrimKey());

        // Indexer

        Indexer indexer = IndexerRegistryUtil.getIndexer(KBArticle.class);

        indexer.delete(kbArticle);

        // Attachments

        PortletFileRepositoryUtil.deletePortletFolder(kbArticle.getAttachmentsFolderId());

        // Subscriptions

        deleteSubscriptions(kbArticle);

        // Workflow

        workflowInstanceLinkLocalService.deleteWorkflowInstanceLinks(kbArticle.getCompanyId(),
                kbArticle.getGroupId(), KBArticle.class.getName(), kbArticle.getResourcePrimKey());

        return kbArticle;
    }

    @Override
    public KBArticle deleteKBArticle(long resourcePrimKey) throws PortalException {

        KBArticle kbArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        return kbArticleLocalService.deleteKBArticle(kbArticle);
    }

    @Override
    public void deleteKBArticles(long groupId, long parentResourcePrimKey) throws PortalException {

        List<KBArticle> childKBArticles = getKBArticles(groupId, parentResourcePrimKey,
                WorkflowConstants.STATUS_ANY, QueryUtil.ALL_POS, QueryUtil.ALL_POS, null);

        for (KBArticle childKBArticle : childKBArticles) {
            kbArticleLocalService.deleteKBArticle(childKBArticle);
        }
    }

    @Override
    public void deleteKBArticles(long[] resourcePrimKeys) throws PortalException {

        List<KBArticle> kbArticles = getKBArticles(resourcePrimKeys, WorkflowConstants.STATUS_ANY, null);

        for (KBArticle kbArticle : kbArticles) {
            kbArticleLocalService.deleteKBArticle(kbArticle);
        }
    }

    @Override
    public void deleteTempAttachment(long groupId, long userId, String fileName, String tempFolderName)
            throws PortalException {

        TempFileEntryUtil.deleteTempFileEntry(groupId, userId, fileName, tempFolderName);
    }

    @Override
    public KBArticle fetchKBArticleByUrlTitle(long groupId, long kbFolderId, String urlTitle) {

        urlTitle = StringUtil.replaceFirst(urlTitle, StringPool.SLASH, StringPool.BLANK);

        KBArticle kbArticle = fetchLatestKBArticleByUrlTitle(groupId, kbFolderId, urlTitle,
                WorkflowConstants.STATUS_APPROVED);

        if (kbArticle == null) {
            kbArticle = fetchLatestKBArticleByUrlTitle(groupId, kbFolderId, urlTitle,
                    WorkflowConstants.STATUS_PENDING);
        }

        return kbArticle;
    }

    @Override
    public KBArticle fetchKBArticleByUrlTitle(long groupId, String kbFolderUrlTitle, String urlTitle)
            throws PortalException {

        urlTitle = StringUtil.replaceFirst(urlTitle, StringPool.SLASH, StringPool.BLANK);

        List<KBArticle> kbArticles = kbArticleFinder.findByUrlTitle(groupId, kbFolderUrlTitle, urlTitle, _STATUSES,
                0, 1);

        if (kbArticles.isEmpty()) {
            return null;
        }

        return kbArticles.get(0);
    }

    @Override
    public KBArticle fetchLatestKBArticle(long resourcePrimKey, int status) {
        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.fetchByResourcePrimKey_First(resourcePrimKey,
                    new KBArticleVersionComparator());
        }

        return kbArticlePersistence.fetchByR_S_First(resourcePrimKey, status, new KBArticleVersionComparator());
    }

    @Override
    public KBArticle fetchLatestKBArticleByUrlTitle(long groupId, long kbFolderId, String urlTitle, int status) {

        urlTitle = StringUtil.replaceFirst(urlTitle, StringPool.SLASH, StringPool.BLANK);

        List<KBArticle> kbArticles = null;

        OrderByComparator<KBArticle> orderByComparator = new KBArticleVersionComparator();

        if (status == WorkflowConstants.STATUS_ANY) {
            kbArticles = kbArticlePersistence.findByG_KBFI_UT(groupId, kbFolderId, urlTitle, 0, 1,
                    orderByComparator);
        } else {
            kbArticles = kbArticlePersistence.findByG_KBFI_UT_ST(groupId, kbFolderId, urlTitle, status, 0, 1,
                    orderByComparator);
        }

        if (kbArticles.isEmpty()) {
            return null;
        }

        return kbArticles.get(0);
    }

    @Override
    public List<KBArticle> getAllDescendantKBArticles(long resourcePrimKey, int status,
            OrderByComparator<KBArticle> orderByComparator) {

        return getAllDescendantKBArticles(resourcePrimKey, status, orderByComparator, false);
    }

    @Override
    public List<KBArticle> getCompanyKBArticles(long companyId, int status, int start, int end,
            OrderByComparator<KBArticle> orderByComparator) {

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.findByC_L(companyId, true, start, end, orderByComparator);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.findByC_M(companyId, true, start, end, orderByComparator);
        }

        return kbArticlePersistence.findByC_S(companyId, status, start, end, orderByComparator);
    }

    @Override
    public int getCompanyKBArticlesCount(long companyId, int status) {
        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.countByC_L(companyId, true);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.countByC_M(companyId, true);
        }

        return kbArticlePersistence.countByC_S(companyId, status);
    }

    @Override
    public List<KBArticle> getGroupKBArticles(long groupId, int status, int start, int end,
            OrderByComparator<KBArticle> orderByComparator) {

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.findByG_L(groupId, true, start, end, orderByComparator);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.findByG_M(groupId, true, start, end, orderByComparator);
        }

        return kbArticlePersistence.findByG_S(groupId, status, start, end, orderByComparator);
    }

    @Override
    public int getGroupKBArticlesCount(long groupId, int status) {
        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.countByG_L(groupId, true);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.countByG_M(groupId, true);
        }

        return kbArticlePersistence.countByG_S(groupId, status);
    }

    @Override
    public KBArticle getKBArticle(long resourcePrimKey, int version) throws PortalException {

        return kbArticlePersistence.findByR_V(resourcePrimKey, version);
    }

    @Override
    public List<KBArticle> getKBArticleAndAllDescendantKBArticles(long resourcePrimKey, int status,
            OrderByComparator<KBArticle> orderByComparator) {

        return getAllDescendantKBArticles(resourcePrimKey, status, orderByComparator, true);
    }

    /**
     * @deprecated As of 7.0.0, replaced by
     *             {@link #getKBArticleAndAllDescendantKBArticles(long, int,
     *             com.liferay.portal.kernel.util.OrderByComparator)}
     */
    @Override
    public List<KBArticle> getKBArticleAndAllDescendants(long resourcePrimKey, int status,
            OrderByComparator<KBArticle> orderByComparator) {

        return getKBArticleAndAllDescendantKBArticles(resourcePrimKey, status, orderByComparator);
    }

    @Override
    public KBArticle getKBArticleByUrlTitle(long groupId, long kbFolderId, String urlTitle) throws PortalException {

        urlTitle = StringUtil.replaceFirst(urlTitle, StringPool.SLASH, StringPool.BLANK);

        // Get the latest KB article that is approved, if none are approved, get
        // the latest unapproved KB article

        KBArticle kbArticle = fetchKBArticleByUrlTitle(groupId, kbFolderId, urlTitle);

        if (kbArticle == null) {
            throw new NoSuchArticleException("No KBArticle exists with the key {groupId=" + groupId
                    + ", kbFolderId=" + kbFolderId + ", urlTitle=" + urlTitle + "}");
        }

        return kbArticle;
    }

    @Override
    public KBArticle getKBArticleByUrlTitle(long groupId, String kbFolderUrlTitle, String urlTitle)
            throws PortalException {

        urlTitle = StringUtil.replaceFirst(urlTitle, StringPool.SLASH, StringPool.BLANK);

        KBArticle kbArticle = fetchKBArticleByUrlTitle(groupId, kbFolderUrlTitle, urlTitle);

        if (kbArticle == null) {
            throw new NoSuchArticleException("No KBArticle with the key {groupId=" + groupId + ", urlTitle="
                    + urlTitle + "} found in a folder with URL " + "title " + kbFolderUrlTitle);
        }

        return kbArticle;
    }

    @Override
    public List<KBArticle> getKBArticles(long groupId, long parentResourcePrimKey, int status, int start, int end,
            OrderByComparator<KBArticle> orderByComparator) {

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.findByG_P_L(groupId, parentResourcePrimKey, true, start, end,
                    orderByComparator);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.findByG_P_M(groupId, parentResourcePrimKey, true, start, end,
                    orderByComparator);
        }

        return kbArticlePersistence.findByG_P_S(groupId, parentResourcePrimKey, status, start, end,
                orderByComparator);
    }

    @Override
    public List<KBArticle> getKBArticles(long[] resourcePrimKeys, int status,
            OrderByComparator<KBArticle> orderByComparator) {

        List<KBArticle> kbArticles = new ArrayList<KBArticle>();

        Long[][] params = new Long[][] { ArrayUtil.toArray(resourcePrimKeys) };

        while ((params = KnowledgeBaseUtil.getParams(params[0])) != null) {
            List<KBArticle> curKBArticles = null;

            if (status == WorkflowConstants.STATUS_ANY) {
                curKBArticles = kbArticlePersistence.findByR_L(ArrayUtil.toArray(params[1]), true);
            } else if (status == WorkflowConstants.STATUS_APPROVED) {
                curKBArticles = kbArticlePersistence.findByR_M(ArrayUtil.toArray(params[1]), true);
            } else {
                curKBArticles = kbArticlePersistence.findByR_S(ArrayUtil.toArray(params[1]), status);
            }

            kbArticles.addAll(curKBArticles);
        }

        if (orderByComparator != null) {
            kbArticles = ListUtil.sort(kbArticles, orderByComparator);
        } else {
            kbArticles = KnowledgeBaseUtil.sort(resourcePrimKeys, kbArticles);
        }

        return Collections.unmodifiableList(kbArticles);
    }

    @Override
    public int getKBArticlesCount(long groupId, long parentResourcePrimKey, int status) {

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.countByG_P_L(groupId, parentResourcePrimKey, true);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.countByG_P_M(groupId, parentResourcePrimKey, true);
        }

        return kbArticlePersistence.countByG_P_S(groupId, parentResourcePrimKey, status);
    }

    @Override
    public List<KBArticle> getKBArticleVersions(long resourcePrimKey, int status, int start, int end,
            OrderByComparator<KBArticle> orderByComparator) {

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.findByResourcePrimKey(resourcePrimKey, start, end, orderByComparator);
        }

        return kbArticlePersistence.findByR_S(resourcePrimKey, status, start, end, orderByComparator);
    }

    @Override
    public int getKBArticleVersionsCount(long resourcePrimKey, int status) {
        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.countByResourcePrimKey(resourcePrimKey);
        }

        return kbArticlePersistence.countByR_S(resourcePrimKey, status);
    }

    @Override
    public KBArticle getLatestKBArticle(long resourcePrimKey, int status) throws PortalException {

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.findByResourcePrimKey_First(resourcePrimKey,
                    new KBArticleVersionComparator());
        }

        return kbArticlePersistence.findByR_S_First(resourcePrimKey, status, new KBArticleVersionComparator());
    }

    @Override
    public KBArticle getLatestKBArticleByUrlTitle(long groupId, long kbFolderId, String urlTitle, int status)
            throws PortalException {

        urlTitle = StringUtil.replaceFirst(urlTitle, StringPool.SLASH, StringPool.BLANK);

        KBArticle latestKBArticle = fetchLatestKBArticleByUrlTitle(groupId, kbFolderId, urlTitle, status);

        if (latestKBArticle == null) {
            throw new NoSuchArticleException("No KBArticle exists with the key {groupId=" + groupId
                    + ", kbFolderId=" + kbFolderId + ", urlTitle=" + urlTitle + ", status=" + status + "}");
        }

        return latestKBArticle;
    }

    @Override
    public KBArticle[] getPreviousAndNextKBArticles(long kbArticleId) throws PortalException {

        KBArticle kbArticle = kbArticlePersistence.findByPrimaryKey(kbArticleId);

        KBArticle[] previousAndNextKBArticles = kbArticlePersistence.findByG_P_L_PrevAndNext(kbArticleId,
                kbArticle.getGroupId(), kbArticle.getParentResourcePrimKey(), true,
                new KBArticlePriorityComparator(true));

        KBArticle previousKBArticle = getPreviousKBArticle(kbArticle, previousAndNextKBArticles);
        KBArticle nextKBArticle = getNextKBArticle(kbArticle, previousAndNextKBArticles);

        return new KBArticle[] { previousKBArticle, kbArticle, nextKBArticle };
    }

    @Override
    public List<KBArticle> getSectionsKBArticles(long groupId, String[] sections, int status, int start, int end,
            OrderByComparator<KBArticle> orderByComparator) {

        String[] array = AdminUtil.escapeSections(sections);

        for (int i = 0; i < array.length; i++) {
            array[i] = StringUtil.quote(array[i], StringPool.PERCENT);
        }

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.findByG_P_S_L(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID, array,
                    true, start, end, orderByComparator);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.findByG_P_S_M(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID, array,
                    true, start, end, orderByComparator);
        }

        return kbArticlePersistence.findByG_P_S_S(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID, array,
                status, start, end, orderByComparator);
    }

    @Override
    public int getSectionsKBArticlesCount(long groupId, String[] sections, int status) {

        String[] array = AdminUtil.escapeSections(sections);

        for (int i = 0; i < array.length; i++) {
            array[i] = StringUtil.quote(array[i], StringPool.PERCENT);
        }

        if (status == WorkflowConstants.STATUS_ANY) {
            return kbArticlePersistence.countByG_P_S_L(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID, array,
                    true);
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            return kbArticlePersistence.countByG_P_S_M(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID, array,
                    true);
        }

        return kbArticlePersistence.countByG_P_S_S(groupId, KBFolderConstants.DEFAULT_PARENT_FOLDER_ID, array,
                status);
    }

    /**
     * @deprecated As of 7.0.0, replaced by {@link #getKBArticles(long, long,
     *             int, int, int,
     *             com.liferay.portal.kernel.util.OrderByComparator)}
     */
    @Override
    public List<KBArticle> getSiblingKBArticles(long groupId, long parentResourcePrimKey, int status, int start,
            int end, OrderByComparator<KBArticle> orderByComparator) {

        return getKBArticles(groupId, parentResourcePrimKey, status, start, end, orderByComparator);
    }

    /**
     * @deprecated As of 7.0.0, replaced by {@link #getKBArticlesCount(long,
     *             long, int)}
     */
    @Override
    public int getSiblingKBArticlesCount(long groupId, long parentResourcePrimKey, int status) {

        return getKBArticlesCount(groupId, parentResourcePrimKey, status);
    }

    @Override
    public String[] getTempAttachmentNames(long groupId, long userId, String tempFolderName)
            throws PortalException {

        return TempFileEntryUtil.getTempFileNames(groupId, userId, tempFolderName);
    }

    @Override
    public void moveKBArticle(long userId, long resourcePrimKey, long parentResourceClassNameId,
            long parentResourcePrimKey, double priority) throws PortalException {

        // KB article

        validate(priority);

        updatePermissionFields(resourcePrimKey, parentResourceClassNameId, parentResourcePrimKey);

        long kbFolderClassNameId = classNameLocalService.getClassNameId(KBFolderConstants.getClassName());

        long kbFolderId = KBFolderConstants.DEFAULT_PARENT_FOLDER_ID;

        if (parentResourceClassNameId == kbFolderClassNameId) {
            kbFolderId = parentResourcePrimKey;
        } else {
            KBArticle latestKBArticle = getLatestKBArticle(parentResourcePrimKey, WorkflowConstants.STATUS_ANY);

            kbFolderId = latestKBArticle.getKbFolderId();
        }

        List<KBArticle> kbArticles = getKBArticleVersions(resourcePrimKey, WorkflowConstants.STATUS_ANY,
                QueryUtil.ALL_POS, QueryUtil.ALL_POS, new KBArticleVersionComparator());

        for (KBArticle curKBArticle : kbArticles) {
            curKBArticle.setParentResourceClassNameId(parentResourceClassNameId);
            curKBArticle.setParentResourcePrimKey(parentResourcePrimKey);

            if (parentResourceClassNameId == kbFolderClassNameId) {
                curKBArticle.setKbFolderId(kbFolderId);
            }

            curKBArticle.setPriority(priority);

            kbArticlePersistence.update(curKBArticle);
        }

        KBArticle kbArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        if (kbArticle.getKbFolderId() != kbFolderId) {
            List<KBArticle> descendantKBArticles = getAllDescendantKBArticles(resourcePrimKey,
                    WorkflowConstants.STATUS_ANY, null);

            for (KBArticle curKBArticle : descendantKBArticles) {
                List<KBArticle> kbArticleVersions = getKBArticleVersions(curKBArticle.getResourcePrimKey(),
                        WorkflowConstants.STATUS_ANY, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
                        new KBArticleVersionComparator());

                for (KBArticle kbArticleVersion : kbArticleVersions) {
                    kbArticleVersion.setKbFolderId(kbFolderId);

                    kbArticlePersistence.update(kbArticleVersion);
                }
            }
        }

        // Social

        KBArticle latestKBArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

        extraDataJSONObject.put("title", latestKBArticle.getTitle());

        if (latestKBArticle.isApproved() || !latestKBArticle.isFirstVersion()) {
            socialActivityLocalService.addActivity(userId, latestKBArticle.getGroupId(), KBArticle.class.getName(),
                    resourcePrimKey, AdminActivityKeys.MOVE_KB_ARTICLE, extraDataJSONObject.toString(), 0);
        }
    }

    @Override
    public List<KBArticle> search(long groupId, String title, String content, int status, Date startDate,
            Date endDate, boolean andOperator, int start, int end, OrderByComparator<KBArticle> orderByComparator) {

        DynamicQuery dynamicQuery = buildDynamicQuery(groupId, title, content, status, startDate, endDate,
                andOperator);

        return dynamicQuery(dynamicQuery, start, end, orderByComparator);
    }

    @Override
    public void subscribeGroupKBArticles(long userId, long groupId) throws PortalException {

        subscriptionLocalService.addSubscription(userId, groupId, KBArticle.class.getName(), groupId);
    }

    @Override
    public void subscribeKBArticle(long userId, long groupId, long resourcePrimKey) throws PortalException {

        subscriptionLocalService.addSubscription(userId, groupId, KBArticle.class.getName(), resourcePrimKey);
    }

    @Override
    public void unsubscribeGroupKBArticles(long userId, long groupId) throws PortalException {

        subscriptionLocalService.deleteSubscription(userId, KBArticle.class.getName(), groupId);
    }

    @Override
    public void unsubscribeKBArticle(long userId, long resourcePrimKey) throws PortalException {

        subscriptionLocalService.deleteSubscription(userId, KBArticle.class.getName(), resourcePrimKey);
    }

    @Override
    public KBArticle updateKBArticle(long userId, long resourcePrimKey, String title, String content,
            String description, String sourceURL, String[] sections, String[] selectedFileNames,
            long[] removeFileEntryIds, ServiceContext serviceContext) throws PortalException {

        // KB article

        User user = userPersistence.findByPrimaryKey(userId);

        validate(title, content, sourceURL);

        KBArticle oldKBArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        int oldVersion = oldKBArticle.getVersion();

        KBArticle kbArticle = null;

        if (oldKBArticle.isApproved()) {
            long kbArticleId = counterLocalService.increment();

            kbArticle = kbArticlePersistence.create(kbArticleId);

            kbArticle.setUuid(serviceContext.getUuid());
            kbArticle.setResourcePrimKey(oldKBArticle.getResourcePrimKey());
            kbArticle.setGroupId(oldKBArticle.getGroupId());
            kbArticle.setCompanyId(user.getCompanyId());
            kbArticle.setUserId(user.getUserId());
            kbArticle.setUserName(user.getFullName());
            kbArticle.setCreateDate(oldKBArticle.getCreateDate());
            kbArticle.setRootResourcePrimKey(oldKBArticle.getRootResourcePrimKey());
            kbArticle.setParentResourceClassNameId(oldKBArticle.getParentResourceClassNameId());
            kbArticle.setParentResourcePrimKey(oldKBArticle.getParentResourcePrimKey());
            kbArticle.setKbFolderId(oldKBArticle.getKbFolderId());
            kbArticle.setVersion(oldVersion + 1);
            kbArticle.setUrlTitle(oldKBArticle.getUrlTitle());
            kbArticle.setPriority(oldKBArticle.getPriority());
            kbArticle.setViewCount(oldKBArticle.getViewCount());
        } else {
            kbArticle = oldKBArticle;
        }

        if (oldKBArticle.isPending()) {
            kbArticle.setStatus(WorkflowConstants.STATUS_PENDING);
        } else {
            kbArticle.setStatus(WorkflowConstants.STATUS_DRAFT);
        }

        kbArticle.setModifiedDate(serviceContext.getModifiedDate(null));
        kbArticle.setTitle(title);
        kbArticle.setContent(content);
        kbArticle.setDescription(description);
        kbArticle.setSourceURL(sourceURL);
        kbArticle.setSections(StringUtil.merge(AdminUtil.escapeSections(sections)));
        kbArticle.setLatest(true);
        kbArticle.setMain(false);

        kbArticlePersistence.update(kbArticle);

        if (oldKBArticle.isApproved()) {
            oldKBArticle.setLatest(false);

            kbArticlePersistence.update(oldKBArticle);
        }

        // Resources

        if ((serviceContext.getGroupPermissions() != null) || (serviceContext.getGuestPermissions() != null)) {

            updateKBArticleResources(kbArticle, serviceContext.getGroupPermissions(),
                    serviceContext.getGuestPermissions());
        }

        // Asset

        updateKBArticleAsset(userId, kbArticle, serviceContext.getAssetCategoryIds(),
                serviceContext.getAssetTagNames(), serviceContext.getAssetLinkEntryIds());

        // Attachments

        addKBArticleAttachments(userId, kbArticle, selectedFileNames);

        removeKBArticleAttachments(removeFileEntryIds);

        // Workflow

        WorkflowHandlerRegistryUtil.startWorkflowInstance(user.getCompanyId(), kbArticle.getGroupId(), userId,
                KBArticle.class.getName(), resourcePrimKey, kbArticle, serviceContext);

        return kbArticle;
    }

    @Override
    public void updateKBArticleAsset(long userId, KBArticle kbArticle, long[] assetCategoryIds,
            String[] assetTagNames, long[] assetLinkEntryIds) throws PortalException {

        // TODO

        long classTypeId = 0;

        AssetEntry assetEntry = assetEntryLocalService.updateEntry(userId, kbArticle.getGroupId(),
                kbArticle.getCreateDate(), kbArticle.getModifiedDate(), KBArticle.class.getName(),
                kbArticle.getClassPK(), kbArticle.getUuid(), classTypeId, assetCategoryIds, assetTagNames, false,
                null, null, null, ContentTypes.TEXT_HTML, kbArticle.getTitle(), kbArticle.getDescription(), null,
                null, null, 0, 0, null, false);

        assetLinkLocalService.updateLinks(userId, assetEntry.getEntryId(), assetLinkEntryIds,
                AssetLinkConstants.TYPE_RELATED);
    }

    @Override
    public void updateKBArticleResources(KBArticle kbArticle, String[] groupPermissions, String[] guestPermissions)
            throws PortalException {

        resourceLocalService.updateResources(kbArticle.getCompanyId(), kbArticle.getGroupId(),
                KBArticle.class.getName(), kbArticle.getResourcePrimKey(), groupPermissions, guestPermissions);
    }

    @Override
    public void updateKBArticlesPriorities(Map<Long, Double> resourcePrimKeyToPriorityMap) throws PortalException {

        for (double priority : resourcePrimKeyToPriorityMap.values()) {
            validate(priority);
        }

        long[] resourcePrimKeys = StringUtil.split(StringUtil.merge(resourcePrimKeyToPriorityMap.keySet()), 0L);

        List<KBArticle> kbArticles = getKBArticles(resourcePrimKeys, WorkflowConstants.STATUS_ANY, null);

        for (KBArticle kbArticle : kbArticles) {
            double priority = resourcePrimKeyToPriorityMap.get(kbArticle.getResourcePrimKey());

            updatePriority(kbArticle.getResourcePrimKey(), priority);
        }
    }

    @Override
    public void updatePriority(long resourcePrimKey, double priority) {
        List<KBArticle> kbArticleVersions = getKBArticleVersions(resourcePrimKey, WorkflowConstants.STATUS_ANY,
                QueryUtil.ALL_POS, QueryUtil.ALL_POS, null);

        for (KBArticle kbArticle : kbArticleVersions) {
            kbArticle.setPriority(priority);

            kbArticlePersistence.update(kbArticle);
        }
    }

    @Override
    public KBArticle updateStatus(long userId, long resourcePrimKey, int status, ServiceContext serviceContext)
            throws PortalException {

        // KB article

        User user = userPersistence.findByPrimaryKey(userId);
        boolean main = false;
        Date now = new Date();

        if (status == WorkflowConstants.STATUS_APPROVED) {
            main = true;
        }

        KBArticle kbArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        kbArticle.setModifiedDate(serviceContext.getModifiedDate(now));
        kbArticle.setMain(main);
        kbArticle.setStatus(status);
        kbArticle.setStatusByUserId(user.getUserId());
        kbArticle.setStatusByUserName(user.getFullName());
        kbArticle.setStatusDate(serviceContext.getModifiedDate(now));

        kbArticlePersistence.update(kbArticle);

        if (status != WorkflowConstants.STATUS_APPROVED) {
            return kbArticle;
        }

        if (!kbArticle.isFirstVersion()) {
            KBArticle oldKBArticle = kbArticlePersistence.findByR_V(resourcePrimKey, kbArticle.getVersion() - 1);

            oldKBArticle.setMain(false);

            kbArticlePersistence.update(oldKBArticle);
        }

        // Asset

        AssetEntry assetEntry = assetEntryLocalService.getEntry(KBArticle.class.getName(),
                kbArticle.getKbArticleId());

        List<AssetLink> assetLinks = assetLinkLocalService.getDirectLinks(assetEntry.getEntryId(),
                AssetLinkConstants.TYPE_RELATED);

        long[] assetLinkEntryIds = StringUtil.split(ListUtil.toString(assetLinks, AssetLink.ENTRY_ID2_ACCESSOR),
                0L);

        updateKBArticleAsset(userId, kbArticle, assetEntry.getCategoryIds(), assetEntry.getTagNames(),
                assetLinkEntryIds);

        SystemEventHierarchyEntryThreadLocal.push(KBArticle.class);

        try {
            assetEntryLocalService.deleteEntry(KBArticle.class.getName(), kbArticle.getKbArticleId());
        } finally {
            SystemEventHierarchyEntryThreadLocal.pop(KBArticle.class);
        }

        assetEntryLocalService.updateVisible(KBArticle.class.getName(), kbArticle.getResourcePrimKey(), true);

        // Social

        JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

        extraDataJSONObject.put("title", kbArticle.getTitle());

        if (!kbArticle.isFirstVersion()) {
            socialActivityLocalService.addActivity(userId, kbArticle.getGroupId(), KBArticle.class.getName(),
                    resourcePrimKey, AdminActivityKeys.UPDATE_KB_ARTICLE, extraDataJSONObject.toString(), 0);
        } else {
            socialActivityLocalService.addActivity(userId, kbArticle.getGroupId(), KBArticle.class.getName(),
                    resourcePrimKey, AdminActivityKeys.ADD_KB_ARTICLE, extraDataJSONObject.toString(), 0);
        }

        // Indexer

        Indexer indexer = IndexerRegistryUtil.getIndexer(KBArticle.class);

        indexer.reindex(kbArticle);

        // Subscriptions

        notifySubscribers(kbArticle, serviceContext);

        return kbArticle;
    }

    @Override
    public void updateViewCount(long userId, long resourcePrimKey, int viewCount) throws PortalException {

        KBArticle kbArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        kbArticle.setViewCount(viewCount);

        kbArticlePersistence.update(kbArticle);

        if (kbArticle.isApproved() || kbArticle.isFirstVersion()) {
            return;
        }

        kbArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_APPROVED);

        kbArticle.setViewCount(viewCount);

        kbArticlePersistence.update(kbArticle);
    }

    protected void addAttachment(long userId, long resourcePrimKey, String fileName, InputStream inputStream,
            String mimeType) throws PortalException {

        KBArticle kbArticle = KBArticleLocalServiceUtil.getLatestKBArticle(resourcePrimKey,
                WorkflowConstants.STATUS_ANY);

        PortletFileRepositoryUtil.addPortletFileEntry(kbArticle.getGroupId(), userId, KBArticle.class.getName(),
                kbArticle.getClassPK(), PortletKeys.KNOWLEDGE_BASE_ARTICLE, kbArticle.getAttachmentsFolderId(),
                inputStream, fileName, mimeType, false);
    }

    protected void addKBArticleAttachment(long userId, long groupId, long resourcePrimKey, String selectedFileName)
            throws PortalException {

        FileEntry tempFileEntry = TempFileEntryUtil.getTempFileEntry(groupId, userId, selectedFileName,
                KnowledgeBaseConstants.TEMP_FOLDER_NAME);

        InputStream inputStream = tempFileEntry.getContentStream();
        String mimeType = tempFileEntry.getMimeType();

        addAttachment(userId, resourcePrimKey, selectedFileName, inputStream, mimeType);

        if (tempFileEntry != null) {
            TempFileEntryUtil.deleteTempFileEntry(tempFileEntry.getFileEntryId());
        }
    }

    protected void addKBArticleAttachments(long userId, KBArticle kbArticle, String[] selectedFileNames)
            throws PortalException {

        if (ArrayUtil.isEmpty(selectedFileNames)) {
            return;
        }

        for (String selectedFileName : selectedFileNames) {
            addKBArticleAttachment(userId, kbArticle.getGroupId(), kbArticle.getResourcePrimKey(),
                    selectedFileName);
        }
    }

    protected DynamicQuery buildDynamicQuery(long groupId, String title, String content, int status, Date startDate,
            Date endDate, boolean andOperator) {

        Junction junction = null;

        if (andOperator) {
            junction = RestrictionsFactoryUtil.conjunction();
        } else {
            junction = RestrictionsFactoryUtil.disjunction();
        }

        Map<String, String> terms = new HashMap<String, String>();

        if (Validator.isNotNull(title)) {
            terms.put("title", title);
        }

        if (Validator.isNotNull(content)) {
            terms.put("content", content);
        }

        for (Map.Entry<String, String> entry : terms.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();

            Disjunction disjunction = RestrictionsFactoryUtil.disjunction();

            for (String keyword : KnowledgeBaseUtil.splitKeywords(value)) {
                Criterion criterion = RestrictionsFactoryUtil.ilike(key,
                        StringUtil.quote(keyword, StringPool.PERCENT));

                disjunction.add(criterion);
            }

            junction.add(disjunction);
        }

        if (status != WorkflowConstants.STATUS_ANY) {
            Property property = PropertyFactoryUtil.forName("status");

            junction.add(property.eq(status));
        }

        if ((endDate != null) && (startDate != null)) {
            Disjunction disjunction = RestrictionsFactoryUtil.disjunction();

            String[] propertyNames = { "createDate", "modifiedDate" };

            for (String propertyName : propertyNames) {
                Property property = PropertyFactoryUtil.forName(propertyName);

                Conjunction conjunction = RestrictionsFactoryUtil.conjunction();

                conjunction.add(property.gt(startDate));
                conjunction.add(property.lt(endDate));

                disjunction.add(conjunction);
            }

            junction.add(disjunction);
        }

        DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(KBArticle.class, getClassLoader());

        if (status == WorkflowConstants.STATUS_ANY) {
            Property property = PropertyFactoryUtil.forName("latest");

            dynamicQuery.add(property.eq(Boolean.TRUE));
        } else if (status == WorkflowConstants.STATUS_APPROVED) {
            Property property = PropertyFactoryUtil.forName("main");

            dynamicQuery.add(property.eq(Boolean.TRUE));
        }

        if (groupId > 0) {
            Property property = PropertyFactoryUtil.forName("groupId");

            dynamicQuery.add(property.eq(groupId));
        }

        return dynamicQuery.add(junction);
    }

    protected void deleteAssets(KBArticle kbArticle) throws PortalException {
        assetEntryLocalService.deleteEntry(KBArticle.class.getName(), kbArticle.getClassPK());

        if (!kbArticle.isApproved() && !kbArticle.isFirstVersion()) {
            assetEntryLocalService.deleteEntry(KBArticle.class.getName(), kbArticle.getResourcePrimKey());
        }
    }

    protected void deleteSubscriptions(KBArticle kbArticle) throws PortalException {

        List<Subscription> subscriptions = subscriptionLocalService.getSubscriptions(kbArticle.getCompanyId(),
                KBArticle.class.getName(), kbArticle.getResourcePrimKey());

        for (Subscription subscription : subscriptions) {
            unsubscribeKBArticle(subscription.getUserId(), subscription.getClassPK());
        }
    }

    protected List<KBArticle> getAllDescendantKBArticles(long resourcePrimKey, int status,
            OrderByComparator<KBArticle> orderByComparator, boolean includeParentArticle) {

        List<KBArticle> kbArticles = null;

        if (includeParentArticle) {
            kbArticles = getKBArticles(new long[] { resourcePrimKey }, status, null);

            kbArticles = ListUtil.copy(kbArticles);
        } else {
            kbArticles = new ArrayList<KBArticle>();
        }

        Long[][] params = new Long[][] { new Long[] { resourcePrimKey } };

        while ((params = KnowledgeBaseUtil.getParams(params[0])) != null) {
            List<KBArticle> curKBArticles = null;

            if (status == WorkflowConstants.STATUS_ANY) {
                curKBArticles = kbArticlePersistence.findByP_L(ArrayUtil.toArray(params[1]), true);
            } else if (status == WorkflowConstants.STATUS_APPROVED) {
                curKBArticles = kbArticlePersistence.findByP_M(ArrayUtil.toArray(params[1]), true);
            } else {
                curKBArticles = kbArticlePersistence.findByP_S(ArrayUtil.toArray(params[1]), status);
            }

            kbArticles.addAll(curKBArticles);

            long[] resourcePrimKeys = StringUtil.split(ListUtil.toString(curKBArticles, "resourcePrimKey"), 0L);

            params[0] = ArrayUtil.append(params[0], ArrayUtil.toArray(resourcePrimKeys));
        }

        if (orderByComparator != null) {
            kbArticles = ListUtil.sort(kbArticles, orderByComparator);
        }

        return Collections.unmodifiableList(kbArticles);
    }

    protected Map<String, String> getEmailKBArticleDiffs(KBArticle kbArticle) {
        Map<String, String> emailKBArticleDiffs = new HashMap<String, String>();

        for (String param : new String[] { "content", "title" }) {
            String value = BeanPropertiesUtil.getString(kbArticle, param);

            try {
                value = AdminUtil.getKBArticleDiff(kbArticle.getResourcePrimKey(), kbArticle.getVersion() - 1,
                        kbArticle.getVersion(), param);
            } catch (Exception e) {
                _log.error(e, e);
            }

            emailKBArticleDiffs.put(param, value);
        }

        return emailKBArticleDiffs;
    }

    protected KBArticle getNextAncestorKBArticle(long kbArticleId, KBArticle[] previousAndNextKBArticles)
            throws PortalException {

        KBArticle kbArticle = kbArticlePersistence.findByPrimaryKey(kbArticleId);

        KBArticle nextKBArticle = previousAndNextKBArticles[2];

        if (nextKBArticle != null) {
            return nextKBArticle;
        }

        KBArticle parentKBArticle = kbArticle.getParentKBArticle();

        if (parentKBArticle == null) {
            return null;
        }

        previousAndNextKBArticles = kbArticlePersistence.findByG_P_L_PrevAndNext(parentKBArticle.getKbArticleId(),
                kbArticle.getGroupId(), parentKBArticle.getParentResourcePrimKey(), true,
                new KBArticlePriorityComparator(true));

        return getNextAncestorKBArticle(parentKBArticle.getKbArticleId(), previousAndNextKBArticles);
    }

    protected KBArticle getNextKBArticle(KBArticle kbArticle, KBArticle[] previousAndNextKBArticles)
            throws PortalException {

        KBArticle firstChildKBArticle = kbArticlePersistence.fetchByG_P_L_First(kbArticle.getGroupId(),
                kbArticle.getResourcePrimKey(), true, new KBArticlePriorityComparator(true));

        if (firstChildKBArticle != null) {
            return firstChildKBArticle;
        }

        return getNextAncestorKBArticle(kbArticle.getKbArticleId(), previousAndNextKBArticles);
    }

    protected KBArticle getPreviousKBArticle(KBArticle kbArticle, KBArticle[] previousAndNextKBArticles)
            throws PortalException {

        KBArticle previousKBArticle = previousAndNextKBArticles[0];

        if (previousKBArticle != null) {
            KBArticle lastSiblingChildKBArticle = kbArticlePersistence.fetchByG_P_L_Last(kbArticle.getGroupId(),
                    previousKBArticle.getResourcePrimKey(), true, new KBArticlePriorityComparator(true));

            if (lastSiblingChildKBArticle != null) {
                previousKBArticle = lastSiblingChildKBArticle;
            }
        } else {
            previousKBArticle = kbArticle.getParentKBArticle();
        }

        return previousKBArticle;
    }

    protected double getPriority(long groupId, long parentResourcePrimKey) {
        if (!PortletPropsValues.ADMIN_KB_ARTICLE_INCREMENT_PRIORITY_ENABLED) {
            return KBArticleConstants.DEFAULT_VERSION;
        }

        List<KBArticle> kbArticles = getKBArticles(groupId, parentResourcePrimKey, WorkflowConstants.STATUS_ANY, 0,
                1, new KBArticlePriorityComparator());

        if (kbArticles.isEmpty()) {
            return KBArticleConstants.DEFAULT_PRIORITY;
        }

        KBArticle kbArticle = kbArticles.get(0);

        return Math.floor(kbArticle.getPriority()) + 1;
    }

    protected long getRootResourcePrimKey(long resourcePrimKey, long parentResourceClassNameId,
            long parentResourcePrimKey) throws PortalException {

        if (parentResourcePrimKey == KBFolderConstants.DEFAULT_PARENT_FOLDER_ID) {

            return resourcePrimKey;
        }

        long classNameId = classNameLocalService.getClassNameId(KBArticleConstants.getClassName());

        if (parentResourceClassNameId == classNameId) {
            KBArticle kbArticle = getLatestKBArticle(parentResourcePrimKey, WorkflowConstants.STATUS_ANY);

            return kbArticle.getRootResourcePrimKey();
        }

        return resourcePrimKey;
    }

    protected Date getTicketExpirationDate() {
        return new Date(System.currentTimeMillis() + _TICKET_EXPIRATION);
    }

    protected String getUniqueUrlTitle(long groupId, long kbFolderId, long kbArticleId, String title)
            throws PortalException {

        String urlTitle = KnowledgeBaseUtil.getUrlTitle(kbArticleId, title);

        String uniqueUrlTitle = urlTitle;

        if (kbFolderId == KBFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
            int kbArticleCount = kbArticlePersistence.countByG_KBFI_UT_ST(groupId, kbFolderId, uniqueUrlTitle,
                    _STATUSES);

            for (int i = 1; kbArticleCount > 0; i++) {
                uniqueUrlTitle = urlTitle + StringPool.DASH + i;

                kbArticleCount = kbArticlePersistence.countByG_KBFI_UT_ST(groupId, kbFolderId, uniqueUrlTitle,
                        _STATUSES);
            }

            return uniqueUrlTitle;
        }

        KBFolder kbFolder = kbFolderPersistence.findByPrimaryKey(kbFolderId);

        int kbArticleCount = kbArticleFinder.countByUrlTitle(groupId, kbFolder.getUrlTitle(), uniqueUrlTitle,
                _STATUSES);

        for (int i = 1; kbArticleCount > 0; i++) {
            uniqueUrlTitle = urlTitle + StringPool.DASH + i;

            kbArticleCount = kbArticleFinder.countByUrlTitle(groupId, kbFolder.getUrlTitle(), uniqueUrlTitle,
                    _STATUSES);
        }

        return uniqueUrlTitle;
    }

    protected String getUniqueUrlTitle(long groupId, long kbFolderId, long kbArticleId, String title,
            String urlTitle) throws PortalException {

        if (Validator.isNull(urlTitle)) {
            return getUniqueUrlTitle(groupId, kbFolderId, kbArticleId, title);
        }

        return urlTitle.substring(1);
    }

    protected boolean isValidFileName(String name) {
        if ((name == null) || name.contains(StringPool.BACK_SLASH) || name.contains(StringPool.SLASH)) {

            return false;
        }

        return true;
    }

    protected void notifySubscribers(KBArticle kbArticle, ServiceContext serviceContext) throws PortalException {

        if (Validator.isNull(serviceContext.getLayoutFullURL())) {
            return;
        }

        PortletPreferences preferences = portletPreferencesLocalService.getPreferences(kbArticle.getCompanyId(),
                kbArticle.getGroupId(), PortletKeys.PREFS_OWNER_TYPE_GROUP, PortletKeys.PREFS_PLID_SHARED,
                PortletKeys.KNOWLEDGE_BASE_ADMIN, null);

        if (serviceContext.isCommandAdd() && !AdminUtil.getEmailKBArticleAddedEnabled(preferences)) {

            return;
        }

        if (serviceContext.isCommandUpdate() && !AdminUtil.getEmailKBArticleUpdatedEnabled(preferences)) {

            return;
        }

        String fromName = AdminUtil.getEmailFromName(preferences, kbArticle.getCompanyId());
        String fromAddress = AdminUtil.getEmailFromAddress(preferences, kbArticle.getCompanyId());

        String kbArticleContent = StringUtil.replace(kbArticle.getContent(), new String[] { "href=\"/", "src=\"/" },
                new String[] { "href=\"" + serviceContext.getPortalURL() + "/",
                        "src=\"" + serviceContext.getPortalURL() + "/" });

        Map<String, String> kbArticleDiffs = getEmailKBArticleDiffs(kbArticle);

        for (String key : kbArticleDiffs.keySet()) {
            String value = StringUtil.replace(kbArticleDiffs.get(key), new String[] { "href=\"/", "src=\"/" },
                    new String[] { "href=\"" + serviceContext.getPortalURL() + "/",
                            "src=\"" + serviceContext.getPortalURL() + "/" });

            kbArticleDiffs.put(key, value);
        }

        String subject = null;
        String body = null;

        if (serviceContext.isCommandAdd()) {
            subject = AdminUtil.getEmailKBArticleAddedSubject(preferences);
            body = AdminUtil.getEmailKBArticleAddedBody(preferences);
        } else {
            subject = AdminUtil.getEmailKBArticleUpdatedSubject(preferences);
            body = AdminUtil.getEmailKBArticleUpdatedBody(preferences);
        }

        SubscriptionSender subscriptionSender = new AdminSubscriptionSender(kbArticle, serviceContext);

        subscriptionSender.setBody(body);
        subscriptionSender.setCompanyId(kbArticle.getCompanyId());
        subscriptionSender.setContextAttribute("[$ARTICLE_CONTENT$]", kbArticleContent, false);
        subscriptionSender.setContextAttribute("[$ARTICLE_CONTENT_DIFF$]", kbArticleDiffs.get("content"), false);
        subscriptionSender.setContextAttribute("[$ARTICLE_TITLE$]", kbArticle.getTitle(), false);
        subscriptionSender.setContextAttribute("[$ARTICLE_TITLE_DIFF$]", kbArticleDiffs.get("title"), false);
        subscriptionSender.setContextUserPrefix("ARTICLE");
        subscriptionSender.setFrom(fromAddress, fromName);
        subscriptionSender.setHtmlFormat(true);
        subscriptionSender.setMailId("kb_article", kbArticle.getKbArticleId());
        subscriptionSender.setPortletId(serviceContext.getPortletId());
        subscriptionSender.setReplyToAddress(fromAddress);
        subscriptionSender.setScopeGroupId(kbArticle.getGroupId());
        subscriptionSender.setSubject(subject);
        subscriptionSender.setUserId(kbArticle.getUserId());

        subscriptionSender.addPersistedSubscribers(KBArticle.class.getName(), kbArticle.getGroupId());
        subscriptionSender.addPersistedSubscribers(KBArticle.class.getName(), kbArticle.getResourcePrimKey());

        while (!kbArticle.isRoot() && (kbArticle.getClassNameId() == kbArticle.getParentResourceClassNameId())) {

            kbArticle = getLatestKBArticle(kbArticle.getParentResourcePrimKey(), WorkflowConstants.STATUS_APPROVED);

            subscriptionSender.addPersistedSubscribers(KBArticle.class.getName(), kbArticle.getResourcePrimKey());
        }

        subscriptionSender.flushNotificationsAsync();
    }

    protected void removeKBArticleAttachments(long[] removeFileEntryIds) throws PortalException {

        if (ArrayUtil.isEmpty(removeFileEntryIds)) {
            return;
        }

        for (long removeFileEntryId : removeFileEntryIds) {
            PortletFileRepositoryUtil.deletePortletFileEntry(removeFileEntryId);
        }
    }

    protected void updatePermissionFields(long resourcePrimKey, long parentResourceClassNameId,
            long parentResourcePrimKey) throws PortalException {

        // See KBArticlePermission#contains

        KBArticle kbArticle = getLatestKBArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);

        if (kbArticle.getParentResourcePrimKey() == parentResourcePrimKey) {
            return;
        }

        long rootResourcePrimKey = getRootResourcePrimKey(resourcePrimKey, parentResourceClassNameId,
                parentResourcePrimKey);

        if (kbArticle.getRootResourcePrimKey() == rootResourcePrimKey) {
            return;
        }

        // Sync database

        List<KBArticle> kbArticles1 = getKBArticleAndAllDescendantKBArticles(resourcePrimKey,
                WorkflowConstants.STATUS_ANY, null);

        for (KBArticle kbArticle1 : kbArticles1) {
            List<KBArticle> kbArticles2 = getKBArticleVersions(kbArticle1.getResourcePrimKey(),
                    WorkflowConstants.STATUS_ANY, QueryUtil.ALL_POS, QueryUtil.ALL_POS, null);

            for (KBArticle kbArticle2 : kbArticles2) {
                kbArticle2.setRootResourcePrimKey(rootResourcePrimKey);

                kbArticlePersistence.update(kbArticle2);
            }
        }

        // Sync indexed permission fields

        SearchEngineUtil.updatePermissionFields(KBArticle.class.getName(), String.valueOf(resourcePrimKey));
    }

    protected void validate(double priority) throws PortalException {
        if (priority <= 0) {
            throw new KBArticlePriorityException();
        }
    }

    protected void validate(String title, String content, String sourceURL) throws PortalException {

        if (Validator.isNull(title)) {
            throw new KBArticleTitleException();
        }

        if (Validator.isNull(content)) {
            throw new KBArticleContentException();
        }

        validateSourceURL(sourceURL);
    }

    protected void validateParent(long resourceClassNameId, long resourcePrimKey) throws PortalException {

        long kbArticleClassNameId = classNameLocalService.getClassNameId(KBArticleConstants.getClassName());
        long kbFolderClassNameId = classNameLocalService.getClassNameId(KBFolderConstants.getClassName());

        if ((resourceClassNameId != kbArticleClassNameId) && (resourceClassNameId != kbFolderClassNameId)) {

            throw new KBArticleParentException(
                    String.format("Invalid parent with resource class name ID %s and " + "resource primary key %s",
                            resourceClassNameId, resourcePrimKey));
        }
    }

    protected void validateSourceURL(String sourceURL) throws PortalException {
        if (Validator.isNull(sourceURL)) {
            return;
        }

        if (!Validator.isUrl(sourceURL)) {
            throw new KBArticleSourceURLException(sourceURL);
        }
    }

    protected void validateUrlTitle(long groupId, long kbFolderId, String urlTitle) throws PortalException {

        if (Validator.isNull(urlTitle)) {
            return;
        }

        if (!KnowledgeBaseUtil.isValidUrlTitle(urlTitle)) {
            throw new InvalidKBArticleUrlTitleException("URL title must start with a '/' and contain only "
                    + "alphanumeric characters, dashes, and underscores");
        }

        Collection<KBArticle> kbArticles = kbArticlePersistence.findByG_KBFI_UT(groupId, kbFolderId,
                urlTitle.substring(1));

        if (!kbArticles.isEmpty()) {
            throw new DuplicateKBArticleUrlTitleException("Duplicate URL title " + urlTitle);
        }
    }

    private static final int[] _STATUSES = { WorkflowConstants.STATUS_APPROVED, WorkflowConstants.STATUS_PENDING };

    private static final long _TICKET_EXPIRATION = Time.HOUR;

    private static Log _log = LogFactoryUtil.getLog(KBArticleLocalServiceImpl.class);

}