Custom shortcode being shown in WPBakery backend rather than the block - php

I have a nested element called Hero Slider. Hero slider is the container and it can only contain a Slider item.
However, my shortcode is being shown in the WPBakery backend maybe suggesting a syntax error, but I cannot see anything that is causing it to appear this way:
The block doesn't even appear when adding a new element.
Here's my custom element: (init.php)
<?php
if (!defined('ABSPATH')) die('-1');
class vcHeroSlider extends WPBakeryShortCode {
// 1. Define constants at compile time (used in mapping)
const slug = 'tp_hero_slider';
const base = 'tp_hero_slider';
// 2. Integrate with hooks
function __construct() {
// For the parent wrapper
add_action( 'vc_before_init', array( $this, 'tp_heroSlider_mapping' ) );
add_shortcode( 'tp_hero_slider', array( $this, 'tp_heroSlider_html' ));
// For child / nested
add_action( 'vc_before_init', array( $this, 'tp_heroSlider_content_mapping' ) );
add_shortcode( 'tp_hero_slider_content', array( $this, 'tp_heroSlider_content_html' ));
}
// 3. Map for parent element
public function tp_heroSlider_mapping() {
vc_map(
array(
'icon' => get_template_directory_uri().'/assets/src/images/html.svg',
'name' => __( 'Hero Slider' , "text-domain" ),
'base' => 'tp_hero_slider',
'description' => __( 'Add slick slider to your page.', "text-domain" ),
'as_parent' => array('only' => 'tp_hero_slider_content'), // set as parent of the content map/html
'content_element' => true,
'show_settings_on_create' => false,
"js_view" => 'VcColumnView',
"category" => __('Hero', "text-domain" ),
'params' => array(
array(
"type" => "textfield",
"heading" => __( "Extra Class Name", "text-domain" ),
"param_name" => "el_class",
"description" => __( "Extra class to be customized via CSS", "text-domain" )
),
array(
'type' => 'css_editor',
'heading' => __( 'Custom Design Options', "text-domain" ),
'param_name' => 'css',
'group' => __( 'Design options', "text-domain" ),
),
),
)
);
}
// 4. Map for child element
public function tp_heroSlider_content_mapping() {
vc_map(
array(
'icon' => get_template_directory_uri().'/assets/src/images/html.svg',
'name' => __('Slider Item', "text-domain" ),
'base' => 'tp_hero_slider_content',
'description' => __( 'Add slide to hero.', "text-domain" ),
"category" => __('Content', 'text-domain'),
'content_element' => true,
'as_child' => array('only' => 'tp_hero_slider'),
'params' => array(
array(
'type' => 'textfield',
'heading' => __( 'Title', 'text-domain'),
'param_name' => 'title',
'value' => esc_html__( '', 'text-domain'),
'admin_label' => true,
'weight' => 0,
'group' => __( 'Content', 'my-text-domain' ),
),
array(
'type' => 'textarea',
'class' => '',
'heading' => __( 'Standfirst', 'text-domain'),
'param_name' => 'standfirst',
'value' => esc_html__( '', 'text-domain'),
'admin_label' => false,
'weight' => 0,
'group' => __( 'Content', 'my-text-domain' ),
)
),
)
);
}
// 5. Mapping markup of parent
public function tp_heroSlider_html( $atts, $content = null) {
$output = '';
$el_class = '';
extract(
shortcode_atts(
array(
'el_class' => '',
), $atts
)
);
static $i = 0;
$output = '<div id="slickslider-'.$i++.'" class="Slick-Slider heroSlider">'. do_shortcode($content) .'</div>';
return $output;
}
// 6. Mapping markup of child
public function tp_heroSlider_content_html( $atts, $content = null ) {
$output = '';
extract(
shortcode_atts(
array(
'title' => '',
'standfirst' => '',
), $atts
)
);
$background_img = wp_get_attachment_image_src(intval($background_img), 'full');
$background_img = $background_img[0];
$output .= '
<!-- Slide -->
<div class="heroSlider__slide">
<div class="overlay"></div>
';
$output .= '
<div class="container">
<div class="row justify-content-center justify-content-lg-start">
<div class="col-10 col-md-6 d-flex flex-column text-center text-lg-left content">';
if (!empty($title)) {
$output .= '<h1>' . $title . '</h1>';
}
if (!empty($standfirst)) {
$output .= '<p class="standfist">' . $standfirst . '</p>';
}
$output .= '
</div>
</div>
</div>
</div>
<!-- Slide -->
';
return $output;
}
}
// 7. Add the container functionality (so you can choose a slider element within the hero_slider element
if(class_exists('WPBakeryShortCodesContainer')){
class WPBakeryShortCode_tp_hero_slider extends WPBakeryShortCodesContainer {}
}
if(class_exists('WPBakeryShortCode')){
class WPBakeryShortCode_tp_hero_slider_content extends WPBakeryShortCode {}
}
new vcHeroSlider(); ?>
How I've added new element:
add_action( 'vc_before_init', 'vc_before_init_actions' );
function vc_before_init_actions() {
$theme = get_template_directory();
require_once( $theme.'/vc_elements/hero-slider/init.php');]
}
Although I don't believe the above is the issue, because I have several other elements defined like that and they all work.

Related

White screen for wp-admin after wordpress function.php change (no log errors)

