Improved password handling on profile page

This commit is contained in:
Adrian Stephens 2026-06-29 06:51:57 +01:00
parent be8139659f
commit 8bc9e566db
3 changed files with 99 additions and 6 deletions

View File

@ -3,7 +3,7 @@
* Plugin Name: FECA Mailshots
* Plugin URI: https://fenedge.co.uk/
* Description: FECA mailshots plugin.
* Version: 1.1.8
* Version: 1.1.10
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: FECA

View File

@ -55,6 +55,9 @@ trait AdminRequestHelpers
. '.feca-mailshots-admin .feca-form .form-table th,.feca-mailshots-admin .feca-form .form-table td{padding-top:12px;padding-bottom:12px;}'
. '.feca-mailshots-admin .feca-form .form-table th{width:220px;}'
. '.feca-mailshots-admin .feca-form .description{margin-top:var(--feca-space-1);}'
. '.feca-mailshots-admin .feca-password-control{display:flex;align-items:center;gap:8px;max-width:34rem;}'
. '.feca-mailshots-admin .feca-password-control .regular-text{flex:1;min-width:220px;margin:0;}'
. '.feca-mailshots-admin .feca-password-toggle{min-width:74px;text-align:center;}'
. '.feca-mailshots-admin .feca-inline-form{display:inline;}'
. '.feca-mailshots-admin .feca-hidden{display:none;}'
. '.feca-mailshots-admin .feca-meta-line{margin:4px 0 var(--feca-space-3) 0;color:#6b7280;font-size:12px;}'

View File

