[amber_mega_menu id="' . (int) $post_id . '"]';
}
}
public static function assets( $hook ) {
global $post;
if ( ! $post || AMBER_MEGA_MENU_CPT !== get_post_type( $post ) ) {
return;
}
self::enqueue( (int) $post->ID );
}
/** Shared by the post editor and the plugin's own settings screens. */
public static function enqueue( $post_id ) {
wp_enqueue_script( 'jquery-ui-sortable' );
wp_enqueue_script( 'jquery-ui-draggable' );
wp_enqueue_script( 'jquery-ui-droppable' );
wp_enqueue_media();
wp_enqueue_style( 'smm-admin', AMBER_MEGA_MENU_URL . 'assets/admin.css', array(), AMBER_MEGA_MENU_VERSION );
wp_enqueue_script( 'smm-admin', AMBER_MEGA_MENU_URL . 'assets/admin.js', array( 'jquery', 'jquery-ui-sortable' ), AMBER_MEGA_MENU_VERSION, true );
wp_localize_script(
'smm-admin',
'AMBER_MEGA_MENU_ADMIN',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'smm_preview' ),
'postId' => (int) $post_id,
'i18n' => array(
'confirm' => __( 'You have unsaved changes to the menu structure. Changing the source will discard them. Continue?', 'amber-mega-menu' ),
'loading' => __( 'Loading items…', 'amber-mega-menu' ),
'error' => __( 'Could not load items for this source.', 'amber-mega-menu' ),
),
)
);
}
/** Live reload of the structure list when the source changes. */
public static function ajax_preview_structure() {
check_ajax_referer( 'smm_preview', 'nonce' );
$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
// A brand new menu has no post yet, so fall back to the plugin capability.
$allowed = current_user_can( 'manage_options' ) || ( $post_id && current_user_can( 'edit_post', $post_id ) );
if ( ! $allowed ) {
wp_send_json_error();
}
$settings = Amber_Mega_Menu_Source::get_settings( $post_id );
$settings['source'] = ( isset( $_POST['source'] ) && 'menu' === $_POST['source'] ) ? 'menu' : 'taxonomy';
$settings['taxonomy'] = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : 'product_cat';
$settings['menu_id'] = isset( $_POST['menu_id'] ) ? (int) $_POST['menu_id'] : 0;
$tree = Amber_Mega_Menu_Source::merged_tree( $post_id, $settings );
ob_start();
if ( empty( $tree ) ) {
echo '
' . esc_html__( 'No items found for the selected source.', 'amber-mega-menu' ) . '
';
} else {
self::render_branch( $tree, 1 );
}
wp_send_json_success( array( 'html' => ob_get_clean() ) );
}
public static function meta_boxes() {
add_meta_box( 'smm_settings', __( 'Mega menu settings', 'amber-mega-menu' ), array( __CLASS__, 'box_settings' ), AMBER_MEGA_MENU_CPT, 'side', 'default' );
add_meta_box( 'smm_structure', __( 'Menu structure (drag to reorder / re-nest)', 'amber-mega-menu' ), array( __CLASS__, 'box_structure' ), AMBER_MEGA_MENU_CPT, 'normal', 'high' );
add_meta_box( 'smm_usage', __( 'How to use', 'amber-mega-menu' ), array( __CLASS__, 'box_usage' ), AMBER_MEGA_MENU_CPT, 'side', 'low' );
}
public static function box_usage( $post ) {
$id = (int) $post->ID;
?>
[amber_mega_menu id=""]
id
—
layout
columns | sideways
columns
2–6
depth
1–6 | 0
trigger
trigger="Catalog"
open
1
[amber_mega_menu id="" trigger="Catalog" layout="sideways"]
[amber_mega_menu id="" layout="columns" columns="4" depth="2"]
[amber_mega_menu id="" open="1" depth="3"]
<?php echo do_shortcode('[amber_mega_menu id=""]'); ?>
ID );
$menus = wp_get_nav_menus();
$taxes = get_taxonomies( array( 'public' => true ), 'objects' );
?>
>
>
hierarchical ) { continue; } ?>
name ); ?>>labels->singular_name . ' (' . $tax->name . ')' ); ?>
hierarchical ) { continue; } ?>
name ); ?>>labels->singular_name . ' (' . $tax->name . ')' ); ?>
ID );
?>
>
>
>
>
';
}
}
/** Render one colour picker with an independent return-to-default action. */
public static function color_control( $s, $key, $label, $default ) {
?>
ID );
$defaults = Amber_Mega_Menu_Source::style_defaults();
?>
ID );
$defaults = Amber_Mega_Menu_Source::style_defaults();
$colors = array(
'accent_color' => __( 'Accent color', 'amber-mega-menu' ),
'panel_bg' => __( 'Panel background', 'amber-mega-menu' ),
'font_color' => __( 'Font color', 'amber-mega-menu' ),
);
?>
$label ) : ?>
ID );
?>
>
>
>
ID );
$panes = array(
array(
'label' => '' !== trim( (string) $settings['tab_label'] ) ? $settings['tab_label'] : __( 'Main menu', 'amber-mega-menu' ),
'tree' => Amber_Mega_Menu_Source::merged_tree( $post->ID, $settings, 0 ),
),
);
/** Premium distributions may add further structure panes (one per extra menu tab). */
$panes = apply_filters( 'amber_mega_menu_structure_panes', $panes, $post, $settings );
?>
1 ) : ?>
$pane ) : ?>
$pane ) : ?>
';
foreach ( $nodes as $node ) {
$node_mode = ! empty( $node['hidden'] ) ? ( isset( $node['hide_mode'] ) ? $node['hide_mode'] : 'branch' ) : '';
$li_class = 'smm-item';
if ( $node_mode ) {
$li_class .= ' is-hidden';
if ( 'branch' === $node_mode ) {
$li_class .= ' is-hidden-branch';
}
}
echo '';
echo '';
echo ' ';
echo '' . esc_html( $node['label'] ) . ' ';
if ( $node['count'] ) {
echo '' . (int) $node['count'] . ' ';
}
echo ' ';
$mode = ! empty( $node['hidden'] ) ? ( 'self' === ( isset( $node['hide_mode'] ) ? $node['hide_mode'] : 'branch' ) ? 'self' : 'branch' ) : '';
echo '' . esc_html__( 'Visibility', 'amber-mega-menu' ) . ' ';
echo '';
echo '' . esc_html__( 'Visible', 'amber-mega-menu' ) . ' ';
echo '' . esc_html__( 'Hide item + sub-items', 'amber-mega-menu' ) . ' ';
if ( ! empty( $node['children'] ) ) {
echo '' . esc_html__( 'Hide only this item', 'amber-mega-menu' ) . ' ';
}
echo ' ';
echo '
';
if ( ! empty( $node['children'] ) ) {
self::render_branch( $node['children'], $level + 1 );
} else {
echo ' ';
}
echo ' ';
}
echo '';
}
public static function save( $post_id, $post ) {
if ( ! isset( $_POST['smm_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['smm_nonce'] ) ), 'smm_save' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'manage_options' ) && ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$in = isset( $_POST['smm'] ) && is_array( $_POST['smm'] ) ? wp_unslash( $_POST['smm'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- every value is sanitised field by field below.
$settings = self::sanitize_input( $in, $post_id );
update_post_meta( $post_id, '_smm_settings', $settings );
if ( isset( $_POST['smm_order'] ) && '' !== $_POST['smm_order'] ) {
$order = json_decode( sanitize_text_field( wp_unslash( $_POST['smm_order'] ) ), true );
if ( is_array( $order ) ) {
update_post_meta( $post_id, '_smm_order', wp_json_encode( self::sanitize_order( $order ) ) );
}
}
// Additional tabs keep their own manual ordering, one entry per tab index.
if ( isset( $_POST['smm_order_tab'] ) && is_array( $_POST['smm_order_tab'] ) ) {
$stored = get_post_meta( $post_id, '_smm_order_tabs', true );
if ( is_string( $stored ) && '' !== $stored ) {
$stored = json_decode( $stored, true );
}
if ( ! is_array( $stored ) ) {
$stored = array();
}
foreach ( map_deep( wp_unslash( $_POST['smm_order_tab'] ), 'sanitize_text_field' ) as $index => $json ) {
if ( '' === $json ) {
continue;
}
$order = json_decode( $json, true );
if ( is_array( $order ) ) {
$stored[ (int) $index ] = self::sanitize_order( $order );
}
}
update_post_meta( $post_id, '_smm_order_tabs', wp_json_encode( $stored ) );
}
}
/** Sanitise posted settings without saving. */
public static function sanitize_input( $in, $post_id ) {
$saved = Amber_Mega_Menu_Source::get_settings( $post_id );
$settings = array(
'source' => in_array( isset( $in['source'] ) ? $in['source'] : '', array( 'menu', 'taxonomy' ), true ) ? $in['source'] : 'taxonomy',
'taxonomy' => isset( $in['taxonomy'] ) ? sanitize_key( $in['taxonomy'] ) : 'product_cat',
'menu_id' => isset( $in['menu_id'] ) ? (int) $in['menu_id'] : 0,
'layout' => ( isset( $in['layout'] ) && 'sideways' === $in['layout'] ) ? 'sideways' : 'columns',
'desktop_display' => ( isset( $in['desktop_display'] ) && 'modal' === $in['desktop_display'] ) ? 'modal' : 'adaptive',
'columns' => isset( $in['columns'] ) ? max( 2, min( 6, (int) $in['columns'] ) ) : 4,
'depth' => isset( $in['depth'] ) ? max( 0, min( 6, (int) $in['depth'] ) ) : 3,
'trigger' => isset( $in['trigger'] ) ? sanitize_text_field( $in['trigger'] ) : '',
'button_bg' => isset( $in['button_bg'] ) && sanitize_hex_color( $in['button_bg'] ) ? sanitize_hex_color( $in['button_bg'] ) : '#0878a8',
'button_bg_hover' => isset( $in['button_bg_hover'] ) && sanitize_hex_color( $in['button_bg_hover'] ) ? sanitize_hex_color( $in['button_bg_hover'] ) : '#06678f',
'button_text' => isset( $in['button_text'] ) && sanitize_hex_color( $in['button_text'] ) ? sanitize_hex_color( $in['button_text'] ) : '#ffffff',
'button_text_hover' => isset( $in['button_text_hover'] ) && sanitize_hex_color( $in['button_text_hover'] ) ? sanitize_hex_color( $in['button_text_hover'] ) : '#ffffff',
'button_border' => isset( $in['button_border'] ) && sanitize_hex_color( $in['button_border'] ) ? sanitize_hex_color( $in['button_border'] ) : '#0878a8',
'button_border_hover' => isset( $in['button_border_hover'] ) && sanitize_hex_color( $in['button_border_hover'] ) ? sanitize_hex_color( $in['button_border_hover'] ) : '#06678f',
'button_border_width' => isset( $in['button_border_width'] ) ? max( 0, min( 12, (int) $in['button_border_width'] ) ) : 1,
'button_radius' => isset( $in['button_radius'] ) ? max( 0, min( 100, (int) $in['button_radius'] ) ) : 4,
'button_font_size' => isset( $in['button_font_size'] ) ? max( 10, min( 40, (int) $in['button_font_size'] ) ) : 15,
'button_font_weight' => isset( $in['button_font_weight'] ) && in_array( (int) $in['button_font_weight'], array( 400, 500, 600, 700, 800, 900 ), true ) ? (int) $in['button_font_weight'] : 700,
'button_padding_y' => isset( $in['button_padding_y'] ) ? max( 0, min( 40, (int) $in['button_padding_y'] ) ) : 11,
'button_padding_x' => isset( $in['button_padding_x'] ) ? max( 0, min( 80, (int) $in['button_padding_x'] ) ) : 18,
'icon_only_breakpoint' => isset( $in['custom_icon_breakpoint'] ) && '' !== trim( $in['custom_icon_breakpoint'] ) ? max( 0, min( 2560, (int) $in['custom_icon_breakpoint'] ) ) : ( isset( $in['icon_only_breakpoint'] ) ? max( 0, min( 9999, (int) $in['icon_only_breakpoint'] ) ) : 0 ),
'show_cart' => empty( $in['show_cart'] ) ? 0 : 1,
'cart_position' => isset( $in['cart_position'] ) && 'before' === $in['cart_position'] ? 'before' : 'after',
'cart_icon' => isset( $in['cart_icon'] ) ? esc_url_raw( $in['cart_icon'] ) : '',
'cart_bubble_bg' => isset( $in['cart_bubble_bg'] ) && sanitize_hex_color( $in['cart_bubble_bg'] ) ? sanitize_hex_color( $in['cart_bubble_bg'] ) : '#f0b429',
'cart_bubble_text' => isset( $in['cart_bubble_text'] ) && sanitize_hex_color( $in['cart_bubble_text'] ) ? sanitize_hex_color( $in['cart_bubble_text'] ) : '#17324d',
'accent_color' => isset( $in['accent_color'] ) && sanitize_hex_color( $in['accent_color'] ) ? sanitize_hex_color( $in['accent_color'] ) : '#0878a8',
'panel_bg' => isset( $in['panel_bg'] ) && sanitize_hex_color( $in['panel_bg'] ) ? sanitize_hex_color( $in['panel_bg'] ) : '#ffffff',
'sideways_left_bg' => isset( $in['sideways_left_bg'] ) && sanitize_hex_color( $in['sideways_left_bg'] ) ? sanitize_hex_color( $in['sideways_left_bg'] ) : '#eef5f8',
'font_color' => isset( $in['font_color'] ) && sanitize_hex_color( $in['font_color'] ) ? sanitize_hex_color( $in['font_color'] ) : '#17324d',
'muted_color' => isset( $in['muted_color'] ) && sanitize_hex_color( $in['muted_color'] ) ? sanitize_hex_color( $in['muted_color'] ) : '#5d7185',
'border_color' => isset( $in['border_color'] ) && sanitize_hex_color( $in['border_color'] ) ? sanitize_hex_color( $in['border_color'] ) : '#dbe6ed',
'highlight_color' => isset( $in['highlight_color'] ) && sanitize_hex_color( $in['highlight_color'] ) ? sanitize_hex_color( $in['highlight_color'] ) : '#ffe08a',
'font_family' => isset( $in['font_family'] ) && in_array( $in['font_family'], array( 'inherit', 'Arial, sans-serif', 'Helvetica, Arial, sans-serif', 'Verdana, sans-serif', 'Georgia, serif', 'Tahoma, sans-serif', 'Trebuchet MS, sans-serif', 'system-ui, sans-serif' ), true ) ? $in['font_family'] : 'inherit',
'font_size' => isset( $in['font_size'] ) ? max( 10, min( 24, (int) $in['font_size'] ) ) : 14,
'parent_font_size' => isset( $in['parent_font_size'] ) ? max( 10, min( 30, (int) $in['parent_font_size'] ) ) : 15,
'category_font_weight' => isset( $in['category_font_weight'] ) && in_array( (int) $in['category_font_weight'], array( 400, 500, 600, 700, 800, 900 ), true ) ? (int) $in['category_font_weight'] : 600,
'panel_radius' => isset( $in['panel_radius'] ) ? max( 0, min( 60, (int) $in['panel_radius'] ) ) : 4,
'control_radius' => isset( $in['control_radius'] ) ? max( 0, min( 60, (int) $in['control_radius'] ) ) : 4,
'image_size' => isset( $in['image_size'] ) ? max( 12, min( 96, (int) $in['image_size'] ) ) : 20,
'image_radius' => isset( $in['image_radius'] ) ? max( 0, min( 48, (int) $in['image_radius'] ) ) : 0,
'show_counts' => empty( $in['show_counts'] ) ? 0 : 1,
'show_images' => empty( $in['show_images'] ) ? 0 : 1,
'hide_empty' => empty( $in['hide_empty'] ) ? 0 : 1,
'width' => isset( $in['width'] ) ? max( 600, min( 1920, (int) $in['width'] ) ) : 1200,
'all_link' => isset( $in['all_link'] ) ? esc_url_raw( $in['all_link'] ) : '',
'all_label' => isset( $in['all_label'] ) ? sanitize_text_field( $in['all_label'] ) : '',
'tab_label' => isset( $in['tab_label'] ) ? sanitize_text_field( $in['tab_label'] ) : '',
'tabs' => self::sanitize_tabs( isset( $in['tabs'] ) ? $in['tabs'] : array() ),
'frontend_text' => self::sanitize_frontend_text( isset( $in['frontend_text'] ) ? $in['frontend_text'] : array(), $saved ),
);
/**
* Extension modules may add their own sanitised settings.
* Only the already-sanitised settings array is passed, never raw input.
*/
$settings = apply_filters( 'smm_sanitize_settings', $settings, $post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- legacy 'smm_' prefix kept for compatibility between the free and premium distributions.
return $settings;
}
/**
* Per-menu overrides for built-in frontend messages. Only known keys are
* kept; when nothing is posted the previously saved values are preserved.
*/
protected static function sanitize_frontend_text( $input, $saved ) {
$keys = array( 'panel_title', 'search_label', 'search_placeholder', 'no_results', 'close_label', 'cart_label', 'empty_message', 'show_all', 'show_less' );
if ( ! is_array( $input ) || empty( $input ) ) {
return isset( $saved['frontend_text'] ) && is_array( $saved['frontend_text'] ) ? $saved['frontend_text'] : array();
}
$out = array();
foreach ( $keys as $key ) {
if ( isset( $input[ $key ] ) && '' !== trim( (string) $input[ $key ] ) ) {
$out[ $key ] = sanitize_text_field( $input[ $key ] );
}
}
return $out;
}
/** Additional menu tabs: keep only rows that point at a real source. */
protected static function sanitize_tabs( $rows ) {
$out = array();
foreach ( (array) $rows as $row ) {
if ( ! is_array( $row ) ) {
continue;
}
$source = ( isset( $row['source'] ) && 'taxonomy' === $row['source'] ) ? 'taxonomy' : 'menu';
$menu_id = isset( $row['menu_id'] ) ? (int) $row['menu_id'] : 0;
$tax = isset( $row['taxonomy'] ) ? sanitize_key( $row['taxonomy'] ) : '';
if ( 'menu' === $source && ! $menu_id ) {
continue;
}
if ( 'taxonomy' === $source && ! $tax ) {
continue;
}
$out[] = array(
'label' => isset( $row['label'] ) ? sanitize_text_field( $row['label'] ) : '',
'source' => $source,
'taxonomy' => $tax ? $tax : 'product_cat',
'menu_id' => $menu_id,
);
}
return $out;
}
protected static function sanitize_order( $nodes ) {
$out = array();
foreach ( (array) $nodes as $node ) {
if ( empty( $node['id'] ) ) {
continue;
}
$out[] = array(
'id' => sanitize_text_field( $node['id'] ),
'label' => isset( $node['label'] ) ? sanitize_text_field( $node['label'] ) : '',
'hidden' => empty( $node['hidden'] ) ? 0 : 1,
// 'branch' hides the item and everything under it; 'self' keeps the
// children and moves them up one level. Missing value = 'branch'.
'hide_mode' => ( isset( $node['hide_mode'] ) && 'self' === $node['hide_mode'] ) ? 'self' : 'branch',
// Source parent at save time; lets the merge tell manual moves apart
// from items re-parented in WordPress afterwards.
'np' => isset( $node['np'] ) ? sanitize_text_field( $node['np'] ) : '',
'children' => isset( $node['children'] ) ? self::sanitize_order( $node['children'] ) : array(),
);
}
return $out;
}
}