Add a custom WooCommerce settings page, including page sections - php

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/

Related

Custom WooCommerce Shipping Method

I have this working code which generating and saving properly a custom shipping method under woocommerce setting > shipping zones.
My problem is that I can't make the shipping method show on the checkout page.
Any help on how to resolve this issue and maybe extend the code a bit would be greatly appreciated.
add_filter('woocommerce_shipping_methods', 'add_local_shipping');
function add_local_shipping($methods) {
$methods['local_shipping'] = 'Local_Shipping_Method';
return $methods;
}
class Local_Shipping_Method extends WC_Shipping_Method {
public function __construct($instance_id = 0) {
$this->id = 'local_shipping';
$this->instance_id = absint($instance_id);
$this->domain = 'local_shipping';
$this->method_title = __('Pickup', $this->domain);
$this->method_description = __('Pickup Location for WooCommerce', $this->domain);
$this->title = __('Pickup Location', $this->domain);
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
$this->instance_form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable' ),
'type' => 'checkbox',
'label' => __( 'Enable this shipping method' ),
'default' => 'yes',
),
'title' => array(
'title' => __( 'Method Title' ),
'type' => 'text',
'description' => __( 'This controls the title which the user sees during checkout.' ),
'default' => __( 'Pickup Location' ),
'desc_tip' => true
),
'tax_status' => array(
'title' => __( 'Tax status', 'woocommerce' ),
'type' => 'select',
'class' => 'wc-enhanced-select',
'default' => 'taxable',
'options' => array(
'taxable' => __( 'Taxable', 'woocommerce' ),
'none' => _x( 'None', 'Tax status', 'woocommerce' ),
),
),
'cost' => array(
'title' => __( 'Cost', 'woocommerce' ),
'type' => 'text',
'placeholder' => '0',
'description' => __( 'Optional cost for pickup.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
),
);
$this->enabled = $this->get_option( 'enabled' );
$this->title = __('Pickup Location', $this->domain);
add_action('woocommerce_update_options_shipping_' . $this->id, array($this, 'process_admin_options'));
}
public function calculate_shipping( $package = array() ) {
$this->add_rate( array(
'id' => $this->id . $this->instance_id,
'label' => $this->title,
'cost' => 0,
) );
}
}
The way you have tried it is wrong. And it's not recommended to keep the text domain in a variable or a method.
To Create a Custom Shipping Method You have to follow these steps I have corrected on your code.
Hope this helps. You can learn more about how to create new shipping method here. Feel free
// To initialize your new shipping method you have to keep it in function
function local_shipping_init() {
if ( ! class_exists( Local_Shipping_Method ) ) {
class Local_Shipping_Method extends WC_Shipping_Method {
public function __construct( $instance_id = 0 ) {
$this->id = 'local_shipping';
$this->instance_id = absint( $instance_id );
$this->method_title = __( 'Pickup', 'text-domain' );
$this->method_description = __( 'Pickup Location for WooCommerce', 'text-domain' );
$this->title = __( 'Pickup Location', 'text-domain' );
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
// then you have to call this method to initiate your settings
$this->init();
}
public function init() {
// this method used to initiate your fields on settings
$this->init_form_fields();
// this is settings instance where you can declar your settings field
$this->init_instance_settings();
// user defined values goes here, not in construct
$this->enabled = $this->get_option( 'enabled' );
$this->title = __( 'Pickup Location', 'text-domain' );
// call this action in init() method to save your settings at the backend
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
}
public function init_instance_settings() {
// you have to keep all the instance settings field inside the init_instance_settings method
$this->instance_form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable' ),
'type' => 'checkbox',
'label' => __( 'Enable this shipping method' ),
'default' => 'yes',
),
'title' => array(
'title' => __( 'Method Title' ),
'type' => 'text',
'description' => __( 'This controls the title which the user sees during checkout.' ),
'default' => __( 'Pickup Location' ),
'desc_tip' => true
),
'tax_status' => array(
'title' => __( 'Tax status', 'woocommerce' ),
'type' => 'select',
'class' => 'wc-enhanced-select',
'default' => 'taxable',
'options' => array(
'taxable' => __( 'Taxable', 'woocommerce' ),
'none' => _x( 'None', 'Tax status', 'woocommerce' ),
),
),
'cost' => array(
'title' => __( 'Cost', 'woocommerce' ),
'type' => 'text',
'placeholder' => '0',
'description' => __( 'Optional cost for pickup.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
),
);
}
public function calculate_shipping( $package = array() ) {
$this->add_rate( array(
'id' => $this->id, // you should define only your shipping method id here
'label' => $this->title,
'cost' => 0,
) );
}
}
}
}
add_action( 'woocommerce_shipping_init', 'local_shipping_init' ); // use this hook to initialize your new custom method
function add_local_shipping( $methods ) {
$methods['local_shipping'] = 'Local_Shipping_Method';
return $methods;
}
add_filter( 'woocommerce_shipping_methods', 'add_local_shipping' );
The code is tested and works fine. See the screenshot below.

