Podcast: add + Create episode button and post-new prefill#48884
Podcast: add + Create episode button and post-new prefill#48884robertbpugh wants to merge 2 commits into
Conversation
Persistent header CTA on the /podcast dashboard plus a server-side prefill that catches post-new.php?podcast_episode=1. Dashboard: - '+ Create episode' button rendered into AdminPage's 'actions' slot (top-right of the unified header). Visible once isSetUp is true; hidden before the user picks a category. Same destination and label for every plan. Server-side prefill (new class New_Episode_Prefill): - On post-new.php?podcast_episode=1, hook wp_insert_post on the initial auto-draft and wp_set_post_categories to the configured podcasting_category_id. Filters narrow to single insert (not update), post post-type, auto-draft status, so the user can still pick a different category in-editor without re-override. - On Premium (Podcast_Gate::has_product_access()), hook default_content to inject '<!-- wp:jetpack/podcast-episode /-->' as the post's initial content. Free users get a plain new post and add a core Audio block themselves. Depends on the Podcast_Gate helper from #48702. CI will fail on class-not-found until that lands; once it merges this should rebase clean on trunk.
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage SummaryThis PR did not change code coverage! That could be good or bad, depending on the situation. Everything covered before, and still is? Great! Nothing was covered before? Not so great. 🤷 |
Review verdict: READY (after fix), with one flagged dependencyScope reviewed: branch diff against trunk — 4 files:
Findings
Flagged dependency (P1, documented in PR body)Codex flagged SeverityNone CRITICAL, 1 MAJOR (cross-PR dependency, already documented), none MINOR. |
There was a problem hiding this comment.
Pull request overview
Adds a "+ Create episode" button to the Podcast dashboard header and wires server-side prefill on post-new.php?podcast_episode=1 to assign the configured podcast category (all plans) and inject a Podcast Episode block (Premium only, via the still-unmerged Podcast_Gate).
Changes:
- New header CTA in
dashboard/index.tsx, visible only once a podcast category is configured. - New
New_Episode_Prefillclass wiringwp_insert_post(category assignment) anddefault_content(block prefill) underadmin_init, query-flag-guarded. - Boot the new class from
Podcast::init()inside theis_admin()branch.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| projects/packages/podcast/src/dashboard/index.tsx | Adds the + Create episode Button and passes it as AdminPage actions slot; builds the new-post URL at module load. |
| projects/packages/podcast/src/class-podcast.php | Boots New_Episode_Prefill::init() alongside Admin_Page::init(). |
| projects/packages/podcast/src/class-new-episode-prefill.php | New class implementing the category and block-content prefill on the ?podcast_episode=1 flag. |
| projects/packages/podcast/changelog/pods-create-episode-button | Changelog entry (minor / added) describing the new CTA and prefill. |
| class New_Episode_Prefill { | ||
|
|
||
| const QUERY_VAR = 'podcast_episode'; | ||
|
|
||
| /** | ||
| * Register hooks. | ||
| * | ||
| * Called from `Podcast::init()` inside the `is_admin()` branch. | ||
| */ | ||
| public static function init() { | ||
| add_action( 'admin_init', array( __CLASS__, 'maybe_register_handlers' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Wire the auto-draft / default-content filters only on | ||
| * `post-new.php?podcast_episode=1`. | ||
| * | ||
| * We bind on `admin_init` rather than at load time so `$pagenow` is | ||
| * settled. | ||
| */ | ||
| public static function maybe_register_handlers() { | ||
| global $pagenow; | ||
| if ( 'post-new.php' !== $pagenow ) { | ||
| return; | ||
| } | ||
| // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| if ( ! isset( $_GET[ self::QUERY_VAR ] ) ) { | ||
| return; | ||
| } | ||
| // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| if ( '1' !== sanitize_text_field( wp_unslash( $_GET[ self::QUERY_VAR ] ) ) ) { | ||
| return; | ||
| } | ||
|
|
||
| add_action( 'wp_insert_post', array( __CLASS__, 'assign_category' ), 10, 3 ); | ||
|
|
||
| if ( Podcast_Gate::has_product_access() ) { | ||
| add_filter( 'default_content', array( __CLASS__, 'prefill_block_content' ), 10, 2 ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Assign the configured podcast category to the new auto-draft. | ||
| * | ||
| * Fires on `wp_insert_post` once for the auto-draft `post-new.php` creates; | ||
| * we filter to that single insert (new, not update; post post-type; | ||
| * auto-draft status) so user-driven saves later in the editor session | ||
| * aren't re-overridden. | ||
| * | ||
| * @param int $post_id Post ID. | ||
| * @param \WP_Post $post Post object. | ||
| * @param bool $update True for updates, false for inserts. | ||
| */ | ||
| public static function assign_category( $post_id, $post, $update ) { | ||
| if ( $update ) { | ||
| return; | ||
| } | ||
| if ( ! ( $post instanceof \WP_Post ) ) { | ||
| return; | ||
| } | ||
| if ( 'post' !== $post->post_type || 'auto-draft' !== $post->post_status ) { | ||
| return; | ||
| } | ||
|
|
||
| $category_id = (int) get_option( 'podcasting_category_id', 0 ); | ||
| if ( $category_id <= 0 ) { | ||
| return; | ||
| } | ||
|
|
||
| wp_set_post_categories( $post_id, array( $category_id ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Inject a Podcast Episode block as the new post's initial content. | ||
| * | ||
| * The `default_content` filter runs once when the editor loads the | ||
| * auto-draft. If a previous filter (or another plugin) has already filled | ||
| * `$content`, leave it alone. | ||
| * | ||
| * @param string $content Default post content. | ||
| * @param \WP_Post $post Post object. | ||
| * @return string | ||
| */ | ||
| public static function prefill_block_content( $content, $post ) { | ||
| if ( ! ( $post instanceof \WP_Post ) || 'post' !== $post->post_type ) { | ||
| return $content; | ||
| } | ||
| if ( '' !== trim( (string) $content ) ) { | ||
| return $content; | ||
| } | ||
| return "<!-- wp:jetpack/podcast-episode /-->\n"; | ||
| } | ||
| } |
| <Button variant="primary" href={ NEW_EPISODE_URL }> | ||
| { __( '+ Create episode', 'jetpack-podcast' ) } | ||
| </Button> |
| const ADMIN_URL = getSiteData()?.admin_url ?? '/wp-admin/'; | ||
|
|
||
| // Server-side filters key off `?podcast_episode=1` to apply the configured | ||
| // podcast category (and, on Premium, prefill the Podcast Episode block). | ||
| const NEW_EPISODE_URL = `${ ADMIN_URL }post-new.php?podcast_episode=1`; |
| public static function assign_category( $post_id, $post, $update ) { | ||
| if ( $update ) { | ||
| return; | ||
| } | ||
| if ( ! ( $post instanceof \WP_Post ) ) { | ||
| return; | ||
| } | ||
| if ( 'post' !== $post->post_type || 'auto-draft' !== $post->post_status ) { | ||
| return; | ||
| } | ||
|
|
||
| $category_id = (int) get_option( 'podcasting_category_id', 0 ); | ||
| if ( $category_id <= 0 ) { | ||
| return; | ||
| } | ||
|
|
||
| wp_set_post_categories( $post_id, array( $category_id ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Inject a Podcast Episode block as the new post's initial content. | ||
| * | ||
| * The `default_content` filter runs once when the editor loads the | ||
| * auto-draft. If a previous filter (or another plugin) has already filled | ||
| * `$content`, leave it alone. | ||
| * | ||
| * @param string $content Default post content. | ||
| * @param \WP_Post $post Post object. | ||
| * @return string | ||
| */ | ||
| public static function prefill_block_content( $content, $post ) { | ||
| if ( ! ( $post instanceof \WP_Post ) || 'post' !== $post->post_type ) { | ||
| return $content; | ||
| } | ||
| if ( '' !== trim( (string) $content ) ) { | ||
| return $content; | ||
| } | ||
| return "<!-- wp:jetpack/podcast-episode /-->\n"; |
|
|
||
| // Header CTA: visible once the show is configured. Same destination and | ||
| // label for every plan — the Premium value-add lives inside the editor | ||
| // (PR 7's server-side filter inserts the Podcast Episode block on load), |
|
|
||
| add_action( 'wp_insert_post', array( __CLASS__, 'assign_category' ), 10, 3 ); | ||
|
|
||
| if ( Podcast_Gate::has_product_access() ) { |
Fixes #
Proposed changes
Persistent "+ Create episode" header CTA on
/podcastplus a server-side prefill behind the same URL flag.Dashboard
+ Create episodebutton rendered into the AdminPageactionsslot (top-right of the unified header). Visible wheneverisSetUpis true (a podcast category is configured) and hidden before setup. Same label, same destination for every plan — the Premium value-add lives inside the editor, not at this button.Server-side prefill (new
Automattic\Jetpack\Podcast\New_Episode_Prefill)admin_initonly when$pagenow === 'post-new.php'and?podcast_episode=1.wp_insert_postaction runs once on the initial auto-draft and callswp_set_post_categories( $post_id, [ podcasting_category_id ] ). Guards on! $update && post_type === 'post' && post_status === 'auto-draft'so user-driven saves later in the session aren't re-overridden.Podcast_Gate::has_product_access()from Podcast: add product-access gate and grandfather sticker constant #48702), thedefault_contentfilter returns<!-- wp:jetpack/podcast-episode /-->. Free users get a plain new post and add a core Audio block themselves with an externally hosted URL.Dependencies
Podcast_Gate::has_product_access()). CI will fail with class-not-found onPodcast_Gateuntil that merges. Once it does, this PR should rebase clean on trunk.WPCOM_Features::PODCASTING).Podcast_Gatecurrently uses the literal'podcasting'slug; once WPCOM_Features: add PODCASTING feature and Premium-and-higher plan mapping #48700 merges, the gate helper can swap to the constant in a separate cleanup — this PR doesn't need that.Per Rob's override: branched from current
trunkrather than frompods-141-add-gate-helper-and-grandfather-sticker-constantbecause that branch doesn't yet include thejetpack/podcast-episodeblock this PR'sdefault_contentfilter references. Trunk has both the block and (after #48702 merges) the gate helper.Related product discussion/links
Does this pull request change what data or activity we track or use?
No.
Testing instructions
add_filter( 'jetpack_podcast_untangle', '__return_true' );/podcast. The "+ Create episode" button should NOT be visible.wp-admin/post-new.php?podcast_episode=1with your configured podcast category already ticked in the post sidebar.podcasting-grandfatheredblog sticker), the new post should already contain an inserted Podcast Episode block ready to receive media.Notes for reviewer:
default_contentfilter no-ops if$contentis already non-empty, so this composes politely with other plugins that prefill new posts.Spec excerpt (from the work order):
Per Rob's override (out-of-office): using
Podcast_Gate::has_product_access()from #48702. Branched from trunk so the Podcast Episode block referenced bydefault_contentexists on this branch.