Skip to content

Commit

Permalink
New feature #18282: Possibility to resend failed admin notification e…
Browse files Browse the repository at this point in the history
…mails(#2539)

* dev: created new table for failed email notification and created new …

* dev: added column surveyid to new table failed_email

* dev: changed columns table failed_email

* LSC-267: added controller/view/model for failed email notifications

* dev: LSC273 self cleaning after 30 days

* Dev: true equal for string comparison

* LSC-267: added grid and some controller/db adjustments for failedEmail

* Save failed emails to database also fixing database table columns

* LSC-267: added/fixed some fields and added basic resend action and modal

* subject col now called email_type in create-database.php

* Notifications for failed e-mails

* Success state as constant

* LSC-267: added controller actions/massive action modals/buttons for f…

* Only one notification, and only for survey owners

* Changed notification message text

* If a survey is deleted, the failed email notifications belonging to t…

* LSC-267: added buttons and javascript generating modals

* LSC-267: added return messages for modals / added ajax reload for jav…

* Dev: PSR-12 fixes

* Dev: Add missing return types

* Dev: Add missing return types

* Dev: Fix link casing

* LSC-267: capitalized controller name in url

* LSC-267: added check for LimeMailer instance

* LSC-267: added docs to saveFailedEmail and removed redundant return

* LSC-267: updated doc blocks and fixed some conditions / added excepti…

* PSR-12 fix

* LSC-267: updated view test for FailedEmail

* LSC-267: updated view test for FailedEmail

* LSC-267: updated permissions for button group and dynamic modalcontent

* LSC-267: updated view test for FailedEmail

* LSC-267: psr-12

* Merge branch 'master' into task/LSC-267_failed_notification_emails

* LSC-267: fixed for url type for get/path

* LSC-267: fixed some typos

* LSC-267: fixed missing surveyid to display correct data in the table

* LSC-267: functional tests for FailedEmail

* LSC-267: fixed wrong return values causing errors in ajax calls

* LSC-267: WIP testing error on CI

* LSC-267: WIP testing error on CI

* LSC-267: WIP testing error on CI

* LSC-267: WIP testing error on CI

* LSC-267: fixed missing function param for saveFailedEmail() / added c…

* Fixed issue: thissurvey is null; addCondition second argument is not …

* Turn on debug 2 in CI

* Fix yaml

* Fix line in sed call

* Fix sed

* LSC-267: added breadcrumb

* LSC-267: fixed id/language/recipient not being set right / header all…

* LSC-267: added additional checks to wait for modal being closed

* LSC-267: adjust waiting time for tests

* LSC-267: adjust waiting time for tests

* Remove debug 2

* Run entire test suite

* LSC-267: added comment for clarification

* LSC-267: removed todo

* Merge branch 'master' into task/LSC-267_failed_notification_emails

* Apply composer no dev

* LSC-267: reverted wrong yaml setting to master default
  • Loading branch information
ptelu committed Aug 17, 2022
1 parent f5ea1fd commit 30cca2f
Show file tree
Hide file tree
Showing 40 changed files with 2,448 additions and 598 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Expand Up @@ -94,12 +94,12 @@ jobs:
php application/commands/console.php install admin password TravisLS no@email.com verbose
cp application/config/config-sample-mysql.php application/config/config.php
# Enable debug=2 in config file. OBS: This assumes debug is on line 61.
# TODO: Disable, a lines was added to config file and some tests started to fail.
# NB: EmCache is always disabled when debug => 2
# NB: There can be a difference in assets used when debug = 0 or 2 (minified version or not)
# sed -i '61s/.*/ "debug"=>2,/' application/config/config.php
# cat application/config/config.php
# Enable debug=2 in config file. OBS: This assumes debug is on line 61.
# TODO: Disable, a lines was added to config file and some tests started to fail.
# NB: EmCache is always disabled when debug => 2
# NB: There can be a difference in assets used when debug = 0 or 2 (minified version or not)
# sed -i '84s/.*/ "debug"=>2,/' application/config/config.php
# cat application/config/config.php
- name: Run syntax check, CodeSniffer, MessDetector, ...
run: composer test
Expand Down
2 changes: 1 addition & 1 deletion application/config/version.php
Expand Up @@ -12,7 +12,7 @@
*/

$config['versionnumber'] = '5.3.32';
$config['dbversionnumber'] = 488;
$config['dbversionnumber'] = 489;
$config['buildnumber'] = '';
$config['updatable'] = true;
$config['templateapiversion'] = 3;
Expand Down
213 changes: 213 additions & 0 deletions application/controllers/FailedEmailController.php
@@ -0,0 +1,213 @@
<?php

class FailedEmailController extends LSBaseController
{
/**
* this is part of renderWrappedTemplate implement in old responses.php
*
* @param string $view
* @return bool
*/
public function beforeRender($view)
{
$surveyId = (int)App()->request->getParam('surveyid');
$this->aData['surveyid'] = $surveyId;
LimeExpressionManager::SetSurveyId($this->aData['surveyid']);
$this->layout = 'layout_questioneditor';
$this->aData['title_bar']['title'] = gT('Failed e-mail notifications');
$this->aData['subaction'] = gT("Failed e-mail notifications");

return parent::beforeRender($view);
}

/**
* @throws CHttpException|CException
*/
public function actionIndex(): void
{
$surveyId = sanitize_int(App()->request->getParam('surveyid'));
$permissions = [
'update' => Permission::model()->hasSurveyPermission($surveyId, 'responses', 'update'),
'delete' => Permission::model()->hasSurveyPermission($surveyId, 'responses', 'delete'),
'read' => Permission::model()->hasSurveyPermission($surveyId, 'responses', 'read')
];
if (!$surveyId) {
throw new CHttpException(403, gT("Invalid survey ID"));
}
if (!$permissions['read']) {
App()->user->setFlash('error', gT("You do not have permission to access this page."));
$this->redirect(['surveyAdministration/view', 'surveyid' => $surveyId]);
}

App()->getClientScript()->registerScriptFile('/application/views/failedEmail/javascript/failedEmail.js', LSYii_ClientScript::POS_BEGIN);
$failedEmailModel = FailedEmail::model();
$failedEmailModel->setAttributes(App()->getRequest()->getParam('FailedEmail'), false);
$failedEmailModel->setAttribute('surveyid', $surveyId);
$pageSize = App()->request->getParam('pageSize') ?? App()->user->getState('pageSize', App()->params['defaultPageSize']);
$massiveAction = App()->getController()->renderPartial('/failedEmail/partials/massive_action_selector', [
'surveyId' => $surveyId,
'permissions' => $permissions
], true);


$this->render('failedEmail_index', [
'failedEmailModel' => $failedEmailModel,
'pageSize' => $pageSize,
'massiveAction' => $massiveAction,
]);
}

/**
* @throws CHttpException|CException
* @return string|void
*/
public function actionResend()
{
$surveyId = (int)sanitize_int(App()->request->getParam('surveyid'));
if (!$surveyId) {
throw new CHttpException(403, gT("Invalid survey ID"));
}
if (!Permission::model()->hasSurveyPermission($surveyId, 'responses', 'update')) {
App()->user->setFlash('error', gT("You do not have permission to access this page."));
$this->redirect(['failedEmail/index/', 'surveyid' => $surveyId]);
}
$preserveResend = App()->request->getParam('preserveResend') ?? false;
$item = [App()->request->getParam('item')];
$items = json_decode(App()->request->getParam('sItems'));
$selectedItems = $items ?? $item;
$emailsByType = [];
if (!empty($selectedItems)) {
$criteria = new CDbCriteria();
$criteria->select = 'id, email_type, recipient';
$criteria->addCondition('surveyid = ' . (int) $surveyId);
$criteria->addInCondition('id', $selectedItems);
$failedEmails = FailedEmail::model()->findAll($criteria);
if (!empty($failedEmails)) {
foreach ($failedEmails as $failedEmail) {
$emailsByType[$failedEmail->email_type][] = [
'id' => $failedEmail->id,
'recipient' => $failedEmail->recipient,
'language' => $failedEmail->language,
];
}
global $thissurvey;
$thissurvey = getSurveyInfo($surveyId);
$result = sendSubmitNotifications($surveyId, $emailsByType, $preserveResend, true);
if (!$preserveResend) {
// only delete FailedEmail entries that have succeeded
$criteria->addCondition('status = :status');
$criteria->params['status'] = FailedEmail::STATE_SUCCESS;
FailedEmail::model()->deleteAll($criteria);
}
// massive action
if ($items) {
return $this->renderPartial('partials/modal/resend_result_body', [
'successfullEmailCount' => $result['successfullEmailCount'],
'failedEmailCount' => $result['failedEmailCount']
]);
}
// single action
return $this->renderPartial('/admin/super/_renderJson', [
"data" => [
'success' => true,
'html' => $this->renderPartial('partials/modal/resend_result', [
'successfullEmailCount' => $result['successfullEmailCount'],
'failedEmailCount' => $result['failedEmailCount']
], true)
]
]);
}
return $this->renderPartial('/admin/super/_renderJson', [
"data" => [
'success' => false,
'message' => gT('No match could be found for selection'),
]
]);
}
return $this->renderPartial('/admin/super/_renderJson', [
"data" => [
'success' => false,
'message' => gT('Please select at least one item'),
]
]);
}

/**
* @throws CHttpException|CException
* @return string|void
*/
public function actionDelete()
{
$surveyId = sanitize_int(App()->request->getParam('surveyid'));
if (!$surveyId) {
throw new CHttpException(403, gT("Invalid survey ID"));
}
if (!Permission::model()->hasSurveyPermission($surveyId, 'responses', 'delete')) {
App()->user->setFlash('error', gT("You do not have permission to access this page."));
$this->redirect(['failedEmail/index/', 'surveyid' => $surveyId]);
}
$item = [App()->request->getParam('item')];
$items = json_decode(App()->request->getParam('sItems'));
$selectedItems = $items ?? $item;
if (!empty($selectedItems)) {
$criteria = new CDbCriteria();
$criteria->select = 'id, email_type, recipient';
$criteria->addCondition('surveyid =' . (int) $surveyId);
$criteria->addInCondition('id', $selectedItems);
$failedEmails = new FailedEmail();
$deletedCount = $failedEmails->deleteAll($criteria);
if ($items) {
return $this->renderPartial('partials/modal/delete_result_body', [
'deletedCount' => $deletedCount,
]);
}
return $this->renderPartial('/admin/super/_renderJson', [
"data" => [
'success' => true,
'html' => $this->renderPartial('partials/modal/delete_result', [
'deletedCount' => $deletedCount,
], true),
]
]);
}
return $this->renderPartial('/admin/super/_renderJson', [
"data" => [
'success' => false,
'message' => gT('Please select at least one item'),
]
]);
}

/**
* @return void
* @throws CException
* @throws CHttpException
*/
public function actionModalContent(): void
{
$id = App()->request->getParam('id');
$failedEmailModel = new FailedEmail();
$failedEmail = $failedEmailModel->findByPk($id);
if (!$failedEmail) {
throw new CHttpException(403, gT("Invalid ID"));
}
$surveyId = $failedEmail->surveyid;
$permissions = [
'update' => Permission::model()->hasSurveyPermission($surveyId, 'responses', 'update'),
'delete' => Permission::model()->hasSurveyPermission($surveyId, 'responses', 'delete'),
'read' => Permission::model()->hasSurveyPermission($surveyId, 'responses', 'read')
];
$contentFile = App()->request->getParam('contentFile');
if (
!($permissions['update'] && $contentFile === 'resend_form')
&& !($permissions['delete'] && $contentFile === 'delete_form')
&& !($permissions['read'] && in_array($contentFile, ['email_content', 'email_error']))
) {
throw new CHttpException(403, gT("You do not have permission to access this page."));
}
App()->getController()->renderPartial(
'/failedEmail/partials/modal/' . $contentFile,
['id' => $id, 'surveyId' => $surveyId, 'failedEmail' => $failedEmail]
);
}
}
60 changes: 59 additions & 1 deletion application/controllers/admin/Authentication.php
Expand Up @@ -315,6 +315,12 @@ public function forgotpassword()
}
}

