reloadAnyResponse.php 36.3 KB
Newer Older
1 2 3 4 5 6 7
<?php
/**
 * Plugin helper for limesurvey : new class and function allowing to reload any survey
 *
 * @author Denis Chenu <denis@sondages.pro>
 * @copyright 2018 Denis Chenu <http://www.sondages.pro>
 * @license AGPL v3
8
 * @version 0.9.3
9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
class reloadAnyResponse extends PluginBase {

22 23
  protected $storage = 'DbStorage';

24 25 26
  static protected $description = 'New class and function allowing to reload any survey.';
  static protected $name = 'reloadAnyResponse';

27
  static protected $dbVersion = 2;
28

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
  /**
   * @var array[] the settings
   */
  protected $settings = array(
    'information' => array(
        'type' => 'info',
        'content' => 'The default settings for all surveys. Remind no system is set in this plugin to show or send link to user.',
    ),
    'allowAdminUser' => array(
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
        'label'=>"Allow admin user to reload any survey with response id.",
        'default'=>1,
    ),
46
    'allowTokenUser' => array(
47 48 49 50 51 52 53 54
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
        'label'=>"Allow user with a valid token.",
        'default'=>1,
    ),
55 56 57 58 59 60
    'uniqueCodeCreate' => array(
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
Chenu Denis's avatar
Chenu Denis committed
61 62
        'label'=>"Create automatically unique code for all surveys.",
        'help'=>"If code exist, it can be always used.",
63 64
        'default'=>0,
    ),
Chenu Denis's avatar
Chenu Denis committed
65 66 67 68 69 70 71 72 73 74
    'uniqueCodeAccess' => array(
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
        'label'=>"Allow entering unique code for all surveys if exist.",
        'help'=>"If you set to no, this disable usage for other plugins.",
        'default'=>1,
    ),
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
    'deleteLinkWhenResponseDeleted' => array(
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
        'label'=>"Delete the link of response when a response is deleted.",
        'help'=>"This delete the response link when response is deleted. If you use VV import, don't activate this option.",
        'default'=>0,
    ),
    'deleteLinkWhenSurveyDeactivated' => array(
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
        'label'=>"Delete the link of all responses of a survey when it's deactivated.",
        'help'=>"Since response table keep the current auto increment value, leave this option can not broke response security.",
        'default'=>0,
    ),
    'deleteLinkWhenSurveyDeleted' => array(
        'type'=>'checkbox',
        'htmlOptions'=>array(
            'value'=>1,
            'uncheckValue'=>0,
        ),
        'label'=>"Delete the link of all responses of a survey when it's deleted.",
        'help'=>"To avoid a big table.",
        'default'=>1,
    ),
105 106 107 108 109 110 111
    'disableMultiAccess' => array(
        'type'=>'info',
        'content'=>"<div class='alert alert-info'>You need renderMessage for disabling multiple access.</div>",
        'default'=>1,
    ),
    'multiAccessTime'=>array(
        'type'=>'int',
112
        'label' => 'Time for disable multiple access (in minutes) (config.php settings replace it of not exist)',
113 114 115
        'help' => 'Before save value or entering survey : test if someone else edit response in this last minutes. Disable save and show a message if yes. Set to empty disable system but then answer of user can be deleted by another user without any information…',
        'htmlOptions'=>array(
            'min'=>1,
116
            'placeholder'=>'Disable',
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
        ),
        'default' => 20,
    ),
    //~ 'multiAccessTimeOptOut'=>array(
        //~ 'type'=>'int',
        //~ 'label' => 'Auto save and close current responses (with a javascript solution) in minutes.',
        //~ 'help' => 'If user didn‘t do any action on his browser during access time, save and close the windows. Set to an empty disable this feature.',
        //~ 'htmlOptions'=>array(
            //~ 'min'=>1,
        //~ ),
        //~ 'default' => 20,
    //~ ),
    //~ 'multiAccessTimeAlert'=>array(
        //~ 'type'=>'int',
        //~ 'label' => 'Time for alert shown for optout of survey',
        //~ 'help' => 'Set to empty to disable. This alert is shown after X minutes, where X is the number here.',
        //~ 'htmlOptions'=>array(
            //~ 'min'=>1,
        //~ ),
        //~ 'default' => 18,
    //~ ),
138 139 140 141 142
    //~ 'uniqueCodeCode' => array(
        //~ 'type'=>'string',
        //~ 'label'=>"Code in GET params to test.",
        //~ 'default'=>'code',
    //~ ),
143 144 145
  );

  /** @inheritdoc **/