Insert Checkbox on WooCommerce general settings page for purchase filter

I am trying to create a an extra option on the General settings WooCommerce page but could not get that working so I tried with the advanced tab instead, which seem to work.
The goal here is to create a checkbox option which enables a catalog mode by applying the filter for is_purchasable.
But, what I cannot figure out is how to apply and save the filter for woocommerce_is_purchasable if the checkbox is marked the settings saved.
Here's what I got so far:
add_filter( 'woocommerce_get_sections_advanced', 'catalog_mode_add_section' );
add_filter( 'woocommerce_get_settings_advanced', 'catalog_mode_all_settings', 10, 2 );
function catalog_mode_add_section( $sections ) {
$sections['catalog-mode'] = __( 'Catalog Mode', 'text-domain' );
return $sections;
}
function catalog_mode_all_settings( $settings, $current_section ) {
if ( $current_section == 'catalog-mode' ) {
$settings_catalog_options = array();
// Add Title to the Settings
$settings_catalog_options[] = array( 'name' => __( 'WooCommerce Catalog Mode', 'text-domain' ), 'type' => 'title', 'desc' => __( 'This turns WooCommerce into a catalog.', 'text-domain' ), 'id' => 'catalog_mode' );
// Add second text field option
$settings_catalog_options[] = array(
'name' => __( 'Catalog Mode', 'text-domain' ),
'id' => 'catalog_mode',
'type' => 'checkbox',
);
$settings_catalog_options[] = array( 'type' => 'sectionend', 'id' => 'catalog_mode' );
return $settings_catalog_options;
} else {
return $settings;
}
}
I'm lost right now..
Anyone?
There is a little mistake in your code, where each setting component need a unique identifier (id)… I have updated your code and your custom option is now saved.
add_filter( 'woocommerce_get_sections_advanced', 'catalog_mode_add_section' );
function catalog_mode_add_section( $sections ) {
$sections['catalog-mode'] = __( 'Catalog Mode', 'text-domain' );
return $sections;
}
add_filter( 'woocommerce_get_settings_advanced', 'catalog_mode_all_settings', 10, 2 );
function catalog_mode_all_settings( $settings, $current_section ) {
if ( $current_section == 'catalog-mode' ) {
$settings_catalog_options = array();
// Add Title to the Settings
$settings_catalog_options[] = array(
'name' => __( 'WooCommerce Catalog Mode', 'text-domain' ),
'type' => 'title',
'desc' => __( 'This turns WooCommerce into a catalog.', 'text-domain' ),
'id' => 'wc_catalog_mode_title'
);
// Add second text field option
$settings_catalog_options[] = array(
'name' => __( 'Catalog Mode', 'text-domain' ),
'type' => 'checkbox',
'id' => 'wc_catalog_mode',
);
$settings_catalog_options[] = array(
'type' => 'sectionend',
'id' => 'wc_catalog_mode_end'
);
return $settings_catalog_options;
}
return $settings;
}
Then in woocommerce_is_purchasable and woocommerce_variation_is_purchasable filters, you will use it this way:
add_filter('woocommerce_is_purchasable', 'product_is_purchasable_filter_callback', 10, 2 );
add_filter( 'woocommerce_variation_is_purchasable', 'product_is_purchasable_filter_callback', 10, 2 );
function product_is_purchasable_filter_callback( $purchasable, $product ) {
if( 'yes' === get_option('wc_catalog_mode') ) {
$purchasable = false;
}
return $purchasable;
}
Code goes in function.php file of the active child theme (or active theme). Tested and works.
You could use "products" section instead of "advanced" replacing your hooks with:
woocommerce_get_sections_products
woocommerce_get_settings_products