/**
* Check if db update is necessary and does the update
*
* @return void
* @throws Exception
*/
public static function runDbUpgrade()
{
// Check if the DB is up to date
Expand Down Expand Up @@ -374,16 +380,68 @@ private function redirectIfLoggedIn()
}

/**
* Redirect after login
* Redirect after login.
* Do a db update if any exists.
* Clean failed_email table (delete entries older then 30days)
*
* @return void
*/
private static function doRedirect()
{
self::runDbUpgrade();
self::cleanFailedEmailTable();
self::createNewFailedEmailsNotification();
$returnUrl = App()->user->getReturnUrl(array('/admin'));
Yii::app()->getController()->redirect($returnUrl);
}

/**
* Delete all entries from failed_email table which are older then 30days
*
* @return void
*/
private static function cleanFailedEmailTable()
{
$criteria = new CDbCriteria();

//filter for 'created' date comparison
$dateNow = new DateTime();

//minus 30days
$dateNow = $dateNow->sub(new DateInterval('P30D'));
$dateNowFormatted = $dateNow->format('Y-m-d H:i');

$criteria->addCondition('created < \'' . $dateNowFormatted . '\'');

FailedEmail::model()->deleteAll($criteria);
}

/**
* Checks failed_email table for entries for this user and creates a UniqueNotification
*
* @return void
*/
private static function createNewFailedEmailsNotification()
{
$failedEmailModel = new FailedEmail();
$failedEmailSurveyTitles = $failedEmailModel->getFailedEmailSurveyTitles();
if (!empty($failedEmailSurveyTitles)) {
$uniqueNotification = new UniqueNotification(
array(
'user_id' => App()->user->id,
'title' => gT('Failed e-mail notifications'),
'markAsNew' => false,
'importance' => Notification::NORMAL_IMPORTANCE,
'message' => Yii::app()->getController()->renderPartial('//failedEmail/notification_message/_notification_message', [
'failedEmailSurveyTitles' => $failedEmailSurveyTitles
], true)
)
);

$uniqueNotification->save();
}
}

/**
* Renders template(s) wrapped in header and footer
*
Expand Down

0 comments on commit 30cca2f

Please sign in to comment.