WP Manifestindependent plugin directory
manifest / content / lh-polls

LH Polls

Simple polls for LocalHero. Options and votes in custom tables, optional comments via wp_comments.

by Peter Shaw · github.com/lhero-org/lh-polls · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/lhero-org/lh-polls/archive/refs/heads/main.zip

Simple member polls for LocalHero. Options and votes stored in custom tables, votes linked to WordPress comments for UI integration, surfaced via a Gutenberg block.

Description

LH Polls provides a WhatsApp-style polling experience for logged-in members. Each poll supports either single-choice or multiple-choice voting. Votes are backed by WordPress comments (enabling use of the existing comments UI and admin screens) with a dedicated votes table providing DB-level uniqueness constraints and fast querying. All read queries are cached via transients backed by the persistent object cache.

Polls are private custom post types — they have no front-end URL of their own and are embedded in pages and posts via the Poll block.

Requirements

  • WordPress 6.6+
  • PHP 8.0+
  • BuddyPress (optional, not required)
  • WP Statuses plugin (optional — enhances the publish metabox dropdown with Closed status)

Installation

  1. Upload the lh-polls directory to wp-content/plugins/
  2. Activate the plugin (or network-activate on Multisite)
  3. Database tables are created automatically on activation and on first load if missing

Usage

Creating a poll

  1. Go to Polls → Add New in the admin menu
  2. Give the poll a title
  3. In the Poll Settings metabox: check Allow multiple choices if voters should be able to select more than one option
  4. In the Options metabox: add options one at a time using the New option field, clicking Update Post after each
  5. When ready, set status to Published to open voting

Embedding a poll

Insert the Poll block in any post or page. Select the poll from the dropdown in the block settings panel or inline. The block renders server-side — vote counts are always live.

Editing a poll from the front end

If the current logged-in user has the edit_post capability for a poll, a small Edit poll link is shown beneath the vote total on the front end. This links directly to the poll's edit screen in the admin dashboard.

Closing a poll

Set the poll's status to Closed using the Status dropdown in the publish metabox. Voting stops immediately. Results remain visible to all users.

Managing votes

The Votes metabox on the poll edit screen shows all votes grouped by option, with voter name, comment text, and date. Individual votes can be deleted via the Delete link. Votes can also be added manually by selecting an option and user and clicking Update Post.

Vote comments are standard WordPress comments of type lh_vote attached to the poll post. They can also be managed via the WordPress comments screen — deleting a comment there will automatically remove the corresponding vote row.

Capabilities

Poll management uses standard WordPress post capabilities (edit_posts, publish_posts, delete_posts etc.). Any user role that can manage posts can manage polls — no custom capability registration is needed.

Frontend voting requires only that the user be logged in.

Poll Statuses

Status Meaning
draft Poll is being set up. Not visible in any block.
publish Poll is live and accepting votes.
lh_poll_closed Voting has ended. Results still visible.
private Visible to admins only — useful for previewing before publishing.

A poll requires at least two options before it can be published or closed. Attempting to publish with fewer than two options will force the status back to draft with an admin notice.

Database Tables

{prefix}lh_poll_options

Stores the options for each poll. Per-site on Multisite (uses $wpdb->prefix).

Column Type Description
option_id BIGINT UNSIGNED Primary key
poll_id BIGINT UNSIGNED Parent poll post ID
label VARCHAR(255) Display label
menu_order INT Sort order

{prefix}lh_poll_votes

Records each vote cast. Per-site on Multisite.

Column Type Description
vote_id BIGINT UNSIGNED Primary key
poll_id BIGINT UNSIGNED Parent poll post ID
option_id BIGINT UNSIGNED Option voted for
user_id BIGINT UNSIGNED Voter
comment_id BIGINT UNSIGNED Linked wp_comments row
voted_at DATETIME Vote timestamp

Unique constraint: (option_id, user_id) — one vote per user per option, enforced at the database level.

Comment Integration

Every vote creates a wp_comments row of type lh_vote attached to the poll post (comment_post_ID = poll_id). This enables:

  • Optional comment text left alongside a vote
  • Viewing and deleting votes via the standard WP comments admin screen
  • Vote counts visible as comment_count on the poll post

Vote comments are excluded from all standard comment queries, feeds, and counts on non-poll posts. Deleting a vote comment via the WP comments screen automatically removes the corresponding vote row via the deleted_comment hook.

Caching

