/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.wiki.service.impl;

import com.liferay.asset.kernel.model.AssetEntry;
import com.liferay.asset.kernel.model.AssetLink;
import com.liferay.asset.kernel.model.AssetLinkConstants;
import com.liferay.document.library.kernel.model.DLFolderConstants;
import com.liferay.expando.kernel.model.ExpandoBridge;
import com.liferay.expando.kernel.util.ExpandoBridgeUtil;
import com.liferay.osgi.service.tracker.collections.map.ServiceTrackerMap;
import com.liferay.osgi.service.tracker.collections.map.ServiceTrackerMapFactory;
import com.liferay.petra.string.CharPool;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.comment.CommentManagerUtil;
import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.diff.DiffHtmlUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.model.LayoutConstants;
import com.liferay.portal.kernel.model.ResourceConstants;
import com.liferay.portal.kernel.model.SystemEventConstants;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.module.configuration.ConfigurationProvider;
import com.liferay.portal.kernel.notifications.UserNotificationDefinition;
import com.liferay.portal.kernel.portlet.PortletURLFactoryUtil;
import com.liferay.portal.kernel.portletfilerepository.PortletFileRepositoryUtil;
import com.liferay.portal.kernel.repository.model.FileEntry;
import com.liferay.portal.kernel.repository.model.Folder;
import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
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.settings.GroupServiceSettingsLocator;
import com.liferay.portal.kernel.settings.LocalizedValuesMap;
import com.liferay.portal.kernel.social.SocialActivityManagerUtil;
import com.liferay.portal.kernel.systemevent.SystemEvent;
import com.liferay.portal.kernel.systemevent.SystemEventHierarchyEntryThreadLocal;
import com.liferay.portal.kernel.util.CalendarFactoryUtil;
import com.liferay.portal.kernel.util.Constants;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.kernel.util.FileUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.LocalizationUtil;
import com.liferay.portal.kernel.util.MathUtil;
import com.liferay.portal.kernel.util.MimeTypesUtil;
import com.liferay.portal.kernel.util.NotificationThreadLocal;
import com.liferay.portal.kernel.util.ObjectValuePair;
import com.liferay.portal.kernel.util.OrderByComparator;
import com.liferay.portal.kernel.util.Portal;
import com.liferay.portal.kernel.util.PortalUtil;
import com.liferay.portal.kernel.util.StringBundler;
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.URLCodec;
import com.liferay.portal.kernel.util.UnicodeProperties;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.util.WebKeys;
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 com.liferay.portal.util.LayoutURLUtil;
import com.liferay.social.kernel.model.SocialActivityConstants;
import com.liferay.trash.kernel.exception.RestoreEntryException;
import com.liferay.trash.kernel.exception.TrashEntryException;
import com.liferay.trash.kernel.model.TrashEntry;
import com.liferay.trash.kernel.model.TrashVersion;
import com.liferay.trash.kernel.util.TrashUtil;
import com.liferay.wiki.configuration.WikiGroupServiceOverriddenConfiguration;
import com.liferay.wiki.constants.WikiConstants;
import com.liferay.wiki.constants.WikiPortletKeys;
import com.liferay.wiki.engine.WikiEngine;
import com.liferay.wiki.engine.impl.WikiEngineRenderer;
import com.liferay.wiki.escape.WikiEscapeUtil;
import com.liferay.wiki.exception.DuplicatePageException;
import com.liferay.wiki.exception.NoSuchPageException;
import com.liferay.wiki.exception.PageContentException;
import com.liferay.wiki.exception.PageTitleException;
import com.liferay.wiki.exception.PageVersionException;
import com.liferay.wiki.model.WikiNode;
import com.liferay.wiki.model.WikiPage;
import com.liferay.wiki.model.WikiPageConstants;
import com.liferay.wiki.model.WikiPageDisplay;
import com.liferay.wiki.model.WikiPageResource;
import com.liferay.wiki.model.impl.WikiPageDisplayImpl;
import com.liferay.wiki.model.impl.WikiPageImpl;
import com.liferay.wiki.processor.WikiPageRenameContentProcessor;
import com.liferay.wiki.service.base.WikiPageLocalServiceBaseImpl;
import com.liferay.wiki.social.WikiActivityKeys;
import com.liferay.wiki.util.WikiCacheHelper;
import com.liferay.wiki.util.WikiCacheThreadLocal;
import com.liferay.wiki.util.WikiUtil;
import com.liferay.wiki.util.comparator.PageCreateDateComparator;
import com.liferay.wiki.util.comparator.PageVersionComparator;
import com.liferay.wiki.validator.WikiPageTitleValidator;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import javax.portlet.PortletRequest;
import javax.portlet.PortletURL;

import javax.servlet.http.HttpServletRequest;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;

/**
 * Provides the local service for accessing, adding, deleting, moving,
 * subscription handling of, trash handling of, updating, and validating wiki
 * pages.
 *
 * @author Brian Wing Shun Chan
 * @author Jorge Ferrer
 * @author Raymond Augé
 * @author Bruno Farache
 * @author Julio Camarero
 * @author Wesley Gong
 * @author Marcellus Tavares
 * @author Zsigmond Rab
 * @author Zsolt Berentey
 * @author Roberto Díaz
 */
public class WikiPageLocalServiceImpl extends WikiPageLocalServiceBaseImpl {

	@Override
	public WikiPage addPage(
			long userId, long nodeId, String title, double version,
			String content, String summary, boolean minorEdit, String format,
			boolean head, String parentTitle, String redirectTitle,
			ServiceContext serviceContext)
		throws PortalException {

		// Page

		User user = userLocalService.getUser(userId);
		WikiNode node = wikiNodePersistence.findByPrimaryKey(nodeId);
		Date now = new Date();

		long pageId = counterLocalService.increment();

		content = SanitizerUtil.sanitize(
			user.getCompanyId(), node.getGroupId(), userId,
			WikiPage.class.getName(), pageId, "text/" + format, content);

		title = StringUtil.replace(
			title, CharPool.NO_BREAK_SPACE, CharPool.SPACE);

		validate(title, nodeId, content, format);

		long resourcePrimKey =
			wikiPageResourceLocalService.getPageResourcePrimKey(
				node.getGroupId(), nodeId, title);

		WikiPage page = wikiPagePersistence.create(pageId);

		page.setUuid(serviceContext.getUuid());
		page.setResourcePrimKey(resourcePrimKey);
		page.setGroupId(node.getGroupId());
		page.setCompanyId(user.getCompanyId());
		page.setUserId(user.getUserId());
		page.setUserName(user.getFullName());
		page.setNodeId(nodeId);
		page.setTitle(title);
		page.setVersion(version);
		page.setMinorEdit(minorEdit);
		page.setContent(content);
		page.setSummary(summary);
		page.setFormat(format);
		page.setHead(head);
		page.setParentTitle(parentTitle);
		page.setRedirectTitle(redirectTitle);
		page.setStatus(WorkflowConstants.STATUS_DRAFT);
		page.setStatusByUserId(userId);
		page.setStatusDate(serviceContext.getModifiedDate(now));
		page.setExpandoBridgeAttributes(serviceContext);

		page = wikiPagePersistence.update(page);

		// Resources

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

			addPageResources(
				page, serviceContext.isAddGroupPermissions(),
				serviceContext.isAddGuestPermissions());
		}
		else {
			addPageResources(
				page, serviceContext.getGroupPermissions(),
				serviceContext.getGuestPermissions());
		}

		// Node

		node.setLastPostDate(serviceContext.getModifiedDate(now));

		wikiNodePersistence.update(node);

		// Asset

		updateAsset(
			userId, page, serviceContext.getAssetCategoryIds(),
			serviceContext.getAssetTagNames(),
			serviceContext.getAssetLinkEntryIds(),
			serviceContext.getAssetPriority());

		// Workflow