After my function.php code was modified I get a white screen when trying to go to my wordpress /wp-admin page. What could be causing the issue? Also general code improvement are welcome, as I never coded in php or wordpress before.
function.php file
<?php
function register_my_menus() {
register_nav_menus(
array(
'header-menu' => __( 'Header Menu' ),
'footer-menu1' => __( 'Footer Menu 1' ),
'footer-menu2' => __( 'Footer Menu 2' ),
'footer-menu3' => __( 'Footer Menu 3' ),
'side-menu' => __( 'Side Menu' ),
)
);
}
add_action( 'init', 'register_my_menus' );
add_theme_support( 'post-thumbnails' );
add_theme_support( 'custom-logo' );
add_filter ( 'nav_menu_css_class', 'so_37823371_menu_item_class', 10, 4 );
function so_37823371_menu_item_class ( $classes, $item, $args, $depth ){
$classes[] = 'nav-item';
return $classes;
}
add_filter( 'nav_menu_link_attributes', function($atts) {
$atts['class'] = "nav-link";
return $atts;
}, 100, 1 );
function my_theme_assets_files() {
wp_enqueue_script( 'custom-js', get_template_directory_uri(). '/js/custom.js' , [], '1.0.0' , true );
wp_localize_script( 'custom-js', 'jsh_ajax', [
'ajax_url' => admin_url( 'admin-ajax.php' )
]);
}
add_action( 'wp_enqueue_scripts', 'my_theme_assets_files' );
add_action("wp_ajax_sk_ajax_posts", "sk_ajax_posts");
add_action("wp_ajax_nopriv_sk_ajax_posts", "sk_ajax_posts");
function sk_ajax_posts() {
$cat_id = $_POST['cat_id'];
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => -1,
'cat' => $cat_id ,
// 'orderby’ => 'title',
// 'order’ => 'ASC',
);
$loop = new WP_Query( $args );
while ( $loop->have_posts() ) : $loop->the_post();
//the_post();
$categories = get_the_category();
$category_name = $categories[0]->name;
?>
<div class="col-md-4 ">
<div class="card border-0">
<div class="image-parent">
<?php echo wp_get_attachment_image( get_post_thumbnail_id( get_the_ID() ), 'full', '', [ 'class' => 'card-img-top' ] ); ?>
<?php echo $category_name; ?>
</div>
<div class="card-body">
<!-- <h6>April 25, 2022</h6> -->
<h5 class="card-title"><?php the_title(); ?></h5>
<p class="card-text"><?php the_excerpt()?></p>
Read the article
</div>
</div>
</div>
<?php
endwhile;
echo paginate_links(array(
'total' => $loop->max_num_pages,
'prev_text' => __('Previous'),
'next_text' => __('Next')
)) . "</div>";
wp_reset_postdata();
die();
}
/*
* Creating a function to create our CPT
*/
function fisher_custom_post_type_advisor() {
// Set UI labels for Custom Post Type
$labels = array(
'name' => _x( 'Advisor\'s', 'Post Type General Name', 'twentytwentyone' ),
'singular_name' => _x( 'advisor', 'Post Type Singular Name', 'twentytwentyone' ),
'menu_name' => __( 'Advisor\'s', 'twentytwentyone' ),
'parent_item_colon' => __( 'advisor', 'twentytwentyone' ),
'all_items' => __( 'All Advisor\'s', 'twentytwentyone' ),
'view_item' => __( 'View Advisor', 'twentytwentyone' ),
'add_new_item' => __( 'Add New Advisor', 'twentytwentyone' ),
'add_new' => __( 'Add New', 'twentytwentyone' ),
'edit_item' => __( 'Edit Advisor', 'twentytwentyone' ),
'update_item' => __( 'Update Advisor', 'twentytwentyone' ),
'search_items' => __( 'Search Advisor', 'twentytwentyone' ),
'not_found' => __( 'Not Found', 'twentytwentyone' ),
'not_found_in_trash' => __( 'Not found in Trash', 'twentytwentyone' ),
);
// Set other options for Custom Post Type
$args = array(
'label' => __( 'Advisor\'s', 'twentytwentyone' ),
'description' => __( 'Advisor\' news and reviews', 'twentytwentyone' ),
'labels' => $labels,
// Features this CPT supports in Post Editor
'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions' ),
// You can associate this CPT with a taxonomy or custom taxonomy.
// 'taxonomies' => array( 'genres' ),
/* A hierarchical CPT is like Pages and can have
* Parent and child items. A non-hierarchical CPT
* is like Posts.
*/
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'show_in_admin_bar' => true,
'menu_position' => 5,
'can_export' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'advisors' ),
'exclude_from_search' => false,
'publicly_queryable' => true,
'capability_type' => 'post',
'show_in_rest' => false,
);
// Registering your Custom Post Type
register_post_type( 'all-advisors', $args );
}
/* Hook into the 'init' action so that the function
* Containing our post type registration is not
* unnecessarily executed.
*/
add_action( 'init', 'fisher_custom_post_type_advisor', 0 );
function global_notice_meta_box() {
add_meta_box(
'advisor-first-name',
__( 'Advisor Detail', 'sitepoint' ),
'global_notice_meta_box_callback',
'all-advisors'
);
}
add_action( 'add_meta_boxes', 'global_notice_meta_box' );
function global_notice_meta_box_callback( $post ) {
// Add a nonce field so we can check for it later.
wp_nonce_field( 'fisher_advisor', 'fisher_advisor' );
global $fields;
$fields = [
array (
'key' => 'first_name',
'label'=> __( 'Full Name'),
'type' => 'text'
),
array (
'key' => 'role',
'label'=> __( 'Title'),
'type' => 'text'
),
array (
'key' => 'professional_certificate_short_form',
'label'=> __( 'Professional Certifications (Short)'),
'type' => 'text'
),
array (
'key' => 'professional_certificate',
'label'=> __( 'Professional Certifications'),
'type' => 'text'
),
array (
'key' => 'adress',
'label'=> __( 'Full Address'),
'type' => 'text'
),
array (
'key' => 'city',
'label' => __( 'Location'),
'type' => 'text'
),
array (
'key' => 'phone',
'label' => __( 'Phone' ),
'type' => 'tel'
),
array (
'key' => 'email',
'label' => __( 'Email' ),
'type' => 'email'
)
];
foreach ( $fields as $field ) {
$value = get_post_meta( $post->ID, '_advisor_'. $field['key'], true );
if ( $field['key'] == 'city' ) {
$args = [ 'post_type' => 'all-locations', 'posts_per_page' => -1 ];
$locations = new WP_Query($args);
if ( $locations->have_posts() ) {
echo '<label><span>' . $field['label'] .'</span>';
echo '<select name="advisor_' . $field['key'] . '">';
while ( $locations->have_posts() ) {
$locations->the_post();
echo '<option value="' . get_the_title() . '">' . get_the_title() . '</option>';
}
echo '</select></label>';
}
} else {
$value = get_post_meta( $post->ID, '_advisor_'. $field['key'], true );
echo '<label><span>' . $field['label'] .'</span> <input type="'. $field['type'] .'" name="advisor_' . $field['key'] . '" value="' . esc_attr( $value ) . '" /></label>';
echo '<br>';
}
}
}
function save_global_notice_meta_box_data( $post_id ) {
// Check if our nonce is set.
if ( ! isset( $_POST['fisher_advisor'] ) ) {
return;
}
// Verify that the nonce is valid.
if ( ! wp_verify_nonce( $_POST['fisher_advisor'], 'fisher_advisor' ) ) {
return;
}
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Check the user's permissions.
if ( isset( $_POST['post_type'] ) && 'all-advisors' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return;
}
}
else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
}
/* OK, it's safe for us to save the data now. */
// Make sure that it is set.
$fields = [
array (
'key' => 'first_name',
'label'=> __( 'Full Name'),
'type' => 'text'
),
array (
'key' => 'role',
'label'=> __( 'Title'),
'type' => 'text'
),
array (
'key' => 'professional_certificate_short_form',
'label'=> __( 'Professional Certifications (Short)'),
'type' => 'text'
),
array (
'key' => 'professional_certificate',
'label'=> __( 'Professional Certifications'),
'type' => 'text'
),
array (
'key' => 'adress',
'label'=> __( 'Full Address'),
'type' => 'text'
),
array (
'key' => 'city',
'label' => __( 'Location'),
'type' => 'text'
),
array (
'key' => 'phone',
'label' => __( 'Phone' ),
'type' => 'tel'
),
array (
'key' => 'email',
'label' => __( 'Email' ),
'type' => 'email'
)
];
foreach ( $fields as $field ) {
if ( ! isset( $_POST['advisor_' . $field['key']] ) ) return;
$sav_field = sanitize_text_field( $_POST['advisor_' . $field['key']] );
update_post_meta( $post_id, '_advisor_' . $field['key'], $sav_field );
}
}
add_action( 'save_post', 'save_global_notice_meta_box_data' );
/*****cpt for location ***/
/*
* Creating a function to create our CPT
*/
function fisher_custom_post_type_location() {
// Set UI labels for Custom Post Type
$labels = array(
'name' => _x( 'Location\'s', 'Post Type General Name', 'twentytwentyone' ),
'singular_name' => _x( 'Location', 'Post Type Singular Name', 'twentytwentyone' ),
'menu_name' => __( 'Location\'s', 'twentytwentyone' ),
'parent_item_colon' => __( 'Location', 'twentytwentyone' ),
'all_items' => __( 'All Location\'s', 'twentytwentyone' ),
'view_item' => __( 'View Location', 'twentytwentyone' ),
'add_new_item' => __( 'Add New Location', 'twentytwentyone' ),
'add_new' => __( 'Add New', 'twentytwentyone' ),
'edit_item' => __( 'Edit Location', 'twentytwentyone' ),
'update_item' => __( 'Update Location', 'twentytwentyone' ),
'search_items' => __( 'Search Location', 'twentytwentyone' ),
'not_found' => __( 'Not Found', 'twentytwentyone' ),
'not_found_in_trash' => __( 'Not found in Trash', 'twentytwentyone' ),
);
// Set other options for Custom Post Type
$args = array(
'label' => __( 'Location\'s', 'twentytwentyone' ),
'description' => __( 'Location\' news and reviews', 'twentytwentyone' ),
'labels' => $labels,
// Features this CPT supports in Post Editor
'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions' ),
// You can associate this CPT with a taxonomy or custom taxonomy.
// 'taxonomies' => array( 'genres' ),
/* A hierarchical CPT is like Pages and can have
* Parent and child items. A non-hierarchical CPT
* is like Posts.
*/
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'show_in_admin_bar' => true,
'menu_position' => 5,
'can_export' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'locations' ),
'exclude_from_search' => false,
'publicly_queryable' => true,
'capability_type' => 'post',
'show_in_rest' => false,
);
// Registering your Custom Post Type
register_post_type( 'all-locations', $args );
}
/* Hook into the 'init' action so that the function
* Containing our post type registration is not
* unnecessarily executed.
*/
add_action( 'init', 'fisher_custom_post_type_location', 0 );
/****locatin-meta-box**** */
function global_notice_meta_box_location() {
add_meta_box(
'advisor-first-name',
__( 'Location Detail', 'sitepoint' ),
'global_notice_meta_box_location_callback',
'all-locations'
);
}
add_action( 'add_meta_boxes', 'global_notice_meta_box_location' );
function global_notice_meta_box_location_callback( $post ) {
// Add a nonce field so we can check for it later.
wp_nonce_field( 'fisher_location', 'fisher_location' );
global $fields;
$fields = [
array (
'key' => 'city',
'label'=> __( 'city'),
'type' => 'text'
),
array (
'key' => 'adress',
'label'=> __( 'adress'),
'type' => 'text'
),
array (
'key' => 'phone',
'label' => __( 'Phone' ),
'type' => 'tel'
),
array (
'key' => 'lati',
'label' => __( 'latitutde' ),
'type' => 'text'
),
array (
'key' => 'langi',
'label' => __( 'langitude' ),
'type' => 'text'
),
];
foreach ( $fields as $field ) {
$value = get_post_meta( $post->ID, '_location_'. $field['key'], true );
echo '<label><span>' . $field['label'] .'</span> <input type="'. $field['type'] .'" name="location_' . $field['key'] . '" value="' . esc_attr( $value ) . '" /></label>';
echo '<br>';
}
}
function save_global_notice_meta_box_location_data( $post_id ) {
// Check if our nonce is set.
if ( ! isset( $_POST['fisher_location'] ) ) {
return;
}
// Verify that the nonce is valid.
if ( ! wp_verify_nonce( $_POST['fisher_location'], 'fisher_location' ) ) {
return;
}
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Check the user's permissions.
if ( isset( $_POST['post_type'] ) && 'all-locations' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return;
}
}
else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
}
/* OK, it's safe for us to save the data now. */
// Make sure that it is set.
$fields = [
array (
'key' => 'city',
'label'=> __( 'city'),
'type' => 'text'
),
array (
'key' => 'adress',
'label'=> __( 'adress'),
'type' => 'text'
),
array (
'key' => 'phone',
'label' => __( 'Phone' ),
'type' => 'tel'
),
array (
'key' => 'lati',
'label' => __( 'latitutde' ),
'type' => 'text'
),
array (
'key' => 'langi',
'label' => __( 'langitude' ),
'type' => 'text'
),
];
foreach ( $fields as $field ) {
if ( ! isset( $_POST['location_' . $field['key']] ) ) return;
$sav_field = sanitize_text_field( $_POST['location_' . $field['key']] );
update_post_meta( $post_id, '_location_' . $field['key'], $sav_field );
}
}
When the below was added, I started getting a blank screen for wordpress when I go to /wp-admin
add_action( 'save_post', 'save_global_notice_meta_box_location_data' );
?>
<?php
//SETTING UP THE PAGE
function set_page() {
add_menu_page('Social Media Links', 'Social Media Links', 'manage_options', 'theme_settings', 'the_page', 'dashicons-share', 40);
}
//CALLING THE FUNCTION
add_action('admin_menu', 'set_page');?>
<?php //THE PAGE
function the_page() {?>
<link rel="stylesheet" href="<?php echo get_template_directory_uri()?>/css/bootstrap.min.css">
<legend style="background: #2271b1;color: #fff;padding: 15px 30px;margin-bottom: 60px;margin-top: 10px;padding-right: 85px;max-width: 99%;">Social media linked accounts</legend>
<div class="container"><div class="row"><div class="col-md-12">
<form method="post" action="options.php">
<?php settings_fields('theme_settings'); ?>
<?php $options = get_option('theme_settings'); ?>
<?php if ($_REQUEST['settings-updated']) : ?>
<div class="updated">
<p><strong class="alret">
<?php _e('Options saved'); ?>
</strong></p>
</div>
<?php endif; ?>
<div class="the-container">
<div id="social" class="container tab-pane fade active show"><br>
<fieldset>
<?php $social_arr = ['facebook','twitter','linkedin','youtube'];
foreach($social_arr as $social){?>
<div class="form-group form_builder_row facebook_container row">
<div class="col-md-2 form_builder_col">
<label class="control-label <?php echo $social;?>" for="<?php echo $social;?>">
<?php echo ucfirst($social);?>:</label>
</div>
<div class="col-md-6 form_builder_col">
<input type="text" class=" form_builder_field form-control input-text field_<?php echo $social;?>" id="<?php echo $social;?>" name="theme_settings[<?php echo $social;?>]" value="<?php echo $options[$social];?>" placeholder="<?php echo ucfirst($social);?>">
</div></div>
<?php }?>
</fieldset>
</div>
</div>
<p class="mt-4">
<input name="theme_settings[submit]" id="submit" value="Save Changes" type="submit" class="button button-primary btn-md ml-3">
</p>
</form>
</div></div></div>
<?php } ?>
<?php
//REGISTERING SETTINGS AND FIELDS
function register_settings_and_fields() {
register_setting('theme_settings','theme_settings');
}
add_action('admin_init', 'register_settings_and_fields');
?>
Have you opened wp-config.php? and set WP_DEBUG to true; this can show some errors more intuitively
Don't use function names like this the_page set_page use a prefix before function name e.g.: lefty_wp_set_page , lefty_wp_the_page or give a function a meaningful name, don't just copy paste things from internet, do some modifications in it to avoid conflicts.