146 147 148 149 150
  public function init()
  {
    $this->subscribe('beforeActivate');
    $oPlugin = Plugin::model()->find("name = :name",array("name"=>get_class($this)));
    if($oPlugin && $oPlugin->active) {
Chenu Denis's avatar
Chenu Denis committed
151
      $this->_setConfig();
152
    }
153
    /* Managing unique code for Response and SurveyDynamic */
154 155
    $this->subscribe('afterModelSave');
    $this->subscribe('afterModelDelete');
156 157 158 159 160
    /* Delete related link for Survey */
    $this->subscribe('afterSurveySave');
    $this->subscribe('afterSurveyDelete');
    $this->subscribe('beforeSurveyDeleteMany');

161
    /* Get the survey by srid and code */
162
    /* Save current session */
163
    $this->subscribe('beforeSurveyPage');
164 165
    /* Replace existing system if srid = new */
    $this->subscribe('beforeLoadResponse');
166 167 168
    /* Survey settings */
    $this->subscribe('beforeSurveySettings');
    $this->subscribe('newSurveySettings');
169 170 171
    /* delete current session*/
    $this->subscribe("afterSurveyComplete",'deleteSurveySession');
    $this->subscribe("afterSurveyQuota",'deleteSurveySession');
172 173
    /* delete current session when unload */
    $this->subscribe("newDirectRequest",'newDirectRequest');
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
  }

  /** @inheritdoc **/
  public function getPluginSettings($getValues=true)
  {
    /* @todo translation of label and help */
    return parent::getPluginSettings($getValues);
  }

  /** @inheritdoc **/
  public function beforeSurveySettings()
  {
    $oEvent = $this->event;
    /* currentDefault translation */
    $allowAdminUserDefault = $this->get('allowAdminUser',null,null,$this->settings['allowAdminUser']['default']) ? gT('Yes') : gT('No');
189
    $allowTokenDefault = $this->get('allowTokenUser',null,null,$this->settings['allowTokenUser']['default']) ? gT('Yes') : gT('No');
190
    $uniqueCodeCreateDefault = $this->get('uniqueCodeCreate',null,null,$this->settings['uniqueCodeCreate']['default']) ? gT('Yes') : gT('No');
Chenu Denis's avatar
Chenu Denis committed
191
    $uniqueCodeAccessDefault = $this->get('uniqueCodeAccess',null,null,$this->settings['uniqueCodeAccess']['default']) ? gT('Yes') : gT('No');
192
    $multiAccessTimeDefault = $this->get('multiAccessTime',null,null,$this->settings['multiAccessTime']['default']) ? $this->get('multiAccessTime',null,null,$this->settings['multiAccessTime']['default']) : gT('Disable');
193 194 195 196 197 198

    $oEvent->set("surveysettings.{$this->id}", array(
      'name' => get_class($this),
      'settings' => array(
        'allowAdminUser'=>array(
          'type'=>'select',
Chenu Denis's avatar
Chenu Denis committed
199
          'label'=>$this->_translate("Allow admin user to reload any response with response id."),
200 201 202 203 204
          'options'=>array(
            1 =>gT("Yes"),
            0 =>gT("No"),
          ),
          'htmlOptions'=>array(
205
            'empty' => CHtml::encode(sprintf($this->_translate("Use default (%s)"),$allowAdminUserDefault)),
206 207 208
          ),
          'current'=>$this->get('allowAdminUser','Survey',$oEvent->get('survey'),"")
        ),
209 210
        'allowTokenUser'=>array(
          'type' => 'select',
Chenu Denis's avatar
Chenu Denis committed
211
          'label' => $this->_translate("Allow participant with token to create or reload responses."),
212 213 214 215 216 217 218 219 220 221
          'help' => $this->_translate("Related to “Enable token-based response persistence” and “Allow multiple responses or update responses” survey settings."),
          'options'=>array(
            1 =>gT("Yes"),
            0 =>gT("No"),
          ),
          'htmlOptions'=>array(
            'empty' => CHtml::encode(sprintf($this->_translate("Use default (%s)"),$allowTokenDefault)),
          ),
          'current'=>$this->get('allowTokenUser','Survey',$oEvent->get('survey'),"")
        ),
222 223
        'uniqueCodeCreate'=>array(
          'type'=>'select',
Chenu Denis's avatar
Chenu Denis committed
224
          'label'=>$this->_translate("Create unique code automatically."),
225 226 227 228 229
          'options'=>array(
            1 =>gT("Yes"),
            0 =>gT("No"),
          ),
          'htmlOptions'=>array(
230
            'empty' => CHtml::encode(sprintf($this->_translate("Use default (%s)"),$uniqueCodeCreateDefault)),
231 232 233
          ),
          'current'=>$this->get('uniqueCodeCreate','Survey',$oEvent->get('survey'),"")
        ),
Chenu Denis's avatar
Chenu Denis committed
234 235
        'uniqueCodeAccess'=>array(
          'type'=>'select',
Chenu Denis's avatar
Chenu Denis committed
236
          'label'=>$this->_translate("Allow using unique code if exist."),
Chenu Denis's avatar
Chenu Denis committed
237 238 239 240 241
          'options'=>array(
            1 =>gT("Yes"),
            0 =>gT("No"),
          ),
          'htmlOptions'=>array(
242
            'empty' => CHtml::encode(sprintf($this->_translate("Use default (%s)"),$uniqueCodeAccessDefault)),
Chenu Denis's avatar
Chenu Denis committed
243 244 245
          ),
          'current'=>$this->get('uniqueCodeAccess','Survey',$oEvent->get('survey'),"")
        ),
246 247 248 249 250 251 252 253 254
        'multiAccessTime'=>array(
          'type'=>'int',
          'label'=>$this->_translate("Time for disable multiple access (in minutes)."),
          'htmlOptions'=>array(
            'min'=>0,
            'placeholder' => CHtml::encode(sprintf($this->_translate("Use default (%s)"),$multiAccessTimeDefault)),
          ),
          'current'=>$this->get('multiAccessTime','Survey',$oEvent->get('survey'),"")
        ),
255 256 257 258 259 260 261 262 263 264 265
      ),
    ));
  }

  /** @inheritdoc **/
  public function newSurveySettings()
  {
    $event = $this->event;
    foreach ($event->get('settings') as $name => $value) {
      $this->set($name, $value, 'Survey', $event->get('survey'));
    }
266 267 268 269 270 271 272 273
  }

  /** @inheritdoc **/
  public function beforeActivate()
  {
    $this->_createDb();
  }