CMB2 repeatable file_list type

I am searching everywhere for an example of how to populate/access images entered inside of repeatable file-list group in CMB2. All the examples I could find is only for a single image: here is the link https://github.com/CMB2/CMB2/wiki/Field-Types#group
MY CODE:
// creating a group
$group_field_id = $bautage->add_field( array(
id' => 'tagen_entries',
'type' => 'group',
'description' => __( 'Tagen', 'cmb2' ),
'repeatable' => true,
'options' => array(
'group_title' => __( 'Tagen {#}', 'cmb2' ), // since version 1.1.4, {#} gets replaced by row number
'add_button' => __( 'Add Another Entry', 'cmb2' ),
'remove_button' => __( 'Remove Entry', 'cmb2' ),
'sortable' => true,
),
) );
// creating repeatable fields
$bautage->add_group_field( $group_field_id, array(
'name' => 'Tag Name',
'id' => 'tag_name',
'type' => 'text',
) );
$bautage->add_group_field( $group_field_id, array(
'name' => 'Tage Photos',
'id' => 'tage_photos',
'type' => 'file_list',
) );
// page code
$tagen_entries = get_post_meta( get_the_ID(), 'tagen_entries', true );
foreach ( (array) $tagen_entries as $key => $entry ) {
if ( isset( $entry['tag_name'] ) ) {
$tag_name = esc_html( $entry['tag_name'] );
echo $tag_name;
}
if ( isset( $entry['tage_bildern'] ) ) {
// loop to bring all the photos
// also need the first photo from each entry to be used as a click trigger to open the slideshow
}
}

Custom checkout billing field on email notifications

