ACF Flexible Content → Elementor Pro migration playbook#
A theme-tied legacy WordPress site-ot (PHP partial + ACF Flexible Content alapú szekciók) hogyan cserélünk vizuálisan editálható Elementorra úgy, hogy:
- URL-ek, slug-ok, SEO meta-k, redirect-ek mind változatlanok maradnak
- A meglévő téma-CSS pixel-pár renderelődik (nincs vizual-regresszió)
- A nem-tech ügyfél (Domi) az Elementor visual builder-ben szerkeszti
Kontextus / Mikor kell#
Tipikus szituáció: - Egyedi WordPress téma, sok PHP partial-lal (partials/section--*.php) - ACF Pro Flexible Content layout-okkal szekcionálható tartalom - Az ügyfél a régi szerkesztő-flowt nehezen érti (CPT admin + ACF mezők) - Ki akarja cserélni vagy bővíteni Elementorral, anélkül hogy az URL-ek változnának
A "naïv" Elementor migráció (a téma cseréje, új oldalak Elementor canvas-szal) sok problémát hoz: - Új URL → SEO regresszió, redirect-szervezés - A téma PHP-szekciók logikája (pl. is_fox_section()) elveszik - A pixel-pár renderelés újra-építése drága (új design-system bevezetése)
Az architektúra: 4 réteg#
1. Réteg — base widget (abstract)#
Egy \Elementor\Widget_Base leszármazott absztrakt osztály, ami minden widget-nek közös csontváz.
// theme/elementor-widgets/base-widget.php
abstract class Foxxi_Base_Widget extends \Elementor\Widget_Base {
public function get_categories() { return ['foxxi']; } // saját kategória
abstract protected function get_layout(): string; // mely partial
abstract protected function map_settings(array $s): array; // controls → render args
protected function render() {
$settings = $this->get_settings_for_display();
$data = $this->map_settings($settings);
// delegate render to existing theme partial
$func_map = [
'slider' => 'fox_static_render_slider',
'about' => 'fox_static_render_about',
'services' => 'fox_static_render_services',
// ...
];
$fn = $func_map[$this->get_layout()] ?? null;
if ($fn && function_exists($fn)) $fn($data);
}
}
Kulcs ötlet: a widget nem renderel HTML-t, csak delegál a meglévő téma partial-jára. Az Elementor "settings" → partial "$d array" mapping a map_settings()-ben történik.
2. Réteg — concrete widgets (thin, controls-only)#
Minden ACF Flexible layout-hoz egy widget. Csak a controls definiálódik bennük.
class Foxxi_Services_Widget extends Foxxi_Base_Widget {
public function get_name() { return 'foxxi-services'; }
public function get_title() { return __('Foxxi — Szolgáltatások', 'foxxi'); }
public function get_icon() { return 'eicon-posts-grid'; }
protected function get_layout(): string { return 'services'; }
protected function register_controls() {
$this->start_controls_section('section_text', ['label' => __('Szöveg + CTA', 'foxxi')]);
$this->add_control('section_heading', [
'label' => __('Cím', 'foxxi'),
'type' => \Elementor\Controls_Manager::TEXT,
]);
$this->add_control('section_lead', [
'label' => __('Alcím / lead szöveg (HTML megengedett)', 'foxxi'),
'type' => \Elementor\Controls_Manager::TEXTAREA,
'rows' => 5,
]);
// CTA button text + URL controls...
$this->end_controls_section();
$this->start_controls_section('section_cards', ['label' => __('Kártyák', 'foxxi')]);
$this->add_control('mode', [
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'auto',
'options' => [
'auto' => 'Automatikus (CPT-ből)',
'manual' => 'Manuális — alább megadott lista',
],
]);
$rep = new \Elementor\Repeater();
$rep->add_control('image', ['type' => \Elementor\Controls_Manager::MEDIA]);
$rep->add_control('title', ['type' => \Elementor\Controls_Manager::TEXT]);
$rep->add_control('link', ['type' => \Elementor\Controls_Manager::TEXT]);
$this->add_control('services', [
'type' => \Elementor\Controls_Manager::REPEATER,
'fields' => $rep->get_controls(),
'title_field' => '{{{ title }}}',
'condition' => ['mode' => 'manual'],
]);
$this->end_controls_section();
}
protected function map_settings(array $s): array {
$services = [];
foreach (($s['services'] ?? []) as $row) {
$services[] = [
'image' => $row['image']['id'] ?? 0,
'title' => $row['title'] ?? '',
'link' => $row['link'] ?? '',
];
}
return [
'section_heading' => $s['section_heading'] ?? '',
'section_lead' => $s['section_lead'] ?? '',
'mode' => $s['mode'] ?? 'auto',
'services' => $services,
];
}
}
3. Réteg — render partial (extended for overrides)#
A meglévő téma partial-t kibővítjük úgy, hogy a paraméterezett $d array-ből felülírható minden mező, az ACF default érték pedig fallback marad.
// static-sections/render.php
function fox_static_render_services(array $d): void {
// 1. Téma-szintű alapértelmezések (is_fox_section() alapján más-más)
if (is_fox_section()) {
$default_heading = 'Szolgáltatásaink';
$default_lead = 'A teljeskörű fogászati ellátás...';
} else {
$default_heading = 'Szolgáltatásaink';
$default_lead = 'Mindketten kizárólag fogszabályozással...';
}
// 2. Per-instance override (Elementor / ACF / shortcode mind ugyanezt küldi)
$section_heading = !empty($d['section_heading']) ? $d['section_heading'] : $default_heading;
$section_lead = isset($d['section_lead']) && $d['section_lead'] !== ''
? $d['section_lead']
: $default_lead;
// 3. Cards: $d['mode'] === 'manual' → $d['services'] lista,
// egyébként WP_Query a CPT-re
if (($d['mode'] ?? 'auto') === 'manual' && !empty($d['services'])) {
$cards = array_map(fn($s) => [
'title' => $s['title'] ?? '',
'link' => $s['link'] ?? '',
'img_url' => $s['image'] ? wp_get_attachment_image_url($s['image'], 'thumbnail') : '',
], $d['services']);
} else {
// Auto: WP_Query a CPT-re ($args is_fox_section() szerint)
}
// 4. Render — ugyanaz az HTML mint az eredeti partial
?>
<div class="section-service">
<!-- ... -->
</div>
<?php
}
Kulcs ötlet: a partial 3 hívási kontextusban ugyanúgy működik: - ACF Flexible Content (régi) — acf_draw_sections() → case 'services': fox_static_render_services($section) - Elementor widget (új) — Foxxi_Services_Widget::render() → fox_static_render_services($mapped_settings) - Shortcode — [foxxi_section type="services"] → fox_static_render_services($section_data)
4. Réteg — loader#
// theme/elementor-widgets/loader.php
if (!did_action('elementor/loaded')) return;
add_action('elementor/elements/categories_registered', function ($mgr) {
$mgr->add_category('foxxi', ['title' => 'Foxxi szekciók', 'icon' => 'fa fa-plug']);
});
add_action('elementor/widgets/register', function ($mgr) {
require_once __DIR__ . '/base-widget.php';
foreach (['slider', 'about', 'services', 'testimonials', 'feature', 'repeater', 'contact'] as $name) {
require_once __DIR__ . '/widget-' . $name . '.php';
$cls = 'Foxxi_' . ucfirst($name) . '_Widget';
if (class_exists($cls)) $mgr->register(new $cls());
}
});
CSS bridge (kötelező)#
A téma partial-jai gyakran full-bleed background blokkokat (.section-service, .featureRow) emit-elnek és Bootstrap row-kat negatív margin-nel. Az Elementor section default 1140px boxed container-rel jön, ami levágja a full-bleed háttért és a row-okat.
A megoldás: minden Elementor section/column/widget-wrap-ot ami foxxi-* widget-et tartalmaz, full-width-re kényszeríteni.
.elementor-section:has([class*="elementor-widget-foxxi-"]),
.elementor-element.elementor-section:has([class*="elementor-widget-foxxi-"]) {
padding: 0 !important;
margin: 0 !important;
max-width: 100% !important;
width: 100% !important;
}
.elementor-section:has([class*="elementor-widget-foxxi-"]) > .elementor-container {
max-width: 100% !important;
width: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
.elementor-section:has([class*="elementor-widget-foxxi-"]) .elementor-column,
.elementor-section:has([class*="elementor-widget-foxxi-"]) .elementor-widget-wrap {
padding: 0 !important;
margin: 0 !important;
}
[class*="elementor-widget-foxxi-"] > .elementor-widget-container {
padding: 0 !important;
margin: 0 !important;
}
A :has() modern szelektor (2026 minden browser-ben működik). Plusz a generator script section-szettings-ekben is content_width: 'full_width', padding/margin = 0.
Migration script — elementor_data generálás PHP-ben#
A page _elementor_data post meta-ja JSON, amit kódból generálhatunk, hogy az ACF Flexible mezőket → Elementor widget-ekre konvertáljuk:
// scripts/migrate-page-to-elementor.php
// wp eval-file ./scripts/migrate-page-to-elementor.php
$page_id = 2; // pl. fogszab homepage
// 1. Az ACF sections backup-olása
$acf_sections = get_post_meta($page_id, 'sections', true);
update_post_meta($page_id, '_acf_backup_' . date('Ymd'), $acf_sections);
// 2. Az ACF layout-ok mappolása foxxi-* widget-ekre
$id_seq = 0;
$el_id = fn() => substr(md5('mig-' . ++$id_seq), 0, 7);
$img = fn($id) => ['id' => (int) $id, 'url' => wp_get_attachment_url($id)];
$section_with_widget = function ($widget_type, array $settings = []) use ($el_id) {
$zero = ['unit' => 'px', 'top' => '0', 'right' => '0', 'bottom' => '0', 'left' => '0', 'isLinked' => true];
return [
'id' => $el_id(), 'elType' => 'section',
'settings' => [
'content_width' => ['unit' => 'full_width', 'size' => '', 'sizes' => []],
'gap' => 'no', 'padding' => $zero, 'margin' => $zero,
],
'elements' => [[
'id' => $el_id(), 'elType' => 'column',
'settings' => ['_column_size' => 100, '_inline_size' => null, 'padding' => $zero, 'margin' => $zero],
'elements' => [[
'id' => $el_id(), 'elType' => 'widget',
'widgetType' => $widget_type,
'settings' => (object) $settings,
'elements' => [],
]],
]],
];
};
$elements = [];
foreach ($acf_sections as $section) {
switch ($section['acf_fc_layout']) {
case 'slider':
$elements[] = $section_with_widget('foxxi-slider', [
'slides' => array_map(fn($s) => [
'_id' => substr(md5(json_encode($s)), 0, 7),
'image' => $img($s['image']),
'title' => $s['title'] ?? '',
'content' => $s['content'] ?? '',
'link' => $s['link'] ?? '',
], $section['slides'] ?? []),
]);
break;
case 'services':
// ...
break;
// case-ek minden ACF layoutra
}
}
// 3. Persistence
update_post_meta($page_id, '_elementor_edit_mode', 'builder');
update_post_meta($page_id, '_elementor_template_type', 'wp-page');
update_post_meta($page_id, '_elementor_version', ELEMENTOR_VERSION);
update_post_meta($page_id, '_wp_page_template', 'elementor_header_footer');
$json = wp_json_encode($elements, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
update_post_meta($page_id, '_elementor_data', wp_slash($json));
\Elementor\Plugin::$instance->files_manager->clear_cache();
Fontos: wp_slash($json) kötelező — a wp_unslash() ami a WP core-ban sok helyen fut, eszi a backslash-eket a JSON-ban (pl. ő → u0151).
Domi-barát szerkeszthetőség (UX)#
Egy non-tech ügyfél (Domi) UX-ben placeholder + üres input == "ezt nem tudom szerkeszteni".
Pattern: a generator script tölts fel valódi tartalommal minden szöveg/URL/repeater mezőt: - Heading / lead / CTA: explicit ortho/fox default szöveggel - Repeater (services, testimonials, team): pre-generated lista a CPT-ből (mode: 'manual') - Kép kontroll: wp_get_attachment_image_url($id, 'thumbnail') URL-lel
Ez kulcsfontosságú — placeholder-rel hagyott input-okat az ügyfél nem találja meg.
Render-pattern a partial-ban: !empty($d['x']) ? $d['x'] : $default — ha a user kitörli az értéket (üresre teszi), visszaesik a téma alapszövegére (V2 paritás).
URL-megőrzés (zero-downtime)#
# 1. Az új Elementor data egy STAGING URL-en (pl. /fogszab-v5/) készül
wp eval-file scripts/create-fogszab-v5-page.php
# 2. Vizuális ellenőrzés Playwright-tal vagy manuálisan
curl -s "https://site/fogszab-v5/?nocache=1" | head
# 3. Ha minden OK: az _elementor_data-t bemásoljuk a LIVE post-ra
LIVE_ID=2 # /fogszabalyozas-invisalign/
STAGING_ID=2269 # /fogszab-v5/
DATA=$(wp post meta get $STAGING_ID _elementor_data)
wp post meta update $LIVE_ID _acf_backup_$(date +%Y%m%d) "$(wp post meta get $LIVE_ID _elementor_data 2>/dev/null)"
wp post meta update $LIVE_ID _elementor_data "$DATA"
wp post meta update $LIVE_ID _elementor_edit_mode builder
wp post meta update $LIVE_ID _wp_page_template elementor_header_footer
wp cache flush && wp w3-total-cache flush all
URL nem változik. SEO marad. Domi a meglévő admin URL-en találja meg.
Header / Footer (Elementor Pro Theme Builder)#
Elementor Pro-val: 1. Templates → Theme Builder 2. Új Header template, display condition: "Entire site" vagy specifikus szekció 3. Az Elementor saját header-widget-jeit használjuk (Site Logo, Nav Menu, Search, stb.) 4. Vagy custom Foxxi widget a fejléchez (logó + menu + cta gomb)
A téma header.php / footer.php fall-back marad — ha nincs Theme Builder template aktív, a téma renderel. Ez biztonsági háló.
Auto vs Theme-fox / Theme-ortho#
A is_fox_section() PHP függvény (body class swap) változatlan marad. Az Elementor renderelt page-ek is megkapják a theme-fox / theme-ortho body class-t, és a CSS override-ok (.split-hero.css) érvényesülnek. Tehát:
- Ortho oldalak (fogszab) → barna design
- Fox oldalak (fogászat) → narancs design
Mind a kettő ugyanazokat a foxxi-* widget-eket használja, csak a render partial belül is_fox_section()-tel váltogat default-okat és színeket.
Backup + rollback#
Minden migrate-elt page-ra:
update_post_meta($page_id, '_acf_backup_' . date('Ymd'), $acf_sections);
update_post_meta($page_id, '_elementor_data_backup_' . date('Ymd'), $old_data);
Rollback: wp post meta update $page_id _elementor_data '' és/vagy az ACF mezőt visszaírni.
Phase 5 — gyakorlati tanulságok (FOXXI, 2026-04-29 → 2026-04-30)#
5a. AI image generation workflow — nano-banana CLI#
A Foxxi fogászati szekció redesign-jához ~30 generált hero/portrait kép készült Gemini 3.1 Flash Image Preview-vel, batch-ben.
Setup:
# nano-banana CLI install (per-host)
git clone https://github.com/kingbootoshi/nano-banana-2-skill ~/tools/nano-banana-2
cd ~/tools/nano-banana-2 && bun install && bun link
echo "GEMINI_API_KEY=..." > ~/.nano-banana/.env
Per-image cost: ~$0.09 (Gemini 3.1 Flash). 20 hero kép ~$1.80.
Tematikus prompting per page-type:
- Hero banner (21:9): "Wide cinematic horizontal banner. {scene}. Empty space on {side} for caption overlay. Editorial photography. 21:9 ultrawide aspect."
- Team portrait (1:1): "Professional headshot of Hungarian {gender} dentist in white coat. Light cream background. 1:1 square aspect."
- Service hero (3:2): "{service-specific dental scene}. Premium clinical photography. 3:2 aspect."
Parallel generation: több nano-banana hívás párhuzamosan (& shell), 3-4 db egyszerre OK.
Bulk upload to WP:
for f in hero-rolunk hero-szolg ...; do
scp "${f}.png.jpeg" "host:/tmp/foxxi-${f}.jpg"
done
ssh host 'wp media import /tmp/foxxi-${f}.jpg --title="..." --porcelain'
A --porcelain flag visszaadja az új attachment ID-t — rögzítsd egy mapping array-be a generator script-be.
5b. Page-hero (.mainImage.mainImagePage) reprodukálása Elementorban#
A foxy theme natív single-{cpt}.php template-jeiben van egy <div class="mainImage mainImagePage"> block (featured image hero + page title overlay). Az elementor_header_footer page-template viszont nem rendeli ezt — a tartalom közvetlenül a header alatt kezdődik, ami csúnya.
Megoldás: új foxxi-page-hero widget ami pixel-pár 1:1 másolatát adja vissza a foxy mainImage HTML-jének:
<div class="mainImage mainImagePage">
<div class="imageBox"><img src="..." style="object-position:..." /></div>
<div class="container-fluid"><div class="row"><div class="col-12">
<div class="caption"><h1>...</h1></div>
</div></div></div>
</div>
Mindkét SVG (.caption narancs trapéz overlay + .blockquote.top blockquote ötszög) egymásba illeszkedik ha a következő .blockquote.top .pink color-ban renderel (#fcefe7 rózsa-krém háttér). A feher color a két SVG közötti vizuális gap-et okozza.
CSS fix az illeszkedéshez:
.elementor-section:has(.elementor-widget-foxxi-page-hero) + .elementor-section .blockquote.top {
margin-top: 0 !important;
padding-top: 0 !important;
}
body.theme-fox .blockquote.top.feher,
body.theme-ortho .blockquote.top.feher {
background: #fcefe7 !important;
}
Migration script (idempotens): array_unshift($elements, $page_hero_section) — a meglévő _elementor_data elejére beilleszti a hero-t. Ha már van, csak a settings-et frissíti.
5c. Theme color pivot — SVG hue-rotate trükk#
Ha runtime akarsz színt cserélni egy theme body class alapján (pl. fogászat narancs → türkiz/zöld), és a HTML/SVG-k már narancsban vannak hard-coded:
body.theme-fox .mainImage .caption,
body.theme-fox .blockquote.top blockquote {
filter: hue-rotate(120deg) saturate(0.85);
}
/* A szöveget visszafordítjuk hogy ne színeződjön: */
body.theme-fox .mainImage .caption h1,
body.theme-fox .blockquote.top blockquote div {
filter: hue-rotate(-120deg) saturate(1.18);
color: var(--target-color) !important;
}
Hue-rotate(120deg) narancs → zöld átfordítás. Saturate(0.85) kissé kontroll-csillapít a túltelítettség ellen.
A dupla-filter pattern (parent: hue-rotate, child: ellenkezőleg) megőrzi a szöveg-színt, csak a SVG-háttért érinti.
5d. ACF Options-fallback PHP — ?? vs !empty()#
A render-funkciókban gyakran szükséges:
// HIBÁS — a `??` csak null esetén ad fall-back-et:
$footer_contact = $d['footer_contact'] ?? get_field('footer_contact', 'options');
// Ha $d['footer_contact'] = '' (üres string), a $? nem fall-back-el.
// HELYES:
$pick = fn($v, $k) => !empty($v) ? $v : get_field($k, 'options');
$footer_contact = $pick($d['footer_contact'] ?? '', 'footer_contact');
Az Elementor widget settings-ből gyakran üres string érkezik (nem null), ezért !empty()-ternary kell.
5e. Brand color tokens — 10-shade scale#
Új Foxxi Dental Mint paletta (#f3faf7 → #154e46) --fox-mint-50 … --fox-mint-900 formában a foxxi-brand.css-ben. Lehetővé teszi a jövőben:
- Elemenkénti szín-finomítás (mid-light hover, deep accent, stb.)
- Dark/light theme-toggle bevezetése
- A/B teszt különböző accent-szintekkel
Az eredeti foxszab paletta (--foxxi-orange, --foxxi-brown, stb.) érintetlen marad — a fogszab oldalak (theme-ortho) változatlanok.
5f. Hibajegyzék: Foxxi Phase 1-5 (3 nap, 20 oldal + theme builder)#
| # | Issue | Root cause | Fix |
|---|---|---|---|
| 1 | Header overlap content | elementor_header_footer template + foxy .placeholder 80px nem elég | :first-of-type section padding-top 40px (kivéve ha page-hero) |
| 2 | Repeater "before"-type képek nem renderelnek | [bafg] shortcode HTML rendben, de jQuery twentytwenty plugin nem inicializál | fox_ensure_twentytwenty_assets() enqueue helper |
| 3 | aboutWrapper text fehér marad cream alapon | foxy .aboutWrapper * { color: #fff } magasabb specificity | universal-selector + !important override |
| 4 | Theme Builder display condition nem triggerel | 'general/include' formátum hibás | helyes: 'include/general' |
| 5 | Footer üres (Kapcsolat/Social) | $d['x'] ?? $opt(...) üres string fall-back hiba | !empty()-ternary |
| 6 | Hero+blockquote vizuális gap | .blockquote.top.feher fehér, kell pink (#fcefe7) | universal :not(.brown):not(.pink) override |
| 7 | Mind 11 fogászat oldal placeholder ACF-fel | normal_content + üres mezők | notion.md brief alapján 100% tartalom-feltöltés |
| 8 | Theme color pivot rajzolt SVG-kkel | hard-coded narancs SVG-k | filter: hue-rotate(120deg) + dupla-filter szöveg-megőrzés |
Phase 4 — Theme Builder display condition format (FOXXI, 2026-04-29)#
Az Elementor Pro Theme Builder display condition formátuma:
- Helyes:
'include/general'— entire site (vagy'include/post/123'egy post-ra) - Helytelen:
'general/include'— ezt aparse_condition()type='general', name='include'-ra parse-olja, és az$is_include = 'include' === 'general'false → a feltétel sosem match-el
A parse_condition() az Elementor Pro conditions-manager.php-ban explode /-vel: első szegmens = type ('include' vagy 'exclude'), második = name ('general', 'singular', 'archive', stb.), harmadik+ = sub-target.
Plus: a _elementor_conditions post meta + elementor_pro_theme_builder_conditions option együtt működnek. A cache regenerate() is futnia kell:
\ElementorPro\Modules\ThemeBuilder\Module::instance()
->get_conditions_manager()
->get_cache()
->regenerate();
A téma natív header.php / footer.php-t override-olni kell, hogy az elementor_theme_do_location('header') hookot meghívja — különben a Theme Builder template nem kapcsolódik be:
// header.php elejére:
if (function_exists('elementor_theme_do_location') && elementor_theme_do_location('header')) {
// TB rendered <header> — skip the legacy theme markup
} else {
// ... eredeti theme header HTML
}
Phase 2 — JS-init függő slider/carousel widgetek (FOXXI, 2026-04-29)#
A gallery_carousel (Swiper-alapú) és before_and_after_gallery (jQuery twentytwenty) layout-ok renderelt HTML-jük van rendben — viszont a JS init nem fut Elementor render-context-ben, mert a téma frontend.js ACF-context-függő szelektorral keresi a slider div-eket, vagy a script csak a wp_enqueue_scripts hook-ban kerül be a meglévő ACF-szekcióknál.
Részleges fix: fox_ensure_twentytwenty_assets() helper enqueue-olja a twentytwenty plugin script-jeit wp_enqueue_scripts hook helyett közvetlenül a render funkcióból. De a Swiper init-hez (gallery_carousel) is hasonló kéne, és néha a CSS is a plugin saját stylesheet-jéből hiányzik.
Tanulság: plugin-függő slider/carousel widget-eknél deploy után tesztelni kell hogy a JS frontend valóban inicializál — különben a HTML látható elemek (img tag) láthatatlanok maradnak CSS / overflow:hidden miatt. Visual issue, nem blokkoló a migrációhoz — a tartalom HTML-ben szerepel, Domi szerkeszteni tudja.
Phase 2 — repeater "before" altípus (FOXXI, 2026-04-29)#
A repeater layout-nak több type altípusa van: image, text, before. A before típus esetén a kép helyett WordPress shortcode renderelődik (pl. [bafg id="321"] → előtte/utána slider). Az adatszerkezetben a image mező hiányzik, helyette shortcode mező van.
A fox_static_render_repeater()-be hozzá kell adni: if ($type === 'before') echo apply_shortcodes($shortcode); else echo '<img ...>'; — különben a shortcode-os repeater elemek kép-placeholder helyként jelennek meg.
Phase 2 — plugin-függő ACF layout-ok (FOXXI, 2026-04-29)#
A video_carousel és before_and_after_gallery ACF layout-okat dedikált plugin-ok renderelik (foxxi-video-carousel 1.2.0, foxxi-before-after-v331). A partials/section--video_carousel.php fallback az acf_draw_sections() ACF-context-ot várja (get_row(), get_sub_field()) — Elementor render-context-ben ez NEM működik.
Megoldás: a generator script kiolvassa a sections_X_videos_Y_* mezőket és explicit array-ben átadja az Elementor widget settings-be. A fox_static_render_video_carousel() függvénynek így a $d['videos'] array elég lesz, plugin nélkül is renderel.
Tanulság: minden plugin-által-renderelt ACF layout-ot standalone PHP függvénybe kell extract-elni a render.php-ben — különben az Elementor migráció plugin-függőségbe ütközik.
Phase 2 — partial-feature parity gotcha (FOXXI, 2026-04-29)#
A theme partials/section--<layout>.php partial-jaiban gyakran több branch van (pl. contact: info_enable vs img_left ágak). Az első round-ban a fox_static_render_<layout>() extracted függvényt csak EGY ág-ra írtam meg (img_left, mert a fogszab homepage-en az kellett) — Kapcsolat oldalon viszont info_enable=1, img_left=0 jött, és az info-kártya (Rendelő / Nyitvatartás / utvonalList) néma maradt.
Tanulság: új page migrációja előtt diff-elj a teljes partial-lal és ellenőrizd hogy MINDEN feltételes ág át van-e véve. ACF Options-page mezők (get_field('x', 'options')) mind elérhetőek render-context-ben is, nem ACF-flexible-context-only.
Phase 1 — gyakorlati tanulságok (FOXXI, 2026-04-29)#
- A LIVE post
_elementor_data0-byte volt → tehát az ACF render-flow független az Elementor adattól. Phase 1-ben elég volt a_wp_page_templateátállítás (default→elementor_header_footer) +_elementor_datafeltöltése, az ACF mezőket NEM kellett törölni — érintetlenül backupban maradnak, és a régi template visszaállítása rollback. - Teljes post_meta JSON backup gyors (~21KB / page) — érdemes minden phase-nél csinálni
/tmp/foxxi-id<X>-backup-<timestamp>.jsonfájlba. wp post meta getüres value-ra exit code 1-et ad, scriptelni2>/dev/nullszükséges.- Cache-flush mátrix Hostinger-en:
wp cache flush && wp w3-total-cache flush all+ Elementor\Elementor\Plugin::$instance->files_manager->clear_cache()PHP-ből, plusz?nocache=Nquery a Hostinger CDN bypass-hoz vizuális ellenőrzéskor.
Hibakeresési sufnitár#
| Tünet | Diagnózis |
|---|---|
| Brown szekció középen 1140px-ben | Section content_width nem 'full_width' / CSS bridge nem érvényesül → cache flush |
ő literál szöveg | wp_slash() hiányzik az update_post_meta előtt |
| Kép szétnyúlik 50%-ra | medium_large méret aránytalan kép esetén → thumbnail (150x150 crop) |
| WPML "Undefined array key fields" warning | Non-blocking, WPML translation parser nem ismeri custom widget-et |
| Admin canvas-on classic editor | Disable Gutenberg / Classic Editor plugin → use_block_editor_for_post filter |
Status / TODO (FOXXI projekt-specifikus)#
- V1: ACF Flexible (eredeti)
- V2: PHP static renderer (
/fogszab-v2/) - V3: Gutenberg core blocks (
/fogszab-v3/) - V4: Custom Gutenberg dynamic blocks (
/fogszab-v4/) - V5: Elementor custom widgets (
/fogszab-v5/) - Phase 1: V5 → ID 2 (live homepage)
- Phase 2: 8 fő fogszab oldal migrálás (Rólunk, Miért a Foxxi, ..., Karrier)
- Phase 3: 6 szolgáltatás CPT → Elementor edit mode
- Phase 4: Header + Footer Theme Builder template
- Phase 5: Fogászat (Fox) szekció új design-system + content copy
Forrás-fájlok (FOXXI repo)#
/root/projektjeim/foxxi/
├── theme-foxy/elementor-widgets/
│ ├── loader.php
│ ├── base-widget.php
│ ├── widget-slider.php
│ ├── widget-about.php
│ ├── widget-services.php
│ ├── widget-testimonials.php
│ ├── widget-feature.php
│ ├── widget-repeater.php
│ └── widget-contact.php
├── theme-foxy/static-sections/render.php # 7 fox_static_render_*() funkció
├── theme-foxy/assets/css/split-hero.css # CSS bridge
└── scripts/create-fogszab-v5-page.php # generator
License plan#
Ez a playbook + példa kód publikus GitHub repó alapanyaga lesz: acf-to-elementor-migration (MIT).