274 275 276 277
  /** @inheritdoc
   * Delete all response link when survey is set to active != Y
   **/
  public function afterSurveySave()
278
  {
279 280 281 282
    if($this->_getCurrentSetting('deleteLinkWhenSurveyDeactivated')) {
      $oSurvey = $this->getEvent()->get('model');
      if($oSurvey->sid && $oSurvey->active != 'Y') {
        $deleted = \reloadAnyResponse\models\responseLink::model()->deleteAll("sid = sid",array('sid'=>$oSurvey->sid));
283
        if($deleted>0) { // Don't log each time, can be saved for something other …
284 285 286 287 288
          $this->log(sprintf("%d responseLink deleted for %d",$deleted,$oSurvey->sid),CLogger::LEVEL_INFO);
        }
      }
    }
  }
289

290 291 292 293 294 295 296 297 298
  /** @inheritdoc
   * Delete all response link when survey is deleted
   **/
  public function afterSurveyDelete()
  {
    if($this->_getCurrentSetting('deleteLinkWhenSurveyDeleted')) {
      $oSurvey = $this->getEvent()->get('model');
      if($oSurvey->sid) {
        $deleted = \reloadAnyResponse\models\responseLink::model()->deleteAll("sid = sid",array('sid'=>$oSurvey->sid));
299
        if($deleted>0) { // Don't log each time, can be saved for something other …
300
          $this->log(sprintf("%d responseLink deleted for %d",$deleted,$oSurvey->sid),CLogger::LEVEL_INFO);
Chenu Denis's avatar
Chenu Denis committed
301
        }
302 303
      }
    }
304 305 306 307 308 309 310 311 312 313 314 315
  }

  /** @inheritdoc
   * Delete all response link when surveys is deleted
   * @todo
   **/
  public function beforeSurveyDeleteMany()
  {
    if($this->_getCurrentSetting('deleteLinkWhenSurveyDeleted')) {
      $criteria = $this->getEvent()->get('filterCriteria');
    }
  }
316

317 318 319 320 321 322 323 324 325
  /** @inheritdoc
   * Create the response link when survey is started
   * Remind : it's better if your plugin create this link directly `$responseLink = \reloadAnyResponse\models\responseLink::setResponseLink($iSurvey,$iResponse,$token);`
   * Before 3.15.0 : afterResponseSave event didn't exist
   **/
  public function afterModelSave()
  {
    $oModel = $this->getEvent()->get('model');
    $className = get_class($oModel);
326
    /* Create responlink for survey and srid (work when start a survey) */
327 328
    if($className == 'SurveyDynamic' || $className == 'Response') {
      $sid = str_replace(array('{{survey_','}}'),array('',''),$oModel->tableName());
329
      /* Test for sid activation */
330
      if(!$this->_getIsActivated('uniqueCodeCreate',$sid)) {
331 332
        return;
      }
333
      $srid = isset($oModel->id) ? $oModel->id : null;
334
      if($sid && $srid) {
Chenu Denis's avatar
Chenu Denis committed
335
        $responseLink = \reloadAnyResponse\models\responseLink::model()->findByPk(array('sid'=>$sid,'srid'=>$srid));
336 337 338
        /* @todo : add a way to reset potential token ?
         * @see https://gitlab.com/SondagesPro/managament/responseListAndManage/blob/80fb8571d394eedda6abfbfd1757c5322f699608/responseListAndManage.php#L2336
         **/
339 340
        if(!$responseLink) {
          $token = isset($oModel->token) ? $oModel->token : null;
341 342
          $responseLink = \reloadAnyResponse\models\responseLink::setResponseLink($sid,$srid,$token);
          if(!$responseLink) {
343 344
            $this->log("Unable to save responseLink with following errors.",CLogger::LEVEL_ERROR);
            $this->log(CVarDumper::dumpAsString($responseLink->getErrors()),CLogger::LEVEL_ERROR);
Chenu Denis's avatar
Chenu Denis committed
345
          }
346 347 348 349 350
        }
      }
    }
  }

