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:
185
tools/av-wpml-create-jobs.php
Normal file
185
tools/av-wpml-create-jobs.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Asterion — Create ATE-flagged translate_job rows for our FR pages
|
||||
* Description:
|
||||
* /?av_wpml_create_jobs=1 → for each FR linked page, insert a row in
|
||||
* wp_icl_translate_job with editor='ate' + translated=1, and adjust the
|
||||
* translation_status row to mirror Bricks #14's pattern (status=2 IN_PROGRESS
|
||||
* with uuid + batch_id). This should switch the page-list icon from pencil
|
||||
* ✏️ to gear ⚙️ and route the click to ATE instead of WP editor.
|
||||
*
|
||||
* /?av_wpml_remove_jobs=1 → revert if it breaks something (delete jobs, set
|
||||
* status=10 again).
|
||||
*
|
||||
* This is exploratory : we mimic the structure WPML's internal send_jobs()
|
||||
* creates, without going through TranslationProxy (cloud).
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
add_action('template_redirect', function () {
|
||||
if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) return;
|
||||
if (! empty($_GET['av_wpml_create_jobs'])) av_wpml_create_jobs();
|
||||
if (! empty($_GET['av_wpml_remove_jobs'])) av_wpml_remove_jobs();
|
||||
});
|
||||
|
||||
function av_target_fr_translations() {
|
||||
global $wpdb;
|
||||
$slugs = ['industries', 'police', 'special-forces', 'military', 'firefighters',
|
||||
'solutions', 'my-proserve', 'proserve-flex', 'proserve-academy', 'customization'];
|
||||
$placeholders = implode(',', array_fill(0, count($slugs), '%s'));
|
||||
return $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT p.ID AS post_id, p.post_title AS title, t.trid, t.translation_id, ts.rid, ts.status
|
||||
FROM {$wpdb->posts} p
|
||||
JOIN {$wpdb->prefix}icl_translations t ON t.element_id = p.ID AND t.element_type='post_page' AND t.language_code='fr'
|
||||
LEFT JOIN {$wpdb->prefix}icl_translation_status ts ON ts.translation_id = t.translation_id
|
||||
WHERE p.post_name IN ($placeholders)",
|
||||
...$slugs
|
||||
));
|
||||
}
|
||||
|
||||
function av_wpml_create_jobs() {
|
||||
global $wpdb;
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$rows = av_target_fr_translations();
|
||||
if (! $rows) { echo "No FR translations found\n"; exit; }
|
||||
|
||||
$batch_id = 1; // reuse Bricks #14's batch (or create new one — using 1 for simplicity)
|
||||
|
||||
foreach ($rows as $r) {
|
||||
if (! $r->rid) {
|
||||
echo "FR #{$r->post_id} '{$r->title}' : no translation_status row, skipping\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. Set translation_status to status=2 (ICL_TM_IN_PROGRESS) to match
|
||||
// Bricks #14's pattern. This is what makes the FR column icon = gear ⚙️
|
||||
// and routes the click to the translation editor (CTE).
|
||||
// Note: editing the EN page directly will show a "Translation in progress,
|
||||
// wait before editing" warning dialog — informative only, click "I understand"
|
||||
// to dismiss.
|
||||
$uuid = wp_generate_uuid4();
|
||||
$wpdb->update(
|
||||
$wpdb->prefix . 'icl_translation_status',
|
||||
[
|
||||
'status' => 2, // ICL_TM_IN_PROGRESS (= gear icon)
|
||||
'needs_update' => 0,
|
||||
'translator_id' => 1,
|
||||
'translation_service' => 'local',
|
||||
'batch_id' => $batch_id,
|
||||
'uuid' => $uuid,
|
||||
'tp_revision' => 1,
|
||||
'timestamp' => current_time('mysql'),
|
||||
'links_fixed' => 1,
|
||||
],
|
||||
['rid' => $r->rid]
|
||||
);
|
||||
|
||||
// 2. Insert/update translate_job row with editor='ate'
|
||||
$existing_job = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT job_id FROM {$wpdb->prefix}icl_translate_job WHERE rid=%d ORDER BY job_id DESC LIMIT 1",
|
||||
$r->rid
|
||||
));
|
||||
|
||||
// editor='wpml' = CTE (Classic Translation Editor), works locally without
|
||||
// cloud sync. translated=1 + completed_date set = the FR is "ready",
|
||||
// matches Bricks #14's pattern (which shows gear icon and opens CTE).
|
||||
// CRITICAL : revision MUST be NULL. WPML's filter_status_css_class() in
|
||||
// class-wpml-tm-translation-status-display.php joins icl_translate_job
|
||||
// with the explicit condition `translate_job.revision IS NULL`. Jobs
|
||||
// with non-null revision are silently ignored → pencil icon, no CTE.
|
||||
$job_data = [
|
||||
'rid' => $r->rid,
|
||||
'translator_id' => 1,
|
||||
'translated' => 1, // FR considered done (we pre-filled via dict)
|
||||
'manager_id' => 1,
|
||||
// 'revision' deliberately omitted → DB default = NULL
|
||||
'title' => $r->title,
|
||||
'deadline_date' => null,
|
||||
'completed_date' => current_time('mysql'),
|
||||
'editor' => 'wpml', // CTE (matches doc_translation_method=1)
|
||||
'editor_job_id' => null,
|
||||
'automatic' => 0,
|
||||
];
|
||||
|
||||
if ($existing_job) {
|
||||
$wpdb->update($wpdb->prefix . 'icl_translate_job', $job_data, ['job_id' => $existing_job]);
|
||||
// $wpdb->update can't write SQL NULL (it coerces to empty string).
|
||||
// Force revision NULL via raw query for the icon's JOIN to match.
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$wpdb->prefix}icl_translate_job SET revision = NULL WHERE job_id = %d",
|
||||
$existing_job
|
||||
));
|
||||
echo "FR #{$r->post_id} '{$r->title}' (rid={$r->rid}) : job UPDATED #{$existing_job} (editor={$job_data['editor']}, revision=NULL)\n";
|
||||
} else {
|
||||
$wpdb->insert($wpdb->prefix . 'icl_translate_job', $job_data);
|
||||
$new_job_id = $wpdb->insert_id;
|
||||
// Belt-and-suspenders : ensure revision is NULL even on fresh insert.
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$wpdb->prefix}icl_translate_job SET revision = NULL WHERE job_id = %d",
|
||||
$new_job_id
|
||||
));
|
||||
echo "FR #{$r->post_id} '{$r->title}' (rid={$r->rid}) : job CREATED #{$new_job_id} (editor={$job_data['editor']}, revision=NULL)\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\nDONE. Refresh WP Admin → Pages. The FR column icon should switch from pencil ✏️ to gear ⚙️.\n";
|
||||
echo "Click the gear to open ATE for that page.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
function av_wpml_remove_jobs() {
|
||||
global $wpdb;
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$rows = av_target_fr_translations();
|
||||
foreach ($rows as $r) {
|
||||
if (! $r->rid) continue;
|
||||
|
||||
// 1. Remove translate_job rows
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->prefix}icl_translate_job WHERE rid=%d",
|
||||
$r->rid
|
||||
));
|
||||
|
||||
// 2. Reset status row : ALL the cloud-sync artifacts cleared
|
||||
$wpdb->update(
|
||||
$wpdb->prefix . 'icl_translation_status',
|
||||
[
|
||||
'status' => 10,
|
||||
'needs_update' => 0,
|
||||
'batch_id' => 0,
|
||||
'uuid' => '',
|
||||
'tp_id' => '',
|
||||
'tp_revision' => 0,
|
||||
'ts_status' => null,
|
||||
'review_status' => null,
|
||||
'ate_comm_retry_count' => 0,
|
||||
'_prevstate' => '',
|
||||
'translation_service' => 'local',
|
||||
'translator_id' => 0,
|
||||
],
|
||||
['rid' => $r->rid]
|
||||
);
|
||||
|
||||
echo "FR #{$r->post_id} : job removed, status fully reset\n";
|
||||
}
|
||||
|
||||
// 3. Cleanup orphan translation_batches rows that may trigger cloud-fetch
|
||||
$orphans = $wpdb->query(
|
||||
"DELETE FROM {$wpdb->prefix}icl_translation_batches
|
||||
WHERE batch_name LIKE 'asterion%' OR id NOT IN
|
||||
(SELECT DISTINCT batch_id FROM {$wpdb->prefix}icl_translation_status WHERE batch_id > 0)"
|
||||
);
|
||||
echo "\nOrphan translation_batches rows cleaned : $orphans\n";
|
||||
|
||||
// 4. Cleanup orphan translation_downloads rows (background ATE sync queue)
|
||||
$tbl_downloads = $wpdb->prefix . 'icl_translation_downloads';
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$tbl_downloads'")) {
|
||||
$cleared = $wpdb->query("DELETE FROM $tbl_downloads");
|
||||
echo "Cleared $cleared rows from icl_translation_downloads (pending cloud sync queue)\n";
|
||||
}
|
||||
|
||||
echo "\nReverted to clean state. WPML queue + page list should be back to normal.\n";
|
||||
exit;
|
||||
}
|
||||
Reference in New Issue
Block a user