Improved password handling on profile page
This commit is contained in:
parent
be8139659f
commit
8bc9e566db
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: FECA Mailshots
|
* Plugin Name: FECA Mailshots
|
||||||
* Plugin URI: https://fenedge.co.uk/
|
* Plugin URI: https://fenedge.co.uk/
|
||||||
* Description: FECA mailshots plugin.
|
* Description: FECA mailshots plugin.
|
||||||
* Version: 1.1.8
|
* Version: 1.1.10
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
* Author: FECA
|
* Author: FECA
|
||||||
|
|
|
||||||
|
|
@ -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,.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 .form-table th{width:220px;}'
|
||||||
. '.feca-mailshots-admin .feca-form .description{margin-top:var(--feca-space-1);}'
|
. '.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-inline-form{display:inline;}'
|
||||||
. '.feca-mailshots-admin .feca-hidden{display:none;}'
|
. '.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;}'
|
. '.feca-mailshots-admin .feca-meta-line{margin:4px 0 var(--feca-space-3) 0;color:#6b7280;font-size:12px;}'
|
||||||
|
|
|
||||||
|
|
@ -46,16 +46,39 @@ final class ProfileAdminPage
|
||||||
}
|
}
|
||||||
|
|
||||||
$uid = $this->wp->currentUserId();
|
$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.' : '';
|
$status = $this->wp->requestParam('saved', '') === '1' ? 'Profile credentials saved.' : '';
|
||||||
$test = $uid > 0 ? $this->testResult($uid) : null;
|
$test = $uid > 0 ? $this->testResult($uid) : null;
|
||||||
$action = htmlspecialchars($this->wp->adminUrl('admin-post.php'));
|
$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 '<div class="wrap feca-mailshots-admin"><h1>Mailshot Profile</h1>';
|
||||||
echo $this->renderAdminUiStyles();
|
echo $this->renderAdminUiStyles();
|
||||||
|
if ($loadError !== '') {
|
||||||
|
echo '<div class="feca-banner feca-banner-error">' . htmlspecialchars($loadError) . '</div>';
|
||||||
|
}
|
||||||
if ($status !== '') {
|
if ($status !== '') {
|
||||||
echo '<div class="feca-banner feca-banner-success">' . htmlspecialchars($status) . '</div>';
|
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) {
|
if ($test !== null) {
|
||||||
$ok = !empty($test['ok']);
|
$ok = !empty($test['ok']);
|
||||||
$kind = strtoupper((string) ($test['kind'] ?? 'CREDENTIAL'));
|
$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 Host', 'smtp_host', $saved['smtp_host'] ?? '');
|
||||||
$this->field('SMTP Port', 'smtp_port', (string) ($saved['smtp_port'] ?? ''));
|
$this->field('SMTP Port', 'smtp_port', (string) ($saved['smtp_port'] ?? ''));
|
||||||
$this->field('SMTP User', 'smtp_user', $saved['smtp_user'] ?? '');
|
$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 Email', 'smtp_from_email', $saved['smtp_from_email'] ?? '');
|
||||||
$this->field('From Name', 'smtp_from_name', $saved['smtp_from_name'] ?? '');
|
$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 Host', 'imap_host', $saved['imap_host'] ?? '');
|
||||||
$this->field('IMAP Port', 'imap_port', (string) ($saved['imap_port'] ?? ''));
|
$this->field('IMAP Port', 'imap_port', (string) ($saved['imap_port'] ?? ''));
|
||||||
$this->field('IMAP User', 'imap_user', $saved['imap_user'] ?? '');
|
$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 Sent Folder', 'imap_sent_folder', $saved['imap_sent_folder'] ?? '');
|
||||||
$this->field('IMAP Mailbox Flags', 'imap_mailbox_flags', $saved['imap_mailbox_flags'] ?? '');
|
$this->field('IMAP Mailbox Flags', 'imap_mailbox_flags', $saved['imap_mailbox_flags'] ?? '');
|
||||||
echo '</table>';
|
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 '<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 '<input type="hidden" name="test_kind" value="">';
|
||||||
echo '</p>';
|
echo '</p>';
|
||||||
echo '</form></div>';
|
echo '</form>';
|
||||||
|
echo $this->renderPasswordToggleScript();
|
||||||
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleSave(): void
|
public function handleSave(): void
|
||||||
|
|
@ -210,6 +235,8 @@ final class ProfileAdminPage
|
||||||
'imap_mailbox_flags' => trim((string) ($this->wp->requestParam('imap_mailbox_flags', '') ?? '')),
|
'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);
|
$result = $kind === 'smtp' ? $this->runSmtpTest($payload) : $this->runImapTest($payload);
|
||||||
$this->wp->updateOption($this->testResultOptionKey($uid), $result);
|
$this->wp->updateOption($this->testResultOptionKey($uid), $result);
|
||||||
|
|
||||||
|
|
@ -225,13 +252,76 @@ final class ProfileAdminPage
|
||||||
{
|
{
|
||||||
echo '<tr>';
|
echo '<tr>';
|
||||||
echo '<th scope="row"><label for="' . htmlspecialchars($name) . '">' . htmlspecialchars($label) . '</label></th>';
|
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 !== '') {
|
if ($hint !== '') {
|
||||||
echo '<p class="description">' . htmlspecialchars($hint) . '</p>';
|
echo '<p class="description">' . htmlspecialchars($hint) . '</p>';
|
||||||
}
|
}
|
||||||
echo '</td></tr>';
|
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
|
private function repo(): MailCredentialRepository
|
||||||
{
|
{
|
||||||
return ($this->repoFactory)();
|
return ($this->repoFactory)();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue