Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up bounce check and add breakdown #74 #89

Merged
merged 1 commit into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 50 additions & 27 deletions classes/check/bounces.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,62 +62,85 @@ public function get_result() : result {
[$handlebounces, $minbounces, $bounceratio] = helper::get_bounce_config();
if (empty($handlebounces)) {
$status = result::OK;
$summary = "Moodle is not configured to handle bounces.";
$summary = get_string('check:bounces:disabled', 'tool_emailutils');
$details = $summary;
} else {
$sql = "SELECT up.userid, up.value AS bouncecount, up2.value AS sendcount
$domainsql = $DB->sql_substr('LOWER(u.email)', $DB->sql_position("'@'", 'u.email') . ' + 1');
$sql = "SELECT up.userid, up.value AS bouncecount, up2.value AS sendcount, $domainsql AS domain
FROM {user_preferences} up
LEFT JOIN {user_preferences} up2 ON up2.name = 'email_send_count' AND up.userid = up2.userid
JOIN {user} u ON u.id = up.userid
WHERE up.name = 'email_bounce_count' AND CAST(up.value AS INTEGER) > :threshold";
// Start with a threshold of 1 as that was a historical default for manually created users.
$bounces = $DB->get_records_sql($sql, ['threshold' => 1]);
$bounces = $DB->get_records_sql($sql, ['threshold' => 0]);
$userswithbounces = count($bounces);

// Split bounces into 3 groups based on whether they meet bounce threshold criteria.
$overthreshold = 0;
$overbouncereq = 0;
$underbouncereq = 0;
$breakdown = [];
$total = (object) [
'overthreshold' => 0,
'overbouncereq' => 0,
'underbouncereq' => 0,
];
foreach ($bounces as $bounce) {
$bouncereq = $bounce->bouncecount >= $minbounces;
$ratioreq = !empty($bounce->sendcount) && ($bounce->bouncecount / $bounce->sendcount >= $bounceratio);
// If there's no send count due to MDL-73798, treat it as 1.
$sendcount = !empty($bounce->sendcount) ? $bounce->sendcount : 1;
$ratioreq = $bounce->bouncecount / $sendcount >= $bounceratio;

if (!isset($breakdown[$bounce->domain])) {
$breakdown[$bounce->domain] = (object) [
'domain' => $bounce->domain,
'overthreshold' => 0,
'overbouncereq' => 0,
'underbouncereq' => 0,
];
}
if ($bouncereq && $ratioreq) {
$overthreshold++;
$total->overthreshold++;
$breakdown[$bounce->domain]->overthreshold++;
} else if (!$ratioreq) {
$overbouncereq++;
$total->overbouncereq++;
$breakdown[$bounce->domain]->overbouncereq++;
} else {
$underbouncereq++;
$total->underbouncereq++;
$breakdown[$bounce->domain]->underbouncereq++;
}
}

$messages = [];
// Order breakdown by users over threshold, overbouncereq, then underbouncereq.
usort($breakdown, function ($a, $b) {
if ($a->overthreshold != $b->overthreshold) {
return $b->overthreshold - $a->overthreshold;
}
if ($a->overbouncereq != $b->overbouncereq) {
return $b->overbouncereq - $a->overbouncereq;
}
return $b->underbouncereq - $a->underbouncereq;
});

if (!$userswithbounces) {
$status = result::OK;
$summary = "No users have had emails rejected.";
} else if (!$overthreshold) {
$summary = get_string('check:bounces:none', 'tool_emailutils');
} else if (!$total->overthreshold) {
$status = result::OK;
$summary = "No users are over the Moodle bounce threshold, but $userswithbounces have had emails rejected";
$summary = get_string('check:bounces:underthreshold', 'tool_emailutils');
} else {
$status = result::WARNING;
$summary = "Found $overthreshold users over the Moodle bounce threshold";
$messages[] = "$overthreshold user(s) have at least $minbounces email rejections with a bounce ratio over $bounceratio";
}

if ($overbouncereq) {
$messages[] = "$overbouncereq user(s) have at least $minbounces email rejections with a bounce ratio under $bounceratio";
}

if ($underbouncereq) {
$allowedbounces = $minbounces - 1;
$messages[] = "$underbouncereq user(s) have between 1 and $allowedbounces email rejections";
$summary = get_string('check:bounces:overthreshold', 'tool_emailutils', [
'count' => $total->overthreshold,
'minbounces' => $minbounces,
'bounceratio' => $bounceratio,
]);
}

// Render config used for calculating threshold.
$details = $OUTPUT->render_from_template('tool_emailutils/bounce_config', [
'handlebounces' => $handlebounces,
'minbounces' => $minbounces,
'bounceratio' => $bounceratio,
'breakdown' => true,
'messages' => $messages,
'breakdown' => $breakdown,
'total' => $total,
]);
}

Expand Down
8 changes: 8 additions & 0 deletions lang/en/tool_emailutils.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
$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['bouncebreakdown'] = 'Breakdown of users with bounces';
$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.';
Expand All @@ -48,6 +49,13 @@
$string['checkdnsnoreply'] = 'DNS Email noreply shape check';
$string['checkdnspostmastertools'] = 'Check Post master tools';
$string['checkdnsspf'] = 'DNS Email SPF check';
$string['check:bounces:breakdown:overthreshold'] = 'Over threshold';
$string['check:bounces:breakdown:overminbounces'] = 'Over minbounces, under ratio';
$string['check:bounces:breakdown:underminbounces'] = 'Under minbounces';
$string['check:bounces:disabled'] = 'The site is not configured to handle bounces.';
$string['check:bounces:none'] = 'No users have recorded bounces.';
$string['check:bounces:overthreshold'] = 'Found {$a->count} users over the Moodle bounce threshold.';
$string['check:bounces:underthreshold'] = 'No users are over the Moodle bounce threshold.';
$string['complaints'] = 'For a list of complaints, search for ".c.invalid"';
$string['configmissing'] = 'Bounce handling is not enabled as $CFG->handlebounces is not set. Please review config-dist.php for more information.';
$string['dkimmanager'] = 'SPF & DKIM manager';
Expand Down
46 changes: 35 additions & 11 deletions templates/bounce_config.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,47 @@
"handlebounces": true,
"minbounces": 10,
"bounceratio": 0.2,
"breakdown": true,
"messages": []
"breakdown": [],
"total": {
"overthreshold": 5,
"overbouncereq": 3,
"underbouncereq": 2
}
}
}}
<h5>Config</h5>
<h5>{{#str}} configuration {{/str}}</h5>
<ul>
<li>handlebounces: {{handlebounces}}</li>
{{#handlebounces}}
<li>minbounces: {{minbounces}}</li>
<li>bounceratio: {{bounceratio}}</li>
{{/handlebounces}}
</ul>
{{#breakdown}}
<h5>Breakdown</h5>
<ul>
{{#messages}}
<li>{{.}}</li>
{{/messages}}
</ul>
{{/breakdown}}

<h5>{{#str}} bouncebreakdown, tool_emailutils {{/str}}</h5>
<table class="admintable generaltable table-bordered table-sm w-auto">
<thead>
<tr>
<th>{{#str}} domain, tool_emailutils {{/str}}</th>
<th class="text-end">{{#str}} check:bounces:breakdown:overthreshold, tool_emailutils{{/str}}</th>
<th class="text-end">{{#str}} check:bounces:breakdown:overminbounces, tool_emailutils{{/str}}</th>
<th class="text-end">{{#str}} check:bounces:breakdown:underminbounces, tool_emailutils{{/str}}</th>
</tr>
</thead>
<tbody>
{{#breakdown}}
<tr>
<td>{{ domain }}</td>
<td class="text-end">{{ overthreshold }}</td>
<td class="text-end">{{ overbouncereq }}</td>
<td class="text-end">{{ underbouncereq }}</td>
</tr>
{{/breakdown}}
<tr class="font-weight-bold">
<td>{{#str}} total {{/str}}</td>
<td class="text-end">{{ total.overthreshold }}</td>
<td class="text-end">{{ total.overbouncereq }}</td>
<td class="text-end">{{ total.underbouncereq }}</td>
</tr>
</tbody>
</table>
4 changes: 2 additions & 2 deletions version.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2024112801;
$plugin->release = 2024112801;
$plugin->version = 2024112900;
$plugin->release = 2024112900;
$plugin->requires = 2024042200;
$plugin->component = 'tool_emailutils';
$plugin->maturity = MATURITY_STABLE;
Expand Down
Loading