351 352 353 354 355 356
  /**
   * @inheritdoc
   * Delete related responseLink when a response is deleted
   * Before 3.15.0 : afterResponseSave event didn't exist
   * This function is in testing currently
   **/
357 358
  public function afterModelDelete()
  {
359 360 361 362 363 364 365 366 367
    if($this->_getCurrentSetting('deleteLinkWhenResponseDeleted')) {
      $oModel = $this->getEvent()->get('model');
      $className = get_class($oModel);
      if($className == 'SurveyDynamic' || $className == 'Response') {
        $sid = str_replace(array('{{survey_','}}'),array('',''),$oModel->tableName());
        $srid = isset($oModel->id) ? $oModel->id : null;
        if($srid) {
          \reloadAnyResponse\models\responseLink::model()->deleteByPk(array('sid'=>$sid,'srid'=>$srid));
        }
368 369 370 371
      }
    }
  }

372 373 374 375
  /** @See event */
  public function beforeLoadResponse()
  {
    $srid = App()->getRequest()->getQuery('srid');
376 377
    $surveyId = $this->getEvent()->get('surveyId');
    $token = App()->getRequest()->getParam('token');
378 379 380
    if($srid=='new' && $token && $this->_getIsActivated('allowTokenUser',$surveyId)) {
        $this->getEvent()->set('response',false);
        return;
381
    }
382 383 384 385
    /* control multi access to token with token and allow edit reponse */
    $oSurvey = Survey::model()->findByPk($surveyId);
    if($oSurvey && $oSurvey->alloweditaftercompletion == "Y" && $oSurvey->tokenanswerspersistence == "Y") {
        /* Get like limesurvey (in this situation) : get the last srid with this token (without plugin …) */
386
        $oResponse = Response::model($surveyId)->find(array(
387 388 389 390 391
            'select' => 'id',
            'condition' => 'token=:token',
            'order' => 'id DESC',
            'params' => array('token' => $token)
        ));
392
        if($this->_getCurrentSetting('disableMultiAccess',$surveyId) && $oResponse && ($since = \reloadAnyResponse\models\surveySession::getIsUsed($surveyId,$oResponse->id))) {
393 394
            $this->_endWithEditionMessage($since);
        }
395
        \reloadAnyResponse\models\surveySession::saveSessionTime($surveyId,$oResponse->id);
396
    }
397 398 399 400 401 402 403
    /* @todo : control what happen with useleft > 1 and tokenanswerspersistence != "Y" */

  }

    /** @inheritdoc **/
    public function beforeSurveyPage()
    {
404 405
        /* Save current session Id to allow same user to reload survey in same browser */
        /* resetAllSessionVariables regenerate session id */
406
        /* Keep previous session id, if user reload start url it reset the sessionId, need to leav access */
407

408
        $surveyid = $this->getEvent()->get('surveyId');
409 410 411
        $multiAccessTime = $this->_getCurrentSetting('multiAccessTime',$surveyid);
        if($multiAccessTime !== '') {
            Yii::app()->setConfig('surveysessiontime_limit',$multiAccessTime);
412
        }
413
        $disableMultiAccess = $this->_getCurrentSetting('disableMultiAccess',$surveyid);
414
        if($multiAccessTime === '0') {
415 416
            $disableMultiAccess = false;
        }
417
        $this->_fixLanguage($surveyid);
418 419 420 421 422 423 424
        /* For token : @todo in beforeReloadReponse */
        /* @todo : delete surveySession is save or clearall action */
        if($disableMultiAccess && ($since = \reloadAnyResponse\models\surveySession::getIsUsed($surveyid))) {
            /* This one is done with current session : maybe allow to keep srid in session and reload it ? */
            $this->saveCurrentSrid($surveyid);
            killSurveySession($surveyid);
            $this->_endWithEditionMessage($since,array(
Chenu Denis's avatar
Chenu Denis committed
425
                'comment' => $this->_translate('We save your current session, you can try to reload the survey in some minutes.'),
426 427 428 429 430 431 432
                'class'=>'alert alert-info',
            ));
        }
        $srid = App()->getRequest()->getQuery('srid');
        if(!$srid && $disableMultiAccess) {
            /* Always save current srid if needed , only reload can disable this */
            \reloadAnyResponse\models\surveySession::saveSessionTime($surveyid);
433 434 435
            if(isset($_SESSION['survey_'.$surveyid]['srid'])) {
                $this->_addUnloadScript($surveyid,$_SESSION['survey_'.$surveyid]['srid']);
            }
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
            return;
        }
        $oSurvey = Survey::model()->findByPk($surveyid);
        $token = App()->getRequest()->getParam('token');
        if($srid == "new") {
            // Done in beforeLoadResponse
          return;
        }
        if(!$srid) {
            $srid = $this->getCurrentSrid($surveyid);
        }
        if(!$srid) {
            return;
        }
        //~ $accesscode = App()->getRequest()->getQuery($this->get('uniqueCodeCode'),null,null,$this->settings['uniqueCodeCode']['default']);
        $accesscode = App()->getRequest()->getQuery('code');
        $editAllowed = false;
453
        if($accesscode && $this->_getIsActivated('uniqueCodeAccess',$surveyid)) {
454 455 456 457 458
            $responseLink = \reloadAnyResponse\models\responseLink::model()->findByPk(array('sid'=>$surveyid,'srid'=>$srid));
            if($responseLink && $responseLink->accesscode == $accesscode) {
                $editAllowed = true;
            }
            if(!$responseLink) {
459
                $this->_HttpException(404,$this->_translate("Sorry, this response didn‘t exist."),$surveyid);
460 461
            }
            if($responseLink && $responseLink->accesscode != $accesscode) {
462
                $this->_HttpException(401,$this->_translate("Sorry, this access code is invalid."),$surveyid);
463 464
            }
        }
465
        if(!$editAllowed && $this->_getIsActivated('allowTokenUser',$surveyid) && $this->_accessibleWithToken($oSurvey)) {
466 467 468 469 470
            $editAllowed = true;
        }
        if(!$editAllowed && $this->_getIsActivated('allowAdminUser',$surveyid) && Permission::model()->hasSurveyPermission($surveyid,'response','update')) {
            $editAllowed = true;
        }
471
        
472
        if(!$editAllowed) {
473
            $this->log("srid used in url without right to reload");
474 475 476 477 478 479
            return;
        }
        if($since = \reloadAnyResponse\models\surveySession::getIsUsed($surveyid,$srid)) {
            $this->_endWithEditionMessage($since);
        }
        $this->_loadReponse($surveyid,$srid,App()->getRequest()->getParam('token'));
480
        $this->_addUnloadScript($surveyid,$srid);
481
  }