@ -46,16 +46,39 @@ final class ProfileAdminPage
}
$uid = $this->wp->currentUserId();
$saved = $uid > 0 ? ($this->repo()->findByUserId($uid) ?? []) : [];
$saved = [];
$loadError = '';
if ($uid > 0) {
try {
$saved = $this->repo()->findByUserId($uid) ?? [];
} catch (\Throwable $e) {
$loadError = 'Unable to load stored profile credentials: ' . $e->getMessage();
}
}
$status = $this->wp->requestParam('saved', '') === '1' ? 'Profile credentials saved.' : '';
$test = $uid > 0 ? $this->testResult($uid) : null;
$action = htmlspecialchars($this->wp->adminUrl('admin-post.php'));
$missingPasswords = $loadError === '' ? $this->missingPasswordLabels($saved) : [];
echo '<div class="wrap feca-mailshots-admin"><h1>Mailshot Profile</h1>';
echo $this->renderAdminUiStyles();
if ($loadError !== '') {
echo '<div class="feca-banner feca-banner-error">' . htmlspecialchars($loadError) . '</div>';
}
if ($status !== '') {
echo '<div class="feca-banner feca-banner-success">' . htmlspecialchars($status) . '</div>';
}
if ($missingPasswords !== []) {
echo '<div class="feca-banner feca-banner-error">';
echo '<strong>' . htmlspecialchars('Missing password configuration.') . '</strong>';
echo '<p>' . htmlspecialchars('Enter and save the missing password before running mailshot tests or sends.') . '</p>';
echo '<ul>';
foreach ($missingPasswords as $label) {
echo '<li>' . htmlspecialchars($label) . '</li>';
}
echo '</ul>';
echo '</div>';
}
if ($test !== null) {
$ok = !empty($test['ok']);
$kind = strtoupper((string) ($test['kind'] ?? 'CREDENTIAL'));
@ -92,7 +115,7 @@ final class ProfileAdminPage
$this->field('SMTP Host', 'smtp_host', $saved['smtp_host'] ?? '');
$this->field('SMTP Port', 'smtp_port', (string) ($saved['smtp_port'] ?? ''));
$this->field('SMTP User', 'smtp_user', $saved['smtp_user'] ?? '');
$this->field('SMTP Password', 'smtp_password', '', 'password', 'Required on each save/test request');
$this->field('SMTP Password', 'smtp_password', (string) ($saved['smtp_password'] ?? ''), 'password');
$this->field('From Email', 'smtp_from_email', $saved['smtp_from_email'] ?? '');
$this->field('From Name', 'smtp_from_name', $saved['smtp_from_name'] ?? '');
@ -105,7 +128,7 @@ final class ProfileAdminPage
$this->field('IMAP Host', 'imap_host', $saved['imap_host'] ?? '');
$this->field('IMAP Port', 'imap_port', (string) ($saved['imap_port'] ?? ''));
$this->field('IMAP User', 'imap_user', $saved['imap_user'] ?? '');
$this->field('IMAP Password', 'imap_password', '', 'password', 'Required on each save/test request');
$this->field('IMAP Password', 'imap_password', (string) ($saved['imap_password'] ?? ''), 'password');
$this->field('IMAP Sent Folder', 'imap_sent_folder', $saved['imap_sent_folder'] ?? '');
$this->field('IMAP Mailbox Flags', 'imap_mailbox_flags', $saved['imap_mailbox_flags'] ?? '');
echo '</table>';
@ -116,7 +139,9 @@ final class ProfileAdminPage
echo '<button type="submit" class="button" name="action" value="feca_mailshots_profile_test" formaction="' . $action . '" formmethod="post" title="Test IMAP login" onclick="this.form.test_kind.value=\'imap\';">Test IMAP</button>';
echo '<input type="hidden" name="test_kind" value="">';
echo '</p>';
echo '</form></div>';
echo '</form>';
echo $this->renderPasswordToggleScript();
echo '</div>';
}
public function handleSave(): void
@ -210,6 +235,8 @@ final class ProfileAdminPage
'imap_mailbox_flags' => trim((string) ($this->wp->requestParam('imap_mailbox_flags', '') ?? '')),
];
$payload = $this->applyStoredPasswordsForTest($uid, $payload);
$result = $kind === 'smtp' ? $this->runSmtpTest($payload) : $this->runImapTest($payload);
$this->wp->updateOption($this->testResultOptionKey($uid), $result);
@ -225,13 +252,76 @@ final class ProfileAdminPage
{
echo '<tr>';
echo '<th scope="row"><label for="' . htmlspecialchars($name) . '">' . htmlspecialchars($label) . '</label></th>';
echo '<td><input class="regular-text" type="' . htmlspecialchars($type) . '" id="' . htmlspecialchars($name) . '" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
echo '<td>';
if ($type === 'password') {
echo '<span class="feca-password-control">';
echo '<input class="regular-text" type="password" autocomplete="off" id="' . htmlspecialchars($name) . '" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
echo '<button type="button" class="button feca-password-toggle" data-feca-password-toggle="' . htmlspecialchars($name) . '" aria-controls="' . htmlspecialchars($name) . '" aria-pressed="false">' . htmlspecialchars('Show') . '</button>';
echo '</span>';
} else {
echo '<input class="regular-text" type="' . htmlspecialchars($type) . '" id="' . htmlspecialchars($name) . '" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
}
if ($hint !== '') {
echo '<p class="description">' . htmlspecialchars($hint) . '</p>';
}
echo '</td></tr>';
}
private function renderPasswordToggleScript(): string
{
return <<<'HTML'
<script>
(function () {
var buttons = document.querySelectorAll('[data-feca-password-toggle]');
Array.prototype.forEach.call(buttons, function (button) {
button.addEventListener('click', function () {
var fieldId = button.getAttribute('data-feca-password-toggle');
var field = fieldId ? document.getElementById(fieldId) : null;
if (!field) {
return;
}
var visible = field.type === 'text';
field.type = visible ? 'password' : 'text';
button.setAttribute('aria-pressed', visible ? 'false' : 'true');
button.textContent = visible ? 'Show' : 'Hide';
});
});
}());
</script>
HTML;
}
/** @param array<string, mixed> $saved @return list<string> */
private function missingPasswordLabels(array $saved): array
{
$missing = [];
if (trim((string) ($saved['smtp_password'] ?? '')) === '') {
$missing[] = 'SMTP password is missing.';
}
if (trim((string) ($saved['imap_password'] ?? '')) === '') {
$missing[] = 'IMAP password is missing.';
}
return $missing;
}
/** @param array<string, mixed> $payload @return array<string, mixed> */
private function applyStoredPasswordsForTest(int $uid, array $payload): array
{
try {
$saved = $this->repo()->findByUserId($uid) ?? [];
} catch (\Throwable $e) {
return $payload;
}
foreach (['smtp_password', 'imap_password'] as $key) {
if (trim((string) ($payload[$key] ?? '')) === '' && trim((string) ($saved[$key] ?? '')) !== '') {
$payload[$key] = (string) $saved[$key];
}
}
return $payload;
}
private function repo(): MailCredentialRepository
{
return ($this->repoFactory)();