25% off ProSNIPC25OFF

Helpful? Yes/No Feedback Widget

Appends a "Was this helpful?" thumbs-up/down widget after single post content; votes are stored in post meta and the user's choice is remembered via a cookie.

Module3 parts · by SnipCraft

PHPLogic

php
<?php
add_filter( 'the_content', 'scseed_helpful_append', 25 );
function scseed_helpful_append( $content ) {
    if ( ! is_single() || ! in_the_loop() || ! is_main_query() ) { return $content; }
    $pid  = get_the_ID();
    $yes  = (int) get_post_meta( $pid, '_scseed_helpful_yes', true );
    $no   = (int) get_post_meta( $pid, '_scseed_helpful_no',  true );
    $nonce = wp_create_nonce( 'scseed_helpful_' . $pid );
    $widget = '<div class="scseed-helpful" data-pid="' . absint( $pid ) . '" data-nonce="' . esc_attr( $nonce ) . '">'
        . '<p class="scseed-helpful-q">' . esc_html__( 'Was this article helpful?', 'scseed' ) . '</p>'
        . '<div class="scseed-helpful-btns">'
        . '<button class="scseed-helpful-btn scseed-helpful-yes" type="button" data-vote="yes" aria-label="Yes">'
        . '<span class="scseed-helpful-icon" aria-hidden="true">&#128077;</span>'
        . '<span class="scseed-helpful-label">' . esc_html__( 'Yes', 'scseed' ) . '</span>'
        . '<span class="scseed-helpful-count">' . absint( $yes ) . '</span>'
        . '</button>'
        . '<button class="scseed-helpful-btn scseed-helpful-no" type="button" data-vote="no" aria-label="No">'
        . '<span class="scseed-helpful-icon" aria-hidden="true">&#128078;</span>'
        . '<span class="scseed-helpful-label">' . esc_html__( 'No', 'scseed' ) . '</span>'
        . '<span class="scseed-helpful-count">' . absint( $no ) . '</span>'
        . '</button>'
        . '</div>'
        . '<p class="scseed-helpful-thanks" aria-live="polite"></p>'
        . '</div>';
    return $content . $widget;
}

add_action( 'wp_ajax_scseed_helpful',        'scseed_helpful_ajax' );
add_action( 'wp_ajax_nopriv_scseed_helpful', 'scseed_helpful_ajax' );
function scseed_helpful_ajax() {
    $pid   = absint( isset( $_POST['pid'] )   ? $_POST['pid']   : 0 );
    $vote  = sanitize_key( isset( $_POST['vote'] ) ? $_POST['vote'] : '' );
    $nonce = sanitize_text_field( isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : '' );

    if ( ! $pid || ! in_array( $vote, array( 'yes', 'no' ), true ) ) {
        wp_send_json_error( 'invalid' );
    }
    if ( ! wp_verify_nonce( $nonce, 'scseed_helpful_' . $pid ) ) {
        wp_send_json_error( 'nonce' );
    }
    $key = '_scseed_helpful_' . $vote;
    $cur = (int) get_post_meta( $pid, $key, true );
    update_post_meta( $pid, $key, $cur + 1 );
    wp_send_json_success( array(
        'yes' => (int) get_post_meta( $pid, '_scseed_helpful_yes', true ),
        'no'  => (int) get_post_meta( $pid, '_scseed_helpful_no',  true ),
    ) );
}

CSSStyles

css
.scseed-helpful {
    margin: 2rem 0 1rem;
    padding: 1.25rem 1.5rem;
    background: #fafbfc;
    border: 1px solid #e8eaed;
    border-radius: 8px;
    text-align: center;
}
.scseed-helpful-q {
    font-size: 0.95rem;
    font-weight: 600;
    color: #333;
    margin: 0 0 0.75rem;
}
.scseed-helpful-btns {
    display: flex;
    justify-content: center;
    gap: 0.75rem;
}
.scseed-helpful-btn {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 1.25rem;
    background: #fff;
    border: 1px solid #d0d0d0;
    border-radius: 99px;
    font-size: 0.875rem;
    cursor: pointer;
    transition: background 0.2s, border-color 0.2s, transform 0.15s;
    font-family: inherit;
    color: #444;
}
.scseed-helpful-btn:hover { background: #f5f5f5; border-color: #aaa; }
.scseed-helpful-btn.scseed-voted { font-weight: 700; transform: scale(1.05); }
.scseed-helpful-yes.scseed-voted { background: #f0fff4; border-color: #4caf50; color: #2e7d32; }
.scseed-helpful-no.scseed-voted  { background: #fff8f0; border-color: #ff9800; color: #e65100; }
.scseed-helpful-btn[disabled] { opacity: 0.6; cursor: default; }
.scseed-helpful-icon { font-size: 1rem; line-height: 1; }
.scseed-helpful-count { font-weight: 700; min-width: 1ch; }
.scseed-helpful-thanks { font-size: 0.85rem; color: #4caf50; margin: 0.5rem 0 0; min-height: 1.2em; }

JavaScriptScript

js
(function() {
    'use strict';
    document.addEventListener('DOMContentLoaded', function() {
        document.querySelectorAll('.scseed-helpful').forEach(function(widget) {
            var pid    = widget.dataset.pid;
            var cookie = 'scseed_helpful_' + pid;

            // Restore voted state
            var voted = getCookie(cookie);
            if ( voted ) { lockWidget(widget, voted, null); }

            widget.querySelectorAll('.scseed-helpful-btn').forEach(function(btn) {
                btn.addEventListener('click', function() {
                    if ( btn.disabled ) { return; }
                    var vote  = btn.dataset.vote;
                    var nonce = widget.dataset.nonce;
                    var ajax  = (typeof ajaxurl !== 'undefined') ? ajaxurl : '/wp-admin/admin-ajax.php';
                    btn.disabled = true;
                    var fd = new FormData();
                    fd.append('action', 'scseed_helpful');
                    fd.append('pid', pid);
                    fd.append('vote', vote);
                    fd.append('nonce', nonce);
                    fetch(ajax, { method: 'POST', body: fd })
                        .then(function(r) { return r.json(); })
                        .then(function(data) {
                            if ( data.success ) {
                                lockWidget(widget, vote, data.data);
                                setCookie(cookie, vote, 365);
                            }
                        })
                        .catch(function() { btn.disabled = false; });
                });
            });
        });

        function lockWidget(widget, vote, counts) {
            widget.querySelectorAll('.scseed-helpful-btn').forEach(function(b) {
                b.disabled = true;
                if ( b.dataset.vote === vote ) { b.classList.add('scseed-voted'); }
            });
            if ( counts ) {
                widget.querySelector('.scseed-helpful-yes .scseed-helpful-count').textContent = counts.yes;
                widget.querySelector('.scseed-helpful-no .scseed-helpful-count').textContent  = counts.no;
            }
            var thanks = widget.querySelector('.scseed-helpful-thanks');
            if ( thanks ) { thanks.textContent = vote === 'yes' ? 'Thanks for your feedback!' : 'Thanks — we'll work to improve it.'; }
        }
        function setCookie(name, val, days) {
            var d = new Date();
            d.setTime(d.getTime() + days * 86400000);
            document.cookie = name + '=' + val + ';expires=' + d.toUTCString() + ';path=/;SameSite=Lax';
        }
        function getCookie(name) {
            var parts = ('; ' + document.cookie).split('; ' + name + '=');
            return parts.length === 2 ? parts.pop().split(';').shift() : '';
        }
    });
}());
#ux#posts#engagement#rating#feedback