In WooCommerce, based on "Dynamic synched custom checkout select fields in WooCommerce" answer code, I am adding some custom fields on checkout which will be displayed under the billing section on the e-mail confirmation
Here is my actual code:
add_action( 'woocommerce_after_checkout_billing_form', 'add_checkout_custom_fields', 20, 1 );
function add_checkout_custom_fields( $checkout) {
$domain = 'woocommerce'; // The domain slug
// First Select field (Master)
woocommerce_form_field( 'delivery_one', array(
'type' => 'select',
'label' => __( 'Art der Lieferung' , $domain),
'class' => array( 'form-row-first' ),
'required' => true,
'options' => array(
'' => __( 'Wählen Art der Lieferung.', $domain ),
'A' => __( 'Hauszustellung', $domain ),
'B' => __( 'Selbst Abholung', $domain ),
),
), $checkout->get_value( 'delivery_one' ) );
// Default option value
$default_option2 = __( 'Wählen Sie Zeitbereich.', $domain );
// Dynamic select field options for Javascript/jQuery
$options_0 = array( '' => $default_option2 );
$options_a = array(
'' => $default_option2,
'1' => __( '09:00-11:00', $domain ),
'2' => __( '10:00-12:00', $domain ),
'3' => __( '11:00-13:00', $domain ),
'4' => __( '12:00-14:00', $domain ),
'5' => __( '13:00-15:00', $domain ),
'6' => __( '14:00-16:00', $domain ),
'7' => __( '15:00-17:00', $domain ),
);
$options_b = array(
'' => $default_option2,
'1' => __( '01:00', $domain ),
'2' => __( '02:00', $domain ),
'3' => __( '03:00', $domain ),
'4' => __( '04:00', $domain ),
'5' => __( '05:00', $domain ),
'6' => __( '06:00', $domain ),
'7' => __( '07:00', $domain ),
'8' => __( '08:00', $domain ),
'9' => __( '09:00', $domain ),
'10' => __( '10:00', $domain ),
'11' => __( '11:00', $domain ),
'12' => __( '12:00', $domain ),
'13' => __( '13:00', $domain ),
'14' => __( '14:00', $domain ),
'15' => __( '15:00', $domain ),
'16' => __( '16:00', $domain ),
'17' => __( '17:00', $domain ),
'18' => __( '18:00', $domain ),
'19' => __( '19:00', $domain ),
'20' => __( '20:00', $domain ),
'21' => __( '21:00', $domain ),
'22' => __( '22:00', $domain ),
'23' => __( '23:00', $domain ),
'24' => __( '24:00', $domain ),
);
// Second Select field (Dynamic Slave)
woocommerce_form_field( 'delivery_two', array(
'type' => 'select',
'label' => __( 'Zeitspanne', $domain ),
'class' => array( 'form-row-last' ),
'required' => true,
'options' => $options_0,
), $checkout->get_value( 'delivery_two' ) );
$required = esc_attr__( 'required', 'woocommerce' );
// jQuery code
?>
<script>
jQuery(function($){
var op0 = <?php echo json_encode($options_0); ?>,
opa = <?php echo json_encode($options_a); ?>,
opb = <?php echo json_encode($options_b); ?>,
select1 = 'select[name="delivery_one"]',
select2 = 'select[name="delivery_two"]';
// Utility function to fill dynamically the select field options
function dynamicSelectOptions( opt ){
var options = '';
$.each( opt, function( key, value ){
options += '<option value="'+key+'">'+value+'</option>';
});
$(select2).html(options);
}
// 1. When dom is loaded we add the select field option for "A" value
// => Disabled (optional) — Uncomment below to enable
// dynamicSelectOptions( opa );
// 2. On live selection event on the first dropdown
$(select1).change(function(){
if( $(this).val() == 'A' )
dynamicSelectOptions( opa );
else if( $(this).val() == 'B' )
dynamicSelectOptions( opb );
else
dynamicSelectOptions( op0 ); // Reset to default
});
});
</script>
<?php
}
// Check checkout custom fields
add_action( 'woocommerce_checkout_process', 'wps_check_checkout_custom_fields', 20 ) ;
function wps_check_checkout_custom_fields() {
// if custom fields are empty stop checkout process displaying an error notice.
if ( empty($_POST['delivery_one']) || empty($_POST['delivery_two']) ){
$notice = __( 'Bitte wählen Sie die Versandart oder den Stundenbereich' );
wc_add_notice( '<strong>' . $notice . '</strong>', 'error' );
}
}
My custom fields and their values, are shown on the checkout form and on the order pages on back end. So far everything works great.
But the problem is that the e-mail that I receive does not contain the custom fields and their values.
How can I display that custom checkout billing fields on email notifications?
Is this code correct?
add_filter( 'woocommerce_email_format_string' , 'add_custom_email_format_string', 20, 2 );
function add_custom_email_format_string( $string, $email ) {
// The post meta key used to save the value in the order post meta data
$meta_key = '_delivery_one';
// Get the instance of the WC_Order object
$order = $email->object;
// Get the value
$value = $order->get_meta($meta_key) ? $order->get_meta($meta_key) : '';
// Additional subject placeholder
$new_placeholders = array( '{delivery_one}' => $value );
return str_replace( array_keys( $additional_placeholders ), array_values( $additional_placeholders ), $string );
}
You have to define a new placeholder that can be parsed.
add_filter( 'woocommerce_email_format_string' , 'add_custom_email_format_string', 20, 2 );
function add_custom_email_format_string( $string, $email ) {
// The post meta key used to save the value in the order post meta data
$meta_key = '_billing_field_newfield';
// Get the instance of the WC_Order object
$order = $email->object;
// Get the value
$value = $order->get_meta($meta_key) ? $order->get_meta($meta_key) : '';
// Additional subject placeholder
$new_placeholders = array( '{billing_field_newfield}' => $value );
// Return the clean replacement value string for "{billing_field_newfield}" placeholder
return str_replace( array_keys( $additional_placeholders ), array_values( $additional_placeholders ), $string );
}
Code goes in function.php file of your active child theme (or active theme). It will works.
Then in Woocommerce > Settings > Emails > "New Order" notification, you will be able to use the dynamic placeholder {billing_field_newfield}…
You should first save input value at the database as an order meta see here and then you can get the meta value in email template. All the email template of woocommerce is customizable and located at plugins/woocommerce/emails/
You can use this hook to store custom input field at order meta
do_action( 'woocommerce_checkout_order_processed', $order_id, $posted_data, $order );
Thanks

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.

Categories