Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • project/maillog
  • issue/maillog-3199008
  • issue/maillog-3225133
  • issue/maillog-3258634
  • issue/maillog-3207944
  • issue/maillog-3259616
  • issue/maillog-3331181
  • issue/maillog-3327968
  • issue/maillog-3357920
  • issue/maillog-3036180
  • issue/maillog-2406219
  • issue/maillog-2638630
  • issue/maillog-3164867
  • issue/maillog-3391501
  • issue/maillog-1662752
  • issue/maillog-3323286
  • issue/maillog-3451177
  • issue/maillog-3176023
  • issue/maillog-3493295
19 results
Show changes
Commits on Source (8)
Maillog 8.x-1.x-dev, 2023-xx-xx
-------------------------------
#3366010 by DamienMcKenna: Optionally display message on all message logs, when
delivery disabled.
#3323286 by mably, DamienMcKenna, _pratik_, ressa, super_romeo, Promo-IL, Steven
Snedker, Martin Mayer, LeDucDuBleuet, szato, jordan.jamous: Notice:
iconv_mime_decode(): Detected an illegal character in input string in
Maillog->mail().
#1662752 by Eduardo Morales Alberti, pluess, joe-b, miro_dietiker, seanr,
DamienMcKenna, Maedi: Maillog log table size escalation.
#1662752 by DamienMcKenna: Minor code cleanup.
#3323286 by mably, DamienMcKenna: Handle subjects that are MIME encoded.
#3391501 by malcomio, DamienMcKenna: Drush integration to clear the maillog
table.
#3451177 by Project Update Bot, damienmckenna: Automated Drupal 11
compatibility fixes.
Maillog 8.x-1.1, 2023-06-01
---------------------------
#3327968 by Charchil Khandelwal, samit.310@gmail.com, Serhii Shandaliuk,
......
......@@ -15,6 +15,7 @@ sent through the site.
installed it is possible to control which of the installed email modules will
be used to send messages from Maillog's settings page, mirroring MailSystem's
functionality.
* Drush integration with the `maillog:clear` command to purge the log table.
## Configuration
......
send: true
nosend_notify: false
log: true
log_notify: false
verbose: true
body_trimmed: false
base64_remove: false
cron_enabled: false
keep_limit_type: time_to_keep
time_to_keep: null
number_to_keep: null
......@@ -4,9 +4,33 @@ maillog.settings:
send:
type: boolean
label: Send emails
nosend_notify:
type: boolean
label: Notify visitor that emails are not being sent
log:
type: boolean
label: Log emails
log_notify:
type: boolean
label: Notify visitor that emails are being logged
verbose:
type: boolean
label: Display mails
body_trimmed:
type: boolean
label: 'Body trimmed'
base64_remove:
type: boolean
label: 'Remove base64 from body'
cron_enabled:
type: boolean
label: 'Enable the cron'
keep_limit_type:
type: string
label: 'Keep backup type'
time_to_keep:
type: integer
label: 'The time to keep the backups in days'
number_to_keep:
type: integer
label: 'Number of backups to keep'
services:
maillog.commands:
class: Drupal\maillog\Commands\MaillogCommands
arguments:
- '@database'
tags:
- { name: drush.command }
name: 'Maillog / Mail Developer'
description: 'Utility to log all Mails for debugging purposes. It is possible to suppress mail delivery for e.g. dev or staging systems.'
package: 'Mail'
core_version_requirement: ^9 || ^10
core_version_requirement: ^9 || ^10 || ^11
type: module
dependencies:
......
......@@ -26,3 +26,37 @@ function maillog_help($route_name, RouteMatchInterface $route_match) {
return $output;
}
}
/**
* Implements hook_cron().
*/
function maillog_cron() {
$config = \Drupal::config('maillog.settings');
$logger = \Drupal::logger('maillog');
if ($config->get('cron_enabled')) {
$type = $config->get('keep_limit_type') ?? '';
$limit = $config->get($type) ?? 0;
$deleted_logs = 0;
try {
$deleted_logs = \Drupal::service('maillog.cleaner')
->clearMaillogs($type, (int) $limit);
}
catch (\Exception $e) {
$logger->error(t('Exception when tried to delete the mail logs, reason: @reason', [
'@reason' => $e->getMessage(),
]));
}
if (empty($deleted_logs)) {
$logger->info(t('Any log deleted because any log meet the conditions'));
}
else {
$logger->info(t('Deleted @count mail logs from database', [
'@count' => $deleted_logs,
]));
}
}
}
services:
maillog.cleaner:
class: Drupal\maillog\MailLogCleaner
arguments: [ '@database' ]
<?php
namespace Drupal\maillog\Commands;
use Drupal\Core\Database\Connection;
use Drush\Commands\DrushCommands;
class MaillogCommands extends DrushCommands {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected Connection $database;
/**
* Constructs a MaillogCommands object.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* Clear maillog entries
*
* @command maillog:clear
*/
public function clear() {
$this->database->truncate('maillog')->execute();
$this->io()->info('All maillog entries have been deleted.');
}
}
......@@ -45,16 +45,29 @@ class MaillogSettingsForm extends ConfigFormBase {
$form['maillog_send'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow the e-mails to be sent.'),
'#title' => $this->t('Allow the e-mails to be delivered.'),
'#default_value' => $config->get('send'),
];
$form['maillog_nosend_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify non-admin users that delivery is disabled?'),
'#description' => $this->t('Normally if the visitor does not have the "Administer Maillog" permission they will not know if email delivery is disabled because it includes a link to this settings page; enabling this option will display a message for visitosr who do not have this permission.'),
'#default_value' => $config->get('nosend_notify') ?? FALSE,
];
$form['maillog_log'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create table entries in maillog table for each e-mail.'),
'#default_value' => $config->get('log'),
];
$form['maillog_log_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify visitors that email was logged?'),
'#default_value' => $config->get('log_notify') ?? FALSE,
];
$form['maillog_verbose'] = [
'#type' => 'checkbox',
'#title' => $this->t('Display the e-mails on page.'),
......@@ -62,6 +75,82 @@ class MaillogSettingsForm extends ConfigFormBase {
'#description' => $this->t('If enabled, anonymous users with permissions will see any verbose output mail.'),
];
$form['maillog_body_trimmed'] = [
'#type' => 'checkbox',
'#title' => $this->t('Body trimmed'),
'#description' => $this->t('Since mail bodies can be large, storing the whole mail body can bloat your database. <br> Check it to store a short body (just the first 512 characters).'),
'#default_value' => $config->get('body_trimmed'),
];
$form['maillog_base64'] = [
'#type' => 'checkbox',
'#title' => $this->t('Remove base64 from body'),
'#default_value' => $config->get('base64_remove'),
'#description' => $this->t('If enabled, all base64 will be deleted from the body.'),
];
$keep_options = $config->get('keep_limit_type');
$form['cron'] = [
'#type' => 'fieldset',
];
$form['cron']['cron_enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable cron'),
'#default_value' => $config->get('cron_enabled'),
];
$form['cron']['keep_limit_type'] = [
'#type' => 'select',
'#title' => $this->t('Keep maillog options'),
'#default_value' => $keep_options,
'#states' => [
'visible' => [
'input[name="cron_enabled"]' => ['checked' => TRUE],
],
'required' => [
'input[name="cron_enabled"]' => ['checked' => TRUE],
],
],
'#options' => [
'number_to_keep' => $this->t('Number of mail logs to keep'),
'time_to_keep' => $this->t('Time limit to keep mail logs'),
],
];
$form['cron']['number_to_keep'] = [
'#type' => 'number',
'#title' => $this->t('Number of mail logs to keep.'),
'#default_value' => $config->get('number_to_keep'),
'#states' => [
'visible' => [
'input[name="cron_enabled"]' => ['checked' => TRUE],
'select[name="keep_limit_type"]' => ['value' => 'number_to_keep'],
],
'required' => [
'input[name="cron_enabled"]' => ['checked' => TRUE],
'select[name="keep_limit_type"]' => ['value' => 'number_to_keep'],
],
],
];
$form['cron']['time_to_keep'] = [
'#type' => 'number',
'#title' => $this->t('Time to keep the mail logs in days.'),
'#default_value' => $config->get('time_to_keep'),
'#states' => [
'visible' => [
'input[name="cron_enabled"]' => ['checked' => TRUE],
'select[name="keep_limit_type"]' => ['value' => 'time_to_keep'],
],
'required' => [
'input[name="cron_enabled"]' => ['checked' => TRUE],
'select[name="keep_limit_type"]' => ['value' => 'time_to_keep'],
],
],
];
return parent::buildForm($form, $form_state);
}
......@@ -71,8 +160,17 @@ class MaillogSettingsForm extends ConfigFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('maillog.settings')
->set('send', $form_state->getValue('maillog_send'))
->set('nosend_notify', $form_state->getValue('maillog_nosend_notify'))
->set('log', $form_state->getValue('maillog_log'))
->set('verbose', $form_state->getValue('maillog_verbose'))->save();
->set('log_notify', $form_state->getValue('maillog_log_notify'))
->set('verbose', $form_state->getValue('maillog_verbose'))
->set('body_trimmed', $form_state->getValue('maillog_body_trimmed'))
->set('base64_remove', $form_state->getValue('maillog_base64'))
->set('cron_enabled', $form_state->getValue('cron_enabled'))
->set('keep_limit_type', $form_state->getValue('keep_limit_type'))
->set('number_to_keep', $form_state->getValue('number_to_keep'))
->set('time_to_keep', $form_state->getValue('time_to_keep'))
->save();
parent::submitForm($form, $form_state);
......
<?php
namespace Drupal\maillog;
use Drupal\Core\Database\Connection;
/**
* Log cleaner.
*/
class MailLogCleaner {
/**
* Database.
*
* @var \Drupal\Core\Database\Connection
*/
private Connection $database;
/**
* Add the dependencies.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* Clear the mail logs from database.
*/
public function clearMaillogs(string $type, int $limit) {
$deleted = 0;
switch ($type) {
case 'time_to_keep':
$time_limit = strtotime('-' . ($limit + 1) . 'days');
$deleted = $this->database->delete('maillog')
->condition('sent_date', $time_limit, '<')
->execute();
break;
case 'number_to_keep':
$min_row = $this->database->select('maillog', 'm')
->fields('m', ['id'])
->orderBy('id', 'DESC')
->range($limit - 1, 1)
->execute()->fetchField();
if ($min_row) {
$deleted = $this->database->delete('maillog')
->condition('id', $min_row, '<')
->execute();
}
break;
}
return $deleted;
}
}
......@@ -41,12 +41,26 @@ class Maillog implements MailInterface {
// In case the subject/from/to is already encoded, decode with
// iconv_mime_decode().
$record->header_message_id = $message['headers']['Message-ID'] ?? $this->t('Not delivered');
$record->subject = $message['subject'];
$record->subject = mb_substr(iconv_mime_decode($record->subject), 0, 255);
$record->body = $message['body'];
$subject = $message['subject'];
if ($this->isMIMEEncoded($subject)) {
$subject = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR);
}
$record->subject = mb_substr($subject, 0, 255);
$record->header_from = $message['from'] ?? NULL;
$record->header_from = iconv_mime_decode($record->header_from);
// Compile the body text for the log.
$body = $message['body'];
if ($config->get('base64_remove')) {
$re = '/"data:[\w]+\/[\w]+;base64,.*"/m';
$str_no_brl = str_replace(PHP_EOL, '', $body);
$body = preg_replace($re, '"removed by maillog"', $str_no_brl);
}
if ($config->get('body_trimmed')) {
$body = mb_substr($body, 0, 512) . '...trimmed';
}
$record->body = $body;
$header_to = [];
if (isset($message['to'])) {
if (is_array($message['to'])) {
......@@ -67,11 +81,16 @@ class Maillog implements MailInterface {
Database::getConnection()->insert('maillog')
->fields((array) $record)
->execute();
// If the "log notify" option is enabled, inform the visitor that the
// email was logged.
if ($config->get('log_notify')) {
\Drupal::messenger()->addStatus($this->t('An email was logged.'), TRUE);
}
}
// Display the email if the verbose is enabled.
if ($config->get('verbose') && \Drupal::currentUser()->hasPermission('view maillog')) {
// Print the message.
$header_output = print_r($message['headers'], TRUE);
$output = $this->t('A mail has been sent: <br/> [Subject] => @subject <br/> [From] => @from <br/> [To] => @to <br/> [Reply-To] => @reply <br/> <pre> [Header] => @header <br/> [Body] => @body </pre>', [
......@@ -94,10 +113,26 @@ class Maillog implements MailInterface {
\Drupal::messenger()->addWarning($message, TRUE);
}
elseif ($config->get('nosend_notify')) {
\Drupal::messenger()->addStatus($this->t('Email delivery is currently disabled and the site attempted to deliver an email.'), TRUE);
}
else {
\Drupal::logger('maillog')->notice('Attempted to send an email, but sending emails is disabled.');
}
return $result ?? TRUE;
}
/**
* Determine if a given string is MIME encoded.
*
* @param string $string
* The string to check.
*
* @return bool
* Whether the string is MIME encoded.
*/
protected function isMIMEEncoded($string) {
return preg_match('/^=\?.+\?.\?.+\?=$/u', $string) === 1;
}
}
......@@ -2,5 +2,5 @@ name: 'Maillog test'
description: 'Contains helper logic for the Maillog tests.'
package: 'Mail'
type: module
core_version_requirement: ^9 || ^10
core_version_requirement: ^9 || ^10 || ^11
hidden: TRUE
<?php
namespace Drupal\Tests\maillog\Functional;
use Drupal\maillog\Plugin\Mail\Maillog;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the maillog clean.
*
* @group maillog
*/
class MailCleanTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['maillog', 'user', 'system', 'views'];
/**
* Define the default theme used for all tests.
*
* @var string
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Use the maillog mail plugin.
$this->config('system.mail')->set('interface.default', 'maillog')->save();
// The system.site.mail setting goes into the From header of outgoing mails.
$this->config('system.site')->set('mail', 'simpletest@example.com')->save();
// Disable e-mail sending.
$this->config('maillog.settings')
->set('send', FALSE)
->save();
}
/**
* Test body trimmed.
*/
public function testBodyTrimmed() {
$this->config('maillog.settings')
->set('body_trimmed', TRUE)
->save();
$mail = \Drupal::service('plugin.manager.mail')->mail('maillog', 'ui_test', "test+trimmed@example.com", \Drupal::languageManager()->getCurrentLanguage(), [], 'me@example.com', FALSE);
$mail['subject'] = 'This is a test subject.';
$mail['body'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed magna mauris, varius ut arcu vitae, varius cursus lorem. Curabitur consequat neque lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras id elit dui. Donec lobortis ante non risus tempor, in vehicula velit molestie. Maecenas egestas ornare arcu, eu euismod ipsum elementum et. Pellentesque tempor nibh lorem, non pellentesque ante mollis sit amet. Maecenas ullamcorper viverra luctus. Aliquam iaculis, libero sed congue blandit, justo nulla pulvinar orci, vitae tempus velit justo consectetur velit. Fusce convallis augue sem, at egestas neque consequat accumsan. Suspendisse aliquam tristique metus, non blandit orci porttitor quis. Nullam vitae arcu erat. Nam eu pretium felis, at mollis tellus. Donec vel ex et velit hendrerit malesuada sit amet ut leo. Donec vel imperdiet nisl, vitae viverra turpis. Nam dignissim, risus at pellentesque euismod, eros metus tristique massa, eget placerat.';
// Send the prepared email.
$sender = new Maillog();
$sender->mail($mail);
// Create a user with valid permissions and go to the maillog overview page.
$permissions = [
'administer maillog',
'view maillog',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
$this->drupalGet('admin/reports/maillog');
$this->assertSession()->statusCodeEquals(200);
// Assert some values and click the subject link.
$this->assertSession()->pageTextContains('simpletest@example.com');
$this->assertSession()->pageTextContains("test+trimmed@example.com");
$this->rebuildContainer();
$query = \Drupal::database()->select('maillog', 'm');
$body = $query->fields('m', ['body'])
->execute()
->fetchField();
$length_trim = 512 + strlen('...trimmed');
$this->assertEquals($length_trim, strlen($body));
$this->assertStringContainsString('iaculis, lib...trimmed', $body);
}
/**
* Test body base 64 remove.
*/
public function testBodyBase64() {
$this->config('maillog.settings')
->set('base64_remove', TRUE)
->save();
$mail = \Drupal::service('plugin.manager.mail')->mail('maillog', 'ui_test', "test+base64@example.com", \Drupal::languageManager()->getCurrentLanguage(), [], 'me@example.com', FALSE);
$mail['subject'] = 'This is a test subject.';
$mail['body'] = 'This message is a test <img src="data:image/jpeg;base64,SGVsbG8gV29ybGQ=" /> email body.';
// Send the prepared email.
$sender = new Maillog();
$sender->mail($mail);
// Create a user with valid permissions and go to the maillog overview page.
$permissions = [
'administer maillog',
'view maillog',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
$this->drupalGet('admin/reports/maillog');
$this->assertSession()->statusCodeEquals(200);
// Assert some values and click the subject link.
$this->assertSession()->pageTextContains('simpletest@example.com');
$this->assertSession()->pageTextContains("test+base64@example.com");
$this->rebuildContainer();
$query = \Drupal::database()->select('maillog', 'm');
$body = $query->fields('m', ['body'])
->execute()
->fetchField();
$this->assertStringContainsString('This message is a test <img src="removed by maillog" /> email body.', $body);
}
/**
* Test keep logs by limit number of logs.
*/
public function testNumberToKeep() {
$keep = 5;
$this->config('maillog.settings')
->set('cron_enabled', TRUE)
->set('keep_limit_type', 'number_to_keep')
->set('number_to_keep', $keep)
->save();
$limit = 10;
$this->generateMails($limit);
// Create a user with valid permissions and go to the maillog overview page.
$permissions = [
'administer maillog',
'view maillog',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
$this->drupalGet('admin/reports/maillog');
$this->assertSession()->statusCodeEquals(200);
// Assert some values and click the subject link.
$this->assertSession()->pageTextContains('simpletest@example.com');
$count = 1;
while ($count <= $limit) {
$this->assertSession()->pageTextContains("test+$count@example.com");
$count++;
}
// Test clear log.
maillog_cron();
$this->drupalGet('admin/reports/maillog');
$this->assertSession()->statusCodeEquals(200);
$count = 1;
// Older test should be removed.
while ($count <= $keep) {
$this->assertSession()->pageTextNotContains("test+$count@example.com");
$count++;
}
$count = 1;
$limit_deleted = $limit;
// Newer test should be kept.
while ($count <= ($keep - $limit)) {
$this->assertSession()->pageTextContains("test+$limit_deleted@example.com");
$limit_deleted--;
$count++;
}
}
/**
* Test keep logs by limit date days.
*/
public function testDateLimitKeep() {
$keep = 5;
$this->config('maillog.settings')
->set('cron_enabled', TRUE)
->set('keep_limit_type', 'time_to_keep')
->set('time_to_keep', $keep)
->save();
$limit = 10;
$this->generateMails($limit);
$this->updateDateMails($limit);
// Create a user with valid permissions and go to the maillog overview page.
$permissions = [
'administer maillog',
'view maillog',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
$this->drupalGet('admin/reports/maillog');
$this->assertSession()->statusCodeEquals(200);
// Assert some values and click the subject link.
$this->assertSession()->pageTextContains('simpletest@example.com');
$count = 1;
while ($count <= $limit) {
$this->assertSession()->pageTextContains("test+$count@example.com");
$count++;
}
// Test clear log.
maillog_cron();
$this->drupalGet('admin/reports/maillog');
$this->assertSession()->statusCodeEquals(200);
$count = 1;
// Older test should be removed.
while ($count <= $keep) {
$this->assertSession()->pageTextNotContains("test+$count@example.com");
$count++;
}
$count = 1;
$limit_deleted = $limit;
// Newer test should be kept.
while ($count <= ($limit - $keep)) {
$this->assertSession()->pageTextContains("test+$limit_deleted@example.com");
$limit_deleted--;
$count++;
}
}
/**
* Generate demo emails.
*/
private function generateMails(int $limit) {
$count = 1;
while ($count <= $limit) {
$mail = \Drupal::service('plugin.manager.mail')->mail('maillog', 'ui_test', "test+$count@example.com", \Drupal::languageManager()->getCurrentLanguage(), [], 'me@example.com', FALSE);
$mail['subject'] = 'This is a test subject.';
$mail['body'] = 'This message is a test email body.';
// Send the prepared email.
$sender = new Maillog();
$sender->mail($mail);
$count++;
}
}
/**
* Update dates logs.
*/
private function updateDateMails(int $limit) {
$this->rebuildContainer();
$query = \Drupal::database()->update('maillog');
// It adds a date to each log on a different day based on the id.
$query->expression('sent_date', "(UNIX_TIMESTAMP() - (86400 * ($limit + 1))) + (86400 * id)");
$query->execute();
}
}