admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'eppro_theme_builder' ),
'i18n' => [
'creating' => __( 'Creating…', 'admore-widgets-suite' ),
'confirmDuplicate' => __( 'Duplicate this template? A draft copy will be created.', 'admore-widgets-suite' ),
'duplicated' => __( 'Template duplicated.', 'admore-widgets-suite' ),
'duplicateFailed' => __( 'Failed to duplicate template.', 'admore-widgets-suite' ),
/* translators: %s: template name */
'confirmDelete' => __( 'Delete "%s"? This action cannot be undone.', 'admore-widgets-suite' ),
'deleted' => __( 'Template deleted.', 'admore-widgets-suite' ),
'deleteFailed' => __( 'Failed to delete template.', 'admore-widgets-suite' ),
'requestFailed' => __( 'An error occurred. Please try again.', 'admore-widgets-suite' ),
],
] );
}
// =========================================================================
// Meta Box (on eppro_hf_template edit screen)
// =========================================================================
public static function register_meta_box(): void {
if ( ! class_exists( 'EPPRO_Theme_Builder_CPT' ) ) return;
add_meta_box(
'eppro_hf_settings',
__( 'Template Settings', 'admore-widgets-suite' ),
[ __CLASS__, 'render_meta_box' ],
EPPRO_Theme_Builder_CPT::POST_TYPE,
'side',
'high'
);
}
public static function render_meta_box( \WP_Post $post ): void {
$type = get_post_meta( $post->ID, '_eppro_hf_type', true ) ?: 'header';
$conditions = get_post_meta( $post->ID, '_eppro_hf_conditions', true ) ?: [];
$priority = get_post_meta( $post->ID, '_eppro_hf_priority', true ) ?: 10;
$sticky = get_post_meta( $post->ID, '_eppro_hf_sticky', true );
$transparent = get_post_meta( $post->ID, '_eppro_hf_transparent', true );
$shrink = get_post_meta( $post->ID, '_eppro_hf_shrink_scroll', true );
$shrink_height = get_post_meta( $post->ID, '_eppro_hf_shrink_height', true ) ?: 60;
wp_nonce_field( self::NONCE, self::NONCE );
$types = [
'header' => __( 'Header', 'admore-widgets-suite' ),
'footer' => __( 'Footer', 'admore-widgets-suite' ),
'single_post' => __( 'Single Post', 'admore-widgets-suite' ),
'single_page' => __( 'Single Page', 'admore-widgets-suite' ),
'archive' => __( 'Archive', 'admore-widgets-suite' ),
'search' => __( 'Search Results', 'admore-widgets-suite' ),
'blog' => __( 'Blog (Posts Page)', 'admore-widgets-suite' ),
'404' => __( '404 Error Page', 'admore-widgets-suite' ),
'product' => __( 'WooCommerce Product', 'admore-widgets-suite' ),
'shop' => __( 'WooCommerce Shop', 'admore-widgets-suite' ),
'cart' => __( 'WooCommerce Cart', 'admore-widgets-suite' ),
'checkout' => __( 'WooCommerce Checkout', 'admore-widgets-suite' ),
'my_account' => __( 'WooCommerce My Account', 'admore-widgets-suite' ),
'section' => __( 'Reusable Section', 'admore-widgets-suite' ),
'landing_page' => __( 'Landing Page (No Header/Footer)', 'admore-widgets-suite' ),
];
$cond_types = EPPRO_Theme_Builder_Conditions::get_condition_types();
?>
tags.
$handle = 'eppro-hf-meta-box';
wp_register_script( $handle, false, [ 'jquery' ], EPPRO_VERSION, true );
wp_enqueue_script( $handle );
wp_localize_script( $handle, 'epproHfMeta', [
'condIdx' => count( $conditions ),
'types' => $cond_types,
] );
wp_add_inline_script( $handle, '
(function($){
var condIdx = parseInt(epproHfMeta.condIdx, 10);
var types = epproHfMeta.types;
$(".eppro-hf-add-cond").on("click", function(){
var opts = "";
$.each(types, function(k,v){ opts += ""; });
var row = "" +
"" +
"" +
"" +
"
";
$("#eppro-hf-conditions").append(row);
condIdx++;
});
$(document).on("click", ".eppro-hf-remove-cond", function(){
$(this).closest(".eppro-hf-cond-row").remove();
});
$("#eppro_shrink_scroll").on("change", function(){
$("#eppro_shrink_height_wrap").toggle(this.checked);
});
})(jQuery);
' );
}
// =========================================================================
// Save Meta
// =========================================================================
public static function save_meta( int $post_id, \WP_Post $post ): void {
if ( ! class_exists( 'EPPRO_Theme_Builder_CPT' ) ) return;
if ( EPPRO_Theme_Builder_CPT::POST_TYPE !== $post->post_type ) return;
if ( ! isset( $_POST[ self::NONCE ] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST[ self::NONCE ] ) ), self::NONCE ) ) return;
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
// Type
$type = sanitize_key( wp_unslash( $_POST['_eppro_hf_type'] ?? 'header' ) );
if ( ! in_array( $type, self::TEMPLATE_TYPES, true ) ) $type = 'header';
update_post_meta( $post_id, '_eppro_hf_type', $type );
// Priority
$priority = absint( $_POST['_eppro_hf_priority'] ?? 10 );
$priority = max( 1, min( 100, $priority ) );
update_post_meta( $post_id, '_eppro_hf_priority', $priority );
// Conditions
$raw_conds = isset( $_POST['_eppro_hf_conditions'] ) && is_array( $_POST['_eppro_hf_conditions'] )
? map_deep( wp_unslash( $_POST['_eppro_hf_conditions'] ), 'sanitize_text_field' )
: [];
$conditions = [];
foreach ( (array) $raw_conds as $cond ) {
$ct = sanitize_key( $cond['type'] ?? '' );
$cv = sanitize_text_field( $cond['value'] ?? '' );
if ( $ct ) {
$conditions[] = [ 'type' => $ct, 'value' => $cv ];
}
}
update_post_meta( $post_id, '_eppro_hf_conditions', $conditions );
// Header Options
update_post_meta( $post_id, '_eppro_hf_sticky', ! empty( $_POST['_eppro_hf_sticky'] ) ? '1' : '' );
update_post_meta( $post_id, '_eppro_hf_transparent', ! empty( $_POST['_eppro_hf_transparent'] ) ? '1' : '' );
update_post_meta( $post_id, '_eppro_hf_shrink_scroll', ! empty( $_POST['_eppro_hf_shrink_scroll'] ) ? '1' : '' );
$shrink_height = absint( $_POST['_eppro_hf_shrink_height'] ?? 60 );
$shrink_height = max( 40, min( 200, $shrink_height ) );
update_post_meta( $post_id, '_eppro_hf_shrink_height', $shrink_height );
// Ensure Elementor edit mode is set
if ( ! get_post_meta( $post_id, '_elementor_edit_mode', true ) ) {
update_post_meta( $post_id, '_elementor_edit_mode', 'builder' );
update_post_meta( $post_id, '_elementor_template_type', 'wp-post' );
}
}
// =========================================================================
// Create Template Handler
// =========================================================================
public static function handle_create(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Insufficient permissions.', 'admore-widgets-suite' ) );
}
check_admin_referer( 'eppro_create_hf_template' );
$title = sanitize_text_field( wp_unslash( $_POST['template_title'] ?? '' ) );
$type = sanitize_key( wp_unslash( $_POST['template_type'] ?? 'header' ) );
if ( ! in_array( $type, self::TEMPLATE_TYPES, true ) ) {
$type = 'header';
}
if ( ! $title ) $title = ucwords( str_replace( '_', ' ', $type ) ) . ' Template';
// Created as a draft: an empty template must not replace the live
// site's header, footer or pages before it has been designed.
// Publishing it from Elementor makes it active.
$post_id = wp_insert_post( [
'post_title' => $title,
'post_type' => EPPRO_Theme_Builder_CPT::POST_TYPE,
'post_status' => 'draft',
] );
if ( is_wp_error( $post_id ) || ! $post_id ) {
wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG . '&error=create_failed' ) );
exit;
}
// A landing page replaces the whole page, so it starts with no
// conditions — the admin picks the pages it applies to.
$conditions = 'landing_page' === $type ? [] : [ [ 'type' => 'entire_site', 'value' => '' ] ];
update_post_meta( $post_id, '_eppro_hf_type', $type );
update_post_meta( $post_id, '_eppro_hf_conditions', $conditions );
update_post_meta( $post_id, '_elementor_edit_mode', 'builder' );
update_post_meta( $post_id, '_elementor_template_type', 'wp-post' );
// Redirect to Elementor editor
if ( class_exists( 'Elementor\Plugin' ) ) {
$doc = \Elementor\Plugin::instance()->documents->get( $post_id );
if ( $doc ) {
wp_safe_redirect( $doc->get_edit_url() );
exit;
}
}
wp_safe_redirect( get_edit_post_link( $post_id, 'raw' ) );
exit;
}
// =========================================================================
// Admin Page Render
// =========================================================================
public static function render_page(): void {
if ( ! class_exists( 'EPPRO_Theme_Builder_CPT' ) || ! class_exists( 'EPPRO_Theme_Builder_Conditions' ) ) {
echo '' . esc_html__( 'Theme Builder', 'admore-widgets-suite' ) . '
';
echo '
' . esc_html__( 'Essential Pro Pack core is still loading. Please ensure Elementor is active, then reload this page.', 'admore-widgets-suite' ) . '
';
return;
}
$templates = get_posts( [
'post_type' => EPPRO_Theme_Builder_CPT::POST_TYPE,
'post_status' => [ 'publish', 'draft', 'private' ],
'posts_per_page' => -1,
'orderby' => 'date',
'order' => 'DESC',
] );
$type_labels = [
'header' => __( 'Header', 'admore-widgets-suite' ),
'footer' => __( 'Footer', 'admore-widgets-suite' ),
'single_post' => __( 'Single Post', 'admore-widgets-suite' ),
'single_page' => __( 'Single Page', 'admore-widgets-suite' ),
'archive' => __( 'Archive', 'admore-widgets-suite' ),
'search' => __( 'Search Results', 'admore-widgets-suite' ),
'blog' => __( 'Blog Page', 'admore-widgets-suite' ),
'404' => __( '404 Page', 'admore-widgets-suite' ),
'product' => __( 'Product', 'admore-widgets-suite' ),
'shop' => __( 'Shop', 'admore-widgets-suite' ),
'cart' => __( 'Cart', 'admore-widgets-suite' ),
'checkout' => __( 'Checkout', 'admore-widgets-suite' ),
'my_account' => __( 'My Account', 'admore-widgets-suite' ),
'section' => __( 'Section', 'admore-widgets-suite' ),
'landing_page' => __( 'Landing Page', 'admore-widgets-suite' ),
];
$allowed_svg = [
'svg' => [ 'width' => [], 'height' => [], 'viewbox' => [], 'fill' => [], 'stroke' => [], 'stroke-width' => [], 'stroke-linecap' => [], 'stroke-linejoin' => [] ],
'path' => [ 'd' => [], 'fill' => [], 'stroke' => [] ],
'rect' => [ 'x' => [], 'y' => [], 'width' => [], 'height' => [], 'rx' => [], 'ry' => [] ],
'line' => [ 'x1' => [], 'y1' => [], 'x2' => [], 'y2' => [] ],
'circle' => [ 'cx' => [], 'cy' => [], 'r' => [] ],
'polyline' => [ 'points' => [] ],
];
$type_icons = [
'header' => '',
'footer' => '',
'single_post' => '',
'single_page' => '',
'archive' => '',
'search' => '',
'blog' => '',
'404' => '',
'product' => '',
'shop' => '',
'cart' => '',
'checkout' => '',
'my_account' => '',
'section' => '',
'landing_page' => '',
];
$type_descriptions = [
'header' => __( 'Site-wide navigation bar', 'admore-widgets-suite' ),
'footer' => __( 'Bottom section for copyright & links', 'admore-widgets-suite' ),
'single_post' => __( 'Template for individual blog posts', 'admore-widgets-suite' ),
'single_page' => __( 'Template for individual pages', 'admore-widgets-suite' ),
'archive' => __( 'Category, tag & date archives', 'admore-widgets-suite' ),
'search' => __( 'Search results listing page', 'admore-widgets-suite' ),
'blog' => __( 'Main posts listing page', 'admore-widgets-suite' ),
'404' => __( 'Page not found error screen', 'admore-widgets-suite' ),
'product' => __( 'WooCommerce single product', 'admore-widgets-suite' ),
'shop' => __( 'WooCommerce products listing', 'admore-widgets-suite' ),
'cart' => __( 'WooCommerce shopping cart', 'admore-widgets-suite' ),
'checkout' => __( 'WooCommerce checkout page', 'admore-widgets-suite' ),
'my_account' => __( 'WooCommerce account dashboard', 'admore-widgets-suite' ),
'section' => __( 'Reusable content blocks', 'admore-widgets-suite' ),
'landing_page' => __( 'Full page without header & footer', 'admore-widgets-suite' ),
];
$cond_labels = EPPRO_Theme_Builder_Conditions::get_condition_types();
// Group templates by type
$templates_by_type = array_fill_keys( array_keys( $type_labels ), [] );
foreach ( $templates as $tpl ) {
$type = get_post_meta( $tpl->ID, '_eppro_hf_type', true ) ?: 'header';
if ( isset( $templates_by_type[ $type ] ) ) {
$templates_by_type[ $type ][] = $tpl;
}
}
?>
$section_templates ) : ?>
ID, '_eppro_hf_type', true ) ?: 'header';
$tpl_conds = get_post_meta( $tpl->ID, '_eppro_hf_conditions', true ) ?: [];
$is_sticky = get_post_meta( $tpl->ID, '_eppro_hf_sticky', true );
$is_transparent = get_post_meta( $tpl->ID, '_eppro_hf_transparent', true );
$edit_url = get_edit_post_link( $tpl->ID, 'raw' );
$elementor_url = '';
if ( class_exists( 'Elementor\Plugin' ) ) {
$doc = \Elementor\Plugin::instance()->documents->get( $tpl->ID );
if ( $doc ) $elementor_url = $doc->get_edit_url();
}
$cond_parts = [];
foreach ( $tpl_conds as $c ) {
$ct = $c['type'] ?? '';
$cv = $c['value'] ?? '';
$clabel = $cond_labels[ $ct ] ?? $ct;
if ( $cv ) $clabel .= ': ' . $cv;
$cond_parts[] = $clabel;
}
?>
post_status ) : ?>
post_status ? __( 'Active', 'admore-widgets-suite' ) : __( 'Draft', 'admore-widgets-suite' ) ); ?>
__( 'Insufficient permissions.', 'admore-widgets-suite' ) ] );
}
$template_id = absint( $_POST['template_id'] ?? 0 );
if ( ! $template_id || EPPRO_Theme_Builder_CPT::POST_TYPE !== get_post_type( $template_id ) ) {
wp_send_json_error( [ 'message' => __( 'Template not found.', 'admore-widgets-suite' ) ] );
}
// Shared duplicator: copies all settings and Elementor data safely; the copy is a draft.
$new_id = EPPRO_Template_Duplicator::do_duplicate( $template_id );
if ( is_wp_error( $new_id ) ) {
wp_send_json_error( [ 'message' => __( 'Failed to duplicate template.', 'admore-widgets-suite' ) ] );
}
wp_send_json_success( [ 'message' => __( 'Template duplicated successfully.', 'admore-widgets-suite' ), 'new_id' => $new_id ] );
}
public static function ajax_delete_template(): void {
check_ajax_referer( 'eppro_theme_builder', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'admore-widgets-suite' ) ] );
}
$template_id = absint( $_POST['template_id'] ?? 0 );
if ( ! $template_id ) {
wp_send_json_error( [ 'message' => __( 'Invalid template ID.', 'admore-widgets-suite' ) ] );
}
$result = wp_delete_post( $template_id, true );
if ( ! $result ) {
wp_send_json_error( [ 'message' => __( 'Failed to delete template.', 'admore-widgets-suite' ) ] );
}
wp_send_json_success( [ 'message' => __( 'Template deleted successfully.', 'admore-widgets-suite' ) ] );
}
}