WikiApp.java

/*
 * Copyright (c) 2002-2023, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.wiki.web;

import fr.paris.lutece.plugins.avatar.service.AvatarService;
import fr.paris.lutece.plugins.wiki.business.Image;
import fr.paris.lutece.plugins.wiki.business.ImageHome;
import fr.paris.lutece.plugins.wiki.business.Topic;
import fr.paris.lutece.plugins.wiki.business.TopicHome;
import fr.paris.lutece.plugins.wiki.business.TopicVersion;
import fr.paris.lutece.plugins.wiki.business.TopicVersionHome;
import fr.paris.lutece.plugins.wiki.business.WikiContent;
import fr.paris.lutece.plugins.wiki.service.DiffService;
import fr.paris.lutece.plugins.wiki.service.RoleService;
import fr.paris.lutece.plugins.wiki.service.WikiLocaleService;
import fr.paris.lutece.plugins.wiki.service.WikiService;
import fr.paris.lutece.plugins.wiki.service.WikiUtils;
import fr.paris.lutece.plugins.wiki.service.parser.LuteceWikiParser;
import fr.paris.lutece.plugins.wiki.utils.auth.WikiAnonymousUser;
import fr.paris.lutece.portal.business.event.ResourceEvent;
import fr.paris.lutece.portal.business.page.Page;
import fr.paris.lutece.portal.business.role.RoleHome;
import fr.paris.lutece.portal.service.content.XPageAppService;
import fr.paris.lutece.portal.service.datastore.DatastoreService;
import fr.paris.lutece.portal.service.event.ResourceEventManager;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.message.SiteMessage;
import fr.paris.lutece.portal.service.message.SiteMessageException;
import fr.paris.lutece.portal.service.message.SiteMessageService;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.plugin.PluginService;
import fr.paris.lutece.portal.service.prefs.UserPreferencesService;
import fr.paris.lutece.portal.service.search.SearchEngine;
import fr.paris.lutece.portal.service.search.SearchResult;
import fr.paris.lutece.portal.service.security.LuteceUser;
import fr.paris.lutece.portal.service.security.SecurityService;
import fr.paris.lutece.portal.service.security.UserNotSignedException;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPathService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.util.mvc.commons.annotations.Action;
import fr.paris.lutece.portal.util.mvc.commons.annotations.View;
import fr.paris.lutece.portal.util.mvc.xpage.MVCApplication;
import fr.paris.lutece.portal.util.mvc.xpage.annotations.Controller;
import fr.paris.lutece.portal.web.l10n.LocaleService;
import fr.paris.lutece.portal.web.resource.ExtendableResourcePluginActionManager;
import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
import fr.paris.lutece.portal.web.xpages.XPage;
import fr.paris.lutece.util.ReferenceItem;
import fr.paris.lutece.util.ReferenceList;
import fr.paris.lutece.util.html.HtmlTemplate;
import fr.paris.lutece.util.html.Paginator;
import fr.paris.lutece.util.url.UrlItem;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.i18n.LocaleString;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import com.fasterxml.jackson.databind.ObjectMapper;


/**
 * This class provides a simple implementation of an XPage
 */
@Controller( xpageName = "wiki", pageTitleProperty = "wiki.pageTitle", pagePathProperty = "wiki.pagePathLabel" )
public class WikiApp extends MVCApplication
{
    private static final String TEMPLATE_MODIFY_WIKI = "skin/plugins/wiki/modify_page/modify_page.html";
    private static final String TEMPLATE_PREVIEW_WIKI = "skin/plugins/wiki/preview_page.html";
    private static final String TEMPLATE_VIEW_WIKI = "skin/plugins/wiki/view_page.html";
    private static final String TEMPLATE_VIEW_HISTORY_WIKI = "skin/plugins/wiki/history_page.html";
    private static final String TEMPLATE_VIEW_DIFF_TOPIC_WIKI = "skin/plugins/wiki/diff_topic.html";
    private static final String TEMPLATE_LIST_WIKI = "skin/plugins/wiki/list_wiki.html";
    private static final String TEMPLATE_MAP_WIKI = "skin/plugins/wiki/map_wiki.html";
    private static final String TEMPLATE_SEARCH_WIKI = "skin/plugins/wiki/search_wiki.html";
    private static final String TEMPLATE_MODIFY_PUBLISHED = "skin/plugins/wiki/modify_published.html";

    private static final String BEAN_SEARCH_ENGINE = "wiki.wikiSearchEngine";

    private static final String PROPERTY_PAGE_PATH = "wiki.pagePathLabel";
    private static final String PROPERTY_PAGE_TITLE = "wiki.pageTitle";
    private static final String PROPERTY_DEFAULT_RESULT_PER_PAGE = "wiki.search_wiki.itemsPerPage";
    private static final String PROPERTY_PATH_MAP = "wiki.path.map";
    private static final String PROPERTY_TITLE_MAP = "wiki.title.map";
    private static final String PROPERTY_PATH_LIST = "wiki.path.list";
    private static final String PROPERTY_TITLE_LIST = "wiki.title.list";
    private static final String PROPERTY_PATH_SEARCH = "wiki.path.search";
    private static final String PROPERTY_TITLE_SEARCH = "wiki.title.search";

    private static final String MARK_TOPIC = "topic";
    private static final String MARK_TOPIC_TITLE = "topic_title";
    private static final String MARK_TOPIC_NAME = "topic_name";
    private static final String MARK_TOPIC_CONTENT_HTML = "topic_content_html";
    private static final String MARK_REFLIST_TOPIC = "reflist_topic";
    private static final String MARK_MAP_TOPIC_TITLE = "map_topic_title";
    private static final String MARK_MAP_TOPIC_CHILDREN = "map_topic_children";
    private static final String MARK_WIKI_ROOT_PAGE_NAME = "wiki_root_page_name";
    private static final String MARK_LATEST_VERSION = "lastVersion";
    private static final String MARK_DIFF_HTML = "diff_html";
    private static final String MARK_DIFF_SOURCE = "diff_source";
    private static final String MARK_RESULT = "result";
    private static final String MARK_LIST_TOPIC_VERSION = "listTopicVersion";
    private static final String MARK_PAGE_ROLES_LIST = "page_roles_list";
    private static final String MARK_QUERY = "query";
    private static final String MARK_QUERY_ENCODED = "query_encoded";
    private static final String MARK_PAGINATOR = "paginator";
    private static final String MARK_NB_ITEMS_PER_PAGE = "nb_items_per_page";
    private static final String MARK_EDIT_ROLE = "has_edit_role";
    private static final String MARK_ADMIN_ROLE = "has_admin_role";
    private static final String MARK_EXTEND = "isExtendInstalled";
    private static final String MARK_LANGUAGES_LIST = "languages_list";
    private static final String MARK_CURRENT_LANGUAGE = "current_language";

    private static final String PARAMETER_LANGUAGE = "language";

    private static final String VIEW_HOME = "home";
    private static final String VIEW_LIST = "list";
    private static final String VIEW_MAP = "map";
    private static final String VIEW_PAGE = "page";
    private static final String VIEW_MODIFY_PAGE = "modifyPage";
    private static final String VIEW_PREVIEW = "preview";
    private static final String VIEW_HISTORY = "history";
    private static final String VIEW_SEARCH = "search";
    private static final String VIEW_DIFF = "diff";
    private static final String VIEW_LIST_IMAGES = "listImages";
    private static final String VIEW_MODIFY_PUBLISHED = "modifyPublished";
    private static final String VIEW_GET_PAGE_HEADINGS = "getPageHeadings";

