View Issue Details

This bug affects 1 person(s).
 10
IDProjectCategoryView StatusLast Update
09629Feature requestsSurvey participants (Tokens)public2021-11-26 14:52
ReporterMazi Assigned Toc_schmitz  
PrioritynormalSeverityfeature 
Status assignedResolutionopen 
Summary09629: Global opt out link for CPDB missing
Description

The Limesurvey manual (link: https://manual.limesurvey.org/Central_participants_database_(User_panel)_Development#Blacklist_Control) list two opt out links:
"There will be two URL mentioned in the mail. One will
allow the participant to blacklist himself globally from any survey from that
server; the other, from a particular survey."

Currently the global opt out link does NOT exist. Instead, the following link structure should work to achieve the same:
/index.php/optout/participants?surveyid=391637&langcode=fr&token=TEST

We should create a new placeholder like {GLOBALOUTURL} which is available via the editor and document this change at the manual (I can do the later)

See related forum topic at https://www.limesurvey.org/en/forum/can-i-do-this-with-limesurvey/100283-opt-out-url-for-central-participant-database#120179

TagsNo tags attached.
Bug heat10
Story point estimate
Users affected %

Relationships

related to 08273 closedgalads Sending invitation don't test particpant blacklist 

Users monitoring this issue

There are no users monitoring this issue.

Activities

DenisChenu

DenisChenu

2015-05-12 10:22

developer   ~32174

Not for me actually , because need more system.

1st : add the link (REPLACEMENT) but only if user are a participant
2nd : Do the global system test before each mail sending (can be done in a core plugin , deactiuvated or activated by default)

Denis

sammousa

sammousa

2015-05-12 11:47

reporter   ~32178

Last edited: 2021-04-28 10:18

I propose adding this to the core instead of CPDB.

Also I'd like to stick to 1 optout URL.
Then we can set the user to optout for the survey and show him / her a page where he / she can optout from all future surveys.

Global opt-out would be stored in a new table and when emails are being sent this table is checked (to prevent the system from sending mails to that email).
Also we could / should add usability features like:

  • Message during token import / creation.

Mazi prefers 2 urls to prevent accidental global optout.

DenisChenu

DenisChenu

2015-05-12 11:57

developer   ~32179

Last edited: 2021-04-28 10:18

sammousa : a OptoutController with checkbox:

  • No new mail for this survey : checked by default
  • No mail from this server/LS instance : unchecked by default

The second create a Global "blacklist"

But : for 3.0, rigth ?

PS: mimic system for "mailing list".

Mazi

Mazi

2021-04-28 12:04

updater   ~64171

@c_schmitz, we once implemented something like this for a customer by changing core files of an older LS 3.x system. We can't provide a pull request but could attach the edited files here if that helps.

c_schmitz

c_schmitz

2021-04-28 12:14

administrator   ~64172

@Mazi Sure, anything helps.
Did you just implement another placeholder or the variant with one URL and dialogue ?

Mazi

Mazi

2021-04-29 09:48

updater   ~64183

@c_schmitz, we used a link like this at our emails: /index.php/optout/participants?surveyid=391637&langcode=fr&token=TEST

The action optout/participants already seemed to exist at the old code. I have attached the files we edited for LS 3.15:

tokens.php: Search for edits marked "EDIT_BLACKLIST. I think we are mainly added a check for blacklisted emails for CSV import.

optoutcontroller.php: Various edits for removing globally opted out emails from all surveys etc.

tokens.php (144,186 bytes)
OptoutController.php (13,130 bytes)   
<?php if (!defined('BASEPATH')) {
    exit('No direct script access allowed');
}
/*
 * LimeSurvey
 * Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
 * All rights reserved.
 * License: GNU/GPL License v2 or later, see LICENSE.php
 * LimeSurvey is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 * See COPYRIGHT.php for copyright notices and details.
 *
 */

/**
 * optout
 *
 * @package LimeSurvey
 * @copyright 2011
 * @access public
 */
class OptoutController extends LSYii_Controller
{

        public $layout = 'bare';
        public $defaultAction = 'tokens';


        function actiontokens()
        {


            $iSurveyID     = Yii::app()->request->getQuery('surveyid');
            $sLanguageCode = Yii::app()->request->getQuery('langcode');
            $sToken        = Token::sanitizeToken(Yii::app()->request->getQuery('token'));

            Yii::app()->loadHelper('database');
            Yii::app()->loadHelper('sanitize');

            //IF there is no survey id, redirect back to the default public page
            if (!$iSurveyID) {
                $this->redirect(array('/'));
            }

            $iSurveyID = (int) $iSurveyID; //Make sure it's an integer (protect from SQL injects)
            //Check that there is a SID
            // Get passed language from form, so that we dont lose this!
            if (!isset($sLanguageCode) || $sLanguageCode == "" || !$sLanguageCode) {
                $sBaseLanguage = Survey::model()->findByPk($iSurveyID)->language;
            } else {
                $sBaseLanguage = sanitize_languagecode($sLanguageCode);
            }

            Yii::app()->setLanguage($sBaseLanguage);

            $aSurveyInfo = getSurveyInfo($iSurveyID, $sBaseLanguage);

            if ($aSurveyInfo == false || !tableExists("{{tokens_{$iSurveyID}}}")) {
                throw new CHttpException(404, "The survey in which you are trying to participate does not seem to exist. It may have been deleted or the link you were given is outdated or incorrect.");
            } else {
                $sMessage = "<p>".gT('Please confirm that you want to opt out of this survey by clicking the button below.').'<br>'.gT("After confirmation you won't receive any invitations or reminders for this survey anymore.")."</p>";
                $sMessage .= '<p><a href="'.Yii::app()->createUrl('optout/removetokens', array('surveyid'=>$iSurveyID, 'langcode'=> $sBaseLanguage, 'token' => $sToken)).'" class="btn btn-default btn-lg">'.gT("I confirm").'</a><p>';
                $this->renderHtml($sMessage, $aSurveyInfo, $iSurveyID);
            }


        }

    /**
     * This function is run when opting out of an individual survey participants table. The other function /optout/participants
     * opts the user out of ALL survey invitations from the system
     */
    function actionremovetokens()
    {
        $iSurveyID = Yii::app()->request->getQuery('surveyid');
        $sLanguageCode = Yii::app()->request->getQuery('langcode');
        $sToken = Token::sanitizeToken(Yii::app()->request->getQuery('token'));
        Yii::app()->loadHelper('database');
        Yii::app()->loadHelper('sanitize');

        if (!$iSurveyID) {
//IF there is no survey id, redirect back to the default public page
            $this->redirect(array('/'));
        }
        $iSurveyID = (int) $iSurveyID; //Make sure it's an integer (protect from SQL injects)
        //Check that there is a SID
        // Get passed language from form, so that we dont lose this!
        if (!isset($sLanguageCode) || $sLanguageCode == "" || !$sLanguageCode) {
            $sBaseLanguage = Survey::model()->findByPk($iSurveyID)->language;
        } else {
            $sBaseLanguage = sanitize_languagecode($sLanguageCode);
        }

        Yii::app()->setLanguage($sBaseLanguage);

        $aSurveyInfo = getSurveyInfo($iSurveyID, $sBaseLanguage);

        if ($aSurveyInfo == false || !tableExists("{{tokens_{$iSurveyID}}}")) {
            throw new CHttpException(404, "The survey in which you are trying to participate does not seem to exist. It may have been deleted or the link you were given is outdated or incorrect.");
        } else {
            LimeExpressionManager::singleton()->loadTokenInformation($iSurveyID, $sToken, false);
            $oToken = Token::model($iSurveyID)->findByAttributes(array('token'=>$sToken));

            if (!isset($oToken)) {
                $sMessage = gT('You are not a participant in this survey.');
                //throw new CHttpException(404, "You are not a participant in this survey.");
            } else {
                if (substr($oToken->emailstatus, 0, strlen('OptOut')) !== 'OptOut') {
                    $oToken->emailstatus = 'OptOut';
                    $oToken->save();
                    $sMessage = gT('You have been successfully removed from this survey.');
                } else {
                    $sMessage = gT('You have already been removed from this survey.');
                }
            }
        }

        $this->renderHtml($sMessage, $aSurveyInfo, $iSurveyID);
    }

    /**
     * This function is run when opting out of the participants system. The other function /optout/token
     * opts the user out of just a single token/survey invite list
     */
    function actionparticipants()
    {
        $iSurveyID = Yii::app()->request->getQuery('surveyid');
        $sLanguageCode = Yii::app()->request->getQuery('langcode');
        $sToken = Token::sanitizeToken(Yii::app()->request->getQuery('token'));
        Yii::app()->loadHelper('database');
        Yii::app()->loadHelper('sanitize');
        if (!$iSurveyID) {
		//IF there is no survey id, redirect back to the default public page
            $this->redirect(array('/'));
        }
        $iSurveyID = (int) $iSurveyID; //Make sure it's an integer (protect from SQL injects)
        //Check that there is a SID
        // Get passed language from form, so that we dont lose this!
        if (!isset($sLanguageCode) || $sLanguageCode == "" || !$sLanguageCode) {
            $sBaseLanguage = Survey::model()->findByPk($iSurveyID)->language;
        } else {
            $sBaseLanguage = sanitize_languagecode($sLanguageCode);
        }
        Yii::app()->setLanguage($sBaseLanguage);

        $aSurveyInfo = getSurveyInfo($iSurveyID, $sBaseLanguage);

        if ($aSurveyInfo == false || !tableExists("{{tokens_{$iSurveyID}}}")) {
            throw new CHttpException(404, "The survey in which you are trying to participate does not seem to exist. It may have been deleted or the link you were given is outdated or incorrect.");
        } else {
            LimeExpressionManager::singleton()->loadTokenInformation($iSurveyID, $sToken, false);
            $oToken = Token::model($iSurveyID)->findByAttributes(array('token' => $sToken));
			
			//START MM NEW 09/2018
			$useremail = $oToken->email;
			//END MM NEW 09/2018
			
            if (!isset($oToken)) {
                $sMessage = gT('You are not a participant in this survey.');	//MM CHECKED
            } else {
                if (substr($oToken->emailstatus, 0, strlen('OptOut')) !== 'OptOut') {
                    $oToken->emailstatus = 'OptOut';
                    $oToken->save();
                    $sMessage = gT('You have been successfully removed from this survey.');
                } else {
                    $sMessage = gT('You have been already removed from this survey.');
                }
                //MM OLD: if(!empty($oToken->participant_id))
				if(!empty($oToken->email))	//MM NEW
                {
                    //Participant also exists in central db
                    //MM OLD: $oParticipant = Participant::model()->findByPk($oToken->participant_id);
					$oParticipant = Participant::model()->findAll("email = '".$oToken->email."'");	//MM NEW
					
					//Check if first (there can be several!) user exists at CPDB and has status blacklisted
					//MM ORIG: if($oParticipant->blacklisted=="Y")
					if($oParticipant[0]->blacklisted=="Y")	//MM NEW
                    {

                        $sMessage .= "<br />";
                        $sMessage .= gT("You have already been removed from the central participants list for this site");
                    } 
					//user not blacklisted -> add new user to CPDB with blacklist = Y
					else
                    {
						/* MM OLD

                        $oParticipant->blacklisted='Y';
                        $oParticipant->save();
						*/
						
						/* MM START: Add not existing user to participant DB */
						$aData = array();
						$uuid = Participant::gen_uuid();
						$aData['participant_id'] = $uuid;
						$aData['firstname'] = $oToken->firstname;
						$aData['lastname'] = $oToken->lastname;
						$aData['email'] = $oToken->email;
						$aData['blacklisted'] = "Y";
						$aData['owner_uid'] = 1;
						$aData['created_by'] = 1;
						
						$result = Participant::model()->insertParticipant($aData);
						
						//check if INSERT statement worked well
						if (is_object($result)) 
						{
							$sMessage .= "<br />";
							$sMessage .= "Sie sind aus dem Umfragetool erfolgreich ausgetragen worden und erhalten keine Umfragen mehr.";
						}
						/* MM END 						
                        $sMessage .= "<br />";
                        $sMessage .= gT("You have been removed from the central participants list for this site");
						*/
					}

                }
				
				//MM NEW 02/2018: Remove users from ALL surveys!
				$oSurvey = new Survey;   

				//fake a superadmin user
				$sUid = 1;       

				//get permissions to access all surveys
				$oSurvey->permission($sUid);

				//if new messages have to be added...
				$sMessage .= "<br />";
				
				//get all surveys
				$aUserSurveys = $oSurvey->with(array('languagesettings'=>array('condition'=>'surveyls_language=language'), 'owner'))->findAll();

				//error if no surveys exist
				if (count($aUserSurveys) == 0) 
				{
								return array('status' => 'No surveys found');
				}

				//check all survey details
				foreach ($aUserSurveys as $oSurvey) 
				{
					$oSurveyLanguageSettings = SurveyLanguageSetting::model()->findByAttributes(array('surveyls_survey_id' => $oSurvey->primaryKey, 'surveyls_language' => $oSurvey->language));
					if (!isset($oSurveyLanguageSettings)) 
					{
										$aSurveyTitle = '';
					} else 
					{
										$aSurveyTitle = $oSurveyLanguageSettings->attributes['surveyls_title'];
					}
					
					//store all survey data here
					$aData[] = array('sid'=>$oSurvey->primaryKey, 'surveyls_title'=>$aSurveyTitle, 'startdate'=>$oSurvey->attributes['startdate'], 'expires'=>$oSurvey->attributes['expires'], 'active'=>$oSurvey->attributes['active']);
						
				}
				//print_r($aData);
				//die();
				//loop through all the surveys we have loaded previously
				foreach($aData as $surveydata)
				{
					if(!isset($surveydata['sid']))
					{
						break;
					
						//this is the current survey ID
						$iSurveyID = $surveydata['sid'];
						
						//check if survey comes with a token table
						if(tableExists("{{tokens_{$iSurveyID}}}"))
						{
							//check if there is a token entry with the given email address.
							$oToken = Token::model($iSurveyID)->findByAttributes(array('email' => $useremail));
							
							//no token, we do not care
							if (!isset($oToken))
							{
								//DO NOTHING
								//$sMessage = gT('You are not a participant in this survey (#'.$iSurveyID.').');
							}
							//token found at other survey -> check details
							else
							{
								//set optout detail
								if (substr($oToken->emailstatus, 0, strlen('OptOut')) !== 'OptOut')
								{
									$oToken->emailstatus = 'OptOut';
									$oToken->save();
									
									$sMessage .= "<br />";
									$sMessage .= gT('Sie sind aus dieser Umfrage ebenfalls ausgetragen worden:');
									$sMessage .= "<br />[$iSurveyID]";
								}
							}
						
						}	//survey uses tokens
					}
				
				}	//loop surveys
				
			//MM END 02/2018: Remove users from ALL surveys!
		
		
			//these belong to above function
            }
        }

        $this->renderHtml($sMessage, $aSurveyInfo, $iSurveyID);
    }

    /**
     * Render something
     *
     * @param string $html
     * @param array $aSurveyInfo
     * @param int $iSurveyID
     * @return void
     */
    private function renderHtml($html, $aSurveyInfo, $iSurveyID)
    {
        $survey = Survey::model()->findByPk($iSurveyID);

        $aSurveyInfo['include_content'] = 'optout';
        $aSurveyInfo['optin_message'] = $html;
        Template::model()->getInstance('', $iSurveyID);

        Yii::app()->twigRenderer->renderTemplateFromFile(
            "layout_global.twig",
            array(
                'oSurvey'     => $survey,
                'aSurveyInfo' => $aSurveyInfo
            ),
            false
        );
        Yii::app()->end();
    }
}
OptoutController.php (13,130 bytes)   
gabrieljenik

gabrieljenik

2021-10-27 15:14

manager   ~66967

PR Master: https://github.com/LimeSurvey/LimeSurvey/pull/2125

This is a starter. More places where to filter could be added.

DenisChenu

DenisChenu

2021-11-26 14:47

developer   ~67586

Last edited: 2021-11-26 14:52

  1. Must remove this link from default email, since this link work only for participant
  2. Mire clear sentence for admin user , see sentence return to particpant : : «You have been removed from the central participants list for this site » , it's not a email blacklist system.

Then : adding it for replacement field, replacement url is OK, but NOT in default message (currently) until we have a real email black list system.

Issue History

Date Modified Username Field Change
2015-05-12 09:57 Mazi New Issue
2015-05-12 09:57 Mazi Status new => assigned
2015-05-12 09:57 Mazi Assigned To => DenisChenu
2015-05-12 10:20 DenisChenu Assigned To DenisChenu =>
2015-05-12 10:22 DenisChenu Note Added: 32174
2015-05-12 10:22 DenisChenu Status assigned => new
2015-05-12 11:46 Mazi Assigned To => sammousa
2015-05-12 11:46 Mazi Status new => assigned
2015-05-12 11:47 sammousa Note Added: 32178
2015-05-12 11:57 DenisChenu Note Added: 32179
2015-05-12 11:59 DenisChenu Note Edited: 32179
2016-12-08 10:39 c_schmitz Category Tokens => Survey participants (Tokens)
2017-09-18 18:41 DenisChenu Assigned To sammousa =>
2017-09-18 18:41 DenisChenu Status assigned => new
2017-10-06 11:54 c_schmitz Assigned To => c_schmitz
2017-10-06 11:54 c_schmitz Status new => assigned
2020-08-03 20:42 cdorin Assigned To c_schmitz => cdorin
2021-04-28 10:18 c_schmitz Assigned To cdorin => c_schmitz
2021-04-28 10:18 c_schmitz Sync to Zoho Project => |Yes|
2021-04-28 10:18 LimeBot Zoho Projects ID => 85781000000802031
2021-04-28 10:18 c_schmitz Target Version => 4.x.x
2021-04-28 10:18 c_schmitz Sync to Zoho Project Yes => |Yes|
2021-04-28 12:04 Mazi Note Added: 64171
2021-04-28 12:14 c_schmitz Note Added: 64172
2021-04-29 09:48 Mazi Note Added: 64183
2021-04-29 09:48 Mazi File Added: tokens.php
2021-04-29 09:48 Mazi File Added: OptoutController.php
2021-10-27 15:14 gabrieljenik Note Added: 66967
2021-10-27 15:14 gabrieljenik Bug heat 8 => 10
2021-11-04 08:48 DenisChenu Relationship added related to 08273
2021-11-26 14:47 DenisChenu Note Added: 67586
2021-11-26 14:52 DenisChenu Note Edited: 67586