MyVoteXPage.java

/*
 * Copyright (c) 2002-2020, 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.participatorybudget.web.vote;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;

import fr.paris.lutece.plugins.extend.business.extender.history.ResourceExtenderHistory;
import fr.paris.lutece.plugins.extend.business.extender.history.ResourceExtenderHistoryFilter;
import fr.paris.lutece.plugins.extend.modules.follow.service.FollowService;
import fr.paris.lutece.plugins.extend.modules.follow.service.IFollowService;
import fr.paris.lutece.plugins.extend.modules.follow.service.extender.FollowResourceExtender;
import fr.paris.lutece.plugins.extend.modules.rating.service.IRatingService;
import fr.paris.lutece.plugins.extend.modules.rating.service.RatingAddOnService;
import fr.paris.lutece.plugins.extend.modules.rating.service.RatingService;
import fr.paris.lutece.plugins.extend.modules.rating.service.security.IRatingSecurityService;
import fr.paris.lutece.plugins.extend.modules.rating.service.security.RatingSecurityService;
import fr.paris.lutece.plugins.extend.modules.rating.service.validator.RatingValidationManagementService;
import fr.paris.lutece.plugins.extend.modules.rating.util.constants.RatingConstants;
import fr.paris.lutece.plugins.extend.service.extender.history.IResourceExtenderHistoryService;
import fr.paris.lutece.plugins.extend.service.extender.history.ResourceExtenderHistoryService;
import fr.paris.lutece.plugins.participatorybudget.business.MyInfosForm;
import fr.paris.lutece.plugins.participatorybudget.business.vote.MyVote;
import fr.paris.lutece.plugins.participatorybudget.business.vote.Vote;
import fr.paris.lutece.plugins.participatorybudget.business.vote.VoteHome;
import fr.paris.lutece.plugins.participatorybudget.business.vote.VotePerLocation;
import fr.paris.lutece.plugins.participatorybudget.service.MyInfosService;
import fr.paris.lutece.plugins.participatorybudget.service.campaign.CampaignService;
import fr.paris.lutece.plugins.participatorybudget.service.rating.BudgetRatingService;
import fr.paris.lutece.plugins.participatorybudget.service.vote.IVoteParArrandissementService;
import fr.paris.lutece.plugins.participatorybudget.service.vote.MyVoteService;
import fr.paris.lutece.plugins.participatorybudget.service.vote.VoteParArrandissementService;
import fr.paris.lutece.plugins.participatorybudget.util.BudgetUtils;
import fr.paris.lutece.plugins.participatorybudget.util.ParticipatoryBudgetConstants;
import fr.paris.lutece.plugins.subscribe.business.Subscription;
import fr.paris.lutece.plugins.subscribe.business.SubscriptionFilter;
import fr.paris.lutece.plugins.subscribe.service.SubscriptionService;
import fr.paris.lutece.portal.service.captcha.CaptchaSecurityService;
import fr.paris.lutece.portal.service.datastore.DatastoreService;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.mail.MailService;
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.PluginService;
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.AppPathService;
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.xpages.XPage;
import fr.paris.lutece.util.html.HtmlTemplate;
import fr.paris.lutece.util.json.AbstractJsonResponse;
import fr.paris.lutece.util.json.ErrorJsonResponse;
import fr.paris.lutece.util.json.JsonResponse;
import fr.paris.lutece.util.json.JsonUtil;
import fr.paris.lutece.util.url.UrlItem;

/**
 * This class provides the user interface to manage ParisConnectUser xpages ( manage, create, modify, remove )
 */
@Controller( xpageName = MyVoteXPage.VIEW_MY_VOTES, pageTitleI18nKey = "participatorybudget.xpage.mesVotes.pageTitle", pagePathI18nKey = "participatorybudget.xpage.mesVotes.pagePathLabel" )
public class MyVoteXPage extends MVCApplication
{

    /**
     * Name of this application
     */
    public static final String TOKEN_DO_SEND_AVIS = "doSendAvis";
    public static final String TOKEN_DO_SUBSCRIBE_ALERT = "doSubscribeAlert";
    public static final String TOKEN_DO_SAVE_MY_INFOS = "doSaveMyInfos";
    public static final String TOKEN_DO_CREATE_MY_INFOS = "doCreateMyInfos";
    public static final String PAGE_MY_INFOS = "mesVotes";
    private static final long serialVersionUID = -4316691400124512414L;
    private static final String JCAPTCHA_PLUGIN = "jcaptcha";
    // Templates
    private static final String TEMPLATE_MY_VOTES = "/skin/plugins/participatorybudget/mes_votes.html";
    private static final String TEMPLATE_BUTTON_CANCEL_VOTE = "/skin/plugins/participatorybudget/button_cancel_vote_html.html";
    private static final String TEMPLATE_NAVBAR_PROJECT = "/skin/plugins/participatorybudget/navbar_project.html";
    private static final String TEMPLATE_BUTTON_DO_VOTE = "/skin/plugins/participatorybudget/button_do_vote_html.html";
    private static final String TEMPLATE_BUTTON_VALID_VOTE = "/skin/plugins/participatorybudget/button_valid_vote.html";