    private static final String ACTION_NEW_PAGE = "newPage";
    private static final String ACTION_MODIFY_PAGE = "modifyPage";
    private static final String ACTION_DELETE_PAGE = "deletePage";
    private static final String ACTION_REMOVE_IMAGE = "removeImage";
    private static final String ACTION_CONFIRM_REMOVE_IMAGE = "confirmRemoveImage";
    private static final String ACTION_REMOVE_VERSION = "removeVersion";
    private static final String ACTION_CONFIRM_REMOVE_VERSION = "confirmRemoveVersion";
    private static final String ACTION_UPLOAD_IMAGE = "uploadImage";
    private static final String ACTION_CHANGE_LANGUAGE = "changeLanguage";
    private static final String ACTION_CANCEL_PUBLISH_PAGE = "cancelPublication";
    private static final String ACTION_CREATE_VERSION_FROM_PUBLISHED = "createVersionFromPublished";
    private static final String ACTION_UPDATE_LAST_EDIT_ATTEMPT = "updateLastEditAttempt";
    private static final String MESSAGE_IMAGE_REMOVED = "wiki.message.image.removed";
    private static final String MESSAGE_CONFIRM_REMOVE_IMAGE = "wiki.message.confirmRemoveImage";
    private static final String MESSAGE_NAME_MANDATORY = "wiki.message.error.name.notNull";
    private static final String MESSAGE_FILE_MANDATORY = "wiki.message.error.file.notNull";
    private static final String MESSAGE_CONFIRM_REMOVE_VERSION = "wiki.message.confirmRemoveVersion";
    private static final String MESSAGE_VERSION_REMOVED = "wiki.message.version.removed";
    private static final String MESSAGE_AUTHENTICATION_REQUIRED = "wiki.message.authenticationRequired";
    private static final String MESSAGE_PATH_HIDDEN = "wiki.message.path.hidden";
    private static final String MESSAGE_NO_PUBLISHED_VERSION = "wiki.view_page.noPublishedVersion";
    private static final String MESSAGE_TITLE_TOO_LONG = "wiki.message.titleTooLong";

    private static final String ANCHOR_IMAGES = "#images";

    private static final String DSKEY_WIKI_ROOT_LABEL = "wiki.site_property.path.rootLabel";
    private static final String DSKEY_WIKI_ROOT_PAGENAME = "wiki.site_property.path.rootPageName";

    private static final String PAGE_DEFAULT = "home";
    private static final String URL_VIEW_PAGE = "page=wiki&view=page&page_name=";

    private static final String PLUGIN_EXTEND = "extend";

    public static final int ACTION_VIEW = 1;

    private static final int MODE_VIEW = 0;
    private static final int MODE_EDIT = 1;
    private static final String MARK_VERSION = "version";

    // private fields
    private String _strCurrentPageIndex;
    private int _nDefaultItemsPerPage;
    private int _nItemsPerPage;