482

483 484 485 486 487
    /**
     * Delete SurveySession for this event srid
     */
    public function deleteSurveySession()
    {
488 489
        $surveyId = $this->getEvent()->get('surveyId');
        $responseId = $this->getEvent()->get('responseId');
490 491
        \reloadAnyResponse\models\surveySession::model()->deleteByPk(array('sid'=>$surveyId,'srid'=>$responseId));
    }
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516

    /** @inheritdoc **/
    public function newDirectRequest()
    {
        if($this->getEvent()->get('target') != get_class($this)) {
            return;
        }
        $surveyId = Yii::app()->getRequest()->getParam('sid');
        $responseId = Yii::app()->getRequest()->getParam('srid');
        if($surveyId && $responseId) {
            \reloadAnyResponse\models\surveySession::model()->deleteByPk(array('sid'=>$surveyId,'srid'=>$responseId));
        }
    }

    /**
     * Add beforeUnload script to delete session
     * @param $surveyid
     * @param $responseId
     * @return @void
     */
    private function _addUnloadScript($surveyId,$responseId)
    {
        $ajaxUrl = $this->api->createUrl('plugins/direct', array('plugin' => get_class($this), 'function' => 'close','sid'=>$surveyId,'srid'=>$responseId));
        $onBeforeUnload = "window.onbeforeunload = function(e) {\n";
        $onBeforeUnload .= " jQuery.ajax({ url:'{$ajaxUrl}' });\n";
517
        $onBeforeUnload .= " return null;\n";
518 519 520
        $onBeforeUnload .= "}\n";
        Yii::app()->getClientScript()->registerScript("reloadAnyResponseBeforeUnload",$onBeforeUnload,CClientScript::POS_HEAD);
    }
521
  /**
522 523 524
   * Get boolean value for setting activation
   * @param string existing $setting
   * @param integer $surveyid
525 526
   * @return boolean
   */
527
  private function _getIsActivated($setting,$surveyid)
528
  {
Chenu Denis's avatar
Chenu Denis committed
529
    $activation = $this->get($setting,'Survey',$surveyid,"");
530 531 532 533 534
    if($activation === '') {
      $activation = $this->get($setting,null,null,$this->settings[$setting]['default']);
    }
    return (bool) $activation;
  }
535 536 537 538 539
    /**
    * Create needed DB
    * @return void
    */
    private function _createDb()
540
    {
541 542 543 544
        if($this->get("dbVersion") == self::$dbVersion) {
            return;
        }
        /* dbVersion not needed */
545
        if(!$this->api->tableExists($this, 'responseLink') && !$this->api->tableExists($this, 'surveySession')) {
546
            $this->api->createTable($this, 'responseLink', array(
547 548
                'sid'=>'int not NULL',
                'srid'=>'int not NULL',
549 550 551 552
                'token'=>'text',
                'accesscode'=>'text',
            ));
            $this->api->createTable($this, 'surveySession', array(
553 554
                'sid' => 'int not NULL',
                'srid' => 'int not NULL',
555 556 557 558
                'token' => 'string(55)',
                'session' => 'text',
                'lastaction' => 'datetime'
            ));
559 560
            $this->set("dbVersion",self::$dbVersion);
            return;
561
        }
562 563
        if(!$this->get("dbVersion")) {
            $tableName = $this->api->getTable($this,'surveySession')->tableName();
564 565
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'sid','int not NULL');
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'srid','int not NULL');
566
            Yii::app()->getDb()->createCommand()->addPrimaryKey('surveysession_sidsrid',$tableName,'sid,srid');