    public static final String SUBSCRIPTION_UPDATE_ON_REALIZATION = "updateOnRealization";

    // Views
    public static final String VIEW_MY_VOTES = "myVotes";

    // Actions
    private static final String ACTION_VALIDATE_VOTES = "validateVote";

    // Markers

    private static final String MARK_USER_VOTES = "userVotes";
    private static final String MARK_USER_TYPE_VOTE = "type_vote";
    private static final String MARK_USER_VOTES_PARIS = "WHOLE CITY";
    private static final String MARK_USER_VOTES_OTHER = "Paris ";
    private static final String MARK_USER_ARRONDISSEMENT = "user_arrondissement";
    private static final String MARK_ARRONDISSEMENT = "arrondissement";
    private static final String MARK_NBR_PROJET_ARRDT = "nbrProjetArrond";
    private static final String MARK_NBR_PROJET_PARIS = "nbrProjetParis";
    private static final String MARK_DATE_VALIDATION = "validate_vote_date";
    private static final String MARK_USER_PSEUDO = "user_pseudo";
    private static final String MARK_LIST_VALIDATE_PROJECTS = "vote_projects_list";
    private static final String PROPERTY_MY_VOTES_PAGE_TITLE = "participatorybudget.xpage.myVotes.pageTitle";
    private static final String PROPERTY_MY_VOTES_PAGE_PATH_LABEL = "participatorybudget.xpage.myVotes.pagePathLabel";

    // Messages

    private static final String MESSAGE_INFO_USER_VOTE_VALIDE = "participatorybudget.messageSuccesSaveVote";
    private static final String MESSAGE_INFO_VOTE = "participatorybudget.message.info.vote";
    private static final String MESSAGE_PAGE_NOT_ACCESSIBLE = "participatorybudget.page_not_accessible";

    // Parameters
    private static final String PARAMETER_ARRONDISSEMENT = "arrondissement";
    private static final String PARAM_NOTIFY_VALIDE = "notify_valide";
    private static final String PARAM_VALIDATE = "validate";
    private static final String PARAM_FOLLOW = "vote_follow";
    private static final String PARAMETER_COMPLETE_INFOS = "completeInfos";

    // Json ERROR CODE
    // Json ERROR CODE
    private static final String JSON_ERROR_CODE_USER_NOT_SIGNED = "USER_NOT_SIGNED";
    private static final String JSON_ERROR_CODE_USER_CAN_NOT_VOTE = "USER_CAN_NOT_VOTE";
    private static final String JSON_ERROR_CODE_USER_ALREADY_VOTED = "USER_ALREADY_VOTED";
    private static final String JSON_ERROR_CHECKED_ARRONDISSEMENT = "JSON_ERROR_CHECKED_ARRONDISSEMENT";
    private static final String JSON_ERROR_ALREADY_VOTED_ARRONDISSEMENT = "JSON_ERROR_ALREADY_VOTED_ARRONDISSEMENT";
    private static final String JSON_ERROR_ALREADY_VOTED_TOUT_PARIS = "JSON_ERROR_ALREADY_VOTED_TOUT_PARIS";
    private static final String JSON_ERROR_VOTE_USER_ARROND = "JSON_ERROR_VOTE_USER_ARROND";
    private static final String JSON_ERROR_CODE_USER__VOTED_MAX = "ERROR_CODE_USER_VOTED_MAX";

    private static final String CONSTANT_HTTP = "http";
    private static final String DATE_EMAIL_VALIDATION_PATTERN = "dd-MM-yyyy 'à' HH:mm:ss";
    private static final String VOTE_TITLE_SEPARATOR = "; ";