    /**
     * Gets the Home page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     */
    @View( value = VIEW_HOME, defaultView = true )
    public XPage getHome( HttpServletRequest request )
    {
        String strWikiRootPageName = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_PAGENAME, "" );
        if ( !"".equals( strWikiRootPageName ) )
        {
            Map<String, String> mapParameters = new ConcurrentHashMap<>( );
            mapParameters.put( Constants.PARAMETER_PAGE_NAME, strWikiRootPageName );

            return redirect( request, VIEW_PAGE, mapParameters );
        }
        return redirectView( request, VIEW_LIST );
    }

    /**
     * Gets list page of all wiki pages
     * 
     * @param request
     *            The HTTP request
     * @return The page
     */
    @View( VIEW_LIST )
    public XPage getTopicsList( HttpServletRequest request )
    {
        List<Topic> listTopic = getTopicsForUser( request );

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

        for ( Topic topic : listTopic )
        {
            mapTopicTitle.put( topic.getPageName( ), getTopicTitle( request, topic ) );
        }

        Map<String, Object> model = getModel( );
        model.put( MARK_MAP_TOPIC_TITLE, mapTopicTitle );
        model.put( MARK_LANGUAGES_LIST, WikiLocaleService.getLanguages( ) );
        model.put( MARK_CURRENT_LANGUAGE, getLanguage( request ) );

        XPage page = getXPage( TEMPLATE_LIST_WIKI, getLocale( request ), model );
        page.setTitle( getPageTitle( I18nService.getLocalizedString( PROPERTY_TITLE_LIST, LocaleService.getContextUserLocale( request ) ) ) );
        page.setExtendedPathLabel( getViewExtendedPath( I18nService.getLocalizedString( PROPERTY_PATH_LIST, LocaleService.getContextUserLocale( request ) ) ) );

        return page;
    }

    /**
     * Gets maps page of the wiki
     * 
     * @param request
     *            The HTTP request
     * @return The page
     */
    @View( VIEW_MAP )
    public XPage getTopicsMap( HttpServletRequest request )
    {
        Collection<Topic> listTopic = getTopicsForUser( request );

        String strWikiRootPageName = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_PAGENAME, AppPropertiesService.getProperty( PAGE_DEFAULT ) );

        Map<String, String> mapTopicTitle = new HashMap<String,String>( );
        Map<String, List<Topic>> mapTopicChildren = new HashMap<String,List<Topic>>( );

        for ( Topic topic : listTopic )
        {
            try{
                mapTopicTitle.put( topic.getPageName( ), getTopicTitle( request, topic ) );

                String strParentPageName = topic.getParentPageName( );
                if ( strParentPageName != null && !topic.getPageName( ).isEmpty( ) )
                {
                    if ( mapTopicChildren.containsKey( strParentPageName ) )
                    {
                        mapTopicChildren.get( strParentPageName ).add( topic );
                    }
                    else
                    {
                        List<Topic> listChildren = new ArrayList<>( );
                        listChildren.add( topic );
                        mapTopicChildren.put( strParentPageName, listChildren );
                    }
                }
            }
            catch ( Exception e )
            {
                AppLogService.error( "Error while getting topic title for {}",topic.getPageName( ), e ); 
            }
        }

        Map<String, Object> model = getModel( );
        model.put( MARK_MAP_TOPIC_TITLE, mapTopicTitle );
        model.put( MARK_MAP_TOPIC_CHILDREN, mapTopicChildren );
        model.put( MARK_WIKI_ROOT_PAGE_NAME, strWikiRootPageName );
        model.put( MARK_LANGUAGES_LIST, WikiLocaleService.getLanguages( ) );
        model.put( MARK_CURRENT_LANGUAGE, getLanguage( request ) );

        XPage page = getXPage( TEMPLATE_MAP_WIKI, getLocale( request ), model );
        page.setTitle( getPageTitle( I18nService.getLocalizedString( PROPERTY_TITLE_MAP, LocaleService.getContextUserLocale( request ) ) ) );
        page.setExtendedPathLabel( getViewExtendedPath( I18nService.getLocalizedString( PROPERTY_PATH_MAP, LocaleService.getContextUserLocale( request ) ) ) );

        return page;
    }

    /**
     * Gets the search page
     *
     * @param request
     *            The request
     * @return Thye XPage
     */
    @View( VIEW_SEARCH )
    public XPage getSearch( HttpServletRequest request )
    {
        String strQuery = request.getParameter( Constants.PARAMETER_QUERY );
        String strPortalUrl = AppPathService.getPortalUrl( );

        UrlItem urlWikiXpage = new UrlItem( strPortalUrl );
        urlWikiXpage.addParameter( XPageAppService.PARAM_XPAGE_APP, Constants.PLUGIN_NAME );
        urlWikiXpage.addParameter( Constants.PARAMETER_VIEW, VIEW_SEARCH );

        _strCurrentPageIndex = Paginator.getPageIndex( request, Paginator.PARAMETER_PAGE_INDEX, _strCurrentPageIndex );
        _nDefaultItemsPerPage = AppPropertiesService.getPropertyInt( PROPERTY_DEFAULT_RESULT_PER_PAGE, 10 );
        _nItemsPerPage = Paginator.getItemsPerPage( request, Paginator.PARAMETER_ITEMS_PER_PAGE, _nItemsPerPage, _nDefaultItemsPerPage );

        SearchEngine engine = (SearchEngine) SpringContextService.getBean( BEAN_SEARCH_ENGINE );
        List<SearchResult> listResults = engine.getSearchResults( strQuery, request );

        Paginator paginator = new Paginator( listResults, _nItemsPerPage, urlWikiXpage.getUrl( ), Constants.PARAMETER_PAGE_INDEX, _strCurrentPageIndex );

        Map<String, Object> model = getModel( );
        model.put( MARK_RESULT, paginator.getPageItems( ) );
        model.put( MARK_QUERY, strQuery );
        String strQueryEncoded = "";
        try {
			strQueryEncoded = URLEncoder.encode( strQuery, "UTF-8" );
		} catch (UnsupportedEncodingException e) {}
        model.put( MARK_QUERY_ENCODED, strQueryEncoded );
        model.put( MARK_PAGINATOR, paginator );
        model.put( MARK_NB_ITEMS_PER_PAGE, String.valueOf( _nItemsPerPage ) );
        model.put( MARK_LANGUAGES_LIST, WikiLocaleService.getLanguages( ) );
        model.put( MARK_CURRENT_LANGUAGE, getLanguage( request ) );

        XPage page = getXPage( TEMPLATE_SEARCH_WIKI, getLocale( request ), model );
        page.setTitle( getPageTitle( I18nService.getLocalizedString( PROPERTY_TITLE_SEARCH, getLocale( request ) ) ) );
        page.setExtendedPathLabel( getViewExtendedPath( I18nService.getLocalizedString( PROPERTY_PATH_SEARCH, getLocale( request ) ) ) );

        return page;
    }

    /**
     * Gets a wiki page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws SiteMessageException
     *             if an error occurs
     */
    @View( VIEW_PAGE )
    public XPage getTopic( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
    {
        if ( !SecurityService.isAuthenticationEnable( ) )
        {
            SiteMessageService.setMessage( request, MESSAGE_AUTHENTICATION_REQUIRED, SiteMessage.TYPE_ERROR );
        }
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = getTopic( request, strPageName, MODE_VIEW );
        TopicVersion version = TopicVersionHome.getPublishedVersion( topic.getIdTopic( ) );
        if ( version == null )
        {
            version = TopicVersionHome.findLastVersion( topic.getIdTopic( ) );
            if(version == null)
            {
            	SiteMessageService.setMessage(request, MESSAGE_NO_PUBLISHED_VERSION, SiteMessage.TYPE_ERROR);
            }
          String strPageTitle = topic.getPageName( );
            String strLanguage = getLanguage( request );
            String strContent = I18nService.getLocalizedString( MESSAGE_NO_PUBLISHED_VERSION, getLocale( request ) );
            WikiContent content = new WikiContent( );
            content.setPageTitle( strPageTitle );
            content.setWikiContent( strContent );
            version.addLocalizedWikiContent( strLanguage, content );
        }
        fillUserData( version );
        String strWikiPage = WikiService.instance( ).getWikiPage( strPageName, version, getPageUrl( request ), getLanguage( request ) );
        
        Collection<Topic> listTopic = getTopicsForUser( request );

        String strWikiRootPageName = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_PAGENAME, AppPropertiesService.getProperty( PAGE_DEFAULT ) );

        Map<String, String> mapTopicTitle = new HashMap( );
        Map<String, List<Topic>> mapTopicChildren = new HashMap( );

        for ( Topic topicSideBar : listTopic )
        {
            try
{
                mapTopicTitle.put( topicSideBar.getPageName( ), getTopicTitle( request, topicSideBar ).replace( '_', ' ' ) );

                String strParentPageName = topicSideBar.getParentPageName( );
                if ( strParentPageName != null && !topicSideBar.getPageName( ).isEmpty( ) )
                {
                    if ( mapTopicChildren.containsKey( strParentPageName ) )
                    {
                        mapTopicChildren.get( strParentPageName ).add( topicSideBar );
                    }
                    else
                    {
                        List<Topic> listChildren = new ArrayList<>( );
                        listChildren.add( topicSideBar );
                        mapTopicChildren.put( strParentPageName, listChildren );
                    }
                }
            }
            catch ( Exception e )
            {
                AppLogService.error( "Error while getting topic title for {}",topicSideBar.getPageName( ), e );  
            }
        }

        Map<String, Object> model = getModel( );
        model.put( MARK_MAP_TOPIC_TITLE, mapTopicTitle );
        model.put( MARK_MAP_TOPIC_CHILDREN, mapTopicChildren );
        model.put( MARK_WIKI_ROOT_PAGE_NAME, strWikiRootPageName );
        
        model.put( MARK_RESULT, strWikiPage );
        model.put( MARK_TOPIC, topic );
        model.put( MARK_TOPIC_TITLE, getTopicTitle( request, topic ) );
        model.put( MARK_LATEST_VERSION, version );
        model.put( MARK_EDIT_ROLE, RoleService.hasEditRole( request, topic ) );
        model.put( MARK_ADMIN_ROLE, RoleService.hasAdminRole( request ) );
        model.put( MARK_EXTEND, isExtend( ) );
        model.put( MARK_LANGUAGES_LIST, WikiLocaleService.getLanguages( ) );
        model.put( MARK_CURRENT_LANGUAGE, getLanguage( request ) );
        XPage page = getXPage( TEMPLATE_VIEW_WIKI, getLocale( request ), model );
        page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
        page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );

        return page;
    }

    /**
     * Creates a new Page
     *
     * @param request
     *            The request
     * @return The XPage
     * @throws UserNotSignedException
     *             if the user is not signed
     * @throws java.io.UnsupportedEncodingException
     *             if an encoding exception occurs
     */
    @Action( ACTION_NEW_PAGE )
    public XPage doCreateTopic( HttpServletRequest request )
            throws UserNotSignedException, UnsupportedEncodingException, fr.paris.lutece.portal.service.message.SiteMessageException
    {
    	LuteceUser user = checkUser( request );

        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        String strParentPageName = request.getParameter( Constants.PARAMETER_PARENT_PAGE_NAME );
        if ( strParentPageName == null )
        {
            strParentPageName = "home";
        }
        String strPageTitle = strPageName;
		if (strPageTitle.length() > 100) {
			SiteMessageService.setMessage(request, MESSAGE_TITLE_TOO_LONG, SiteMessage.TYPE_ERROR);
		}
        strPageName = WikiUtils.normalize( strPageName );

        Topic topic = TopicHome.findByName( strPageName );
        if ( topic == null )
        {
            topic = new Topic( );
            topic.setPageName( strPageName );
            topic.setViewRole( Page.ROLE_NONE );
            topic.setEditRole( Page.ROLE_NONE );
            topic.setParentPageName( strParentPageName );

            TopicHome.create( topic );
            
            String strLocale = getLanguage( request );
            if( request.getParameter( Constants.PARAMETER_LOCAL ) != null )
            {
                strLocale = request.getParameter( Constants.PARAMETER_LOCAL );
            }
            
            String strComment = request.getParameter( Constants.PARAMETER_MODIFICATION_COMMENT )!= null?request.getParameter( Constants.PARAMETER_MODIFICATION_COMMENT ):"";
            String strContent = "";
            TopicVersion topicVersion = new TopicVersion( );
            topicVersion.setIdTopic( topic.getIdTopic( ) );
            topicVersion.setUserName( user.getName( ) );
            topicVersion.setEditComment( strComment );
            topicVersion.setIdTopicVersionPrevious( 0 );
            topicVersion.setIsPublished( false );
            for ( String strLang : WikiLocaleService.getLanguages( ) )
            {
                WikiContent content = new WikiContent( );
                content.setPageTitle( strPageTitle );
                content.setWikiContent( strContent );
                topicVersion.addLocalizedWikiContent( strLang, content );
            }
            
            TopicVersionHome.addTopicVersion( topicVersion );
            TopicHome.update( topic );
            
            ResourceEvent resourceEvent = new ResourceEvent( );
            resourceEvent.setIdResource( String.valueOf( topic.getIdTopic( ) ) );
            resourceEvent.setTypeResource( Topic.RESOURCE_TYPE );
            ResourceEventManager.fireAddedResource( resourceEvent );
        }

        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, strPageName );
        mapParameters.put( Constants.PARAMETER_PAGE_TITLE, URLEncoder.encode( strPageTitle, "UTF-8" ) );

        return redirect( request, VIEW_MODIFY_PAGE, mapParameters );
    }

    /**
     * Displays the form to update a wiki page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws SiteMessageException
     *             if an exception occurs
     */
    @View( VIEW_MODIFY_PAGE )
    public XPage getModifyTopic( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
    {
        checkUser( request );

        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        String strLocale = getLanguage( request );
        Integer nVersion = getVersionTopicVersionId( request );
        if( request.getParameter( Constants.PARAMETER_LOCAL ) != null )
        {
            strLocale = request.getParameter( Constants.PARAMETER_LOCAL );
        }

        Topic topic = TopicHome.findByName( strPageName );
        Map userEditing = TopicHome.getLastEditAttempt( topic.getIdTopic() );
        if( userEditing != null && !userEditing.isEmpty() )
        {

            Boolean isEditing = (Boolean) userEditing.get( "is_editing" );
            if( isEditing )
            {
                String strUserEditing = (String) userEditing.get( "name_user_editing" );
                if( strUserEditing != null && !strUserEditing.isEmpty() && !getRegistredUser( request ).getName().equals( strUserEditing ) )
                {
                Object[] messageArgs = { strUserEditing };
                String strMessageKey = "wiki.message.error.pageAlreadyEditing";
                String titleMessageKey = "wiki.message.error.somebodyIsEditing";
                SiteMessageService.setMessage( request, strMessageKey,messageArgs,titleMessageKey, SiteMessage.TYPE_STOP );
                }
            }
        }
        TopicVersion topicVersion;
        if( nVersion != null )
        {
            topicVersion = TopicVersionHome.findByPrimaryKey( nVersion );
        }
        else {
            topicVersion = TopicVersionHome.findLastVersion( topic.getIdTopic( ) );

        }
        if (topicVersion!= null && topicVersion.getIsPublished( ) )
        {
            Map<String, String> mapParameters = new ConcurrentHashMap<>( );
            mapParameters.put( Constants.PARAMETER_PAGE_NAME, strPageName );
            return redirect( request, VIEW_MODIFY_PUBLISHED, mapParameters );
        }

            if (topicVersion == null)
            {
                topicVersion = new TopicVersion( );
                topicVersion.setIdTopic( topic.getIdTopic( ) );
                WikiContent content = new WikiContent( );
                content.setPageTitle( topic.getPageName( ) );
                content.setWikiContent( "" );
                content.setPageTitle( topic.getPageName( ));
                topicVersion.addLocalizedWikiContent( strLocale, content );
            }


        Collection<Topic> listTopic = getTopicsForUser( request );
        Map<String, String> mapTopicTitle = new HashMap<>( );
        for ( Topic topicSideBar : listTopic )
        {
            try
            {
                mapTopicTitle.put( topicSideBar.getPageName( ), getTopicTitle( request, topicSideBar ) );
            }
            catch ( Exception e )
            {
                AppLogService.error( "Error while getting topic title for {}",topicSideBar.getPageName( ), e );  
            }
        }

        String strWikiRootPageName = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_PAGENAME, AppPropertiesService.getProperty( PAGE_DEFAULT ) );

        ReferenceList topicRefList = getTopicsReferenceListForUser( request, true );
        topicRefList.removeIf( x -> x.getCode( ).equals( topic.getPageName( ) ) );

        Map<String, Object> model = getModel( );
        model.put( MARK_TOPIC, topic );
        model.put( MARK_VERSION, topicVersion );
        model.put( MARK_PAGE_ROLES_LIST, RoleService.getUserRoles( request ) );
        model.put( MARK_EDIT_ROLE, RoleService.hasEditRole( request, topic ) );
        model.put( MARK_MAP_TOPIC_TITLE, mapTopicTitle );
        model.put( MARK_WIKI_ROOT_PAGE_NAME, strWikiRootPageName );
        model.put( MARK_ADMIN_ROLE, RoleService.hasAdminRole( request ) );
        model.put( MARK_LANGUAGES_LIST, WikiLocaleService.getLanguages( ) );
        model.put( MARK_REFLIST_TOPIC, topicRefList );
        model.put( Constants.PARAMETER_LOCAL, strLocale );
        model.put( Constants.PARAMETER_ACTION_TOPIC_NAME_LIST, getTopicNameListForUser( request ) );
        ExtendableResourcePluginActionManager.fillModel( request, null, model, Integer.toString( topic.getIdTopic( ) ), Topic.RESOURCE_TYPE );

        XPage page = getXPage( TEMPLATE_MODIFY_WIKI, request.getLocale( ), model );
        page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
        page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );

        return page;
    }

    /**
     * Modifies a wiki page
     *
     * @param request
     *            The HTTP Request
     * @return The redirect URL
     * @throws UserNotSignedException
     *             If user not connected
     * @throws SiteMessageException 
     * 	            If the title is too long
     */
    @Action( ACTION_MODIFY_PAGE )
    public XPage doModifyTopic( HttpServletRequest request ) throws UserNotSignedException, SiteMessageException
    {
        LuteceUser user = checkUser( request );
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = TopicHome.findByName( strPageName );
            String strTopicId = request.getParameter( Constants.PARAMETER_TOPIC_ID );
            String strComment = request.getParameter( Constants.PARAMETER_MODIFICATION_COMMENT )!= null?request.getParameter( Constants.PARAMETER_MODIFICATION_COMMENT ):"";
            String strViewRole = request.getParameter( Constants.PARAMETER_VIEW_ROLE )!= null?request.getParameter( Constants.PARAMETER_VIEW_ROLE ):Page.ROLE_NONE;
            String strEditRole = request.getParameter( Constants.PARAMETER_EDIT_ROLE )!= null?request.getParameter( Constants.PARAMETER_EDIT_ROLE ):Page.ROLE_NONE;
            Boolean publish = Boolean.parseBoolean(request.getParameter( Constants.PARAMETER_PUBLISH ));
            int nTopicId = Integer.parseInt( strTopicId );
            String strLanguage = getLanguage( request );
            String strParentPageName = TopicVersionHome.getPageNameFromTitle( request.getParameter( Constants.PARAMETER_PARENT_PAGE_NAME ), strLanguage ) != null ? TopicVersionHome.getPageNameFromTitle( request.getParameter( Constants.PARAMETER_PARENT_PAGE_NAME ), strLanguage ) : "";
            String strPageTitle = request.getParameter( Constants.PARAMETER_PAGE_TITLE + "_" + strLanguage );
            if (strPageTitle.length() > 100) {
    			SiteMessageService.setMessage( request, MESSAGE_TITLE_TOO_LONG, SiteMessage.TYPE_ERROR );
    		}
            String strContent = request.getParameter( Constants.PARAMETER_CONTENT + "_" + strLanguage );
           TopicVersion topicVersion = TopicVersionHome.findLastVersion( topic.getIdTopic( ) );
        if(!RoleService.hasEditRole( request, topic ) )
        {
            throw new UserNotSignedException(  );
        }
            if ( publish )
        {
            TopicVersion previousPublished = TopicVersionHome.getPublishedVersion( nTopicId );
            if ( previousPublished != null )
            {
                TopicVersionHome.updatePublishedVersion( previousPublished.getIdTopicVersion(), false );
            }
        }
        if ( topicVersion == null )
        {
            topicVersion = new TopicVersion( );
            topicVersion.setIdTopic( nTopicId );
            topicVersion.setUserName( user.getName( ) );
            topicVersion.setEditComment( strComment );
            topicVersion.setIdTopicVersionPrevious( 0 );

            for ( String strLang : WikiLocaleService.getLanguages( ) )
            {
                WikiContent content = new WikiContent( );
                content.setPageTitle( strPageTitle );
                content.setWikiContent( strContent );
                topicVersion.addLocalizedWikiContent( strLang, content );
            }
        } else
        {
            topicVersion.setUserName( user.getName( ) );
            topicVersion.setEditComment( strComment );
            topicVersion.setIdTopicVersionPrevious( topicVersion.getIdTopicVersion( ) );

            for ( String strLang : WikiLocaleService.getLanguages( ) )
            {
                if ( strLang.equals( strLanguage ) )
                {
                    WikiContent content = new WikiContent( );
                    content.setPageTitle( strPageTitle );
                    content.setWikiContent( LuteceWikiParser.renderWiki( strContent ) );
                    topicVersion.addLocalizedWikiContent( strLanguage, content );
                }
                else
                    if ( topicVersion.getWikiContent( strLang ) != null )
                    {
                        WikiContent content = topicVersion.getWikiContent( strLang );
                        content.setWikiContent( LuteceWikiParser.renderWiki( content.getWikiContent( ) ) );
                        topicVersion.addLocalizedWikiContent( strLang, content );
                    }

            }
        }
        topicVersion.setIsPublished( publish );
            TopicVersionHome.addTopicVersion( topicVersion );
            topic.setViewRole( strViewRole );
            topic.setEditRole( strEditRole );
            topic.setParentPageName( strParentPageName );
            TopicHome.update( topic );
            
            ResourceEvent resourceEvent = new ResourceEvent( );
            resourceEvent.setIdResource( String.valueOf( topic.getIdTopic( ) ) );
            resourceEvent.setTypeResource( Topic.RESOURCE_TYPE );
            ResourceEventManager.fireUpdatedResource( resourceEvent );

        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, strPageName );

        return redirect( request, VIEW_PAGE, mapParameters );
    }

    /**
     * Displays the preview of a wiki page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws SiteMessageException
     *             if an exception occurs
     */
    @View( VIEW_PREVIEW )
    public XPage getPreviewTopic( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
    {
        LuteceUser user = checkUser( request );
        String strPreviousVersionId = request.getParameter( "id_topic_version" );
        String strTopicId = request.getParameter( Constants.PARAMETER_TOPIC_ID );
        String strComment = request.getParameter( Constants.PARAMETER_MODIFICATION_COMMENT );
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        String strViewRole = request.getParameter( Constants.PARAMETER_VIEW_ROLE );
        String strEditRole = request.getParameter( Constants.PARAMETER_EDIT_ROLE );
        String strParentPageName = request.getParameter( Constants.PARAMETER_PARENT_PAGE_NAME );
        String strLanguage = getLanguage( request );
        TopicVersion topicVersion = new TopicVersion( );
        if(!RoleService.hasEditRole( request, TopicHome.findByName( strPageName ) ) )
        {
            throw new UserNotSignedException(  );
        }
        int nPreviousVersionId = 0;
        if(strPreviousVersionId != null)
        {
            nPreviousVersionId = Integer.parseInt( strPreviousVersionId );
            int nTopicId = Integer.parseInt( strTopicId );
             topicVersion = TopicVersionHome.findLastVersion( nTopicId );
        }
        topicVersion.setIdTopic( Integer.parseInt( strTopicId ) );
        topicVersion.setUserName( user.getName( ) );
        topicVersion.setEditComment( strComment );
        topicVersion.setIdTopicVersionPrevious( nPreviousVersionId );
        for ( String strLang : WikiLocaleService.getLanguages( ) )
        {
            if ( strLang.equals( strLanguage ) )
            {
                String strPageTitle = request.getParameter( Constants.PARAMETER_PAGE_TITLE  );
                String strContent = request.getParameter( Constants.PARAMETER_CONTENT + "_" + strLanguage );
                WikiContent content = new WikiContent( );
                content.setPageTitle( strPageTitle );
                content.setWikiContent( LuteceWikiParser.renderWiki( strContent ) );
                topicVersion.addLocalizedWikiContent( strLanguage, content );
            }
        }

        Topic topic = TopicHome.findByName( strPageName );
        topic.setViewRole( strViewRole );
        topic.setEditRole( strEditRole );
        topic.setParentPageName( strParentPageName );

        request.getSession( ).setAttribute( MARK_TOPIC, topic );
        request.getSession( ).setAttribute( MARK_LATEST_VERSION, topicVersion );

        String strContent = request.getParameter( Constants.PARAMETER_CONTENT + "_" + strLanguage );
        String strPageContent = new LuteceWikiParser( strContent, strPageName, strLanguage ).toString( );
        String strPageTitle = request.getParameter( Constants.PARAMETER_PAGE_TITLE + "_" + strLanguage );

        Map<String, Object> model = getModel( );
        model.put( MARK_RESULT, strPageContent );
        model.put( MARK_TOPIC, topic );
        model.put( MARK_LATEST_VERSION, topicVersion );
        model.put( MARK_TOPIC_TITLE, strPageTitle );
        model.put( MARK_VERSION, topicVersion );

        XPage page = getXPage( TEMPLATE_PREVIEW_WIKI, request.getLocale( ), model );
        page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
        page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );

        return page;
    }
    
    /**
     * Displays the history page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws SiteMessageException
     *             if an error occurs
     */
    @View( VIEW_HISTORY )
    public XPage getHistory( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
    {
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = getTopic( request, strPageName, MODE_VIEW );
        Map<String, Object> model = getModel( );
        Collection<TopicVersion> listTopicVersions = TopicVersionHome.findAllVersions( topic.getIdTopic( ) );

        fillUsersData( listTopicVersions );
        model.put( MARK_LIST_TOPIC_VERSION, listTopicVersions );
        model.put( MARK_TOPIC, topic );
        model.put( MARK_EDIT_ROLE, RoleService.hasEditRole( request, topic ) );
        model.put( MARK_ADMIN_ROLE, RoleService.hasAdminRole( request ) );
        model.put( MARK_LANGUAGES_LIST, WikiLocaleService.getLanguages( ) );
        model.put( MARK_CURRENT_LANGUAGE, getLanguage( request ) );

        XPage page = getXPage( TEMPLATE_VIEW_HISTORY_WIKI, request.getLocale( ), model );
        page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
        page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );

        return page;
    }

    /**
     * Displays the diff page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws SiteMessageException
     *             if an error occurs
     */
    @View( VIEW_DIFF )
    public XPage getDiff( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
    {
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = getTopic( request, strPageName, MODE_VIEW );
        String strNewVersion = request.getParameter( Constants.PARAMETER_NEW_VERSION );
        String strOldVersion = request.getParameter( Constants.PARAMETER_OLD_VERSION );
        int nNewTopicVersion = Integer.parseInt( strNewVersion );
        int nOldTopicVersion = Integer.parseInt( strOldVersion );
        int nPrevTopicVersion = ( nOldTopicVersion == 0 ) ? nNewTopicVersion : nOldTopicVersion;

        TopicVersion newTopicVersion = TopicVersionHome.findByPrimaryKey( nNewTopicVersion );
        TopicVersion oldTopicVersion = TopicVersionHome.findByPrimaryKey( nPrevTopicVersion );

        String strLanguage = getLanguage( request );
        String strNewHtml = WikiService.instance( ).getWikiPage( strPageName, newTopicVersion, strLanguage );
        String strOldHtml = WikiService.instance( ).getWikiPage( strPageName, oldTopicVersion, strLanguage );
        String strNewSource = WikiService.instance( ).getPageSource( strPageName, newTopicVersion, strLanguage );
        String strOldSource = WikiService.instance( ).getPageSource( strPageName, oldTopicVersion, strLanguage );
        String strDiffHtml = DiffService.getDiff( strOldHtml, strNewHtml );
        String strDiffSource = DiffService.getDiff( strOldSource, strNewSource );

        Map<String, Object> model = getModel( );
        model.put( MARK_DIFF_HTML, strDiffHtml );
        model.put( MARK_DIFF_SOURCE, strDiffSource );
        model.put( MARK_TOPIC, topic );

        XPage page = getXPage( TEMPLATE_VIEW_DIFF_TOPIC_WIKI, request.getLocale( ), model );
        page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
        page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );

        return page;
    }

    /**
     * Deletes a wiki page
     *
     * @param request
     *            The HTTP Request
     * @return The redirect URL
     * @throws UserNotSignedException
     *             if user not connected
     */
    @Action( ACTION_DELETE_PAGE )
    public XPage doDeleteTopic( HttpServletRequest request ) throws UserNotSignedException
    {
        checkUser( request );

        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = TopicHome.findByName( strPageName );
   
        // Requires Admin role
        if ( RoleService.hasAdminRole( request ) )
        {
        	List<String> lang = WikiLocaleService.getLanguages();
			for (String strLang : lang) {
				TopicHome.setChildrenToEmptyParent( TopicVersionHome.findLastVersion( topic.getIdTopic( ) ).getWikiContent( strLang ).getPageTitle( ) );
			}           
            TopicHome.remove( topic.getIdTopic( ) );

            ResourceEvent resourceEvent = new ResourceEvent( );
            resourceEvent.setIdResource( String.valueOf( topic.getIdTopic( ) ) );
            resourceEvent.setTypeResource( Topic.RESOURCE_TYPE );
            ResourceEventManager.fireDeletedResource( resourceEvent );
        }

        return redirectView( request, VIEW_HOME );
    }

    /**
     * Uploads an image
     * 
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws UserNotSignedException
     *             if the user is not signed
     */
    @Action( ACTION_UPLOAD_IMAGE )
    public XPage doUploadImage( HttpServletRequest request ) throws UserNotSignedException
    {
        checkUser( request );
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        String strName = request.getParameter( Constants.PARAMETER_IMAGE_NAME );
        String strTopicId = request.getParameter( Constants.PARAMETER_TOPIC_ID );
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        FileItem fileItem = multipartRequest.getFile( Constants.PARAMETER_IMAGE_FILE );
        if(!RoleService.hasEditRole( request, TopicHome.findByName( strPageName ) ))
        {
            throw new UserNotSignedException( );
        }
        Image image = new Image( );
        boolean bError = false;

        if ( ( fileItem == null ) || ( fileItem.getName( ) == null ) || "".equals( fileItem.getName( ) ) )
        {
            bError = true;
            addError( MESSAGE_FILE_MANDATORY, request.getLocale( ) );
        }

        if ( ( strName == null ) || strName.trim( ).equals( "" ) )
        {
            bError = true;
            addError( MESSAGE_NAME_MANDATORY, request.getLocale( ) );
        }

        if ( !bError )
        {
            image.setName( strName );
            image.setTopicId( Integer.parseInt( strTopicId ) );

            if ( ( fileItem != null ) && ( fileItem.getName( ) != null ) && !"".equals( fileItem.getName( ) ) )
            {
                image.setValue( fileItem.get( ) );
                image.setMimeType( fileItem.getContentType( ) );
            }
            else
            {
                image.setValue( null );
            }

            image.setWidth( 500 );
            image.setHeight( 500 );

            ImageHome.create( image );
        }

        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, strPageName );

        return redirect( request, VIEW_MODIFY_PAGE, mapParameters );
    }

    /**
     * Manages the removal form of a image whose identifier is in the http request
     *
     * @param request
     *            The Http request
     * @return the html code to confirm
     * @throws SiteMessageException
     *             A site message
     */
    @Action( ACTION_CONFIRM_REMOVE_IMAGE )
    public XPage getConfirmRemoveImage( HttpServletRequest request ) throws SiteMessageException
    {
        int nId = Integer.parseInt( request.getParameter( Constants.PARAMETER_IMAGE_ID ) );
        UrlItem url = new UrlItem( AppPathService.getPortalUrl( ) );
        url.addParameter( Constants.PARAMETER_PAGE, Constants.PLUGIN_NAME );
        url.addParameter( Constants.PARAMETER_PAGE_NAME, request.getParameter( Constants.PARAMETER_PAGE_NAME ) );
        url.addParameter( Constants.PARAMETER_ACTION, ACTION_REMOVE_IMAGE );
        url.addParameter( Constants.PARAMETER_IMAGE_ID, nId );

        SiteMessageService.setMessage( request, MESSAGE_CONFIRM_REMOVE_IMAGE, SiteMessage.TYPE_CONFIRMATION, url.getUrl( ) );

        return null;
    }

    /**
     * Handles the removal form of an image
     *
     * @param request
     *            The Http request
     * @return the jsp URL to display the form to manage images
     * @throws UserNotSignedException
     *             if the user is not signed
     */
    @Action( ACTION_REMOVE_IMAGE )
    public XPage doRemoveImage( HttpServletRequest request ) throws UserNotSignedException
    {
        checkUser( request );
        int nId = Integer.parseInt( request.getParameter( Constants.PARAMETER_IMAGE_ID ) );
        if ( RoleService.hasAdminRole( request ) )
        {
            ImageHome.remove( nId );
            addInfo( MESSAGE_IMAGE_REMOVED, getLocale( request ) );
        }
        
        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, TopicHome.findByPrimaryKey( nId ).getPageName( ) + ANCHOR_IMAGES );

        return redirect( request, VIEW_MODIFY_PAGE, mapParameters );
    }

    /**
     * Manages the removal form of a topic version whose identifier is in the http request
     *
     * @param request
     *            The Http request
     * @return the html code to confirm
     * @throws SiteMessageException
     *             A site message
     */
    @Action( ACTION_CONFIRM_REMOVE_VERSION )
    public XPage getConfirmRemoveVersion( HttpServletRequest request ) throws SiteMessageException
    {
        int nId = Integer.parseInt( request.getParameter( Constants.PARAMETER_TOPIC_VERSION_ID ) );
        UrlItem url = new UrlItem( AppPathService.getPortalUrl( ) );
        url.addParameter( Constants.PARAMETER_PAGE, Constants.PLUGIN_NAME );
        url.addParameter( Constants.PARAMETER_PAGE_NAME, request.getParameter( Constants.PARAMETER_PAGE_NAME ) );
        url.addParameter( Constants.PARAMETER_ACTION, ACTION_REMOVE_VERSION );
        url.addParameter( Constants.PARAMETER_TOPIC_VERSION_ID, nId );

        SiteMessageService.setMessage( request, MESSAGE_CONFIRM_REMOVE_VERSION, SiteMessage.TYPE_CONFIRMATION, url.getUrl( ) );

        return null;
    }

    /**
     * Handles the removal form of a topic version
     *
     * @param request
     *            The Http request
     * @return the jsp URL to display the form to manage images
     * @throws UserNotSignedException
     *             if the user is not signed
     */
    @Action( ACTION_REMOVE_VERSION )
    public XPage doRemoveVersion( HttpServletRequest request ) throws UserNotSignedException
    {
        checkUser( request );

        // requires admin role
        if ( RoleService.hasAdminRole( request ) )
        {
            int nId = Integer.parseInt( request.getParameter( Constants.PARAMETER_TOPIC_VERSION_ID ) );
            TopicVersionHome.remove( nId );
            addInfo( MESSAGE_VERSION_REMOVED, getLocale( request ) );
        }

        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, request.getParameter( Constants.PARAMETER_PAGE_NAME ) );

        return redirect( request, VIEW_HISTORY, mapParameters );
    }

    /**
     * Returns the image list as JSON
     * 
     * @param request
     *            The HTTP request
     * @return A JSON flow
     */
    @View( VIEW_LIST_IMAGES )
    public XPage getListImages( HttpServletRequest request ) throws UserNotSignedException
    {
        String strTopicId = request.getParameter( Constants.PARAMETER_TOPIC_ID );
        JSONArray array = new JSONArray( );
        int nTopicId = strTopicId!=null ? Integer.parseInt( strTopicId ) : null;
         if(!getTopicIdListForUser( request ).contains( nTopicId ))
        {
            throw new UserNotSignedException( );
        }
        if ( strTopicId != null )
        {
            List<Image> list = ImageHome.findByTopic( nTopicId );
            for ( Image image : list )
            {
                JSONObject jsonImage = new JSONObject( );
                jsonImage.accumulate( "id", image.getId( ) );
                jsonImage.accumulate( "name", image.getName( ) );
                array.add( jsonImage );
            }
        }

        return responseJSON( array.toString( ) );
    }
    /**
     * Displays the options if you want to modify published
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws SiteMessageException
     *             if an exception occurs
     */
    @View( VIEW_MODIFY_PUBLISHED )
    public XPage doModifyPublished( HttpServletRequest request ) throws UserNotSignedException
    {
        checkUser( request );

        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        strPageName = WikiUtils.normalize( strPageName );
        if( !getTopicNameListForUser( request ).contains( strPageName ) )
        {
            throw new UserNotSignedException( );
        }
        Topic topic = TopicHome.findByName( strPageName );
        Map<String, Object> model = getModel( );

        model.put( Constants.PARAMETER_PAGE_NAME, strPageName );

        XPage page = getXPage( TEMPLATE_MODIFY_PUBLISHED, request.getLocale( ), model );
        page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
        page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );
        return page;
    }

    /**
     * Creates a new version from the published version
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws UserNotSignedException
     *             if the user is not signed
     */
    @Action( ACTION_CREATE_VERSION_FROM_PUBLISHED )
    public XPage doCreateVersionFromPublished( HttpServletRequest request ) throws UserNotSignedException
    {
        LuteceUser user = WikiAnonymousUser.checkUser( request );
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = TopicHome.findByName( strPageName );

        if ( RoleService.hasEditRole( request, topic ) )
        {
            TopicVersion publishedVersion = TopicVersionHome.getPublishedVersion( topic.getIdTopic( ) );
            publishedVersion.setUserName( user.getName( ) );
            publishedVersion.setEditComment( "Version created from the published version" );
            publishedVersion.setIdTopicVersionPrevious( publishedVersion.getIdTopicVersion( ) );
            publishedVersion.setIsPublished( false );

            TopicVersionHome.addTopicVersion( publishedVersion );
        }

        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, strPageName );

        return redirect( request, VIEW_MODIFY_PAGE, mapParameters );
    }

    /**
     * Cancel publication of a page
     *
     * @param request
     *            The HTTP request
     * @return The XPage
     * @throws UserNotSignedException
     *             if the user is not signed
     */
    @Action( ACTION_CANCEL_PUBLISH_PAGE )
    public XPage doUnpublish( HttpServletRequest request ) throws UserNotSignedException
    {
        String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
        Topic topic = TopicHome.findByName( strPageName );
        TopicVersion publishedVersion = TopicVersionHome.getPublishedVersion( topic.getIdTopic( ) );
        Map<String, String> mapParameters = new ConcurrentHashMap<>( );
        if ( publishedVersion != null )
        {
	        if ( RoleService.hasEditRole( request, topic ) )
	        {
	            TopicVersion previousPublished = TopicVersionHome.getPublishedVersion( topic.getIdTopic( ) );
	            if ( previousPublished != null )
	            {
	                previousPublished.setIsPublished( false );
	                TopicVersionHome.updateTopicVersion( previousPublished );
	            }
	        }
	        Integer topicVersionId = publishedVersion.getIdTopicVersion( );
        	mapParameters.put( Constants.PARAMETER_TOPIC_VERSION_ID, String.valueOf( topicVersionId ) );
        }
        mapParameters.put( Constants.PARAMETER_PAGE_NAME, topic.getPageName( ) );
        return redirect( request, VIEW_MODIFY_PAGE, mapParameters );
    }

    /**
     * update last edit attempt
     * @param request
     * @return
     * @throws UserNotSignedException
     */
    @Action( ACTION_UPDATE_LAST_EDIT_ATTEMPT )
    public XPage doUpdateLastEditAttempt( HttpServletRequest request ) throws UserNotSignedException
    {
        LuteceUser user = checkUser( request );

        int topicId = Integer.parseInt(request.getParameter( fr.paris.lutece.plugins.wiki.web.Constants.PARAMETER_TOPIC_ID ));
        Topic topic = TopicHome.findByPrimaryKey( topicId );
        if ( !RoleService.hasEditRole( request, topic ) )
        {
            throw new UserNotSignedException( );
        }
        TopicHome.updateLastEditAttempt( topicId, user.getName( ) );
        return responseJSON( "ok" );
    }
    @View( VIEW_GET_PAGE_HEADINGS )
    public XPage getPageHeadings( HttpServletRequest request ) throws UserNotSignedException, com.fasterxml.jackson.core.JsonProcessingException
    {
        String pageName = request.getParameter( "pageName" );
        String locale = request.getParameter( "locale" );
        Topic topic = TopicHome.findByName( pageName );


        List<HashMap<String, String>> headings = new ArrayList<HashMap<String, String>>( );
        try
        {
            if ( getTopicNameListForUser( request ).contains( pageName ) )
            {
                // if you merge this after LUT-25169, change findLastVersion to getPublishedVersion
                TopicVersion topicVersion = TopicVersionHome.getPublishedVersion( topic.getIdTopic( ) );

                String htmlContent = WikiService.instance( ).getWikiPage( pageName, topicVersion, getPageUrl( request ), locale );

                Document htmlDocument = Jsoup.parse( htmlContent );
                Element docBody = htmlDocument.body( );
                for ( Element element : docBody.select( "h1, h2, h3, h4, h5, h6" ) )
                {
                    HashMap<String, String> heading = new HashMap<String, String>( );
                    heading.put( "header_id", element.id( ) );
                    heading.put( "header_text", element.text( ) );
                    headings.add( heading );
                }
            }
            else
            {
                throw new UserNotSignedException( );
            }
        }
        catch ( Exception e )
        {
            throw new UserNotSignedException( );
        }
        ObjectMapper mapper = new ObjectMapper();
        String res = mapper.writeValueAsString(headings);

        return responseJSON( res );
    }
    // /////////////////// Utils ////////////////////////////
    /**
     * Checks the connected user
     *
     * @param request
     *            The HTTP request
     * @return The user
     * @throws UserNotSignedException
     *             if user not connected
     */
    private LuteceUser checkUser( HttpServletRequest request ) throws UserNotSignedException
    {
        LuteceUser user;

        if ( SecurityService.isAuthenticationEnable( ) )
        {
            user = SecurityService.getInstance( ).getRemoteUser( request );

            if ( user == null )
            {
                throw new UserNotSignedException( );
            }
        }
        else
        {
            user = new WikiAnonymousUser( );
        }

        return user;
    }

    /**
     * Gets the topic corresponding to the given page name
     *
     * @param request
     *            The HTTP request
     * @param strPageName
     *            The page name
     * @param nMode
     *            The mode VIEW vs EDIT
     * @return The topic
     * @throws SiteMessageException
     *             If an error occurs
     */
    private Topic getTopic( HttpServletRequest request, String strPageName, int nMode ) throws SiteMessageException, UserNotSignedException
    {
        Topic topic = TopicHome.findByName( strPageName );

        if ( topic == null )
        {
            SiteMessageService.setMessage( request, Constants.MESSAGE_PAGE_NOT_EXISTS, SiteMessage.TYPE_STOP );
        }
        else
        {
            String strRole;
            String strMessage;
            switch( nMode )
            {
                case MODE_EDIT:
                    strRole = topic.getEditRole( );
                    strMessage = Constants.MESSAGE_USER_NOT_IN_ROLE;
                    break;
                case MODE_VIEW:
                default:
                    strRole = topic.getViewRole( );
                    strMessage = Constants.MESSAGE_USER_NOT_IN_ROLE;
                    break;
            }

            if ( SecurityService.isAuthenticationEnable( ) && ( !Page.ROLE_NONE.equals( strRole ) ) )
            {
                LuteceUser user = SecurityService.getInstance( ).getRemoteUser( request );

                if ( user == null )
                {
                    throw new UserNotSignedException( );
                }

                if ( !SecurityService.getInstance( ).isUserInRole( request, strRole ) )
                {
                    SiteMessageService.setMessage( request, strMessage, SiteMessage.TYPE_STOP );
                }
            }
        }

        return topic;
    }

    /**
     * Gets topics visible for a given user
     *
     * @param request
     *            The HTTP request
     * @return The list of topics
     */
    private List<Topic> getTopicsForUser( HttpServletRequest request )
    {
        Collection<Topic> listTopicAll = TopicHome.getTopicsList( );
        List<Topic> listTopic;

        if ( SecurityService.isAuthenticationEnable( ) )
        {
            listTopic = new ArrayList<>( );

            for ( Topic topic : listTopicAll )
            {
                String strRole = topic.getViewRole( );

                if ( !Page.ROLE_NONE.equals( strRole ) )
                {
                    if ( SecurityService.getInstance( ).isUserInRole( request, strRole ) )
                    {
                        listTopic.add( topic );
                    }
                }
                else
                {
                    listTopic.add( topic );
                }
            }
        }
        else
        {
            listTopic = new ArrayList<>( listTopicAll );
        }

        return listTopic;
    }

    /**
     * Returns a reference list of pages for user
     * 
     * @param request
     *            The HTTP request
     * @param bFirstItem
     *            True if the reference list must have a first empty default item
     * @return The reference list
     */
    private ReferenceList getTopicsReferenceListForUser( HttpServletRequest request, boolean bFirstItem )
    {
        ReferenceList list = new ReferenceList( );

        if ( bFirstItem )
        {
            list.addItem( "", "" );
        }
        for ( Topic topic : getTopicsForUser( request ) )
        {
            list.addItem( topic.getPageName( ), getTopicTitle( request, topic ) );
        }

        return list;
    }
    /*
     * @return the list of pages for user with topic name as value
     * @param request
     */
    private List<String> getTopicNameListForUser( HttpServletRequest request )
    {
        List<String> list = new ArrayList<>( );

        for ( Topic topic : getTopicsForUser( request ) )
        {
            list.add( topic.getPageName( ) );
        }

        return list;
    }
    /*
     * @return the list of pages for user with topic name as value
     * @param request
     */
    private List<Integer> getTopicIdListForUser( HttpServletRequest request )
    {
        List<Integer> list = new ArrayList<>( );

        for ( Topic topic : getTopicsForUser( request ) )
        {
            list.add( topic.getIdTopic( ) );
        }

        return list;
    }


    /**
     * Builds the page title
     *
     * @param strPageName
     *            The page name
     * @return The page title
     */
    private String getPageTitle( String strPageName )
    {
        StringBuilder sbPath = new StringBuilder( );
        sbPath.append( AppPropertiesService.getProperty( PROPERTY_PAGE_TITLE ) ).append( ' ' ).append( strPageName );

        return sbPath.toString( );
    }

    /**
     * Builds the extended path for views other than page
     *
     * @param strViewTitle
     *            The view title
     * @return The list that content path info
     */
    private ReferenceList getViewExtendedPath( String strViewTitle )
    {
        String strWikiRootLabel = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_LABEL, AppPropertiesService.getProperty( PROPERTY_PAGE_PATH ) );
        String strWikiRootPageName = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_PAGENAME, PAGE_DEFAULT );

        ReferenceList list = new ReferenceList( );

        list.addItem( strWikiRootLabel, URL_VIEW_PAGE + strWikiRootPageName );
        list.addItem( strViewTitle, "" );

        return list;
    }

    /**
     * Builds the extended path for wiki page
     *
     * @param topic
     *            The topic page
     * @param request
     *            The request
     * @return The list that content path info
     */
    private ReferenceList getPageExtendedPath( Topic topic, HttpServletRequest request )
    {
        String strWikiRootLabel = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_LABEL, AppPropertiesService.getProperty( PROPERTY_PAGE_PATH ) );
        String strWikiRootPageName = DatastoreService.getDataValue( DSKEY_WIKI_ROOT_PAGENAME, AppPropertiesService.getProperty( PAGE_DEFAULT ) );

        ReferenceList list = new ReferenceList( );
        ReferenceItem item;

        String strTopicTitle;
        String strTopicUrl;

        if ( !topic.getPageName( ).equals( strWikiRootPageName ) )
        {
            list.addItem( getTopicTitle( request, topic ), "" );
        }

        while ( topic != null && !topic.getParentPageName( ).isEmpty( ) && topic.getParentPageName( ) != null
                && !topic.getParentPageName( ).equals( strWikiRootPageName ) && !isNameInReferenceList( list, URL_VIEW_PAGE + topic.getParentPageName( ) ) )
        {
            topic = TopicHome.findByName( topic.getParentPageName( ) );

            if ( topic != null )
            {
                strTopicTitle = getTopicTitle( request, topic );
                strTopicUrl = URL_VIEW_PAGE + topic.getPageName( );

                if ( SecurityService.isAuthenticationEnable( ) && ( !Page.ROLE_NONE.equals( topic.getViewRole( ) ) ) )
                {
                    if ( !SecurityService.getInstance( ).isUserInRole( request, topic.getViewRole( ) ) )
                    {
                        strTopicTitle = I18nService.getLocalizedString( MESSAGE_PATH_HIDDEN, getLocale( request ) );
                        strTopicUrl = "";
                    }
                }

                item = new ReferenceItem( );
                item.setCode( strTopicTitle );
                item.setName( strTopicUrl );

                list.add( 0, item );
            }
        }

        item = new ReferenceItem( );
        item.setCode( strWikiRootLabel );
        item.setName( URL_VIEW_PAGE + strWikiRootPageName );

        list.add( 0, item );

        return list;
    }

    /**
     * Checks if name is already in an item of the reference list
     * 
     * @param list
     *            The reference list
     * @param name
     *            The name
     * @return true if the name is in list and false otherwise
     */
    private boolean isNameInReferenceList( ReferenceList list, String name )
    {
        for ( ReferenceItem item : list )
        {
            if ( item.getName( ).equals( name ) )
            {
                return true;
            }
        }

        return false;
    }

    /**
     * Fills all versions with users infos
     * 
     * @param listVersions
     *            The version
     */
    private void fillUsersData( Collection<TopicVersion> listVersions )
    {
        for ( TopicVersion version : listVersions )
        {
            fillUserData( version );
        }
    }

    /**
     * Fills the version with users infos
     * 
     * @param version
     *            The version
     */
    private void fillUserData( TopicVersion version )
    {
        String strUserId = version.getUserName( );

        LuteceUser user = SecurityService.getInstance( ).getUser( strUserId );

        if ( user != null )
        {
            version.setUserName( user.getUserInfo( LuteceUser.NAME_GIVEN ) + " " + user.getUserInfo( LuteceUser.NAME_FAMILY ) );
            version.setUserAvatarUrl( AvatarService.getAvatarUrl( user.getEmail( ) ) );
        }
        else
        {
            version.setUserAvatarUrl( AvatarService.getAvatarUrl( strUserId ) );
        }

        version.setUserPseudo( UserPreferencesService.instance( ).getNickname( strUserId ) );
    }

    /**
     * Gets the current page URL from the request
     * 
     * @param request
     *            The request
     * @return The URL
     */
    private String getPageUrl( HttpServletRequest request )
    {
        return request.getRequestURI( ).substring( request.getContextPath( ).length( ) + 1 ) + "?" + request.getQueryString( );
    }

    /**
     * Returns if the plugin Extend is available
     * 
     * @return true if extend is installed and activated otherwise false
     */
    private boolean isExtend( )
    {
        Plugin plugin = PluginService.getPlugin( PLUGIN_EXTEND );
        return ( ( plugin != null ) && plugin.isInstalled( ) );
    }

    /**
     * Stores the current selected language in the user's session
     * 
     * @param request
     *            The request
     * @param strLanguage
     *            The language
     */
    private void setLanguage( HttpServletRequest request, String strLanguage )
    {
        Locale userSelectedLocale = new Locale( strLanguage );
        if ( LocaleService.isSupported( userSelectedLocale ) )
        {
            LocaleService.setUserSelectedLocale( request, userSelectedLocale );
        }
    }

    /**
     * Retrieves the current selected language from the user's session
     * 
     * @param request
     *            The request
     * @return The Language
     */
    private String getLanguage( HttpServletRequest request )
    {
        String strLanguage = null;

        if ( request.getParameter( PARAMETER_LANGUAGE ) != null )
        {
            // consider the language parameter in the URL if exists
            strLanguage = request.getParameter( PARAMETER_LANGUAGE );
            setLanguage( request, strLanguage );
        }
        else
        {
            // otherwise, consider the language stored in the user's session
            strLanguage = LocaleService.getUserSelectedLocale( request ).getLanguage( );
            setLanguage( request, strLanguage );
        }
        return LocaleService.getContextUserLocale( request ).getLanguage( );
    }

    /**
     * Returns a topic title
     * 
     * @param topic
     *            The topic
     * @param strLanguage
     *            The language
     * @return The title
     */
    private String getTopicTitle( Topic topic, String strLanguage )
    {
        TopicVersion version = TopicVersionHome.getPublishedVersion( topic.getIdTopic( ) );
        if ( version != null && StringUtils.isNotEmpty( version.getWikiContent( strLanguage ).getPageTitle( ) ) )
        {
            return version.getWikiContent( strLanguage ).getPageTitle( );
        }
        else
        {
            return topic.getPageName( );
        }
    }

    /**
     * Returns a topic title
     * 
     * @param request
     *            The HTTP request
     * @param topic
     *            The topic
     * @return The title
     */
    private String getTopicTitle( HttpServletRequest request, Topic topic )
    {
        return getTopicTitle( topic, getLanguage( request ) );
    }

    /**
     * Retrieves the current selected version from the parameter
     *
     * @param request
     *            The request
     * @return The Language
     */
    private Integer getVersionTopicVersionId( HttpServletRequest request )
    {
        Integer versionId = null;

        if ( request.getParameter( Constants.PARAMETER_TOPIC_VERSION_ID ) != null )
        {
            versionId = Integer.parseInt( request.getParameter( Constants.PARAMETER_TOPIC_VERSION_ID ) );
            return versionId;
        }

        return versionId;
    }
}