File: /var/www/kevin-demo/wp-content/plugins/allspice/includes/mealplans-page.php
<?php
// includes/mealplans-page.php
//
// Native meal-plans landing page - the marketing surface for the meal plans a publisher
// builds in Allspice (thefresh20.com/meal-plans/ is the shape being chased). Same
// create/adopt/regenerate lifecycle as the membership page: generated pages are drafts of
// plain editable Gutenberg blocks the publisher owns, with exactly one dynamic piece - the
// existing allspice/mealplan-products grid, which hydrates from the public products API and
// routes every tap into the widget's meal-plan pane.
//
// Detail pages (/meal-plans/classic/-style) are not generated: products change in the
// Allspice dashboard, and auto-managed per-product pages would fight publisher edits. The
// allspice/mealplan-feature block (blocks.php) exists for that instead - the publisher makes
// a page at whatever slug they like and drops the block in, configured with a product id.
if (!defined('ABSPATH')) exit;
const ALLSPICE_MEALPLANS_PAGE_OPTION = 'allspice_mealplans_page_id';
const ALLSPICE_CREATE_MEALPLANS_PAGE_ACTION = 'allspice_create_mealplans_page';
const ALLSPICE_REGENERATE_MEALPLANS_PAGE_ACTION = 'allspice_regenerate_mealplans_page';
function allspice_mealplans_page_slug(): string {
$slug = sanitize_title((string)apply_filters('allspice_mealplans_page_slug', 'meal-plans'));
return $slug !== '' ? $slug : 'meal-plans';
}
function allspice_mealplans_page_title(): string {
$title = trim((string)apply_filters('allspice_mealplans_page_title', 'Meal Plans'));
return $title !== '' ? $title : 'Meal Plans';
}
/* ---------------------------------------------------------------------- page content */
/*
* The generated page: core blocks the publisher edits like any page content, with the
* products grid as the one dynamic drop-in. Copy is starter copy - every
* sentence is expected to be rewritten in the site's voice; the structure (hero → grid →
* how-it-works → testimonial → FAQ → closing CTA) is the part being provided.
*
* The closing CTA is a plain core button whose href is the #allspice-mealplans hash - the
* same deep link the products block and campaign URLs use, handled by the page bundle's
* hash-action boot. No shortcode, no custom block needed for a CTA.
*/
function allspice_mealplans_page_pattern(): string {
return <<<HTML
<!-- wp:group {"className":"allspice-mealplans-page","layout":{"type":"constrained"}} -->
<div class="wp-block-group allspice-mealplans-page"><!-- wp:heading {"textAlign":"center","level":1} -->
<h1 class="wp-block-heading has-text-align-center">Meal plans that survive a real week</h1>
<!-- /wp:heading -->
<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">Every plan comes as complete days of tried-and-true recipes from this site - with the grocery list built for you and step-by-step help while you cook.</p>
<!-- /wp:paragraph -->
<!-- wp:allspice/mealplan-products {"title":"Choose your plan","subtitle":"Tap a plan to preview the full week."} /-->
<!-- wp:heading {"textAlign":"center","level":2} -->
<h2 class="wp-block-heading has-text-align-center">What comes with every plan</h2>
<!-- /wp:heading -->
<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">A full week, planned</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Complete days of breakfasts, lunches and dinners - not a pile of links. Open any day to see exactly what's cooking.</p>
<!-- /wp:paragraph --></div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Groceries build themselves</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>One tap turns any plan - or any single day - into an organized grocery list, combined and sorted by aisle.</p>
<!-- /wp:paragraph --></div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Help while you cook</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Every recipe opens with guided cooking and an assistant that answers substitutions, scaling and "can I make this ahead?" on the spot.</p>
<!-- /wp:paragraph --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- wp:heading {"textAlign":"center","level":2} -->
<h2 class="wp-block-heading has-text-align-center">What cooks are saying</h2>
<!-- /wp:heading -->
<!-- wp:columns {"className":"allspice-mp-testimonials"} -->
<div class="wp-block-columns allspice-mp-testimonials"><!-- wp:column {"className":"allspice-mp-testimonial"} -->
<div class="wp-block-column allspice-mp-testimonial"><!-- wp:paragraph {"className":"allspice-mp-testimonial__stars"} -->
<p class="allspice-mp-testimonial__stars" aria-hidden="true">★★★★★</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__quote"} -->
<p class="allspice-mp-testimonial__quote">"[Replace this placeholder with a real reader's words - what did these plans change about their week?]"</p>
<!-- /wp:paragraph -->
<!-- wp:separator {"className":"allspice-mp-testimonial__rule"} -->
<hr class="wp-block-separator has-alpha-channel-opacity allspice-mp-testimonial__rule"/>
<!-- /wp:separator -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__name"} -->
<p class="allspice-mp-testimonial__name"><strong>[Reader name]</strong></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__role"} -->
<p class="allspice-mp-testimonial__role">[e.g. Cooking these plans since 2023]</p>
<!-- /wp:paragraph --></div>
<!-- /wp:column -->
<!-- wp:column {"className":"allspice-mp-testimonial"} -->
<div class="wp-block-column allspice-mp-testimonial"><!-- wp:paragraph {"className":"allspice-mp-testimonial__stars"} -->
<p class="allspice-mp-testimonial__stars" aria-hidden="true">★★★★★</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__quote"} -->
<p class="allspice-mp-testimonial__quote">"[A second reader's words - the grocery list, the time saved, the picky eater won over.]"</p>
<!-- /wp:paragraph -->
<!-- wp:separator {"className":"allspice-mp-testimonial__rule"} -->
<hr class="wp-block-separator has-alpha-channel-opacity allspice-mp-testimonial__rule"/>
<!-- /wp:separator -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__name"} -->
<p class="allspice-mp-testimonial__name"><strong>[Reader name]</strong></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__role"} -->
<p class="allspice-mp-testimonial__role">[e.g. Feeding a family of five]</p>
<!-- /wp:paragraph --></div>
<!-- /wp:column -->
<!-- wp:column {"className":"allspice-mp-testimonial"} -->
<div class="wp-block-column allspice-mp-testimonial"><!-- wp:paragraph {"className":"allspice-mp-testimonial__stars"} -->
<p class="allspice-mp-testimonial__stars" aria-hidden="true">★★★★★</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__quote"} -->
<p class="allspice-mp-testimonial__quote">"[A third reader's words - keep it short and specific; one true sentence beats three vague ones.]"</p>
<!-- /wp:paragraph -->
<!-- wp:separator {"className":"allspice-mp-testimonial__rule"} -->
<hr class="wp-block-separator has-alpha-channel-opacity allspice-mp-testimonial__rule"/>
<!-- /wp:separator -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__name"} -->
<p class="allspice-mp-testimonial__name"><strong>[Reader name]</strong></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"className":"allspice-mp-testimonial__role"} -->
<p class="allspice-mp-testimonial__role">[e.g. Longtime reader]</p>
<!-- /wp:paragraph --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- wp:heading {"textAlign":"center","level":2} -->
<h2 class="wp-block-heading has-text-align-center">Frequently Asked Questions</h2>
<!-- /wp:heading -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">How do I use a meal plan?</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Tap any plan above to preview the full week. When you're ready, add it to your planner - you can swap individual meals, change serving sizes, and generate the grocery list from there.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Do I need an account?</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>A free account keeps your plans, grocery lists and saved recipes together on every device. You can browse the plans without one.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Can I change a plan after adding it?</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Yes - every plan becomes fully yours once added. Swap meals in or out, move days around, and the grocery list keeps up.</p>
<!-- /wp:paragraph -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons"><!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="#allspice-mealplans">Browse the meal plans</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons --></div>
<!-- /wp:group -->
HTML;
}
/* Container + testimonial-card styling for the generated page, riding the always-loaded gate
stylesheet handle exactly as the membership page does. The pattern must not depend on the
theme supplying a constrained layout: classic/full-width page canvases (FitCook) rendered
every section 0px from the viewport edge because nothing anywhere set a max-width. Scoped
under the wrapper class and kept minimal, so theme/publisher styling still wins the rest. */
add_action('wp_enqueue_scripts', 'allspice_mealplans_page_styles', 22);
function allspice_mealplans_page_styles(): void {
wp_add_inline_style('allspice-gate',
'.allspice-mealplans-page{max-width:1120px;margin:0 auto;padding:8px 20px 28px;box-sizing:border-box}'
. '.allspice-mealplans-page h1.wp-block-heading{margin:10px 0 8px}'
. '.allspice-mealplans-page h2.wp-block-heading{margin:40px 0 16px}'
/* Testimonials: card grid (stars, quote, rule pinned to the bottom, name, tenure) so
the three names align even when quotes run different lengths. */
. '.allspice-mp-testimonials{gap:20px}'
. '.allspice-mp-testimonials .allspice-mp-testimonial{background:#f7f2f4;border-radius:16px;padding:26px 24px;display:flex;flex-direction:column}'
. '.allspice-mp-testimonial .allspice-mp-testimonial__stars{color:#7c3a5c;font-size:15px;letter-spacing:3px;margin:0 0 12px}'
. '.allspice-mp-testimonial .allspice-mp-testimonial__quote{margin:0 0 18px;line-height:1.55}'
. '.allspice-mp-testimonial .allspice-mp-testimonial__rule{margin:auto 0 14px;width:100%;max-width:none;border:0;border-top:1px solid rgba(0,0,0,.12);opacity:1}'
. '.allspice-mp-testimonial .allspice-mp-testimonial__name{margin:0}'
. '.allspice-mp-testimonial .allspice-mp-testimonial__role{margin:2px 0 0;font-size:.9em;opacity:.65}'
);
}
function allspice_mealplans_page_default_content(): string {
if (function_exists('register_block_type')) {
return allspice_mealplans_page_pattern();
}
/* A WP too old for blocks still gets a working page: the hash deep link alone. */
return '<p><a href="#allspice-mealplans">Browse the meal plans</a></p>';
}
/* ------------------------------------------------------------------------ admin creation */
/* The saved meal-plans page when it is still a valid, non-trashed page - else null. */
function allspice_mealplans_page_current() {
$saved = (int)get_option(ALLSPICE_MEALPLANS_PAGE_OPTION, 0);
if ($saved <= 0) return null;
$post = get_post($saved);
if (!$post || $post->post_type !== 'page' || $post->post_status === 'trash') return null;
return $post;
}
function allspice_mealplans_page_editor_url(int $page_id): string {
return admin_url('post.php?post=' . $page_id . '&action=edit');
}
/*
* Create Meal Plans Page - same contract as the membership page: idempotent, never
* publishes, never overwrites publisher edits.
* 1. valid saved page id -> reuse
* 2. page at the slug -> adopt (store its id)
* 3. otherwise -> create a DRAFT page from the pattern
*/
function allspice_create_mealplans_page(bool $force_create = false): array {
if (!$force_create) {
$current = allspice_mealplans_page_current();
if ($current !== null) {
return ['ok' => true, 'page_id' => (int)$current->ID, 'created' => false, 'reason' => 'reused_saved_id'];
}
$slug = allspice_mealplans_page_slug();
$by_slug = function_exists('get_page_by_path') ? get_page_by_path($slug, OBJECT, 'page') : null;
if ($by_slug && $by_slug->post_status !== 'trash') {
update_option(ALLSPICE_MEALPLANS_PAGE_OPTION, (int)$by_slug->ID, false);
return ['ok' => true, 'page_id' => (int)$by_slug->ID, 'created' => false, 'reason' => 'adopted_slug_page'];
}
}
$page_id = wp_insert_post([
'post_type' => 'page',
'post_status' => 'draft', // NEVER published automatically
'post_title' => allspice_mealplans_page_title(),
'post_name' => allspice_mealplans_page_slug(),
'post_content' => allspice_mealplans_page_default_content(),
], true);
if (is_wp_error($page_id) || (int)$page_id <= 0) {
return ['ok' => false, 'page_id' => 0, 'created' => false, 'reason' => 'insert_failed'];
}
update_option(ALLSPICE_MEALPLANS_PAGE_OPTION, (int)$page_id, false);
return ['ok' => true, 'page_id' => (int)$page_id, 'created' => true, 'reason' => 'created_draft'];
}
/*
* Regenerate: current generated page goes to Trash (recoverable - publisher edits are never
* destroyed), then a fresh draft is created from the latest pattern.
*/
function allspice_regenerate_mealplans_page(): array {
$current = allspice_mealplans_page_current();
if ($current !== null) {
wp_delete_post((int)$current->ID, false); /* false = Trash, NEVER permanent delete */
}
delete_option(ALLSPICE_MEALPLANS_PAGE_OPTION);
$r = allspice_create_mealplans_page(true);
$r['trashed_page_id'] = $current !== null ? (int)$current->ID : 0;
return $r;
}
add_action('admin_post_' . ALLSPICE_CREATE_MEALPLANS_PAGE_ACTION, 'allspice_admin_post_create_mealplans_page');
function allspice_admin_post_create_mealplans_page(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden', 403);
check_admin_referer(ALLSPICE_CREATE_MEALPLANS_PAGE_ACTION);
$r = allspice_create_mealplans_page();
if (!empty($r['ok']) && (int)$r['page_id'] > 0) {
wp_safe_redirect(allspice_mealplans_page_editor_url((int)$r['page_id']));
exit;
}
set_transient('allspice_admin_notice', ['msg' => 'Could not create the meal plans page.', 'type' => 'error'], 30);
wp_safe_redirect(admin_url('options-general.php?page=' . ALLSPICE_SETTINGS_PAGE));
exit;
}
add_action('admin_post_' . ALLSPICE_REGENERATE_MEALPLANS_PAGE_ACTION, 'allspice_admin_post_regenerate_mealplans_page');
function allspice_admin_post_regenerate_mealplans_page(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden', 403);
check_admin_referer(ALLSPICE_REGENERATE_MEALPLANS_PAGE_ACTION);
$r = allspice_regenerate_mealplans_page();
if (!empty($r['ok']) && (int)$r['page_id'] > 0) {
wp_safe_redirect(allspice_mealplans_page_editor_url((int)$r['page_id']));
exit;
}
set_transient('allspice_admin_notice', ['msg' => 'Could not regenerate the meal plans page.', 'type' => 'error'], 30);
wp_safe_redirect(admin_url('options-general.php?page=' . ALLSPICE_SETTINGS_PAGE));
exit;
}