    // Key
    private static final String KEY_ENABLE_CAPTCHA_USER_INFORMATIONS = "participatorybudget.site_property.enable_captcha_user_informations";
    private static final String KEY_ERROR_CODE_USER_ALREADY_VOTED = "participatorybudget.site_property.error_code_user_already_voted";
    private static final String KEY_ERROR_CODE_USER_CAN_NOT_VOTE = "participatorybudget.site_property.error_code_user_can_not_vote";
    private static final String KEY_ERROR_CODE_USER__VOTED_MAX = "participatorybudget.site_property.error_code_user__voted_max";
    private static final String KEY_ERROR_VOTE_USER_ARROND = "participatorybudget.site_property.error_vote_user_arrond";
    private static final String KEY_ERROR_ALREADY_VOTED_TOUT_PARIS = "participatorybudget.site_property.error_already_voted_tout_paris";
    private static final String KEY_ERROR_ALREADY_VOTED_ARRONDISSEMENT = "participatorybudget.site_property.error_already_voted_arrondissement";
    private static final String KEY_ERROR_CHECKED_ARRONDISSEMENT = "participatorybudget.site_property.error_checked_arrondissement";
    private static final String KEY_ERROR_CODE_USER_NOT_SIGNED = "participatorybudget.site_property.error_code_user_not_signed";
    private static final String KEY_VALIDATION_VOTE_EMAIL_SENDER = "participatorybudget.site_property.vote_confirmation.sender";
    private static final String KEY_VALIDATION_VOTE_EMAIL_CC = "participatorybudget.site_property.vote_confirmation.cc";
    private static final String KEY_VALIDATION_VOTE_EMAIL_SUBJECT = "participatorybudget.site_property.vote_confirmation.subject";
    private static final String KEY_VALIDATION_VOTE_EMAIL_TEMPLATE = "participatorybudget.site_property.vote_confirmation.template.htmlblock";

    // Constant
    private static final String LOCATION_LABEL = "location";
    private static final String LOCATION_VALUE = "whole_city";
    private static final String SEPARATOR = "||";

    private MyVoteService _myVoteService = SpringContextService.getBean( MyVoteService.BEAN_NAME );
    private IRatingService _ratingService = SpringContextService.getBean( RatingService.BEAN_SERVICE );
    private IRatingSecurityService _ratingSecurityService = SpringContextService.getBean( RatingSecurityService.BEAN_SERVICE );

    private IVoteParArrandissementService _nbrVoteService = VoteParArrandissementService.getInstance( );

    // Messages
    private static final String MESSAGE_INFO_USER_MUST_COMPLETE_PROFILE = "participatorybudget.labelUserMustCompleteProfile";

    private CaptchaSecurityService _captchaService = new CaptchaSecurityService( );

    @Inject
    private IFollowService _followService = SpringContextService.getBean( FollowService.BEAN_SERVICE );
    @Inject
    private IResourceExtenderHistoryService _resourceExtenderService = SpringContextService.getBean( ResourceExtenderHistoryService.BEAN_SERVICE );

    /**
     * Get the page to view the ratings of the current user
     * 
     * @param request
     *            The request
     * @return The HTML content to display
     * @throws UserNotSignedException
     *             If the user has not signed in
     * @throws SiteMessageException
     */
    @View( VIEW_MY_VOTES )
    public XPage getMyVotes( HttpServletRequest request ) throws UserNotSignedException, SiteMessageException
    {

        if ( !CampaignService.getInstance( ).isDuring( "VOTE" ) )
        {
            SiteMessageService.setMessage( request, MESSAGE_PAGE_NOT_ACCESSIBLE, SiteMessage.TYPE_STOP );
        }

        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

        if ( !SecurityService.isAuthenticationEnable( ) || user == null )
        {
            throw new UserNotSignedException( );
        }
        String notify_valide = request.getParameter( PARAM_NOTIFY_VALIDE );

        MyInfosForm myInfo = MyInfosService.loadUserInfos( user );

        boolean isValidated = _myVoteService.isUserVoteValidated( user.getName( ) );
        int maxDcmtArrondissement = 0;
        if ( _nbrVoteService.selectVotePerLocation( myInfo.getArrondissement( ) ) != null )
        {
            maxDcmtArrondissement = _nbrVoteService.selectVotePerLocation( myInfo.getArrondissement( ) ).getNbVotes( );
        }
        int maxDcmtToutParis = _nbrVoteService.selectVotePerLocation( "whole_city" ).getNbVotes( );

        Map<String, Object> model = getModel( );

        if ( notify_valide != null && notify_valide.equals( "true" ) )
        {
            addInfo( MESSAGE_INFO_USER_VOTE_VALIDE, request.getLocale( ) );
        }
        model.put( MARK_USER_ARRONDISSEMENT, BudgetUtils.getArrondissementDisplay( user ) );
        model.put( MARK_NBR_PROJET_ARRDT, maxDcmtArrondissement );
        model.put( MARK_NBR_PROJET_PARIS, maxDcmtToutParis );

        model.put( MARK_USER_VOTES, _myVoteService.getUserVote( user ) );

        model.put( BudgetUtils.MARK_VOTE_VALIDATED, isValidated );

        model.put( BudgetUtils.MARK_CAMPAIGN_SERVICE, CampaignService.getInstance( ) );

        XPage page = getXPage( TEMPLATE_MY_VOTES, getLocale( request ), model );
        page.setTitle( I18nService.getLocalizedString( PROPERTY_MY_VOTES_PAGE_TITLE, Locale.FRENCH ) );
        page.setPathLabel( I18nService.getLocalizedString( PROPERTY_MY_VOTES_PAGE_PATH_LABEL, Locale.FRENCH ) );

        return page;
    }