567

568
            $tableName = $this->api->getTable($this,'responseLink')->tableName();
569 570
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'sid','int not NULL');
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'srid','int not NULL');
571
            Yii::app()->getDb()->createCommand()->addPrimaryKey('responselink_sidsrid',$tableName,'sid,srid');
572 573 574 575 576 577 578 579 580 581 582
            $this->set("dbVersion",2);
        }
        if($this->get("dbVersion") < 2 ) {
            $tableName = $this->api->getTable($this,'surveySession')->tableName();
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'sid','int not NULL');
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'srid','int not NULL');

            $tableName = $this->api->getTable($this,'responseLink')->tableName();
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'sid','int not NULL');
            Yii::app()->getDb()->createCommand()->alterColumn($tableName,'srid','int not NULL');
            $this->set("dbVersion",2);
583 584 585 586
        }

        /* all done */
        $this->set("dbVersion",self::$dbVersion);
587 588
    }

589
  /**
590 591
   * Add needed alias and put it in autoloader,
   * add surveysessiontime_limit to global config
592 593
   * @return void
   */
Chenu Denis's avatar
Chenu Denis committed
594
  private function _setConfig()
595 596
  {
    Yii::setPathOfAlias(get_class($this), dirname(__FILE__));
597
    $this->_createDb();
598 599 600 601 602 603 604
    if(!empty(Yii::app()->getConfig('surveysessiontime_limit')) ) {
        /* Allow to force surveysessiontime_limit in config.php , to do : show it to admin */
        Yii::app()->setConfig('surveysessiontimeDisable',true);
    }
    if(empty(Yii::app()->getConfig('surveysessiontime_limit')) ) {
        Yii::app()->setConfig('surveysessiontime_limit',$this->get('multiAccessTime',null,null,$this->settings['multiAccessTime']['default']));
    }
605 606 607 608 609 610 611 612 613 614
    $messageSource=array(
        'class' => 'CGettextMessageSource',
        //'cacheID' => get_class($this).'Lang',
        'cachingDuration'=>3600,
        'forceTranslation' => true,
        'useMoFile' => true,
        'basePath' => __DIR__ . DIRECTORY_SEPARATOR.'locale',
        'catalog'=>'messages',// default from Yii
    );
    Yii::app()->setComponent(get_class($this).'Lang',$messageSource);
615 616 617
  }

  /**
618
   * Create Survey and add current response in $_SESSION
619 620 621 622 623 624 625
   * @param integer $surveydi
   * @param integer $srid
   * @throws Error 404
   * @return void
   */
  private function _loadReponse($surveyid,$srid,$token = null)
  {
626 627 628 629

    if(isset($_SESSION['survey_'.$surveyid]['srid']) && $_SESSION['survey_'.$surveyid]['srid'] == $srid) {
      return;
    }
630
    $oResponse = SurveyDynamic::model($surveyid)->find("id = :srid",array(':srid'=>$srid));
631
    $language = Yii::app()->getLanguage();
632
    if(!$oResponse) {
633
      $this->_HttpException(404, $this->_translate('Response not found.'),$surveyid);
634 635
    }
    $oSurvey = Survey::model()->findByPk($surveyid);
636
    // Validate token : @todo review for admin user
637 638
    if(!Permission::model()->hasSurveyPermission($surveyid,'response','update') && $oResponse->token) {
      if($oResponse->token != $token) {
639
        $this->_HttpException(401, $this->_translate('Access to this response need a valid token.'),$surveyid);
640 641
      }
    }
642
    killSurveySession($surveyid); // Is this needed ?
643 644 645 646
    LimeExpressionManager::SetDirtyFlag();
    $_SESSION['survey_'.$surveyid]['srid'] = $oResponse->id;
    if (!empty($oResponse->lastpage)) {
        $_SESSION['survey_'.$surveyid]['LEMtokenResume'] = true;
647
        // If the response was completed start at the beginning and not at the last page - just makes more sense
648 649 650
        if (empty($oResponse->submitdate)) {
            $_SESSION['survey_'.$surveyid]['step'] = $oResponse->lastpage;
        }
651 652 653 654 655
        if(!empty($oResponse->submitdate) && $oSurvey->alloweditaftercompletion != 'Y') {
            $oResponse->submitdate = null;
            $oResponse->save();
            // Better to set Survey to alloweditaftercompletion == 'Y', but unable at this time on afterFindSurvey event
        }
656
    }
657
    $_SESSION['survey_'.$surveyid]['s_lang'] = $language; /* buildsurveysession use session lang … , send a notic if not set */
658 659 660 661
    buildsurveysession($surveyid);
    if (!empty($oResponse->submitdate)) {
        $_SESSION['survey_'.$surveyid]['maxstep'] = $_SESSION['survey_'.$surveyid]['totalsteps'];
    }
662 663 664
    if (!empty($oResponse->token)) {
        $_SESSION['survey_'.$surveyid]['token'] = $oResponse->token;
    }
665 666 667 668
    if(version_compare(Yii::app()->getConfig('versionnumber'),"3",">=")) {
        randomizationGroupsAndQuestions($surveyid);
        initFieldArray($surveyid, $_SESSION['survey_'.$surveyid]['fieldmap']);
    }
669
    loadanswers();
670
    \reloadAnyResponse\models\surveySession::saveSessionTime($surveyid,$oResponse->id);
671
  }
