diff --git a/application/helpers/admin/token_helper.php b/application/helpers/admin/token_helper.php index 18f80c61cfc..ae720b9babc 100644 --- a/application/helpers/admin/token_helper.php +++ b/application/helpers/admin/token_helper.php @@ -19,9 +19,10 @@ * @param integer $iSurveyID * @param CActiveRecord[] $aResultTokens * @param string $sType type of notification invite|register|remind|confirm +* @param bool $continueOnError Don't stop on first invalid participant * @return array of results */ -function emailTokens($iSurveyID, $aResultTokens, $sType) +function emailTokens($iSurveyID, $aResultTokens, $sType, $continueOnError = false) { if (!in_array($sType, ['invite','remind','register','confirm'])) { throw new Exception('Invalid email type'); @@ -43,7 +44,11 @@ function emailTokens($iSurveyID, $aResultTokens, $sType) 'status' => 'fail', 'error' => 'Token not valid yet' ); - break 1; + if ($continueOnError) { + continue; + } else { + break 1; + } } if (isset($aTokenRow['validuntil']) && trim($aTokenRow['validuntil']) != '' && convertDateTimeFormat($aTokenRow['validuntil'], 'Y-m-d H:i:s', 'U') * 1 < date('U') * 1) { $aResult[$aTokenRow['tid']] = array( @@ -52,7 +57,11 @@ function emailTokens($iSurveyID, $aResultTokens, $sType) 'status' => 'fail', 'error' => 'Token not valid anymore' ); - break 1; + if ($continueOnError) { + continue; + } else { + break 1; + } } if ($mail->sendMessage()) { $oToken = Token::model($iSurveyID)->findByPk($aTokenRow['tid']); diff --git a/application/helpers/remotecontrol/remotecontrol_handle.php b/application/helpers/remotecontrol/remotecontrol_handle.php index aa8d3e7356a..9ee5ad282fa 100644 --- a/application/helpers/remotecontrol/remotecontrol_handle.php +++ b/application/helpers/remotecontrol/remotecontrol_handle.php @@ -2686,9 +2686,10 @@ public function mail_registered_participants($sSessionKey, $iSurveyID, $override * @param int $iSurveyID ID of the survey that participants belong * @param array $aTokenIds Ids of the participant to invite * @param bool $bEmail Send only pending invites (TRUE) or resend invites only (FALSE) + * @param bool $continueOnError Don't stop on first invalid participant * @return array Result of the action */ - public function invite_participants($sSessionKey, $iSurveyID, $aTokenIds = null, $bEmail = true) + public function invite_participants($sSessionKey, $iSurveyID, $aTokenIds = null, $bEmail = true, $continueOnError = false) { Yii::app()->loadHelper('admin/token'); if (!$this->_checkSessionKey($sSessionKey)) { @@ -2736,7 +2737,7 @@ public function invite_participants($sSessionKey, $iSurveyID, $aTokenIds = null, if (empty($aResultTokens)) { return array('status' => 'Error: No candidate tokens'); } - $aResult = emailTokens($iSurveyID, $aResultTokens, 'invite'); + $aResult = emailTokens($iSurveyID, $aResultTokens, 'invite', $continueOnError); $iLeft = $iAllTokensCount - count($aResultTokens); $aResult['status'] = $iLeft . " left to send"; @@ -2758,9 +2759,10 @@ public function invite_participants($sSessionKey, $iSurveyID, $aTokenIds = null, * @param int $iMinDaysBetween (optional) parameter days from last reminder * @param int $iMaxReminders (optional) parameter Maximum reminders count * @param array $aTokenIds Ids of the participant to remind (optional filter) + * @param bool $continueOnError Don't stop on first invalid participant * @return array in case of success array of result of each email send action and count of invitations left to send in status key */ - public function remind_participants($sSessionKey, $iSurveyID, $iMinDaysBetween = null, $iMaxReminders = null, $aTokenIds = false) + public function remind_participants($sSessionKey, $iSurveyID, $iMinDaysBetween = null, $iMaxReminders = null, $aTokenIds = false, $continueOnError = false) { Yii::app()->loadHelper('admin/token'); if (!$this->_checkSessionKey($sSessionKey)) { @@ -2806,7 +2808,7 @@ public function remind_participants($sSessionKey, $iSurveyID, $iMinDaysBetween = return array('status' => 'Error: No candidate tokens'); } - $aResult = emailTokens($iSurveyID, $aResultTokens, 'remind'); + $aResult = emailTokens($iSurveyID, $aResultTokens, 'remind', $continueOnError); $iLeft = $iAllTokensCount - count($aResultTokens); $aResult['status'] = $iLeft . " left to send"; diff --git a/tests/data/surveys/limesurvey_survey_InviteParticipantsTest.lsa b/tests/data/surveys/limesurvey_survey_InviteParticipantsTest.lsa new file mode 100644 index 00000000000..3207d513664 Binary files /dev/null and b/tests/data/surveys/limesurvey_survey_InviteParticipantsTest.lsa differ diff --git a/tests/unit/helpers/remotecontrol/BaseTest.php b/tests/unit/helpers/remotecontrol/BaseTest.php index d0231f3eecb..56677acf165 100644 --- a/tests/unit/helpers/remotecontrol/BaseTest.php +++ b/tests/unit/helpers/remotecontrol/BaseTest.php @@ -2,7 +2,7 @@ namespace ls\tests; -abstract class BaseTest extends TestHelper +abstract class BaseTest extends TestBaseClass { /* @var User */ public $user; @@ -16,7 +16,6 @@ abstract class BaseTest extends TestHelper protected function setUp(): void { - $this->importAll(); \Yii::import('application.helpers.remotecontrol.remotecontrol_handle', true); $user = \User::model()->findByPk(1); diff --git a/tests/unit/helpers/remotecontrol/InviteParticipantsTest.php b/tests/unit/helpers/remotecontrol/InviteParticipantsTest.php new file mode 100644 index 00000000000..9da3e868516 --- /dev/null +++ b/tests/unit/helpers/remotecontrol/InviteParticipantsTest.php @@ -0,0 +1,90 @@ +setController(new DummyController('dummyid')); + } + + public function testInviteParticipants() + { + $sessionKey = $this->handler->get_session_key($this->getUsername(), $this->getPassword()); + $result = $this->handler->invite_participants($sessionKey, self::$surveyId); + // Assert emails for participants 1 and 2 were sent + $this->assertParticipantResultIsOk($result, 1); + $this->assertParticipantResultIsOk($result, 2); + // Assert email for participant 3 was not sent + $this->assertParticipantResultIsNotOk($result, 3); + // Assert email sending stopped on error + $this->assertArrayNotHasKey(4, $result); + $this->assertArrayNotHasKey(5, $result); + } + + public function testInviteParticipantsSkippingErrors() + { + $sessionKey = $this->handler->get_session_key($this->getUsername(), $this->getPassword()); + $result = $this->handler->invite_participants($sessionKey, self::$surveyId, null, true, true); + // Assert emails for participants 1 and 2 were sent + $this->assertParticipantResultIsOk($result, 1); + $this->assertParticipantResultIsOk($result, 2); + // Assert email for participant 3 was not sent + $this->assertParticipantResultIsNotOk($result, 3); + // Assert emails for participants 4 and 5 were sent + $this->assertParticipantResultIsOk($result, 4); + $this->assertParticipantResultIsOk($result, 5); + } + + /** + * Assert the results for a participant are OK. + * @param array $results The raw results from invite_participants or remind_participants + * @param int $tid The participant ID + */ + private function assertParticipantResultIsOk($results, $tid) + { + $this->assertArrayHasKey($tid, $results); + $participantResults = $results[$tid]; + $this->assertArrayHasKey("status", $participantResults); + $isOk = $this->isParticipantResultOk($participantResults, $tid); + $this->assertTrue($isOk); + } + + /** + * Assert the results for a participant are not OK. + * @param array $results The raw results from invite_participants or remind_participants + * @param int $tid The participant ID + */ + private function assertParticipantResultIsNotOk($results, $tid) + { + $this->assertArrayHasKey($tid, $results); + $participantResults = $results[$tid]; + $this->assertArrayHasKey("status", $participantResults); + $isOk = $this->isParticipantResultOk($participantResults, $tid); + $this->assertFalse($isOk); + } + + /** + * Returns true if the results for a participant are OK. + * @param array Specific participant result from invite_participants or remind_participants + * @param int $tid The participant ID + * @return bool + */ + private function isParticipantResultOk($results, $tid) + { + // For this test, the result is considered OK if it's actually OK (status = OK), + // or if it failed because email function couldn't be instantiated, as we don't + // have a simple way to mock the LimeMailer. We could use a plugin that returns + // true on beforeTokenEmail, but it doesn't seem much better than this. + $validError = 'Could not instantiate mail function.'; + return $results['status'] == 'OK' || (!empty($results['error']) && $results['error'] == $validError); + } +}