Add a custom WooCommerce settings page, including page sections

I'm trying to add a custom settings tab to the WooCommerce settings screen. Basically I want to achieve a similar thing to the Products settings tab, with the subsections/subtabs:
I haven't been able to find any decent documentation on how to do this but I've been able to add a custom tab using this snippet:
class WC_Settings_Tab_Demo {
public static function init() {
add_filter( 'woocommerce_settings_tabs_array', __CLASS__ . '::add_settings_tab', 50 );
}
public static function add_settings_tab( $settings_tabs ) {
$settings_tabs['test'] = __( 'Settings Demo Tab', 'woocommerce-settings-tab-demo' );
return $settings_tabs;
}
}
WC_Settings_Tab_Demo::init();
Based on what I've dug up from various threads/tutorials, I've been trying to add the sections/subtabs to the new settings tab something like this:
// creating a new sub tab in API settings
add_filter( 'woocommerce_get_sections_test','add_subtab' );
function add_subtab( $sections ) {
$sections['custom_settings'] = __( 'Custom Settings', 'woocommerce-custom-settings-tab' );
$sections['more_settings'] = __( 'More Settings', 'woocommerce-custom-settings-tab' );
return $sections;
}
// adding settings (HTML Form)
add_filter( 'woocommerce_get_settings_test', 'add_subtab_settings', 10, 2 );
function add_subtab_settings( $settings, $current_section ) {
// $current_section = (isset($_GET['section']) && !empty($_GET['section']))? $_GET['section']:'';
if ( $current_section == 'custom_settings' ) {
$custom_settings = array();
$custom_settings[] = array( 'name' => __( 'Custom Settings', 'text-domain' ),
'type' => 'title',
'desc' => __( 'The following options are used to ...', 'text-domain' ),
'id' => 'custom_settings'
);
$custom_settings[] = array(
'name' => __( 'Field 1', 'text-domain' ),
'id' => 'field_one',
'type' => 'text',
'default' => get_option('field_one'),
);
$custom_settings[] = array( 'type' => 'sectionend', 'id' => 'test-options' );
return $custom_settings;
} else {
// If not, return the standard settings
return $settings;
}
}
I've been able to add new subsections to the Products tab using similar code to the above, but it isn't working for my new custom tab. Where am I going wrong here?
1) To add a setting tab with sections, you can firstly use the woocommerce_settings_tabs_array filter hook:
// Add the tab to the tabs array
function filter_woocommerce_settings_tabs_array( $settings_tabs ) {
$settings_tabs['my-custom-tab'] = __( 'My custom tab', 'woocommerce' );
return $settings_tabs;
}
add_filter( 'woocommerce_settings_tabs_array', 'filter_woocommerce_settings_tabs_array', 99 );
2) To add new sections to the page, you can use the woocommerce_sections_{$current_tab} composite hook where {$current_tab} need to be replaced by the key slug that is set in the first function:
// Add new sections to the page
function action_woocommerce_sections_my_custom_tab() {
global $current_section;
$tab_id = 'my-custom-tab';
// Must contain more than one section to display the links
// Make first element's key empty ('')
$sections = array(
'' => __( 'Overview', 'woocommerce' ),
'my-section-1' => __( 'My section 1', 'woocommerce' ),
'my-section-2' => __( 'My section 2', 'woocommerce' )
);
echo '<ul class="subsubsub">';
$array_keys = array_keys( $sections );
foreach ( $sections as $id => $label ) {
echo '<li>' . $label . ' ' . ( end( $array_keys ) == $id ? '' : '|' ) . ' </li>';
}
echo '</ul><br class="clear" />';
}
add_action( 'woocommerce_sections_my-custom-tab', 'action_woocommerce_sections_my_custom_tab', 10 );
3) For adding the settings, as well as for processing/saving, we will use a custom function, which we will then call:
// Settings function
function get_custom_settings() {
global $current_section;
$settings = array();
if ( $current_section == 'my-section-1' ) {
// My section 1
$settings = array(
// Title
array(
'title' => __( 'Your title 1', 'woocommerce' ),
'type' => 'title',
'id' => 'custom_settings_1'
),
// Text
array(
'title' => __( 'Your title 1.1', 'text-domain' ),
'type' => 'text',
'desc' => __( 'Your description 1.1', 'woocommerce' ),
'desc_tip' => true,
'id' => 'custom_settings_1_text',
'css' => 'min-width:300px;'
),
// Select
array(
'title' => __( 'Your title 1.2', 'woocommerce' ),
'desc' => __( 'Your description 1.2', 'woocommerce' ),
'id' => 'custom_settings_1_select',
'class' => 'wc-enhanced-select',
'css' => 'min-width:300px;',
'default' => 'aa',
'type' => 'select',
'options' => array(
'aa' => __( 'aa', 'woocommerce' ),
'bb' => __( 'bb', 'woocommerce' ),
'cc' => __( 'cc', 'woocommerce' ),
'dd' => __( 'dd', 'woocommerce' ),
),
'desc_tip' => true,
),
// Section end
array(
'type' => 'sectionend',
'id' => 'custom_settings_1'
),
);
} elseif ( $current_section == 'my-section-2' ) {
// My section 2
$settings = array(
// Title
array(
'title' => __( 'Your title 2', 'woocommerce' ),
'type' => 'title',
'id' => 'custom_settings_2'
),
// Text
array(
'title' => __( 'Your title 2.2', 'text-domain' ),
'type' => 'text',
'desc' => __( 'Your description 2.1', 'woocommerce' ),
'desc_tip' => true,
'id' => 'custom_settings_2_text',
'css' => 'min-width:300px;'
),
// Section end
array(
'type' => 'sectionend',
'id' => 'custom_settings_2'
),
);
} else {
// Overview
$settings = array(
// Title
array(
'title' => __( 'Overview', 'woocommerce' ),
'type' => 'title',
'id' => 'custom_settings_overview'
),
// Section end
array(
'type' => 'sectionend',
'id' => 'custom_settings_overview'
),
);
}
return $settings;
}
3.1) Add settings, via the woocommerce_settings_{$current_tab} composite hook:
// Add settings
function action_woocommerce_settings_my_custom_tab() {
// Call settings function
$settings = get_custom_settings();
WC_Admin_Settings::output_fields( $settings );
}
add_action( 'woocommerce_settings_my-custom-tab', 'action_woocommerce_settings_my_custom_tab', 10 );
3.2) Process/save the settings, via the woocommerce_settings_save_{$current_tab} composite hook:
// Process/save the settings
function action_woocommerce_settings_save_my_custom_tab() {
global $current_section;
$tab_id = 'my-custom-tab';
// Call settings function
$settings = get_custom_settings();
WC_Admin_Settings::save_fields( $settings );
if ( $current_section ) {
do_action( 'woocommerce_update_options_' . $tab_id . '_' . $current_section );
}
}
add_action( 'woocommerce_settings_save_my-custom-tab', 'action_woocommerce_settings_save_my_custom_tab', 10 );
Result:
Based on:
Implement a custom WooCommerce settings page, including page sections
woocommerce/includes/admin/settings/

