Skip to content

Add podcast abilities for the Abilities API - #389

Draft
phpbits wants to merge 4 commits into
developfrom
do/387-abilities-api
Draft

phpbits wants to merge 4 commits into
developfrom
do/387-abilities-api

Conversation

@phpbits

@phpbits phpbits commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Description of the Change

Adds five podcast abilities to the WordPress Abilities API. AI agents and other clients can now list, read, create, update and delete podcasts, including their Apple Podcasts settings and platform links. The abilities are exposed to REST and marked public for the MCP Adapter.

What changed

  • A new includes/abilities.php registers the simple-podcasting category and the five abilities. It loads only when wp_register_ability() exists, so older WordPress versions are not affected.
  • The term screen save code now calls a shared helper, update_podcast_term_meta(), in includes/datatypes.php. The admin screen and the abilities use the same sanitizing and storage code. The admin screen still checks the capability and nonce first.
  • Each ability has an input schema that rejects unknown properties, and an output schema.
  • The cover image must be an image in the media library that the user can read.
  • Platform links must be http or https URLs. javascript: and other schemes are rejected.
  • The PR adds a manual check script at tests/bin/abilities-check.php, a README section and a changelog entry.

The abilities

Ability What it does Who can run it Annotations
simple-podcasting/list-podcasts Lists podcasts. Optional search matches the name. manage_categories (Editor and above) readonly, idempotent
simple-podcasting/get-podcast Returns one podcast by id, with feed URL, episode count, all feed fields and platform links. edit_term on that podcast readonly, idempotent
simple-podcasting/create-podcast Creates a podcast. name is required. Accepts the feed fields, image, and platforms. manage_categories not idempotent
simple-podcasting/update-podcast Changes only the fields you send. id is required. edit_term on that podcast idempotent
simple-podcasting/delete-podcast Deletes a podcast. id and confirm: true are required. Episodes are not deleted. delete_term on that podcast destructive

Podcast fields: name, slug, description, subtitle, talent_name, email, summary, copyright, explicit (no, yes, clean), language, image (media ID), keywords, type_of_show, category_1 to category_3, icon_theme, and platforms (an object keyed by platform, such as apple-podcasts or spotify).

Errors the abilities return

  • ability_invalid_input: wrong type, unknown property, bad explicit or category value.
  • invalid_platform_url: a platform link is not an http or https URL.
  • invalid_image: the image is not an image attachment the user can read.
  • confirmation_required: delete was called with confirm: false.
  • podcast_not_found: the ID is not a podcast term.
  • delete_failed: WordPress could not delete the term.
  • Permission failures return 403, or 401 when logged out.

Things to know

  • update-podcast replaces all platform links. A platform you leave out of platforms is cleared. An agent that wants to add one link should read the podcast first and send all links back.
  • The abilities store explicit as no, yes or clean. The wp-admin dropdown stores 0, 1 and 2. This mismatch already exists in the plugin, so the dropdown does not show an ability-saved value as selected. The feed output is not changed by this PR.
  • A request for an ID that does not exist returns a permission error, not podcast_not_found. The permission check runs first, and this does not reveal whether an ID exists.
  • Delete is marked destructive and not idempotent. WordPress core sends an ability to the HTTP DELETE method only when it is both, and this ability uses POST.
  • Episodes are not covered. This PR only manages podcast terms.
  • The @since x.x.x tags need the real release version before release.

Closes #387

How to test the Change

Manual script

Run this with WP-CLI on a site that has the plugin active and dist/ built:

wp eval-file wp-content/plugins/simple-podcasting/tests/bin/abilities-check.php

It creates and deletes its own podcasts. It prints Success: All 19 checks passed. It covers registration, logged-out and subscriber denial, create, update, get, list, delete, an invalid URL, a non-web URL scheme, a missing image and the delete confirmation.

By hand

Log in as an administrator, open Posts > Add New and run this in the browser console:

const base = '/wp-abilities/v1/abilities/simple-podcasting';
const post = ( name, input ) => wp.apiFetch( { path: `${ base }/${ name }/run`, method: 'POST', data: { input } } );
const read = ( name, input ) => wp.apiFetch( { path: `${ base }/${ name }/run?` + new URLSearchParams( Object.entries( input ).map( ( [ k, v ] ) => [ `input[${ k }]`, v ] ) ) } );
  1. Create. Run await post( 'create-podcast', { name: 'Manual Test Show', talent_name: 'Jane Doe', summary: 'A test show.', explicit: 'no', category_1: 'arts', platforms: { 'apple-podcasts': 'https://podcasts.apple.com/us/podcast/id123' } } ). Expect an id, the slug manual-test-show, a feed_url ending in /podcasts/manual-test-show/feed/ and the Apple link. In wp-admin, open Posts > Podcasts and edit the podcast. The author, summary, category and Apple link should show. Use the id below.
  2. Get. Run await read( 'get-podcast', { id: ID } ). Expect the same values.
  3. List. Run await read( 'list-podcasts', { search: '' } ), then { search: 'Manual' }. The second call should return only podcasts with "Manual" in the name. A search with no match returns an empty list.
  4. Update. Run await post( 'update-podcast', { id: ID, summary: 'New summary', platforms: { spotify: 'https://open.spotify.com/show/abc' } } ). The summary changes, talent_name stays, and only the Spotify link remains. Then run { id: ID, name: 'Renamed Show' }. The name changes, but the slug and feed URL do not.
  5. Delete. Run { id: ID, confirm: false }. Expect confirmation_required and the podcast stays. Run { id: ID, confirm: true }. Expect { success: true, id: ID }. A get-podcast call for that ID now fails with a 403.
  6. Invalid input. Each of these must return an error and create nothing: create-podcast with {}, with an unknown property, with explicit: 'maybe', with category_1: 'not-a-category', with a platform URL of not a url, with javascript://example.com/%0Aalert(1), with an unknown platform, and with image: 999999.
  7. Cover image. Upload an image and pass its ID as image to create-podcast. Expect image_url to show the file. Pass the ID of a PDF. Expect invalid_image.
  8. Permissions. As an Author, Contributor or Subscriber, every ability returns 403. An Editor can use all five. Logged out, the REST route returns 401:
curl -sk -L -X POST -H 'Content-Type: application/json' -d '{"input":{"id":1,"confirm":true}}' https://example.test/wp-json/wp-abilities/v1/abilities/simple-podcasting/delete-podcast/run -w "\nHTTP %{http_code}\n"
  1. Term screen. Add and edit a podcast in wp-admin. Fields should save and show again. A summary of He said "hi" & left should keep its quotes with no backslashes. An invalid Spotify URL should save as an empty field, and the feed should still be valid XML.

With an AI client

Use a client that can call abilities, such as the AI plugin or an MCP client, as an Editor. Ask it to list podcasts, create one with a summary and category, add an Apple Podcasts link, and delete it. It should use the matching ability and only send confirm: true after you agree. As a Subscriber, "create a podcast" should return a permission error and create nothing.

Checks run locally

  • wp eval-file check script: 19 of 19 pass.
  • PHPUnit: 88 tests pass.
  • PHPCS: no new errors. Warnings in blocks.php and datatypes.php were already there.

Changelog Entry

Added - simple-podcasting/list-podcasts, get-podcast, create-podcast, update-podcast and delete-podcast abilities for the WordPress Abilities API, so AI agents and other clients can manage podcasts and their Apple Podcasts settings

Credits

Props @phpbits

Checklist:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Abilities API functionality to plugin

1 participant