feat: Leave Feedback form — multi-select checkboxes + actually-working submit
The right-column "Leave Feedback" form on the About page has been
expanded from two radio buttons (user picked one of two) to seven
checkboxes (user can pick any), plus an optional message textarea.
The submit button — which previously called toggleSection() on
element IDs that never existed and did nothing — now AJAX-posts
to a new server-side handler that emails the site admin via
wp_mail().
FORM OPTIONS (checkboxes — multi-select)
- I have ideas to improve this plugin
- I need help with this plugin
- I found a bug
- I'd like to request a new feature
- I'd like to share my use case
- Just saying thanks
- Other
SUBMISSION FLOW
- Client-side: at least one checkbox OR a message is required; an
inline hint shows otherwise.
- AJAX POST wp_notes_submit_feedback with topics[] + message + nonce.
- Server handler (manage_options capability + nonce checked)
sanitizes input, allow-lists the seven topic keys, builds a
plain-text email body (sender + site URL + plugin version +
checked topics with pretty labels + message), and ships it to
get_option('admin_email') via wp_mail() with Reply-To set to the
submitting user.
- Inline success message replaces the form on success; inline error
lets the user retry on failure.
The toggleSection() helper that the old broken handler used is kept
defined for now — it's no longer referenced anywhere on the About
page, so it's flagged in the changelog for a future Tier-2 removal
pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,44 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed — Leave Feedback form (more options, multi-select, wired to email)
|
||||||
|
The right-column "Leave Feedback" form on the About page has been
|
||||||
|
expanded from two radio buttons to **seven checkboxes** (users can
|
||||||
|
pick more than one), a new optional message textarea, and a submit
|
||||||
|
button that **actually does something** — it AJAX-posts to a new
|
||||||
|
WP handler that emails the site admin via `wp_mail()`.
|
||||||
|
|
||||||
|
**Form options (checkboxes — multi-select):**
|
||||||
|
- I have ideas to improve this plugin
|
||||||
|
- I need help with this plugin
|
||||||
|
- I found a bug
|
||||||
|
- I'd like to request a new feature
|
||||||
|
- I'd like to share my use case
|
||||||
|
- Just saying thanks 🍀
|
||||||
|
- Other
|
||||||
|
|
||||||
|
**Submission flow:**
|
||||||
|
1. Client-side: at least one checkbox OR a message is required;
|
||||||
|
otherwise an inline hint shows.
|
||||||
|
2. AJAX POST `wp_notes_submit_feedback` with topics[] + message +
|
||||||
|
nonce.
|
||||||
|
3. Server-side handler (`manage_options` capability + nonce checked)
|
||||||
|
sanitizes input, allow-lists the topic keys, then builds a plain-
|
||||||
|
text email and ships it to `get_option('admin_email')` via
|
||||||
|
`wp_mail()`. Reply-To is set to the submitting user's email so
|
||||||
|
the admin can reply directly.
|
||||||
|
4. Email body includes: sender (display name + email + WP login),
|
||||||
|
site URL, plugin version, the checked topics (pretty-labelled),
|
||||||
|
and the message.
|
||||||
|
5. Inline success message replaces the form on success; inline
|
||||||
|
error message lets the user retry on failure.
|
||||||
|
|
||||||
|
The old radio-button + broken `toggleSection('feedback-form-...')`
|
||||||
|
logic that pointed at non-existent IDs has been replaced entirely.
|
||||||
|
The `toggleSection()` helper is kept defined but is now genuinely
|
||||||
|
unused on the About page — flagged for removal in a future Tier-2
|
||||||
|
pass.
|
||||||
|
|
||||||
### Changed — About page rewritten (content + layout)
|
### Changed — About page rewritten (content + layout)
|
||||||
The About page (`Settings → WP Notes → About`) has been rewritten
|
The About page (`Settings → WP Notes → About`) has been rewritten
|
||||||
from "wall of nested toggle boxes with outdated content" to
|
from "wall of nested toggle boxes with outdated content" to
|
||||||
|
|||||||
+63
-9
@@ -164,9 +164,24 @@ function wp_notes_about_page() {
|
|||||||
</a>
|
</a>
|
||||||
<!-- Feedback Options -->
|
<!-- Feedback Options -->
|
||||||
<form id="wp-notes-feedback-options" style="margin-top: 20px;">
|
<form id="wp-notes-feedback-options" style="margin-top: 20px;">
|
||||||
<label><input type="radio" name="feedback" value="improve"> I have ideas to improve this plugin</label><br>
|
<p style="margin: 0 0 8px; font-weight: 600;">What's on your mind? <span style="font-weight: 400; color: #646970;">(pick any)</span></p>
|
||||||
<label><input type="radio" name="feedback" value="help"> I need help with this plugin</label><br>
|
<label style="display:block; margin-bottom:6px;"><input type="checkbox" name="feedback_topics[]" value="improve"> I have ideas to improve this plugin</label>
|
||||||
<input type="submit" value="Submit Feedback" style="background: #0073aa; color: #fff; border: none; padding: 8px 12px; cursor: pointer; margin-top: 10px;">
|
<label style="display:block; margin-bottom:6px;"><input type="checkbox" name="feedback_topics[]" value="help"> I need help with this plugin</label>
|
||||||
|
<label style="display:block; margin-bottom:6px;"><input type="checkbox" name="feedback_topics[]" value="bug"> I found a bug</label>
|
||||||
|
<label style="display:block; margin-bottom:6px;"><input type="checkbox" name="feedback_topics[]" value="feature"> I'd like to request a new feature</label>
|
||||||
|
<label style="display:block; margin-bottom:6px;"><input type="checkbox" name="feedback_topics[]" value="use-case"> I'd like to share my use case</label>
|
||||||
|
<label style="display:block; margin-bottom:6px;"><input type="checkbox" name="feedback_topics[]" value="thanks"> Just saying thanks 🍀</label>
|
||||||
|
<label style="display:block; margin-bottom:10px;"><input type="checkbox" name="feedback_topics[]" value="other"> Other</label>
|
||||||
|
|
||||||
|
<p style="margin: 12px 0 4px; font-weight: 600;">Anything to add? <span style="font-weight: 400; color: #646970;">(optional)</span></p>
|
||||||
|
<textarea name="feedback_message" rows="4" style="width: 100%; box-sizing: border-box;" placeholder="A few sentences with more detail…"></textarea>
|
||||||
|
|
||||||
|
<?php wp_nonce_field('wp_notes_feedback_submit', 'wp_notes_feedback_nonce'); ?>
|
||||||
|
|
||||||
|
<p style="margin-top: 12px; margin-bottom: 0;">
|
||||||
|
<input type="submit" id="wp-notes-feedback-submit" value="Submit Feedback" class="button button-primary">
|
||||||
|
</p>
|
||||||
|
<p id="wp-notes-feedback-status" role="status" style="margin: 10px 0 0; min-height: 1.4em;"></p>
|
||||||
</form>
|
</form>
|
||||||
<br>
|
<br>
|
||||||
<!-- Buy Me a Coffee Button -->
|
<!-- Buy Me a Coffee Button -->
|
||||||
@@ -196,15 +211,54 @@ function wp_notes_about_page() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// JavaScript to handle feedback form toggling
|
// Submit the feedback form → AJAX → wp_mail() to the site admin.
|
||||||
|
// No toggleSection() shuffle anymore (the old code targeted IDs
|
||||||
|
// that never existed). Validates that at least one topic is
|
||||||
|
// checked, otherwise the submit is a no-op with a hint.
|
||||||
document.getElementById('wp-notes-feedback-options').addEventListener('submit', function(e) {
|
document.getElementById('wp-notes-feedback-options').addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var feedbackType = document.querySelector('input[name="feedback"]:checked').value;
|
var form = e.target;
|
||||||
if (feedbackType === 'improve') {
|
var btn = document.getElementById('wp-notes-feedback-submit');
|
||||||
toggleSection('feedback-form-improve');
|
var statusEl = document.getElementById('wp-notes-feedback-status');
|
||||||
} else if (feedbackType === 'help') {
|
var topics = Array.from(form.querySelectorAll('input[name="feedback_topics[]"]:checked')).map(function (i) { return i.value; });
|
||||||
toggleSection('feedback-form-help');
|
var message = (form.querySelector('textarea[name="feedback_message"]').value || '').trim();
|
||||||
|
var nonce = (form.querySelector('input[name="wp_notes_feedback_nonce"]').value || '');
|
||||||
|
|
||||||
|
if (topics.length === 0 && message === '') {
|
||||||
|
statusEl.textContent = 'Pick at least one option (or write a message) before submitting.';
|
||||||
|
statusEl.style.color = '#8a2424';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.value = 'Sending…';
|
||||||
|
statusEl.textContent = '';
|
||||||
|
statusEl.style.color = '';
|
||||||
|
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('action', 'wp_notes_submit_feedback');
|
||||||
|
fd.append('nonce', nonce);
|
||||||
|
topics.forEach(function (t) { fd.append('topics[]', t); });
|
||||||
|
fd.append('message', message);
|
||||||
|
|
||||||
|
fetch(ajaxurl, { method: 'POST', credentials: 'same-origin', body: fd })
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (res) {
|
||||||
|
if (res && res.success) {
|
||||||
|
form.innerHTML = '<p style="color:#1e6e40; font-weight:600; margin:0;">Thanks — your feedback has been sent. 🍀</p>';
|
||||||
|
} else {
|
||||||
|
statusEl.textContent = (res && res.data) ? String(res.data) : 'Sorry, something went wrong. Please try again.';
|
||||||
|
statusEl.style.color = '#8a2424';
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.value = 'Submit Feedback';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
statusEl.textContent = 'Network error — please try again in a moment.';
|
||||||
|
statusEl.style.color = '#8a2424';
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.value = 'Submit Feedback';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
@@ -657,6 +657,74 @@ function wp_notes_add_inline_scripts() {
|
|||||||
</script>";
|
</script>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive the "Leave Feedback" form from the About page and forward
|
||||||
|
// it to the site admin via wp_mail(). Topics is an array of short
|
||||||
|
// keys (improve / help / bug / feature / use-case / thanks / other);
|
||||||
|
// message is an optional free-text textarea. Sender info is taken
|
||||||
|
// from wp_get_current_user() so we never trust client-supplied
|
||||||
|
// identity. Capability-gated to manage_options because the About
|
||||||
|
// page is admin-only.
|
||||||
|
add_action('wp_ajax_wp_notes_submit_feedback', 'wp_notes_ajax_submit_feedback');
|
||||||
|
function wp_notes_ajax_submit_feedback() {
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error('Insufficient permissions.', 403);
|
||||||
|
}
|
||||||
|
check_ajax_referer('wp_notes_feedback_submit', 'nonce');
|
||||||
|
|
||||||
|
$allowed_topics = ['improve', 'help', 'bug', 'feature', 'use-case', 'thanks', 'other'];
|
||||||
|
$raw_topics = isset($_POST['topics']) && is_array($_POST['topics']) ? wp_unslash($_POST['topics']) : [];
|
||||||
|
$topics = array_values(array_intersect($allowed_topics, array_map('sanitize_key', $raw_topics)));
|
||||||
|
|
||||||
|
$message = isset($_POST['message']) ? sanitize_textarea_field(wp_unslash($_POST['message'])) : '';
|
||||||
|
|
||||||
|
if (empty($topics) && $message === '') {
|
||||||
|
wp_send_json_error('Pick at least one topic or write a message.', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = wp_get_current_user();
|
||||||
|
$site = get_bloginfo('name');
|
||||||
|
$to = get_option('admin_email');
|
||||||
|
|
||||||
|
$topic_labels = [
|
||||||
|
'improve' => 'Ideas to improve the plugin',
|
||||||
|
'help' => 'Needs help with the plugin',
|
||||||
|
'bug' => 'Reporting a bug',
|
||||||
|
'feature' => 'New feature request',
|
||||||
|
'use-case' => 'Sharing a use case',
|
||||||
|
'thanks' => 'Saying thanks',
|
||||||
|
'other' => 'Other',
|
||||||
|
];
|
||||||
|
$topics_pretty = array_map(function ($t) use ($topic_labels) { return $topic_labels[$t] ?? $t; }, $topics);
|
||||||
|
|
||||||
|
$subject = sprintf('[%s] WP Notes feedback from %s', $site, $user->display_name ?: $user->user_login);
|
||||||
|
$body = "Feedback received via WP Notes → About page\n";
|
||||||
|
$body .= str_repeat('-', 48) . "\n\n";
|
||||||
|
$body .= 'From: ' . ($user->display_name ?: $user->user_login) . ' <' . $user->user_email . ">\n";
|
||||||
|
$body .= 'Site: ' . home_url() . "\n";
|
||||||
|
$body .= 'Plugin: v' . WP_NOTES_VERSION . "\n\n";
|
||||||
|
$body .= "Topics:\n";
|
||||||
|
if (!empty($topics_pretty)) {
|
||||||
|
foreach ($topics_pretty as $label) { $body .= ' - ' . $label . "\n"; }
|
||||||
|
} else {
|
||||||
|
$body .= " (none selected)\n";
|
||||||
|
}
|
||||||
|
$body .= "\nMessage:\n";
|
||||||
|
$body .= $message !== '' ? $message . "\n" : "(no message provided)\n";
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Content-Type: text/plain; charset=UTF-8',
|
||||||
|
'Reply-To: ' . sanitize_email($user->user_email),
|
||||||
|
];
|
||||||
|
|
||||||
|
$sent = wp_mail($to, $subject, $body, $headers);
|
||||||
|
|
||||||
|
if (!$sent) {
|
||||||
|
wp_send_json_error('Email could not be sent. Check the site mail configuration.', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(['delivered_to' => $to]);
|
||||||
|
}
|
||||||
|
|
||||||
// Persist a user's dismissal of the "No active/completed notes found"
|
// Persist a user's dismissal of the "No active/completed notes found"
|
||||||
// empty-state notice so it doesn't reappear on the next page load.
|
// empty-state notice so it doesn't reappear on the next page load.
|
||||||
// Triggered by inline JS in wp_notes_add_inline_scripts() when the
|
// Triggered by inline JS in wp_notes_add_inline_scripts() when the
|
||||||
|
|||||||
Reference in New Issue
Block a user