    public String saveUserArr( HttpServletRequest request )
    {
        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

        if ( user != null )
        {
            String arr = request.getParameter( MARK_ARRONDISSEMENT );

            BudgetUtils.setArrondissement( user.getName( ), arr );
        }
        return StringUtils.EMPTY;
    }

    /**
     * Load user current votes
     * 
     * @param request
     *            The request
     * @return The JSON result
     * @throws SiteMessageException
     */
    public String loadVotes( HttpServletRequest request )
    {
        if ( !CampaignService.getInstance( ).isDuring( "VOTE" ) )
        {
            return JsonUtil.buildJsonResponse( new JsonResponse( "closed" ) );
        }
        Map<String, Object> model = new HashMap<String, Object>( );

        List<MyVote> listMyVoteFull = new ArrayList<MyVote>( );
        List<MyVote> listMyVoteParis = new ArrayList<MyVote>( );
        List<MyVote> listMyVoteOther = new ArrayList<MyVote>( );

        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

        if ( user != null )
        {
            listMyVoteFull = _myVoteService.getUserVote( user );
            model.put( MARK_USER_ARRONDISSEMENT, BudgetUtils.getArrondissementDisplay( user ) );
            model.put( BudgetUtils.MARK_VOTE_VALIDATED, _myVoteService.isUserVoteValidated( user.getName( ) ) );
        }

        for ( MyVote vote : listMyVoteFull )
        {
            if ( LOCATION_VALUE.equals( vote.getDocument( ).getAttribute( LOCATION_LABEL ).getTextValue( ) ) )
            {
                listMyVoteParis.add( vote );
            }
            else
            {
                listMyVoteOther.add( vote );
            }
        }

        model.put( MARK_USER_VOTES, listMyVoteParis );
        model.put( MARK_USER_TYPE_VOTE, MARK_USER_VOTES_PARIS );

        String templateVotesParis = AppTemplateService.getTemplate( TEMPLATE_NAVBAR_PROJECT, request.getLocale( ), model ).getHtml( );

        model.put( MARK_USER_VOTES, listMyVoteOther );
        if ( user != null )
        {
            model.put( MARK_USER_TYPE_VOTE, BudgetUtils.getArrondissementDisplay( user ) );
        }
        else
        {
            model.put( MARK_USER_TYPE_VOTE, MARK_USER_VOTES_OTHER );
        }

        String templateVotesOther = AppTemplateService.getTemplate( TEMPLATE_NAVBAR_PROJECT, request.getLocale( ), model ).getHtml( );

        model.put( MARK_NBR_PROJET_ARRDT, listMyVoteOther.size( ) );
        model.put( MARK_NBR_PROJET_PARIS, listMyVoteParis.size( ) );

        String templateValidBtn = AppTemplateService.getTemplate( TEMPLATE_BUTTON_VALID_VOTE, request.getLocale( ), model ).getHtml( );

        boolean arrChoosed = true;
        if ( user != null && BudgetUtils.getArrondissement( user.getName( ) ).isEmpty( ) )
        {
            arrChoosed = false;
        }

        return JsonUtil.buildJsonResponse(
                new JsonResponse( templateVotesParis + SEPARATOR + templateVotesOther + SEPARATOR + templateValidBtn + SEPARATOR + arrChoosed ) );
    }

    /**
     * Do vote
     * 
     * @param request
     *            The request
     * @return The JSON result
     * @throws SiteMessageException
     */
    public String doVote( HttpServletRequest request ) throws SiteMessageException
    {
        String strIdExtendableResource = request.getParameter( RatingConstants.PARAMETER_ID_EXTENDABLE_RESOURCE );
        String strExtendableResourceType = request.getParameter( RatingConstants.PARAMETER_EXTENDABLE_RESOURCE_TYPE );
        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );
        String strUserId = user.getName( );
        MyInfosForm myInfos = MyInfosService.loadUserInfos( user );
        int maxDcmtArrondissement = 0;
        int maxDcmtToutParis = 0;
        VotePerLocation voteLoc = _nbrVoteService.selectVotePerLocation( myInfos.getArrondissement( ) );
        if ( voteLoc != null )
        {
            maxDcmtArrondissement = voteLoc.getNbVotes( );
        }
        voteLoc = _nbrVoteService.selectVotePerLocation( "whole_city" );
        if ( voteLoc != null )
        {
            maxDcmtToutParis = voteLoc.getNbVotes( );
        }
        String locationProjet = request.getParameter( ParticipatoryBudgetConstants.PROJECT_LOCATION );

        try
        {

            // This is duplicated in RatingValidationService.java because here
            // we want to return json
            // instead of redirecting to an url or throwing a
            // SiteMessageException
            if ( !CampaignService.getInstance( ).isDuring( "VOTE" ) || _myVoteService.isUserVoteValidated( strUserId ) )
            {
                SiteMessageService.setMessage( request, RatingConstants.MESSAGE_CANNOT_VOTE, SiteMessage.TYPE_STOP );
            }

            if ( StringUtils.isBlank( strIdExtendableResource ) || StringUtils.isBlank( strExtendableResourceType ) )
            {
                SiteMessageService.setMessage( request, RatingConstants.MESSAGE_ERROR_GENERIC_MESSAGE, SiteMessage.TYPE_STOP );
            }
            if ( VoteHome.getVote( strUserId, Integer.parseInt( strIdExtendableResource ) ) != null )
            {
                return doCancelVote( request );
            }
            // Check if the user can vote or not
            try
            {
                if ( !_ratingSecurityService.canVote( request, strIdExtendableResource, strExtendableResourceType ) )
                {

                    if ( _ratingSecurityService.hasAlreadyVoted( request, strIdExtendableResource, strExtendableResourceType ) )
                    {

                        return JsonUtil.buildJsonResponse( new ErrorJsonResponse( JSON_ERROR_CODE_USER_ALREADY_VOTED,
                                DatastoreService.getDataValue( KEY_ERROR_CODE_USER_ALREADY_VOTED, "" ) ) );

                    }
                    return JsonUtil.buildJsonResponse(
                            new ErrorJsonResponse( JSON_ERROR_CODE_USER_CAN_NOT_VOTE, DatastoreService.getDataValue( KEY_ERROR_CODE_USER_CAN_NOT_VOTE, "" ) ) );
                }
            }
            catch( UserNotSignedException e )
            {
                SiteMessageService.setMessage( request, RatingConstants.MESSAGE_CANNOT_VOTE, SiteMessage.TYPE_STOP );
            }
        }
        catch( SiteMessageException e )
        {
            return JsonUtil.buildJsonResponse( new ErrorJsonResponse( AppPathService.getSiteMessageUrl( request ) ) );
        }

        //
        // This is duplicated in RatingValidationService.java because here we
        // want to return json
        // instead of redirecting to an url or throwing a SiteMessageException
        int nbrVoteUserArrond = VoteHome.getVoteUserNotLocation( strUserId, 75000 );
        int nbrVoteUserParis = VoteHome.getVoteUserArrondissement( strUserId, 75000 );

        if ( nbrVoteUserParis >= maxDcmtToutParis && nbrVoteUserArrond >= maxDcmtArrondissement )
        {

            return JsonUtil.buildJsonResponse(
                    new ErrorJsonResponse( JSON_ERROR_CODE_USER__VOTED_MAX, DatastoreService.getDataValue( KEY_ERROR_CODE_USER__VOTED_MAX, "" ) ) );
        }
        if ( nbrVoteUserParis >= maxDcmtToutParis && maxDcmtToutParis > 0
                && request.getParameter( ParticipatoryBudgetConstants.PROJECT_LOCATION ).equals( ParticipatoryBudgetConstants.LOCATION_WHOLE_CITY ) )
        {

            return JsonUtil.buildJsonResponse(
                    new ErrorJsonResponse( JSON_ERROR_ALREADY_VOTED_TOUT_PARIS, DatastoreService.getDataValue( KEY_ERROR_ALREADY_VOTED_TOUT_PARIS, "" ) ) );
        }
        if ( nbrVoteUserArrond >= maxDcmtArrondissement && maxDcmtArrondissement > 0
                && !request.getParameter( ParticipatoryBudgetConstants.PROJECT_LOCATION ).equals( ParticipatoryBudgetConstants.LOCATION_WHOLE_CITY ) )
        {

            return JsonUtil.buildJsonResponse( new ErrorJsonResponse( JSON_ERROR_ALREADY_VOTED_ARRONDISSEMENT,
                    DatastoreService.getDataValue( KEY_ERROR_ALREADY_VOTED_ARRONDISSEMENT, "" ) ) );

        }
        if ( locationProjet != null && !locationProjet.equals( BudgetUtils.getArrondissementDisplay( user ) )
                && !locationProjet.equals( ParticipatoryBudgetConstants.LOCATION_WHOLE_CITY ) )
        {

            return JsonUtil
                    .buildJsonResponse( new ErrorJsonResponse( JSON_ERROR_VOTE_USER_ARROND, DatastoreService.getDataValue( KEY_ERROR_VOTE_USER_ARROND, "" ) ) );

        }

        // Do RatingValidationManagementService after the previous checks to get
        // nice JSON error messages
        // instead of url redirects. If a user manually uses
        // extend/modules/rating/doVote.jsp instead of
        // participatorybudget/doVote.jsp, he will get redirects but that's not a
        // problem for a manually crafted url
        String strErrorUrl = RatingValidationManagementService.validateRating( request, SecurityService.getInstance( ).getRegisteredUser( request ),
                strIdExtendableResource, strExtendableResourceType, BudgetRatingService.VOTE_VALUE );