		return startWorkflowInstance(userId, page, serviceContext);
	}

	@Override
	public WikiPage addPage(
			long userId, long nodeId, String title, String content,
			String summary, boolean minorEdit, ServiceContext serviceContext)
		throws PortalException {

		double version = WikiPageConstants.VERSION_DEFAULT;

		WikiNode node = wikiNodePersistence.findByPrimaryKey(nodeId);

		WikiGroupServiceOverriddenConfiguration
			wikiGroupServiceOverriddenConfiguration =
				configurationProvider.getConfiguration(
					WikiGroupServiceOverriddenConfiguration.class,
					new GroupServiceSettingsLocator(
						node.getGroupId(), WikiConstants.SERVICE_NAME));

		String format = wikiGroupServiceOverriddenConfiguration.defaultFormat();

		boolean head = false;
		String parentTitle = null;
		String redirectTitle = null;

		return addPage(
			userId, nodeId, title, version, content, summary, minorEdit, format,
			head, parentTitle, redirectTitle, serviceContext);
	}

	@Override
	public FileEntry addPageAttachment(
			long userId, long nodeId, String title, String fileName, File file,
			String mimeType)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		Folder folder = page.addAttachmentsFolder();

		fileName = PortletFileRepositoryUtil.getUniqueFileName(
			page.getGroupId(), folder.getFolderId(), fileName);

		FileEntry fileEntry = PortletFileRepositoryUtil.addPortletFileEntry(
			page.getGroupId(), userId, WikiPage.class.getName(),
			page.getResourcePrimKey(), WikiConstants.SERVICE_NAME,
			folder.getFolderId(), file, fileName, mimeType, true);

		if (userId == 0) {
			userId = page.getUserId();
		}

		JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

		extraDataJSONObject.put("fileEntryId", fileEntry.getFileEntryId());
		extraDataJSONObject.put("fileEntryTitle", fileEntry.getTitle());
		extraDataJSONObject.put("title", page.getTitle());
		extraDataJSONObject.put("version", page.getVersion());

		SocialActivityManagerUtil.addActivity(
			userId, page, SocialActivityConstants.TYPE_ADD_ATTACHMENT,
			extraDataJSONObject.toString(), 0);

		return fileEntry;
	}

	@Override
	public FileEntry addPageAttachment(
			long userId, long nodeId, String title, String fileName,
			InputStream inputStream, String mimeType)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		Folder folder = page.addAttachmentsFolder();

		fileName = PortletFileRepositoryUtil.getUniqueFileName(
			page.getGroupId(), folder.getFolderId(), fileName);

		FileEntry fileEntry = PortletFileRepositoryUtil.addPortletFileEntry(
			page.getGroupId(), userId, WikiPage.class.getName(),
			page.getResourcePrimKey(), WikiConstants.SERVICE_NAME,
			folder.getFolderId(), inputStream, fileName, mimeType, true);

		if (userId == 0) {
			userId = page.getUserId();
		}

		JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

		extraDataJSONObject.put("fileEntryId", fileEntry.getFileEntryId());
		extraDataJSONObject.put("fileEntryTitle", fileEntry.getTitle());
		extraDataJSONObject.put("title", page.getTitle());
		extraDataJSONObject.put("version", page.getVersion());

		SocialActivityManagerUtil.addActivity(
			userId, page, SocialActivityConstants.TYPE_ADD_ATTACHMENT,
			extraDataJSONObject.toString(), 0);

		return fileEntry;
	}

	@Override
	public List<FileEntry> addPageAttachments(
			long userId, long nodeId, String title,
			List<ObjectValuePair<String, InputStream>> inputStreamOVPs)
		throws PortalException {

		List<FileEntry> fileEntries = new ArrayList<>();

		if (inputStreamOVPs.isEmpty()) {
			return Collections.emptyList();
		}

		for (ObjectValuePair<String, InputStream> inputStreamOVP :
				inputStreamOVPs) {

			String fileName = inputStreamOVP.getKey();
			InputStream inputStream = inputStreamOVP.getValue();

			File file = null;

			try {
				file = FileUtil.createTempFile(inputStream);

				String mimeType = MimeTypesUtil.getContentType(file, fileName);

				FileEntry fileEntry = addPageAttachment(
					userId, nodeId, title, fileName, file, mimeType);

				fileEntries.add(fileEntry);
			}
			catch (IOException ioe) {
				throw new SystemException(
					"Unable to write temporary file", ioe);
			}
			finally {
				FileUtil.delete(file);
			}
		}

		return fileEntries;
	}

	@Override
	public void addPageResources(
			long nodeId, String title, boolean addGroupPermissions,
			boolean addGuestPermissions)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		addPageResources(page, addGroupPermissions, addGuestPermissions);
	}

	@Override
	public void addPageResources(
			long nodeId, String title, String[] groupPermissions,
			String[] guestPermissions)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		addPageResources(page, groupPermissions, guestPermissions);
	}

	@Override
	public void addPageResources(
			WikiPage page, boolean addGroupPermissions,
			boolean addGuestPermissions)
		throws PortalException {

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

	@Override
	public void addPageResources(
			WikiPage page, String[] groupPermissions, String[] guestPermissions)
		throws PortalException {

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

	@Override
	public FileEntry addTempFileEntry(
			long groupId, long userId, String folderName, String fileName,
			InputStream inputStream, String mimeType)
		throws PortalException {

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

	/**
	 * @deprecated As of Wilberforce (7.0.x), replaced by {@link
	 *             #addTempFileEntry(long, long, String, String, InputStream,
	 *             String)}
	 */
	@Deprecated
	@Override
	public void addTempPageAttachment(
			long groupId, long userId, String fileName, String tempFolderName,
			InputStream inputStream, String mimeType)
		throws PortalException {

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

	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();

		Bundle bundle = FrameworkUtil.getBundle(WikiPageLocalServiceImpl.class);

		BundleContext bundleContext = bundle.getBundleContext();

		_serviceTrackerMap = ServiceTrackerMapFactory.singleValueMap(
			bundleContext, WikiPageRenameContentProcessor.class,
			"wiki.format.name");

		_serviceTrackerMap.open();
	}

	@Override
	public WikiPage changeParent(
			long userId, long nodeId, String title, String newParentTitle,
			ServiceContext serviceContext)
		throws PortalException {

		if (Validator.isNotNull(newParentTitle)) {
			WikiPage parentPage = getPage(nodeId, newParentTitle);

			if (Validator.isNotNull(parentPage.getRedirectTitle())) {
				newParentTitle = parentPage.getRedirectTitle();
			}
		}

		WikiPage page = getPage(nodeId, title);

		String originalParentTitle = page.getParentTitle();

		double version = page.getVersion();
		String content = page.getContent();

		String summary = serviceContext.translate(
			"changed-parent-from-x", originalParentTitle);
		boolean minorEdit = false;
		String format = page.getFormat();
		String redirectTitle = page.getRedirectTitle();

		populateServiceContext(serviceContext, page);

		page = updatePage(
			userId, nodeId, title, version, content, summary, minorEdit, format,
			newParentTitle, redirectTitle, serviceContext);

		List<WikiPage> oldPages = wikiPagePersistence.findByN_T_H(
			nodeId, title, false);

		for (WikiPage oldPage : oldPages) {
			if (!WorkflowThreadLocal.isEnabled()) {
				oldPage.setParentTitle(originalParentTitle);

				wikiPagePersistence.update(oldPage);
			}
		}

		return page;
	}

	@Override
	public void copyPageAttachments(
			long userId, long templateNodeId, String templateTitle, long nodeId,
			String title)
		throws PortalException {

		WikiPage templatePage = getPage(templateNodeId, templateTitle);

		List<FileEntry> templateFileEntries =
			templatePage.getAttachmentsFileEntries();

		for (FileEntry templateFileEntry : templateFileEntries) {
			addPageAttachment(
				userId, nodeId, title, templateFileEntry.getTitle(),
				templateFileEntry.getContentStream(),
				templateFileEntry.getMimeType());
		}
	}

	@Override
	public void deletePage(long nodeId, String title) throws PortalException {
		List<WikiPage> pages = wikiPagePersistence.findByN_T_H(
			nodeId, title, true, 0, 1);

		if (!pages.isEmpty()) {
			wikiPageLocalService.deletePage(pages.get(0));
		}
	}

	@Override
	@SystemEvent(
		action = SystemEventConstants.ACTION_SKIP, send = false,
		type = SystemEventConstants.TYPE_DELETE
	)
	public void deletePage(WikiPage page) throws PortalException {

		// Child pages

		List<WikiPage> childPages = wikiPagePersistence.findByN_P(
			page.getNodeId(), page.getTitle());

		for (WikiPage childPage : childPages) {
			if (childPage.isApproved() || childPage.isInTrashImplicitly()) {
				wikiPageLocalService.deletePage(childPage);
			}
			else {
				childPage.setParentTitle(StringPool.BLANK);

				wikiPagePersistence.update(childPage);
			}
		}

		List<WikiPage> redirectorPages = getRedirectorPages(
			page.getNodeId(), page.getTitle());

		for (WikiPage redirectorPage : redirectorPages) {
			if (redirectorPage.isApproved() ||
				redirectorPage.isInTrashImplicitly()) {

				wikiPageLocalService.deletePage(redirectorPage);
			}
			else {
				redirectorPage.setRedirectTitle(StringPool.BLANK);

				wikiPagePersistence.update(redirectorPage);
			}
		}

		List<WikiPage> versionPages = wikiPagePersistence.findByN_T(
			page.getNodeId(), page.getTitle());

		wikiPagePersistence.removeByN_T(page.getNodeId(), page.getTitle());

		// References

		wikiPagePersistence.removeByN_R(page.getNodeId(), page.getTitle());

		// Resources

		resourceLocalService.deleteResource(
			page.getCompanyId(), WikiPage.class.getName(),
			ResourceConstants.SCOPE_INDIVIDUAL, page.getResourcePrimKey());

		// Resource

		WikiPageResource pageResource = wikiPageResourcePersistence.fetchByN_T(
			page.getNodeId(), page.getTitle());

		if (pageResource != null) {
			wikiPageResourcePersistence.remove(pageResource);
		}

		// Attachments

		long folderId = page.getAttachmentsFolderId();

		if (folderId != DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
			PortletFileRepositoryUtil.deletePortletFolder(folderId);
		}

		// Subscriptions

		subscriptionLocalService.deleteSubscriptions(
			page.getCompanyId(), WikiPage.class.getName(),
			page.getResourcePrimKey());

		// Asset

		SystemEventHierarchyEntryThreadLocal.pop(
			page.getModelClass(), page.getPageId());

		try {
			for (WikiPage versionPage : versionPages) {
				assetEntryLocalService.deleteEntry(
					WikiPage.class.getName(), versionPage.getPrimaryKey());
			}
		}
		finally {
			SystemEventHierarchyEntryThreadLocal.push(
				page.getModelClass(), page.getPageId());
		}

		assetEntryLocalService.deleteEntry(
			WikiPage.class.getName(), page.getResourcePrimKey());

		// Comment

		CommentManagerUtil.deleteDiscussion(
			WikiPage.class.getName(), page.getResourcePrimKey());

		// Expando

		expandoRowLocalService.deleteRows(page.getPrimaryKey());

		// Ratings

		ratingsStatsLocalService.deleteStats(
			WikiPage.class.getName(), page.getResourcePrimKey());

		// Trash

		if (page.isInTrash()) {
			if (page.isInTrashExplicitly()) {
				page.setTitle(TrashUtil.getOriginalTitle(page.getTitle()));

				trashEntryLocalService.deleteEntry(
					WikiPage.class.getName(), page.getResourcePrimKey());
			}
			else {
				for (WikiPage versionPage : versionPages) {
					trashVersionLocalService.deleteTrashVersion(
						WikiPage.class.getName(), versionPage.getPageId());
				}
			}
		}

		// Indexer

		Indexer<WikiPage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			WikiPage.class);

		indexer.delete(page);

		// Cache

		clearPageCache(page);

		// Version pages

		for (WikiPage versionPage : versionPages) {

			// Workflow

			workflowInstanceLinkLocalService.deleteWorkflowInstanceLinks(
				versionPage.getCompanyId(), versionPage.getGroupId(),
				WikiPage.class.getName(), versionPage.getPageId());
		}

		if (pageResource != null) {
			systemEventLocalService.addSystemEvent(
				0, page.getGroupId(), page.getModelClassName(),
				page.getPrimaryKey(), pageResource.getUuid(), null,
				SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
		}
	}

	@Override
	public void deletePageAttachment(long nodeId, String title, String fileName)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		long folderId = page.getAttachmentsFolderId();

		if (folderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
			return;
		}

		FileEntry fileEntry = PortletFileRepositoryUtil.getPortletFileEntry(
			page.getGroupId(), folderId, fileName);

		deletePageAttachment(fileEntry.getFileEntryId());
	}

	@Override
	public void deletePageAttachments(long nodeId, String title)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		long folderId = page.getAttachmentsFolderId();

		if (folderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
			return;
		}

		PortletFileRepositoryUtil.deletePortletFileEntries(
			page.getGroupId(), folderId);
	}

	@Override
	public void deletePages(long nodeId) throws PortalException {
		List<WikiPage> pages = wikiPagePersistence.findByN_H_P(
			nodeId, true, StringPool.BLANK);

		for (WikiPage page : pages) {
			wikiPageLocalService.deletePage(page);
		}

		pages = wikiPagePersistence.findByN_H_P(
			nodeId, false, StringPool.BLANK);

		for (WikiPage page : pages) {
			wikiPageLocalService.deletePage(page);
		}
	}

	@Override
	public void deleteTempFileEntry(
			long groupId, long userId, String folderName, String fileName)
		throws PortalException {

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

	@Override
	public void deleteTrashPageAttachments(long nodeId, String title)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		long folderId = page.getAttachmentsFolderId();

		if (folderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
			return;
		}

		PortletFileRepositoryUtil.deletePortletFileEntries(
			page.getGroupId(), folderId, WorkflowConstants.STATUS_IN_TRASH);
	}

	@Override
	public void destroy() {
		super.destroy();

		_serviceTrackerMap.close();
	}

	@Override
	public void discardDraft(long nodeId, String title, double version)
		throws PortalException {

		wikiPagePersistence.removeByN_T_V(nodeId, title, version);
	}

	@Override
	public WikiPage fetchLatestPage(
		long resourcePrimKey, int status, boolean preferApproved) {

		WikiPage page = null;

		OrderByComparator<WikiPage> orderByComparator =
			new PageVersionComparator();

		if (status == WorkflowConstants.STATUS_ANY) {
			if (preferApproved) {
				page = wikiPagePersistence.fetchByR_S_First(
					resourcePrimKey, WorkflowConstants.STATUS_APPROVED,
					orderByComparator);
			}

			if (page == null) {
				page = wikiPagePersistence.fetchByResourcePrimKey_First(
					resourcePrimKey, orderByComparator);
			}
		}
		else {
			page = wikiPagePersistence.fetchByR_S_First(
				resourcePrimKey, status, orderByComparator);
		}

		return page;
	}

	@Override
	public WikiPage fetchLatestPage(
		long resourcePrimKey, long nodeId, int status, boolean preferApproved) {

		WikiPage page = null;

		OrderByComparator<WikiPage> orderByComparator =
			new PageVersionComparator();

		if (status == WorkflowConstants.STATUS_ANY) {
			if (preferApproved) {
				page = wikiPagePersistence.fetchByR_N_S_First(
					resourcePrimKey, nodeId, WorkflowConstants.STATUS_APPROVED,
					orderByComparator);
			}

			if (page == null) {
				page = wikiPagePersistence.fetchByR_N_First(
					resourcePrimKey, nodeId, orderByComparator);
			}
		}
		else {
			page = wikiPagePersistence.fetchByR_N_S_First(
				resourcePrimKey, nodeId, status, orderByComparator);
		}

		return page;
	}

	@Override
	public WikiPage fetchLatestPage(
		long nodeId, String title, int status, boolean preferApproved) {

		WikiPage page = null;

		OrderByComparator<WikiPage> orderByComparator =
			new PageVersionComparator();

		if (status == WorkflowConstants.STATUS_ANY) {
			if (preferApproved) {
				page = wikiPagePersistence.fetchByN_T_S_First(
					nodeId, title, WorkflowConstants.STATUS_APPROVED,
					orderByComparator);
			}

			if (page == null) {
				page = wikiPagePersistence.fetchByN_T_First(
					nodeId, title, orderByComparator);
			}
		}
		else {
			page = wikiPagePersistence.fetchByN_T_S_First(
				nodeId, title, status, orderByComparator);
		}

		return page;
	}

	@Override
	public WikiPage fetchPage(long resourcePrimKey) {
		WikiPageResource pageResource =
			wikiPageResourcePersistence.fetchByPrimaryKey(resourcePrimKey);

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

		return fetchPage(pageResource.getNodeId(), pageResource.getTitle());
	}

	@Override
	public WikiPage fetchPage(long nodeId, String title) {
		return wikiPagePersistence.fetchByN_T_H_First(
			nodeId, title, true, null);
	}

	@Override
	public WikiPage fetchPage(long nodeId, String title, double version) {
		WikiPage page = null;

		if (version == 0) {
			page = fetchPage(nodeId, title);
		}
		else {
			page = wikiPagePersistence.fetchByN_T_V(nodeId, title, version);
		}

		return page;
	}

	@Override
	public List<WikiPage> getChildren(
		long nodeId, boolean head, String parentTitle) {

		return getChildren(
			nodeId, head, parentTitle, WorkflowConstants.STATUS_APPROVED);
	}

	@Override
	public List<WikiPage> getChildren(
		long nodeId, boolean head, String parentTitle, int status) {

		return wikiPagePersistence.findByN_H_P_S(
			nodeId, head, parentTitle, status);
	}

	@Override
	public List<WikiPage> getChildren(
		long nodeId, boolean head, String parentTitle, int start, int end) {

		return wikiPagePersistence.findByN_H_P_S(
			nodeId, head, parentTitle, WorkflowConstants.STATUS_APPROVED, start,
			end);
	}

	@Override
	public List<WikiPage> getChildren(
		long nodeId, boolean head, String parentTitle, int status, int start,
		int end, OrderByComparator obc) {

		return wikiPagePersistence.findByN_H_P_S(
			nodeId, head, parentTitle, status, start, end, obc);
	}

	@Override
	public int getChildrenCount(long nodeId, boolean head, String parentTitle) {
		return getChildrenCount(
			nodeId, head, parentTitle, WorkflowConstants.STATUS_APPROVED);
	}

	@Override
	public int getChildrenCount(
		long nodeId, boolean head, String parentTitle, int status) {

		return wikiPagePersistence.countByN_H_P_S(
			nodeId, head, parentTitle, status);
	}

	@Override
	public List<WikiPage> getDependentPages(
		long nodeId, boolean head, String title, int status) {

		List<WikiPage> dependentPages = new ArrayList<>();

		List<WikiPage> childPages = getChildren(nodeId, head, title, status);

		dependentPages.addAll(childPages);

		List<WikiPage> redirectorPages = getRedirectorPages(
			nodeId, head, title, status);

		dependentPages.addAll(redirectorPages);

		return dependentPages;
	}

	@Override
	public WikiPage getDraftPage(long nodeId, String title)
		throws PortalException {

		WikiPage page = wikiPagePersistence.fetchByN_T_S_First(
			nodeId, title, WorkflowConstants.STATUS_DRAFT, null);

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

		return wikiPagePersistence.findByN_T_S_First(
			nodeId, title, WorkflowConstants.STATUS_PENDING, null);
	}

	@Override
	public List<WikiPage> getIncomingLinks(long nodeId, String title)
		throws PortalException {

		Set<WikiPage> links = new HashSet<>();

		List<WikiPage> pages = wikiPagePersistence.findByN_H(nodeId, true);

		for (WikiPage page : pages) {
			if (isLinkedTo(page, title)) {
				links.add(page);
			}
		}

		List<WikiPage> redirectorPages = getRedirectorPages(nodeId, title);

		for (WikiPage redirectorPage : redirectorPages) {
			for (WikiPage page : pages) {
				if (isLinkedTo(page, redirectorPage.getTitle())) {
					links.add(page);
				}
			}
		}

		return ListUtil.sort(new ArrayList<WikiPage>(links));
	}

	@Override
	public WikiPage getLatestPage(
			long resourcePrimKey, int status, boolean preferApproved)
		throws PortalException {

		WikiPage page = fetchLatestPage(
			resourcePrimKey, status, preferApproved);

		if (page == null) {
			StringBundler sb = new StringBundler(5);

			sb.append("{resourcePrimKey=");
			sb.append(resourcePrimKey);
			sb.append(", status=");
			sb.append(status);
			sb.append("}");

			throw new NoSuchPageException(sb.toString());
		}

		return page;
	}

	@Override
	public WikiPage getLatestPage(
			long resourcePrimKey, long nodeId, int status,
			boolean preferApproved)
		throws PortalException {

		WikiPage page = fetchLatestPage(
			resourcePrimKey, nodeId, status, preferApproved);

		if (page == null) {
			StringBundler sb = new StringBundler(7);

			sb.append("{resourcePrimKey=");
			sb.append(resourcePrimKey);
			sb.append(", nodeId=");
			sb.append(nodeId);
			sb.append(", status=");
			sb.append(status);
			sb.append("}");

			throw new NoSuchPageException(sb.toString());
		}

		return page;
	}

	@Override
	public WikiPage getLatestPage(
			long nodeId, String title, int status, boolean preferApproved)
		throws PortalException {

		WikiPage page = fetchLatestPage(nodeId, title, status, preferApproved);

		if (page == null) {
			StringBundler sb = new StringBundler(7);

			sb.append("{nodeId=");
			sb.append(nodeId);
			sb.append(", title=");
			sb.append(title);
			sb.append(", status=");
			sb.append(status);
			sb.append("}");

			throw new NoSuchPageException(sb.toString());
		}

		return page;
	}

	@Override
	public List<WikiPage> getNoAssetPages() {
		return wikiPageFinder.findByNoAssets();
	}

	@Override
	public List<WikiPage> getOrphans(long nodeId) throws PortalException {
		List<WikiPage> pages = wikiPagePersistence.findByN_H_S(
			nodeId, true, WorkflowConstants.STATUS_APPROVED);

		return wikiEngineRenderer.filterOrphans(pages);
	}

	@Override
	public List<WikiPage> getOutgoingLinks(long nodeId, String title)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		Map<String, WikiPage> pages = new LinkedHashMap<>();

		Map<String, Boolean> links = wikiCacheHelper.getOutgoingLinks(
			page, wikiEngineRenderer);

		for (Map.Entry<String, Boolean> entry : links.entrySet()) {
			String curTitle = entry.getKey();
			Boolean exists = entry.getValue();

			if (exists) {
				WikiPage curPage = getPage(nodeId, curTitle);

				if (!pages.containsKey(curPage.getTitle())) {
					pages.put(curPage.getTitle(), curPage);
				}
			}
			else {
				WikiPageImpl newPage = new WikiPageImpl();

				newPage.setNew(true);
				newPage.setNodeId(nodeId);
				newPage.setTitle(curTitle);

				if (!pages.containsKey(curTitle)) {
					pages.put(curTitle, newPage);
				}
			}
		}

		return ListUtil.fromMapValues(pages);
	}

	@Override
	public WikiPage getPage(long resourcePrimKey) throws PortalException {
		return getPage(resourcePrimKey, Boolean.TRUE);
	}

	@Override
	public WikiPage getPage(long resourcePrimKey, Boolean head)
		throws PortalException {

		WikiPageResource pageResource =
			wikiPageResourcePersistence.findByPrimaryKey(resourcePrimKey);

		return getPage(pageResource.getNodeId(), pageResource.getTitle(), head);
	}

	@Override
	public WikiPage getPage(long nodeId, String title) throws PortalException {
		return wikiPagePersistence.findByN_T_H_First(nodeId, title, true, null);
	}

	@Override
	public WikiPage getPage(long nodeId, String title, Boolean head)
		throws PortalException {

		if (head == null) {
			return wikiPagePersistence.findByN_T_First(nodeId, title, null);
		}

		return wikiPagePersistence.findByN_T_H_First(nodeId, title, head, null);
	}

	@Override
	public WikiPage getPage(long nodeId, String title, double version)
		throws PortalException {

		WikiPage page = null;

		if (version == 0) {
			page = getPage(nodeId, title);
		}
		else {
			page = wikiPagePersistence.findByN_T_V(nodeId, title, version);
		}

		return page;
	}

	@Override
	public WikiPage getPageByPageId(long pageId) throws PortalException {
		return wikiPagePersistence.findByPrimaryKey(pageId);
	}

	@Override
	public WikiPageDisplay getPageDisplay(
			long nodeId, String title, PortletURL viewPageURL,
			PortletURL editPageURL, String attachmentURLPrefix)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		return getPageDisplay(
			page, viewPageURL, editPageURL, attachmentURLPrefix);
	}

	@Override
	public WikiPageDisplay getPageDisplay(
			WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
			String attachmentURLPrefix)
		throws PortalException {

		String formattedContent = wikiEngineRenderer.convert(
			page, viewPageURL, editPageURL, attachmentURLPrefix);

		return new WikiPageDisplayImpl(
			page.getUserId(), page.getNodeId(), page.getTitle(),
			page.getVersion(), page.getContent(), formattedContent,
			page.getFormat(), page.isHead(), page.getAttachmentsFileEntries());
	}

	@Override
	public WikiPageDisplay getPageDisplay(
			WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
			String attachmentURLPrefix, ServiceContext serviceContext)
		throws PortalException {

		return getPageDisplay(
			page, viewPageURL, () -> editPageURL, attachmentURLPrefix,
			serviceContext);
	}

	@Override
	public WikiPageDisplay getPageDisplay(
			WikiPage page, PortletURL viewPageURL,
			Supplier<PortletURL> editPageURLSupplier,
			String attachmentURLPrefix, ServiceContext serviceContext)
		throws PortalException {

		HttpServletRequest request = serviceContext.getRequest();

		boolean workflowAssetPreview = false;

		if (request != null) {
			workflowAssetPreview = GetterUtil.getBoolean(
				request.getAttribute(WebKeys.WORKFLOW_ASSET_PREVIEW));
		}

		if (!workflowAssetPreview && page.isApproved()) {
			return wikiCacheHelper.getDisplay(
				page.getNodeId(), page.getTitle(), viewPageURL,
				editPageURLSupplier, attachmentURLPrefix);
		}

		return getPageDisplay(
			page, viewPageURL, editPageURLSupplier.get(), attachmentURLPrefix);
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, boolean head, int start, int end) {

		return getPages(
			nodeId, head, start, end, new PageCreateDateComparator(false));
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, boolean head, int status, int start, int end) {

		return getPages(
			nodeId, head, status, start, end,
			new PageCreateDateComparator(false));
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, boolean head, int status, int start, int end,
		OrderByComparator<WikiPage> obc) {

		if (status == WorkflowConstants.STATUS_ANY) {
			return wikiPagePersistence.findByN_H(nodeId, head, start, end, obc);
		}

		return wikiPagePersistence.findByN_H_S(
			nodeId, head, status, start, end, obc);
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, boolean head, int start, int end,
		OrderByComparator<WikiPage> obc) {

		return getPages(
			nodeId, head, WorkflowConstants.STATUS_APPROVED, start, end, obc);
	}

	@Override
	public List<WikiPage> getPages(long nodeId, int start, int end) {
		return getPages(
			nodeId, start, end, new PageCreateDateComparator(false));
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, int start, int end, OrderByComparator<WikiPage> obc) {

		return wikiPagePersistence.findByNodeId(nodeId, start, end, obc);
	}

	@Override
	public List<WikiPage> getPages(
		long resourcePrimKey, long nodeId, int status) {

		return wikiPagePersistence.findByR_N_S(resourcePrimKey, nodeId, status);
	}

	@Override
	public List<WikiPage> getPages(
		long userId, long nodeId, int status, int start, int end) {

		if (userId > 0) {
			return wikiPagePersistence.findByU_N_S(
				userId, nodeId, status, start, end,
				new PageCreateDateComparator(false));
		}

		return wikiPagePersistence.findByN_S(
			nodeId, status, start, end, new PageCreateDateComparator(false));
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, String title, boolean head, int start, int end) {

		return wikiPagePersistence.findByN_T_H(
			nodeId, title, head, start, end,
			new PageCreateDateComparator(false));
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, String title, int start, int end) {

		return wikiPagePersistence.findByN_T(
			nodeId, title, start, end, new PageCreateDateComparator(false));
	}

	@Override
	public List<WikiPage> getPages(
		long nodeId, String title, int start, int end,
		OrderByComparator<WikiPage> obc) {

		return wikiPagePersistence.findByN_T(nodeId, title, start, end, obc);
	}

	@Override
	public List<WikiPage> getPages(String format) {
		return wikiPagePersistence.findByFormat(format);
	}

	@Override
	public int getPagesCount(long nodeId) {
		return wikiPagePersistence.countByNodeId(nodeId);
	}

	@Override
	public int getPagesCount(long nodeId, boolean head) {
		return wikiPagePersistence.countByN_H_S(
			nodeId, head, WorkflowConstants.STATUS_APPROVED);
	}

	@Override
	public int getPagesCount(long nodeId, boolean head, int status) {
		if (status == WorkflowConstants.STATUS_ANY) {
			return wikiPagePersistence.countByN_H_NotS(
				nodeId, head, WorkflowConstants.STATUS_IN_TRASH);
		}

		return wikiPagePersistence.countByN_H_S(nodeId, head, status);
	}

	@Override
	public int getPagesCount(long nodeId, int status) {
		return wikiPagePersistence.countByN_S(nodeId, status);
	}

	@Override
	public int getPagesCount(long userId, long nodeId, int status) {
		if (userId > 0) {
			return wikiPagePersistence.countByU_N_S(userId, nodeId, status);
		}

		return wikiPagePersistence.countByN_S(nodeId, status);
	}

	@Override
	public int getPagesCount(long nodeId, String title) {
		return wikiPagePersistence.countByN_T(nodeId, title);
	}

	@Override
	public int getPagesCount(long nodeId, String title, boolean head) {
		return wikiPagePersistence.countByN_T_H(nodeId, title, head);
	}

	@Override
	public int getPagesCount(String format) {
		return wikiPagePersistence.countByFormat(format);
	}

	@Override
	public WikiPage getPreviousVersionPage(WikiPage page)
		throws PortalException {

		double previousVersion = MathUtil.format(page.getVersion() - 0.1, 1, 1);

		if (previousVersion < 1) {
			return null;
		}

		return getPage(page.getNodeId(), page.getTitle(), previousVersion);
	}

	@Override
	public List<WikiPage> getRecentChanges(
		long groupId, long nodeId, int start, int end) {

		Calendar cal = CalendarFactoryUtil.getCalendar();

		cal.add(Calendar.WEEK_OF_YEAR, -1);

		return wikiPageFinder.findByModifiedDate(
			groupId, nodeId, cal.getTime(), false, start, end);
	}

	@Override
	public int getRecentChangesCount(long groupId, long nodeId) {
		Calendar cal = CalendarFactoryUtil.getCalendar();

		cal.add(Calendar.WEEK_OF_YEAR, -1);

		return wikiPageFinder.countByModifiedDate(
			groupId, nodeId, cal.getTime(), false);
	}

	@Override
	public List<WikiPage> getRedirectorPages(
		long nodeId, boolean head, String redirectTitle, int status) {

		return wikiPagePersistence.findByN_H_R_S(
			nodeId, head, redirectTitle, status);
	}

	@Override
	public List<WikiPage> getRedirectorPages(
		long nodeId, String redirectTitle) {

		return wikiPagePersistence.findByN_R(nodeId, redirectTitle);
	}

	@Override
	public String[] getTempFileNames(
			long groupId, long userId, String folderName)
		throws PortalException {

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

	@Override
	public boolean hasDraftPage(long nodeId, String title) {
		int count = wikiPagePersistence.countByN_T_S(
			nodeId, title, WorkflowConstants.STATUS_DRAFT);

		if (count > 0) {
			return true;
		}

		return false;
	}

	@Override
	public void moveDependentToTrash(WikiPage page, long trashEntryId)
		throws PortalException {

		moveDependentToTrash(page, trashEntryId, false);
	}

	/**
	 * @deprecated As of Wilberforce (7.0.x), replaced by {@link
	 *             #renamePage(long, long, String, String, ServiceContext)}
	 */
	@Deprecated
	@Override
	public void movePage(
			long userId, long nodeId, String title, String newTitle,
			ServiceContext serviceContext)
		throws PortalException {

		renamePage(userId, nodeId, title, newTitle, true, serviceContext);
	}

	@Override
	public FileEntry movePageAttachmentToTrash(
			long userId, long nodeId, String title, String fileName)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		FileEntry fileEntry = PortletFileRepositoryUtil.getPortletFileEntry(
			page.getGroupId(), page.getAttachmentsFolderId(), fileName);

		fileEntry = PortletFileRepositoryUtil.movePortletFileEntryToTrash(
			userId, fileEntry.getFileEntryId());

		JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

		extraDataJSONObject.put("fileEntryId", fileEntry.getFileEntryId());
		extraDataJSONObject.put(
			"fileEntryTitle", TrashUtil.getOriginalTitle(fileEntry.getTitle()));
		extraDataJSONObject.put("title", page.getTitle());
		extraDataJSONObject.put("version", page.getVersion());

		SocialActivityManagerUtil.addActivity(
			userId, page, SocialActivityConstants.TYPE_MOVE_ATTACHMENT_TO_TRASH,
			extraDataJSONObject.toString(), 0);

		return fileEntry;
	}

	@Override
	public WikiPage movePageFromTrash(
			long userId, long nodeId, String title, long newNodeId,
			String newParentTitle)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		if (!page.isInTrash()) {
			throw new RestoreEntryException(
				RestoreEntryException.INVALID_STATUS);
		}

		if (page.isInTrashExplicitly()) {
			movePageFromTrash(userId, page, newNodeId, newParentTitle);
		}
		else {
			moveDependentFromTrash(page, newNodeId, newParentTitle);
		}

		return page;
	}

	/**
	 * @deprecated As of Wilberforce (7.0.x), replaced by {@link
	 *             #movePageFromTrash(long, long, String, long, String)} *
	 */
	@Deprecated
	@Override
	public WikiPage movePageFromTrash(
			long userId, long nodeId, String title, String newParentTitle,
			ServiceContext serviceContext)
		throws PortalException {

		return movePageFromTrash(userId, nodeId, title, nodeId, newParentTitle);
	}

	@Override
	public WikiPage movePageToTrash(long userId, long nodeId, String title)
		throws PortalException {

		WikiPage page = wikiPagePersistence.fetchByN_T_H_First(
			nodeId, title, true, null);

		if (page != null) {
			return movePageToTrash(userId, page);
		}

		return null;
	}

	@Override
	public WikiPage movePageToTrash(
			long userId, long nodeId, String title, double version)
		throws PortalException {

		WikiPage page = wikiPagePersistence.findByN_T_V(nodeId, title, version);

		return movePageToTrash(userId, page);
	}

	@Override
	public WikiPage movePageToTrash(long userId, WikiPage page)
		throws PortalException {

		if (page.isInTrash()) {
			throw new TrashEntryException();
		}

		// Page

		int oldStatus = page.getStatus();
		String oldTitle = page.getTitle();

		if (oldStatus == WorkflowConstants.STATUS_PENDING) {
			page.setStatus(WorkflowConstants.STATUS_DRAFT);

			page = wikiPagePersistence.update(page);
		}

		List<WikiPage> pageVersions = wikiPagePersistence.findByR_N_H(
			page.getResourcePrimKey(), page.getNodeId(), false);

		pageVersions = ListUtil.sort(pageVersions, new PageVersionComparator());

		List<ObjectValuePair<Long, Integer>> pageVersionStatusOVPs =
			new ArrayList<>();

		if ((pageVersions != null) && !pageVersions.isEmpty()) {
			pageVersionStatusOVPs = getPageVersionStatuses(pageVersions);
		}

		page = updateStatus(
			userId, page, WorkflowConstants.STATUS_IN_TRASH,
			new ServiceContext(), new HashMap<String, Serializable>());

		// Trash

		WikiPageResource pageResource =
			wikiPageResourcePersistence.fetchByPrimaryKey(
				page.getResourcePrimKey());

		UnicodeProperties typeSettingsProperties = new UnicodeProperties();

		typeSettingsProperties.put("title", page.getTitle());

		TrashEntry trashEntry = trashEntryLocalService.addTrashEntry(
			userId, page.getGroupId(), WikiPage.class.getName(),
			page.getResourcePrimKey(), page.getUuid(), null, oldStatus,
			pageVersionStatusOVPs, typeSettingsProperties);

		String trashTitle = TrashUtil.getTrashTitle(trashEntry.getEntryId());

		for (WikiPage pageVersion : pageVersions) {
			pageVersion.setTitle(trashTitle);
			pageVersion.setStatus(WorkflowConstants.STATUS_IN_TRASH);

			wikiPagePersistence.update(pageVersion);
		}

		pageResource.setTitle(trashTitle);

		wikiPageResourcePersistence.update(pageResource);

		page.setTitle(trashTitle);

		page = wikiPagePersistence.update(page);

		// Child pages

		moveDependentChildPagesToTrash(
			page.getNodeId(), oldTitle, trashTitle, trashEntry.getEntryId(),
			true);

		// Redirect pages

		moveDependentRedirectorPagesToTrash(
			page.getNodeId(), oldTitle, trashTitle, trashEntry.getEntryId(),
			true);

		// Asset

		assetEntryLocalService.updateVisible(
			WikiPage.class.getName(), page.getResourcePrimKey(), false);

		// Attachments

		for (FileEntry fileEntry : page.getAttachmentsFileEntries()) {
			PortletFileRepositoryUtil.movePortletFileEntryToTrash(
				userId, fileEntry.getFileEntryId());
		}

		// Comment

		CommentManagerUtil.moveDiscussionToTrash(
			WikiPage.class.getName(), page.getResourcePrimKey());

		// Social

		JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

		extraDataJSONObject.put(
			"title", TrashUtil.getOriginalTitle(page.getTitle()));
		extraDataJSONObject.put("version", page.getVersion());

		SocialActivityManagerUtil.addActivity(
			userId, page, SocialActivityConstants.TYPE_MOVE_TO_TRASH,
			extraDataJSONObject.toString(), 0);

		// Indexer

		Indexer<WikiPage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			WikiPage.class);

		indexer.reindex(page);

		// Workflow

		if (oldStatus == WorkflowConstants.STATUS_PENDING) {
			workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
				page.getCompanyId(), page.getGroupId(),
				WikiPage.class.getName(), page.getPageId());
		}

		return page;
	}

	@Override
	public void renamePage(
			long userId, long nodeId, String title, String newTitle,
			boolean strict, ServiceContext serviceContext)
		throws PortalException {

		WikiPage latestWikiPage = fetchLatestPage(
			nodeId, title, WorkflowConstants.STATUS_ANY, false);

		if ((latestWikiPage != null) && !latestWikiPage.isApproved()) {
			throw new PageVersionException();
		}

		newTitle = StringUtil.replace(
			newTitle, CharPool.NO_BREAK_SPACE, CharPool.SPACE);

		wikiPageTitleValidator.validate(newTitle);

		if (StringUtil.equalsIgnoreCase(title, newTitle)) {
			throw new DuplicatePageException(newTitle);
		}

		if (isUsedTitle(nodeId, newTitle)) {

			// Support moving back to a previously moved title

			WikiPage page = getPage(nodeId, newTitle);

			String content = page.getContent();

			if (((page.getVersion() == WikiPageConstants.VERSION_DEFAULT) &&
				 (content.length() < 200)) ||
				!strict) {

				deletePage(nodeId, newTitle);
			}
			else {
				throw new DuplicatePageException(newTitle);
			}
		}

		WikiPage page = getPage(nodeId, title);

		String summary = page.getSummary();

		if (Validator.isNotNull(page.getRedirectTitle())) {
			page.setRedirectTitle(StringPool.BLANK);

			summary = StringPool.BLANK;
		}

		populateServiceContext(serviceContext, page);

		serviceContext.setCommand(Constants.RENAME);

		WikiPageRenameContentProcessor wikiPageRenameContentProcessor =
			_serviceTrackerMap.getService(page.getFormat());

		String content = page.getContent();

		if (wikiPageRenameContentProcessor != null) {
			List<WikiPage> versionPages = wikiPagePersistence.findByN_T_H(
				nodeId, title, false);

			for (WikiPage curPage : versionPages) {
				curPage.setTitle(newTitle);
				curPage.setContent(
					wikiPageRenameContentProcessor.processContent(
						curPage.getNodeId(), title, newTitle,
						curPage.getContent()));

				wikiPagePersistence.update(curPage);
			}

			content = wikiPageRenameContentProcessor.processContent(
				page.getNodeId(), title, newTitle, content);
		}

		updatePage(
			userId, page, 0, newTitle, content, summary, page.isMinorEdit(),
			page.getFormat(), page.getParentTitle(), page.getRedirectTitle(),
			serviceContext);
	}

	@Override
	public void renamePage(
			long userId, long nodeId, String title, String newTitle,
			ServiceContext serviceContext)
		throws PortalException {

		renamePage(userId, nodeId, title, newTitle, true, serviceContext);
	}

	@Override
	public void restorePageAttachmentFromTrash(
			long userId, long nodeId, String title, String fileName)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		FileEntry fileEntry = PortletFileRepositoryUtil.getPortletFileEntry(
			page.getGroupId(), page.getAttachmentsFolderId(), fileName);

		JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

		extraDataJSONObject.put("fileEntryId", fileEntry.getFileEntryId());
		extraDataJSONObject.put(
			"fileEntryTitle", TrashUtil.getOriginalTitle(fileEntry.getTitle()));
		extraDataJSONObject.put("title", page.getTitle());
		extraDataJSONObject.put("version", page.getVersion());

		PortletFileRepositoryUtil.restorePortletFileEntryFromTrash(
			userId, fileEntry.getFileEntryId());

		SocialActivityManagerUtil.addActivity(
			userId, page,
			SocialActivityConstants.TYPE_RESTORE_ATTACHMENT_FROM_TRASH,
			extraDataJSONObject.toString(), 0);
	}

	@Override
	public void restorePageFromTrash(long userId, WikiPage page)
		throws PortalException {

		if (!page.isInTrash()) {
			throw new RestoreEntryException(
				RestoreEntryException.INVALID_STATUS);
		}

		if (page.isInTrashExplicitly()) {
			movePageFromTrash(
				userId, page, page.getNodeId(), page.getParentTitle());
		}
		else {
			moveDependentFromTrash(
				page, page.getNodeId(), page.getParentTitle());
		}
	}

	@Override
	public WikiPage revertPage(
			long userId, long nodeId, String title, double version,
			ServiceContext serviceContext)
		throws PortalException {

		WikiPage oldPage = getPage(nodeId, title, version);

		populateServiceContext(serviceContext, oldPage);

		return updatePage(
			userId, nodeId, title, 0, oldPage.getContent(),
			WikiPageConstants.REVERTED + " to " + version, false,
			oldPage.getFormat(), getParentPageTitle(oldPage),
			oldPage.getRedirectTitle(), serviceContext);
	}

	@Override
	public void subscribePage(long userId, long nodeId, String title)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		subscriptionLocalService.addSubscription(
			userId, page.getGroupId(), WikiPage.class.getName(),
			page.getResourcePrimKey());
	}

	@Override
	public void unsubscribePage(long userId, long nodeId, String title)
		throws PortalException {

		WikiPage page = getPage(nodeId, title);

		subscriptionLocalService.deleteSubscription(
			userId, WikiPage.class.getName(), page.getResourcePrimKey());
	}

	@Override
	public void updateAsset(
			long userId, WikiPage page, long[] assetCategoryIds,
			String[] assetTagNames, long[] assetLinkEntryIds, Double priority)
		throws PortalException {

		boolean addDraftAssetEntry = false;

		if (!page.isApproved() &&
			(page.getVersion() != WikiPageConstants.VERSION_DEFAULT)) {

			int approvedPagesCount = wikiPagePersistence.countByN_T_S(
				page.getNodeId(), page.getTitle(),
				WorkflowConstants.STATUS_APPROVED);

			if (approvedPagesCount > 0) {
				addDraftAssetEntry = true;
			}
		}

		AssetEntry assetEntry = null;

		Date publishDate = null;

		if (addDraftAssetEntry) {
			assetEntry = assetEntryLocalService.updateEntry(
				userId, page.getGroupId(), page.getCreateDate(),
				page.getModifiedDate(), WikiPage.class.getName(),
				page.getPrimaryKey(), page.getUuid(), 0, assetCategoryIds,
				assetTagNames, true, false, null, null, publishDate, null,
				ContentTypes.TEXT_HTML, page.getTitle(), null, null, null, null,
				0, 0, priority);
		}
		else {
			if (page.isApproved()) {
				publishDate = page.getCreateDate();
			}

			assetEntry = assetEntryLocalService.updateEntry(
				userId, page.getGroupId(), page.getCreateDate(),
				page.getModifiedDate(), WikiPage.class.getName(),
				page.getResourcePrimKey(), page.getUuid(), 0, assetCategoryIds,
				assetTagNames, true, page.isApproved(), null, null, publishDate,
				null, ContentTypes.TEXT_HTML, page.getTitle(), null, null, null,
				null, 0, 0, priority);
		}

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

	@Override
	public WikiPage updatePage(
			long userId, long nodeId, String title, double version,
			String content, String summary, boolean minorEdit, String format,
			String parentTitle, String redirectTitle,
			ServiceContext serviceContext)
		throws PortalException {

		// Page

		WikiPage oldPage = null;

		try {
			oldPage = wikiPagePersistence.findByN_T_First(nodeId, title, null);

			if ((version > 0) && (version != oldPage.getVersion())) {
				throw new PageVersionException();
			}

			return updatePage(
				userId, oldPage, 0, StringPool.BLANK, content, summary,
				minorEdit, format, parentTitle, redirectTitle, serviceContext);
		}
		catch (NoSuchPageException nspe) {
			return addPage(
				userId, nodeId, title, WikiPageConstants.VERSION_DEFAULT,
				content, summary, minorEdit, format, true, parentTitle,
				redirectTitle, serviceContext);
		}
	}

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

		WikiPageResource pageResource =
			wikiPageResourcePersistence.findByPrimaryKey(resourcePrimKey);

		WikiPage page = wikiPagePersistence.findByN_T_First(
			pageResource.getNodeId(), pageResource.getTitle(),
			new PageVersionComparator());

		return updateStatus(
			userId, page, status, serviceContext,
			new HashMap<String, Serializable>());
	}

	/**
	 * @deprecated As of Wilberforce (7.0.x), replaced by {@link
	 *             #updateStatus(long, WikiPage, int, ServiceContext, Map)}
	 */
	@Deprecated
	@Override
	public WikiPage updateStatus(
			long userId, WikiPage page, int status,
			ServiceContext serviceContext)
		throws PortalException {

		return updateStatus(
			userId, page, status, serviceContext,
			new HashMap<String, Serializable>());
	}

	@Override
	public WikiPage updateStatus(
			long userId, WikiPage page, int status,
			ServiceContext serviceContext,
			Map<String, Serializable> workflowContext)
		throws PortalException {

		// Page

		User user = userLocalService.getUser(userId);

		int oldStatus = page.getStatus();

		page.setStatus(status);
		page.setStatusByUserId(userId);
		page.setStatusByUserName(user.getFullName());
		page.setStatusDate(new Date());

		page = wikiPagePersistence.update(page);

		if (status == WorkflowConstants.STATUS_APPROVED) {
			String cmd = GetterUtil.getString(
				workflowContext.get(WorkflowConstants.CONTEXT_COMMAND));

			if (cmd.equals(Constants.RENAME)) {
				long resourcePrimKey = page.getResourcePrimKey();

				WikiPage oldPage = getPage(resourcePrimKey, true);

				page = doRenamePage(
					userId, page.getNodeId(), oldPage.getTitle(),
					page.getTitle(), serviceContext);
			}

			// Asset

			if ((oldStatus != WorkflowConstants.STATUS_APPROVED) &&
				(page.getVersion() != WikiPageConstants.VERSION_DEFAULT)) {

				AssetEntry draftAssetEntry = assetEntryLocalService.fetchEntry(
					WikiPage.class.getName(), page.getPrimaryKey());

				if (draftAssetEntry != null) {
					long[] assetCategoryIds = draftAssetEntry.getCategoryIds();
					String[] assetTagNames = draftAssetEntry.getTagNames();

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

					long[] assetLinkEntryIds = ListUtil.toLongArray(
						assetLinks, AssetLink.ENTRY_ID2_ACCESSOR);

					AssetEntry assetEntry = assetEntryLocalService.updateEntry(
						userId, page.getGroupId(), page.getCreateDate(),
						page.getModifiedDate(), WikiPage.class.getName(),
						page.getResourcePrimKey(), page.getUuid(), 0,
						assetCategoryIds, assetTagNames, true, true, null, null,
						page.getCreateDate(), null, ContentTypes.TEXT_HTML,
						page.getTitle(), null, null, null, null, 0, 0, null);

					// Asset Links

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

					SystemEventHierarchyEntryThreadLocal.push(WikiPage.class);

					try {
						assetEntryLocalService.deleteEntry(
							draftAssetEntry.getEntryId());
					}
					finally {
						SystemEventHierarchyEntryThreadLocal.pop(
							WikiPage.class);
					}
				}
			}

			assetEntryLocalService.updateVisible(
				WikiPage.class.getName(), page.getResourcePrimKey(), true);

			// Social

			WikiGroupServiceOverriddenConfiguration
				wikiGroupServiceOverriddenConfiguration =
					configurationProvider.getConfiguration(
						WikiGroupServiceOverriddenConfiguration.class,
						new GroupServiceSettingsLocator(
							page.getGroupId(), WikiConstants.SERVICE_NAME));

			if ((oldStatus != WorkflowConstants.STATUS_IN_TRASH) &&
				(!page.isMinorEdit() ||
				 wikiGroupServiceOverriddenConfiguration.
					 pageMinorEditAddSocialActivity())) {

				JSONObject extraDataJSONObject =
					JSONFactoryUtil.createJSONObject();

				extraDataJSONObject.put("title", page.getTitle());
				extraDataJSONObject.put("version", page.getVersion());

				int type = WikiActivityKeys.UPDATE_PAGE;

				if (serviceContext.isCommandAdd()) {
					type = WikiActivityKeys.ADD_PAGE;
				}

				SocialActivityManagerUtil.addActivity(
					userId, page, type, extraDataJSONObject.toString(), 0);
			}

			// Subscriptions

			if (NotificationThreadLocal.isEnabled() &&
				(!page.isMinorEdit() ||
				 wikiGroupServiceOverriddenConfiguration.
					 pageMinorEditSendEmail())) {

				notifySubscribers(
					userId, page,
					(String)workflowContext.get(WorkflowConstants.CONTEXT_URL),
					serviceContext);
			}

			// Cache

			clearPageCache(page);
		}

		// Head

		if (status == WorkflowConstants.STATUS_APPROVED) {
			page.setHead(true);

			List<WikiPage> pages = wikiPagePersistence.findByN_T_H(
				page.getNodeId(), page.getTitle(), true);

			for (WikiPage curPage : pages) {
				if (!curPage.equals(page)) {
					curPage.setHead(false);

					wikiPagePersistence.update(curPage);
				}
			}
		}
		else if (status != WorkflowConstants.STATUS_IN_TRASH) {
			page.setHead(false);

			List<WikiPage> pages = wikiPagePersistence.findByN_T_S(
				page.getNodeId(), page.getTitle(),
				WorkflowConstants.STATUS_APPROVED);

			for (WikiPage curPage : pages) {
				if (!curPage.equals(page)) {
					curPage.setHead(true);

					wikiPagePersistence.update(curPage);

					break;
				}
			}
		}

		// Indexer

		Indexer<WikiPage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			WikiPage.class);

		indexer.reindex(page);

		return wikiPagePersistence.update(page);
	}

	/**
	 * @deprecated As of Wilberforce (7.0.x), replaced by {@link
	 *             WikiPageTitleValidator#validate(String)}
	 */
	@Deprecated
	@Override
	public void validateTitle(String title) throws PortalException {
		wikiPageTitleValidator.validate(title);
	}

	protected void clearPageCache(WikiPage page) {
		if (!WikiCacheThreadLocal.isClearCache()) {
			return;
		}

		wikiCacheHelper.clearCache(page.getNodeId());
	}

	protected void deletePageAttachment(long fileEntryId)
		throws PortalException {

		PortletFileRepositoryUtil.deletePortletFileEntry(fileEntryId);
	}

	protected WikiPage doRenamePage(
			long userId, long nodeId, String title, String newTitle,
			ServiceContext serviceContext)
		throws PortalException {

		// Version pages

		List<WikiPage> versionPages = wikiPagePersistence.findByN_T(
			nodeId, title, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
			new PageVersionComparator());

		WikiPage page = fetchLatestPage(
			nodeId, newTitle, WorkflowConstants.STATUS_ANY, false);

		if (page == null) {
			page = getLatestPage(
				nodeId, title, WorkflowConstants.STATUS_ANY, false);
		}

		for (WikiPage versionPage : versionPages) {
			versionPage.setTitle(newTitle);
			versionPage.setRedirectTitle(page.getRedirectTitle());

			wikiPagePersistence.update(versionPage);
		}

		// Page resource

		long resourcePrimKey = page.getResourcePrimKey();

		WikiPageResource pageResource =
			wikiPageResourcePersistence.findByPrimaryKey(resourcePrimKey);

		pageResource.setTitle(newTitle);

		wikiPageResourcePersistence.update(pageResource);

		// Create stub page at the old location

		double version = WikiPageConstants.VERSION_DEFAULT;
		String summary = LanguageUtil.format(
			serviceContext.getLocale(), "renamed-as-x", newTitle);
		String format = page.getFormat();
		boolean head = true;
		String parentTitle = page.getParentTitle();

		String redirectTitle = page.getTitle();

		String content =
			StringPool.DOUBLE_OPEN_BRACKET + redirectTitle +
				StringPool.DOUBLE_CLOSE_BRACKET;

		serviceContext.setAddGroupPermissions(true);
		serviceContext.setAddGuestPermissions(true);

		populateServiceContext(serviceContext, page);

		boolean workflowEnabled = WorkflowThreadLocal.isEnabled();

		WorkflowThreadLocal.setEnabled(false);

		serviceContext.setCommand(Constants.ADD);

		WikiPage newPage = addPage(
			userId, nodeId, title, version, content, summary, false, format,
			head, parentTitle, redirectTitle, serviceContext);

		resourceLocalService.deleteResource(
			page.getCompanyId(), WikiPage.class.getName(),
			ResourceConstants.SCOPE_INDIVIDUAL, newPage.getResourcePrimKey());

		resourceLocalService.copyModelResources(
			page.getCompanyId(), WikiPage.class.getName(),
			page.getResourcePrimKey(), newPage.getResourcePrimKey());

		WorkflowThreadLocal.setEnabled(workflowEnabled);

		// Child pages

		List<WikiPage> childPages = wikiPagePersistence.findByN_P(
			nodeId, title);

		for (WikiPage childPage : childPages) {
			childPage.setParentTitle(newTitle);

			wikiPagePersistence.update(childPage);
		}

		// Redirect pages

		List<WikiPage> redirectorPages = getRedirectorPages(nodeId, title);

		for (WikiPage redirectorPage : redirectorPages) {
			redirectorPage.setRedirectTitle(newTitle);

			wikiPagePersistence.update(redirectorPage);
		}

		// Asset

		updateAsset(
			userId, page, serviceContext.getAssetCategoryIds(),
			serviceContext.getAssetTagNames(),
			serviceContext.getAssetLinkEntryIds(),
			serviceContext.getAssetPriority());

		return page;
	}

	protected String getDiffsURL(
			WikiPage page, WikiPage previousVersionPage,
			ServiceContext serviceContext)
		throws PortalException {

		if (previousVersionPage == null) {
			return StringPool.BLANK;
		}

		HttpServletRequest request = serviceContext.getRequest();

		if (request == null) {
			return StringPool.BLANK;
		}

		PortletURL portletURL = null;

		long plid = serviceContext.getPlid();

		if (plid == LayoutConstants.DEFAULT_PLID) {
			portletURL = PortalUtil.getControlPanelPortletURL(
				request, WikiPortletKeys.WIKI_ADMIN,
				PortletRequest.RENDER_PHASE);
		}
		else {
			portletURL = PortletURLFactoryUtil.create(
				request, WikiPortletKeys.WIKI, plid,
				PortletRequest.RENDER_PHASE);
		}

		portletURL.setParameter(
			"mvcRenderCommandName", "/wiki/compare_versions");
		portletURL.setParameter("nodeId", String.valueOf(page.getNodeId()));
		portletURL.setParameter("title", page.getTitle());
		portletURL.setParameter(
			"sourceVersion", String.valueOf(previousVersionPage.getVersion()));
		portletURL.setParameter(
			"targetVersion", String.valueOf(page.getVersion()));
		portletURL.setParameter("type", "html");

		return portletURL.toString();
	}

	protected String getPageURL(WikiPage page, ServiceContext serviceContext)
		throws PortalException {

		HttpServletRequest request = serviceContext.getRequest();

		if (request == null) {
			return StringPool.BLANK;
		}

		String layoutFullURL = LayoutURLUtil.getLayoutURL(
			page.getGroupId(), WikiPortletKeys.WIKI, serviceContext);

		if (Validator.isNotNull(layoutFullURL)) {
			return StringBundler.concat(
				layoutFullURL, Portal.FRIENDLY_URL_SEPARATOR, "wiki/",
				String.valueOf(page.getNodeId()), StringPool.SLASH,
				URLCodec.encodeURL(WikiEscapeUtil.escapeName(page.getTitle())));
		}

		PortletURL portletURL = PortalUtil.getControlPanelPortletURL(
			request, WikiPortletKeys.WIKI_ADMIN, PortletRequest.RENDER_PHASE);

		portletURL.setParameter(
			"mvcRenderCommandName", "/wiki/view_page_activities");
		portletURL.setParameter("nodeId", String.valueOf(page.getNodeId()));
		portletURL.setParameter("title", page.getTitle());

		return portletURL.toString();
	}

	protected List<ObjectValuePair<Long, Integer>> getPageVersionStatuses(
		List<WikiPage> pages) {

		List<ObjectValuePair<Long, Integer>> pageVersionStatusOVPs =
			new ArrayList<>(pages.size());

		for (WikiPage page : pages) {
			int status = page.getStatus();

			if (status == WorkflowConstants.STATUS_PENDING) {
				status = WorkflowConstants.STATUS_DRAFT;
			}

			ObjectValuePair<Long, Integer> pageVersionStatusOVP =
				new ObjectValuePair<>(page.getPageId(), status);

			pageVersionStatusOVPs.add(pageVersionStatusOVP);
		}

		return pageVersionStatusOVPs;
	}

	protected String getParentPageTitle(WikiPage page) {

		// LPS-4586

		try {
			WikiPage parentPage = getPage(
				page.getNodeId(), page.getParentTitle());

			return parentPage.getTitle();
		}
		catch (Exception e) {
			return null;
		}
	}

	protected boolean isLinkedTo(WikiPage page, String targetTitle)
		throws PortalException {

		Map<String, Boolean> links = wikiCacheHelper.getOutgoingLinks(
			page, wikiEngineRenderer);

		Boolean link = links.get(StringUtil.toLowerCase(targetTitle));

		if (link != null) {
			return true;
		}

		return false;
	}

	protected boolean isUsedTitle(long nodeId, String title) {
		if (getPagesCount(nodeId, title) > 0) {
			return true;
		}

		return false;
	}

	protected void moveDependentChildPagesFromTrash(
			WikiPage newParentPage, long oldParentPageNodeId,
			String oldParentPageTitle)
		throws PortalException {

		List<WikiPage> childPages = getChildren(
			oldParentPageNodeId, true, oldParentPageTitle,
			WorkflowConstants.STATUS_IN_TRASH);

		for (WikiPage childPage : childPages) {
			childPage = getPageByPageId(childPage.getPageId());

			childPage.setParentTitle(newParentPage.getTitle());

			childPage = wikiPagePersistence.update(childPage);

			if (childPage.isInTrashImplicitly()) {
				moveDependentFromTrash(
					childPage, newParentPage.getNodeId(),
					newParentPage.getTitle());
			}
		}
	}

	protected void moveDependentChildPagesToTrash(
			long parentNodeId, String parentTitle, String parentTrashTitle,
			long trashEntryId, boolean createTrashVersion)
		throws PortalException {

		List<WikiPage> childPages = wikiPagePersistence.findByN_H_P(
			parentNodeId, true, parentTitle);

		for (WikiPage childPage : childPages) {
			childPage = getPageByPageId(childPage.getPageId());

			childPage.setParentTitle(parentTrashTitle);

			childPage = wikiPagePersistence.update(childPage);

			if (!childPage.isInTrash()) {
				moveDependentToTrash(
					childPage, trashEntryId, createTrashVersion);
			}
		}
	}

	protected void moveDependentFromTrash(
			WikiPage page, long newNodeId, String newParentTitle)
		throws PortalException {

		// Page

		String trashTitle = page.getTitle();

		TrashVersion trashVersion = trashVersionLocalService.fetchVersion(
			WikiPage.class.getName(), page.getPageId());

		long oldNodeId = page.getNodeId();

		if (newNodeId == 0) {
			newNodeId = oldNodeId;
		}

		page.setNodeId(newNodeId);

		WikiPageResource pageResource =
			wikiPageResourcePersistence.findByPrimaryKey(
				page.getResourcePrimKey());

		pageResource.setNodeId(newNodeId);

		if (trashVersion != null) {
			String originalTitle = TrashUtil.getOriginalTitle(page.getTitle());

			pageResource.setTitle(originalTitle);

			page.setTitle(originalTitle);
		}

		wikiPageResourcePersistence.update(pageResource);

		page.setParentTitle(newParentTitle);

		page = wikiPagePersistence.update(page);

		int oldStatus = WorkflowConstants.STATUS_APPROVED;

		if (trashVersion != null) {
			oldStatus = trashVersion.getStatus();
		}

		// Version pages

		List<WikiPage> versionPages = wikiPagePersistence.findByR_N(
			page.getResourcePrimKey(), page.getNodeId());

		for (WikiPage versionPage : versionPages) {

			// Version page

			versionPage.setNodeId(newNodeId);
			versionPage.setTitle(page.getTitle());
			versionPage.setParentTitle(newParentTitle);

			trashVersion = trashVersionLocalService.fetchVersion(
				WikiPage.class.getName(), versionPage.getPageId());

			int versionPageOldStatus = WorkflowConstants.STATUS_APPROVED;

			if (trashVersion != null) {
				versionPageOldStatus = trashVersion.getStatus();
			}

			versionPage.setStatus(versionPageOldStatus);

			wikiPagePersistence.update(versionPage);

			// Trash

			if (trashVersion != null) {
				trashVersionLocalService.deleteTrashVersion(trashVersion);
			}
		}

		// Asset

		if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
			assetEntryLocalService.updateVisible(
				WikiPage.class.getName(), page.getResourcePrimKey(), true);
		}

		// Attachments

		WikiNode node = page.getNode();

		for (FileEntry fileEntry : page.getAttachmentsFileEntries()) {
			PortletFileRepositoryUtil.restorePortletFileEntryFromTrash(
				node.getStatusByUserId(), fileEntry.getFileEntryId());
		}

		// Indexer

		Indexer<WikiPage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			WikiPage.class);

		indexer.reindex(page);

		// Child pages

		moveDependentChildPagesFromTrash(page, oldNodeId, trashTitle);

		// Redirect pages

		moveDependentRedirectorPagesFromTrash(page, oldNodeId, trashTitle);
	}

	protected void moveDependentRedirectorPagesFromTrash(
			WikiPage newRedirectPage, long oldRedirectPageNodeId,
			String oldRedirectPageTrashTitle)
		throws PortalException {

		List<WikiPage> redirectorPages = getRedirectorPages(
			oldRedirectPageNodeId, true, oldRedirectPageTrashTitle,
			WorkflowConstants.STATUS_IN_TRASH);

		for (WikiPage redirectorPage : redirectorPages) {
			redirectorPage = getPageByPageId(redirectorPage.getPageId());

			redirectorPage.setRedirectTitle(newRedirectPage.getTitle());

			redirectorPage = wikiPagePersistence.update(redirectorPage);

			if (redirectorPage.isInTrashImplicitly()) {
				moveDependentFromTrash(
					redirectorPage, newRedirectPage.getNodeId(),
					redirectorPage.getParentTitle());
			}
		}
	}

	protected void moveDependentRedirectorPagesToTrash(
			long redirectPageNodeId, String redirectPageTitle,
			String redirectPageTrashTitle, long trashEntryId,
			boolean createTrashVersion)
		throws PortalException {

		List<WikiPage> redirectorPages = wikiPagePersistence.findByN_H_R(
			redirectPageNodeId, true, redirectPageTitle);

		for (WikiPage redirectorPage : redirectorPages) {
			redirectorPage = getPageByPageId(redirectorPage.getPageId());

			redirectorPage.setRedirectTitle(redirectPageTrashTitle);

			redirectorPage = wikiPagePersistence.update(redirectorPage);

			if (!redirectorPage.isInTrash()) {
				moveDependentToTrash(
					redirectorPage, trashEntryId, createTrashVersion);
			}
		}
	}

	protected void moveDependentToTrash(
			WikiPage page, long trashEntryId, boolean createTrashVersion)
		throws PortalException {

		// Page

		String oldTitle = page.getTitle();

		String trashTitle = oldTitle;

		if (createTrashVersion) {
			UnicodeProperties typeSettingsProperties = new UnicodeProperties();

			typeSettingsProperties.put("title", oldTitle);

			TrashVersion trashVersion =
				trashVersionLocalService.addTrashVersion(
					trashEntryId, WikiPage.class.getName(), page.getPageId(),
					page.getStatus(), typeSettingsProperties);

			trashTitle = TrashUtil.getTrashTitle(trashVersion.getVersionId());

			WikiPageResource pageResource =
				wikiPageResourcePersistence.findByPrimaryKey(
					page.getResourcePrimKey());

			pageResource.setTitle(trashTitle);

			wikiPageResourcePersistence.update(pageResource);

			page.setTitle(trashTitle);

			page = wikiPagePersistence.update(page);
		}

		int oldStatus = page.getStatus();

		if (oldStatus == WorkflowConstants.STATUS_IN_TRASH) {
			return;
		}

		// Version pages

		List<WikiPage> versionPages = wikiPagePersistence.findByR_N(
			page.getResourcePrimKey(), page.getNodeId());

		for (WikiPage versionPage : versionPages) {

			// Version page

			versionPage.setTitle(page.getTitle());

			int versionPageOldStatus = versionPage.getStatus();

			versionPage.setStatus(WorkflowConstants.STATUS_IN_TRASH);

			versionPage = wikiPagePersistence.update(versionPage);

			// Trash

			int status = versionPageOldStatus;

			if (versionPageOldStatus == WorkflowConstants.STATUS_PENDING) {
				status = WorkflowConstants.STATUS_DRAFT;
			}

			if (versionPageOldStatus != WorkflowConstants.STATUS_APPROVED) {
				trashVersionLocalService.addTrashVersion(
					trashEntryId, WikiPage.class.getName(),
					versionPage.getPageId(), status, null);
			}
		}

		// Asset

		if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
			assetEntryLocalService.updateVisible(
				WikiPage.class.getName(), page.getResourcePrimKey(), false);
		}

		// Attachments

		WikiNode node = page.getNode();

		for (FileEntry fileEntry : page.getAttachmentsFileEntries()) {
			PortletFileRepositoryUtil.movePortletFileEntryToTrash(
				node.getStatusByUserId(), fileEntry.getFileEntryId());
		}

		// Indexer

		Indexer<WikiPage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			WikiPage.class);

		indexer.reindex(page);

		// Cache

		if (WikiCacheThreadLocal.isClearCache()) {
			wikiCacheHelper.clearCache(page.getNodeId());
		}

		// Workflow

		if (oldStatus == WorkflowConstants.STATUS_PENDING) {
			workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
				page.getCompanyId(), page.getGroupId(),
				WikiPage.class.getName(), page.getResourcePrimKey());
		}

		// Child pages

		moveDependentChildPagesToTrash(
			page.getNodeId(), oldTitle, trashTitle, trashEntryId,
			createTrashVersion);

		// Redirect pages

		moveDependentRedirectorPagesToTrash(
			page.getNodeId(), oldTitle, trashTitle, trashEntryId,
			createTrashVersion);
	}

	protected void movePageFromTrash(
			long userId, WikiPage page, long newNodeId, String newParentTitle)
		throws PortalException {

		// Page

		String trashTitle = page.getTitle();

		String originalTitle = TrashUtil.getOriginalTitle(trashTitle);

		long oldNodeId = page.getNodeId();

		if (newNodeId == 0) {
			newNodeId = oldNodeId;
		}

		List<WikiPage> pageVersions = wikiPagePersistence.findByR_N_H(
			page.getResourcePrimKey(), oldNodeId, false);

		for (WikiPage pageVersion : pageVersions) {
			pageVersion.setNodeId(newNodeId);
			pageVersion.setTitle(originalTitle);
			pageVersion.setParentTitle(newParentTitle);

			wikiPagePersistence.update(pageVersion);
		}

		WikiPageResource pageResource =
			wikiPageResourcePersistence.fetchByPrimaryKey(
				page.getResourcePrimKey());

		pageResource.setNodeId(newNodeId);
		pageResource.setTitle(originalTitle);

		wikiPageResourcePersistence.update(pageResource);

		page.setNodeId(newNodeId);
		page.setTitle(originalTitle);

		WikiPage parentPage = page.getParentPage();

		if ((parentPage != null) && parentPage.isInTrash()) {
			page.setParentTitle(StringPool.BLANK);
		}

		if (Validator.isNotNull(newParentTitle)) {
			WikiPage newParentPage = getPage(newNodeId, newParentTitle);

			if (!newParentPage.isInTrash()) {
				page.setParentTitle(newParentTitle);
			}
		}

		WikiPage redirectPage = page.getRedirectPage();

		if ((redirectPage != null) && redirectPage.isInTrash()) {
			page.setRedirectTitle(StringPool.BLANK);
		}

		page = wikiPagePersistence.update(page);

		TrashEntry trashEntry = trashEntryLocalService.getEntry(
			WikiPage.class.getName(), page.getResourcePrimKey());

		updateStatus(
			userId, page, trashEntry.getStatus(), new ServiceContext(),
			new HashMap<String, Serializable>());

		// Attachments

		for (FileEntry fileEntry : page.getDeletedAttachmentsFileEntries()) {
			PortletFileRepositoryUtil.restorePortletFileEntryFromTrash(
				userId, fileEntry.getFileEntryId());
		}

		// Child pages

		moveDependentChildPagesFromTrash(page, oldNodeId, trashTitle);

		// Redirect pages

		moveDependentRedirectorPagesFromTrash(page, oldNodeId, trashTitle);

		// Trash

		List<TrashVersion> trashVersions = trashVersionLocalService.getVersions(
			trashEntry.getEntryId());

		for (TrashVersion trashVersion : trashVersions) {
			WikiPage trashArticleVersion = wikiPagePersistence.findByPrimaryKey(
				trashVersion.getClassPK());

			trashArticleVersion.setStatus(trashVersion.getStatus());

			wikiPagePersistence.update(trashArticleVersion);
		}

		trashEntryLocalService.deleteEntry(
			WikiPage.class.getName(), page.getResourcePrimKey());

		// Comment

		CommentManagerUtil.restoreDiscussionFromTrash(
			WikiPage.class.getName(), page.getResourcePrimKey());

		// Social

		JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();

		extraDataJSONObject.put("title", page.getTitle());
		extraDataJSONObject.put("version", page.getVersion());

		SocialActivityManagerUtil.addActivity(
			userId, page, SocialActivityConstants.TYPE_RESTORE_FROM_TRASH,
			extraDataJSONObject.toString(), 0);

		Indexer<WikiPage> indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			WikiPage.class);

		indexer.reindex(page);
	}

	protected void notifySubscribers(
			long userId, WikiPage page, String pageURL,
			ServiceContext serviceContext)
		throws PortalException {

		if (!page.isApproved() || Validator.isNull(pageURL)) {
			return;
		}

		WikiGroupServiceOverriddenConfiguration
			wikiGroupServiceOverriddenConfiguration =
				configurationProvider.getConfiguration(
					WikiGroupServiceOverriddenConfiguration.class,
					new GroupServiceSettingsLocator(
						page.getGroupId(), WikiConstants.SERVICE_NAME));

		boolean update = false;

		if (page.getVersion() > WikiPageConstants.VERSION_DEFAULT) {
			update = true;
		}

		if (!update &&
			wikiGroupServiceOverriddenConfiguration.emailPageAddedEnabled()) {
		}
		else if (update &&
				 wikiGroupServiceOverriddenConfiguration.
					 emailPageUpdatedEnabled()) {
		}
		else {
			return;
		}

		String portalURL = serviceContext.getPortalURL();

		WikiPage previousVersionPage = getPreviousVersionPage(page);

		String attachmentURLPrefix = WikiUtil.getAttachmentURLPrefix(
			serviceContext.getPathMain(), serviceContext.getPlid(),
			page.getNodeId(), page.getTitle());

		attachmentURLPrefix = portalURL + attachmentURLPrefix;

		String pageDiffs = StringPool.BLANK;

		try {
			pageDiffs = wikiEngineRenderer.diffHtml(
				previousVersionPage, page, null, null, attachmentURLPrefix);
		}
		catch (Exception e) {
		}

		String pageContent = null;

		if (Objects.equals(page.getFormat(), "creole")) {
			pageContent = wikiEngineRenderer.convert(
				page, null, null, attachmentURLPrefix);
		}
		else {
			pageContent = page.getContent();
			pageContent = WikiUtil.processContent(pageContent);
		}

		String pageTitle = page.getTitle();

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

		LocalizedValuesMap subjectLocalizedValuesMap = null;
		LocalizedValuesMap bodyLocalizedValuesMap = null;

		if (update) {
			subjectLocalizedValuesMap =
				wikiGroupServiceOverriddenConfiguration.
					emailPageUpdatedSubject();
			bodyLocalizedValuesMap =
				wikiGroupServiceOverriddenConfiguration.emailPageUpdatedBody();
		}
		else {
			subjectLocalizedValuesMap =
				wikiGroupServiceOverriddenConfiguration.emailPageAddedSubject();
			bodyLocalizedValuesMap =
				wikiGroupServiceOverriddenConfiguration.emailPageAddedBody();
		}

		SubscriptionSender subscriptionSender = new SubscriptionSender();

		subscriptionSender.setClassName(page.getModelClassName());
		subscriptionSender.setClassPK(page.getPageId());
		subscriptionSender.setCompanyId(page.getCompanyId());
		subscriptionSender.setContextAttribute(
			"[$PAGE_CONTENT$]", pageContent, false);
		subscriptionSender.setContextAttribute(
			"[$PAGE_DIFFS$]", DiffHtmlUtil.replaceStyles(pageDiffs), false);

		WikiNode node = page.getNode();

		subscriptionSender.setContextAttributes(
			"[$DIFFS_URL$]",
			getDiffsURL(page, previousVersionPage, serviceContext),
			"[$NODE_NAME$]", node.getName(), "[$PAGE_DATE_UPDATE$]",
			page.getModifiedDate(), "[$PAGE_ID$]", page.getPageId(),
			"[$PAGE_SUMMARY$]", page.getSummary(), "[$PAGE_TITLE$]", pageTitle,
			"[$PAGE_URL$]", pageURL);

		subscriptionSender.setContextCreatorUserPrefix("PAGE");
		subscriptionSender.setCreatorUserId(page.getUserId());
		subscriptionSender.setCurrentUserId(userId);
		subscriptionSender.setEntryTitle(pageTitle);
		subscriptionSender.setEntryURL(pageURL);
		subscriptionSender.setFrom(fromAddress, fromName);
		subscriptionSender.setHtmlFormat(true);

		if (bodyLocalizedValuesMap != null) {
			subscriptionSender.setLocalizedBodyMap(
				LocalizationUtil.getMap(bodyLocalizedValuesMap));
		}

		if (subjectLocalizedValuesMap != null) {
			subscriptionSender.setLocalizedSubjectMap(
				LocalizationUtil.getMap(subjectLocalizedValuesMap));
		}

		subscriptionSender.setMailId(
			"wiki_page", page.getNodeId(), page.getPageId());

		int notificationType =
			UserNotificationDefinition.NOTIFICATION_TYPE_ADD_ENTRY;

		if (update) {
			notificationType =
				UserNotificationDefinition.NOTIFICATION_TYPE_UPDATE_ENTRY;
		}

		subscriptionSender.setNotificationType(notificationType);

		subscriptionSender.setPortletId(WikiPortletKeys.WIKI);
		subscriptionSender.setReplyToAddress(fromAddress);
		subscriptionSender.setScopeGroupId(page.getGroupId());
		subscriptionSender.setServiceContext(serviceContext);

		subscriptionSender.addPersistedSubscribers(
			WikiNode.class.getName(), page.getNodeId());

		subscriptionSender.addPersistedSubscribers(
			WikiPage.class.getName(), page.getResourcePrimKey());

		subscriptionSender.flushNotificationsAsync();
	}

	protected void populateServiceContext(
			ServiceContext serviceContext, WikiPage page)
		throws PortalException {

		long[] assetCategoryIds = assetCategoryLocalService.getCategoryIds(
			WikiPage.class.getName(), page.getResourcePrimKey());

		serviceContext.setAssetCategoryIds(assetCategoryIds);

		AssetEntry assetEntry = assetEntryLocalService.getEntry(
			WikiPage.class.getName(), page.getResourcePrimKey());

		List<AssetLink> assetLinks = assetLinkLocalService.getLinks(
			assetEntry.getEntryId());

		long[] assetLinkEntryIds = ListUtil.toLongArray(
			assetLinks, AssetLink.ENTRY_ID2_ACCESSOR);

		serviceContext.setAssetLinkEntryIds(assetLinkEntryIds);

		String[] assetTagNames = assetTagLocalService.getTagNames(
			WikiPage.class.getName(), page.getResourcePrimKey());

		serviceContext.setAssetTagNames(assetTagNames);

		ExpandoBridge expandoBridge = page.getExpandoBridge();

		serviceContext.setExpandoBridgeAttributes(
			expandoBridge.getAttributes());
	}

	protected WikiPage startWorkflowInstance(
			long userId, WikiPage page, ServiceContext serviceContext)
		throws PortalException {

		Map<String, Serializable> workflowContext = new HashMap<>();

		workflowContext.put(
			WorkflowConstants.CONTEXT_COMMAND, serviceContext.getCommand());
		workflowContext.put(
			WorkflowConstants.CONTEXT_URL, getPageURL(page, serviceContext));

		return WorkflowHandlerRegistryUtil.startWorkflowInstance(
			page.getCompanyId(), page.getGroupId(), userId,
			WikiPage.class.getName(), page.getPageId(), page, serviceContext,
			workflowContext);
	}

	protected WikiPage updatePage(
			long userId, WikiPage oldPage, long newNodeId, String newTitle,
			String content, String summary, boolean minorEdit, String format,
			String parentTitle, String redirectTitle,
			ServiceContext serviceContext)
		throws PortalException {

		User user = userLocalService.getUser(userId);

		long pageId = 0;

		if (oldPage.isApproved()) {
			pageId = counterLocalService.increment();
		}
		else {
			pageId = oldPage.getPageId();
		}

		content = SanitizerUtil.sanitize(
			user.getCompanyId(), oldPage.getGroupId(), userId,
			WikiPage.class.getName(), pageId, "text/" + format, content);

		long nodeId = oldPage.getNodeId();

		if (newNodeId != 0) {
			nodeId = newNodeId;
		}

		if (!format.equals(oldPage.getFormat())) {
			validate(nodeId, content, format);
		}

		serviceContext.validateModifiedDate(
			oldPage, PageVersionException.class);

		long resourcePrimKey =
			wikiPageResourceLocalService.getPageResourcePrimKey(
				oldPage.getGroupId(), oldPage.getNodeId(), oldPage.getTitle());

		Date now = new Date();

		WikiPage page = oldPage;

		double newVersion = oldPage.getVersion();

		if (oldPage.isApproved()) {
			newVersion = MathUtil.format(oldPage.getVersion() + 0.1, 1, 1);

			page = wikiPagePersistence.create(pageId);

			page.setUuid(serviceContext.getUuid());
		}

		page.setResourcePrimKey(resourcePrimKey);
		page.setGroupId(oldPage.getGroupId());
		page.setCompanyId(user.getCompanyId());
		page.setUserId(user.getUserId());
		page.setUserName(user.getFullName());
		page.setCreateDate(oldPage.getCreateDate());
		page.setNodeId(nodeId);
		page.setTitle(
			Validator.isNull(newTitle) ? oldPage.getTitle() : newTitle);
		page.setVersion(newVersion);
		page.setMinorEdit(minorEdit);
		page.setContent(content);

		if (oldPage.isPending()) {
			page.setStatus(oldPage.getStatus());
		}
		else {
			page.setStatus(WorkflowConstants.STATUS_DRAFT);
		}

		page.setSummary(summary);
		page.setFormat(format);

		if (Validator.isNotNull(parentTitle)) {
			page.setParentTitle(parentTitle);
		}

		if (Validator.isNotNull(redirectTitle)) {
			page.setRedirectTitle(redirectTitle);
		}

		ExpandoBridgeUtil.setExpandoBridgeAttributes(
			oldPage.getExpandoBridge(), page.getExpandoBridge(),
			serviceContext);

		page = wikiPagePersistence.update(page);

		// Node

		WikiNode node = wikiNodePersistence.findByPrimaryKey(nodeId);

		node.setLastPostDate(serviceContext.getModifiedDate(now));

		wikiNodePersistence.update(node);

		// Asset

		updateAsset(
			userId, page, serviceContext.getAssetCategoryIds(),
			serviceContext.getAssetTagNames(),
			serviceContext.getAssetLinkEntryIds(),
			serviceContext.getAssetPriority());

		// Workflow

		return startWorkflowInstance(userId, page, serviceContext);
	}

	protected void validate(long nodeId, String content, String format)
		throws PortalException {

		WikiEngine wikiEngine = wikiEngineRenderer.fetchWikiEngine(format);

		if (!wikiEngine.validate(nodeId, content)) {
			throw new PageContentException();
		}
	}

	protected void validate(
			String title, long nodeId, String content, String format)
		throws PortalException {

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

		if (isUsedTitle(nodeId, title)) {
			throw new DuplicatePageException("{nodeId=" + nodeId + "}");
		}

		wikiPageTitleValidator.validate(title);

		validate(nodeId, content, format);
	}

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

	@ServiceReference(type = WikiCacheHelper.class)
	protected WikiCacheHelper wikiCacheHelper;

	@ServiceReference(type = WikiEngineRenderer.class)
	protected WikiEngineRenderer wikiEngineRenderer;

	@ServiceReference(type = WikiPageTitleValidator.class)
	protected WikiPageTitleValidator wikiPageTitleValidator;

	private ServiceTrackerMap<String, WikiPageRenameContentProcessor>
		_serviceTrackerMap;

}