Else statement being executed in custom shortcode

I have the following shortcode mapped to display a dropdown field in WPBakery:
array(
'type' => 'dropdown',
'heading' => esc_html__( 'Type of container', 'js_composer' ),
'param_name' => 'container_type',
'value' => array(
'Container' => 'container',
'Container fluid (full width)' => 'container-fluid',
),
'std' => 'container',
'description' => esc_html__( 'Select whether you want the content to be contained or full width on the screen.', 'js_composer' ),
)
It is being initialised in another file where its attributes and markup is defined:
<?php
/**
* Shortcode attributes
* #var $container_type
*/
class vcRow extends WPBakeryShortCode {
function __construct() {
add_action( 'init', array( $this, 'vc_row_mapping' ) );
add_shortcode( 'vc_row', array( $this, 'vc_row' ) );
}
public function vc_text_mapping() {
vc_map(
array(
'name' => __('row', 'text-domain'),
'base' => 'vc_row',
'description' => __('', 'text-domain'),
'params' => array(
array(
'type' => 'dropdown',
'heading' => esc_html__( 'Type of container', 'js_composer' ),
'param_name' => 'container_type',
'value' => array(
'Container' => 'container',
'Container fluid (full width)' => 'container-fluid',
),
'std' => 'container',
),
);
}
public function vc_row($atts, $content) {
extract(
shortcode_atts(
array(
'container_type' => '',
),
$atts
)
);
if ($container_type == "container-fluid"){
echo "container-fluid";
} else {
echo "container";
}
echo "<p>".$container_type."</p>";
}
}
new vcRow();?>
One of my fields is set to 'container-fluid, but anechoof$container_type` is showing "container" (meaning the else statement is being executed. Unsure why?

Custom divi builder child module not rendering

I am trying to create some custom Divi builder modules.
I followed the sparse documentation at their website, trying to create a module and a child module. Everything seems to be working fine, but the rendering.
It is just rendering the module shortcode as a string, and not the content.
Current code of the parent module is
class DEDE_Cards extends ET_Builder_Module {
public function init(){
$this->name = esc_html__('Custom Card', 'dede-designvox-divi-extension');
$this->plural = esc_html__('Custom Cards', 'dede-designvox-divi-extension');
$this->main_css_element = 'cards-wrapper';
$this->slug = 'dede_cards';
$this->vb_support = 'on';
$this->child_slug = 'dede_card_item';
}
public function get_fields(){
return array(
'title' => array(
'label' => esc_html__( 'Title', 'dede-designvox-divi-extension' ),
'type' => 'text',
'toggle_slug' => 'main_content',
'description' => esc_html__( 'Type the section title' ),
),
'card_title_position' => array(
'label' => esc_html__( 'Title Position', 'dede-designvox-divi-extension' ),
'type' => 'select',
'options' => array(
'overlay' => esc_html__( 'Overlay', 'et_builder' ),
'top' => esc_html__( 'Over image', 'et_builder' ),
'bottom' => esc_html__( 'Under image', 'et_builder' ),
),
'toggle_slug' => 'main_content',
'description' => esc_html__( 'Here you can choose the position of the card title', 'dede-designvox-divi-extension' ),
'default_on_front' => 'bottom',
),
);
}
function before_render() {
global $dede_card_item_number;
$dede_card_item_number = 1;
}
public function render($unprocessed_props, $content = null, $render_slug ){
global $dede_card_item_number;
return sprintf(
'<div>%1$s</div>',
$this->content
);
}
public function add_new_child_text() {
return esc_html__( 'Add New Card Item1', 'dede-designvox-divi-extension' );
}
}
new DEDE_Cards;
And then the child module
class DEDE_Card_Item extends ET_Builder_Module {
public function init(){
$this->name = esc_html__('Custom Card', 'dede-designvox-divi-extension');
$this->plural = esc_html__('Custom Cards', 'dede-designvox-divi-extension');
$this->type= 'child';
$this->child_title_var = 'title';
$this->no_render = true;
$this->slug = 'dede_card_item';
$this->vb_support = 'on';
}
public function get_fields(){
return array(
'title' => array(
'label' => esc_html__('Title', 'dede-designvox-divi-extension'),
'type' => 'text',
'description' => esc_html__('The title of this card', 'dede-designvox-divi-extension'),
'toggle_slug' => 'main_content',
),
'desc' => array(
'label' => esc_html__( 'Description', 'dede-designvox-divi-extension' ),
'type' => 'textarea',
'description' => esc_html__( 'The card description' ),
'toggle_slug' => 'main_content',
),
'src' => array(
'label' => esc_html__( 'Image', 'dede-designvox-divi-extension'),
'type' => 'upload',
'description' => esc_html__( 'Upload your desired image, or type in the URL to the image', 'dede-designvox-divi-extension'),
'upload_button_text' => esc_attr__('Upload an image', 'dede-designvox-divi-extension'),
'choose_text' => esc_attr__('Choose an Image', 'dede-designvox-divi-extension'),
'update_text' => esc_attr__('Set as Image', 'dede-designvox-divi-extension'),
'affects' => 'alt',
'toggle_slug' => 'main_content',
),
'alt' => array(
'label' => esc_html__( 'Image Alternative Text', 'dede-designvox-divi-extension' ),
'type' => 'text',
'depends_show_if' => 'on',
'depends_on' => array(
'src',
),
'description' => esc_html__( 'This defines the HTML ALT text. A short description of your image can be placed here.', 'dede-designvox-divi-extension' ),
'toggle_slug' => 'main_content',
),
'url' => array(
'label' => esc_html__( 'Link URL', 'dede-designvox-divi-extension' ),
'type' => 'text',
'description' => esc_html__( 'Enter the link that you would like this card to direct to.', 'dede-designvox-divi-extension' ),
'toggle_slug' => 'main_content',
),
);
}
}
new DEDE_Card_Item;
The shortcode is just being rendered as a string on the website, like:
[dede_card_item _builder_version=”3.14″ title=”#1 card title” desc=”#1 card desc” src=”http://localhost:8888/wp-content/uploads/2018/09/14079861_1202408189819777_4296465020285869305_n.jpg” use_background_color_gradient=”off” background_color_gradient_start=”#2b87da” background_color_gradient_end=”#29c4a9″ background_color_gradient_type=”linear” background_color_gradient_direction=”180deg” background_color_gradient_direction_radial=”center” background_color_gradient_start_position=”0%” background_color_gradient_end_position=”100%” background_color_gradient_overlays_image=”off” parallax=”off” parallax_method=”on” background_size=”cover” background_position=”center” background_repeat=”no-repeat” background_blend=”normal” allow_player_pause=”off” background_video_pause_outside_viewport=”on” text_shadow_style=”none” module_text_shadow_style=”none” box_shadow_style=”none” /][dede_card_item _builder_version=”3.14″ title=”#1 card title” desc=”#1 card desc” src=”http://localhost:8888/wp-content/uploads/2018/09/14079861_1202408189819777_4296465020285869305_n.jpg” use_background_color_gradient=”off” background_color_gradient_start=”#2b87da” background_color_gradient_end=”#29c4a9″ background_color_gradient_type=”linear” background_color_gradient_direction=”180deg” background_color_gradient_direction_radial=”center” background_color_gradient_start_position=”0%” background_color_gradient_end_position=”100%” background_color_gradient_overlays_image=”off” parallax=”off” parallax_method=”on” background_size=”cover” background_position=”center” background_repeat=”no-repeat” background_blend=”normal” allow_player_pause=”off” background_video_pause_outside_viewport=”on” text_shadow_style=”none” module_text_shadow_style=”none” box_shadow_style=”none” /][dede_card_item _builder_version=”3.14″ title=”#1 card title” desc=”#1 card desc” src=”http://localhost:8888/wp-content/uploads/2018/09/14079861_1202408189819777_4296465020285869305_n.jpg” use_background_color_gradient=”off” background_color_gradient_start=”#2b87da” background_color_gradient_end=”#29c4a9″ background_color_gradient_type=”linear” background_color_gradient_direction=”180deg” background_color_gradient_direction_radial=”center” background_color_gradient_start_position=”0%” background_color_gradient_end_position=”100%” background_color_gradient_overlays_image=”off” parallax=”off” parallax_method=”on” background_size=”cover” background_position=”center” background_repeat=”no-repeat” background_blend=”normal” allow_player_pause=”off” background_video_pause_outside_viewport=”on” text_shadow_style=”none” module_text_shadow_style=”none” box_shadow_style=”none” /][dede_card_item _builder_version=”3.14″ title=”#1 card title” desc=”#1 card desc” src=”http://localhost:8888/wp-content/uploads/2018/09/14079861_1202408189819777_4296465020285869305_n.jpg” use_background_color_gradient=”off” background_color_gradient_start=”#2b87da” background_color_gradient_end=”#29c4a9″ background_color_gradient_type=”linear” background_color_gradient_direction=”180deg” background_color_gradient_direction_radial=”center” background_color_gradient_start_position=”0%” background_color_gradient_end_position=”100%” background_color_gradient_overlays_image=”off” parallax=”off” parallax_method=”on” background_size=”cover” background_position=”center” background_repeat=”no-repeat” background_blend=”normal” allow_player_pause=”off” background_video_pause_outside_viewport=”on” text_shadow_style=”none” module_text_shadow_style=”none” box_shadow_style=”none” /]
You just have to leave out the line $this->no_render = true; and then implement the function render in the child Module.
I try to create the same module to add child items inside module. But I have another problem, when I click add child item I get the empty popup ((( like this https://prnt.sc/156jfev. I tried the same code as author
it's working for me you can try it,
Parents modules::
<?php
class GRDI_Barchart extends ET_Builder_Module {
public $slug = 'grdi_bar_chart';
public $vb_support = 'on';
protected $module_credits = array(
'module_uri' => '',
'author' => '',
'author_uri' => '',
);
public function init() {
$this->name = esc_html__( 'Bar Chart', 'grdi-graphina-divi' );
$this->plural = esc_html__( 'Bar Chart', 'et_builder' );
$this->slug = 'grdi_bar_chart';
$this->vb_support = 'on';
$this->child_slug = 'grdi_bar_chart_child';
$this->settings_modal_toggles = array(
'general' => array(
'toggles' => array(
'main_content' => esc_html__( 'Graphina Text', 'dsm-supreme-modules-for-divi' ),
),
),
'advanced' => array(
'toggles' => array(
'separator' => array(
'title' => esc_html__( 'Separator', 'dsm-supreme-modules-for-divi' ),
'priority' => 70,
),
'image' => array(
'title' => esc_html__( 'Image', 'dsm-supreme-modules-for-divi' ),
'priority' => 69,
),
),
),
);
}
public function get_fields() {
return array(
'title' => array(
'label' => esc_html__( 'Title', 'grdi-graphina-divi' ),
'type' => 'text',
'option_category' => 'basic_option',
'description' => esc_html__( 'Input your desired heading here.', 'simp-simple-extension' ),
'toggle_slug' => 'main_content',
),
'content' => array(
'label' => esc_html__( 'Description', 'grdi-graphina-divi' ),
'type' => 'tiny_mce',
'option_category' => 'basic_option',
'description' => esc_html__( 'Content entered here will appear inside the module.', 'grdi-graphina-divi' ),
'toggle_slug' => 'main_content',
),
);
}
public function get_advanced_fields_config() {
return array();
}
public function get_charts(){
echo "charts";
}
public function render( $attrs, $content = null, $render_slug ) {
wp_enqueue_script( 'graphina_apex_chart_script' );
wp_enqueue_style( 'graphina_apex_chart_style' );
wp_enqueue_style( 'graphina-public' );
echo "<pre/>";
$var = "<script>
var options = {
series: [{
name: 'Sales',
data: [4, 3, 10, 9, 29, 19, 22, 9, 12, 7, 19, 5, 13, 9, 17, 2, 7, 5]
}],
chart: {
type: 'bar',
height: 350
},
plotOptions: {
bar: {
borderRadius: 4,
horizontal: true,
}
},
dataLabels: {
enabled: false
},
xaxis: {
categories: ['South Korea', 'Canada', 'United Kingdom', 'Netherlands', 'Italy', 'France', 'Japan',
'United States', 'China', 'Germany'
],
}
};
var chart = new ApexCharts(document.querySelector('#charts'), options);
chart.render();
</script>" ;
$html = sprintf(
'<h1>%1$s</h1>
<p>%2$s</p>
<div id="charts"></div>',
$this->props['title'],
$this->props['content']
);
echo $html ;
echo $var ;
}
}
new GRDI_Barchart;
Child Modules::
<?php
class GRDI_BarchartChild extends ET_Builder_Module {
function init() {
$this->name = esc_html__( 'Bar Chart Items', 'et_builder' );
$this->plural = esc_html__( 'Bar Chart Items', 'et_builder' );
$this->slug = 'grdi_bar_chart_child';
$this->vb_support = 'on';
$this->type = 'child';
$this->child_title_var = 'title';
$this->no_render = true;
$this->settings_text = esc_html__( 'Bar Chart Settings', 'et_builder' );
$this->settings_modal_toggles = array(
'general' => array(
'toggles' => array(
'main_content' => et_builder_i18n( 'Text' ),
),
),
'advanced' => array(
'toggles' => array(
'icon' => esc_html__( 'Icon', 'et_builder' ),
'toggle_layout' => esc_html__( 'Toggle', 'et_builder' ),
'text' => array(
'title' => et_builder_i18n( 'Text' ),
'priority' => 49,
),
),
),
);
}
function get_fields() {
$fields = array(
'child_title' => array(
'label' => et_builder_i18n( 'Data Label' ),
'type' => 'text',
'option_category' => 'basic_option',
'description' => esc_html__( 'Data Label', 'et_builder' ),
'toggle_slug' => 'main_content',
'dynamic_content' => 'text',
'hover' => 'tabs',
'mobile_options' => true,
),
'child_content' => array(
'label' => et_builder_i18n( 'Data Value' ),
'type' => 'text',
'option_category' => 'basic_option',
'description' => esc_html__( 'Data Value', 'et_builder' ),
'toggle_slug' => 'main_content',
'dynamic_content' => 'text',
'hover' => 'tabs',
'mobile_options' => true,
),
);
return $fields;
}
function render($attrs, $content, $render_slug){
$output = sprintf(
'<h1>%1$s</h1>
<p>%2$s</p>',
$this->props['child_title'],
$this->props['child_content']
);
return $output;
}
}
if ( et_builder_should_load_all_module_data() ) {
new GRDI_BarchartChild();
}
I was having similar issue to Mikael, where the child's setting looked like:
Just in case anyone is having similar issue, i realised its because the child's module wasn't being loaded. Since i was using a custom extension, there was a file called loader.phpin my extension that was responsible for loading the module files and it was loading the parent module file but skipping the child because of the pesky if statement with preg_match.
$module_files = glob( __DIR__ . '/modules/**/*.php' );
foreach ( (array) $module_files as $module_file ) {
if ( $module_file && preg_match( "/\/modules\/\b([^\/]+)\/\\1\.php$/", $module_file ) ) {
require_once $module_file;
}
}
if you replace it with:
foreach ( (array) $module_files as $module_file ) {
// if ( $module_file && preg_match( "/\/modules\/\b([^\/]+)\/\\1\.php$/", $module_file ) ) {
require_once $module_file;
//}
}
then it worked for me :)
Really hope that saves someone the 3 hours of debugging i spent.

custom vc elements won't render on translated pages wpml plugin

I have created a custom post type for a slider posts and then a custom visual composer element to gather those posts from that post type and then assemble it to create a slider but it won't render on the translated pages like if I create a german page, the custom vc element (slider) won't render or show unto that page. Any help, ideas please?
here's the vc element
<?php class axada_custom_elements extends WPBakeryShortCode {
// Element Init
function __construct() {
add_action( 'init', array( $this, 'axada_custom_elements_mapping' ) );
add_shortcode( 'axada_slider', array( $this, 'axada_slider_html' ) );
}
// Element Mapping
public function axada_custom_elements_mapping() {
// Stop all if VC is not enabled
if ( !defined( 'WPB_VC_VERSION' ) ) {
return;
}
// Map the block with vc_map()
vc_map(
array(
'name' => __('Axada Slider', 'text-domain'),
'base' => 'axada_slider',
'description' => __('Axada Slider', 'text-domain'),
'category' => __('Axada Elements', 'text-domain'),
"as_parent" => array('except' => ''), // Use only|except attributes to limit child shortcodes (separate multiple values with comma)
"content_element" => true,
"show_settings_on_create" => true,
"is_container" => true,
'icon' => get_template_directory_uri().'/img/axada-short-logo.png',
'params' => array(
array(
'type' => 'textfield',
'holder' => 'div',
'class' => 'title-class',
'heading' => __( 'Slider ID', 'text-domain' ),
'param_name' => 'slider_id',
'value' => __( '', 'text-domain' ),
'description' => __( 'Slider ID', 'text-domain' ),
'admin_label' => false,
'weight' => 0,
'group' => 'Custom Group',
),
array(
'type' => 'textfield',
'holder' => 'div',
'class' => 'title-class',
'heading' => __( 'Slider Classes', 'text-domain' ),
'param_name' => 'slider_class',
'value' => __( '', 'text-domain' ),
'description' => __( 'Slider Classes', 'text-domain' ),
'admin_label' => false,
'weight' => 0,
'group' => 'Custom Group',
),
array(
'type' => 'textfield',
'holder' => 'div',
'class' => 'title-class',
'heading' => __( 'Slider Custom Styles', 'text-domain' ),
'param_name' => 'slider_custom_styles',
'value' => __( '', 'text-domain' ),
'description' => __( 'Slider Custom Styles', 'text-domain' ),
'admin_label' => false,
'weight' => 0,
'group' => 'Custom Group',
),
),
)
);
}
// Element HTML
public function axada_slider_html( $atts ) {
// Params extraction
extract(
shortcode_atts(
array(
'slider_id' => '',
'slider_class' => '',
'slider_custom_styles' => '',
),
$atts
)
);
$slider_custom_styles = $slider_custom_styles !== '' ? 'style="'.$slider_custom_styles.'"' : '';
// Fill $html var with data
//query for slider
ob_start();
//query for slider
$args = array(
'post_type' => 'axada_slider',
'meta_key' => '_arrangement_value_key',
'orderby' => 'meta_value',
'order' => 'ASC',
'post_status' => 'publish',
);
$slider = new WP_Query( $args );
if ( $slider->have_posts() ) :
//start slider
?>
<section class="kenburns image-slider slider-all-controls controls-inside pt0 pb0 height-70 vid-bg bg-dark" id="home-slider-wrapper">
<ul<?php echo $slider_id !== '' ? ' id="'.$slider_id.'" ' : ' '; ?>class="slides<?php echo $slider_class !== '' ? ' '.$slider_class : ''?>"<?php echo $slider_custom_styles !== '' ? ' style="'.$slider_custom_styles.'" ' : ''; ?> >
<?php while ( $slider->have_posts() ) : $slider->the_post(); ?>
<li>
<?php if(get_post_meta( get_the_ID(), '_axada_slider_video_value_key', true ) && get_post_meta( get_the_ID(), '_axada_slider_video_value_key', true )!==''){?>
<div class="player" data-video-id="<?php echo get_post_meta( get_the_ID(), '_axada_slider_video_value_key', true ); ?>" data-start-at="0"></div>
<div class="background-image-holder bg-size-cover">
<img alt="image" class="background-image" src="<?php the_post_thumbnail_url(); ?>" />
</div>
<?php }else{ ?>
<div class="background-image-holder bg-size-cover">
<img alt="image" class="background-image" src="<?php the_post_thumbnail_url('full'); ?>">
</div>
<div class="container v-align-transform">
<div class="row text-center">
<div class="full-width divider mb-xs-64"></div>
<?php the_content(); ?>
</div>
</div>
<?php } ?>
</li>
<?php endwhile; ?>
</ul>
</section>
<?php
//end slider
endif;
return ob_get_clean();
}

Categories