From 86ff4f7bdc34b94cbfba11408e6e9cdbfa61846d Mon Sep 17 00:00:00 2001 From: Benjamin Walker Date: Thu, 14 Nov 2024 11:43:06 +1000 Subject: [PATCH] Change suppression list download to report #55 --- aws/suppression_list.php | 64 ++++--- .../local/entities/suppressed_email.php | 171 ++++++++++++++++++ .../local/systemreports/suppression_list.php | 107 +++++++++++ classes/suppression_list.php | 60 ------ classes/task/update_suppression_list.php | 2 +- db/install.xml | 5 +- db/upgrade.php | 15 ++ lang/en/tool_emailutils.php | 10 +- settings.php | 12 +- tests/suppressionlist_test.php | 12 -- version.php | 4 +- 11 files changed, 345 insertions(+), 117 deletions(-) create mode 100644 classes/reportbuilder/local/entities/suppressed_email.php create mode 100644 classes/reportbuilder/local/systemreports/suppression_list.php delete mode 100644 classes/suppression_list.php diff --git a/aws/suppression_list.php b/aws/suppression_list.php index ce22c9e..274acbc 100644 --- a/aws/suppression_list.php +++ b/aws/suppression_list.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Email suppression list download page. + * Email suppression list report page. * * @package tool_emailutils * @copyright 2024 onwards Catalyst IT {@link http://www.catalyst-eu.net/} @@ -23,37 +23,41 @@ * @author Waleed ul hassan */ -require_once(__DIR__ . '/../../../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); +use core_reportbuilder\system_report_factory; +use tool_emailutils\reportbuilder\local\systemreports\suppression_list; -// Ensure the user is logged in and has the necessary permissions. -require_login(); -require_capability('moodle/site:config', context_system::instance()); +require(__DIR__.'/../../../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); -$action = optional_param('action', '', PARAM_ALPHA); +admin_externalpage_setup('tool_emailutils_bounces', '', [], '', ['pagelayout' => 'report']); -if ($action === 'download') { - $content = \tool_emailutils\suppression_list::generate_csv(); - $filename = 'email_suppression_list_' . date('Y-m-d') . '.csv'; - header('Content-Type: text/csv'); - header('Content-Disposition: attachment; filename="' . $filename . '"'); - header('Content-Length: ' . strlen($content)); - echo $content; - exit; +echo $OUTPUT->header(); +echo $OUTPUT->heading(get_string('aws_suppressionlist', 'tool_emailutils')); + +// Add warnings if the suppression list hasn't been set up or run recently. +if (empty(get_config('tool_emailutils', 'enable_suppression_list'))) { + echo $OUTPUT->notification(get_string('aws_suppressionlist_disabled', 'tool_emailutils'), 'warning'); } else { - // Display the download page. - admin_externalpage_setup('toolemailutilssuppressionlist'); - echo $OUTPUT->header(); - echo $OUTPUT->heading(get_string('suppressionlist', 'tool_emailutils')); - - echo html_writer::start_tag('div', ['class' => 'suppressionlist-download']); - echo html_writer::tag('p', get_string('suppressionlistdesc', 'tool_emailutils')); - echo html_writer::link( - new moodle_url('/admin/tool/emailutils/aws/suppression_list.php', ['action' => 'download']), - get_string('downloadsuppressionlist', 'tool_emailutils'), - ['class' => 'btn btn-primary'] - ); - echo html_writer::end_tag('div'); - - echo $OUTPUT->footer(); + // Check if the update task has run recently. + $task = \core\task\manager::get_scheduled_task('\tool_emailutils\task\update_suppression_list'); + if ($task && $task->is_enabled()) { + $lastrun = $task->get_last_run_time(); + if (empty($lastrun)) { + echo $OUTPUT->notification(get_string('aws_suppressionlist_tasknever', 'tool_emailutils'), 'warning'); + } else if ($lastrun < (time() - DAYSECS)) { + echo $OUTPUT->notification(get_string('aws_suppressionlist_taskupdated', 'tool_emailutils', format_time(time() - $lastrun)), 'warning'); + } else { + // Always show the last update time as info. + echo $OUTPUT->notification(get_string('aws_suppressionlist_taskupdated', 'tool_emailutils', format_time(time() - $lastrun)), 'info'); + } + } else { + echo $OUTPUT->notification(get_string('aws_suppressionlist_taskdisabled', 'tool_emailutils'), 'warning'); + } } + +$report = system_report_factory::create(suppression_list::class, context_system::instance()); + +echo $report->output(); + +echo $OUTPUT->footer(); + diff --git a/classes/reportbuilder/local/entities/suppressed_email.php b/classes/reportbuilder/local/entities/suppressed_email.php new file mode 100644 index 0000000..cb019d2 --- /dev/null +++ b/classes/reportbuilder/local/entities/suppressed_email.php @@ -0,0 +1,171 @@ +. + +namespace tool_emailutils\reportbuilder\local\entities; + +use lang_string; +use core_reportbuilder\local\entities\base; +use core_reportbuilder\local\filters\date; +use core_reportbuilder\local\filters\text; +use core_reportbuilder\local\helpers\format; +use core_reportbuilder\local\report\column; +use core_reportbuilder\local\report\filter; + +/** + * SES account level suppression list entity class class implementation. + * + * Defines all the columns and filters that can be added to reports that use this entity. + * + * @package tool_emailutils + * @author Benjamin Walker + * @copyright Catalyst IT 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + */ +class suppressed_email extends base { + + /** + * Database tables that this entity uses + * + * @return string[] + */ + protected function get_default_tables(): array { + return [ + 'tool_emailutils_suppression', + ]; + } + + /** + * The default title for this entity + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('aws_suppressionlist', 'tool_emailutils'); + } + + /** + * Initialize the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this->add_filter($filter); + $this->add_condition($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $tablealias = $this->get_table_alias('tool_emailutils_suppression'); + + // Email column. + $columns[] = (new column( + 'email', + new lang_string('email'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.email") + ->set_is_sortable(true); + + // Reason column. + $columns[] = (new column( + 'reason', + new lang_string('suppressionreason', 'tool_emailutils'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.reason") + ->set_is_sortable(true); + + // Created at column. + $columns[] = (new column( + 'created', + new lang_string('suppressioncreated', 'tool_emailutils'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TIMESTAMP) + ->add_fields("{$tablealias}.created_at") + ->set_is_sortable(true) + ->add_callback([format::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig')); + + return $columns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + $tablealias = $this->get_table_alias('tool_emailutils_suppression'); + + // Email filter. + $filters[] = (new filter( + text::class, + 'email', + new lang_string('email'), + $this->get_entity_name(), + "{$tablealias}.email" + )) + ->add_joins($this->get_joins()); + + // Reason filter. + $filters[] = (new filter( + text::class, + 'reason', + new lang_string('suppressionreason', 'tool_emailutils'), + $this->get_entity_name(), + "{$tablealias}.reason" + )) + ->add_joins($this->get_joins()); + + // Created at filter. + $filters[] = (new filter( + date::class, + 'created', + new lang_string('suppressioncreated', 'tool_emailutils'), + $this->get_entity_name(), + "{$tablealias}.created" + )) + ->add_joins($this->get_joins()) + ->set_limited_operators([ + date::DATE_ANY, + date::DATE_RANGE, + date::DATE_PREVIOUS, + date::DATE_CURRENT, + ]); + + return $filters; + } +} diff --git a/classes/reportbuilder/local/systemreports/suppression_list.php b/classes/reportbuilder/local/systemreports/suppression_list.php new file mode 100644 index 0000000..0481d7e --- /dev/null +++ b/classes/reportbuilder/local/systemreports/suppression_list.php @@ -0,0 +1,107 @@ +. + +namespace tool_emailutils\reportbuilder\local\systemreports; + +use context_system; +use tool_emailutils\reportbuilder\local\entities\suppressed_email; +use core_reportbuilder\system_report; +use core_reportbuilder\local\entities\user; + +/** + * SES account level suppression list class implementation. + * + * @package tool_emailutils + * @author Benjamin Walker + * @copyright Catalyst IT 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + */ +class suppression_list extends system_report { + + /** + * Initialise report, we need to set the main table, load our entities and set columns/filters + */ + protected function initialise(): void { + // Our main entity, it contains all of the column definitions that we need. + $entitymain = new suppressed_email(); + $entitymainalias = $entitymain->get_table_alias('tool_emailutils_suppression'); + + $this->set_main_table('tool_emailutils_suppression', $entitymainalias); + $this->add_entity($entitymain); + + // We can join the "user" entity to our "main" entity using standard SQL JOIN. + $entityuser = new user(); + $entityuseralias = $entityuser->get_table_alias('user'); + $this->add_entity($entityuser + ->add_join("LEFT JOIN {user} {$entityuseralias} ON {$entityuseralias}.email = {$entitymainalias}.email") + ); + + // Now we can call our helper methods to add the content we want to include in the report. + $this->add_columns(); + $this->add_filters(); + + // Set if report can be downloaded. + $this->set_downloadable(true, 'email_suppression_list_' . date('Y-m-d')); + } + + /** + * Validates access to view this report + * + * @return bool + */ + protected function can_view(): bool { + return has_capability('moodle/site:config', context_system::instance()); + } + + /** + * Adds the columns we want to display in the report + * + * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their + * unique identifier + */ + protected function add_columns(): void { + $columns = [ + 'suppressed_email:email', + 'suppressed_email:reason', + 'suppressed_email:created', + 'user:fullnamewithlink', + ]; + + $this->add_columns_from_entities($columns); + + // Default sorting. + $this->set_initial_sort_column('suppressed_email:created', SORT_DESC); + } + + /** + * Adds the filters we want to display in the report + * + * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their + * unique identifier + */ + protected function add_filters(): void { + $filters = [ + 'suppressed_email:email', + 'suppressed_email:reason', + 'suppressed_email:created', + 'user:fullname', + 'user:username', + ]; + + $this->add_filters_from_entities($filters); + } +} diff --git a/classes/suppression_list.php b/classes/suppression_list.php deleted file mode 100644 index 38dde3e..0000000 --- a/classes/suppression_list.php +++ /dev/null @@ -1,60 +0,0 @@ -. - -namespace tool_emailutils; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/csvlib.class.php'); - -/** - * Class for handling suppression list operations. - * - * @package tool_emailutils - * @copyright 2024 onwards Catalyst IT {@link http://www.catalyst-eu.net/} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class suppression_list { - - /** - * Generate CSV content for the suppression list. - * - * @return string The CSV content. - * @throws \dml_exception - */ - public static function generate_csv(): string { - global $DB; - - $csvexport = new \csv_export_writer('comma'); - - // Add CSV headers. - $csvexport->add_data(['Email', 'Reason', 'Created At']); - - // Fetch suppression list from database. - $suppressionlist = $DB->get_records('tool_emailutils_suppression'); - - // Add suppression list data to CSV. - foreach ($suppressionlist as $item) { - $csvexport->add_data([ - $item->email, - $item->reason, - $item->created_at, - ]); - } - - return $csvexport->print_csv_data(true); - } -} diff --git a/classes/task/update_suppression_list.php b/classes/task/update_suppression_list.php index f4a6cfc..104bd2c 100644 --- a/classes/task/update_suppression_list.php +++ b/classes/task/update_suppression_list.php @@ -110,7 +110,7 @@ protected function fetch_aws_ses_suppression_list(): array { $suppressionlist[] = [ 'email' => $item['EmailAddress'], 'reason' => $item['Reason'], - 'created_at' => $item['LastUpdateTime']->format('Y-m-d H:i:s'), + 'created_at' => $item['LastUpdateTime']->getTimestamp(), ]; } $params['NextToken'] = $result['NextToken'] ?? null; diff --git a/db/install.xml b/db/install.xml index d640f8a..3d98ded 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -37,8 +37,7 @@ - + diff --git a/db/upgrade.php b/db/upgrade.php index 15e4e93..2fbeefe 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -63,5 +63,20 @@ function xmldb_tool_emailutils_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024100101, 'tool', 'emailutils'); } + if ($oldversion < 2024111800) { + + // The stored timestamps have lost timezones. These are replaced daily so easier to just remove instead of fix. + $DB->delete_records('tool_emailutils_suppression'); + + // Changing type of field created_at on table tool_emailutils_suppression to int. + $table = new xmldb_table('tool_emailutils_suppression'); + $field = new xmldb_field('created_at', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'reason'); + + // Launch change of type for field created_at. + $dbman->change_field_type($table, $field); + + // Emailutils savepoint reached. + upgrade_plugin_savepoint(true, 2024111800, 'tool', 'emailutils'); + } return true; } diff --git a/lang/en/tool_emailutils.php b/lang/en/tool_emailutils.php index 01cf50f..64712be 100644 --- a/lang/en/tool_emailutils.php +++ b/lang/en/tool_emailutils.php @@ -30,6 +30,11 @@ $string['aws_region_desc'] = 'The AWS region for your SES service'; $string['aws_secret'] = 'AWS Secret Key'; $string['aws_secret_desc'] = 'Your AWS Secret Access Key'; +$string['aws_suppressionlist'] = 'AWS suppression list'; +$string['aws_suppressionlist_disabled'] = 'Updating of the account level AWS suppression list is currently disabled. This can be enabled by changing enable_suppression_list in AWS SES settings.'; +$string['aws_suppressionlist_taskdisabled'] = 'The scheduled task to update this list is disabled.'; +$string['aws_suppressionlist_tasknever'] = 'The scheduled task to update this list has never been run successfully.'; +$string['aws_suppressionlist_taskupdated'] = 'The scheduled task to update this list was last run {$a} ago.'; $string['bouncecheckfull'] = 'Are you absolutely sure you want to reset the bounce count for {$a} ?'; $string['bouncecount'] = 'Bounce count'; $string['bounceconfig'] = 'Bounce handling is enabled. Emails will not be sent to addresses with over {$a->minbounces} bounces and a bounce ratio above {$a->bounceratio}. These values can be changed in config.php.'; @@ -64,7 +69,6 @@ '; $string['domain'] = 'Domain'; $string['domaindefaultnoreply'] = 'Default noreply'; -$string['downloadsuppressionlist'] = 'Download Suppression List'; $string['enabled'] = 'Enabled'; $string['enabled_help'] = 'Allow the plugin to process incoming messages'; $string['enable_suppression_list'] = 'Enable email suppression list'; @@ -107,8 +111,8 @@ $string['selectornotblank'] = 'Selector cannot be empty'; $string['sendcount'] = 'Send count'; $string['settings'] = 'AWS SES settings'; -$string['suppressionlist'] = 'Email Suppression List'; -$string['suppressionlistdesc'] = 'Download the email suppression list from AWS SES for your account.'; +$string['suppressionreason'] = 'Reason'; +$string['suppressioncreated'] = 'Created at'; $string['task_update_suppression_list'] = 'Update email suppression list'; $string['username'] = 'Username'; $string['username_help'] = 'HTTP Basic Auth Username'; diff --git a/settings.php b/settings.php index a4e1a04..0435870 100644 --- a/settings.php +++ b/settings.php @@ -115,12 +115,6 @@ '') ); - $ADMIN->add('tool_emailutils', new admin_externalpage( - 'toolemailutilssuppressionlist', - new lang_string('suppressionlist', 'tool_emailutils'), - new moodle_url('/admin/tool/emailutils/aws/suppression_list.php') - )); - // Add AWS credentials settings. $settings->add(new admin_setting_configtext( @@ -145,4 +139,10 @@ ); $ADMIN->add('tool_emailutils', $settings); + + $ADMIN->add('tool_emailutils', new admin_externalpage( + 'tool_emailutils_suppressionlist', + new lang_string('aws_suppressionlist', 'tool_emailutils'), + new moodle_url('/admin/tool/emailutils/aws/suppression_list.php') + )); } diff --git a/tests/suppressionlist_test.php b/tests/suppressionlist_test.php index bb8678c..8b22f34 100644 --- a/tests/suppressionlist_test.php +++ b/tests/suppressionlist_test.php @@ -83,7 +83,6 @@ protected function setup_test_environment(bool $enablefeature): \tool_emailutils * 4. A CSV file is generated with the correct headers and content matching the database. * * @covers \tool_emailutils\task\update_suppression_list::execute - * @covers \tool_emailutils\suppression_list::generate_csv * * @return void * @throws \dml_exception @@ -119,17 +118,6 @@ public function test_suppression_list_update_and_export(): void { } $this->assertTrue($foundtest1, 'test1@example.com not found in the database'); $this->assertTrue($foundtest2, 'test2@example.com not found in the database'); - - // Now test the CSV file generation. - $csvcontent = \tool_emailutils\suppression_list::generate_csv(); - - // Verify the CSV content. - $lines = explode("\n", trim($csvcontent)); - $this->assertEquals('Email,Reason,"Created At"', $lines[0]); - $this->assertStringContainsString('test1@example.com', $lines[1]); - $this->assertStringContainsString('BOUNCE', $lines[1]); - $this->assertStringContainsString('test2@example.com', $lines[2]); - $this->assertStringContainsString('COMPLAINT', $lines[2]); } /** diff --git a/version.php b/version.php index 8535166..140b5a5 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024111400; -$plugin->release = 2024111400; +$plugin->version = 2024111800; +$plugin->release = 2024111800; $plugin->requires = 2024042200; $plugin->component = 'tool_emailutils'; $plugin->maturity = MATURITY_STABLE;