feat(bricks+wpml): full programmatic page generation + native WPML CTE integration
Sessions 3-4 deliverables. Establishes the Asterion content pipeline :
EN pages generated programmatically from content-source/*-data.php, FR
translations rebuilt natively by Bricks's WPML integration from EN structure
+ WPML strings (no direct FR _bricks_page_content_2 writes).
## tools/_av-bricks-shared.php (the library)
Shared helpers + element atoms (eyebrow/heading/text/button/text-link) +
section builders (hero/prose/list/cards/cta/upsell/closer) + page write
pipeline that handles : write EN content via $wpdb (bypass Bricks's filter),
force Bricks::Elements::load_elements() so controls are populated, fire
wpml_page_builder_register_strings, push dict FR into icl_string_translations
(skip user CTE edits, auto-prefix /fr on internal URLs), trigger Bricks
native FR rebuild via wpml_page_builder_string_translated, and ensure a CTE
job exists (icl_translate_job.editor='wpml' + revision=NULL + populate
icl_translate rows with base64+gzip-encoded EN/FR strings).
## tools/av-generate-{industries,solutions}.php
Thin wrappers : URL trigger (template_redirect, not init — Bricks elements
load on 'wp' hook), add_filter('av_fr_translation_dict', ...) to register
FR strings, content-type-specific hub builder. ~150 FR strings per type.
## tools/av-translate-{industries,solutions}-fr.php
One-shot seeds for the initial FR linked posts via WPML trid mapping.
## tools/av-wpml-* operational mu-plugins
- set-cte : switch doc_translation_method to ICL_TM_TMETHOD_EDITOR (=1)
- create-jobs : reset CTE jobs (status=10 + needs_update=1 + editor=wpml +
revision=NULL). Integrated into pipeline via av_ensure_wpml_cte_job().
- fix-status / repair : align FR posts to correct trid + cleanup orphans.
- push-translations : push dict into icl_string_translations.
- plugins-check / debug / dump-strings / icl-dump / rescan : diagnostics.
## tools/av-fix-link-types.php
One-shot DB migration : convert legacy link.type='internal'+url to 'external'
across all _bricks_page_*_2 meta values (JSON and PHP-serialized). Bricks's
set_link_attributes() only renders href= for type='external' — older session-3
templates lost their href silently.
## Other tools/ mu-plugins
- av-bricks-rest-bridge : pivot — exposes Bricks CPTs over REST, dual-format
read filter (JSON for MCP / array for renderer), auto-mirror content→header/
footer for templates.
- av-apply-bricks-styles, av-cleanup-orphans, av-create-wp-menu, av-debug-meta,
av-dump-bricks, av-fix-template-meta : misc operational scripts.
## Bricks Config/
JSON snapshots of bricks_theme_styles, bricks_color_palette,
bricks_global_settings for diffable inspection.
## .gitignore
Add .mcp.json — holds per-developer WP App Password for the Bricks MCP server.
## wp-content/themes/asterion-bricks/assets/css/utilities.css
Note that all responsive overrides are now emitted natively by the page
generator via Bricks per-breakpoint settings, no custom CSS needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
149
tools/av-generate-industries.php
Normal file
149
tools/av-generate-industries.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Asterion — Generate industry pages (one-shot)
|
||||
* Description: /?av_generate_industries=1 reads content-source/industries-data.php
|
||||
* and creates/updates : hub /industries/ + 4 child pages (police, special-forces,
|
||||
* military, firefighters). Idempotent.
|
||||
*
|
||||
* Heavy lifting (builders + FR pipeline) lives in _av-bricks-shared.php.
|
||||
* This file only owns : URL trigger + hub builder + FR strings for Industries.
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
require_once __DIR__ . '/_av-bricks-shared.php';
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
Register Industries-specific FR strings into the shared dict.
|
||||
----------------------------------------------------------------- */
|
||||
add_filter('av_fr_translation_dict', function ($dict) {
|
||||
return array_merge($dict, [
|
||||
// Hub
|
||||
'Industries' => 'Secteurs',
|
||||
"Built for those who carry the weight."
|
||||
=> "Conçu pour celles et ceux qui en portent le poids.",
|
||||
"Police, special forces, military, and emergency services share a hard truth: their craft is rehearsed in the worst conditions, and rarely enough. PROSERVE\u{2122} puts contemporary training within everyone's reach \u{2014} every shift, every budget, every operating context."
|
||||
=> "Police, forces spéciales, armée et services d'urgence partagent une vérité dure : leur métier se répète dans les pires conditions, et trop rarement. PROSERVE\u{2122} met l'entraînement contemporain à la portée de tous \u{2014} chaque service, chaque budget, chaque contexte opérationnel.",
|
||||
"Four operating contexts. One platform."
|
||||
=> "Quatre contextes opérationnels. Une seule plateforme.",
|
||||
|
||||
// Card eyebrows + tile labels
|
||||
'National & Municipal' => 'Nationale & Municipale',
|
||||
'Police' => 'Police',
|
||||
"Police training, when the schedule won't bend."
|
||||
=> "L'entraînement police, quand l'agenda ne fléchit pas.",
|
||||
'Tactical units' => 'Unités tactiques',
|
||||
'Special Forces' => 'Forces spéciales',
|
||||
"Special forces train differently. Their simulation should, too."
|
||||
=> "Les forces spéciales s'entraînent autrement. Leur simulation devrait suivre.",
|
||||
'Armed forces' => 'Forces armées',
|
||||
'Military' => 'Armée',
|
||||
"Military VR training. Sovereign. Modular. Field-deployable."
|
||||
=> "Entraînement VR pour l'armée. Souverain. Modulaire. Déployable sur le terrain.",
|
||||
'Emergency services' => "Services d'urgence",
|
||||
'Firefighters' => 'Sapeurs-pompiers',
|
||||
"Train the worst day. Safely. Repeatedly."
|
||||
=> "Entraînez-vous au pire jour. En sécurité. Indéfiniment.",
|
||||
|
||||
// Card body concatenations from hub builder
|
||||
"National & Municipal \u{2014} Police training, when the schedule won't bend."
|
||||
=> "Nationale & Municipale \u{2014} L'entraînement police, quand l'agenda ne fléchit pas.",
|
||||
"Tactical units \u{2014} Special forces train differently. Their simulation should, too."
|
||||
=> "Unités tactiques \u{2014} Les forces spéciales s'entraînent autrement. Leur simulation devrait suivre.",
|
||||
"Armed forces \u{2014} Military VR training. Sovereign. Modular. Field-deployable."
|
||||
=> "Forces armées \u{2014} Entraînement VR pour l'armée. Souverain. Modulaire. Déployable sur le terrain.",
|
||||
"Emergency services \u{2014} Train the worst day. Safely. Repeatedly."
|
||||
=> "Services d'urgence \u{2014} Entraînez-vous au pire jour. En sécurité. Indéfiniment.",
|
||||
|
||||
// Hero / detail-page CTA labels specific to Industries
|
||||
'Request a Demo for police use' => 'Demander une démo pour la police',
|
||||
'Request a Demo for fire & emergency' => "Demander une démo pour pompiers & urgences",
|
||||
]);
|
||||
});
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
URL trigger — fires on template_redirect (NOT init) so Bricks's
|
||||
element controls are loaded (happens on 'wp' hook). Without loaded
|
||||
controls, Bricks's wpml_page_builder_register_strings handler skips
|
||||
every element silently and NO strings reach icl_strings → ATE empty.
|
||||
----------------------------------------------------------------- */
|
||||
add_action('template_redirect', function () {
|
||||
if (empty($_GET['av_generate_industries'])) return;
|
||||
if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) wp_die('localhost only');
|
||||
|
||||
$data_file = 'C:\\ASTERION\\GIT\\WP2026\\content-source\\industries-data.php';
|
||||
if (! file_exists($data_file)) wp_die("data file not found: $data_file");
|
||||
|
||||
$industries = include $data_file;
|
||||
if (! is_array($industries)) wp_die('industries data not array');
|
||||
|
||||
$parent_id = av_ensure_hub_page('industries', 'Industries');
|
||||
$report = ["Rebuilding /industries/ hub + child detail pages..."];
|
||||
|
||||
// Hub
|
||||
$hub_elements = av_build_industries_hub_elements($industries);
|
||||
$hub_write = av_write_bricks_page_elements($parent_id, $hub_elements);
|
||||
$report[] = " HUB → page #$parent_id (rebuilt) — " . count($hub_elements)
|
||||
. " elements (json={$hub_write['json_len']}, stored={$hub_write['stored_len']})";
|
||||
|
||||
// Detail pages
|
||||
foreach ($industries as $slug => $d) {
|
||||
$page = av_upsert_detail_page($slug, 'industries', $d);
|
||||
$report[] = " $slug → page #{$page['id']} ({$page['action']}) — {$page['element_count']} elements (json={$page['json_len']}, stored={$page['stored_len']})";
|
||||
}
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo implode("\n", $report);
|
||||
exit;
|
||||
});
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
Industries hub builder : hero + 4-card grid + closer CTA
|
||||
----------------------------------------------------------------- */
|
||||
function av_build_industries_hub_elements($industries) {
|
||||
$els = [];
|
||||
|
||||
// HERO
|
||||
$els = array_merge($els, av_build_hero('hub', [
|
||||
'eyebrow' => 'Industries',
|
||||
'tagline' => "Built for those who carry the weight.",
|
||||
'hero_lead' => "Police, special forces, military, and emergency services share a hard truth: their craft is rehearsed in the worst conditions, and rarely enough. PROSERVE\u{2122} puts contemporary training within everyone's reach \u{2014} every shift, every budget, every operating context.",
|
||||
'ctas' => [
|
||||
['label' => 'Book a demo', 'href' => '/request-demo/', 'variant' => 'primary'],
|
||||
['label' => 'Request a T&E Kit', 'href' => '/request-te-kit/', 'variant' => 'secondary'],
|
||||
],
|
||||
]));
|
||||
|
||||
// 4-card grid — one card per industry
|
||||
$cards_section = [
|
||||
'eyebrow' => '',
|
||||
'title' => 'Four operating contexts. One platform.',
|
||||
'items' => [],
|
||||
];
|
||||
foreach ($industries as $slug => $d) {
|
||||
$cards_section['items'][] = [
|
||||
'title' => $d['title'],
|
||||
'body' => ($d['eyebrow'] ?? '') . " \u{2014} " . ($d['tagline'] ?? ''),
|
||||
];
|
||||
}
|
||||
$els = array_merge($els, av_build_cards_section('hubgrid', 0, $cards_section, 'var(--color-bg-subtle)'));
|
||||
|
||||
// Append "Learn more →" link in each card (post-build patch)
|
||||
$i = 0;
|
||||
foreach ($industries as $slug => $d) {
|
||||
$kid = "khubgridc0x{$i}";
|
||||
$lid = "lnhubgridc0x{$i}";
|
||||
$els[] = av_text_link($lid, $kid, "Learn more \u{2192}", "/industries/$slug/");
|
||||
foreach ($els as &$el) {
|
||||
if (($el['id'] ?? '') === $kid) {
|
||||
$el['children'][] = $lid;
|
||||
}
|
||||
}
|
||||
unset($el);
|
||||
$i++;
|
||||
}
|
||||
|
||||
// CLOSER CTA
|
||||
$els = array_merge($els, av_build_closer_cta('hub'));
|
||||
|
||||
return $els;
|
||||
}
|
||||
Reference in New Issue
Block a user