Chenu Denis's avatar
Chenu Denis committed
672

673 674
  /**
   * Create a new response for token
675
   * @todo : validate if we need it or if it's a bug in LS version tested
676 677 678 679 680 681
   * @param int $surveyid
   * @param string $token
   * @return null|\Response
   */
  private function _createNewResponse($surveyid,$token) {
      $oSurvey = Survey::model()->findByPk($surveyid);
682
      if($this->_accessibleWithToken($oSurvey)) {
683 684
        return;
      }
685
      if($oSurvey->tokenanswerspersistence != "Y") {
686 687
        return;
      }
688 689 690
      //~ if($oSurvey->alloweditaftercompletion != "Y") {
        //~ return;
      //~ }
691 692 693 694
    /* some control */
    if(!(
      ($this->_getIsActivated('allowAdminUser',$surveyid) && Permission::model()->hasSurveyPermission($surveyid,'response','create'))
      ||
695
      ($this->_getIsActivated('allowTokenUser',$surveyid))
696 697
      )) {
      // Disable here
Chenu Denis's avatar
Chenu Denis committed
698
      $this->log("Try to create a new reponse with token but without valid rights",'warning');
699 700 701 702 703 704 705 706 707 708
      return;
    }
    $oToken = Token::model($surveyid)->findByAttributes(array('token' => $token));
    if(empty($oToken)) {
      return;
    }
    $oResponse = Response::create($surveyid);
    $oResponse->token = $oToken->token;
    $oResponse->startlanguage = Yii::app()->getLanguage();
    /* @todo generate if not set */
709 710 711
    if(version_compare(Yii::app()->getConfig('versionnumber'),"3",">=") ) {
        $oResponse->seed = isset($_SESSION['survey_'.$surveyid]['startingValues']['seed']) ? $_SESSION['survey_'.$surveyid]['startingValues']['seed'] : null;
    }
712 713 714 715 716 717 718 719 720
    $oResponse->lastpage=-1;
    if($oSurvey->datestamp == 'Y') {
        $date = dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", Yii::app()->getConfig('timeadjust'));
        $oResponse->datestamp = $date;
        $oResponse->startdate = $date;
    }
    $oResponse->save();
    return $oResponse;
  }
Chenu Denis's avatar
Chenu Denis committed
721 722 723 724 725 726 727 728 729 730 731 732
  /**
   * @inheritdoc adding string, by default current event
   * @param string
   */
  public function log($message, $level = \CLogger::LEVEL_TRACE,$logDetail = null)
  {
    if(!$logDetail && $this->getEvent()) {
      $logDetail = $this->getEvent()->getEventName();
    } // What to put if no event ?
    parent::log($message, $level);
    Yii::log($message, $level,'application.plugins.reloadAnyResponse.'.$logDetail);
  }
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763

    /**
     * Save current srid if exist in specific session
     * @return void
     */
    public function saveCurrentSrid($surveyId)
    {
        $currentSrid = isset($_SESSION['survey_'.$surveyId]['srid']) ? $_SESSION['survey_'.$surveyId]['srid'] : null;
        if(!$currentSrid) {
            return;
        }
        $sessionCurrentSrid = Yii::app()->session['reloadAnyResponsecurrentSrid'];
        if(empty($sessionCurrentSrid)) {
            $sessionCurrentSrid = array();
        }
        $sessionCurrentSrid[$surveyId] = $currentSrid;
        Yii::app()->session['reloadAnyResponsecurrentSrid'] = $sessionCurrentSrid[$surveyId];
    }

    /**
     * Get current srid if exist in specific session
     * @return integer|null
     */
    public function getCurrentSrid($surveyId)
    {
        $sessionCurrentSrid = Yii::app()->session['reloadAnyResponsecurrentSrid'];
        if(empty($sessionCurrentSrid) || empty($sessionCurrentSrid[$surveyId])) {
            return;
        }
        return $sessionCurrentSrid[$surveyId];
    }
764 765 766 767 768 769 770 771 772 773 774 775

    /**
     * Did this survey have token with reload available
     * @var \Survey
     * @return boolean
     */
    private function _accessibleWithToken($oSurvey)
    {
        Yii::import('application.helpers.common_helper', true);
        return $oSurvey->anonymized != "Y" && tableExists("{{tokens_".$oSurvey->sid."}}");
    }

