All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.knowledge.base.service.impl.KBArticleLocalServiceImpl Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/**
 * 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.knowledge.base.service.impl;

import aQute.bnd.annotation.ProviderType;

import com.liferay.asset.kernel.model.AssetEntry;
import com.liferay.asset.kernel.model.AssetLink;
import com.liferay.asset.kernel.model.AssetLinkConstants;
import com.liferay.knowledge.base.configuration.KBGroupServiceConfiguration;
import com.liferay.knowledge.base.constants.AdminActivityKeys;
import com.liferay.knowledge.base.constants.KBArticleConstants;
import com.liferay.knowledge.base.constants.KBConstants;
import com.liferay.knowledge.base.constants.KBFolderConstants;
import com.liferay.knowledge.base.exception.KBArticleContentException;
import com.liferay.knowledge.base.exception.KBArticleParentException;
import com.liferay.knowledge.base.exception.KBArticlePriorityException;
import com.liferay.knowledge.base.exception.KBArticleSourceURLException;
import com.liferay.knowledge.base.exception.KBArticleStatusException;
import com.liferay.knowledge.base.exception.KBArticleTitleException;
import com.liferay.knowledge.base.exception.KBArticleUrlTitleException;
import com.liferay.knowledge.base.exception.NoSuchArticleException;
import com.liferay.knowledge.base.internal.importer.KBArticleImporter;
import com.liferay.knowledge.base.internal.util.KBArticleLocalSiblingNavigationHelper;
import com.liferay.knowledge.base.model.KBArticle;
import com.liferay.knowledge.base.model.KBFolder;
import com.liferay.knowledge.base.service.base.KBArticleLocalServiceBaseImpl;
import com.liferay.knowledge.base.service.util.AdminSubscriptionSender;
import com.liferay.knowledge.base.service.util.AdminUtil;
import com.liferay.knowledge.base.service.util.KnowledgeBaseConstants;
import com.liferay.knowledge.base.util.KnowledgeBaseUtil;
import com.liferay.knowledge.base.util.comparator.KBArticlePriorityComparator;
import com.liferay.knowledge.base.util.comparator.KBArticleVersionComparator;
import com.liferay.portal.kernel.bean.BeanPropertiesUtil;
import com.liferay.portal.kernel.bean.BeanReference;
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.model.Group;
import com.liferay.portal.kernel.model.ModelHintsUtil;
import com.liferay.portal.kernel.model.ResourceConstants;
import com.liferay.portal.kernel.model.Subscription;
import com.liferay.portal.kernel.model.SystemEventConstants;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.module.configuration.ConfigurationException;
import com.liferay.portal.kernel.module.configuration.ConfigurationProvider;
import com.liferay.portal.kernel.portletfilerepository.PortletFileRepository;
import com.liferay.portal.kernel.repository.model.FileEntry;
import com.liferay.portal.kernel.search.IndexWriterHelperUtil;
import com.liferay.portal.kernel.search.Indexer;
import com.liferay.portal.kernel.search.IndexerRegistryUtil;
import com.liferay.portal.kernel.service.ServiceContext;
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.CharPool;
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.SubscriptionSender;
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.kernel.workflow.WorkflowThreadLocal;
import com.liferay.portal.spring.extender.service.ServiceReference;

import java.io.InputStream;
import java.io.Serializable;

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;

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

	@Override
	public FileEntry addAttachment(
			long userId, long resourcePrimKey, String fileName,
			InputStream inputStream, String mimeType)
		throws PortalException {

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

		return portletFileRepository.addPortletFileEntry(
			kbArticle.getGroupId(), userId, KBArticle.class.getName(),
			kbArticle.getClassPK(), KBConstants.SERVICE_NAME,
			kbArticle.getAttachmentsFolderId(), inputStream, fileName, mimeType,
			false);
	}

	@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 = userLocalService.getUser(userId);
		long groupId = serviceContext.getScopeGroupId();
		urlTitle = normalizeUrlTitle(urlTitle);
		double priority = getPriority(groupId, parentResourcePrimKey);
		Date now = new Date();

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

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

		urlTitle = StringUtil.toLowerCase(urlTitle);

		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

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

	@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 int addKBArticlesMarkdown(
			long userId, long groupId, long parentKbFolderId, String fileName,
			boolean prioritizeByNumericalPrefix, InputStream inputStream,
			ServiceContext serviceContext)
		throws PortalException {

		boolean workflowEnabled = WorkflowThreadLocal.isEnabled();

		try {
			WorkflowThreadLocal.setEnabled(false);

			return kbArticleImporter.processZipFile(
				userId, groupId, parentKbFolderId, prioritizeByNumericalPrefix,
				inputStream, serviceContext);
		}
		finally {
			WorkflowThreadLocal.setEnabled(workflowEnabled);
		}
	}

	@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 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

		portletFileRepository.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 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 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 fetchFirstChildKBArticle(
		long groupId, long parentResourcePrimKey) {

		return kbArticlePersistence.fetchByG_P_L_First(
			groupId, parentResourcePrimKey, true,
			new KBArticlePriorityComparator(true));
	}

	@Override
	public KBArticle fetchKBArticle(
		long resourcePrimKey, long groupId, int version) {

		return kbArticlePersistence.fetchByR_G_V(
			resourcePrimKey, groupId, version);
	}

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

		urlTitle = StringUtil.replaceFirst(
			urlTitle, CharPool.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, CharPool.SLASH, StringPool.BLANK);

		List 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 fetchLatestKBArticle(long resourcePrimKey, long groupId) {
		return kbArticlePersistence.fetchByR_G_L_First(
			resourcePrimKey, groupId, true, null);
	}

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

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

		List kbArticles = null;

		OrderByComparator 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 getAllDescendantKBArticles(
		long resourcePrimKey, int status,
		OrderByComparator orderByComparator) {

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

	@Override
	public List getCompanyKBArticles(
		long companyId, int status, int start, int end,
		OrderByComparator 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 getGroupKBArticles(
		long groupId, int status, int start, int end,
		OrderByComparator 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 getKBArticleAndAllDescendantKBArticles(
		long resourcePrimKey, int status,
		OrderByComparator orderByComparator) {

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

	/**
	 * @deprecated As of 1.1.0, replaced by {@link
	 *             #getKBArticleAndAllDescendantKBArticles(long, int,
	 *             OrderByComparator)}
	 */
	@Deprecated
	@Override
	public List getKBArticleAndAllDescendants(
		long resourcePrimKey, int status,
		OrderByComparator orderByComparator) {

		return getKBArticleAndAllDescendantKBArticles(
			resourcePrimKey, status, orderByComparator);
	}

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

		urlTitle = StringUtil.replaceFirst(
			urlTitle, CharPool.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, CharPool.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 getKBArticles(
		long groupId, long parentResourcePrimKey, int status, int start,
		int end, OrderByComparator 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 getKBArticles(
		long[] resourcePrimKeys, int status,
		OrderByComparator orderByComparator) {

		List kbArticles = new ArrayList<>();

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

		while ((params = KnowledgeBaseUtil.getParams(params[0])) != null) {
			List 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 getKBArticleVersions(
		long resourcePrimKey, int status, int start, int end,
		OrderByComparator 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 List getKBFolderKBArticles(
		long groupId, long kbFolderId) {

		return kbArticlePersistence.findByG_KBFI_L(groupId, kbFolderId, true);
	}

	@Override
	public int getKBFolderKBArticlesCount(
		long groupId, long kbFolderId, int status) {

		return kbArticlePersistence.countByG_KBFI_S(
			groupId, kbFolderId, 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, CharPool.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 {

		KBArticleLocalSiblingNavigationHelper
			kbArticleLocalSiblingNavigationHelper =
				new KBArticleLocalSiblingNavigationHelper(kbArticlePersistence);

		return kbArticleLocalSiblingNavigationHelper.
			getPreviousAndNextKBArticles(kbArticleId);
	}

	@Override
	public List getSectionsKBArticles(
		long groupId, String[] sections, int status, int start, int end,
		OrderByComparator 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_S_L(
				groupId, array, true, start, end, orderByComparator);
		}
		else if (status == WorkflowConstants.STATUS_APPROVED) {
			return kbArticlePersistence.findByG_S_M(
				groupId, array, true, start, end, orderByComparator);
		}

		return kbArticlePersistence.findByG_S_S(
			groupId, 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_S_L(groupId, array, true);
		}
		else if (status == WorkflowConstants.STATUS_APPROVED) {
			return kbArticlePersistence.countByG_S_M(groupId, array, true);
		}

		return kbArticlePersistence.countByG_S_S(groupId, array, status);
	}

	/**
	 * @deprecated As of 1.1.0, replaced by {@link #getKBArticles(long, long,
	 *             int, int, int,
	 *             OrderByComparator)}
	 */
	@Deprecated
	@Override
	public List getSiblingKBArticles(
		long groupId, long parentResourcePrimKey, int status, int start,
		int end, OrderByComparator orderByComparator) {

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

	/**
	 * @deprecated As of 1.1.0, replaced by {@link #getKBArticlesCount(long,
	 *             long, int)}
	 */
	@Deprecated
	@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 {

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

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

		validateParent(
			kbArticle, parentResourceClassNameId, parentResourcePrimKey);
		validateParentStatus(
			parentResourceClassNameId, parentResourcePrimKey,
			kbArticle.getStatus());

		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 parentKBArticle = getLatestKBArticle(
				parentResourcePrimKey, WorkflowConstants.STATUS_ANY);

			kbFolderId = parentKBArticle.getKbFolderId();
		}

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

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

			curKBArticle.setKbFolderId(kbFolderId);
			curKBArticle.setPriority(priority);

			kbArticlePersistence.update(curKBArticle);
		}

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

			for (KBArticle curKBArticle : descendantKBArticles) {
				List 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 KBArticle revertKBArticle(
			long userId, long resourcePrimKey, int version,
			ServiceContext serviceContext)
		throws PortalException {

		KBArticle kbArticle = kbArticleLocalService.getKBArticle(
			resourcePrimKey, version);

		return updateKBArticle(
			userId, resourcePrimKey, kbArticle.getTitle(),
			kbArticle.getContent(), kbArticle.getDescription(),
			kbArticle.getSourceURL(), StringUtil.split(kbArticle.getSections()),
			null, null, serviceContext);
	}

	@Override
	public List search(
		long groupId, String title, String content, int status, Date startDate,
		Date endDate, boolean andOperator, int start, int end,
		OrderByComparator 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 = userLocalService.getUser(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 resourcePrimKeyToPriorityMap)
		throws PortalException {

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

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

		List 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 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 = userLocalService.getUser(userId);
		boolean main = false;
		Date now = new Date();

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

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

		validateParentStatus(
			kbArticle.getParentResourceClassNameId(),
			kbArticle.getParentResourcePrimKey(), status);

		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 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(userId, 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 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 terms = new HashMap<>();

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

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

		for (Map.Entry 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 subscriptions =
			subscriptionLocalService.getSubscriptions(
				kbArticle.getCompanyId(), KBArticle.class.getName(),
				kbArticle.getResourcePrimKey());

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

	protected void getAllDescendantKBArticles(
		List kbArticles, long resourcePrimKey, int status,
		OrderByComparator orderByComparator) {

		List curKBArticles = null;

		if (status == WorkflowConstants.STATUS_ANY) {
			curKBArticles = kbArticlePersistence.findByP_L(
				resourcePrimKey, true, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
				orderByComparator);
		}
		else if (status == WorkflowConstants.STATUS_APPROVED) {
			curKBArticles = kbArticlePersistence.findByP_M(
				resourcePrimKey, true, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
				orderByComparator);
		}
		else {
			curKBArticles = kbArticlePersistence.findByP_S(
				resourcePrimKey, status, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
				orderByComparator);
		}

		for (KBArticle curKBArticle : curKBArticles) {
			kbArticles.add(curKBArticle);

			getAllDescendantKBArticles(
				kbArticles, curKBArticle.getResourcePrimKey(), status,
				orderByComparator);
		}
	}

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

		List kbArticles = null;

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

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

		getAllDescendantKBArticles(
			kbArticles, resourcePrimKey, status, orderByComparator);

		return Collections.unmodifiableList(kbArticles);
	}

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

		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 KBGroupServiceConfiguration getKBGroupServiceConfiguration(
			long groupId)
		throws ConfigurationException {

		return configurationProvider.getGroupConfiguration(
			KBGroupServiceConfiguration.class, groupId);
	}

	protected double getPriority(long groupId, long parentResourcePrimKey)
		throws PortalException {

		KBGroupServiceConfiguration kbGroupServiceConfiguration =
			getKBGroupServiceConfiguration(groupId);

		if (!kbGroupServiceConfiguration.articleIncrementPriorityEnabled()) {
			return KBArticleConstants.DEFAULT_VERSION;
		}

		List 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 kbArticlesCount = kbArticlePersistence.countByG_KBFI_UT_ST(
				groupId, kbFolderId, uniqueUrlTitle, _STATUSES);

			for (int i = 1; kbArticlesCount > 0; i++) {
				uniqueUrlTitle = getUniqueUrlTitle(urlTitle, i);

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

			return uniqueUrlTitle;
		}

		KBFolder kbFolder = kbFolderPersistence.findByPrimaryKey(kbFolderId);

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

		for (int i = 1; kbArticlesCount > 0; i++) {
			uniqueUrlTitle = getUniqueUrlTitle(urlTitle, i);

			kbArticlesCount = 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 String getUniqueUrlTitle(String urlTitle, int suffix) {
		String uniqueUrlTitle = urlTitle + StringPool.DASH + suffix;

		int maxLength = ModelHintsUtil.getMaxLength(
			KBArticle.class.getName(), "urlTitle");

		return StringUtil.shorten(
			uniqueUrlTitle, maxLength, StringPool.DASH + suffix);
	}

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

			return false;
		}

		return true;
	}

	protected String normalizeUrlTitle(String urlTitle) {
		if (urlTitle == null) {
			return null;
		}

		if (StringUtil.startsWith(urlTitle, CharPool.SLASH)) {
			return urlTitle;
		}

		return StringPool.SLASH + urlTitle;
	}

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

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

		KBGroupServiceConfiguration kbGroupServiceConfiguration =
			getKBGroupServiceConfiguration(kbArticle.getGroupId());

		if (serviceContext.isCommandAdd() &&
			!kbGroupServiceConfiguration.emailKBArticleAddedEnabled()) {

			return;
		}

		if (serviceContext.isCommandUpdate() &&
			!kbGroupServiceConfiguration.emailKBArticleUpdatedEnabled()) {

			return;
		}

		String fromName = kbGroupServiceConfiguration.emailFromName();
		String fromAddress = kbGroupServiceConfiguration.emailFromAddress();

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

		Map kbArticleDiffs = getEmailKBArticleDiffs(kbArticle);

		for (Map.Entry entry : kbArticleDiffs.entrySet()) {
			String value = StringUtil.replace(
				entry.getValue(), new String[] {"href=\"/", "src=\"/"},
				new String[] {
					"href=\"" + serviceContext.getPortalURL() + "/",
					"src=\"" + serviceContext.getPortalURL() + "/"
				});

			kbArticleDiffs.put(entry.getKey(), value);
		}

		String subject = null;
		String body = null;

		if (serviceContext.isCommandAdd()) {
			subject = kbGroupServiceConfiguration.emailKBArticleAddedSubject();
			body = kbGroupServiceConfiguration.emailKBArticleAddedBody();
		}
		else {
			subject =
				kbGroupServiceConfiguration.emailKBArticleUpdatedSubject();
			body = kbGroupServiceConfiguration.emailKBArticleUpdatedBody();
		}

		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.setContextCreatorUserPrefix("ARTICLE");
		subscriptionSender.setCreatorUserId(kbArticle.getUserId());
		subscriptionSender.setCurrentUserId(userId);
		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.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) {
			portletFileRepository.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 kbArticles1 = getKBArticleAndAllDescendantKBArticles(
			resourcePrimKey, WorkflowConstants.STATUS_ANY, null);

		for (KBArticle kbArticle1 : kbArticles1) {
			List 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

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

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

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

		if (Validator.isNull(title)) {
			throw new KBArticleTitleException("Title is null");
		}

		if (Validator.isNull(content)) {
			throw new KBArticleContentException("Content is null");
		}

		validateSourceURL(sourceURL);
	}

	protected void validateParent(
			KBArticle kbArticle, long parentResourceClassNameId,
			long parentResourcePrimKey)
		throws PortalException {

		validateParent(parentResourceClassNameId, parentResourcePrimKey);

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

		if (parentResourceClassNameId == kbArticleClassNameId) {
			KBArticle parentKBArticle = getLatestKBArticle(
				parentResourcePrimKey, WorkflowConstants.STATUS_ANY);

			List ancestorResourcePrimaryKeys =
				parentKBArticle.getAncestorResourcePrimaryKeys();

			if (ancestorResourcePrimaryKeys.contains(
					kbArticle.getResourcePrimKey())) {

				throw new KBArticleParentException(
					String.format(
						"Cannot move KBArticle %s inside its descendant " +
							"KBArticle %s",
						kbArticle.getResourcePrimKey(),
						parentKBArticle.getResourcePrimKey()));
			}
		}
	}

	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 validateParentStatus(
			long parentResourceClassNameId, long parentResourcePrimKey,
			int status)
		throws PortalException {

		long kbFolderClassNameId = classNameLocalService.getClassNameId(
			KBFolder.class);

		if (parentResourceClassNameId == kbFolderClassNameId) {
			return;
		}

		KBArticle kbArticle = fetchLatestKBArticle(
			parentResourcePrimKey, WorkflowConstants.STATUS_APPROVED);

		if ((kbArticle == null) &&
			(status == WorkflowConstants.STATUS_APPROVED)) {

			throw new KBArticleStatusException();
		}
	}

	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 KBArticleUrlTitleException.
				MustNotContainInvalidCharacters(urlTitle);
		}

		int urlTitleMaxSize = ModelHintsUtil.getMaxLength(
			KBArticle.class.getName(), "urlTitle");

		if (urlTitle.length() > urlTitleMaxSize) {
			throw new KBArticleUrlTitleException.MustNotExceedMaximumSize(
				urlTitle, urlTitleMaxSize);
		}

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

		if (!kbArticles.isEmpty()) {
			throw new KBArticleUrlTitleException.MustNotBeDuplicate(urlTitle);
		}
	}

	@ServiceReference(type = ConfigurationProvider.class)
	protected ConfigurationProvider configurationProvider;

	@ServiceReference(type = KBArticleImporter.class)
	protected KBArticleImporter kbArticleImporter;

	@BeanReference(type = PortletFileRepository.class)
	protected PortletFileRepository portletFileRepository;

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

	private static final long _TICKET_EXPIRATION = Time.HOUR;

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

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy