WP Manifestindependent plugin directory
manifest / editor / content-poll

ContentPoll AI WP Registry grade B. Medium-severity findings · 2 findingsWP RegistryMedium-severity findings · 2 findingsOpen the reportB

A modern, accessible polling block that lets visitors vote on questions about your content. Generate the poll using AI. Supports multiple AI providers including OpenAI, Claude, Gemini, Ollama, Azure OpenAI, and Grok.

★ 2stars
71release downloads
955composer installs
0.10.0latest · 10mo ago

Install

Download content-poll.zip From the 0.10.0 release on GitHub, Dec 22, 2025

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/soderlind/content-poll/releases/download/0.10.0/content-poll.zip

On Packagist as soderlind/content-poll:

composer require soderlind/content-poll
Updates itself. It ships its own updater (Plugin Update Checker), so new versions appear under Dashboard → Updates.

A modern, accessible polling block that lets visitors vote on questions about your content, with beautiful card-style options and optional AI suggestions.

Engage your audience by asking them to vote on aspects of the page they're reading. AI analyzes your content and suggests relevant polling questions, or create your own. Visitors vote once anonymously, then see real-time results with visual progress bars.

Perfect for: Blog posts, product pages, documentation, news articles, tutorials, reviews – any content where you want to gauge reader opinions or preferences.

ImageImageImageImage

Features | Installation | Usage | Design Features | Development | Privacy & Security | REST API | Roadmap

✨ Features

Core Voting

  • Card-Style Interface: Modern, clickable option cards with radio button indicators (no traditional buttons)
  • Ordered Options: Automatic A, B, C, D labeling for clarity
  • Anonymous & Secure: Cookie-based deduplication with SHA-256 hashing
  • One Vote Per Visitor: Secure token prevents duplicate voting
  • Visual Results: Progress bars with percentages and vote counts
  • Theme Adaptive: Automatically matches your WordPress theme colors
  • Persistent Selection: Returning voters see their previously selected option checked

AI-Powered Suggestions (7 Providers)

AI reads your page content and generates contextually relevant poll questions:

  • Heuristic (Default) - Built-in keyword extraction from your content, no API required. Use it for tests and basic suggestions.
  • OpenAI - GPT models analyze your content using a multi-step PocketFlow pipeline (topics → poll draft → validation) for structured, topic-aware questions and options.
  • Azure OpenAI - Enterprise Azure OpenAI Service using the same PocketFlow multi-step flow as OpenAI.
  • Anthropic Claude - Claude 3.5 Sonnet analyzes content context
  • Google Gemini - Gemini 1.5 Flash (free tier available)
  • Ollama - Self-hosted local models process content privately
  • Grok (xAI) - Real‑time reasoning model from xAI; concise, context-aware poll suggestions

Example: On a blog post about photography tips, AI might suggest: "Which photography technique interests you most?" with options like "Composition", "Lighting", "Editing", "Equipment".

Admin Features

  • Settings Page: Configure AI provider, API keys, and models at Settings → ContentPoll AI
  • Flexible Options: Choose 2-6 voting options per block
  • Lock on First Vote: Options become immutable after the first vote to preserve data integrity
  • Debug Mode: Test reset functionality when WP_DEBUG is enabled
  • Model Validation: Real-time API testing when saving AI settings

Developer-Friendly

  • REST API: Complete endpoints for voting, results, and suggestions
  • Modern Stack: Built with @wordpress/scripts, React hooks, and webpack
  • CSP Compliant: No inline scripts, data attributes for i18n
  • Accessibility: ARIA labels, keyboard navigation, semantic HTML
  • Internationalization: Translation-ready with .pot file

📦 Installation

Quick Install

  1. Download content-poll.zip
  2. Upload via Plugins → Add New → Upload Plugin
  3. Activate via WordPress Admin → Plugins

Configuration (Optional)

  1. Go to Settings → ContentPoll AI
  2. Choose an AI provider for suggestions
  3. Enter your API key or endpoint details
  4. Click Save Settings (plugin tests API connection automatically)

See AI Provider Integration Guide for detailed setup instructions for each AI provider.

Environment Variables / Constants

For deployment automation (Docker, CI/CD), configure AI settings via environment variables or wp-config.php constants:

// wp-config.php
define( 'CONTENT_POLL_AI_PROVIDER', 'openai' );
define( 'CONTENT_POLL_OPENAI_KEY', 'sk-your-api-key' );
# Environment variables
export CONTENT_POLL_AI_PROVIDER=openai
export CONTENT_POLL_OPENAI_KEY=sk-your-api-key

See the AI Provider Integration Guide for the full list of configuration variables.

Updates

Development Install

# Install via Composer
composer require soderlind/content-poll
# Install dependencies
composer install
npm install

# Build assets
npm run build

# Or watch for changes
npm run start

🚀 Usage

Basic Usage

  1. Add Block: In the editor, insert the "ContentPoll AI" block (display name may appear as ContentPoll) into your post/page
  2. Set Question: Enter a question about your content (or use AI to generate one)
  3. Configure Options: Add 2-6 answer options related to your content
  4. Publish: Visitors reading that page can now vote on your question and see results

AI Suggestions (Content-Aware)

  1. Go to Settings → ContentPoll AI
  2. Select an AI provider (OpenAI, Anthropic, Gemini, Ollama, Grok, or Azure)
  3. Enter your API key/endpoint
  4. Write your post content first (AI needs content to analyze)
  5. Add the ContentPoll AI block
  6. Click Generate Suggestions - AI reads the page content and suggests a relevant question
  7. Review and adjust the AI-generated question and options

Results Display

After voting, visitors see:

  • Their selected option marked with a checkmark
  • Vote counts for each option (e.g., "3 votes")
  • Visual progress bars showing percentages
  • Clean card layout with A, B, C, D labels

🎨 Design Features

  • No Buttons: Uses semantic <li> elements styled as interactive cards
  • Radio Indicators: Visual radio button circles that fill when selected
  • Hover Effects: Cards lift and shadow on hover with smooth transitions
  • Pointer Cursor: Clear visual feedback that options are clickable
  • Theme Colors: Uses WordPress CSS custom properties (--wp--preset--color--*)
  • Responsive: Works beautifully on all screen sizes
  • Box Sizing: Proper containment prevents overflow issues

CSS Customization

You can fully restyle the poll via standard CSS. The front‑end markup exposes predictable class names and leverages WordPress design tokens (--wp--preset--color--*) plus a custom radius variable (--wp--custom--border--radius). Override these in your theme or a custom stylesheet.

Key wrapper & structural classes:

  • .content-poll – Outer container (padding, border, background, overall radius)
  • .content-poll__question – Question text
  • .content-poll__options – List wrapper for interactive options
  • .content-poll__option – Individual clickable card
  • .content-poll__radio – Circular selection indicator within each option
  • .content-poll__label – Option text span inside the card
  • .content-poll__message – Post‑vote message container
  • .content-poll__results – Wrapper for results view
  • .content-poll__result-item – Individual result card
  • .content-poll__result-label – Flex row containing option label + percentage
  • .content-poll__result-count – Percentage text (now showing % after changes)
  • .content-poll__result-bar / .content-poll__result-fill – Progress bar track & fill (gradient)
  • .content-poll__results-total – Total votes summary element (appended dynamically)
  • State class: .content-poll--results-only – Hides interactive option list after voting

Useful CSS variables (fallbacks shown in code):

  • --wp--preset--color--primary
  • --wp--preset--color--secondary
  • --wp--preset--color--contrast
  • --wp--preset--color--base
  • --wp--custom--border--radius

Quick Examples

Rounded cards & softer shadow:

.content-poll__option {
  border-radius: 16px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.08);
}

Custom gradient for result bar:

.content-poll__result-fill {
  background: linear-gradient(90deg,#667eea,#764ba2);
}

Larger percentage emphasis:

.content-poll__result-count {
  font-size: 0.95em;
  font-weight: 600;
  opacity: 1;
}

Change option hover behavior:

.content-poll__option:hover {
  transform: scale(1.01);
  box-shadow: 0 6px 18px rgba(0,0,0,0.12);
}

Adjust overall corner radius globally:

.content-poll {
  --wp--custom--border--radius: 12px;
}

Minimal flat style:

.content-poll__option {
  box-shadow: none;
  border: 1px solid #e0e0e0;
}
.content-poll__option:hover {
  transform: none;
  box-shadow: none;
  background: #f9f9f9;
}

Hide total votes (if you prefer only percentages):

.content-poll__results-total { display: none; }

Accessibility Notes

  • Percentages are rendered with an aria-label containing both the raw vote count and percentage for screen readers.
  • Preserve contrast when changing gradients or text colors.

Best Practices

  • Prefer overriding variables for broad changes (theme color alignment).
  • Keep hover/active transitions <= 0.25s for responsiveness.
  • Maintain readable touch targets: keep option padding ≥ 0.75em.
  • Test focus outlines after customizations to retain keyboard accessibility.

🔧 Development

Start with the tutorial.

Architecture: See ContentPoll AI Flow Architecture for detailed documentation on the PocketFlow-inspired multi-step AI pipeline, including the Flow/Node pattern, sequence diagrams, and code examples.

Available Scripts

npm run build        # Production build (minified)
npm run start        # Development mode with watch
npm test             # Run all tests (JS + PHP linting)
npm run test:js      # Vitest unit tests
npm run lint:js      # ESLint JavaScript files
composer test        # PHPUnit tests

Testing

  • JavaScript: Vitest for helper functions and logic
  • PHP: PHPUnit for storage, aggregation, and API validation
  • Linting: WordPress coding standards via @wordpress/scripts
  • Database Safety: Tests include safeguards to prevent accidental data loss

Run Tests Safely:

# JavaScript tests (safe - no database changes)
npm test

# PHP tests with production database protection
composer test

# PHP tests with separate test database (recommended)
WP_TESTS_DB_PREFIX=test_ composer test

See tests/README.md for detailed testing documentation and database safety configuration.

Project Structure

content-poll/
├── src/
│   ├── block/vote-block/      # Gutenberg block (JS + CSS)
│   └── php/                    # Backend logic
│       ├── Admin/              # Settings page
│       ├── Blocks/             # Block registration
│       ├── REST/               # API endpoints
│       ├── Security/           # Nonce & token handling
│       └── Services/           # Vote storage & AI
├── build/                      # Compiled assets (webpack)
├── tests/                      # PHPUnit tests
├── languages/                  # Translation files
└── docs/                       # Documentation

🔒 Privacy & Security

Data Collection

  • Block ID: UUID for each vote instance
  • Option Index: Which option was selected (0-5)
  • Hashed Token: SHA-256 hash of cookie + AUTH_KEY

What We DON'T Collect

  • ❌ IP addresses
  • ❌ User agents
  • ❌ Email addresses
  • ❌ Personal information

GDPR Compliance

  • Anonymous voting only
  • No personally identifiable information
  • Uninstall script removes all data
  • Cookie notice: Site owners should inform users about voting cookies

🌐 REST API

Vote

POST /wp-json/content-poll/v1/block/{blockId}/vote
Headers: X-WP-Nonce
Body: { "optionIndex": 0, "postId": 123 }

Results

GET /wp-json/content-poll/v1/block/{blockId}/results
Response: {
  "blockId": "...",
  "totalVotes": 42,
  "counts": { "0": 15, "1": 12, "2": 10, "3": 5 },
  "percentages": { "0": 35.71, "1": 28.57, "2": 23.81, "3": 11.90 },
  "userVote": 1  // If user has voted
}

AI Suggestions (Editor Only)

GET /wp-json/content-poll/v1/suggest?postId=123
Response: {
  "question": "What aspect interests you most?",
  "options": ["Option A", "Option B", "Option C", "Option D"]
}

🗺️ Roadmap

  • [x] Admin dashboard with vote analytics
  • [ ] Export votes to CSV
  • [ ] Transient caching for high-traffic sites
  • [ ] Custom result templates
  • [ ] Vote scheduling (start/end dates)
  • [ ] Multiple votes per user (optional)
  • [ ] Integration with popular form plugins

📄 Copyright and License

ContentPoll AI is copyright 2025 Per Soderlind

ContentPoll AI is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version.

ContentPoll AI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with the Extension. If not, see http://www.gnu.org/licenses/.

🙏 Credits

Built with modern WordPress tools and best practices. Uses @wordpress/scripts for building, WordPress design system for styling, and follows WordPress coding standards.

PocketFlow-inspired multi-step poll generation flow design is based on ideas and patterns from the PocketFlow project.

Releases

16 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
0.10.0 latest
Dec 22, 2025 10mo ago
content-poll.zip
51
Nov 24, 2025 11mo ago
content-poll.zip
3
Nov 24, 2025 11mo ago
content-poll.zip
0
Nov 19, 2025 11mo ago
content-poll.zip
1
Nov 16, 2025 11mo ago
content-poll.zip
3
Nov 15, 2025 11mo ago
content-poll.zip
0
Nov 15, 2025 11mo ago
content-poll.zip
1
Nov 15, 2025 11mo ago
content-poll.zip
0
Nov 14, 2025 11mo ago
content-poll.zip
1
Nov 14, 2025 11mo ago
content-poll.zip
1
Nov 13, 2025 11mo ago
content-poll.zip
1
Nov 13, 2025 11mo ago
content-poll.zip
3
Nov 13, 2025 11mo ago
content-poll.zip
0
Nov 13, 2025 11mo ago
content-poll.zip
1
Nov 13, 2025 11mo ago
content-poll.zip
1
Nov 13, 2025 11mo ago
content-poll.zip
4
B grade

Security

ContentPoll AI 0.10.0 · audited by WP Registry

Medium-severity findings.

2 medium
Audited release
0.10.0
Findings
2
Worst severity
medium
Content hash
36f5617cbc2e25b8f25ad7ca…

Findings

  • medium Public vote endpoint allows unlimited votes by rotating the client cookie

    auth_bypass

    POST /wp-json/content-poll/v1/block/{pollId}/vote is registered with permission_callback __return_true. The only gate is wp_verify_nonce(…, 'wp_rest'), and GET /content-poll/v1/nonce (also __return_true) plus the block's data-nonce attribute mint that nonce for uid 0. Voter identity is SHA-256 of cookie content_poll_token concatenated with AUTH_KEY; get_or_create_token() trusts any supplied cookie or, if the cookie is omitted, generates a fresh token per request. The UNIQUE(poll_id, hashed_token) constraint therefore does not bind a browser, IP, or account — an unauthenticated caller repeats GET /nonce then POST /vote without a cookie (or with a new cookie each time) and inserts one row per request. The handler never checks that pollId exists in any post, that postId is that poll's post, or that optionIndex is in range for that poll's options (0–5 is always accepted). VoteStorageService::get_aggregate() and the public results route feed those rows into frontend progress bars, so published poll tallies can be inflated or diluted and {prefix}vote_block_submissions can grow without bound. The plugin's own docs claim the nonce and hashed cookie stop bot stuffing and enforce one vote per visitor; they do not.

    src/php/REST/VoteController.php:22-132

    Recommendation

    Do not treat a client-set cookie as a one-vote lock. Bind votes to a server-side token (HttpOnly cookie whose value is a lookup key into a transient/row you issued, not a value the client may invent), rate-limit by IP on the vote route, and reject pollId/postId pairs that do not match a content-poll/vote-block in that post. A public wp_rest nonce is not bot protection; keep it for CSRF but add the server-side identity and existence checks. Consider requiring a logged-in user for polls that must be trustworthy.

  • medium Suggest REST route leaks any post's content to users with only edit_posts

    missing_capability

    GET /wp-json/content-poll/v1/suggest is gated solely on current_user_can('edit_posts') (Contributor and above). The callback takes attacker-chosen postId, loads get_post() with no current_user_can('read_post') or current_user_can('edit_post', $post_id), and passes $post->post_content into AISuggestionService::suggest(). Heuristic mode tokenizes the full content and returns the top keywords as the question/options; AI providers send the first 1000 characters to OpenAI/Azure/Anthropic/Gemini/Ollama/Grok/Exo using the site-wide API key from content_poll_options. A Contributor who cannot open another author's draft, a private page, or a password-protected post can still read a content-derived poll and, when a paid provider is configured, exfiltrate that excerpt to a third party while burning quota. The editor UI only sends the current postId; the REST route does not enforce that.

    src/php/REST/SuggestionController.php:18-50

    Recommendation

    In permission_callback (or at the start of suggest()), require current_user_can('edit_post', $post_id) so the caller must be allowed to edit that specific post. Do not call get_post()/suggest() for posts the user cannot read. Rate-limit AI providers and do not send private/draft content to third-party APIs unless that same object-level cap passed.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.