776 777 778 779 780 781 782 783
    /**
     * Ending with the delay if renderMessage is available (if not : log as error …)
     * @param float $since last edit
     * @param string|string[] $comment array with 'comment' and 'class'
     * @return void
     */
    private function _endWithEditionMessage($since,$comment=null)
    {
784
        $messageString = sprintf($this->_translate("Sorry, someone update this response to the questionnaire a short time ago. The last action was made less than %s minutes ago."),ceil($since));
785 786
        if(!Yii::getPathOfAlias('renderMessage')) {
            /* plugin log */
787
            $this->log("You need to download and activate renderMessage plugin for disableMultiAccess",'warning');
788
            /* Yii log as vardump if debug>0 to be shown to user (with red part) */
789 790
            Yii::log("reloadAnyReponse plugin : You need to download and activate renderMessage plugin for disableMultiAccess", 'warning', 'vardump');
            $this->_HttpException("409",$messageString);
791 792
            return;
        }
793 794
        $message = CHtml::tag("h1",array("class"=>'text-danger'),"409 Conflict");
        $message .= CHtml::tag("div",array("class"=>'alert alert-danger'),$messageString);
795 796 797 798 799 800 801
        if($comment) {
            if(is_string($comment)) {
                $comment = array(
                    'comment'=> $comment,
                    'class'=>'alert alert-info',
                );
            }
802
            $message .= CHtml::tag("div",array("class"=>$comment['class']),$comment['comment']);
803
        }
804
        header($_SERVER["SERVER_PROTOCOL"]." 409 Conflict",true,409);
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
        \renderMessage\messageHelper::renderContent($message);

    }

    /**
     * Get current setting for current survey (use empty string as null value)
     * @param string setting to get
     * @param integer survey id
     * @return string|array|null
     */
    private function _getCurrentSetting($setting,$surveyId = null)
    {
        if($surveyId) {
            $value = $this->get($setting,'Survey',$surveyId,'');
            if($value !== '') {
                return $value;
            }
        }
        $default = (isset($this->settings[$setting]['default'])) ? $this->settings[$setting]['default'] : null;
        return $this->get($setting,null,null,$default);
    }
826 827 828 829 830 831

    /**
     * get translation
     * @param string
     * @return string
     */
832 833 834
    private function _translate($string, $language = null){
        $messageSource = get_class($this).'Lang';
        return Yii::t('',$string,array(),$messageSource);
835 836
    }

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
    /**
     * Throw specific error
     * @param @errorCode (404/401/403 totally supported)
     * @param @errorMessage
     * @param $surveyId
     * @throw CHttpException
     */
    private function _HttpException($errorCode,$errorMessage,$surveyId=null)
    {
        $errorCodeHeader = array(
            '401' => "401 Unauthorized",
            '403' => "403 Forbidden",
            '404' => "404 Not Found",
            '409' => "409 Conflict",
        );

        $limesurveyVersion = Yii::app()->getConfig("versionnumber");
        if(version_compare($limesurveyVersion,"3.14.0",'>=')) {
            // @todo : Set template by survey
            throw new CHttpException($errorCode, $errorMessage);
        }

        if(!array_key_exists($errorCode,$errorCodeHeader)) {
            // Unable to do own system
            throw new CHttpException($errorCode, $errorMessage);
        }
        if(version_compare($limesurveyVersion,"3.0.0")) {
            header($_SERVER["SERVER_PROTOCOL"]." ".$errorCodeHeader[$errorCode],true,$errorCode);
            Yii::app()->twigRenderer->renderTemplateFromFile("layout_errors.twig",
                array('aSurveyInfo' =>array(
                    'aError'=>array(
                        'error'=>$errorCodeHeader[$errorCode],
                        'title'=>$errorCodeHeader[$errorCode],
                        'message'=>$errorMessage,
                    ),
                    'adminemail' => null,
                    'adminname' => Yii::app()->getConfig('siteadminname'),
                )),
            false);
        }
        /* lesser than 3 */
        if(Yii::getPathOfAlias('renderMessage')) {
            $message = CHtml::tag("h1",array("class"=>'text-danger'),$errorCodeHeader[$errorCode]);
            $message .= CHtml::tag("div",array("class"=>'alert alert-danger'),$errorMessage);
            header($_SERVER["SERVER_PROTOCOL"]." ".$errorCodeHeader[$errorCode],true,$errorCode);
            \renderMessage\messageHelper::renderContent($message);
        }
        throw new CHttpException($errorCode, $errorMessage);
    }
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909

    /**
     * Set language according to survey or current language
     * @param $surveyid
     * @return $void
     */
    private function _fixLanguage($surveyId=null)
    {
        $currentLang = Yii::app()->getLanguage();
        if((empty($currentLang) || $currentLang == "en_US") ) {
            if(!empty($_SESSION['adminlang']) && $_SESSION['adminlang']!="auto") {
                $currentLang = $_SESSION['adminlang'];
            } else {
                $currentLang = Yii::app()->getConfig("defaultlang");
            }
        }
        if($surveyId) {
            $oSurvey = Survey::model()->findByPk($surveyId);
            if($oSurvey && !in_array($currentLang,$oSurvey->getAllLanguages())) {
                $currentLang = $oSurvey->language;
            }
        }
        Yii::app()->setLanguage($currentLang);
    }
910
}