All poll read queries are cached as transients backed by the persistent object cache (Redis on LocalHero). Cache is keyed per poll and automatically flushed on any vote insert, delete, or option change via LH_Polls_DB::flush_poll_cache().

Cached queries:

  • Poll options list
  • Option IDs
  • Vote counts per option
  • Per-option vote list with comment data
  • Per-user voted option IDs
  • Per-user poll-level voted flag
  • Per-user per-option voted flag

Cache TTL is DAY_IN_SECONDS. Since invalidation is explicit on every write, the TTL is a safety net only.

Cache Invalidation (Page Cache)

After a vote is cast or changed, the plugin fires do_action('litespeed_purge_url', $page_url) to purge the embedding page from LiteSpeed's page cache. On non-LiteSpeed environments this action is a no-op.

Front-End Voting Flow

Voting uses a plain HTML form POST back to the current page — no AJAX, no backend admin URLs. The form is handled by template_redirect in LH_Polls_Block. After processing, the user is redirected back to the same page (with a #lh-poll-{id} fragment anchor) with a ?lh_poll_msg parameter that triggers an inline feedback message.

For single-choice polls, a Change vote button is shown to users who have already voted, allowing them to select a different option.

Multisite

Tables are per-site, using $wpdb->prefix. On network activation, tables are created for all existing sites. New sites added after activation get their tables via the wpmu_new_blog hook. The lh_polls_db_version option is stored per-site.

Constants

Constant Description
LH_POLLS_VERSION Plugin version
LH_POLLS_FILE Absolute path to main plugin file
LH_POLLS_DIR Plugin directory path
LH_POLLS_URL Plugin directory URL

Hooks

No public hooks are exposed yet. Planned for a future release.

File Structure

lh-polls/
├── lh-polls.php                          # Main plugin file, bootstrap
├── readme.md                             # This file
├── index.php                             # Directory silence file
├── includes/
│   ├── class-lh-polls-db.php             # All DB queries, table creation, caching
│   ├── class-lh-polls-post-types.php     # CPT, metaboxes, admin handlers, validation
│   ├── class-lh-polls-comments.php       # Comment/vote sync, comment filtering
│   └── class-lh-polls-block.php          # Block registration, SSR, front-end form handler
└── blocks/lh-polls/
    ├── editor.js                          # Block editor JS (poll picker)
    ├── admin.js                           # Admin JS (Closed status in publish metabox)
    ├── style.css                          # Front-end styles
    └── editor.css                         # Editor styles

Changelog

1.0.5 — 2026-06-04

  • Added front-end edit link for users with edit_post capability on the poll

1.0.4 — 2026-06-04

  • Switched capability_type to post — polls now use standard post capabilities (edit_posts etc.), no custom capability registration needed
  • Replaced manage_options checks in admin handlers with edit_posts

1.0.3 — 2026-06-04

  • Added minimum two options validation — polls cannot be published or closed with fewer than two options; attempting to do so forces status back to draft with an admin notice

1.0.2 — 2026-06-04

  • Added post_type => array('lh_poll') to register_post_status for WP Statuses plugin compatibility
  • Updated admin JS to target #wp-statuses-dropdown (WP Statuses) with Chosen refresh, falling back to #post_status (standard WP)

1.0.1 — 2026-06-04

  • Replaced inline wp_add_inline_script with proper registered admin.js and wp_localize_script for the Closed status dropdown
  • Fixed get_the_ID() returning 0 on admin screens — now reads post ID from $_GET['post']
  • Added DOMContentLoaded guard to admin script

1.0.0 — 2026-06-04

  • Initial release
  • lh_poll private CPT with options in {prefix}lh_poll_options custom table
  • Votes in {prefix}lh_poll_votes with UNIQUE KEY (option_id, user_id) DB-level constraint
  • Every vote backed by a wp_comments row of type lh_vote; deleting the comment removes the vote row
  • Single-choice and multiple-choice modes controlled by _poll_allow_multiple post meta
  • lh_poll_closed custom post status for ended polls; open/closed determined by post_status = publish
  • Poll block with server-side render; form POST back to current page via template_redirect
  • Change vote support for single-choice polls
  • Admin votes metabox with per-option breakdown, manual vote add/delete
  • Full transient caching of all read queries, flushed on any write via flush_poll_cache()
  • LiteSpeed page cache purge via do_action('litespeed_purge_url') after votes
  • Multisite support: per-site tables, wpmu_new_blog hook, network activation loop
  • WP Statuses plugin compatibility for publish metabox dropdown