        if ( StringUtils.isNotEmpty( strErrorUrl ) )
        {
            if ( !strErrorUrl.startsWith( CONSTANT_HTTP ) )
            {
                strErrorUrl = AppPathService.getBaseUrl( request ) + strErrorUrl;
            }

            return JsonUtil.buildJsonResponse( new ErrorJsonResponse( strErrorUrl ) );
        }
        // Users can only vote 1 in budget participatif implementation
        _ratingService.doVote( strIdExtendableResource, strExtendableResourceType, BudgetRatingService.VOTE_VALUE, request );

        // TODO : send notifications to mailing list
        // sendNotification( request, strIdExtendableResource,
        // strExtendableResourceType, nVoteValue );

        Map<String, Object> model = new HashMap<String, Object>( );

        model.put( RatingConstants.MARK_ID_EXTENDABLE_RESOURCE, strIdExtendableResource );
        model.put( RatingConstants.MARK_EXTENDABLE_RESOURCE_TYPE, strExtendableResourceType );

        HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_BUTTON_CANCEL_VOTE, request.getLocale( ), model );

        return JsonUtil.buildJsonResponse( new JsonResponse( template.getHtml( ) ) );
    }

    /**
     * Do cancel a vote
     * 
     * @param request
     *            The request
     * @return The JSON result
     */
    public String doCancelVote( HttpServletRequest request )
    {
        String strIdExtendableResource = request.getParameter( RatingConstants.PARAMETER_ID_EXTENDABLE_RESOURCE );
        String strExtendableResourceType = request.getParameter( RatingConstants.PARAMETER_EXTENDABLE_RESOURCE_TYPE );
        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

        String strUserId = user.getName( );
        if ( !CampaignService.getInstance( ).isDuring( "VOTE" ) || _myVoteService.isUserVoteValidated( strUserId ) )
        {

            try
            {
                SiteMessageService.setMessage( request, RatingConstants.MESSAGE_CANNOT_VOTE, SiteMessage.TYPE_STOP );
            }
            catch( SiteMessageException e )
            {
                return JsonUtil.buildJsonResponse( new ErrorJsonResponse( AppPathService.getSiteMessageUrl( request ) ) );
            }
        }

        try
        {
            if ( StringUtils.isBlank( strIdExtendableResource ) || StringUtils.isBlank( strExtendableResourceType ) )
            {
                SiteMessageService.setMessage( request, RatingConstants.MESSAGE_ERROR_GENERIC_MESSAGE, SiteMessage.TYPE_STOP );
            }

            // Check if the user can vote or not
            if ( !_ratingSecurityService.canDeleteVote( request, strIdExtendableResource, strExtendableResourceType )
                    && VoteHome.getVote( strUserId, Integer.parseInt( strIdExtendableResource ) ) == null )
            {
                SiteMessageService.setMessage( request, RatingConstants.MESSAGE_CANNOT_VOTE, SiteMessage.TYPE_STOP );
            }
        }
        catch( SiteMessageException e )
        {
            return JsonUtil.buildJsonResponse( new ErrorJsonResponse( AppPathService.getSiteMessageUrl( request ) ) );
        }

        _ratingService.doCancelVote( user, strIdExtendableResource, strExtendableResourceType );

        Map<String, Object> model = new HashMap<String, Object>( );

        model.put( RatingConstants.MARK_ID_EXTENDABLE_RESOURCE, strIdExtendableResource );
        model.put( RatingConstants.MARK_EXTENDABLE_RESOURCE_TYPE, strExtendableResourceType );

        HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_BUTTON_DO_VOTE, request.getLocale( ), model );

        return JsonUtil.buildJsonResponse( new JsonResponse( template.getHtml( ) ) );
    }

    /**
     * 
     * @param request
     * @return
     * @throws UserNotSignedException
     */
    public String doCancelVoteUser( HttpServletRequest request ) throws UserNotSignedException
    {
        return _myVoteService.cancelVote( request );
    }

    /**
     * Retrieve JSON for vote recap before confirmation
     * 
     * @param request
     * @return
     * @throws UserNotSignedException
     */
    public String valideVote( HttpServletRequest request ) throws UserNotSignedException
    {

        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

        int nbrVoteArrdt = VoteHome.getVoteUserNotLocation( user.getName( ), 75000 );
        int nbrVoteParis = VoteHome.getVoteUserArrondissement( user.getName( ), 75000 );
        int nbrVoteTotal = nbrVoteArrdt + nbrVoteParis;

        MyVote myVote = new MyVote( );
        myVote.setNbTotVotes( nbrVoteTotal );
        myVote.setNbTotVotesArrondissement( nbrVoteArrdt );
        myVote.setNbTotVotesToutParis( nbrVoteParis );
        myVote.setVoteVlidated( false );

        AbstractJsonResponse jsonResponse = new JsonResponse( myVote );

        return JsonUtil.buildJsonResponse( jsonResponse );

    }

    /**
     * Validate votes for the user
     * 
     * @param request
     * @return
     * @throws UserNotSignedException
     * @throws SiteMessageException
     */
    @Action( value = ACTION_VALIDATE_VOTES )
    public XPage doValideVote( HttpServletRequest request ) throws UserNotSignedException, SiteMessageException
    {

        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );
        boolean bValidateVote = BooleanUtils.toBoolean( request.getParameter( PARAM_VALIDATE ) );
        boolean bVotesInFollow = BooleanUtils.toBoolean( request.getParameter( PARAM_FOLLOW ) );

        if ( bValidateVote )
        {

            _myVoteService.validateVotes( user.getName( ), Vote.Status.STATUS_VALIDATED.getValeur( ) );
            addInfo( MESSAGE_INFO_USER_VOTE_VALIDE, request.getLocale( ) );

            if ( bVotesInFollow )
            {
                // Creating extender entry
                ResourceExtenderHistoryFilter filter = new ResourceExtenderHistoryFilter( );
                filter.setExtendableResourceType( RatingAddOnService.PROPERTY_RESOURCE_TYPE );
                filter.setUserGuid( user.getName( ) );
                filter.setExtenderType( FollowResourceExtender.RESOURCE_EXTENDER );
                List<ResourceExtenderHistory> listResourceExtender = _resourceExtenderService.findByFilter( filter );
                for ( Vote vote : VoteHome.getVoteUser( user.getName( ) ) )
                {
                    String strProjetId = String.valueOf( vote.getProjetId( ) );
                    boolean bAlreadyFollow = false;
                    for ( ResourceExtenderHistory resourceExtenderHistory : listResourceExtender )
                    {
                        if ( StringUtils.equals( resourceExtenderHistory.getIdExtendableResource( ), strProjetId ) )
                        {
                            bAlreadyFollow = true;
                            break;
                        }
                    }
                    if ( !bAlreadyFollow )
                    {
                        _followService.doFollow( String.valueOf( vote.getProjetId( ) ), RatingAddOnService.PROPERTY_RESOURCE_TYPE, 1, request );
                    }
                }

                // Activating notification
                String strLuteceUserName = user.getName( );
                if ( !hasUserSubscribedToResource( strLuteceUserName, SUBSCRIPTION_UPDATE_ON_REALIZATION ) )
                {
                    Subscription subscription = new Subscription( );
                    subscription.setIdSubscribedResource( "*" );
                    subscription.setUserId( strLuteceUserName );
                    subscription.setSubscriptionKey( SUBSCRIPTION_UPDATE_ON_REALIZATION );
                    subscription.setSubscriptionProvider( "participatoryideation.subscriptionProviderName" );
                    SubscriptionService.getInstance( ).createSubscription( subscription );
                }
            }

            // Send email
            sendConfirmationVoteEmail( user );
        }

        return getMyVotes( request );

    }

    private boolean hasUserSubscribedToResource( String strLuteceUserName, String strSubscriptionKey )
    {
        SubscriptionFilter filter = new SubscriptionFilter( strLuteceUserName, "participatoryideation.subscriptionProviderName", strSubscriptionKey, "*" );
        List<Subscription> listSubscription = SubscriptionService.getInstance( ).findByFilter( filter );
        if ( ( listSubscription != null ) && ( listSubscription.size( ) > 0 ) )
        {
            return true;
        }
        return false;
    }

    /**
     * Send email to the user to confirm the validation of the vote
     * 
     * @param user
     *            The user from who to send confirmation email
     */
    private void sendConfirmationVoteEmail( LuteceUser user )
    {
        if ( user != null )
        {
            // Retrieve the list of the validated vote of the user
            List<Vote> listVote = VoteHome.getVoteUser( user.getName( ), Vote.Status.STATUS_VALIDATED.getValeur( ) );

            if ( listVote != null && !listVote.isEmpty( ) )
            {
                // // Create the list of the projects title which will be display in the email
                // StringBuilder strListProjectTitleBuilder = new StringBuilder( StringUtils.EMPTY );
                // Iterator<Vote> voteIterator = listVote.iterator( );
                // while ( voteIterator.hasNext( ) )
                // {
                // strListProjectTitleBuilder.append( voteIterator.next( ).getTitle( ) );
                // if( voteIterator.hasNext( ) )
                // {
                // strListProjectTitleBuilder.append( VOTE_TITLE_SEPARATOR );
                // }
                // }

                // Retrieve the confirmation vote email information
                String strEmailSender = DatastoreService.getDataValue( KEY_VALIDATION_VOTE_EMAIL_SENDER, StringUtils.EMPTY );
                String strEmailCc = DatastoreService.getDataValue( KEY_VALIDATION_VOTE_EMAIL_CC, StringUtils.EMPTY );
                String strEmailSubject = DatastoreService.getDataValue( KEY_VALIDATION_VOTE_EMAIL_SUBJECT, StringUtils.EMPTY );
                String strEmailTemplate = DatastoreService.getDataValue( KEY_VALIDATION_VOTE_EMAIL_TEMPLATE, StringUtils.EMPTY );

                // Compute the current date and retrieve the MyInfosForm of the user to get its nickname
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat( DATE_EMAIL_VALIDATION_PATTERN );
                String strFormattedDate = simpleDateFormat.format( new Date( ) );
                MyInfosForm userInfoForm = MyInfosService.loadUserInfos( user );

                // Create the map for the freemarker marker for email
                Map<String, Object> model = new HashMap<>( );
                model.put( MARK_DATE_VALIDATION, strFormattedDate );
                model.put( MARK_USER_PSEUDO, ( userInfoForm != null ) ? userInfoForm.getNickname( ) : null );
                model.put( MARK_LIST_VALIDATE_PROJECTS, listVote );

                // Replace the Freemarker marks with the models data
                HtmlTemplate htmlTemplateMail = AppTemplateService.getTemplateFromStringFtl( strEmailTemplate, Locale.FRENCH, model );

                // Send the mail to the user
                MailService.sendMailHtml( user.getEmail( ), strEmailCc, null, strEmailSender, strEmailSender, strEmailSubject, htmlTemplateMail.getHtml( ) );
            }
        }
    }

    /**
     * DoSend Avis using the AJAX mode
     * 
     * @param request
     *            The request
     */
    /*
     * public String doSendAvis(HttpServletRequest request) { String strMail = request.getParameter(PARAMETER_MAIL); String strMessage =
     * request.getParameter(PARAMETER_MESSAGE); String strBackUrl = DatastoreService.getDataValue( KEY_PARIS_CONNECT_SEND_AVIS_BACK_URL,
     * "https://budgetparticipatif.paris.fr/bp/"); AbstractJsonResponse jsonResponse = null;
     * 
     * // if(SecurityTokenService.getInstance().validate(request, // TOKEN_DO_SEND_AVIS) ) // // { // // AppLogService.error(
     * "doSubscribeAlert: Token not validated" ); // // jsonResponse=new // BudgetErrorJsonResponse(JSON_ERROR_DURING_SEND_AVIS,SecurityTokenService.
     * getInstance().getToken(request, // TOKEN_DO_SEND_AVIS));
     * 
     * // }else // //
     * 
     * if (StringUtils.isNotEmpty(strMessage) && ParisConnectService.getInstance().sendAvisMessage(strMail, strMessage, strBackUrl)) { jsonResponse = new
     * JsonResponse(new CampaignResponse(Boolean.TRUE, SecurityTokenService.getInstance().getToken(request, TOKEN_DO_SEND_AVIS)));
     * 
     * } else { jsonResponse = new CampaignErrorJsonResponse( JSON_ERROR_DURING_SEND_AVIS, SecurityTokenService .getInstance() .getToken(request,
     * TOKEN_DO_SEND_AVIS)); }
     * 
     * return JsonUtil.buildJsonResponse(jsonResponse); }
     */

    /**
     * 
     * @return true if the captcha is enable
     */
    private boolean isEnableCaptcha( )
    {
        return PluginService.isPluginEnable( JCAPTCHA_PLUGIN ) && new Boolean( DatastoreService.getDataValue( KEY_ENABLE_CAPTCHA_USER_INFORMATIONS, "false" ) );
    }

    /**
     * 
     * @param request
     * @param user
     * @return
     */
    private static String isArrondissementValide( HttpServletRequest request, LuteceUser user )
    {

        AbstractJsonResponse jsonResponse = null;
        String arrondissement = request.getParameter( PARAMETER_ARRONDISSEMENT );
        String codePostal = BudgetUtils.getArrondissementDisplay( user ).trim( );

        if ( !arrondissement.equals( codePostal ) && !arrondissement.equals( ParticipatoryBudgetConstants.LOCATION_WHOLE_CITY ) )
        {

            jsonResponse = new ErrorJsonResponse( JSON_ERROR_CHECKED_ARRONDISSEMENT, DatastoreService.getDataValue( KEY_ERROR_CHECKED_ARRONDISSEMENT, "" ) );
            return JsonUtil.buildJsonResponse( jsonResponse );

        }
        else
        {

            return null;
        }
    }

    /**
     * Get the URL to view My Vote of a user
     * 
     * @param request
     *            The request
     * @return The URL
     */
    public static String getUrlViewUserVote( HttpServletRequest request )
    {
        UrlItem urlItem = new UrlItem( AppPathService.getBaseUrl( request ) + AppPathService.getPortalUrl( ) );
        urlItem.addParameter( "page", "mesVotes" );
        urlItem.addParameter( "view", VIEW_MY_VOTES );

        return urlItem.getUrl( );
    }

}